2008-12-04 19:52:33 +03:00
/*
* LED driver for WM8350 driven LEDS .
*
* Copyright ( C ) 2007 , 2008 Wolfson Microelectronics PLC .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/leds.h>
# include <linux/err.h>
# include <linux/mfd/wm8350/pmic.h>
# include <linux/regulator/consumer.h>
/* Microamps */
static const int isink_cur [ ] = {
4 ,
5 ,
6 ,
7 ,
8 ,
10 ,
11 ,
14 ,
16 ,
19 ,
23 ,
27 ,
32 ,
39 ,
46 ,
54 ,
65 ,
77 ,
92 ,
109 ,
130 ,
154 ,
183 ,
218 ,
259 ,
308 ,
367 ,
436 ,
518 ,
616 ,
733 ,
872 ,
1037 ,
1233 ,
1466 ,
1744 ,
2073 ,
2466 ,
2933 ,
3487 ,
4147 ,
4932 ,
5865 ,
6975 ,
8294 ,
9864 ,
11730 ,
13949 ,
16589 ,
19728 ,
23460 ,
27899 ,
33178 ,
39455 ,
46920 ,
55798 ,
66355 ,
78910 ,
93840 ,
111596 ,
132710 ,
157820 ,
187681 ,
223191
} ;
# define to_wm8350_led(led_cdev) \
container_of ( led_cdev , struct wm8350_led , cdev )
static void wm8350_led_enable ( struct wm8350_led * led )
{
int ret ;
if ( led - > enabled )
return ;
ret = regulator_enable ( led - > isink ) ;
if ( ret ! = 0 ) {
dev_err ( led - > cdev . dev , " Failed to enable ISINK: %d \n " , ret ) ;
return ;
}
ret = regulator_enable ( led - > dcdc ) ;
if ( ret ! = 0 ) {
dev_err ( led - > cdev . dev , " Failed to enable DCDC: %d \n " , ret ) ;
regulator_disable ( led - > isink ) ;
return ;
}
led - > enabled = 1 ;
}
static void wm8350_led_disable ( struct wm8350_led * led )
{
int ret ;
if ( ! led - > enabled )
return ;
ret = regulator_disable ( led - > dcdc ) ;
if ( ret ! = 0 ) {
dev_err ( led - > cdev . dev , " Failed to disable DCDC: %d \n " , ret ) ;
return ;
}
ret = regulator_disable ( led - > isink ) ;
if ( ret ! = 0 ) {
dev_err ( led - > cdev . dev , " Failed to disable ISINK: %d \n " , ret ) ;
regulator_enable ( led - > dcdc ) ;
return ;
}
led - > enabled = 0 ;
}
static void led_work ( struct work_struct * work )
{
struct wm8350_led * led = container_of ( work , struct wm8350_led , work ) ;
int ret ;
int uA ;
unsigned long flags ;
mutex_lock ( & led - > mutex ) ;
spin_lock_irqsave ( & led - > value_lock , flags ) ;
if ( led - > value = = LED_OFF ) {
spin_unlock_irqrestore ( & led - > value_lock , flags ) ;
wm8350_led_disable ( led ) ;
goto out ;
}
/* This scales linearly into the index of valid current
* settings which results in a linear scaling of perceived
* brightness due to the non - linear current settings provided
* by the hardware .
*/
uA = ( led - > max_uA_index * led - > value ) / LED_FULL ;
spin_unlock_irqrestore ( & led - > value_lock , flags ) ;
BUG_ON ( uA > = ARRAY_SIZE ( isink_cur ) ) ;
ret = regulator_set_current_limit ( led - > isink , isink_cur [ uA ] ,
isink_cur [ uA ] ) ;
if ( ret ! = 0 )
dev_err ( led - > cdev . dev , " Failed to set %duA: %d \n " ,
isink_cur [ uA ] , ret ) ;
wm8350_led_enable ( led ) ;
out :
mutex_unlock ( & led - > mutex ) ;
}
static void wm8350_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
struct wm8350_led * led = to_wm8350_led ( led_cdev ) ;
unsigned long flags ;
spin_lock_irqsave ( & led - > value_lock , flags ) ;
led - > value = value ;
schedule_work ( & led - > work ) ;
spin_unlock_irqrestore ( & led - > value_lock , flags ) ;
}
static void wm8350_led_shutdown ( struct platform_device * pdev )
{
struct wm8350_led * led = platform_get_drvdata ( pdev ) ;
mutex_lock ( & led - > mutex ) ;
led - > value = LED_OFF ;
wm8350_led_disable ( led ) ;
mutex_unlock ( & led - > mutex ) ;
}
static int wm8350_led_probe ( struct platform_device * pdev )
{
struct regulator * isink , * dcdc ;
struct wm8350_led * led ;
struct wm8350_led_platform_data * pdata = pdev - > dev . platform_data ;
int ret , i ;
if ( pdata = = NULL ) {
dev_err ( & pdev - > dev , " no platform data \n " ) ;
return - ENODEV ;
}
if ( pdata - > max_uA < isink_cur [ 0 ] ) {
dev_err ( & pdev - > dev , " Invalid maximum current %duA \n " ,
pdata - > max_uA ) ;
return - EINVAL ;
}
isink = regulator_get ( & pdev - > dev , " led_isink " ) ;
if ( IS_ERR ( isink ) ) {
printk ( KERN_ERR " %s: cant get ISINK \n " , __func__ ) ;
return PTR_ERR ( isink ) ;
}
dcdc = regulator_get ( & pdev - > dev , " led_vcc " ) ;
if ( IS_ERR ( dcdc ) ) {
printk ( KERN_ERR " %s: cant get DCDC \n " , __func__ ) ;
ret = PTR_ERR ( dcdc ) ;
goto err_isink ;
}
led = kzalloc ( sizeof ( * led ) , GFP_KERNEL ) ;
if ( led = = NULL ) {
ret = - ENOMEM ;
goto err_dcdc ;
}
led - > cdev . brightness_set = wm8350_led_set ;
led - > cdev . default_trigger = pdata - > default_trigger ;
led - > cdev . name = pdata - > name ;
2009-01-08 20:55:03 +03:00
led - > cdev . flags | = LED_CORE_SUSPENDRESUME ;
2008-12-04 19:52:33 +03:00
led - > enabled = regulator_is_enabled ( isink ) ;
led - > isink = isink ;
led - > dcdc = dcdc ;
for ( i = 0 ; i < ARRAY_SIZE ( isink_cur ) - 1 ; i + + )
if ( isink_cur [ i ] > = pdata - > max_uA )
break ;
led - > max_uA_index = i ;
if ( pdata - > max_uA ! = isink_cur [ i ] )
dev_warn ( & pdev - > dev ,
" Maximum current %duA is not directly supported, "
" check platform data \n " ,
pdata - > max_uA ) ;
spin_lock_init ( & led - > value_lock ) ;
mutex_init ( & led - > mutex ) ;
INIT_WORK ( & led - > work , led_work ) ;
led - > value = LED_OFF ;
platform_set_drvdata ( pdev , led ) ;
ret = led_classdev_register ( & pdev - > dev , & led - > cdev ) ;
if ( ret < 0 )
goto err_led ;
return 0 ;
err_led :
kfree ( led ) ;
err_dcdc :
regulator_put ( dcdc ) ;
err_isink :
regulator_put ( isink ) ;
return ret ;
}
static int wm8350_led_remove ( struct platform_device * pdev )
{
struct wm8350_led * led = platform_get_drvdata ( pdev ) ;
led_classdev_unregister ( & led - > cdev ) ;
flush_scheduled_work ( ) ;
wm8350_led_disable ( led ) ;
regulator_put ( led - > dcdc ) ;
regulator_put ( led - > isink ) ;
kfree ( led ) ;
return 0 ;
}
static struct platform_driver wm8350_led_driver = {
. driver = {
. name = " wm8350-led " ,
. owner = THIS_MODULE ,
} ,
. probe = wm8350_led_probe ,
. remove = wm8350_led_remove ,
. shutdown = wm8350_led_shutdown ,
} ;
static int __devinit wm8350_led_init ( void )
{
return platform_driver_register ( & wm8350_led_driver ) ;
}
module_init ( wm8350_led_init ) ;
static void wm8350_led_exit ( void )
{
platform_driver_unregister ( & wm8350_led_driver ) ;
}
module_exit ( wm8350_led_exit ) ;
MODULE_AUTHOR ( " Mark Brown " ) ;
MODULE_DESCRIPTION ( " WM8350 LED driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:wm8350-led " ) ;