diff options
author | Arnd Bergmann <arnd@arndb.de> | 2020-05-16 00:04:40 +0300 |
---|---|---|
committer | Arnd Bergmann <arnd@arndb.de> | 2020-05-16 00:04:40 +0300 |
commit | a875e0e5a2a307b4c35127f225d0b0b4be2bc75e (patch) | |
tree | 835c0c615d5133650849781b6b79c326b220be0c /drivers/bus | |
parent | 5df04698bd39c71243220be353aa9def4e1bacd1 (diff) | |
parent | 848685c25da99d871bbd87369f3c3d6eead661ac (diff) | |
download | linux-a875e0e5a2a307b4c35127f225d0b0b4be2bc75e.tar.xz |
Merge tag 'vexpress-modules-for-soc-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/robh/linux into arm/soc
VExpress modularization
This series enables building various Versatile Express platform drivers
as modules. The primary target is the Fast Model FVP which is supported
in Android. As Android is moving towards their GKI, or generic kernel,
the hardware support has to be in modules. Currently ARCH_VEXPRESS
enables several built-in only drivers. Some of these are needed, but
some are only needed for older 32-bit VExpress platforms and can just
be disabled.
* tag 'vexpress-modules-for-soc-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/robh/linux:
ARM: vexpress: Don't select VEXPRESS_CONFIG
bus: vexpress-config: Support building as module
vexpress: Move setting master site to vexpress-config bus
bus: vexpress-config: simplify config bus probing
bus: vexpress-config: Merge vexpress-syscfg into vexpress-config
mfd: vexpress-sysreg: Support building as a module
mfd: vexpress-sysreg: Use devres API variants
mfd: vexpress-sysreg: Drop unused syscon child devices
mfd: vexpress-sysreg: Drop selecting CONFIG_CLKSRC_MMIO
clk: vexpress-osc: Support building as a module
clk: vexpress-osc: Use the devres clock API variants
clk: versatile: Only enable SP810 on 32-bit by default
clk: versatile: Rework kconfig structure
amba: Retry adding deferred devices at late_initcall
arm64: vexpress: Don't select CONFIG_POWER_RESET_VEXPRESS
ARM: vexpress: Move vexpress_flags_set() into arch code
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Diffstat (limited to 'drivers/bus')
-rw-r--r-- | drivers/bus/Kconfig | 2 | ||||
-rw-r--r-- | drivers/bus/vexpress-config.c | 354 |
2 files changed, 286 insertions, 70 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index d4c8898868fc..2d73d4b6c9a6 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -192,7 +192,7 @@ config UNIPHIER_SYSTEM_BUS needed to use on-board devices connected to UniPhier SoCs. config VEXPRESS_CONFIG - bool "Versatile Express configuration bus" + tristate "Versatile Express configuration bus" default y if ARCH_VEXPRESS depends on ARM || ARM64 depends on OF diff --git a/drivers/bus/vexpress-config.c b/drivers/bus/vexpress-config.c index ff70575b2db6..a58ac0c8e282 100644 --- a/drivers/bus/vexpress-config.c +++ b/drivers/bus/vexpress-config.c @@ -6,10 +6,61 @@ #include <linux/err.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> #include <linux/of.h> +#include <linux/platform_device.h> #include <linux/of_device.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> #include <linux/vexpress.h> +#define SYS_MISC 0x0 +#define SYS_MISC_MASTERSITE (1 << 14) + +#define SYS_PROCID0 0x24 +#define SYS_PROCID1 0x28 +#define SYS_HBI_MASK 0xfff +#define SYS_PROCIDx_HBI_SHIFT 0 + +#define SYS_CFGDATA 0x40 + +#define SYS_CFGCTRL 0x44 +#define SYS_CFGCTRL_START (1 << 31) +#define SYS_CFGCTRL_WRITE (1 << 30) +#define SYS_CFGCTRL_DCC(n) (((n) & 0xf) << 26) +#define SYS_CFGCTRL_FUNC(n) (((n) & 0x3f) << 20) +#define SYS_CFGCTRL_SITE(n) (((n) & 0x3) << 16) +#define SYS_CFGCTRL_POSITION(n) (((n) & 0xf) << 12) +#define SYS_CFGCTRL_DEVICE(n) (((n) & 0xfff) << 0) + +#define SYS_CFGSTAT 0x48 +#define SYS_CFGSTAT_ERR (1 << 1) +#define SYS_CFGSTAT_COMPLETE (1 << 0) + +#define VEXPRESS_SITE_MB 0 +#define VEXPRESS_SITE_DB1 1 +#define VEXPRESS_SITE_DB2 2 +#define VEXPRESS_SITE_MASTER 0xf + +struct vexpress_syscfg { + struct device *dev; + void __iomem *base; + struct list_head funcs; +}; + +struct vexpress_syscfg_func { + struct list_head list; + struct vexpress_syscfg *syscfg; + struct regmap *regmap; + int num_templates; + u32 template[]; /* Keep it last! */ +}; + +struct vexpress_config_bridge_ops { + struct regmap * (*regmap_init)(struct device *dev, void *context); + void (*regmap_exit)(struct regmap *regmap, void *context); +}; struct vexpress_config_bridge { struct vexpress_config_bridge_ops *ops; @@ -18,26 +69,20 @@ struct vexpress_config_bridge { static DEFINE_MUTEX(vexpress_config_mutex); -static struct class *vexpress_config_class; static u32 vexpress_config_site_master = VEXPRESS_SITE_MASTER; -void vexpress_config_set_master(u32 site) +static void vexpress_config_set_master(u32 site) { vexpress_config_site_master = site; } -u32 vexpress_config_get_master(void) -{ - return vexpress_config_site_master; -} - -void vexpress_config_lock(void *arg) +static void vexpress_config_lock(void *arg) { mutex_lock(&vexpress_config_mutex); } -void vexpress_config_unlock(void *arg) +static void vexpress_config_unlock(void *arg) { mutex_unlock(&vexpress_config_mutex); } @@ -59,7 +104,7 @@ static void vexpress_config_find_prop(struct device_node *node, } } -int vexpress_config_get_topo(struct device_node *node, u32 *site, +static int vexpress_config_get_topo(struct device_node *node, u32 *site, u32 *position, u32 *dcc) { vexpress_config_find_prop(node, "arm,vexpress,site", site); @@ -88,9 +133,6 @@ struct regmap *devm_regmap_init_vexpress_config(struct device *dev) struct regmap *regmap; struct regmap **res; - if (WARN_ON(dev->parent->class != vexpress_config_class)) - return ERR_PTR(-ENODEV); - bridge = dev_get_drvdata(dev->parent); if (WARN_ON(!bridge)) return ERR_PTR(-EINVAL); @@ -113,91 +155,265 @@ struct regmap *devm_regmap_init_vexpress_config(struct device *dev) } EXPORT_SYMBOL_GPL(devm_regmap_init_vexpress_config); -struct device *vexpress_config_bridge_register(struct device *parent, - struct vexpress_config_bridge_ops *ops, void *context) +static int vexpress_syscfg_exec(struct vexpress_syscfg_func *func, + int index, bool write, u32 *data) { - struct device *dev; - struct vexpress_config_bridge *bridge; + struct vexpress_syscfg *syscfg = func->syscfg; + u32 command, status; + int tries; + long timeout; - if (!vexpress_config_class) { - vexpress_config_class = class_create(THIS_MODULE, - "vexpress-config"); - if (IS_ERR(vexpress_config_class)) - return (void *)vexpress_config_class; + if (WARN_ON(index >= func->num_templates)) + return -EINVAL; + + command = readl(syscfg->base + SYS_CFGCTRL); + if (WARN_ON(command & SYS_CFGCTRL_START)) + return -EBUSY; + + command = func->template[index]; + command |= SYS_CFGCTRL_START; + command |= write ? SYS_CFGCTRL_WRITE : 0; + + /* Use a canary for reads */ + if (!write) + *data = 0xdeadbeef; + + dev_dbg(syscfg->dev, "func %p, command %x, data %x\n", + func, command, *data); + writel(*data, syscfg->base + SYS_CFGDATA); + writel(0, syscfg->base + SYS_CFGSTAT); + writel(command, syscfg->base + SYS_CFGCTRL); + mb(); + + /* The operation can take ages... Go to sleep, 100us initially */ + tries = 100; + timeout = 100; + do { + if (!irqs_disabled()) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(usecs_to_jiffies(timeout)); + if (signal_pending(current)) + return -EINTR; + } else { + udelay(timeout); + } + + status = readl(syscfg->base + SYS_CFGSTAT); + if (status & SYS_CFGSTAT_ERR) + return -EFAULT; + + if (timeout > 20) + timeout -= 20; + } while (--tries && !(status & SYS_CFGSTAT_COMPLETE)); + if (WARN_ON_ONCE(!tries)) + return -ETIMEDOUT; + + if (!write) { + *data = readl(syscfg->base + SYS_CFGDATA); + dev_dbg(syscfg->dev, "func %p, read data %x\n", func, *data); } - dev = device_create(vexpress_config_class, parent, 0, - NULL, "%s.bridge", dev_name(parent)); + return 0; +} + +static int vexpress_syscfg_read(void *context, unsigned int index, + unsigned int *val) +{ + struct vexpress_syscfg_func *func = context; + + return vexpress_syscfg_exec(func, index, false, val); +} + +static int vexpress_syscfg_write(void *context, unsigned int index, + unsigned int val) +{ + struct vexpress_syscfg_func *func = context; + + return vexpress_syscfg_exec(func, index, true, &val); +} + +static struct regmap_config vexpress_syscfg_regmap_config = { + .lock = vexpress_config_lock, + .unlock = vexpress_config_unlock, + .reg_bits = 32, + .val_bits = 32, + .reg_read = vexpress_syscfg_read, + .reg_write = vexpress_syscfg_write, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + - if (IS_ERR(dev)) - return dev; +static struct regmap *vexpress_syscfg_regmap_init(struct device *dev, + void *context) +{ + int err; + struct vexpress_syscfg *syscfg = context; + struct vexpress_syscfg_func *func; + struct property *prop; + const __be32 *val = NULL; + __be32 energy_quirk[4]; + int num; + u32 site, position, dcc; + int i; + + err = vexpress_config_get_topo(dev->of_node, &site, + &position, &dcc); + if (err) + return ERR_PTR(err); + + prop = of_find_property(dev->of_node, + "arm,vexpress-sysreg,func", NULL); + if (!prop) + return ERR_PTR(-EINVAL); - bridge = devm_kmalloc(dev, sizeof(*bridge), GFP_KERNEL); - if (!bridge) { - put_device(dev); - device_unregister(dev); + num = prop->length / sizeof(u32) / 2; + val = prop->value; + + /* + * "arm,vexpress-energy" function used to be described + * by its first device only, now it requires both + */ + if (num == 1 && of_device_is_compatible(dev->of_node, + "arm,vexpress-energy")) { + num = 2; + energy_quirk[0] = *val; + energy_quirk[2] = *val++; + energy_quirk[1] = *val; + energy_quirk[3] = cpu_to_be32(be32_to_cpup(val) + 1); + val = energy_quirk; + } + + func = kzalloc(struct_size(func, template, num), GFP_KERNEL); + if (!func) return ERR_PTR(-ENOMEM); + + func->syscfg = syscfg; + func->num_templates = num; + + for (i = 0; i < num; i++) { + u32 function, device; + + function = be32_to_cpup(val++); + device = be32_to_cpup(val++); + + dev_dbg(dev, "func %p: %u/%u/%u/%u/%u\n", + func, site, position, dcc, + function, device); + + func->template[i] = SYS_CFGCTRL_DCC(dcc); + func->template[i] |= SYS_CFGCTRL_SITE(site); + func->template[i] |= SYS_CFGCTRL_POSITION(position); + func->template[i] |= SYS_CFGCTRL_FUNC(function); + func->template[i] |= SYS_CFGCTRL_DEVICE(device); } - bridge->ops = ops; - bridge->context = context; - dev_set_drvdata(dev, bridge); + vexpress_syscfg_regmap_config.max_register = num - 1; - dev_dbg(parent, "Registered bridge '%s', parent node %p\n", - dev_name(dev), parent->of_node); + func->regmap = regmap_init(dev, NULL, func, + &vexpress_syscfg_regmap_config); - return dev; -} + if (IS_ERR(func->regmap)) { + void *err = func->regmap; + kfree(func); + return err; + } + + list_add(&func->list, &syscfg->funcs); -static int vexpress_config_node_match(struct device *dev, const void *data) + return func->regmap; +} + +static void vexpress_syscfg_regmap_exit(struct regmap *regmap, void *context) { - const struct device_node *node = data; + struct vexpress_syscfg *syscfg = context; + struct vexpress_syscfg_func *func, *tmp; - dev_dbg(dev, "Parent node %p, looking for %p\n", - dev->parent->of_node, node); + regmap_exit(regmap); - return dev->parent->of_node == node; + list_for_each_entry_safe(func, tmp, &syscfg->funcs, list) { + if (func->regmap == regmap) { + list_del(&syscfg->funcs); + kfree(func); + break; + } + } } -static int vexpress_config_populate(struct device_node *node) +static struct vexpress_config_bridge_ops vexpress_syscfg_bridge_ops = { + .regmap_init = vexpress_syscfg_regmap_init, + .regmap_exit = vexpress_syscfg_regmap_exit, +}; + + +static int vexpress_syscfg_probe(struct platform_device *pdev) { - struct device_node *bridge; - struct device *parent; - int ret; + struct vexpress_syscfg *syscfg; + struct resource *res; + struct vexpress_config_bridge *bridge; + struct device_node *node; + int master; + u32 dt_hbi; + + syscfg = devm_kzalloc(&pdev->dev, sizeof(*syscfg), GFP_KERNEL); + if (!syscfg) + return -ENOMEM; + syscfg->dev = &pdev->dev; + INIT_LIST_HEAD(&syscfg->funcs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + syscfg->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(syscfg->base)) + return PTR_ERR(syscfg->base); - bridge = of_parse_phandle(node, "arm,vexpress,config-bridge", 0); + bridge = devm_kmalloc(&pdev->dev, sizeof(*bridge), GFP_KERNEL); if (!bridge) - return -EINVAL; + return -ENOMEM; - parent = class_find_device(vexpress_config_class, NULL, bridge, - vexpress_config_node_match); - of_node_put(bridge); - if (WARN_ON(!parent)) - return -ENODEV; + bridge->ops = &vexpress_syscfg_bridge_ops; + bridge->context = syscfg; - ret = of_platform_populate(node, NULL, NULL, parent); + dev_set_drvdata(&pdev->dev, bridge); - put_device(parent); + master = readl(syscfg->base + SYS_MISC) & SYS_MISC_MASTERSITE ? + VEXPRESS_SITE_DB2 : VEXPRESS_SITE_DB1; + vexpress_config_set_master(master); - return ret; -} + /* Confirm board type against DT property, if available */ + if (of_property_read_u32(of_root, "arm,hbi", &dt_hbi) == 0) { + u32 id = readl(syscfg->base + (master == VEXPRESS_SITE_DB1 ? + SYS_PROCID0 : SYS_PROCID1)); + u32 hbi = (id >> SYS_PROCIDx_HBI_SHIFT) & SYS_HBI_MASK; -static int __init vexpress_config_init(void) -{ - int err = 0; - struct device_node *node; + if (WARN_ON(dt_hbi != hbi)) + dev_warn(&pdev->dev, "DT HBI (%x) is not matching hardware (%x)!\n", + dt_hbi, hbi); + } - /* Need the config devices early, before the "normal" devices... */ for_each_compatible_node(node, NULL, "arm,vexpress,config-bus") { - err = vexpress_config_populate(node); - if (err) { - of_node_put(node); - break; - } + struct device_node *bridge_np; + + bridge_np = of_parse_phandle(node, "arm,vexpress,config-bridge", 0); + if (bridge_np != pdev->dev.parent->of_node) + continue; + + of_platform_populate(node, NULL, NULL, &pdev->dev); } - return err; + return 0; } -postcore_initcall(vexpress_config_init); +static const struct platform_device_id vexpress_syscfg_id_table[] = { + { "vexpress-syscfg", }, + {}, +}; +MODULE_DEVICE_TABLE(platform, vexpress_syscfg_id_table); + +static struct platform_driver vexpress_syscfg_driver = { + .driver.name = "vexpress-syscfg", + .id_table = vexpress_syscfg_id_table, + .probe = vexpress_syscfg_probe, +}; +module_platform_driver(vexpress_syscfg_driver); +MODULE_LICENSE("GPL v2"); |