1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * tegra30_i2s.c - Tegra30 I2S driver |
4 | * |
5 | * Author: Stephen Warren <swarren@nvidia.com> |
6 | * Copyright (c) 2010-2012, NVIDIA CORPORATION. All rights reserved. |
7 | * |
8 | * Based on code copyright/by: |
9 | * |
10 | * Copyright (c) 2009-2010, NVIDIA Corporation. |
11 | * Scott Peterson <speterson@nvidia.com> |
12 | * |
13 | * Copyright (C) 2010 Google, Inc. |
14 | * Iliyan Malchev <malchev@google.com> |
15 | */ |
16 | |
17 | #include <linux/clk.h> |
18 | #include <linux/device.h> |
19 | #include <linux/io.h> |
20 | #include <linux/module.h> |
21 | #include <linux/of.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/pm_runtime.h> |
24 | #include <linux/regmap.h> |
25 | #include <linux/reset.h> |
26 | #include <linux/slab.h> |
27 | #include <sound/core.h> |
28 | #include <sound/pcm.h> |
29 | #include <sound/pcm_params.h> |
30 | #include <sound/soc.h> |
31 | #include <sound/dmaengine_pcm.h> |
32 | |
33 | #include "tegra30_ahub.h" |
34 | #include "tegra30_i2s.h" |
35 | |
36 | #define DRV_NAME "tegra30-i2s" |
37 | |
38 | static __maybe_unused int tegra30_i2s_runtime_suspend(struct device *dev) |
39 | { |
40 | struct tegra30_i2s *i2s = dev_get_drvdata(dev); |
41 | |
42 | regcache_cache_only(map: i2s->regmap, enable: true); |
43 | |
44 | clk_disable_unprepare(clk: i2s->clk_i2s); |
45 | |
46 | return 0; |
47 | } |
48 | |
49 | static __maybe_unused int tegra30_i2s_runtime_resume(struct device *dev) |
50 | { |
51 | struct tegra30_i2s *i2s = dev_get_drvdata(dev); |
52 | int ret; |
53 | |
54 | ret = clk_prepare_enable(clk: i2s->clk_i2s); |
55 | if (ret) { |
56 | dev_err(dev, "clk_enable failed: %d\n" , ret); |
57 | return ret; |
58 | } |
59 | |
60 | regcache_cache_only(map: i2s->regmap, enable: false); |
61 | regcache_mark_dirty(map: i2s->regmap); |
62 | |
63 | ret = regcache_sync(map: i2s->regmap); |
64 | if (ret) |
65 | goto disable_clocks; |
66 | |
67 | return 0; |
68 | |
69 | disable_clocks: |
70 | clk_disable_unprepare(clk: i2s->clk_i2s); |
71 | |
72 | return ret; |
73 | } |
74 | |
75 | static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, |
76 | unsigned int fmt) |
77 | { |
78 | struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
79 | unsigned int mask = 0, val = 0; |
80 | |
81 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
82 | case SND_SOC_DAIFMT_NB_NF: |
83 | break; |
84 | default: |
85 | return -EINVAL; |
86 | } |
87 | |
88 | mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE; |
89 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
90 | case SND_SOC_DAIFMT_BP_FP: |
91 | val |= TEGRA30_I2S_CTRL_MASTER_ENABLE; |
92 | break; |
93 | case SND_SOC_DAIFMT_BC_FC: |
94 | break; |
95 | default: |
96 | return -EINVAL; |
97 | } |
98 | |
99 | mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK | |
100 | TEGRA30_I2S_CTRL_LRCK_MASK; |
101 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
102 | case SND_SOC_DAIFMT_DSP_A: |
103 | val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; |
104 | val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; |
105 | break; |
106 | case SND_SOC_DAIFMT_DSP_B: |
107 | val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; |
108 | val |= TEGRA30_I2S_CTRL_LRCK_R_LOW; |
109 | break; |
110 | case SND_SOC_DAIFMT_I2S: |
111 | val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; |
112 | val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; |
113 | break; |
114 | case SND_SOC_DAIFMT_RIGHT_J: |
115 | val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; |
116 | val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; |
117 | break; |
118 | case SND_SOC_DAIFMT_LEFT_J: |
119 | val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; |
120 | val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; |
121 | break; |
122 | default: |
123 | return -EINVAL; |
124 | } |
125 | |
126 | pm_runtime_get_sync(dev: dai->dev); |
127 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CTRL, mask, val); |
128 | pm_runtime_put(dev: dai->dev); |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream, |
134 | struct snd_pcm_hw_params *params, |
135 | struct snd_soc_dai *dai) |
136 | { |
137 | struct device *dev = dai->dev; |
138 | struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
139 | unsigned int mask, val, reg; |
140 | int ret, sample_size, srate, i2sclock, bitcnt; |
141 | struct tegra30_ahub_cif_conf cif_conf; |
142 | |
143 | if (params_channels(p: params) != 2) |
144 | return -EINVAL; |
145 | |
146 | mask = TEGRA30_I2S_CTRL_BIT_SIZE_MASK; |
147 | switch (params_format(p: params)) { |
148 | case SNDRV_PCM_FORMAT_S16_LE: |
149 | val = TEGRA30_I2S_CTRL_BIT_SIZE_16; |
150 | sample_size = 16; |
151 | break; |
152 | default: |
153 | return -EINVAL; |
154 | } |
155 | |
156 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CTRL, mask, val); |
157 | |
158 | srate = params_rate(p: params); |
159 | |
160 | /* Final "* 2" required by Tegra hardware */ |
161 | i2sclock = srate * params_channels(p: params) * sample_size * 2; |
162 | |
163 | bitcnt = (i2sclock / (2 * srate)) - 1; |
164 | if (bitcnt < 0 || bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) |
165 | return -EINVAL; |
166 | |
167 | ret = clk_set_rate(clk: i2s->clk_i2s, rate: i2sclock); |
168 | if (ret) { |
169 | dev_err(dev, "Can't set I2S clock rate: %d\n" , ret); |
170 | return ret; |
171 | } |
172 | |
173 | val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; |
174 | |
175 | if (i2sclock % (2 * srate)) |
176 | val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE; |
177 | |
178 | regmap_write(map: i2s->regmap, TEGRA30_I2S_TIMING, val); |
179 | |
180 | cif_conf.threshold = 0; |
181 | cif_conf.audio_channels = 2; |
182 | cif_conf.client_channels = 2; |
183 | cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; |
184 | cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; |
185 | cif_conf.expand = 0; |
186 | cif_conf.stereo_conv = 0; |
187 | cif_conf.replicate = 0; |
188 | cif_conf.truncate = 0; |
189 | cif_conf.mono_conv = 0; |
190 | |
191 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
192 | cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; |
193 | reg = TEGRA30_I2S_CIF_RX_CTRL; |
194 | } else { |
195 | cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; |
196 | reg = TEGRA30_I2S_CIF_TX_CTRL; |
197 | } |
198 | |
199 | i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf); |
200 | |
201 | val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) | |
202 | (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT); |
203 | regmap_write(map: i2s->regmap, TEGRA30_I2S_OFFSET, val); |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s) |
209 | { |
210 | tegra30_ahub_enable_tx_fifo(txcif: i2s->playback_fifo_cif); |
211 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CTRL, |
212 | TEGRA30_I2S_CTRL_XFER_EN_TX, |
213 | TEGRA30_I2S_CTRL_XFER_EN_TX); |
214 | } |
215 | |
216 | static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s) |
217 | { |
218 | tegra30_ahub_disable_tx_fifo(txcif: i2s->playback_fifo_cif); |
219 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CTRL, |
220 | TEGRA30_I2S_CTRL_XFER_EN_TX, val: 0); |
221 | } |
222 | |
223 | static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s) |
224 | { |
225 | tegra30_ahub_enable_rx_fifo(rxcif: i2s->capture_fifo_cif); |
226 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CTRL, |
227 | TEGRA30_I2S_CTRL_XFER_EN_RX, |
228 | TEGRA30_I2S_CTRL_XFER_EN_RX); |
229 | } |
230 | |
231 | static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s) |
232 | { |
233 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CTRL, |
234 | TEGRA30_I2S_CTRL_XFER_EN_RX, val: 0); |
235 | tegra30_ahub_disable_rx_fifo(rxcif: i2s->capture_fifo_cif); |
236 | } |
237 | |
238 | static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd, |
239 | struct snd_soc_dai *dai) |
240 | { |
241 | struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
242 | |
243 | switch (cmd) { |
244 | case SNDRV_PCM_TRIGGER_START: |
245 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
246 | case SNDRV_PCM_TRIGGER_RESUME: |
247 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
248 | tegra30_i2s_start_playback(i2s); |
249 | else |
250 | tegra30_i2s_start_capture(i2s); |
251 | break; |
252 | case SNDRV_PCM_TRIGGER_STOP: |
253 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
254 | case SNDRV_PCM_TRIGGER_SUSPEND: |
255 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
256 | tegra30_i2s_stop_playback(i2s); |
257 | else |
258 | tegra30_i2s_stop_capture(i2s); |
259 | break; |
260 | default: |
261 | return -EINVAL; |
262 | } |
263 | |
264 | return 0; |
265 | } |
266 | |
267 | static int tegra30_i2s_set_tdm(struct snd_soc_dai *dai, |
268 | unsigned int tx_mask, unsigned int rx_mask, |
269 | int slots, int slot_width) |
270 | { |
271 | struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
272 | unsigned int mask, val; |
273 | |
274 | dev_dbg(dai->dev, "%s: txmask=0x%08x rxmask=0x%08x slots=%d width=%d\n" , |
275 | __func__, tx_mask, rx_mask, slots, slot_width); |
276 | |
277 | mask = TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK | |
278 | TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK | |
279 | TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK; |
280 | |
281 | val = (tx_mask << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) | |
282 | (rx_mask << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) | |
283 | ((slots - 1) << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT); |
284 | |
285 | pm_runtime_get_sync(dev: dai->dev); |
286 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_SLOT_CTRL, mask, val); |
287 | /* set the fsync width to minimum of 1 clock width */ |
288 | regmap_update_bits(map: i2s->regmap, TEGRA30_I2S_CH_CTRL, |
289 | TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK, val: 0x0); |
290 | pm_runtime_put(dev: dai->dev); |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static int tegra30_i2s_probe(struct snd_soc_dai *dai) |
296 | { |
297 | struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); |
298 | |
299 | snd_soc_dai_init_dma_data(dai, playback: &i2s->playback_dma_data, |
300 | capture: &i2s->capture_dma_data); |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static const struct snd_soc_dai_ops tegra30_i2s_dai_ops = { |
306 | .probe = tegra30_i2s_probe, |
307 | .set_fmt = tegra30_i2s_set_fmt, |
308 | .hw_params = tegra30_i2s_hw_params, |
309 | .trigger = tegra30_i2s_trigger, |
310 | .set_tdm_slot = tegra30_i2s_set_tdm, |
311 | }; |
312 | |
313 | static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { |
314 | .playback = { |
315 | .stream_name = "Playback" , |
316 | .channels_min = 2, |
317 | .channels_max = 2, |
318 | .rates = SNDRV_PCM_RATE_8000_96000, |
319 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
320 | }, |
321 | .capture = { |
322 | .stream_name = "Capture" , |
323 | .channels_min = 2, |
324 | .channels_max = 2, |
325 | .rates = SNDRV_PCM_RATE_8000_96000, |
326 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
327 | }, |
328 | .ops = &tegra30_i2s_dai_ops, |
329 | .symmetric_rate = 1, |
330 | }; |
331 | |
332 | static const struct snd_soc_component_driver tegra30_i2s_component = { |
333 | .name = DRV_NAME, |
334 | .legacy_dai_naming = 1, |
335 | }; |
336 | |
337 | static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg) |
338 | { |
339 | switch (reg) { |
340 | case TEGRA30_I2S_CTRL: |
341 | case TEGRA30_I2S_TIMING: |
342 | case TEGRA30_I2S_OFFSET: |
343 | case TEGRA30_I2S_CH_CTRL: |
344 | case TEGRA30_I2S_SLOT_CTRL: |
345 | case TEGRA30_I2S_CIF_RX_CTRL: |
346 | case TEGRA30_I2S_CIF_TX_CTRL: |
347 | case TEGRA30_I2S_FLOWCTL: |
348 | case TEGRA30_I2S_TX_STEP: |
349 | case TEGRA30_I2S_FLOW_STATUS: |
350 | case TEGRA30_I2S_FLOW_TOTAL: |
351 | case TEGRA30_I2S_FLOW_OVER: |
352 | case TEGRA30_I2S_FLOW_UNDER: |
353 | case TEGRA30_I2S_LCOEF_1_4_0: |
354 | case TEGRA30_I2S_LCOEF_1_4_1: |
355 | case TEGRA30_I2S_LCOEF_1_4_2: |
356 | case TEGRA30_I2S_LCOEF_1_4_3: |
357 | case TEGRA30_I2S_LCOEF_1_4_4: |
358 | case TEGRA30_I2S_LCOEF_1_4_5: |
359 | case TEGRA30_I2S_LCOEF_2_4_0: |
360 | case TEGRA30_I2S_LCOEF_2_4_1: |
361 | case TEGRA30_I2S_LCOEF_2_4_2: |
362 | return true; |
363 | default: |
364 | return false; |
365 | } |
366 | } |
367 | |
368 | static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg) |
369 | { |
370 | switch (reg) { |
371 | case TEGRA30_I2S_FLOW_STATUS: |
372 | case TEGRA30_I2S_FLOW_TOTAL: |
373 | case TEGRA30_I2S_FLOW_OVER: |
374 | case TEGRA30_I2S_FLOW_UNDER: |
375 | return true; |
376 | default: |
377 | return false; |
378 | } |
379 | } |
380 | |
381 | static const struct regmap_config tegra30_i2s_regmap_config = { |
382 | .reg_bits = 32, |
383 | .reg_stride = 4, |
384 | .val_bits = 32, |
385 | .max_register = TEGRA30_I2S_LCOEF_2_4_2, |
386 | .writeable_reg = tegra30_i2s_wr_rd_reg, |
387 | .readable_reg = tegra30_i2s_wr_rd_reg, |
388 | .volatile_reg = tegra30_i2s_volatile_reg, |
389 | .cache_type = REGCACHE_FLAT, |
390 | }; |
391 | |
392 | static const struct tegra30_i2s_soc_data tegra30_i2s_config = { |
393 | .set_audio_cif = tegra30_ahub_set_cif, |
394 | }; |
395 | |
396 | static const struct tegra30_i2s_soc_data tegra124_i2s_config = { |
397 | .set_audio_cif = tegra124_ahub_set_cif, |
398 | }; |
399 | |
400 | static const struct of_device_id tegra30_i2s_of_match[] = { |
401 | { .compatible = "nvidia,tegra124-i2s" , .data = &tegra124_i2s_config }, |
402 | { .compatible = "nvidia,tegra30-i2s" , .data = &tegra30_i2s_config }, |
403 | {}, |
404 | }; |
405 | |
406 | static int tegra30_i2s_platform_probe(struct platform_device *pdev) |
407 | { |
408 | struct tegra30_i2s *i2s; |
409 | const struct tegra30_i2s_soc_data *soc_data; |
410 | u32 cif_ids[2]; |
411 | void __iomem *regs; |
412 | int ret; |
413 | |
414 | i2s = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct tegra30_i2s), GFP_KERNEL); |
415 | if (!i2s) { |
416 | ret = -ENOMEM; |
417 | goto err; |
418 | } |
419 | dev_set_drvdata(dev: &pdev->dev, data: i2s); |
420 | |
421 | soc_data = of_device_get_match_data(dev: &pdev->dev); |
422 | if (!soc_data) { |
423 | dev_err(&pdev->dev, "Error: No device match found\n" ); |
424 | ret = -ENODEV; |
425 | goto err; |
426 | } |
427 | i2s->soc_data = soc_data; |
428 | |
429 | i2s->dai = tegra30_i2s_dai_template; |
430 | i2s->dai.name = dev_name(dev: &pdev->dev); |
431 | |
432 | ret = of_property_read_u32_array(np: pdev->dev.of_node, |
433 | propname: "nvidia,ahub-cif-ids" , out_values: cif_ids, |
434 | ARRAY_SIZE(cif_ids)); |
435 | if (ret < 0) |
436 | goto err; |
437 | |
438 | i2s->playback_i2s_cif = cif_ids[0]; |
439 | i2s->capture_i2s_cif = cif_ids[1]; |
440 | |
441 | i2s->clk_i2s = devm_clk_get(dev: &pdev->dev, NULL); |
442 | if (IS_ERR(ptr: i2s->clk_i2s)) { |
443 | dev_err(&pdev->dev, "Can't retrieve i2s clock\n" ); |
444 | ret = PTR_ERR(ptr: i2s->clk_i2s); |
445 | goto err; |
446 | } |
447 | |
448 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
449 | if (IS_ERR(ptr: regs)) { |
450 | ret = PTR_ERR(ptr: regs); |
451 | goto err; |
452 | } |
453 | |
454 | i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, |
455 | &tegra30_i2s_regmap_config); |
456 | if (IS_ERR(ptr: i2s->regmap)) { |
457 | dev_err(&pdev->dev, "regmap init failed\n" ); |
458 | ret = PTR_ERR(ptr: i2s->regmap); |
459 | goto err; |
460 | } |
461 | regcache_cache_only(map: i2s->regmap, enable: true); |
462 | |
463 | pm_runtime_enable(dev: &pdev->dev); |
464 | |
465 | i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
466 | i2s->playback_dma_data.maxburst = 4; |
467 | ret = tegra30_ahub_allocate_tx_fifo(txcif: &i2s->playback_fifo_cif, |
468 | dmachan: i2s->playback_dma_chan, |
469 | dmachan_len: sizeof(i2s->playback_dma_chan), |
470 | fiforeg: &i2s->playback_dma_data.addr); |
471 | if (ret) { |
472 | dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n" , ret); |
473 | goto err_pm_disable; |
474 | } |
475 | ret = tegra30_ahub_set_rx_cif_source(rxcif: i2s->playback_i2s_cif, |
476 | txcif: i2s->playback_fifo_cif); |
477 | if (ret) { |
478 | dev_err(&pdev->dev, "Could not route TX FIFO: %d\n" , ret); |
479 | goto err_free_tx_fifo; |
480 | } |
481 | |
482 | i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
483 | i2s->capture_dma_data.maxburst = 4; |
484 | ret = tegra30_ahub_allocate_rx_fifo(rxcif: &i2s->capture_fifo_cif, |
485 | dmachan: i2s->capture_dma_chan, |
486 | dmachan_len: sizeof(i2s->capture_dma_chan), |
487 | fiforeg: &i2s->capture_dma_data.addr); |
488 | if (ret) { |
489 | dev_err(&pdev->dev, "Could not alloc RX FIFO: %d\n" , ret); |
490 | goto err_unroute_tx_fifo; |
491 | } |
492 | ret = tegra30_ahub_set_rx_cif_source(rxcif: i2s->capture_fifo_cif, |
493 | txcif: i2s->capture_i2s_cif); |
494 | if (ret) { |
495 | dev_err(&pdev->dev, "Could not route TX FIFO: %d\n" , ret); |
496 | goto err_free_rx_fifo; |
497 | } |
498 | |
499 | ret = snd_soc_register_component(dev: &pdev->dev, component_driver: &tegra30_i2s_component, |
500 | dai_drv: &i2s->dai, num_dai: 1); |
501 | if (ret) { |
502 | dev_err(&pdev->dev, "Could not register DAI: %d\n" , ret); |
503 | ret = -ENOMEM; |
504 | goto err_unroute_rx_fifo; |
505 | } |
506 | |
507 | ret = tegra_pcm_platform_register_with_chan_names(dev: &pdev->dev, |
508 | config: &i2s->dma_config, txdmachan: i2s->playback_dma_chan, |
509 | rxdmachan: i2s->capture_dma_chan); |
510 | if (ret) { |
511 | dev_err(&pdev->dev, "Could not register PCM: %d\n" , ret); |
512 | goto err_unregister_component; |
513 | } |
514 | |
515 | return 0; |
516 | |
517 | err_unregister_component: |
518 | snd_soc_unregister_component(dev: &pdev->dev); |
519 | err_unroute_rx_fifo: |
520 | tegra30_ahub_unset_rx_cif_source(rxcif: i2s->capture_fifo_cif); |
521 | err_free_rx_fifo: |
522 | tegra30_ahub_free_rx_fifo(rxcif: i2s->capture_fifo_cif); |
523 | err_unroute_tx_fifo: |
524 | tegra30_ahub_unset_rx_cif_source(rxcif: i2s->playback_i2s_cif); |
525 | err_free_tx_fifo: |
526 | tegra30_ahub_free_tx_fifo(txcif: i2s->playback_fifo_cif); |
527 | err_pm_disable: |
528 | pm_runtime_disable(dev: &pdev->dev); |
529 | err: |
530 | return ret; |
531 | } |
532 | |
533 | static void tegra30_i2s_platform_remove(struct platform_device *pdev) |
534 | { |
535 | struct tegra30_i2s *i2s = dev_get_drvdata(dev: &pdev->dev); |
536 | |
537 | tegra_pcm_platform_unregister(dev: &pdev->dev); |
538 | snd_soc_unregister_component(dev: &pdev->dev); |
539 | |
540 | tegra30_ahub_unset_rx_cif_source(rxcif: i2s->capture_fifo_cif); |
541 | tegra30_ahub_free_rx_fifo(rxcif: i2s->capture_fifo_cif); |
542 | |
543 | tegra30_ahub_unset_rx_cif_source(rxcif: i2s->playback_i2s_cif); |
544 | tegra30_ahub_free_tx_fifo(txcif: i2s->playback_fifo_cif); |
545 | |
546 | pm_runtime_disable(dev: &pdev->dev); |
547 | } |
548 | |
549 | static const struct dev_pm_ops tegra30_i2s_pm_ops = { |
550 | SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, |
551 | tegra30_i2s_runtime_resume, NULL) |
552 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
553 | pm_runtime_force_resume) |
554 | }; |
555 | |
556 | static struct platform_driver tegra30_i2s_driver = { |
557 | .driver = { |
558 | .name = DRV_NAME, |
559 | .of_match_table = tegra30_i2s_of_match, |
560 | .pm = &tegra30_i2s_pm_ops, |
561 | }, |
562 | .probe = tegra30_i2s_platform_probe, |
563 | .remove_new = tegra30_i2s_platform_remove, |
564 | }; |
565 | module_platform_driver(tegra30_i2s_driver); |
566 | |
567 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>" ); |
568 | MODULE_DESCRIPTION("Tegra30 I2S ASoC driver" ); |
569 | MODULE_LICENSE("GPL" ); |
570 | MODULE_ALIAS("platform:" DRV_NAME); |
571 | MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match); |
572 | |