343 lines · c
1// SPDX-License-Identifier: GPL-2.0+2//3// AMD Vangogh ACP PCI Driver4//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 board135 * 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 else277 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