memory: emif: add interrupt and temperature handling

Add an ISR for EMIF that:
	1. reports details of access errors
	2. takes action on thermal events

Also clear all interrupts on shut-down. Pending IRQs
may casue problems during warm-reset.

Temperature handling:
EMIF can be configured to poll the temperature level
of an LPDDR2 device from the MR4 mode register in the
device. EMIF generates an interrupt whenever it identifies
a temperature level change between two consecutive pollings.

Some of the timing parameters need to be de-rated at high
temperatures. The interrupt handler takes care of doing
this and also takes care of going back to nominal settings
when temperature falls back to nominal levels.

Signed-off-by: Aneesh V <aneesh@ti.com>
Reviewed-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Reviewed-by: Benoit Cousson <b-cousson@ti.com>
[santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc]
Signed-off-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Aneesh V 2012-04-27 17:54:07 +05:30 committed by Greg Kroah-Hartman
parent a93de288aa
commit 68b4aee35d

View File

@ -544,6 +544,42 @@ static u32 get_pwr_mgmt_ctrl(u32 freq, struct emif_data *emif, u32 ip_rev)
return pwr_mgmt_ctrl;
}
/*
* Get the temperature level of the EMIF instance:
* Reads the MR4 register of attached SDRAM parts to find out the temperature
* level. If there are two parts attached(one on each CS), then the temperature
* level for the EMIF instance is the higher of the two temperatures.
*/
static void get_temperature_level(struct emif_data *emif)
{
u32 temp, temperature_level;
void __iomem *base;
base = emif->base;
/* Read mode register 4 */
writel(DDR_MR4, base + EMIF_LPDDR2_MODE_REG_CONFIG);
temperature_level = readl(base + EMIF_LPDDR2_MODE_REG_DATA);
temperature_level = (temperature_level & MR4_SDRAM_REF_RATE_MASK) >>
MR4_SDRAM_REF_RATE_SHIFT;
if (emif->plat_data->device_info->cs1_used) {
writel(DDR_MR4 | CS_MASK, base + EMIF_LPDDR2_MODE_REG_CONFIG);
temp = readl(base + EMIF_LPDDR2_MODE_REG_DATA);
temp = (temp & MR4_SDRAM_REF_RATE_MASK)
>> MR4_SDRAM_REF_RATE_SHIFT;
temperature_level = max(temp, temperature_level);
}
/* treat everything less than nominal(3) in MR4 as nominal */
if (unlikely(temperature_level < SDRAM_TEMP_NOMINAL))
temperature_level = SDRAM_TEMP_NOMINAL;
/* if we get reserved value in MR4 persist with the existing value */
if (likely(temperature_level != SDRAM_TEMP_RESERVED_4))
emif->temperature_level = temperature_level;
}
/*
* Program EMIF shadow registers that are not dependent on temperature
* or voltage
@ -627,6 +663,158 @@ out:
writel(ref_ctrl, base + EMIF_SDRAM_REFRESH_CTRL_SHDW);
}
static irqreturn_t handle_temp_alert(void __iomem *base, struct emif_data *emif)
{
u32 old_temp_level;
irqreturn_t ret = IRQ_HANDLED;
spin_lock_irqsave(&emif_lock, irq_state);
old_temp_level = emif->temperature_level;
get_temperature_level(emif);
if (unlikely(emif->temperature_level == old_temp_level)) {
goto out;
} else if (!emif->curr_regs) {
dev_err(emif->dev, "temperature alert before registers are calculated, not de-rating timings\n");
goto out;
}
if (emif->temperature_level < old_temp_level ||
emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) {
/*
* Temperature coming down - defer handling to thread OR
* Temperature far too high - do kernel_power_off() from
* thread context
*/
ret = IRQ_WAKE_THREAD;
} else {
/* Temperature is going up - handle immediately */
setup_temperature_sensitive_regs(emif, emif->curr_regs);
do_freq_update();
}
out:
spin_unlock_irqrestore(&emif_lock, irq_state);
return ret;
}
static irqreturn_t emif_interrupt_handler(int irq, void *dev_id)
{
u32 interrupts;
struct emif_data *emif = dev_id;
void __iomem *base = emif->base;
struct device *dev = emif->dev;
irqreturn_t ret = IRQ_HANDLED;
/* Save the status and clear it */
interrupts = readl(base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS);
writel(interrupts, base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS);
/*
* Handle temperature alert
* Temperature alert should be same for all ports
* So, it's enough to process it only for one of the ports
*/
if (interrupts & TA_SYS_MASK)
ret = handle_temp_alert(base, emif);
if (interrupts & ERR_SYS_MASK)
dev_err(dev, "Access error from SYS port - %x\n", interrupts);
if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) {
/* Save the status and clear it */
interrupts = readl(base + EMIF_LL_OCP_INTERRUPT_STATUS);
writel(interrupts, base + EMIF_LL_OCP_INTERRUPT_STATUS);
if (interrupts & ERR_LL_MASK)
dev_err(dev, "Access error from LL port - %x\n",
interrupts);
}
return ret;
}
static irqreturn_t emif_threaded_isr(int irq, void *dev_id)
{
struct emif_data *emif = dev_id;
if (emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) {
dev_emerg(emif->dev, "SDRAM temperature exceeds operating limit.. Needs shut down!!!\n");
kernel_power_off();
return IRQ_HANDLED;
}
spin_lock_irqsave(&emif_lock, irq_state);
if (emif->curr_regs) {
setup_temperature_sensitive_regs(emif, emif->curr_regs);
do_freq_update();
} else {
dev_err(emif->dev, "temperature alert before registers are calculated, not de-rating timings\n");
}
spin_unlock_irqrestore(&emif_lock, irq_state);
return IRQ_HANDLED;
}
static void clear_all_interrupts(struct emif_data *emif)
{
void __iomem *base = emif->base;
writel(readl(base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS),
base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS);
if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE)
writel(readl(base + EMIF_LL_OCP_INTERRUPT_STATUS),
base + EMIF_LL_OCP_INTERRUPT_STATUS);
}
static void disable_and_clear_all_interrupts(struct emif_data *emif)
{
void __iomem *base = emif->base;
/* Disable all interrupts */
writel(readl(base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET),
base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_CLEAR);
if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE)
writel(readl(base + EMIF_LL_OCP_INTERRUPT_ENABLE_SET),
base + EMIF_LL_OCP_INTERRUPT_ENABLE_CLEAR);
/* Clear all interrupts */
clear_all_interrupts(emif);
}
static int __init_or_module setup_interrupts(struct emif_data *emif, u32 irq)
{
u32 interrupts, type;
void __iomem *base = emif->base;
type = emif->plat_data->device_info->type;
clear_all_interrupts(emif);
/* Enable interrupts for SYS interface */
interrupts = EN_ERR_SYS_MASK;
if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4)
interrupts |= EN_TA_SYS_MASK;
writel(interrupts, base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET);
/* Enable interrupts for LL interface */
if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) {
/* TA need not be enabled for LL */
interrupts = EN_ERR_LL_MASK;
writel(interrupts, base + EMIF_LL_OCP_INTERRUPT_ENABLE_SET);
}
/* setup IRQ handlers */
return devm_request_threaded_irq(emif->dev, irq,
emif_interrupt_handler,
emif_threaded_isr,
0, dev_name(emif->dev),
emif);
}
static void get_default_timings(struct emif_data *emif)
{
struct emif_platform_data *pd = emif->plat_data;
@ -803,6 +991,7 @@ static int __init_or_module emif_probe(struct platform_device *pdev)
{
struct emif_data *emif;
struct resource *res;
int irq;
emif = get_device_details(pdev);
if (!emif) {
@ -831,6 +1020,16 @@ static int __init_or_module emif_probe(struct platform_device *pdev)
goto error;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(emif->dev, "%s: error getting IRQ resource - %d\n",
__func__, irq);
goto error;
}
disable_and_clear_all_interrupts(emif);
setup_interrupts(emif, irq);
/* One-time actions taken on probing the first device */
if (!emif1) {
emif1 = emif;
@ -843,14 +1042,21 @@ static int __init_or_module emif_probe(struct platform_device *pdev)
*/
}
dev_info(&pdev->dev, "%s: device configured with addr = %p\n",
__func__, emif->base);
dev_info(&pdev->dev, "%s: device configured with addr = %p and IRQ%d\n",
__func__, emif->base, irq);
return 0;
error:
return -ENODEV;
}
static void emif_shutdown(struct platform_device *pdev)
{
struct emif_data *emif = platform_get_drvdata(pdev);
disable_and_clear_all_interrupts(emif);
}
static int get_emif_reg_values(struct emif_data *emif, u32 freq,
struct emif_regs *regs)
{
@ -1154,6 +1360,7 @@ static void __attribute__((unused)) freq_post_notify_handling(void)
}
static struct platform_driver emif_driver = {
.shutdown = emif_shutdown,
.driver = {
.name = "emif",
},