diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-04-01 16:13:21 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-04-01 16:13:21 -0700 |
commit | 675c354a95d5375153b8bb80a0448cab916c7991 (patch) | |
tree | 88cbc5a5a31dd1c1016271006a8d56cfe0abf7bd /drivers/memory | |
parent | c70929147a10fa4538886cb23b934b509c4c0e49 (diff) | |
parent | 1b3fa22e0234d613df967445cd34807e10fa54fa (diff) |
Merge tag 'char-misc-3.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc driver patches from Greg KH:
"Here's the big char/misc driver updates for 3.15-rc1.
Lots of various things here, including the new mcb driver subsystem.
All of these have been in linux-next for a while"
* tag 'char-misc-3.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (118 commits)
extcon: Move OF helper function to extcon core and change function name
extcon: of: Remove unnecessary function call by using the name of device_node
extcon: gpio: Use SIMPLE_DEV_PM_OPS macro
extcon: palmas: Use SIMPLE_DEV_PM_OPS macro
mei: don't use deprecated DEFINE_PCI_DEVICE_TABLE macro
mei: amthif: fix checkpatch error
mei: client.h fix checkpatch errors
mei: use cl_dbg where appropriate
mei: fix Unnecessary space after function pointer name
mei: report consistently copy_from/to_user failures
mei: drop pr_fmt macros
mei: make me hw headers private to me hw.
mei: fix memory leak of pending write cb objects
mei: me: do not reset when less than expected data is received
drivers: mcb: Fix build error discovered by 0-day bot
cs5535-mfgpt: Simplify dependencies
spmi: pm: drop bus-level PM suspend/resume routines
spmi: pmic_arb: make selectable on ARCH_QCOM
Drivers: hv: vmbus: Increase the limit on the number of pfns we can handle
pch_phub: Report error writing MAC back to user
...
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/ti-aemif.c | 427 |
4 files changed, 753 insertions, 0 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/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); |