2011-01-04 20:16:50 +05:30
/*
* mfld_machine . c - ASoc Machine driver for Intel Medfield MID platform
*
* Copyright ( C ) 2010 Intel Corp
* Author : Vinod Koul < vinod . koul @ intel . com >
* Author : Harsha Priya < priya . harsha @ intel . com >
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; version 2 of the License .
*
* 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 . ,
* 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/init.h>
# include <linux/device.h>
# include <linux/slab.h>
2011-02-09 21:44:33 +05:30
# include <linux/io.h>
2011-07-15 12:38:28 -04:00
# include <linux/module.h>
2011-01-04 20:16:50 +05:30
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
2011-02-09 21:44:33 +05:30
# include <sound/jack.h>
2011-01-04 20:16:50 +05:30
# include "../codecs/sn95031.h"
# define MID_MONO 1
# define MID_STEREO 2
# define MID_MAX_CAP 5
2011-02-09 21:44:33 +05:30
# define MFLD_JACK_INSERT 0x04
enum soc_mic_bias_zones {
MFLD_MV_START = 0 ,
/* mic bias volutage range for Headphones*/
MFLD_MV_HP = 400 ,
/* mic bias volutage range for American Headset*/
MFLD_MV_AM_HS = 650 ,
/* mic bias volutage range for Headset*/
MFLD_MV_HS = 2000 ,
MFLD_MV_UNDEFINED ,
} ;
2011-01-04 20:16:50 +05:30
static unsigned int hs_switch ;
static unsigned int lo_dac ;
2011-02-09 21:44:33 +05:30
struct mfld_mc_private {
void __iomem * int_base ;
u8 interrupt_status ;
} ;
struct snd_soc_jack mfld_jack ;
/*Headset jack detection DAPM pins */
static struct snd_soc_jack_pin mfld_jack_pins [ ] = {
{
. pin = " Headphones " ,
. mask = SND_JACK_HEADPHONE ,
} ,
{
. pin = " AMIC1 " ,
. mask = SND_JACK_MICROPHONE ,
} ,
} ;
2011-02-10 12:58:01 +05:30
/* jack detection voltage zones */
static struct snd_soc_jack_zone mfld_zones [ ] = {
{ MFLD_MV_START , MFLD_MV_AM_HS , SND_JACK_HEADPHONE } ,
{ MFLD_MV_AM_HS , MFLD_MV_HS , SND_JACK_HEADSET } ,
} ;
2011-01-04 20:16:50 +05:30
/* sound card controls */
static const char * headset_switch_text [ ] = { " Earpiece " , " Headset " } ;
static const char * lo_text [ ] = { " Vibra " , " Headset " , " IHF " , " None " } ;
static const struct soc_enum headset_enum =
SOC_ENUM_SINGLE_EXT ( 2 , headset_switch_text ) ;
static const struct soc_enum lo_enum =
SOC_ENUM_SINGLE_EXT ( 4 , lo_text ) ;
static int headset_get_switch ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
ucontrol - > value . integer . value [ 0 ] = hs_switch ;
return 0 ;
}
static int headset_set_switch ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct snd_soc_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
2014-02-18 15:22:21 +00:00
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
2011-01-04 20:16:50 +05:30
if ( ucontrol - > value . integer . value [ 0 ] = = hs_switch )
return 0 ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_mutex_lock ( dapm ) ;
2011-01-04 20:16:50 +05:30
if ( ucontrol - > value . integer . value [ 0 ] ) {
pr_debug ( " hs_set HS path \n " ) ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_enable_pin_unlocked ( dapm , " Headphones " ) ;
snd_soc_dapm_disable_pin_unlocked ( dapm , " EPOUT " ) ;
2011-01-04 20:16:50 +05:30
} else {
pr_debug ( " hs_set EP path \n " ) ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_disable_pin_unlocked ( dapm , " Headphones " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " EPOUT " ) ;
2011-01-04 20:16:50 +05:30
}
2014-02-18 15:22:21 +00:00
snd_soc_dapm_sync_unlocked ( dapm ) ;
snd_soc_dapm_mutex_unlock ( dapm ) ;
2011-01-04 20:16:50 +05:30
hs_switch = ucontrol - > value . integer . value [ 0 ] ;
return 0 ;
}
static void lo_enable_out_pins ( struct snd_soc_codec * codec )
{
2014-02-18 15:22:21 +00:00
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " IHFOUTL " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " IHFOUTR " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " LINEOUTL " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " LINEOUTR " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " VIB1OUT " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " VIB2OUT " ) ;
2011-01-04 20:16:50 +05:30
if ( hs_switch ) {
2014-02-18 15:22:21 +00:00
snd_soc_dapm_enable_pin_unlocked ( dapm , " Headphones " ) ;
snd_soc_dapm_disable_pin_unlocked ( dapm , " EPOUT " ) ;
2011-01-04 20:16:50 +05:30
} else {
2014-02-18 15:22:21 +00:00
snd_soc_dapm_disable_pin_unlocked ( dapm , " Headphones " ) ;
snd_soc_dapm_enable_pin_unlocked ( dapm , " EPOUT " ) ;
2011-01-04 20:16:50 +05:30
}
}
static int lo_get_switch ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
ucontrol - > value . integer . value [ 0 ] = lo_dac ;
return 0 ;
}
static int lo_set_switch ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct snd_soc_codec * codec = snd_kcontrol_chip ( kcontrol ) ;
2014-02-18 15:22:21 +00:00
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
2011-01-04 20:16:50 +05:30
if ( ucontrol - > value . integer . value [ 0 ] = = lo_dac )
return 0 ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_mutex_lock ( dapm ) ;
2011-01-04 20:16:50 +05:30
/* we dont want to work with last state of lineout so just enable all
* pins and then disable pins not required
*/
lo_enable_out_pins ( codec ) ;
2014-02-18 15:22:21 +00:00
2011-01-04 20:16:50 +05:30
switch ( ucontrol - > value . integer . value [ 0 ] ) {
case 0 :
pr_debug ( " set vibra path \n " ) ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_disable_pin_unlocked ( dapm , " VIB1OUT " ) ;
snd_soc_dapm_disable_pin_unlocked ( dapm , " VIB2OUT " ) ;
2011-01-04 20:16:50 +05:30
snd_soc_update_bits ( codec , SN95031_LOCTL , 0x66 , 0 ) ;
break ;
case 1 :
pr_debug ( " set hs path \n " ) ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_disable_pin_unlocked ( dapm , " Headphones " ) ;
snd_soc_dapm_disable_pin_unlocked ( dapm , " EPOUT " ) ;
2011-01-04 20:16:50 +05:30
snd_soc_update_bits ( codec , SN95031_LOCTL , 0x66 , 0x22 ) ;
break ;
case 2 :
pr_debug ( " set spkr path \n " ) ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_disable_pin_unlocked ( dapm , " IHFOUTL " ) ;
snd_soc_dapm_disable_pin_unlocked ( dapm , " IHFOUTR " ) ;
2011-01-04 20:16:50 +05:30
snd_soc_update_bits ( codec , SN95031_LOCTL , 0x66 , 0x44 ) ;
break ;
case 3 :
pr_debug ( " set null path \n " ) ;
2014-02-18 15:22:21 +00:00
snd_soc_dapm_disable_pin_unlocked ( dapm , " LINEOUTL " ) ;
snd_soc_dapm_disable_pin_unlocked ( dapm , " LINEOUTR " ) ;
2011-01-04 20:16:50 +05:30
snd_soc_update_bits ( codec , SN95031_LOCTL , 0x66 , 0x66 ) ;
break ;
}
2014-02-18 15:22:21 +00:00
snd_soc_dapm_sync_unlocked ( dapm ) ;
snd_soc_dapm_mutex_unlock ( dapm ) ;
2011-01-04 20:16:50 +05:30
lo_dac = ucontrol - > value . integer . value [ 0 ] ;
return 0 ;
}
static const struct snd_kcontrol_new mfld_snd_controls [ ] = {
SOC_ENUM_EXT ( " Playback Switch " , headset_enum ,
headset_get_switch , headset_set_switch ) ,
SOC_ENUM_EXT ( " Lineout Mux " , lo_enum ,
lo_get_switch , lo_set_switch ) ,
} ;
2011-02-09 21:44:33 +05:30
static const struct snd_soc_dapm_widget mfld_widgets [ ] = {
SND_SOC_DAPM_HP ( " Headphones " , NULL ) ,
SND_SOC_DAPM_MIC ( " Mic " , NULL ) ,
} ;
static const struct snd_soc_dapm_route mfld_map [ ] = {
{ " Headphones " , NULL , " HPOUTR " } ,
{ " Headphones " , NULL , " HPOUTL " } ,
{ " Mic " , NULL , " AMIC1 " } ,
} ;
static void mfld_jack_check ( unsigned int intr_status )
{
struct mfld_jack_data jack_data ;
jack_data . mfld_jack = & mfld_jack ;
jack_data . intr_id = intr_status ;
sn95031_jack_detection ( & jack_data ) ;
/* TODO: add american headset detection post gpiolib support */
}
2011-01-04 20:16:50 +05:30
static int mfld_init ( struct snd_soc_pcm_runtime * runtime )
{
struct snd_soc_codec * codec = runtime - > codec ;
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
int ret_val ;
2011-02-09 21:44:33 +05:30
/* Add jack sense widgets */
snd_soc_dapm_new_controls ( dapm , mfld_widgets , ARRAY_SIZE ( mfld_widgets ) ) ;
/* Set up the map */
snd_soc_dapm_add_routes ( dapm , mfld_map , ARRAY_SIZE ( mfld_map ) ) ;
/* always connected */
snd_soc_dapm_enable_pin ( dapm , " Headphones " ) ;
snd_soc_dapm_enable_pin ( dapm , " Mic " ) ;
2012-02-03 17:43:09 +00:00
ret_val = snd_soc_add_codec_controls ( codec , mfld_snd_controls ,
2011-01-04 20:16:50 +05:30
ARRAY_SIZE ( mfld_snd_controls ) ) ;
if ( ret_val ) {
pr_err ( " soc_add_controls failed %d " , ret_val ) ;
return ret_val ;
}
/* default is earpiece pin, userspace sets it explcitly */
2011-02-09 21:44:33 +05:30
snd_soc_dapm_disable_pin ( dapm , " Headphones " ) ;
2011-01-04 20:16:50 +05:30
/* default is lineout NC, userspace sets it explcitly */
snd_soc_dapm_disable_pin ( dapm , " LINEOUTL " ) ;
snd_soc_dapm_disable_pin ( dapm , " LINEOUTR " ) ;
lo_dac = 3 ;
hs_switch = 0 ;
2011-01-28 22:28:32 +05:30
/* we dont use linein in this so set to NC */
snd_soc_dapm_disable_pin ( dapm , " LINEINL " ) ;
snd_soc_dapm_disable_pin ( dapm , " LINEINR " ) ;
2011-02-09 21:44:33 +05:30
/* Headset and button jack detection */
ret_val = snd_soc_jack_new ( codec , " Intel(R) MID Audio Jack " ,
SND_JACK_HEADSET | SND_JACK_BTN_0 |
SND_JACK_BTN_1 , & mfld_jack ) ;
if ( ret_val ) {
pr_err ( " jack creation failed \n " ) ;
return ret_val ;
}
ret_val = snd_soc_jack_add_pins ( & mfld_jack ,
ARRAY_SIZE ( mfld_jack_pins ) , mfld_jack_pins ) ;
if ( ret_val ) {
pr_err ( " adding jack pins failed \n " ) ;
return ret_val ;
}
2011-02-10 12:58:01 +05:30
ret_val = snd_soc_jack_add_zones ( & mfld_jack ,
ARRAY_SIZE ( mfld_zones ) , mfld_zones ) ;
if ( ret_val ) {
pr_err ( " adding jack zones failed \n " ) ;
return ret_val ;
}
2011-02-09 21:44:33 +05:30
/* we want to check if anything is inserted at boot,
* so send a fake event to codec and it will read adc
* to find if anything is there or not */
mfld_jack_check ( MFLD_JACK_INSERT ) ;
return ret_val ;
2011-01-04 20:16:50 +05:30
}
2011-12-14 19:23:01 +08:00
static struct snd_soc_dai_link mfld_msic_dailink [ ] = {
2011-01-04 20:16:50 +05:30
{
. name = " Medfield Headset " ,
. stream_name = " Headset " ,
. cpu_dai_name = " Headset-cpu-dai " ,
. codec_dai_name = " SN95031 Headset " ,
. codec_name = " sn95031 " ,
. platform_name = " sst-platform " ,
. init = mfld_init ,
} ,
{
. name = " Medfield Speaker " ,
. stream_name = " Speaker " ,
. cpu_dai_name = " Speaker-cpu-dai " ,
. codec_dai_name = " SN95031 Speaker " ,
. codec_name = " sn95031 " ,
. platform_name = " sst-platform " ,
. init = NULL ,
} ,
{
. name = " Medfield Vibra " ,
. stream_name = " Vibra1 " ,
. cpu_dai_name = " Vibra1-cpu-dai " ,
. codec_dai_name = " SN95031 Vibra1 " ,
. codec_name = " sn95031 " ,
. platform_name = " sst-platform " ,
. init = NULL ,
} ,
{
. name = " Medfield Haptics " ,
. stream_name = " Vibra2 " ,
. cpu_dai_name = " Vibra2-cpu-dai " ,
. codec_dai_name = " SN95031 Vibra2 " ,
. codec_name = " sn95031 " ,
. platform_name = " sst-platform " ,
2012-08-16 17:10:42 +05:30
. init = NULL ,
} ,
{
. name = " Medfield Compress " ,
. stream_name = " Speaker " ,
. cpu_dai_name = " Compress-cpu-dai " ,
. codec_dai_name = " SN95031 Speaker " ,
. codec_name = " sn95031 " ,
. platform_name = " sst-platform " ,
2011-01-04 20:16:50 +05:30
. init = NULL ,
} ,
} ;
/* SoC card */
static struct snd_soc_card snd_soc_card_mfld = {
. name = " medfield_audio " ,
2011-12-23 14:50:17 +08:00
. owner = THIS_MODULE ,
2011-01-04 20:16:50 +05:30
. dai_link = mfld_msic_dailink ,
. num_links = ARRAY_SIZE ( mfld_msic_dailink ) ,
} ;
2011-02-09 21:44:33 +05:30
static irqreturn_t snd_mfld_jack_intr_handler ( int irq , void * dev )
{
struct mfld_mc_private * mc_private = ( struct mfld_mc_private * ) dev ;
memcpy_fromio ( & mc_private - > interrupt_status ,
( ( void * ) ( mc_private - > int_base ) ) ,
sizeof ( u8 ) ) ;
return IRQ_WAKE_THREAD ;
}
static irqreturn_t snd_mfld_jack_detection ( int irq , void * data )
{
struct mfld_mc_private * mc_drv_ctx = ( struct mfld_mc_private * ) data ;
if ( mfld_jack . codec = = NULL )
return IRQ_HANDLED ;
mfld_jack_check ( mc_drv_ctx - > interrupt_status ) ;
return IRQ_HANDLED ;
}
2012-12-07 09:26:25 -05:00
static int snd_mfld_mc_probe ( struct platform_device * pdev )
2011-01-04 20:16:50 +05:30
{
2011-02-09 21:44:33 +05:30
int ret_val = 0 , irq ;
struct mfld_mc_private * mc_drv_ctx ;
struct resource * irq_mem ;
2011-01-04 20:16:50 +05:30
pr_debug ( " snd_mfld_mc_probe called \n " ) ;
2011-02-09 21:44:33 +05:30
/* retrive the irq number */
irq = platform_get_irq ( pdev , 0 ) ;
/* audio interrupt base of SRAM location where
* interrupts are stored by System FW */
2013-06-25 10:16:56 +08:00
mc_drv_ctx = devm_kzalloc ( & pdev - > dev , sizeof ( * mc_drv_ctx ) , GFP_ATOMIC ) ;
2011-02-09 21:44:33 +05:30
if ( ! mc_drv_ctx ) {
pr_err ( " allocation failed \n " ) ;
2011-01-04 20:16:50 +05:30
return - ENOMEM ;
}
2011-02-09 21:44:33 +05:30
irq_mem = platform_get_resource_byname (
pdev , IORESOURCE_MEM , " IRQ_BASE " ) ;
if ( ! irq_mem ) {
pr_err ( " no mem resource given \n " ) ;
2013-06-25 10:16:56 +08:00
return - ENODEV ;
2011-02-09 21:44:33 +05:30
}
2013-06-25 10:16:56 +08:00
mc_drv_ctx - > int_base = devm_ioremap_nocache ( & pdev - > dev , irq_mem - > start ,
resource_size ( irq_mem ) ) ;
2011-02-09 21:44:33 +05:30
if ( ! mc_drv_ctx - > int_base ) {
pr_err ( " Mapping of cache failed \n " ) ;
2013-06-25 10:16:56 +08:00
return - ENOMEM ;
2011-02-09 21:44:33 +05:30
}
/* register for interrupt */
2013-06-25 10:16:56 +08:00
ret_val = devm_request_threaded_irq ( & pdev - > dev , irq ,
snd_mfld_jack_intr_handler ,
2011-02-09 21:44:33 +05:30
snd_mfld_jack_detection ,
IRQF_SHARED , pdev - > dev . driver - > name , mc_drv_ctx ) ;
if ( ret_val ) {
pr_err ( " cannot register IRQ \n " ) ;
2013-06-25 10:16:56 +08:00
return ret_val ;
2011-02-09 21:44:33 +05:30
}
2011-02-15 18:28:55 +05:30
/* register the soc card */
snd_soc_card_mfld . dev = & pdev - > dev ;
2013-09-17 09:49:04 +05:30
ret_val = devm_snd_soc_register_card ( & pdev - > dev , & snd_soc_card_mfld ) ;
2011-01-04 20:16:50 +05:30
if ( ret_val ) {
2011-02-15 18:28:55 +05:30
pr_debug ( " snd_soc_register_card failed %d \n " , ret_val ) ;
2013-06-25 10:16:56 +08:00
return ret_val ;
2011-01-04 20:16:50 +05:30
}
2011-02-09 21:44:33 +05:30
platform_set_drvdata ( pdev , mc_drv_ctx ) ;
2011-01-04 20:16:50 +05:30
pr_debug ( " successfully exited probe \n " ) ;
2013-06-25 10:16:56 +08:00
return 0 ;
2011-01-04 20:16:50 +05:30
}
static struct platform_driver snd_mfld_mc_driver = {
. driver = {
. owner = THIS_MODULE ,
. name = " msic_audio " ,
} ,
. probe = snd_mfld_mc_probe ,
} ;
2011-11-24 11:51:56 +08:00
module_platform_driver ( snd_mfld_mc_driver ) ;
2011-01-04 20:16:50 +05:30
MODULE_DESCRIPTION ( " ASoC Intel(R) MID Machine driver " ) ;
MODULE_AUTHOR ( " Vinod Koul <vinod.koul@intel.com> " ) ;
MODULE_AUTHOR ( " Harsha Priya <priya.harsha@intel.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:msic-audio " ) ;