diff options
author | Andrea Bastoni <bastoni@cs.unc.edu> | 2010-05-30 19:16:45 -0400 |
---|---|---|
committer | Andrea Bastoni <bastoni@cs.unc.edu> | 2010-05-30 19:16:45 -0400 |
commit | ada47b5fe13d89735805b566185f4885f5a3f750 (patch) | |
tree | 644b88f8a71896307d71438e9b3af49126ffb22b /drivers/video/backlight/88pm860x_bl.c | |
parent | 43e98717ad40a4ae64545b5ba047c7b86aa44f4f (diff) | |
parent | 3280f21d43ee541f97f8cda5792150d2dbec20d5 (diff) |
Merge branch 'wip-2.6.34' into old-private-masterarchived-private-master
Diffstat (limited to 'drivers/video/backlight/88pm860x_bl.c')
-rw-r--r-- | drivers/video/backlight/88pm860x_bl.c | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/drivers/video/backlight/88pm860x_bl.c b/drivers/video/backlight/88pm860x_bl.c new file mode 100644 index 000000000000..68d2518fadaa --- /dev/null +++ b/drivers/video/backlight/88pm860x_bl.c | |||
@@ -0,0 +1,307 @@ | |||
1 | /* | ||
2 | * Backlight driver for Marvell Semiconductor 88PM8606 | ||
3 | * | ||
4 | * Copyright (C) 2009 Marvell International Ltd. | ||
5 | * Haojian Zhuang <haojian.zhuang@marvell.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/fb.h> | ||
16 | #include <linux/i2c.h> | ||
17 | #include <linux/backlight.h> | ||
18 | #include <linux/mfd/88pm860x.h> | ||
19 | #include <linux/slab.h> | ||
20 | |||
21 | #define MAX_BRIGHTNESS (0xFF) | ||
22 | #define MIN_BRIGHTNESS (0) | ||
23 | |||
24 | #define CURRENT_MASK (0x1F << 1) | ||
25 | |||
26 | struct pm860x_backlight_data { | ||
27 | struct pm860x_chip *chip; | ||
28 | struct i2c_client *i2c; | ||
29 | int current_brightness; | ||
30 | int port; | ||
31 | int pwm; | ||
32 | int iset; | ||
33 | }; | ||
34 | |||
35 | static inline int wled_a(int port) | ||
36 | { | ||
37 | int ret; | ||
38 | |||
39 | ret = ((port - PM8606_BACKLIGHT1) << 1) + 2; | ||
40 | return ret; | ||
41 | } | ||
42 | |||
43 | static inline int wled_b(int port) | ||
44 | { | ||
45 | int ret; | ||
46 | |||
47 | ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; | ||
48 | return ret; | ||
49 | } | ||
50 | |||
51 | /* WLED2 & WLED3 share the same IDC */ | ||
52 | static inline int wled_idc(int port) | ||
53 | { | ||
54 | int ret; | ||
55 | |||
56 | switch (port) { | ||
57 | case PM8606_BACKLIGHT1: | ||
58 | case PM8606_BACKLIGHT2: | ||
59 | ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; | ||
60 | break; | ||
61 | case PM8606_BACKLIGHT3: | ||
62 | default: | ||
63 | ret = ((port - PM8606_BACKLIGHT2) << 1) + 3; | ||
64 | break; | ||
65 | } | ||
66 | return ret; | ||
67 | } | ||
68 | |||
69 | static int pm860x_backlight_set(struct backlight_device *bl, int brightness) | ||
70 | { | ||
71 | struct pm860x_backlight_data *data = bl_get_data(bl); | ||
72 | struct pm860x_chip *chip = data->chip; | ||
73 | unsigned char value; | ||
74 | int ret; | ||
75 | |||
76 | if (brightness > MAX_BRIGHTNESS) | ||
77 | value = MAX_BRIGHTNESS; | ||
78 | else | ||
79 | value = brightness; | ||
80 | |||
81 | ret = pm860x_reg_write(data->i2c, wled_a(data->port), value); | ||
82 | if (ret < 0) | ||
83 | goto out; | ||
84 | |||
85 | if ((data->current_brightness == 0) && brightness) { | ||
86 | if (data->iset) { | ||
87 | ret = pm860x_set_bits(data->i2c, wled_idc(data->port), | ||
88 | CURRENT_MASK, data->iset); | ||
89 | if (ret < 0) | ||
90 | goto out; | ||
91 | } | ||
92 | if (data->pwm) { | ||
93 | ret = pm860x_set_bits(data->i2c, PM8606_PWM, | ||
94 | PM8606_PWM_FREQ_MASK, data->pwm); | ||
95 | if (ret < 0) | ||
96 | goto out; | ||
97 | } | ||
98 | if (brightness == MAX_BRIGHTNESS) { | ||
99 | /* set WLED_ON bit as 100% */ | ||
100 | ret = pm860x_set_bits(data->i2c, wled_b(data->port), | ||
101 | PM8606_WLED_ON, PM8606_WLED_ON); | ||
102 | } | ||
103 | } else { | ||
104 | if (brightness == MAX_BRIGHTNESS) { | ||
105 | /* set WLED_ON bit as 100% */ | ||
106 | ret = pm860x_set_bits(data->i2c, wled_b(data->port), | ||
107 | PM8606_WLED_ON, PM8606_WLED_ON); | ||
108 | } else { | ||
109 | /* clear WLED_ON bit since it's not 100% */ | ||
110 | ret = pm860x_set_bits(data->i2c, wled_b(data->port), | ||
111 | PM8606_WLED_ON, 0); | ||
112 | } | ||
113 | } | ||
114 | if (ret < 0) | ||
115 | goto out; | ||
116 | |||
117 | dev_dbg(chip->dev, "set brightness %d\n", value); | ||
118 | data->current_brightness = value; | ||
119 | return 0; | ||
120 | out: | ||
121 | dev_dbg(chip->dev, "set brightness %d failure with return " | ||
122 | "value:%d\n", value, ret); | ||
123 | return ret; | ||
124 | } | ||
125 | |||
126 | static int pm860x_backlight_update_status(struct backlight_device *bl) | ||
127 | { | ||
128 | int brightness = bl->props.brightness; | ||
129 | |||
130 | if (bl->props.power != FB_BLANK_UNBLANK) | ||
131 | brightness = 0; | ||
132 | |||
133 | if (bl->props.fb_blank != FB_BLANK_UNBLANK) | ||
134 | brightness = 0; | ||
135 | |||
136 | if (bl->props.state & BL_CORE_SUSPENDED) | ||
137 | brightness = 0; | ||
138 | |||
139 | return pm860x_backlight_set(bl, brightness); | ||
140 | } | ||
141 | |||
142 | static int pm860x_backlight_get_brightness(struct backlight_device *bl) | ||
143 | { | ||
144 | struct pm860x_backlight_data *data = bl_get_data(bl); | ||
145 | struct pm860x_chip *chip = data->chip; | ||
146 | int ret; | ||
147 | |||
148 | ret = pm860x_reg_read(data->i2c, wled_a(data->port)); | ||
149 | if (ret < 0) | ||
150 | goto out; | ||
151 | data->current_brightness = ret; | ||
152 | dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); | ||
153 | return data->current_brightness; | ||
154 | out: | ||
155 | return -EINVAL; | ||
156 | } | ||
157 | |||
158 | static struct backlight_ops pm860x_backlight_ops = { | ||
159 | .options = BL_CORE_SUSPENDRESUME, | ||
160 | .update_status = pm860x_backlight_update_status, | ||
161 | .get_brightness = pm860x_backlight_get_brightness, | ||
162 | }; | ||
163 | |||
164 | static int __check_device(struct pm860x_backlight_pdata *pdata, char *name) | ||
165 | { | ||
166 | struct pm860x_backlight_pdata *p = pdata; | ||
167 | int ret = -EINVAL; | ||
168 | |||
169 | while (p && p->id) { | ||
170 | if ((p->id != PM8606_ID_BACKLIGHT) || (p->flags < 0)) | ||
171 | break; | ||
172 | |||
173 | if (!strncmp(name, pm860x_backlight_name[p->flags], | ||
174 | MFD_NAME_SIZE)) { | ||
175 | ret = (int)p->flags; | ||
176 | break; | ||
177 | } | ||
178 | p++; | ||
179 | } | ||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | static int pm860x_backlight_probe(struct platform_device *pdev) | ||
184 | { | ||
185 | struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); | ||
186 | struct pm860x_platform_data *pm860x_pdata; | ||
187 | struct pm860x_backlight_pdata *pdata = NULL; | ||
188 | struct pm860x_backlight_data *data; | ||
189 | struct backlight_device *bl; | ||
190 | struct resource *res; | ||
191 | struct backlight_properties props; | ||
192 | unsigned char value; | ||
193 | char name[MFD_NAME_SIZE]; | ||
194 | int ret; | ||
195 | |||
196 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | ||
197 | if (res == NULL) { | ||
198 | dev_err(&pdev->dev, "No I/O resource!\n"); | ||
199 | return -EINVAL; | ||
200 | } | ||
201 | |||
202 | if (pdev->dev.parent->platform_data) { | ||
203 | pm860x_pdata = pdev->dev.parent->platform_data; | ||
204 | pdata = pm860x_pdata->backlight; | ||
205 | } | ||
206 | if (pdata == NULL) { | ||
207 | dev_err(&pdev->dev, "platform data isn't assigned to " | ||
208 | "backlight\n"); | ||
209 | return -EINVAL; | ||
210 | } | ||
211 | |||
212 | data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL); | ||
213 | if (data == NULL) | ||
214 | return -ENOMEM; | ||
215 | strncpy(name, res->name, MFD_NAME_SIZE); | ||
216 | data->chip = chip; | ||
217 | data->i2c = (chip->id == CHIP_PM8606) ? chip->client \ | ||
218 | : chip->companion; | ||
219 | data->current_brightness = MAX_BRIGHTNESS; | ||
220 | data->pwm = pdata->pwm; | ||
221 | data->iset = pdata->iset; | ||
222 | data->port = __check_device(pdata, name); | ||
223 | if (data->port < 0) { | ||
224 | dev_err(&pdev->dev, "wrong platform data is assigned"); | ||
225 | return -EINVAL; | ||
226 | } | ||
227 | |||
228 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
229 | props.max_brightness = MAX_BRIGHTNESS; | ||
230 | bl = backlight_device_register(name, &pdev->dev, data, | ||
231 | &pm860x_backlight_ops, &props); | ||
232 | if (IS_ERR(bl)) { | ||
233 | dev_err(&pdev->dev, "failed to register backlight\n"); | ||
234 | kfree(data); | ||
235 | return PTR_ERR(bl); | ||
236 | } | ||
237 | bl->props.brightness = MAX_BRIGHTNESS; | ||
238 | |||
239 | platform_set_drvdata(pdev, bl); | ||
240 | |||
241 | /* Enable reference VSYS */ | ||
242 | ret = pm860x_reg_read(data->i2c, PM8606_VSYS); | ||
243 | if (ret < 0) | ||
244 | goto out; | ||
245 | if ((ret & PM8606_VSYS_EN) == 0) { | ||
246 | value = ret | PM8606_VSYS_EN; | ||
247 | ret = pm860x_reg_write(data->i2c, PM8606_VSYS, value); | ||
248 | if (ret < 0) | ||
249 | goto out; | ||
250 | } | ||
251 | /* Enable reference OSC */ | ||
252 | ret = pm860x_reg_read(data->i2c, PM8606_MISC); | ||
253 | if (ret < 0) | ||
254 | goto out; | ||
255 | if ((ret & PM8606_MISC_OSC_EN) == 0) { | ||
256 | value = ret | PM8606_MISC_OSC_EN; | ||
257 | ret = pm860x_reg_write(data->i2c, PM8606_MISC, value); | ||
258 | if (ret < 0) | ||
259 | goto out; | ||
260 | } | ||
261 | /* read current backlight */ | ||
262 | ret = pm860x_backlight_get_brightness(bl); | ||
263 | if (ret < 0) | ||
264 | goto out; | ||
265 | |||
266 | backlight_update_status(bl); | ||
267 | return 0; | ||
268 | out: | ||
269 | kfree(data); | ||
270 | return ret; | ||
271 | } | ||
272 | |||
273 | static int pm860x_backlight_remove(struct platform_device *pdev) | ||
274 | { | ||
275 | struct backlight_device *bl = platform_get_drvdata(pdev); | ||
276 | struct pm860x_backlight_data *data = bl_get_data(bl); | ||
277 | |||
278 | backlight_device_unregister(bl); | ||
279 | kfree(data); | ||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | static struct platform_driver pm860x_backlight_driver = { | ||
284 | .driver = { | ||
285 | .name = "88pm860x-backlight", | ||
286 | .owner = THIS_MODULE, | ||
287 | }, | ||
288 | .probe = pm860x_backlight_probe, | ||
289 | .remove = pm860x_backlight_remove, | ||
290 | }; | ||
291 | |||
292 | static int __init pm860x_backlight_init(void) | ||
293 | { | ||
294 | return platform_driver_register(&pm860x_backlight_driver); | ||
295 | } | ||
296 | module_init(pm860x_backlight_init); | ||
297 | |||
298 | static void __exit pm860x_backlight_exit(void) | ||
299 | { | ||
300 | platform_driver_unregister(&pm860x_backlight_driver); | ||
301 | } | ||
302 | module_exit(pm860x_backlight_exit); | ||
303 | |||
304 | MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606"); | ||
305 | MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); | ||
306 | MODULE_LICENSE("GPL"); | ||
307 | MODULE_ALIAS("platform:88pm860x-backlight"); | ||