1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // AMD Vangogh ACP PCI Driver |
4 | // |
5 | // Copyright (C) 2021, 2023 Advanced Micro Devices, Inc. All rights reserved. |
6 | |
7 | #include <linux/pci.h> |
8 | #include <linux/module.h> |
9 | #include <linux/io.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/pm_runtime.h> |
14 | |
15 | #include "acp5x.h" |
16 | #include "../mach-config.h" |
17 | |
18 | struct acp5x_dev_data { |
19 | void __iomem *acp5x_base; |
20 | bool acp5x_audio_mode; |
21 | struct resource *res; |
22 | struct platform_device *pdev[ACP5x_DEVS]; |
23 | }; |
24 | |
25 | static int acp5x_power_on(void __iomem *acp5x_base) |
26 | { |
27 | u32 val; |
28 | int timeout; |
29 | |
30 | val = acp_readl(base_addr: acp5x_base + ACP_PGFSM_STATUS); |
31 | |
32 | if (val == 0) |
33 | return val; |
34 | |
35 | if ((val & ACP_PGFSM_STATUS_MASK) != |
36 | ACP_POWER_ON_IN_PROGRESS) |
37 | acp_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, |
38 | base_addr: acp5x_base + ACP_PGFSM_CONTROL); |
39 | timeout = 0; |
40 | while (++timeout < 500) { |
41 | val = acp_readl(base_addr: acp5x_base + ACP_PGFSM_STATUS); |
42 | if ((val & ACP_PGFSM_STATUS_MASK) == ACP_POWERED_ON) |
43 | return 0; |
44 | udelay(1); |
45 | } |
46 | return -ETIMEDOUT; |
47 | } |
48 | |
49 | static int acp5x_reset(void __iomem *acp5x_base) |
50 | { |
51 | u32 val; |
52 | int timeout; |
53 | |
54 | acp_writel(val: 1, base_addr: acp5x_base + ACP_SOFT_RESET); |
55 | timeout = 0; |
56 | while (++timeout < 500) { |
57 | val = acp_readl(base_addr: acp5x_base + ACP_SOFT_RESET); |
58 | if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) |
59 | break; |
60 | cpu_relax(); |
61 | } |
62 | acp_writel(val: 0, base_addr: acp5x_base + ACP_SOFT_RESET); |
63 | timeout = 0; |
64 | while (++timeout < 500) { |
65 | val = acp_readl(base_addr: acp5x_base + ACP_SOFT_RESET); |
66 | if (!val) |
67 | return 0; |
68 | cpu_relax(); |
69 | } |
70 | return -ETIMEDOUT; |
71 | } |
72 | |
73 | static void acp5x_enable_interrupts(void __iomem *acp5x_base) |
74 | { |
75 | acp_writel(val: 0x01, base_addr: acp5x_base + ACP_EXTERNAL_INTR_ENB); |
76 | } |
77 | |
78 | static void acp5x_disable_interrupts(void __iomem *acp5x_base) |
79 | { |
80 | acp_writel(ACP_EXT_INTR_STAT_CLEAR_MASK, base_addr: acp5x_base + |
81 | ACP_EXTERNAL_INTR_STAT); |
82 | acp_writel(val: 0x00, base_addr: acp5x_base + ACP_EXTERNAL_INTR_CNTL); |
83 | acp_writel(val: 0x00, base_addr: acp5x_base + ACP_EXTERNAL_INTR_ENB); |
84 | } |
85 | |
86 | static int acp5x_init(void __iomem *acp5x_base) |
87 | { |
88 | int ret; |
89 | |
90 | /* power on */ |
91 | ret = acp5x_power_on(acp5x_base); |
92 | if (ret) { |
93 | pr_err("ACP5x power on failed\n" ); |
94 | return ret; |
95 | } |
96 | acp_writel(val: 0x01, base_addr: acp5x_base + ACP_CONTROL); |
97 | /* Reset */ |
98 | ret = acp5x_reset(acp5x_base); |
99 | if (ret) { |
100 | pr_err("ACP5x reset failed\n" ); |
101 | return ret; |
102 | } |
103 | acp_writel(val: 0x03, base_addr: acp5x_base + ACP_CLKMUX_SEL); |
104 | acp5x_enable_interrupts(acp5x_base); |
105 | return 0; |
106 | } |
107 | |
108 | static int acp5x_deinit(void __iomem *acp5x_base) |
109 | { |
110 | int ret; |
111 | |
112 | acp5x_disable_interrupts(acp5x_base); |
113 | /* Reset */ |
114 | ret = acp5x_reset(acp5x_base); |
115 | if (ret) { |
116 | pr_err("ACP5x reset failed\n" ); |
117 | return ret; |
118 | } |
119 | acp_writel(val: 0x00, base_addr: acp5x_base + ACP_CLKMUX_SEL); |
120 | acp_writel(val: 0x00, base_addr: acp5x_base + ACP_CONTROL); |
121 | return 0; |
122 | } |
123 | |
124 | static int snd_acp5x_probe(struct pci_dev *pci, |
125 | const struct pci_device_id *pci_id) |
126 | { |
127 | struct acp5x_dev_data *adata; |
128 | struct platform_device_info pdevinfo[ACP5x_DEVS]; |
129 | unsigned int irqflags, flag; |
130 | int ret, i; |
131 | u32 addr, val; |
132 | |
133 | /* |
134 | * Return if ACP config flag is defined, except when board |
135 | * supports SOF while it is not being enabled in kernel config. |
136 | */ |
137 | flag = snd_amd_acp_find_config(pci); |
138 | if (flag != FLAG_AMD_LEGACY && |
139 | (flag != FLAG_AMD_SOF || IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_VANGOGH))) |
140 | return -ENODEV; |
141 | |
142 | irqflags = IRQF_SHARED; |
143 | if (pci->revision != 0x50) |
144 | return -ENODEV; |
145 | |
146 | if (pci_enable_device(dev: pci)) { |
147 | dev_err(&pci->dev, "pci_enable_device failed\n" ); |
148 | return -ENODEV; |
149 | } |
150 | |
151 | ret = pci_request_regions(pci, "AMD ACP5x audio" ); |
152 | if (ret < 0) { |
153 | dev_err(&pci->dev, "pci_request_regions failed\n" ); |
154 | goto disable_pci; |
155 | } |
156 | |
157 | adata = devm_kzalloc(dev: &pci->dev, size: sizeof(struct acp5x_dev_data), |
158 | GFP_KERNEL); |
159 | if (!adata) { |
160 | ret = -ENOMEM; |
161 | goto release_regions; |
162 | } |
163 | addr = pci_resource_start(pci, 0); |
164 | adata->acp5x_base = devm_ioremap(dev: &pci->dev, offset: addr, |
165 | pci_resource_len(pci, 0)); |
166 | if (!adata->acp5x_base) { |
167 | ret = -ENOMEM; |
168 | goto release_regions; |
169 | } |
170 | pci_set_master(dev: pci); |
171 | pci_set_drvdata(pdev: pci, data: adata); |
172 | ret = acp5x_init(acp5x_base: adata->acp5x_base); |
173 | if (ret) |
174 | goto release_regions; |
175 | |
176 | val = acp_readl(base_addr: adata->acp5x_base + ACP_PIN_CONFIG); |
177 | switch (val) { |
178 | case I2S_MODE: |
179 | adata->res = devm_kzalloc(dev: &pci->dev, |
180 | size: sizeof(struct resource) * ACP5x_RES, |
181 | GFP_KERNEL); |
182 | if (!adata->res) { |
183 | ret = -ENOMEM; |
184 | goto de_init; |
185 | } |
186 | |
187 | adata->res[0].name = "acp5x_i2s_iomem" ; |
188 | adata->res[0].flags = IORESOURCE_MEM; |
189 | adata->res[0].start = addr; |
190 | adata->res[0].end = addr + (ACP5x_REG_END - ACP5x_REG_START); |
191 | |
192 | adata->res[1].name = "acp5x_i2s_sp" ; |
193 | adata->res[1].flags = IORESOURCE_MEM; |
194 | adata->res[1].start = addr + ACP5x_I2STDM_REG_START; |
195 | adata->res[1].end = addr + ACP5x_I2STDM_REG_END; |
196 | |
197 | adata->res[2].name = "acp5x_i2s_hs" ; |
198 | adata->res[2].flags = IORESOURCE_MEM; |
199 | adata->res[2].start = addr + ACP5x_HS_TDM_REG_START; |
200 | adata->res[2].end = addr + ACP5x_HS_TDM_REG_END; |
201 | |
202 | adata->res[3].name = "acp5x_i2s_irq" ; |
203 | adata->res[3].flags = IORESOURCE_IRQ; |
204 | adata->res[3].start = pci->irq; |
205 | adata->res[3].end = adata->res[3].start; |
206 | |
207 | adata->acp5x_audio_mode = ACP5x_I2S_MODE; |
208 | |
209 | memset(&pdevinfo, 0, sizeof(pdevinfo)); |
210 | pdevinfo[0].name = "acp5x_i2s_dma" ; |
211 | pdevinfo[0].id = 0; |
212 | pdevinfo[0].parent = &pci->dev; |
213 | pdevinfo[0].num_res = 4; |
214 | pdevinfo[0].res = &adata->res[0]; |
215 | pdevinfo[0].data = &irqflags; |
216 | pdevinfo[0].size_data = sizeof(irqflags); |
217 | |
218 | pdevinfo[1].name = "acp5x_i2s_playcap" ; |
219 | pdevinfo[1].id = 0; |
220 | pdevinfo[1].parent = &pci->dev; |
221 | pdevinfo[1].num_res = 1; |
222 | pdevinfo[1].res = &adata->res[1]; |
223 | |
224 | pdevinfo[2].name = "acp5x_i2s_playcap" ; |
225 | pdevinfo[2].id = 1; |
226 | pdevinfo[2].parent = &pci->dev; |
227 | pdevinfo[2].num_res = 1; |
228 | pdevinfo[2].res = &adata->res[2]; |
229 | |
230 | pdevinfo[3].name = "acp5x_mach" ; |
231 | pdevinfo[3].id = 0; |
232 | pdevinfo[3].parent = &pci->dev; |
233 | for (i = 0; i < ACP5x_DEVS; i++) { |
234 | adata->pdev[i] = |
235 | platform_device_register_full(pdevinfo: &pdevinfo[i]); |
236 | if (IS_ERR(ptr: adata->pdev[i])) { |
237 | dev_err(&pci->dev, "cannot register %s device\n" , |
238 | pdevinfo[i].name); |
239 | ret = PTR_ERR(ptr: adata->pdev[i]); |
240 | goto unregister_devs; |
241 | } |
242 | } |
243 | break; |
244 | default: |
245 | dev_info(&pci->dev, "ACP audio mode : %d\n" , val); |
246 | } |
247 | pm_runtime_set_autosuspend_delay(dev: &pci->dev, delay: 2000); |
248 | pm_runtime_use_autosuspend(dev: &pci->dev); |
249 | pm_runtime_put_noidle(dev: &pci->dev); |
250 | pm_runtime_allow(dev: &pci->dev); |
251 | return 0; |
252 | |
253 | unregister_devs: |
254 | for (--i; i >= 0; i--) |
255 | platform_device_unregister(adata->pdev[i]); |
256 | de_init: |
257 | if (acp5x_deinit(acp5x_base: adata->acp5x_base)) |
258 | dev_err(&pci->dev, "ACP de-init failed\n" ); |
259 | release_regions: |
260 | pci_release_regions(pci); |
261 | disable_pci: |
262 | pci_disable_device(dev: pci); |
263 | |
264 | return ret; |
265 | } |
266 | |
267 | static int snd_acp5x_suspend(struct device *dev) |
268 | { |
269 | int ret; |
270 | struct acp5x_dev_data *adata; |
271 | |
272 | adata = dev_get_drvdata(dev); |
273 | ret = acp5x_deinit(acp5x_base: adata->acp5x_base); |
274 | if (ret) |
275 | dev_err(dev, "ACP de-init failed\n" ); |
276 | else |
277 | dev_dbg(dev, "ACP de-initialized\n" ); |
278 | |
279 | return ret; |
280 | } |
281 | |
282 | static int snd_acp5x_resume(struct device *dev) |
283 | { |
284 | int ret; |
285 | struct acp5x_dev_data *adata; |
286 | |
287 | adata = dev_get_drvdata(dev); |
288 | ret = acp5x_init(acp5x_base: adata->acp5x_base); |
289 | if (ret) { |
290 | dev_err(dev, "ACP init failed\n" ); |
291 | return ret; |
292 | } |
293 | return 0; |
294 | } |
295 | |
296 | static const struct dev_pm_ops acp5x_pm = { |
297 | RUNTIME_PM_OPS(snd_acp5x_suspend, snd_acp5x_resume, NULL) |
298 | SYSTEM_SLEEP_PM_OPS(snd_acp5x_suspend, snd_acp5x_resume) |
299 | }; |
300 | |
301 | static void snd_acp5x_remove(struct pci_dev *pci) |
302 | { |
303 | struct acp5x_dev_data *adata; |
304 | int i, ret; |
305 | |
306 | adata = pci_get_drvdata(pdev: pci); |
307 | if (adata->acp5x_audio_mode == ACP5x_I2S_MODE) { |
308 | for (i = 0; i < ACP5x_DEVS; i++) |
309 | platform_device_unregister(adata->pdev[i]); |
310 | } |
311 | ret = acp5x_deinit(acp5x_base: adata->acp5x_base); |
312 | if (ret) |
313 | dev_err(&pci->dev, "ACP de-init failed\n" ); |
314 | pm_runtime_forbid(dev: &pci->dev); |
315 | pm_runtime_get_noresume(dev: &pci->dev); |
316 | pci_release_regions(pci); |
317 | pci_disable_device(dev: pci); |
318 | } |
319 | |
320 | static const struct pci_device_id snd_acp5x_ids[] = { |
321 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID), |
322 | .class = PCI_CLASS_MULTIMEDIA_OTHER << 8, |
323 | .class_mask = 0xffffff }, |
324 | { 0, }, |
325 | }; |
326 | MODULE_DEVICE_TABLE(pci, snd_acp5x_ids); |
327 | |
328 | static struct pci_driver acp5x_driver = { |
329 | .name = KBUILD_MODNAME, |
330 | .id_table = snd_acp5x_ids, |
331 | .probe = snd_acp5x_probe, |
332 | .remove = snd_acp5x_remove, |
333 | .driver = { |
334 | .pm = pm_ptr(&acp5x_pm), |
335 | } |
336 | }; |
337 | |
338 | module_pci_driver(acp5x_driver); |
339 | |
340 | MODULE_AUTHOR("Vijendar.Mukunda@amd.com" ); |
341 | MODULE_DESCRIPTION("AMD Vangogh ACP PCI driver" ); |
342 | MODULE_LICENSE("GPL v2" ); |
343 | |