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, var: rule->deps[0])->max, |
63 | }; |
64 | |
65 | return snd_interval_refine(i: hw_param_interval(params, var: rule->var), |
66 | v: &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(c: comp); |
74 | int ret; |
75 | |
76 | ret = snd_pcm_hw_constraint_list(runtime: substream->runtime, cond: 0, |
77 | SNDRV_PCM_HW_PARAM_RATE, |
78 | l: &lochnagar_sc_rate_constraint); |
79 | if (ret) |
80 | return ret; |
81 | |
82 | return snd_pcm_hw_rule_add(runtime: substream->runtime, cond: 0, |
83 | SNDRV_PCM_HW_PARAM_RATE, |
84 | func: lochnagar_sc_hw_rule_rate, private: 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(c: comp); |
93 | int ret; |
94 | |
95 | ret = clk_prepare_enable(clk: 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(runtime: substream->runtime, cond: 0, |
106 | SNDRV_PCM_HW_PARAM_CHANNELS, |
107 | l: &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(c: comp); |
115 | |
116 | clk_disable_unprepare(clk: 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_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 | |
214 | static 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 | .endianness = 1, |
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(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
229 | if (!priv) |
230 | return -ENOMEM; |
231 | |
232 | priv->mclk = devm_clk_get(dev: &pdev->dev, id: "mclk" ); |
233 | if (IS_ERR(ptr: priv->mclk)) { |
234 | ret = PTR_ERR(ptr: priv->mclk); |
235 | dev_err(&pdev->dev, "Failed to get MCLK: %d\n" , ret); |
236 | return ret; |
237 | } |
238 | |
239 | platform_set_drvdata(pdev, data: priv); |
240 | |
241 | return devm_snd_soc_register_component(dev: &pdev->dev, |
242 | component_driver: &lochnagar_sc_driver, |
243 | dai_drv: 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 = 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 | |