2019-03-20 17:37:32 +00:00
// SPDX-License-Identifier: GPL-2.0
//
// Lochnagar sound card driver
//
// Copyright (c) 2017-2019 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
//
// Author: Charles Keepax <ckeepax@opensource.cirrus.com>
// Piotr Stankiewicz <piotrs@opensource.cirrus.com>
# include <linux/clk.h>
# include <linux/module.h>
# include <sound/soc.h>
# include <linux/mfd/lochnagar.h>
# include <linux/mfd/lochnagar1_regs.h>
# include <linux/mfd/lochnagar2_regs.h>
struct lochnagar_sc_priv {
struct clk * mclk ;
} ;
static const struct snd_soc_dapm_widget lochnagar_sc_widgets [ ] = {
SND_SOC_DAPM_LINE ( " Line Jack " , NULL ) ,
SND_SOC_DAPM_LINE ( " USB Audio " , NULL ) ,
} ;
static const struct snd_soc_dapm_route lochnagar_sc_routes [ ] = {
{ " Line Jack " , NULL , " AIF1 Playback " } ,
{ " AIF1 Capture " , NULL , " Line Jack " } ,
{ " USB Audio " , NULL , " USB1 Playback " } ,
{ " USB Audio " , NULL , " USB2 Playback " } ,
{ " USB1 Capture " , NULL , " USB Audio " } ,
{ " USB2 Capture " , NULL , " USB Audio " } ,
} ;
static const unsigned int lochnagar_sc_chan_vals [ ] = {
4 , 8 ,
} ;
static const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = {
. count = ARRAY_SIZE ( lochnagar_sc_chan_vals ) ,
. list = lochnagar_sc_chan_vals ,
} ;
static const unsigned int lochnagar_sc_rate_vals [ ] = {
8000 , 16000 , 24000 , 32000 , 48000 , 96000 , 192000 ,
22050 , 44100 , 88200 , 176400 ,
} ;
static const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = {
. count = ARRAY_SIZE ( lochnagar_sc_rate_vals ) ,
. list = lochnagar_sc_rate_vals ,
} ;
static int lochnagar_sc_hw_rule_rate ( struct snd_pcm_hw_params * params ,
struct snd_pcm_hw_rule * rule )
{
struct snd_interval range = {
. min = 8000 ,
. max = 24576000 / hw_param_interval ( params , rule - > deps [ 0 ] ) - > max ,
} ;
return snd_interval_refine ( hw_param_interval ( params , rule - > var ) ,
& range ) ;
}
static int lochnagar_sc_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_component * comp = dai - > component ;
struct lochnagar_sc_priv * priv = snd_soc_component_get_drvdata ( comp ) ;
int ret ;
ret = snd_pcm_hw_constraint_list ( substream - > runtime , 0 ,
SNDRV_PCM_HW_PARAM_RATE ,
& lochnagar_sc_rate_constraint ) ;
if ( ret )
return ret ;
return snd_pcm_hw_rule_add ( substream - > runtime , 0 ,
SNDRV_PCM_HW_PARAM_RATE ,
lochnagar_sc_hw_rule_rate , priv ,
SNDRV_PCM_HW_PARAM_FRAME_BITS , - 1 ) ;
}
static int lochnagar_sc_line_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_component * comp = dai - > component ;
struct lochnagar_sc_priv * priv = snd_soc_component_get_drvdata ( comp ) ;
int ret ;
ret = clk_prepare_enable ( priv - > mclk ) ;
if ( ret < 0 ) {
dev_err ( dai - > dev , " Failed to enable MCLK: %d \n " , ret ) ;
return ret ;
}
ret = lochnagar_sc_startup ( substream , dai ) ;
if ( ret )
return ret ;
return snd_pcm_hw_constraint_list ( substream - > runtime , 0 ,
SNDRV_PCM_HW_PARAM_CHANNELS ,
& lochnagar_sc_chan_constraint ) ;
}
static void lochnagar_sc_line_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_component * comp = dai - > component ;
struct lochnagar_sc_priv * priv = snd_soc_component_get_drvdata ( comp ) ;
clk_disable_unprepare ( priv - > mclk ) ;
}
static int lochnagar_sc_check_fmt ( struct snd_soc_dai * dai , unsigned int fmt ,
unsigned int tar )
{
tar | = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF ;
if ( ( fmt & ~ SND_SOC_DAIFMT_CLOCK_MASK ) ! = tar )
return - EINVAL ;
return 0 ;
}
static int lochnagar_sc_set_line_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
{
return lochnagar_sc_check_fmt ( dai , fmt , SND_SOC_DAIFMT_CBS_CFS ) ;
}
static int lochnagar_sc_set_usb_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
{
return lochnagar_sc_check_fmt ( dai , fmt , SND_SOC_DAIFMT_CBM_CFM ) ;
}
static const struct snd_soc_dai_ops lochnagar_sc_line_ops = {
. startup = lochnagar_sc_line_startup ,
. shutdown = lochnagar_sc_line_shutdown ,
. set_fmt = lochnagar_sc_set_line_fmt ,
} ;
static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = {
. startup = lochnagar_sc_startup ,
. set_fmt = lochnagar_sc_set_usb_fmt ,
} ;
static struct snd_soc_dai_driver lochnagar_sc_dai [ ] = {
{
. name = " lochnagar-line " ,
. playback = {
. stream_name = " AIF1 Playback " ,
. channels_min = 4 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
} ,
. capture = {
. stream_name = " AIF1 Capture " ,
. channels_min = 4 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
} ,
. ops = & lochnagar_sc_line_ops ,
2021-01-15 13:55:56 +09:00
. symmetric_rate = true ,
. symmetric_sample_bits = true ,
2019-03-20 17:37:32 +00:00
} ,
{
. name = " lochnagar-usb1 " ,
. playback = {
. stream_name = " USB1 Playback " ,
. channels_min = 1 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
} ,
. capture = {
. stream_name = " USB1 Capture " ,
. channels_min = 1 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
} ,
. ops = & lochnagar_sc_usb_ops ,
2021-01-15 13:55:56 +09:00
. symmetric_rate = true ,
. symmetric_sample_bits = true ,
2019-03-20 17:37:32 +00:00
} ,
{
. name = " lochnagar-usb2 " ,
. playback = {
. stream_name = " USB2 Playback " ,
. channels_min = 1 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
} ,
. capture = {
. stream_name = " USB2 Capture " ,
. channels_min = 1 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
} ,
. ops = & lochnagar_sc_usb_ops ,
2021-01-15 13:55:56 +09:00
. symmetric_rate = true ,
. symmetric_sample_bits = true ,
2019-03-20 17:37:32 +00:00
} ,
} ;
static const struct snd_soc_component_driver lochnagar_sc_driver = {
. non_legacy_dai_naming = 1 ,
. dapm_widgets = lochnagar_sc_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( lochnagar_sc_widgets ) ,
. dapm_routes = lochnagar_sc_routes ,
. num_dapm_routes = ARRAY_SIZE ( lochnagar_sc_routes ) ,
} ;
static int lochnagar_sc_probe ( struct platform_device * pdev )
{
struct lochnagar_sc_priv * priv ;
int ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > mclk = devm_clk_get ( & pdev - > dev , " mclk " ) ;
if ( IS_ERR ( priv - > mclk ) ) {
ret = PTR_ERR ( priv - > mclk ) ;
dev_err ( & pdev - > dev , " Failed to get MCLK: %d \n " , ret ) ;
return ret ;
}
platform_set_drvdata ( pdev , priv ) ;
return devm_snd_soc_register_component ( & pdev - > dev ,
& lochnagar_sc_driver ,
lochnagar_sc_dai ,
ARRAY_SIZE ( lochnagar_sc_dai ) ) ;
}
static const struct of_device_id lochnagar_of_match [ ] = {
{ . compatible = " cirrus,lochnagar2-soundcard " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , lochnagar_of_match ) ;
static struct platform_driver lochnagar_sc_codec_driver = {
. driver = {
. name = " lochnagar-soundcard " ,
. of_match_table = of_match_ptr ( lochnagar_of_match ) ,
} ,
. probe = lochnagar_sc_probe ,
} ;
module_platform_driver ( lochnagar_sc_codec_driver ) ;
MODULE_DESCRIPTION ( " ASoC Lochnagar Sound Card Driver " ) ;
MODULE_AUTHOR ( " Piotr Stankiewicz <piotrs@opensource.cirrus.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:lochnagar-soundcard " ) ;