Blame view
drivers/leds/leds-lp55xx-common.c
13.6 KB
c93d08fa7 leds-lp55xx: add ... |
1 |
/* |
ff45262a8 leds: add new LP5... |
2 |
* LP5521/LP5523/LP55231/LP5562 Common Driver |
c93d08fa7 leds-lp55xx: add ... |
3 4 5 6 7 8 9 10 11 12 13 |
* * Copyright 2012 Texas Instruments * * Author: Milo(Woogyom) Kim <milo.kim@ti.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Derived from leds-lp5521.c, leds-lp5523.c */ |
53b419226 leds: lp55xx: use... |
14 |
#include <linux/clk.h> |
a85908dd7 leds-lp55xx: use ... |
15 |
#include <linux/delay.h> |
10c06d178 leds-lp55xx: supp... |
16 |
#include <linux/firmware.h> |
c93d08fa7 leds-lp55xx: add ... |
17 18 19 20 |
#include <linux/i2c.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/platform_data/leds-lp55xx.h> |
7542a04b1 leds: lp55xx: add... |
21 |
#include <linux/slab.h> |
30dae2f98 leds: lp55xx: han... |
22 23 |
#include <linux/gpio.h> #include <linux/of_gpio.h> |
c93d08fa7 leds-lp55xx: add ... |
24 25 |
#include "leds-lp55xx-common.h" |
53b419226 leds: lp55xx: use... |
26 27 |
/* External clock rate */ #define LP55XX_CLK_32K 32768 |
a6e4679a0 leds-lp55xx: use ... |
28 29 30 31 |
static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev) { return container_of(cdev, struct lp55xx_led, cdev); } |
a96bfa135 leds-lp55xx: prov... |
32 33 34 35 |
static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev) { return cdev_to_lp55xx_led(dev_get_drvdata(dev)); } |
48068d5de leds-lp55xx: use ... |
36 37 38 39 40 41 42 43 44 |
static void lp55xx_reset_device(struct lp55xx_chip *chip) { struct lp55xx_device_config *cfg = chip->cfg; u8 addr = cfg->reset.addr; u8 val = cfg->reset.val; /* no error checking here because no ACK from the device after reset */ lp55xx_write(chip, addr, val); } |
e3a700d8a leds-lp55xx: use ... |
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
static int lp55xx_detect_device(struct lp55xx_chip *chip) { struct lp55xx_device_config *cfg = chip->cfg; u8 addr = cfg->enable.addr; u8 val = cfg->enable.val; int ret; ret = lp55xx_write(chip, addr, val); if (ret) return ret; usleep_range(1000, 2000); ret = lp55xx_read(chip, addr, &val); if (ret) return ret; if (val != cfg->enable.val) return -ENODEV; return 0; } |
ffbdccdbb leds-lp55xx: use ... |
67 68 69 70 71 72 73 74 75 |
static int lp55xx_post_init_device(struct lp55xx_chip *chip) { struct lp55xx_device_config *cfg = chip->cfg; if (!cfg->post_init_device) return 0; return cfg->post_init_device(chip); } |
a96bfa135 leds-lp55xx: prov... |
76 77 78 79 80 |
static ssize_t lp55xx_show_current(struct device *dev, struct device_attribute *attr, char *buf) { struct lp55xx_led *led = dev_to_lp55xx_led(dev); |
24d321284 leds: lp55xx: fix... |
81 82 |
return scnprintf(buf, PAGE_SIZE, "%d ", led->led_current); |
a96bfa135 leds-lp55xx: prov... |
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
} static ssize_t lp55xx_store_current(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct lp55xx_led *led = dev_to_lp55xx_led(dev); struct lp55xx_chip *chip = led->chip; unsigned long curr; if (kstrtoul(buf, 0, &curr)) return -EINVAL; if (curr > led->max_current) return -EINVAL; if (!chip->cfg->set_led_current) return len; mutex_lock(&chip->lock); chip->cfg->set_led_current(led, (u8)curr); mutex_unlock(&chip->lock); return len; } static ssize_t lp55xx_show_max_current(struct device *dev, struct device_attribute *attr, char *buf) { struct lp55xx_led *led = dev_to_lp55xx_led(dev); |
24d321284 leds: lp55xx: fix... |
114 115 |
return scnprintf(buf, PAGE_SIZE, "%d ", led->max_current); |
a96bfa135 leds-lp55xx: prov... |
116 117 118 119 120 |
} static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, lp55xx_show_current, lp55xx_store_current); static DEVICE_ATTR(max_current, S_IRUGO , lp55xx_show_max_current, NULL); |
44a1255b0 leds: lp55xx-comm... |
121 |
static struct attribute *lp55xx_led_attrs[] = { |
a96bfa135 leds-lp55xx: prov... |
122 123 |
&dev_attr_led_current.attr, &dev_attr_max_current.attr, |
0e2023463 leds-lp55xx: use ... |
124 125 |
NULL, }; |
44a1255b0 leds: lp55xx-comm... |
126 |
ATTRIBUTE_GROUPS(lp55xx_led); |
0e2023463 leds-lp55xx: use ... |
127 128 129 130 |
static void lp55xx_set_brightness(struct led_classdev *cdev, enum led_brightness brightness) { |
a6e4679a0 leds-lp55xx: use ... |
131 132 133 134 |
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev); led->brightness = (u8)brightness; schedule_work(&led->brightness_work); |
0e2023463 leds-lp55xx: use ... |
135 |
} |
9e9b3db1b leds-lp55xx: use ... |
136 137 138 |
static int lp55xx_init_led(struct lp55xx_led *led, struct lp55xx_chip *chip, int chan) { |
0e2023463 leds-lp55xx: use ... |
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
struct lp55xx_platform_data *pdata = chip->pdata; struct lp55xx_device_config *cfg = chip->cfg; struct device *dev = &chip->cl->dev; char name[32]; int ret; int max_channel = cfg->max_channel; if (chan >= max_channel) { dev_err(dev, "invalid channel: %d / %d ", chan, max_channel); return -EINVAL; } if (pdata->led_config[chan].led_current == 0) return 0; led->led_current = pdata->led_config[chan].led_current; led->max_current = pdata->led_config[chan].max_current; led->chan_nr = pdata->led_config[chan].chan_nr; |
f65f0a1a9 leds: lp55xx: ena... |
158 |
led->cdev.default_trigger = pdata->led_config[chan].default_trigger; |
0e2023463 leds-lp55xx: use ... |
159 160 161 162 163 164 165 166 167 |
if (led->chan_nr >= max_channel) { dev_err(dev, "Use channel numbers between 0 and %d ", max_channel - 1); return -EINVAL; } led->cdev.brightness_set = lp55xx_set_brightness; |
44a1255b0 leds: lp55xx-comm... |
168 |
led->cdev.groups = lp55xx_led_groups; |
0e2023463 leds-lp55xx: use ... |
169 170 171 172 173 174 175 176 |
if (pdata->led_config[chan].name) { led->cdev.name = pdata->led_config[chan].name; } else { snprintf(name, sizeof(name), "%s:channel%d", pdata->label ? : chip->cl->name, chan); led->cdev.name = name; } |
0e2023463 leds-lp55xx: use ... |
177 178 179 180 181 182 |
ret = led_classdev_register(dev, &led->cdev); if (ret) { dev_err(dev, "led register err: %d ", ret); return ret; } |
9e9b3db1b leds-lp55xx: use ... |
183 184 |
return 0; } |
10c06d178 leds-lp55xx: supp... |
185 186 187 188 |
static void lp55xx_firmware_loaded(const struct firmware *fw, void *context) { struct lp55xx_chip *chip = context; struct device *dev = &chip->cl->dev; |
93ad8a1d5 leds: lp5523: Sup... |
189 |
enum lp55xx_engine_index idx = chip->engine_idx; |
10c06d178 leds-lp55xx: supp... |
190 191 192 193 194 195 196 197 198 |
if (!fw) { dev_err(dev, "firmware request failed "); goto out; } /* handling firmware data is chip dependent */ mutex_lock(&chip->lock); |
93ad8a1d5 leds: lp5523: Sup... |
199 |
chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD; |
10c06d178 leds-lp55xx: supp... |
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
chip->fw = fw; if (chip->cfg->firmware_cb) chip->cfg->firmware_cb(chip); mutex_unlock(&chip->lock); out: /* firmware should be released for other channel use */ release_firmware(chip->fw); } static int lp55xx_request_firmware(struct lp55xx_chip *chip) { const char *name = chip->cl->name; struct device *dev = &chip->cl->dev; return request_firmware_nowait(THIS_MODULE, true, name, dev, GFP_KERNEL, chip, lp55xx_firmware_loaded); } static ssize_t lp55xx_show_engine_select(struct device *dev, struct device_attribute *attr, char *buf) { struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); struct lp55xx_chip *chip = led->chip; return sprintf(buf, "%d ", chip->engine_idx); } static ssize_t lp55xx_store_engine_select(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); struct lp55xx_chip *chip = led->chip; unsigned long val; int ret; if (kstrtoul(buf, 0, &val)) return -EINVAL; /* select the engine to be run */ switch (val) { case LP55XX_ENGINE_1: case LP55XX_ENGINE_2: case LP55XX_ENGINE_3: mutex_lock(&chip->lock); chip->engine_idx = val; ret = lp55xx_request_firmware(chip); mutex_unlock(&chip->lock); break; default: dev_err(dev, "%lu: invalid engine index. (1, 2, 3) ", val); return -EINVAL; } if (ret) { dev_err(dev, "request firmware err: %d ", ret); return ret; } return len; } static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start) { if (chip->cfg->run_engine) chip->cfg->run_engine(chip, start); } static ssize_t lp55xx_store_engine_run(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); struct lp55xx_chip *chip = led->chip; unsigned long val; if (kstrtoul(buf, 0, &val)) return -EINVAL; /* run or stop the selected engine */ if (val <= 0) { lp55xx_run_engine(chip, false); return len; } mutex_lock(&chip->lock); lp55xx_run_engine(chip, true); mutex_unlock(&chip->lock); return len; } static DEVICE_ATTR(select_engine, S_IRUGO | S_IWUSR, lp55xx_show_engine_select, lp55xx_store_engine_select); static DEVICE_ATTR(run_engine, S_IWUSR, NULL, lp55xx_store_engine_run); |
b3b6f8119 leds-lp55xx: add ... |
303 |
static struct attribute *lp55xx_engine_attributes[] = { |
10c06d178 leds-lp55xx: supp... |
304 305 |
&dev_attr_select_engine.attr, &dev_attr_run_engine.attr, |
b3b6f8119 leds-lp55xx: add ... |
306 307 308 309 310 311 |
NULL, }; static const struct attribute_group lp55xx_engine_attr_group = { .attrs = lp55xx_engine_attributes, }; |
c93d08fa7 leds-lp55xx: add ... |
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val) { return i2c_smbus_write_byte_data(chip->cl, reg, val); } EXPORT_SYMBOL_GPL(lp55xx_write); int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val) { s32 ret; ret = i2c_smbus_read_byte_data(chip->cl, reg); if (ret < 0) return ret; *val = ret; return 0; } EXPORT_SYMBOL_GPL(lp55xx_read); int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val) { int ret; u8 tmp; ret = lp55xx_read(chip, reg, &tmp); if (ret) return ret; tmp &= ~mask; tmp |= val & mask; return lp55xx_write(chip, reg, tmp); } EXPORT_SYMBOL_GPL(lp55xx_update_bits); |
53b419226 leds: lp55xx: use... |
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
bool lp55xx_is_extclk_used(struct lp55xx_chip *chip) { struct clk *clk; int err; clk = devm_clk_get(&chip->cl->dev, "32k_clk"); if (IS_ERR(clk)) goto use_internal_clk; err = clk_prepare_enable(clk); if (err) goto use_internal_clk; if (clk_get_rate(clk) != LP55XX_CLK_32K) { clk_disable_unprepare(clk); goto use_internal_clk; } dev_info(&chip->cl->dev, "%dHz external clock used ", LP55XX_CLK_32K); chip->clk = clk; return true; use_internal_clk: dev_info(&chip->cl->dev, "internal clock used "); return false; } EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used); |
a85908dd7 leds-lp55xx: use ... |
376 377 378 |
int lp55xx_init_device(struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata; |
48068d5de leds-lp55xx: use ... |
379 |
struct lp55xx_device_config *cfg; |
a85908dd7 leds-lp55xx: use ... |
380 381 382 383 384 385 |
struct device *dev = &chip->cl->dev; int ret = 0; WARN_ON(!chip); pdata = chip->pdata; |
48068d5de leds-lp55xx: use ... |
386 |
cfg = chip->cfg; |
a85908dd7 leds-lp55xx: use ... |
387 |
|
48068d5de leds-lp55xx: use ... |
388 |
if (!pdata || !cfg) |
a85908dd7 leds-lp55xx: use ... |
389 |
return -EINVAL; |
30dae2f98 leds: lp55xx: han... |
390 391 392 |
if (gpio_is_valid(pdata->enable_gpio)) { ret = devm_gpio_request_one(dev, pdata->enable_gpio, GPIOF_DIR_OUT, "lp5523_enable"); |
a85908dd7 leds-lp55xx: use ... |
393 |
if (ret < 0) { |
30dae2f98 leds: lp55xx: han... |
394 395 396 |
dev_err(dev, "could not acquire enable gpio (err=%d) ", ret); |
a85908dd7 leds-lp55xx: use ... |
397 398 |
goto err; } |
a85908dd7 leds-lp55xx: use ... |
399 |
|
30dae2f98 leds: lp55xx: han... |
400 |
gpio_set_value(pdata->enable_gpio, 0); |
a85908dd7 leds-lp55xx: use ... |
401 |
usleep_range(1000, 2000); /* Keep enable down at least 1ms */ |
30dae2f98 leds: lp55xx: han... |
402 |
gpio_set_value(pdata->enable_gpio, 1); |
a85908dd7 leds-lp55xx: use ... |
403 404 |
usleep_range(1000, 2000); /* 500us abs min. */ } |
48068d5de leds-lp55xx: use ... |
405 406 407 408 409 410 411 |
lp55xx_reset_device(chip); /* * Exact value is not available. 10 - 20ms * appears to be enough for reset. */ usleep_range(10000, 20000); |
e3a700d8a leds-lp55xx: use ... |
412 413 414 415 416 417 |
ret = lp55xx_detect_device(chip); if (ret) { dev_err(dev, "device detection err: %d ", ret); goto err; } |
ffbdccdbb leds-lp55xx: use ... |
418 419 |
/* chip specific initialization */ ret = lp55xx_post_init_device(chip); |
22ebeb488 leds-lp55xx: clea... |
420 421 422 423 424 |
if (ret) { dev_err(dev, "post init device err: %d ", ret); goto err_post_init; } |
ffbdccdbb leds-lp55xx: use ... |
425 426 |
return 0; |
22ebeb488 leds-lp55xx: clea... |
427 |
err_post_init: |
6ce617626 leds-lp55xx: use ... |
428 429 430 431 432 433 434 435 436 |
lp55xx_deinit_device(chip); err: return ret; } EXPORT_SYMBOL_GPL(lp55xx_init_device); void lp55xx_deinit_device(struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata = chip->pdata; |
53b419226 leds: lp55xx: use... |
437 438 |
if (chip->clk) clk_disable_unprepare(chip->clk); |
30dae2f98 leds: lp55xx: han... |
439 440 |
if (gpio_is_valid(pdata->enable_gpio)) gpio_set_value(pdata->enable_gpio, 0); |
a85908dd7 leds-lp55xx: use ... |
441 |
} |
6ce617626 leds-lp55xx: use ... |
442 |
EXPORT_SYMBOL_GPL(lp55xx_deinit_device); |
a85908dd7 leds-lp55xx: use ... |
443 |
|
9e9b3db1b leds-lp55xx: use ... |
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata = chip->pdata; struct lp55xx_device_config *cfg = chip->cfg; int num_channels = pdata->num_channels; struct lp55xx_led *each; u8 led_current; int ret; int i; if (!cfg->brightness_work_fn) { dev_err(&chip->cl->dev, "empty brightness configuration "); return -EINVAL; } for (i = 0; i < num_channels; i++) { /* do not initialize channels that are not connected */ if (pdata->led_config[i].led_current == 0) continue; led_current = pdata->led_config[i].led_current; each = led + i; ret = lp55xx_init_led(each, chip, i); if (ret) goto err_init_led; INIT_WORK(&each->brightness_work, cfg->brightness_work_fn); chip->num_leds++; each->chip = chip; /* setting led current at each channel */ if (cfg->set_led_current) cfg->set_led_current(each, led_current); } return 0; err_init_led: |
d9067d284 leds-lp55xx: fix ... |
485 |
lp55xx_unregister_leds(led, chip); |
9e9b3db1b leds-lp55xx: use ... |
486 487 488 |
return ret; } EXPORT_SYMBOL_GPL(lp55xx_register_leds); |
c3a68ebfc leds-lp55xx: use ... |
489 490 491 492 |
void lp55xx_unregister_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) { int i; struct lp55xx_led *each; |
c3a68ebfc leds-lp55xx: use ... |
493 494 495 |
for (i = 0; i < chip->num_leds; i++) { each = led + i; |
c3a68ebfc leds-lp55xx: use ... |
496 497 498 499 500 |
led_classdev_unregister(&each->cdev); flush_work(&each->brightness_work); } } EXPORT_SYMBOL_GPL(lp55xx_unregister_leds); |
b3b6f8119 leds-lp55xx: add ... |
501 502 503 |
int lp55xx_register_sysfs(struct lp55xx_chip *chip) { struct device *dev = &chip->cl->dev; |
240085e25 leds-lp55xx: supp... |
504 505 506 507 508 509 510 511 512 |
struct lp55xx_device_config *cfg = chip->cfg; int ret; if (!cfg->run_engine || !cfg->firmware_cb) goto dev_specific_attrs; ret = sysfs_create_group(&dev->kobj, &lp55xx_engine_attr_group); if (ret) return ret; |
b3b6f8119 leds-lp55xx: add ... |
513 |
|
240085e25 leds-lp55xx: supp... |
514 515 516 |
dev_specific_attrs: return cfg->dev_attr_group ? sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0; |
b3b6f8119 leds-lp55xx: add ... |
517 518 |
} EXPORT_SYMBOL_GPL(lp55xx_register_sysfs); |
ba6fa8465 leds-lp55xx: add ... |
519 520 521 522 523 524 525 526 527 528 529 |
void lp55xx_unregister_sysfs(struct lp55xx_chip *chip) { struct device *dev = &chip->cl->dev; struct lp55xx_device_config *cfg = chip->cfg; if (cfg->dev_attr_group) sysfs_remove_group(&dev->kobj, cfg->dev_attr_group); sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group); } EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs); |
7542a04b1 leds: lp55xx: add... |
530 531 |
int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np) { |
2dac91280 leds: lp55xx: sup... |
532 |
struct device_node *child; |
7542a04b1 leds: lp55xx: add... |
533 |
struct lp55xx_platform_data *pdata; |
2dac91280 leds: lp55xx: sup... |
534 535 536 |
struct lp55xx_led_config *cfg; int num_channels; int i = 0; |
7542a04b1 leds: lp55xx: add... |
537 538 539 540 |
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; |
2dac91280 leds: lp55xx: sup... |
541 542 543 544 545 546 |
num_channels = of_get_child_count(np); if (num_channels == 0) { dev_err(dev, "no LED channels "); return -EINVAL; } |
7542a04b1 leds: lp55xx: add... |
547 |
|
2dac91280 leds: lp55xx: sup... |
548 549 |
cfg = devm_kzalloc(dev, sizeof(*cfg) * num_channels, GFP_KERNEL); if (!cfg) |
7542a04b1 leds: lp55xx: add... |
550 |
return -ENOMEM; |
2dac91280 leds: lp55xx: sup... |
551 552 553 554 555 556 557 558 559 |
pdata->led_config = &cfg[0]; pdata->num_channels = num_channels; for_each_child_of_node(np, child) { cfg[i].chan_nr = i; of_property_read_string(child, "chan-name", &cfg[i].name); of_property_read_u8(child, "led-cur", &cfg[i].led_current); of_property_read_u8(child, "max-cur", &cfg[i].max_current); |
f65f0a1a9 leds: lp55xx: ena... |
560 561 |
cfg[i].default_trigger = of_get_property(child, "linux,default-trigger", NULL); |
2dac91280 leds: lp55xx: sup... |
562 563 |
i++; |
7542a04b1 leds: lp55xx: add... |
564 |
} |
2dac91280 leds: lp55xx: sup... |
565 566 567 |
of_property_read_string(np, "label", &pdata->label); of_property_read_u8(np, "clock-mode", &pdata->clock_mode); |
30dae2f98 leds: lp55xx: han... |
568 |
pdata->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0); |
33b3a561f leds: support new... |
569 570 |
/* LP8501 specific */ of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel); |
7542a04b1 leds: lp55xx: add... |
571 572 573 574 575 |
dev->platform_data = pdata; return 0; } EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata); |
c93d08fa7 leds-lp55xx: add ... |
576 577 578 |
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); MODULE_DESCRIPTION("LP55xx Common Driver"); MODULE_LICENSE("GPL"); |