diff options
Diffstat (limited to 'drivers/memory')
| -rw-r--r-- | drivers/memory/Kconfig | 15 | ||||
| -rw-r--r-- | drivers/memory/Makefile | 2 | ||||
| -rw-r--r-- | drivers/memory/fsl_ifc.c | 309 | ||||
| -rw-r--r-- | drivers/memory/mvebu-devbus.c | 234 | ||||
| -rw-r--r-- | drivers/memory/ti-aemif.c | 427 | 
5 files changed, 915 insertions, 72 deletions
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 29a11db365b..c59e9c96e86 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -7,6 +7,17 @@ menuconfig MEMORY  if MEMORY +config TI_AEMIF +	tristate "Texas Instruments AEMIF driver" +	depends on (ARCH_DAVINCI || ARCH_KEYSTONE) && OF +	help +	  This driver is for the AEMIF module available in Texas Instruments +	  SoCs. AEMIF stands for Asynchronous External Memory Interface and +	  is intended to provide a glue-less interface to a variety of +	  asynchronuous memory devices like ASRAM, NOR and NAND memory. A total +	  of 256M bytes of any of these memories can be accessed at a given +	  time via four chip selects with 64M byte access per chip select. +  config TI_EMIF  	tristate "Texas Instruments EMIF driver"  	depends on ARCH_OMAP2PLUS @@ -50,4 +61,8 @@ config TEGRA30_MC  	  analysis, especially for IOMMU/SMMU(System Memory Management  	  Unit) module. +config FSL_IFC +	bool +	depends on FSL_SOC +  endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 969d923dad9..71160a2b731 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -5,7 +5,9 @@  ifeq ($(CONFIG_DDR),y)  obj-$(CONFIG_OF)		+= of_memory.o  endif +obj-$(CONFIG_TI_AEMIF)		+= ti-aemif.o  obj-$(CONFIG_TI_EMIF)		+= emif.o +obj-$(CONFIG_FSL_IFC)		+= fsl_ifc.o  obj-$(CONFIG_MVEBU_DEVBUS)	+= mvebu-devbus.o  obj-$(CONFIG_TEGRA20_MC)	+= tegra20-mc.o  obj-$(CONFIG_TEGRA30_MC)	+= tegra30-mc.o diff --git a/drivers/memory/fsl_ifc.c b/drivers/memory/fsl_ifc.c new file mode 100644 index 00000000000..3d5d792d5cb --- /dev/null +++ b/drivers/memory/fsl_ifc.c @@ -0,0 +1,309 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc + * + * Freescale Integrated Flash Controller + * + * Author: Dipen Dudhat <Dipen.Dudhat@freescale.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/compiler.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/fsl_ifc.h> +#include <asm/prom.h> + +struct fsl_ifc_ctrl *fsl_ifc_ctrl_dev; +EXPORT_SYMBOL(fsl_ifc_ctrl_dev); + +/* + * convert_ifc_address - convert the base address + * @addr_base:	base address of the memory bank + */ +unsigned int convert_ifc_address(phys_addr_t addr_base) +{ +	return addr_base & CSPR_BA; +} +EXPORT_SYMBOL(convert_ifc_address); + +/* + * fsl_ifc_find - find IFC bank + * @addr_base:	base address of the memory bank + * + * This function walks IFC banks comparing "Base address" field of the CSPR + * registers with the supplied addr_base argument. When bases match this + * function returns bank number (starting with 0), otherwise it returns + * appropriate errno value. + */ +int fsl_ifc_find(phys_addr_t addr_base) +{ +	int i = 0; + +	if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->regs) +		return -ENODEV; + +	for (i = 0; i < ARRAY_SIZE(fsl_ifc_ctrl_dev->regs->cspr_cs); i++) { +		u32 cspr = in_be32(&fsl_ifc_ctrl_dev->regs->cspr_cs[i].cspr); +		if (cspr & CSPR_V && (cspr & CSPR_BA) == +				convert_ifc_address(addr_base)) +			return i; +	} + +	return -ENOENT; +} +EXPORT_SYMBOL(fsl_ifc_find); + +static int fsl_ifc_ctrl_init(struct fsl_ifc_ctrl *ctrl) +{ +	struct fsl_ifc_regs __iomem *ifc = ctrl->regs; + +	/* +	 * Clear all the common status and event registers +	 */ +	if (in_be32(&ifc->cm_evter_stat) & IFC_CM_EVTER_STAT_CSER) +		out_be32(&ifc->cm_evter_stat, IFC_CM_EVTER_STAT_CSER); + +	/* enable all error and events */ +	out_be32(&ifc->cm_evter_en, IFC_CM_EVTER_EN_CSEREN); + +	/* enable all error and event interrupts */ +	out_be32(&ifc->cm_evter_intr_en, IFC_CM_EVTER_INTR_EN_CSERIREN); +	out_be32(&ifc->cm_erattr0, 0x0); +	out_be32(&ifc->cm_erattr1, 0x0); + +	return 0; +} + +static int fsl_ifc_ctrl_remove(struct platform_device *dev) +{ +	struct fsl_ifc_ctrl *ctrl = dev_get_drvdata(&dev->dev); + +	free_irq(ctrl->nand_irq, ctrl); +	free_irq(ctrl->irq, ctrl); + +	irq_dispose_mapping(ctrl->nand_irq); +	irq_dispose_mapping(ctrl->irq); + +	iounmap(ctrl->regs); + +	dev_set_drvdata(&dev->dev, NULL); +	kfree(ctrl); + +	return 0; +} + +/* + * NAND events are split between an operational interrupt which only + * receives OPC, and an error interrupt that receives everything else, + * including non-NAND errors.  Whichever interrupt gets to it first + * records the status and wakes the wait queue. + */ +static DEFINE_SPINLOCK(nand_irq_lock); + +static u32 check_nand_stat(struct fsl_ifc_ctrl *ctrl) +{ +	struct fsl_ifc_regs __iomem *ifc = ctrl->regs; +	unsigned long flags; +	u32 stat; + +	spin_lock_irqsave(&nand_irq_lock, flags); + +	stat = in_be32(&ifc->ifc_nand.nand_evter_stat); +	if (stat) { +		out_be32(&ifc->ifc_nand.nand_evter_stat, stat); +		ctrl->nand_stat = stat; +		wake_up(&ctrl->nand_wait); +	} + +	spin_unlock_irqrestore(&nand_irq_lock, flags); + +	return stat; +} + +static irqreturn_t fsl_ifc_nand_irq(int irqno, void *data) +{ +	struct fsl_ifc_ctrl *ctrl = data; + +	if (check_nand_stat(ctrl)) +		return IRQ_HANDLED; + +	return IRQ_NONE; +} + +/* + * NOTE: This interrupt is used to report ifc events of various kinds, + * such as transaction errors on the chipselects. + */ +static irqreturn_t fsl_ifc_ctrl_irq(int irqno, void *data) +{ +	struct fsl_ifc_ctrl *ctrl = data; +	struct fsl_ifc_regs __iomem *ifc = ctrl->regs; +	u32 err_axiid, err_srcid, status, cs_err, err_addr; +	irqreturn_t ret = IRQ_NONE; + +	/* read for chip select error */ +	cs_err = in_be32(&ifc->cm_evter_stat); +	if (cs_err) { +		dev_err(ctrl->dev, "transaction sent to IFC is not mapped to" +				"any memory bank 0x%08X\n", cs_err); +		/* clear the chip select error */ +		out_be32(&ifc->cm_evter_stat, IFC_CM_EVTER_STAT_CSER); + +		/* read error attribute registers print the error information */ +		status = in_be32(&ifc->cm_erattr0); +		err_addr = in_be32(&ifc->cm_erattr1); + +		if (status & IFC_CM_ERATTR0_ERTYP_READ) +			dev_err(ctrl->dev, "Read transaction error" +				"CM_ERATTR0 0x%08X\n", status); +		else +			dev_err(ctrl->dev, "Write transaction error" +				"CM_ERATTR0 0x%08X\n", status); + +		err_axiid = (status & IFC_CM_ERATTR0_ERAID) >> +					IFC_CM_ERATTR0_ERAID_SHIFT; +		dev_err(ctrl->dev, "AXI ID of the error" +					"transaction 0x%08X\n", err_axiid); + +		err_srcid = (status & IFC_CM_ERATTR0_ESRCID) >> +					IFC_CM_ERATTR0_ESRCID_SHIFT; +		dev_err(ctrl->dev, "SRC ID of the error" +					"transaction 0x%08X\n", err_srcid); + +		dev_err(ctrl->dev, "Transaction Address corresponding to error" +					"ERADDR 0x%08X\n", err_addr); + +		ret = IRQ_HANDLED; +	} + +	if (check_nand_stat(ctrl)) +		ret = IRQ_HANDLED; + +	return ret; +} + +/* + * fsl_ifc_ctrl_probe + * + * called by device layer when it finds a device matching + * one our driver can handled. This code allocates all of + * the resources needed for the controller only.  The + * resources for the NAND banks themselves are allocated + * in the chip probe function. +*/ +static int fsl_ifc_ctrl_probe(struct platform_device *dev) +{ +	int ret = 0; + + +	dev_info(&dev->dev, "Freescale Integrated Flash Controller\n"); + +	fsl_ifc_ctrl_dev = kzalloc(sizeof(*fsl_ifc_ctrl_dev), GFP_KERNEL); +	if (!fsl_ifc_ctrl_dev) +		return -ENOMEM; + +	dev_set_drvdata(&dev->dev, fsl_ifc_ctrl_dev); + +	/* IOMAP the entire IFC region */ +	fsl_ifc_ctrl_dev->regs = of_iomap(dev->dev.of_node, 0); +	if (!fsl_ifc_ctrl_dev->regs) { +		dev_err(&dev->dev, "failed to get memory region\n"); +		ret = -ENODEV; +		goto err; +	} + +	/* get the Controller level irq */ +	fsl_ifc_ctrl_dev->irq = irq_of_parse_and_map(dev->dev.of_node, 0); +	if (fsl_ifc_ctrl_dev->irq == NO_IRQ) { +		dev_err(&dev->dev, "failed to get irq resource " +							"for IFC\n"); +		ret = -ENODEV; +		goto err; +	} + +	/* get the nand machine irq */ +	fsl_ifc_ctrl_dev->nand_irq = +			irq_of_parse_and_map(dev->dev.of_node, 1); + +	fsl_ifc_ctrl_dev->dev = &dev->dev; + +	ret = fsl_ifc_ctrl_init(fsl_ifc_ctrl_dev); +	if (ret < 0) +		goto err; + +	init_waitqueue_head(&fsl_ifc_ctrl_dev->nand_wait); + +	ret = request_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_irq, IRQF_SHARED, +			  "fsl-ifc", fsl_ifc_ctrl_dev); +	if (ret != 0) { +		dev_err(&dev->dev, "failed to install irq (%d)\n", +			fsl_ifc_ctrl_dev->irq); +		goto err_irq; +	} + +	if (fsl_ifc_ctrl_dev->nand_irq) { +		ret = request_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_nand_irq, +				0, "fsl-ifc-nand", fsl_ifc_ctrl_dev); +		if (ret != 0) { +			dev_err(&dev->dev, "failed to install irq (%d)\n", +				fsl_ifc_ctrl_dev->nand_irq); +			goto err_nandirq; +		} +	} + +	return 0; + +err_nandirq: +	free_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_ctrl_dev); +	irq_dispose_mapping(fsl_ifc_ctrl_dev->nand_irq); +err_irq: +	free_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_dev); +	irq_dispose_mapping(fsl_ifc_ctrl_dev->irq); +err: +	return ret; +} + +static const struct of_device_id fsl_ifc_match[] = { +	{ +		.compatible = "fsl,ifc", +	}, +	{}, +}; + +static struct platform_driver fsl_ifc_ctrl_driver = { +	.driver = { +		.name	= "fsl-ifc", +		.of_match_table = fsl_ifc_match, +	}, +	.probe       = fsl_ifc_ctrl_probe, +	.remove      = fsl_ifc_ctrl_remove, +}; + +static int __init fsl_ifc_init(void) +{ +	return platform_driver_register(&fsl_ifc_ctrl_driver); +} +subsys_initcall(fsl_ifc_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Freescale Integrated Flash Controller driver"); diff --git a/drivers/memory/mvebu-devbus.c b/drivers/memory/mvebu-devbus.c index 110c0362705..ff7138fd66d 100644 --- a/drivers/memory/mvebu-devbus.c +++ b/drivers/memory/mvebu-devbus.c @@ -2,7 +2,7 @@   * Marvell EBU SoC Device Bus Controller   * (memory controller for NOR/NAND/SRAM/FPGA devices)   * - * Copyright (C) 2013 Marvell + * Copyright (C) 2013-2014 Marvell   *   * 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 @@ -30,19 +30,47 @@  #include <linux/platform_device.h>  /* Register definitions */ -#define DEV_WIDTH_BIT		30 -#define BADR_SKEW_BIT		28 -#define RD_HOLD_BIT		23 -#define ACC_NEXT_BIT		17 -#define RD_SETUP_BIT		12 -#define ACC_FIRST_BIT		6 - -#define SYNC_ENABLE_BIT		24 -#define WR_HIGH_BIT		16 -#define WR_LOW_BIT		8 - -#define READ_PARAM_OFFSET	0x0 -#define WRITE_PARAM_OFFSET	0x4 +#define ARMADA_DEV_WIDTH_SHIFT		30 +#define ARMADA_BADR_SKEW_SHIFT		28 +#define ARMADA_RD_HOLD_SHIFT		23 +#define ARMADA_ACC_NEXT_SHIFT		17 +#define ARMADA_RD_SETUP_SHIFT		12 +#define ARMADA_ACC_FIRST_SHIFT		6 + +#define ARMADA_SYNC_ENABLE_SHIFT	24 +#define ARMADA_WR_HIGH_SHIFT		16 +#define ARMADA_WR_LOW_SHIFT		8 + +#define ARMADA_READ_PARAM_OFFSET	0x0 +#define ARMADA_WRITE_PARAM_OFFSET	0x4 + +#define ORION_RESERVED			(0x2 << 30) +#define ORION_BADR_SKEW_SHIFT		28 +#define ORION_WR_HIGH_EXT_BIT		BIT(27) +#define ORION_WR_HIGH_EXT_MASK		0x8 +#define ORION_WR_LOW_EXT_BIT		BIT(26) +#define ORION_WR_LOW_EXT_MASK		0x8 +#define ORION_ALE_WR_EXT_BIT		BIT(25) +#define ORION_ALE_WR_EXT_MASK		0x8 +#define ORION_ACC_NEXT_EXT_BIT		BIT(24) +#define ORION_ACC_NEXT_EXT_MASK		0x10 +#define ORION_ACC_FIRST_EXT_BIT		BIT(23) +#define ORION_ACC_FIRST_EXT_MASK	0x10 +#define ORION_TURN_OFF_EXT_BIT		BIT(22) +#define ORION_TURN_OFF_EXT_MASK		0x8 +#define ORION_DEV_WIDTH_SHIFT		20 +#define ORION_WR_HIGH_SHIFT		17 +#define ORION_WR_HIGH_MASK		0x7 +#define ORION_WR_LOW_SHIFT		14 +#define ORION_WR_LOW_MASK		0x7 +#define ORION_ALE_WR_SHIFT		11 +#define ORION_ALE_WR_MASK		0x7 +#define ORION_ACC_NEXT_SHIFT		7 +#define ORION_ACC_NEXT_MASK		0xF +#define ORION_ACC_FIRST_SHIFT		3 +#define ORION_ACC_FIRST_MASK		0xF +#define ORION_TURN_OFF_SHIFT		0 +#define ORION_TURN_OFF_MASK		0x7  struct devbus_read_params {  	u32 bus_width; @@ -89,117 +117,167 @@ static int get_timing_param_ps(struct devbus *devbus,  	return 0;  } -static int devbus_set_timing_params(struct devbus *devbus, -				    struct device_node *node) +static int devbus_get_timing_params(struct devbus *devbus, +				    struct device_node *node, +				    struct devbus_read_params *r, +				    struct devbus_write_params *w)  { -	struct devbus_read_params r; -	struct devbus_write_params w; -	u32 value;  	int err; -	dev_dbg(devbus->dev, "Setting timing parameter, tick is %lu ps\n", -		devbus->tick_ps); - -	/* Get read timings */ -	err = of_property_read_u32(node, "devbus,bus-width", &r.bus_width); +	err = of_property_read_u32(node, "devbus,bus-width", &r->bus_width);  	if (err < 0) {  		dev_err(devbus->dev,  			"%s has no 'devbus,bus-width' property\n",  			node->full_name);  		return err;  	} -	/* Convert bit width to byte width */ -	r.bus_width /= 8; + +	/* +	 * The bus width is encoded into the register as 0 for 8 bits, +	 * and 1 for 16 bits, so we do the necessary conversion here. +	 */ +	if (r->bus_width == 8) +		r->bus_width = 0; +	else if (r->bus_width == 16) +		r->bus_width = 1; +	else { +		dev_err(devbus->dev, "invalid bus width %d\n", r->bus_width); +		return -EINVAL; +	}  	err = get_timing_param_ps(devbus, node, "devbus,badr-skew-ps", -				 &r.badr_skew); +				 &r->badr_skew);  	if (err < 0)  		return err;  	err = get_timing_param_ps(devbus, node, "devbus,turn-off-ps", -				 &r.turn_off); +				 &r->turn_off);  	if (err < 0)  		return err;  	err = get_timing_param_ps(devbus, node, "devbus,acc-first-ps", -				 &r.acc_first); +				 &r->acc_first);  	if (err < 0)  		return err;  	err = get_timing_param_ps(devbus, node, "devbus,acc-next-ps", -				 &r.acc_next); +				 &r->acc_next);  	if (err < 0)  		return err; -	err = get_timing_param_ps(devbus, node, "devbus,rd-setup-ps", -				 &r.rd_setup); -	if (err < 0) -		return err; - -	err = get_timing_param_ps(devbus, node, "devbus,rd-hold-ps", -				 &r.rd_hold); -	if (err < 0) -		return err; - -	/* Get write timings */ -	err = of_property_read_u32(node, "devbus,sync-enable", -				  &w.sync_enable); -	if (err < 0) { -		dev_err(devbus->dev, -			"%s has no 'devbus,sync-enable' property\n", -			node->full_name); -		return err; +	if (of_device_is_compatible(devbus->dev->of_node, "marvell,mvebu-devbus")) { +		err = get_timing_param_ps(devbus, node, "devbus,rd-setup-ps", +					  &r->rd_setup); +		if (err < 0) +			return err; + +		err = get_timing_param_ps(devbus, node, "devbus,rd-hold-ps", +					  &r->rd_hold); +		if (err < 0) +			return err; + +		err = of_property_read_u32(node, "devbus,sync-enable", +					   &w->sync_enable); +		if (err < 0) { +			dev_err(devbus->dev, +				"%s has no 'devbus,sync-enable' property\n", +				node->full_name); +			return err; +		}  	}  	err = get_timing_param_ps(devbus, node, "devbus,ale-wr-ps", -				 &w.ale_wr); +				 &w->ale_wr);  	if (err < 0)  		return err;  	err = get_timing_param_ps(devbus, node, "devbus,wr-low-ps", -				 &w.wr_low); +				 &w->wr_low);  	if (err < 0)  		return err;  	err = get_timing_param_ps(devbus, node, "devbus,wr-high-ps", -				 &w.wr_high); +				 &w->wr_high);  	if (err < 0)  		return err; +	return 0; +} + +static void devbus_orion_set_timing_params(struct devbus *devbus, +					  struct device_node *node, +					  struct devbus_read_params *r, +					  struct devbus_write_params *w) +{ +	u32 value; + +	/* +	 * The hardware designers found it would be a good idea to +	 * split most of the values in the register into two fields: +	 * one containing all the low-order bits, and another one +	 * containing just the high-order bit. For all of those +	 * fields, we have to split the value into these two parts. +	 */ +	value =	(r->turn_off   & ORION_TURN_OFF_MASK)  << ORION_TURN_OFF_SHIFT  | +		(r->acc_first  & ORION_ACC_FIRST_MASK) << ORION_ACC_FIRST_SHIFT | +		(r->acc_next   & ORION_ACC_NEXT_MASK)  << ORION_ACC_NEXT_SHIFT  | +		(w->ale_wr     & ORION_ALE_WR_MASK)    << ORION_ALE_WR_SHIFT    | +		(w->wr_low     & ORION_WR_LOW_MASK)    << ORION_WR_LOW_SHIFT    | +		(w->wr_high    & ORION_WR_HIGH_MASK)   << ORION_WR_HIGH_SHIFT   | +		r->bus_width                           << ORION_DEV_WIDTH_SHIFT | +		((r->turn_off  & ORION_TURN_OFF_EXT_MASK)  ? ORION_TURN_OFF_EXT_BIT  : 0) | +		((r->acc_first & ORION_ACC_FIRST_EXT_MASK) ? ORION_ACC_FIRST_EXT_BIT : 0) | +		((r->acc_next  & ORION_ACC_NEXT_EXT_MASK)  ? ORION_ACC_NEXT_EXT_BIT  : 0) | +		((w->ale_wr    & ORION_ALE_WR_EXT_MASK)    ? ORION_ALE_WR_EXT_BIT    : 0) | +		((w->wr_low    & ORION_WR_LOW_EXT_MASK)    ? ORION_WR_LOW_EXT_BIT    : 0) | +		((w->wr_high   & ORION_WR_HIGH_EXT_MASK)   ? ORION_WR_HIGH_EXT_BIT   : 0) | +		(r->badr_skew << ORION_BADR_SKEW_SHIFT) | +		ORION_RESERVED; + +	writel(value, devbus->base); +} + +static void devbus_armada_set_timing_params(struct devbus *devbus, +					   struct device_node *node, +					   struct devbus_read_params *r, +					   struct devbus_write_params *w) +{ +	u32 value; +  	/* Set read timings */ -	value = r.bus_width << DEV_WIDTH_BIT | -		r.badr_skew << BADR_SKEW_BIT | -		r.rd_hold   << RD_HOLD_BIT   | -		r.acc_next  << ACC_NEXT_BIT  | -		r.rd_setup  << RD_SETUP_BIT  | -		r.acc_first << ACC_FIRST_BIT | -		r.turn_off; +	value = r->bus_width << ARMADA_DEV_WIDTH_SHIFT | +		r->badr_skew << ARMADA_BADR_SKEW_SHIFT | +		r->rd_hold   << ARMADA_RD_HOLD_SHIFT   | +		r->acc_next  << ARMADA_ACC_NEXT_SHIFT  | +		r->rd_setup  << ARMADA_RD_SETUP_SHIFT  | +		r->acc_first << ARMADA_ACC_FIRST_SHIFT | +		r->turn_off;  	dev_dbg(devbus->dev, "read parameters register 0x%p = 0x%x\n", -		devbus->base + READ_PARAM_OFFSET, +		devbus->base + ARMADA_READ_PARAM_OFFSET,  		value); -	writel(value, devbus->base + READ_PARAM_OFFSET); +	writel(value, devbus->base + ARMADA_READ_PARAM_OFFSET);  	/* Set write timings */ -	value = w.sync_enable  << SYNC_ENABLE_BIT | -		w.wr_low       << WR_LOW_BIT      | -		w.wr_high      << WR_HIGH_BIT     | -		w.ale_wr; +	value = w->sync_enable  << ARMADA_SYNC_ENABLE_SHIFT | +		w->wr_low       << ARMADA_WR_LOW_SHIFT      | +		w->wr_high      << ARMADA_WR_HIGH_SHIFT     | +		w->ale_wr;  	dev_dbg(devbus->dev, "write parameters register: 0x%p = 0x%x\n", -		devbus->base + WRITE_PARAM_OFFSET, +		devbus->base + ARMADA_WRITE_PARAM_OFFSET,  		value); -	writel(value, devbus->base + WRITE_PARAM_OFFSET); - -	return 0; +	writel(value, devbus->base + ARMADA_WRITE_PARAM_OFFSET);  }  static int mvebu_devbus_probe(struct platform_device *pdev)  {  	struct device *dev = &pdev->dev;  	struct device_node *node = pdev->dev.of_node; +	struct devbus_read_params r; +	struct devbus_write_params w;  	struct devbus *devbus;  	struct resource *res;  	struct clk *clk; @@ -229,10 +307,21 @@ static int mvebu_devbus_probe(struct platform_device *pdev)  	rate = clk_get_rate(clk) / 1000;  	devbus->tick_ps = 1000000000 / rate; -	/* Read the device tree node and set the new timing parameters */ -	err = devbus_set_timing_params(devbus, node); -	if (err < 0) -		return err; +	dev_dbg(devbus->dev, "Setting timing parameter, tick is %lu ps\n", +		devbus->tick_ps); + +	if (!of_property_read_bool(node, "devbus,keep-config")) { +		/* Read the Device Tree node */ +		err = devbus_get_timing_params(devbus, node, &r, &w); +		if (err < 0) +			return err; + +		/* Set the new timing parameters */ +		if (of_device_is_compatible(node, "marvell,orion-devbus")) +			devbus_orion_set_timing_params(devbus, node, &r, &w); +		else +			devbus_armada_set_timing_params(devbus, node, &r, &w); +	}  	/*  	 * We need to create a child device explicitly from here to @@ -248,6 +337,7 @@ static int mvebu_devbus_probe(struct platform_device *pdev)  static const struct of_device_id mvebu_devbus_of_match[] = {  	{ .compatible = "marvell,mvebu-devbus" }, +	{ .compatible = "marvell,orion-devbus" },  	{},  };  MODULE_DEVICE_TABLE(of, mvebu_devbus_of_match); diff --git a/drivers/memory/ti-aemif.c b/drivers/memory/ti-aemif.c new file mode 100644 index 00000000000..d3df7602f40 --- /dev/null +++ b/drivers/memory/ti-aemif.c @@ -0,0 +1,427 @@ +/* + * TI AEMIF driver + * + * Copyright (C) 2010 - 2013 Texas Instruments Incorporated. http://www.ti.com/ + * + * Authors: + * Murali Karicheri <m-karicheri2@ti.com> + * Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#define TA_SHIFT	2 +#define RHOLD_SHIFT	4 +#define RSTROBE_SHIFT	7 +#define RSETUP_SHIFT	13 +#define WHOLD_SHIFT	17 +#define WSTROBE_SHIFT	20 +#define WSETUP_SHIFT	26 +#define EW_SHIFT	30 +#define SS_SHIFT	31 + +#define TA(x)		((x) << TA_SHIFT) +#define RHOLD(x)	((x) << RHOLD_SHIFT) +#define RSTROBE(x)	((x) << RSTROBE_SHIFT) +#define RSETUP(x)	((x) << RSETUP_SHIFT) +#define WHOLD(x)	((x) << WHOLD_SHIFT) +#define WSTROBE(x)	((x) << WSTROBE_SHIFT) +#define WSETUP(x)	((x) << WSETUP_SHIFT) +#define EW(x)		((x) << EW_SHIFT) +#define SS(x)		((x) << SS_SHIFT) + +#define ASIZE_MAX	0x1 +#define TA_MAX		0x3 +#define RHOLD_MAX	0x7 +#define RSTROBE_MAX	0x3f +#define RSETUP_MAX	0xf +#define WHOLD_MAX	0x7 +#define WSTROBE_MAX	0x3f +#define WSETUP_MAX	0xf +#define EW_MAX		0x1 +#define SS_MAX		0x1 +#define NUM_CS		4 + +#define TA_VAL(x)	(((x) & TA(TA_MAX)) >> TA_SHIFT) +#define RHOLD_VAL(x)	(((x) & RHOLD(RHOLD_MAX)) >> RHOLD_SHIFT) +#define RSTROBE_VAL(x)	(((x) & RSTROBE(RSTROBE_MAX)) >> RSTROBE_SHIFT) +#define RSETUP_VAL(x)	(((x) & RSETUP(RSETUP_MAX)) >> RSETUP_SHIFT) +#define WHOLD_VAL(x)	(((x) & WHOLD(WHOLD_MAX)) >> WHOLD_SHIFT) +#define WSTROBE_VAL(x)	(((x) & WSTROBE(WSTROBE_MAX)) >> WSTROBE_SHIFT) +#define WSETUP_VAL(x)	(((x) & WSETUP(WSETUP_MAX)) >> WSETUP_SHIFT) +#define EW_VAL(x)	(((x) & EW(EW_MAX)) >> EW_SHIFT) +#define SS_VAL(x)	(((x) & SS(SS_MAX)) >> SS_SHIFT) + +#define NRCSR_OFFSET	0x00 +#define AWCCR_OFFSET	0x04 +#define A1CR_OFFSET	0x10 + +#define ACR_ASIZE_MASK	0x3 +#define ACR_EW_MASK	BIT(30) +#define ACR_SS_MASK	BIT(31) +#define ASIZE_16BIT	1 + +#define CONFIG_MASK	(TA(TA_MAX) | \ +				RHOLD(RHOLD_MAX) | \ +				RSTROBE(RSTROBE_MAX) |	\ +				RSETUP(RSETUP_MAX) | \ +				WHOLD(WHOLD_MAX) | \ +				WSTROBE(WSTROBE_MAX) | \ +				WSETUP(WSETUP_MAX) | \ +				EW(EW_MAX) | SS(SS_MAX) | \ +				ASIZE_MAX) + +/** + * struct aemif_cs_data: structure to hold cs parameters + * @cs: chip-select number + * @wstrobe: write strobe width, ns + * @rstrobe: read strobe width, ns + * @wsetup: write setup width, ns + * @whold: write hold width, ns + * @rsetup: read setup width, ns + * @rhold: read hold width, ns + * @ta: minimum turn around time, ns + * @enable_ss: enable/disable select strobe mode + * @enable_ew: enable/disable extended wait mode + * @asize: width of the asynchronous device's data bus + */ +struct aemif_cs_data { +	u8	cs; +	u16	wstrobe; +	u16	rstrobe; +	u8	wsetup; +	u8	whold; +	u8	rsetup; +	u8	rhold; +	u8	ta; +	u8	enable_ss; +	u8	enable_ew; +	u8	asize; +}; + +/** + * struct aemif_device: structure to hold device data + * @base: base address of AEMIF registers + * @clk: source clock + * @clk_rate: clock's rate in kHz + * @num_cs: number of assigned chip-selects + * @cs_offset: start number of cs nodes + * @cs_data: array of chip-select settings + */ +struct aemif_device { +	void __iomem *base; +	struct clk *clk; +	unsigned long clk_rate; +	u8 num_cs; +	int cs_offset; +	struct aemif_cs_data cs_data[NUM_CS]; +}; + +/** + * aemif_calc_rate - calculate timing data. + * @pdev: platform device to calculate for + * @wanted: The cycle time needed in nanoseconds. + * @clk: The input clock rate in kHz. + * @max: The maximum divider value that can be programmed. + * + * On success, returns the calculated timing value minus 1 for easy + * programming into AEMIF timing registers, else negative errno. + */ +static int aemif_calc_rate(struct platform_device *pdev, int wanted, +			   unsigned long clk, int max) +{ +	int result; + +	result = DIV_ROUND_UP((wanted * clk), NSEC_PER_MSEC) - 1; + +	dev_dbg(&pdev->dev, "%s: result %d from %ld, %d\n", __func__, result, +		clk, wanted); + +	/* It is generally OK to have a more relaxed timing than requested... */ +	if (result < 0) +		result = 0; + +	/* ... But configuring tighter timings is not an option. */ +	else if (result > max) +		result = -EINVAL; + +	return result; +} + +/** + * aemif_config_abus - configure async bus parameters + * @pdev: platform device to configure for + * @csnum: aemif chip select number + * + * This function programs the given timing values (in real clock) into the + * AEMIF registers taking the AEMIF clock into account. + * + * This function does not use any locking while programming the AEMIF + * because it is expected that there is only one user of a given + * chip-select. + * + * Returns 0 on success, else negative errno. + */ +static int aemif_config_abus(struct platform_device *pdev, int csnum) +{ +	struct aemif_device *aemif = platform_get_drvdata(pdev); +	struct aemif_cs_data *data = &aemif->cs_data[csnum]; +	int ta, rhold, rstrobe, rsetup, whold, wstrobe, wsetup; +	unsigned long clk_rate = aemif->clk_rate; +	unsigned offset; +	u32 set, val; + +	offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4; + +	ta	= aemif_calc_rate(pdev, data->ta, clk_rate, TA_MAX); +	rhold	= aemif_calc_rate(pdev, data->rhold, clk_rate, RHOLD_MAX); +	rstrobe	= aemif_calc_rate(pdev, data->rstrobe, clk_rate, RSTROBE_MAX); +	rsetup	= aemif_calc_rate(pdev, data->rsetup, clk_rate, RSETUP_MAX); +	whold	= aemif_calc_rate(pdev, data->whold, clk_rate, WHOLD_MAX); +	wstrobe	= aemif_calc_rate(pdev, data->wstrobe, clk_rate, WSTROBE_MAX); +	wsetup	= aemif_calc_rate(pdev, data->wsetup, clk_rate, WSETUP_MAX); + +	if (ta < 0 || rhold < 0 || rstrobe < 0 || rsetup < 0 || +	    whold < 0 || wstrobe < 0 || wsetup < 0) { +		dev_err(&pdev->dev, "%s: cannot get suitable timings\n", +			__func__); +		return -EINVAL; +	} + +	set = TA(ta) | RHOLD(rhold) | RSTROBE(rstrobe) | RSETUP(rsetup) | +		WHOLD(whold) | WSTROBE(wstrobe) | WSETUP(wsetup); + +	set |= (data->asize & ACR_ASIZE_MASK); +	if (data->enable_ew) +		set |= ACR_EW_MASK; +	if (data->enable_ss) +		set |= ACR_SS_MASK; + +	val = readl(aemif->base + offset); +	val &= ~CONFIG_MASK; +	val |= set; +	writel(val, aemif->base + offset); + +	return 0; +} + +static inline int aemif_cycles_to_nsec(int val, unsigned long clk_rate) +{ +	return ((val + 1) * NSEC_PER_MSEC) / clk_rate; +} + +/** + * aemif_get_hw_params - function to read hw register values + * @pdev: platform device to read for + * @csnum: aemif chip select number + * + * This function reads the defaults from the registers and update + * the timing values. Required for get/set commands and also for + * the case when driver needs to use defaults in hardware. + */ +static void aemif_get_hw_params(struct platform_device *pdev, int csnum) +{ +	struct aemif_device *aemif = platform_get_drvdata(pdev); +	struct aemif_cs_data *data = &aemif->cs_data[csnum]; +	unsigned long clk_rate = aemif->clk_rate; +	u32 val, offset; + +	offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4; +	val = readl(aemif->base + offset); + +	data->ta = aemif_cycles_to_nsec(TA_VAL(val), clk_rate); +	data->rhold = aemif_cycles_to_nsec(RHOLD_VAL(val), clk_rate); +	data->rstrobe = aemif_cycles_to_nsec(RSTROBE_VAL(val), clk_rate); +	data->rsetup = aemif_cycles_to_nsec(RSETUP_VAL(val), clk_rate); +	data->whold = aemif_cycles_to_nsec(WHOLD_VAL(val), clk_rate); +	data->wstrobe = aemif_cycles_to_nsec(WSTROBE_VAL(val), clk_rate); +	data->wsetup = aemif_cycles_to_nsec(WSETUP_VAL(val), clk_rate); +	data->enable_ew = EW_VAL(val); +	data->enable_ss = SS_VAL(val); +	data->asize = val & ASIZE_MAX; +} + +/** + * of_aemif_parse_abus_config - parse CS configuration from DT + * @pdev: platform device to parse for + * @np: device node ptr + * + * This function update the emif async bus configuration based on the values + * configured in a cs device binding node. + */ +static int of_aemif_parse_abus_config(struct platform_device *pdev, +				      struct device_node *np) +{ +	struct aemif_device *aemif = platform_get_drvdata(pdev); +	struct aemif_cs_data *data; +	u32 cs; +	u32 val; + +	if (of_property_read_u32(np, "ti,cs-chipselect", &cs)) { +		dev_dbg(&pdev->dev, "cs property is required"); +		return -EINVAL; +	} + +	if (cs - aemif->cs_offset >= NUM_CS || cs < aemif->cs_offset) { +		dev_dbg(&pdev->dev, "cs number is incorrect %d", cs); +		return -EINVAL; +	} + +	if (aemif->num_cs >= NUM_CS) { +		dev_dbg(&pdev->dev, "cs count is more than %d", NUM_CS); +		return -EINVAL; +	} + +	data = &aemif->cs_data[aemif->num_cs]; +	data->cs = cs; + +	/* read the current value in the hw register */ +	aemif_get_hw_params(pdev, aemif->num_cs++); + +	/* override the values from device node */ +	if (!of_property_read_u32(np, "ti,cs-min-turnaround-ns", &val)) +		data->ta = val; + +	if (!of_property_read_u32(np, "ti,cs-read-hold-ns", &val)) +		data->rhold = val; + +	if (!of_property_read_u32(np, "ti,cs-read-strobe-ns", &val)) +		data->rstrobe = val; + +	if (!of_property_read_u32(np, "ti,cs-read-setup-ns", &val)) +		data->rsetup = val; + +	if (!of_property_read_u32(np, "ti,cs-write-hold-ns", &val)) +		data->whold = val; + +	if (!of_property_read_u32(np, "ti,cs-write-strobe-ns", &val)) +		data->wstrobe = val; + +	if (!of_property_read_u32(np, "ti,cs-write-setup-ns", &val)) +		data->wsetup = val; + +	if (!of_property_read_u32(np, "ti,cs-bus-width", &val)) +		if (val == 16) +			data->asize = 1; +	data->enable_ew = of_property_read_bool(np, "ti,cs-extended-wait-mode"); +	data->enable_ss = of_property_read_bool(np, "ti,cs-select-strobe-mode"); +	return 0; +} + +static const struct of_device_id aemif_of_match[] = { +	{ .compatible = "ti,davinci-aemif", }, +	{ .compatible = "ti,da850-aemif", }, +	{}, +}; + +static int aemif_probe(struct platform_device *pdev) +{ +	int i; +	int ret = -ENODEV; +	struct resource *res; +	struct device *dev = &pdev->dev; +	struct device_node *np = dev->of_node; +	struct device_node *child_np; +	struct aemif_device *aemif; + +	if (np == NULL) +		return 0; + +	aemif = devm_kzalloc(dev, sizeof(*aemif), GFP_KERNEL); +	if (!aemif) +		return -ENOMEM; + +	platform_set_drvdata(pdev, aemif); + +	aemif->clk = devm_clk_get(dev, NULL); +	if (IS_ERR(aemif->clk)) { +		dev_err(dev, "cannot get clock 'aemif'\n"); +		return PTR_ERR(aemif->clk); +	} + +	clk_prepare_enable(aemif->clk); +	aemif->clk_rate = clk_get_rate(aemif->clk) / MSEC_PER_SEC; + +	if (of_device_is_compatible(np, "ti,da850-aemif")) +		aemif->cs_offset = 2; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	aemif->base = devm_ioremap_resource(dev, res); +	if (IS_ERR(aemif->base)) { +		ret = PTR_ERR(aemif->base); +		goto error; +	} + +	/* +	 * For every controller device node, there is a cs device node that +	 * describe the bus configuration parameters. This functions iterate +	 * over these nodes and update the cs data array. +	 */ +	for_each_available_child_of_node(np, child_np) { +		ret = of_aemif_parse_abus_config(pdev, child_np); +		if (ret < 0) +			goto error; +	} + +	for (i = 0; i < aemif->num_cs; i++) { +		ret = aemif_config_abus(pdev, i); +		if (ret < 0) { +			dev_err(dev, "Error configuring chip select %d\n", +				aemif->cs_data[i].cs); +			goto error; +		} +	} + +	/* +	 * Create a child devices explicitly from here to +	 * guarantee that the child will be probed after the AEMIF timing +	 * parameters are set. +	 */ +	for_each_available_child_of_node(np, child_np) { +		ret = of_platform_populate(child_np, NULL, NULL, dev); +		if (ret < 0) +			goto error; +	} + +	return 0; +error: +	clk_disable_unprepare(aemif->clk); +	return ret; +} + +static int aemif_remove(struct platform_device *pdev) +{ +	struct aemif_device *aemif = platform_get_drvdata(pdev); + +	clk_disable_unprepare(aemif->clk); +	return 0; +} + +static struct platform_driver aemif_driver = { +	.probe = aemif_probe, +	.remove = aemif_remove, +	.driver = { +		.name = KBUILD_MODNAME, +		.owner = THIS_MODULE, +		.of_match_table = of_match_ptr(aemif_of_match), +	}, +}; + +module_platform_driver(aemif_driver); + +MODULE_AUTHOR("Murali Karicheri <m-karicheri2@ti.com>"); +MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>"); +MODULE_DESCRIPTION("Texas Instruments AEMIF driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME);  | 
