[U-Boot] [PATCH 07/14] tegra: Add PMU to manage power supplies
Simon Glass
sjg at chromium.org
Mon Dec 26 20:33:00 CET 2011
From: Jimmy Zhang <jimmzhang at 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 <sjg at 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
More information about the U-Boot
mailing list