262 lines
7.1 KiB
C
262 lines
7.1 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
/*
|
||
|
* Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/bitfield.h>
|
||
|
#include <linux/bitops.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/genalloc.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/mfd/syscon.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/property.h>
|
||
|
#include <linux/regmap.h>
|
||
|
#include <linux/remoteproc.h>
|
||
|
#include <linux/reset.h>
|
||
|
#include <linux/sizes.h>
|
||
|
|
||
|
#include "remoteproc_internal.h"
|
||
|
|
||
|
#define AO_REMAP_REG0 0x0
|
||
|
#define AO_REMAP_REG0_REMAP_AHB_SRAM_BITS_17_14_FOR_ARM_CPU GENMASK(3, 0)
|
||
|
|
||
|
#define AO_REMAP_REG1 0x4
|
||
|
#define AO_REMAP_REG1_MOVE_AHB_SRAM_TO_0X0_INSTEAD_OF_DDR BIT(4)
|
||
|
#define AO_REMAP_REG1_REMAP_AHB_SRAM_BITS_17_14_FOR_MEDIA_CPU GENMASK(3, 0)
|
||
|
|
||
|
#define AO_CPU_CNTL 0x0
|
||
|
#define AO_CPU_CNTL_AHB_SRAM_BITS_31_20 GENMASK(28, 16)
|
||
|
#define AO_CPU_CNTL_HALT BIT(9)
|
||
|
#define AO_CPU_CNTL_UNKNONWN BIT(8)
|
||
|
#define AO_CPU_CNTL_RUN BIT(0)
|
||
|
|
||
|
#define AO_CPU_STAT 0x4
|
||
|
|
||
|
#define AO_SECURE_REG0 0x0
|
||
|
#define AO_SECURE_REG0_AHB_SRAM_BITS_19_12 GENMASK(15, 8)
|
||
|
|
||
|
/* Only bits [31:20] and [17:14] are usable, all other bits must be zero */
|
||
|
#define MESON_AO_RPROC_SRAM_USABLE_BITS 0xfff3c000ULL
|
||
|
|
||
|
#define MESON_AO_RPROC_MEMORY_OFFSET 0x10000000
|
||
|
|
||
|
struct meson_mx_ao_arc_rproc_priv {
|
||
|
void __iomem *remap_base;
|
||
|
void __iomem *cpu_base;
|
||
|
unsigned long sram_va;
|
||
|
phys_addr_t sram_pa;
|
||
|
size_t sram_size;
|
||
|
struct gen_pool *sram_pool;
|
||
|
struct reset_control *arc_reset;
|
||
|
struct clk *arc_pclk;
|
||
|
struct regmap *secbus2_regmap;
|
||
|
};
|
||
|
|
||
|
static int meson_mx_ao_arc_rproc_start(struct rproc *rproc)
|
||
|
{
|
||
|
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
|
||
|
phys_addr_t translated_sram_addr;
|
||
|
u32 tmp;
|
||
|
int ret;
|
||
|
|
||
|
ret = clk_prepare_enable(priv->arc_pclk);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
tmp = FIELD_PREP(AO_REMAP_REG0_REMAP_AHB_SRAM_BITS_17_14_FOR_ARM_CPU,
|
||
|
priv->sram_pa >> 14);
|
||
|
writel(tmp, priv->remap_base + AO_REMAP_REG0);
|
||
|
|
||
|
/*
|
||
|
* The SRAM content as seen by the ARC core always starts at 0x0
|
||
|
* regardless of the value given here (this was discovered by trial and
|
||
|
* error). For SoCs older than Meson6 we probably have to set
|
||
|
* AO_REMAP_REG1_MOVE_AHB_SRAM_TO_0X0_INSTEAD_OF_DDR to achieve the
|
||
|
* same. (At least) For Meson8 and newer that bit must not be set.
|
||
|
*/
|
||
|
writel(0x0, priv->remap_base + AO_REMAP_REG1);
|
||
|
|
||
|
regmap_update_bits(priv->secbus2_regmap, AO_SECURE_REG0,
|
||
|
AO_SECURE_REG0_AHB_SRAM_BITS_19_12,
|
||
|
FIELD_PREP(AO_SECURE_REG0_AHB_SRAM_BITS_19_12,
|
||
|
priv->sram_pa >> 12));
|
||
|
|
||
|
ret = reset_control_reset(priv->arc_reset);
|
||
|
if (ret) {
|
||
|
clk_disable_unprepare(priv->arc_pclk);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
usleep_range(10, 100);
|
||
|
|
||
|
/*
|
||
|
* Convert from 0xd9000000 to 0xc9000000 as the vendor driver does.
|
||
|
* This only seems to be relevant for the AO_CPU_CNTL register. It is
|
||
|
* unknown why this is needed.
|
||
|
*/
|
||
|
translated_sram_addr = priv->sram_pa - MESON_AO_RPROC_MEMORY_OFFSET;
|
||
|
|
||
|
tmp = FIELD_PREP(AO_CPU_CNTL_AHB_SRAM_BITS_31_20,
|
||
|
translated_sram_addr >> 20);
|
||
|
tmp |= AO_CPU_CNTL_UNKNONWN | AO_CPU_CNTL_RUN;
|
||
|
writel(tmp, priv->cpu_base + AO_CPU_CNTL);
|
||
|
|
||
|
usleep_range(20, 200);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int meson_mx_ao_arc_rproc_stop(struct rproc *rproc)
|
||
|
{
|
||
|
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
|
||
|
|
||
|
writel(AO_CPU_CNTL_HALT, priv->cpu_base + AO_CPU_CNTL);
|
||
|
|
||
|
clk_disable_unprepare(priv->arc_pclk);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void *meson_mx_ao_arc_rproc_da_to_va(struct rproc *rproc, u64 da,
|
||
|
size_t len, bool *is_iomem)
|
||
|
{
|
||
|
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
|
||
|
|
||
|
/* The memory from the ARC core's perspective always starts at 0x0. */
|
||
|
if ((da + len) > priv->sram_size)
|
||
|
return NULL;
|
||
|
|
||
|
return (void *)priv->sram_va + da;
|
||
|
}
|
||
|
|
||
|
static struct rproc_ops meson_mx_ao_arc_rproc_ops = {
|
||
|
.start = meson_mx_ao_arc_rproc_start,
|
||
|
.stop = meson_mx_ao_arc_rproc_stop,
|
||
|
.da_to_va = meson_mx_ao_arc_rproc_da_to_va,
|
||
|
.get_boot_addr = rproc_elf_get_boot_addr,
|
||
|
.load = rproc_elf_load_segments,
|
||
|
.sanity_check = rproc_elf_sanity_check,
|
||
|
};
|
||
|
|
||
|
static int meson_mx_ao_arc_rproc_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct meson_mx_ao_arc_rproc_priv *priv;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
const char *fw_name = NULL;
|
||
|
struct rproc *rproc;
|
||
|
int ret;
|
||
|
|
||
|
device_property_read_string(dev, "firmware-name", &fw_name);
|
||
|
|
||
|
rproc = devm_rproc_alloc(dev, "meson-mx-ao-arc",
|
||
|
&meson_mx_ao_arc_rproc_ops, fw_name,
|
||
|
sizeof(*priv));
|
||
|
if (!rproc)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
rproc->has_iommu = false;
|
||
|
priv = rproc->priv;
|
||
|
|
||
|
priv->sram_pool = of_gen_pool_get(dev->of_node, "sram", 0);
|
||
|
if (!priv->sram_pool) {
|
||
|
dev_err(dev, "Could not get SRAM pool\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
priv->sram_size = gen_pool_avail(priv->sram_pool);
|
||
|
|
||
|
priv->sram_va = gen_pool_alloc(priv->sram_pool, priv->sram_size);
|
||
|
if (!priv->sram_va) {
|
||
|
dev_err(dev, "Could not alloc memory in SRAM pool\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
priv->sram_pa = gen_pool_virt_to_phys(priv->sram_pool, priv->sram_va);
|
||
|
if (priv->sram_pa & ~MESON_AO_RPROC_SRAM_USABLE_BITS) {
|
||
|
dev_err(dev, "SRAM address contains unusable bits\n");
|
||
|
ret = -EINVAL;
|
||
|
goto err_free_genpool;
|
||
|
}
|
||
|
|
||
|
priv->secbus2_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
|
||
|
"amlogic,secbus2");
|
||
|
if (IS_ERR(priv->secbus2_regmap)) {
|
||
|
dev_err(dev, "Failed to find SECBUS2 regmap\n");
|
||
|
ret = PTR_ERR(priv->secbus2_regmap);
|
||
|
goto err_free_genpool;
|
||
|
}
|
||
|
|
||
|
priv->remap_base = devm_platform_ioremap_resource_byname(pdev, "remap");
|
||
|
if (IS_ERR(priv->remap_base)) {
|
||
|
ret = PTR_ERR(priv->remap_base);
|
||
|
goto err_free_genpool;
|
||
|
}
|
||
|
|
||
|
priv->cpu_base = devm_platform_ioremap_resource_byname(pdev, "cpu");
|
||
|
if (IS_ERR(priv->cpu_base)) {
|
||
|
ret = PTR_ERR(priv->cpu_base);
|
||
|
goto err_free_genpool;
|
||
|
}
|
||
|
|
||
|
priv->arc_reset = devm_reset_control_get_exclusive(dev, NULL);
|
||
|
if (IS_ERR(priv->arc_reset)) {
|
||
|
dev_err(dev, "Failed to get ARC reset\n");
|
||
|
ret = PTR_ERR(priv->arc_reset);
|
||
|
goto err_free_genpool;
|
||
|
}
|
||
|
|
||
|
priv->arc_pclk = devm_clk_get(dev, NULL);
|
||
|
if (IS_ERR(priv->arc_pclk)) {
|
||
|
dev_err(dev, "Failed to get the ARC PCLK\n");
|
||
|
ret = PTR_ERR(priv->arc_pclk);
|
||
|
goto err_free_genpool;
|
||
|
}
|
||
|
|
||
|
platform_set_drvdata(pdev, rproc);
|
||
|
|
||
|
ret = rproc_add(rproc);
|
||
|
if (ret)
|
||
|
goto err_free_genpool;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_free_genpool:
|
||
|
gen_pool_free(priv->sram_pool, priv->sram_va, priv->sram_size);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int meson_mx_ao_arc_rproc_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct rproc *rproc = platform_get_drvdata(pdev);
|
||
|
struct meson_mx_ao_arc_rproc_priv *priv = rproc->priv;
|
||
|
|
||
|
rproc_del(rproc);
|
||
|
gen_pool_free(priv->sram_pool, priv->sram_va, priv->sram_size);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id meson_mx_ao_arc_rproc_match[] = {
|
||
|
{ .compatible = "amlogic,meson8-ao-arc" },
|
||
|
{ .compatible = "amlogic,meson8b-ao-arc" },
|
||
|
{ /* sentinel */ }
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, meson_mx_ao_arc_rproc_match);
|
||
|
|
||
|
static struct platform_driver meson_mx_ao_arc_rproc_driver = {
|
||
|
.probe = meson_mx_ao_arc_rproc_probe,
|
||
|
.remove = meson_mx_ao_arc_rproc_remove,
|
||
|
.driver = {
|
||
|
.name = "meson-mx-ao-arc-rproc",
|
||
|
.of_match_table = meson_mx_ao_arc_rproc_match,
|
||
|
},
|
||
|
};
|
||
|
module_platform_driver(meson_mx_ao_arc_rproc_driver);
|
||
|
|
||
|
MODULE_DESCRIPTION("Amlogic Meson6/8/8b/8m2 AO ARC remote processor driver");
|
||
|
MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
|
||
|
MODULE_LICENSE("GPL v2");
|