Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/amd/vangogh/pci-acp5x.c
26490 views
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(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
49
static 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
73
static void acp5x_enable_interrupts(void __iomem *acp5x_base)
74
{
75
acp_writel(0x01, 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, 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
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(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
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(0x00, acp5x_base + ACP_CLKMUX_SEL);
120
acp_writel(0x00, 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(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
253
unregister_devs:
254
for (--i; i >= 0; i--)
255
platform_device_unregister(adata->pdev[i]);
256
de_init:
257
if (acp5x_deinit(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(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(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(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(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
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("[email protected]");
341
MODULE_DESCRIPTION("AMD Vangogh ACP PCI driver");
342
MODULE_LICENSE("GPL v2");
343
344