1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Backlight driver for Analog Devices ADP8860 Backlight Devices |
4 | * |
5 | * Copyright 2009-2010 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/slab.h> |
18 | #include <linux/workqueue.h> |
19 | |
20 | #include <linux/platform_data/adp8860.h> |
21 | #define ADP8860_EXT_FEATURES |
22 | #define ADP8860_USE_LEDS |
23 | |
24 | #define ADP8860_MFDVID 0x00 /* Manufacturer and device ID */ |
25 | #define ADP8860_MDCR 0x01 /* Device mode and status */ |
26 | #define ADP8860_MDCR2 0x02 /* Device mode and Status Register 2 */ |
27 | #define ADP8860_INTR_EN 0x03 /* Interrupts enable */ |
28 | #define ADP8860_CFGR 0x04 /* Configuration register */ |
29 | #define ADP8860_BLSEN 0x05 /* Sink enable backlight or independent */ |
30 | #define ADP8860_BLOFF 0x06 /* Backlight off timeout */ |
31 | #define ADP8860_BLDIM 0x07 /* Backlight dim timeout */ |
32 | #define ADP8860_BLFR 0x08 /* Backlight fade in and out rates */ |
33 | #define ADP8860_BLMX1 0x09 /* Backlight (Brightness Level 1-daylight) maximum current */ |
34 | #define ADP8860_BLDM1 0x0A /* Backlight (Brightness Level 1-daylight) dim current */ |
35 | #define ADP8860_BLMX2 0x0B /* Backlight (Brightness Level 2-office) maximum current */ |
36 | #define ADP8860_BLDM2 0x0C /* Backlight (Brightness Level 2-office) dim current */ |
37 | #define ADP8860_BLMX3 0x0D /* Backlight (Brightness Level 3-dark) maximum current */ |
38 | #define ADP8860_BLDM3 0x0E /* Backlight (Brightness Level 3-dark) dim current */ |
39 | #define ADP8860_ISCFR 0x0F /* Independent sink current fade control register */ |
40 | #define ADP8860_ISCC 0x10 /* Independent sink current control register */ |
41 | #define ADP8860_ISCT1 0x11 /* Independent Sink Current Timer Register LED[7:5] */ |
42 | #define ADP8860_ISCT2 0x12 /* Independent Sink Current Timer Register LED[4:1] */ |
43 | #define ADP8860_ISCF 0x13 /* Independent sink current fade register */ |
44 | #define ADP8860_ISC7 0x14 /* Independent Sink Current LED7 */ |
45 | #define ADP8860_ISC6 0x15 /* Independent Sink Current LED6 */ |
46 | #define ADP8860_ISC5 0x16 /* Independent Sink Current LED5 */ |
47 | #define ADP8860_ISC4 0x17 /* Independent Sink Current LED4 */ |
48 | #define ADP8860_ISC3 0x18 /* Independent Sink Current LED3 */ |
49 | #define ADP8860_ISC2 0x19 /* Independent Sink Current LED2 */ |
50 | #define ADP8860_ISC1 0x1A /* Independent Sink Current LED1 */ |
51 | #define ADP8860_CCFG 0x1B /* Comparator configuration */ |
52 | #define ADP8860_CCFG2 0x1C /* Second comparator configuration */ |
53 | #define ADP8860_L2_TRP 0x1D /* L2 comparator reference */ |
54 | #define ADP8860_L2_HYS 0x1E /* L2 hysteresis */ |
55 | #define ADP8860_L3_TRP 0x1F /* L3 comparator reference */ |
56 | #define ADP8860_L3_HYS 0x20 /* L3 hysteresis */ |
57 | #define ADP8860_PH1LEVL 0x21 /* First phototransistor ambient light level-low byte register */ |
58 | #define ADP8860_PH1LEVH 0x22 /* First phototransistor ambient light level-high byte register */ |
59 | #define ADP8860_PH2LEVL 0x23 /* Second phototransistor ambient light level-low byte register */ |
60 | #define ADP8860_PH2LEVH 0x24 /* Second phototransistor ambient light level-high byte register */ |
61 | |
62 | #define ADP8860_MANUFID 0x0 /* Analog Devices ADP8860 Manufacturer ID */ |
63 | #define ADP8861_MANUFID 0x4 /* Analog Devices ADP8861 Manufacturer ID */ |
64 | #define ADP8863_MANUFID 0x2 /* Analog Devices ADP8863 Manufacturer ID */ |
65 | |
66 | #define ADP8860_DEVID(x) ((x) & 0xF) |
67 | #define ADP8860_MANID(x) ((x) >> 4) |
68 | |
69 | /* MDCR Device mode and status */ |
70 | #define INT_CFG (1 << 6) |
71 | #define NSTBY (1 << 5) |
72 | #define DIM_EN (1 << 4) |
73 | #define GDWN_DIS (1 << 3) |
74 | #define SIS_EN (1 << 2) |
75 | #define CMP_AUTOEN (1 << 1) |
76 | #define BLEN (1 << 0) |
77 | |
78 | /* ADP8860_CCFG Main ALS comparator level enable */ |
79 | #define L3_EN (1 << 1) |
80 | #define L2_EN (1 << 0) |
81 | |
82 | #define CFGR_BLV_SHIFT 3 |
83 | #define CFGR_BLV_MASK 0x3 |
84 | #define ADP8860_FLAG_LED_MASK 0xFF |
85 | |
86 | #define FADE_VAL(in, out) ((0xF & (in)) | ((0xF & (out)) << 4)) |
87 | #define BL_CFGR_VAL(law, blv) ((((blv) & CFGR_BLV_MASK) << CFGR_BLV_SHIFT) | ((0x3 & (law)) << 1)) |
88 | #define ALS_CCFG_VAL(filt) ((0x7 & filt) << 5) |
89 | |
90 | enum { |
91 | adp8860, |
92 | adp8861, |
93 | adp8863 |
94 | }; |
95 | |
96 | struct adp8860_led { |
97 | struct led_classdev cdev; |
98 | struct work_struct work; |
99 | struct i2c_client *client; |
100 | enum led_brightness new_brightness; |
101 | int id; |
102 | int flags; |
103 | }; |
104 | |
105 | struct adp8860_bl { |
106 | struct i2c_client *client; |
107 | struct backlight_device *bl; |
108 | struct adp8860_led *led; |
109 | struct adp8860_backlight_platform_data *pdata; |
110 | struct mutex lock; |
111 | unsigned long cached_daylight_max; |
112 | int id; |
113 | int revid; |
114 | int current_brightness; |
115 | unsigned en_ambl_sens:1; |
116 | unsigned gdwn_dis:1; |
117 | }; |
118 | |
119 | static int adp8860_read(struct i2c_client *client, int reg, uint8_t *val) |
120 | { |
121 | int ret; |
122 | |
123 | ret = i2c_smbus_read_byte_data(client, command: reg); |
124 | if (ret < 0) { |
125 | dev_err(&client->dev, "failed reading at 0x%02x\n" , reg); |
126 | return ret; |
127 | } |
128 | |
129 | *val = (uint8_t)ret; |
130 | return 0; |
131 | } |
132 | |
133 | static int adp8860_write(struct i2c_client *client, u8 reg, u8 val) |
134 | { |
135 | return i2c_smbus_write_byte_data(client, command: reg, value: val); |
136 | } |
137 | |
138 | static int adp8860_set_bits(struct i2c_client *client, int reg, uint8_t bit_mask) |
139 | { |
140 | struct adp8860_bl *data = i2c_get_clientdata(client); |
141 | uint8_t reg_val; |
142 | int ret; |
143 | |
144 | mutex_lock(&data->lock); |
145 | |
146 | ret = adp8860_read(client, reg, val: ®_val); |
147 | |
148 | if (!ret && ((reg_val & bit_mask) != bit_mask)) { |
149 | reg_val |= bit_mask; |
150 | ret = adp8860_write(client, reg, val: reg_val); |
151 | } |
152 | |
153 | mutex_unlock(lock: &data->lock); |
154 | return ret; |
155 | } |
156 | |
157 | static int adp8860_clr_bits(struct i2c_client *client, int reg, uint8_t bit_mask) |
158 | { |
159 | struct adp8860_bl *data = i2c_get_clientdata(client); |
160 | uint8_t reg_val; |
161 | int ret; |
162 | |
163 | mutex_lock(&data->lock); |
164 | |
165 | ret = adp8860_read(client, reg, val: ®_val); |
166 | |
167 | if (!ret && (reg_val & bit_mask)) { |
168 | reg_val &= ~bit_mask; |
169 | ret = adp8860_write(client, reg, val: reg_val); |
170 | } |
171 | |
172 | mutex_unlock(lock: &data->lock); |
173 | return ret; |
174 | } |
175 | |
176 | /* |
177 | * Independent sink / LED |
178 | */ |
179 | #if defined(ADP8860_USE_LEDS) |
180 | static void adp8860_led_work(struct work_struct *work) |
181 | { |
182 | struct adp8860_led *led = container_of(work, struct adp8860_led, work); |
183 | |
184 | adp8860_write(client: led->client, ADP8860_ISC1 - led->id + 1, |
185 | val: led->new_brightness >> 1); |
186 | } |
187 | |
188 | static void adp8860_led_set(struct led_classdev *led_cdev, |
189 | enum led_brightness value) |
190 | { |
191 | struct adp8860_led *led; |
192 | |
193 | led = container_of(led_cdev, struct adp8860_led, cdev); |
194 | led->new_brightness = value; |
195 | schedule_work(work: &led->work); |
196 | } |
197 | |
198 | static int adp8860_led_setup(struct adp8860_led *led) |
199 | { |
200 | struct i2c_client *client = led->client; |
201 | int ret = 0; |
202 | |
203 | ret = adp8860_write(client, ADP8860_ISC1 - led->id + 1, val: 0); |
204 | ret |= adp8860_set_bits(client, ADP8860_ISCC, bit_mask: 1 << (led->id - 1)); |
205 | |
206 | if (led->id > 4) |
207 | ret |= adp8860_set_bits(client, ADP8860_ISCT1, |
208 | bit_mask: (led->flags & 0x3) << ((led->id - 5) * 2)); |
209 | else |
210 | ret |= adp8860_set_bits(client, ADP8860_ISCT2, |
211 | bit_mask: (led->flags & 0x3) << ((led->id - 1) * 2)); |
212 | |
213 | return ret; |
214 | } |
215 | |
216 | static int adp8860_led_probe(struct i2c_client *client) |
217 | { |
218 | struct adp8860_backlight_platform_data *pdata = |
219 | dev_get_platdata(dev: &client->dev); |
220 | struct adp8860_bl *data = i2c_get_clientdata(client); |
221 | struct adp8860_led *led, *led_dat; |
222 | struct led_info *cur_led; |
223 | int ret, i; |
224 | |
225 | led = devm_kcalloc(dev: &client->dev, n: pdata->num_leds, size: sizeof(*led), |
226 | GFP_KERNEL); |
227 | if (led == NULL) |
228 | return -ENOMEM; |
229 | |
230 | ret = adp8860_write(client, ADP8860_ISCFR, val: pdata->led_fade_law); |
231 | ret = adp8860_write(client, ADP8860_ISCT1, |
232 | val: (pdata->led_on_time & 0x3) << 6); |
233 | ret |= adp8860_write(client, ADP8860_ISCF, |
234 | FADE_VAL(pdata->led_fade_in, pdata->led_fade_out)); |
235 | |
236 | if (ret) { |
237 | dev_err(&client->dev, "failed to write\n" ); |
238 | return ret; |
239 | } |
240 | |
241 | for (i = 0; i < pdata->num_leds; ++i) { |
242 | cur_led = &pdata->leds[i]; |
243 | led_dat = &led[i]; |
244 | |
245 | led_dat->id = cur_led->flags & ADP8860_FLAG_LED_MASK; |
246 | |
247 | if (led_dat->id > 7 || led_dat->id < 1) { |
248 | dev_err(&client->dev, "Invalid LED ID %d\n" , |
249 | led_dat->id); |
250 | ret = -EINVAL; |
251 | goto err; |
252 | } |
253 | |
254 | if (pdata->bl_led_assign & (1 << (led_dat->id - 1))) { |
255 | dev_err(&client->dev, "LED %d used by Backlight\n" , |
256 | led_dat->id); |
257 | ret = -EBUSY; |
258 | goto err; |
259 | } |
260 | |
261 | led_dat->cdev.name = cur_led->name; |
262 | led_dat->cdev.default_trigger = cur_led->default_trigger; |
263 | led_dat->cdev.brightness_set = adp8860_led_set; |
264 | led_dat->cdev.brightness = LED_OFF; |
265 | led_dat->flags = cur_led->flags >> FLAG_OFFT_SHIFT; |
266 | led_dat->client = client; |
267 | led_dat->new_brightness = LED_OFF; |
268 | INIT_WORK(&led_dat->work, adp8860_led_work); |
269 | |
270 | ret = led_classdev_register(parent: &client->dev, led_cdev: &led_dat->cdev); |
271 | if (ret) { |
272 | dev_err(&client->dev, "failed to register LED %d\n" , |
273 | led_dat->id); |
274 | goto err; |
275 | } |
276 | |
277 | ret = adp8860_led_setup(led: led_dat); |
278 | if (ret) { |
279 | dev_err(&client->dev, "failed to write\n" ); |
280 | i++; |
281 | goto err; |
282 | } |
283 | } |
284 | |
285 | data->led = led; |
286 | |
287 | return 0; |
288 | |
289 | err: |
290 | for (i = i - 1; i >= 0; --i) { |
291 | led_classdev_unregister(led_cdev: &led[i].cdev); |
292 | cancel_work_sync(work: &led[i].work); |
293 | } |
294 | |
295 | return ret; |
296 | } |
297 | |
298 | static int adp8860_led_remove(struct i2c_client *client) |
299 | { |
300 | struct adp8860_backlight_platform_data *pdata = |
301 | dev_get_platdata(dev: &client->dev); |
302 | struct adp8860_bl *data = i2c_get_clientdata(client); |
303 | int i; |
304 | |
305 | for (i = 0; i < pdata->num_leds; i++) { |
306 | led_classdev_unregister(led_cdev: &data->led[i].cdev); |
307 | cancel_work_sync(work: &data->led[i].work); |
308 | } |
309 | |
310 | return 0; |
311 | } |
312 | #else |
313 | static int adp8860_led_probe(struct i2c_client *client) |
314 | { |
315 | return 0; |
316 | } |
317 | |
318 | static int adp8860_led_remove(struct i2c_client *client) |
319 | { |
320 | return 0; |
321 | } |
322 | #endif |
323 | |
324 | static int adp8860_bl_set(struct backlight_device *bl, int brightness) |
325 | { |
326 | struct adp8860_bl *data = bl_get_data(bl_dev: bl); |
327 | struct i2c_client *client = data->client; |
328 | int ret = 0; |
329 | |
330 | if (data->en_ambl_sens) { |
331 | if ((brightness > 0) && (brightness < ADP8860_MAX_BRIGHTNESS)) { |
332 | /* Disable Ambient Light auto adjust */ |
333 | ret |= adp8860_clr_bits(client, ADP8860_MDCR, |
334 | CMP_AUTOEN); |
335 | ret |= adp8860_write(client, ADP8860_BLMX1, val: brightness); |
336 | } else { |
337 | /* |
338 | * MAX_BRIGHTNESS -> Enable Ambient Light auto adjust |
339 | * restore daylight l1 sysfs brightness |
340 | */ |
341 | ret |= adp8860_write(client, ADP8860_BLMX1, |
342 | val: data->cached_daylight_max); |
343 | ret |= adp8860_set_bits(client, ADP8860_MDCR, |
344 | CMP_AUTOEN); |
345 | } |
346 | } else |
347 | ret |= adp8860_write(client, ADP8860_BLMX1, val: brightness); |
348 | |
349 | if (data->current_brightness && brightness == 0) |
350 | ret |= adp8860_set_bits(client, |
351 | ADP8860_MDCR, DIM_EN); |
352 | else if (data->current_brightness == 0 && brightness) |
353 | ret |= adp8860_clr_bits(client, |
354 | ADP8860_MDCR, DIM_EN); |
355 | |
356 | if (!ret) |
357 | data->current_brightness = brightness; |
358 | |
359 | return ret; |
360 | } |
361 | |
362 | static int adp8860_bl_update_status(struct backlight_device *bl) |
363 | { |
364 | return adp8860_bl_set(bl, brightness: backlight_get_brightness(bd: bl)); |
365 | } |
366 | |
367 | static int adp8860_bl_get_brightness(struct backlight_device *bl) |
368 | { |
369 | struct adp8860_bl *data = bl_get_data(bl_dev: bl); |
370 | |
371 | return data->current_brightness; |
372 | } |
373 | |
374 | static const struct backlight_ops adp8860_bl_ops = { |
375 | .update_status = adp8860_bl_update_status, |
376 | .get_brightness = adp8860_bl_get_brightness, |
377 | }; |
378 | |
379 | static int adp8860_bl_setup(struct backlight_device *bl) |
380 | { |
381 | struct adp8860_bl *data = bl_get_data(bl_dev: bl); |
382 | struct i2c_client *client = data->client; |
383 | struct adp8860_backlight_platform_data *pdata = data->pdata; |
384 | int ret = 0; |
385 | |
386 | ret |= adp8860_write(client, ADP8860_BLSEN, val: ~pdata->bl_led_assign); |
387 | ret |= adp8860_write(client, ADP8860_BLMX1, val: pdata->l1_daylight_max); |
388 | ret |= adp8860_write(client, ADP8860_BLDM1, val: pdata->l1_daylight_dim); |
389 | |
390 | if (data->en_ambl_sens) { |
391 | data->cached_daylight_max = pdata->l1_daylight_max; |
392 | ret |= adp8860_write(client, ADP8860_BLMX2, |
393 | val: pdata->l2_office_max); |
394 | ret |= adp8860_write(client, ADP8860_BLDM2, |
395 | val: pdata->l2_office_dim); |
396 | ret |= adp8860_write(client, ADP8860_BLMX3, |
397 | val: pdata->l3_dark_max); |
398 | ret |= adp8860_write(client, ADP8860_BLDM3, |
399 | val: pdata->l3_dark_dim); |
400 | |
401 | ret |= adp8860_write(client, ADP8860_L2_TRP, val: pdata->l2_trip); |
402 | ret |= adp8860_write(client, ADP8860_L2_HYS, val: pdata->l2_hyst); |
403 | ret |= adp8860_write(client, ADP8860_L3_TRP, val: pdata->l3_trip); |
404 | ret |= adp8860_write(client, ADP8860_L3_HYS, val: pdata->l3_hyst); |
405 | ret |= adp8860_write(client, ADP8860_CCFG, L2_EN | L3_EN | |
406 | ALS_CCFG_VAL(pdata->abml_filt)); |
407 | } |
408 | |
409 | ret |= adp8860_write(client, ADP8860_CFGR, |
410 | BL_CFGR_VAL(pdata->bl_fade_law, 0)); |
411 | |
412 | ret |= adp8860_write(client, ADP8860_BLFR, FADE_VAL(pdata->bl_fade_in, |
413 | pdata->bl_fade_out)); |
414 | |
415 | ret |= adp8860_set_bits(client, ADP8860_MDCR, BLEN | DIM_EN | NSTBY | |
416 | (data->gdwn_dis ? GDWN_DIS : 0)); |
417 | |
418 | return ret; |
419 | } |
420 | |
421 | static ssize_t adp8860_show(struct device *dev, char *buf, int reg) |
422 | { |
423 | struct adp8860_bl *data = dev_get_drvdata(dev); |
424 | int error; |
425 | uint8_t reg_val; |
426 | |
427 | mutex_lock(&data->lock); |
428 | error = adp8860_read(client: data->client, reg, val: ®_val); |
429 | mutex_unlock(lock: &data->lock); |
430 | |
431 | if (error < 0) |
432 | return error; |
433 | |
434 | return sprintf(buf, fmt: "%u\n" , reg_val); |
435 | } |
436 | |
437 | static ssize_t adp8860_store(struct device *dev, const char *buf, |
438 | size_t count, int reg) |
439 | { |
440 | struct adp8860_bl *data = dev_get_drvdata(dev); |
441 | unsigned long val; |
442 | int ret; |
443 | |
444 | ret = kstrtoul(s: buf, base: 10, res: &val); |
445 | if (ret) |
446 | return ret; |
447 | |
448 | mutex_lock(&data->lock); |
449 | adp8860_write(client: data->client, reg, val); |
450 | mutex_unlock(lock: &data->lock); |
451 | |
452 | return count; |
453 | } |
454 | |
455 | static ssize_t adp8860_bl_l3_dark_max_show(struct device *dev, |
456 | struct device_attribute *attr, char *buf) |
457 | { |
458 | return adp8860_show(dev, buf, ADP8860_BLMX3); |
459 | } |
460 | |
461 | static ssize_t adp8860_bl_l3_dark_max_store(struct device *dev, |
462 | struct device_attribute *attr, const char *buf, size_t count) |
463 | { |
464 | return adp8860_store(dev, buf, count, ADP8860_BLMX3); |
465 | } |
466 | |
467 | static DEVICE_ATTR(l3_dark_max, 0664, adp8860_bl_l3_dark_max_show, |
468 | adp8860_bl_l3_dark_max_store); |
469 | |
470 | static ssize_t adp8860_bl_l2_office_max_show(struct device *dev, |
471 | struct device_attribute *attr, char *buf) |
472 | { |
473 | return adp8860_show(dev, buf, ADP8860_BLMX2); |
474 | } |
475 | |
476 | static ssize_t adp8860_bl_l2_office_max_store(struct device *dev, |
477 | struct device_attribute *attr, const char *buf, size_t count) |
478 | { |
479 | return adp8860_store(dev, buf, count, ADP8860_BLMX2); |
480 | } |
481 | static DEVICE_ATTR(l2_office_max, 0664, adp8860_bl_l2_office_max_show, |
482 | adp8860_bl_l2_office_max_store); |
483 | |
484 | static ssize_t adp8860_bl_l1_daylight_max_show(struct device *dev, |
485 | struct device_attribute *attr, char *buf) |
486 | { |
487 | return adp8860_show(dev, buf, ADP8860_BLMX1); |
488 | } |
489 | |
490 | static ssize_t adp8860_bl_l1_daylight_max_store(struct device *dev, |
491 | struct device_attribute *attr, const char *buf, size_t count) |
492 | { |
493 | struct adp8860_bl *data = dev_get_drvdata(dev); |
494 | int ret = kstrtoul(s: buf, base: 10, res: &data->cached_daylight_max); |
495 | |
496 | if (ret) |
497 | return ret; |
498 | |
499 | return adp8860_store(dev, buf, count, ADP8860_BLMX1); |
500 | } |
501 | static DEVICE_ATTR(l1_daylight_max, 0664, adp8860_bl_l1_daylight_max_show, |
502 | adp8860_bl_l1_daylight_max_store); |
503 | |
504 | static ssize_t adp8860_bl_l3_dark_dim_show(struct device *dev, |
505 | struct device_attribute *attr, char *buf) |
506 | { |
507 | return adp8860_show(dev, buf, ADP8860_BLDM3); |
508 | } |
509 | |
510 | static ssize_t adp8860_bl_l3_dark_dim_store(struct device *dev, |
511 | struct device_attribute *attr, |
512 | const char *buf, size_t count) |
513 | { |
514 | return adp8860_store(dev, buf, count, ADP8860_BLDM3); |
515 | } |
516 | static DEVICE_ATTR(l3_dark_dim, 0664, adp8860_bl_l3_dark_dim_show, |
517 | adp8860_bl_l3_dark_dim_store); |
518 | |
519 | static ssize_t adp8860_bl_l2_office_dim_show(struct device *dev, |
520 | struct device_attribute *attr, char *buf) |
521 | { |
522 | return adp8860_show(dev, buf, ADP8860_BLDM2); |
523 | } |
524 | |
525 | static ssize_t adp8860_bl_l2_office_dim_store(struct device *dev, |
526 | struct device_attribute *attr, |
527 | const char *buf, size_t count) |
528 | { |
529 | return adp8860_store(dev, buf, count, ADP8860_BLDM2); |
530 | } |
531 | static DEVICE_ATTR(l2_office_dim, 0664, adp8860_bl_l2_office_dim_show, |
532 | adp8860_bl_l2_office_dim_store); |
533 | |
534 | static ssize_t adp8860_bl_l1_daylight_dim_show(struct device *dev, |
535 | struct device_attribute *attr, char *buf) |
536 | { |
537 | return adp8860_show(dev, buf, ADP8860_BLDM1); |
538 | } |
539 | |
540 | static ssize_t adp8860_bl_l1_daylight_dim_store(struct device *dev, |
541 | struct device_attribute *attr, |
542 | const char *buf, size_t count) |
543 | { |
544 | return adp8860_store(dev, buf, count, ADP8860_BLDM1); |
545 | } |
546 | static DEVICE_ATTR(l1_daylight_dim, 0664, adp8860_bl_l1_daylight_dim_show, |
547 | adp8860_bl_l1_daylight_dim_store); |
548 | |
549 | #ifdef ADP8860_EXT_FEATURES |
550 | static ssize_t adp8860_bl_ambient_light_level_show(struct device *dev, |
551 | struct device_attribute *attr, char *buf) |
552 | { |
553 | struct adp8860_bl *data = dev_get_drvdata(dev); |
554 | int error; |
555 | uint8_t reg_val; |
556 | uint16_t ret_val; |
557 | |
558 | mutex_lock(&data->lock); |
559 | error = adp8860_read(client: data->client, ADP8860_PH1LEVL, val: ®_val); |
560 | if (!error) { |
561 | ret_val = reg_val; |
562 | error = adp8860_read(client: data->client, ADP8860_PH1LEVH, val: ®_val); |
563 | } |
564 | mutex_unlock(lock: &data->lock); |
565 | |
566 | if (error) |
567 | return error; |
568 | |
569 | /* Return 13-bit conversion value for the first light sensor */ |
570 | ret_val += (reg_val & 0x1F) << 8; |
571 | |
572 | return sprintf(buf, fmt: "%u\n" , ret_val); |
573 | } |
574 | static DEVICE_ATTR(ambient_light_level, 0444, |
575 | adp8860_bl_ambient_light_level_show, NULL); |
576 | |
577 | static ssize_t adp8860_bl_ambient_light_zone_show(struct device *dev, |
578 | struct device_attribute *attr, char *buf) |
579 | { |
580 | struct adp8860_bl *data = dev_get_drvdata(dev); |
581 | int error; |
582 | uint8_t reg_val; |
583 | |
584 | mutex_lock(&data->lock); |
585 | error = adp8860_read(client: data->client, ADP8860_CFGR, val: ®_val); |
586 | mutex_unlock(lock: &data->lock); |
587 | |
588 | if (error < 0) |
589 | return error; |
590 | |
591 | return sprintf(buf, fmt: "%u\n" , |
592 | ((reg_val >> CFGR_BLV_SHIFT) & CFGR_BLV_MASK) + 1); |
593 | } |
594 | |
595 | static ssize_t adp8860_bl_ambient_light_zone_store(struct device *dev, |
596 | struct device_attribute *attr, |
597 | const char *buf, size_t count) |
598 | { |
599 | struct adp8860_bl *data = dev_get_drvdata(dev); |
600 | unsigned long val; |
601 | uint8_t reg_val; |
602 | int ret; |
603 | |
604 | ret = kstrtoul(s: buf, base: 10, res: &val); |
605 | if (ret) |
606 | return ret; |
607 | |
608 | if (val == 0) { |
609 | /* Enable automatic ambient light sensing */ |
610 | adp8860_set_bits(client: data->client, ADP8860_MDCR, CMP_AUTOEN); |
611 | } else if ((val > 0) && (val <= 3)) { |
612 | /* Disable automatic ambient light sensing */ |
613 | adp8860_clr_bits(client: data->client, ADP8860_MDCR, CMP_AUTOEN); |
614 | |
615 | /* Set user supplied ambient light zone */ |
616 | mutex_lock(&data->lock); |
617 | ret = adp8860_read(client: data->client, ADP8860_CFGR, val: ®_val); |
618 | if (!ret) { |
619 | reg_val &= ~(CFGR_BLV_MASK << CFGR_BLV_SHIFT); |
620 | reg_val |= (val - 1) << CFGR_BLV_SHIFT; |
621 | adp8860_write(client: data->client, ADP8860_CFGR, val: reg_val); |
622 | } |
623 | mutex_unlock(lock: &data->lock); |
624 | } |
625 | |
626 | return count; |
627 | } |
628 | static DEVICE_ATTR(ambient_light_zone, 0664, |
629 | adp8860_bl_ambient_light_zone_show, |
630 | adp8860_bl_ambient_light_zone_store); |
631 | #endif |
632 | |
633 | static struct attribute *adp8860_bl_attributes[] = { |
634 | &dev_attr_l3_dark_max.attr, |
635 | &dev_attr_l3_dark_dim.attr, |
636 | &dev_attr_l2_office_max.attr, |
637 | &dev_attr_l2_office_dim.attr, |
638 | &dev_attr_l1_daylight_max.attr, |
639 | &dev_attr_l1_daylight_dim.attr, |
640 | #ifdef ADP8860_EXT_FEATURES |
641 | &dev_attr_ambient_light_level.attr, |
642 | &dev_attr_ambient_light_zone.attr, |
643 | #endif |
644 | NULL |
645 | }; |
646 | |
647 | static const struct attribute_group adp8860_bl_attr_group = { |
648 | .attrs = adp8860_bl_attributes, |
649 | }; |
650 | |
651 | static int adp8860_probe(struct i2c_client *client) |
652 | { |
653 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
654 | struct backlight_device *bl; |
655 | struct adp8860_bl *data; |
656 | struct adp8860_backlight_platform_data *pdata = |
657 | dev_get_platdata(dev: &client->dev); |
658 | struct backlight_properties props; |
659 | uint8_t reg_val; |
660 | int ret; |
661 | |
662 | if (!i2c_check_functionality(adap: client->adapter, |
663 | I2C_FUNC_SMBUS_BYTE_DATA)) { |
664 | dev_err(&client->dev, "SMBUS Byte Data not Supported\n" ); |
665 | return -EIO; |
666 | } |
667 | |
668 | if (!pdata) { |
669 | dev_err(&client->dev, "no platform data?\n" ); |
670 | return -EINVAL; |
671 | } |
672 | |
673 | data = devm_kzalloc(dev: &client->dev, size: sizeof(*data), GFP_KERNEL); |
674 | if (data == NULL) |
675 | return -ENOMEM; |
676 | |
677 | ret = adp8860_read(client, ADP8860_MFDVID, val: ®_val); |
678 | if (ret < 0) |
679 | return ret; |
680 | |
681 | switch (ADP8860_MANID(reg_val)) { |
682 | case ADP8863_MANUFID: |
683 | data->gdwn_dis = !!pdata->gdwn_dis; |
684 | fallthrough; |
685 | case ADP8860_MANUFID: |
686 | data->en_ambl_sens = !!pdata->en_ambl_sens; |
687 | break; |
688 | case ADP8861_MANUFID: |
689 | data->gdwn_dis = !!pdata->gdwn_dis; |
690 | break; |
691 | default: |
692 | dev_err(&client->dev, "failed to probe\n" ); |
693 | return -ENODEV; |
694 | } |
695 | |
696 | /* It's confirmed that the DEVID field is actually a REVID */ |
697 | |
698 | data->revid = ADP8860_DEVID(reg_val); |
699 | data->client = client; |
700 | data->pdata = pdata; |
701 | data->id = id->driver_data; |
702 | data->current_brightness = 0; |
703 | i2c_set_clientdata(client, data); |
704 | |
705 | memset(&props, 0, sizeof(props)); |
706 | props.type = BACKLIGHT_RAW; |
707 | props.max_brightness = ADP8860_MAX_BRIGHTNESS; |
708 | |
709 | mutex_init(&data->lock); |
710 | |
711 | bl = devm_backlight_device_register(dev: &client->dev, |
712 | name: dev_driver_string(dev: &client->dev), |
713 | parent: &client->dev, devdata: data, ops: &adp8860_bl_ops, props: &props); |
714 | if (IS_ERR(ptr: bl)) { |
715 | dev_err(&client->dev, "failed to register backlight\n" ); |
716 | return PTR_ERR(ptr: bl); |
717 | } |
718 | |
719 | bl->props.brightness = ADP8860_MAX_BRIGHTNESS; |
720 | |
721 | data->bl = bl; |
722 | |
723 | if (data->en_ambl_sens) |
724 | ret = sysfs_create_group(kobj: &bl->dev.kobj, |
725 | grp: &adp8860_bl_attr_group); |
726 | |
727 | if (ret) { |
728 | dev_err(&client->dev, "failed to register sysfs\n" ); |
729 | return ret; |
730 | } |
731 | |
732 | ret = adp8860_bl_setup(bl); |
733 | if (ret) { |
734 | ret = -EIO; |
735 | goto out; |
736 | } |
737 | |
738 | backlight_update_status(bd: bl); |
739 | |
740 | dev_info(&client->dev, "%s Rev.%d Backlight\n" , |
741 | client->name, data->revid); |
742 | |
743 | if (pdata->num_leds) |
744 | adp8860_led_probe(client); |
745 | |
746 | return 0; |
747 | |
748 | out: |
749 | if (data->en_ambl_sens) |
750 | sysfs_remove_group(kobj: &data->bl->dev.kobj, |
751 | grp: &adp8860_bl_attr_group); |
752 | |
753 | return ret; |
754 | } |
755 | |
756 | static void adp8860_remove(struct i2c_client *client) |
757 | { |
758 | struct adp8860_bl *data = i2c_get_clientdata(client); |
759 | |
760 | adp8860_clr_bits(client, ADP8860_MDCR, NSTBY); |
761 | |
762 | if (data->led) |
763 | adp8860_led_remove(client); |
764 | |
765 | if (data->en_ambl_sens) |
766 | sysfs_remove_group(kobj: &data->bl->dev.kobj, |
767 | grp: &adp8860_bl_attr_group); |
768 | } |
769 | |
770 | #ifdef CONFIG_PM_SLEEP |
771 | static int adp8860_i2c_suspend(struct device *dev) |
772 | { |
773 | struct i2c_client *client = to_i2c_client(dev); |
774 | |
775 | adp8860_clr_bits(client, ADP8860_MDCR, NSTBY); |
776 | |
777 | return 0; |
778 | } |
779 | |
780 | static int adp8860_i2c_resume(struct device *dev) |
781 | { |
782 | struct i2c_client *client = to_i2c_client(dev); |
783 | |
784 | adp8860_set_bits(client, ADP8860_MDCR, NSTBY | BLEN); |
785 | |
786 | return 0; |
787 | } |
788 | #endif |
789 | |
790 | static SIMPLE_DEV_PM_OPS(adp8860_i2c_pm_ops, adp8860_i2c_suspend, |
791 | adp8860_i2c_resume); |
792 | |
793 | static const struct i2c_device_id adp8860_id[] = { |
794 | { "adp8860" , adp8860 }, |
795 | { "adp8861" , adp8861 }, |
796 | { "adp8863" , adp8863 }, |
797 | { } |
798 | }; |
799 | MODULE_DEVICE_TABLE(i2c, adp8860_id); |
800 | |
801 | static struct i2c_driver adp8860_driver = { |
802 | .driver = { |
803 | .name = KBUILD_MODNAME, |
804 | .pm = &adp8860_i2c_pm_ops, |
805 | }, |
806 | .probe = adp8860_probe, |
807 | .remove = adp8860_remove, |
808 | .id_table = adp8860_id, |
809 | }; |
810 | |
811 | module_i2c_driver(adp8860_driver); |
812 | |
813 | MODULE_LICENSE("GPL v2" ); |
814 | MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>" ); |
815 | MODULE_DESCRIPTION("ADP8860 Backlight driver" ); |
816 | |