Message ID | 1461767754-12189-2-git-send-email-boris.brezillon@free-electrons.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
2016-04-27 16:35 GMT+02:00 Boris Brezillon <boris.brezillon@free-electrons.com>: > The EBI (External Bus Interface) is used to access external peripherals > (NOR, SRAM, NAND, and other specific devices like ethernet controllers). > Each device is assigned a CS line and an address range and can have its > own configuration (timings, access mode, bus width, ...). > This driver provides a generic DT binding to configure a device according > to its requirements. > For specific device controllers (like the NAND one) the SMC timings > should be configured by the controller driver through the matrix and > smc syscon regmaps. > > Signed-off-by: Jean-Jacques Hiblot <jjhiblot@traphandler.com> > Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com> > --- > drivers/memory/Kconfig | 11 + > drivers/memory/Makefile | 1 + > drivers/memory/atmel-ebi.c | 662 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 674 insertions(+) > create mode 100644 drivers/memory/atmel-ebi.c > > diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig > index 51d5cd2..4780136 100644 > --- a/drivers/memory/Kconfig > +++ b/drivers/memory/Kconfig > @@ -25,6 +25,17 @@ config ATMEL_SDRAMC > Starting with the at91sam9g45, this controller supports SDR, DDR and > LP-DDR memories. > > +config ATMEL_EBI > + bool "Atmel EBI driver" > + default y > + depends on ARCH_AT91 && OF > + select MFD_SYSCON > + help > + Driver for Atmel 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, etc. > + > config TI_AEMIF > tristate "Texas Instruments AEMIF driver" > depends on (ARCH_DAVINCI || ARCH_KEYSTONE) && OF > diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile > index 890bdf4..965da6b 100644 > --- a/drivers/memory/Makefile > +++ b/drivers/memory/Makefile > @@ -7,6 +7,7 @@ obj-$(CONFIG_OF) += of_memory.o > endif > obj-$(CONFIG_ARM_PL172_MPMC) += pl172.o > obj-$(CONFIG_ATMEL_SDRAMC) += atmel-sdramc.o > +obj-$(CONFIG_ATMEL_EBI) += atmel-ebi.o > obj-$(CONFIG_TI_AEMIF) += ti-aemif.o > obj-$(CONFIG_TI_EMIF) += emif.o > obj-$(CONFIG_OMAP_GPMC) += omap-gpmc.o > diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c > new file mode 100644 > index 0000000..2a72b53 > --- /dev/null > +++ b/drivers/memory/atmel-ebi.c > @@ -0,0 +1,662 @@ > +/* > + * EBI driver for Atmel 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/clk.h> > +#include <linux/io.h> > +#include <linux/mfd/syscon.h> > +#include <linux/mfd/syscon/atmel-matrix.h> > +#include <linux/mfd/syscon/atmel-smc.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/regmap.h> > + > +struct at91sam9_smc_timings { > + u32 ncs_rd_setup_ns; > + u32 nrd_setup_ns; > + u32 ncs_wr_setup_ns; > + u32 nwe_setup_ns; > + u32 ncs_rd_pulse_ns; > + u32 nrd_pulse_ns; > + u32 ncs_wr_pulse_ns; > + u32 nwe_pulse_ns; > + u32 nrd_cycle_ns; > + u32 nwe_cycle_ns; > + u32 tdf_ns; > +}; > + > +struct at91sam9_smc_generic_fields { > + struct regmap_field *setup; > + struct regmap_field *pulse; > + struct regmap_field *cycle; > + struct regmap_field *mode; > +}; > + > +struct at91sam9_ebi_dev_config { > + struct at91sam9_smc_timings timings; > + u32 mode; > +}; > + > +struct at91_ebi_dev_config { > + union { > + struct at91sam9_ebi_dev_config sam9; > + }; > +}; > + > +struct at91_ebi; > + > +struct at91_ebi_dev { > + struct device_node *np; > + struct at91_ebi *ebi; > + u32 mode; > + int cs; > + struct at91_ebi_dev_config config; > +}; > + > +struct at91_ebi_caps { > + unsigned int available_cs; > + const struct reg_field *ebi_csa; > + void (*get_config)(struct at91_ebi_dev *ebid, > + struct at91_ebi_dev_config *conf); > + int (*xlate_config)(struct at91_ebi_dev *ebid, > + struct at91_ebi_dev_config *conf); > + int (*apply_config)(struct at91_ebi_dev *ebid, > + struct at91_ebi_dev_config *conf); > + int (*init)(struct at91_ebi *ebi); > +}; > + > +struct at91_ebi { > + struct clk *clk; > + struct regmap *smc; > + struct regmap *matrix; > + > + struct regmap_field *ebi_csa; > + > + struct device *dev; > + const struct at91_ebi_caps *caps; > + struct at91_ebi_dev *devs[AT91_MATRIX_EBI_NUM_CS]; > + void *priv; > +}; > + > +static void at91sam9_ebi_get_config(struct at91_ebi_dev *ebid, > + struct at91_ebi_dev_config *conf) > +{ > + struct at91sam9_smc_generic_fields *fields = ebid->ebi->priv; > + unsigned int clk_rate = clk_get_rate(ebid->ebi->clk); > + struct at91sam9_ebi_dev_config *config = &conf->sam9; > + struct at91sam9_smc_timings *timings = &config->timings; > + unsigned int val; > + > + regmap_fields_read(fields->mode, ebid->cs, &val); > + config->mode = val & ~AT91_SMC_TDF; > + > + val = (val & AT91_SMC_TDF) >> 16; > + timings->tdf_ns = clk_rate * val; > + > + regmap_fields_read(fields->setup, ebid->cs, &val); > + timings->ncs_rd_setup_ns = (val >> 24) & 0x1f; > + timings->ncs_rd_setup_ns += ((val >> 29) & 0x1) * 128; > + timings->ncs_rd_setup_ns *= clk_rate; > + timings->nrd_setup_ns = (val >> 16) & 0x1f; > + timings->nrd_setup_ns += ((val >> 21) & 0x1) * 128; > + timings->nrd_setup_ns *= clk_rate; > + timings->ncs_wr_setup_ns = (val >> 8) & 0x1f; > + timings->ncs_wr_setup_ns += ((val >> 13) & 0x1) * 128; > + timings->ncs_wr_setup_ns *= clk_rate; > + timings->nwe_setup_ns = val & 0x1f; > + timings->nwe_setup_ns += ((val >> 5) & 0x1) * 128; > + timings->nwe_setup_ns *= clk_rate; > + > + regmap_fields_read(fields->pulse, ebid->cs, &val); > + timings->ncs_rd_pulse_ns = (val >> 24) & 0x3f; > + timings->ncs_rd_pulse_ns += ((val >> 30) & 0x1) * 256; > + timings->ncs_rd_pulse_ns *= clk_rate; > + timings->nrd_pulse_ns = (val >> 16) & 0x3f; > + timings->nrd_pulse_ns += ((val >> 22) & 0x1) * 256; > + timings->nrd_pulse_ns *= clk_rate; > + timings->ncs_wr_pulse_ns = (val >> 8) & 0x3f; > + timings->ncs_wr_pulse_ns += ((val >> 14) & 0x1) * 256; > + timings->ncs_wr_pulse_ns *= clk_rate; > + timings->nwe_pulse_ns = val & 0x3f; > + timings->nwe_pulse_ns += ((val >> 6) & 0x1) * 256; > + timings->nwe_pulse_ns *= clk_rate; > + > + regmap_fields_read(fields->cycle, ebid->cs, &val); > + timings->nrd_cycle_ns = (val >> 16) & 0x7f; > + timings->nrd_cycle_ns += ((val >> 23) & 0x3) * 256; > + timings->nrd_cycle_ns *= clk_rate; > + timings->nwe_cycle_ns = val & 0x7f; > + timings->nwe_cycle_ns += ((val >> 7) & 0x3) * 256; > + timings->nwe_cycle_ns *= clk_rate; > +} > + > +static int at91sam9_smc_xslate_timings(struct at91_ebi_dev *ebid, > + struct at91sam9_smc_timings *timings) > +{ > + struct device_node *np = ebid->np; > + > + of_property_read_u32(np, "atmel,ncs-rd-setup-ns", > + &timings->ncs_rd_setup_ns); > + of_property_read_u32(np, "atmel,nrd-setup-ns", > + &timings->nrd_setup_ns); > + of_property_read_u32(np, "atmel,ncs-wr-setup-ns", > + &timings->ncs_wr_setup_ns); > + of_property_read_u32(np, "atmel,nwe-setup-ns", > + &timings->nwe_setup_ns); > + of_property_read_u32(np, "atmel,ncs-rd-pulse-ns", > + &timings->ncs_rd_pulse_ns); > + of_property_read_u32(np, "atmel,nrd-pulse-ns", > + &timings->nrd_pulse_ns); > + of_property_read_u32(np, "atmel,ncs-wr-pulse-ns", > + &timings->ncs_wr_pulse_ns); > + of_property_read_u32(np, "atmel,nwe-pulse-ns", &timings->nwe_pulse_ns); > + of_property_read_u32(np, "atmel,nwe-cycle-ns", &timings->nwe_cycle_ns); > + of_property_read_u32(np, "atmel,nrd-cycle-ns", &timings->nrd_cycle_ns); > + of_property_read_u32(np, "atmel,tdf-ns", &timings->tdf_ns); > + > + return 0; > +} > + > +static int at91sam9_ebi_xslate_config(struct at91_ebi_dev *ebid, > + struct at91_ebi_dev_config *conf) > +{ > + struct at91sam9_ebi_dev_config *config = &conf->sam9; > + struct device_node *np = ebid->np; > + const char *tmp_str; > + u32 tmp; > + int ret; > + > + ret = of_property_read_u32(np, "atmel,bus-width", &tmp); > + if (!ret) { > + config->mode &= ~AT91_SMC_DBW; > + switch (tmp) { > + case 8: > + config->mode |= AT91_SMC_DBW_8; > + break; > + > + case 16: > + config->mode |= AT91_SMC_DBW_16; > + break; > + > + case 32: > + config->mode |= AT91_SMC_DBW_32; > + break; > + > + default: > + return -EINVAL; > + } > + } > + > + tmp_str = NULL; > + of_property_read_string(np, "atmel,tdf-mode", &tmp_str); > + if (tmp_str) { > + config->mode &= ~AT91_SMC_TDFMODE_OPTIMIZED; > + if (!strcmp(tmp_str, "optimized")) > + config->mode |= AT91_SMC_TDFMODE_OPTIMIZED; > + } > + > + tmp_str = NULL; > + of_property_read_string(np, "atmel,byte-access-type", &tmp_str); > + if (tmp_str) { > + config->mode &= AT91_SMC_BAT; > + if (!strcmp(tmp_str, "write")) > + config->mode |= AT91_SMC_BAT_WRITE; > + } > + > + tmp_str = NULL; > + of_property_read_string(np, "atmel,read-mode", &tmp_str); > + if (tmp_str) { > + config->mode &= ~AT91_SMC_READMODE; > + if (!strcmp(tmp_str, "nrd")) > + config->mode |= AT91_SMC_READMODE_NRD; > + } > + > + tmp_str = NULL; > + of_property_read_string(np, "atmel,write-mode", &tmp_str); > + if (tmp_str) { > + config->mode &= ~AT91_SMC_WRITEMODE; > + if (!strcmp(tmp_str, "nwe")) > + config->mode |= AT91_SMC_WRITEMODE_NWE; > + } > + > + tmp_str = NULL; > + of_property_read_string(np, "atmel,exnw-mode", &tmp_str); > + if (tmp_str) { > + config->mode &= ~AT91_SMC_EXNWMODE; > + if (!strcmp(tmp_str, "frozen")) > + config->mode |= AT91_SMC_EXNWMODE_FROZEN; > + else if (!strcmp(tmp_str, "ready")) > + config->mode |= AT91_SMC_EXNWMODE_READY; > + } > + > + tmp = 0; > + ret = of_property_read_u32(np, "atmel,page-mode", &tmp); > + if (!ret) { > + config->mode &= ~AT91_SMC_PS; > + switch (tmp) { > + case 4: > + config->mode |= AT91_SMC_PS_4; > + break; > + > + case 8: > + config->mode |= AT91_SMC_PS_8; > + break; > + > + case 16: > + config->mode |= AT91_SMC_PS_16; > + break; > + > + case 32: > + config->mode |= AT91_SMC_PS_32; > + break; > + > + default: > + return -EINVAL; > + } > + > + config->mode |= AT91_SMC_PMEN; > + } > + > + return at91sam9_smc_xslate_timings(ebid, &config->timings); > +} > + > +static int at91sam9_ebi_apply_config(struct at91_ebi_dev *ebid, > + struct at91_ebi_dev_config *conf) > +{ > + unsigned int clk_rate = clk_get_rate(ebid->ebi->clk); > + struct at91sam9_ebi_dev_config *config = &conf->sam9; > + struct at91sam9_smc_timings *timings = &config->timings; > + struct at91sam9_smc_generic_fields *fields = ebid->ebi->priv; > + u32 coded_val; > + u32 val; > + > + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, > + timings->ncs_rd_setup_ns); > + val = AT91SAM9_SMC_NCS_NRDSETUP(coded_val); > + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, > + timings->nrd_setup_ns); > + val |= AT91SAM9_SMC_NRDSETUP(coded_val); > + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, > + timings->ncs_wr_setup_ns); > + val |= AT91SAM9_SMC_NCS_WRSETUP(coded_val); > + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, > + timings->nwe_setup_ns); > + val |= AT91SAM9_SMC_NWESETUP(coded_val); > + regmap_fields_write(fields->setup, ebid->cs, val); > + > + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, > + timings->ncs_rd_pulse_ns); > + val = AT91SAM9_SMC_NCS_NRDPULSE(coded_val); > + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, > + timings->nrd_pulse_ns); > + val |= AT91SAM9_SMC_NRDPULSE(coded_val); > + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, > + timings->ncs_wr_pulse_ns); > + val |= AT91SAM9_SMC_NCS_WRPULSE(coded_val); > + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, > + timings->nwe_pulse_ns); > + val |= AT91SAM9_SMC_NWEPULSE(coded_val); > + regmap_fields_write(fields->pulse, ebid->cs, val); > + > + coded_val = at91sam9_smc_cycle_ns_to_cycles(clk_rate, > + timings->nrd_cycle_ns); > + val = AT91SAM9_SMC_NRDCYCLE(coded_val); > + coded_val = at91sam9_smc_cycle_ns_to_cycles(clk_rate, > + timings->nwe_cycle_ns); > + val |= AT91SAM9_SMC_NWECYCLE(coded_val); > + regmap_fields_write(fields->cycle, ebid->cs, val); > + > + val = DIV_ROUND_UP(timings->tdf_ns, clk_rate); > + if (val > AT91_SMC_TDF_MAX) > + val = AT91_SMC_TDF_MAX; > + regmap_fields_write(fields->mode, ebid->cs, > + config->mode | AT91_SMC_TDF_(val)); > + > + /* > + * Adjust config to what's actually configured in the hardware block. > + * Can be slightly different because of rounding policies. > + */ > + at91sam9_ebi_get_config(ebid, conf); > + > + return 0; > +} > + > + > +static int at91sam9_ebi_init(struct at91_ebi *ebi) > +{ > + struct at91sam9_smc_generic_fields *fields; > + struct reg_field field = REG_FIELD(0, 0, 31); > + > + fields = devm_kzalloc(ebi->dev, sizeof(*fields), GFP_KERNEL); > + if (!fields) > + return -ENOMEM; > + > + field.id_size = fls(ebi->caps->available_cs); > + field.id_offset = AT91SAM9_SMC_GENERIC_BLK_SZ; > + > + field.reg = AT91SAM9_SMC_SETUP(AT91SAM9_SMC_GENERIC); > + fields->setup = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->setup)) > + return PTR_ERR(fields->setup); > + > + field.reg = AT91SAM9_SMC_PULSE(AT91SAM9_SMC_GENERIC); > + fields->pulse = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->pulse)) > + return PTR_ERR(fields->pulse); > + > + field.reg = AT91SAM9_SMC_CYCLE(AT91SAM9_SMC_GENERIC); > + fields->cycle = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->cycle)) > + return PTR_ERR(fields->cycle); > + > + field.reg = AT91SAM9_SMC_MODE(AT91SAM9_SMC_GENERIC); > + fields->mode = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->mode)) > + return PTR_ERR(fields->mode); > + > + ebi->priv = fields; > + > + return 0; > +} > + > +static int sama5d3_ebi_init(struct at91_ebi *ebi) > +{ > + struct at91sam9_smc_generic_fields *fields; > + struct reg_field field = REG_FIELD(0, 0, 31); > + > + fields = devm_kzalloc(ebi->dev, sizeof(*fields), GFP_KERNEL); > + if (!fields) > + return -ENOMEM; > + > + field.id_size = fls(ebi->caps->available_cs); > + field.id_offset = SAMA5_SMC_GENERIC_BLK_SZ; > + > + field.reg = AT91SAM9_SMC_SETUP(SAMA5_SMC_GENERIC); > + fields->setup = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->setup)) > + return PTR_ERR(fields->setup); > + > + field.reg = AT91SAM9_SMC_PULSE(SAMA5_SMC_GENERIC); > + fields->pulse = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->pulse)) > + return PTR_ERR(fields->pulse); > + > + field.reg = AT91SAM9_SMC_CYCLE(SAMA5_SMC_GENERIC); > + fields->cycle = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->cycle)) > + return PTR_ERR(fields->cycle); > + > + field.reg = SAMA5_SMC_MODE(SAMA5_SMC_GENERIC); > + fields->mode = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); > + if (IS_ERR(fields->mode)) > + return PTR_ERR(fields->mode); > + > + ebi->priv = fields; > + > + return 0; > +} > + > +static int at91_ebi_dev_setup(struct at91_ebi *ebi, struct device_node *np) > +{ > + const struct at91_ebi_caps *caps = ebi->caps; > + struct at91_ebi_dev_config conf; > + struct device *dev = ebi->dev; > + struct device_node *dev_np; > + struct at91_ebi_dev *ebid; > + u32 tmp; > + int ret; > + > + dev_np = of_get_next_child(np, NULL); > + if (!dev_np) > + return -EINVAL; > + > + if (!of_device_is_available(dev_np)) > + return 0; > + > + ebid = devm_kzalloc(ebi->dev, sizeof(*ebid), GFP_KERNEL); > + if (!ebid) > + return -ENOMEM; > + > + ret = of_property_read_u32(dev_np, "reg", &tmp); > + if (ret < 0) { > + dev_err(dev, "missing mandatory reg property\n"); > + return ret; > + } > + > + if (tmp > AT91_MATRIX_EBI_NUM_CS || > + !(BIT(tmp) & ebi->caps->available_cs)) { > + dev_err(dev, "invalid reg property\n"); > + return -EINVAL; > + } > + > + ebid->cs = tmp; > + ebid->np = np; > + ebid->ebi = ebi; > + > + /* > + * Attach the EBI device to the generic SMC logic. > + * FIXME: some drivers (like the NAND controller driver) might have > + * to change this config afterwards. We should expose a new API once > + * this requirement becomes a reality. > + */ > + if (ebi->ebi_csa) > + regmap_field_update_bits(ebi->ebi_csa, BIT(ebid->cs), 0); > + > + caps->get_config(ebid, &conf); > + > + ret = caps->xlate_config(ebid, &conf); > + if (ret) > + return ret; > + > + ret = caps->apply_config(ebid, &conf); > + if (ret) > + return ret; > + > + ebid->config = conf; > + ebi->devs[ebid->cs] = ebid; > + > + return of_platform_populate(np, of_default_bus_match_table, NULL, dev); > +} > + > +static const struct reg_field at91sam9260_ebi_csa = > + REG_FIELD(AT91SAM9260_MATRIX_EBICSA, 0, > + AT91_MATRIX_EBI_NUM_CS - 1); > + > +static const struct at91_ebi_caps at91sam9260_ebi_caps = { > + .available_cs = 0xff, > + .ebi_csa = &at91sam9260_ebi_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct reg_field at91sam9261_ebi_csa = > + REG_FIELD(AT91SAM9261_MATRIX_EBICSA, 0, > + AT91_MATRIX_EBI_NUM_CS - 1); > + > +static const struct at91_ebi_caps at91sam9261_ebi_caps = { > + .available_cs = 0xff, > + .ebi_csa = &at91sam9261_ebi_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct reg_field at91sam9263_ebi0_csa = > + REG_FIELD(AT91SAM9263_MATRIX_EBI0CSA, 0, > + AT91_MATRIX_EBI_NUM_CS - 1); > + > +static const struct at91_ebi_caps at91sam9263_ebi0_caps = { > + .available_cs = 0x3f, > + .ebi_csa = &at91sam9263_ebi0_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct reg_field at91sam9263_ebi1_csa = > + REG_FIELD(AT91SAM9263_MATRIX_EBI1CSA, 0, > + AT91_MATRIX_EBI_NUM_CS - 1); > + > +static const struct at91_ebi_caps at91sam9263_ebi1_caps = { > + .available_cs = 0x7, > + .ebi_csa = &at91sam9263_ebi1_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct reg_field at91sam9rl_ebi_csa = > + REG_FIELD(AT91SAM9RL_MATRIX_EBICSA, 0, > + AT91_MATRIX_EBI_NUM_CS - 1); > + > +static const struct at91_ebi_caps at91sam9rl_ebi_caps = { > + .available_cs = 0x3f, > + .ebi_csa = &at91sam9rl_ebi_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct reg_field at91sam9g45_ebi_csa = > + REG_FIELD(AT91SAM9G45_MATRIX_EBICSA, 0, > + AT91_MATRIX_EBI_NUM_CS - 1); > + > +static const struct at91_ebi_caps at91sam9g45_ebi_caps = { > + .available_cs = 0x3f, > + .ebi_csa = &at91sam9g45_ebi_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct at91_ebi_caps at91sam9x5_ebi_caps = { > + .available_cs = 0x3f, > + .ebi_csa = &at91sam9263_ebi0_csa, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = at91sam9_ebi_init, > +}; > + > +static const struct at91_ebi_caps sama5d3_ebi_caps = { > + .available_cs = 0xf, > + .xlate_config = at91sam9_ebi_xslate_config, > + .apply_config = at91sam9_ebi_apply_config, > + .init = sama5d3_ebi_init, > +}; > + > +static const struct of_device_id at91_ebi_id_table[] = { > + { > + .compatible = "atmel,at91sam9260-ebi", > + .data = &at91sam9260_ebi_caps, > + }, > + { > + .compatible = "atmel,at91sam9261-ebi", > + .data = &at91sam9261_ebi_caps, > + }, > + { > + .compatible = "atmel,at91sam9263-ebi0", > + .data = &at91sam9263_ebi0_caps, > + }, > + { > + .compatible = "atmel,at91sam9263-ebi1", > + .data = &at91sam9263_ebi1_caps, > + }, > + { > + .compatible = "atmel,at91sam9rl-ebi", > + .data = &at91sam9rl_ebi_caps, > + }, > + { > + .compatible = "atmel,at91sam9g45-ebi", > + .data = &at91sam9g45_ebi_caps, > + }, > + { > + .compatible = "atmel,at91sam9x5-ebi", > + .data = &at91sam9x5_ebi_caps, > + }, > + { > + .compatible = "atmel,sama5d3-ebi", > + .data = &sama5d3_ebi_caps, > + }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, at91_ebi_id_table); > + > +static int at91_ebi_probe(struct platform_device *pdev) > +{ > + const struct of_device_id *match; > + struct device_node *child; > + struct at91_ebi *ebi; > + struct clk *clk; > + int ret; > + > + match = of_match_device(at91_ebi_id_table, &pdev->dev); > + if (!match || !match->data) > + return -EINVAL; > + > + ebi = devm_kzalloc(&pdev->dev, sizeof(*ebi), GFP_KERNEL); > + if (!ebi) > + return -ENOMEM; > + > + ebi->caps = match->data; > + ebi->dev = &pdev->dev; > + > + clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(clk)) > + return PTR_ERR(clk); > + > + ebi->clk = clk; > + > + ebi->smc = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, > + "atmel,smc"); > + if (IS_ERR(ebi->smc)) > + return PTR_ERR(ebi->smc); > + > + /* > + * The sama5d3 does not provide an EBICSA register and thus does need > + * to access the matrix registers. > + */ > + if (ebi->caps->ebi_csa) { > + ebi->matrix = > + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, > + "atmel,matrix"); > + if (IS_ERR(ebi->matrix)) > + return PTR_ERR(ebi->matrix); > + > + ebi->ebi_csa = regmap_field_alloc(ebi->matrix, > + *ebi->caps->ebi_csa); > + if (IS_ERR(ebi->ebi_csa)) > + return PTR_ERR(ebi->ebi_csa); > + } > + > + ret = ebi->caps->init(ebi); > + if (ret) > + return ret; > + > + for_each_child_of_node(pdev->dev.of_node, child) { > + ret = at91_ebi_dev_setup(ebi, child); > + if (ret) > + return ret; I'm not sure about breaking out of the loop here, if at91_ebi_dev_setup() for a device fail then the remaining devices won't be probed. If you add a bad config for a fpga on CS1, you can prevent your NAND on CS2 to be probed. > + } > + > + return ret; > +} > + > +static struct platform_driver at91_ebi_driver = { > + .driver = { > + .name = "atmel-ebi", > + .of_match_table = at91_ebi_id_table, > + }, > +}; > +module_platform_driver_probe(at91_ebi_driver, at91_ebi_probe); > + > +MODULE_AUTHOR("JJ Hiblot"); > +MODULE_DESCRIPTION("Atmel EBI driver"); > +MODULE_LICENSE("GPL"); > -- > 2.7.4 >
On Thu, 28 Apr 2016 10:47:24 +0200 Jean-Jacques Hiblot <jjhiblot@traphandler.com> wrote: > > +static int at91_ebi_probe(struct platform_device *pdev) > > +{ > > + const struct of_device_id *match; > > + struct device_node *child; > > + struct at91_ebi *ebi; > > + struct clk *clk; > > + int ret; > > + > > + match = of_match_device(at91_ebi_id_table, &pdev->dev); > > + if (!match || !match->data) > > + return -EINVAL; > > + > > + ebi = devm_kzalloc(&pdev->dev, sizeof(*ebi), GFP_KERNEL); > > + if (!ebi) > > + return -ENOMEM; > > + > > + ebi->caps = match->data; > > + ebi->dev = &pdev->dev; > > + > > + clk = devm_clk_get(&pdev->dev, NULL); > > + if (IS_ERR(clk)) > > + return PTR_ERR(clk); > > + > > + ebi->clk = clk; > > + > > + ebi->smc = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, > > + "atmel,smc"); > > + if (IS_ERR(ebi->smc)) > > + return PTR_ERR(ebi->smc); > > + > > + /* > > + * The sama5d3 does not provide an EBICSA register and thus does need > > + * to access the matrix registers. > > + */ > > + if (ebi->caps->ebi_csa) { > > + ebi->matrix = > > + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, > > + "atmel,matrix"); > > + if (IS_ERR(ebi->matrix)) > > + return PTR_ERR(ebi->matrix); > > + > > + ebi->ebi_csa = regmap_field_alloc(ebi->matrix, > > + *ebi->caps->ebi_csa); > > + if (IS_ERR(ebi->ebi_csa)) > > + return PTR_ERR(ebi->ebi_csa); > > + } > > + > > + ret = ebi->caps->init(ebi); > > + if (ret) > > + return ret; > > + > > + for_each_child_of_node(pdev->dev.of_node, child) { > > + ret = at91_ebi_dev_setup(ebi, child); > > + if (ret) > > + return ret; > > I'm not sure about breaking out of the loop here, if > at91_ebi_dev_setup() for a device fail then the remaining devices > won't be probed. If you add a bad config for a fpga on CS1, you can > prevent your NAND on CS2 to be probed. Fair enough, I'll continue iterating even if an error occurs.
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 51d5cd2..4780136 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -25,6 +25,17 @@ config ATMEL_SDRAMC Starting with the at91sam9g45, this controller supports SDR, DDR and LP-DDR memories. +config ATMEL_EBI + bool "Atmel EBI driver" + default y + depends on ARCH_AT91 && OF + select MFD_SYSCON + help + Driver for Atmel 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, etc. + config TI_AEMIF tristate "Texas Instruments AEMIF driver" depends on (ARCH_DAVINCI || ARCH_KEYSTONE) && OF diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 890bdf4..965da6b 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_OF) += of_memory.o endif obj-$(CONFIG_ARM_PL172_MPMC) += pl172.o obj-$(CONFIG_ATMEL_SDRAMC) += atmel-sdramc.o +obj-$(CONFIG_ATMEL_EBI) += atmel-ebi.o obj-$(CONFIG_TI_AEMIF) += ti-aemif.o obj-$(CONFIG_TI_EMIF) += emif.o obj-$(CONFIG_OMAP_GPMC) += omap-gpmc.o diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c new file mode 100644 index 0000000..2a72b53 --- /dev/null +++ b/drivers/memory/atmel-ebi.c @@ -0,0 +1,662 @@ +/* + * EBI driver for Atmel 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/clk.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/atmel-matrix.h> +#include <linux/mfd/syscon/atmel-smc.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +struct at91sam9_smc_timings { + u32 ncs_rd_setup_ns; + u32 nrd_setup_ns; + u32 ncs_wr_setup_ns; + u32 nwe_setup_ns; + u32 ncs_rd_pulse_ns; + u32 nrd_pulse_ns; + u32 ncs_wr_pulse_ns; + u32 nwe_pulse_ns; + u32 nrd_cycle_ns; + u32 nwe_cycle_ns; + u32 tdf_ns; +}; + +struct at91sam9_smc_generic_fields { + struct regmap_field *setup; + struct regmap_field *pulse; + struct regmap_field *cycle; + struct regmap_field *mode; +}; + +struct at91sam9_ebi_dev_config { + struct at91sam9_smc_timings timings; + u32 mode; +}; + +struct at91_ebi_dev_config { + union { + struct at91sam9_ebi_dev_config sam9; + }; +}; + +struct at91_ebi; + +struct at91_ebi_dev { + struct device_node *np; + struct at91_ebi *ebi; + u32 mode; + int cs; + struct at91_ebi_dev_config config; +}; + +struct at91_ebi_caps { + unsigned int available_cs; + const struct reg_field *ebi_csa; + void (*get_config)(struct at91_ebi_dev *ebid, + struct at91_ebi_dev_config *conf); + int (*xlate_config)(struct at91_ebi_dev *ebid, + struct at91_ebi_dev_config *conf); + int (*apply_config)(struct at91_ebi_dev *ebid, + struct at91_ebi_dev_config *conf); + int (*init)(struct at91_ebi *ebi); +}; + +struct at91_ebi { + struct clk *clk; + struct regmap *smc; + struct regmap *matrix; + + struct regmap_field *ebi_csa; + + struct device *dev; + const struct at91_ebi_caps *caps; + struct at91_ebi_dev *devs[AT91_MATRIX_EBI_NUM_CS]; + void *priv; +}; + +static void at91sam9_ebi_get_config(struct at91_ebi_dev *ebid, + struct at91_ebi_dev_config *conf) +{ + struct at91sam9_smc_generic_fields *fields = ebid->ebi->priv; + unsigned int clk_rate = clk_get_rate(ebid->ebi->clk); + struct at91sam9_ebi_dev_config *config = &conf->sam9; + struct at91sam9_smc_timings *timings = &config->timings; + unsigned int val; + + regmap_fields_read(fields->mode, ebid->cs, &val); + config->mode = val & ~AT91_SMC_TDF; + + val = (val & AT91_SMC_TDF) >> 16; + timings->tdf_ns = clk_rate * val; + + regmap_fields_read(fields->setup, ebid->cs, &val); + timings->ncs_rd_setup_ns = (val >> 24) & 0x1f; + timings->ncs_rd_setup_ns += ((val >> 29) & 0x1) * 128; + timings->ncs_rd_setup_ns *= clk_rate; + timings->nrd_setup_ns = (val >> 16) & 0x1f; + timings->nrd_setup_ns += ((val >> 21) & 0x1) * 128; + timings->nrd_setup_ns *= clk_rate; + timings->ncs_wr_setup_ns = (val >> 8) & 0x1f; + timings->ncs_wr_setup_ns += ((val >> 13) & 0x1) * 128; + timings->ncs_wr_setup_ns *= clk_rate; + timings->nwe_setup_ns = val & 0x1f; + timings->nwe_setup_ns += ((val >> 5) & 0x1) * 128; + timings->nwe_setup_ns *= clk_rate; + + regmap_fields_read(fields->pulse, ebid->cs, &val); + timings->ncs_rd_pulse_ns = (val >> 24) & 0x3f; + timings->ncs_rd_pulse_ns += ((val >> 30) & 0x1) * 256; + timings->ncs_rd_pulse_ns *= clk_rate; + timings->nrd_pulse_ns = (val >> 16) & 0x3f; + timings->nrd_pulse_ns += ((val >> 22) & 0x1) * 256; + timings->nrd_pulse_ns *= clk_rate; + timings->ncs_wr_pulse_ns = (val >> 8) & 0x3f; + timings->ncs_wr_pulse_ns += ((val >> 14) & 0x1) * 256; + timings->ncs_wr_pulse_ns *= clk_rate; + timings->nwe_pulse_ns = val & 0x3f; + timings->nwe_pulse_ns += ((val >> 6) & 0x1) * 256; + timings->nwe_pulse_ns *= clk_rate; + + regmap_fields_read(fields->cycle, ebid->cs, &val); + timings->nrd_cycle_ns = (val >> 16) & 0x7f; + timings->nrd_cycle_ns += ((val >> 23) & 0x3) * 256; + timings->nrd_cycle_ns *= clk_rate; + timings->nwe_cycle_ns = val & 0x7f; + timings->nwe_cycle_ns += ((val >> 7) & 0x3) * 256; + timings->nwe_cycle_ns *= clk_rate; +} + +static int at91sam9_smc_xslate_timings(struct at91_ebi_dev *ebid, + struct at91sam9_smc_timings *timings) +{ + struct device_node *np = ebid->np; + + of_property_read_u32(np, "atmel,ncs-rd-setup-ns", + &timings->ncs_rd_setup_ns); + of_property_read_u32(np, "atmel,nrd-setup-ns", + &timings->nrd_setup_ns); + of_property_read_u32(np, "atmel,ncs-wr-setup-ns", + &timings->ncs_wr_setup_ns); + of_property_read_u32(np, "atmel,nwe-setup-ns", + &timings->nwe_setup_ns); + of_property_read_u32(np, "atmel,ncs-rd-pulse-ns", + &timings->ncs_rd_pulse_ns); + of_property_read_u32(np, "atmel,nrd-pulse-ns", + &timings->nrd_pulse_ns); + of_property_read_u32(np, "atmel,ncs-wr-pulse-ns", + &timings->ncs_wr_pulse_ns); + of_property_read_u32(np, "atmel,nwe-pulse-ns", &timings->nwe_pulse_ns); + of_property_read_u32(np, "atmel,nwe-cycle-ns", &timings->nwe_cycle_ns); + of_property_read_u32(np, "atmel,nrd-cycle-ns", &timings->nrd_cycle_ns); + of_property_read_u32(np, "atmel,tdf-ns", &timings->tdf_ns); + + return 0; +} + +static int at91sam9_ebi_xslate_config(struct at91_ebi_dev *ebid, + struct at91_ebi_dev_config *conf) +{ + struct at91sam9_ebi_dev_config *config = &conf->sam9; + struct device_node *np = ebid->np; + const char *tmp_str; + u32 tmp; + int ret; + + ret = of_property_read_u32(np, "atmel,bus-width", &tmp); + if (!ret) { + config->mode &= ~AT91_SMC_DBW; + switch (tmp) { + case 8: + config->mode |= AT91_SMC_DBW_8; + break; + + case 16: + config->mode |= AT91_SMC_DBW_16; + break; + + case 32: + config->mode |= AT91_SMC_DBW_32; + break; + + default: + return -EINVAL; + } + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,tdf-mode", &tmp_str); + if (tmp_str) { + config->mode &= ~AT91_SMC_TDFMODE_OPTIMIZED; + if (!strcmp(tmp_str, "optimized")) + config->mode |= AT91_SMC_TDFMODE_OPTIMIZED; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,byte-access-type", &tmp_str); + if (tmp_str) { + config->mode &= AT91_SMC_BAT; + if (!strcmp(tmp_str, "write")) + config->mode |= AT91_SMC_BAT_WRITE; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,read-mode", &tmp_str); + if (tmp_str) { + config->mode &= ~AT91_SMC_READMODE; + if (!strcmp(tmp_str, "nrd")) + config->mode |= AT91_SMC_READMODE_NRD; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,write-mode", &tmp_str); + if (tmp_str) { + config->mode &= ~AT91_SMC_WRITEMODE; + if (!strcmp(tmp_str, "nwe")) + config->mode |= AT91_SMC_WRITEMODE_NWE; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,exnw-mode", &tmp_str); + if (tmp_str) { + config->mode &= ~AT91_SMC_EXNWMODE; + if (!strcmp(tmp_str, "frozen")) + config->mode |= AT91_SMC_EXNWMODE_FROZEN; + else if (!strcmp(tmp_str, "ready")) + config->mode |= AT91_SMC_EXNWMODE_READY; + } + + tmp = 0; + ret = of_property_read_u32(np, "atmel,page-mode", &tmp); + if (!ret) { + config->mode &= ~AT91_SMC_PS; + switch (tmp) { + case 4: + config->mode |= AT91_SMC_PS_4; + break; + + case 8: + config->mode |= AT91_SMC_PS_8; + break; + + case 16: + config->mode |= AT91_SMC_PS_16; + break; + + case 32: + config->mode |= AT91_SMC_PS_32; + break; + + default: + return -EINVAL; + } + + config->mode |= AT91_SMC_PMEN; + } + + return at91sam9_smc_xslate_timings(ebid, &config->timings); +} + +static int at91sam9_ebi_apply_config(struct at91_ebi_dev *ebid, + struct at91_ebi_dev_config *conf) +{ + unsigned int clk_rate = clk_get_rate(ebid->ebi->clk); + struct at91sam9_ebi_dev_config *config = &conf->sam9; + struct at91sam9_smc_timings *timings = &config->timings; + struct at91sam9_smc_generic_fields *fields = ebid->ebi->priv; + u32 coded_val; + u32 val; + + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, + timings->ncs_rd_setup_ns); + val = AT91SAM9_SMC_NCS_NRDSETUP(coded_val); + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, + timings->nrd_setup_ns); + val |= AT91SAM9_SMC_NRDSETUP(coded_val); + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, + timings->ncs_wr_setup_ns); + val |= AT91SAM9_SMC_NCS_WRSETUP(coded_val); + coded_val = at91sam9_smc_setup_ns_to_cycles(clk_rate, + timings->nwe_setup_ns); + val |= AT91SAM9_SMC_NWESETUP(coded_val); + regmap_fields_write(fields->setup, ebid->cs, val); + + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, + timings->ncs_rd_pulse_ns); + val = AT91SAM9_SMC_NCS_NRDPULSE(coded_val); + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, + timings->nrd_pulse_ns); + val |= AT91SAM9_SMC_NRDPULSE(coded_val); + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, + timings->ncs_wr_pulse_ns); + val |= AT91SAM9_SMC_NCS_WRPULSE(coded_val); + coded_val = at91sam9_smc_pulse_ns_to_cycles(clk_rate, + timings->nwe_pulse_ns); + val |= AT91SAM9_SMC_NWEPULSE(coded_val); + regmap_fields_write(fields->pulse, ebid->cs, val); + + coded_val = at91sam9_smc_cycle_ns_to_cycles(clk_rate, + timings->nrd_cycle_ns); + val = AT91SAM9_SMC_NRDCYCLE(coded_val); + coded_val = at91sam9_smc_cycle_ns_to_cycles(clk_rate, + timings->nwe_cycle_ns); + val |= AT91SAM9_SMC_NWECYCLE(coded_val); + regmap_fields_write(fields->cycle, ebid->cs, val); + + val = DIV_ROUND_UP(timings->tdf_ns, clk_rate); + if (val > AT91_SMC_TDF_MAX) + val = AT91_SMC_TDF_MAX; + regmap_fields_write(fields->mode, ebid->cs, + config->mode | AT91_SMC_TDF_(val)); + + /* + * Adjust config to what's actually configured in the hardware block. + * Can be slightly different because of rounding policies. + */ + at91sam9_ebi_get_config(ebid, conf); + + return 0; +} + + +static int at91sam9_ebi_init(struct at91_ebi *ebi) +{ + struct at91sam9_smc_generic_fields *fields; + struct reg_field field = REG_FIELD(0, 0, 31); + + fields = devm_kzalloc(ebi->dev, sizeof(*fields), GFP_KERNEL); + if (!fields) + return -ENOMEM; + + field.id_size = fls(ebi->caps->available_cs); + field.id_offset = AT91SAM9_SMC_GENERIC_BLK_SZ; + + field.reg = AT91SAM9_SMC_SETUP(AT91SAM9_SMC_GENERIC); + fields->setup = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->setup)) + return PTR_ERR(fields->setup); + + field.reg = AT91SAM9_SMC_PULSE(AT91SAM9_SMC_GENERIC); + fields->pulse = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->pulse)) + return PTR_ERR(fields->pulse); + + field.reg = AT91SAM9_SMC_CYCLE(AT91SAM9_SMC_GENERIC); + fields->cycle = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->cycle)) + return PTR_ERR(fields->cycle); + + field.reg = AT91SAM9_SMC_MODE(AT91SAM9_SMC_GENERIC); + fields->mode = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->mode)) + return PTR_ERR(fields->mode); + + ebi->priv = fields; + + return 0; +} + +static int sama5d3_ebi_init(struct at91_ebi *ebi) +{ + struct at91sam9_smc_generic_fields *fields; + struct reg_field field = REG_FIELD(0, 0, 31); + + fields = devm_kzalloc(ebi->dev, sizeof(*fields), GFP_KERNEL); + if (!fields) + return -ENOMEM; + + field.id_size = fls(ebi->caps->available_cs); + field.id_offset = SAMA5_SMC_GENERIC_BLK_SZ; + + field.reg = AT91SAM9_SMC_SETUP(SAMA5_SMC_GENERIC); + fields->setup = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->setup)) + return PTR_ERR(fields->setup); + + field.reg = AT91SAM9_SMC_PULSE(SAMA5_SMC_GENERIC); + fields->pulse = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->pulse)) + return PTR_ERR(fields->pulse); + + field.reg = AT91SAM9_SMC_CYCLE(SAMA5_SMC_GENERIC); + fields->cycle = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->cycle)) + return PTR_ERR(fields->cycle); + + field.reg = SAMA5_SMC_MODE(SAMA5_SMC_GENERIC); + fields->mode = devm_regmap_field_alloc(ebi->dev, ebi->smc, field); + if (IS_ERR(fields->mode)) + return PTR_ERR(fields->mode); + + ebi->priv = fields; + + return 0; +} + +static int at91_ebi_dev_setup(struct at91_ebi *ebi, struct device_node *np) +{ + const struct at91_ebi_caps *caps = ebi->caps; + struct at91_ebi_dev_config conf; + struct device *dev = ebi->dev; + struct device_node *dev_np; + struct at91_ebi_dev *ebid; + u32 tmp; + int ret; + + dev_np = of_get_next_child(np, NULL); + if (!dev_np) + return -EINVAL; + + if (!of_device_is_available(dev_np)) + return 0; + + ebid = devm_kzalloc(ebi->dev, sizeof(*ebid), GFP_KERNEL); + if (!ebid) + return -ENOMEM; + + ret = of_property_read_u32(dev_np, "reg", &tmp); + if (ret < 0) { + dev_err(dev, "missing mandatory reg property\n"); + return ret; + } + + if (tmp > AT91_MATRIX_EBI_NUM_CS || + !(BIT(tmp) & ebi->caps->available_cs)) { + dev_err(dev, "invalid reg property\n"); + return -EINVAL; + } + + ebid->cs = tmp; + ebid->np = np; + ebid->ebi = ebi; + + /* + * Attach the EBI device to the generic SMC logic. + * FIXME: some drivers (like the NAND controller driver) might have + * to change this config afterwards. We should expose a new API once + * this requirement becomes a reality. + */ + if (ebi->ebi_csa) + regmap_field_update_bits(ebi->ebi_csa, BIT(ebid->cs), 0); + + caps->get_config(ebid, &conf); + + ret = caps->xlate_config(ebid, &conf); + if (ret) + return ret; + + ret = caps->apply_config(ebid, &conf); + if (ret) + return ret; + + ebid->config = conf; + ebi->devs[ebid->cs] = ebid; + + return of_platform_populate(np, of_default_bus_match_table, NULL, dev); +} + +static const struct reg_field at91sam9260_ebi_csa = + REG_FIELD(AT91SAM9260_MATRIX_EBICSA, 0, + AT91_MATRIX_EBI_NUM_CS - 1); + +static const struct at91_ebi_caps at91sam9260_ebi_caps = { + .available_cs = 0xff, + .ebi_csa = &at91sam9260_ebi_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct reg_field at91sam9261_ebi_csa = + REG_FIELD(AT91SAM9261_MATRIX_EBICSA, 0, + AT91_MATRIX_EBI_NUM_CS - 1); + +static const struct at91_ebi_caps at91sam9261_ebi_caps = { + .available_cs = 0xff, + .ebi_csa = &at91sam9261_ebi_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct reg_field at91sam9263_ebi0_csa = + REG_FIELD(AT91SAM9263_MATRIX_EBI0CSA, 0, + AT91_MATRIX_EBI_NUM_CS - 1); + +static const struct at91_ebi_caps at91sam9263_ebi0_caps = { + .available_cs = 0x3f, + .ebi_csa = &at91sam9263_ebi0_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct reg_field at91sam9263_ebi1_csa = + REG_FIELD(AT91SAM9263_MATRIX_EBI1CSA, 0, + AT91_MATRIX_EBI_NUM_CS - 1); + +static const struct at91_ebi_caps at91sam9263_ebi1_caps = { + .available_cs = 0x7, + .ebi_csa = &at91sam9263_ebi1_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct reg_field at91sam9rl_ebi_csa = + REG_FIELD(AT91SAM9RL_MATRIX_EBICSA, 0, + AT91_MATRIX_EBI_NUM_CS - 1); + +static const struct at91_ebi_caps at91sam9rl_ebi_caps = { + .available_cs = 0x3f, + .ebi_csa = &at91sam9rl_ebi_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct reg_field at91sam9g45_ebi_csa = + REG_FIELD(AT91SAM9G45_MATRIX_EBICSA, 0, + AT91_MATRIX_EBI_NUM_CS - 1); + +static const struct at91_ebi_caps at91sam9g45_ebi_caps = { + .available_cs = 0x3f, + .ebi_csa = &at91sam9g45_ebi_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct at91_ebi_caps at91sam9x5_ebi_caps = { + .available_cs = 0x3f, + .ebi_csa = &at91sam9263_ebi0_csa, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = at91sam9_ebi_init, +}; + +static const struct at91_ebi_caps sama5d3_ebi_caps = { + .available_cs = 0xf, + .xlate_config = at91sam9_ebi_xslate_config, + .apply_config = at91sam9_ebi_apply_config, + .init = sama5d3_ebi_init, +}; + +static const struct of_device_id at91_ebi_id_table[] = { + { + .compatible = "atmel,at91sam9260-ebi", + .data = &at91sam9260_ebi_caps, + }, + { + .compatible = "atmel,at91sam9261-ebi", + .data = &at91sam9261_ebi_caps, + }, + { + .compatible = "atmel,at91sam9263-ebi0", + .data = &at91sam9263_ebi0_caps, + }, + { + .compatible = "atmel,at91sam9263-ebi1", + .data = &at91sam9263_ebi1_caps, + }, + { + .compatible = "atmel,at91sam9rl-ebi", + .data = &at91sam9rl_ebi_caps, + }, + { + .compatible = "atmel,at91sam9g45-ebi", + .data = &at91sam9g45_ebi_caps, + }, + { + .compatible = "atmel,at91sam9x5-ebi", + .data = &at91sam9x5_ebi_caps, + }, + { + .compatible = "atmel,sama5d3-ebi", + .data = &sama5d3_ebi_caps, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, at91_ebi_id_table); + +static int at91_ebi_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *child; + struct at91_ebi *ebi; + struct clk *clk; + int ret; + + match = of_match_device(at91_ebi_id_table, &pdev->dev); + if (!match || !match->data) + return -EINVAL; + + ebi = devm_kzalloc(&pdev->dev, sizeof(*ebi), GFP_KERNEL); + if (!ebi) + return -ENOMEM; + + ebi->caps = match->data; + ebi->dev = &pdev->dev; + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ebi->clk = clk; + + ebi->smc = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "atmel,smc"); + if (IS_ERR(ebi->smc)) + return PTR_ERR(ebi->smc); + + /* + * The sama5d3 does not provide an EBICSA register and thus does need + * to access the matrix registers. + */ + if (ebi->caps->ebi_csa) { + ebi->matrix = + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "atmel,matrix"); + if (IS_ERR(ebi->matrix)) + return PTR_ERR(ebi->matrix); + + ebi->ebi_csa = regmap_field_alloc(ebi->matrix, + *ebi->caps->ebi_csa); + if (IS_ERR(ebi->ebi_csa)) + return PTR_ERR(ebi->ebi_csa); + } + + ret = ebi->caps->init(ebi); + if (ret) + return ret; + + for_each_child_of_node(pdev->dev.of_node, child) { + ret = at91_ebi_dev_setup(ebi, child); + if (ret) + return ret; + } + + return ret; +} + +static struct platform_driver at91_ebi_driver = { + .driver = { + .name = "atmel-ebi", + .of_match_table = at91_ebi_id_table, + }, +}; +module_platform_driver_probe(at91_ebi_driver, at91_ebi_probe); + +MODULE_AUTHOR("JJ Hiblot"); +MODULE_DESCRIPTION("Atmel EBI driver"); +MODULE_LICENSE("GPL");