cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

pci-acp6x.c (8037B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * AMD Yellow Carp ACP PCI Driver
      4 *
      5 * Copyright 2021 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/interrupt.h>
     14#include <sound/pcm_params.h>
     15#include <linux/pm_runtime.h>
     16
     17#include "acp6x.h"
     18
     19struct acp6x_dev_data {
     20	void __iomem *acp6x_base;
     21	struct resource *res;
     22	bool acp6x_audio_mode;
     23	struct platform_device *pdev[ACP6x_DEVS];
     24};
     25
     26static int acp6x_power_on(void __iomem *acp_base)
     27{
     28	u32 val;
     29	int timeout;
     30
     31	val = acp6x_readl(acp_base + ACP_PGFSM_STATUS);
     32
     33	if (!val)
     34		return val;
     35
     36	if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS)
     37		acp6x_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL);
     38	timeout = 0;
     39	while (++timeout < 500) {
     40		val = acp6x_readl(acp_base + ACP_PGFSM_STATUS);
     41		if (!val)
     42			return 0;
     43		udelay(1);
     44	}
     45	return -ETIMEDOUT;
     46}
     47
     48static int acp6x_reset(void __iomem *acp_base)
     49{
     50	u32 val;
     51	int timeout;
     52
     53	acp6x_writel(1, acp_base + ACP_SOFT_RESET);
     54	timeout = 0;
     55	while (++timeout < 500) {
     56		val = acp6x_readl(acp_base + ACP_SOFT_RESET);
     57		if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK)
     58			break;
     59		cpu_relax();
     60	}
     61	acp6x_writel(0, acp_base + ACP_SOFT_RESET);
     62	timeout = 0;
     63	while (++timeout < 500) {
     64		val = acp6x_readl(acp_base + ACP_SOFT_RESET);
     65		if (!val)
     66			return 0;
     67		cpu_relax();
     68	}
     69	return -ETIMEDOUT;
     70}
     71
     72static void acp6x_enable_interrupts(void __iomem *acp_base)
     73{
     74	acp6x_writel(0x01, acp_base + ACP_EXTERNAL_INTR_ENB);
     75}
     76
     77static void acp6x_disable_interrupts(void __iomem *acp_base)
     78{
     79	acp6x_writel(ACP_EXT_INTR_STAT_CLEAR_MASK, acp_base +
     80		     ACP_EXTERNAL_INTR_STAT);
     81	acp6x_writel(0x00, acp_base + ACP_EXTERNAL_INTR_CNTL);
     82	acp6x_writel(0x00, acp_base + ACP_EXTERNAL_INTR_ENB);
     83}
     84
     85static int acp6x_init(void __iomem *acp_base)
     86{
     87	int ret;
     88
     89	/* power on */
     90	ret = acp6x_power_on(acp_base);
     91	if (ret) {
     92		pr_err("ACP power on failed\n");
     93		return ret;
     94	}
     95	acp6x_writel(0x01, acp_base + ACP_CONTROL);
     96	/* Reset */
     97	ret = acp6x_reset(acp_base);
     98	if (ret) {
     99		pr_err("ACP reset failed\n");
    100		return ret;
    101	}
    102	acp6x_writel(0x03, acp_base + ACP_CLKMUX_SEL);
    103	acp6x_enable_interrupts(acp_base);
    104	return 0;
    105}
    106
    107static int acp6x_deinit(void __iomem *acp_base)
    108{
    109	int ret;
    110
    111	acp6x_disable_interrupts(acp_base);
    112	/* Reset */
    113	ret = acp6x_reset(acp_base);
    114	if (ret) {
    115		pr_err("ACP reset failed\n");
    116		return ret;
    117	}
    118	acp6x_writel(0x00, acp_base + ACP_CLKMUX_SEL);
    119	acp6x_writel(0x00, acp_base + ACP_CONTROL);
    120	return 0;
    121}
    122
    123static irqreturn_t acp6x_irq_handler(int irq, void *dev_id)
    124{
    125	struct acp6x_dev_data *adata;
    126	struct pdm_dev_data *yc_pdm_data;
    127	u32 val;
    128
    129	adata = dev_id;
    130	if (!adata)
    131		return IRQ_NONE;
    132
    133	val = acp6x_readl(adata->acp6x_base + ACP_EXTERNAL_INTR_STAT);
    134	if (val & BIT(PDM_DMA_STAT)) {
    135		yc_pdm_data = dev_get_drvdata(&adata->pdev[0]->dev);
    136		acp6x_writel(BIT(PDM_DMA_STAT), adata->acp6x_base + ACP_EXTERNAL_INTR_STAT);
    137		if (yc_pdm_data->capture_stream)
    138			snd_pcm_period_elapsed(yc_pdm_data->capture_stream);
    139		return IRQ_HANDLED;
    140	}
    141	return IRQ_NONE;
    142}
    143
    144static int snd_acp6x_probe(struct pci_dev *pci,
    145			   const struct pci_device_id *pci_id)
    146{
    147	struct acp6x_dev_data *adata;
    148	struct platform_device_info pdevinfo[ACP6x_DEVS];
    149	int index = 0;
    150	int val = 0x00;
    151	u32 addr;
    152	unsigned int irqflags;
    153	int ret;
    154
    155	irqflags = IRQF_SHARED;
    156	/* Yellow Carp device check */
    157	switch (pci->revision) {
    158	case 0x60:
    159	case 0x6f:
    160		break;
    161	default:
    162		dev_err(&pci->dev, "acp6x pci device not found\n");
    163		return -ENODEV;
    164	}
    165	if (pci_enable_device(pci)) {
    166		dev_err(&pci->dev, "pci_enable_device failed\n");
    167		return -ENODEV;
    168	}
    169
    170	ret = pci_request_regions(pci, "AMD ACP3x audio");
    171	if (ret < 0) {
    172		dev_err(&pci->dev, "pci_request_regions failed\n");
    173		goto disable_pci;
    174	}
    175
    176	adata = devm_kzalloc(&pci->dev, sizeof(struct acp6x_dev_data),
    177			     GFP_KERNEL);
    178	if (!adata) {
    179		ret = -ENOMEM;
    180		goto release_regions;
    181	}
    182
    183	addr = pci_resource_start(pci, 0);
    184	adata->acp6x_base = devm_ioremap(&pci->dev, addr,
    185					 pci_resource_len(pci, 0));
    186	if (!adata->acp6x_base) {
    187		ret = -ENOMEM;
    188		goto release_regions;
    189	}
    190	pci_set_master(pci);
    191	pci_set_drvdata(pci, adata);
    192	ret = acp6x_init(adata->acp6x_base);
    193	if (ret)
    194		goto release_regions;
    195	val = acp6x_readl(adata->acp6x_base + ACP_PIN_CONFIG);
    196	switch (val) {
    197	case ACP_CONFIG_0:
    198	case ACP_CONFIG_1:
    199	case ACP_CONFIG_2:
    200	case ACP_CONFIG_3:
    201	case ACP_CONFIG_9:
    202	case ACP_CONFIG_15:
    203		dev_info(&pci->dev, "Audio Mode %d\n", val);
    204		break;
    205	default:
    206		adata->res = devm_kzalloc(&pci->dev,
    207					  sizeof(struct resource),
    208					  GFP_KERNEL);
    209		if (!adata->res) {
    210			ret = -ENOMEM;
    211			goto de_init;
    212		}
    213
    214		adata->res->name = "acp_iomem";
    215		adata->res->flags = IORESOURCE_MEM;
    216		adata->res->start = addr;
    217		adata->res->end = addr + (ACP6x_REG_END - ACP6x_REG_START);
    218
    219		adata->acp6x_audio_mode = ACP6x_PDM_MODE;
    220
    221		memset(&pdevinfo, 0, sizeof(pdevinfo));
    222		pdevinfo[0].name = "acp_yc_pdm_dma";
    223		pdevinfo[0].id = 0;
    224		pdevinfo[0].parent = &pci->dev;
    225		pdevinfo[0].num_res = 1;
    226		pdevinfo[0].res = adata->res;
    227
    228		pdevinfo[1].name = "dmic-codec";
    229		pdevinfo[1].id = 0;
    230		pdevinfo[1].parent = &pci->dev;
    231
    232		pdevinfo[2].name = "acp_yc_mach";
    233		pdevinfo[2].id = 0;
    234		pdevinfo[2].parent = &pci->dev;
    235
    236		for (index = 0; index < ACP6x_DEVS; index++) {
    237			adata->pdev[index] =
    238				platform_device_register_full(&pdevinfo[index]);
    239			if (IS_ERR(adata->pdev[index])) {
    240				dev_err(&pci->dev, "cannot register %s device\n",
    241					pdevinfo[index].name);
    242				ret = PTR_ERR(adata->pdev[index]);
    243				goto unregister_devs;
    244			}
    245		}
    246		break;
    247	}
    248	ret = devm_request_irq(&pci->dev, pci->irq, acp6x_irq_handler,
    249			       irqflags, "ACP_PCI_IRQ", adata);
    250	if (ret) {
    251		dev_err(&pci->dev, "ACP PCI IRQ request failed\n");
    252		goto unregister_devs;
    253	}
    254	pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS);
    255	pm_runtime_use_autosuspend(&pci->dev);
    256	pm_runtime_put_noidle(&pci->dev);
    257	pm_runtime_allow(&pci->dev);
    258
    259	return 0;
    260unregister_devs:
    261	for (--index; index >= 0; index--)
    262		platform_device_unregister(adata->pdev[index]);
    263de_init:
    264	if (acp6x_deinit(adata->acp6x_base))
    265		dev_err(&pci->dev, "ACP de-init failed\n");
    266release_regions:
    267	pci_release_regions(pci);
    268disable_pci:
    269	pci_disable_device(pci);
    270
    271	return ret;
    272}
    273
    274static int __maybe_unused snd_acp6x_suspend(struct device *dev)
    275{
    276	struct acp6x_dev_data *adata;
    277	int ret;
    278
    279	adata = dev_get_drvdata(dev);
    280	ret = acp6x_deinit(adata->acp6x_base);
    281	if (ret)
    282		dev_err(dev, "ACP de-init failed\n");
    283	return ret;
    284}
    285
    286static int __maybe_unused snd_acp6x_resume(struct device *dev)
    287{
    288	struct acp6x_dev_data *adata;
    289	int ret;
    290
    291	adata = dev_get_drvdata(dev);
    292	ret = acp6x_init(adata->acp6x_base);
    293	if (ret)
    294		dev_err(dev, "ACP init failed\n");
    295	return ret;
    296}
    297
    298static const struct dev_pm_ops acp6x_pm = {
    299	SET_RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume, NULL)
    300	SET_SYSTEM_SLEEP_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume)
    301};
    302
    303static void snd_acp6x_remove(struct pci_dev *pci)
    304{
    305	struct acp6x_dev_data *adata;
    306	int ret, index;
    307
    308	adata = pci_get_drvdata(pci);
    309	if (adata->acp6x_audio_mode == ACP6x_PDM_MODE) {
    310		for (index = 0; index < ACP6x_DEVS; index++)
    311			platform_device_unregister(adata->pdev[index]);
    312	}
    313	ret = acp6x_deinit(adata->acp6x_base);
    314	if (ret)
    315		dev_err(&pci->dev, "ACP de-init failed\n");
    316	pm_runtime_forbid(&pci->dev);
    317	pm_runtime_get_noresume(&pci->dev);
    318	pci_release_regions(pci);
    319	pci_disable_device(pci);
    320}
    321
    322static const struct pci_device_id snd_acp6x_ids[] = {
    323	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID),
    324	.class = PCI_CLASS_MULTIMEDIA_OTHER << 8,
    325	.class_mask = 0xffffff },
    326	{ 0, },
    327};
    328MODULE_DEVICE_TABLE(pci, snd_acp6x_ids);
    329
    330static struct pci_driver yc_acp6x_driver  = {
    331	.name = KBUILD_MODNAME,
    332	.id_table = snd_acp6x_ids,
    333	.probe = snd_acp6x_probe,
    334	.remove = snd_acp6x_remove,
    335	.driver = {
    336		.pm = &acp6x_pm,
    337	}
    338};
    339
    340module_pci_driver(yc_acp6x_driver);
    341
    342MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
    343MODULE_DESCRIPTION("AMD ACP Yellow Carp PCI driver");
    344MODULE_LICENSE("GPL v2");