Re: [RFC PATCH 4/8] misc: emif: add basic infrastructure for EMIF driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Aneesh,

On 2/4/2012 1:16 PM, Aneesh V wrote:
> 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@xxxxxx>
> ---
>   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.

Nit: 2012?

> + *
> + * Aneesh V<aneesh@xxxxxx>
> + * Santosh Shilimkar<santosh.shilimkar@xxxxxx>
> + *
> + * 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

Do you really need to store the 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 */

Nit: That comment is useless, since you already have the kerneldoc comment before.

> +	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);

You should use the devm_* version of this API to get the simplify the error handling / removal.

> +	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;

The error report will not be really useful since you will not return the exact source of failure.

> +
> +	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");

That's not a very good error message, isn't?

> +	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;

You should reserve the region before ioremapping it. Something like that:

if (!devm_request_mem_region(dev, res->start, resource_size(res), pdev->name)) {
       dev_err(dev, "Region already claimed\n");
       return -EBUSY;
}

> +
> +	emif->base = ioremap(res->start, SZ_1M);

Use devm_ioremap.

> +	if (!emif->base)
> +		goto error;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

You should use platform_get_irq instead.

> +	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");

Because??? You should be a little bit more verbose in case of failure.

Regards,
Benoit

> +	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@xxxxxx>
> + *
> + * 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 */

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux