Commit | Line | Data |
---|---|---|
b7da8c5c IK |
1 | /* |
2 | * LED driver : leds-ktd2692.c | |
3 | * | |
4 | * Copyright (C) 2015 Samsung Electronics | |
5 | * Ingi Kim <ingi2.kim@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #include <linux/delay.h> | |
13 | #include <linux/err.h> | |
14 | #include <linux/gpio/consumer.h> | |
15 | #include <linux/led-class-flash.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/mutex.h> | |
18 | #include <linux/of.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/regulator/consumer.h> | |
b7da8c5c IK |
21 | |
22 | /* Value related the movie mode */ | |
23 | #define KTD2692_MOVIE_MODE_CURRENT_LEVELS 16 | |
24 | #define KTD2692_MM_TO_FL_RATIO(x) ((x) / 3) | |
25 | #define KTD2962_MM_MIN_CURR_THRESHOLD_SCALE 8 | |
26 | ||
27 | /* Value related the flash mode */ | |
28 | #define KTD2692_FLASH_MODE_TIMEOUT_LEVELS 8 | |
29 | #define KTD2692_FLASH_MODE_TIMEOUT_DISABLE 0 | |
30 | #define KTD2692_FLASH_MODE_CURR_PERCENT(x) (((x) * 16) / 100) | |
31 | ||
32 | /* Macro for getting offset of flash timeout */ | |
33 | #define GET_TIMEOUT_OFFSET(timeout, step) ((timeout) / (step)) | |
34 | ||
35 | /* Base register address */ | |
36 | #define KTD2692_REG_LVP_BASE 0x00 | |
37 | #define KTD2692_REG_FLASH_TIMEOUT_BASE 0x20 | |
38 | #define KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE 0x40 | |
39 | #define KTD2692_REG_MOVIE_CURRENT_BASE 0x60 | |
40 | #define KTD2692_REG_FLASH_CURRENT_BASE 0x80 | |
41 | #define KTD2692_REG_MODE_BASE 0xA0 | |
42 | ||
43 | /* Set bit coding time for expresswire interface */ | |
44 | #define KTD2692_TIME_RESET_US 700 | |
45 | #define KTD2692_TIME_DATA_START_TIME_US 10 | |
46 | #define KTD2692_TIME_HIGH_END_OF_DATA_US 350 | |
47 | #define KTD2692_TIME_LOW_END_OF_DATA_US 10 | |
48 | #define KTD2692_TIME_SHORT_BITSET_US 4 | |
49 | #define KTD2692_TIME_LONG_BITSET_US 12 | |
50 | ||
51 | /* KTD2692 default length of name */ | |
52 | #define KTD2692_NAME_LENGTH 20 | |
53 | ||
54 | enum ktd2692_bitset { | |
55 | KTD2692_LOW = 0, | |
56 | KTD2692_HIGH, | |
57 | }; | |
58 | ||
59 | /* Movie / Flash Mode Control */ | |
60 | enum ktd2692_led_mode { | |
61 | KTD2692_MODE_DISABLE = 0, /* default */ | |
62 | KTD2692_MODE_MOVIE, | |
63 | KTD2692_MODE_FLASH, | |
64 | }; | |
65 | ||
66 | struct ktd2692_led_config_data { | |
67 | /* maximum LED current in movie mode */ | |
68 | u32 movie_max_microamp; | |
69 | /* maximum LED current in flash mode */ | |
70 | u32 flash_max_microamp; | |
71 | /* maximum flash timeout */ | |
72 | u32 flash_max_timeout; | |
73 | /* max LED brightness level */ | |
74 | enum led_brightness max_brightness; | |
75 | }; | |
76 | ||
77 | struct ktd2692_context { | |
78 | /* Related LED Flash class device */ | |
79 | struct led_classdev_flash fled_cdev; | |
80 | ||
81 | /* secures access to the device */ | |
82 | struct mutex lock; | |
83 | struct regulator *regulator; | |
b7da8c5c IK |
84 | |
85 | struct gpio_desc *aux_gpio; | |
86 | struct gpio_desc *ctrl_gpio; | |
87 | ||
88 | enum ktd2692_led_mode mode; | |
89 | enum led_brightness torch_brightness; | |
90 | }; | |
91 | ||
92 | static struct ktd2692_context *fled_cdev_to_led( | |
93 | struct led_classdev_flash *fled_cdev) | |
94 | { | |
95 | return container_of(fled_cdev, struct ktd2692_context, fled_cdev); | |
96 | } | |
97 | ||
98 | static void ktd2692_expresswire_start(struct ktd2692_context *led) | |
99 | { | |
100 | gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); | |
101 | udelay(KTD2692_TIME_DATA_START_TIME_US); | |
102 | } | |
103 | ||
104 | static void ktd2692_expresswire_reset(struct ktd2692_context *led) | |
105 | { | |
106 | gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); | |
107 | udelay(KTD2692_TIME_RESET_US); | |
108 | } | |
109 | ||
110 | static void ktd2692_expresswire_end(struct ktd2692_context *led) | |
111 | { | |
112 | gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); | |
113 | udelay(KTD2692_TIME_LOW_END_OF_DATA_US); | |
114 | gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); | |
115 | udelay(KTD2692_TIME_HIGH_END_OF_DATA_US); | |
116 | } | |
117 | ||
118 | static void ktd2692_expresswire_set_bit(struct ktd2692_context *led, bool bit) | |
119 | { | |
120 | /* | |
121 | * The Low Bit(0) and High Bit(1) is based on a time detection | |
122 | * algorithm between time low and time high | |
123 | * Time_(L_LB) : Low time of the Low Bit(0) | |
124 | * Time_(H_LB) : High time of the LOW Bit(0) | |
125 | * Time_(L_HB) : Low time of the High Bit(1) | |
126 | * Time_(H_HB) : High time of the High Bit(1) | |
127 | * | |
128 | * It can be simplified to: | |
129 | * Low Bit(0) : 2 * Time_(H_LB) < Time_(L_LB) | |
130 | * High Bit(1) : 2 * Time_(L_HB) < Time_(H_HB) | |
131 | * HIGH ___ ____ _.. _________ ___ | |
132 | * |_________| |_.. |____| |__| | |
133 | * LOW <L_LB> <H_LB> <L_HB> <H_HB> | |
134 | * [ Low Bit (0) ] [ High Bit(1) ] | |
135 | */ | |
136 | if (bit) { | |
137 | gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); | |
138 | udelay(KTD2692_TIME_SHORT_BITSET_US); | |
139 | gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); | |
140 | udelay(KTD2692_TIME_LONG_BITSET_US); | |
141 | } else { | |
142 | gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); | |
143 | udelay(KTD2692_TIME_LONG_BITSET_US); | |
144 | gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); | |
145 | udelay(KTD2692_TIME_SHORT_BITSET_US); | |
146 | } | |
147 | } | |
148 | ||
149 | static void ktd2692_expresswire_write(struct ktd2692_context *led, u8 value) | |
150 | { | |
151 | int i; | |
152 | ||
153 | ktd2692_expresswire_start(led); | |
154 | for (i = 7; i >= 0; i--) | |
155 | ktd2692_expresswire_set_bit(led, value & BIT(i)); | |
156 | ktd2692_expresswire_end(led); | |
157 | } | |
158 | ||
9d79a05e JA |
159 | static int ktd2692_led_brightness_set(struct led_classdev *led_cdev, |
160 | enum led_brightness brightness) | |
b7da8c5c | 161 | { |
9d79a05e JA |
162 | struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); |
163 | struct ktd2692_context *led = fled_cdev_to_led(fled_cdev); | |
164 | ||
b7da8c5c IK |
165 | mutex_lock(&led->lock); |
166 | ||
167 | if (brightness == LED_OFF) { | |
168 | led->mode = KTD2692_MODE_DISABLE; | |
169 | gpiod_direction_output(led->aux_gpio, KTD2692_LOW); | |
170 | } else { | |
171 | ktd2692_expresswire_write(led, brightness | | |
172 | KTD2692_REG_MOVIE_CURRENT_BASE); | |
173 | led->mode = KTD2692_MODE_MOVIE; | |
174 | } | |
175 | ||
176 | ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE); | |
177 | mutex_unlock(&led->lock); | |
b7da8c5c IK |
178 | |
179 | return 0; | |
180 | } | |
181 | ||
182 | static int ktd2692_led_flash_strobe_set(struct led_classdev_flash *fled_cdev, | |
183 | bool state) | |
184 | { | |
185 | struct ktd2692_context *led = fled_cdev_to_led(fled_cdev); | |
186 | struct led_flash_setting *timeout = &fled_cdev->timeout; | |
187 | u32 flash_tm_reg; | |
188 | ||
189 | mutex_lock(&led->lock); | |
190 | ||
191 | if (state) { | |
192 | flash_tm_reg = GET_TIMEOUT_OFFSET(timeout->val, timeout->step); | |
193 | ktd2692_expresswire_write(led, flash_tm_reg | |
194 | | KTD2692_REG_FLASH_TIMEOUT_BASE); | |
195 | ||
196 | led->mode = KTD2692_MODE_FLASH; | |
197 | gpiod_direction_output(led->aux_gpio, KTD2692_HIGH); | |
198 | } else { | |
199 | led->mode = KTD2692_MODE_DISABLE; | |
200 | gpiod_direction_output(led->aux_gpio, KTD2692_LOW); | |
201 | } | |
202 | ||
203 | ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE); | |
204 | ||
205 | fled_cdev->led_cdev.brightness = LED_OFF; | |
206 | led->mode = KTD2692_MODE_DISABLE; | |
207 | ||
208 | mutex_unlock(&led->lock); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int ktd2692_led_flash_timeout_set(struct led_classdev_flash *fled_cdev, | |
214 | u32 timeout) | |
215 | { | |
216 | return 0; | |
217 | } | |
218 | ||
219 | static void ktd2692_init_movie_current_max(struct ktd2692_led_config_data *cfg) | |
220 | { | |
221 | u32 offset, step; | |
222 | u32 movie_current_microamp; | |
223 | ||
224 | offset = KTD2692_MOVIE_MODE_CURRENT_LEVELS; | |
225 | step = KTD2692_MM_TO_FL_RATIO(cfg->flash_max_microamp) | |
226 | / KTD2692_MOVIE_MODE_CURRENT_LEVELS; | |
227 | ||
228 | do { | |
229 | movie_current_microamp = step * offset; | |
230 | offset--; | |
231 | } while ((movie_current_microamp > cfg->movie_max_microamp) && | |
232 | (offset > 0)); | |
233 | ||
234 | cfg->max_brightness = offset; | |
235 | } | |
236 | ||
237 | static void ktd2692_init_flash_timeout(struct led_classdev_flash *fled_cdev, | |
238 | struct ktd2692_led_config_data *cfg) | |
239 | { | |
240 | struct led_flash_setting *setting; | |
241 | ||
242 | setting = &fled_cdev->timeout; | |
243 | setting->min = KTD2692_FLASH_MODE_TIMEOUT_DISABLE; | |
244 | setting->max = cfg->flash_max_timeout; | |
245 | setting->step = cfg->flash_max_timeout | |
246 | / (KTD2692_FLASH_MODE_TIMEOUT_LEVELS - 1); | |
247 | setting->val = cfg->flash_max_timeout; | |
248 | } | |
249 | ||
250 | static void ktd2692_setup(struct ktd2692_context *led) | |
251 | { | |
252 | led->mode = KTD2692_MODE_DISABLE; | |
253 | ktd2692_expresswire_reset(led); | |
254 | gpiod_direction_output(led->aux_gpio, KTD2692_LOW); | |
255 | ||
256 | ktd2692_expresswire_write(led, (KTD2962_MM_MIN_CURR_THRESHOLD_SCALE - 1) | |
257 | | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE); | |
258 | ktd2692_expresswire_write(led, KTD2692_FLASH_MODE_CURR_PERCENT(45) | |
259 | | KTD2692_REG_FLASH_CURRENT_BASE); | |
260 | } | |
261 | ||
262 | static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev, | |
263 | struct ktd2692_led_config_data *cfg) | |
264 | { | |
265 | struct device_node *np = dev->of_node; | |
266 | struct device_node *child_node; | |
267 | int ret; | |
268 | ||
269 | if (!dev->of_node) | |
270 | return -ENXIO; | |
271 | ||
5e324ebf | 272 | led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS); |
cbe99c53 AB |
273 | ret = PTR_ERR_OR_ZERO(led->ctrl_gpio); |
274 | if (ret) { | |
b7da8c5c IK |
275 | dev_err(dev, "cannot get ctrl-gpios %d\n", ret); |
276 | return ret; | |
277 | } | |
278 | ||
5e324ebf | 279 | led->aux_gpio = devm_gpiod_get(dev, "aux", GPIOD_ASIS); |
cbe99c53 AB |
280 | ret = PTR_ERR_OR_ZERO(led->aux_gpio); |
281 | if (ret) { | |
b7da8c5c IK |
282 | dev_err(dev, "cannot get aux-gpios %d\n", ret); |
283 | return ret; | |
284 | } | |
285 | ||
286 | led->regulator = devm_regulator_get(dev, "vin"); | |
287 | if (IS_ERR(led->regulator)) | |
288 | led->regulator = NULL; | |
289 | ||
290 | if (led->regulator) { | |
291 | ret = regulator_enable(led->regulator); | |
292 | if (ret) | |
293 | dev_err(dev, "Failed to enable supply: %d\n", ret); | |
294 | } | |
295 | ||
296 | child_node = of_get_next_available_child(np, NULL); | |
297 | if (!child_node) { | |
298 | dev_err(dev, "No DT child node found for connected LED.\n"); | |
299 | return -EINVAL; | |
300 | } | |
301 | ||
302 | led->fled_cdev.led_cdev.name = | |
303 | of_get_property(child_node, "label", NULL) ? : child_node->name; | |
304 | ||
305 | ret = of_property_read_u32(child_node, "led-max-microamp", | |
306 | &cfg->movie_max_microamp); | |
307 | if (ret) { | |
308 | dev_err(dev, "failed to parse led-max-microamp\n"); | |
77e7915b | 309 | goto err_parse_dt; |
b7da8c5c IK |
310 | } |
311 | ||
312 | ret = of_property_read_u32(child_node, "flash-max-microamp", | |
313 | &cfg->flash_max_microamp); | |
314 | if (ret) { | |
315 | dev_err(dev, "failed to parse flash-max-microamp\n"); | |
77e7915b | 316 | goto err_parse_dt; |
b7da8c5c IK |
317 | } |
318 | ||
319 | ret = of_property_read_u32(child_node, "flash-max-timeout-us", | |
320 | &cfg->flash_max_timeout); | |
77e7915b | 321 | if (ret) { |
b7da8c5c | 322 | dev_err(dev, "failed to parse flash-max-timeout-us\n"); |
77e7915b IK |
323 | goto err_parse_dt; |
324 | } | |
b7da8c5c | 325 | |
77e7915b | 326 | err_parse_dt: |
b7da8c5c IK |
327 | of_node_put(child_node); |
328 | return ret; | |
329 | } | |
330 | ||
331 | static const struct led_flash_ops flash_ops = { | |
332 | .strobe_set = ktd2692_led_flash_strobe_set, | |
333 | .timeout_set = ktd2692_led_flash_timeout_set, | |
334 | }; | |
335 | ||
336 | static int ktd2692_probe(struct platform_device *pdev) | |
337 | { | |
338 | struct ktd2692_context *led; | |
339 | struct led_classdev *led_cdev; | |
340 | struct led_classdev_flash *fled_cdev; | |
341 | struct ktd2692_led_config_data led_cfg; | |
342 | int ret; | |
343 | ||
344 | led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); | |
345 | if (!led) | |
346 | return -ENOMEM; | |
347 | ||
348 | fled_cdev = &led->fled_cdev; | |
349 | led_cdev = &fled_cdev->led_cdev; | |
350 | ||
351 | ret = ktd2692_parse_dt(led, &pdev->dev, &led_cfg); | |
352 | if (ret) | |
353 | return ret; | |
354 | ||
355 | ktd2692_init_flash_timeout(fled_cdev, &led_cfg); | |
356 | ktd2692_init_movie_current_max(&led_cfg); | |
357 | ||
358 | fled_cdev->ops = &flash_ops; | |
359 | ||
360 | led_cdev->max_brightness = led_cfg.max_brightness; | |
9d79a05e | 361 | led_cdev->brightness_set_blocking = ktd2692_led_brightness_set; |
b7da8c5c IK |
362 | led_cdev->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH; |
363 | ||
364 | mutex_init(&led->lock); | |
b7da8c5c IK |
365 | |
366 | platform_set_drvdata(pdev, led); | |
367 | ||
368 | ret = led_classdev_flash_register(&pdev->dev, fled_cdev); | |
369 | if (ret) { | |
370 | dev_err(&pdev->dev, "can't register LED %s\n", led_cdev->name); | |
371 | mutex_destroy(&led->lock); | |
372 | return ret; | |
373 | } | |
374 | ||
375 | ktd2692_setup(led); | |
376 | ||
377 | return 0; | |
378 | } | |
379 | ||
380 | static int ktd2692_remove(struct platform_device *pdev) | |
381 | { | |
382 | struct ktd2692_context *led = platform_get_drvdata(pdev); | |
383 | int ret; | |
384 | ||
385 | led_classdev_flash_unregister(&led->fled_cdev); | |
b7da8c5c IK |
386 | |
387 | if (led->regulator) { | |
388 | ret = regulator_disable(led->regulator); | |
389 | if (ret) | |
390 | dev_err(&pdev->dev, | |
391 | "Failed to disable supply: %d\n", ret); | |
392 | } | |
393 | ||
394 | mutex_destroy(&led->lock); | |
395 | ||
396 | return 0; | |
397 | } | |
398 | ||
399 | static const struct of_device_id ktd2692_match[] = { | |
400 | { .compatible = "kinetic,ktd2692", }, | |
401 | { /* sentinel */ }, | |
402 | }; | |
3682c7bd | 403 | MODULE_DEVICE_TABLE(of, ktd2692_match); |
b7da8c5c IK |
404 | |
405 | static struct platform_driver ktd2692_driver = { | |
406 | .driver = { | |
407 | .name = "ktd2692", | |
408 | .of_match_table = ktd2692_match, | |
409 | }, | |
410 | .probe = ktd2692_probe, | |
411 | .remove = ktd2692_remove, | |
412 | }; | |
413 | ||
414 | module_platform_driver(ktd2692_driver); | |
415 | ||
416 | MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>"); | |
417 | MODULE_DESCRIPTION("Kinetic KTD2692 LED driver"); | |
418 | MODULE_LICENSE("GPL v2"); |