18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Lochnagar sound card driver 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Copyright (c) 2017-2019 Cirrus Logic, Inc. and 68c2ecf20Sopenharmony_ci// Cirrus Logic International Semiconductor Ltd. 78c2ecf20Sopenharmony_ci// 88c2ecf20Sopenharmony_ci// Author: Charles Keepax <ckeepax@opensource.cirrus.com> 98c2ecf20Sopenharmony_ci// Piotr Stankiewicz <piotrs@opensource.cirrus.com> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/clk.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <sound/soc.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/mfd/lochnagar.h> 168c2ecf20Sopenharmony_ci#include <linux/mfd/lochnagar1_regs.h> 178c2ecf20Sopenharmony_ci#include <linux/mfd/lochnagar2_regs.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct lochnagar_sc_priv { 208c2ecf20Sopenharmony_ci struct clk *mclk; 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget lochnagar_sc_widgets[] = { 248c2ecf20Sopenharmony_ci SND_SOC_DAPM_LINE("Line Jack", NULL), 258c2ecf20Sopenharmony_ci SND_SOC_DAPM_LINE("USB Audio", NULL), 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route lochnagar_sc_routes[] = { 298c2ecf20Sopenharmony_ci { "Line Jack", NULL, "AIF1 Playback" }, 308c2ecf20Sopenharmony_ci { "AIF1 Capture", NULL, "Line Jack" }, 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci { "USB Audio", NULL, "USB1 Playback" }, 338c2ecf20Sopenharmony_ci { "USB Audio", NULL, "USB2 Playback" }, 348c2ecf20Sopenharmony_ci { "USB1 Capture", NULL, "USB Audio" }, 358c2ecf20Sopenharmony_ci { "USB2 Capture", NULL, "USB Audio" }, 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic const unsigned int lochnagar_sc_chan_vals[] = { 398c2ecf20Sopenharmony_ci 4, 8, 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = { 438c2ecf20Sopenharmony_ci .count = ARRAY_SIZE(lochnagar_sc_chan_vals), 448c2ecf20Sopenharmony_ci .list = lochnagar_sc_chan_vals, 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic const unsigned int lochnagar_sc_rate_vals[] = { 488c2ecf20Sopenharmony_ci 8000, 16000, 24000, 32000, 48000, 96000, 192000, 498c2ecf20Sopenharmony_ci 22050, 44100, 88200, 176400, 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = { 538c2ecf20Sopenharmony_ci .count = ARRAY_SIZE(lochnagar_sc_rate_vals), 548c2ecf20Sopenharmony_ci .list = lochnagar_sc_rate_vals, 558c2ecf20Sopenharmony_ci}; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int lochnagar_sc_hw_rule_rate(struct snd_pcm_hw_params *params, 588c2ecf20Sopenharmony_ci struct snd_pcm_hw_rule *rule) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci struct snd_interval range = { 618c2ecf20Sopenharmony_ci .min = 8000, 628c2ecf20Sopenharmony_ci .max = 24576000 / hw_param_interval(params, rule->deps[0])->max, 638c2ecf20Sopenharmony_ci }; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci return snd_interval_refine(hw_param_interval(params, rule->var), 668c2ecf20Sopenharmony_ci &range); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int lochnagar_sc_startup(struct snd_pcm_substream *substream, 708c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct snd_soc_component *comp = dai->component; 738c2ecf20Sopenharmony_ci struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); 748c2ecf20Sopenharmony_ci int ret; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci ret = snd_pcm_hw_constraint_list(substream->runtime, 0, 778c2ecf20Sopenharmony_ci SNDRV_PCM_HW_PARAM_RATE, 788c2ecf20Sopenharmony_ci &lochnagar_sc_rate_constraint); 798c2ecf20Sopenharmony_ci if (ret) 808c2ecf20Sopenharmony_ci return ret; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci return snd_pcm_hw_rule_add(substream->runtime, 0, 838c2ecf20Sopenharmony_ci SNDRV_PCM_HW_PARAM_RATE, 848c2ecf20Sopenharmony_ci lochnagar_sc_hw_rule_rate, priv, 858c2ecf20Sopenharmony_ci SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); 868c2ecf20Sopenharmony_ci} 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic int lochnagar_sc_line_startup(struct snd_pcm_substream *substream, 898c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct snd_soc_component *comp = dai->component; 928c2ecf20Sopenharmony_ci struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); 938c2ecf20Sopenharmony_ci int ret; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci ret = clk_prepare_enable(priv->mclk); 968c2ecf20Sopenharmony_ci if (ret < 0) { 978c2ecf20Sopenharmony_ci dev_err(dai->dev, "Failed to enable MCLK: %d\n", ret); 988c2ecf20Sopenharmony_ci return ret; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci ret = lochnagar_sc_startup(substream, dai); 1028c2ecf20Sopenharmony_ci if (ret) 1038c2ecf20Sopenharmony_ci return ret; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci return snd_pcm_hw_constraint_list(substream->runtime, 0, 1068c2ecf20Sopenharmony_ci SNDRV_PCM_HW_PARAM_CHANNELS, 1078c2ecf20Sopenharmony_ci &lochnagar_sc_chan_constraint); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic void lochnagar_sc_line_shutdown(struct snd_pcm_substream *substream, 1118c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci struct snd_soc_component *comp = dai->component; 1148c2ecf20Sopenharmony_ci struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci clk_disable_unprepare(priv->mclk); 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int lochnagar_sc_check_fmt(struct snd_soc_dai *dai, unsigned int fmt, 1208c2ecf20Sopenharmony_ci unsigned int tar) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci tar |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if ((fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) != tar) 1258c2ecf20Sopenharmony_ci return -EINVAL; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci return 0; 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic int lochnagar_sc_set_line_fmt(struct snd_soc_dai *dai, unsigned int fmt) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBS_CFS); 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBM_CFM); 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops lochnagar_sc_line_ops = { 1418c2ecf20Sopenharmony_ci .startup = lochnagar_sc_line_startup, 1428c2ecf20Sopenharmony_ci .shutdown = lochnagar_sc_line_shutdown, 1438c2ecf20Sopenharmony_ci .set_fmt = lochnagar_sc_set_line_fmt, 1448c2ecf20Sopenharmony_ci}; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { 1478c2ecf20Sopenharmony_ci .startup = lochnagar_sc_startup, 1488c2ecf20Sopenharmony_ci .set_fmt = lochnagar_sc_set_usb_fmt, 1498c2ecf20Sopenharmony_ci}; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver lochnagar_sc_dai[] = { 1528c2ecf20Sopenharmony_ci { 1538c2ecf20Sopenharmony_ci .name = "lochnagar-line", 1548c2ecf20Sopenharmony_ci .playback = { 1558c2ecf20Sopenharmony_ci .stream_name = "AIF1 Playback", 1568c2ecf20Sopenharmony_ci .channels_min = 4, 1578c2ecf20Sopenharmony_ci .channels_max = 8, 1588c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 1598c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S32_LE, 1608c2ecf20Sopenharmony_ci }, 1618c2ecf20Sopenharmony_ci .capture = { 1628c2ecf20Sopenharmony_ci .stream_name = "AIF1 Capture", 1638c2ecf20Sopenharmony_ci .channels_min = 4, 1648c2ecf20Sopenharmony_ci .channels_max = 8, 1658c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 1668c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S32_LE, 1678c2ecf20Sopenharmony_ci }, 1688c2ecf20Sopenharmony_ci .ops = &lochnagar_sc_line_ops, 1698c2ecf20Sopenharmony_ci .symmetric_rates = true, 1708c2ecf20Sopenharmony_ci .symmetric_samplebits = true, 1718c2ecf20Sopenharmony_ci }, 1728c2ecf20Sopenharmony_ci { 1738c2ecf20Sopenharmony_ci .name = "lochnagar-usb1", 1748c2ecf20Sopenharmony_ci .playback = { 1758c2ecf20Sopenharmony_ci .stream_name = "USB1 Playback", 1768c2ecf20Sopenharmony_ci .channels_min = 1, 1778c2ecf20Sopenharmony_ci .channels_max = 8, 1788c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 1798c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S32_LE, 1808c2ecf20Sopenharmony_ci }, 1818c2ecf20Sopenharmony_ci .capture = { 1828c2ecf20Sopenharmony_ci .stream_name = "USB1 Capture", 1838c2ecf20Sopenharmony_ci .channels_min = 1, 1848c2ecf20Sopenharmony_ci .channels_max = 8, 1858c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 1868c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S32_LE, 1878c2ecf20Sopenharmony_ci }, 1888c2ecf20Sopenharmony_ci .ops = &lochnagar_sc_usb_ops, 1898c2ecf20Sopenharmony_ci .symmetric_rates = true, 1908c2ecf20Sopenharmony_ci .symmetric_samplebits = true, 1918c2ecf20Sopenharmony_ci }, 1928c2ecf20Sopenharmony_ci { 1938c2ecf20Sopenharmony_ci .name = "lochnagar-usb2", 1948c2ecf20Sopenharmony_ci .playback = { 1958c2ecf20Sopenharmony_ci .stream_name = "USB2 Playback", 1968c2ecf20Sopenharmony_ci .channels_min = 1, 1978c2ecf20Sopenharmony_ci .channels_max = 8, 1988c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 1998c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S32_LE, 2008c2ecf20Sopenharmony_ci }, 2018c2ecf20Sopenharmony_ci .capture = { 2028c2ecf20Sopenharmony_ci .stream_name = "USB2 Capture", 2038c2ecf20Sopenharmony_ci .channels_min = 1, 2048c2ecf20Sopenharmony_ci .channels_max = 8, 2058c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_KNOT, 2068c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S32_LE, 2078c2ecf20Sopenharmony_ci }, 2088c2ecf20Sopenharmony_ci .ops = &lochnagar_sc_usb_ops, 2098c2ecf20Sopenharmony_ci .symmetric_rates = true, 2108c2ecf20Sopenharmony_ci .symmetric_samplebits = true, 2118c2ecf20Sopenharmony_ci }, 2128c2ecf20Sopenharmony_ci}; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver lochnagar_sc_driver = { 2158c2ecf20Sopenharmony_ci .non_legacy_dai_naming = 1, 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci .dapm_widgets = lochnagar_sc_widgets, 2188c2ecf20Sopenharmony_ci .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets), 2198c2ecf20Sopenharmony_ci .dapm_routes = lochnagar_sc_routes, 2208c2ecf20Sopenharmony_ci .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes), 2218c2ecf20Sopenharmony_ci}; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic int lochnagar_sc_probe(struct platform_device *pdev) 2248c2ecf20Sopenharmony_ci{ 2258c2ecf20Sopenharmony_ci struct lochnagar_sc_priv *priv; 2268c2ecf20Sopenharmony_ci int ret; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 2298c2ecf20Sopenharmony_ci if (!priv) 2308c2ecf20Sopenharmony_ci return -ENOMEM; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci priv->mclk = devm_clk_get(&pdev->dev, "mclk"); 2338c2ecf20Sopenharmony_ci if (IS_ERR(priv->mclk)) { 2348c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->mclk); 2358c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret); 2368c2ecf20Sopenharmony_ci return ret; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci return devm_snd_soc_register_component(&pdev->dev, 2428c2ecf20Sopenharmony_ci &lochnagar_sc_driver, 2438c2ecf20Sopenharmony_ci lochnagar_sc_dai, 2448c2ecf20Sopenharmony_ci ARRAY_SIZE(lochnagar_sc_dai)); 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_cistatic const struct of_device_id lochnagar_of_match[] = { 2488c2ecf20Sopenharmony_ci { .compatible = "cirrus,lochnagar2-soundcard" }, 2498c2ecf20Sopenharmony_ci {} 2508c2ecf20Sopenharmony_ci}; 2518c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, lochnagar_of_match); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic struct platform_driver lochnagar_sc_codec_driver = { 2548c2ecf20Sopenharmony_ci .driver = { 2558c2ecf20Sopenharmony_ci .name = "lochnagar-soundcard", 2568c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(lochnagar_of_match), 2578c2ecf20Sopenharmony_ci }, 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci .probe = lochnagar_sc_probe, 2608c2ecf20Sopenharmony_ci}; 2618c2ecf20Sopenharmony_cimodule_platform_driver(lochnagar_sc_codec_driver); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver"); 2648c2ecf20Sopenharmony_ciMODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>"); 2658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2668c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:lochnagar-soundcard"); 267