mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-14 21:01:29 +00:00
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:
parent
a93de288aa
commit
68b4aee35d
@ -544,6 +544,42 @@ static u32 get_pwr_mgmt_ctrl(u32 freq, struct emif_data *emif, u32 ip_rev)
|
|||||||
return pwr_mgmt_ctrl;
|
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
|
* Program EMIF shadow registers that are not dependent on temperature
|
||||||
* or voltage
|
* or voltage
|
||||||
@ -627,6 +663,158 @@ out:
|
|||||||
writel(ref_ctrl, base + EMIF_SDRAM_REFRESH_CTRL_SHDW);
|
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)
|
static void get_default_timings(struct emif_data *emif)
|
||||||
{
|
{
|
||||||
struct emif_platform_data *pd = emif->plat_data;
|
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 emif_data *emif;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
|
int irq;
|
||||||
|
|
||||||
emif = get_device_details(pdev);
|
emif = get_device_details(pdev);
|
||||||
if (!emif) {
|
if (!emif) {
|
||||||
@ -831,6 +1020,16 @@ static int __init_or_module emif_probe(struct platform_device *pdev)
|
|||||||
goto error;
|
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 */
|
/* One-time actions taken on probing the first device */
|
||||||
if (!emif1) {
|
if (!emif1) {
|
||||||
emif1 = emif;
|
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",
|
dev_info(&pdev->dev, "%s: device configured with addr = %p and IRQ%d\n",
|
||||||
__func__, emif->base);
|
__func__, emif->base, irq);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
error:
|
error:
|
||||||
return -ENODEV;
|
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,
|
static int get_emif_reg_values(struct emif_data *emif, u32 freq,
|
||||||
struct emif_regs *regs)
|
struct emif_regs *regs)
|
||||||
{
|
{
|
||||||
@ -1154,6 +1360,7 @@ static void __attribute__((unused)) freq_post_notify_handling(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver emif_driver = {
|
static struct platform_driver emif_driver = {
|
||||||
|
.shutdown = emif_shutdown,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "emif",
|
.name = "emif",
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user