Commit | Line | Data |
---|---|---|
c93d08fa MWK |
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 | ||
a85908dd | 15 | #include <linux/delay.h> |
c93d08fa MWK |
16 | #include <linux/i2c.h> |
17 | #include <linux/leds.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/platform_data/leds-lp55xx.h> | |
20 | ||
21 | #include "leds-lp55xx-common.h" | |
22 | ||
a6e4679a MWK |
23 | static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev) |
24 | { | |
25 | return container_of(cdev, struct lp55xx_led, cdev); | |
26 | } | |
27 | ||
a96bfa13 MWK |
28 | static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev) |
29 | { | |
30 | return cdev_to_lp55xx_led(dev_get_drvdata(dev)); | |
31 | } | |
32 | ||
48068d5d MWK |
33 | static void lp55xx_reset_device(struct lp55xx_chip *chip) |
34 | { | |
35 | struct lp55xx_device_config *cfg = chip->cfg; | |
36 | u8 addr = cfg->reset.addr; | |
37 | u8 val = cfg->reset.val; | |
38 | ||
39 | /* no error checking here because no ACK from the device after reset */ | |
40 | lp55xx_write(chip, addr, val); | |
41 | } | |
42 | ||
e3a700d8 MWK |
43 | static int lp55xx_detect_device(struct lp55xx_chip *chip) |
44 | { | |
45 | struct lp55xx_device_config *cfg = chip->cfg; | |
46 | u8 addr = cfg->enable.addr; | |
47 | u8 val = cfg->enable.val; | |
48 | int ret; | |
49 | ||
50 | ret = lp55xx_write(chip, addr, val); | |
51 | if (ret) | |
52 | return ret; | |
53 | ||
54 | usleep_range(1000, 2000); | |
55 | ||
56 | ret = lp55xx_read(chip, addr, &val); | |
57 | if (ret) | |
58 | return ret; | |
59 | ||
60 | if (val != cfg->enable.val) | |
61 | return -ENODEV; | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
ffbdccdb MWK |
66 | static int lp55xx_post_init_device(struct lp55xx_chip *chip) |
67 | { | |
68 | struct lp55xx_device_config *cfg = chip->cfg; | |
69 | ||
70 | if (!cfg->post_init_device) | |
71 | return 0; | |
72 | ||
73 | return cfg->post_init_device(chip); | |
74 | } | |
75 | ||
a96bfa13 MWK |
76 | static ssize_t lp55xx_show_current(struct device *dev, |
77 | struct device_attribute *attr, | |
78 | char *buf) | |
79 | { | |
80 | struct lp55xx_led *led = dev_to_lp55xx_led(dev); | |
81 | ||
82 | return sprintf(buf, "%d\n", led->led_current); | |
83 | } | |
84 | ||
85 | static ssize_t lp55xx_store_current(struct device *dev, | |
86 | struct device_attribute *attr, | |
87 | const char *buf, size_t len) | |
88 | { | |
89 | struct lp55xx_led *led = dev_to_lp55xx_led(dev); | |
90 | struct lp55xx_chip *chip = led->chip; | |
91 | unsigned long curr; | |
92 | ||
93 | if (kstrtoul(buf, 0, &curr)) | |
94 | return -EINVAL; | |
95 | ||
96 | if (curr > led->max_current) | |
97 | return -EINVAL; | |
98 | ||
99 | if (!chip->cfg->set_led_current) | |
100 | return len; | |
101 | ||
102 | mutex_lock(&chip->lock); | |
103 | chip->cfg->set_led_current(led, (u8)curr); | |
104 | mutex_unlock(&chip->lock); | |
105 | ||
106 | return len; | |
107 | } | |
108 | ||
109 | static ssize_t lp55xx_show_max_current(struct device *dev, | |
110 | struct device_attribute *attr, | |
111 | char *buf) | |
112 | { | |
113 | struct lp55xx_led *led = dev_to_lp55xx_led(dev); | |
114 | ||
115 | return sprintf(buf, "%d\n", led->max_current); | |
116 | } | |
117 | ||
118 | static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, lp55xx_show_current, | |
119 | lp55xx_store_current); | |
120 | static DEVICE_ATTR(max_current, S_IRUGO , lp55xx_show_max_current, NULL); | |
121 | ||
0e202346 | 122 | static struct attribute *lp55xx_led_attributes[] = { |
a96bfa13 MWK |
123 | &dev_attr_led_current.attr, |
124 | &dev_attr_max_current.attr, | |
0e202346 MWK |
125 | NULL, |
126 | }; | |
127 | ||
128 | static struct attribute_group lp55xx_led_attr_group = { | |
129 | .attrs = lp55xx_led_attributes | |
130 | }; | |
131 | ||
132 | static void lp55xx_set_brightness(struct led_classdev *cdev, | |
133 | enum led_brightness brightness) | |
134 | { | |
a6e4679a MWK |
135 | struct lp55xx_led *led = cdev_to_lp55xx_led(cdev); |
136 | ||
137 | led->brightness = (u8)brightness; | |
138 | schedule_work(&led->brightness_work); | |
0e202346 MWK |
139 | } |
140 | ||
9e9b3db1 MWK |
141 | static int lp55xx_init_led(struct lp55xx_led *led, |
142 | struct lp55xx_chip *chip, int chan) | |
143 | { | |
0e202346 MWK |
144 | struct lp55xx_platform_data *pdata = chip->pdata; |
145 | struct lp55xx_device_config *cfg = chip->cfg; | |
146 | struct device *dev = &chip->cl->dev; | |
147 | char name[32]; | |
148 | int ret; | |
149 | int max_channel = cfg->max_channel; | |
150 | ||
151 | if (chan >= max_channel) { | |
152 | dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel); | |
153 | return -EINVAL; | |
154 | } | |
155 | ||
156 | if (pdata->led_config[chan].led_current == 0) | |
157 | return 0; | |
158 | ||
159 | led->led_current = pdata->led_config[chan].led_current; | |
160 | led->max_current = pdata->led_config[chan].max_current; | |
161 | led->chan_nr = pdata->led_config[chan].chan_nr; | |
162 | ||
163 | if (led->chan_nr >= max_channel) { | |
164 | dev_err(dev, "Use channel numbers between 0 and %d\n", | |
165 | max_channel - 1); | |
166 | return -EINVAL; | |
167 | } | |
168 | ||
169 | led->cdev.brightness_set = lp55xx_set_brightness; | |
170 | ||
171 | if (pdata->led_config[chan].name) { | |
172 | led->cdev.name = pdata->led_config[chan].name; | |
173 | } else { | |
174 | snprintf(name, sizeof(name), "%s:channel%d", | |
175 | pdata->label ? : chip->cl->name, chan); | |
176 | led->cdev.name = name; | |
177 | } | |
178 | ||
179 | /* | |
180 | * register led class device for each channel and | |
181 | * add device attributes | |
182 | */ | |
183 | ||
184 | ret = led_classdev_register(dev, &led->cdev); | |
185 | if (ret) { | |
186 | dev_err(dev, "led register err: %d\n", ret); | |
187 | return ret; | |
188 | } | |
189 | ||
190 | ret = sysfs_create_group(&led->cdev.dev->kobj, &lp55xx_led_attr_group); | |
191 | if (ret) { | |
192 | dev_err(dev, "led sysfs err: %d\n", ret); | |
193 | led_classdev_unregister(&led->cdev); | |
194 | return ret; | |
195 | } | |
196 | ||
9e9b3db1 MWK |
197 | return 0; |
198 | } | |
199 | ||
c93d08fa MWK |
200 | int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val) |
201 | { | |
202 | return i2c_smbus_write_byte_data(chip->cl, reg, val); | |
203 | } | |
204 | EXPORT_SYMBOL_GPL(lp55xx_write); | |
205 | ||
206 | int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val) | |
207 | { | |
208 | s32 ret; | |
209 | ||
210 | ret = i2c_smbus_read_byte_data(chip->cl, reg); | |
211 | if (ret < 0) | |
212 | return ret; | |
213 | ||
214 | *val = ret; | |
215 | return 0; | |
216 | } | |
217 | EXPORT_SYMBOL_GPL(lp55xx_read); | |
218 | ||
219 | int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val) | |
220 | { | |
221 | int ret; | |
222 | u8 tmp; | |
223 | ||
224 | ret = lp55xx_read(chip, reg, &tmp); | |
225 | if (ret) | |
226 | return ret; | |
227 | ||
228 | tmp &= ~mask; | |
229 | tmp |= val & mask; | |
230 | ||
231 | return lp55xx_write(chip, reg, tmp); | |
232 | } | |
233 | EXPORT_SYMBOL_GPL(lp55xx_update_bits); | |
234 | ||
a85908dd MWK |
235 | int lp55xx_init_device(struct lp55xx_chip *chip) |
236 | { | |
237 | struct lp55xx_platform_data *pdata; | |
48068d5d | 238 | struct lp55xx_device_config *cfg; |
a85908dd MWK |
239 | struct device *dev = &chip->cl->dev; |
240 | int ret = 0; | |
241 | ||
242 | WARN_ON(!chip); | |
243 | ||
244 | pdata = chip->pdata; | |
48068d5d | 245 | cfg = chip->cfg; |
a85908dd | 246 | |
48068d5d | 247 | if (!pdata || !cfg) |
a85908dd MWK |
248 | return -EINVAL; |
249 | ||
250 | if (pdata->setup_resources) { | |
251 | ret = pdata->setup_resources(); | |
252 | if (ret < 0) { | |
253 | dev_err(dev, "setup resoure err: %d\n", ret); | |
254 | goto err; | |
255 | } | |
256 | } | |
257 | ||
258 | if (pdata->enable) { | |
259 | pdata->enable(0); | |
260 | usleep_range(1000, 2000); /* Keep enable down at least 1ms */ | |
261 | pdata->enable(1); | |
262 | usleep_range(1000, 2000); /* 500us abs min. */ | |
263 | } | |
264 | ||
48068d5d MWK |
265 | lp55xx_reset_device(chip); |
266 | ||
267 | /* | |
268 | * Exact value is not available. 10 - 20ms | |
269 | * appears to be enough for reset. | |
270 | */ | |
271 | usleep_range(10000, 20000); | |
272 | ||
e3a700d8 MWK |
273 | ret = lp55xx_detect_device(chip); |
274 | if (ret) { | |
275 | dev_err(dev, "device detection err: %d\n", ret); | |
276 | goto err; | |
277 | } | |
278 | ||
ffbdccdb MWK |
279 | /* chip specific initialization */ |
280 | ret = lp55xx_post_init_device(chip); | |
22ebeb48 MWK |
281 | if (ret) { |
282 | dev_err(dev, "post init device err: %d\n", ret); | |
283 | goto err_post_init; | |
284 | } | |
ffbdccdb MWK |
285 | |
286 | return 0; | |
287 | ||
22ebeb48 | 288 | err_post_init: |
6ce61762 MWK |
289 | lp55xx_deinit_device(chip); |
290 | err: | |
291 | return ret; | |
292 | } | |
293 | EXPORT_SYMBOL_GPL(lp55xx_init_device); | |
294 | ||
295 | void lp55xx_deinit_device(struct lp55xx_chip *chip) | |
296 | { | |
297 | struct lp55xx_platform_data *pdata = chip->pdata; | |
298 | ||
22ebeb48 MWK |
299 | if (pdata->enable) |
300 | pdata->enable(0); | |
6ce61762 | 301 | |
22ebeb48 MWK |
302 | if (pdata->release_resources) |
303 | pdata->release_resources(); | |
a85908dd | 304 | } |
6ce61762 | 305 | EXPORT_SYMBOL_GPL(lp55xx_deinit_device); |
a85908dd | 306 | |
9e9b3db1 MWK |
307 | int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) |
308 | { | |
309 | struct lp55xx_platform_data *pdata = chip->pdata; | |
310 | struct lp55xx_device_config *cfg = chip->cfg; | |
311 | int num_channels = pdata->num_channels; | |
312 | struct lp55xx_led *each; | |
313 | u8 led_current; | |
314 | int ret; | |
315 | int i; | |
316 | ||
317 | if (!cfg->brightness_work_fn) { | |
318 | dev_err(&chip->cl->dev, "empty brightness configuration\n"); | |
319 | return -EINVAL; | |
320 | } | |
321 | ||
322 | for (i = 0; i < num_channels; i++) { | |
323 | ||
324 | /* do not initialize channels that are not connected */ | |
325 | if (pdata->led_config[i].led_current == 0) | |
326 | continue; | |
327 | ||
328 | led_current = pdata->led_config[i].led_current; | |
329 | each = led + i; | |
330 | ret = lp55xx_init_led(each, chip, i); | |
331 | if (ret) | |
332 | goto err_init_led; | |
333 | ||
334 | INIT_WORK(&each->brightness_work, cfg->brightness_work_fn); | |
335 | ||
336 | chip->num_leds++; | |
337 | each->chip = chip; | |
338 | ||
339 | /* setting led current at each channel */ | |
340 | if (cfg->set_led_current) | |
341 | cfg->set_led_current(each, led_current); | |
342 | } | |
343 | ||
344 | return 0; | |
345 | ||
346 | err_init_led: | |
347 | for (i = 0; i < chip->num_leds; i++) { | |
348 | each = led + i; | |
349 | led_classdev_unregister(&each->cdev); | |
350 | flush_work(&each->brightness_work); | |
351 | } | |
352 | return ret; | |
353 | } | |
354 | EXPORT_SYMBOL_GPL(lp55xx_register_leds); | |
355 | ||
c93d08fa MWK |
356 | MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); |
357 | MODULE_DESCRIPTION("LP55xx Common Driver"); | |
358 | MODULE_LICENSE("GPL"); |