1 #include <linux/kernel.h>
2 #include <linux/platform_device.h>
3 #include <linux/leds.h>
5 #include <linux/atmel_pwm.h>
6 #include <linux/slab.h>
10 struct led_classdev cdev
;
11 struct pwm_channel pwmc
;
12 struct gpio_led
*desc
;
19 * For simplicity, we use "brightness" as if it were a linear function
20 * of PWM duty cycle. However, a logarithmic function of duty cycle is
21 * probably a better match for perceived brightness: two is half as bright
22 * as four, four is half as bright as eight, etc
24 static void pwmled_brightness(struct led_classdev
*cdev
, enum led_brightness b
)
28 /* update the duty cycle for the *next* period */
29 led
= container_of(cdev
, struct pwmled
, cdev
);
30 pwm_channel_writel(&led
->pwmc
, PWM_CUPD
, led
->mult
* (unsigned) b
);
34 * NOTE: we reuse the platform_data structure of GPIO leds,
35 * but repurpose its "gpio" number as a PWM channel number.
37 static int __init
pwmled_probe(struct platform_device
*pdev
)
39 const struct gpio_led_platform_data
*pdata
;
44 pdata
= pdev
->dev
.platform_data
;
45 if (!pdata
|| pdata
->num_leds
< 1)
48 leds
= kcalloc(pdata
->num_leds
, sizeof(*leds
), GFP_KERNEL
);
52 for (i
= 0; i
< pdata
->num_leds
; i
++) {
53 struct pwmled
*led
= leds
+ i
;
54 const struct gpio_led
*dat
= pdata
->leds
+ i
;
57 led
->cdev
.name
= dat
->name
;
58 led
->cdev
.brightness
= LED_OFF
;
59 led
->cdev
.brightness_set
= pwmled_brightness
;
60 led
->cdev
.default_trigger
= dat
->default_trigger
;
62 led
->active_low
= dat
->active_low
;
64 status
= pwm_channel_alloc(dat
->gpio
, &led
->pwmc
);
69 * Prescale clock by 2^x, so PWM counts in low MHz.
70 * Start each cycle with the LED active, so increasing
71 * the duty cycle gives us more time on (== brighter).
76 pwm_channel_writel(&led
->pwmc
, PWM_CMR
, tmp
);
79 * Pick a period so PWM cycles at 100+ Hz; and a multiplier
80 * for scaling duty cycle: brightness * mult.
82 tmp
= (led
->pwmc
.mck
/ (1 << 5)) / 100;
85 pwm_channel_writel(&led
->pwmc
, PWM_CDTY
,
86 led
->cdev
.brightness
* 255);
87 pwm_channel_writel(&led
->pwmc
, PWM_CPRD
,
90 pwm_channel_enable(&led
->pwmc
);
92 /* Hand it over to the LED framework */
93 status
= led_classdev_register(&pdev
->dev
, &led
->cdev
);
95 pwm_channel_free(&led
->pwmc
);
100 platform_set_drvdata(pdev
, leds
);
105 for (i
= i
- 1; i
>= 0; i
--) {
106 led_classdev_unregister(&leds
[i
].cdev
);
107 pwm_channel_free(&leds
[i
].pwmc
);
115 static int __exit
pwmled_remove(struct platform_device
*pdev
)
117 const struct gpio_led_platform_data
*pdata
;
121 pdata
= pdev
->dev
.platform_data
;
122 leds
= platform_get_drvdata(pdev
);
124 for (i
= 0; i
< pdata
->num_leds
; i
++) {
125 struct pwmled
*led
= leds
+ i
;
127 led_classdev_unregister(&led
->cdev
);
128 pwm_channel_free(&led
->pwmc
);
132 platform_set_drvdata(pdev
, NULL
);
136 /* work with hotplug and coldplug */
137 MODULE_ALIAS("platform:leds-atmel-pwm");
139 static struct platform_driver pwmled_driver
= {
141 .name
= "leds-atmel-pwm",
142 .owner
= THIS_MODULE
,
144 /* REVISIT add suspend() and resume() methods */
145 .remove
= __exit_p(pwmled_remove
),
148 static int __init
modinit(void)
150 return platform_driver_probe(&pwmled_driver
, pwmled_probe
);
152 module_init(modinit
);
154 static void __exit
modexit(void)
156 platform_driver_unregister(&pwmled_driver
);
158 module_exit(modexit
);
160 MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness");
161 MODULE_LICENSE("GPL");