2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2018-08-31 20:14:35 +03:00
/*
* ASoC driver for PROTO AudioCODEC ( with a WM8731 )
*
* Author : Florian Meier , < koalo @ koalo . de >
* Copyright 2013
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/soc.h>
# include <sound/jack.h>
# include "../codecs/wm8731.h"
# define XTAL_RATE 12288000 /* This is fixed on this board */
static int snd_proto_init ( struct snd_soc_pcm_runtime * rtd )
{
struct snd_soc_card * card = rtd - > card ;
2020-03-23 08:17:22 +03:00
struct snd_soc_dai * codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2018-08-31 20:14:35 +03:00
/* Set proto sysclk */
int ret = snd_soc_dai_set_sysclk ( codec_dai , WM8731_SYSCLK_XTAL ,
XTAL_RATE , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
dev_err ( card - > dev , " Failed to set WM8731 SYSCLK: %d \n " ,
ret ) ;
return ret ;
}
return 0 ;
}
static const struct snd_soc_dapm_widget snd_proto_widget [ ] = {
SND_SOC_DAPM_MIC ( " Microphone Jack " , NULL ) ,
SND_SOC_DAPM_HP ( " Headphone Jack " , NULL ) ,
} ;
static const struct snd_soc_dapm_route snd_proto_route [ ] = {
/* speaker connected to LHPOUT/RHPOUT */
{ " Headphone Jack " , NULL , " LHPOUT " } ,
{ " Headphone Jack " , NULL , " RHPOUT " } ,
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
{ " MICIN " , NULL , " Mic Bias " } ,
{ " Mic Bias " , NULL , " Microphone Jack " } ,
} ;
/* audio machine driver */
static struct snd_soc_card snd_proto = {
. name = " snd_mikroe_proto " ,
. owner = THIS_MODULE ,
. dapm_widgets = snd_proto_widget ,
. num_dapm_widgets = ARRAY_SIZE ( snd_proto_widget ) ,
. dapm_routes = snd_proto_route ,
. num_dapm_routes = ARRAY_SIZE ( snd_proto_route ) ,
} ;
static int snd_proto_probe ( struct platform_device * pdev )
{
struct snd_soc_dai_link * dai ;
2019-06-06 07:13:38 +03:00
struct snd_soc_dai_link_component * comp ;
2018-08-31 20:14:35 +03:00
struct device_node * np = pdev - > dev . of_node ;
struct device_node * codec_np , * cpu_np ;
struct device_node * bitclkmaster = NULL ;
struct device_node * framemaster = NULL ;
unsigned int dai_fmt ;
int ret = 0 ;
if ( ! np ) {
dev_err ( & pdev - > dev , " No device node supplied \n " ) ;
return - EINVAL ;
}
snd_proto . dev = & pdev - > dev ;
ret = snd_soc_of_parse_card_name ( & snd_proto , " model " ) ;
if ( ret )
return ret ;
dai = devm_kzalloc ( & pdev - > dev , sizeof ( * dai ) , GFP_KERNEL ) ;
if ( ! dai )
return - ENOMEM ;
2019-06-28 04:46:49 +03:00
/* for cpus/codecs/platforms */
comp = devm_kzalloc ( & pdev - > dev , 3 * sizeof ( * comp ) , GFP_KERNEL ) ;
2019-06-06 07:13:38 +03:00
if ( ! comp )
return - ENOMEM ;
2018-08-31 20:14:35 +03:00
snd_proto . dai_link = dai ;
snd_proto . num_links = 1 ;
2019-06-06 07:13:38 +03:00
dai - > cpus = & comp [ 0 ] ;
dai - > num_cpus = 1 ;
dai - > codecs = & comp [ 1 ] ;
dai - > num_codecs = 1 ;
2019-06-28 04:46:49 +03:00
dai - > platforms = & comp [ 2 ] ;
dai - > num_platforms = 1 ;
2019-06-06 07:13:38 +03:00
2018-08-31 20:14:35 +03:00
dai - > name = " WM8731 " ;
dai - > stream_name = " WM8731 HiFi " ;
2019-06-06 07:13:38 +03:00
dai - > codecs - > dai_name = " wm8731-hifi " ;
2018-08-31 20:14:35 +03:00
dai - > init = & snd_proto_init ;
codec_np = of_parse_phandle ( np , " audio-codec " , 0 ) ;
if ( ! codec_np ) {
dev_err ( & pdev - > dev , " audio-codec node missing \n " ) ;
return - EINVAL ;
}
2019-06-06 07:13:38 +03:00
dai - > codecs - > of_node = codec_np ;
2018-08-31 20:14:35 +03:00
cpu_np = of_parse_phandle ( np , " i2s-controller " , 0 ) ;
if ( ! cpu_np ) {
dev_err ( & pdev - > dev , " i2s-controller missing \n " ) ;
2022-03-08 04:39:48 +03:00
ret = - EINVAL ;
goto put_codec_node ;
2018-08-31 20:14:35 +03:00
}
2019-06-06 07:13:38 +03:00
dai - > cpus - > of_node = cpu_np ;
2019-06-28 04:46:49 +03:00
dai - > platforms - > of_node = cpu_np ;
2018-08-31 20:14:35 +03:00
2021-06-14 03:57:19 +03:00
dai_fmt = snd_soc_daifmt_parse_format ( np , NULL ) ;
snd_soc_daifmt_parse_clock_provider_as_phandle ( np , NULL ,
& bitclkmaster , & framemaster ) ;
2018-08-31 20:14:35 +03:00
if ( bitclkmaster ! = framemaster ) {
dev_err ( & pdev - > dev , " Must be the same bitclock and frame master \n " ) ;
2022-03-08 04:39:48 +03:00
ret = - EINVAL ;
goto put_cpu_node ;
2018-08-31 20:14:35 +03:00
}
if ( bitclkmaster ) {
if ( codec_np = = bitclkmaster )
2021-09-15 20:30:23 +03:00
dai_fmt | = SND_SOC_DAIFMT_CBP_CFP ;
2018-08-31 20:14:35 +03:00
else
2021-09-15 20:30:23 +03:00
dai_fmt | = SND_SOC_DAIFMT_CBC_CFC ;
2021-06-14 03:57:19 +03:00
} else {
dai_fmt | = snd_soc_daifmt_parse_clock_provider_as_flag ( np , NULL ) ;
2018-08-31 20:14:35 +03:00
}
2021-06-14 03:57:19 +03:00
2018-08-31 20:14:35 +03:00
2022-03-08 04:39:48 +03:00
dai - > dai_fmt = dai_fmt ;
2018-08-31 20:14:35 +03:00
ret = snd_soc_register_card ( & snd_proto ) ;
2021-12-14 05:08:32 +03:00
if ( ret )
dev_err_probe ( & pdev - > dev , ret ,
" snd_soc_register_card() failed \n " ) ;
2018-08-31 20:14:35 +03:00
2022-03-08 04:39:48 +03:00
put_cpu_node :
of_node_put ( bitclkmaster ) ;
of_node_put ( framemaster ) ;
of_node_put ( cpu_np ) ;
put_codec_node :
of_node_put ( codec_np ) ;
2018-08-31 20:14:35 +03:00
return ret ;
}
2023-03-15 18:05:22 +03:00
static void snd_proto_remove ( struct platform_device * pdev )
2018-08-31 20:14:35 +03:00
{
2022-06-21 17:58:34 +03:00
snd_soc_unregister_card ( & snd_proto ) ;
2018-08-31 20:14:35 +03:00
}
static const struct of_device_id snd_proto_of_match [ ] = {
{ . compatible = " mikroe,mikroe-proto " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , snd_proto_of_match ) ;
static struct platform_driver snd_proto_driver = {
. driver = {
. name = " snd-mikroe-proto " ,
. of_match_table = snd_proto_of_match ,
} ,
. probe = snd_proto_probe ,
2023-03-15 18:05:22 +03:00
. remove_new = snd_proto_remove ,
2018-08-31 20:14:35 +03:00
} ;
module_platform_driver ( snd_proto_driver ) ;
MODULE_AUTHOR ( " Florian Meier " ) ;
MODULE_DESCRIPTION ( " ASoC Driver for PROTO board (WM8731) " ) ;
MODULE_LICENSE ( " GPL " ) ;