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 19 struct lochnagar_sc_priv { 20 struct clk *mclk; 21 }; 22 23 static 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 28 static 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 38 static const unsigned int lochnagar_sc_chan_vals[] = { 39 4, 8, 40 }; 41 42 static 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 47 static const unsigned int lochnagar_sc_rate_vals[] = { 48 8000, 16000, 24000, 32000, 48000, 96000, 192000, 49 22050, 44100, 88200, 176400, 50 }; 51 52 static 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 57 static 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 69 static 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 88 static 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 110 static 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 119 static 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 130 static 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 135 static 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 140 static 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 146 static 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 151 static 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_rates = true, 170 .symmetric_samplebits = 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_rates = true, 190 .symmetric_samplebits = 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_rates = true, 210 .symmetric_samplebits = true, 211 }, 212 }; 213 214 static const struct snd_soc_component_driver lochnagar_sc_driver = { 215 .non_legacy_dai_naming = 1, 216 217 .dapm_widgets = lochnagar_sc_widgets, 218 .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets), 219 .dapm_routes = lochnagar_sc_routes, 220 .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes), 221 }; 222 223 static int lochnagar_sc_probe(struct platform_device *pdev) 224 { 225 struct lochnagar_sc_priv *priv; 226 int ret; 227 228 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 229 if (!priv) 230 return -ENOMEM; 231 232 priv->mclk = devm_clk_get(&pdev->dev, "mclk"); 233 if (IS_ERR(priv->mclk)) { 234 ret = PTR_ERR(priv->mclk); 235 dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret); 236 return ret; 237 } 238 239 platform_set_drvdata(pdev, priv); 240 241 return devm_snd_soc_register_component(&pdev->dev, 242 &lochnagar_sc_driver, 243 lochnagar_sc_dai, 244 ARRAY_SIZE(lochnagar_sc_dai)); 245 } 246 247 static const struct of_device_id lochnagar_of_match[] = { 248 { .compatible = "cirrus,lochnagar2-soundcard" }, 249 {} 250 }; 251 MODULE_DEVICE_TABLE(of, lochnagar_of_match); 252 253 static struct platform_driver lochnagar_sc_codec_driver = { 254 .driver = { 255 .name = "lochnagar-soundcard", 256 .of_match_table = of_match_ptr(lochnagar_of_match), 257 }, 258 259 .probe = lochnagar_sc_probe, 260 }; 261 module_platform_driver(lochnagar_sc_codec_driver); 262 263 MODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver"); 264 MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>"); 265 MODULE_LICENSE("GPL v2"); 266 MODULE_ALIAS("platform:lochnagar-soundcard"); 267