1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
2 | // |
3 | // This file is provided under a dual BSD/GPLv2 license. When using or |
4 | // redistributing this file, you may do so under either license. |
5 | // |
6 | // Copyright(c) 2023 Advanced Micro Devices, Inc. |
7 | // |
8 | // Authors: Syed Saba kareem <syed.sabakareem@amd.com> |
9 | /* |
10 | * Hardware interface for ACP7.0 block |
11 | */ |
12 | |
13 | #include <linux/platform_device.h> |
14 | #include <linux/module.h> |
15 | #include <linux/err.h> |
16 | #include <linux/io.h> |
17 | #include <sound/pcm_params.h> |
18 | #include <sound/soc.h> |
19 | #include <sound/soc-dai.h> |
20 | #include <linux/dma-mapping.h> |
21 | #include <linux/pm_runtime.h> |
22 | #include <linux/pci.h> |
23 | #include "amd.h" |
24 | #include "acp-mach.h" |
25 | |
26 | #define DRV_NAME "acp_asoc_acp70" |
27 | |
28 | static struct acp_resource rsrc = { |
29 | .offset = 0, |
30 | .no_of_ctrls = 2, |
31 | .irqp_used = 1, |
32 | .soc_mclk = true, |
33 | .irq_reg_offset = 0x1a00, |
34 | .i2s_pin_cfg_offset = 0x1440, |
35 | .i2s_mode = 0x0a, |
36 | .scratch_reg_offset = 0x12800, |
37 | .sram_pte_offset = 0x03802800, |
38 | }; |
39 | |
40 | static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_acp_machines[] = { |
41 | { |
42 | .id = "AMDI0029" , |
43 | .drv_name = "acp70-acp" , |
44 | }, |
45 | {}, |
46 | }; |
47 | |
48 | static struct snd_soc_dai_driver acp70_dai[] = { |
49 | { |
50 | .name = "acp-i2s-sp" , |
51 | .id = I2S_SP_INSTANCE, |
52 | .playback = { |
53 | .stream_name = "I2S SP Playback" , |
54 | .rates = SNDRV_PCM_RATE_8000_96000, |
55 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | |
56 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
57 | .channels_min = 2, |
58 | .channels_max = 8, |
59 | .rate_min = 8000, |
60 | .rate_max = 96000, |
61 | }, |
62 | .capture = { |
63 | .stream_name = "I2S SP Capture" , |
64 | .rates = SNDRV_PCM_RATE_8000_48000, |
65 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | |
66 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
67 | .channels_min = 2, |
68 | .channels_max = 2, |
69 | .rate_min = 8000, |
70 | .rate_max = 48000, |
71 | }, |
72 | .ops = &asoc_acp_cpu_dai_ops, |
73 | }, |
74 | { |
75 | .name = "acp-i2s-bt" , |
76 | .id = I2S_BT_INSTANCE, |
77 | .playback = { |
78 | .stream_name = "I2S BT Playback" , |
79 | .rates = SNDRV_PCM_RATE_8000_96000, |
80 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | |
81 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
82 | .channels_min = 2, |
83 | .channels_max = 8, |
84 | .rate_min = 8000, |
85 | .rate_max = 96000, |
86 | }, |
87 | .capture = { |
88 | .stream_name = "I2S BT Capture" , |
89 | .rates = SNDRV_PCM_RATE_8000_48000, |
90 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | |
91 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
92 | .channels_min = 2, |
93 | .channels_max = 2, |
94 | .rate_min = 8000, |
95 | .rate_max = 48000, |
96 | }, |
97 | .ops = &asoc_acp_cpu_dai_ops, |
98 | }, |
99 | { |
100 | .name = "acp-i2s-hs" , |
101 | .id = I2S_HS_INSTANCE, |
102 | .playback = { |
103 | .stream_name = "I2S HS Playback" , |
104 | .rates = SNDRV_PCM_RATE_8000_96000, |
105 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | |
106 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
107 | .channels_min = 2, |
108 | .channels_max = 8, |
109 | .rate_min = 8000, |
110 | .rate_max = 96000, |
111 | }, |
112 | .capture = { |
113 | .stream_name = "I2S HS Capture" , |
114 | .rates = SNDRV_PCM_RATE_8000_48000, |
115 | .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | |
116 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, |
117 | .channels_min = 2, |
118 | .channels_max = 8, |
119 | .rate_min = 8000, |
120 | .rate_max = 48000, |
121 | }, |
122 | .ops = &asoc_acp_cpu_dai_ops, |
123 | }, |
124 | { |
125 | .name = "acp-pdm-dmic" , |
126 | .id = DMIC_INSTANCE, |
127 | .capture = { |
128 | .rates = SNDRV_PCM_RATE_8000_48000, |
129 | .formats = SNDRV_PCM_FMTBIT_S32_LE, |
130 | .channels_min = 2, |
131 | .channels_max = 2, |
132 | .rate_min = 8000, |
133 | .rate_max = 48000, |
134 | }, |
135 | .ops = &acp_dmic_dai_ops, |
136 | }, |
137 | }; |
138 | |
139 | static int acp_acp70_audio_probe(struct platform_device *pdev) |
140 | { |
141 | struct device *dev = &pdev->dev; |
142 | struct acp_chip_info *chip; |
143 | struct acp_dev_data *adata; |
144 | struct resource *res; |
145 | |
146 | chip = dev_get_platdata(dev: &pdev->dev); |
147 | if (!chip || !chip->base) { |
148 | dev_err(&pdev->dev, "ACP chip data is NULL\n" ); |
149 | return -ENODEV; |
150 | } |
151 | |
152 | if (chip->acp_rev != ACP70_DEV) { |
153 | dev_err(&pdev->dev, "Un-supported ACP Revision %d\n" , chip->acp_rev); |
154 | return -ENODEV; |
155 | } |
156 | |
157 | adata = devm_kzalloc(dev, size: sizeof(struct acp_dev_data), GFP_KERNEL); |
158 | if (!adata) |
159 | return -ENOMEM; |
160 | |
161 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acp_mem" ); |
162 | if (!res) { |
163 | dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n" ); |
164 | return -ENODEV; |
165 | } |
166 | |
167 | adata->acp_base = devm_ioremap(dev: &pdev->dev, offset: res->start, size: resource_size(res)); |
168 | if (!adata->acp_base) |
169 | return -ENOMEM; |
170 | |
171 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "acp_dai_irq" ); |
172 | if (!res) { |
173 | dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n" ); |
174 | return -ENODEV; |
175 | } |
176 | |
177 | adata->i2s_irq = res->start; |
178 | adata->dev = dev; |
179 | adata->dai_driver = acp70_dai; |
180 | adata->num_dai = ARRAY_SIZE(acp70_dai); |
181 | adata->rsrc = &rsrc; |
182 | adata->machines = snd_soc_acpi_amd_acp70_acp_machines; |
183 | adata->platform = ACP70; |
184 | adata->flag = chip->flag; |
185 | acp_machine_select(adata); |
186 | |
187 | dev_set_drvdata(dev, data: adata); |
188 | acp_enable_interrupts(adata); |
189 | acp_platform_register(dev); |
190 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, ACP_SUSPEND_DELAY_MS); |
191 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
192 | pm_runtime_mark_last_busy(dev: &pdev->dev); |
193 | pm_runtime_set_active(dev: &pdev->dev); |
194 | pm_runtime_enable(dev: &pdev->dev); |
195 | return 0; |
196 | } |
197 | |
198 | static void acp_acp70_audio_remove(struct platform_device *pdev) |
199 | { |
200 | struct device *dev = &pdev->dev; |
201 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
202 | |
203 | acp_disable_interrupts(adata); |
204 | acp_platform_unregister(dev); |
205 | pm_runtime_disable(dev: &pdev->dev); |
206 | } |
207 | |
208 | static int __maybe_unused acp70_pcm_resume(struct device *dev) |
209 | { |
210 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
211 | struct acp_stream *stream; |
212 | struct snd_pcm_substream *substream; |
213 | snd_pcm_uframes_t buf_in_frames; |
214 | u64 buf_size; |
215 | |
216 | spin_lock(lock: &adata->acp_lock); |
217 | list_for_each_entry(stream, &adata->stream_list, list) { |
218 | if (stream) { |
219 | substream = stream->substream; |
220 | if (substream && substream->runtime) { |
221 | buf_in_frames = (substream->runtime->buffer_size); |
222 | buf_size = frames_to_bytes(runtime: substream->runtime, size: buf_in_frames); |
223 | config_pte_for_stream(adata, stream); |
224 | config_acp_dma(adata, stream, size: buf_size); |
225 | if (stream->dai_id) |
226 | restore_acp_i2s_params(substream, adata, stream); |
227 | else |
228 | restore_acp_pdm_params(substream, adata); |
229 | } |
230 | } |
231 | } |
232 | spin_unlock(lock: &adata->acp_lock); |
233 | return 0; |
234 | } |
235 | |
236 | static const struct dev_pm_ops acp70_dma_pm_ops = { |
237 | SET_SYSTEM_SLEEP_PM_OPS(NULL, acp70_pcm_resume) |
238 | }; |
239 | |
240 | static struct platform_driver acp70_driver = { |
241 | .probe = acp_acp70_audio_probe, |
242 | .remove_new = acp_acp70_audio_remove, |
243 | .driver = { |
244 | .name = "acp_asoc_acp70" , |
245 | .pm = &acp70_dma_pm_ops, |
246 | }, |
247 | }; |
248 | |
249 | module_platform_driver(acp70_driver); |
250 | |
251 | MODULE_DESCRIPTION("AMD ACP ACP70 Driver" ); |
252 | MODULE_IMPORT_NS(SND_SOC_ACP_COMMON); |
253 | MODULE_LICENSE("Dual BSD/GPL" ); |
254 | MODULE_ALIAS("platform:" DRV_NAME); |
255 | |