1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * AMD RPL ACP PCI Driver |
4 | * |
5 | * Copyright 2022 Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <linux/pci.h> |
9 | #include <linux/module.h> |
10 | #include <linux/io.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm_runtime.h> |
14 | |
15 | #include "rpl_acp6x.h" |
16 | |
17 | struct rpl_dev_data { |
18 | void __iomem *acp6x_base; |
19 | }; |
20 | |
21 | static int rpl_power_on(void __iomem *acp_base) |
22 | { |
23 | u32 val; |
24 | int timeout; |
25 | |
26 | val = rpl_acp_readl(base_addr: acp_base + ACP_PGFSM_STATUS); |
27 | |
28 | if (!val) |
29 | return val; |
30 | |
31 | if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS) |
32 | rpl_acp_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, base_addr: acp_base + ACP_PGFSM_CONTROL); |
33 | timeout = 0; |
34 | while (++timeout < 500) { |
35 | val = rpl_acp_readl(base_addr: acp_base + ACP_PGFSM_STATUS); |
36 | if (!val) |
37 | return 0; |
38 | udelay(1); |
39 | } |
40 | return -ETIMEDOUT; |
41 | } |
42 | |
43 | static int rpl_reset(void __iomem *acp_base) |
44 | { |
45 | u32 val; |
46 | int timeout; |
47 | |
48 | rpl_acp_writel(val: 1, base_addr: acp_base + ACP_SOFT_RESET); |
49 | timeout = 0; |
50 | while (++timeout < 500) { |
51 | val = rpl_acp_readl(base_addr: acp_base + ACP_SOFT_RESET); |
52 | if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) |
53 | break; |
54 | cpu_relax(); |
55 | } |
56 | rpl_acp_writel(val: 0, base_addr: acp_base + ACP_SOFT_RESET); |
57 | timeout = 0; |
58 | while (++timeout < 500) { |
59 | val = rpl_acp_readl(base_addr: acp_base + ACP_SOFT_RESET); |
60 | if (!val) |
61 | return 0; |
62 | cpu_relax(); |
63 | } |
64 | return -ETIMEDOUT; |
65 | } |
66 | |
67 | static int rpl_init(void __iomem *acp_base) |
68 | { |
69 | int ret; |
70 | |
71 | /* power on */ |
72 | ret = rpl_power_on(acp_base); |
73 | if (ret) { |
74 | pr_err("ACP power on failed\n" ); |
75 | return ret; |
76 | } |
77 | rpl_acp_writel(val: 0x01, base_addr: acp_base + ACP_CONTROL); |
78 | /* Reset */ |
79 | ret = rpl_reset(acp_base); |
80 | if (ret) { |
81 | pr_err("ACP reset failed\n" ); |
82 | return ret; |
83 | } |
84 | rpl_acp_writel(val: 0x03, base_addr: acp_base + ACP_CLKMUX_SEL); |
85 | return 0; |
86 | } |
87 | |
88 | static int rpl_deinit(void __iomem *acp_base) |
89 | { |
90 | int ret; |
91 | |
92 | /* Reset */ |
93 | ret = rpl_reset(acp_base); |
94 | if (ret) { |
95 | pr_err("ACP reset failed\n" ); |
96 | return ret; |
97 | } |
98 | rpl_acp_writel(val: 0x00, base_addr: acp_base + ACP_CLKMUX_SEL); |
99 | rpl_acp_writel(val: 0x00, base_addr: acp_base + ACP_CONTROL); |
100 | return 0; |
101 | } |
102 | |
103 | static int snd_rpl_probe(struct pci_dev *pci, |
104 | const struct pci_device_id *pci_id) |
105 | { |
106 | struct rpl_dev_data *adata; |
107 | u32 addr; |
108 | int ret; |
109 | |
110 | /* RPL device check */ |
111 | switch (pci->revision) { |
112 | case 0x62: |
113 | break; |
114 | default: |
115 | dev_dbg(&pci->dev, "acp6x pci device not found\n" ); |
116 | return -ENODEV; |
117 | } |
118 | if (pci_enable_device(dev: pci)) { |
119 | dev_err(&pci->dev, "pci_enable_device failed\n" ); |
120 | return -ENODEV; |
121 | } |
122 | |
123 | ret = pci_request_regions(pci, "AMD ACP6x audio" ); |
124 | if (ret < 0) { |
125 | dev_err(&pci->dev, "pci_request_regions failed\n" ); |
126 | goto disable_pci; |
127 | } |
128 | |
129 | adata = devm_kzalloc(dev: &pci->dev, size: sizeof(struct rpl_dev_data), |
130 | GFP_KERNEL); |
131 | if (!adata) { |
132 | ret = -ENOMEM; |
133 | goto release_regions; |
134 | } |
135 | |
136 | addr = pci_resource_start(pci, 0); |
137 | adata->acp6x_base = devm_ioremap(dev: &pci->dev, offset: addr, |
138 | pci_resource_len(pci, 0)); |
139 | if (!adata->acp6x_base) { |
140 | ret = -ENOMEM; |
141 | goto release_regions; |
142 | } |
143 | pci_set_master(dev: pci); |
144 | pci_set_drvdata(pdev: pci, data: adata); |
145 | ret = rpl_init(acp_base: adata->acp6x_base); |
146 | if (ret) |
147 | goto release_regions; |
148 | pm_runtime_set_autosuspend_delay(dev: &pci->dev, ACP_SUSPEND_DELAY_MS); |
149 | pm_runtime_use_autosuspend(dev: &pci->dev); |
150 | pm_runtime_put_noidle(dev: &pci->dev); |
151 | pm_runtime_allow(dev: &pci->dev); |
152 | |
153 | return 0; |
154 | release_regions: |
155 | pci_release_regions(pci); |
156 | disable_pci: |
157 | pci_disable_device(dev: pci); |
158 | |
159 | return ret; |
160 | } |
161 | |
162 | static int __maybe_unused snd_rpl_suspend(struct device *dev) |
163 | { |
164 | struct rpl_dev_data *adata; |
165 | int ret; |
166 | |
167 | adata = dev_get_drvdata(dev); |
168 | ret = rpl_deinit(acp_base: adata->acp6x_base); |
169 | if (ret) |
170 | dev_err(dev, "ACP de-init failed\n" ); |
171 | return ret; |
172 | } |
173 | |
174 | static int __maybe_unused snd_rpl_resume(struct device *dev) |
175 | { |
176 | struct rpl_dev_data *adata; |
177 | int ret; |
178 | |
179 | adata = dev_get_drvdata(dev); |
180 | ret = rpl_init(acp_base: adata->acp6x_base); |
181 | if (ret) |
182 | dev_err(dev, "ACP init failed\n" ); |
183 | return ret; |
184 | } |
185 | |
186 | static const struct dev_pm_ops rpl_pm = { |
187 | SET_RUNTIME_PM_OPS(snd_rpl_suspend, snd_rpl_resume, NULL) |
188 | SET_SYSTEM_SLEEP_PM_OPS(snd_rpl_suspend, snd_rpl_resume) |
189 | }; |
190 | |
191 | static void snd_rpl_remove(struct pci_dev *pci) |
192 | { |
193 | struct rpl_dev_data *adata; |
194 | int ret; |
195 | |
196 | adata = pci_get_drvdata(pdev: pci); |
197 | ret = rpl_deinit(acp_base: adata->acp6x_base); |
198 | if (ret) |
199 | dev_err(&pci->dev, "ACP de-init failed\n" ); |
200 | pm_runtime_forbid(dev: &pci->dev); |
201 | pm_runtime_get_noresume(dev: &pci->dev); |
202 | pci_release_regions(pci); |
203 | pci_disable_device(dev: pci); |
204 | } |
205 | |
206 | static const struct pci_device_id snd_rpl_ids[] = { |
207 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID), |
208 | .class = PCI_CLASS_MULTIMEDIA_OTHER << 8, |
209 | .class_mask = 0xffffff }, |
210 | { 0, }, |
211 | }; |
212 | MODULE_DEVICE_TABLE(pci, snd_rpl_ids); |
213 | |
214 | static struct pci_driver rpl_acp6x_driver = { |
215 | .name = KBUILD_MODNAME, |
216 | .id_table = snd_rpl_ids, |
217 | .probe = snd_rpl_probe, |
218 | .remove = snd_rpl_remove, |
219 | .driver = { |
220 | .pm = &rpl_pm, |
221 | } |
222 | }; |
223 | |
224 | module_pci_driver(rpl_acp6x_driver); |
225 | |
226 | MODULE_DESCRIPTION("AMD ACP RPL PCI driver" ); |
227 | MODULE_LICENSE("GPL v2" ); |
228 | |