2008-02-08 15:21:22 +03:00
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/leds.h>
# include <linux/io.h>
# include <linux/atmel_pwm.h>
struct pwmled {
struct led_classdev cdev ;
struct pwm_channel pwmc ;
struct gpio_led * desc ;
u32 mult ;
u8 active_low ;
} ;
/*
* For simplicity , we use " brightness " as if it were a linear function
* of PWM duty cycle . However , a logarithmic function of duty cycle is
* probably a better match for perceived brightness : two is half as bright
* as four , four is half as bright as eight , etc
*/
static void pwmled_brightness ( struct led_classdev * cdev , enum led_brightness b )
{
struct pwmled * led ;
/* update the duty cycle for the *next* period */
led = container_of ( cdev , struct pwmled , cdev ) ;
pwm_channel_writel ( & led - > pwmc , PWM_CUPD , led - > mult * ( unsigned ) b ) ;
}
/*
* NOTE : we reuse the platform_data structure of GPIO leds ,
* but repurpose its " gpio " number as a PWM channel number .
*/
static int __init pwmled_probe ( struct platform_device * pdev )
{
const struct gpio_led_platform_data * pdata ;
struct pwmled * leds ;
2008-05-31 18:18:55 +04:00
int i ;
2008-02-08 15:21:22 +03:00
int status ;
pdata = pdev - > dev . platform_data ;
if ( ! pdata | | pdata - > num_leds < 1 )
return - ENODEV ;
leds = kcalloc ( pdata - > num_leds , sizeof ( * leds ) , GFP_KERNEL ) ;
if ( ! leds )
return - ENOMEM ;
for ( i = 0 ; i < pdata - > num_leds ; i + + ) {
struct pwmled * led = leds + i ;
const struct gpio_led * dat = pdata - > leds + i ;
u32 tmp ;
led - > cdev . name = dat - > name ;
led - > cdev . brightness = LED_OFF ;
led - > cdev . brightness_set = pwmled_brightness ;
led - > cdev . default_trigger = dat - > default_trigger ;
led - > active_low = dat - > active_low ;
status = pwm_channel_alloc ( dat - > gpio , & led - > pwmc ) ;
if ( status < 0 )
goto err ;
/*
* Prescale clock by 2 ^ x , so PWM counts in low MHz .
* Start each cycle with the LED active , so increasing
* the duty cycle gives us more time on ( = = brighter ) .
*/
tmp = 5 ;
if ( ! led - > active_low )
tmp | = PWM_CPR_CPOL ;
pwm_channel_writel ( & led - > pwmc , PWM_CMR , tmp ) ;
/*
* Pick a period so PWM cycles at 100 + Hz ; and a multiplier
* for scaling duty cycle : brightness * mult .
*/
tmp = ( led - > pwmc . mck / ( 1 < < 5 ) ) / 100 ;
tmp / = 255 ;
led - > mult = tmp ;
pwm_channel_writel ( & led - > pwmc , PWM_CDTY ,
led - > cdev . brightness * 255 ) ;
pwm_channel_writel ( & led - > pwmc , PWM_CPRD ,
LED_FULL * tmp ) ;
pwm_channel_enable ( & led - > pwmc ) ;
/* Hand it over to the LED framework */
status = led_classdev_register ( & pdev - > dev , & led - > cdev ) ;
if ( status < 0 ) {
pwm_channel_free ( & led - > pwmc ) ;
goto err ;
}
}
platform_set_drvdata ( pdev , leds ) ;
return 0 ;
err :
if ( i > 0 ) {
for ( i = i - 1 ; i > = 0 ; i - - ) {
led_classdev_unregister ( & leds [ i ] . cdev ) ;
pwm_channel_free ( & leds [ i ] . pwmc ) ;
}
}
kfree ( leds ) ;
return status ;
}
static int __exit pwmled_remove ( struct platform_device * pdev )
{
const struct gpio_led_platform_data * pdata ;
struct pwmled * leds ;
unsigned i ;
pdata = pdev - > dev . platform_data ;
leds = platform_get_drvdata ( pdev ) ;
for ( i = 0 ; i < pdata - > num_leds ; i + + ) {
struct pwmled * led = leds + i ;
led_classdev_unregister ( & led - > cdev ) ;
pwm_channel_free ( & led - > pwmc ) ;
}
kfree ( leds ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
2008-04-16 01:34:30 +04:00
/* work with hotplug and coldplug */
MODULE_ALIAS ( " platform:leds-atmel-pwm " ) ;
2008-02-08 15:21:22 +03:00
static struct platform_driver pwmled_driver = {
. driver = {
. name = " leds-atmel-pwm " ,
. owner = THIS_MODULE ,
} ,
/* REVISIT add suspend() and resume() methods */
. remove = __exit_p ( pwmled_remove ) ,
} ;
static int __init modinit ( void )
{
return platform_driver_probe ( & pwmled_driver , pwmled_probe ) ;
}
module_init ( modinit ) ;
static void __exit modexit ( void )
{
platform_driver_unregister ( & pwmled_driver ) ;
}
module_exit ( modexit ) ;
MODULE_DESCRIPTION ( " Driver for LEDs with PWM-controlled brightness " ) ;
MODULE_LICENSE ( " GPL " ) ;