File Index
Files and directories
Sign in
FILE

pci-acp5x.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/amd/vangogh/pci-acp5x.c C
  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");
~/ Read-only file preview.