2019-01-19 14:33:33 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
2019-10-14 13:20:14 +03:00
* Copyright 2019 Google , Inc .
*
* ChromeOS Embedded Controller codec driver .
2019-01-19 14:33:33 +03:00
*
* This driver uses the cros - ec interface to communicate with the ChromeOS
* EC for audio function .
*/
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/kernel.h>
# include <linux/module.h>
2019-09-02 12:53:05 +03:00
# include <linux/platform_data/cros_ec_commands.h>
# include <linux/platform_data/cros_ec_proto.h>
2019-01-19 14:33:33 +03:00
# include <linux/platform_device.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/tlv.h>
2019-10-14 13:20:14 +03:00
struct cros_ec_codec_priv {
2019-01-19 14:33:33 +03:00
struct device * dev ;
struct cros_ec_device * ec_device ;
} ;
2019-10-14 13:20:14 +03:00
static int send_ec_host_command ( struct cros_ec_device * ec_dev , uint32_t cmd ,
uint8_t * out , size_t outsize ,
uint8_t * in , size_t insize )
2019-01-19 14:33:33 +03:00
{
int ret ;
2019-10-14 13:20:14 +03:00
struct cros_ec_command * msg ;
msg = kmalloc ( sizeof ( * msg ) + max ( outsize , insize ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
2019-01-19 14:33:33 +03:00
msg - > version = 0 ;
2019-10-14 13:20:14 +03:00
msg - > command = cmd ;
msg - > outsize = outsize ;
msg - > insize = insize ;
if ( outsize )
memcpy ( msg - > data , out , outsize ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
ret = cros_ec_cmd_xfer_status ( ec_dev , msg ) ;
if ( ret < 0 )
goto error ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
if ( insize )
memcpy ( in , msg - > data , insize ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
ret = 0 ;
error :
kfree ( msg ) ;
2019-01-19 14:33:33 +03:00
return ret ;
}
2019-10-14 13:20:14 +03:00
static int dmic_get_gain ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
2019-01-19 14:33:33 +03:00
{
2019-10-14 13:20:14 +03:00
struct snd_soc_component * component =
snd_soc_kcontrol_component ( kcontrol ) ;
struct cros_ec_codec_priv * priv =
2019-01-19 14:33:33 +03:00
snd_soc_component_get_drvdata ( component ) ;
2019-10-14 13:20:15 +03:00
struct ec_param_ec_codec_dmic p ;
2019-10-17 17:00:10 +03:00
struct ec_response_ec_codec_dmic_get_gain_idx r ;
2019-10-14 13:20:14 +03:00
int ret ;
2019-01-19 14:33:33 +03:00
2019-10-17 17:00:10 +03:00
p . cmd = EC_CODEC_DMIC_GET_GAIN_IDX ;
p . get_gain_idx_param . channel = EC_CODEC_DMIC_CHANNEL_0 ;
2019-10-14 13:20:15 +03:00
ret = send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_DMIC ,
2019-10-14 13:20:14 +03:00
( uint8_t * ) & p , sizeof ( p ) ,
( uint8_t * ) & r , sizeof ( r ) ) ;
if ( ret < 0 )
return ret ;
2019-10-17 17:00:10 +03:00
ucontrol - > value . integer . value [ 0 ] = r . gain ;
2019-01-19 14:33:33 +03:00
2019-10-17 17:00:10 +03:00
p . cmd = EC_CODEC_DMIC_GET_GAIN_IDX ;
p . get_gain_idx_param . channel = EC_CODEC_DMIC_CHANNEL_1 ;
ret = send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_DMIC ,
( uint8_t * ) & p , sizeof ( p ) ,
( uint8_t * ) & r , sizeof ( r ) ) ;
if ( ret < 0 )
return ret ;
ucontrol - > value . integer . value [ 1 ] = r . gain ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
return 0 ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
static int dmic_put_gain ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
2019-01-19 14:33:33 +03:00
{
2019-10-14 13:20:14 +03:00
struct snd_soc_component * component =
snd_soc_kcontrol_component ( kcontrol ) ;
struct cros_ec_codec_priv * priv =
snd_soc_component_get_drvdata ( component ) ;
struct soc_mixer_control * control =
( struct soc_mixer_control * ) kcontrol - > private_value ;
int max_dmic_gain = control - > max ;
int left = ucontrol - > value . integer . value [ 0 ] ;
int right = ucontrol - > value . integer . value [ 1 ] ;
2019-10-14 13:20:15 +03:00
struct ec_param_ec_codec_dmic p ;
2019-10-17 17:00:10 +03:00
int ret ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
if ( left > max_dmic_gain | | right > max_dmic_gain )
2019-01-19 14:33:33 +03:00
return - EINVAL ;
2019-10-14 13:20:14 +03:00
dev_dbg ( component - > dev , " set mic gain to %u, %u \n " , left , right ) ;
2019-01-19 14:33:33 +03:00
2019-10-17 17:00:10 +03:00
p . cmd = EC_CODEC_DMIC_SET_GAIN_IDX ;
p . set_gain_idx_param . channel = EC_CODEC_DMIC_CHANNEL_0 ;
p . set_gain_idx_param . gain = left ;
ret = send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_DMIC ,
( uint8_t * ) & p , sizeof ( p ) , NULL , 0 ) ;
if ( ret < 0 )
return ret ;
p . cmd = EC_CODEC_DMIC_SET_GAIN_IDX ;
p . set_gain_idx_param . channel = EC_CODEC_DMIC_CHANNEL_1 ;
p . set_gain_idx_param . gain = right ;
2019-10-14 13:20:15 +03:00
return send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_DMIC ,
2019-10-14 13:20:14 +03:00
( uint8_t * ) & p , sizeof ( p ) , NULL , 0 ) ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
static const DECLARE_TLV_DB_SCALE ( dmic_gain_tlv , 0 , 100 , 0 ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
enum {
DMIC_CTL_GAIN = 0 ,
} ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
static struct snd_kcontrol_new dmic_controls [ ] = {
[ DMIC_CTL_GAIN ] =
SOC_DOUBLE_EXT_TLV ( " EC Mic Gain " , SND_SOC_NOPM , SND_SOC_NOPM ,
0 , 0 , 0 , dmic_get_gain , dmic_put_gain ,
dmic_gain_tlv ) ,
} ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:15 +03:00
static int dmic_probe ( struct snd_soc_component * component )
{
struct cros_ec_codec_priv * priv =
snd_soc_component_get_drvdata ( component ) ;
struct device * dev = priv - > dev ;
struct soc_mixer_control * control ;
2019-10-17 17:00:10 +03:00
struct ec_param_ec_codec_dmic p ;
struct ec_response_ec_codec_dmic_get_max_gain r ;
int ret ;
2019-10-14 13:20:15 +03:00
2019-10-17 17:00:10 +03:00
p . cmd = EC_CODEC_DMIC_GET_MAX_GAIN ;
ret = send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_DMIC ,
( uint8_t * ) & p , sizeof ( p ) ,
( uint8_t * ) & r , sizeof ( r ) ) ;
if ( ret < 0 ) {
dev_warn ( dev , " get_max_gain() unsupported \n " ) ;
return 0 ;
2019-10-14 13:20:15 +03:00
}
2019-10-17 17:00:10 +03:00
dev_dbg ( dev , " max gain = %d \n " , r . max_gain ) ;
2019-10-14 13:20:15 +03:00
control = ( struct soc_mixer_control * )
dmic_controls [ DMIC_CTL_GAIN ] . private_value ;
2019-10-17 17:00:10 +03:00
control - > max = r . max_gain ;
control - > platform_max = r . max_gain ;
2019-10-14 13:20:15 +03:00
return snd_soc_add_component_controls ( component ,
& dmic_controls [ DMIC_CTL_GAIN ] , 1 ) ;
}
2019-10-14 13:20:14 +03:00
static int i2s_rx_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
2019-01-19 14:33:33 +03:00
{
struct snd_soc_component * component = dai - > component ;
2019-10-14 13:20:14 +03:00
struct cros_ec_codec_priv * priv =
snd_soc_component_get_drvdata ( component ) ;
struct ec_param_ec_codec_i2s_rx p ;
enum ec_codec_i2s_rx_sample_depth depth ;
2019-01-19 14:33:33 +03:00
int ret ;
2019-10-14 13:20:14 +03:00
if ( params_rate ( params ) ! = 48000 )
2019-01-19 14:33:33 +03:00
return - EINVAL ;
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S16_LE :
2019-10-14 13:20:14 +03:00
depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_16 ;
2019-01-19 14:33:33 +03:00
break ;
case SNDRV_PCM_FORMAT_S24_LE :
2019-10-14 13:20:14 +03:00
depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_24 ;
2019-01-19 14:33:33 +03:00
break ;
default :
return - EINVAL ;
}
2019-10-14 13:20:14 +03:00
dev_dbg ( component - > dev , " set depth to %u \n " , depth ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
p . cmd = EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH ;
p . set_sample_depth_param . depth = depth ;
ret = send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_I2S_RX ,
( uint8_t * ) & p , sizeof ( p ) , NULL , 0 ) ;
2019-01-19 14:33:33 +03:00
if ( ret < 0 )
return ret ;
2019-10-14 13:20:14 +03:00
dev_dbg ( component - > dev , " set bclk to %u \n " ,
snd_soc_params_to_bclk ( params ) ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
p . cmd = EC_CODEC_I2S_RX_SET_BCLK ;
p . set_bclk_param . bclk = snd_soc_params_to_bclk ( params ) ;
return send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_I2S_RX ,
( uint8_t * ) & p , sizeof ( p ) , NULL , 0 ) ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
static int i2s_rx_set_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
2019-01-19 14:33:33 +03:00
{
2019-10-14 13:20:14 +03:00
struct snd_soc_component * component = dai - > component ;
struct cros_ec_codec_priv * priv =
2019-01-19 14:33:33 +03:00
snd_soc_component_get_drvdata ( component ) ;
2019-10-14 13:20:14 +03:00
struct ec_param_ec_codec_i2s_rx p ;
enum ec_codec_i2s_rx_daifmt daifmt ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBS_CFS :
break ;
default :
2019-01-19 14:33:33 +03:00
return - EINVAL ;
2019-10-14 13:20:14 +03:00
}
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
break ;
default :
return - EINVAL ;
}
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
daifmt = EC_CODEC_I2S_RX_DAIFMT_I2S ;
break ;
case SND_SOC_DAIFMT_RIGHT_J :
daifmt = EC_CODEC_I2S_RX_DAIFMT_RIGHT_J ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
daifmt = EC_CODEC_I2S_RX_DAIFMT_LEFT_J ;
break ;
default :
return - EINVAL ;
}
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
dev_dbg ( component - > dev , " set format to %u \n " , daifmt ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
p . cmd = EC_CODEC_I2S_RX_SET_DAIFMT ;
p . set_daifmt_param . daifmt = daifmt ;
return send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_I2S_RX ,
( uint8_t * ) & p , sizeof ( p ) , NULL , 0 ) ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
static const struct snd_soc_dai_ops i2s_rx_dai_ops = {
. hw_params = i2s_rx_hw_params ,
. set_fmt = i2s_rx_set_fmt ,
} ;
static int i2s_rx_event ( struct snd_soc_dapm_widget * w ,
struct snd_kcontrol * kcontrol , int event )
2019-01-19 14:33:33 +03:00
{
struct snd_soc_component * component =
snd_soc_dapm_to_component ( w - > dapm ) ;
2019-10-14 13:20:14 +03:00
struct cros_ec_codec_priv * priv =
snd_soc_component_get_drvdata ( component ) ;
struct ec_param_ec_codec_i2s_rx p ;
2019-01-19 14:33:33 +03:00
switch ( event ) {
case SND_SOC_DAPM_PRE_PMU :
2019-10-14 13:20:14 +03:00
dev_dbg ( component - > dev , " enable I2S RX \n " ) ;
p . cmd = EC_CODEC_I2S_RX_ENABLE ;
break ;
2019-01-19 14:33:33 +03:00
case SND_SOC_DAPM_PRE_PMD :
2019-10-14 13:20:14 +03:00
dev_dbg ( component - > dev , " disable I2S RX \n " ) ;
p . cmd = EC_CODEC_I2S_RX_DISABLE ;
break ;
default :
return 0 ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
return send_ec_host_command ( priv - > ec_device , EC_CMD_EC_CODEC_I2S_RX ,
( uint8_t * ) & p , sizeof ( p ) , NULL , 0 ) ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
static struct snd_soc_dapm_widget i2s_rx_dapm_widgets [ ] = {
2019-01-19 14:33:33 +03:00
SND_SOC_DAPM_INPUT ( " DMIC " ) ,
2019-10-14 13:20:14 +03:00
SND_SOC_DAPM_SUPPLY ( " I2S RX Enable " , SND_SOC_NOPM , 0 , 0 , i2s_rx_event ,
2019-01-19 14:33:33 +03:00
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD ) ,
2019-10-14 13:20:14 +03:00
SND_SOC_DAPM_AIF_OUT ( " I2S RX " , " I2S Capture " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
} ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
static struct snd_soc_dapm_route i2s_rx_dapm_routes [ ] = {
{ " I2S RX " , NULL , " DMIC " } ,
{ " I2S RX " , NULL , " I2S RX Enable " } ,
2019-01-19 14:33:33 +03:00
} ;
2019-10-14 13:20:14 +03:00
static struct snd_soc_dai_driver i2s_rx_dai_driver = {
. name = " EC Codec I2S RX " ,
. capture = {
. stream_name = " I2S Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE ,
} ,
. ops = & i2s_rx_dai_ops ,
2019-01-19 14:33:33 +03:00
} ;
2019-10-14 13:20:14 +03:00
static int i2s_rx_probe ( struct snd_soc_component * component )
2019-01-19 14:33:33 +03:00
{
2019-10-14 13:20:15 +03:00
return dmic_probe ( component ) ;
2019-01-19 14:33:33 +03:00
}
2019-10-14 13:20:14 +03:00
static const struct snd_soc_component_driver i2s_rx_component_driver = {
. probe = i2s_rx_probe ,
. dapm_widgets = i2s_rx_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( i2s_rx_dapm_widgets ) ,
. dapm_routes = i2s_rx_dapm_routes ,
. num_dapm_routes = ARRAY_SIZE ( i2s_rx_dapm_routes ) ,
2019-01-19 14:33:33 +03:00
} ;
2019-10-14 13:20:14 +03:00
static int cros_ec_codec_platform_probe ( struct platform_device * pdev )
2019-01-19 14:33:33 +03:00
{
2019-10-14 13:20:14 +03:00
struct device * dev = & pdev - > dev ;
struct cros_ec_device * ec_device = dev_get_drvdata ( pdev - > dev . parent ) ;
struct cros_ec_codec_priv * priv ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
2019-01-19 14:33:33 +03:00
return - ENOMEM ;
2019-10-14 13:20:14 +03:00
priv - > dev = dev ;
priv - > ec_device = ec_device ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
platform_set_drvdata ( pdev , priv ) ;
2019-01-19 14:33:33 +03:00
2019-10-14 13:20:14 +03:00
return devm_snd_soc_register_component ( dev , & i2s_rx_component_driver ,
& i2s_rx_dai_driver , 1 ) ;
2019-01-19 14:33:33 +03:00
}
# ifdef CONFIG_OF
static const struct of_device_id cros_ec_codec_of_match [ ] = {
{ . compatible = " google,cros-ec-codec " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cros_ec_codec_of_match ) ;
# endif
static struct platform_driver cros_ec_codec_platform_driver = {
. driver = {
2019-10-14 13:20:14 +03:00
. name = " cros-ec-codec " ,
2019-01-19 14:33:33 +03:00
. of_match_table = of_match_ptr ( cros_ec_codec_of_match ) ,
} ,
. probe = cros_ec_codec_platform_probe ,
} ;
module_platform_driver ( cros_ec_codec_platform_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " ChromeOS EC codec driver " ) ;
MODULE_AUTHOR ( " Cheng-Yi Chiang <cychiang@chromium.org> " ) ;
2019-10-14 13:20:14 +03:00
MODULE_ALIAS ( " platform:cros-ec-codec " ) ;