[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