1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Backlight driver for Analog Devices ADP8870 Backlight Devices |
4 | * |
5 | * Copyright 2009-2011 Analog Devices Inc. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/init.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/pm.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/fb.h> |
15 | #include <linux/backlight.h> |
16 | #include <linux/leds.h> |
17 | #include <linux/workqueue.h> |
18 | #include <linux/slab.h> |
19 | |
20 | #include <linux/platform_data/adp8870.h> |
21 | #define ADP8870_EXT_FEATURES |
22 | #define ADP8870_USE_LEDS |
23 | |
24 | |
25 | #define ADP8870_MFDVID 0x00 /* Manufacturer and device ID */ |
26 | #define ADP8870_MDCR 0x01 /* Device mode and status */ |
27 | #define ADP8870_INT_STAT 0x02 /* Interrupts status */ |
28 | #define ADP8870_INT_EN 0x03 /* Interrupts enable */ |
29 | #define ADP8870_CFGR 0x04 /* Configuration register */ |
30 | #define ADP8870_BLSEL 0x05 /* Sink enable backlight or independent */ |
31 | #define ADP8870_PWMLED 0x06 /* PWM Enable Selection Register */ |
32 | #define ADP8870_BLOFF 0x07 /* Backlight off timeout */ |
33 | #define ADP8870_BLDIM 0x08 /* Backlight dim timeout */ |
34 | #define ADP8870_BLFR 0x09 /* Backlight fade in and out rates */ |
35 | #define ADP8870_BLMX1 0x0A /* Backlight (Brightness Level 1-daylight) maximum current */ |
36 | #define ADP8870_BLDM1 0x0B /* Backlight (Brightness Level 1-daylight) dim current */ |
37 | #define ADP8870_BLMX2 0x0C /* Backlight (Brightness Level 2-bright) maximum current */ |
38 | #define ADP8870_BLDM2 0x0D /* Backlight (Brightness Level 2-bright) dim current */ |
39 | #define ADP8870_BLMX3 0x0E /* Backlight (Brightness Level 3-office) maximum current */ |
40 | #define ADP8870_BLDM3 0x0F /* Backlight (Brightness Level 3-office) dim current */ |
41 | #define ADP8870_BLMX4 0x10 /* Backlight (Brightness Level 4-indoor) maximum current */ |
42 | #define ADP8870_BLDM4 0x11 /* Backlight (Brightness Level 4-indoor) dim current */ |
43 | #define ADP8870_BLMX5 0x12 /* Backlight (Brightness Level 5-dark) maximum current */ |
44 | #define ADP8870_BLDM5 0x13 /* Backlight (Brightness Level 5-dark) dim current */ |
45 | #define ADP8870_ISCLAW 0x1A /* Independent sink current fade law register */ |
46 | #define ADP8870_ISCC 0x1B /* Independent sink current control register */ |
47 | #define ADP8870_ISCT1 0x1C /* Independent Sink Current Timer Register LED[7:5] */ |
48 | #define ADP8870_ISCT2 0x1D /* Independent Sink Current Timer Register LED[4:1] */ |
49 | #define ADP8870_ISCF 0x1E /* Independent sink current fade register */ |
50 | #define ADP8870_ISC1 0x1F /* Independent Sink Current LED1 */ |
51 | #define ADP8870_ISC2 0x20 /* Independent Sink Current LED2 */ |
52 | #define ADP8870_ISC3 0x21 /* Independent Sink Current LED3 */ |
53 | #define ADP8870_ISC4 0x22 /* Independent Sink Current LED4 */ |
54 | #define ADP8870_ISC5 0x23 /* Independent Sink Current LED5 */ |
55 | #define ADP8870_ISC6 0x24 /* Independent Sink Current LED6 */ |
56 | #define ADP8870_ISC7 0x25 /* Independent Sink Current LED7 (Brightness Level 1-daylight) */ |
57 | #define ADP8870_ISC7_L2 0x26 /* Independent Sink Current LED7 (Brightness Level 2-bright) */ |
58 | #define ADP8870_ISC7_L3 0x27 /* Independent Sink Current LED7 (Brightness Level 3-office) */ |
59 | #define ADP8870_ISC7_L4 0x28 /* Independent Sink Current LED7 (Brightness Level 4-indoor) */ |
60 | #define ADP8870_ISC7_L5 0x29 /* Independent Sink Current LED7 (Brightness Level 5-dark) */ |
61 | #define ADP8870_CMP_CTL 0x2D /* ALS Comparator Control Register */ |
62 | #define ADP8870_ALS1_EN 0x2E /* Main ALS comparator level enable */ |
63 | #define ADP8870_ALS2_EN 0x2F /* Second ALS comparator level enable */ |
64 | #define ADP8870_ALS1_STAT 0x30 /* Main ALS Comparator Status Register */ |
65 | #define ADP8870_ALS2_STAT 0x31 /* Second ALS Comparator Status Register */ |
66 | #define ADP8870_L2TRP 0x32 /* L2 comparator reference */ |
67 | #define ADP8870_L2HYS 0x33 /* L2 hysteresis */ |
68 | #define ADP8870_L3TRP 0x34 /* L3 comparator reference */ |
69 | #define ADP8870_L3HYS 0x35 /* L3 hysteresis */ |
70 | #define ADP8870_L4TRP 0x36 /* L4 comparator reference */ |
71 | #define ADP8870_L4HYS 0x37 /* L4 hysteresis */ |
72 | #define ADP8870_L5TRP 0x38 /* L5 comparator reference */ |
73 | #define ADP8870_L5HYS 0x39 /* L5 hysteresis */ |
74 | #define ADP8870_PH1LEVL 0x40 /* First phototransistor ambient light level-low byte register */ |
75 | #define ADP8870_PH1LEVH 0x41 /* First phototransistor ambient light level-high byte register */ |
76 | #define ADP8870_PH2LEVL 0x42 /* Second phototransistor ambient light level-low byte register */ |
77 | #define ADP8870_PH2LEVH 0x43 /* Second phototransistor ambient light level-high byte register */ |
78 | |
79 | #define ADP8870_MANUFID 0x3 /* Analog Devices AD8870 Manufacturer and device ID */ |
80 | #define ADP8870_DEVID(x) ((x) & 0xF) |
81 | #define ADP8870_MANID(x) ((x) >> 4) |
82 | |
83 | /* MDCR Device mode and status */ |
84 | #define D7ALSEN (1 << 7) |
85 | #define INT_CFG (1 << 6) |
86 | #define NSTBY (1 << 5) |
87 | #define DIM_EN (1 << 4) |
88 | #define GDWN_DIS (1 << 3) |
89 | #define SIS_EN (1 << 2) |
90 | #define CMP_AUTOEN (1 << 1) |
91 | #define BLEN (1 << 0) |
92 | |
93 | /* ADP8870_ALS1_EN Main ALS comparator level enable */ |
94 | #define L5_EN (1 << 3) |
95 | #define L4_EN (1 << 2) |
96 | #define L3_EN (1 << 1) |
97 | #define L2_EN (1 << 0) |
98 | |
99 | #define CFGR_BLV_SHIFT 3 |
100 | #define CFGR_BLV_MASK 0x7 |
101 | #define ADP8870_FLAG_LED_MASK 0xFF |
102 | |
103 | #define FADE_VAL(in, out) ((0xF & (in)) | ((0xF & (out)) << 4)) |
104 | #define BL_CFGR_VAL(law, blv) ((((blv) & CFGR_BLV_MASK) << CFGR_BLV_SHIFT) | ((0x3 & (law)) << 1)) |
105 | #define ALS_CMPR_CFG_VAL(filt) ((0x7 & (filt)) << 1) |
106 | |
107 | struct adp8870_bl { |
108 | struct i2c_client *client; |
109 | struct backlight_device *bl; |
110 | struct adp8870_led *led; |
111 | struct adp8870_backlight_platform_data *pdata; |
112 | struct mutex lock; |
113 | unsigned long cached_daylight_max; |
114 | int id; |
115 | int revid; |
116 | int current_brightness; |
117 | }; |
118 | |
119 | struct adp8870_led { |
120 | struct led_classdev cdev; |
121 | struct work_struct work; |
122 | struct i2c_client *client; |
123 | enum led_brightness new_brightness; |
124 | int id; |
125 | int flags; |
126 | }; |
127 | |
128 | static int adp8870_read(struct i2c_client *client, int reg, uint8_t *val) |
129 | { |
130 | int ret; |
131 | |
132 | ret = i2c_smbus_read_byte_data(client, command: reg); |
133 | if (ret < 0) { |
134 | dev_err(&client->dev, "failed reading at 0x%02x\n" , reg); |
135 | return ret; |
136 | } |
137 | |
138 | *val = ret; |
139 | return 0; |
140 | } |
141 | |
142 | |
143 | static int adp8870_write(struct i2c_client *client, u8 reg, u8 val) |
144 | { |
145 | int ret = i2c_smbus_write_byte_data(client, command: reg, value: val); |
146 | |
147 | if (ret) |
148 | dev_err(&client->dev, "failed to write\n" ); |
149 | |
150 | return ret; |
151 | } |
152 | |
153 | static int adp8870_set_bits(struct i2c_client *client, int reg, uint8_t bit_mask) |
154 | { |
155 | struct adp8870_bl *data = i2c_get_clientdata(client); |
156 | uint8_t reg_val; |
157 | int ret; |
158 | |
159 | mutex_lock(&data->lock); |
160 | |
161 | ret = adp8870_read(client, reg, val: ®_val); |
162 | |
163 | if (!ret && ((reg_val & bit_mask) != bit_mask)) { |
164 | reg_val |= bit_mask; |
165 | ret = adp8870_write(client, reg, val: reg_val); |
166 | } |
167 | |
168 | mutex_unlock(lock: &data->lock); |
169 | return ret; |
170 | } |
171 | |
172 | static int adp8870_clr_bits(struct i2c_client *client, int reg, uint8_t bit_mask) |
173 | { |
174 | struct adp8870_bl *data = i2c_get_clientdata(client); |
175 | uint8_t reg_val; |
176 | int ret; |
177 | |
178 | mutex_lock(&data->lock); |
179 | |
180 | ret = adp8870_read(client, reg, val: ®_val); |
181 | |
182 | if (!ret && (reg_val & bit_mask)) { |
183 | reg_val &= ~bit_mask; |
184 | ret = adp8870_write(client, reg, val: reg_val); |
185 | } |
186 | |
187 | mutex_unlock(lock: &data->lock); |
188 | return ret; |
189 | } |
190 | |
191 | /* |
192 | * Independent sink / LED |
193 | */ |
194 | #if defined(ADP8870_USE_LEDS) |
195 | static void adp8870_led_work(struct work_struct *work) |
196 | { |
197 | struct adp8870_led *led = container_of(work, struct adp8870_led, work); |
198 | |
199 | adp8870_write(client: led->client, ADP8870_ISC1 + led->id - 1, |
200 | val: led->new_brightness >> 1); |
201 | } |
202 | |
203 | static void adp8870_led_set(struct led_classdev *led_cdev, |
204 | enum led_brightness value) |
205 | { |
206 | struct adp8870_led *led; |
207 | |
208 | led = container_of(led_cdev, struct adp8870_led, cdev); |
209 | led->new_brightness = value; |
210 | /* |
211 | * Use workqueue for IO since I2C operations can sleep. |
212 | */ |
213 | schedule_work(work: &led->work); |
214 | } |
215 | |
216 | static int adp8870_led_setup(struct adp8870_led *led) |
217 | { |
218 | struct i2c_client *client = led->client; |
219 | int ret = 0; |
220 | |
221 | ret = adp8870_write(client, ADP8870_ISC1 + led->id - 1, val: 0); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | ret = adp8870_set_bits(client, ADP8870_ISCC, bit_mask: 1 << (led->id - 1)); |
226 | if (ret) |
227 | return ret; |
228 | |
229 | if (led->id > 4) |
230 | ret = adp8870_set_bits(client, ADP8870_ISCT1, |
231 | bit_mask: (led->flags & 0x3) << ((led->id - 5) * 2)); |
232 | else |
233 | ret = adp8870_set_bits(client, ADP8870_ISCT2, |
234 | bit_mask: (led->flags & 0x3) << ((led->id - 1) * 2)); |
235 | |
236 | return ret; |
237 | } |
238 | |
239 | static int adp8870_led_probe(struct i2c_client *client) |
240 | { |
241 | struct adp8870_backlight_platform_data *pdata = |
242 | dev_get_platdata(dev: &client->dev); |
243 | struct adp8870_bl *data = i2c_get_clientdata(client); |
244 | struct adp8870_led *led, *led_dat; |
245 | struct led_info *cur_led; |
246 | int ret, i; |
247 | |
248 | led = devm_kcalloc(dev: &client->dev, n: pdata->num_leds, size: sizeof(*led), |
249 | GFP_KERNEL); |
250 | if (led == NULL) |
251 | return -ENOMEM; |
252 | |
253 | ret = adp8870_write(client, ADP8870_ISCLAW, val: pdata->led_fade_law); |
254 | if (ret) |
255 | return ret; |
256 | |
257 | ret = adp8870_write(client, ADP8870_ISCT1, |
258 | val: (pdata->led_on_time & 0x3) << 6); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | ret = adp8870_write(client, ADP8870_ISCF, |
263 | FADE_VAL(pdata->led_fade_in, pdata->led_fade_out)); |
264 | if (ret) |
265 | return ret; |
266 | |
267 | for (i = 0; i < pdata->num_leds; ++i) { |
268 | cur_led = &pdata->leds[i]; |
269 | led_dat = &led[i]; |
270 | |
271 | led_dat->id = cur_led->flags & ADP8870_FLAG_LED_MASK; |
272 | |
273 | if (led_dat->id > 7 || led_dat->id < 1) { |
274 | dev_err(&client->dev, "Invalid LED ID %d\n" , |
275 | led_dat->id); |
276 | ret = -EINVAL; |
277 | goto err; |
278 | } |
279 | |
280 | if (pdata->bl_led_assign & (1 << (led_dat->id - 1))) { |
281 | dev_err(&client->dev, "LED %d used by Backlight\n" , |
282 | led_dat->id); |
283 | ret = -EBUSY; |
284 | goto err; |
285 | } |
286 | |
287 | led_dat->cdev.name = cur_led->name; |
288 | led_dat->cdev.default_trigger = cur_led->default_trigger; |
289 | led_dat->cdev.brightness_set = adp8870_led_set; |
290 | led_dat->cdev.brightness = LED_OFF; |
291 | led_dat->flags = cur_led->flags >> FLAG_OFFT_SHIFT; |
292 | led_dat->client = client; |
293 | led_dat->new_brightness = LED_OFF; |
294 | INIT_WORK(&led_dat->work, adp8870_led_work); |
295 | |
296 | ret = led_classdev_register(parent: &client->dev, led_cdev: &led_dat->cdev); |
297 | if (ret) { |
298 | dev_err(&client->dev, "failed to register LED %d\n" , |
299 | led_dat->id); |
300 | goto err; |
301 | } |
302 | |
303 | ret = adp8870_led_setup(led: led_dat); |
304 | if (ret) { |
305 | dev_err(&client->dev, "failed to write\n" ); |
306 | i++; |
307 | goto err; |
308 | } |
309 | } |
310 | |
311 | data->led = led; |
312 | |
313 | return 0; |
314 | |
315 | err: |
316 | for (i = i - 1; i >= 0; --i) { |
317 | led_classdev_unregister(led_cdev: &led[i].cdev); |
318 | cancel_work_sync(work: &led[i].work); |
319 | } |
320 | |
321 | return ret; |
322 | } |
323 | |
324 | static int adp8870_led_remove(struct i2c_client *client) |
325 | { |
326 | struct adp8870_backlight_platform_data *pdata = |
327 | dev_get_platdata(dev: &client->dev); |
328 | struct adp8870_bl *data = i2c_get_clientdata(client); |
329 | int i; |
330 | |
331 | for (i = 0; i < pdata->num_leds; i++) { |
332 | led_classdev_unregister(led_cdev: &data->led[i].cdev); |
333 | cancel_work_sync(work: &data->led[i].work); |
334 | } |
335 | |
336 | return 0; |
337 | } |
338 | #else |
339 | static int adp8870_led_probe(struct i2c_client *client) |
340 | { |
341 | return 0; |
342 | } |
343 | |
344 | static int adp8870_led_remove(struct i2c_client *client) |
345 | { |
346 | return 0; |
347 | } |
348 | #endif |
349 | |
350 | static int adp8870_bl_set(struct backlight_device *bl, int brightness) |
351 | { |
352 | struct adp8870_bl *data = bl_get_data(bl_dev: bl); |
353 | struct i2c_client *client = data->client; |
354 | int ret = 0; |
355 | |
356 | if (data->pdata->en_ambl_sens) { |
357 | if ((brightness > 0) && (brightness < ADP8870_MAX_BRIGHTNESS)) { |
358 | /* Disable Ambient Light auto adjust */ |
359 | ret = adp8870_clr_bits(client, ADP8870_MDCR, |
360 | CMP_AUTOEN); |
361 | if (ret) |
362 | return ret; |
363 | ret = adp8870_write(client, ADP8870_BLMX1, val: brightness); |
364 | if (ret) |
365 | return ret; |
366 | } else { |
367 | /* |
368 | * MAX_BRIGHTNESS -> Enable Ambient Light auto adjust |
369 | * restore daylight l1 sysfs brightness |
370 | */ |
371 | ret = adp8870_write(client, ADP8870_BLMX1, |
372 | val: data->cached_daylight_max); |
373 | if (ret) |
374 | return ret; |
375 | |
376 | ret = adp8870_set_bits(client, ADP8870_MDCR, |
377 | CMP_AUTOEN); |
378 | if (ret) |
379 | return ret; |
380 | } |
381 | } else { |
382 | ret = adp8870_write(client, ADP8870_BLMX1, val: brightness); |
383 | if (ret) |
384 | return ret; |
385 | } |
386 | |
387 | if (data->current_brightness && brightness == 0) |
388 | ret = adp8870_set_bits(client, |
389 | ADP8870_MDCR, DIM_EN); |
390 | else if (data->current_brightness == 0 && brightness) |
391 | ret = adp8870_clr_bits(client, |
392 | ADP8870_MDCR, DIM_EN); |
393 | |
394 | if (!ret) |
395 | data->current_brightness = brightness; |
396 | |
397 | return ret; |
398 | } |
399 | |
400 | static int adp8870_bl_update_status(struct backlight_device *bl) |
401 | { |
402 | return adp8870_bl_set(bl, brightness: backlight_get_brightness(bd: bl)); |
403 | } |
404 | |
405 | static int adp8870_bl_get_brightness(struct backlight_device *bl) |
406 | { |
407 | struct adp8870_bl *data = bl_get_data(bl_dev: bl); |
408 | |
409 | return data->current_brightness; |
410 | } |
411 | |
412 | static const struct backlight_ops adp8870_bl_ops = { |
413 | .update_status = adp8870_bl_update_status, |
414 | .get_brightness = adp8870_bl_get_brightness, |
415 | }; |
416 | |
417 | static int adp8870_bl_setup(struct backlight_device *bl) |
418 | { |
419 | struct adp8870_bl *data = bl_get_data(bl_dev: bl); |
420 | struct i2c_client *client = data->client; |
421 | struct adp8870_backlight_platform_data *pdata = data->pdata; |
422 | int ret = 0; |
423 | |
424 | ret = adp8870_write(client, ADP8870_BLSEL, val: ~pdata->bl_led_assign); |
425 | if (ret) |
426 | return ret; |
427 | |
428 | ret = adp8870_write(client, ADP8870_PWMLED, val: pdata->pwm_assign); |
429 | if (ret) |
430 | return ret; |
431 | |
432 | ret = adp8870_write(client, ADP8870_BLMX1, val: pdata->l1_daylight_max); |
433 | if (ret) |
434 | return ret; |
435 | |
436 | ret = adp8870_write(client, ADP8870_BLDM1, val: pdata->l1_daylight_dim); |
437 | if (ret) |
438 | return ret; |
439 | |
440 | if (pdata->en_ambl_sens) { |
441 | data->cached_daylight_max = pdata->l1_daylight_max; |
442 | ret = adp8870_write(client, ADP8870_BLMX2, |
443 | val: pdata->l2_bright_max); |
444 | if (ret) |
445 | return ret; |
446 | ret = adp8870_write(client, ADP8870_BLDM2, |
447 | val: pdata->l2_bright_dim); |
448 | if (ret) |
449 | return ret; |
450 | |
451 | ret = adp8870_write(client, ADP8870_BLMX3, |
452 | val: pdata->l3_office_max); |
453 | if (ret) |
454 | return ret; |
455 | ret = adp8870_write(client, ADP8870_BLDM3, |
456 | val: pdata->l3_office_dim); |
457 | if (ret) |
458 | return ret; |
459 | |
460 | ret = adp8870_write(client, ADP8870_BLMX4, |
461 | val: pdata->l4_indoor_max); |
462 | if (ret) |
463 | return ret; |
464 | |
465 | ret = adp8870_write(client, ADP8870_BLDM4, |
466 | val: pdata->l4_indor_dim); |
467 | if (ret) |
468 | return ret; |
469 | |
470 | ret = adp8870_write(client, ADP8870_BLMX5, |
471 | val: pdata->l5_dark_max); |
472 | if (ret) |
473 | return ret; |
474 | |
475 | ret = adp8870_write(client, ADP8870_BLDM5, |
476 | val: pdata->l5_dark_dim); |
477 | if (ret) |
478 | return ret; |
479 | |
480 | ret = adp8870_write(client, ADP8870_L2TRP, val: pdata->l2_trip); |
481 | if (ret) |
482 | return ret; |
483 | |
484 | ret = adp8870_write(client, ADP8870_L2HYS, val: pdata->l2_hyst); |
485 | if (ret) |
486 | return ret; |
487 | |
488 | ret = adp8870_write(client, ADP8870_L3TRP, val: pdata->l3_trip); |
489 | if (ret) |
490 | return ret; |
491 | |
492 | ret = adp8870_write(client, ADP8870_L3HYS, val: pdata->l3_hyst); |
493 | if (ret) |
494 | return ret; |
495 | |
496 | ret = adp8870_write(client, ADP8870_L4TRP, val: pdata->l4_trip); |
497 | if (ret) |
498 | return ret; |
499 | |
500 | ret = adp8870_write(client, ADP8870_L4HYS, val: pdata->l4_hyst); |
501 | if (ret) |
502 | return ret; |
503 | |
504 | ret = adp8870_write(client, ADP8870_L5TRP, val: pdata->l5_trip); |
505 | if (ret) |
506 | return ret; |
507 | |
508 | ret = adp8870_write(client, ADP8870_L5HYS, val: pdata->l5_hyst); |
509 | if (ret) |
510 | return ret; |
511 | |
512 | ret = adp8870_write(client, ADP8870_ALS1_EN, L5_EN | L4_EN | |
513 | L3_EN | L2_EN); |
514 | if (ret) |
515 | return ret; |
516 | |
517 | ret = adp8870_write(client, ADP8870_CMP_CTL, |
518 | ALS_CMPR_CFG_VAL(pdata->abml_filt)); |
519 | if (ret) |
520 | return ret; |
521 | } |
522 | |
523 | ret = adp8870_write(client, ADP8870_CFGR, |
524 | BL_CFGR_VAL(pdata->bl_fade_law, 0)); |
525 | if (ret) |
526 | return ret; |
527 | |
528 | ret = adp8870_write(client, ADP8870_BLFR, FADE_VAL(pdata->bl_fade_in, |
529 | pdata->bl_fade_out)); |
530 | if (ret) |
531 | return ret; |
532 | /* |
533 | * ADP8870 Rev0 requires GDWN_DIS bit set |
534 | */ |
535 | |
536 | ret = adp8870_set_bits(client, ADP8870_MDCR, BLEN | DIM_EN | NSTBY | |
537 | (data->revid == 0 ? GDWN_DIS : 0)); |
538 | |
539 | return ret; |
540 | } |
541 | |
542 | static ssize_t adp8870_show(struct device *dev, char *buf, int reg) |
543 | { |
544 | struct adp8870_bl *data = dev_get_drvdata(dev); |
545 | int error; |
546 | uint8_t reg_val; |
547 | |
548 | mutex_lock(&data->lock); |
549 | error = adp8870_read(client: data->client, reg, val: ®_val); |
550 | mutex_unlock(lock: &data->lock); |
551 | |
552 | if (error < 0) |
553 | return error; |
554 | |
555 | return sprintf(buf, fmt: "%u\n" , reg_val); |
556 | } |
557 | |
558 | static ssize_t adp8870_store(struct device *dev, const char *buf, |
559 | size_t count, int reg) |
560 | { |
561 | struct adp8870_bl *data = dev_get_drvdata(dev); |
562 | unsigned long val; |
563 | int ret; |
564 | |
565 | ret = kstrtoul(s: buf, base: 10, res: &val); |
566 | if (ret) |
567 | return ret; |
568 | |
569 | mutex_lock(&data->lock); |
570 | adp8870_write(client: data->client, reg, val); |
571 | mutex_unlock(lock: &data->lock); |
572 | |
573 | return count; |
574 | } |
575 | |
576 | static ssize_t adp8870_bl_l5_dark_max_show(struct device *dev, |
577 | struct device_attribute *attr, char *buf) |
578 | { |
579 | return adp8870_show(dev, buf, ADP8870_BLMX5); |
580 | } |
581 | |
582 | static ssize_t adp8870_bl_l5_dark_max_store(struct device *dev, |
583 | struct device_attribute *attr, const char *buf, size_t count) |
584 | { |
585 | return adp8870_store(dev, buf, count, ADP8870_BLMX5); |
586 | } |
587 | static DEVICE_ATTR(l5_dark_max, 0664, adp8870_bl_l5_dark_max_show, |
588 | adp8870_bl_l5_dark_max_store); |
589 | |
590 | |
591 | static ssize_t adp8870_bl_l4_indoor_max_show(struct device *dev, |
592 | struct device_attribute *attr, char *buf) |
593 | { |
594 | return adp8870_show(dev, buf, ADP8870_BLMX4); |
595 | } |
596 | |
597 | static ssize_t adp8870_bl_l4_indoor_max_store(struct device *dev, |
598 | struct device_attribute *attr, const char *buf, size_t count) |
599 | { |
600 | return adp8870_store(dev, buf, count, ADP8870_BLMX4); |
601 | } |
602 | static DEVICE_ATTR(l4_indoor_max, 0664, adp8870_bl_l4_indoor_max_show, |
603 | adp8870_bl_l4_indoor_max_store); |
604 | |
605 | |
606 | static ssize_t adp8870_bl_l3_office_max_show(struct device *dev, |
607 | struct device_attribute *attr, char *buf) |
608 | { |
609 | return adp8870_show(dev, buf, ADP8870_BLMX3); |
610 | } |
611 | |
612 | static ssize_t adp8870_bl_l3_office_max_store(struct device *dev, |
613 | struct device_attribute *attr, const char *buf, size_t count) |
614 | { |
615 | return adp8870_store(dev, buf, count, ADP8870_BLMX3); |
616 | } |
617 | |
618 | static DEVICE_ATTR(l3_office_max, 0664, adp8870_bl_l3_office_max_show, |
619 | adp8870_bl_l3_office_max_store); |
620 | |
621 | static ssize_t adp8870_bl_l2_bright_max_show(struct device *dev, |
622 | struct device_attribute *attr, char *buf) |
623 | { |
624 | return adp8870_show(dev, buf, ADP8870_BLMX2); |
625 | } |
626 | |
627 | static ssize_t adp8870_bl_l2_bright_max_store(struct device *dev, |
628 | struct device_attribute *attr, const char *buf, size_t count) |
629 | { |
630 | return adp8870_store(dev, buf, count, ADP8870_BLMX2); |
631 | } |
632 | static DEVICE_ATTR(l2_bright_max, 0664, adp8870_bl_l2_bright_max_show, |
633 | adp8870_bl_l2_bright_max_store); |
634 | |
635 | static ssize_t adp8870_bl_l1_daylight_max_show(struct device *dev, |
636 | struct device_attribute *attr, char *buf) |
637 | { |
638 | return adp8870_show(dev, buf, ADP8870_BLMX1); |
639 | } |
640 | |
641 | static ssize_t adp8870_bl_l1_daylight_max_store(struct device *dev, |
642 | struct device_attribute *attr, const char *buf, size_t count) |
643 | { |
644 | struct adp8870_bl *data = dev_get_drvdata(dev); |
645 | int ret = kstrtoul(s: buf, base: 10, res: &data->cached_daylight_max); |
646 | |
647 | if (ret) |
648 | return ret; |
649 | |
650 | return adp8870_store(dev, buf, count, ADP8870_BLMX1); |
651 | } |
652 | static DEVICE_ATTR(l1_daylight_max, 0664, adp8870_bl_l1_daylight_max_show, |
653 | adp8870_bl_l1_daylight_max_store); |
654 | |
655 | static ssize_t adp8870_bl_l5_dark_dim_show(struct device *dev, |
656 | struct device_attribute *attr, char *buf) |
657 | { |
658 | return adp8870_show(dev, buf, ADP8870_BLDM5); |
659 | } |
660 | |
661 | static ssize_t adp8870_bl_l5_dark_dim_store(struct device *dev, |
662 | struct device_attribute *attr, |
663 | const char *buf, size_t count) |
664 | { |
665 | return adp8870_store(dev, buf, count, ADP8870_BLDM5); |
666 | } |
667 | static DEVICE_ATTR(l5_dark_dim, 0664, adp8870_bl_l5_dark_dim_show, |
668 | adp8870_bl_l5_dark_dim_store); |
669 | |
670 | static ssize_t adp8870_bl_l4_indoor_dim_show(struct device *dev, |
671 | struct device_attribute *attr, char *buf) |
672 | { |
673 | return adp8870_show(dev, buf, ADP8870_BLDM4); |
674 | } |
675 | |
676 | static ssize_t adp8870_bl_l4_indoor_dim_store(struct device *dev, |
677 | struct device_attribute *attr, |
678 | const char *buf, size_t count) |
679 | { |
680 | return adp8870_store(dev, buf, count, ADP8870_BLDM4); |
681 | } |
682 | static DEVICE_ATTR(l4_indoor_dim, 0664, adp8870_bl_l4_indoor_dim_show, |
683 | adp8870_bl_l4_indoor_dim_store); |
684 | |
685 | |
686 | static ssize_t adp8870_bl_l3_office_dim_show(struct device *dev, |
687 | struct device_attribute *attr, char *buf) |
688 | { |
689 | return adp8870_show(dev, buf, ADP8870_BLDM3); |
690 | } |
691 | |
692 | static ssize_t adp8870_bl_l3_office_dim_store(struct device *dev, |
693 | struct device_attribute *attr, |
694 | const char *buf, size_t count) |
695 | { |
696 | return adp8870_store(dev, buf, count, ADP8870_BLDM3); |
697 | } |
698 | static DEVICE_ATTR(l3_office_dim, 0664, adp8870_bl_l3_office_dim_show, |
699 | adp8870_bl_l3_office_dim_store); |
700 | |
701 | static ssize_t adp8870_bl_l2_bright_dim_show(struct device *dev, |
702 | struct device_attribute *attr, char *buf) |
703 | { |
704 | return adp8870_show(dev, buf, ADP8870_BLDM2); |
705 | } |
706 | |
707 | static ssize_t adp8870_bl_l2_bright_dim_store(struct device *dev, |
708 | struct device_attribute *attr, |
709 | const char *buf, size_t count) |
710 | { |
711 | return adp8870_store(dev, buf, count, ADP8870_BLDM2); |
712 | } |
713 | static DEVICE_ATTR(l2_bright_dim, 0664, adp8870_bl_l2_bright_dim_show, |
714 | adp8870_bl_l2_bright_dim_store); |
715 | |
716 | static ssize_t adp8870_bl_l1_daylight_dim_show(struct device *dev, |
717 | struct device_attribute *attr, char *buf) |
718 | { |
719 | return adp8870_show(dev, buf, ADP8870_BLDM1); |
720 | } |
721 | |
722 | static ssize_t adp8870_bl_l1_daylight_dim_store(struct device *dev, |
723 | struct device_attribute *attr, |
724 | const char *buf, size_t count) |
725 | { |
726 | return adp8870_store(dev, buf, count, ADP8870_BLDM1); |
727 | } |
728 | static DEVICE_ATTR(l1_daylight_dim, 0664, adp8870_bl_l1_daylight_dim_show, |
729 | adp8870_bl_l1_daylight_dim_store); |
730 | |
731 | #ifdef ADP8870_EXT_FEATURES |
732 | static ssize_t adp8870_bl_ambient_light_level_show(struct device *dev, |
733 | struct device_attribute *attr, char *buf) |
734 | { |
735 | struct adp8870_bl *data = dev_get_drvdata(dev); |
736 | int error; |
737 | uint8_t reg_val; |
738 | uint16_t ret_val; |
739 | |
740 | mutex_lock(&data->lock); |
741 | error = adp8870_read(client: data->client, ADP8870_PH1LEVL, val: ®_val); |
742 | if (error < 0) { |
743 | mutex_unlock(lock: &data->lock); |
744 | return error; |
745 | } |
746 | ret_val = reg_val; |
747 | error = adp8870_read(client: data->client, ADP8870_PH1LEVH, val: ®_val); |
748 | mutex_unlock(lock: &data->lock); |
749 | |
750 | if (error < 0) |
751 | return error; |
752 | |
753 | /* Return 13-bit conversion value for the first light sensor */ |
754 | ret_val += (reg_val & 0x1F) << 8; |
755 | |
756 | return sprintf(buf, fmt: "%u\n" , ret_val); |
757 | } |
758 | static DEVICE_ATTR(ambient_light_level, 0444, |
759 | adp8870_bl_ambient_light_level_show, NULL); |
760 | |
761 | static ssize_t adp8870_bl_ambient_light_zone_show(struct device *dev, |
762 | struct device_attribute *attr, char *buf) |
763 | { |
764 | struct adp8870_bl *data = dev_get_drvdata(dev); |
765 | int error; |
766 | uint8_t reg_val; |
767 | |
768 | mutex_lock(&data->lock); |
769 | error = adp8870_read(client: data->client, ADP8870_CFGR, val: ®_val); |
770 | mutex_unlock(lock: &data->lock); |
771 | |
772 | if (error < 0) |
773 | return error; |
774 | |
775 | return sprintf(buf, fmt: "%u\n" , |
776 | ((reg_val >> CFGR_BLV_SHIFT) & CFGR_BLV_MASK) + 1); |
777 | } |
778 | |
779 | static ssize_t adp8870_bl_ambient_light_zone_store(struct device *dev, |
780 | struct device_attribute *attr, |
781 | const char *buf, size_t count) |
782 | { |
783 | struct adp8870_bl *data = dev_get_drvdata(dev); |
784 | unsigned long val; |
785 | uint8_t reg_val; |
786 | int ret; |
787 | |
788 | ret = kstrtoul(s: buf, base: 10, res: &val); |
789 | if (ret) |
790 | return ret; |
791 | |
792 | if (val == 0) { |
793 | /* Enable automatic ambient light sensing */ |
794 | adp8870_set_bits(client: data->client, ADP8870_MDCR, CMP_AUTOEN); |
795 | } else if ((val > 0) && (val < 6)) { |
796 | /* Disable automatic ambient light sensing */ |
797 | adp8870_clr_bits(client: data->client, ADP8870_MDCR, CMP_AUTOEN); |
798 | |
799 | /* Set user supplied ambient light zone */ |
800 | mutex_lock(&data->lock); |
801 | ret = adp8870_read(client: data->client, ADP8870_CFGR, val: ®_val); |
802 | if (!ret) { |
803 | reg_val &= ~(CFGR_BLV_MASK << CFGR_BLV_SHIFT); |
804 | reg_val |= (val - 1) << CFGR_BLV_SHIFT; |
805 | adp8870_write(client: data->client, ADP8870_CFGR, val: reg_val); |
806 | } |
807 | mutex_unlock(lock: &data->lock); |
808 | } |
809 | |
810 | return count; |
811 | } |
812 | static DEVICE_ATTR(ambient_light_zone, 0664, |
813 | adp8870_bl_ambient_light_zone_show, |
814 | adp8870_bl_ambient_light_zone_store); |
815 | #endif |
816 | |
817 | static struct attribute *adp8870_bl_attributes[] = { |
818 | &dev_attr_l5_dark_max.attr, |
819 | &dev_attr_l5_dark_dim.attr, |
820 | &dev_attr_l4_indoor_max.attr, |
821 | &dev_attr_l4_indoor_dim.attr, |
822 | &dev_attr_l3_office_max.attr, |
823 | &dev_attr_l3_office_dim.attr, |
824 | &dev_attr_l2_bright_max.attr, |
825 | &dev_attr_l2_bright_dim.attr, |
826 | &dev_attr_l1_daylight_max.attr, |
827 | &dev_attr_l1_daylight_dim.attr, |
828 | #ifdef ADP8870_EXT_FEATURES |
829 | &dev_attr_ambient_light_level.attr, |
830 | &dev_attr_ambient_light_zone.attr, |
831 | #endif |
832 | NULL |
833 | }; |
834 | |
835 | static const struct attribute_group adp8870_bl_attr_group = { |
836 | .attrs = adp8870_bl_attributes, |
837 | }; |
838 | |
839 | static int adp8870_probe(struct i2c_client *client) |
840 | { |
841 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
842 | struct backlight_properties props; |
843 | struct backlight_device *bl; |
844 | struct adp8870_bl *data; |
845 | struct adp8870_backlight_platform_data *pdata = |
846 | dev_get_platdata(dev: &client->dev); |
847 | uint8_t reg_val; |
848 | int ret; |
849 | |
850 | if (!i2c_check_functionality(adap: client->adapter, |
851 | I2C_FUNC_SMBUS_BYTE_DATA)) { |
852 | dev_err(&client->dev, "SMBUS Byte Data not Supported\n" ); |
853 | return -EIO; |
854 | } |
855 | |
856 | if (!pdata) { |
857 | dev_err(&client->dev, "no platform data?\n" ); |
858 | return -EINVAL; |
859 | } |
860 | |
861 | ret = adp8870_read(client, ADP8870_MFDVID, val: ®_val); |
862 | if (ret < 0) |
863 | return -EIO; |
864 | |
865 | if (ADP8870_MANID(reg_val) != ADP8870_MANUFID) { |
866 | dev_err(&client->dev, "failed to probe\n" ); |
867 | return -ENODEV; |
868 | } |
869 | |
870 | data = devm_kzalloc(dev: &client->dev, size: sizeof(*data), GFP_KERNEL); |
871 | if (data == NULL) |
872 | return -ENOMEM; |
873 | |
874 | data->revid = ADP8870_DEVID(reg_val); |
875 | data->client = client; |
876 | data->pdata = pdata; |
877 | data->id = id->driver_data; |
878 | data->current_brightness = 0; |
879 | i2c_set_clientdata(client, data); |
880 | |
881 | mutex_init(&data->lock); |
882 | |
883 | memset(&props, 0, sizeof(props)); |
884 | props.type = BACKLIGHT_RAW; |
885 | props.max_brightness = props.brightness = ADP8870_MAX_BRIGHTNESS; |
886 | bl = devm_backlight_device_register(dev: &client->dev, |
887 | name: dev_driver_string(dev: &client->dev), |
888 | parent: &client->dev, devdata: data, ops: &adp8870_bl_ops, props: &props); |
889 | if (IS_ERR(ptr: bl)) { |
890 | dev_err(&client->dev, "failed to register backlight\n" ); |
891 | return PTR_ERR(ptr: bl); |
892 | } |
893 | |
894 | data->bl = bl; |
895 | |
896 | if (pdata->en_ambl_sens) { |
897 | ret = sysfs_create_group(kobj: &bl->dev.kobj, |
898 | grp: &adp8870_bl_attr_group); |
899 | if (ret) { |
900 | dev_err(&client->dev, "failed to register sysfs\n" ); |
901 | return ret; |
902 | } |
903 | } |
904 | |
905 | ret = adp8870_bl_setup(bl); |
906 | if (ret) { |
907 | ret = -EIO; |
908 | goto out; |
909 | } |
910 | |
911 | backlight_update_status(bd: bl); |
912 | |
913 | dev_info(&client->dev, "Rev.%d Backlight\n" , data->revid); |
914 | |
915 | if (pdata->num_leds) |
916 | adp8870_led_probe(client); |
917 | |
918 | return 0; |
919 | |
920 | out: |
921 | if (data->pdata->en_ambl_sens) |
922 | sysfs_remove_group(kobj: &data->bl->dev.kobj, |
923 | grp: &adp8870_bl_attr_group); |
924 | |
925 | return ret; |
926 | } |
927 | |
928 | static void adp8870_remove(struct i2c_client *client) |
929 | { |
930 | struct adp8870_bl *data = i2c_get_clientdata(client); |
931 | |
932 | adp8870_clr_bits(client, ADP8870_MDCR, NSTBY); |
933 | |
934 | if (data->led) |
935 | adp8870_led_remove(client); |
936 | |
937 | if (data->pdata->en_ambl_sens) |
938 | sysfs_remove_group(kobj: &data->bl->dev.kobj, |
939 | grp: &adp8870_bl_attr_group); |
940 | } |
941 | |
942 | #ifdef CONFIG_PM_SLEEP |
943 | static int adp8870_i2c_suspend(struct device *dev) |
944 | { |
945 | struct i2c_client *client = to_i2c_client(dev); |
946 | |
947 | adp8870_clr_bits(client, ADP8870_MDCR, NSTBY); |
948 | |
949 | return 0; |
950 | } |
951 | |
952 | static int adp8870_i2c_resume(struct device *dev) |
953 | { |
954 | struct i2c_client *client = to_i2c_client(dev); |
955 | |
956 | adp8870_set_bits(client, ADP8870_MDCR, NSTBY | BLEN); |
957 | |
958 | return 0; |
959 | } |
960 | #endif |
961 | |
962 | static SIMPLE_DEV_PM_OPS(adp8870_i2c_pm_ops, adp8870_i2c_suspend, |
963 | adp8870_i2c_resume); |
964 | |
965 | static const struct i2c_device_id adp8870_id[] = { |
966 | { "adp8870" , 0 }, |
967 | { } |
968 | }; |
969 | MODULE_DEVICE_TABLE(i2c, adp8870_id); |
970 | |
971 | static struct i2c_driver adp8870_driver = { |
972 | .driver = { |
973 | .name = KBUILD_MODNAME, |
974 | .pm = &adp8870_i2c_pm_ops, |
975 | }, |
976 | .probe = adp8870_probe, |
977 | .remove = adp8870_remove, |
978 | .id_table = adp8870_id, |
979 | }; |
980 | |
981 | module_i2c_driver(adp8870_driver); |
982 | |
983 | MODULE_LICENSE("GPL v2" ); |
984 | MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>" ); |
985 | MODULE_DESCRIPTION("ADP8870 Backlight driver" ); |
986 | |