2009-10-22 14:26:45 +04:00
/*
* MFD driver for twl4030 codec submodule
*
* Author : Peter Ujfalusi < peter . ujfalusi @ nokia . com >
*
* Copyright : ( C ) 2009 Nokia Corporation
*
* 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/kernel.h>
# include <linux/fs.h>
# include <linux/platform_device.h>
2009-12-15 08:33:10 +03:00
# include <linux/i2c/twl.h>
2009-10-22 14:26:45 +04:00
# include <linux/mfd/core.h>
# include <linux/mfd/twl4030-codec.h>
# define TWL4030_CODEC_CELLS 2
static struct platform_device * twl4030_codec_dev ;
struct twl4030_codec_resource {
int request_count ;
u8 reg ;
u8 mask ;
} ;
struct twl4030_codec {
2009-11-04 10:58:19 +03:00
unsigned int audio_mclk ;
2009-10-22 14:26:45 +04:00
struct mutex mutex ;
struct twl4030_codec_resource resource [ TWL4030_CODEC_RES_MAX ] ;
struct mfd_cell cells [ TWL4030_CODEC_CELLS ] ;
} ;
/*
* Modify the resource , the function returns the content of the register
* after the modification .
*/
static int twl4030_codec_set_resource ( enum twl4030_codec_res id , int enable )
{
struct twl4030_codec * codec = platform_get_drvdata ( twl4030_codec_dev ) ;
u8 val ;
2009-12-15 08:33:10 +03:00
twl_i2c_read_u8 ( TWL4030_MODULE_AUDIO_VOICE , & val ,
2009-10-22 14:26:45 +04:00
codec - > resource [ id ] . reg ) ;
if ( enable )
val | = codec - > resource [ id ] . mask ;
else
val & = ~ codec - > resource [ id ] . mask ;
2009-12-15 08:33:10 +03:00
twl_i2c_write_u8 ( TWL4030_MODULE_AUDIO_VOICE ,
2009-10-22 14:26:45 +04:00
val , codec - > resource [ id ] . reg ) ;
return val ;
}
static inline int twl4030_codec_get_resource ( enum twl4030_codec_res id )
{
struct twl4030_codec * codec = platform_get_drvdata ( twl4030_codec_dev ) ;
u8 val ;
2009-12-15 08:33:10 +03:00
twl_i2c_read_u8 ( TWL4030_MODULE_AUDIO_VOICE , & val ,
2009-10-22 14:26:45 +04:00
codec - > resource [ id ] . reg ) ;
return val ;
}
/*
* Enable the resource .
* The function returns with error or the content of the register
*/
int twl4030_codec_enable_resource ( enum twl4030_codec_res id )
{
struct twl4030_codec * codec = platform_get_drvdata ( twl4030_codec_dev ) ;
int val ;
if ( id > = TWL4030_CODEC_RES_MAX ) {
dev_err ( & twl4030_codec_dev - > dev ,
" Invalid resource ID (%u) \n " , id ) ;
return - EINVAL ;
}
mutex_lock ( & codec - > mutex ) ;
if ( ! codec - > resource [ id ] . request_count )
/* Resource was disabled, enable it */
val = twl4030_codec_set_resource ( id , 1 ) ;
else
val = twl4030_codec_get_resource ( id ) ;
codec - > resource [ id ] . request_count + + ;
mutex_unlock ( & codec - > mutex ) ;
return val ;
}
EXPORT_SYMBOL_GPL ( twl4030_codec_enable_resource ) ;
/*
* Disable the resource .
* The function returns with error or the content of the register
*/
int twl4030_codec_disable_resource ( unsigned id )
{
struct twl4030_codec * codec = platform_get_drvdata ( twl4030_codec_dev ) ;
int val ;
if ( id > = TWL4030_CODEC_RES_MAX ) {
dev_err ( & twl4030_codec_dev - > dev ,
" Invalid resource ID (%u) \n " , id ) ;
return - EINVAL ;
}
mutex_lock ( & codec - > mutex ) ;
if ( ! codec - > resource [ id ] . request_count ) {
dev_err ( & twl4030_codec_dev - > dev ,
" Resource has been disabled already (%u) \n " , id ) ;
mutex_unlock ( & codec - > mutex ) ;
return - EPERM ;
}
codec - > resource [ id ] . request_count - - ;
if ( ! codec - > resource [ id ] . request_count )
/* Resource can be disabled now */
val = twl4030_codec_set_resource ( id , 0 ) ;
else
val = twl4030_codec_get_resource ( id ) ;
mutex_unlock ( & codec - > mutex ) ;
return val ;
}
EXPORT_SYMBOL_GPL ( twl4030_codec_disable_resource ) ;
2009-11-04 10:58:19 +03:00
unsigned int twl4030_codec_get_mclk ( void )
{
struct twl4030_codec * codec = platform_get_drvdata ( twl4030_codec_dev ) ;
return codec - > audio_mclk ;
}
EXPORT_SYMBOL_GPL ( twl4030_codec_get_mclk ) ;
2009-10-22 14:26:45 +04:00
static int __devinit twl4030_codec_probe ( struct platform_device * pdev )
{
struct twl4030_codec * codec ;
struct twl4030_codec_data * pdata = pdev - > dev . platform_data ;
struct mfd_cell * cell = NULL ;
int ret , childs = 0 ;
2009-11-04 10:58:19 +03:00
u8 val ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " Platform data is missing \n " ) ;
return - EINVAL ;
}
/* Configure APLL_INFREQ and disable APLL if enabled */
val = 0 ;
switch ( pdata - > audio_mclk ) {
case 19200000 :
val | = TWL4030_APLL_INFREQ_19200KHZ ;
break ;
case 26000000 :
val | = TWL4030_APLL_INFREQ_26000KHZ ;
break ;
case 38400000 :
val | = TWL4030_APLL_INFREQ_38400KHZ ;
break ;
default :
dev_err ( & pdev - > dev , " Invalid audio_mclk \n " ) ;
return - EINVAL ;
}
2009-12-15 08:33:10 +03:00
twl_i2c_write_u8 ( TWL4030_MODULE_AUDIO_VOICE ,
2009-11-04 10:58:19 +03:00
val , TWL4030_REG_APLL_CTL ) ;
2009-10-22 14:26:45 +04:00
codec = kzalloc ( sizeof ( struct twl4030_codec ) , GFP_KERNEL ) ;
if ( ! codec )
return - ENOMEM ;
platform_set_drvdata ( pdev , codec ) ;
twl4030_codec_dev = pdev ;
mutex_init ( & codec - > mutex ) ;
2009-11-04 10:58:19 +03:00
codec - > audio_mclk = pdata - > audio_mclk ;
2009-10-22 14:26:45 +04:00
/* Codec power */
codec - > resource [ TWL4030_CODEC_RES_POWER ] . reg = TWL4030_REG_CODEC_MODE ;
codec - > resource [ TWL4030_CODEC_RES_POWER ] . mask = TWL4030_CODECPDZ ;
/* PLL */
codec - > resource [ TWL4030_CODEC_RES_APLL ] . reg = TWL4030_REG_APLL_CTL ;
codec - > resource [ TWL4030_CODEC_RES_APLL ] . mask = TWL4030_APLL_EN ;
if ( pdata - > audio ) {
cell = & codec - > cells [ childs ] ;
cell - > name = " twl4030_codec_audio " ;
cell - > platform_data = pdata - > audio ;
cell - > data_size = sizeof ( * pdata - > audio ) ;
childs + + ;
}
if ( pdata - > vibra ) {
cell = & codec - > cells [ childs ] ;
cell - > name = " twl4030_codec_vibra " ;
cell - > platform_data = pdata - > vibra ;
cell - > data_size = sizeof ( * pdata - > vibra ) ;
childs + + ;
}
if ( childs )
ret = mfd_add_devices ( & pdev - > dev , pdev - > id , codec - > cells ,
childs , NULL , 0 ) ;
else {
dev_err ( & pdev - > dev , " No platform data found for childs \n " ) ;
ret = - ENODEV ;
}
if ( ! ret )
return 0 ;
platform_set_drvdata ( pdev , NULL ) ;
kfree ( codec ) ;
twl4030_codec_dev = NULL ;
return ret ;
}
static int __devexit twl4030_codec_remove ( struct platform_device * pdev )
{
struct twl4030_codec * codec = platform_get_drvdata ( pdev ) ;
mfd_remove_devices ( & pdev - > dev ) ;
platform_set_drvdata ( pdev , NULL ) ;
kfree ( codec ) ;
twl4030_codec_dev = NULL ;
return 0 ;
}
MODULE_ALIAS ( " platform:twl4030_codec " ) ;
static struct platform_driver twl4030_codec_driver = {
. probe = twl4030_codec_probe ,
. remove = __devexit_p ( twl4030_codec_remove ) ,
. driver = {
. owner = THIS_MODULE ,
. name = " twl4030_codec " ,
} ,
} ;
static int __devinit twl4030_codec_init ( void )
{
return platform_driver_register ( & twl4030_codec_driver ) ;
}
module_init ( twl4030_codec_init ) ;
static void __devexit twl4030_codec_exit ( void )
{
platform_driver_unregister ( & twl4030_codec_driver ) ;
}
module_exit ( twl4030_codec_exit ) ;
MODULE_AUTHOR ( " Peter Ujfalusi <peter.ujfalusi@nokia.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;