Commit a59ec1e7ff98cc4365d5b1bff4e7102e86b5716b
Committed by
Linus Torvalds
1 parent
7f81c8890c
Exists in
master
and in
39 other branches
backlight: new driver for the ADP8870 backlight devices
Signed-off-by: Michael Hennerich <michael.hennerich@analog.com> Signed-off-by: Mike Frysinger <vapier@gentoo.org> Cc: Richard Purdie <rpurdie@rpsys.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Showing 5 changed files with 1233 additions and 0 deletions Side-by-side Diff
Documentation/ABI/testing/sysfs-class-backlight-driver-adp8870
1 | +What: /sys/class/backlight/<backlight>/<ambient light zone>_max | |
2 | +What: /sys/class/backlight/<backlight>/l1_daylight_max | |
3 | +What: /sys/class/backlight/<backlight>/l2_bright_max | |
4 | +What: /sys/class/backlight/<backlight>/l3_office_max | |
5 | +What: /sys/class/backlight/<backlight>/l4_indoor_max | |
6 | +What: /sys/class/backlight/<backlight>/l5_dark_max | |
7 | +Date: Mai 2011 | |
8 | +KernelVersion: 2.6.40 | |
9 | +Contact: device-drivers-devel@blackfin.uclinux.org | |
10 | +Description: | |
11 | + Control the maximum brightness for <ambient light zone> | |
12 | + on this <backlight>. Values are between 0 and 127. This file | |
13 | + will also show the brightness level stored for this | |
14 | + <ambient light zone>. | |
15 | + | |
16 | +What: /sys/class/backlight/<backlight>/<ambient light zone>_dim | |
17 | +What: /sys/class/backlight/<backlight>/l2_bright_dim | |
18 | +What: /sys/class/backlight/<backlight>/l3_office_dim | |
19 | +What: /sys/class/backlight/<backlight>/l4_indoor_dim | |
20 | +What: /sys/class/backlight/<backlight>/l5_dark_dim | |
21 | +Date: Mai 2011 | |
22 | +KernelVersion: 2.6.40 | |
23 | +Contact: device-drivers-devel@blackfin.uclinux.org | |
24 | +Description: | |
25 | + Control the dim brightness for <ambient light zone> | |
26 | + on this <backlight>. Values are between 0 and 127, typically | |
27 | + set to 0. Full off when the backlight is disabled. | |
28 | + This file will also show the dim brightness level stored for | |
29 | + this <ambient light zone>. | |
30 | + | |
31 | +What: /sys/class/backlight/<backlight>/ambient_light_level | |
32 | +Date: Mai 2011 | |
33 | +KernelVersion: 2.6.40 | |
34 | +Contact: device-drivers-devel@blackfin.uclinux.org | |
35 | +Description: | |
36 | + Get conversion value of the light sensor. | |
37 | + This value is updated every 80 ms (when the light sensor | |
38 | + is enabled). Returns integer between 0 (dark) and | |
39 | + 8000 (max ambient brightness) | |
40 | + | |
41 | +What: /sys/class/backlight/<backlight>/ambient_light_zone | |
42 | +Date: Mai 2011 | |
43 | +KernelVersion: 2.6.40 | |
44 | +Contact: device-drivers-devel@blackfin.uclinux.org | |
45 | +Description: | |
46 | + Get/Set current ambient light zone. Reading returns | |
47 | + integer between 1..5 (1 = daylight, 2 = bright, ..., 5 = dark). | |
48 | + Writing a value between 1..5 forces the backlight controller | |
49 | + to enter the corresponding ambient light zone. | |
50 | + Writing 0 returns to normal/automatic ambient light level | |
51 | + operation. The ambient light sensing feature on these devices | |
52 | + is an extension to the API documented in | |
53 | + Documentation/ABI/stable/sysfs-class-backlight. | |
54 | + It can be enabled by writing the value stored in | |
55 | + /sys/class/backlight/<backlight>/max_brightness to | |
56 | + /sys/class/backlight/<backlight>/brightness. |
drivers/video/backlight/Kconfig
... | ... | @@ -302,6 +302,18 @@ |
302 | 302 | To compile this driver as a module, choose M here: the module will |
303 | 303 | be called adp8860_bl. |
304 | 304 | |
305 | +config BACKLIGHT_ADP8870 | |
306 | + tristate "Backlight Driver for ADP8870 using WLED" | |
307 | + depends on BACKLIGHT_CLASS_DEVICE && I2C | |
308 | + select NEW_LEDS | |
309 | + select LEDS_CLASS | |
310 | + help | |
311 | + If you have a LCD backlight connected to the ADP8870, | |
312 | + say Y here to enable this driver. | |
313 | + | |
314 | + To compile this driver as a module, choose M here: the module will | |
315 | + be called adp8870_bl. | |
316 | + | |
305 | 317 | config BACKLIGHT_88PM860X |
306 | 318 | tristate "Backlight Driver for 88PM8606 using WLED" |
307 | 319 | depends on MFD_88PM860X |
drivers/video/backlight/Makefile
... | ... | @@ -34,6 +34,7 @@ |
34 | 34 | obj-$(CONFIG_BACKLIGHT_ADX) += adx_bl.o |
35 | 35 | obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o |
36 | 36 | obj-$(CONFIG_BACKLIGHT_ADP8860) += adp8860_bl.o |
37 | +obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o | |
37 | 38 | obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o |
38 | 39 | obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o |
drivers/video/backlight/adp8870_bl.c
Changes suppressed. Click to show
1 | +/* | |
2 | + * Backlight driver for Analog Devices ADP8870 Backlight Devices | |
3 | + * | |
4 | + * Copyright 2009-2011 Analog Devices Inc. | |
5 | + * | |
6 | + * Licensed under the GPL-2 or later. | |
7 | + */ | |
8 | + | |
9 | +#include <linux/module.h> | |
10 | +#include <linux/version.h> | |
11 | +#include <linux/init.h> | |
12 | +#include <linux/errno.h> | |
13 | +#include <linux/pm.h> | |
14 | +#include <linux/platform_device.h> | |
15 | +#include <linux/i2c.h> | |
16 | +#include <linux/fb.h> | |
17 | +#include <linux/backlight.h> | |
18 | +#include <linux/leds.h> | |
19 | +#include <linux/workqueue.h> | |
20 | +#include <linux/slab.h> | |
21 | + | |
22 | +#include <linux/i2c/adp8870.h> | |
23 | +#define ADP8870_EXT_FEATURES | |
24 | +#define ADP8870_USE_LEDS | |
25 | + | |
26 | + | |
27 | +#define ADP8870_MFDVID 0x00 /* Manufacturer and device ID */ | |
28 | +#define ADP8870_MDCR 0x01 /* Device mode and status */ | |
29 | +#define ADP8870_INT_STAT 0x02 /* Interrupts status */ | |
30 | +#define ADP8870_INT_EN 0x03 /* Interrupts enable */ | |
31 | +#define ADP8870_CFGR 0x04 /* Configuration register */ | |
32 | +#define ADP8870_BLSEL 0x05 /* Sink enable backlight or independent */ | |
33 | +#define ADP8870_PWMLED 0x06 /* PWM Enable Selection Register */ | |
34 | +#define ADP8870_BLOFF 0x07 /* Backlight off timeout */ | |
35 | +#define ADP8870_BLDIM 0x08 /* Backlight dim timeout */ | |
36 | +#define ADP8870_BLFR 0x09 /* Backlight fade in and out rates */ | |
37 | +#define ADP8870_BLMX1 0x0A /* Backlight (Brightness Level 1-daylight) maximum current */ | |
38 | +#define ADP8870_BLDM1 0x0B /* Backlight (Brightness Level 1-daylight) dim current */ | |
39 | +#define ADP8870_BLMX2 0x0C /* Backlight (Brightness Level 2-bright) maximum current */ | |
40 | +#define ADP8870_BLDM2 0x0D /* Backlight (Brightness Level 2-bright) dim current */ | |
41 | +#define ADP8870_BLMX3 0x0E /* Backlight (Brightness Level 3-office) maximum current */ | |
42 | +#define ADP8870_BLDM3 0x0F /* Backlight (Brightness Level 3-office) dim current */ | |
43 | +#define ADP8870_BLMX4 0x10 /* Backlight (Brightness Level 4-indoor) maximum current */ | |
44 | +#define ADP8870_BLDM4 0x11 /* Backlight (Brightness Level 4-indoor) dim current */ | |
45 | +#define ADP8870_BLMX5 0x12 /* Backlight (Brightness Level 5-dark) maximum current */ | |
46 | +#define ADP8870_BLDM5 0x13 /* Backlight (Brightness Level 5-dark) dim current */ | |
47 | +#define ADP8870_ISCLAW 0x1A /* Independent sink current fade law register */ | |
48 | +#define ADP8870_ISCC 0x1B /* Independent sink current control register */ | |
49 | +#define ADP8870_ISCT1 0x1C /* Independent Sink Current Timer Register LED[7:5] */ | |
50 | +#define ADP8870_ISCT2 0x1D /* Independent Sink Current Timer Register LED[4:1] */ | |
51 | +#define ADP8870_ISCF 0x1E /* Independent sink current fade register */ | |
52 | +#define ADP8870_ISC1 0x1F /* Independent Sink Current LED1 */ | |
53 | +#define ADP8870_ISC2 0x20 /* Independent Sink Current LED2 */ | |
54 | +#define ADP8870_ISC3 0x21 /* Independent Sink Current LED3 */ | |
55 | +#define ADP8870_ISC4 0x22 /* Independent Sink Current LED4 */ | |
56 | +#define ADP8870_ISC5 0x23 /* Independent Sink Current LED5 */ | |
57 | +#define ADP8870_ISC6 0x24 /* Independent Sink Current LED6 */ | |
58 | +#define ADP8870_ISC7 0x25 /* Independent Sink Current LED7 (Brightness Level 1-daylight) */ | |
59 | +#define ADP8870_ISC7_L2 0x26 /* Independent Sink Current LED7 (Brightness Level 2-bright) */ | |
60 | +#define ADP8870_ISC7_L3 0x27 /* Independent Sink Current LED7 (Brightness Level 3-office) */ | |
61 | +#define ADP8870_ISC7_L4 0x28 /* Independent Sink Current LED7 (Brightness Level 4-indoor) */ | |
62 | +#define ADP8870_ISC7_L5 0x29 /* Independent Sink Current LED7 (Brightness Level 5-dark) */ | |
63 | +#define ADP8870_CMP_CTL 0x2D /* ALS Comparator Control Register */ | |
64 | +#define ADP8870_ALS1_EN 0x2E /* Main ALS comparator level enable */ | |
65 | +#define ADP8870_ALS2_EN 0x2F /* Second ALS comparator level enable */ | |
66 | +#define ADP8870_ALS1_STAT 0x30 /* Main ALS Comparator Status Register */ | |
67 | +#define ADP8870_ALS2_STAT 0x31 /* Second ALS Comparator Status Register */ | |
68 | +#define ADP8870_L2TRP 0x32 /* L2 comparator reference */ | |
69 | +#define ADP8870_L2HYS 0x33 /* L2 hysteresis */ | |
70 | +#define ADP8870_L3TRP 0x34 /* L3 comparator reference */ | |
71 | +#define ADP8870_L3HYS 0x35 /* L3 hysteresis */ | |
72 | +#define ADP8870_L4TRP 0x36 /* L4 comparator reference */ | |
73 | +#define ADP8870_L4HYS 0x37 /* L4 hysteresis */ | |
74 | +#define ADP8870_L5TRP 0x38 /* L5 comparator reference */ | |
75 | +#define ADP8870_L5HYS 0x39 /* L5 hysteresis */ | |
76 | +#define ADP8870_PH1LEVL 0x40 /* First phototransistor ambient light level-low byte register */ | |
77 | +#define ADP8870_PH1LEVH 0x41 /* First phototransistor ambient light level-high byte register */ | |
78 | +#define ADP8870_PH2LEVL 0x42 /* Second phototransistor ambient light level-low byte register */ | |
79 | +#define ADP8870_PH2LEVH 0x43 /* Second phototransistor ambient light level-high byte register */ | |
80 | + | |
81 | +#define ADP8870_MANUFID 0x3 /* Analog Devices AD8870 Manufacturer and device ID */ | |
82 | +#define ADP8870_DEVID(x) ((x) & 0xF) | |
83 | +#define ADP8870_MANID(x) ((x) >> 4) | |
84 | + | |
85 | +/* MDCR Device mode and status */ | |
86 | +#define D7ALSEN (1 << 7) | |
87 | +#define INT_CFG (1 << 6) | |
88 | +#define NSTBY (1 << 5) | |
89 | +#define DIM_EN (1 << 4) | |
90 | +#define GDWN_DIS (1 << 3) | |
91 | +#define SIS_EN (1 << 2) | |
92 | +#define CMP_AUTOEN (1 << 1) | |
93 | +#define BLEN (1 << 0) | |
94 | + | |
95 | +/* ADP8870_ALS1_EN Main ALS comparator level enable */ | |
96 | +#define L5_EN (1 << 3) | |
97 | +#define L4_EN (1 << 2) | |
98 | +#define L3_EN (1 << 1) | |
99 | +#define L2_EN (1 << 0) | |
100 | + | |
101 | +#define CFGR_BLV_SHIFT 3 | |
102 | +#define CFGR_BLV_MASK 0x7 | |
103 | +#define ADP8870_FLAG_LED_MASK 0xFF | |
104 | + | |
105 | +#define FADE_VAL(in, out) ((0xF & (in)) | ((0xF & (out)) << 4)) | |
106 | +#define BL_CFGR_VAL(law, blv) ((((blv) & CFGR_BLV_MASK) << CFGR_BLV_SHIFT) | ((0x3 & (law)) << 1)) | |
107 | +#define ALS_CMPR_CFG_VAL(filt) ((0x7 & (filt)) << 1) | |
108 | + | |
109 | +struct adp8870_bl { | |
110 | + struct i2c_client *client; | |
111 | + struct backlight_device *bl; | |
112 | + struct adp8870_led *led; | |
113 | + struct adp8870_backlight_platform_data *pdata; | |
114 | + struct mutex lock; | |
115 | + unsigned long cached_daylight_max; | |
116 | + int id; | |
117 | + int revid; | |
118 | + int current_brightness; | |
119 | +}; | |
120 | + | |
121 | +struct adp8870_led { | |
122 | + struct led_classdev cdev; | |
123 | + struct work_struct work; | |
124 | + struct i2c_client *client; | |
125 | + enum led_brightness new_brightness; | |
126 | + int id; | |
127 | + int flags; | |
128 | +}; | |
129 | + | |
130 | +static int adp8870_read(struct i2c_client *client, int reg, uint8_t *val) | |
131 | +{ | |
132 | + int ret; | |
133 | + | |
134 | + ret = i2c_smbus_read_byte_data(client, reg); | |
135 | + if (ret < 0) { | |
136 | + dev_err(&client->dev, "failed reading at 0x%02x\n", reg); | |
137 | + return ret; | |
138 | + } | |
139 | + | |
140 | + *val = ret; | |
141 | + return 0; | |
142 | +} | |
143 | + | |
144 | + | |
145 | +static int adp8870_write(struct i2c_client *client, u8 reg, u8 val) | |
146 | +{ | |
147 | + int ret = i2c_smbus_write_byte_data(client, reg, val); | |
148 | + if (ret) | |
149 | + dev_err(&client->dev, "failed to write\n"); | |
150 | + | |
151 | + return ret; | |
152 | +} | |
153 | + | |
154 | +static int adp8870_set_bits(struct i2c_client *client, int reg, uint8_t bit_mask) | |
155 | +{ | |
156 | + struct adp8870_bl *data = i2c_get_clientdata(client); | |
157 | + uint8_t reg_val; | |
158 | + int ret; | |
159 | + | |
160 | + mutex_lock(&data->lock); | |
161 | + | |
162 | + ret = adp8870_read(client, reg, ®_val); | |
163 | + | |
164 | + if (!ret && ((reg_val & bit_mask) == 0)) { | |
165 | + reg_val |= bit_mask; | |
166 | + ret = adp8870_write(client, reg, reg_val); | |
167 | + } | |
168 | + | |
169 | + mutex_unlock(&data->lock); | |
170 | + return ret; | |
171 | +} | |
172 | + | |
173 | +static int adp8870_clr_bits(struct i2c_client *client, int reg, uint8_t bit_mask) | |
174 | +{ | |
175 | + struct adp8870_bl *data = i2c_get_clientdata(client); | |
176 | + uint8_t reg_val; | |
177 | + int ret; | |
178 | + | |
179 | + mutex_lock(&data->lock); | |
180 | + | |
181 | + ret = adp8870_read(client, reg, ®_val); | |
182 | + | |
183 | + if (!ret && (reg_val & bit_mask)) { | |
184 | + reg_val &= ~bit_mask; | |
185 | + ret = adp8870_write(client, reg, reg_val); | |
186 | + } | |
187 | + | |
188 | + mutex_unlock(&data->lock); | |
189 | + return ret; | |
190 | +} | |
191 | + | |
192 | +/* | |
193 | + * Independent sink / LED | |
194 | + */ | |
195 | +#if defined(ADP8870_USE_LEDS) | |
196 | +static void adp8870_led_work(struct work_struct *work) | |
197 | +{ | |
198 | + struct adp8870_led *led = container_of(work, struct adp8870_led, work); | |
199 | + adp8870_write(led->client, ADP8870_ISC1 + led->id - 1, | |
200 | + 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(&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, 0); | |
222 | + if (ret) | |
223 | + return ret; | |
224 | + | |
225 | + ret = adp8870_set_bits(client, ADP8870_ISCC, 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 | + (led->flags & 0x3) << ((led->id - 5) * 2)); | |
232 | + else | |
233 | + ret = adp8870_set_bits(client, ADP8870_ISCT2, | |
234 | + (led->flags & 0x3) << ((led->id - 1) * 2)); | |
235 | + | |
236 | + return ret; | |
237 | +} | |
238 | + | |
239 | +static int __devinit adp8870_led_probe(struct i2c_client *client) | |
240 | +{ | |
241 | + struct adp8870_backlight_platform_data *pdata = | |
242 | + client->dev.platform_data; | |
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 | + | |
249 | + led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL); | |
250 | + if (led == NULL) { | |
251 | + dev_err(&client->dev, "failed to alloc memory\n"); | |
252 | + return -ENOMEM; | |
253 | + } | |
254 | + | |
255 | + ret = adp8870_write(client, ADP8870_ISCLAW, pdata->led_fade_law); | |
256 | + if (ret) | |
257 | + goto err_free; | |
258 | + | |
259 | + ret = adp8870_write(client, ADP8870_ISCT1, | |
260 | + (pdata->led_on_time & 0x3) << 6); | |
261 | + if (ret) | |
262 | + goto err_free; | |
263 | + | |
264 | + ret = adp8870_write(client, ADP8870_ISCF, | |
265 | + FADE_VAL(pdata->led_fade_in, pdata->led_fade_out)); | |
266 | + if (ret) | |
267 | + goto err_free; | |
268 | + | |
269 | + for (i = 0; i < pdata->num_leds; ++i) { | |
270 | + cur_led = &pdata->leds[i]; | |
271 | + led_dat = &led[i]; | |
272 | + | |
273 | + led_dat->id = cur_led->flags & ADP8870_FLAG_LED_MASK; | |
274 | + | |
275 | + if (led_dat->id > 7 || led_dat->id < 1) { | |
276 | + dev_err(&client->dev, "Invalid LED ID %d\n", | |
277 | + led_dat->id); | |
278 | + goto err; | |
279 | + } | |
280 | + | |
281 | + if (pdata->bl_led_assign & (1 << (led_dat->id - 1))) { | |
282 | + dev_err(&client->dev, "LED %d used by Backlight\n", | |
283 | + led_dat->id); | |
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(&client->dev, &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_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[i].cdev); | |
318 | + cancel_work_sync(&led[i].work); | |
319 | + } | |
320 | + | |
321 | + err_free: | |
322 | + kfree(led); | |
323 | + | |
324 | + return ret; | |
325 | +} | |
326 | + | |
327 | +static int __devexit adp8870_led_remove(struct i2c_client *client) | |
328 | +{ | |
329 | + struct adp8870_backlight_platform_data *pdata = | |
330 | + client->dev.platform_data; | |
331 | + struct adp8870_bl *data = i2c_get_clientdata(client); | |
332 | + int i; | |
333 | + | |
334 | + for (i = 0; i < pdata->num_leds; i++) { | |
335 | + led_classdev_unregister(&data->led[i].cdev); | |
336 | + cancel_work_sync(&data->led[i].work); | |
337 | + } | |
338 | + | |
339 | + kfree(data->led); | |
340 | + return 0; | |
341 | +} | |
342 | +#else | |
343 | +static int __devinit adp8870_led_probe(struct i2c_client *client) | |
344 | +{ | |
345 | + return 0; | |
346 | +} | |
347 | + | |
348 | +static int __devexit adp8870_led_remove(struct i2c_client *client) | |
349 | +{ | |
350 | + return 0; | |
351 | +} | |
352 | +#endif | |
353 | + | |
354 | +static int adp8870_bl_set(struct backlight_device *bl, int brightness) | |
355 | +{ | |
356 | + struct adp8870_bl *data = bl_get_data(bl); | |
357 | + struct i2c_client *client = data->client; | |
358 | + int ret = 0; | |
359 | + | |
360 | + if (data->pdata->en_ambl_sens) { | |
361 | + if ((brightness > 0) && (brightness < ADP8870_MAX_BRIGHTNESS)) { | |
362 | + /* Disable Ambient Light auto adjust */ | |
363 | + ret = adp8870_clr_bits(client, ADP8870_MDCR, | |
364 | + CMP_AUTOEN); | |
365 | + if (ret) | |
366 | + return ret; | |
367 | + ret = adp8870_write(client, ADP8870_BLMX1, brightness); | |
368 | + if (ret) | |
369 | + return ret; | |
370 | + } else { | |
371 | + /* | |
372 | + * MAX_BRIGHTNESS -> Enable Ambient Light auto adjust | |
373 | + * restore daylight l1 sysfs brightness | |
374 | + */ | |
375 | + ret = adp8870_write(client, ADP8870_BLMX1, | |
376 | + data->cached_daylight_max); | |
377 | + if (ret) | |
378 | + return ret; | |
379 | + | |
380 | + ret = adp8870_set_bits(client, ADP8870_MDCR, | |
381 | + CMP_AUTOEN); | |
382 | + if (ret) | |
383 | + return ret; | |
384 | + } | |
385 | + } else { | |
386 | + ret = adp8870_write(client, ADP8870_BLMX1, brightness); | |
387 | + if (ret) | |
388 | + return ret; | |
389 | + } | |
390 | + | |
391 | + if (data->current_brightness && brightness == 0) | |
392 | + ret = adp8870_set_bits(client, | |
393 | + ADP8870_MDCR, DIM_EN); | |
394 | + else if (data->current_brightness == 0 && brightness) | |
395 | + ret = adp8870_clr_bits(client, | |
396 | + ADP8870_MDCR, DIM_EN); | |
397 | + | |
398 | + if (!ret) | |
399 | + data->current_brightness = brightness; | |
400 | + | |
401 | + return ret; | |
402 | +} | |
403 | + | |
404 | +static int adp8870_bl_update_status(struct backlight_device *bl) | |
405 | +{ | |
406 | + int brightness = bl->props.brightness; | |
407 | + if (bl->props.power != FB_BLANK_UNBLANK) | |
408 | + brightness = 0; | |
409 | + | |
410 | + if (bl->props.fb_blank != FB_BLANK_UNBLANK) | |
411 | + brightness = 0; | |
412 | + | |
413 | + return adp8870_bl_set(bl, brightness); | |
414 | +} | |
415 | + | |
416 | +static int adp8870_bl_get_brightness(struct backlight_device *bl) | |
417 | +{ | |
418 | + struct adp8870_bl *data = bl_get_data(bl); | |
419 | + | |
420 | + return data->current_brightness; | |
421 | +} | |
422 | + | |
423 | +static const struct backlight_ops adp8870_bl_ops = { | |
424 | + .update_status = adp8870_bl_update_status, | |
425 | + .get_brightness = adp8870_bl_get_brightness, | |
426 | +}; | |
427 | + | |
428 | +static int adp8870_bl_setup(struct backlight_device *bl) | |
429 | +{ | |
430 | + struct adp8870_bl *data = bl_get_data(bl); | |
431 | + struct i2c_client *client = data->client; | |
432 | + struct adp8870_backlight_platform_data *pdata = data->pdata; | |
433 | + int ret = 0; | |
434 | + | |
435 | + ret = adp8870_write(client, ADP8870_BLSEL, ~pdata->bl_led_assign); | |
436 | + if (ret) | |
437 | + return ret; | |
438 | + | |
439 | + ret = adp8870_write(client, ADP8870_PWMLED, pdata->pwm_assign); | |
440 | + if (ret) | |
441 | + return ret; | |
442 | + | |
443 | + ret = adp8870_write(client, ADP8870_BLMX1, pdata->l1_daylight_max); | |
444 | + if (ret) | |
445 | + return ret; | |
446 | + | |
447 | + ret = adp8870_write(client, ADP8870_BLDM1, pdata->l1_daylight_dim); | |
448 | + if (ret) | |
449 | + return ret; | |
450 | + | |
451 | + if (pdata->en_ambl_sens) { | |
452 | + data->cached_daylight_max = pdata->l1_daylight_max; | |
453 | + ret = adp8870_write(client, ADP8870_BLMX2, | |
454 | + pdata->l2_bright_max); | |
455 | + if (ret) | |
456 | + return ret; | |
457 | + ret = adp8870_write(client, ADP8870_BLDM2, | |
458 | + pdata->l2_bright_dim); | |
459 | + if (ret) | |
460 | + return ret; | |
461 | + | |
462 | + ret = adp8870_write(client, ADP8870_BLMX3, | |
463 | + pdata->l3_office_max); | |
464 | + if (ret) | |
465 | + return ret; | |
466 | + ret = adp8870_write(client, ADP8870_BLDM3, | |
467 | + pdata->l3_office_dim); | |
468 | + if (ret) | |
469 | + return ret; | |
470 | + | |
471 | + ret = adp8870_write(client, ADP8870_BLMX4, | |
472 | + pdata->l4_indoor_max); | |
473 | + if (ret) | |
474 | + return ret; | |
475 | + | |
476 | + ret = adp8870_write(client, ADP8870_BLDM4, | |
477 | + pdata->l4_indor_dim); | |
478 | + if (ret) | |
479 | + return ret; | |
480 | + | |
481 | + ret = adp8870_write(client, ADP8870_BLMX5, | |
482 | + pdata->l5_dark_max); | |
483 | + if (ret) | |
484 | + return ret; | |
485 | + | |
486 | + ret = adp8870_write(client, ADP8870_BLDM5, | |
487 | + pdata->l5_dark_dim); | |
488 | + if (ret) | |
489 | + return ret; | |
490 | + | |
491 | + ret = adp8870_write(client, ADP8870_L2TRP, pdata->l2_trip); | |
492 | + if (ret) | |
493 | + return ret; | |
494 | + | |
495 | + ret = adp8870_write(client, ADP8870_L2HYS, pdata->l2_hyst); | |
496 | + if (ret) | |
497 | + return ret; | |
498 | + | |
499 | + ret = adp8870_write(client, ADP8870_L3TRP, pdata->l3_trip); | |
500 | + if (ret) | |
501 | + return ret; | |
502 | + | |
503 | + ret = adp8870_write(client, ADP8870_L3HYS, pdata->l3_hyst); | |
504 | + if (ret) | |
505 | + return ret; | |
506 | + | |
507 | + ret = adp8870_write(client, ADP8870_L4TRP, pdata->l4_trip); | |
508 | + if (ret) | |
509 | + return ret; | |
510 | + | |
511 | + ret = adp8870_write(client, ADP8870_L4HYS, pdata->l4_hyst); | |
512 | + if (ret) | |
513 | + return ret; | |
514 | + | |
515 | + ret = adp8870_write(client, ADP8870_L5TRP, pdata->l5_trip); | |
516 | + if (ret) | |
517 | + return ret; | |
518 | + | |
519 | + ret = adp8870_write(client, ADP8870_L5HYS, pdata->l5_hyst); | |
520 | + if (ret) | |
521 | + return ret; | |
522 | + | |
523 | + ret = adp8870_write(client, ADP8870_ALS1_EN, L5_EN | L4_EN | | |
524 | + L3_EN | L2_EN); | |
525 | + if (ret) | |
526 | + return ret; | |
527 | + | |
528 | + ret = adp8870_write(client, ADP8870_CMP_CTL, | |
529 | + ALS_CMPR_CFG_VAL(pdata->abml_filt)); | |
530 | + if (ret) | |
531 | + return ret; | |
532 | + } | |
533 | + | |
534 | + ret = adp8870_write(client, ADP8870_CFGR, | |
535 | + BL_CFGR_VAL(pdata->bl_fade_law, 0)); | |
536 | + if (ret) | |
537 | + return ret; | |
538 | + | |
539 | + ret = adp8870_write(client, ADP8870_BLFR, FADE_VAL(pdata->bl_fade_in, | |
540 | + pdata->bl_fade_out)); | |
541 | + if (ret) | |
542 | + return ret; | |
543 | + /* | |
544 | + * ADP8870 Rev0 requires GDWN_DIS bit set | |
545 | + */ | |
546 | + | |
547 | + ret = adp8870_set_bits(client, ADP8870_MDCR, BLEN | DIM_EN | NSTBY | | |
548 | + (data->revid == 0 ? GDWN_DIS : 0)); | |
549 | + | |
550 | + return ret; | |
551 | +} | |
552 | + | |
553 | +static ssize_t adp8870_show(struct device *dev, char *buf, int reg) | |
554 | +{ | |
555 | + struct adp8870_bl *data = dev_get_drvdata(dev); | |
556 | + int error; | |
557 | + uint8_t reg_val; | |
558 | + | |
559 | + mutex_lock(&data->lock); | |
560 | + error = adp8870_read(data->client, reg, ®_val); | |
561 | + mutex_unlock(&data->lock); | |
562 | + | |
563 | + if (error < 0) | |
564 | + return error; | |
565 | + | |
566 | + return sprintf(buf, "%u\n", reg_val); | |
567 | +} | |
568 | + | |
569 | +static ssize_t adp8870_store(struct device *dev, const char *buf, | |
570 | + size_t count, int reg) | |
571 | +{ | |
572 | + struct adp8870_bl *data = dev_get_drvdata(dev); | |
573 | + unsigned long val; | |
574 | + int ret; | |
575 | + | |
576 | + ret = strict_strtoul(buf, 10, &val); | |
577 | + if (ret) | |
578 | + return ret; | |
579 | + | |
580 | + mutex_lock(&data->lock); | |
581 | + adp8870_write(data->client, reg, val); | |
582 | + mutex_unlock(&data->lock); | |
583 | + | |
584 | + return count; | |
585 | +} | |
586 | + | |
587 | +static ssize_t adp8870_bl_l5_dark_max_show(struct device *dev, | |
588 | + struct device_attribute *attr, char *buf) | |
589 | +{ | |
590 | + return adp8870_show(dev, buf, ADP8870_BLMX5); | |
591 | +} | |
592 | + | |
593 | +static ssize_t adp8870_bl_l5_dark_max_store(struct device *dev, | |
594 | + struct device_attribute *attr, const char *buf, size_t count) | |
595 | +{ | |
596 | + return adp8870_store(dev, buf, count, ADP8870_BLMX5); | |
597 | +} | |
598 | +static DEVICE_ATTR(l5_dark_max, 0664, adp8870_bl_l5_dark_max_show, | |
599 | + adp8870_bl_l5_dark_max_store); | |
600 | + | |
601 | + | |
602 | +static ssize_t adp8870_bl_l4_indoor_max_show(struct device *dev, | |
603 | + struct device_attribute *attr, char *buf) | |
604 | +{ | |
605 | + return adp8870_show(dev, buf, ADP8870_BLMX4); | |
606 | +} | |
607 | + | |
608 | +static ssize_t adp8870_bl_l4_indoor_max_store(struct device *dev, | |
609 | + struct device_attribute *attr, const char *buf, size_t count) | |
610 | +{ | |
611 | + return adp8870_store(dev, buf, count, ADP8870_BLMX4); | |
612 | +} | |
613 | +static DEVICE_ATTR(l4_indoor_max, 0664, adp8870_bl_l4_indoor_max_show, | |
614 | + adp8870_bl_l4_indoor_max_store); | |
615 | + | |
616 | + | |
617 | +static ssize_t adp8870_bl_l3_office_max_show(struct device *dev, | |
618 | + struct device_attribute *attr, char *buf) | |
619 | +{ | |
620 | + return adp8870_show(dev, buf, ADP8870_BLMX3); | |
621 | +} | |
622 | + | |
623 | +static ssize_t adp8870_bl_l3_office_max_store(struct device *dev, | |
624 | + struct device_attribute *attr, const char *buf, size_t count) | |
625 | +{ | |
626 | + return adp8870_store(dev, buf, count, ADP8870_BLMX3); | |
627 | +} | |
628 | + | |
629 | +static DEVICE_ATTR(l3_office_max, 0664, adp8870_bl_l3_office_max_show, | |
630 | + adp8870_bl_l3_office_max_store); | |
631 | + | |
632 | +static ssize_t adp8870_bl_l2_bright_max_show(struct device *dev, | |
633 | + struct device_attribute *attr, char *buf) | |
634 | +{ | |
635 | + return adp8870_show(dev, buf, ADP8870_BLMX2); | |
636 | +} | |
637 | + | |
638 | +static ssize_t adp8870_bl_l2_bright_max_store(struct device *dev, | |
639 | + struct device_attribute *attr, const char *buf, size_t count) | |
640 | +{ | |
641 | + return adp8870_store(dev, buf, count, ADP8870_BLMX2); | |
642 | +} | |
643 | +static DEVICE_ATTR(l2_bright_max, 0664, adp8870_bl_l2_bright_max_show, | |
644 | + adp8870_bl_l2_bright_max_store); | |
645 | + | |
646 | +static ssize_t adp8870_bl_l1_daylight_max_show(struct device *dev, | |
647 | + struct device_attribute *attr, char *buf) | |
648 | +{ | |
649 | + return adp8870_show(dev, buf, ADP8870_BLMX1); | |
650 | +} | |
651 | + | |
652 | +static ssize_t adp8870_bl_l1_daylight_max_store(struct device *dev, | |
653 | + struct device_attribute *attr, const char *buf, size_t count) | |
654 | +{ | |
655 | + struct adp8870_bl *data = dev_get_drvdata(dev); | |
656 | + int ret = strict_strtoul(buf, 10, &data->cached_daylight_max); | |
657 | + if (ret) | |
658 | + return ret; | |
659 | + | |
660 | + return adp8870_store(dev, buf, count, ADP8870_BLMX1); | |
661 | +} | |
662 | +static DEVICE_ATTR(l1_daylight_max, 0664, adp8870_bl_l1_daylight_max_show, | |
663 | + adp8870_bl_l1_daylight_max_store); | |
664 | + | |
665 | +static ssize_t adp8870_bl_l5_dark_dim_show(struct device *dev, | |
666 | + struct device_attribute *attr, char *buf) | |
667 | +{ | |
668 | + return adp8870_show(dev, buf, ADP8870_BLDM5); | |
669 | +} | |
670 | + | |
671 | +static ssize_t adp8870_bl_l5_dark_dim_store(struct device *dev, | |
672 | + struct device_attribute *attr, | |
673 | + const char *buf, size_t count) | |
674 | +{ | |
675 | + return adp8870_store(dev, buf, count, ADP8870_BLDM5); | |
676 | +} | |
677 | +static DEVICE_ATTR(l5_dark_dim, 0664, adp8870_bl_l5_dark_dim_show, | |
678 | + adp8870_bl_l5_dark_dim_store); | |
679 | + | |
680 | +static ssize_t adp8870_bl_l4_indoor_dim_show(struct device *dev, | |
681 | + struct device_attribute *attr, char *buf) | |
682 | +{ | |
683 | + return adp8870_show(dev, buf, ADP8870_BLDM4); | |
684 | +} | |
685 | + | |
686 | +static ssize_t adp8870_bl_l4_indoor_dim_store(struct device *dev, | |
687 | + struct device_attribute *attr, | |
688 | + const char *buf, size_t count) | |
689 | +{ | |
690 | + return adp8870_store(dev, buf, count, ADP8870_BLDM4); | |
691 | +} | |
692 | +static DEVICE_ATTR(l4_indoor_dim, 0664, adp8870_bl_l4_indoor_dim_show, | |
693 | + adp8870_bl_l4_indoor_dim_store); | |
694 | + | |
695 | + | |
696 | +static ssize_t adp8870_bl_l3_office_dim_show(struct device *dev, | |
697 | + struct device_attribute *attr, char *buf) | |
698 | +{ | |
699 | + return adp8870_show(dev, buf, ADP8870_BLDM3); | |
700 | +} | |
701 | + | |
702 | +static ssize_t adp8870_bl_l3_office_dim_store(struct device *dev, | |
703 | + struct device_attribute *attr, | |
704 | + const char *buf, size_t count) | |
705 | +{ | |
706 | + return adp8870_store(dev, buf, count, ADP8870_BLDM3); | |
707 | +} | |
708 | +static DEVICE_ATTR(l3_office_dim, 0664, adp8870_bl_l3_office_dim_show, | |
709 | + adp8870_bl_l3_office_dim_store); | |
710 | + | |
711 | +static ssize_t adp8870_bl_l2_bright_dim_show(struct device *dev, | |
712 | + struct device_attribute *attr, char *buf) | |
713 | +{ | |
714 | + return adp8870_show(dev, buf, ADP8870_BLDM2); | |
715 | +} | |
716 | + | |
717 | +static ssize_t adp8870_bl_l2_bright_dim_store(struct device *dev, | |
718 | + struct device_attribute *attr, | |
719 | + const char *buf, size_t count) | |
720 | +{ | |
721 | + return adp8870_store(dev, buf, count, ADP8870_BLDM2); | |
722 | +} | |
723 | +static DEVICE_ATTR(l2_bright_dim, 0664, adp8870_bl_l2_bright_dim_show, | |
724 | + adp8870_bl_l2_bright_dim_store); | |
725 | + | |
726 | +static ssize_t adp8870_bl_l1_daylight_dim_show(struct device *dev, | |
727 | + struct device_attribute *attr, char *buf) | |
728 | +{ | |
729 | + return adp8870_show(dev, buf, ADP8870_BLDM1); | |
730 | +} | |
731 | + | |
732 | +static ssize_t adp8870_bl_l1_daylight_dim_store(struct device *dev, | |
733 | + struct device_attribute *attr, | |
734 | + const char *buf, size_t count) | |
735 | +{ | |
736 | + return adp8870_store(dev, buf, count, ADP8870_BLDM1); | |
737 | +} | |
738 | +static DEVICE_ATTR(l1_daylight_dim, 0664, adp8870_bl_l1_daylight_dim_show, | |
739 | + adp8870_bl_l1_daylight_dim_store); | |
740 | + | |
741 | +#ifdef ADP8870_EXT_FEATURES | |
742 | +static ssize_t adp8870_bl_ambient_light_level_show(struct device *dev, | |
743 | + struct device_attribute *attr, char *buf) | |
744 | +{ | |
745 | + struct adp8870_bl *data = dev_get_drvdata(dev); | |
746 | + int error; | |
747 | + uint8_t reg_val; | |
748 | + uint16_t ret_val; | |
749 | + | |
750 | + mutex_lock(&data->lock); | |
751 | + error = adp8870_read(data->client, ADP8870_PH1LEVL, ®_val); | |
752 | + if (error < 0) { | |
753 | + mutex_unlock(&data->lock); | |
754 | + return error; | |
755 | + } | |
756 | + ret_val = reg_val; | |
757 | + error = adp8870_read(data->client, ADP8870_PH1LEVH, ®_val); | |
758 | + mutex_unlock(&data->lock); | |
759 | + | |
760 | + if (error < 0) | |
761 | + return error; | |
762 | + | |
763 | + /* Return 13-bit conversion value for the first light sensor */ | |
764 | + ret_val += (reg_val & 0x1F) << 8; | |
765 | + | |
766 | + return sprintf(buf, "%u\n", ret_val); | |
767 | +} | |
768 | +static DEVICE_ATTR(ambient_light_level, 0444, | |
769 | + adp8870_bl_ambient_light_level_show, NULL); | |
770 | + | |
771 | +static ssize_t adp8870_bl_ambient_light_zone_show(struct device *dev, | |
772 | + struct device_attribute *attr, char *buf) | |
773 | +{ | |
774 | + struct adp8870_bl *data = dev_get_drvdata(dev); | |
775 | + int error; | |
776 | + uint8_t reg_val; | |
777 | + | |
778 | + mutex_lock(&data->lock); | |
779 | + error = adp8870_read(data->client, ADP8870_CFGR, ®_val); | |
780 | + mutex_unlock(&data->lock); | |
781 | + | |
782 | + if (error < 0) | |
783 | + return error; | |
784 | + | |
785 | + return sprintf(buf, "%u\n", | |
786 | + ((reg_val >> CFGR_BLV_SHIFT) & CFGR_BLV_MASK) + 1); | |
787 | +} | |
788 | + | |
789 | +static ssize_t adp8870_bl_ambient_light_zone_store(struct device *dev, | |
790 | + struct device_attribute *attr, | |
791 | + const char *buf, size_t count) | |
792 | +{ | |
793 | + struct adp8870_bl *data = dev_get_drvdata(dev); | |
794 | + unsigned long val; | |
795 | + uint8_t reg_val; | |
796 | + int ret; | |
797 | + | |
798 | + ret = strict_strtoul(buf, 10, &val); | |
799 | + if (ret) | |
800 | + return ret; | |
801 | + | |
802 | + if (val == 0) { | |
803 | + /* Enable automatic ambient light sensing */ | |
804 | + adp8870_set_bits(data->client, ADP8870_MDCR, CMP_AUTOEN); | |
805 | + } else if ((val > 0) && (val < 6)) { | |
806 | + /* Disable automatic ambient light sensing */ | |
807 | + adp8870_clr_bits(data->client, ADP8870_MDCR, CMP_AUTOEN); | |
808 | + | |
809 | + /* Set user supplied ambient light zone */ | |
810 | + mutex_lock(&data->lock); | |
811 | + adp8870_read(data->client, ADP8870_CFGR, ®_val); | |
812 | + reg_val &= ~(CFGR_BLV_MASK << CFGR_BLV_SHIFT); | |
813 | + reg_val |= (val - 1) << CFGR_BLV_SHIFT; | |
814 | + adp8870_write(data->client, ADP8870_CFGR, reg_val); | |
815 | + mutex_unlock(&data->lock); | |
816 | + } | |
817 | + | |
818 | + return count; | |
819 | +} | |
820 | +static DEVICE_ATTR(ambient_light_zone, 0664, | |
821 | + adp8870_bl_ambient_light_zone_show, | |
822 | + adp8870_bl_ambient_light_zone_store); | |
823 | +#endif | |
824 | + | |
825 | +static struct attribute *adp8870_bl_attributes[] = { | |
826 | + &dev_attr_l5_dark_max.attr, | |
827 | + &dev_attr_l5_dark_dim.attr, | |
828 | + &dev_attr_l4_indoor_max.attr, | |
829 | + &dev_attr_l4_indoor_dim.attr, | |
830 | + &dev_attr_l3_office_max.attr, | |
831 | + &dev_attr_l3_office_dim.attr, | |
832 | + &dev_attr_l2_bright_max.attr, | |
833 | + &dev_attr_l2_bright_dim.attr, | |
834 | + &dev_attr_l1_daylight_max.attr, | |
835 | + &dev_attr_l1_daylight_dim.attr, | |
836 | +#ifdef ADP8870_EXT_FEATURES | |
837 | + &dev_attr_ambient_light_level.attr, | |
838 | + &dev_attr_ambient_light_zone.attr, | |
839 | +#endif | |
840 | + NULL | |
841 | +}; | |
842 | + | |
843 | +static const struct attribute_group adp8870_bl_attr_group = { | |
844 | + .attrs = adp8870_bl_attributes, | |
845 | +}; | |
846 | + | |
847 | +static int __devinit adp8870_probe(struct i2c_client *client, | |
848 | + const struct i2c_device_id *id) | |
849 | +{ | |
850 | + struct backlight_properties props; | |
851 | + struct backlight_device *bl; | |
852 | + struct adp8870_bl *data; | |
853 | + struct adp8870_backlight_platform_data *pdata = | |
854 | + client->dev.platform_data; | |
855 | + uint8_t reg_val; | |
856 | + int ret; | |
857 | + | |
858 | + if (!i2c_check_functionality(client->adapter, | |
859 | + I2C_FUNC_SMBUS_BYTE_DATA)) { | |
860 | + dev_err(&client->dev, "SMBUS Byte Data not Supported\n"); | |
861 | + return -EIO; | |
862 | + } | |
863 | + | |
864 | + if (!pdata) { | |
865 | + dev_err(&client->dev, "no platform data?\n"); | |
866 | + return -EINVAL; | |
867 | + } | |
868 | + | |
869 | + ret = adp8870_read(client, ADP8870_MFDVID, ®_val); | |
870 | + if (ret < 0) | |
871 | + return -EIO; | |
872 | + | |
873 | + if (ADP8870_MANID(reg_val) != ADP8870_MANUFID) { | |
874 | + dev_err(&client->dev, "failed to probe\n"); | |
875 | + return -ENODEV; | |
876 | + } | |
877 | + | |
878 | + data = kzalloc(sizeof(*data), GFP_KERNEL); | |
879 | + if (data == NULL) | |
880 | + return -ENOMEM; | |
881 | + | |
882 | + data->revid = ADP8870_DEVID(reg_val); | |
883 | + data->client = client; | |
884 | + data->pdata = pdata; | |
885 | + data->id = id->driver_data; | |
886 | + data->current_brightness = 0; | |
887 | + i2c_set_clientdata(client, data); | |
888 | + | |
889 | + mutex_init(&data->lock); | |
890 | + | |
891 | + memset(&props, 0, sizeof(props)); | |
892 | + props.max_brightness = props.brightness = ADP8870_MAX_BRIGHTNESS; | |
893 | + bl = backlight_device_register(dev_driver_string(&client->dev), | |
894 | + &client->dev, data, &adp8870_bl_ops, &props); | |
895 | + if (IS_ERR(bl)) { | |
896 | + dev_err(&client->dev, "failed to register backlight\n"); | |
897 | + ret = PTR_ERR(bl); | |
898 | + goto out2; | |
899 | + } | |
900 | + | |
901 | + data->bl = bl; | |
902 | + | |
903 | + if (pdata->en_ambl_sens) | |
904 | + ret = sysfs_create_group(&bl->dev.kobj, | |
905 | + &adp8870_bl_attr_group); | |
906 | + | |
907 | + if (ret) { | |
908 | + dev_err(&client->dev, "failed to register sysfs\n"); | |
909 | + goto out1; | |
910 | + } | |
911 | + | |
912 | + ret = adp8870_bl_setup(bl); | |
913 | + if (ret) { | |
914 | + ret = -EIO; | |
915 | + goto out; | |
916 | + } | |
917 | + | |
918 | + backlight_update_status(bl); | |
919 | + | |
920 | + dev_info(&client->dev, "Rev.%d Backlight\n", data->revid); | |
921 | + | |
922 | + if (pdata->num_leds) | |
923 | + adp8870_led_probe(client); | |
924 | + | |
925 | + return 0; | |
926 | + | |
927 | +out: | |
928 | + if (data->pdata->en_ambl_sens) | |
929 | + sysfs_remove_group(&data->bl->dev.kobj, | |
930 | + &adp8870_bl_attr_group); | |
931 | +out1: | |
932 | + backlight_device_unregister(bl); | |
933 | +out2: | |
934 | + i2c_set_clientdata(client, NULL); | |
935 | + kfree(data); | |
936 | + | |
937 | + return ret; | |
938 | +} | |
939 | + | |
940 | +static int __devexit adp8870_remove(struct i2c_client *client) | |
941 | +{ | |
942 | + struct adp8870_bl *data = i2c_get_clientdata(client); | |
943 | + | |
944 | + adp8870_clr_bits(client, ADP8870_MDCR, NSTBY); | |
945 | + | |
946 | + if (data->led) | |
947 | + adp8870_led_remove(client); | |
948 | + | |
949 | + if (data->pdata->en_ambl_sens) | |
950 | + sysfs_remove_group(&data->bl->dev.kobj, | |
951 | + &adp8870_bl_attr_group); | |
952 | + | |
953 | + backlight_device_unregister(data->bl); | |
954 | + i2c_set_clientdata(client, NULL); | |
955 | + kfree(data); | |
956 | + | |
957 | + return 0; | |
958 | +} | |
959 | + | |
960 | +#ifdef CONFIG_PM | |
961 | +static int adp8870_i2c_suspend(struct i2c_client *client, pm_message_t message) | |
962 | +{ | |
963 | + adp8870_clr_bits(client, ADP8870_MDCR, NSTBY); | |
964 | + | |
965 | + return 0; | |
966 | +} | |
967 | + | |
968 | +static int adp8870_i2c_resume(struct i2c_client *client) | |
969 | +{ | |
970 | + adp8870_set_bits(client, ADP8870_MDCR, NSTBY); | |
971 | + | |
972 | + return 0; | |
973 | +} | |
974 | +#else | |
975 | +#define adp8870_i2c_suspend NULL | |
976 | +#define adp8870_i2c_resume NULL | |
977 | +#endif | |
978 | + | |
979 | +static const struct i2c_device_id adp8870_id[] = { | |
980 | + { "adp8870", 0 }, | |
981 | + { } | |
982 | +}; | |
983 | +MODULE_DEVICE_TABLE(i2c, adp8870_id); | |
984 | + | |
985 | +static struct i2c_driver adp8870_driver = { | |
986 | + .driver = { | |
987 | + .name = KBUILD_MODNAME, | |
988 | + }, | |
989 | + .probe = adp8870_probe, | |
990 | + .remove = __devexit_p(adp8870_remove), | |
991 | + .suspend = adp8870_i2c_suspend, | |
992 | + .resume = adp8870_i2c_resume, | |
993 | + .id_table = adp8870_id, | |
994 | +}; | |
995 | + | |
996 | +static int __init adp8870_init(void) | |
997 | +{ | |
998 | + return i2c_add_driver(&adp8870_driver); | |
999 | +} | |
1000 | +module_init(adp8870_init); | |
1001 | + | |
1002 | +static void __exit adp8870_exit(void) | |
1003 | +{ | |
1004 | + i2c_del_driver(&adp8870_driver); | |
1005 | +} | |
1006 | +module_exit(adp8870_exit); | |
1007 | + | |
1008 | +MODULE_LICENSE("GPL v2"); | |
1009 | +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | |
1010 | +MODULE_DESCRIPTION("ADP8870 Backlight driver"); | |
1011 | +MODULE_ALIAS("platform:adp8870-backlight"); |
include/linux/i2c/adp8870.h
1 | +/* | |
2 | + * Definitions and platform data for Analog Devices | |
3 | + * Backlight drivers ADP8870 | |
4 | + * | |
5 | + * Copyright 2009-2010 Analog Devices Inc. | |
6 | + * | |
7 | + * Licensed under the GPL-2 or later. | |
8 | + */ | |
9 | + | |
10 | +#ifndef __LINUX_I2C_ADP8870_H | |
11 | +#define __LINUX_I2C_ADP8870_H | |
12 | + | |
13 | +#define ID_ADP8870 8870 | |
14 | + | |
15 | +#define ADP8870_MAX_BRIGHTNESS 0x7F | |
16 | +#define FLAG_OFFT_SHIFT 8 | |
17 | + | |
18 | +/* | |
19 | + * LEDs subdevice platform data | |
20 | + */ | |
21 | + | |
22 | +#define ADP8870_LED_DIS_BLINK (0 << FLAG_OFFT_SHIFT) | |
23 | +#define ADP8870_LED_OFFT_600ms (1 << FLAG_OFFT_SHIFT) | |
24 | +#define ADP8870_LED_OFFT_1200ms (2 << FLAG_OFFT_SHIFT) | |
25 | +#define ADP8870_LED_OFFT_1800ms (3 << FLAG_OFFT_SHIFT) | |
26 | + | |
27 | +#define ADP8870_LED_ONT_200ms 0 | |
28 | +#define ADP8870_LED_ONT_600ms 1 | |
29 | +#define ADP8870_LED_ONT_800ms 2 | |
30 | +#define ADP8870_LED_ONT_1200ms 3 | |
31 | + | |
32 | +#define ADP8870_LED_D7 (7) | |
33 | +#define ADP8870_LED_D6 (6) | |
34 | +#define ADP8870_LED_D5 (5) | |
35 | +#define ADP8870_LED_D4 (4) | |
36 | +#define ADP8870_LED_D3 (3) | |
37 | +#define ADP8870_LED_D2 (2) | |
38 | +#define ADP8870_LED_D1 (1) | |
39 | + | |
40 | +/* | |
41 | + * Backlight subdevice platform data | |
42 | + */ | |
43 | + | |
44 | +#define ADP8870_BL_D7 (1 << 6) | |
45 | +#define ADP8870_BL_D6 (1 << 5) | |
46 | +#define ADP8870_BL_D5 (1 << 4) | |
47 | +#define ADP8870_BL_D4 (1 << 3) | |
48 | +#define ADP8870_BL_D3 (1 << 2) | |
49 | +#define ADP8870_BL_D2 (1 << 1) | |
50 | +#define ADP8870_BL_D1 (1 << 0) | |
51 | + | |
52 | +#define ADP8870_FADE_T_DIS 0 /* Fade Timer Disabled */ | |
53 | +#define ADP8870_FADE_T_300ms 1 /* 0.3 Sec */ | |
54 | +#define ADP8870_FADE_T_600ms 2 | |
55 | +#define ADP8870_FADE_T_900ms 3 | |
56 | +#define ADP8870_FADE_T_1200ms 4 | |
57 | +#define ADP8870_FADE_T_1500ms 5 | |
58 | +#define ADP8870_FADE_T_1800ms 6 | |
59 | +#define ADP8870_FADE_T_2100ms 7 | |
60 | +#define ADP8870_FADE_T_2400ms 8 | |
61 | +#define ADP8870_FADE_T_2700ms 9 | |
62 | +#define ADP8870_FADE_T_3000ms 10 | |
63 | +#define ADP8870_FADE_T_3500ms 11 | |
64 | +#define ADP8870_FADE_T_4000ms 12 | |
65 | +#define ADP8870_FADE_T_4500ms 13 | |
66 | +#define ADP8870_FADE_T_5000ms 14 | |
67 | +#define ADP8870_FADE_T_5500ms 15 /* 5.5 Sec */ | |
68 | + | |
69 | +#define ADP8870_FADE_LAW_LINEAR 0 | |
70 | +#define ADP8870_FADE_LAW_SQUARE 1 | |
71 | +#define ADP8870_FADE_LAW_CUBIC1 2 | |
72 | +#define ADP8870_FADE_LAW_CUBIC2 3 | |
73 | + | |
74 | +#define ADP8870_BL_AMBL_FILT_80ms 0 /* Light sensor filter time */ | |
75 | +#define ADP8870_BL_AMBL_FILT_160ms 1 | |
76 | +#define ADP8870_BL_AMBL_FILT_320ms 2 | |
77 | +#define ADP8870_BL_AMBL_FILT_640ms 3 | |
78 | +#define ADP8870_BL_AMBL_FILT_1280ms 4 | |
79 | +#define ADP8870_BL_AMBL_FILT_2560ms 5 | |
80 | +#define ADP8870_BL_AMBL_FILT_5120ms 6 | |
81 | +#define ADP8870_BL_AMBL_FILT_10240ms 7 /* 10.24 sec */ | |
82 | + | |
83 | +/* | |
84 | + * Blacklight current 0..30mA | |
85 | + */ | |
86 | +#define ADP8870_BL_CUR_mA(I) ((I * 127) / 30) | |
87 | + | |
88 | +/* | |
89 | + * L2 comparator current 0..1106uA | |
90 | + */ | |
91 | +#define ADP8870_L2_COMP_CURR_uA(I) ((I * 255) / 1106) | |
92 | + | |
93 | +/* | |
94 | + * L3 comparator current 0..551uA | |
95 | + */ | |
96 | +#define ADP8870_L3_COMP_CURR_uA(I) ((I * 255) / 551) | |
97 | + | |
98 | +/* | |
99 | + * L4 comparator current 0..275uA | |
100 | + */ | |
101 | +#define ADP8870_L4_COMP_CURR_uA(I) ((I * 255) / 275) | |
102 | + | |
103 | +/* | |
104 | + * L5 comparator current 0..138uA | |
105 | + */ | |
106 | +#define ADP8870_L5_COMP_CURR_uA(I) ((I * 255) / 138) | |
107 | + | |
108 | +struct adp8870_backlight_platform_data { | |
109 | + u8 bl_led_assign; /* 1 = Backlight 0 = Individual LED */ | |
110 | + u8 pwm_assign; /* 1 = Enables PWM mode */ | |
111 | + | |
112 | + u8 bl_fade_in; /* Backlight Fade-In Timer */ | |
113 | + u8 bl_fade_out; /* Backlight Fade-Out Timer */ | |
114 | + u8 bl_fade_law; /* fade-on/fade-off transfer characteristic */ | |
115 | + | |
116 | + u8 en_ambl_sens; /* 1 = enable ambient light sensor */ | |
117 | + u8 abml_filt; /* Light sensor filter time */ | |
118 | + | |
119 | + u8 l1_daylight_max; /* use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
120 | + u8 l1_daylight_dim; /* typ = 0, use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
121 | + u8 l2_bright_max; /* use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
122 | + u8 l2_bright_dim; /* typ = 0, use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
123 | + u8 l3_office_max; /* use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
124 | + u8 l3_office_dim; /* typ = 0, use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
125 | + u8 l4_indoor_max; /* use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
126 | + u8 l4_indor_dim; /* typ = 0, use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
127 | + u8 l5_dark_max; /* use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
128 | + u8 l5_dark_dim; /* typ = 0, use BL_CUR_mA(I) 0 <= I <= 30 mA */ | |
129 | + | |
130 | + u8 l2_trip; /* use L2_COMP_CURR_uA(I) 0 <= I <= 1106 uA */ | |
131 | + u8 l2_hyst; /* use L2_COMP_CURR_uA(I) 0 <= I <= 1106 uA */ | |
132 | + u8 l3_trip; /* use L3_COMP_CURR_uA(I) 0 <= I <= 551 uA */ | |
133 | + u8 l3_hyst; /* use L3_COMP_CURR_uA(I) 0 <= I <= 551 uA */ | |
134 | + u8 l4_trip; /* use L4_COMP_CURR_uA(I) 0 <= I <= 275 uA */ | |
135 | + u8 l4_hyst; /* use L4_COMP_CURR_uA(I) 0 <= I <= 275 uA */ | |
136 | + u8 l5_trip; /* use L5_COMP_CURR_uA(I) 0 <= I <= 138 uA */ | |
137 | + u8 l5_hyst; /* use L6_COMP_CURR_uA(I) 0 <= I <= 138 uA */ | |
138 | + | |
139 | + /** | |
140 | + * Independent Current Sinks / LEDS | |
141 | + * Sinks not assigned to the Backlight can be exposed to | |
142 | + * user space using the LEDS CLASS interface | |
143 | + */ | |
144 | + | |
145 | + int num_leds; | |
146 | + struct led_info *leds; | |
147 | + u8 led_fade_in; /* LED Fade-In Timer */ | |
148 | + u8 led_fade_out; /* LED Fade-Out Timer */ | |
149 | + u8 led_fade_law; /* fade-on/fade-off transfer characteristic */ | |
150 | + u8 led_on_time; | |
151 | +}; | |
152 | + | |
153 | +#endif /* __LINUX_I2C_ADP8870_H */ |