| // 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"); |