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(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			   acp5x_base + ACP_PGFSM_CONTROL);
39	timeout = 0;
40	while (++timeout < 500) {
41		val = acp_readl(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(1, acp5x_base + ACP_SOFT_RESET);
55	timeout = 0;
56	while (++timeout < 500) {
57		val = acp_readl(acp5x_base + ACP_SOFT_RESET);
58		if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK)
59			break;
60		cpu_relax();
61	}
62	acp_writel(0, acp5x_base + ACP_SOFT_RESET);
63	timeout = 0;
64	while (++timeout < 500) {
65		val = acp_readl(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(0x01, 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, acp5x_base +
81		   ACP_EXTERNAL_INTR_STAT);
82	acp_writel(0x00, acp5x_base + ACP_EXTERNAL_INTR_CNTL);
83	acp_writel(0x00, 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(0x01, 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(0x03, 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(0x00, acp5x_base + ACP_CLKMUX_SEL);
120	acp_writel(0x00, 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(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(&pci->dev, 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(&pci->dev, addr,
165					 pci_resource_len(pci, 0));
166	if (!adata->acp5x_base) {
167		ret = -ENOMEM;
168		goto release_regions;
169	}
170	pci_set_master(pci);
171	pci_set_drvdata(pci, adata);
172	ret = acp5x_init(adata->acp5x_base);
173	if (ret)
174		goto release_regions;
175
176	val = acp_readl(adata->acp5x_base + ACP_PIN_CONFIG);
177	switch (val) {
178	case I2S_MODE:
179		adata->res = devm_kzalloc(&pci->dev,
180					  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[i]);
236			if (IS_ERR(adata->pdev[i])) {
237				dev_err(&pci->dev, "cannot register %s device\n",
238					pdevinfo[i].name);
239				ret = PTR_ERR(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(&pci->dev, 2000);
248	pm_runtime_use_autosuspend(&pci->dev);
249	pm_runtime_put_noidle(&pci->dev);
250	pm_runtime_allow(&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(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(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(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(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(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(adata->acp5x_base);
312	if (ret)
313		dev_err(&pci->dev, "ACP de-init failed\n");
314	pm_runtime_forbid(&pci->dev);
315	pm_runtime_get_noresume(&pci->dev);
316	pci_release_regions(pci);
317	pci_disable_device(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