lochnagar-sc.c (7075B)
1// SPDX-License-Identifier: GPL-2.0 2// 3// Lochnagar sound card driver 4// 5// Copyright (c) 2017-2019 Cirrus Logic, Inc. and 6// Cirrus Logic International Semiconductor Ltd. 7// 8// Author: Charles Keepax <ckeepax@opensource.cirrus.com> 9// Piotr Stankiewicz <piotrs@opensource.cirrus.com> 10 11#include <linux/clk.h> 12#include <linux/module.h> 13#include <sound/soc.h> 14 15#include <linux/mfd/lochnagar.h> 16#include <linux/mfd/lochnagar1_regs.h> 17#include <linux/mfd/lochnagar2_regs.h> 18 19struct lochnagar_sc_priv { 20 struct clk *mclk; 21}; 22 23static const struct snd_soc_dapm_widget lochnagar_sc_widgets[] = { 24 SND_SOC_DAPM_LINE("Line Jack", NULL), 25 SND_SOC_DAPM_LINE("USB Audio", NULL), 26}; 27 28static const struct snd_soc_dapm_route lochnagar_sc_routes[] = { 29 { "Line Jack", NULL, "AIF1 Playback" }, 30 { "AIF1 Capture", NULL, "Line Jack" }, 31 32 { "USB Audio", NULL, "USB1 Playback" }, 33 { "USB Audio", NULL, "USB2 Playback" }, 34 { "USB1 Capture", NULL, "USB Audio" }, 35 { "USB2 Capture", NULL, "USB Audio" }, 36}; 37 38static const unsigned int lochnagar_sc_chan_vals[] = { 39 4, 8, 40}; 41 42static const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = { 43 .count = ARRAY_SIZE(lochnagar_sc_chan_vals), 44 .list = lochnagar_sc_chan_vals, 45}; 46 47static const unsigned int lochnagar_sc_rate_vals[] = { 48 8000, 16000, 24000, 32000, 48000, 96000, 192000, 49 22050, 44100, 88200, 176400, 50}; 51 52static const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = { 53 .count = ARRAY_SIZE(lochnagar_sc_rate_vals), 54 .list = lochnagar_sc_rate_vals, 55}; 56 57static int lochnagar_sc_hw_rule_rate(struct snd_pcm_hw_params *params, 58 struct snd_pcm_hw_rule *rule) 59{ 60 struct snd_interval range = { 61 .min = 8000, 62 .max = 24576000 / hw_param_interval(params, rule->deps[0])->max, 63 }; 64 65 return snd_interval_refine(hw_param_interval(params, rule->var), 66 &range); 67} 68 69static int lochnagar_sc_startup(struct snd_pcm_substream *substream, 70 struct snd_soc_dai *dai) 71{ 72 struct snd_soc_component *comp = dai->component; 73 struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); 74 int ret; 75 76 ret = snd_pcm_hw_constraint_list(substream->runtime, 0, 77 SNDRV_PCM_HW_PARAM_RATE, 78 &lochnagar_sc_rate_constraint); 79 if (ret) 80 return ret; 81 82 return snd_pcm_hw_rule_add(substream->runtime, 0, 83 SNDRV_PCM_HW_PARAM_RATE, 84 lochnagar_sc_hw_rule_rate, priv, 85 SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); 86} 87 88static int lochnagar_sc_line_startup(struct snd_pcm_substream *substream, 89 struct snd_soc_dai *dai) 90{ 91 struct snd_soc_component *comp = dai->component; 92 struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); 93 int ret; 94 95 ret = clk_prepare_enable(priv->mclk); 96 if (ret < 0) { 97 dev_err(dai->dev, "Failed to enable MCLK: %d\n", ret); 98 return ret; 99 } 100 101 ret = lochnagar_sc_startup(substream, dai); 102 if (ret) 103 return ret; 104 105 return snd_pcm_hw_constraint_list(substream->runtime, 0, 106 SNDRV_PCM_HW_PARAM_CHANNELS, 107 &lochnagar_sc_chan_constraint); 108} 109 110static void lochnagar_sc_line_shutdown(struct snd_pcm_substream *substream, 111 struct snd_soc_dai *dai) 112{ 113 struct snd_soc_component *comp = dai->component; 114 struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); 115 116 clk_disable_unprepare(priv->mclk); 117} 118 119static int lochnagar_sc_check_fmt(struct snd_soc_dai *dai, unsigned int fmt, 120 unsigned int tar) 121{ 122 tar |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; 123 124 if ((fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) != tar) 125 return -EINVAL; 126 127 return 0; 128} 129 130static int lochnagar_sc_set_line_fmt(struct snd_soc_dai *dai, unsigned int fmt) 131{ 132 return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBS_CFS); 133} 134 135static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) 136{ 137 return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBM_CFM); 138} 139 140static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { 141 .startup = lochnagar_sc_line_startup, 142 .shutdown = lochnagar_sc_line_shutdown, 143 .set_fmt = lochnagar_sc_set_line_fmt, 144}; 145 146static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { 147 .startup = lochnagar_sc_startup, 148 .set_fmt = lochnagar_sc_set_usb_fmt, 149}; 150 151static struct snd_soc_dai_driver lochnagar_sc_dai[] = { 152 { 153 .name = "lochnagar-line", 154 .playback = { 155 .stream_name = "AIF1 Playback", 156 .channels_min = 4, 157 .channels_max = 8, 158 .rates = SNDRV_PCM_RATE_KNOT, 159 .formats = SNDRV_PCM_FMTBIT_S32_LE, 160 }, 161 .capture = { 162 .stream_name = "AIF1 Capture", 163 .channels_min = 4, 164 .channels_max = 8, 165 .rates = SNDRV_PCM_RATE_KNOT, 166 .formats = SNDRV_PCM_FMTBIT_S32_LE, 167 }, 168 .ops = &lochnagar_sc_line_ops, 169 .symmetric_rate = true, 170 .symmetric_sample_bits = true, 171 }, 172 { 173 .name = "lochnagar-usb1", 174 .playback = { 175 .stream_name = "USB1 Playback", 176 .channels_min = 1, 177 .channels_max = 8, 178 .rates = SNDRV_PCM_RATE_KNOT, 179 .formats = SNDRV_PCM_FMTBIT_S32_LE, 180 }, 181 .capture = { 182 .stream_name = "USB1 Capture", 183 .channels_min = 1, 184 .channels_max = 8, 185 .rates = SNDRV_PCM_RATE_KNOT, 186 .formats = SNDRV_PCM_FMTBIT_S32_LE, 187 }, 188 .ops = &lochnagar_sc_usb_ops, 189 .symmetric_rate = true, 190 .symmetric_sample_bits = true, 191 }, 192 { 193 .name = "lochnagar-usb2", 194 .playback = { 195 .stream_name = "USB2 Playback", 196 .channels_min = 1, 197 .channels_max = 8, 198 .rates = SNDRV_PCM_RATE_KNOT, 199 .formats = SNDRV_PCM_FMTBIT_S32_LE, 200 }, 201 .capture = { 202 .stream_name = "USB2 Capture", 203 .channels_min = 1, 204 .channels_max = 8, 205 .rates = SNDRV_PCM_RATE_KNOT, 206 .formats = SNDRV_PCM_FMTBIT_S32_LE, 207 }, 208 .ops = &lochnagar_sc_usb_ops, 209 .symmetric_rate = true, 210 .symmetric_sample_bits = true, 211 }, 212}; 213 214static const struct snd_soc_component_driver lochnagar_sc_driver = { 215 .dapm_widgets = lochnagar_sc_widgets, 216 .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets), 217 .dapm_routes = lochnagar_sc_routes, 218 .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes), 219 220 .non_legacy_dai_naming = 1, 221 .endianness = 1, 222}; 223 224static int lochnagar_sc_probe(struct platform_device *pdev) 225{ 226 struct lochnagar_sc_priv *priv; 227 int ret; 228 229 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 230 if (!priv) 231 return -ENOMEM; 232 233 priv->mclk = devm_clk_get(&pdev->dev, "mclk"); 234 if (IS_ERR(priv->mclk)) { 235 ret = PTR_ERR(priv->mclk); 236 dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret); 237 return ret; 238 } 239 240 platform_set_drvdata(pdev, priv); 241 242 return devm_snd_soc_register_component(&pdev->dev, 243 &lochnagar_sc_driver, 244 lochnagar_sc_dai, 245 ARRAY_SIZE(lochnagar_sc_dai)); 246} 247 248static const struct of_device_id lochnagar_of_match[] = { 249 { .compatible = "cirrus,lochnagar2-soundcard" }, 250 {} 251}; 252MODULE_DEVICE_TABLE(of, lochnagar_of_match); 253 254static struct platform_driver lochnagar_sc_codec_driver = { 255 .driver = { 256 .name = "lochnagar-soundcard", 257 .of_match_table = of_match_ptr(lochnagar_of_match), 258 }, 259 260 .probe = lochnagar_sc_probe, 261}; 262module_platform_driver(lochnagar_sc_codec_driver); 263 264MODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver"); 265MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>"); 266MODULE_LICENSE("GPL v2"); 267MODULE_ALIAS("platform:lochnagar-soundcard");