diff --git a/arch/arm/mach-omap/include/mach/gpmc_nand.h b/arch/arm/mach-omap/include/mach/gpmc_nand.h index 8d138ec..8839486 100644 --- a/arch/arm/mach-omap/include/mach/gpmc_nand.h +++ b/arch/arm/mach-omap/include/mach/gpmc_nand.h @@ -59,6 +59,9 @@ struct nand_ecclayout *oob; /** gpmc config for nand */ struct gpmc_config *nand_cfg; + + struct device_node *of_node; + struct device_node *elm_of_node; }; int omap_add_gpmc_nand_device(struct gpmc_nand_platform_data *pdata); diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 5938d3f..b0d3c1f 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -4,4 +4,10 @@ depends on ARCH_IMX bool "i.MX WEIM driver" +config BUS_OMAP_GPMC + depends on ARCH_OMAP + depends on OFDEVICE + depends on OMAP_GPMC + bool "TI OMAP/AM33xx GPMC support" + endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 42a8d49..f1c5ac7 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_IMX_WEIM) += imx-weim.o +obj-$(CONFIG_BUS_OMAP_GPMC) += omap-gpmc.o diff --git a/drivers/bus/omap-gpmc.c b/drivers/bus/omap-gpmc.c new file mode 100644 index 0000000..ad21af2 --- /dev/null +++ b/drivers/bus/omap-gpmc.c @@ -0,0 +1,523 @@ +/* + * OMAP GPMC driver. Based upon the corresponding Linux Code + * + * Copyright (C) 2013 Sascha Hauer, Pengutronix, + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPMC_CS_NUM 8 +#define GPMC_NR_WAITPINS 4 + +#define GPMC_CONFIG1_WRAPBURST_SUPP (1 << 31) +#define GPMC_CONFIG1_READMULTIPLE_SUPP (1 << 30) +#define GPMC_CONFIG1_READTYPE_ASYNC (0 << 29) +#define GPMC_CONFIG1_READTYPE_SYNC (1 << 29) +#define GPMC_CONFIG1_WRITEMULTIPLE_SUPP (1 << 28) +#define GPMC_CONFIG1_WRITETYPE_ASYNC (0 << 27) +#define GPMC_CONFIG1_WRITETYPE_SYNC (1 << 27) +#define GPMC_CONFIG1_CLKACTIVATIONTIME(val) ((val & 3) << 25) +#define GPMC_CONFIG1_PAGE_LEN(val) ((val & 3) << 23) +#define GPMC_CONFIG1_WAIT_READ_MON (1 << 22) +#define GPMC_CONFIG1_WAIT_WRITE_MON (1 << 21) +#define GPMC_CONFIG1_WAIT_MON_IIME(val) ((val & 3) << 18) +#define GPMC_CONFIG1_WAIT_PIN_SEL(val) ((val & 3) << 16) +#define GPMC_CONFIG1_DEVICESIZE(val) ((val & 3) << 12) +#define GPMC_CONFIG1_DEVICESIZE_16 GPMC_CONFIG1_DEVICESIZE(1) +#define GPMC_CONFIG1_DEVICETYPE(val) ((val & 3) << 10) +#define GPMC_CONFIG1_DEVICETYPE_NOR GPMC_CONFIG1_DEVICETYPE(0) +#define GPMC_CONFIG1_MUXTYPE(val) ((val & 3) << 8) +#define GPMC_CONFIG1_TIME_PARA_GRAN (1 << 4) +#define GPMC_CONFIG1_FCLK_DIV(val) (val & 3) +#define GPMC_CONFIG1_FCLK_DIV2 (GPMC_CONFIG1_FCLK_DIV(1)) +#define GPMC_CONFIG1_FCLK_DIV3 (GPMC_CONFIG1_FCLK_DIV(2)) +#define GPMC_CONFIG1_FCLK_DIV4 (GPMC_CONFIG1_FCLK_DIV(3)) +#define GPMC_CONFIG2_CSEXTRADELAY (1 << 7) +#define GPMC_CONFIG3_ADVEXTRADELAY (1 << 7) +#define GPMC_CONFIG4_OEEXTRADELAY (1 << 7) +#define GPMC_CONFIG4_WEEXTRADELAY (1 << 23) +#define GPMC_CONFIG6_CYCLE2CYCLEDIFFCSEN (1 << 6) +#define GPMC_CONFIG6_CYCLE2CYCLESAMECSEN (1 << 7) +#define GPMC_CONFIG7_CSVALID (1 << 6) + +static unsigned int gpmc_cs_num = GPMC_CS_NUM; +static unsigned int gpmc_nr_waitpins; +static unsigned long gpmc_l3_clk = 100000000; /* This should be a proper clock */ +static void __iomem *gpmc_base; + +/* bool type time settings */ +struct gpmc_bool_timings { + bool cycle2cyclediffcsen; + bool cycle2cyclesamecsen; + bool we_extra_delay; + bool oe_extra_delay; + bool adv_extra_delay; + bool cs_extra_delay; + bool time_para_granularity; +}; + +/* + * Note that all values in this struct are in nanoseconds except sync_clk + * (which is in picoseconds), while the register values are in gpmc_fck cycles. + */ +struct gpmc_timings { + /* Minimum clock period for synchronous mode (in picoseconds) */ + u32 sync_clk; + + /* Chip-select signal timings corresponding to 1 */ + u32 cs_on; /* Assertion time */ + u32 cs_rd_off; /* Read deassertion time */ + u32 cs_wr_off; /* Write deassertion time */ + + /* ADV signal timings corresponding to GPMC_CONFIG3 */ + u32 adv_on; /* Assertion time */ + u32 adv_rd_off; /* Read deassertion time */ + u32 adv_wr_off; /* Write deassertion time */ + + /* WE signals timings corresponding to GPMC_CONFIG4 */ + u32 we_on; /* WE assertion time */ + u32 we_off; /* WE deassertion time */ + + /* OE signals timings corresponding to GPMC_CONFIG4 */ + u32 oe_on; /* OE assertion time */ + u32 oe_off; /* OE deassertion time */ + + /* Access time and cycle time timings corresponding to GPMC_CONFIG5 */ + u32 page_burst_access; /* Multiple access word delay */ + u32 access; /* Start-cycle to first data valid delay */ + u32 rd_cycle; /* Total read cycle time */ + u32 wr_cycle; /* Total write cycle time */ + + u32 bus_turnaround; + u32 cycle2cycle_delay; + + u32 wait_monitoring; + u32 clk_activation; + + /* The following are only on OMAP3430 */ + u32 wr_access; /* WRACCESSTIME */ + u32 wr_data_mux_bus; /* WRDATAONADMUXBUS */ + + struct gpmc_bool_timings bool_timings; +}; + +struct gpmc_settings { + bool burst_wrap; /* enables wrap bursting */ + bool burst_read; /* enables read page/burst mode */ + bool burst_write; /* enables write page/burst mode */ + bool device_nand; /* device is NAND */ + bool sync_read; /* enables synchronous reads */ + bool sync_write; /* enables synchronous writes */ + bool wait_on_read; /* monitor wait on reads */ + bool wait_on_write; /* monitor wait on writes */ + u32 burst_len; /* page/burst length */ + u32 device_width; /* device bus width (8 or 16 bit) */ + u32 mux_add_data; /* multiplex address & data */ + u32 wait_pin; /* wait-pin to be used */ +}; + +struct imx_gpmc { + struct device_d *dev; + void __iomem *base; + struct imx_gpmc_devtype *devtype; +}; + +static void gpmc_cs_bool_timings(struct gpmc_config *gpmc_config, const struct gpmc_bool_timings *p) +{ + if (p->time_para_granularity) + gpmc_config->cfg[0] |= GPMC_CONFIG1_TIME_PARA_GRAN; + + if (p->cs_extra_delay) + gpmc_config->cfg[1] |= GPMC_CONFIG2_CSEXTRADELAY; + if (p->adv_extra_delay) + gpmc_config->cfg[2] |= GPMC_CONFIG3_ADVEXTRADELAY; + if (p->oe_extra_delay) + gpmc_config->cfg[3] |= GPMC_CONFIG4_OEEXTRADELAY; + if (p->we_extra_delay) + gpmc_config->cfg[3] |= GPMC_CONFIG4_OEEXTRADELAY; + if (p->cycle2cyclesamecsen) + gpmc_config->cfg[5] |= GPMC_CONFIG6_CYCLE2CYCLESAMECSEN; + if (p->cycle2cyclediffcsen) + gpmc_config->cfg[5] |= GPMC_CONFIG6_CYCLE2CYCLEDIFFCSEN; +} + +static unsigned long gpmc_get_fclk_period(void) +{ + unsigned long rate = gpmc_l3_clk; + + rate /= 1000; + rate = 1000000000 / rate; /* In picoseconds */ + + return rate; +} + +static unsigned int gpmc_ns_to_ticks(unsigned int time_ns) +{ + unsigned long tick_ps; + + /* Calculate in picosecs to yield more exact results */ + tick_ps = gpmc_get_fclk_period(); + + return (time_ns * 1000 + tick_ps - 1) / tick_ps; +} + +int gpmc_calc_divider(unsigned int sync_clk) +{ + int div; + u32 l; + + l = sync_clk + (gpmc_get_fclk_period() - 1); + div = l / gpmc_get_fclk_period(); + if (div > 4) + return -1; + if (div <= 0) + div = 1; + + return div; +} + +static int set_cfg(struct gpmc_config *gpmc_config, int reg, + int st_bit, int end_bit, int time) +{ + int ticks, mask, nr_bits; + + if (time == 0) + ticks = 0; + else + ticks = gpmc_ns_to_ticks(time); + + nr_bits = end_bit - st_bit + 1; + if (ticks >= 1 << nr_bits) + return -EINVAL; + + mask = (1 << nr_bits) - 1; + gpmc_config->cfg[reg] &= ~(mask << st_bit); + gpmc_config->cfg[reg] |= ticks << st_bit; + + return 0; +} + +static int gpmc_timings_to_config(struct gpmc_config *gpmc_config, const struct gpmc_timings *t) +{ + int div, ret = 0; + + div = gpmc_calc_divider(t->sync_clk); + if (div < 0) + return div; + + ret |= set_cfg(gpmc_config, 0, 18, 19, t->wait_monitoring); + ret |= set_cfg(gpmc_config, 0, 25, 26, t->clk_activation); + + ret |= set_cfg(gpmc_config, 1, 0, 3, t->cs_on); + ret |= set_cfg(gpmc_config, 1, 8, 12, t->cs_rd_off); + ret |= set_cfg(gpmc_config, 1, 16, 20, t->cs_wr_off); + + ret |= set_cfg(gpmc_config, 2, 0, 3, t->adv_on); + ret |= set_cfg(gpmc_config, 2, 8, 12, t->adv_rd_off); + ret |= set_cfg(gpmc_config, 2, 16, 20, t->adv_wr_off); + + ret |= set_cfg(gpmc_config, 3, 0, 3, t->oe_on); + ret |= set_cfg(gpmc_config, 3, 8, 12, t->oe_off); + ret |= set_cfg(gpmc_config, 3, 16, 19, t->we_on); + ret |= set_cfg(gpmc_config, 3, 24, 28, t->we_off); + + ret |= set_cfg(gpmc_config, 4, 0, 4, t->rd_cycle); + ret |= set_cfg(gpmc_config, 4, 8, 12, t->wr_cycle); + ret |= set_cfg(gpmc_config, 4, 16, 20, t->access); + + ret |= set_cfg(gpmc_config, 4, 24, 27, t->page_burst_access); + + ret |= set_cfg(gpmc_config, 5, 0, 3, t->bus_turnaround); + ret |= set_cfg(gpmc_config, 5, 8, 11, t->cycle2cycle_delay); + ret |= set_cfg(gpmc_config, 5, 16, 19, t->wr_data_mux_bus); + ret |= set_cfg(gpmc_config, 5, 24, 28, t->wr_access); + + if (ret) + return ret; + + gpmc_cs_bool_timings(gpmc_config, &t->bool_timings); + + return 0; +} + +/** + * gpmc_read_settings_dt - read gpmc settings from device-tree + * @np: pointer to device-tree node for a gpmc child device + * @p: pointer to gpmc settings structure + * + * Reads the GPMC settings for a GPMC child device from device-tree and + * stores them in the GPMC settings structure passed. The GPMC settings + * structure is initialised to zero by this function and so any + * previously stored settings will be cleared. + */ +static void gpmc_read_settings_dt(struct device_node *np, struct gpmc_settings *p) +{ + memset(p, 0, sizeof(struct gpmc_settings)); + + p->sync_read = of_property_read_bool(np, "gpmc,sync-read"); + p->sync_write = of_property_read_bool(np, "gpmc,sync-write"); + of_property_read_u32(np, "gpmc,device-width", &p->device_width); + of_property_read_u32(np, "gpmc,mux-add-data", &p->mux_add_data); + + if (!of_property_read_u32(np, "gpmc,burst-length", &p->burst_len)) { + p->burst_wrap = of_property_read_bool(np, "gpmc,burst-wrap"); + p->burst_read = of_property_read_bool(np, "gpmc,burst-read"); + p->burst_write = of_property_read_bool(np, "gpmc,burst-write"); + if (!p->burst_read && !p->burst_write) + pr_warn("%s: page/burst-length set but not used!\n", + __func__); + } + + if (!of_property_read_u32(np, "gpmc,wait-pin", &p->wait_pin)) { + p->wait_on_read = of_property_read_bool(np, + "gpmc,wait-on-read"); + p->wait_on_write = of_property_read_bool(np, + "gpmc,wait-on-write"); + if (!p->wait_on_read && !p->wait_on_write) + pr_warn("%s: read/write wait monitoring not enabled!\n", + __func__); + } +} + +static void gpmc_read_timings_dt(struct device_node *np, + struct gpmc_timings *gpmc_t) +{ + struct gpmc_bool_timings *p; + + if (!np || !gpmc_t) + return; + + memset(gpmc_t, 0, sizeof(*gpmc_t)); + + /* minimum clock period for syncronous mode */ + of_property_read_u32(np, "gpmc,sync-clk-ps", &gpmc_t->sync_clk); + + /* chip select timtings */ + of_property_read_u32(np, "gpmc,cs-on-ns", &gpmc_t->cs_on); + of_property_read_u32(np, "gpmc,cs-rd-off-ns", &gpmc_t->cs_rd_off); + of_property_read_u32(np, "gpmc,cs-wr-off-ns", &gpmc_t->cs_wr_off); + + /* ADV signal timings */ + of_property_read_u32(np, "gpmc,adv-on-ns", &gpmc_t->adv_on); + of_property_read_u32(np, "gpmc,adv-rd-off-ns", &gpmc_t->adv_rd_off); + of_property_read_u32(np, "gpmc,adv-wr-off-ns", &gpmc_t->adv_wr_off); + + /* WE signal timings */ + of_property_read_u32(np, "gpmc,we-on-ns", &gpmc_t->we_on); + of_property_read_u32(np, "gpmc,we-off-ns", &gpmc_t->we_off); + + /* OE signal timings */ + of_property_read_u32(np, "gpmc,oe-on-ns", &gpmc_t->oe_on); + of_property_read_u32(np, "gpmc,oe-off-ns", &gpmc_t->oe_off); + + /* access and cycle timings */ + of_property_read_u32(np, "gpmc,page-burst-access-ns", + &gpmc_t->page_burst_access); + of_property_read_u32(np, "gpmc,access-ns", &gpmc_t->access); + of_property_read_u32(np, "gpmc,rd-cycle-ns", &gpmc_t->rd_cycle); + of_property_read_u32(np, "gpmc,wr-cycle-ns", &gpmc_t->wr_cycle); + of_property_read_u32(np, "gpmc,bus-turnaround-ns", + &gpmc_t->bus_turnaround); + of_property_read_u32(np, "gpmc,cycle2cycle-delay-ns", + &gpmc_t->cycle2cycle_delay); + of_property_read_u32(np, "gpmc,wait-monitoring-ns", + &gpmc_t->wait_monitoring); + of_property_read_u32(np, "gpmc,clk-activation-ns", + &gpmc_t->clk_activation); + + /* only applicable to OMAP3+ */ + of_property_read_u32(np, "gpmc,wr-access-ns", &gpmc_t->wr_access); + of_property_read_u32(np, "gpmc,wr-data-mux-bus-ns", + &gpmc_t->wr_data_mux_bus); + + /* bool timing parameters */ + p = &gpmc_t->bool_timings; + + p->cycle2cyclediffcsen = + of_property_read_bool(np, "gpmc,cycle2cycle-diffcsen"); + p->cycle2cyclesamecsen = + of_property_read_bool(np, "gpmc,cycle2cycle-samecsen"); + p->we_extra_delay = of_property_read_bool(np, "gpmc,we-extra-delay"); + p->oe_extra_delay = of_property_read_bool(np, "gpmc,oe-extra-delay"); + p->adv_extra_delay = of_property_read_bool(np, "gpmc,adv-extra-delay"); + p->cs_extra_delay = of_property_read_bool(np, "gpmc,cs-extra-delay"); + p->time_para_granularity = + of_property_read_bool(np, "gpmc,time-para-granularity"); +} + +struct dt_eccmode { + const char *name; + enum gpmc_ecc_mode mode; +}; + +static struct dt_eccmode modes[] = { + { + .name = "ham1", + .mode = OMAP_ECC_HAMMING_CODE_HW_ROMCODE, + }, { + .name = "sw", + .mode = OMAP_ECC_HAMMING_CODE_HW_ROMCODE, + }, { + .name = "hw", + .mode = OMAP_ECC_HAMMING_CODE_HW_ROMCODE, + }, { + .name = "hw-romcode", + .mode = OMAP_ECC_HAMMING_CODE_HW_ROMCODE, + }, { + .name = "bch4", + .mode = OMAP_ECC_BCH4_CODE_HW, + }, { + .name = "bch8", + .mode = OMAP_ECC_BCH8_CODE_HW, + }, { + .name = "bch8-romcode", + .mode = OMAP_ECC_BCH8_CODE_HW_ROMCODE, + }, +}; + +static int gpmc_probe_nand_child(struct device_d *dev, + struct device_node *child) +{ + u32 val; + const char *s; + struct gpmc_timings gpmc_t; + struct gpmc_nand_platform_data gpmc_nand_data = {}; + struct resource res; + struct gpmc_settings gpmc_settings = {}; + int ret, i; + + if (of_property_read_u32(child, "reg", &val) < 0) { + dev_err(dev, "%s has no 'reg' property\n", + child->full_name); + return -ENODEV; + } + + gpmc_base = dev_get_mem_region(dev, 0); + if (!gpmc_base) + return -ENODEV; + + gpmc_nand_data.cs = val; + gpmc_nand_data.of_node = child; + + /* Detect availability of ELM module */ + gpmc_nand_data.elm_of_node = of_parse_phandle(child, "ti,elm-id", 0); + if (gpmc_nand_data.elm_of_node == NULL) + gpmc_nand_data.elm_of_node = + of_parse_phandle(child, "elm_id", 0); + if (gpmc_nand_data.elm_of_node == NULL) + pr_warn("%s: ti,elm-id property not found\n", __func__); + + /* select ecc-scheme for NAND */ + if (of_property_read_string(child, "ti,nand-ecc-opt", &s)) { + pr_err("%s: ti,nand-ecc-opt not found\n", __func__); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(modes); i++) { + struct dt_eccmode *mode = &modes[i]; + if (!strcmp(s, mode->name)) + gpmc_nand_data.ecc_mode = mode->mode; + } + + val = of_get_nand_bus_width(child); + if (val == 16) + gpmc_nand_data.device_width = NAND_BUSWIDTH_16; + + gpmc_read_timings_dt(child, &gpmc_t); + gpmc_read_settings_dt(child, &gpmc_settings); + + gpmc_nand_data.wait_mon_pin = gpmc_settings.wait_pin; + + gpmc_nand_data.nand_cfg = xzalloc(sizeof(*gpmc_nand_data.nand_cfg)); + + gpmc_timings_to_config(gpmc_nand_data.nand_cfg, &gpmc_t); + + gpmc_nand_data.nand_cfg->cfg[0] |= 2 << 10; + + ret = of_address_to_resource(child, 0, &res); + if (ret) + pr_err("of_address_to_resource failed\n"); + + gpmc_nand_data.nand_cfg->base = res.start; + gpmc_nand_data.nand_cfg->size = GPMC_SIZE_16M; + + gpmc_cs_config(gpmc_nand_data.cs, gpmc_nand_data.nand_cfg); + + dev = device_alloc("gpmc_nand", DEVICE_ID_DYNAMIC); + device_add_resource(dev, NULL, (resource_size_t)gpmc_base, SZ_4K, IORESOURCE_MEM); + device_add_data(dev, &gpmc_nand_data, sizeof(gpmc_nand_data)); + dev->device_node = child; + platform_device_register(dev); + + return 0; +} + +static int gpmc_probe(struct device_d *dev) +{ + struct device_node *child, *node = dev->device_node; + int ret; + + gpmc_generic_init(0x12); + + ret = of_property_read_u32(node, "gpmc,num-cs", + &gpmc_cs_num); + if (ret < 0) + return ret; + + ret = of_property_read_u32(node, "gpmc,num-waitpins", + &gpmc_nr_waitpins); + if (ret < 0) + return ret; + + for_each_child_of_node(node, child) { + + if (!child->name) + continue; + + if (!strncmp(child->name, "nand", 4)) + ret = gpmc_probe_nand_child(dev, child); + else + dev_warn(dev, "unhandled child %s\n", child->name); + + if (ret) + break; + } + + if (ret) + goto gpmc_err; + + return 0; + +gpmc_err: + return ret; +} + +static struct of_device_id gpmc_id_table[] = { + { .compatible = "ti,omap2420-gpmc" }, + { .compatible = "ti,omap2430-gpmc" }, + { .compatible = "ti,omap3430-gpmc" }, /* omap3430 & omap3630 */ + { .compatible = "ti,omap4430-gpmc" }, /* omap4430 & omap4460 & omap543x */ + { .compatible = "ti,am3352-gpmc" }, /* am335x devices */ + { } +}; + +static struct driver_d gpmc_driver = { + .name = "omap-gpmc", + .of_compatible = DRV_OF_COMPAT(gpmc_id_table), + .probe = gpmc_probe, +}; +device_platform_driver(gpmc_driver);