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
18struct 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
25static 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
49static 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
73static void acp5x_enable_interrupts(void __iomem *acp5x_base)
74{
75 acp_writel(val: 0x01, base_addr: acp5x_base + ACP_EXTERNAL_INTR_ENB);
76}
77
78static 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
86static 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
108static 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
124static 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
253unregister_devs:
254 for (--i; i >= 0; i--)
255 platform_device_unregister(adata->pdev[i]);
256de_init:
257 if (acp5x_deinit(acp5x_base: adata->acp5x_base))
258 dev_err(&pci->dev, "ACP de-init failed\n");
259release_regions:
260 pci_release_regions(pci);
261disable_pci:
262 pci_disable_device(dev: pci);
263
264 return ret;
265}
266
267static 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
282static 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
296static 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
301static 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
320static 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};
326MODULE_DEVICE_TABLE(pci, snd_acp5x_ids);
327
328static 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
338module_pci_driver(acp5x_driver);
339
340MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
341MODULE_DESCRIPTION("AMD Vangogh ACP PCI driver");
342MODULE_LICENSE("GPL v2");
343

source code of linux/sound/soc/amd/vangogh/pci-acp5x.c