1/* 2 * drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver 3 * 4 * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 and 8 * only version 2 as published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 */ 16 17#include <linux/module.h> 18#include <linux/of_device.h> 19#include <linux/delay.h> 20#include <linux/mmc/mmc.h> 21#include <linux/slab.h> 22 23#include "sdhci-pltfm.h" 24 25#define CORE_MCI_VERSION 0x50 26#define CORE_VERSION_MAJOR_SHIFT 28 27#define CORE_VERSION_MAJOR_MASK (0xf << CORE_VERSION_MAJOR_SHIFT) 28#define CORE_VERSION_MINOR_MASK 0xff 29 30#define CORE_HC_MODE 0x78 31#define HC_MODE_EN 0x1 32#define CORE_POWER 0x0 33#define CORE_SW_RST BIT(7) 34 35#define MAX_PHASES 16 36#define CORE_DLL_LOCK BIT(7) 37#define CORE_DLL_EN BIT(16) 38#define CORE_CDR_EN BIT(17) 39#define CORE_CK_OUT_EN BIT(18) 40#define CORE_CDR_EXT_EN BIT(19) 41#define CORE_DLL_PDN BIT(29) 42#define CORE_DLL_RST BIT(30) 43#define CORE_DLL_CONFIG 0x100 44#define CORE_DLL_STATUS 0x108 45 46#define CORE_VENDOR_SPEC 0x10c 47#define CORE_CLK_PWRSAVE BIT(1) 48 49#define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c 50 51#define CDR_SELEXT_SHIFT 20 52#define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT) 53#define CMUX_SHIFT_PHASE_SHIFT 24 54#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT) 55 56struct sdhci_msm_host { 57 struct platform_device *pdev; 58 void __iomem *core_mem; /* MSM SDCC mapped address */ 59 struct clk *clk; /* main SD/MMC bus clock */ 60 struct clk *pclk; /* SDHC peripheral bus clock */ 61 struct clk *bus_clk; /* SDHC bus voter clock */ 62 struct mmc_host *mmc; 63 struct sdhci_pltfm_data sdhci_msm_pdata; 64}; 65 66/* Platform specific tuning */ 67static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll) 68{ 69 u32 wait_cnt = 50; 70 u8 ck_out_en; 71 struct mmc_host *mmc = host->mmc; 72 73 /* Poll for CK_OUT_EN bit. max. poll time = 50us */ 74 ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & 75 CORE_CK_OUT_EN); 76 77 while (ck_out_en != poll) { 78 if (--wait_cnt == 0) { 79 dev_err(mmc_dev(mmc), "%s: CK_OUT_EN bit is not %d\n", 80 mmc_hostname(mmc), poll); 81 return -ETIMEDOUT; 82 } 83 udelay(1); 84 85 ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & 86 CORE_CK_OUT_EN); 87 } 88 89 return 0; 90} 91 92static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase) 93{ 94 int rc; 95 static const u8 grey_coded_phase_table[] = { 96 0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, 97 0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8 98 }; 99 unsigned long flags; 100 u32 config; 101 struct mmc_host *mmc = host->mmc; 102 103 spin_lock_irqsave(&host->lock, flags); 104 105 config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); 106 config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN); 107 config |= (CORE_CDR_EXT_EN | CORE_DLL_EN); 108 writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); 109 110 /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */ 111 rc = msm_dll_poll_ck_out_en(host, 0); 112 if (rc) 113 goto err_out; 114 115 /* 116 * Write the selected DLL clock output phase (0 ... 15) 117 * to CDR_SELEXT bit field of DLL_CONFIG register. 118 */ 119 config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); 120 config &= ~CDR_SELEXT_MASK; 121 config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT; 122 writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); 123 124 /* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */ 125 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 126 | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); 127 128 /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */ 129 rc = msm_dll_poll_ck_out_en(host, 1); 130 if (rc) 131 goto err_out; 132 133 config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); 134 config |= CORE_CDR_EN; 135 config &= ~CORE_CDR_EXT_EN; 136 writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); 137 goto out; 138 139err_out: 140 dev_err(mmc_dev(mmc), "%s: Failed to set DLL phase: %d\n", 141 mmc_hostname(mmc), phase); 142out: 143 spin_unlock_irqrestore(&host->lock, flags); 144 return rc; 145} 146 147/* 148 * Find out the greatest range of consecuitive selected 149 * DLL clock output phases that can be used as sampling 150 * setting for SD3.0 UHS-I card read operation (in SDR104 151 * timing mode) or for eMMC4.5 card read operation (in HS200 152 * timing mode). 153 * Select the 3/4 of the range and configure the DLL with the 154 * selected DLL clock output phase. 155 */ 156 157static int msm_find_most_appropriate_phase(struct sdhci_host *host, 158 u8 *phase_table, u8 total_phases) 159{ 160 int ret; 161 u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} }; 162 u8 phases_per_row[MAX_PHASES] = { 0 }; 163 int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0; 164 int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0; 165 bool phase_0_found = false, phase_15_found = false; 166 struct mmc_host *mmc = host->mmc; 167 168 if (!total_phases || (total_phases > MAX_PHASES)) { 169 dev_err(mmc_dev(mmc), "%s: Invalid argument: total_phases=%d\n", 170 mmc_hostname(mmc), total_phases); 171 return -EINVAL; 172 } 173 174 for (cnt = 0; cnt < total_phases; cnt++) { 175 ranges[row_index][col_index] = phase_table[cnt]; 176 phases_per_row[row_index] += 1; 177 col_index++; 178 179 if ((cnt + 1) == total_phases) { 180 continue; 181 /* check if next phase in phase_table is consecutive or not */ 182 } else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) { 183 row_index++; 184 col_index = 0; 185 } 186 } 187 188 if (row_index >= MAX_PHASES) 189 return -EINVAL; 190 191 /* Check if phase-0 is present in first valid window? */ 192 if (!ranges[0][0]) { 193 phase_0_found = true; 194 phase_0_raw_index = 0; 195 /* Check if cycle exist between 2 valid windows */ 196 for (cnt = 1; cnt <= row_index; cnt++) { 197 if (phases_per_row[cnt]) { 198 for (i = 0; i < phases_per_row[cnt]; i++) { 199 if (ranges[cnt][i] == 15) { 200 phase_15_found = true; 201 phase_15_raw_index = cnt; 202 break; 203 } 204 } 205 } 206 } 207 } 208 209 /* If 2 valid windows form cycle then merge them as single window */ 210 if (phase_0_found && phase_15_found) { 211 /* number of phases in raw where phase 0 is present */ 212 u8 phases_0 = phases_per_row[phase_0_raw_index]; 213 /* number of phases in raw where phase 15 is present */ 214 u8 phases_15 = phases_per_row[phase_15_raw_index]; 215 216 if (phases_0 + phases_15 >= MAX_PHASES) 217 /* 218 * If there are more than 1 phase windows then total 219 * number of phases in both the windows should not be 220 * more than or equal to MAX_PHASES. 221 */ 222 return -EINVAL; 223 224 /* Merge 2 cyclic windows */ 225 i = phases_15; 226 for (cnt = 0; cnt < phases_0; cnt++) { 227 ranges[phase_15_raw_index][i] = 228 ranges[phase_0_raw_index][cnt]; 229 if (++i >= MAX_PHASES) 230 break; 231 } 232 233 phases_per_row[phase_0_raw_index] = 0; 234 phases_per_row[phase_15_raw_index] = phases_15 + phases_0; 235 } 236 237 for (cnt = 0; cnt <= row_index; cnt++) { 238 if (phases_per_row[cnt] > curr_max) { 239 curr_max = phases_per_row[cnt]; 240 selected_row_index = cnt; 241 } 242 } 243 244 i = (curr_max * 3) / 4; 245 if (i) 246 i--; 247 248 ret = ranges[selected_row_index][i]; 249 250 if (ret >= MAX_PHASES) { 251 ret = -EINVAL; 252 dev_err(mmc_dev(mmc), "%s: Invalid phase selected=%d\n", 253 mmc_hostname(mmc), ret); 254 } 255 256 return ret; 257} 258 259static inline void msm_cm_dll_set_freq(struct sdhci_host *host) 260{ 261 u32 mclk_freq = 0, config; 262 263 /* Program the MCLK value to MCLK_FREQ bit field */ 264 if (host->clock <= 112000000) 265 mclk_freq = 0; 266 else if (host->clock <= 125000000) 267 mclk_freq = 1; 268 else if (host->clock <= 137000000) 269 mclk_freq = 2; 270 else if (host->clock <= 150000000) 271 mclk_freq = 3; 272 else if (host->clock <= 162000000) 273 mclk_freq = 4; 274 else if (host->clock <= 175000000) 275 mclk_freq = 5; 276 else if (host->clock <= 187000000) 277 mclk_freq = 6; 278 else if (host->clock <= 200000000) 279 mclk_freq = 7; 280 281 config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); 282 config &= ~CMUX_SHIFT_PHASE_MASK; 283 config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT; 284 writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); 285} 286 287/* Initialize the DLL (Programmable Delay Line) */ 288static int msm_init_cm_dll(struct sdhci_host *host) 289{ 290 struct mmc_host *mmc = host->mmc; 291 int wait_cnt = 50; 292 unsigned long flags; 293 294 spin_lock_irqsave(&host->lock, flags); 295 296 /* 297 * Make sure that clock is always enabled when DLL 298 * tuning is in progress. Keeping PWRSAVE ON may 299 * turn off the clock. 300 */ 301 writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) 302 & ~CORE_CLK_PWRSAVE), host->ioaddr + CORE_VENDOR_SPEC); 303 304 /* Write 1 to DLL_RST bit of DLL_CONFIG register */ 305 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 306 | CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); 307 308 /* Write 1 to DLL_PDN bit of DLL_CONFIG register */ 309 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 310 | CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); 311 msm_cm_dll_set_freq(host); 312 313 /* Write 0 to DLL_RST bit of DLL_CONFIG register */ 314 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 315 & ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); 316 317 /* Write 0 to DLL_PDN bit of DLL_CONFIG register */ 318 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 319 & ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); 320 321 /* Set DLL_EN bit to 1. */ 322 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 323 | CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG); 324 325 /* Set CK_OUT_EN bit to 1. */ 326 writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) 327 | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); 328 329 /* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */ 330 while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) & 331 CORE_DLL_LOCK)) { 332 /* max. wait for 50us sec for LOCK bit to be set */ 333 if (--wait_cnt == 0) { 334 dev_err(mmc_dev(mmc), "%s: DLL failed to LOCK\n", 335 mmc_hostname(mmc)); 336 spin_unlock_irqrestore(&host->lock, flags); 337 return -ETIMEDOUT; 338 } 339 udelay(1); 340 } 341 342 spin_unlock_irqrestore(&host->lock, flags); 343 return 0; 344} 345 346static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) 347{ 348 int tuning_seq_cnt = 3; 349 u8 phase, tuned_phases[16], tuned_phase_cnt = 0; 350 int rc; 351 struct mmc_host *mmc = host->mmc; 352 struct mmc_ios ios = host->mmc->ios; 353 354 /* 355 * Tuning is required for SDR104, HS200 and HS400 cards and 356 * if clock frequency is greater than 100MHz in these modes. 357 */ 358 if (host->clock <= 100 * 1000 * 1000 || 359 !((ios.timing == MMC_TIMING_MMC_HS200) || 360 (ios.timing == MMC_TIMING_UHS_SDR104))) 361 return 0; 362 363retry: 364 /* First of all reset the tuning block */ 365 rc = msm_init_cm_dll(host); 366 if (rc) 367 return rc; 368 369 phase = 0; 370 do { 371 /* Set the phase in delay line hw block */ 372 rc = msm_config_cm_dll_phase(host, phase); 373 if (rc) 374 return rc; 375 376 rc = mmc_send_tuning(mmc, opcode, NULL); 377 if (!rc) { 378 /* Tuning is successful at this tuning point */ 379 tuned_phases[tuned_phase_cnt++] = phase; 380 dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", 381 mmc_hostname(mmc), phase); 382 } 383 } while (++phase < ARRAY_SIZE(tuned_phases)); 384 385 if (tuned_phase_cnt) { 386 rc = msm_find_most_appropriate_phase(host, tuned_phases, 387 tuned_phase_cnt); 388 if (rc < 0) 389 return rc; 390 else 391 phase = rc; 392 393 /* 394 * Finally set the selected phase in delay 395 * line hw block. 396 */ 397 rc = msm_config_cm_dll_phase(host, phase); 398 if (rc) 399 return rc; 400 dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", 401 mmc_hostname(mmc), phase); 402 } else { 403 if (--tuning_seq_cnt) 404 goto retry; 405 /* Tuning failed */ 406 dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", 407 mmc_hostname(mmc)); 408 rc = -EIO; 409 } 410 411 return rc; 412} 413 414static const struct of_device_id sdhci_msm_dt_match[] = { 415 { .compatible = "qcom,sdhci-msm-v4" }, 416 {}, 417}; 418 419MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); 420 421static struct sdhci_ops sdhci_msm_ops = { 422 .platform_execute_tuning = sdhci_msm_execute_tuning, 423 .reset = sdhci_reset, 424 .set_clock = sdhci_set_clock, 425 .set_bus_width = sdhci_set_bus_width, 426 .set_uhs_signaling = sdhci_set_uhs_signaling, 427}; 428 429static int sdhci_msm_probe(struct platform_device *pdev) 430{ 431 struct sdhci_host *host; 432 struct sdhci_pltfm_host *pltfm_host; 433 struct sdhci_msm_host *msm_host; 434 struct resource *core_memres; 435 int ret; 436 u16 host_version, core_minor; 437 u32 core_version, caps; 438 u8 core_major; 439 440 msm_host = devm_kzalloc(&pdev->dev, sizeof(*msm_host), GFP_KERNEL); 441 if (!msm_host) 442 return -ENOMEM; 443 444 msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; 445 host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0); 446 if (IS_ERR(host)) 447 return PTR_ERR(host); 448 449 pltfm_host = sdhci_priv(host); 450 pltfm_host->priv = msm_host; 451 msm_host->mmc = host->mmc; 452 msm_host->pdev = pdev; 453 454 ret = mmc_of_parse(host->mmc); 455 if (ret) 456 goto pltfm_free; 457 458 sdhci_get_of_property(pdev); 459 460 /* Setup SDCC bus voter clock. */ 461 msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus"); 462 if (!IS_ERR(msm_host->bus_clk)) { 463 /* Vote for max. clk rate for max. performance */ 464 ret = clk_set_rate(msm_host->bus_clk, INT_MAX); 465 if (ret) 466 goto pltfm_free; 467 ret = clk_prepare_enable(msm_host->bus_clk); 468 if (ret) 469 goto pltfm_free; 470 } 471 472 /* Setup main peripheral bus clock */ 473 msm_host->pclk = devm_clk_get(&pdev->dev, "iface"); 474 if (IS_ERR(msm_host->pclk)) { 475 ret = PTR_ERR(msm_host->pclk); 476 dev_err(&pdev->dev, "Perpheral clk setup failed (%d)\n", ret); 477 goto bus_clk_disable; 478 } 479 480 ret = clk_prepare_enable(msm_host->pclk); 481 if (ret) 482 goto bus_clk_disable; 483 484 /* Setup SDC MMC clock */ 485 msm_host->clk = devm_clk_get(&pdev->dev, "core"); 486 if (IS_ERR(msm_host->clk)) { 487 ret = PTR_ERR(msm_host->clk); 488 dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret); 489 goto pclk_disable; 490 } 491 492 /* Vote for maximum clock rate for maximum performance */ 493 ret = clk_set_rate(msm_host->clk, INT_MAX); 494 if (ret) 495 dev_warn(&pdev->dev, "core clock boost failed\n"); 496 497 ret = clk_prepare_enable(msm_host->clk); 498 if (ret) 499 goto pclk_disable; 500 501 core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1); 502 msm_host->core_mem = devm_ioremap_resource(&pdev->dev, core_memres); 503 504 if (IS_ERR(msm_host->core_mem)) { 505 dev_err(&pdev->dev, "Failed to remap registers\n"); 506 ret = PTR_ERR(msm_host->core_mem); 507 goto clk_disable; 508 } 509 510 /* Reset the core and Enable SDHC mode */ 511 writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | 512 CORE_SW_RST, msm_host->core_mem + CORE_POWER); 513 514 /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ 515 usleep_range(1000, 5000); 516 if (readl(msm_host->core_mem + CORE_POWER) & CORE_SW_RST) { 517 dev_err(&pdev->dev, "Stuck in reset\n"); 518 ret = -ETIMEDOUT; 519 goto clk_disable; 520 } 521 522 /* Set HC_MODE_EN bit in HC_MODE register */ 523 writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); 524 525 host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; 526 host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; 527 528 host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); 529 dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", 530 host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> 531 SDHCI_VENDOR_VER_SHIFT)); 532 533 core_version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION); 534 core_major = (core_version & CORE_VERSION_MAJOR_MASK) >> 535 CORE_VERSION_MAJOR_SHIFT; 536 core_minor = core_version & CORE_VERSION_MINOR_MASK; 537 dev_dbg(&pdev->dev, "MCI Version: 0x%08x, major: 0x%04x, minor: 0x%02x\n", 538 core_version, core_major, core_minor); 539 540 /* 541 * Support for some capabilities is not advertised by newer 542 * controller versions and must be explicitly enabled. 543 */ 544 if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) { 545 caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES); 546 caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT; 547 writel_relaxed(caps, host->ioaddr + 548 CORE_VENDOR_SPEC_CAPABILITIES0); 549 } 550 551 ret = sdhci_add_host(host); 552 if (ret) 553 goto clk_disable; 554 555 return 0; 556 557clk_disable: 558 clk_disable_unprepare(msm_host->clk); 559pclk_disable: 560 clk_disable_unprepare(msm_host->pclk); 561bus_clk_disable: 562 if (!IS_ERR(msm_host->bus_clk)) 563 clk_disable_unprepare(msm_host->bus_clk); 564pltfm_free: 565 sdhci_pltfm_free(pdev); 566 return ret; 567} 568 569static int sdhci_msm_remove(struct platform_device *pdev) 570{ 571 struct sdhci_host *host = platform_get_drvdata(pdev); 572 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 573 struct sdhci_msm_host *msm_host = pltfm_host->priv; 574 int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 575 0xffffffff); 576 577 sdhci_remove_host(host, dead); 578 sdhci_pltfm_free(pdev); 579 clk_disable_unprepare(msm_host->clk); 580 clk_disable_unprepare(msm_host->pclk); 581 if (!IS_ERR(msm_host->bus_clk)) 582 clk_disable_unprepare(msm_host->bus_clk); 583 return 0; 584} 585 586static struct platform_driver sdhci_msm_driver = { 587 .probe = sdhci_msm_probe, 588 .remove = sdhci_msm_remove, 589 .driver = { 590 .name = "sdhci_msm", 591 .of_match_table = sdhci_msm_dt_match, 592 }, 593}; 594 595module_platform_driver(sdhci_msm_driver); 596 597MODULE_DESCRIPTION("Qualcomm Secure Digital Host Controller Interface driver"); 598MODULE_LICENSE("GPL v2"); 599