diff options
Diffstat (limited to 'drivers/soc/sunxi')
| -rw-r--r-- | drivers/soc/sunxi/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/soc/sunxi/Makefile | 3 | ||||
| -rw-r--r-- | drivers/soc/sunxi/sunxi_mbus.c | 127 | ||||
| -rw-r--r-- | drivers/soc/sunxi/sunxi_sram.c | 502 |
4 files changed, 653 insertions, 0 deletions
diff --git a/drivers/soc/sunxi/Kconfig b/drivers/soc/sunxi/Kconfig new file mode 100644 index 000000000..8aecbc9b1 --- /dev/null +++ b/drivers/soc/sunxi/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Allwinner sunXi SoC drivers +# + +config SUNXI_MBUS + bool + default ARCH_SUNXI + depends on ARM || ARM64 + help + Say y to enable the fixups needed to support the Allwinner + MBUS DMA quirks. + +config SUNXI_SRAM + bool + default ARCH_SUNXI + select REGMAP_MMIO + help + Say y here to enable the SRAM controller support. This + device is responsible on mapping the SRAM in the sunXi SoCs + whether to the CPU/DMA, or to the devices. diff --git a/drivers/soc/sunxi/Makefile b/drivers/soc/sunxi/Makefile new file mode 100644 index 000000000..549159571 --- /dev/null +++ b/drivers/soc/sunxi/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SUNXI_MBUS) += sunxi_mbus.o +obj-$(CONFIG_SUNXI_SRAM) += sunxi_sram.o diff --git a/drivers/soc/sunxi/sunxi_mbus.c b/drivers/soc/sunxi/sunxi_mbus.c new file mode 100644 index 000000000..8bc5f62ff --- /dev/null +++ b/drivers/soc/sunxi/sunxi_mbus.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2020 Maxime Ripard <maxime@cerno.tech> */ + +#include <linux/device.h> +#include <linux/dma-map-ops.h> +#include <linux/init.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +static const char * const sunxi_mbus_devices[] = { + /* + * The display engine virtual devices are not strictly speaking + * connected to the MBUS, but since DRM will perform all the + * memory allocations and DMA operations through that device, we + * need to have the quirk on those devices too. + */ + "allwinner,sun4i-a10-display-engine", + "allwinner,sun5i-a10s-display-engine", + "allwinner,sun5i-a13-display-engine", + "allwinner,sun6i-a31-display-engine", + "allwinner,sun6i-a31s-display-engine", + "allwinner,sun7i-a20-display-engine", + "allwinner,sun8i-a23-display-engine", + "allwinner,sun8i-a33-display-engine", + "allwinner,sun9i-a80-display-engine", + + /* + * And now we have the regular devices connected to the MBUS + * (that we know of). + */ + "allwinner,sun4i-a10-csi1", + "allwinner,sun4i-a10-display-backend", + "allwinner,sun4i-a10-display-frontend", + "allwinner,sun4i-a10-video-engine", + "allwinner,sun5i-a13-display-backend", + "allwinner,sun5i-a13-video-engine", + "allwinner,sun6i-a31-csi", + "allwinner,sun6i-a31-display-backend", + "allwinner,sun7i-a20-csi0", + "allwinner,sun7i-a20-display-backend", + "allwinner,sun7i-a20-display-frontend", + "allwinner,sun7i-a20-video-engine", + "allwinner,sun8i-a23-display-backend", + "allwinner,sun8i-a23-display-frontend", + "allwinner,sun8i-a33-display-backend", + "allwinner,sun8i-a33-display-frontend", + "allwinner,sun8i-a33-video-engine", + "allwinner,sun8i-a83t-csi", + "allwinner,sun8i-h3-csi", + "allwinner,sun8i-h3-video-engine", + "allwinner,sun8i-v3s-csi", + "allwinner,sun9i-a80-display-backend", + "allwinner,sun50i-a64-csi", + "allwinner,sun50i-a64-video-engine", + "allwinner,sun50i-h5-video-engine", + NULL, +}; + +static int sunxi_mbus_notifier(struct notifier_block *nb, + unsigned long event, void *__dev) +{ + struct device *dev = __dev; + int ret; + + if (event != BUS_NOTIFY_ADD_DEVICE) + return NOTIFY_DONE; + + /* + * Only the devices that need a large memory bandwidth do DMA + * directly over the memory bus (called MBUS), instead of going + * through the regular system bus. + */ + if (!of_device_compatible_match(dev->of_node, sunxi_mbus_devices)) + return NOTIFY_DONE; + + /* + * Devices with an interconnects property have the MBUS + * relationship described in their DT and dealt with by + * of_dma_configure, so we can just skip them. + * + * Older DTs or SoCs who are not clearly understood need to set + * that DMA offset though. + */ + if (of_property_present(dev->of_node, "interconnects")) + return NOTIFY_DONE; + + ret = dma_direct_set_offset(dev, PHYS_OFFSET, 0, SZ_4G); + if (ret) + dev_err(dev, "Couldn't setup our DMA offset: %d\n", ret); + + return NOTIFY_DONE; +} + +static struct notifier_block sunxi_mbus_nb = { + .notifier_call = sunxi_mbus_notifier, +}; + +static const char * const sunxi_mbus_platforms[] __initconst = { + "allwinner,sun4i-a10", + "allwinner,sun5i-a10s", + "allwinner,sun5i-a13", + "allwinner,sun6i-a31", + "allwinner,sun7i-a20", + "allwinner,sun8i-a23", + "allwinner,sun8i-a33", + "allwinner,sun8i-a83t", + "allwinner,sun8i-h3", + "allwinner,sun8i-r40", + "allwinner,sun8i-v3", + "allwinner,sun8i-v3s", + "allwinner,sun9i-a80", + "allwinner,sun50i-a64", + "allwinner,sun50i-h5", + "nextthing,gr8", + NULL, +}; + +static int __init sunxi_mbus_init(void) +{ + if (!of_machine_compatible_match(sunxi_mbus_platforms)) + return 0; + + bus_register_notifier(&platform_bus_type, &sunxi_mbus_nb); + return 0; +} +arch_initcall(sunxi_mbus_init); diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c new file mode 100644 index 000000000..2ccaeacf8 --- /dev/null +++ b/drivers/soc/sunxi/sunxi_sram.c @@ -0,0 +1,502 @@ +/* + * Allwinner SoCs SRAM Controller Driver + * + * Copyright (C) 2015 Maxime Ripard + * + * Author: Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/limits.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <linux/soc/sunxi/sunxi_sram.h> + +struct sunxi_sram_func { + char *func; + u8 val; + u32 reg_val; +}; + +struct sunxi_sram_data { + char *name; + u8 reg; + u8 offset; + u8 width; + const struct sunxi_sram_func *func; +}; + +struct sunxi_sram_desc { + struct sunxi_sram_data data; + u8 claim_cnt; +}; + +#define SUNXI_SRAM_MAP(_reg_val, _val, _func) \ + { \ + .func = _func, \ + .val = _val, \ + .reg_val = _reg_val, \ + } + +#define SUNXI_SRAM_DATA(_name, _reg, _off, _width, ...) \ + { \ + .name = _name, \ + .reg = _reg, \ + .offset = _off, \ + .width = _width, \ + .func = (const struct sunxi_sram_func[]){ \ + __VA_ARGS__, { } }, \ + } + +static struct sunxi_sram_desc sun4i_a10_sram_a3_a4 = { + .data = SUNXI_SRAM_DATA("A3-A4", 0x4, 0x4, 2, + SUNXI_SRAM_MAP(0, 0, "cpu"), + SUNXI_SRAM_MAP(1, 1, "emac")), +}; + +static struct sunxi_sram_desc sun4i_a10_sram_c1 = { + .data = SUNXI_SRAM_DATA("C1", 0x0, 0x0, 31, + SUNXI_SRAM_MAP(0, 0, "cpu"), + SUNXI_SRAM_MAP(0x7fffffff, 1, "ve")), +}; + +static struct sunxi_sram_desc sun4i_a10_sram_d = { + .data = SUNXI_SRAM_DATA("D", 0x4, 0x0, 1, + SUNXI_SRAM_MAP(0, 0, "cpu"), + SUNXI_SRAM_MAP(1, 1, "usb-otg")), +}; + +static struct sunxi_sram_desc sun50i_a64_sram_c = { + .data = SUNXI_SRAM_DATA("C", 0x4, 24, 1, + SUNXI_SRAM_MAP(1, 0, "cpu"), + SUNXI_SRAM_MAP(0, 1, "de2")), +}; + +static struct sunxi_sram_desc sun50i_h616_ve_sram = { + .data = SUNXI_SRAM_DATA("VE", 0x0, 0, 1, + SUNXI_SRAM_MAP(1, 0, "cpu"), + SUNXI_SRAM_MAP(0, 1, "ve")), +}; + +static const struct of_device_id sunxi_sram_dt_ids[] = { + { + .compatible = "allwinner,sun4i-a10-sram-a3-a4", + .data = &sun4i_a10_sram_a3_a4.data, + }, + { + .compatible = "allwinner,sun4i-a10-sram-c1", + .data = &sun4i_a10_sram_c1.data, + }, + { + .compatible = "allwinner,sun4i-a10-sram-d", + .data = &sun4i_a10_sram_d.data, + }, + { + .compatible = "allwinner,sun50i-a64-sram-c", + .data = &sun50i_a64_sram_c.data, + }, + { + .compatible = "allwinner,sun50i-h616-ve-sram", + .data = &sun50i_h616_ve_sram.data, + }, + {} +}; + +static struct device *sram_dev; +static DEFINE_SPINLOCK(sram_lock); +static void __iomem *base; + +static int sunxi_sram_show(struct seq_file *s, void *data) +{ + struct device_node *sram_node, *section_node; + const struct sunxi_sram_data *sram_data; + const struct of_device_id *match; + const struct sunxi_sram_func *func; + const __be32 *sram_addr_p, *section_addr_p; + u32 val; + + seq_puts(s, "Allwinner sunXi SRAM\n"); + seq_puts(s, "--------------------\n\n"); + + for_each_child_of_node(sram_dev->of_node, sram_node) { + if (!of_device_is_compatible(sram_node, "mmio-sram")) + continue; + + sram_addr_p = of_get_address(sram_node, 0, NULL, NULL); + + seq_printf(s, "sram@%08x\n", + be32_to_cpu(*sram_addr_p)); + + for_each_child_of_node(sram_node, section_node) { + match = of_match_node(sunxi_sram_dt_ids, section_node); + if (!match) + continue; + sram_data = match->data; + + section_addr_p = of_get_address(section_node, 0, + NULL, NULL); + + seq_printf(s, "\tsection@%04x\t(%s)\n", + be32_to_cpu(*section_addr_p), + sram_data->name); + + val = readl(base + sram_data->reg); + val >>= sram_data->offset; + val &= GENMASK(sram_data->width - 1, 0); + + for (func = sram_data->func; func->func; func++) { + seq_printf(s, "\t\t%s%c\n", func->func, + func->reg_val == val ? + '*' : ' '); + } + } + + seq_puts(s, "\n"); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(sunxi_sram); + +static inline struct sunxi_sram_desc *to_sram_desc(const struct sunxi_sram_data *data) +{ + return container_of(data, struct sunxi_sram_desc, data); +} + +static const struct sunxi_sram_data *sunxi_sram_get_match(struct device_node *np, u8 val, + unsigned int *reg_value) +{ + const struct of_device_id *match; + const struct sunxi_sram_data *data; + const struct sunxi_sram_func *func; + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + match = of_match_node(sunxi_sram_dt_ids, np); + if (!match) + return ERR_PTR(-ENODEV); + + data = match->data; + if (!data) + return ERR_PTR(-EINVAL); + + for (func = data->func; func->func; func++) + if (val == func->val) + break; + + if (!func->func) + return ERR_PTR(-EINVAL); + + if (reg_value) + *reg_value = func->reg_val; + + return data; +} + +#define SUNXI_SRAM_PROP "allwinner,sram" +#define SUNXI_SRAM_CELLS 1 + +static int sunxi_sram_claim_one(struct device_node *np, u8 arg) +{ + const struct sunxi_sram_data *sram_data; + struct sunxi_sram_desc *sram_desc; + unsigned int device; + u32 val, mask; + + sram_data = sunxi_sram_get_match(np, arg, &device); + if (IS_ERR(sram_data)) + return PTR_ERR(sram_data); + + sram_desc = to_sram_desc(sram_data); + + spin_lock(&sram_lock); + + if (sram_desc->claim_cnt) { + if (!WARN_ON(sram_desc->claim_cnt == U8_MAX)) + sram_desc->claim_cnt++; + } else { + mask = GENMASK(sram_data->offset + sram_data->width - 1, + sram_data->offset); + val = readl(base + sram_data->reg); + val &= ~mask; + writel(val | ((device << sram_data->offset) & mask), + base + sram_data->reg); + + sram_desc->claim_cnt++; + } + + spin_unlock(&sram_lock); + + return 0; +} + +static void sunxi_sram_release_one(struct device_node *np, u8 arg) +{ + const struct sunxi_sram_data *sram_data; + struct sunxi_sram_desc *sram_desc; + + sram_data = sunxi_sram_get_match(np, arg, NULL); + if (IS_ERR(sram_data)) + return; + + sram_desc = to_sram_desc(sram_data); + + spin_lock(&sram_lock); + if (!WARN_ON(sram_desc->claim_cnt == 0)) + sram_desc->claim_cnt--; + spin_unlock(&sram_lock); +} + +int sunxi_sram_claim(struct device *dev) +{ + struct of_phandle_iterator it; + int err; + int count = 0; + + if (IS_ERR(base)) + return PTR_ERR(base); + + if (!base) + return -EPROBE_DEFER; + + if (!dev || !dev->of_node) + return -EINVAL; + + of_for_each_phandle(&it, err, dev->of_node, SUNXI_SRAM_PROP, + NULL, SUNXI_SRAM_CELLS) { + u32 args[SUNXI_SRAM_CELLS]; + + of_phandle_iterator_args(&it, args, SUNXI_SRAM_CELLS); + + err = sunxi_sram_claim_one(it.node, args[0]); + if (err) + goto err; + + count++; + } + + if (count == 0) + return -ENOENT; + + return 0; + +err: + while (count--) { + struct of_phandle_args args; + + of_parse_phandle_with_fixed_args(dev->of_node, SUNXI_SRAM_PROP, + SUNXI_SRAM_CELLS, count, &args); + sunxi_sram_release_one(args.np, args.args[0]); + } + + return err; +} +EXPORT_SYMBOL(sunxi_sram_claim); + +void sunxi_sram_release(struct device *dev) +{ + struct of_phandle_iterator it; + int err; + + if (!dev || !dev->of_node) + return; + + of_for_each_phandle(&it, err, dev->of_node, SUNXI_SRAM_PROP, + NULL, SUNXI_SRAM_CELLS) { + u32 args[SUNXI_SRAM_CELLS]; + + of_phandle_iterator_args(&it, args, SUNXI_SRAM_CELLS); + + sunxi_sram_release_one(it.node, args[0]); + } +} +EXPORT_SYMBOL(sunxi_sram_release); + +struct sunxi_sramc_variant { + int num_emac_clocks; + bool has_ldo_ctrl; + bool has_ths_offset; +}; + +static const struct sunxi_sramc_variant sun4i_a10_sramc_variant = { + /* Nothing special */ +}; + +static const struct sunxi_sramc_variant sun8i_h3_sramc_variant = { + .num_emac_clocks = 1, +}; + +static const struct sunxi_sramc_variant sun20i_d1_sramc_variant = { + .num_emac_clocks = 1, + .has_ldo_ctrl = true, +}; + +static const struct sunxi_sramc_variant sun50i_a64_sramc_variant = { + .num_emac_clocks = 1, +}; + +static const struct sunxi_sramc_variant sun50i_h616_sramc_variant = { + .num_emac_clocks = 2, + .has_ths_offset = true, +}; + +static const struct sunxi_sramc_variant sun55i_a523_sramc_variant = { + .num_emac_clocks = 2, +}; + +#define SUNXI_SRAM_THS_OFFSET_REG 0x0 +#define SUNXI_SRAM_EMAC_CLOCK_REG 0x30 +#define SUNXI_SYS_LDO_CTRL_REG 0x150 + +static bool sunxi_sram_regmap_accessible_reg(struct device *dev, + unsigned int reg) +{ + const struct sunxi_sramc_variant *variant = dev_get_drvdata(dev); + + if (reg == SUNXI_SRAM_THS_OFFSET_REG && variant->has_ths_offset) + return true; + if (reg >= SUNXI_SRAM_EMAC_CLOCK_REG && + reg < SUNXI_SRAM_EMAC_CLOCK_REG + variant->num_emac_clocks * 4) + return true; + if (reg == SUNXI_SYS_LDO_CTRL_REG && variant->has_ldo_ctrl) + return true; + + return false; +} + +static void sunxi_sram_lock(void *_lock) +{ + spinlock_t *lock = _lock; + + spin_lock(lock); +} + +static void sunxi_sram_unlock(void *_lock) +{ + spinlock_t *lock = _lock; + + spin_unlock(lock); +} + +static const struct regmap_config sunxi_sram_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + /* last defined register */ + .max_register = SUNXI_SYS_LDO_CTRL_REG, + /* other devices have no business accessing other registers */ + .readable_reg = sunxi_sram_regmap_accessible_reg, + .writeable_reg = sunxi_sram_regmap_accessible_reg, + .lock = sunxi_sram_lock, + .unlock = sunxi_sram_unlock, + .lock_arg = &sram_lock, +}; + +static int __init sunxi_sram_probe(struct platform_device *pdev) +{ + const struct sunxi_sramc_variant *variant; + struct device *dev = &pdev->dev; + struct regmap *regmap; + int ret; + + sram_dev = &pdev->dev; + + variant = of_device_get_match_data(&pdev->dev); + if (!variant) + return -EINVAL; + + dev_set_drvdata(dev, (struct sunxi_sramc_variant *)variant); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + if (variant->num_emac_clocks || variant->has_ldo_ctrl) { + regmap = devm_regmap_init_mmio(dev, base, &sunxi_sram_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = of_syscon_register_regmap(dev->of_node, regmap); + if (ret) + return ret; + } + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + debugfs_create_file("sram", 0444, NULL, NULL, &sunxi_sram_fops); + + return 0; +} + +static const struct of_device_id sunxi_sram_dt_match[] = { + { + .compatible = "allwinner,sun4i-a10-sram-controller", + .data = &sun4i_a10_sramc_variant, + }, + { + .compatible = "allwinner,sun4i-a10-system-control", + .data = &sun4i_a10_sramc_variant, + }, + { + .compatible = "allwinner,sun5i-a13-system-control", + .data = &sun4i_a10_sramc_variant, + }, + { + .compatible = "allwinner,sun8i-a23-system-control", + .data = &sun4i_a10_sramc_variant, + }, + { + .compatible = "allwinner,sun8i-h3-system-control", + .data = &sun8i_h3_sramc_variant, + }, + { + .compatible = "allwinner,sun20i-d1-system-control", + .data = &sun20i_d1_sramc_variant, + }, + { + .compatible = "allwinner,sun50i-a64-sram-controller", + .data = &sun50i_a64_sramc_variant, + }, + { + .compatible = "allwinner,sun50i-a64-system-control", + .data = &sun50i_a64_sramc_variant, + }, + { + .compatible = "allwinner,sun50i-h5-system-control", + .data = &sun50i_a64_sramc_variant, + }, + { + .compatible = "allwinner,sun50i-h616-system-control", + .data = &sun50i_h616_sramc_variant, + }, + { + .compatible = "allwinner,sun55i-a523-system-control", + .data = &sun55i_a523_sramc_variant, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, sunxi_sram_dt_match); + +static struct platform_driver sunxi_sram_driver = { + .driver = { + .name = "sunxi-sram", + .of_match_table = sunxi_sram_dt_match, + }, +}; +builtin_platform_driver_probe(sunxi_sram_driver, sunxi_sram_probe); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner sunXi SRAM Controller Driver"); |
