1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * omap-mcpdm.c -- OMAP ALSA SoC DAI driver using McPDM port |
4 | * |
5 | * Copyright (C) 2009 - 2011 Texas Instruments |
6 | * |
7 | * Author: Misael Lopez Cruz <misael.lopez@ti.com> |
8 | * Contact: Jorge Eduardo Candelaria <x0107209@ti.com> |
9 | * Margarita Olaya <magi.olaya@ti.com> |
10 | * Peter Ujfalusi <peter.ujfalusi@ti.com> |
11 | */ |
12 | |
13 | #include <linux/init.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/module.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/err.h> |
19 | #include <linux/io.h> |
20 | #include <linux/irq.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/pm_runtime.h> |
23 | |
24 | #include <sound/core.h> |
25 | #include <sound/pcm.h> |
26 | #include <sound/pcm_params.h> |
27 | #include <sound/soc.h> |
28 | #include <sound/dmaengine_pcm.h> |
29 | |
30 | #include "omap-mcpdm.h" |
31 | #include "sdma-pcm.h" |
32 | |
33 | struct mcpdm_link_config { |
34 | u32 link_mask; /* channel mask for the direction */ |
35 | u32 threshold; /* FIFO threshold */ |
36 | }; |
37 | |
38 | struct omap_mcpdm { |
39 | struct device *dev; |
40 | unsigned long phys_base; |
41 | void __iomem *io_base; |
42 | int irq; |
43 | struct pm_qos_request pm_qos_req; |
44 | int latency[2]; |
45 | |
46 | struct mutex mutex; |
47 | |
48 | /* Playback/Capture configuration */ |
49 | struct mcpdm_link_config config[2]; |
50 | |
51 | /* McPDM dn offsets for rx1, and 2 channels */ |
52 | u32 dn_rx_offset; |
53 | |
54 | /* McPDM needs to be restarted due to runtime reconfiguration */ |
55 | bool restart; |
56 | |
57 | /* pm state for suspend/resume handling */ |
58 | int pm_active_count; |
59 | |
60 | struct snd_dmaengine_dai_dma_data dma_data[2]; |
61 | }; |
62 | |
63 | /* |
64 | * Stream DMA parameters |
65 | */ |
66 | |
67 | static inline void omap_mcpdm_write(struct omap_mcpdm *mcpdm, u16 reg, u32 val) |
68 | { |
69 | writel_relaxed(val, mcpdm->io_base + reg); |
70 | } |
71 | |
72 | static inline int omap_mcpdm_read(struct omap_mcpdm *mcpdm, u16 reg) |
73 | { |
74 | return readl_relaxed(mcpdm->io_base + reg); |
75 | } |
76 | |
77 | #ifdef DEBUG |
78 | static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) |
79 | { |
80 | dev_dbg(mcpdm->dev, "***********************\n" ); |
81 | dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n" , |
82 | omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS_RAW)); |
83 | dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n" , |
84 | omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS)); |
85 | dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n" , |
86 | omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_SET)); |
87 | dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n" , |
88 | omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_CLR)); |
89 | dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n" , |
90 | omap_mcpdm_read(mcpdm, MCPDM_REG_IRQWAKE_EN)); |
91 | dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n" , |
92 | omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_SET)); |
93 | dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n" , |
94 | omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_CLR)); |
95 | dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n" , |
96 | omap_mcpdm_read(mcpdm, MCPDM_REG_DMAWAKEEN)); |
97 | dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n" , |
98 | omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL)); |
99 | dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n" , |
100 | omap_mcpdm_read(mcpdm, MCPDM_REG_DN_DATA)); |
101 | dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n" , |
102 | omap_mcpdm_read(mcpdm, MCPDM_REG_UP_DATA)); |
103 | dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n" , |
104 | omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_DN)); |
105 | dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n" , |
106 | omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_UP)); |
107 | dev_dbg(mcpdm->dev, "***********************\n" ); |
108 | } |
109 | #else |
110 | static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) {} |
111 | #endif |
112 | |
113 | /* |
114 | * Enables the transfer through the PDM interface to/from the Phoenix |
115 | * codec by enabling the corresponding UP or DN channels. |
116 | */ |
117 | static void omap_mcpdm_start(struct omap_mcpdm *mcpdm) |
118 | { |
119 | u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); |
120 | u32 link_mask = mcpdm->config[0].link_mask | mcpdm->config[1].link_mask; |
121 | |
122 | ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); |
123 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl); |
124 | |
125 | ctrl |= link_mask; |
126 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl); |
127 | |
128 | ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); |
129 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl); |
130 | } |
131 | |
132 | /* |
133 | * Disables the transfer through the PDM interface to/from the Phoenix |
134 | * codec by disabling the corresponding UP or DN channels. |
135 | */ |
136 | static void omap_mcpdm_stop(struct omap_mcpdm *mcpdm) |
137 | { |
138 | u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); |
139 | u32 link_mask = MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK; |
140 | |
141 | ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); |
142 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl); |
143 | |
144 | ctrl &= ~(link_mask); |
145 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl); |
146 | |
147 | ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); |
148 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl); |
149 | |
150 | } |
151 | |
152 | /* |
153 | * Is the physical McPDM interface active. |
154 | */ |
155 | static inline int omap_mcpdm_active(struct omap_mcpdm *mcpdm) |
156 | { |
157 | return omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL) & |
158 | (MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK); |
159 | } |
160 | |
161 | /* |
162 | * Configures McPDM uplink, and downlink for audio. |
163 | * This function should be called before omap_mcpdm_start. |
164 | */ |
165 | static void omap_mcpdm_open_streams(struct omap_mcpdm *mcpdm) |
166 | { |
167 | u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); |
168 | |
169 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: ctrl | MCPDM_WD_EN); |
170 | |
171 | omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_SET, |
172 | MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL | |
173 | MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); |
174 | |
175 | /* Enable DN RX1/2 offset cancellation feature, if configured */ |
176 | if (mcpdm->dn_rx_offset) { |
177 | u32 dn_offset = mcpdm->dn_rx_offset; |
178 | |
179 | omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, val: dn_offset); |
180 | dn_offset |= (MCPDM_DN_OFST_RX1_EN | MCPDM_DN_OFST_RX2_EN); |
181 | omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, val: dn_offset); |
182 | } |
183 | |
184 | omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_DN, |
185 | val: mcpdm->config[SNDRV_PCM_STREAM_PLAYBACK].threshold); |
186 | omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_UP, |
187 | val: mcpdm->config[SNDRV_PCM_STREAM_CAPTURE].threshold); |
188 | |
189 | omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_SET, |
190 | MCPDM_DMA_DN_ENABLE | MCPDM_DMA_UP_ENABLE); |
191 | } |
192 | |
193 | /* |
194 | * Cleans McPDM uplink, and downlink configuration. |
195 | * This function should be called when the stream is closed. |
196 | */ |
197 | static void omap_mcpdm_close_streams(struct omap_mcpdm *mcpdm) |
198 | { |
199 | /* Disable irq request generation for downlink */ |
200 | omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR, |
201 | MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL); |
202 | |
203 | /* Disable DMA request generation for downlink */ |
204 | omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_DN_ENABLE); |
205 | |
206 | /* Disable irq request generation for uplink */ |
207 | omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR, |
208 | MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); |
209 | |
210 | /* Disable DMA request generation for uplink */ |
211 | omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_UP_ENABLE); |
212 | |
213 | /* Disable RX1/2 offset cancellation */ |
214 | if (mcpdm->dn_rx_offset) |
215 | omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, val: 0); |
216 | } |
217 | |
218 | static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id) |
219 | { |
220 | struct omap_mcpdm *mcpdm = dev_id; |
221 | int irq_status; |
222 | |
223 | irq_status = omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS); |
224 | |
225 | /* Acknowledge irq event */ |
226 | omap_mcpdm_write(mcpdm, MCPDM_REG_IRQSTATUS, val: irq_status); |
227 | |
228 | if (irq_status & MCPDM_DN_IRQ_FULL) |
229 | dev_dbg(mcpdm->dev, "DN (playback) FIFO Full\n" ); |
230 | |
231 | if (irq_status & MCPDM_DN_IRQ_EMPTY) |
232 | dev_dbg(mcpdm->dev, "DN (playback) FIFO Empty\n" ); |
233 | |
234 | if (irq_status & MCPDM_DN_IRQ) |
235 | dev_dbg(mcpdm->dev, "DN (playback) write request\n" ); |
236 | |
237 | if (irq_status & MCPDM_UP_IRQ_FULL) |
238 | dev_dbg(mcpdm->dev, "UP (capture) FIFO Full\n" ); |
239 | |
240 | if (irq_status & MCPDM_UP_IRQ_EMPTY) |
241 | dev_dbg(mcpdm->dev, "UP (capture) FIFO Empty\n" ); |
242 | |
243 | if (irq_status & MCPDM_UP_IRQ) |
244 | dev_dbg(mcpdm->dev, "UP (capture) write request\n" ); |
245 | |
246 | return IRQ_HANDLED; |
247 | } |
248 | |
249 | static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, |
250 | struct snd_soc_dai *dai) |
251 | { |
252 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); |
253 | |
254 | mutex_lock(&mcpdm->mutex); |
255 | |
256 | if (!snd_soc_dai_active(dai)) |
257 | omap_mcpdm_open_streams(mcpdm); |
258 | |
259 | mutex_unlock(lock: &mcpdm->mutex); |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream, |
265 | struct snd_soc_dai *dai) |
266 | { |
267 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); |
268 | int tx = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); |
269 | int stream1 = tx ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; |
270 | int stream2 = tx ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; |
271 | |
272 | mutex_lock(&mcpdm->mutex); |
273 | |
274 | if (!snd_soc_dai_active(dai)) { |
275 | if (omap_mcpdm_active(mcpdm)) { |
276 | omap_mcpdm_stop(mcpdm); |
277 | omap_mcpdm_close_streams(mcpdm); |
278 | mcpdm->config[0].link_mask = 0; |
279 | mcpdm->config[1].link_mask = 0; |
280 | } |
281 | } |
282 | |
283 | if (mcpdm->latency[stream2]) |
284 | cpu_latency_qos_update_request(req: &mcpdm->pm_qos_req, |
285 | new_value: mcpdm->latency[stream2]); |
286 | else if (mcpdm->latency[stream1]) |
287 | cpu_latency_qos_remove_request(req: &mcpdm->pm_qos_req); |
288 | |
289 | mcpdm->latency[stream1] = 0; |
290 | |
291 | mutex_unlock(lock: &mcpdm->mutex); |
292 | } |
293 | |
294 | static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream, |
295 | struct snd_pcm_hw_params *params, |
296 | struct snd_soc_dai *dai) |
297 | { |
298 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); |
299 | int stream = substream->stream; |
300 | struct snd_dmaengine_dai_dma_data *dma_data; |
301 | u32 threshold; |
302 | int channels, latency; |
303 | int link_mask = 0; |
304 | |
305 | channels = params_channels(p: params); |
306 | switch (channels) { |
307 | case 5: |
308 | if (stream == SNDRV_PCM_STREAM_CAPTURE) |
309 | /* up to 3 channels for capture */ |
310 | return -EINVAL; |
311 | link_mask |= 1 << 4; |
312 | fallthrough; |
313 | case 4: |
314 | if (stream == SNDRV_PCM_STREAM_CAPTURE) |
315 | /* up to 3 channels for capture */ |
316 | return -EINVAL; |
317 | link_mask |= 1 << 3; |
318 | fallthrough; |
319 | case 3: |
320 | link_mask |= 1 << 2; |
321 | fallthrough; |
322 | case 2: |
323 | link_mask |= 1 << 1; |
324 | fallthrough; |
325 | case 1: |
326 | link_mask |= 1 << 0; |
327 | break; |
328 | default: |
329 | /* unsupported number of channels */ |
330 | return -EINVAL; |
331 | } |
332 | |
333 | dma_data = snd_soc_dai_get_dma_data(dai, substream); |
334 | |
335 | threshold = mcpdm->config[stream].threshold; |
336 | /* Configure McPDM channels, and DMA packet size */ |
337 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
338 | link_mask <<= 3; |
339 | |
340 | /* If capture is not running assume a stereo stream to come */ |
341 | if (!mcpdm->config[!stream].link_mask) |
342 | mcpdm->config[!stream].link_mask = 0x3; |
343 | |
344 | dma_data->maxburst = |
345 | (MCPDM_DN_THRES_MAX - threshold) * channels; |
346 | latency = threshold; |
347 | } else { |
348 | /* If playback is not running assume a stereo stream to come */ |
349 | if (!mcpdm->config[!stream].link_mask) |
350 | mcpdm->config[!stream].link_mask = (0x3 << 3); |
351 | |
352 | dma_data->maxburst = threshold * channels; |
353 | latency = (MCPDM_DN_THRES_MAX - threshold); |
354 | } |
355 | |
356 | /* |
357 | * The DMA must act to a DMA request within latency time (usec) to avoid |
358 | * under/overflow |
359 | */ |
360 | mcpdm->latency[stream] = latency * USEC_PER_SEC / params_rate(p: params); |
361 | |
362 | if (!mcpdm->latency[stream]) |
363 | mcpdm->latency[stream] = 10; |
364 | |
365 | /* Check if we need to restart McPDM with this stream */ |
366 | if (mcpdm->config[stream].link_mask && |
367 | mcpdm->config[stream].link_mask != link_mask) |
368 | mcpdm->restart = true; |
369 | |
370 | mcpdm->config[stream].link_mask = link_mask; |
371 | |
372 | return 0; |
373 | } |
374 | |
375 | static int omap_mcpdm_prepare(struct snd_pcm_substream *substream, |
376 | struct snd_soc_dai *dai) |
377 | { |
378 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); |
379 | struct pm_qos_request *pm_qos_req = &mcpdm->pm_qos_req; |
380 | int tx = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); |
381 | int stream1 = tx ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; |
382 | int stream2 = tx ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; |
383 | int latency = mcpdm->latency[stream2]; |
384 | |
385 | /* Prevent omap hardware from hitting off between FIFO fills */ |
386 | if (!latency || mcpdm->latency[stream1] < latency) |
387 | latency = mcpdm->latency[stream1]; |
388 | |
389 | if (cpu_latency_qos_request_active(req: pm_qos_req)) |
390 | cpu_latency_qos_update_request(req: pm_qos_req, new_value: latency); |
391 | else if (latency) |
392 | cpu_latency_qos_add_request(req: pm_qos_req, value: latency); |
393 | |
394 | if (!omap_mcpdm_active(mcpdm)) { |
395 | omap_mcpdm_start(mcpdm); |
396 | omap_mcpdm_reg_dump(mcpdm); |
397 | } else if (mcpdm->restart) { |
398 | omap_mcpdm_stop(mcpdm); |
399 | omap_mcpdm_start(mcpdm); |
400 | mcpdm->restart = false; |
401 | omap_mcpdm_reg_dump(mcpdm); |
402 | } |
403 | |
404 | return 0; |
405 | } |
406 | |
407 | static int omap_mcpdm_probe(struct snd_soc_dai *dai) |
408 | { |
409 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); |
410 | int ret; |
411 | |
412 | pm_runtime_enable(dev: mcpdm->dev); |
413 | |
414 | /* Disable lines while request is ongoing */ |
415 | pm_runtime_get_sync(dev: mcpdm->dev); |
416 | omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, val: 0x00); |
417 | |
418 | ret = request_irq(irq: mcpdm->irq, handler: omap_mcpdm_irq_handler, flags: 0, name: "McPDM" , |
419 | dev: (void *)mcpdm); |
420 | |
421 | pm_runtime_put_sync(dev: mcpdm->dev); |
422 | |
423 | if (ret) { |
424 | dev_err(mcpdm->dev, "Request for IRQ failed\n" ); |
425 | pm_runtime_disable(dev: mcpdm->dev); |
426 | } |
427 | |
428 | /* Configure McPDM threshold values */ |
429 | mcpdm->config[SNDRV_PCM_STREAM_PLAYBACK].threshold = 2; |
430 | mcpdm->config[SNDRV_PCM_STREAM_CAPTURE].threshold = |
431 | MCPDM_UP_THRES_MAX - 3; |
432 | |
433 | snd_soc_dai_init_dma_data(dai, |
434 | playback: &mcpdm->dma_data[SNDRV_PCM_STREAM_PLAYBACK], |
435 | capture: &mcpdm->dma_data[SNDRV_PCM_STREAM_CAPTURE]); |
436 | |
437 | return ret; |
438 | } |
439 | |
440 | static int omap_mcpdm_remove(struct snd_soc_dai *dai) |
441 | { |
442 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); |
443 | |
444 | free_irq(mcpdm->irq, (void *)mcpdm); |
445 | pm_runtime_disable(dev: mcpdm->dev); |
446 | |
447 | if (cpu_latency_qos_request_active(req: &mcpdm->pm_qos_req)) |
448 | cpu_latency_qos_remove_request(req: &mcpdm->pm_qos_req); |
449 | |
450 | return 0; |
451 | } |
452 | |
453 | static const struct snd_soc_dai_ops omap_mcpdm_dai_ops = { |
454 | .probe = omap_mcpdm_probe, |
455 | .remove = omap_mcpdm_remove, |
456 | .startup = omap_mcpdm_dai_startup, |
457 | .shutdown = omap_mcpdm_dai_shutdown, |
458 | .hw_params = omap_mcpdm_dai_hw_params, |
459 | .prepare = omap_mcpdm_prepare, |
460 | .probe_order = SND_SOC_COMP_ORDER_LATE, |
461 | .remove_order = SND_SOC_COMP_ORDER_EARLY, |
462 | }; |
463 | |
464 | #ifdef CONFIG_PM_SLEEP |
465 | static int omap_mcpdm_suspend(struct snd_soc_component *component) |
466 | { |
467 | struct omap_mcpdm *mcpdm = snd_soc_component_get_drvdata(c: component); |
468 | |
469 | if (snd_soc_component_active(component)) { |
470 | omap_mcpdm_stop(mcpdm); |
471 | omap_mcpdm_close_streams(mcpdm); |
472 | } |
473 | |
474 | mcpdm->pm_active_count = 0; |
475 | while (pm_runtime_active(dev: mcpdm->dev)) { |
476 | pm_runtime_put_sync(dev: mcpdm->dev); |
477 | mcpdm->pm_active_count++; |
478 | } |
479 | |
480 | return 0; |
481 | } |
482 | |
483 | static int omap_mcpdm_resume(struct snd_soc_component *component) |
484 | { |
485 | struct omap_mcpdm *mcpdm = snd_soc_component_get_drvdata(c: component); |
486 | |
487 | if (mcpdm->pm_active_count) { |
488 | while (mcpdm->pm_active_count--) |
489 | pm_runtime_get_sync(dev: mcpdm->dev); |
490 | |
491 | if (snd_soc_component_active(component)) { |
492 | omap_mcpdm_open_streams(mcpdm); |
493 | omap_mcpdm_start(mcpdm); |
494 | } |
495 | } |
496 | |
497 | |
498 | return 0; |
499 | } |
500 | #else |
501 | #define omap_mcpdm_suspend NULL |
502 | #define omap_mcpdm_resume NULL |
503 | #endif |
504 | |
505 | #define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) |
506 | #define OMAP_MCPDM_FORMATS SNDRV_PCM_FMTBIT_S32_LE |
507 | |
508 | static struct snd_soc_dai_driver omap_mcpdm_dai = { |
509 | .playback = { |
510 | .channels_min = 1, |
511 | .channels_max = 5, |
512 | .rates = OMAP_MCPDM_RATES, |
513 | .formats = OMAP_MCPDM_FORMATS, |
514 | .sig_bits = 24, |
515 | }, |
516 | .capture = { |
517 | .channels_min = 1, |
518 | .channels_max = 3, |
519 | .rates = OMAP_MCPDM_RATES, |
520 | .formats = OMAP_MCPDM_FORMATS, |
521 | .sig_bits = 24, |
522 | }, |
523 | .ops = &omap_mcpdm_dai_ops, |
524 | }; |
525 | |
526 | static const struct snd_soc_component_driver omap_mcpdm_component = { |
527 | .name = "omap-mcpdm" , |
528 | .suspend = omap_mcpdm_suspend, |
529 | .resume = omap_mcpdm_resume, |
530 | .legacy_dai_naming = 1, |
531 | }; |
532 | |
533 | void omap_mcpdm_configure_dn_offsets(struct snd_soc_pcm_runtime *rtd, |
534 | u8 rx1, u8 rx2) |
535 | { |
536 | struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); |
537 | |
538 | mcpdm->dn_rx_offset = MCPDM_DNOFST_RX1(rx1) | MCPDM_DNOFST_RX2(rx2); |
539 | } |
540 | EXPORT_SYMBOL_GPL(omap_mcpdm_configure_dn_offsets); |
541 | |
542 | static int asoc_mcpdm_probe(struct platform_device *pdev) |
543 | { |
544 | struct omap_mcpdm *mcpdm; |
545 | struct resource *res; |
546 | int ret; |
547 | |
548 | mcpdm = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct omap_mcpdm), GFP_KERNEL); |
549 | if (!mcpdm) |
550 | return -ENOMEM; |
551 | |
552 | platform_set_drvdata(pdev, data: mcpdm); |
553 | |
554 | mutex_init(&mcpdm->mutex); |
555 | |
556 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma" ); |
557 | if (res == NULL) |
558 | return -ENOMEM; |
559 | |
560 | mcpdm->dma_data[0].addr = res->start + MCPDM_REG_DN_DATA; |
561 | mcpdm->dma_data[1].addr = res->start + MCPDM_REG_UP_DATA; |
562 | |
563 | mcpdm->dma_data[0].filter_data = "dn_link" ; |
564 | mcpdm->dma_data[1].filter_data = "up_link" ; |
565 | |
566 | mcpdm->io_base = devm_platform_ioremap_resource_byname(pdev, name: "mpu" ); |
567 | if (IS_ERR(ptr: mcpdm->io_base)) |
568 | return PTR_ERR(ptr: mcpdm->io_base); |
569 | |
570 | mcpdm->irq = platform_get_irq(pdev, 0); |
571 | if (mcpdm->irq < 0) |
572 | return mcpdm->irq; |
573 | |
574 | mcpdm->dev = &pdev->dev; |
575 | |
576 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
577 | component_driver: &omap_mcpdm_component, |
578 | dai_drv: &omap_mcpdm_dai, num_dai: 1); |
579 | if (ret) |
580 | return ret; |
581 | |
582 | return sdma_pcm_platform_register(dev: &pdev->dev, txdmachan: "dn_link" , rxdmachan: "up_link" ); |
583 | } |
584 | |
585 | static const struct of_device_id omap_mcpdm_of_match[] = { |
586 | { .compatible = "ti,omap4-mcpdm" , }, |
587 | { } |
588 | }; |
589 | MODULE_DEVICE_TABLE(of, omap_mcpdm_of_match); |
590 | |
591 | static struct platform_driver asoc_mcpdm_driver = { |
592 | .driver = { |
593 | .name = "omap-mcpdm" , |
594 | .of_match_table = omap_mcpdm_of_match, |
595 | }, |
596 | |
597 | .probe = asoc_mcpdm_probe, |
598 | }; |
599 | |
600 | module_platform_driver(asoc_mcpdm_driver); |
601 | |
602 | MODULE_ALIAS("platform:omap-mcpdm" ); |
603 | MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>" ); |
604 | MODULE_DESCRIPTION("OMAP PDM SoC Interface" ); |
605 | MODULE_LICENSE("GPL" ); |
606 | |