Root/drivers/leds/leds-atmel-pwm.c

1#include <linux/kernel.h>
2#include <linux/platform_device.h>
3#include <linux/leds.h>
4#include <linux/io.h>
5#include <linux/atmel_pwm.h>
6#include <linux/slab.h>
7#include <linux/module.h>
8
9
10struct pwmled {
11    struct led_classdev cdev;
12    struct pwm_channel pwmc;
13    struct gpio_led *desc;
14    u32 mult;
15    u8 active_low;
16};
17
18
19/*
20 * For simplicity, we use "brightness" as if it were a linear function
21 * of PWM duty cycle. However, a logarithmic function of duty cycle is
22 * probably a better match for perceived brightness: two is half as bright
23 * as four, four is half as bright as eight, etc
24 */
25static void pwmled_brightness(struct led_classdev *cdev, enum led_brightness b)
26{
27    struct pwmled *led;
28
29    /* update the duty cycle for the *next* period */
30    led = container_of(cdev, struct pwmled, cdev);
31    pwm_channel_writel(&led->pwmc, PWM_CUPD, led->mult * (unsigned) b);
32}
33
34/*
35 * NOTE: we reuse the platform_data structure of GPIO leds,
36 * but repurpose its "gpio" number as a PWM channel number.
37 */
38static int pwmled_probe(struct platform_device *pdev)
39{
40    const struct gpio_led_platform_data *pdata;
41    struct pwmled *leds;
42    int i;
43    int status;
44
45    pdata = pdev->dev.platform_data;
46    if (!pdata || pdata->num_leds < 1)
47        return -ENODEV;
48
49    leds = devm_kzalloc(&pdev->dev, pdata->num_leds * sizeof(*leds),
50            GFP_KERNEL);
51    if (!leds)
52        return -ENOMEM;
53
54    for (i = 0; i < pdata->num_leds; i++) {
55        struct pwmled *led = leds + i;
56        const struct gpio_led *dat = pdata->leds + i;
57        u32 tmp;
58
59        led->cdev.name = dat->name;
60        led->cdev.brightness = LED_OFF;
61        led->cdev.brightness_set = pwmled_brightness;
62        led->cdev.default_trigger = dat->default_trigger;
63
64        led->active_low = dat->active_low;
65
66        status = pwm_channel_alloc(dat->gpio, &led->pwmc);
67        if (status < 0)
68            goto err;
69
70        /*
71         * Prescale clock by 2^x, so PWM counts in low MHz.
72         * Start each cycle with the LED active, so increasing
73         * the duty cycle gives us more time on (== brighter).
74         */
75        tmp = 5;
76        if (!led->active_low)
77            tmp |= PWM_CPR_CPOL;
78        pwm_channel_writel(&led->pwmc, PWM_CMR, tmp);
79
80        /*
81         * Pick a period so PWM cycles at 100+ Hz; and a multiplier
82         * for scaling duty cycle: brightness * mult.
83         */
84        tmp = (led->pwmc.mck / (1 << 5)) / 100;
85        tmp /= 255;
86        led->mult = tmp;
87        pwm_channel_writel(&led->pwmc, PWM_CDTY,
88                led->cdev.brightness * 255);
89        pwm_channel_writel(&led->pwmc, PWM_CPRD,
90                LED_FULL * tmp);
91
92        pwm_channel_enable(&led->pwmc);
93
94        /* Hand it over to the LED framework */
95        status = led_classdev_register(&pdev->dev, &led->cdev);
96        if (status < 0) {
97            pwm_channel_free(&led->pwmc);
98            goto err;
99        }
100    }
101
102    platform_set_drvdata(pdev, leds);
103    return 0;
104
105err:
106    if (i > 0) {
107        for (i = i - 1; i >= 0; i--) {
108            led_classdev_unregister(&leds[i].cdev);
109            pwm_channel_free(&leds[i].pwmc);
110        }
111    }
112
113    return status;
114}
115
116static int __exit pwmled_remove(struct platform_device *pdev)
117{
118    const struct gpio_led_platform_data *pdata;
119    struct pwmled *leds;
120    unsigned i;
121
122    pdata = pdev->dev.platform_data;
123    leds = platform_get_drvdata(pdev);
124
125    for (i = 0; i < pdata->num_leds; i++) {
126        struct pwmled *led = leds + i;
127
128        led_classdev_unregister(&led->cdev);
129        pwm_channel_free(&led->pwmc);
130    }
131
132    platform_set_drvdata(pdev, NULL);
133    return 0;
134}
135
136static struct platform_driver pwmled_driver = {
137    .driver = {
138        .name = "leds-atmel-pwm",
139        .owner = THIS_MODULE,
140    },
141    /* REVISIT add suspend() and resume() methods */
142    .probe = pwmled_probe,
143    .remove = __exit_p(pwmled_remove),
144};
145
146module_platform_driver(pwmled_driver);
147
148MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness");
149MODULE_LICENSE("GPL");
150MODULE_ALIAS("platform:leds-atmel-pwm");
151

Archive Download this file



interactive