diff options
-rw-r--r-- | arch/arm/mvebu-common/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mvebu-common/mbus.c | 471 | ||||
-rw-r--r-- | include/linux/mbus.h | 73 |
3 files changed, 545 insertions, 0 deletions
diff --git a/arch/arm/mvebu-common/Makefile b/arch/arm/mvebu-common/Makefile index 4d20d2ca53..391a125203 100644 --- a/arch/arm/mvebu-common/Makefile +++ b/arch/arm/mvebu-common/Makefile @@ -7,4 +7,5 @@ # obj-y = dram.o +obj-$(CONFIG_ARMADA_XP) += mbus.o obj-y += timer.o diff --git a/arch/arm/mvebu-common/mbus.c b/arch/arm/mvebu-common/mbus.c new file mode 100644 index 0000000000..05c9ef2cbb --- /dev/null +++ b/arch/arm/mvebu-common/mbus.c @@ -0,0 +1,471 @@ +/* + * Address map functions for Marvell EBU SoCs (Kirkwood, Armada + * 370/XP, Dove, Orion5x and MV78xx0) + * + * Ported from the Barebox version to U-Boot by: + * Stefan Roese <sr@denx.de> + * + * The Barebox version is: + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * + * based on mbus driver from Linux + * (C) Copyright 2008 Marvell Semiconductor + * + * SPDX-License-Identifier: GPL-2.0 + * + * The Marvell EBU SoCs have a configurable physical address space: + * the physical address at which certain devices (PCIe, NOR, NAND, + * etc.) sit can be configured. The configuration takes place through + * two sets of registers: + * + * - One to configure the access of the CPU to the devices. Depending + * on the families, there are between 8 and 20 configurable windows, + * each can be use to create a physical memory window that maps to a + * specific device. Devices are identified by a tuple (target, + * attribute). + * + * - One to configure the access to the CPU to the SDRAM. There are + * either 2 (for Dove) or 4 (for other families) windows to map the + * SDRAM into the physical address space. + * + * This driver: + * + * - Reads out the SDRAM address decoding windows at initialization + * time, and fills the mbus_dram_info structure with these + * informations. The exported function mv_mbus_dram_info() allow + * device drivers to get those informations related to the SDRAM + * address decoding windows. This is because devices also have their + * own windows (configured through registers that are part of each + * device register space), and therefore the drivers for Marvell + * devices have to configure those device -> SDRAM windows to ensure + * that DMA works properly. + * + * - Provides an API for platform code or device drivers to + * dynamically add or remove address decoding windows for the CPU -> + * device accesses. This API is mvebu_mbus_add_window_by_id(), + * mvebu_mbus_add_window_remap_by_id() and + * mvebu_mbus_del_window(). + */ + +#include <common.h> +#include <asm/errno.h> +#include <asm/io.h> +#include <asm/arch/cpu.h> +#include <asm/arch/soc.h> +#include <linux/mbus.h> + +#define BIT(nr) (1UL << (nr)) + +/* DDR target is the same on all platforms */ +#define TARGET_DDR 0 + +/* CPU Address Decode Windows registers */ +#define WIN_CTRL_OFF 0x0000 +#define WIN_CTRL_ENABLE BIT(0) +#define WIN_CTRL_TGT_MASK 0xf0 +#define WIN_CTRL_TGT_SHIFT 4 +#define WIN_CTRL_ATTR_MASK 0xff00 +#define WIN_CTRL_ATTR_SHIFT 8 +#define WIN_CTRL_SIZE_MASK 0xffff0000 +#define WIN_CTRL_SIZE_SHIFT 16 +#define WIN_BASE_OFF 0x0004 +#define WIN_BASE_LOW 0xffff0000 +#define WIN_BASE_HIGH 0xf +#define WIN_REMAP_LO_OFF 0x0008 +#define WIN_REMAP_LOW 0xffff0000 +#define WIN_REMAP_HI_OFF 0x000c + +#define ATTR_HW_COHERENCY (0x1 << 4) + +#define DDR_BASE_CS_OFF(n) (0x0000 + ((n) << 3)) +#define DDR_BASE_CS_HIGH_MASK 0xf +#define DDR_BASE_CS_LOW_MASK 0xff000000 +#define DDR_SIZE_CS_OFF(n) (0x0004 + ((n) << 3)) +#define DDR_SIZE_ENABLED BIT(0) +#define DDR_SIZE_CS_MASK 0x1c +#define DDR_SIZE_CS_SHIFT 2 +#define DDR_SIZE_MASK 0xff000000 + +#define DOVE_DDR_BASE_CS_OFF(n) ((n) << 4) + +struct mvebu_mbus_state; + +struct mvebu_mbus_soc_data { + unsigned int num_wins; + unsigned int num_remappable_wins; + unsigned int (*win_cfg_offset)(const int win); + void (*setup_cpu_target)(struct mvebu_mbus_state *s); +}; + +struct mvebu_mbus_state mbus_state + __attribute__ ((section(".data"))); +static struct mbus_dram_target_info mbus_dram_info + __attribute__ ((section(".data"))); + +/* + * Functions to manipulate the address decoding windows + */ + +static void mvebu_mbus_read_window(struct mvebu_mbus_state *mbus, + int win, int *enabled, u64 *base, + u32 *size, u8 *target, u8 *attr, + u64 *remap) +{ + void __iomem *addr = mbus->mbuswins_base + + mbus->soc->win_cfg_offset(win); + u32 basereg = readl(addr + WIN_BASE_OFF); + u32 ctrlreg = readl(addr + WIN_CTRL_OFF); + + if (!(ctrlreg & WIN_CTRL_ENABLE)) { + *enabled = 0; + return; + } + + *enabled = 1; + *base = ((u64)basereg & WIN_BASE_HIGH) << 32; + *base |= (basereg & WIN_BASE_LOW); + *size = (ctrlreg | ~WIN_CTRL_SIZE_MASK) + 1; + + if (target) + *target = (ctrlreg & WIN_CTRL_TGT_MASK) >> WIN_CTRL_TGT_SHIFT; + + if (attr) + *attr = (ctrlreg & WIN_CTRL_ATTR_MASK) >> WIN_CTRL_ATTR_SHIFT; + + if (remap) { + if (win < mbus->soc->num_remappable_wins) { + u32 remap_low = readl(addr + WIN_REMAP_LO_OFF); + u32 remap_hi = readl(addr + WIN_REMAP_HI_OFF); + *remap = ((u64)remap_hi << 32) | remap_low; + } else { + *remap = 0; + } + } +} + +static void mvebu_mbus_disable_window(struct mvebu_mbus_state *mbus, + int win) +{ + void __iomem *addr; + + addr = mbus->mbuswins_base + mbus->soc->win_cfg_offset(win); + + writel(0, addr + WIN_BASE_OFF); + writel(0, addr + WIN_CTRL_OFF); + if (win < mbus->soc->num_remappable_wins) { + writel(0, addr + WIN_REMAP_LO_OFF); + writel(0, addr + WIN_REMAP_HI_OFF); + } +} + +/* Checks whether the given window number is available */ +static int mvebu_mbus_window_is_free(struct mvebu_mbus_state *mbus, + const int win) +{ + void __iomem *addr = mbus->mbuswins_base + + mbus->soc->win_cfg_offset(win); + u32 ctrl = readl(addr + WIN_CTRL_OFF); + return !(ctrl & WIN_CTRL_ENABLE); +} + +/* + * Checks whether the given (base, base+size) area doesn't overlap an + * existing region + */ +static int mvebu_mbus_window_conflicts(struct mvebu_mbus_state *mbus, + phys_addr_t base, size_t size, + u8 target, u8 attr) +{ + u64 end = (u64)base + size; + int win; + + for (win = 0; win < mbus->soc->num_wins; win++) { + u64 wbase, wend; + u32 wsize; + u8 wtarget, wattr; + int enabled; + + mvebu_mbus_read_window(mbus, win, + &enabled, &wbase, &wsize, + &wtarget, &wattr, NULL); + + if (!enabled) + continue; + + wend = wbase + wsize; + + /* + * Check if the current window overlaps with the + * proposed physical range + */ + if ((u64)base < wend && end > wbase) + return 0; + + /* + * Check if target/attribute conflicts + */ + if (target == wtarget && attr == wattr) + return 0; + } + + return 1; +} + +static int mvebu_mbus_find_window(struct mvebu_mbus_state *mbus, + phys_addr_t base, size_t size) +{ + int win; + + for (win = 0; win < mbus->soc->num_wins; win++) { + u64 wbase; + u32 wsize; + int enabled; + + mvebu_mbus_read_window(mbus, win, + &enabled, &wbase, &wsize, + NULL, NULL, NULL); + + if (!enabled) + continue; + + if (base == wbase && size == wsize) + return win; + } + + return -ENODEV; +} + +static int mvebu_mbus_setup_window(struct mvebu_mbus_state *mbus, + int win, phys_addr_t base, size_t size, + phys_addr_t remap, u8 target, + u8 attr) +{ + void __iomem *addr = mbus->mbuswins_base + + mbus->soc->win_cfg_offset(win); + u32 ctrl, remap_addr; + + ctrl = ((size - 1) & WIN_CTRL_SIZE_MASK) | + (attr << WIN_CTRL_ATTR_SHIFT) | + (target << WIN_CTRL_TGT_SHIFT) | + WIN_CTRL_ENABLE; + + writel(base & WIN_BASE_LOW, addr + WIN_BASE_OFF); + writel(ctrl, addr + WIN_CTRL_OFF); + if (win < mbus->soc->num_remappable_wins) { + if (remap == MVEBU_MBUS_NO_REMAP) + remap_addr = base; + else + remap_addr = remap; + writel(remap_addr & WIN_REMAP_LOW, addr + WIN_REMAP_LO_OFF); + writel(0, addr + WIN_REMAP_HI_OFF); + } + + return 0; +} + +static int mvebu_mbus_alloc_window(struct mvebu_mbus_state *mbus, + phys_addr_t base, size_t size, + phys_addr_t remap, u8 target, + u8 attr) +{ + int win; + + if (remap == MVEBU_MBUS_NO_REMAP) { + for (win = mbus->soc->num_remappable_wins; + win < mbus->soc->num_wins; win++) + if (mvebu_mbus_window_is_free(mbus, win)) + return mvebu_mbus_setup_window(mbus, win, base, + size, remap, + target, attr); + } + + + for (win = 0; win < mbus->soc->num_wins; win++) + if (mvebu_mbus_window_is_free(mbus, win)) + return mvebu_mbus_setup_window(mbus, win, base, size, + remap, target, attr); + + return -ENOMEM; +} + +/* + * SoC-specific functions and definitions + */ + +static unsigned int armada_370_xp_mbus_win_offset(int win) +{ + /* The register layout is a bit annoying and the below code + * tries to cope with it. + * - At offset 0x0, there are the registers for the first 8 + * windows, with 4 registers of 32 bits per window (ctrl, + * base, remap low, remap high) + * - Then at offset 0x80, there is a hole of 0x10 bytes for + * the internal registers base address and internal units + * sync barrier register. + * - Then at offset 0x90, there the registers for 12 + * windows, with only 2 registers of 32 bits per window + * (ctrl, base). + */ + if (win < 8) + return win << 4; + else + return 0x90 + ((win - 8) << 3); +} + +static unsigned int orion5x_mbus_win_offset(int win) +{ + return win << 4; +} + +static void mvebu_mbus_default_setup_cpu_target(struct mvebu_mbus_state *mbus) +{ + int i; + int cs; + + mbus_dram_info.mbus_dram_target_id = TARGET_DDR; + + for (i = 0, cs = 0; i < 4; i++) { + u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i)); + u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i)); + + /* + * We only take care of entries for which the chip + * select is enabled, and that don't have high base + * address bits set (devices can only access the first + * 32 bits of the memory). + */ + if ((size & DDR_SIZE_ENABLED) && + !(base & DDR_BASE_CS_HIGH_MASK)) { + struct mbus_dram_window *w; + + w = &mbus_dram_info.cs[cs++]; + w->cs_index = i; + w->mbus_attr = 0xf & ~(1 << i); +#if defined(CONFIG_ARMADA_XP) + w->mbus_attr |= ATTR_HW_COHERENCY; +#endif + w->base = base & DDR_BASE_CS_LOW_MASK; + w->size = (size | ~DDR_SIZE_MASK) + 1; + } + } + mbus_dram_info.num_cs = cs; +} + +static const struct mvebu_mbus_soc_data +armada_370_xp_mbus_data __maybe_unused = { + .num_wins = 20, + .num_remappable_wins = 8, + .win_cfg_offset = armada_370_xp_mbus_win_offset, + .setup_cpu_target = mvebu_mbus_default_setup_cpu_target, +}; + +static const struct mvebu_mbus_soc_data +kirkwood_mbus_data __maybe_unused = { + .num_wins = 8, + .num_remappable_wins = 4, + .win_cfg_offset = orion5x_mbus_win_offset, + .setup_cpu_target = mvebu_mbus_default_setup_cpu_target, +}; + +/* + * Public API of the driver + */ +const struct mbus_dram_target_info *mvebu_mbus_dram_info(void) +{ + return &mbus_dram_info; +} + +int mvebu_mbus_add_window_remap_by_id(unsigned int target, + unsigned int attribute, + phys_addr_t base, size_t size, + phys_addr_t remap) +{ + struct mvebu_mbus_state *s = &mbus_state; + + if (!mvebu_mbus_window_conflicts(s, base, size, target, attribute)) { + printf("Cannot add window '%x:%x', conflicts with another window\n", + target, attribute); + return -EINVAL; + } + + return mvebu_mbus_alloc_window(s, base, size, remap, target, attribute); +} + +int mvebu_mbus_add_window_by_id(unsigned int target, unsigned int attribute, + phys_addr_t base, size_t size) +{ + return mvebu_mbus_add_window_remap_by_id(target, attribute, base, + size, MVEBU_MBUS_NO_REMAP); +} + +int mvebu_mbus_del_window(phys_addr_t base, size_t size) +{ + int win; + + win = mvebu_mbus_find_window(&mbus_state, base, size); + if (win < 0) + return win; + + mvebu_mbus_disable_window(&mbus_state, win); + return 0; +} + +int mbus_dt_setup_win(struct mvebu_mbus_state *mbus, + u32 base, u32 size, u8 target, u8 attr) +{ + if (!mvebu_mbus_window_conflicts(mbus, base, size, target, attr)) { + printf("Cannot add window '%04x:%04x', conflicts with another window\n", + target, attr); + return -EBUSY; + } + + /* + * In U-Boot we first try to add the mbus window to the remap windows. + * If this fails, lets try to add the windows to the non-remap windows. + */ + if (mvebu_mbus_alloc_window(mbus, base, size, base, target, attr)) { + if (mvebu_mbus_alloc_window(mbus, base, size, + MVEBU_MBUS_NO_REMAP, target, attr)) + return -ENOMEM; + } + + return 0; +} + +int mvebu_mbus_probe(struct mbus_win windows[], int count) +{ + int win; + int ret; + int i; + +#if defined(CONFIG_KIRKWOOD) + mbus_state.soc = &kirkwood_mbus_data; +#endif +#if defined(CONFIG_ARMADA_XP) + mbus_state.soc = &armada_370_xp_mbus_data; +#endif + + mbus_state.mbuswins_base = (void __iomem *)MVEBU_CPU_WIN_BASE; + mbus_state.sdramwins_base = (void __iomem *)MVEBU_SDRAM_BASE; + + for (win = 0; win < mbus_state.soc->num_wins; win++) + mvebu_mbus_disable_window(&mbus_state, win); + + mbus_state.soc->setup_cpu_target(&mbus_state); + + /* Setup statically declared windows in the DT */ + for (i = 0; i < count; i++) { + u32 base, size; + u8 target, attr; + + target = windows[i].target; + attr = windows[i].attr; + base = windows[i].base; + size = windows[i].size; + ret = mbus_dt_setup_win(&mbus_state, base, size, target, attr); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/include/linux/mbus.h b/include/linux/mbus.h new file mode 100644 index 0000000000..717cbeab37 --- /dev/null +++ b/include/linux/mbus.h @@ -0,0 +1,73 @@ +/* + * Marvell MBUS common definitions. + * + * Copyright (C) 2008 Marvell Semiconductor + * + * 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. + */ + +#ifndef __LINUX_MBUS_H +#define __LINUX_MBUS_H + +struct resource; + +struct mbus_dram_target_info { + /* + * The 4-bit MBUS target ID of the DRAM controller. + */ + u8 mbus_dram_target_id; + + /* + * The base address, size, and MBUS attribute ID for each + * of the possible DRAM chip selects. Peripherals are + * required to support at least 4 decode windows. + */ + int num_cs; + struct mbus_dram_window { + u8 cs_index; + u8 mbus_attr; + u32 base; + u32 size; + } cs[4]; +}; + +struct mvebu_mbus_state { + void __iomem *mbuswins_base; + void __iomem *sdramwins_base; + struct dentry *debugfs_root; + struct dentry *debugfs_sdram; + struct dentry *debugfs_devs; + const struct mvebu_mbus_soc_data *soc; + int hw_io_coherency; +}; + +/* Flags for PCI/PCIe address decoding regions */ +#define MVEBU_MBUS_PCI_IO 0x1 +#define MVEBU_MBUS_PCI_MEM 0x2 +#define MVEBU_MBUS_PCI_WA 0x3 + +/* + * Magic value that explicits that we don't need a remapping-capable + * address decoding window. + */ +#define MVEBU_MBUS_NO_REMAP (0xffffffff) + +/* Maximum size of a mbus window name */ +#define MVEBU_MBUS_MAX_WINNAME_SZ 32 + +const struct mbus_dram_target_info *mvebu_mbus_dram_info(void); +void mvebu_mbus_get_pcie_mem_aperture(struct resource *res); +void mvebu_mbus_get_pcie_io_aperture(struct resource *res); +int mvebu_mbus_add_window_remap_by_id(unsigned int target, + unsigned int attribute, + phys_addr_t base, size_t size, + phys_addr_t remap); +int mvebu_mbus_add_window_by_id(unsigned int target, unsigned int attribute, + phys_addr_t base, size_t size); +int mvebu_mbus_del_window(phys_addr_t base, size_t size); +int mbus_dt_setup_win(struct mvebu_mbus_state *mbus, + u32 base, u32 size, u8 target, u8 attr); + +#endif /* __LINUX_MBUS_H */ |