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