From: Jimmy Zhang <jimmzh...@nvidia.com>

Power supplies must be adjusted in line with clock frequency. This code
provides a simple routine to set the voltage to allow operation at maximum
frequency.

Signed-off-by: Simon Glass <s...@chromium.org>
---
 arch/arm/cpu/armv7/tegra2/Makefile     |    1 +
 arch/arm/cpu/armv7/tegra2/pmu.c        |  355 ++++++++++++++++++++++++++++++++
 arch/arm/include/asm/arch-tegra2/pmu.h |   63 ++++++
 3 files changed, 419 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/cpu/armv7/tegra2/pmu.c
 create mode 100644 arch/arm/include/asm/arch-tegra2/pmu.h

diff --git a/arch/arm/cpu/armv7/tegra2/Makefile 
b/arch/arm/cpu/armv7/tegra2/Makefile
index dcd6329..dba684d 100644
--- a/arch/arm/cpu/armv7/tegra2/Makefile
+++ b/arch/arm/cpu/armv7/tegra2/Makefile
@@ -35,6 +35,7 @@ LIB   =  $(obj)lib$(SOC).o
 SOBJS  := lowlevel_init.o
 COBJS-y        := ap20.o board.o clock.o funcmux.o pinmux.o sys_info.o timer.o
 COBJS-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o
+COBJS-$(CONFIG_TEGRA_PMU) += pmu.o
 COBJS-$(CONFIG_USB_EHCI_TEGRA) += usb.o
 
 COBJS  := $(COBJS-y)
diff --git a/arch/arm/cpu/armv7/tegra2/pmu.c b/arch/arm/cpu/armv7/tegra2/pmu.c
new file mode 100644
index 0000000..4bc87ae
--- /dev/null
+++ b/arch/arm/cpu/armv7/tegra2/pmu.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2011 The Chromium OS Authors.
+ * (C) Copyright 2010,2011 NVIDIA Corporation <www.nvidia.com>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <asm/arch/ap20.h>
+#include <asm/arch/pmu.h>
+#include <asm/arch/tegra2.h>
+#include <asm/arch/sys_proto.h>
+#include <i2c.h>
+
+/*
+ * abs() handles unsigned ints, shorts and chars and returns a signed long.
+ * TODO: Move this into common?
+ */
+#define abs(x) ({                                              \
+               long ret;                                       \
+               {                                               \
+                       typeof((x)) __x = (x);                  \
+                       ret = (__x < 0) ? -__x : __x;           \
+               }                                               \
+               ret;                                            \
+       })
+
+/* Amount to step voltage by (either positive or negative) */
+#define stp(x, y) ((x < y) ? VDD_TRANSITION_STEP : -VDD_TRANSITION_STEP)
+
+#define MAX_I2C_RETRY  3
+int pmu_read(int reg)
+{
+       int     i;
+       uchar   data;
+       int     retval = -1;
+       int     old_bus_num;
+
+       old_bus_num = i2c_get_bus_num();
+       i2c_set_bus_num(DVC_I2C_BUS_NUMBER);
+
+       for (i = 0; i < MAX_I2C_RETRY; ++i) {
+               if (!i2c_read(PMU_I2C_ADDRESS, reg, 1, &data, 1)) {
+                       retval = (int)data;
+                       goto exit;
+               }
+
+               /* i2c access failed, retry */
+               udelay(100);
+       }
+
+exit:
+       i2c_set_bus_num(old_bus_num);
+       return retval;
+}
+
+int pmu_write(int reg, uchar *data, uint len)
+{
+       int     i;
+       int     retval = -1;
+       int     old_bus_num;
+
+       old_bus_num = i2c_get_bus_num();
+       i2c_set_bus_num(DVC_I2C_BUS_NUMBER);
+
+       for (i = 0; i < MAX_I2C_RETRY; ++i) {
+               if (!i2c_write(PMU_I2C_ADDRESS, reg, 1, data, len)) {
+                       retval = 0;
+                       goto exit;
+               }
+
+               /* i2c access failed, retry */
+               udelay(100);
+       }
+
+exit:
+       i2c_set_bus_num(old_bus_num);
+       return retval;
+}
+
+#ifdef CONFIG_TEGRA_CLOCK_SCALING
+struct vdd_settings {
+       int     data;
+       int     nominal;
+};
+
+enum vdd_type {
+       core = 0,
+       cpu = 1,
+};
+
+static struct vdd_settings vdd_current[] = {
+       /* vdd core */
+       {.data          = 0,
+        .nominal       = 0,
+       },
+
+       /* vdd cpu */
+       {.data          = 0,
+        .nominal       = 0,
+       },
+};
+
+static int vdd_init_nominal_table(void)
+{
+       /* by default, the table has been filled with T25 settings */
+       switch (tegra_get_chip_type()) {
+       case TEGRA_SOC_T20:
+               vdd_current[core].nominal = VDD_CORE_NOMINAL_T20;
+               vdd_current[cpu].nominal = VDD_CPU_NOMINAL_T20;
+               break;
+       case TEGRA_SOC_T25:
+               vdd_current[core].nominal = VDD_CORE_NOMINAL_T25;
+               vdd_current[cpu].nominal = VDD_CPU_NOMINAL_T25;
+               break;
+       default:
+               /* unknown chip type */
+               return -1;
+       }
+       return 0;
+}
+
+/* get current vdd_core and vdd_cpu */
+static int tegra2_get_voltage(void)
+{
+       int     reg;
+       int     data1, data2;
+
+       /*
+        * Each vdd has two supply sources, ie, v1 and v2.
+        * The supply control reg1 and reg2 determine the current selection.
+        */
+       /* get supply control reg1 and reg2 */
+       data1 = pmu_read(PMU_SUPPLY_CONTROL_REG1);
+       if (data1 == -1)
+               return -1;
+
+       data2 = pmu_read(PMU_SUPPLY_CONTROL_REG2);
+       if (data2 == -1)
+               return -1;
+
+       reg = PMU_CORE_VOLTAGE_REG;     /* set default to v1 */
+       if ((data1 | data2) & VDD_CORE_SUPPLY2_SEL)
+               reg++;  /* v2 is selected*/
+
+       /* get vdd_core */
+       vdd_current[core].data = pmu_read(reg);
+       if (vdd_current[core].data == -1)
+               return -1;
+
+       reg = PMU_CPU_VOLTAGE_REG;      /* set default to v1 */
+       if ((data1 | data2) & VDD_CPU_SUPPLY2_SEL)
+               reg++;  /* v2 is selected*/
+
+       /* get vdd_cpu */
+       vdd_current[cpu].data = pmu_read(reg);
+       if (vdd_current[cpu].data == -1)
+               return -1;
+
+       return 0;
+}
+
+static int tegra2_set_voltage(int reg, int data, int control)
+{
+       uchar   data_buffer[3];
+       uchar   control_bit = (uchar)control;
+
+       /*
+        * only one supply is needed in u-boot. set both v1 and v2 to
+        * same value.
+        *
+        * when both v1 and v2 are set to same value, we just need to set
+        * control1 reg to trigger the supply selection.
+        */
+       data_buffer[0] = data_buffer[1] = (uchar)data;
+       data_buffer[2] = VDD_TRANSITION_RATE;
+
+       if (!pmu_write(reg, data_buffer, 3) &&          /* v1, v2 and rate */
+           !pmu_write(PMU_SUPPLY_CONTROL_REG1, &control_bit, 1)) /* trigger */
+               return 0;
+       return -1;
+}
+
+int pmu_is_voltage_nominal(void)
+{
+       if ((vdd_current[core].data == vdd_current[core].nominal) &&
+           (vdd_current[cpu].data == vdd_current[cpu].nominal))
+               return 1;
+       return 0;
+}
+
+static void calculate_next_voltage(int *data, int nominal, int step)
+{
+       if (abs(nominal - *data) > VDD_TRANSITION_STEP)
+               *data += step;
+       else
+               *data = nominal;
+}
+
+/*
+ * It is required by tegra2 soc that vdd_core must be higher than vdd_cpu
+ * with certain range at all time. If current settings doesn't meet this
+ * condition, pmu_adjust_voltage just simply returns without setting any
+ * voltage.
+ */
+static int pmu_adjust_voltage(void)
+{
+       int step_core, step_cpu;
+       int adjust_vdd_core_late;
+
+       /*
+        * if vdd_core < vdd_cpu + rel
+        *    skip
+        *
+        * This condition may happen when system reboots due to kernel crash.
+        */
+       if (vdd_current[core].data < (vdd_current[cpu].data + VDD_RELATION))
+               return -1;
+
+       /*
+        * Since vdd_core and vdd_cpu may both stand at either greater or less
+        * than their nominal voltage, the adjustment may go either directions.
+        *
+        * Make sure vdd_core is always higher than vdd_cpu with certain margin.
+        * So, find out which vdd to adjust first in each step.
+        *
+        * case 1: both vdd_core and vdd_cpu need to move up
+        *              adjust vdd_core before vdd_cpu
+        *
+        * case 2: both vdd_core and vdd_cpu need to move down
+        *              adjust vdd_cpu before vdd_core
+        *
+        * case 3: vdd_core moves down and vdd_cpu moves up
+        *              adjusting either one first is fine.
+        */
+       step_core = stp(vdd_current[core].data, vdd_current[core].nominal);
+       step_cpu = stp(vdd_current[cpu].data, vdd_current[cpu].nominal);
+
+       /*
+        * Adjust vdd_core and vdd_cpu one step at a time until they reach
+        * their nominal values.
+        */
+       while ((vdd_current[core].data != vdd_current[core].nominal) ||
+              (vdd_current[cpu].data != vdd_current[cpu].nominal)) {
+
+               adjust_vdd_core_late = 0;
+
+               /* if vdd_core hasn't reached its nominal value? */
+               if (vdd_current[core].data != vdd_current[core].nominal) {
+
+                       calculate_next_voltage(&vdd_current[core].data,
+                                              vdd_current[core].nominal,
+                                              step_core);
+
+                       /*
+                        * if case 1 and case 3, set new vdd_core first.
+                        * otherwise, hold down until new vdd_cpu is set.
+                        */
+                       if (step_cpu > 0) {
+                               /* adjust vdd_core first */
+                               if (tegra2_set_voltage(PMU_CORE_VOLTAGE_REG,
+                                               vdd_current[core].data,
+                                               VDD_CORE_SUPPLY_CONTROL))
+                                       return -1;
+                       } else
+                               /* set flag to adjust vdd_core later */
+                               adjust_vdd_core_late = 1;
+               }
+
+               /* if vdd_cpu hasn't reached its nominal value? */
+               if (vdd_current[cpu].data != vdd_current[cpu].nominal) {
+
+                       calculate_next_voltage(&vdd_current[cpu].data,
+                                              vdd_current[cpu].nominal,
+                                              step_cpu);
+
+                       /* adjust vdd_cpu */
+                       if (tegra2_set_voltage(PMU_CPU_VOLTAGE_REG,
+                                              vdd_current[cpu].data,
+                                              VDD_CPU_SUPPLY_CONTROL))
+                               return -1;
+               }
+
+               /*
+                * if vdd_core late flag is set
+                */
+               if (adjust_vdd_core_late) {
+                       /* adjust vdd_core */
+                       if (tegra2_set_voltage(PMU_CORE_VOLTAGE_REG,
+                                              vdd_current[core].data,
+                                              VDD_CORE_SUPPLY_CONTROL))
+                               return -1;
+               }
+       }
+       return 0;
+}
+
+static int pmu_set_pwm_mode(int smx)
+{
+       int ret = 0;
+       uchar val = 0;
+
+       ret = pmu_read(PMU_PWM_PFM_MODE_REG);
+       if (ret == -1)
+               return ret;
+
+       val = (uchar)ret;
+       val |= (1 << smx);
+
+       ret = pmu_write(PMU_PWM_PFM_MODE_REG, &val, 1);
+       if (ret == -1)
+               return ret;
+
+       return 0;
+}
+
+int pmu_set_nominal(void)
+{
+       /* fill in nominal values based on chip type */
+       if (vdd_init_nominal_table())
+               return -1;
+
+       /* Set SM1 in PWM-only mode */
+       if (pmu_set_pwm_mode(SM1_PWM_BIT))
+               return -1;
+
+       /* get current voltage settings */
+       if (tegra2_get_voltage())
+               return -1;
+
+       /* if current voltage is already set to nominal, skip */
+       if (pmu_is_voltage_nominal())
+               return 0;
+
+       /* adjust vdd_core and/or vdd_cpu */
+       return pmu_adjust_voltage();
+}
+#endif /* CONFIG_TEGRA_CLOCK_SCALING */
diff --git a/arch/arm/include/asm/arch-tegra2/pmu.h 
b/arch/arm/include/asm/arch-tegra2/pmu.h
new file mode 100644
index 0000000..303dda7
--- /dev/null
+++ b/arch/arm/include/asm/arch-tegra2/pmu.h
@@ -0,0 +1,63 @@
+/*
+ *  (C) Copyright 2010,2011
+ *  NVIDIA Corporation <www.nvidia.com>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#ifndef _ARCH_PMU_H_
+#define _ARCH_PMU_H_
+
+#define DVC_I2C_BUS_NUMBER     0
+#define PMU_I2C_ADDRESS                0x34
+
+#define PMU_CORE_VOLTAGE_REG   0x26
+#define PMU_CPU_VOLTAGE_REG    0x23
+#define PMU_SUPPLY_CONTROL_REG1        0x20
+#define PMU_SUPPLY_CONTROL_REG2        0x21
+#define VDD_CPU_SUPPLY_CONTROL 0x01
+#define VDD_CPU_SUPPLY2_SEL    0x02
+#define VDD_CORE_SUPPLY_CONTROL        0x04
+#define VDD_CORE_SUPPLY2_SEL   0x08
+
+#define VDD_CORE_NOMINAL_T25   0x17    /* 1.3v */
+#define VDD_CPU_NOMINAL_T25    0x10    /* 1.125v */
+
+#define VDD_CORE_NOMINAL_T20   0x16    /* 1.275v */
+#define VDD_CPU_NOMINAL_T20    0x0f    /* 1.1v */
+
+#define VDD_RELATION           0x02    /*  50mv */
+#define VDD_TRANSITION_STEP    0x06    /* 150mv */
+#define VDD_TRANSITION_RATE    0x06    /* 3.52mv/us */
+
+/*
+ * SMn PWM/PFM Mode Selection
+ */
+#define PMU_PWM_PFM_MODE_REG   0x47
+#define SM0_PWM_BIT            0
+#define SM1_PWM_BIT            1
+#define SM2_PWM_BIT            2
+
+int pmu_read(int reg);
+int pmu_write(int reg, uchar *data, uint len);
+
+int pmu_set_nominal(void);
+int pmu_is_voltage_nominal(void);
+
+#endif /* _ARCH_PMU_H_ */
-- 
1.7.3.1

_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
http://lists.denx.de/mailman/listinfo/u-boot

Reply via email to