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
2015-01-05 12:01:33 +03:00
static const struct reg_default twl6040_defaults [ ] = {
2013-11-29 18:03:45 +04:00
{ 0x01 , 0x4B } , /* REG_ASICID (ro) */
{ 0x02 , 0x00 } , /* REG_ASICREV (ro) */
{ 0x03 , 0x00 } , /* REG_INTID */
{ 0x04 , 0x00 } , /* REG_INTMR */
{ 0x05 , 0x00 } , /* REG_NCPCTRL */
{ 0x06 , 0x00 } , /* REG_LDOCTL */
{ 0x07 , 0x60 } , /* REG_HPPLLCTL */
{ 0x08 , 0x00 } , /* REG_LPPLLCTL */
{ 0x09 , 0x4A } , /* REG_LPPLLDIV */
{ 0x0A , 0x00 } , /* REG_AMICBCTL */
{ 0x0B , 0x00 } , /* REG_DMICBCTL */
{ 0x0C , 0x00 } , /* REG_MICLCTL */
{ 0x0D , 0x00 } , /* REG_MICRCTL */
{ 0x0E , 0x00 } , /* REG_MICGAIN */
{ 0x0F , 0x1B } , /* REG_LINEGAIN */
{ 0x10 , 0x00 } , /* REG_HSLCTL */
{ 0x11 , 0x00 } , /* REG_HSRCTL */
{ 0x12 , 0x00 } , /* REG_HSGAIN */
{ 0x13 , 0x00 } , /* REG_EARCTL */
{ 0x14 , 0x00 } , /* REG_HFLCTL */
{ 0x15 , 0x00 } , /* REG_HFLGAIN */
{ 0x16 , 0x00 } , /* REG_HFRCTL */
{ 0x17 , 0x00 } , /* REG_HFRGAIN */
{ 0x18 , 0x00 } , /* REG_VIBCTLL */
{ 0x19 , 0x00 } , /* REG_VIBDATL */
{ 0x1A , 0x00 } , /* REG_VIBCTLR */
{ 0x1B , 0x00 } , /* REG_VIBDATR */
{ 0x1C , 0x00 } , /* REG_HKCTL1 */
{ 0x1D , 0x00 } , /* REG_HKCTL2 */
{ 0x1E , 0x00 } , /* REG_GPOCTL */
{ 0x1F , 0x00 } , /* REG_ALB */
{ 0x20 , 0x00 } , /* REG_DLB */
/* 0x28, REG_TRIM1 */
/* 0x29, REG_TRIM2 */
/* 0x2A, REG_TRIM3 */
/* 0x2B, REG_HSOTRIM */
/* 0x2C, REG_HFOTRIM */
{ 0x2D , 0x08 } , /* REG_ACCCTL */
{ 0x2E , 0x00 } , /* REG_STATUS (ro) */
} ;
2015-07-16 18:36:21 +03:00
static struct reg_sequence twl6040_patch [ ] = {
2014-04-01 17:44:59 +04:00
/*
* Select I2C bus access to dual access registers
* Interrupt register is cleared on read
* Select fast mode for i2c ( 400 KHz )
*/
{ TWL6040_REG_ACCCTL ,
TWL6040_I2CSEL | TWL6040_INTCLRMODE | TWL6040_I2CMODE ( 1 ) } ,
2013-11-29 18:03:45 +04:00
} ;
2017-11-11 18:38:44 +03:00
static bool twl6040_has_vibra ( struct device_node * parent )
2012-05-23 18:23:21 +04:00
{
2017-11-11 18:38:44 +03:00
struct device_node * node ;
node = of_get_child_by_name ( parent , " vibra " ) ;
if ( node ) {
of_node_put ( node ) ;
2012-05-23 18:23:21 +04:00
return true ;
2017-11-11 18:38:44 +03:00
}
2012-05-23 18:23:21 +04:00
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
2013-08-31 20:48:19 +04:00
ret = regmap_read ( twl6040 - > regmap , reg , & val ) ;
if ( ret < 0 )
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-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 ;
2016-04-20 18:16:38 +03:00
ret = clk_prepare_enable ( twl6040 - > clk32k ) ;
if ( ret ) {
twl6040 - > power_count = 0 ;
goto out ;
}
2014-04-03 14:54:41 +04:00
2013-11-29 18:03:45 +04:00
/* Allow writes to the chip */
regcache_cache_only ( twl6040 - > regmap , false ) ;
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 ) {
2016-04-20 18:16:37 +03:00
clk_disable_unprepare ( twl6040 - > clk32k ) ;
2011-04-27 11:14:07 +04:00
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 ) {
2016-04-20 18:16:37 +03:00
clk_disable_unprepare ( twl6040 - > clk32k ) ;
2011-04-27 11:14:07 +04:00
twl6040 - > power_count = 0 ;
goto out ;
}
}
2013-11-29 18:03:45 +04:00
2019-02-14 19:03:45 +03:00
/*
* Register access can produce errors after power - up unless we
* wait at least 8 ms based on measurements on duovero .
*/
usleep_range ( 10000 , 12000 ) ;
2013-11-29 18:03:45 +04:00
/* Sync with the HW */
2019-02-14 19:03:45 +03:00
ret = regcache_sync ( twl6040 - > regmap ) ;
if ( ret ) {
dev_err ( twl6040 - > dev , " Failed to sync with the HW: %i \n " ,
ret ) ;
goto out ;
}
2013-11-29 18:03:45 +04:00
2011-07-04 11:28:28 +04:00
/* Default PLL configuration after power up */
twl6040 - > pll = TWL6040_SYSCLK_SEL_LPPLL ;
2016-06-20 17:07:18 +03:00
twl6040 - > sysclk_rate = 19200000 ;
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
}
2013-11-29 18:03:45 +04:00
/* Set regmap to cache only and mark it as dirty */
regcache_cache_only ( twl6040 - > regmap , true ) ;
regcache_mark_dirty ( twl6040 - > regmap ) ;
2016-06-20 17:07:18 +03:00
twl6040 - > sysclk_rate = 0 ;
if ( twl6040 - > pll = = TWL6040_SYSCLK_SEL_HPPLL ) {
clk_disable_unprepare ( twl6040 - > mclk ) ;
twl6040 - > mclk_rate = 0 ;
}
2014-04-03 14:54:41 +04:00
clk_disable_unprepare ( twl6040 - > clk32k ) ;
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 ) {
2016-06-20 17:07:18 +03:00
twl6040 - > sysclk_rate = 0 ;
twl6040 - > mclk_rate = 0 ;
2012-01-14 23:58:44 +04:00
}
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 */
2016-06-20 17:07:18 +03:00
if ( twl6040 - > sysclk_rate ! = freq_out ) {
2012-01-14 23:58:44 +04:00
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 ;
}
2016-06-20 17:07:18 +03:00
clk_disable_unprepare ( twl6040 - > mclk ) ;
2011-04-27 11:14:07 +04:00
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 ;
}
2016-06-20 17:07:18 +03:00
if ( twl6040 - > mclk_rate ! = freq_in ) {
2012-01-14 23:58:44 +04:00
hppllctl & = ~ TWL6040_MCLK_MSK ;
switch ( freq_in ) {
case 12000000 :
/* PLL enabled, active mode */
hppllctl | = TWL6040_MCLK_12000KHZ |
TWL6040_HPLLENA ;
break ;
case 19200000 :
2014-05-06 12:46:10 +04:00
/* PLL enabled, bypass mode */
hppllctl | = TWL6040_MCLK_19200KHZ |
TWL6040_HPLLBP | TWL6040_HPLLENA ;
2012-01-14 23:58:44 +04:00
break ;
case 26000000 :
/* PLL enabled, active mode */
hppllctl | = TWL6040_MCLK_26000KHZ |
TWL6040_HPLLENA ;
break ;
case 38400000 :
2014-05-06 12:46:10 +04:00
/* PLL enabled, bypass mode */
2012-01-14 23:58:44 +04:00
hppllctl | = TWL6040_MCLK_38400KHZ |
2014-05-06 12:46:10 +04:00
TWL6040_HPLLBP | TWL6040_HPLLENA ;
2012-01-14 23:58:44 +04:00
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
2016-06-20 17:07:18 +03:00
/* When switching to HPPLL, enable the mclk first */
if ( pll_id ! = twl6040 - > pll )
clk_prepare_enable ( twl6040 - > mclk ) ;
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 ) ;
2016-06-20 17:07:18 +03:00
twl6040 - > mclk_rate = freq_in ;
2012-01-14 23:58:44 +04:00
}
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 ;
}
2016-06-20 17:07:18 +03:00
twl6040 - > sysclk_rate = freq_out ;
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 )
{
2016-06-20 17:07:18 +03:00
return twl6040 - > sysclk_rate ;
2011-04-27 11:14:07 +04:00
}
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 )
{
2013-08-31 20:48:19 +04:00
unsigned int reg ;
int ret ;
2011-10-12 12:57:55 +04:00
u8 status ;
2013-08-31 20:48:19 +04:00
ret = regmap_read ( twl6040 - > regmap , TWL6040_REG_VIBCTLL , & reg ) ;
if ( ret ! = 0 )
return ret ;
status = reg ;
ret = regmap_read ( twl6040 - > regmap , TWL6040_REG_VIBCTLR , & reg ) ;
if ( ret ! = 0 )
return ret ;
status | = reg ;
2011-10-12 12:57:55 +04:00
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 ;
}
2013-08-31 20:48:19 +04:00
static bool twl6040_volatile_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
2013-11-29 18:03:45 +04:00
case TWL6040_REG_ASICID :
case TWL6040_REG_ASICREV :
case TWL6040_REG_INTID :
case TWL6040_REG_LPPLLCTL :
case TWL6040_REG_HPPLLCTL :
case TWL6040_REG_STATUS :
return true ;
default :
return false ;
}
}
static bool twl6040_writeable_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case TWL6040_REG_ASICID :
case TWL6040_REG_ASICREV :
case TWL6040_REG_STATUS :
2013-08-31 20:48:19 +04:00
return false ;
default :
return true ;
}
}
2015-01-05 12:01:33 +03:00
static const struct regmap_config twl6040_regmap_config = {
2012-04-03 12:56:51 +04:00
. reg_bits = 8 ,
. val_bits = 8 ,
2013-11-29 18:03:45 +04:00
. reg_defaults = twl6040_defaults ,
. num_reg_defaults = ARRAY_SIZE ( twl6040_defaults ) ,
2012-04-03 12:56:51 +04:00
. max_register = TWL6040_REG_STATUS , /* 0x2e */
. readable_reg = twl6040_readable_reg ,
2013-08-31 20:48:19 +04:00
. volatile_reg = twl6040_volatile_reg ,
2013-11-29 18:03:45 +04:00
. writeable_reg = twl6040_writeable_reg ,
2013-08-31 20:48:19 +04:00
. cache_type = REGCACHE_RBTREE ,
2018-09-01 19:50:41 +03:00
. use_single_read = true ,
. use_single_write = true ,
2012-04-03 12:56:51 +04:00
} ;
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
{
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
2013-07-12 15:32:02 +04:00
if ( ! node ) {
dev_err ( & client - > dev , " of node 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 ) ;
2013-07-12 15:32:04 +04:00
if ( ! twl6040 )
return - ENOMEM ;
2012-04-03 12:56:51 +04:00
2012-04-25 06:09:46 +04:00
twl6040 - > regmap = devm_regmap_init_i2c ( client , & twl6040_regmap_config ) ;
2013-07-12 15:32:04 +04:00
if ( IS_ERR ( twl6040 - > regmap ) )
return PTR_ERR ( twl6040 - > regmap ) ;
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
2014-04-03 14:54:41 +04:00
twl6040 - > clk32k = devm_clk_get ( & client - > dev , " clk32k " ) ;
if ( IS_ERR ( twl6040 - > clk32k ) ) {
2015-09-18 19:29:04 +03:00
if ( PTR_ERR ( twl6040 - > clk32k ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
2016-06-20 17:07:18 +03:00
dev_dbg ( & client - > dev , " clk32k is not handled \n " ) ;
2014-04-03 14:54:41 +04:00
twl6040 - > clk32k = NULL ;
}
2016-06-20 17:07:18 +03:00
twl6040 - > mclk = devm_clk_get ( & client - > dev , " mclk " ) ;
if ( IS_ERR ( twl6040 - > mclk ) ) {
if ( PTR_ERR ( twl6040 - > mclk ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
dev_dbg ( & client - > dev , " mclk is not handled \n " ) ;
twl6040 - > mclk = NULL ;
}
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 ,
2013-07-12 15:32:03 +04:00
twl6040 - > supplies ) ;
2012-05-02 17:54:42 +04:00
if ( ret ! = 0 ) {
dev_err ( & client - > dev , " Failed to get supplies: %d \n " , ret ) ;
2013-10-13 20:06:12 +04:00
return ret ;
2012-05-02 17:54:42 +04:00
}
ret = regulator_bulk_enable ( TWL6040_NUM_SUPPLIES , twl6040 - > supplies ) ;
if ( ret ! = 0 ) {
dev_err ( & client - > dev , " Failed to enable supplies: %d \n " , ret ) ;
2013-10-13 20:06:12 +04:00
return ret ;
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 ) ;
2014-04-01 17:45:00 +04:00
regmap_register_patch ( twl6040 - > regmap , twl6040_patch ,
ARRAY_SIZE ( twl6040_patch ) ) ;
2011-04-27 11:14:07 +04:00
twl6040 - > rev = twl6040_reg_read ( twl6040 , TWL6040_REG_ASICREV ) ;
2014-02-25 17:28:19 +04:00
if ( twl6040 - > rev < 0 ) {
dev_err ( & client - > dev , " Failed to read revision register: %d \n " ,
twl6040 - > rev ) ;
2014-08-07 00:12:15 +04:00
ret = twl6040 - > rev ;
2014-02-25 17:28:19 +04:00
goto gpio_err ;
}
2011-04-27 11:14:07 +04:00
2011-09-15 16:39:26 +04:00
/* ERRATA: Automatic power-up is not possible in ES1.0 */
2013-07-12 15:32:02 +04:00
if ( twl6040_get_revid ( twl6040 ) > TWL6040_REV_ES1_0 )
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 ,
2013-07-12 15:32:03 +04:00
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 ;
2014-04-01 17:45:01 +04:00
/* Clear any pending interrupt */
twl6040_reg_read ( twl6040 , TWL6040_REG_INTID ) ;
2011-04-27 11:14:07 +04:00
}
2013-07-12 15:32:03 +04:00
ret = regmap_add_irq_chip ( twl6040 - > regmap , twl6040 - > irq , IRQF_ONESHOT ,
2014-07-21 16:13:48 +04:00
0 , & twl6040_irq_chip , & twl6040 - > irq_data ) ;
2012-10-11 15:55:32 +04:00
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 ,
2013-07-12 15:32:03 +04:00
TWL6040_IRQ_READY ) ;
2012-10-11 15:55:32 +04:00
twl6040 - > irq_th = regmap_irq_get_virq ( twl6040 - > irq_data ,
2013-07-12 15:32:03 +04:00
TWL6040_IRQ_TH ) ;
2012-10-11 15:55:32 +04:00
2013-02-20 13:30:21 +04:00
ret = devm_request_threaded_irq ( twl6040 - > dev , twl6040 - > irq_ready , NULL ,
2013-07-12 15:32:03 +04:00
twl6040_readyint_handler , IRQF_ONESHOT ,
" 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 ,
2013-07-12 15:32:03 +04:00
twl6040_thint_handler , IRQF_ONESHOT ,
" twl6040_irq_th " , twl6040 ) ;
2012-10-11 15:55:31 +04:00
if ( ret ) {
dev_err ( twl6040 - > dev , " Thermal IRQ request failed: %d \n " , ret ) ;
2013-09-25 11:37:15 +04:00
goto readyirq_err ;
2011-04-27 11:14:07 +04:00
}
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 .
*/
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 ) ;
children + + ;
2011-04-27 11:14:07 +04:00
2013-07-12 15:32:02 +04:00
/* Vibra input driver support */
if ( twl6040_has_vibra ( 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 ) ;
2011-04-27 11:14:07 +04:00
children + + ;
}
2013-07-12 15:32:02 +04:00
/* GPO support */
cell = & twl6040 - > cells [ children ] ;
cell - > name = " twl6040-gpo " ;
children + + ;
2012-08-16 16:13:14 +04:00
2016-08-31 14:46:21 +03:00
/* PDM clock support */
cell = & twl6040 - > cells [ children ] ;
cell - > name = " twl6040-pdmclk " ;
children + + ;
2013-11-29 18:03:45 +04:00
/* The chip is powered down so mark regmap to cache only and dirty */
regcache_cache_only ( twl6040 - > regmap , true ) ;
regcache_mark_dirty ( twl6040 - > regmap ) ;
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 )
2013-09-25 11:37:15 +04:00
goto readyirq_err ;
2011-04-27 11:14:07 +04:00
return 0 ;
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 ) ;
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 ) ;
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 ) ;
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 " ,
} ,
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 " ) ;