diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 18:42:30 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 18:42:30 -0800 |
commit | 9638685e32af961943b679fcb72d4ddd458eb18f (patch) | |
tree | 83cea0db18256448e2eaa9aec3bdbd3b89f26646 /drivers/bus | |
parent | 03d7d12415e3a4791994e566f1245838bc505c6b (diff) | |
parent | ce96cb7386a57b270648f9ba6003065329a26bd3 (diff) | |
download | linux-stable-9638685e32af961943b679fcb72d4ddd458eb18f.tar.gz linux-stable-9638685e32af961943b679fcb72d4ddd458eb18f.tar.bz2 linux-stable-9638685e32af961943b679fcb72d4ddd458eb18f.zip |
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC driver updates from Olof Johansson:
"Driver updates for ARM SoCs. Some for SoC-family code under
drivers/soc, but also some other driver updates that don't belong
anywhere else. We also bring in the drivers/reset code through
arm-soc.
Some of the larger updates:
- Qualcomm support for SMEM, SMSM, SMP2P. All used to communicate
with other parts of the chip/board on these platforms, all
proprietary protocols that don't fit into other subsystems and live
in drivers/soc for now.
- System bus driver for UniPhier
- Driver for the TI Wakeup M3 IPC device
- Power management for Raspberry PI
+ Again a bunch of other smaller updates and patches"
* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (38 commits)
bus: uniphier: allow only built-in driver
ARM: bcm2835: clarify RASPBERRYPI_FIRMWARE dependency
MAINTAINERS: Drop Kumar Gala from QCOM
bus: uniphier-system-bus: add UniPhier System Bus driver
ARM: bcm2835: add rpi power domain driver
dt-bindings: add rpi power domain driver bindings
ARM: bcm2835: Define two new packets from the latest firmware.
drivers/soc: make mediatek/mtk-scpsys.c explicitly non-modular
soc: mediatek: SCPSYS: Add regulator support
MAINTAINERS: Change QCOM entries
soc: qcom: smd-rpm: Add existing platform support
memory/tegra: Add number of TLB lines for Tegra124
reset: hi6220: fix modular build
soc: qcom: Introduce WCNSS_CTRL SMD client
ARM: qcom: select ARM_CPU_SUSPEND for power management
MAINTAINERS: Add rules for Qualcomm dts files
soc: qcom: enable smsm/smp2p modular build
serial: msm_serial: Make config tristate
soc: qcom: smp2p: Qualcomm Shared Memory Point to Point
soc: qcom: smsm: Add driver for Qualcomm SMSM
...
Diffstat (limited to 'drivers/bus')
-rw-r--r-- | drivers/bus/Kconfig | 8 | ||||
-rw-r--r-- | drivers/bus/Makefile | 1 | ||||
-rw-r--r-- | drivers/bus/uniphier-system-bus.c | 281 |
3 files changed, 290 insertions, 0 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 116b363b7987..129d47bcc5fc 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -131,6 +131,14 @@ config SUNXI_RSB with various RSB based devices, such as AXP223, AXP8XX PMICs, and AC100/AC200 ICs. +config UNIPHIER_SYSTEM_BUS + bool "UniPhier System Bus driver" + depends on ARCH_UNIPHIER && OF + default y + help + Support for UniPhier System Bus, a simple external bus. This is + needed to use on-board devices connected to UniPhier SoCs. + config VEXPRESS_CONFIG bool "Versatile Express configuration bus" default y if ARCH_VEXPRESS diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index fcb9f9794a1f..ccff007ee7e8 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -17,4 +17,5 @@ obj-$(CONFIG_OMAP_INTERCONNECT) += omap_l3_smx.o omap_l3_noc.o obj-$(CONFIG_OMAP_OCP2SCP) += omap-ocp2scp.o obj-$(CONFIG_SUNXI_RSB) += sunxi-rsb.o obj-$(CONFIG_SIMPLE_PM_BUS) += simple-pm-bus.o +obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o diff --git a/drivers/bus/uniphier-system-bus.c b/drivers/bus/uniphier-system-bus.c new file mode 100644 index 000000000000..834a2aeaf27a --- /dev/null +++ b/drivers/bus/uniphier-system-bus.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/io.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +/* System Bus Controller registers */ +#define UNIPHIER_SBC_BASE 0x100 /* base address of bank0 space */ +#define UNIPHIER_SBC_BASE_BE BIT(0) /* bank_enable */ +#define UNIPHIER_SBC_CTRL0 0x200 /* timing parameter 0 of bank0 */ +#define UNIPHIER_SBC_CTRL1 0x204 /* timing parameter 1 of bank0 */ +#define UNIPHIER_SBC_CTRL2 0x208 /* timing parameter 2 of bank0 */ +#define UNIPHIER_SBC_CTRL3 0x20c /* timing parameter 3 of bank0 */ +#define UNIPHIER_SBC_CTRL4 0x300 /* timing parameter 4 of bank0 */ + +#define UNIPHIER_SBC_STRIDE 0x10 /* register stride to next bank */ +#define UNIPHIER_SBC_NR_BANKS 8 /* number of banks (chip select) */ +#define UNIPHIER_SBC_BASE_DUMMY 0xffffffff /* data to squash bank 0, 1 */ + +struct uniphier_system_bus_bank { + u32 base; + u32 end; +}; + +struct uniphier_system_bus_priv { + struct device *dev; + void __iomem *membase; + struct uniphier_system_bus_bank bank[UNIPHIER_SBC_NR_BANKS]; +}; + +static int uniphier_system_bus_add_bank(struct uniphier_system_bus_priv *priv, + int bank, u32 addr, u64 paddr, u32 size) +{ + u64 end, mask; + + dev_dbg(priv->dev, + "range found: bank = %d, addr = %08x, paddr = %08llx, size = %08x\n", + bank, addr, paddr, size); + + if (bank >= ARRAY_SIZE(priv->bank)) { + dev_err(priv->dev, "unsupported bank number %d\n", bank); + return -EINVAL; + } + + if (priv->bank[bank].base || priv->bank[bank].end) { + dev_err(priv->dev, + "range for bank %d has already been specified\n", bank); + return -EINVAL; + } + + if (paddr > U32_MAX) { + dev_err(priv->dev, "base address %llx is too high\n", paddr); + return -EINVAL; + } + + end = paddr + size; + + if (addr > paddr) { + dev_err(priv->dev, + "base %08x cannot be mapped to %08llx of parent\n", + addr, paddr); + return -EINVAL; + } + paddr -= addr; + + paddr = round_down(paddr, 0x00020000); + end = round_up(end, 0x00020000); + + if (end > U32_MAX) { + dev_err(priv->dev, "end address %08llx is too high\n", end); + return -EINVAL; + } + mask = paddr ^ (end - 1); + mask = roundup_pow_of_two(mask); + + paddr = round_down(paddr, mask); + end = round_up(end, mask); + + priv->bank[bank].base = paddr; + priv->bank[bank].end = end; + + dev_dbg(priv->dev, "range added: bank = %d, addr = %08x, end = %08x\n", + bank, priv->bank[bank].base, priv->bank[bank].end); + + return 0; +} + +static int uniphier_system_bus_check_overlap( + const struct uniphier_system_bus_priv *priv) +{ + int i, j; + + for (i = 0; i < ARRAY_SIZE(priv->bank); i++) { + for (j = i + 1; j < ARRAY_SIZE(priv->bank); j++) { + if (priv->bank[i].end > priv->bank[j].base || + priv->bank[i].base < priv->bank[j].end) { + dev_err(priv->dev, + "region overlap between bank%d and bank%d\n", + i, j); + return -EINVAL; + } + } + } + + return 0; +} + +static void uniphier_system_bus_check_boot_swap( + struct uniphier_system_bus_priv *priv) +{ + void __iomem *base_reg = priv->membase + UNIPHIER_SBC_BASE; + int is_swapped; + + is_swapped = !(readl(base_reg) & UNIPHIER_SBC_BASE_BE); + + dev_dbg(priv->dev, "Boot Swap: %s\n", is_swapped ? "on" : "off"); + + /* + * If BOOT_SWAP was asserted on power-on-reset, the CS0 and CS1 are + * swapped. In this case, bank0 and bank1 should be swapped as well. + */ + if (is_swapped) + swap(priv->bank[0], priv->bank[1]); +} + +static void uniphier_system_bus_set_reg( + const struct uniphier_system_bus_priv *priv) +{ + void __iomem *base_reg = priv->membase + UNIPHIER_SBC_BASE; + u32 base, end, mask, val; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->bank); i++) { + base = priv->bank[i].base; + end = priv->bank[i].end; + + if (base == end) { + /* + * If SBC_BASE0 or SBC_BASE1 is set to zero, the access + * to anywhere in the system bus space is routed to + * bank 0 (if boot swap if off) or bank 1 (if boot swap + * if on). It means that CPUs cannot get access to + * bank 2 or later. In other words, bank 0/1 cannot + * be disabled even if its bank_enable bits is cleared. + * This seems odd, but it is how this hardware goes. + * As a workaround, dummy data (0xffffffff) should be + * set when the bank 0/1 is unused. As for bank 2 and + * later, they can be simply disable by clearing the + * bank_enable bit. + */ + if (i < 2) + val = UNIPHIER_SBC_BASE_DUMMY; + else + val = 0; + } else { + mask = base ^ (end - 1); + + val = base & 0xfffe0000; + val |= (~mask >> 16) & 0xfffe; + val |= UNIPHIER_SBC_BASE_BE; + } + dev_dbg(priv->dev, "SBC_BASE[%d] = 0x%08x\n", i, val); + + writel(val, base_reg + UNIPHIER_SBC_STRIDE * i); + } +} + +static int uniphier_system_bus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uniphier_system_bus_priv *priv; + struct resource *regs; + const __be32 *ranges; + u32 cells, addr, size; + u64 paddr; + int pna, bank, rlen, rone, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->membase = devm_ioremap_resource(dev, regs); + if (IS_ERR(priv->membase)) + return PTR_ERR(priv->membase); + + priv->dev = dev; + + pna = of_n_addr_cells(dev->of_node); + + ret = of_property_read_u32(dev->of_node, "#address-cells", &cells); + if (ret) { + dev_err(dev, "failed to get #address-cells\n"); + return ret; + } + if (cells != 2) { + dev_err(dev, "#address-cells must be 2\n"); + return -EINVAL; + } + + ret = of_property_read_u32(dev->of_node, "#size-cells", &cells); + if (ret) { + dev_err(dev, "failed to get #size-cells\n"); + return ret; + } + if (cells != 1) { + dev_err(dev, "#size-cells must be 1\n"); + return -EINVAL; + } + + ranges = of_get_property(dev->of_node, "ranges", &rlen); + if (!ranges) { + dev_err(dev, "failed to get ranges property\n"); + return -ENOENT; + } + + rlen /= sizeof(*ranges); + rone = pna + 2; + + for (; rlen >= rone; rlen -= rone) { + bank = be32_to_cpup(ranges++); + addr = be32_to_cpup(ranges++); + paddr = of_translate_address(dev->of_node, ranges); + if (paddr == OF_BAD_ADDR) + return -EINVAL; + ranges += pna; + size = be32_to_cpup(ranges++); + + ret = uniphier_system_bus_add_bank(priv, bank, addr, + paddr, size); + if (ret) + return ret; + } + + ret = uniphier_system_bus_check_overlap(priv); + if (ret) + return ret; + + uniphier_system_bus_check_boot_swap(priv); + + uniphier_system_bus_set_reg(priv); + + /* Now, the bus is configured. Populate platform_devices below it */ + return of_platform_populate(dev->of_node, of_default_bus_match_table, + NULL, dev); +} + +static const struct of_device_id uniphier_system_bus_match[] = { + { .compatible = "socionext,uniphier-system-bus" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_system_bus_match); + +static struct platform_driver uniphier_system_bus_driver = { + .probe = uniphier_system_bus_probe, + .driver = { + .name = "uniphier-system-bus", + .of_match_table = uniphier_system_bus_match, + }, +}; +module_platform_driver(uniphier_system_bus_driver); + +MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>"); +MODULE_DESCRIPTION("UniPhier System Bus driver"); +MODULE_LICENSE("GPL"); |