• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt-6.x.4708/linux/linux-2.6.36/drivers/video/backlight/
1/*
2 * Copyright (C) 2008 Atmel Corporation
3 *
4 * Backlight driver using Atmel PWM peripheral.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 as published by
8 * the Free Software Foundation.
9 */
10#include <linux/init.h>
11#include <linux/kernel.h>
12#include <linux/module.h>
13#include <linux/platform_device.h>
14#include <linux/fb.h>
15#include <linux/clk.h>
16#include <linux/gpio.h>
17#include <linux/backlight.h>
18#include <linux/atmel_pwm.h>
19#include <linux/atmel-pwm-bl.h>
20#include <linux/slab.h>
21
22struct atmel_pwm_bl {
23	const struct atmel_pwm_bl_platform_data	*pdata;
24	struct backlight_device			*bldev;
25	struct platform_device			*pdev;
26	struct pwm_channel			pwmc;
27	int					gpio_on;
28};
29
30static int atmel_pwm_bl_set_intensity(struct backlight_device *bd)
31{
32	struct atmel_pwm_bl *pwmbl = bl_get_data(bd);
33	int intensity = bd->props.brightness;
34	int pwm_duty;
35
36	if (bd->props.power != FB_BLANK_UNBLANK)
37		intensity = 0;
38	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
39		intensity = 0;
40
41	if (pwmbl->pdata->pwm_active_low)
42		pwm_duty = pwmbl->pdata->pwm_duty_min + intensity;
43	else
44		pwm_duty = pwmbl->pdata->pwm_duty_max - intensity;
45
46	if (pwm_duty > pwmbl->pdata->pwm_duty_max)
47		pwm_duty = pwmbl->pdata->pwm_duty_max;
48	if (pwm_duty < pwmbl->pdata->pwm_duty_min)
49		pwm_duty = pwmbl->pdata->pwm_duty_min;
50
51	if (!intensity) {
52		if (pwmbl->gpio_on != -1) {
53			gpio_set_value(pwmbl->gpio_on,
54					0 ^ pwmbl->pdata->on_active_low);
55		}
56		pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty);
57		pwm_channel_disable(&pwmbl->pwmc);
58	} else {
59		pwm_channel_enable(&pwmbl->pwmc);
60		pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty);
61		if (pwmbl->gpio_on != -1) {
62			gpio_set_value(pwmbl->gpio_on,
63					1 ^ pwmbl->pdata->on_active_low);
64		}
65	}
66
67	return 0;
68}
69
70static int atmel_pwm_bl_get_intensity(struct backlight_device *bd)
71{
72	struct atmel_pwm_bl *pwmbl = bl_get_data(bd);
73	u8 intensity;
74
75	if (pwmbl->pdata->pwm_active_low) {
76		intensity = pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY) -
77			pwmbl->pdata->pwm_duty_min;
78	} else {
79		intensity = pwmbl->pdata->pwm_duty_max -
80			pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY);
81	}
82
83	return intensity;
84}
85
86static int atmel_pwm_bl_init_pwm(struct atmel_pwm_bl *pwmbl)
87{
88	unsigned long pwm_rate = pwmbl->pwmc.mck;
89	unsigned long prescale = DIV_ROUND_UP(pwm_rate,
90			(pwmbl->pdata->pwm_frequency *
91			 pwmbl->pdata->pwm_compare_max)) - 1;
92
93	/*
94	 * Prescale must be power of two and maximum 0xf in size because of
95	 * hardware limit. PWM speed will be:
96	 *	PWM module clock speed / (2 ^ prescale).
97	 */
98	prescale = fls(prescale);
99	if (prescale > 0xf)
100		prescale = 0xf;
101
102	pwm_channel_writel(&pwmbl->pwmc, PWM_CMR, prescale);
103	pwm_channel_writel(&pwmbl->pwmc, PWM_CDTY,
104			pwmbl->pdata->pwm_duty_min +
105			pwmbl->bldev->props.brightness);
106	pwm_channel_writel(&pwmbl->pwmc, PWM_CPRD,
107			pwmbl->pdata->pwm_compare_max);
108
109	dev_info(&pwmbl->pdev->dev, "Atmel PWM backlight driver "
110			"(%lu Hz)\n", pwmbl->pwmc.mck /
111			pwmbl->pdata->pwm_compare_max /
112			(1 << prescale));
113
114	return pwm_channel_enable(&pwmbl->pwmc);
115}
116
117static const struct backlight_ops atmel_pwm_bl_ops = {
118	.get_brightness = atmel_pwm_bl_get_intensity,
119	.update_status  = atmel_pwm_bl_set_intensity,
120};
121
122static int atmel_pwm_bl_probe(struct platform_device *pdev)
123{
124	struct backlight_properties props;
125	const struct atmel_pwm_bl_platform_data *pdata;
126	struct backlight_device *bldev;
127	struct atmel_pwm_bl *pwmbl;
128	int retval;
129
130	pwmbl = kzalloc(sizeof(struct atmel_pwm_bl), GFP_KERNEL);
131	if (!pwmbl)
132		return -ENOMEM;
133
134	pwmbl->pdev = pdev;
135
136	pdata = pdev->dev.platform_data;
137	if (!pdata) {
138		retval = -ENODEV;
139		goto err_free_mem;
140	}
141
142	if (pdata->pwm_compare_max < pdata->pwm_duty_max ||
143			pdata->pwm_duty_min > pdata->pwm_duty_max ||
144			pdata->pwm_frequency == 0) {
145		retval = -EINVAL;
146		goto err_free_mem;
147	}
148
149	pwmbl->pdata = pdata;
150	pwmbl->gpio_on = pdata->gpio_on;
151
152	retval = pwm_channel_alloc(pdata->pwm_channel, &pwmbl->pwmc);
153	if (retval)
154		goto err_free_mem;
155
156	if (pwmbl->gpio_on != -1) {
157		retval = gpio_request(pwmbl->gpio_on, "gpio_atmel_pwm_bl");
158		if (retval) {
159			pwmbl->gpio_on = -1;
160			goto err_free_pwm;
161		}
162
163		/* Turn display off by default. */
164		retval = gpio_direction_output(pwmbl->gpio_on,
165				0 ^ pdata->on_active_low);
166		if (retval)
167			goto err_free_gpio;
168	}
169
170	memset(&props, 0, sizeof(struct backlight_properties));
171	props.max_brightness = pdata->pwm_duty_max - pdata->pwm_duty_min;
172	bldev = backlight_device_register("atmel-pwm-bl", &pdev->dev, pwmbl,
173					  &atmel_pwm_bl_ops, &props);
174	if (IS_ERR(bldev)) {
175		retval = PTR_ERR(bldev);
176		goto err_free_gpio;
177	}
178
179	pwmbl->bldev = bldev;
180
181	platform_set_drvdata(pdev, pwmbl);
182
183	/* Power up the backlight by default at middle intesity. */
184	bldev->props.power = FB_BLANK_UNBLANK;
185	bldev->props.brightness = bldev->props.max_brightness / 2;
186
187	retval = atmel_pwm_bl_init_pwm(pwmbl);
188	if (retval)
189		goto err_free_bl_dev;
190
191	atmel_pwm_bl_set_intensity(bldev);
192
193	return 0;
194
195err_free_bl_dev:
196	platform_set_drvdata(pdev, NULL);
197	backlight_device_unregister(bldev);
198err_free_gpio:
199	if (pwmbl->gpio_on != -1)
200		gpio_free(pwmbl->gpio_on);
201err_free_pwm:
202	pwm_channel_free(&pwmbl->pwmc);
203err_free_mem:
204	kfree(pwmbl);
205	return retval;
206}
207
208static int __exit atmel_pwm_bl_remove(struct platform_device *pdev)
209{
210	struct atmel_pwm_bl *pwmbl = platform_get_drvdata(pdev);
211
212	if (pwmbl->gpio_on != -1) {
213		gpio_set_value(pwmbl->gpio_on, 0);
214		gpio_free(pwmbl->gpio_on);
215	}
216	pwm_channel_disable(&pwmbl->pwmc);
217	pwm_channel_free(&pwmbl->pwmc);
218	backlight_device_unregister(pwmbl->bldev);
219	platform_set_drvdata(pdev, NULL);
220	kfree(pwmbl);
221
222	return 0;
223}
224
225static struct platform_driver atmel_pwm_bl_driver = {
226	.driver = {
227		.name = "atmel-pwm-bl",
228	},
229	/* REVISIT add suspend() and resume() */
230	.remove = __exit_p(atmel_pwm_bl_remove),
231};
232
233static int __init atmel_pwm_bl_init(void)
234{
235	return platform_driver_probe(&atmel_pwm_bl_driver, atmel_pwm_bl_probe);
236}
237module_init(atmel_pwm_bl_init);
238
239static void __exit atmel_pwm_bl_exit(void)
240{
241	platform_driver_unregister(&atmel_pwm_bl_driver);
242}
243module_exit(atmel_pwm_bl_exit);
244
245MODULE_AUTHOR("Hans-Christian egtvedt <hans-christian.egtvedt@atmel.com>");
246MODULE_DESCRIPTION("Atmel PWM backlight driver");
247MODULE_LICENSE("GPL");
248