1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Xilinx ASoC SPDIF audio support |
4 | // |
5 | // Copyright (C) 2018 Xilinx, Inc. |
6 | // |
7 | // Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com> |
8 | // |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/io.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_platform.h> |
15 | #include <linux/platform_device.h> |
16 | #include <sound/pcm_params.h> |
17 | #include <sound/soc.h> |
18 | |
19 | #define XLNX_SPDIF_RATES \ |
20 | (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ |
21 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ |
22 | SNDRV_PCM_RATE_192000) |
23 | |
24 | #define XLNX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) |
25 | |
26 | #define XSPDIF_IRQ_STS_REG 0x20 |
27 | #define XSPDIF_IRQ_ENABLE_REG 0x28 |
28 | #define XSPDIF_SOFT_RESET_REG 0x40 |
29 | #define XSPDIF_CONTROL_REG 0x44 |
30 | #define XSPDIF_CHAN_0_STS_REG 0x4C |
31 | #define XSPDIF_GLOBAL_IRQ_ENABLE_REG 0x1C |
32 | #define XSPDIF_CH_A_USER_DATA_REG_0 0x64 |
33 | |
34 | #define XSPDIF_CORE_ENABLE_MASK BIT(0) |
35 | #define XSPDIF_FIFO_FLUSH_MASK BIT(1) |
36 | #define XSPDIF_CH_STS_MASK BIT(5) |
37 | #define XSPDIF_GLOBAL_IRQ_ENABLE BIT(31) |
38 | #define XSPDIF_CLOCK_CONFIG_BITS_MASK GENMASK(5, 2) |
39 | #define XSPDIF_CLOCK_CONFIG_BITS_SHIFT 2 |
40 | #define XSPDIF_SOFT_RESET_VALUE 0xA |
41 | |
42 | #define MAX_CHANNELS 2 |
43 | #define AES_SAMPLE_WIDTH 32 |
44 | #define CH_STATUS_UPDATE_TIMEOUT 40 |
45 | |
46 | struct spdif_dev_data { |
47 | u32 mode; |
48 | u32 aclk; |
49 | bool rx_chsts_updated; |
50 | void __iomem *base; |
51 | struct clk *axi_clk; |
52 | wait_queue_head_t chsts_q; |
53 | }; |
54 | |
55 | static irqreturn_t xlnx_spdifrx_irq_handler(int irq, void *arg) |
56 | { |
57 | u32 val; |
58 | struct spdif_dev_data *ctx = arg; |
59 | |
60 | val = readl(addr: ctx->base + XSPDIF_IRQ_STS_REG); |
61 | if (val & XSPDIF_CH_STS_MASK) { |
62 | writel(val: val & XSPDIF_CH_STS_MASK, |
63 | addr: ctx->base + XSPDIF_IRQ_STS_REG); |
64 | val = readl(addr: ctx->base + |
65 | XSPDIF_IRQ_ENABLE_REG); |
66 | writel(val: val & ~XSPDIF_CH_STS_MASK, |
67 | addr: ctx->base + XSPDIF_IRQ_ENABLE_REG); |
68 | |
69 | ctx->rx_chsts_updated = true; |
70 | wake_up_interruptible(&ctx->chsts_q); |
71 | return IRQ_HANDLED; |
72 | } |
73 | |
74 | return IRQ_NONE; |
75 | } |
76 | |
77 | static int xlnx_spdif_startup(struct snd_pcm_substream *substream, |
78 | struct snd_soc_dai *dai) |
79 | { |
80 | u32 val; |
81 | struct spdif_dev_data *ctx = dev_get_drvdata(dev: dai->dev); |
82 | |
83 | val = readl(addr: ctx->base + XSPDIF_CONTROL_REG); |
84 | val |= XSPDIF_FIFO_FLUSH_MASK; |
85 | writel(val, addr: ctx->base + XSPDIF_CONTROL_REG); |
86 | |
87 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { |
88 | writel(XSPDIF_CH_STS_MASK, |
89 | addr: ctx->base + XSPDIF_IRQ_ENABLE_REG); |
90 | writel(XSPDIF_GLOBAL_IRQ_ENABLE, |
91 | addr: ctx->base + XSPDIF_GLOBAL_IRQ_ENABLE_REG); |
92 | } |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | static void xlnx_spdif_shutdown(struct snd_pcm_substream *substream, |
98 | struct snd_soc_dai *dai) |
99 | { |
100 | struct spdif_dev_data *ctx = dev_get_drvdata(dev: dai->dev); |
101 | |
102 | writel(XSPDIF_SOFT_RESET_VALUE, addr: ctx->base + XSPDIF_SOFT_RESET_REG); |
103 | } |
104 | |
105 | static int xlnx_spdif_hw_params(struct snd_pcm_substream *substream, |
106 | struct snd_pcm_hw_params *params, |
107 | struct snd_soc_dai *dai) |
108 | { |
109 | u32 val, clk_div, clk_cfg; |
110 | struct spdif_dev_data *ctx = dev_get_drvdata(dev: dai->dev); |
111 | |
112 | clk_div = DIV_ROUND_CLOSEST(ctx->aclk, MAX_CHANNELS * AES_SAMPLE_WIDTH * |
113 | params_rate(params)); |
114 | |
115 | switch (clk_div) { |
116 | case 4: |
117 | clk_cfg = 0; |
118 | break; |
119 | case 8: |
120 | clk_cfg = 1; |
121 | break; |
122 | case 16: |
123 | clk_cfg = 2; |
124 | break; |
125 | case 24: |
126 | clk_cfg = 3; |
127 | break; |
128 | case 32: |
129 | clk_cfg = 4; |
130 | break; |
131 | case 48: |
132 | clk_cfg = 5; |
133 | break; |
134 | case 64: |
135 | clk_cfg = 6; |
136 | break; |
137 | default: |
138 | return -EINVAL; |
139 | } |
140 | |
141 | val = readl(addr: ctx->base + XSPDIF_CONTROL_REG); |
142 | val &= ~XSPDIF_CLOCK_CONFIG_BITS_MASK; |
143 | val |= clk_cfg << XSPDIF_CLOCK_CONFIG_BITS_SHIFT; |
144 | writel(val, addr: ctx->base + XSPDIF_CONTROL_REG); |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static int rx_stream_detect(struct snd_soc_dai *dai) |
150 | { |
151 | int err; |
152 | struct spdif_dev_data *ctx = dev_get_drvdata(dev: dai->dev); |
153 | unsigned long jiffies = msecs_to_jiffies(CH_STATUS_UPDATE_TIMEOUT); |
154 | |
155 | /* start capture only if stream is detected within 40ms timeout */ |
156 | err = wait_event_interruptible_timeout(ctx->chsts_q, |
157 | ctx->rx_chsts_updated, |
158 | jiffies); |
159 | if (!err) { |
160 | dev_err(dai->dev, "No streaming audio detected!\n" ); |
161 | return -EINVAL; |
162 | } |
163 | ctx->rx_chsts_updated = false; |
164 | |
165 | return 0; |
166 | } |
167 | |
168 | static int xlnx_spdif_trigger(struct snd_pcm_substream *substream, int cmd, |
169 | struct snd_soc_dai *dai) |
170 | { |
171 | u32 val; |
172 | int ret = 0; |
173 | struct spdif_dev_data *ctx = dev_get_drvdata(dev: dai->dev); |
174 | |
175 | val = readl(addr: ctx->base + XSPDIF_CONTROL_REG); |
176 | switch (cmd) { |
177 | case SNDRV_PCM_TRIGGER_START: |
178 | case SNDRV_PCM_TRIGGER_RESUME: |
179 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
180 | val |= XSPDIF_CORE_ENABLE_MASK; |
181 | writel(val, addr: ctx->base + XSPDIF_CONTROL_REG); |
182 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
183 | ret = rx_stream_detect(dai); |
184 | break; |
185 | case SNDRV_PCM_TRIGGER_STOP: |
186 | case SNDRV_PCM_TRIGGER_SUSPEND: |
187 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
188 | val &= ~XSPDIF_CORE_ENABLE_MASK; |
189 | writel(val, addr: ctx->base + XSPDIF_CONTROL_REG); |
190 | break; |
191 | default: |
192 | ret = -EINVAL; |
193 | } |
194 | |
195 | return ret; |
196 | } |
197 | |
198 | static const struct snd_soc_dai_ops xlnx_spdif_dai_ops = { |
199 | .startup = xlnx_spdif_startup, |
200 | .shutdown = xlnx_spdif_shutdown, |
201 | .trigger = xlnx_spdif_trigger, |
202 | .hw_params = xlnx_spdif_hw_params, |
203 | }; |
204 | |
205 | static struct snd_soc_dai_driver xlnx_spdif_tx_dai = { |
206 | .name = "xlnx_spdif_tx" , |
207 | .playback = { |
208 | .channels_min = 2, |
209 | .channels_max = 2, |
210 | .rates = XLNX_SPDIF_RATES, |
211 | .formats = XLNX_SPDIF_FORMATS, |
212 | }, |
213 | .ops = &xlnx_spdif_dai_ops, |
214 | }; |
215 | |
216 | static struct snd_soc_dai_driver xlnx_spdif_rx_dai = { |
217 | .name = "xlnx_spdif_rx" , |
218 | .capture = { |
219 | .channels_min = 2, |
220 | .channels_max = 2, |
221 | .rates = XLNX_SPDIF_RATES, |
222 | .formats = XLNX_SPDIF_FORMATS, |
223 | }, |
224 | .ops = &xlnx_spdif_dai_ops, |
225 | }; |
226 | |
227 | static const struct snd_soc_component_driver xlnx_spdif_component = { |
228 | .name = "xlnx-spdif" , |
229 | .legacy_dai_naming = 1, |
230 | }; |
231 | |
232 | static const struct of_device_id xlnx_spdif_of_match[] = { |
233 | { .compatible = "xlnx,spdif-2.0" , }, |
234 | {}, |
235 | }; |
236 | MODULE_DEVICE_TABLE(of, xlnx_spdif_of_match); |
237 | |
238 | static int xlnx_spdif_probe(struct platform_device *pdev) |
239 | { |
240 | int ret; |
241 | struct snd_soc_dai_driver *dai_drv; |
242 | struct spdif_dev_data *ctx; |
243 | |
244 | struct device *dev = &pdev->dev; |
245 | struct device_node *node = dev->of_node; |
246 | |
247 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
248 | if (!ctx) |
249 | return -ENOMEM; |
250 | |
251 | ctx->axi_clk = devm_clk_get(dev, id: "s_axi_aclk" ); |
252 | if (IS_ERR(ptr: ctx->axi_clk)) { |
253 | ret = PTR_ERR(ptr: ctx->axi_clk); |
254 | dev_err(dev, "failed to get s_axi_aclk(%d)\n" , ret); |
255 | return ret; |
256 | } |
257 | ret = clk_prepare_enable(clk: ctx->axi_clk); |
258 | if (ret) { |
259 | dev_err(dev, "failed to enable s_axi_aclk(%d)\n" , ret); |
260 | return ret; |
261 | } |
262 | |
263 | ctx->base = devm_platform_ioremap_resource(pdev, index: 0); |
264 | if (IS_ERR(ptr: ctx->base)) { |
265 | ret = PTR_ERR(ptr: ctx->base); |
266 | goto clk_err; |
267 | } |
268 | ret = of_property_read_u32(np: node, propname: "xlnx,spdif-mode" , out_value: &ctx->mode); |
269 | if (ret < 0) { |
270 | dev_err(dev, "cannot get SPDIF mode\n" ); |
271 | goto clk_err; |
272 | } |
273 | if (ctx->mode) { |
274 | dai_drv = &xlnx_spdif_tx_dai; |
275 | } else { |
276 | ret = platform_get_irq(pdev, 0); |
277 | if (ret < 0) |
278 | goto clk_err; |
279 | ret = devm_request_irq(dev, irq: ret, |
280 | handler: xlnx_spdifrx_irq_handler, |
281 | irqflags: 0, devname: "XLNX_SPDIF_RX" , dev_id: ctx); |
282 | if (ret) { |
283 | dev_err(dev, "spdif rx irq request failed\n" ); |
284 | ret = -ENODEV; |
285 | goto clk_err; |
286 | } |
287 | |
288 | init_waitqueue_head(&ctx->chsts_q); |
289 | dai_drv = &xlnx_spdif_rx_dai; |
290 | } |
291 | |
292 | ret = of_property_read_u32(np: node, propname: "xlnx,aud_clk_i" , out_value: &ctx->aclk); |
293 | if (ret < 0) { |
294 | dev_err(dev, "cannot get aud_clk_i value\n" ); |
295 | goto clk_err; |
296 | } |
297 | |
298 | dev_set_drvdata(dev, data: ctx); |
299 | |
300 | ret = devm_snd_soc_register_component(dev, component_driver: &xlnx_spdif_component, |
301 | dai_drv, num_dai: 1); |
302 | if (ret) { |
303 | dev_err(dev, "SPDIF component registration failed\n" ); |
304 | goto clk_err; |
305 | } |
306 | |
307 | writel(XSPDIF_SOFT_RESET_VALUE, addr: ctx->base + XSPDIF_SOFT_RESET_REG); |
308 | dev_info(dev, "%s DAI registered\n" , dai_drv->name); |
309 | |
310 | clk_err: |
311 | clk_disable_unprepare(clk: ctx->axi_clk); |
312 | return ret; |
313 | } |
314 | |
315 | static void xlnx_spdif_remove(struct platform_device *pdev) |
316 | { |
317 | struct spdif_dev_data *ctx = dev_get_drvdata(dev: &pdev->dev); |
318 | |
319 | clk_disable_unprepare(clk: ctx->axi_clk); |
320 | } |
321 | |
322 | static struct platform_driver xlnx_spdif_driver = { |
323 | .driver = { |
324 | .name = "xlnx-spdif" , |
325 | .of_match_table = xlnx_spdif_of_match, |
326 | }, |
327 | .probe = xlnx_spdif_probe, |
328 | .remove_new = xlnx_spdif_remove, |
329 | }; |
330 | module_platform_driver(xlnx_spdif_driver); |
331 | |
332 | MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>" ); |
333 | MODULE_DESCRIPTION("XILINX SPDIF driver" ); |
334 | MODULE_LICENSE("GPL v2" ); |
335 | |