lkml.org 
[lkml]   [2012]   [Feb]   [4]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[RFC PATCH 4/8] misc: emif: add basic infrastructure for EMIF driver
    Date
    EMIF is an SDRAM controller used in various Texas Instruments
    SoCs. EMIF supports, based on its revision, one or more of
    LPDDR2/DDR2/DDR3 protocols.

    Add the basic infrastructure for EMIF driver that includes
    driver registration, probe, parsing of platform data etc.

    Signed-off-by: Aneesh V <aneesh@ti.com>
    ---
    drivers/misc/Kconfig | 12 ++
    drivers/misc/Makefile | 1 +
    drivers/misc/emif.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++++
    include/linux/emif.h | 160 ++++++++++++++++++++++++++
    4 files changed, 473 insertions(+), 0 deletions(-)
    create mode 100644 drivers/misc/emif.c
    create mode 100644 include/linux/emif.h

    diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
    index 8337bf6..d68184a 100644
    --- a/drivers/misc/Kconfig
    +++ b/drivers/misc/Kconfig
    @@ -459,6 +459,18 @@ config DDR
    information. This data is useful for drivers handling
    DDR SDRAM controllers.

    +config EMIF
    + tristate "Texas Instruments EMIF driver"
    + select DDR
    + help
    + This driver is for the EMIF module available in Texas Instruments
    + SoCs. EMIF is an SDRAM controller that, based on its revision,
    + supports one or more of DDR2, DDR3, and LPDDR2 SDRAM protocols.
    + This driver takes care of only LPDDR2 memories presently. The
    + functions of the driver includes re-configuring AC timing
    + parameters and other settings during frequency, voltage and
    + temperature changes
    +
    config ARM_CHARLCD
    bool "ARM Ltd. Character LCD Driver"
    depends on PLAT_VERSATILE
    diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
    index 4759166..076db0f 100644
    --- a/drivers/misc/Makefile
    +++ b/drivers/misc/Makefile
    @@ -37,6 +37,7 @@ obj-$(CONFIG_C2PORT) += c2port/
    obj-$(CONFIG_IWMC3200TOP) += iwmc3200top/
    obj-$(CONFIG_HMC6352) += hmc6352.o
    obj-$(CONFIG_DDR) += jedec_ddr_data.o
    +obj-$(CONFIG_EMIF) += emif.o
    obj-y += eeprom/
    obj-y += cb710/
    obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
    diff --git a/drivers/misc/emif.c b/drivers/misc/emif.c
    new file mode 100644
    index 0000000..ba1e3f9
    --- /dev/null
    +++ b/drivers/misc/emif.c
    @@ -0,0 +1,300 @@
    +/*
    + * EMIF driver
    + *
    + * Copyright (C) 2010 Texas Instruments, Inc.
    + *
    + * Aneesh V <aneesh@ti.com>
    + * Santosh Shilimkar <santosh.shilimkar@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/kernel.h>
    +#include <linux/reboot.h>
    +#include <linux/emif.h>
    +#include <linux/io.h>
    +#include <linux/device.h>
    +#include <linux/platform_device.h>
    +#include <linux/interrupt.h>
    +#include <linux/slab.h>
    +#include <linux/seq_file.h>
    +#include <linux/module.h>
    +#include <linux/spinlock.h>
    +#include "emif_regs.h"
    +
    +/**
    + * struct emif_data - Per device static data for driver's use
    + * @duplicate: Whether the DDR devices attached to this EMIF
    + * instance are exactly same as that on EMIF1. In
    + * this case we can save some memory and processing
    + * @temperature_level: Maximum temperature of LPDDR2 devices attached
    + * to this EMIF - read from MR4 register. If there
    + * are two devices attached to this EMIF, this
    + * value is the maximum of the two temperature
    + * levels.
    + * @irq: IRQ number
    + * @lock: lock for protecting temperature_level and
    + * associated actions from race conditions
    + * @base: base address of memory-mapped IO registers.
    + * @dev: device pointer.
    + * @addressing table with addressing information from the spec
    + * @regs_cache: An array of 'struct emif_regs' that stores
    + * calculated register values for different
    + * frequencies, to avoid re-calculating them on
    + * each DVFS transition.
    + * @curr_regs: The set of register values used in the last
    + * frequency change (i.e. corresponding to the
    + * frequency in effect at the moment)
    + * @plat_data: Pointer to saved platform data.
    + */
    +struct emif_data {
    + u8 duplicate;
    + u8 temperature_level;
    + u32 irq;
    + spinlock_t lock; /* lock to prevent races */
    + void __iomem *base;
    + struct device *dev;
    + const struct lpddr2_addressing *addressing;
    + struct emif_regs *regs_cache[EMIF_MAX_NUM_FREQUENCIES];
    + struct emif_regs *curr_regs;
    + struct emif_platform_data *plat_data;
    +};
    +
    +static struct emif_data *emif1;
    +
    +static void __exit cleanup_emif(struct emif_data *emif)
    +{
    + if (emif) {
    + if (emif->plat_data) {
    + kfree(emif->plat_data->custom_configs);
    + kfree(emif->plat_data->timings);
    + kfree(emif->plat_data->min_tck);
    + kfree(emif->plat_data->device_info);
    + kfree(emif->plat_data);
    + }
    + kfree(emif);
    + }
    +}
    +
    +static void get_default_timings(struct emif_data *emif)
    +{
    + struct emif_platform_data *pd = emif->plat_data;
    +
    + pd->timings = lpddr2_jedec_timings;
    + pd->timings_arr_size = ARRAY_SIZE(lpddr2_jedec_timings);
    +
    + dev_warn(emif->dev, "Using default timings\n");
    +}
    +
    +static int is_dev_data_valid(u32 type, u32 density, u32 io_width, u32 phy_type,
    + u32 ip_rev, struct device *dev)
    +{
    + int valid;
    +
    + valid = (type == DDR_TYPE_LPDDR2_S4 ||
    + type == DDR_TYPE_LPDDR2_S2)
    + && (density >= DDR_DENSITY_64Mb
    + && density <= DDR_DENSITY_8Gb)
    + && (io_width >= DDR_IO_WIDTH_8
    + && io_width <= DDR_IO_WIDTH_32);
    +
    + /* Combinations of EMIF and PHY revisions that we support today */
    + switch (ip_rev) {
    + case EMIF_4D:
    + valid = valid && (phy_type == EMIF_PHY_TYPE_ATTILAPHY);
    + break;
    + case EMIF_4D5:
    + valid = valid && (phy_type == EMIF_PHY_TYPE_INTELLIPHY);
    + break;
    + default:
    + valid = 0;
    + }
    +
    + if (!valid)
    + dev_err(dev, "Invalid DDR details\n");
    + return valid;
    +}
    +
    +static int is_custom_config_valid(struct emif_custom_configs *cust_cfgs,
    + struct device *dev)
    +{
    + int valid = 1;
    +
    + if ((cust_cfgs->mask & EMIF_CUSTOM_CONFIG_LPMODE) &&
    + (cust_cfgs->lpmode != EMIF_LP_MODE_DISABLE))
    + valid = cust_cfgs->lpmode_freq_threshold &&
    + cust_cfgs->lpmode_timeout_performance &&
    + cust_cfgs->lpmode_timeout_power;
    +
    + if (cust_cfgs->mask & EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL)
    + valid = valid && cust_cfgs->temp_alert_poll_interval_ms;
    +
    + if (!valid)
    + dev_warn(dev, "Invalid custom configs\n");
    +
    + return valid;
    +}
    +
    +static struct emif_data * __init get_device_details(
    + struct platform_device *pdev)
    +{
    + u32 size;
    + struct emif_data *emif = NULL;
    + struct ddr_device_info *dev_info;
    + struct emif_platform_data *pd;
    +
    + pd = pdev->dev.platform_data;
    +
    + if (!(pd && pd->device_info && is_dev_data_valid(pd->device_info->type,
    + pd->device_info->density, pd->device_info->io_width,
    + pd->phy_type, pd->ip_rev, &pdev->dev)))
    + goto error;
    +
    + emif = kzalloc(sizeof(struct emif_data), GFP_KERNEL);
    + pd = kmemdup(pd, sizeof(*pd), GFP_KERNEL);
    + dev_info = kmemdup(pd->device_info,
    + sizeof(*pd->device_info), GFP_KERNEL);
    +
    + if (!emif || !pd || !dev_info)
    + goto error;
    +
    + emif->plat_data = pd;
    + emif->dev = &pdev->dev;
    + emif->temperature_level = SDRAM_TEMP_NOMINAL;
    +
    + pd->device_info = dev_info;
    +
    + /*
    + * For EMIF instances other than EMIF1 see if the devices connected
    + * are exactly same as on EMIF1(which is typically the case). If so,
    + * mark it as a duplicate of EMIF1 and skip copying timings data.
    + * This will save some memory and some computation later.
    + */
    + emif->duplicate = emif1 && (memcmp(dev_info,
    + emif1->plat_data->device_info,
    + sizeof(struct ddr_device_info)) == 0);
    +
    + if (emif->duplicate) {
    + pd->timings = NULL;
    + pd->min_tck = NULL;
    + goto out;
    + }
    +
    + /*
    + * Copy custom configs - ignore allocation error, if any, as
    + * custom_configs is not very critical
    + */
    + if (pd->custom_configs)
    + pd->custom_configs = kmemdup(pd->custom_configs,
    + sizeof(*pd->custom_configs), GFP_KERNEL);
    +
    + if (pd->custom_configs &&
    + !is_custom_config_valid(pd->custom_configs, emif->dev)) {
    + kfree(pd->custom_configs);
    + pd->custom_configs = NULL;
    + }
    +
    + /*
    + * Copy timings and min-tck values from platform data. If it is not
    + * available or if memory allocation fails, use JEDEC defaults
    + */
    + size = sizeof(struct lpddr2_timings) * pd->timings_arr_size;
    + if (pd->timings)
    + pd->timings = kmemdup(pd->timings, size, GFP_KERNEL);
    +
    + if (!pd->timings)
    + get_default_timings(emif);
    +
    + if (pd->min_tck)
    + pd->min_tck = kmemdup(pd->min_tck, sizeof(*pd->min_tck),
    + GFP_KERNEL);
    +
    + if (!pd->min_tck) {
    + pd->min_tck = &lpddr2_min_tck;
    + dev_warn(emif->dev, "Using default min-tck values\n");
    + }
    +
    + goto out;
    +
    +error:
    + dev_err(&pdev->dev, "get_device_details() failure!!\n");
    + cleanup_emif(emif);
    + return NULL;
    +
    +out:
    + return emif;
    +}
    +
    +static int __init emif_probe(struct platform_device *pdev)
    +{
    + struct emif_data *emif;
    + struct resource *res;
    +
    + emif = get_device_details(pdev);
    +
    + if (!emif)
    + goto error;
    +
    + if (!emif1)
    + emif1 = emif;
    +
    + /* Save pointers to each other in emif and device structures */
    + emif->dev = &pdev->dev;
    + platform_set_drvdata(pdev, emif);
    +
    + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    + if (!res)
    + goto error;
    +
    + emif->base = ioremap(res->start, SZ_1M);
    + if (!emif->base)
    + goto error;
    +
    + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    + if (!res)
    + goto error;
    + emif->irq = res->start;
    +
    + dev_info(&pdev->dev, "Device configured with addr = %p and IRQ%d\n",
    + emif->base, emif->irq);
    + return 0;
    +
    +error:
    + dev_err(&pdev->dev, "probe failure!!\n");
    + cleanup_emif(emif);
    + return -ENODEV;
    +}
    +
    +static int __exit emif_remove(struct platform_device *pdev)
    +{
    + struct emif_data *emif = platform_get_drvdata(pdev);
    +
    + cleanup_emif(emif);
    +
    + return 0;
    +}
    +
    +static struct platform_driver emif_driver = {
    + .remove = __exit_p(emif_remove),
    + .driver = {
    + .name = "emif",
    + },
    +};
    +
    +static int __init emif_register(void)
    +{
    + return platform_driver_probe(&emif_driver, emif_probe);
    +}
    +
    +static void __exit emif_unregister(void)
    +{
    + platform_driver_unregister(&emif_driver);
    +}
    +
    +module_init(emif_register);
    +module_exit(emif_unregister);
    +MODULE_DESCRIPTION("TI EMIF SDRAM Controller Driver");
    +MODULE_LICENSE("GPL");
    +MODULE_ALIAS("platform:emif");
    +MODULE_AUTHOR("Texas Instruments Inc");
    diff --git a/include/linux/emif.h b/include/linux/emif.h
    new file mode 100644
    index 0000000..4ddf20b
    --- /dev/null
    +++ b/include/linux/emif.h
    @@ -0,0 +1,160 @@
    +/*
    + * Definitions for EMIF SDRAM controller in TI SoCs
    + *
    + * Copyright (C) 2010 Texas Instruments, Inc.
    + *
    + * Aneesh V <aneesh@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.
    + */
    +#ifndef __LINUX_EMIF_H
    +#define __LINUX_EMIF_H
    +
    +#ifndef __ASSEMBLY__
    +#include <linux/jedec_ddr.h>
    +
    +/*
    + * Maximum number of different frequencies supported by EMIF driver
    + * Determines the number of entries in the pointer array for register
    + * cache
    + */
    +#define EMIF_MAX_NUM_FREQUENCIES 6
    +
    +/* EMIF_PWR_MGMT_CTRL register */
    +/* Low power modes */
    +#define EMIF_LP_MODE_DISABLE 0
    +#define EMIF_LP_MODE_CLOCK_STOP 1
    +#define EMIF_LP_MODE_SELF_REFRESH 2
    +#define EMIF_LP_MODE_PWR_DN 4
    +
    +/* Hardware capabilities */
    +#define EMIF_HW_CAPS_LL_INTERFACE 0x00000001
    +
    +/* EMIF IP Revisions */
    +#define EMIF_4D 1
    +#define EMIF_4D5 2
    +
    +/* PHY types */
    +#define EMIF_PHY_TYPE_ATTILAPHY 1
    +#define EMIF_PHY_TYPE_INTELLIPHY 2
    +
    +/* Custom config requests */
    +#define EMIF_CUSTOM_CONFIG_LPMODE 0x00000001
    +#define EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL 0x00000002
    +
    +/*
    + * Structure containing shadow of important registers in EMIF
    + * The calculation function fills in this structure to be later used for
    + * initialisation and DVFS
    + */
    +struct emif_regs {
    + u32 freq;
    + u32 ref_ctrl_shdw;
    + u32 ref_ctrl_shdw_derated;
    + u32 sdram_tim1_shdw;
    + u32 sdram_tim1_shdw_derated;
    + u32 sdram_tim2_shdw;
    + u32 sdram_tim3_shdw;
    + u32 sdram_tim3_shdw_derated;
    + u32 pwr_mgmt_ctrl_shdw;
    + union {
    + u32 read_idle_ctrl_shdw_normal;
    + u32 dll_calib_ctrl_shdw_normal;
    + };
    + union {
    + u32 read_idle_ctrl_shdw_volt_ramp;
    + u32 dll_calib_ctrl_shdw_volt_ramp;
    + };
    +
    + u32 phy_ctrl_1_shdw;
    + u32 ext_phy_ctrl_2_shdw;
    + u32 ext_phy_ctrl_3_shdw;
    + u32 ext_phy_ctrl_4_shdw;
    +};
    +
    +/**
    + * struct ddr_device_info - All information about the DDR device except AC
    + * timing parameters
    + * @type: Device type (LPDDR2-S4, LPDDR2-S2 etc)
    + * @density: Device density
    + * @io_width: Bus width
    + * @cs1_used: Whether there is a DDR device attached to the second
    + * chip-select(CS1) of this EMIF instance
    + * @cal_resistors_per_cs: Whether there is one calibration resistor per
    + * chip-select or whether it's a single one for both
    + * @manufacturer: Manufacturer name string
    + */
    +struct ddr_device_info {
    + u32 type;
    + u32 density;
    + u32 io_width;
    + u32 cs1_used;
    + u32 cal_resistors_per_cs;
    + char manufacturer[10];
    +};
    +
    +/**
    + * struct emif_custom_configs - Custom configuration parameters/policies
    + * passed from the platform layer
    + * @mask: Mask to indicate which configs are requested
    + * @lpmode: LPMODE to be used in PWR_MGMT_CTRL register
    + * @lpmode_timeout_performance: Timeout before LPMODE entry when higher
    + * performance is desired at the cost of power (typically
    + * at higher OPPs)
    + * @lpmode_timeout_power: Timeout before LPMODE entry when better power
    + * savings is desired and performance is not important
    + * (typically at lower loads indicated by lower OPPs)
    + * @lpmode_freq_threshold: The DDR frequency threshold to identify between
    + * the above two cases:
    + * timeout = (freq >= lpmode_freq_threshold) ?
    + * lpmode_timeout_performance :
    + * lpmode_timeout_power;
    + * @temp_alert_poll_interval_ms: LPDDR2 MR4 polling interval at nominal
    + * temperature(in milliseconds). When temperature is high
    + * polling is done 4 times as frequently.
    + */
    +struct emif_custom_configs {
    + u32 mask;
    + u32 lpmode;
    + u32 lpmode_timeout_performance;
    + u32 lpmode_timeout_power;
    + u32 lpmode_freq_threshold;
    + u32 temp_alert_poll_interval_ms;
    +};
    +
    +/**
    + * struct emif_platform_data - Platform data passed on EMIF platform
    + * device creation. Used by the driver.
    + * @hw_caps: Hw capabilities of the EMIF IP in the respective SoC
    + * @device_info: Device info structure containing information such
    + * as type, bus width, density etc
    + * @timings: Timings information from device datasheet passed
    + * as an array of 'struct lpddr2_timings'. Can be NULL
    + * if if default timings are ok
    + * @timings_arr_size: Size of the timings array. Depends on the number
    + * of different frequencies for which timings data
    + * is provided
    + * @min_tck: Minimum value of some timing parameters in terms
    + * of number of cycles. Can be NULL if default values
    + * are ok
    + * @custom_configs: Custom configurations requested by SoC or board
    + * code and the data for them. Can be NULL if default
    + * configurations done by the driver are ok. See
    + * documentation for 'struct emif_custom_configs' for
    + * more details
    + */
    +struct emif_platform_data {
    + u32 hw_caps;
    + struct ddr_device_info *device_info;
    + const struct lpddr2_timings *timings;
    + u32 timings_arr_size;
    + const struct lpddr2_min_tck *min_tck;
    + struct emif_custom_configs *custom_configs;
    + u32 ip_rev;
    + u32 phy_type;
    +};
    +#endif /* __ASSEMBLY__ */
    +
    +#endif /* __LINUX_EMIF_H */
    --
    1.7.1


    \
     
     \ /
      Last update: 2012-02-04 13:19    [W:0.073 / U:34.648 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site