2011-04-27 11:14:07 +04:00
/*
* MFD driver for TWL6040 audio device
*
* Authors : Misael Lopez Cruz < misael . lopez @ ti . com >
* Jorge Eduardo Candelaria < jorge . candelaria @ ti . com >
* Peter Ujfalusi < peter . ujfalusi @ ti . com >
*
* Copyright : ( C ) 2011 Texas Instruments , Inc .
*
* 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 .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/kernel.h>
2012-05-02 17:54:42 +04:00
# include <linux/err.h>
2011-04-27 11:14:07 +04:00
# include <linux/platform_device.h>
2012-05-16 15:11:58 +04:00
# include <linux/of.h>
# include <linux/of_irq.h>
# include <linux/of_gpio.h>
# include <linux/of_platform.h>
2011-04-27 11:14:07 +04:00
# include <linux/gpio.h>
# include <linux/delay.h>
2012-04-03 12:56:51 +04:00
# include <linux/i2c.h>
# include <linux/regmap.h>
2011-04-27 11:14:07 +04:00
# include <linux/mfd/core.h>
# include <linux/mfd/twl6040.h>
2012-05-02 17:54:42 +04:00
# include <linux/regulator/consumer.h>
2011-04-27 11:14:07 +04:00
2011-10-12 12:57:54 +04:00
# define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1)
2012-05-02 17:54:42 +04:00
# define TWL6040_NUM_SUPPLIES (2)
2011-10-12 12:57:54 +04:00
2012-05-23 18:23:21 +04:00
static bool twl6040_has_vibra ( struct twl6040_platform_data * pdata ,
struct device_node * node )
{
if ( pdata & & pdata - > vibra )
return true ;
# ifdef CONFIG_OF
if ( of_find_node_by_name ( node , " vibra " ) )
return true ;
# endif
return false ;
}
2011-04-27 11:14:07 +04:00
int twl6040_reg_read ( struct twl6040 * twl6040 , unsigned int reg )
{
int ret ;
2012-04-03 12:56:51 +04:00
unsigned int val ;
2011-04-27 11:14:07 +04:00
2011-10-12 12:57:54 +04:00
/* Vibra control registers from cache */
if ( unlikely ( reg = = TWL6040_REG_VIBCTLL | |
reg = = TWL6040_REG_VIBCTLR ) ) {
val = twl6040 - > vibra_ctrl_cache [ VIBRACTRL_MEMBER ( reg ) ] ;
} else {
2012-04-03 12:56:51 +04:00
ret = regmap_read ( twl6040 - > regmap , reg , & val ) ;
2012-07-11 06:06:34 +04:00
if ( ret < 0 )
2011-10-12 12:57:54 +04:00
return ret ;
2011-04-27 11:14:07 +04:00
}
return val ;
}
EXPORT_SYMBOL ( twl6040_reg_read ) ;
int twl6040_reg_write ( struct twl6040 * twl6040 , unsigned int reg , u8 val )
{
int ret ;
2012-04-03 12:56:51 +04:00
ret = regmap_write ( twl6040 - > regmap , reg , val ) ;
2011-10-12 12:57:54 +04:00
/* Cache the vibra control registers */
if ( reg = = TWL6040_REG_VIBCTLL | | reg = = TWL6040_REG_VIBCTLR )
twl6040 - > vibra_ctrl_cache [ VIBRACTRL_MEMBER ( reg ) ] = val ;
2011-04-27 11:14:07 +04:00
return ret ;
}
EXPORT_SYMBOL ( twl6040_reg_write ) ;
int twl6040_set_bits ( struct twl6040 * twl6040 , unsigned int reg , u8 mask )
{
2012-07-11 06:06:34 +04:00
return regmap_update_bits ( twl6040 - > regmap , reg , mask , mask ) ;
2011-04-27 11:14:07 +04:00
}
EXPORT_SYMBOL ( twl6040_set_bits ) ;
int twl6040_clear_bits ( struct twl6040 * twl6040 , unsigned int reg , u8 mask )
{
2012-07-11 06:06:34 +04:00
return regmap_update_bits ( twl6040 - > regmap , reg , mask , 0 ) ;
2011-04-27 11:14:07 +04:00
}
EXPORT_SYMBOL ( twl6040_clear_bits ) ;
/* twl6040 codec manual power-up sequence */
2012-10-11 15:55:30 +04:00
static int twl6040_power_up_manual ( struct twl6040 * twl6040 )
2011-04-27 11:14:07 +04:00
{
u8 ldoctl , ncpctl , lppllctl ;
int ret ;
/* enable high-side LDO, reference system and internal oscillator */
ldoctl = TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA ;
ret = twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
if ( ret )
return ret ;
usleep_range ( 10000 , 10500 ) ;
/* enable negative charge pump */
ncpctl = TWL6040_NCPENA ;
ret = twl6040_reg_write ( twl6040 , TWL6040_REG_NCPCTL , ncpctl ) ;
if ( ret )
goto ncp_err ;
usleep_range ( 1000 , 1500 ) ;
/* enable low-side LDO */
ldoctl | = TWL6040_LSLDOENA ;
ret = twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
if ( ret )
goto lsldo_err ;
usleep_range ( 1000 , 1500 ) ;
/* enable low-power PLL */
lppllctl = TWL6040_LPLLENA ;
ret = twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL , lppllctl ) ;
if ( ret )
goto lppll_err ;
usleep_range ( 5000 , 5500 ) ;
/* disable internal oscillator */
ldoctl & = ~ TWL6040_OSCENA ;
ret = twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
if ( ret )
goto osc_err ;
return 0 ;
osc_err :
lppllctl & = ~ TWL6040_LPLLENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL , lppllctl ) ;
lppll_err :
ldoctl & = ~ TWL6040_LSLDOENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
lsldo_err :
ncpctl & = ~ TWL6040_NCPENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_NCPCTL , ncpctl ) ;
ncp_err :
ldoctl & = ~ ( TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA ) ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
2012-10-11 15:55:30 +04:00
dev_err ( twl6040 - > dev , " manual power-up failed \n " ) ;
2011-04-27 11:14:07 +04:00
return ret ;
}
/* twl6040 manual power-down sequence */
2012-10-11 15:55:30 +04:00
static void twl6040_power_down_manual ( struct twl6040 * twl6040 )
2011-04-27 11:14:07 +04:00
{
u8 ncpctl , ldoctl , lppllctl ;
ncpctl = twl6040_reg_read ( twl6040 , TWL6040_REG_NCPCTL ) ;
ldoctl = twl6040_reg_read ( twl6040 , TWL6040_REG_LDOCTL ) ;
lppllctl = twl6040_reg_read ( twl6040 , TWL6040_REG_LPPLLCTL ) ;
/* enable internal oscillator */
ldoctl | = TWL6040_OSCENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
usleep_range ( 1000 , 1500 ) ;
/* disable low-power PLL */
lppllctl & = ~ TWL6040_LPLLENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL , lppllctl ) ;
/* disable low-side LDO */
ldoctl & = ~ TWL6040_LSLDOENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
/* disable negative charge pump */
ncpctl & = ~ TWL6040_NCPENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_NCPCTL , ncpctl ) ;
/* disable high-side LDO, reference system and internal oscillator */
ldoctl & = ~ ( TWL6040_HSLDOENA | TWL6040_REFENA | TWL6040_OSCENA ) ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LDOCTL , ldoctl ) ;
}
2012-10-11 15:55:31 +04:00
static irqreturn_t twl6040_readyint_handler ( int irq , void * data )
2011-04-27 11:14:07 +04:00
{
struct twl6040 * twl6040 = data ;
2012-10-11 15:55:31 +04:00
complete ( & twl6040 - > ready ) ;
2011-04-27 11:14:07 +04:00
2012-10-11 15:55:31 +04:00
return IRQ_HANDLED ;
}
2011-04-27 11:14:07 +04:00
2012-10-11 15:55:31 +04:00
static irqreturn_t twl6040_thint_handler ( int irq , void * data )
{
struct twl6040 * twl6040 = data ;
u8 status ;
status = twl6040_reg_read ( twl6040 , TWL6040_REG_STATUS ) ;
if ( status & TWL6040_TSHUTDET ) {
dev_warn ( twl6040 - > dev , " Thermal shutdown, powering-off " ) ;
twl6040_power ( twl6040 , 0 ) ;
} else {
dev_warn ( twl6040 - > dev , " Leaving thermal shutdown, powering-on " ) ;
twl6040_power ( twl6040 , 1 ) ;
2011-04-27 11:14:07 +04:00
}
return IRQ_HANDLED ;
}
2012-10-11 15:55:30 +04:00
static int twl6040_power_up_automatic ( struct twl6040 * twl6040 )
2011-04-27 11:14:07 +04:00
{
int time_left ;
2012-10-11 15:55:30 +04:00
gpio_set_value ( twl6040 - > audpwron , 1 ) ;
2011-04-27 11:14:07 +04:00
time_left = wait_for_completion_timeout ( & twl6040 - > ready ,
msecs_to_jiffies ( 144 ) ) ;
if ( ! time_left ) {
2012-10-11 15:55:30 +04:00
u8 intid ;
dev_warn ( twl6040 - > dev , " timeout waiting for READYINT \n " ) ;
2011-04-27 11:14:07 +04:00
intid = twl6040_reg_read ( twl6040 , TWL6040_REG_INTID ) ;
if ( ! ( intid & TWL6040_READYINT ) ) {
2012-10-11 15:55:30 +04:00
dev_err ( twl6040 - > dev , " automatic power-up failed \n " ) ;
gpio_set_value ( twl6040 - > audpwron , 0 ) ;
2011-04-27 11:14:07 +04:00
return - ETIMEDOUT ;
}
}
return 0 ;
}
int twl6040_power ( struct twl6040 * twl6040 , int on )
{
int ret = 0 ;
mutex_lock ( & twl6040 - > mutex ) ;
if ( on ) {
/* already powered-up */
if ( twl6040 - > power_count + + )
goto out ;
2012-10-11 15:55:30 +04:00
if ( gpio_is_valid ( twl6040 - > audpwron ) ) {
/* use automatic power-up sequence */
ret = twl6040_power_up_automatic ( twl6040 ) ;
2011-04-27 11:14:07 +04:00
if ( ret ) {
twl6040 - > power_count = 0 ;
goto out ;
}
} else {
/* use manual power-up sequence */
2012-10-11 15:55:30 +04:00
ret = twl6040_power_up_manual ( twl6040 ) ;
2011-04-27 11:14:07 +04:00
if ( ret ) {
twl6040 - > power_count = 0 ;
goto out ;
}
}
2011-07-04 11:28:28 +04:00
/* Default PLL configuration after power up */
twl6040 - > pll = TWL6040_SYSCLK_SEL_LPPLL ;
2011-04-27 11:14:07 +04:00
twl6040 - > sysclk = 19200000 ;
2012-01-14 23:58:43 +04:00
twl6040 - > mclk = 32768 ;
2011-04-27 11:14:07 +04:00
} else {
/* already powered-down */
if ( ! twl6040 - > power_count ) {
2011-09-15 16:39:23 +04:00
dev_err ( twl6040 - > dev ,
2011-04-27 11:14:07 +04:00
" device is already powered-off \n " ) ;
ret = - EPERM ;
goto out ;
}
if ( - - twl6040 - > power_count )
goto out ;
2012-10-11 15:55:30 +04:00
if ( gpio_is_valid ( twl6040 - > audpwron ) ) {
2011-04-27 11:14:07 +04:00
/* use AUDPWRON line */
2012-10-11 15:55:30 +04:00
gpio_set_value ( twl6040 - > audpwron , 0 ) ;
2011-04-27 11:14:07 +04:00
/* power-down sequence latency */
usleep_range ( 500 , 700 ) ;
} else {
/* use manual power-down sequence */
2012-10-11 15:55:30 +04:00
twl6040_power_down_manual ( twl6040 ) ;
2011-04-27 11:14:07 +04:00
}
twl6040 - > sysclk = 0 ;
2012-01-14 23:58:43 +04:00
twl6040 - > mclk = 0 ;
2011-04-27 11:14:07 +04:00
}
out :
mutex_unlock ( & twl6040 - > mutex ) ;
return ret ;
}
EXPORT_SYMBOL ( twl6040_power ) ;
2011-07-04 11:28:28 +04:00
int twl6040_set_pll ( struct twl6040 * twl6040 , int pll_id ,
2011-04-27 11:14:07 +04:00
unsigned int freq_in , unsigned int freq_out )
{
u8 hppllctl , lppllctl ;
int ret = 0 ;
mutex_lock ( & twl6040 - > mutex ) ;
hppllctl = twl6040_reg_read ( twl6040 , TWL6040_REG_HPPLLCTL ) ;
lppllctl = twl6040_reg_read ( twl6040 , TWL6040_REG_LPPLLCTL ) ;
2012-01-14 23:58:44 +04:00
/* Force full reconfiguration when switching between PLL */
if ( pll_id ! = twl6040 - > pll ) {
twl6040 - > sysclk = 0 ;
twl6040 - > mclk = 0 ;
}
2011-07-04 11:28:28 +04:00
switch ( pll_id ) {
case TWL6040_SYSCLK_SEL_LPPLL :
2011-04-27 11:14:07 +04:00
/* low-power PLL divider */
2012-01-14 23:58:44 +04:00
/* Change the sysclk configuration only if it has been canged */
if ( twl6040 - > sysclk ! = freq_out ) {
switch ( freq_out ) {
case 17640000 :
lppllctl | = TWL6040_LPLLFIN ;
break ;
case 19200000 :
lppllctl & = ~ TWL6040_LPLLFIN ;
break ;
default :
dev_err ( twl6040 - > dev ,
" freq_out %d not supported \n " ,
freq_out ) ;
ret = - EINVAL ;
goto pll_out ;
}
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL ,
lppllctl ) ;
2011-04-27 11:14:07 +04:00
}
2012-01-14 23:58:44 +04:00
/* The PLL in use has not been change, we can exit */
if ( twl6040 - > pll = = pll_id )
break ;
2011-04-27 11:14:07 +04:00
switch ( freq_in ) {
case 32768 :
lppllctl | = TWL6040_LPLLENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL ,
lppllctl ) ;
mdelay ( 5 ) ;
lppllctl & = ~ TWL6040_HPLLSEL ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL ,
lppllctl ) ;
hppllctl & = ~ TWL6040_HPLLENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_HPPLLCTL ,
hppllctl ) ;
break ;
default :
2011-09-15 16:39:23 +04:00
dev_err ( twl6040 - > dev ,
2011-04-27 11:14:07 +04:00
" freq_in %d not supported \n " , freq_in ) ;
ret = - EINVAL ;
goto pll_out ;
}
break ;
2011-07-04 11:28:28 +04:00
case TWL6040_SYSCLK_SEL_HPPLL :
2011-04-27 11:14:07 +04:00
/* high-performance PLL can provide only 19.2 MHz */
if ( freq_out ! = 19200000 ) {
2011-09-15 16:39:23 +04:00
dev_err ( twl6040 - > dev ,
2011-04-27 11:14:07 +04:00
" freq_out %d not supported \n " , freq_out ) ;
ret = - EINVAL ;
goto pll_out ;
}
2012-01-14 23:58:44 +04:00
if ( twl6040 - > mclk ! = freq_in ) {
hppllctl & = ~ TWL6040_MCLK_MSK ;
switch ( freq_in ) {
case 12000000 :
/* PLL enabled, active mode */
hppllctl | = TWL6040_MCLK_12000KHZ |
TWL6040_HPLLENA ;
break ;
case 19200000 :
/*
* PLL disabled
* ( enable PLL if MCLK jitter quality
* doesn ' t meet specification )
*/
hppllctl | = TWL6040_MCLK_19200KHZ ;
break ;
case 26000000 :
/* PLL enabled, active mode */
hppllctl | = TWL6040_MCLK_26000KHZ |
TWL6040_HPLLENA ;
break ;
case 38400000 :
/* PLL enabled, active mode */
hppllctl | = TWL6040_MCLK_38400KHZ |
TWL6040_HPLLENA ;
break ;
default :
dev_err ( twl6040 - > dev ,
" freq_in %d not supported \n " , freq_in ) ;
ret = - EINVAL ;
goto pll_out ;
}
2011-04-27 11:14:07 +04:00
/*
2012-01-14 23:58:44 +04:00
* enable clock slicer to ensure input waveform is
* square
2011-04-27 11:14:07 +04:00
*/
2012-01-14 23:58:44 +04:00
hppllctl | = TWL6040_HPLLSQRENA ;
2011-04-27 11:14:07 +04:00
2012-01-14 23:58:44 +04:00
twl6040_reg_write ( twl6040 , TWL6040_REG_HPPLLCTL ,
hppllctl ) ;
usleep_range ( 500 , 700 ) ;
lppllctl | = TWL6040_HPLLSEL ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL ,
lppllctl ) ;
lppllctl & = ~ TWL6040_LPLLENA ;
twl6040_reg_write ( twl6040 , TWL6040_REG_LPPLLCTL ,
lppllctl ) ;
}
2011-04-27 11:14:07 +04:00
break ;
default :
2011-09-15 16:39:23 +04:00
dev_err ( twl6040 - > dev , " unknown pll id %d \n " , pll_id ) ;
2011-04-27 11:14:07 +04:00
ret = - EINVAL ;
goto pll_out ;
}
twl6040 - > sysclk = freq_out ;
2012-01-14 23:58:43 +04:00
twl6040 - > mclk = freq_in ;
2011-07-04 11:28:28 +04:00
twl6040 - > pll = pll_id ;
2011-04-27 11:14:07 +04:00
pll_out :
mutex_unlock ( & twl6040 - > mutex ) ;
return ret ;
}
EXPORT_SYMBOL ( twl6040_set_pll ) ;
2011-07-04 11:28:28 +04:00
int twl6040_get_pll ( struct twl6040 * twl6040 )
2011-04-27 11:14:07 +04:00
{
2011-07-04 11:28:28 +04:00
if ( twl6040 - > power_count )
return twl6040 - > pll ;
else
return - ENODEV ;
2011-04-27 11:14:07 +04:00
}
EXPORT_SYMBOL ( twl6040_get_pll ) ;
unsigned int twl6040_get_sysclk ( struct twl6040 * twl6040 )
{
return twl6040 - > sysclk ;
}
EXPORT_SYMBOL ( twl6040_get_sysclk ) ;
2011-10-12 12:57:55 +04:00
/* Get the combined status of the vibra control register */
int twl6040_get_vibralr_status ( struct twl6040 * twl6040 )
{
u8 status ;
status = twl6040 - > vibra_ctrl_cache [ 0 ] | twl6040 - > vibra_ctrl_cache [ 1 ] ;
status & = ( TWL6040_VIBENA | TWL6040_VIBSEL ) ;
return status ;
}
EXPORT_SYMBOL ( twl6040_get_vibralr_status ) ;
2011-07-05 12:40:33 +04:00
static struct resource twl6040_vibra_rsrc [ ] = {
{
. flags = IORESOURCE_IRQ ,
} ,
} ;
static struct resource twl6040_codec_rsrc [ ] = {
{
. flags = IORESOURCE_IRQ ,
} ,
} ;
2012-04-03 12:56:51 +04:00
static bool twl6040_readable_reg ( struct device * dev , unsigned int reg )
2011-04-27 11:14:07 +04:00
{
2012-04-03 12:56:51 +04:00
/* Register 0 is not readable */
if ( ! reg )
return false ;
return true ;
}
static struct regmap_config twl6040_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = TWL6040_REG_STATUS , /* 0x2e */
. readable_reg = twl6040_readable_reg ,
} ;
2012-10-11 15:55:32 +04:00
static const struct regmap_irq twl6040_irqs [ ] = {
{ . reg_offset = 0 , . mask = TWL6040_THINT , } ,
{ . reg_offset = 0 , . mask = TWL6040_PLUGINT | TWL6040_UNPLUGINT , } ,
{ . reg_offset = 0 , . mask = TWL6040_HOOKINT , } ,
{ . reg_offset = 0 , . mask = TWL6040_HFINT , } ,
{ . reg_offset = 0 , . mask = TWL6040_VIBINT , } ,
{ . reg_offset = 0 , . mask = TWL6040_READYINT , } ,
} ;
static struct regmap_irq_chip twl6040_irq_chip = {
. name = " twl6040 " ,
. irqs = twl6040_irqs ,
. num_irqs = ARRAY_SIZE ( twl6040_irqs ) ,
. num_regs = 1 ,
. status_base = TWL6040_REG_INTID ,
. mask_base = TWL6040_REG_INTMR ,
} ;
2012-12-22 03:03:15 +04:00
static int twl6040_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
2012-04-03 12:56:51 +04:00
{
struct twl6040_platform_data * pdata = client - > dev . platform_data ;
2012-05-16 15:11:58 +04:00
struct device_node * node = client - > dev . of_node ;
2011-04-27 11:14:07 +04:00
struct twl6040 * twl6040 ;
struct mfd_cell * cell = NULL ;
2012-05-16 15:11:57 +04:00
int irq , ret , children = 0 ;
2011-04-27 11:14:07 +04:00
2012-05-16 15:11:58 +04:00
if ( ! pdata & & ! node ) {
2012-04-03 12:56:51 +04:00
dev_err ( & client - > dev , " Platform data is missing \n " ) ;
2011-04-27 11:14:07 +04:00
return - EINVAL ;
}
2011-07-04 21:15:19 +04:00
/* In order to operate correctly we need valid interrupt config */
2012-05-16 15:11:56 +04:00
if ( ! client - > irq ) {
2012-04-03 12:56:51 +04:00
dev_err ( & client - > dev , " Invalid IRQ configuration \n " ) ;
2011-07-04 21:15:19 +04:00
return - EINVAL ;
}
2012-04-03 12:56:51 +04:00
twl6040 = devm_kzalloc ( & client - > dev , sizeof ( struct twl6040 ) ,
GFP_KERNEL ) ;
if ( ! twl6040 ) {
ret = - ENOMEM ;
goto err ;
}
2012-04-25 06:09:46 +04:00
twl6040 - > regmap = devm_regmap_init_i2c ( client , & twl6040_regmap_config ) ;
2012-04-03 12:56:51 +04:00
if ( IS_ERR ( twl6040 - > regmap ) ) {
ret = PTR_ERR ( twl6040 - > regmap ) ;
goto err ;
}
2011-04-27 11:14:07 +04:00
2012-04-03 12:56:51 +04:00
i2c_set_clientdata ( client , twl6040 ) ;
2011-04-27 11:14:07 +04:00
2012-05-02 17:54:42 +04:00
twl6040 - > supplies [ 0 ] . supply = " vio " ;
twl6040 - > supplies [ 1 ] . supply = " v2v1 " ;
2013-02-20 13:30:21 +04:00
ret = devm_regulator_bulk_get ( & client - > dev , TWL6040_NUM_SUPPLIES ,
2012-05-02 17:54:42 +04:00
twl6040 - > supplies ) ;
if ( ret ! = 0 ) {
dev_err ( & client - > dev , " Failed to get supplies: %d \n " , ret ) ;
goto regulator_get_err ;
}
ret = regulator_bulk_enable ( TWL6040_NUM_SUPPLIES , twl6040 - > supplies ) ;
if ( ret ! = 0 ) {
dev_err ( & client - > dev , " Failed to enable supplies: %d \n " , ret ) ;
2013-02-20 13:30:21 +04:00
goto regulator_get_err ;
2012-05-02 17:54:42 +04:00
}
2012-04-03 12:56:51 +04:00
twl6040 - > dev = & client - > dev ;
twl6040 - > irq = client - > irq ;
2011-04-27 11:14:07 +04:00
mutex_init ( & twl6040 - > mutex ) ;
init_completion ( & twl6040 - > ready ) ;
twl6040 - > rev = twl6040_reg_read ( twl6040 , TWL6040_REG_ASICREV ) ;
2011-09-15 16:39:26 +04:00
/* ERRATA: Automatic power-up is not possible in ES1.0 */
2012-05-16 15:11:58 +04:00
if ( twl6040_get_revid ( twl6040 ) > TWL6040_REV_ES1_0 ) {
if ( pdata )
twl6040 - > audpwron = pdata - > audpwron_gpio ;
else
twl6040 - > audpwron = of_get_named_gpio ( node ,
" ti,audpwron-gpio " , 0 ) ;
} else
2011-09-15 16:39:26 +04:00
twl6040 - > audpwron = - EINVAL ;
2011-04-27 11:14:07 +04:00
if ( gpio_is_valid ( twl6040 - > audpwron ) ) {
2013-02-20 13:30:21 +04:00
ret = devm_gpio_request_one ( & client - > dev , twl6040 - > audpwron ,
GPIOF_OUT_INIT_LOW , " audpwron " ) ;
2011-04-27 11:14:07 +04:00
if ( ret )
2012-05-02 17:54:42 +04:00
goto gpio_err ;
2011-04-27 11:14:07 +04:00
}
2012-10-11 15:55:32 +04:00
ret = regmap_add_irq_chip ( twl6040 - > regmap , twl6040 - > irq ,
IRQF_ONESHOT , 0 , & twl6040_irq_chip ,
& twl6040 - > irq_data ) ;
if ( ret < 0 )
2013-02-20 13:30:21 +04:00
goto gpio_err ;
2011-07-04 21:15:19 +04:00
2012-10-11 15:55:32 +04:00
twl6040 - > irq_ready = regmap_irq_get_virq ( twl6040 - > irq_data ,
TWL6040_IRQ_READY ) ;
twl6040 - > irq_th = regmap_irq_get_virq ( twl6040 - > irq_data ,
TWL6040_IRQ_TH ) ;
2013-02-20 13:30:21 +04:00
ret = devm_request_threaded_irq ( twl6040 - > dev , twl6040 - > irq_ready , NULL ,
2012-10-11 15:55:32 +04:00
twl6040_readyint_handler , IRQF_ONESHOT ,
2011-07-04 21:16:23 +04:00
" twl6040_irq_ready " , twl6040 ) ;
2011-07-04 21:15:19 +04:00
if ( ret ) {
2012-10-11 15:55:31 +04:00
dev_err ( twl6040 - > dev , " READY IRQ request failed: %d \n " , ret ) ;
goto readyirq_err ;
}
2013-02-20 13:30:21 +04:00
ret = devm_request_threaded_irq ( twl6040 - > dev , twl6040 - > irq_th , NULL ,
2012-10-11 15:55:32 +04:00
twl6040_thint_handler , IRQF_ONESHOT ,
2012-10-11 15:55:31 +04:00
" twl6040_irq_th " , twl6040 ) ;
if ( ret ) {
dev_err ( twl6040 - > dev , " Thermal IRQ request failed: %d \n " , ret ) ;
goto thirq_err ;
2011-04-27 11:14:07 +04:00
}
/* dual-access registers controlled by I2C only */
twl6040_set_bits ( twl6040 , TWL6040_REG_ACCCTL , TWL6040_I2CSEL ) ;
2012-05-16 15:11:57 +04:00
/*
* The main functionality of twl6040 to provide audio on OMAP4 + systems .
* We can add the ASoC codec child whenever this driver has been loaded .
* The ASoC codec can work without pdata , pass the platform_data only if
* it has been provided .
*/
2012-10-11 15:55:32 +04:00
irq = regmap_irq_get_virq ( twl6040 - > irq_data , TWL6040_IRQ_PLUG ) ;
2012-05-16 15:11:57 +04:00
cell = & twl6040 - > cells [ children ] ;
cell - > name = " twl6040-codec " ;
twl6040_codec_rsrc [ 0 ] . start = irq ;
twl6040_codec_rsrc [ 0 ] . end = irq ;
cell - > resources = twl6040_codec_rsrc ;
cell - > num_resources = ARRAY_SIZE ( twl6040_codec_rsrc ) ;
2012-05-16 15:11:58 +04:00
if ( pdata & & pdata - > codec ) {
2011-06-01 14:05:10 +04:00
cell - > platform_data = pdata - > codec ;
cell - > pdata_size = sizeof ( * pdata - > codec ) ;
2011-04-27 11:14:07 +04:00
}
2012-05-16 15:11:57 +04:00
children + + ;
2011-04-27 11:14:07 +04:00
2012-05-23 18:23:21 +04:00
if ( twl6040_has_vibra ( pdata , node ) ) {
2012-10-11 15:55:32 +04:00
irq = regmap_irq_get_virq ( twl6040 - > irq_data , TWL6040_IRQ_VIB ) ;
2011-07-05 12:40:33 +04:00
2011-04-27 11:14:07 +04:00
cell = & twl6040 - > cells [ children ] ;
cell - > name = " twl6040-vibra " ;
2011-07-05 12:40:33 +04:00
twl6040_vibra_rsrc [ 0 ] . start = irq ;
twl6040_vibra_rsrc [ 0 ] . end = irq ;
cell - > resources = twl6040_vibra_rsrc ;
cell - > num_resources = ARRAY_SIZE ( twl6040_vibra_rsrc ) ;
2012-05-16 15:11:58 +04:00
if ( pdata & & pdata - > vibra ) {
cell - > platform_data = pdata - > vibra ;
cell - > pdata_size = sizeof ( * pdata - > vibra ) ;
}
2011-04-27 11:14:07 +04:00
children + + ;
}
2012-08-16 16:13:14 +04:00
/*
* Enable the GPO driver in the following cases :
* DT booted kernel or legacy boot with valid gpo platform_data
*/
if ( ! pdata | | ( pdata & & pdata - > gpo ) ) {
cell = & twl6040 - > cells [ children ] ;
cell - > name = " twl6040-gpo " ;
if ( pdata ) {
cell - > platform_data = pdata - > gpo ;
cell - > pdata_size = sizeof ( * pdata - > gpo ) ;
}
children + + ;
}
2012-05-16 15:11:57 +04:00
ret = mfd_add_devices ( & client - > dev , - 1 , twl6040 - > cells , children ,
2012-09-11 11:16:36 +04:00
NULL , 0 , NULL ) ;
2012-05-16 15:11:57 +04:00
if ( ret )
2011-04-27 11:14:07 +04:00
goto mfd_err ;
return 0 ;
mfd_err :
2013-02-20 13:30:21 +04:00
devm_free_irq ( & client - > dev , twl6040 - > irq_th , twl6040 ) ;
2012-10-11 15:55:31 +04:00
thirq_err :
2013-02-20 13:30:21 +04:00
devm_free_irq ( & client - > dev , twl6040 - > irq_ready , twl6040 ) ;
2012-10-11 15:55:31 +04:00
readyirq_err :
2012-10-11 15:55:32 +04:00
regmap_del_irq_chip ( twl6040 - > irq , twl6040 - > irq_data ) ;
2012-05-02 17:54:42 +04:00
gpio_err :
regulator_bulk_disable ( TWL6040_NUM_SUPPLIES , twl6040 - > supplies ) ;
regulator_get_err :
2012-04-03 12:56:51 +04:00
i2c_set_clientdata ( client , NULL ) ;
err :
2011-04-27 11:14:07 +04:00
return ret ;
}
2012-12-22 03:03:15 +04:00
static int twl6040_remove ( struct i2c_client * client )
2011-04-27 11:14:07 +04:00
{
2012-04-03 12:56:51 +04:00
struct twl6040 * twl6040 = i2c_get_clientdata ( client ) ;
2011-04-27 11:14:07 +04:00
if ( twl6040 - > power_count )
twl6040_power ( twl6040 , 0 ) ;
2013-02-20 13:30:21 +04:00
devm_free_irq ( & client - > dev , twl6040 - > irq_ready , twl6040 ) ;
devm_free_irq ( & client - > dev , twl6040 - > irq_th , twl6040 ) ;
2012-10-11 15:55:32 +04:00
regmap_del_irq_chip ( twl6040 - > irq , twl6040 - > irq_data ) ;
2011-04-27 11:14:07 +04:00
2012-04-03 12:56:51 +04:00
mfd_remove_devices ( & client - > dev ) ;
i2c_set_clientdata ( client , NULL ) ;
2011-04-27 11:14:07 +04:00
2012-05-02 17:54:42 +04:00
regulator_bulk_disable ( TWL6040_NUM_SUPPLIES , twl6040 - > supplies ) ;
2011-04-27 11:14:07 +04:00
return 0 ;
}
2012-04-03 12:56:51 +04:00
static const struct i2c_device_id twl6040_i2c_id [ ] = {
{ " twl6040 " , 0 , } ,
2012-07-16 13:49:44 +04:00
{ " twl6041 " , 0 , } ,
2012-04-03 12:56:51 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , twl6040_i2c_id ) ;
static struct i2c_driver twl6040_driver = {
. driver = {
. name = " twl6040 " ,
. owner = THIS_MODULE ,
} ,
2011-04-27 11:14:07 +04:00
. probe = twl6040_probe ,
2012-12-22 03:03:15 +04:00
. remove = twl6040_remove ,
2012-04-03 12:56:51 +04:00
. id_table = twl6040_i2c_id ,
2011-04-27 11:14:07 +04:00
} ;
2012-04-03 12:56:51 +04:00
module_i2c_driver ( twl6040_driver ) ;
2011-04-27 11:14:07 +04:00
MODULE_DESCRIPTION ( " TWL6040 MFD " ) ;
MODULE_AUTHOR ( " Misael Lopez Cruz <misael.lopez@ti.com> " ) ;
MODULE_AUTHOR ( " Jorge Eduardo Candelaria <jorge.candelaria@ti.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:twl6040 " ) ;