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