1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * AMD Yellow Carp ACP PCI Driver |
4 | * |
5 | * Copyright 2021 Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <linux/pci.h> |
9 | #include <linux/module.h> |
10 | #include <linux/io.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/interrupt.h> |
14 | #include <sound/pcm_params.h> |
15 | #include <linux/pm_runtime.h> |
16 | |
17 | #include "acp6x.h" |
18 | |
19 | struct acp6x_dev_data { |
20 | void __iomem *acp6x_base; |
21 | struct resource *res; |
22 | bool acp6x_audio_mode; |
23 | struct platform_device *pdev[ACP6x_DEVS]; |
24 | }; |
25 | |
26 | static int acp6x_power_on(void __iomem *acp_base) |
27 | { |
28 | u32 val; |
29 | int timeout; |
30 | |
31 | val = acp6x_readl(base_addr: acp_base + ACP_PGFSM_STATUS); |
32 | |
33 | if (!val) |
34 | return val; |
35 | |
36 | if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS) |
37 | acp6x_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, base_addr: acp_base + ACP_PGFSM_CONTROL); |
38 | timeout = 0; |
39 | while (++timeout < 500) { |
40 | val = acp6x_readl(base_addr: acp_base + ACP_PGFSM_STATUS); |
41 | if (!val) |
42 | return 0; |
43 | udelay(1); |
44 | } |
45 | return -ETIMEDOUT; |
46 | } |
47 | |
48 | static int acp6x_reset(void __iomem *acp_base) |
49 | { |
50 | u32 val; |
51 | int timeout; |
52 | |
53 | acp6x_writel(val: 1, base_addr: acp_base + ACP_SOFT_RESET); |
54 | timeout = 0; |
55 | while (++timeout < 500) { |
56 | val = acp6x_readl(base_addr: acp_base + ACP_SOFT_RESET); |
57 | if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) |
58 | break; |
59 | cpu_relax(); |
60 | } |
61 | acp6x_writel(val: 0, base_addr: acp_base + ACP_SOFT_RESET); |
62 | timeout = 0; |
63 | while (++timeout < 500) { |
64 | val = acp6x_readl(base_addr: acp_base + ACP_SOFT_RESET); |
65 | if (!val) |
66 | return 0; |
67 | cpu_relax(); |
68 | } |
69 | return -ETIMEDOUT; |
70 | } |
71 | |
72 | static void acp6x_enable_interrupts(void __iomem *acp_base) |
73 | { |
74 | acp6x_writel(val: 0x01, base_addr: acp_base + ACP_EXTERNAL_INTR_ENB); |
75 | } |
76 | |
77 | static void acp6x_disable_interrupts(void __iomem *acp_base) |
78 | { |
79 | acp6x_writel(ACP_EXT_INTR_STAT_CLEAR_MASK, base_addr: acp_base + |
80 | ACP_EXTERNAL_INTR_STAT); |
81 | acp6x_writel(val: 0x00, base_addr: acp_base + ACP_EXTERNAL_INTR_CNTL); |
82 | acp6x_writel(val: 0x00, base_addr: acp_base + ACP_EXTERNAL_INTR_ENB); |
83 | } |
84 | |
85 | static int acp6x_init(void __iomem *acp_base) |
86 | { |
87 | int ret; |
88 | |
89 | /* power on */ |
90 | ret = acp6x_power_on(acp_base); |
91 | if (ret) { |
92 | pr_err("ACP power on failed\n" ); |
93 | return ret; |
94 | } |
95 | acp6x_writel(val: 0x01, base_addr: acp_base + ACP_CONTROL); |
96 | /* Reset */ |
97 | ret = acp6x_reset(acp_base); |
98 | if (ret) { |
99 | pr_err("ACP reset failed\n" ); |
100 | return ret; |
101 | } |
102 | acp6x_writel(val: 0x03, base_addr: acp_base + ACP_CLKMUX_SEL); |
103 | acp6x_enable_interrupts(acp_base); |
104 | return 0; |
105 | } |
106 | |
107 | static int acp6x_deinit(void __iomem *acp_base) |
108 | { |
109 | int ret; |
110 | |
111 | acp6x_disable_interrupts(acp_base); |
112 | /* Reset */ |
113 | ret = acp6x_reset(acp_base); |
114 | if (ret) { |
115 | pr_err("ACP reset failed\n" ); |
116 | return ret; |
117 | } |
118 | acp6x_writel(val: 0x00, base_addr: acp_base + ACP_CLKMUX_SEL); |
119 | acp6x_writel(val: 0x00, base_addr: acp_base + ACP_CONTROL); |
120 | return 0; |
121 | } |
122 | |
123 | static irqreturn_t acp6x_irq_handler(int irq, void *dev_id) |
124 | { |
125 | struct acp6x_dev_data *adata; |
126 | struct pdm_dev_data *yc_pdm_data; |
127 | u32 val; |
128 | |
129 | adata = dev_id; |
130 | if (!adata) |
131 | return IRQ_NONE; |
132 | |
133 | val = acp6x_readl(base_addr: adata->acp6x_base + ACP_EXTERNAL_INTR_STAT); |
134 | if (val & BIT(PDM_DMA_STAT)) { |
135 | yc_pdm_data = dev_get_drvdata(dev: &adata->pdev[0]->dev); |
136 | acp6x_writel(BIT(PDM_DMA_STAT), base_addr: adata->acp6x_base + ACP_EXTERNAL_INTR_STAT); |
137 | if (yc_pdm_data->capture_stream) |
138 | snd_pcm_period_elapsed(substream: yc_pdm_data->capture_stream); |
139 | return IRQ_HANDLED; |
140 | } |
141 | return IRQ_NONE; |
142 | } |
143 | |
144 | static int snd_acp6x_probe(struct pci_dev *pci, |
145 | const struct pci_device_id *pci_id) |
146 | { |
147 | struct acp6x_dev_data *adata; |
148 | struct platform_device_info pdevinfo[ACP6x_DEVS]; |
149 | int index = 0; |
150 | int val = 0x00; |
151 | u32 addr; |
152 | unsigned int irqflags, flag; |
153 | int ret; |
154 | |
155 | irqflags = IRQF_SHARED; |
156 | |
157 | /* Return if acp config flag is defined */ |
158 | flag = snd_amd_acp_find_config(pci); |
159 | if (flag) |
160 | return -ENODEV; |
161 | |
162 | /* Yellow Carp device check */ |
163 | switch (pci->revision) { |
164 | case 0x60: |
165 | case 0x6f: |
166 | break; |
167 | default: |
168 | dev_dbg(&pci->dev, "acp6x pci device not found\n" ); |
169 | return -ENODEV; |
170 | } |
171 | if (pci_enable_device(dev: pci)) { |
172 | dev_err(&pci->dev, "pci_enable_device failed\n" ); |
173 | return -ENODEV; |
174 | } |
175 | |
176 | ret = pci_request_regions(pci, "AMD ACP3x audio" ); |
177 | if (ret < 0) { |
178 | dev_err(&pci->dev, "pci_request_regions failed\n" ); |
179 | goto disable_pci; |
180 | } |
181 | |
182 | adata = devm_kzalloc(dev: &pci->dev, size: sizeof(struct acp6x_dev_data), |
183 | GFP_KERNEL); |
184 | if (!adata) { |
185 | ret = -ENOMEM; |
186 | goto release_regions; |
187 | } |
188 | |
189 | addr = pci_resource_start(pci, 0); |
190 | adata->acp6x_base = devm_ioremap(dev: &pci->dev, offset: addr, |
191 | pci_resource_len(pci, 0)); |
192 | if (!adata->acp6x_base) { |
193 | ret = -ENOMEM; |
194 | goto release_regions; |
195 | } |
196 | pci_set_master(dev: pci); |
197 | pci_set_drvdata(pdev: pci, data: adata); |
198 | ret = acp6x_init(acp_base: adata->acp6x_base); |
199 | if (ret) |
200 | goto release_regions; |
201 | val = acp6x_readl(base_addr: adata->acp6x_base + ACP_PIN_CONFIG); |
202 | switch (val) { |
203 | case ACP_CONFIG_0: |
204 | case ACP_CONFIG_1: |
205 | case ACP_CONFIG_2: |
206 | case ACP_CONFIG_3: |
207 | case ACP_CONFIG_9: |
208 | case ACP_CONFIG_15: |
209 | dev_info(&pci->dev, "Audio Mode %d\n" , val); |
210 | break; |
211 | default: |
212 | adata->res = devm_kzalloc(dev: &pci->dev, |
213 | size: sizeof(struct resource), |
214 | GFP_KERNEL); |
215 | if (!adata->res) { |
216 | ret = -ENOMEM; |
217 | goto de_init; |
218 | } |
219 | |
220 | adata->res->name = "acp_iomem" ; |
221 | adata->res->flags = IORESOURCE_MEM; |
222 | adata->res->start = addr; |
223 | adata->res->end = addr + (ACP6x_REG_END - ACP6x_REG_START); |
224 | |
225 | adata->acp6x_audio_mode = ACP6x_PDM_MODE; |
226 | |
227 | memset(&pdevinfo, 0, sizeof(pdevinfo)); |
228 | pdevinfo[0].name = "acp_yc_pdm_dma" ; |
229 | pdevinfo[0].id = 0; |
230 | pdevinfo[0].parent = &pci->dev; |
231 | pdevinfo[0].num_res = 1; |
232 | pdevinfo[0].res = adata->res; |
233 | |
234 | pdevinfo[1].name = "dmic-codec" ; |
235 | pdevinfo[1].id = 0; |
236 | pdevinfo[1].parent = &pci->dev; |
237 | |
238 | pdevinfo[2].name = "acp_yc_mach" ; |
239 | pdevinfo[2].id = 0; |
240 | pdevinfo[2].parent = &pci->dev; |
241 | |
242 | for (index = 0; index < ACP6x_DEVS; index++) { |
243 | adata->pdev[index] = |
244 | platform_device_register_full(pdevinfo: &pdevinfo[index]); |
245 | if (IS_ERR(ptr: adata->pdev[index])) { |
246 | dev_err(&pci->dev, "cannot register %s device\n" , |
247 | pdevinfo[index].name); |
248 | ret = PTR_ERR(ptr: adata->pdev[index]); |
249 | goto unregister_devs; |
250 | } |
251 | } |
252 | break; |
253 | } |
254 | ret = devm_request_irq(dev: &pci->dev, irq: pci->irq, handler: acp6x_irq_handler, |
255 | irqflags, devname: "ACP_PCI_IRQ" , dev_id: adata); |
256 | if (ret) { |
257 | dev_err(&pci->dev, "ACP PCI IRQ request failed\n" ); |
258 | goto unregister_devs; |
259 | } |
260 | pm_runtime_set_autosuspend_delay(dev: &pci->dev, ACP_SUSPEND_DELAY_MS); |
261 | pm_runtime_use_autosuspend(dev: &pci->dev); |
262 | pm_runtime_put_noidle(dev: &pci->dev); |
263 | pm_runtime_allow(dev: &pci->dev); |
264 | |
265 | return 0; |
266 | unregister_devs: |
267 | for (--index; index >= 0; index--) |
268 | platform_device_unregister(adata->pdev[index]); |
269 | de_init: |
270 | if (acp6x_deinit(acp_base: adata->acp6x_base)) |
271 | dev_err(&pci->dev, "ACP de-init failed\n" ); |
272 | release_regions: |
273 | pci_release_regions(pci); |
274 | disable_pci: |
275 | pci_disable_device(dev: pci); |
276 | |
277 | return ret; |
278 | } |
279 | |
280 | static int __maybe_unused snd_acp6x_suspend(struct device *dev) |
281 | { |
282 | struct acp6x_dev_data *adata; |
283 | int ret; |
284 | |
285 | adata = dev_get_drvdata(dev); |
286 | ret = acp6x_deinit(acp_base: adata->acp6x_base); |
287 | if (ret) |
288 | dev_err(dev, "ACP de-init failed\n" ); |
289 | return ret; |
290 | } |
291 | |
292 | static int __maybe_unused snd_acp6x_resume(struct device *dev) |
293 | { |
294 | struct acp6x_dev_data *adata; |
295 | int ret; |
296 | |
297 | adata = dev_get_drvdata(dev); |
298 | ret = acp6x_init(acp_base: adata->acp6x_base); |
299 | if (ret) |
300 | dev_err(dev, "ACP init failed\n" ); |
301 | return ret; |
302 | } |
303 | |
304 | static const struct dev_pm_ops acp6x_pm = { |
305 | SET_RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume, NULL) |
306 | SET_SYSTEM_SLEEP_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume) |
307 | }; |
308 | |
309 | static void snd_acp6x_remove(struct pci_dev *pci) |
310 | { |
311 | struct acp6x_dev_data *adata; |
312 | int ret, index; |
313 | |
314 | adata = pci_get_drvdata(pdev: pci); |
315 | if (adata->acp6x_audio_mode == ACP6x_PDM_MODE) { |
316 | for (index = 0; index < ACP6x_DEVS; index++) |
317 | platform_device_unregister(adata->pdev[index]); |
318 | } |
319 | ret = acp6x_deinit(acp_base: adata->acp6x_base); |
320 | if (ret) |
321 | dev_err(&pci->dev, "ACP de-init failed\n" ); |
322 | pm_runtime_forbid(dev: &pci->dev); |
323 | pm_runtime_get_noresume(dev: &pci->dev); |
324 | pci_release_regions(pci); |
325 | pci_disable_device(dev: pci); |
326 | } |
327 | |
328 | static const struct pci_device_id snd_acp6x_ids[] = { |
329 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID), |
330 | .class = PCI_CLASS_MULTIMEDIA_OTHER << 8, |
331 | .class_mask = 0xffffff }, |
332 | { 0, }, |
333 | }; |
334 | MODULE_DEVICE_TABLE(pci, snd_acp6x_ids); |
335 | |
336 | static struct pci_driver yc_acp6x_driver = { |
337 | .name = KBUILD_MODNAME, |
338 | .id_table = snd_acp6x_ids, |
339 | .probe = snd_acp6x_probe, |
340 | .remove = snd_acp6x_remove, |
341 | .driver = { |
342 | .pm = &acp6x_pm, |
343 | } |
344 | }; |
345 | |
346 | module_pci_driver(yc_acp6x_driver); |
347 | |
348 | MODULE_AUTHOR("Vijendar.Mukunda@amd.com" ); |
349 | MODULE_DESCRIPTION("AMD ACP Yellow Carp PCI driver" ); |
350 | MODULE_LICENSE("GPL v2" ); |
351 | |