3 files changed, 192 insertions(+)
create mode 100644 drivers/bus/atmel-smc.c
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index 552373c..8c944db5 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -12,6 +12,15 @@ config IMX_WEIM
The WEIM(Wireless External Interface Module) works like a bus.
You can attach many different devices on it, such as NOR, onenand.
+config ATMEL_SMC
+ bool "Atmel SMC/EBI driver"
+ depends on SOC_AT91SAM9 && OF
+ help
+ Driver for Atmel SMC/EBI controller.
+ Used to configure the EBI (external bus interface) when the device-
+ tree is used. This bus supports NANDs, external ethernet controller,
+ SRAMs, ATA devices.
+
config MVEBU_MBUS
bool
depends on PLAT_ORION
diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
index 8947bdd..4364003 100644
--- a/drivers/bus/Makefile
+++ b/drivers/bus/Makefile
@@ -2,6 +2,7 @@
# Makefile for the bus drivers.
#
+obj-$(CONFIG_ATMEL_SMC) += atmel-smc.o
obj-$(CONFIG_IMX_WEIM) += imx-weim.o
obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o
obj-$(CONFIG_OMAP_OCP2SCP) += omap-ocp2scp.o
diff --git a/drivers/bus/atmel-smc.c b/drivers/bus/atmel-smc.c
new file mode 100644
index 0000000..06e530d
--- /dev/null
+++ b/drivers/bus/atmel-smc.c
@@ -0,0 +1,182 @@
+/*
+ * EBI driver for Atmel SAM9 chips
+ * inspired by the fsl weim bus driver
+ *
+ * Copyright (C) 2013 JJ Hiblot.
+ *
+ * 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/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <mach/at91sam9_smc.h>
+
+struct at91_smc_devtype {
+ unsigned int cs_count;
+};
+
+static const struct at91_smc_devtype sam9261_smc_devtype = {
+ .cs_count = 6,
+};
+
+static const struct of_device_id smc_id_table[] = {
+ { .compatible = "atmel,at91sam9261-smc", .data = &sam9261_smc_devtype},
+ { }
+};
+MODULE_DEVICE_TABLE(of, smc_id_table);
+
+struct smc_parameters_type {
+ const char *name;
+ u16 reg;
+ u16 width;
+ u16 shift;
+};
+
+#define SETUP 0
+#define PULSE 1
+#define CYCLE 2
+#define MODE 3
+static const struct smc_parameters_type smc_parameters[] = {
+ {"smc,ncs_read_setup", SETUP, 6, 24},
+ {"smc,nrd_setup", SETUP, 6, 16},
+ {"smc,ncs_write_setup", SETUP, 6, 8},
+ {"smc,nwe_setup", SETUP, 6, 0},
+ {"smc,ncs_read_pulse", PULSE, 6, 24},
+ {"smc,nrd_pulse", PULSE, 6, 16},
+ {"smc,ncs_write_pulse", PULSE, 6, 8},
+ {"smc,nwe_pulse", PULSE, 6, 0},
+ {"smc,read_cycle", CYCLE, 9, 16},
+ {"smc,write_cycle", CYCLE, 9, 0},
+ {"smc,burst_size", MODE, 2, 28},
+ {"smc,burst_enabled", MODE, 1, 24},
+ {"smc,tdf_mode", MODE, 1, 20},
+ {"smc,tdf_cycles", MODE, 4, 16},
+ {"smc,bus_width", MODE, 2, 12},
+ {"smc,byte_access_type", MODE, 1, 8},
+ {"smc,nwait_mode", MODE, 2, 4},
+ {"smc,write_mode", MODE, 1, 0},
+ {"smc,read_mode", MODE, 1, 1},
+ {NULL}
+};
+
+/* Parse and set the timing for this device. */
+static int __init smc_timing_setup(struct device *dev, struct device_node *np,
+ void __iomem *base, const struct at91_smc_devtype *devtype)
+{
+ u32 val;
+ int ret;
+ u32 cs;
+ const struct smc_parameters_type *p = smc_parameters;
+ u32 shadow_smc_regs[5];
+
+ ret = of_property_read_u32(np, "smc,cs" , &cs);
+ if (ret < 0) {
+ dev_err(dev, "missing mandatory property : smc,cs\n");
+ return ret;
+ }
+ if (val >= devtype->cs_count) {
+ dev_err(dev, "invalid value for property smc,cs (=%d)."
+ "Must be in range 0 to %d\n", cs, devtype->cs_count-1);
+ return -EINVAL;
+ }
+
+ /* set the timing for EBI */
+ base += (0x10 * cs);
+ shadow_smc_regs[SETUP] = readl_relaxed(base + AT91_SMC_SETUP);
+ shadow_smc_regs[PULSE] = readl_relaxed(base + AT91_SMC_PULSE);
+ shadow_smc_regs[CYCLE] = readl_relaxed(base + AT91_SMC_CYCLE);
+ shadow_smc_regs[MODE] = readl_relaxed(base + AT91_SMC_MODE);
+
+ while (p->name) {
+ ret = of_property_read_u32(np, p->name , &val);
+ if (ret == -EINVAL) {
+ dev_dbg(dev, "cs %d: property %s not set.\n", cs,
+ p->name);
+ p++;
+ continue;
+ } else if (ret) {
+ dev_err(dev, "cs %d: can't get property %s.\n", cs,
+ p->name);
+ return ret;
+ }
+ if (val >= (1<<p->width)) {
+ dev_err(dev, "cs %d: property %s out of range.\n", cs,
+ p->name);
+ return -ERANGE;
+ }
+ shadow_smc_regs[p->reg] &= ~(((1<<p->width)-1) << p->shift);
+ shadow_smc_regs[p->reg] |= (val << p->shift);
+ p++;
+ }
+ writel_relaxed(shadow_smc_regs[SETUP], base + AT91_SMC_SETUP);
+ writel_relaxed(shadow_smc_regs[PULSE], base + AT91_SMC_PULSE);
+ writel_relaxed(shadow_smc_regs[CYCLE], base + AT91_SMC_CYCLE);
+ writel_relaxed(shadow_smc_regs[MODE], base + AT91_SMC_MODE);
+ return 0;
+}
+
+static int __init smc_parse_dt(struct platform_device *pdev,
+ void __iomem *base)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id = of_match_device(smc_id_table, dev);
+ const struct at91_smc_devtype *devtype = of_id->data;
+ struct device_node *child;
+ int ret;
+
+ for_each_child_of_node(dev->of_node, child) {
+ if (!child->name)
+ continue;
+
+ ret = smc_timing_setup(dev, child, base, devtype);
+ if (ret) {
+ dev_err(dev, "%s set timing failed.\n",
+ child->full_name);
+ return ret;
+ }
+ }
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret)
+ dev_err(dev, "%s fail to create devices.\n",
+ dev->of_node->full_name);
+ return ret;
+}
+
+static int __init smc_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *base;
+ int ret;
+
+ /* get the resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_request_and_ioremap(&pdev->dev, res);
+ if (IS_ERR(base)) {
+ dev_err(&pdev->dev, "can't map SMC base address\n");
+ return PTR_ERR(base);
+ }
+
+ /* parse the device node */
+ ret = smc_parse_dt(pdev, base);
+ if (!ret)
+ dev_info(&pdev->dev, "Driver registered.\n");
+
+ return ret;
+}
+
+static struct platform_driver smc_driver = {
+ .driver = {
+ .name = "atmel-smc",
+ .owner = THIS_MODULE,
+ .of_match_table = smc_id_table,
+ },
+};
+module_platform_driver_probe(smc_driver, smc_probe);
+
+MODULE_AUTHOR("JJ Hiblot");
+MODULE_DESCRIPTION("Atmel's SMC/EBI driver");
+MODULE_LICENSE("GPL");