diff options
Diffstat (limited to 'sound/soc/codecs/lochnagar-sc.c')
-rw-r--r-- | sound/soc/codecs/lochnagar-sc.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c new file mode 100644 index 000000000..13fbd8830 --- /dev/null +++ b/sound/soc/codecs/lochnagar-sc.c @@ -0,0 +1,266 @@ +// 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, + .symmetric_rate = true, + .symmetric_sample_bits = true, + }, + { + .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, + .symmetric_rate = true, + .symmetric_sample_bits = true, + }, + { + .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, + .symmetric_rate = true, + .symmetric_sample_bits = true, + }, +}; + +static const struct snd_soc_component_driver lochnagar_sc_driver = { + .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), + + .endianness = 1, +}; + +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"); |