ARM: tegra: clock: Add LPDDR2 temperature controls
Alex Frid [Wed, 15 Feb 2012 07:00:41 +0000 (23:00 -0800)]
Added interfaces for
- reading scaled  LPDDR2 temperature from MR4 register
- controlling refresh rate according LPDDR2 specification

For now, these interfaces are only used by debufs nodes:

/sys/kernel/debug/tegra_emc/dram_temperature (read only)

/sys/kernel/debug/tegra_emc/over_temp_state (read/write,
0 - set regular low temperature refresh rate,
1 - speed up refresh for high temperature)

Bug 939626

Signed-off-by: Alex Frid <afrid@nvidia.com>
(cherry picked from commit 373ff7e49235f6e222b42e324b6a2dc9eac633e6)

Change-Id: I9cfaaeeab16d5b49acb91824fecc6b0ee8f3cdbb
Reviewed-on: http://git-master/r/89349
Tested-by: Aleksandr Frid <afrid@nvidia.com>
Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>

arch/arm/mach-tegra/tegra3_emc.c
arch/arm/mach-tegra/tegra3_emc.h

index 23d2d7b..746caea 100644 (file)
@@ -190,6 +190,7 @@ static int emc_num_burst_regs;
 static struct clk_mux_sel tegra_emc_clk_sel[TEGRA_EMC_TABLE_MAX_SIZE];
 static struct tegra_emc_table start_timing;
 static const struct tegra_emc_table *emc_timing;
+static unsigned long dram_over_temp_state = DRAM_OVER_TEMP_NONE;
 
 static const struct tegra_emc_table *tegra_emc_table;
 static int tegra_emc_table_size;
@@ -209,6 +210,8 @@ static struct {
        spinlock_t spinlock;
 } emc_stats;
 
+static DEFINE_SPINLOCK(emc_access_lock);
+
 static void __iomem *emc_base = IO_ADDRESS(TEGRA_EMC_BASE);
 static void __iomem *mc_base = IO_ADDRESS(TEGRA_MC_BASE);
 static void __iomem *clk_base = IO_ADDRESS(TEGRA_CLK_RESET_BASE);
@@ -290,6 +293,37 @@ static inline void auto_cal_disable(void)
        }
 }
 
+static inline void set_over_temp_timing(
+       const struct tegra_emc_table *next_timing, unsigned long state)
+{
+#define REFRESH_SPEEDUP(val)                                                 \
+       do {                                                                  \
+               val = ((val) & 0xFFFF0000) | (((val) & 0xFFFF) >> 2);         \
+       } while (0)
+
+       u32 ref = next_timing->burst_regs[EMC_REFRESH_INDEX];
+       u32 pre_ref = next_timing->burst_regs[EMC_PRE_REFRESH_REQ_CNT_INDEX];
+       u32 dsr_cntrl = next_timing->burst_regs[EMC_DYN_SELF_REF_CONTROL_INDEX];
+
+       switch (state) {
+       case DRAM_OVER_TEMP_NONE:
+               break;
+       case DRAM_OVER_TEMP_REFRESH:
+               REFRESH_SPEEDUP(ref);
+               REFRESH_SPEEDUP(pre_ref);
+               REFRESH_SPEEDUP(dsr_cntrl);
+               break;
+       default:
+               pr_err("%s: Failed to set dram over temp state %lu\n",
+                      __func__, state);
+               BUG();
+       }
+
+       __raw_writel(ref, burst_reg_addr[EMC_REFRESH_INDEX]);
+       __raw_writel(pre_ref, burst_reg_addr[EMC_PRE_REFRESH_REQ_CNT_INDEX]);
+       __raw_writel(dsr_cntrl, burst_reg_addr[EMC_DYN_SELF_REF_CONTROL_INDEX]);
+}
+
 static inline void set_mc_arbiter_limits(void)
 {
        u32 reg = mc_readl(MC_EMEM_ARB_OUTSTANDING_REQ);
@@ -523,6 +557,9 @@ static noinline void emc_set_clock(const struct tegra_emc_table *next_timing,
                        continue;
                __raw_writel(next_timing->burst_regs[i], burst_reg_addr[i]);
        }
+       if ((dram_type == DRAM_TYPE_LPDDR2) &&
+           (dram_over_temp_state != DRAM_OVER_TEMP_NONE))
+               set_over_temp_timing(next_timing, dram_over_temp_state);
        wmb();
        barrier();
 
@@ -669,6 +706,7 @@ int tegra_emc_set_rate(unsigned long rate)
        int i;
        u32 clk_setting;
        const struct tegra_emc_table *last_timing;
+       unsigned long flags;
 
        if (!tegra_emc_table)
                return -EINVAL;
@@ -697,10 +735,14 @@ int tegra_emc_set_rate(unsigned long rate)
                last_timing = emc_timing;
 
        clk_setting = tegra_emc_clk_sel[i].value;
+
+       spin_lock_irqsave(&emc_access_lock, flags);
        emc_set_clock(&tegra_emc_table[i], last_timing, clk_setting);
        if (!emc_timing)
                emc_cfg_power_restore();
        emc_timing = &tegra_emc_table[i];
+       spin_unlock_irqrestore(&emc_access_lock, flags);
+
        emc_last_stats_update(i);
 
        pr_debug("%s: rate %lu setting 0x%x\n", __func__, rate, clk_setting);
@@ -1027,6 +1069,70 @@ int tegra_emc_get_dram_type(void)
        return dram_type;
 }
 
+static int emc_read_mrr(int dev, int addr)
+{
+       int ret;
+       u32 val;
+
+       if (dram_type != DRAM_TYPE_LPDDR2)
+               return -ENODEV;
+
+       ret = wait_for_update(EMC_STATUS, EMC_STATUS_MRR_DIVLD, false);
+       if (ret)
+               return ret;
+
+       val = dev ? DRAM_DEV_SEL_1 : DRAM_DEV_SEL_0;
+       val |= (addr << EMC_MRR_MA_SHIFT) & EMC_MRR_MA_MASK;
+       emc_writel(val, EMC_MRR);
+
+       ret = wait_for_update(EMC_STATUS, EMC_STATUS_MRR_DIVLD, true);
+       if (ret)
+               return ret;
+
+       /* FIXME: bit swap decoding */
+       val = emc_readl(EMC_MRR) & EMC_MRR_DATA_MASK;
+       return val;
+}
+
+int tegra_emc_get_dram_temperature(void)
+{
+       int mr4;
+       unsigned long flags;
+
+       spin_lock_irqsave(&emc_access_lock, flags);
+
+       mr4 = emc_read_mrr(0, 4);
+       if (IS_ERR_VALUE(mr4)) {
+               spin_unlock_irqrestore(&emc_access_lock, flags);
+               return mr4;
+       }
+
+       mr4 &= LPDDR2_MR4_TEMP_MASK;
+       spin_unlock_irqrestore(&emc_access_lock, flags);
+       return mr4;
+}
+
+int tegra_emc_set_over_temp_state(unsigned long state)
+{
+       unsigned long flags;
+
+       if (dram_type != DRAM_TYPE_LPDDR2)
+               return -ENODEV;
+
+       spin_lock_irqsave(&emc_access_lock, flags);
+
+       /* Update refresh timing if state changed */
+       if (emc_timing && (dram_over_temp_state != state)) {
+               set_over_temp_timing(emc_timing, state);
+               emc_timing_update();
+               if (state != DRAM_OVER_TEMP_NONE)
+                       emc_writel(EMC_REF_FORCE_CMD, EMC_REF);
+               dram_over_temp_state = state;
+       }
+       spin_unlock_irqrestore(&emc_access_lock, flags);
+       return 0;
+}
+
 #ifdef CONFIG_DEBUG_FS
 
 static struct dentry *emc_debugfs_root;
@@ -1065,6 +1171,27 @@ static const struct file_operations emc_stats_fops = {
        .release        = single_release,
 };
 
+static int dram_temperature_get(void *data, u64 *val)
+{
+       *val = tegra_emc_get_dram_temperature();
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(dram_temperature_fops, dram_temperature_get,
+                       NULL, "%lld\n");
+
+static int over_temp_state_get(void *data, u64 *val)
+{
+       *val = dram_over_temp_state;
+       return 0;
+}
+static int over_temp_state_set(void *data, u64 val)
+{
+       tegra_emc_set_over_temp_state(val);
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(over_temp_state_fops, over_temp_state_get,
+                       over_temp_state_set, "%llu\n");
+
 static int __init tegra_emc_debug_init(void)
 {
        if (!tegra_emc_table)
@@ -1078,6 +1205,14 @@ static int __init tegra_emc_debug_init(void)
                "stats", S_IRUGO, emc_debugfs_root, NULL, &emc_stats_fops))
                goto err_out;
 
+       if (!debugfs_create_file("dram_temperature", S_IRUGO, emc_debugfs_root,
+                                NULL, &dram_temperature_fops))
+               goto err_out;
+
+       if (!debugfs_create_file("over_temp_state", S_IRUGO | S_IWUSR,
+                                emc_debugfs_root, NULL, &over_temp_state_fops))
+               goto err_out;
+
        return 0;
 
 err_out:
index f076dcd..48b2d9c 100644 (file)
@@ -45,12 +45,19 @@ struct tegra_emc_table {
        int emc_min_mv;
 };
 
+enum {
+       DRAM_OVER_TEMP_NONE = 0,
+       DRAM_OVER_TEMP_REFRESH,
+};
+
 struct clk;
 
 void tegra_init_emc(const struct tegra_emc_table *table, int table_size);
 
 void tegra_emc_dram_type_init(struct clk *c);
 int tegra_emc_get_dram_type(void);
+int tegra_emc_get_dram_temperature(void);
+int tegra_emc_set_over_temp_state(unsigned long state);
 
 #ifdef CONFIG_PM_SLEEP
 void tegra_mc_timing_restore(void);
@@ -131,6 +138,8 @@ static inline void tegra_mc_timing_restore(void)
 #define EMC_MODE_SET_DLL_RESET                 (0x1 << 8)
 #define EMC_MODE_SET_LONG_CNT                  (0x1 << 26)
 #define EMC_EMRS                               0xd0
+#define EMC_REF                                        0xd4
+#define EMC_REF_FORCE_CMD                      1
 
 #define EMC_SELF_REF                           0xe0
 #define EMC_SELF_REF_CMD_ENABLED               (0x1 << 0)
@@ -146,6 +155,11 @@ enum {
 
 #define EMC_MRW                                        0xe8
 #define EMC_MRR                                        0xec
+#define EMC_MRR_MA_SHIFT                       16
+#define EMC_MRR_MA_MASK                                (0xFF << EMC_MRR_MA_SHIFT)
+#define EMC_MRR_DATA_MASK                      ((0x1 << EMC_MRR_MA_SHIFT) - 1)
+#define LPDDR2_MR4_TEMP_MASK                   0x7
+
 #define EMC_XM2DQSPADCTRL3                     0xf8
 #define EMC_XM2DQSPADCTRL3_VREF_ENABLE         (0x1 << 5)
 #define EMC_FBIO_SPARE                         0x100
@@ -175,6 +189,7 @@ enum {
 #define EMC_AUTO_CAL_STATUS_ACTIVE             (0x1 << 31)
 #define EMC_STATUS                             0x2b4
 #define EMC_STATUS_TIMING_UPDATE_STALLED       (0x1 << 23)
+#define EMC_STATUS_MRR_DIVLD                   (0x1 << 20)
 
 #define EMC_CFG_2                              0x2b8
 #define EMC_CFG_2_MODE_SHIFT                   0