Skip to content

Commit

Permalink
memory: emif: add basic infrastructure for EMIF driver
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Santosh Shilimkar <[email protected]>
Reviewed-by: Benoit Cousson <[email protected]>
[[email protected]: Moved to drivers/memory from drivers/misc]
Signed-off-by: Santosh Shilimkar <[email protected]>
Tested-by: Lokesh Vutla <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
aneeshv authored and gregkh committed May 2, 2012
1 parent 6c8b090 commit 7ec9445
Show file tree
Hide file tree
Showing 8 changed files with 511 additions and 0 deletions.
57 changes: 57 additions & 0 deletions Documentation/memory-devices/ti-emif.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
TI EMIF SDRAM Controller Driver:

Author
========
Aneesh V <[email protected]>

Location
============
driver/memory/emif.c

Supported SoCs:
===================
TI OMAP44xx
TI OMAP54xx

Menuconfig option:
==========================
Device Drivers
Memory devices
Texas Instruments EMIF driver

Description
===========
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

Platform Data (see include/linux/platform_data/emif_plat.h):
=====================================================================
DDR device details and other board dependent and SoC dependent
information can be passed through platform data (struct emif_platform_data)
- DDR device details: 'struct ddr_device_info'
- Device AC timings: 'struct lpddr2_timings' and 'struct lpddr2_min_tck'
- Custom configurations: customizable policy options through
'struct emif_custom_configs'
- IP revision
- PHY type

Interface to the external world:
================================
EMIF driver registers notifiers for voltage and frequency changes
affecting EMIF and takes appropriate actions when these are invoked.
- freq_pre_notify_handling()
- freq_post_notify_handling()
- volt_notify_handling()

Debugfs
========
The driver creates two debugfs entries per device.
- regcache_dump : dump of register values calculated and saved for all
frequencies used so far.
- mr4 : last polled value of MR4 register in the LPDDR2 device. MR4
indicates the current temperature level of the device.
2 changes: 2 additions & 0 deletions drivers/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@ source "drivers/devfreq/Kconfig"

source "drivers/extcon/Kconfig"

source "drivers/memory/Kconfig"

endmenu
1 change: 1 addition & 0 deletions drivers/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,4 @@ obj-$(CONFIG_HYPERV) += hv/

obj-$(CONFIG_PM_DEVFREQ) += devfreq/
obj-$(CONFIG_EXTCON) += extcon/
obj-$(CONFIG_MEMORY) += memory/
22 changes: 22 additions & 0 deletions drivers/memory/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# Memory devices
#

menuconfig MEMORY
bool "Memory Controller drivers"

if MEMORY

config TI_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

endif
5 changes: 5 additions & 0 deletions drivers/memory/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
# Makefile for memory devices
#

obj-$(CONFIG_TI_EMIF) += emif.o
289 changes: 289 additions & 0 deletions drivers/memory/emif.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
/*
* EMIF driver
*
* Copyright (C) 2012 Texas Instruments, Inc.
*
* Aneesh V <[email protected]>
* Santosh Shilimkar <[email protected]>
*
* 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/platform_data/emif_plat.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/list.h>
#include <memory/jedec_ddr.h>
#include "emif.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.
* @node: node in the device list
* @base: base address of memory-mapped IO registers.
* @dev: device pointer.
* @plat_data: Pointer to saved platform data.
*/
struct emif_data {
u8 duplicate;
u8 temperature_level;
struct list_head node;
void __iomem *base;
struct device *dev;
struct emif_platform_data *plat_data;
};

static struct emif_data *emif1;
static LIST_HEAD(device_list);

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, "%s: using default timings\n", __func__);
}

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, "%s: invalid DDR details\n", __func__);
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, "%s: invalid custom configs\n", __func__);

return valid;
}

static struct emif_data *__init_or_module get_device_details(
struct platform_device *pdev)
{
u32 size;
struct emif_data *emif = NULL;
struct ddr_device_info *dev_info;
struct emif_custom_configs *cust_cfgs;
struct emif_platform_data *pd;
struct device *dev;
void *temp;

pd = pdev->dev.platform_data;
dev = &pdev->dev;

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, dev))) {
dev_err(dev, "%s: invalid device data\n", __func__);
goto error;
}

emif = devm_kzalloc(dev, sizeof(*emif), GFP_KERNEL);
temp = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
dev_info = devm_kzalloc(dev, sizeof(*dev_info), GFP_KERNEL);

if (!emif || !pd || !dev_info) {
dev_err(dev, "%s:%d: allocation error\n", __func__, __LINE__);
goto error;
}

memcpy(temp, pd, sizeof(*pd));
pd = temp;
memcpy(dev_info, pd->device_info, sizeof(*dev_info));

pd->device_info = dev_info;
emif->plat_data = pd;
emif->dev = dev;
emif->temperature_level = SDRAM_TEMP_NOMINAL;

/*
* 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;
} else if (emif1) {
dev_warn(emif->dev, "%s: Non-symmetric DDR geometry\n",
__func__);
}

/*
* Copy custom configs - ignore allocation error, if any, as
* custom_configs is not very critical
*/
cust_cfgs = pd->custom_configs;
if (cust_cfgs && is_custom_config_valid(cust_cfgs, dev)) {
temp = devm_kzalloc(dev, sizeof(*cust_cfgs), GFP_KERNEL);
if (temp)
memcpy(temp, cust_cfgs, sizeof(*cust_cfgs));
else
dev_warn(dev, "%s:%d: allocation error\n", __func__,
__LINE__);
pd->custom_configs = temp;
}

/*
* 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) {
temp = devm_kzalloc(dev, size, GFP_KERNEL);
if (temp) {
memcpy(temp, pd->timings, sizeof(*pd->timings));
pd->timings = temp;
} else {
dev_warn(dev, "%s:%d: allocation error\n", __func__,
__LINE__);
get_default_timings(emif);
}
} else {
get_default_timings(emif);
}

if (pd->min_tck) {
temp = devm_kzalloc(dev, sizeof(*pd->min_tck), GFP_KERNEL);
if (temp) {
memcpy(temp, pd->min_tck, sizeof(*pd->min_tck));
pd->min_tck = temp;
} else {
dev_warn(dev, "%s:%d: allocation error\n", __func__,
__LINE__);
pd->min_tck = &lpddr2_jedec_min_tck;
}
} else {
pd->min_tck = &lpddr2_jedec_min_tck;
}

out:
return emif;

error:
return NULL;
}

static int __init_or_module emif_probe(struct platform_device *pdev)
{
struct emif_data *emif;
struct resource *res;

emif = get_device_details(pdev);
if (!emif) {
pr_err("%s: error getting device data\n", __func__);
goto error;
}

if (!emif1)
emif1 = emif;

list_add(&emif->node, &device_list);

/* 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) {
dev_err(emif->dev, "%s: error getting memory resource\n",
__func__);
goto error;
}

emif->base = devm_request_and_ioremap(emif->dev, res);
if (!emif->base) {
dev_err(emif->dev, "%s: devm_request_and_ioremap() failed\n",
__func__);
goto error;
}

dev_info(&pdev->dev, "%s: device configured with addr = %p\n",
__func__, emif->base);

return 0;
error:
return -ENODEV;
}

static struct platform_driver emif_driver = {
.driver = {
.name = "emif",
},
};

static int __init_or_module 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");
Loading

0 comments on commit 7ec9445

Please sign in to comment.