2019-05-29 17:17:58 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-06-10 15:15:54 +03:00
/*
* Copyright ( c ) 2015 The Linux Foundation . All rights reserved .
*/
# include <linux/device.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/clk.h>
# include <linux/platform_device.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
2017-08-17 11:02:11 +03:00
# include <sound/jack.h>
2015-06-10 15:15:54 +03:00
# include <sound/soc.h>
2017-08-17 11:02:11 +03:00
# include <uapi/linux/input-event-codes.h>
2015-06-10 15:15:54 +03:00
# include <dt-bindings/sound/apq8016-lpass.h>
struct apq8016_sbc_data {
void __iomem * mic_iomux ;
void __iomem * spkr_iomux ;
2017-08-17 11:02:11 +03:00
struct snd_soc_jack jack ;
bool jack_setup ;
2015-06-10 15:15:54 +03:00
struct snd_soc_dai_link dai_link [ ] ; /* dynamically allocated */
} ;
2016-02-11 15:18:45 +03:00
# define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21)
2015-06-10 15:15:54 +03:00
# define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17)
# define MIC_CTRL_TLMM_SCLK_EN BIT(1)
# define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16))
2017-08-02 16:17:47 +03:00
# define DEFAULT_MCLK_RATE 9600000
2015-06-10 15:15:54 +03:00
static int apq8016_sbc_dai_init ( struct snd_soc_pcm_runtime * rtd )
{
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
2017-12-05 07:24:13 +03:00
struct snd_soc_component * component ;
2017-08-02 16:17:47 +03:00
struct snd_soc_dai_link * dai_link = rtd - > dai_link ;
2015-06-10 15:15:54 +03:00
struct snd_soc_card * card = rtd - > card ;
struct apq8016_sbc_data * pdata = snd_soc_card_get_drvdata ( card ) ;
2017-08-02 16:17:47 +03:00
int i , rval ;
2015-06-10 15:15:54 +03:00
switch ( cpu_dai - > id ) {
case MI2S_PRIMARY :
writel ( readl ( pdata - > spkr_iomux ) | SPKR_CTL_PRI_WS_SLAVE_SEL_11 ,
pdata - > spkr_iomux ) ;
break ;
case MI2S_QUATERNARY :
/* Configure the Quat MI2S to TLMM */
writel ( readl ( pdata - > mic_iomux ) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 |
MIC_CTRL_TLMM_SCLK_EN ,
pdata - > mic_iomux ) ;
break ;
2016-02-11 15:18:45 +03:00
case MI2S_TERTIARY :
writel ( readl ( pdata - > mic_iomux ) | MIC_CTRL_TER_WS_SLAVE_SEL |
MIC_CTRL_TLMM_SCLK_EN ,
pdata - > mic_iomux ) ;
break ;
2015-06-10 15:15:54 +03:00
default :
dev_err ( card - > dev , " unsupported cpu dai configuration \n " ) ;
2017-08-02 16:17:47 +03:00
return - EINVAL ;
}
2015-06-10 15:15:54 +03:00
2017-08-17 11:02:11 +03:00
if ( ! pdata - > jack_setup ) {
struct snd_jack * jack ;
rval = snd_soc_card_jack_new ( card , " Headset Jack " ,
SND_JACK_HEADSET |
SND_JACK_HEADPHONE |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 ,
& pdata - > jack , NULL , 0 ) ;
if ( rval < 0 ) {
dev_err ( card - > dev , " Unable to add Headphone Jack \n " ) ;
return rval ;
}
jack = pdata - > jack . jack ;
2017-11-22 23:56:43 +03:00
snd_jack_set_key ( jack , SND_JACK_BTN_0 , KEY_PLAYPAUSE ) ;
2017-08-17 11:02:11 +03:00
snd_jack_set_key ( jack , SND_JACK_BTN_1 , KEY_VOICECOMMAND ) ;
snd_jack_set_key ( jack , SND_JACK_BTN_2 , KEY_VOLUMEUP ) ;
snd_jack_set_key ( jack , SND_JACK_BTN_3 , KEY_VOLUMEDOWN ) ;
pdata - > jack_setup = true ;
}
2017-08-02 16:17:47 +03:00
for ( i = 0 ; i < dai_link - > num_codecs ; i + + ) {
struct snd_soc_dai * dai = rtd - > codec_dais [ i ] ;
2017-12-05 07:24:13 +03:00
component = dai - > component ;
2017-08-02 16:17:47 +03:00
/* Set default mclk for internal codec */
2017-12-05 07:24:13 +03:00
rval = snd_soc_component_set_sysclk ( component , 0 , 0 , DEFAULT_MCLK_RATE ,
2017-08-02 16:17:47 +03:00
SND_SOC_CLOCK_IN ) ;
if ( rval ! = 0 & & rval ! = - ENOTSUPP ) {
dev_warn ( card - > dev , " Failed to set mclk: %d \n " , rval ) ;
return rval ;
}
2017-12-05 07:24:13 +03:00
rval = snd_soc_component_set_jack ( component , & pdata - > jack , NULL ) ;
2017-08-17 11:02:11 +03:00
if ( rval ! = 0 & & rval ! = - ENOTSUPP ) {
dev_warn ( card - > dev , " Failed to set jack: %d \n " , rval ) ;
return rval ;
}
2015-06-10 15:15:54 +03:00
}
2017-08-02 16:17:47 +03:00
return 0 ;
2015-06-10 15:15:54 +03:00
}
static struct apq8016_sbc_data * apq8016_sbc_parse_of ( struct snd_soc_card * card )
{
struct device * dev = card - > dev ;
struct snd_soc_dai_link * link ;
struct device_node * np , * codec , * cpu , * node = dev - > of_node ;
struct apq8016_sbc_data * data ;
int ret , num_links ;
ret = snd_soc_of_parse_card_name ( card , " qcom,model " ) ;
if ( ret ) {
dev_err ( dev , " Error parsing card name: %d \n " , ret ) ;
return ERR_PTR ( ret ) ;
}
2016-09-06 12:57:43 +03:00
/* DAPM routes */
if ( of_property_read_bool ( node , " qcom,audio-routing " ) ) {
ret = snd_soc_of_parse_audio_routing ( card ,
" qcom,audio-routing " ) ;
if ( ret )
return ERR_PTR ( ret ) ;
}
2015-06-10 15:15:54 +03:00
/* Populate links */
num_links = of_get_child_count ( node ) ;
/* Allocate the private data and the DAI link array */
treewide: Use struct_size() for devm_kmalloc() and friends
Replaces open-coded struct size calculations with struct_size() for
devm_*, f2fs_*, and sock_* allocations. Automatically generated (and
manually adjusted) from the following Coccinelle script:
// Direct reference to struct field.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(*VAR->ELEMENT), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// mr = kzalloc(sizeof(*mr) + m * sizeof(mr->map[0]), GFP_KERNEL);
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(VAR->ELEMENT[0]), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// Same pattern, but can't trivially locate the trailing element name,
// or variable name.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
expression SOMETHING, COUNT, ELEMENT;
@@
- alloc(HANDLE, sizeof(SOMETHING) + COUNT * sizeof(ELEMENT), GFP)
+ alloc(HANDLE, CHECKME_struct_size(&SOMETHING, ELEMENT, COUNT), GFP)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-05-09 02:08:53 +03:00
data = devm_kzalloc ( dev ,
struct_size ( data , dai_link , num_links ) ,
2015-06-10 15:15:54 +03:00
GFP_KERNEL ) ;
if ( ! data )
return ERR_PTR ( - ENOMEM ) ;
card - > dai_link = & data - > dai_link [ 0 ] ;
card - > num_links = num_links ;
link = data - > dai_link ;
for_each_child_of_node ( node , np ) {
cpu = of_get_child_by_name ( np , " cpu " ) ;
codec = of_get_child_by_name ( np , " codec " ) ;
if ( ! cpu | | ! codec ) {
dev_err ( dev , " Can't find cpu/codec DT node \n " ) ;
2019-02-19 18:46:50 +03:00
ret = - EINVAL ;
goto error ;
2015-06-10 15:15:54 +03:00
}
link - > cpu_of_node = of_parse_phandle ( cpu , " sound-dai " , 0 ) ;
if ( ! link - > cpu_of_node ) {
dev_err ( card - > dev , " error getting cpu phandle \n " ) ;
2019-02-19 18:46:50 +03:00
ret = - EINVAL ;
goto error ;
2015-06-10 15:15:54 +03:00
}
ret = snd_soc_of_get_dai_name ( cpu , & link - > cpu_dai_name ) ;
if ( ret ) {
dev_err ( card - > dev , " error getting cpu dai name \n " ) ;
2019-02-19 18:46:50 +03:00
goto error ;
2015-06-10 15:15:54 +03:00
}
2016-10-20 17:20:48 +03:00
ret = snd_soc_of_get_dai_link_codecs ( dev , codec , link ) ;
if ( ret < 0 ) {
2015-06-10 15:15:54 +03:00
dev_err ( card - > dev , " error getting codec dai name \n " ) ;
2019-02-19 18:46:50 +03:00
goto error ;
2015-06-10 15:15:54 +03:00
}
link - > platform_of_node = link - > cpu_of_node ;
ret = of_property_read_string ( np , " link-name " , & link - > name ) ;
if ( ret ) {
dev_err ( card - > dev , " error getting codec dai_link name \n " ) ;
2019-02-19 18:46:50 +03:00
goto error ;
2015-06-10 15:15:54 +03:00
}
link - > stream_name = link - > name ;
link - > init = apq8016_sbc_dai_init ;
link + + ;
2019-02-19 18:46:50 +03:00
of_node_put ( cpu ) ;
of_node_put ( codec ) ;
2015-06-10 15:15:54 +03:00
}
return data ;
2019-02-19 18:46:50 +03:00
error :
of_node_put ( np ) ;
of_node_put ( cpu ) ;
of_node_put ( codec ) ;
return ERR_PTR ( ret ) ;
2015-06-10 15:15:54 +03:00
}
2016-09-06 12:57:42 +03:00
static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets [ ] = {
SND_SOC_DAPM_MIC ( " Handset Mic " , NULL ) ,
SND_SOC_DAPM_MIC ( " Headset Mic " , NULL ) ,
SND_SOC_DAPM_MIC ( " Secondary Mic " , NULL ) ,
SND_SOC_DAPM_MIC ( " Digital Mic1 " , NULL ) ,
SND_SOC_DAPM_MIC ( " Digital Mic2 " , NULL ) ,
} ;
2015-06-10 15:15:54 +03:00
static int apq8016_sbc_platform_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct snd_soc_card * card ;
struct apq8016_sbc_data * data ;
struct resource * res ;
card = devm_kzalloc ( dev , sizeof ( * card ) , GFP_KERNEL ) ;
if ( ! card )
return - ENOMEM ;
card - > dev = dev ;
2016-09-06 12:57:42 +03:00
card - > dapm_widgets = apq8016_sbc_dapm_widgets ;
card - > num_dapm_widgets = ARRAY_SIZE ( apq8016_sbc_dapm_widgets ) ;
2015-06-10 15:15:54 +03:00
data = apq8016_sbc_parse_of ( card ) ;
if ( IS_ERR ( data ) ) {
dev_err ( & pdev - > dev , " Error resolving dai links: %ld \n " ,
PTR_ERR ( data ) ) ;
return PTR_ERR ( data ) ;
}
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " mic-iomux " ) ;
data - > mic_iomux = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( data - > mic_iomux ) )
return PTR_ERR ( data - > mic_iomux ) ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " spkr-iomux " ) ;
data - > spkr_iomux = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( data - > spkr_iomux ) )
return PTR_ERR ( data - > spkr_iomux ) ;
snd_soc_card_set_drvdata ( card , data ) ;
return devm_snd_soc_register_card ( & pdev - > dev , card ) ;
}
static const struct of_device_id apq8016_sbc_device_id [ ] = {
{ . compatible = " qcom,apq8016-sbc-sndcard " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , apq8016_sbc_device_id ) ;
static struct platform_driver apq8016_sbc_platform_driver = {
. driver = {
. name = " qcom-apq8016-sbc " ,
. of_match_table = of_match_ptr ( apq8016_sbc_device_id ) ,
} ,
. probe = apq8016_sbc_platform_probe ,
} ;
module_platform_driver ( apq8016_sbc_platform_driver ) ;
MODULE_AUTHOR ( " Srinivas Kandagatla <srinivas.kandagatla@linaro.org " ) ;
MODULE_DESCRIPTION ( " APQ8016 ASoC Machine Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;