diff options
author | Simran Rai <ssimran@broadcom.com> | 2016-05-17 20:01:09 -0400 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2016-05-31 12:50:42 -0400 |
commit | 1200a7d9b2c65ffb2dd673add65cd5dc95671489 (patch) | |
tree | 52b84394ab174fa2e981669b8d4dae1ee378ddd3 | |
parent | a6ee05d94e8fca0c9eed71669a32c8f1fd0f24e7 (diff) |
ASoC: cygnus: Add Cygnus audio DMA driver
This patch adds Cygnus audio DMA driver. It supports playback
and capture modes and uses ringbuffers for data transfer.
Signed-off-by: Lori Hikichi <lhikichi@broadcom.com>
Signed-off-by: Simran Rai <ssimran@broadcom.com>
Reviewed-by: Ray Jui <rjui@broadcom.com>
Reviewed-by: Arun Parameswaran <arunp@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r-- | sound/soc/bcm/Kconfig | 9 | ||||
-rw-r--r-- | sound/soc/bcm/Makefile | 5 | ||||
-rw-r--r-- | sound/soc/bcm/cygnus-pcm.c | 861 |
3 files changed, 875 insertions, 0 deletions
diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 6a834e109f1d..d528aaceaad9 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig | |||
@@ -7,3 +7,12 @@ config SND_BCM2835_SOC_I2S | |||
7 | Say Y or M if you want to add support for codecs attached to | 7 | Say Y or M if you want to add support for codecs attached to |
8 | the BCM2835 I2S interface. You will also need | 8 | the BCM2835 I2S interface. You will also need |
9 | to select the audio interfaces to support below. | 9 | to select the audio interfaces to support below. |
10 | |||
11 | config SND_SOC_CYGNUS | ||
12 | tristate "SoC platform audio for Broadcom Cygnus chips" | ||
13 | depends on ARCH_BCM_CYGNUS || COMPILE_TEST | ||
14 | help | ||
15 | Say Y if you want to add support for ASoC audio on Broadcom | ||
16 | Cygnus chips (bcm958300, bcm958305, bcm911360) | ||
17 | |||
18 | If you don't know what to do here, say N. \ No newline at end of file | ||
diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index bc816b71e5a4..fc739d007884 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile | |||
@@ -3,3 +3,8 @@ snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o | |||
3 | 3 | ||
4 | obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o | 4 | obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o |
5 | 5 | ||
6 | # CYGNUS Platform Support | ||
7 | snd-soc-cygnus-objs := cygnus-pcm.o cygnus-ssp.o | ||
8 | |||
9 | obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o | ||
10 | |||
diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c new file mode 100644 index 000000000000..d616e096462e --- /dev/null +++ b/sound/soc/bcm/cygnus-pcm.c | |||
@@ -0,0 +1,861 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2015 Broadcom Corporation | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation version 2. | ||
7 | * | ||
8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
9 | * kind, whether express or implied; without even the implied warranty | ||
10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | #include <linux/debugfs.h> | ||
14 | #include <linux/dma-mapping.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/slab.h> | ||
19 | #include <linux/timer.h> | ||
20 | #include <sound/core.h> | ||
21 | #include <sound/pcm.h> | ||
22 | #include <sound/pcm_params.h> | ||
23 | #include <sound/soc.h> | ||
24 | #include <sound/soc-dai.h> | ||
25 | |||
26 | #include "cygnus-ssp.h" | ||
27 | |||
28 | /* Register offset needed for ASoC PCM module */ | ||
29 | |||
30 | #define INTH_R5F_STATUS_OFFSET 0x040 | ||
31 | #define INTH_R5F_CLEAR_OFFSET 0x048 | ||
32 | #define INTH_R5F_MASK_SET_OFFSET 0x050 | ||
33 | #define INTH_R5F_MASK_CLEAR_OFFSET 0x054 | ||
34 | |||
35 | #define BF_REARM_FREE_MARK_OFFSET 0x344 | ||
36 | #define BF_REARM_FULL_MARK_OFFSET 0x348 | ||
37 | |||
38 | /* Ring Buffer Ctrl Regs --- Start */ | ||
39 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */ | ||
40 | #define SRC_RBUF_0_RDADDR_OFFSET 0x500 | ||
41 | #define SRC_RBUF_1_RDADDR_OFFSET 0x518 | ||
42 | #define SRC_RBUF_2_RDADDR_OFFSET 0x530 | ||
43 | #define SRC_RBUF_3_RDADDR_OFFSET 0x548 | ||
44 | #define SRC_RBUF_4_RDADDR_OFFSET 0x560 | ||
45 | #define SRC_RBUF_5_RDADDR_OFFSET 0x578 | ||
46 | #define SRC_RBUF_6_RDADDR_OFFSET 0x590 | ||
47 | |||
48 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */ | ||
49 | #define SRC_RBUF_0_WRADDR_OFFSET 0x504 | ||
50 | #define SRC_RBUF_1_WRADDR_OFFSET 0x51c | ||
51 | #define SRC_RBUF_2_WRADDR_OFFSET 0x534 | ||
52 | #define SRC_RBUF_3_WRADDR_OFFSET 0x54c | ||
53 | #define SRC_RBUF_4_WRADDR_OFFSET 0x564 | ||
54 | #define SRC_RBUF_5_WRADDR_OFFSET 0x57c | ||
55 | #define SRC_RBUF_6_WRADDR_OFFSET 0x594 | ||
56 | |||
57 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */ | ||
58 | #define SRC_RBUF_0_BASEADDR_OFFSET 0x508 | ||
59 | #define SRC_RBUF_1_BASEADDR_OFFSET 0x520 | ||
60 | #define SRC_RBUF_2_BASEADDR_OFFSET 0x538 | ||
61 | #define SRC_RBUF_3_BASEADDR_OFFSET 0x550 | ||
62 | #define SRC_RBUF_4_BASEADDR_OFFSET 0x568 | ||
63 | #define SRC_RBUF_5_BASEADDR_OFFSET 0x580 | ||
64 | #define SRC_RBUF_6_BASEADDR_OFFSET 0x598 | ||
65 | |||
66 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */ | ||
67 | #define SRC_RBUF_0_ENDADDR_OFFSET 0x50c | ||
68 | #define SRC_RBUF_1_ENDADDR_OFFSET 0x524 | ||
69 | #define SRC_RBUF_2_ENDADDR_OFFSET 0x53c | ||
70 | #define SRC_RBUF_3_ENDADDR_OFFSET 0x554 | ||
71 | #define SRC_RBUF_4_ENDADDR_OFFSET 0x56c | ||
72 | #define SRC_RBUF_5_ENDADDR_OFFSET 0x584 | ||
73 | #define SRC_RBUF_6_ENDADDR_OFFSET 0x59c | ||
74 | |||
75 | /* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */ | ||
76 | #define SRC_RBUF_0_FREE_MARK_OFFSET 0x510 | ||
77 | #define SRC_RBUF_1_FREE_MARK_OFFSET 0x528 | ||
78 | #define SRC_RBUF_2_FREE_MARK_OFFSET 0x540 | ||
79 | #define SRC_RBUF_3_FREE_MARK_OFFSET 0x558 | ||
80 | #define SRC_RBUF_4_FREE_MARK_OFFSET 0x570 | ||
81 | #define SRC_RBUF_5_FREE_MARK_OFFSET 0x588 | ||
82 | #define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0 | ||
83 | |||
84 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */ | ||
85 | #define DST_RBUF_0_RDADDR_OFFSET 0x5c0 | ||
86 | #define DST_RBUF_1_RDADDR_OFFSET 0x5d8 | ||
87 | #define DST_RBUF_2_RDADDR_OFFSET 0x5f0 | ||
88 | #define DST_RBUF_3_RDADDR_OFFSET 0x608 | ||
89 | #define DST_RBUF_4_RDADDR_OFFSET 0x620 | ||
90 | #define DST_RBUF_5_RDADDR_OFFSET 0x638 | ||
91 | |||
92 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */ | ||
93 | #define DST_RBUF_0_WRADDR_OFFSET 0x5c4 | ||
94 | #define DST_RBUF_1_WRADDR_OFFSET 0x5dc | ||
95 | #define DST_RBUF_2_WRADDR_OFFSET 0x5f4 | ||
96 | #define DST_RBUF_3_WRADDR_OFFSET 0x60c | ||
97 | #define DST_RBUF_4_WRADDR_OFFSET 0x624 | ||
98 | #define DST_RBUF_5_WRADDR_OFFSET 0x63c | ||
99 | |||
100 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */ | ||
101 | #define DST_RBUF_0_BASEADDR_OFFSET 0x5c8 | ||
102 | #define DST_RBUF_1_BASEADDR_OFFSET 0x5e0 | ||
103 | #define DST_RBUF_2_BASEADDR_OFFSET 0x5f8 | ||
104 | #define DST_RBUF_3_BASEADDR_OFFSET 0x610 | ||
105 | #define DST_RBUF_4_BASEADDR_OFFSET 0x628 | ||
106 | #define DST_RBUF_5_BASEADDR_OFFSET 0x640 | ||
107 | |||
108 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */ | ||
109 | #define DST_RBUF_0_ENDADDR_OFFSET 0x5cc | ||
110 | #define DST_RBUF_1_ENDADDR_OFFSET 0x5e4 | ||
111 | #define DST_RBUF_2_ENDADDR_OFFSET 0x5fc | ||
112 | #define DST_RBUF_3_ENDADDR_OFFSET 0x614 | ||
113 | #define DST_RBUF_4_ENDADDR_OFFSET 0x62c | ||
114 | #define DST_RBUF_5_ENDADDR_OFFSET 0x644 | ||
115 | |||
116 | /* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */ | ||
117 | #define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0 | ||
118 | #define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8 | ||
119 | #define DST_RBUF_2_FULL_MARK_OFFSET 0x600 | ||
120 | #define DST_RBUF_3_FULL_MARK_OFFSET 0x618 | ||
121 | #define DST_RBUF_4_FULL_MARK_OFFSET 0x630 | ||
122 | #define DST_RBUF_5_FULL_MARK_OFFSET 0x648 | ||
123 | /* Ring Buffer Ctrl Regs --- End */ | ||
124 | |||
125 | /* Error Status Regs --- Start */ | ||
126 | /* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */ | ||
127 | #define ESR0_STATUS_OFFSET 0x900 | ||
128 | #define ESR1_STATUS_OFFSET 0x918 | ||
129 | #define ESR2_STATUS_OFFSET 0x930 | ||
130 | #define ESR3_STATUS_OFFSET 0x948 | ||
131 | #define ESR4_STATUS_OFFSET 0x960 | ||
132 | |||
133 | /* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */ | ||
134 | #define ESR0_STATUS_CLR_OFFSET 0x908 | ||
135 | #define ESR1_STATUS_CLR_OFFSET 0x920 | ||
136 | #define ESR2_STATUS_CLR_OFFSET 0x938 | ||
137 | #define ESR3_STATUS_CLR_OFFSET 0x950 | ||
138 | #define ESR4_STATUS_CLR_OFFSET 0x968 | ||
139 | |||
140 | /* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */ | ||
141 | #define ESR0_MASK_STATUS_OFFSET 0x90c | ||
142 | #define ESR1_MASK_STATUS_OFFSET 0x924 | ||
143 | #define ESR2_MASK_STATUS_OFFSET 0x93c | ||
144 | #define ESR3_MASK_STATUS_OFFSET 0x954 | ||
145 | #define ESR4_MASK_STATUS_OFFSET 0x96c | ||
146 | |||
147 | /* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */ | ||
148 | #define ESR0_MASK_SET_OFFSET 0x910 | ||
149 | #define ESR1_MASK_SET_OFFSET 0x928 | ||
150 | #define ESR2_MASK_SET_OFFSET 0x940 | ||
151 | #define ESR3_MASK_SET_OFFSET 0x958 | ||
152 | #define ESR4_MASK_SET_OFFSET 0x970 | ||
153 | |||
154 | /* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */ | ||
155 | #define ESR0_MASK_CLR_OFFSET 0x914 | ||
156 | #define ESR1_MASK_CLR_OFFSET 0x92c | ||
157 | #define ESR2_MASK_CLR_OFFSET 0x944 | ||
158 | #define ESR3_MASK_CLR_OFFSET 0x95c | ||
159 | #define ESR4_MASK_CLR_OFFSET 0x974 | ||
160 | /* Error Status Regs --- End */ | ||
161 | |||
162 | #define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */ | ||
163 | #define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */ | ||
164 | #define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */ | ||
165 | #define R5F_ESR3_SHIFT 3 /* esr3 = freemark */ | ||
166 | #define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */ | ||
167 | |||
168 | |||
169 | /* Mask for R5F register. Set all relevant interrupt for playback handler */ | ||
170 | #define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \ | ||
171 | BIT(R5F_ESR1_SHIFT) | \ | ||
172 | BIT(R5F_ESR3_SHIFT)) | ||
173 | |||
174 | /* Mask for R5F register. Set all relevant interrupt for capture handler */ | ||
175 | #define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT)) | ||
176 | |||
177 | /* | ||
178 | * PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick. | ||
179 | * This number should be a multiple of 256. Minimum value is 256 | ||
180 | */ | ||
181 | #define PERIOD_BYTES_MIN 0x100 | ||
182 | |||
183 | static const struct snd_pcm_hardware cygnus_pcm_hw = { | ||
184 | .info = SNDRV_PCM_INFO_MMAP | | ||
185 | SNDRV_PCM_INFO_MMAP_VALID | | ||
186 | SNDRV_PCM_INFO_INTERLEAVED, | ||
187 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | ||
188 | SNDRV_PCM_FMTBIT_S32_LE, | ||
189 | |||
190 | /* A period is basically an interrupt */ | ||
191 | .period_bytes_min = PERIOD_BYTES_MIN, | ||
192 | .period_bytes_max = 0x10000, | ||
193 | |||
194 | /* period_min/max gives range of approx interrupts per buffer */ | ||
195 | .periods_min = 2, | ||
196 | .periods_max = 8, | ||
197 | |||
198 | /* | ||
199 | * maximum buffer size in bytes = period_bytes_max * periods_max | ||
200 | * We allocate this amount of data for each enabled channel | ||
201 | */ | ||
202 | .buffer_bytes_max = 4 * 0x8000, | ||
203 | }; | ||
204 | |||
205 | static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32); | ||
206 | |||
207 | static struct cygnus_aio_port *cygnus_dai_get_dma_data( | ||
208 | struct snd_pcm_substream *substream) | ||
209 | { | ||
210 | struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; | ||
211 | |||
212 | return snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); | ||
213 | } | ||
214 | |||
215 | static void ringbuf_set_initial(void __iomem *audio_io, | ||
216 | struct ringbuf_regs *p_rbuf, | ||
217 | bool is_playback, | ||
218 | u32 start, | ||
219 | u32 periodsize, | ||
220 | u32 bufsize) | ||
221 | { | ||
222 | u32 initial_rd; | ||
223 | u32 initial_wr; | ||
224 | u32 end; | ||
225 | u32 fmark_val; /* free or full mark */ | ||
226 | |||
227 | p_rbuf->period_bytes = periodsize; | ||
228 | p_rbuf->buf_size = bufsize; | ||
229 | |||
230 | if (is_playback) { | ||
231 | /* Set the pointers to indicate full (flip uppermost bit) */ | ||
232 | initial_rd = start; | ||
233 | initial_wr = initial_rd ^ BIT(31); | ||
234 | } else { | ||
235 | /* Set the pointers to indicate empty */ | ||
236 | initial_wr = start; | ||
237 | initial_rd = initial_wr; | ||
238 | } | ||
239 | |||
240 | end = start + bufsize - 1; | ||
241 | |||
242 | /* | ||
243 | * The interrupt will fire when free/full mark is *exceeded* | ||
244 | * The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark | ||
245 | * to be PERIOD_BYTES_MIN less than the period size. | ||
246 | */ | ||
247 | fmark_val = periodsize - PERIOD_BYTES_MIN; | ||
248 | |||
249 | writel(start, audio_io + p_rbuf->baseaddr); | ||
250 | writel(end, audio_io + p_rbuf->endaddr); | ||
251 | writel(fmark_val, audio_io + p_rbuf->fmark); | ||
252 | writel(initial_rd, audio_io + p_rbuf->rdaddr); | ||
253 | writel(initial_wr, audio_io + p_rbuf->wraddr); | ||
254 | } | ||
255 | |||
256 | static int configure_ringbuf_regs(struct snd_pcm_substream *substream) | ||
257 | { | ||
258 | struct cygnus_aio_port *aio; | ||
259 | struct ringbuf_regs *p_rbuf; | ||
260 | int status = 0; | ||
261 | |||
262 | aio = cygnus_dai_get_dma_data(substream); | ||
263 | |||
264 | /* Map the ssp portnum to a set of ring buffers. */ | ||
265 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
266 | p_rbuf = &aio->play_rb_regs; | ||
267 | |||
268 | switch (aio->portnum) { | ||
269 | case 0: | ||
270 | *p_rbuf = RINGBUF_REG_PLAYBACK(0); | ||
271 | break; | ||
272 | case 1: | ||
273 | *p_rbuf = RINGBUF_REG_PLAYBACK(2); | ||
274 | break; | ||
275 | case 2: | ||
276 | *p_rbuf = RINGBUF_REG_PLAYBACK(4); | ||
277 | break; | ||
278 | case 3: /* SPDIF */ | ||
279 | *p_rbuf = RINGBUF_REG_PLAYBACK(6); | ||
280 | break; | ||
281 | default: | ||
282 | status = -EINVAL; | ||
283 | } | ||
284 | } else { | ||
285 | p_rbuf = &aio->capture_rb_regs; | ||
286 | |||
287 | switch (aio->portnum) { | ||
288 | case 0: | ||
289 | *p_rbuf = RINGBUF_REG_CAPTURE(0); | ||
290 | break; | ||
291 | case 1: | ||
292 | *p_rbuf = RINGBUF_REG_CAPTURE(2); | ||
293 | break; | ||
294 | case 2: | ||
295 | *p_rbuf = RINGBUF_REG_CAPTURE(4); | ||
296 | break; | ||
297 | default: | ||
298 | status = -EINVAL; | ||
299 | } | ||
300 | } | ||
301 | |||
302 | return status; | ||
303 | } | ||
304 | |||
305 | static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream) | ||
306 | { | ||
307 | struct cygnus_aio_port *aio; | ||
308 | struct ringbuf_regs *p_rbuf = NULL; | ||
309 | |||
310 | aio = cygnus_dai_get_dma_data(substream); | ||
311 | |||
312 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
313 | p_rbuf = &aio->play_rb_regs; | ||
314 | else | ||
315 | p_rbuf = &aio->capture_rb_regs; | ||
316 | |||
317 | return p_rbuf; | ||
318 | } | ||
319 | |||
320 | static void enable_intr(struct snd_pcm_substream *substream) | ||
321 | { | ||
322 | struct cygnus_aio_port *aio; | ||
323 | u32 clear_mask; | ||
324 | |||
325 | aio = cygnus_dai_get_dma_data(substream); | ||
326 | |||
327 | /* The port number maps to the bit position to be cleared */ | ||
328 | clear_mask = BIT(aio->portnum); | ||
329 | |||
330 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
331 | /* Clear interrupt status before enabling them */ | ||
332 | writel(clear_mask, aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET); | ||
333 | writel(clear_mask, aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET); | ||
334 | writel(clear_mask, aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET); | ||
335 | /* Unmask the interrupts of the given port*/ | ||
336 | writel(clear_mask, aio->cygaud->audio + ESR0_MASK_CLR_OFFSET); | ||
337 | writel(clear_mask, aio->cygaud->audio + ESR1_MASK_CLR_OFFSET); | ||
338 | writel(clear_mask, aio->cygaud->audio + ESR3_MASK_CLR_OFFSET); | ||
339 | |||
340 | writel(ANY_PLAYBACK_IRQ, | ||
341 | aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); | ||
342 | } else { | ||
343 | writel(clear_mask, aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET); | ||
344 | writel(clear_mask, aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET); | ||
345 | writel(clear_mask, aio->cygaud->audio + ESR2_MASK_CLR_OFFSET); | ||
346 | writel(clear_mask, aio->cygaud->audio + ESR4_MASK_CLR_OFFSET); | ||
347 | |||
348 | writel(ANY_CAPTURE_IRQ, | ||
349 | aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); | ||
350 | } | ||
351 | |||
352 | } | ||
353 | |||
354 | static void disable_intr(struct snd_pcm_substream *substream) | ||
355 | { | ||
356 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
357 | struct cygnus_aio_port *aio; | ||
358 | u32 set_mask; | ||
359 | |||
360 | aio = cygnus_dai_get_dma_data(substream); | ||
361 | |||
362 | dev_dbg(rtd->cpu_dai->dev, "%s on port %d\n", __func__, aio->portnum); | ||
363 | |||
364 | /* The port number maps to the bit position to be set */ | ||
365 | set_mask = BIT(aio->portnum); | ||
366 | |||
367 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
368 | /* Mask the interrupts of the given port*/ | ||
369 | writel(set_mask, aio->cygaud->audio + ESR0_MASK_SET_OFFSET); | ||
370 | writel(set_mask, aio->cygaud->audio + ESR1_MASK_SET_OFFSET); | ||
371 | writel(set_mask, aio->cygaud->audio + ESR3_MASK_SET_OFFSET); | ||
372 | } else { | ||
373 | writel(set_mask, aio->cygaud->audio + ESR2_MASK_SET_OFFSET); | ||
374 | writel(set_mask, aio->cygaud->audio + ESR4_MASK_SET_OFFSET); | ||
375 | } | ||
376 | |||
377 | } | ||
378 | |||
379 | static int cygnus_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
380 | { | ||
381 | int ret = 0; | ||
382 | |||
383 | switch (cmd) { | ||
384 | case SNDRV_PCM_TRIGGER_START: | ||
385 | case SNDRV_PCM_TRIGGER_RESUME: | ||
386 | enable_intr(substream); | ||
387 | break; | ||
388 | |||
389 | case SNDRV_PCM_TRIGGER_STOP: | ||
390 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
391 | disable_intr(substream); | ||
392 | break; | ||
393 | default: | ||
394 | ret = -EINVAL; | ||
395 | } | ||
396 | |||
397 | return ret; | ||
398 | } | ||
399 | |||
400 | static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream) | ||
401 | { | ||
402 | struct cygnus_aio_port *aio; | ||
403 | struct ringbuf_regs *p_rbuf = NULL; | ||
404 | u32 regval; | ||
405 | |||
406 | aio = cygnus_dai_get_dma_data(substream); | ||
407 | |||
408 | p_rbuf = get_ringbuf(substream); | ||
409 | |||
410 | /* | ||
411 | * If free/full mark interrupt occurs, provide timestamp | ||
412 | * to ALSA and update appropriate idx by period_bytes | ||
413 | */ | ||
414 | snd_pcm_period_elapsed(substream); | ||
415 | |||
416 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
417 | /* Set the ring buffer to full */ | ||
418 | regval = readl(aio->cygaud->audio + p_rbuf->rdaddr); | ||
419 | regval = regval ^ BIT(31); | ||
420 | writel(regval, aio->cygaud->audio + p_rbuf->wraddr); | ||
421 | } else { | ||
422 | /* Set the ring buffer to empty */ | ||
423 | regval = readl(aio->cygaud->audio + p_rbuf->wraddr); | ||
424 | writel(regval, aio->cygaud->audio + p_rbuf->rdaddr); | ||
425 | } | ||
426 | } | ||
427 | |||
428 | /* | ||
429 | * ESR0/1/3 status Description | ||
430 | * 0x1 I2S0_out port caused interrupt | ||
431 | * 0x2 I2S1_out port caused interrupt | ||
432 | * 0x4 I2S2_out port caused interrupt | ||
433 | * 0x8 SPDIF_out port caused interrupt | ||
434 | */ | ||
435 | static void handle_playback_irq(struct cygnus_audio *cygaud) | ||
436 | { | ||
437 | void __iomem *audio_io; | ||
438 | u32 port; | ||
439 | u32 esr_status0, esr_status1, esr_status3; | ||
440 | |||
441 | audio_io = cygaud->audio; | ||
442 | |||
443 | /* | ||
444 | * ESR status gets updates with/without interrupts enabled. | ||
445 | * So, check the ESR mask, which provides interrupt enable/ | ||
446 | * disable status and use it to determine which ESR status | ||
447 | * should be serviced. | ||
448 | */ | ||
449 | esr_status0 = readl(audio_io + ESR0_STATUS_OFFSET); | ||
450 | esr_status0 &= ~readl(audio_io + ESR0_MASK_STATUS_OFFSET); | ||
451 | esr_status1 = readl(audio_io + ESR1_STATUS_OFFSET); | ||
452 | esr_status1 &= ~readl(audio_io + ESR1_MASK_STATUS_OFFSET); | ||
453 | esr_status3 = readl(audio_io + ESR3_STATUS_OFFSET); | ||
454 | esr_status3 &= ~readl(audio_io + ESR3_MASK_STATUS_OFFSET); | ||
455 | |||
456 | for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) { | ||
457 | u32 esrmask = BIT(port); | ||
458 | |||
459 | /* | ||
460 | * Ringbuffer or FIFO underflow | ||
461 | * If we get this interrupt then, it is also true that we have | ||
462 | * not yet responded to the freemark interrupt. | ||
463 | * Log a debug message. The freemark handler below will | ||
464 | * handle getting everything going again. | ||
465 | */ | ||
466 | if ((esrmask & esr_status1) || (esrmask & esr_status0)) { | ||
467 | dev_dbg(cygaud->dev, | ||
468 | "Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n", | ||
469 | esr_status0, esr_status1, esr_status3); | ||
470 | } | ||
471 | |||
472 | /* | ||
473 | * Freemark is hit. This is the normal interrupt. | ||
474 | * In typical operation the read and write regs will be equal | ||
475 | */ | ||
476 | if (esrmask & esr_status3) { | ||
477 | struct snd_pcm_substream *playstr; | ||
478 | |||
479 | playstr = cygaud->portinfo[port].play_stream; | ||
480 | cygnus_pcm_period_elapsed(playstr); | ||
481 | } | ||
482 | } | ||
483 | |||
484 | /* Clear ESR interrupt */ | ||
485 | writel(esr_status0, audio_io + ESR0_STATUS_CLR_OFFSET); | ||
486 | writel(esr_status1, audio_io + ESR1_STATUS_CLR_OFFSET); | ||
487 | writel(esr_status3, audio_io + ESR3_STATUS_CLR_OFFSET); | ||
488 | /* Rearm freemark logic by writing 1 to the correct bit */ | ||
489 | writel(esr_status3, audio_io + BF_REARM_FREE_MARK_OFFSET); | ||
490 | } | ||
491 | |||
492 | /* | ||
493 | * ESR2/4 status Description | ||
494 | * 0x1 I2S0_in port caused interrupt | ||
495 | * 0x2 I2S1_in port caused interrupt | ||
496 | * 0x4 I2S2_in port caused interrupt | ||
497 | */ | ||
498 | static void handle_capture_irq(struct cygnus_audio *cygaud) | ||
499 | { | ||
500 | void __iomem *audio_io; | ||
501 | u32 port; | ||
502 | u32 esr_status2, esr_status4; | ||
503 | |||
504 | audio_io = cygaud->audio; | ||
505 | |||
506 | /* | ||
507 | * ESR status gets updates with/without interrupts enabled. | ||
508 | * So, check the ESR mask, which provides interrupt enable/ | ||
509 | * disable status and use it to determine which ESR status | ||
510 | * should be serviced. | ||
511 | */ | ||
512 | esr_status2 = readl(audio_io + ESR2_STATUS_OFFSET); | ||
513 | esr_status2 &= ~readl(audio_io + ESR2_MASK_STATUS_OFFSET); | ||
514 | esr_status4 = readl(audio_io + ESR4_STATUS_OFFSET); | ||
515 | esr_status4 &= ~readl(audio_io + ESR4_MASK_STATUS_OFFSET); | ||
516 | |||
517 | for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) { | ||
518 | u32 esrmask = BIT(port); | ||
519 | |||
520 | /* | ||
521 | * Ringbuffer or FIFO overflow | ||
522 | * If we get this interrupt then, it is also true that we have | ||
523 | * not yet responded to the fullmark interrupt. | ||
524 | * Log a debug message. The fullmark handler below will | ||
525 | * handle getting everything going again. | ||
526 | */ | ||
527 | if (esrmask & esr_status2) | ||
528 | dev_dbg(cygaud->dev, | ||
529 | "Overflow: esr2=0x%x\n", esr_status2); | ||
530 | |||
531 | if (esrmask & esr_status4) { | ||
532 | struct snd_pcm_substream *capstr; | ||
533 | |||
534 | capstr = cygaud->portinfo[port].capture_stream; | ||
535 | cygnus_pcm_period_elapsed(capstr); | ||
536 | } | ||
537 | } | ||
538 | |||
539 | writel(esr_status2, audio_io + ESR2_STATUS_CLR_OFFSET); | ||
540 | writel(esr_status4, audio_io + ESR4_STATUS_CLR_OFFSET); | ||
541 | /* Rearm fullmark logic by writing 1 to the correct bit */ | ||
542 | writel(esr_status4, audio_io + BF_REARM_FULL_MARK_OFFSET); | ||
543 | } | ||
544 | |||
545 | static irqreturn_t cygnus_dma_irq(int irq, void *data) | ||
546 | { | ||
547 | u32 r5_status; | ||
548 | struct cygnus_audio *cygaud = data; | ||
549 | |||
550 | /* | ||
551 | * R5 status bits Description | ||
552 | * 0 ESR0 (playback FIFO interrupt) | ||
553 | * 1 ESR1 (playback rbuf interrupt) | ||
554 | * 2 ESR2 (capture rbuf interrupt) | ||
555 | * 3 ESR3 (Freemark play. interrupt) | ||
556 | * 4 ESR4 (Fullmark capt. interrupt) | ||
557 | */ | ||
558 | r5_status = readl(cygaud->audio + INTH_R5F_STATUS_OFFSET); | ||
559 | |||
560 | if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ))) | ||
561 | return IRQ_NONE; | ||
562 | |||
563 | /* If playback interrupt happened */ | ||
564 | if (ANY_PLAYBACK_IRQ & r5_status) { | ||
565 | handle_playback_irq(cygaud); | ||
566 | writel(ANY_PLAYBACK_IRQ & r5_status, | ||
567 | cygaud->audio + INTH_R5F_CLEAR_OFFSET); | ||
568 | } | ||
569 | |||
570 | /* If capture interrupt happened */ | ||
571 | if (ANY_CAPTURE_IRQ & r5_status) { | ||
572 | handle_capture_irq(cygaud); | ||
573 | writel(ANY_CAPTURE_IRQ & r5_status, | ||
574 | cygaud->audio + INTH_R5F_CLEAR_OFFSET); | ||
575 | } | ||
576 | |||
577 | return IRQ_HANDLED; | ||
578 | } | ||
579 | |||
580 | static int cygnus_pcm_open(struct snd_pcm_substream *substream) | ||
581 | { | ||
582 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
583 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
584 | struct cygnus_aio_port *aio; | ||
585 | int ret; | ||
586 | |||
587 | aio = cygnus_dai_get_dma_data(substream); | ||
588 | if (!aio) | ||
589 | return -ENODEV; | ||
590 | |||
591 | dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); | ||
592 | |||
593 | snd_soc_set_runtime_hwparams(substream, &cygnus_pcm_hw); | ||
594 | |||
595 | ret = snd_pcm_hw_constraint_step(runtime, 0, | ||
596 | SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN); | ||
597 | if (ret < 0) | ||
598 | return ret; | ||
599 | |||
600 | ret = snd_pcm_hw_constraint_step(runtime, 0, | ||
601 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN); | ||
602 | if (ret < 0) | ||
603 | return ret; | ||
604 | /* | ||
605 | * Keep track of which substream belongs to which port. | ||
606 | * This info is needed by snd_pcm_period_elapsed() in irq_handler | ||
607 | */ | ||
608 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
609 | aio->play_stream = substream; | ||
610 | else | ||
611 | aio->capture_stream = substream; | ||
612 | |||
613 | return 0; | ||
614 | } | ||
615 | |||
616 | static int cygnus_pcm_close(struct snd_pcm_substream *substream) | ||
617 | { | ||
618 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
619 | struct cygnus_aio_port *aio; | ||
620 | |||
621 | aio = cygnus_dai_get_dma_data(substream); | ||
622 | |||
623 | dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); | ||
624 | |||
625 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
626 | aio->play_stream = NULL; | ||
627 | else | ||
628 | aio->capture_stream = NULL; | ||
629 | |||
630 | if (!aio->play_stream && !aio->capture_stream) | ||
631 | dev_dbg(rtd->cpu_dai->dev, "freed port %d\n", aio->portnum); | ||
632 | |||
633 | return 0; | ||
634 | } | ||
635 | |||
636 | static int cygnus_pcm_hw_params(struct snd_pcm_substream *substream, | ||
637 | struct snd_pcm_hw_params *params) | ||
638 | { | ||
639 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
640 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
641 | struct cygnus_aio_port *aio; | ||
642 | int ret = 0; | ||
643 | |||
644 | aio = cygnus_dai_get_dma_data(substream); | ||
645 | dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); | ||
646 | |||
647 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
648 | runtime->dma_bytes = params_buffer_bytes(params); | ||
649 | |||
650 | return ret; | ||
651 | } | ||
652 | |||
653 | static int cygnus_pcm_hw_free(struct snd_pcm_substream *substream) | ||
654 | { | ||
655 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
656 | struct cygnus_aio_port *aio; | ||
657 | |||
658 | aio = cygnus_dai_get_dma_data(substream); | ||
659 | dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); | ||
660 | |||
661 | snd_pcm_set_runtime_buffer(substream, NULL); | ||
662 | return 0; | ||
663 | } | ||
664 | |||
665 | static int cygnus_pcm_prepare(struct snd_pcm_substream *substream) | ||
666 | { | ||
667 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
668 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
669 | struct cygnus_aio_port *aio; | ||
670 | unsigned long bufsize, periodsize; | ||
671 | int ret = 0; | ||
672 | bool is_play; | ||
673 | u32 start; | ||
674 | struct ringbuf_regs *p_rbuf = NULL; | ||
675 | |||
676 | aio = cygnus_dai_get_dma_data(substream); | ||
677 | dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); | ||
678 | |||
679 | bufsize = snd_pcm_lib_buffer_bytes(substream); | ||
680 | periodsize = snd_pcm_lib_period_bytes(substream); | ||
681 | |||
682 | dev_dbg(rtd->cpu_dai->dev, "%s (buf_size %lu) (period_size %lu)\n", | ||
683 | __func__, bufsize, periodsize); | ||
684 | |||
685 | configure_ringbuf_regs(substream); | ||
686 | |||
687 | p_rbuf = get_ringbuf(substream); | ||
688 | |||
689 | start = runtime->dma_addr; | ||
690 | |||
691 | is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; | ||
692 | |||
693 | ringbuf_set_initial(aio->cygaud->audio, p_rbuf, is_play, start, | ||
694 | periodsize, bufsize); | ||
695 | |||
696 | return ret; | ||
697 | } | ||
698 | |||
699 | static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_pcm_substream *substream) | ||
700 | { | ||
701 | struct cygnus_aio_port *aio; | ||
702 | unsigned int res = 0, cur = 0, base = 0; | ||
703 | struct ringbuf_regs *p_rbuf = NULL; | ||
704 | |||
705 | aio = cygnus_dai_get_dma_data(substream); | ||
706 | |||
707 | /* | ||
708 | * Get the offset of the current read (for playack) or write | ||
709 | * index (for capture). Report this value back to the asoc framework. | ||
710 | */ | ||
711 | p_rbuf = get_ringbuf(substream); | ||
712 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
713 | cur = readl(aio->cygaud->audio + p_rbuf->rdaddr); | ||
714 | else | ||
715 | cur = readl(aio->cygaud->audio + p_rbuf->wraddr); | ||
716 | |||
717 | base = readl(aio->cygaud->audio + p_rbuf->baseaddr); | ||
718 | |||
719 | /* | ||
720 | * Mask off the MSB of the rdaddr,wraddr and baseaddr | ||
721 | * since MSB is not part of the address | ||
722 | */ | ||
723 | res = (cur & 0x7fffffff) - (base & 0x7fffffff); | ||
724 | |||
725 | return bytes_to_frames(substream->runtime, res); | ||
726 | } | ||
727 | |||
728 | static int cygnus_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
729 | { | ||
730 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
731 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
732 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
733 | size_t size; | ||
734 | |||
735 | size = cygnus_pcm_hw.buffer_bytes_max; | ||
736 | |||
737 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
738 | buf->dev.dev = pcm->card->dev; | ||
739 | buf->private_data = NULL; | ||
740 | buf->area = dma_alloc_coherent(pcm->card->dev, size, | ||
741 | &buf->addr, GFP_KERNEL); | ||
742 | |||
743 | dev_dbg(rtd->cpu_dai->dev, "%s: size 0x%zx @ %pK\n", | ||
744 | __func__, size, buf->area); | ||
745 | |||
746 | if (!buf->area) { | ||
747 | dev_err(rtd->cpu_dai->dev, "%s: dma_alloc failed\n", __func__); | ||
748 | return -ENOMEM; | ||
749 | } | ||
750 | buf->bytes = size; | ||
751 | |||
752 | return 0; | ||
753 | } | ||
754 | |||
755 | |||
756 | static const struct snd_pcm_ops cygnus_pcm_ops = { | ||
757 | .open = cygnus_pcm_open, | ||
758 | .close = cygnus_pcm_close, | ||
759 | .ioctl = snd_pcm_lib_ioctl, | ||
760 | .hw_params = cygnus_pcm_hw_params, | ||
761 | .hw_free = cygnus_pcm_hw_free, | ||
762 | .prepare = cygnus_pcm_prepare, | ||
763 | .trigger = cygnus_pcm_trigger, | ||
764 | .pointer = cygnus_pcm_pointer, | ||
765 | }; | ||
766 | |||
767 | static void cygnus_dma_free_dma_buffers(struct snd_pcm *pcm) | ||
768 | { | ||
769 | struct snd_pcm_substream *substream; | ||
770 | struct snd_dma_buffer *buf; | ||
771 | |||
772 | substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; | ||
773 | if (substream) { | ||
774 | buf = &substream->dma_buffer; | ||
775 | if (buf->area) { | ||
776 | dma_free_coherent(pcm->card->dev, buf->bytes, | ||
777 | buf->area, buf->addr); | ||
778 | buf->area = NULL; | ||
779 | } | ||
780 | } | ||
781 | |||
782 | substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; | ||
783 | if (substream) { | ||
784 | buf = &substream->dma_buffer; | ||
785 | if (buf->area) { | ||
786 | dma_free_coherent(pcm->card->dev, buf->bytes, | ||
787 | buf->area, buf->addr); | ||
788 | buf->area = NULL; | ||
789 | } | ||
790 | } | ||
791 | } | ||
792 | |||
793 | static int cygnus_dma_new(struct snd_soc_pcm_runtime *rtd) | ||
794 | { | ||
795 | struct snd_card *card = rtd->card->snd_card; | ||
796 | struct snd_pcm *pcm = rtd->pcm; | ||
797 | int ret; | ||
798 | |||
799 | if (!card->dev->dma_mask) | ||
800 | card->dev->dma_mask = &cygnus_dma_dmamask; | ||
801 | if (!card->dev->coherent_dma_mask) | ||
802 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); | ||
803 | |||
804 | if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { | ||
805 | ret = cygnus_pcm_preallocate_dma_buffer(pcm, | ||
806 | SNDRV_PCM_STREAM_PLAYBACK); | ||
807 | if (ret) | ||
808 | return ret; | ||
809 | } | ||
810 | |||
811 | if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { | ||
812 | ret = cygnus_pcm_preallocate_dma_buffer(pcm, | ||
813 | SNDRV_PCM_STREAM_CAPTURE); | ||
814 | if (ret) { | ||
815 | cygnus_dma_free_dma_buffers(pcm); | ||
816 | return ret; | ||
817 | } | ||
818 | } | ||
819 | |||
820 | return 0; | ||
821 | } | ||
822 | |||
823 | static struct snd_soc_platform_driver cygnus_soc_platform = { | ||
824 | .ops = &cygnus_pcm_ops, | ||
825 | .pcm_new = cygnus_dma_new, | ||
826 | .pcm_free = cygnus_dma_free_dma_buffers, | ||
827 | }; | ||
828 | |||
829 | int cygnus_soc_platform_register(struct device *dev, | ||
830 | struct cygnus_audio *cygaud) | ||
831 | { | ||
832 | int rc = 0; | ||
833 | |||
834 | dev_dbg(dev, "%s Enter\n", __func__); | ||
835 | |||
836 | rc = devm_request_irq(dev, cygaud->irq_num, cygnus_dma_irq, | ||
837 | IRQF_SHARED, "cygnus-audio", cygaud); | ||
838 | if (rc) { | ||
839 | dev_err(dev, "%s request_irq error %d\n", __func__, rc); | ||
840 | return rc; | ||
841 | } | ||
842 | |||
843 | rc = snd_soc_register_platform(dev, &cygnus_soc_platform); | ||
844 | if (rc) { | ||
845 | dev_err(dev, "%s failed\n", __func__); | ||
846 | return rc; | ||
847 | } | ||
848 | |||
849 | return 0; | ||
850 | } | ||
851 | |||
852 | int cygnus_soc_platform_unregister(struct device *dev) | ||
853 | { | ||
854 | snd_soc_unregister_platform(dev); | ||
855 | |||
856 | return 0; | ||
857 | } | ||
858 | |||
859 | MODULE_LICENSE("GPL v2"); | ||
860 | MODULE_AUTHOR("Broadcom"); | ||
861 | MODULE_DESCRIPTION("Cygnus ASoC PCM module"); | ||