Root/drivers/leds/leds-lp55xx-common.c

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

Archive Download this file



interactive