Files
linux-nv-oot/drivers/platform/tegra/nvadsp/amc.c
Viswanath L e2a1904d46 nvadsp: Add multi-instance support
ADSP host driver is enhanced to be multi-instance
capable and reentrant:
 - Trailing unique identifier string in compatible DT property, like
    "adsp", "adsp1" or "aon", is used to identify the driver instances
 - Each probed driver instance is inserted into a global list, from
    which the handle can be fetched using 'nvadsp_get_handle' API
    (passing the above unique identifier as argument)
 - Above unique identifier is also used as name for the DBFS
    directory (containing files like adsp_console, adsp_logger, etc.)
 - 'nvadsp_get_handle' is the only exported API; all other APIs are
    accessible via function pointers within 'struct nvadsp_handle'
 - APIs above maintain one-is-to-one correspondence with all
    legacy APIs, with the addition of a new argument
    'struct nvadsp_handle *' at the beginning
 - Legacy APIs continue to be supported, but they are hardwired to
    work only if the kernel probes just one driver instance
 - All driver files are cleaned up to not use any global state
    variables (necessary for reentrancy)

Bug 3682950

Change-Id: Id5db49e861b2f81716ae8352b36b406654da2bbd
Signed-off-by: Viswanath L <viswanathl@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3092701
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Dara Ramesh <dramesh@nvidia.com>
2024-04-11 18:05:18 -07:00

182 lines
4.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2014-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#include <linux/tegra_nvadsp.h>
#include <linux/interrupt.h>
#include <linux/version.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/virt/hv-ivc.h>
#include "dev.h"
#include "amc.h"
static inline u32 amc_readl(struct nvadsp_drv_data *drv, u32 reg)
{
return readl(drv->base_regs[AMC] + reg);
}
static inline void amc_writel(struct nvadsp_drv_data *drv, u32 val, u32 reg)
{
writel(val, drv->base_regs[AMC] + reg);
}
#ifdef CONFIG_AMC_SAVE_RESTORE
static void wmemcpy_to_aram(u32 to_aram, const u32 *from_mem, size_t wlen)
{
u32 base, offset;
base = to_aram & AMC_ARAM_APERTURE_DATA_LEN;
amc_writel(base, AMC_ARAM_APERTURE_BASE);
offset = to_aram % AMC_ARAM_APERTURE_DATA_LEN;
while (wlen--) {
if (offset == AMC_ARAM_APERTURE_DATA_LEN) {
base += AMC_ARAM_APERTURE_DATA_LEN;
amc_writel(base, AMC_ARAM_APERTURE_BASE);
offset = 0;
}
amc_writel(*from_mem, AMC_ARAM_APERTURE_DATA_START + offset);
from_mem++;
offset += 4;
}
}
static void wmemcpy_from_aram(u32 *to_mem, const u32 from_aram, size_t wlen)
{
u32 base, offset;
base = from_aram & AMC_ARAM_APERTURE_DATA_LEN;
amc_writel(base, AMC_ARAM_APERTURE_BASE);
offset = from_aram % AMC_ARAM_APERTURE_DATA_LEN;
while (wlen--) {
if (offset == AMC_ARAM_APERTURE_DATA_LEN) {
base += AMC_ARAM_APERTURE_DATA_LEN;
amc_writel(base, AMC_ARAM_APERTURE_BASE);
offset = 0;
}
*to_mem = amc_readl(AMC_ARAM_APERTURE_DATA_START + offset);
to_mem++;
offset += 4;
}
}
int nvadsp_aram_save(struct platform_device *pdev)
{
struct nvadsp_drv_data *d = platform_get_drvdata(pdev);
wmemcpy_from_aram(d->state.aram, AMC_ARAM_START, AMC_ARAM_WSIZE);
return 0;
}
int nvadsp_aram_restore(struct platform_device *pdev)
{
struct nvadsp_drv_data *ndd = platform_get_drvdata(pdev);
wmemcpy_to_aram(AMC_ARAM_START, ndd->state.aram, AMC_ARAM_WSIZE);
return 0;
}
int nvadsp_amc_save(struct platform_device *pdev)
{
struct nvadsp_drv_data *d = platform_get_drvdata(pdev);
u32 val, offset = 0;
int i = 0;
offset = 0x0;
val = readl(d->base_regs[AMC] + offset);
d->state.amc_regs[i++] = val;
offset = 0x8;
val = readl(d->base_regs[AMC] + offset);
d->state.amc_regs[i++] = val;
return 0;
}
int nvadsp_amc_restore(struct platform_device *pdev)
{
struct nvadsp_drv_data *d = platform_get_drvdata(pdev);
u32 val, offset = 0;
int i = 0;
offset = 0x0;
val = d->state.amc_regs[i++];
writel(val, d->base_regs[AMC] + offset);
offset = 0x8;
val = d->state.amc_regs[i++];
writel(val, d->base_regs[AMC] + offset);
return 0;
}
#endif /* CONFIG_AMC_SAVE_RESTORE */
static irqreturn_t nvadsp_amc_error_int_handler(int irq, void *devid)
{
struct platform_device *pdev = devid;
struct nvadsp_drv_data *drv = platform_get_drvdata(pdev);
u32 val, addr, status, intr = 0;
status = amc_readl(drv, AMC_INT_STATUS);
addr = amc_readl(drv, AMC_ERROR_ADDR);
if (status & AMC_INT_STATUS_ARAM) {
/*
* Ignore addresses lesser than AMC_ERROR_ADDR_IGNORE (4k)
* as those are spurious ones due a hardware issue.
*/
if (!(drv->chip_data->amc_err_war) ||
(addr > AMC_ERROR_ADDR_IGNORE))
pr_info("nvadsp: invalid ARAM access. address: 0x%x\n",
addr);
intr |= AMC_INT_INVALID_ARAM_ACCESS;
}
if (status & AMC_INT_STATUS_REG) {
pr_info("nvadsp: invalid AMC reg access. address: 0x%x\n",
addr);
intr |= AMC_INT_INVALID_REG_ACCESS;
}
val = amc_readl(drv, AMC_INT_CLR);
val |= intr;
amc_writel(drv, val, AMC_INT_CLR);
return IRQ_HANDLED;
}
void nvadsp_free_amc_interrupts(struct platform_device *pdev)
{
struct nvadsp_drv_data *drv = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
if (drv->chip_data->amc_not_avlbl)
return;
if (!is_tegra_hypervisor_mode())
devm_free_irq(dev, drv->agic_irqs[AMC_ERR_VIRQ], pdev);
}
int nvadsp_setup_amc_interrupts(struct platform_device *pdev)
{
struct nvadsp_drv_data *drv = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
int ret = 0;
if (drv->chip_data->amc_not_avlbl)
return ret;
if (!is_tegra_hypervisor_mode())
ret = devm_request_irq(dev, drv->agic_irqs[AMC_ERR_VIRQ],
nvadsp_amc_error_int_handler, 0,
"AMC error int", pdev);
return ret;
}