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(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, acp_base + ACP_PGFSM_CONTROL); 33 timeout = 0; 34 while (++timeout < 500) { 35 val = rpl_acp_readl(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(1, acp_base + ACP_SOFT_RESET); 49 timeout = 0; 50 while (++timeout < 500) { 51 val = rpl_acp_readl(acp_base + ACP_SOFT_RESET); 52 if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) 53 break; 54 cpu_relax(); 55 } 56 rpl_acp_writel(0, acp_base + ACP_SOFT_RESET); 57 timeout = 0; 58 while (++timeout < 500) { 59 val = rpl_acp_readl(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(0x01, 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(0x03, 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(0x00, acp_base + ACP_CLKMUX_SEL); 99 rpl_acp_writel(0x00, 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(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(&pci->dev, 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(&pci->dev, addr, 138 pci_resource_len(pci, 0)); 139 if (!adata->acp6x_base) { 140 ret = -ENOMEM; 141 goto release_regions; 142 } 143 pci_set_master(pci); 144 pci_set_drvdata(pci, adata); 145 ret = rpl_init(adata->acp6x_base); 146 if (ret) 147 goto release_regions; 148 pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS); 149 pm_runtime_use_autosuspend(&pci->dev); 150 pm_runtime_put_noidle(&pci->dev); 151 pm_runtime_allow(&pci->dev); 152 153 return 0; 154 release_regions: 155 pci_release_regions(pci); 156 disable_pci: 157 pci_disable_device(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(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(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(pci); 197 ret = rpl_deinit(adata->acp6x_base); 198 if (ret) 199 dev_err(&pci->dev, "ACP de-init failed\n"); 200 pm_runtime_forbid(&pci->dev); 201 pm_runtime_get_noresume(&pci->dev); 202 pci_release_regions(pci); 203 pci_disable_device(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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.