--- /dev/null
+/*
+ * Freescale i.MX28 Boot PMIC init
+ *
+ * Copyright (C) 2012 Lothar Wassmann <LW@karo-electronics.de>
+ * based on: spl_power_init.c
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * 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
+ */
+/* #define DEBUG */
+
+#include <common.h>
+#include <config.h>
+#include <asm/io.h>
+#include <asm/arch/imx-regs.h>
+
+#include "mx28_init.h"
+
+static struct mx28_power_regs *power_regs = (void *)MXS_POWER_BASE;
+
+#include "debug.h"
+
+#ifdef DEBUG
+#undef __arch_getl
+#undef __arch_putl
+
+#define __arch_getl(a) arch_getl(a, __func__, __LINE__)
+static inline unsigned int arch_getl(volatile void *addr,
+ const char *fn, unsigned int ln)
+{
+ volatile unsigned int *a = addr;
+ unsigned int val = *a;
+ dbg(0, "%s@%d: Read %x from %p\n", fn, ln, val, addr);
+ return val;
+}
+
+#define __arch_putl(v, a) arch_putl(v, a, __func__, __LINE__)
+static inline void arch_putl(unsigned int val, volatile void *addr,
+ const char *fn, unsigned int ln)
+{
+ volatile unsigned int *a = addr;
+
+ dbg(0, "%s@%d: Writing %x to %p\n", fn, ln, val, addr);
+ *a = val;
+}
+#endif
+
+static void memdump(unsigned long addr, unsigned long len)
+{
+#ifdef DEBUG
+ int i;
+ uint32_t *ptr = (void *)addr;
+
+ for (i = 0; i < len; i++) {
+ if (i % 4 == 0)
+ dbg(0, "\n%x:", &ptr[i]);
+ dbg(0, " %x", ptr[i]);
+ }
+ dbg(0, "\n");
+#endif
+}
+
+static void mx28_power_clock2xtal(void)
+{
+ struct mx28_clkctrl_regs *clkctrl_regs =
+ (struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+ /* Set XTAL as CPU reference clock */
+ writel(CLKCTRL_CLKSEQ_BYPASS_CPU,
+ &clkctrl_regs->hw_clkctrl_clkseq_set);
+}
+
+static void mx28_power_clock2pll(void)
+{
+ struct mx28_clkctrl_regs *clkctrl_regs =
+ (struct mx28_clkctrl_regs *)MXS_CLKCTRL_BASE;
+
+ readl(&clkctrl_regs->hw_clkctrl_pll0ctrl0);
+ readl(&clkctrl_regs->hw_clkctrl_pll0ctrl1);
+ writel(CLKCTRL_PLL0CTRL0_POWER,
+ &clkctrl_regs->hw_clkctrl_pll0ctrl0_set);
+ readl(&clkctrl_regs->hw_clkctrl_pll0ctrl0);
+ readl(&clkctrl_regs->hw_clkctrl_frac0);
+ readl(&clkctrl_regs->hw_clkctrl_frac1);
+ readl(&clkctrl_regs->hw_clkctrl_clkseq);
+ early_delay(100);
+ writel(CLKCTRL_CLKSEQ_BYPASS_CPU,
+ &clkctrl_regs->hw_clkctrl_clkseq_clr);
+}
+
+static void mx28_power_clear_auto_restart(void)
+{
+ struct mx28_rtc_regs *rtc_regs =
+ (struct mx28_rtc_regs *)MXS_RTC_BASE;
+
+ writel(RTC_CTRL_SFTRST, &rtc_regs->hw_rtc_ctrl_clr);
+ while (readl(&rtc_regs->hw_rtc_ctrl) & RTC_CTRL_SFTRST)
+ ;
+
+ writel(RTC_CTRL_CLKGATE, &rtc_regs->hw_rtc_ctrl_clr);
+ while (readl(&rtc_regs->hw_rtc_ctrl) & RTC_CTRL_CLKGATE)
+ ;
+#ifdef CONFIG_MACH_MX28EVK
+ /*
+ * Due to the hardware design bug of mx28 EVK-A
+ * we need to set the AUTO_RESTART bit.
+ */
+ if (readl(&rtc_regs->hw_rtc_persistent0) & RTC_PERSISTENT0_AUTO_RESTART)
+ return;
+
+ while (readl(&rtc_regs->hw_rtc_stat) & RTC_STAT_NEW_REGS_MASK)
+ ;
+
+ setbits_le32(&rtc_regs->hw_rtc_persistent0,
+ RTC_PERSISTENT0_AUTO_RESTART);
+ writel(RTC_CTRL_FORCE_UPDATE, &rtc_regs->hw_rtc_ctrl_set);
+ writel(RTC_CTRL_FORCE_UPDATE, &rtc_regs->hw_rtc_ctrl_clr);
+ while (readl(&rtc_regs->hw_rtc_stat) & RTC_STAT_NEW_REGS_MASK)
+ ;
+ while (readl(&rtc_regs->hw_rtc_stat) & RTC_STAT_STALE_REGS_MASK)
+ ;
+#endif
+}
+
+static void mx28_power_set_linreg(void)
+{
+ /* Set linear regulator 25mV below switching converter */
+ clrsetbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_LINREG_OFFSET_MASK,
+ POWER_VDDDCTRL_LINREG_OFFSET_1STEPS_BELOW);
+
+ clrsetbits_le32(&power_regs->hw_power_vddactrl,
+ POWER_VDDACTRL_LINREG_OFFSET_MASK,
+ POWER_VDDACTRL_LINREG_OFFSET_1STEPS_BELOW);
+
+ clrsetbits_le32(&power_regs->hw_power_vddioctrl,
+ POWER_VDDIOCTRL_LINREG_OFFSET_MASK,
+ POWER_VDDIOCTRL_LINREG_OFFSET_1STEPS_BELOW);
+}
+
+static void mx28_src_power_init(void)
+{
+ /* Improve efficieny and reduce transient ripple */
+ writel(POWER_LOOPCTRL_TOGGLE_DIF | POWER_LOOPCTRL_EN_CM_HYST |
+ POWER_LOOPCTRL_EN_DF_HYST, &power_regs->hw_power_loopctrl_set);
+
+ clrsetbits_le32(&power_regs->hw_power_dclimits,
+ POWER_DCLIMITS_POSLIMIT_BUCK_MASK,
+ 0x30 << POWER_DCLIMITS_POSLIMIT_BUCK_OFFSET);
+
+ /* Increase the RCSCALE level for quick DCDC response to dynamic load */
+ clrsetbits_le32(&power_regs->hw_power_loopctrl,
+ POWER_LOOPCTRL_EN_RCSCALE_MASK,
+ POWER_LOOPCTRL_RCSCALE_THRESH |
+ POWER_LOOPCTRL_EN_RCSCALE_8X);
+
+ clrsetbits_le32(&power_regs->hw_power_minpwr,
+ POWER_MINPWR_HALFFETS, POWER_MINPWR_DOUBLE_FETS);
+#if 0
+ /* 5V to battery handoff ... FIXME */
+ setbits_le32(&power_regs->hw_power_5vctrl, POWER_5VCTRL_DCDC_XFER);
+ early_delay(30);
+ clrbits_le32(&power_regs->hw_power_5vctrl, POWER_5VCTRL_DCDC_XFER);
+#endif
+}
+
+void mx28_powerdown(void)
+{
+ writel(POWER_RESET_UNLOCK_KEY, &power_regs->hw_power_reset);
+ writel(POWER_RESET_UNLOCK_KEY | POWER_RESET_PWD_OFF,
+ &power_regs->hw_power_reset);
+}
+
+static void mx28_fixed_batt_boot(void)
+{
+ clrsetbits_le32(&power_regs->hw_power_battmonitor,
+ POWER_BATTMONITOR_EN_BATADJ |
+ POWER_BATTMONITOR_BATT_VAL_MASK,
+ POWER_BATTMONITOR_PWDN_BATTBRNOUT |
+ ((5000 / 8) << POWER_BATTMONITOR_BATT_VAL_OFFSET));
+ writel(POWER_CTRL_BATT_BO_IRQ, &power_regs->hw_power_ctrl_clr);
+
+ setbits_le32(&power_regs->hw_power_5vctrl,
+ POWER_5VCTRL_PWDN_5VBRNOUT |
+ POWER_5VCTRL_ENABLE_DCDC |
+ POWER_5VCTRL_ILIMIT_EQ_ZERO |
+ POWER_5VCTRL_PWDN_5VBRNOUT |
+ POWER_5VCTRL_PWD_CHARGE_4P2_MASK);
+
+ writel(POWER_CHARGE_PWD_BATTCHRG, &power_regs->hw_power_charge_set);
+
+ clrbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_DISABLE_FET |
+ POWER_VDDDCTRL_ENABLE_LINREG |
+ POWER_VDDDCTRL_DISABLE_STEPPING);
+
+ /* Stop 5V detection */
+ writel(POWER_5VCTRL_PWRUP_VBUS_CMPS,
+ &power_regs->hw_power_5vctrl_clr);
+}
+
+static void mx28_switch_vdds_to_dcdc_source(void)
+{
+ clrsetbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_DISABLE_FET |
+ POWER_VDDDCTRL_DISABLE_STEPPING,
+ POWER_VDDDCTRL_ENABLE_LINREG);
+
+ clrsetbits_le32(&power_regs->hw_power_vddactrl,
+ POWER_VDDACTRL_DISABLE_FET |
+ POWER_VDDACTRL_DISABLE_STEPPING,
+ POWER_VDDACTRL_ENABLE_LINREG);
+
+ clrbits_le32(&power_regs->hw_power_vddioctrl,
+ POWER_VDDIOCTRL_DISABLE_FET |
+ POWER_VDDIOCTRL_DISABLE_STEPPING);
+
+ clrbits_le32(&power_regs->hw_power_vddmemctrl,
+ POWER_VDDMEMCTRL_ENABLE_ILIMIT |
+ POWER_VDDMEMCTRL_ENABLE_LINREG);
+}
+
+static void mx28_enable_output_rail_protection(void)
+{
+ writel(POWER_CTRL_VDDD_BO_IRQ | POWER_CTRL_VDDA_BO_IRQ |
+ POWER_CTRL_VDDIO_BO_IRQ, &power_regs->hw_power_ctrl_clr);
+
+ setbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_PWDN_BRNOUT);
+
+ setbits_le32(&power_regs->hw_power_vddactrl,
+ POWER_VDDACTRL_PWDN_BRNOUT);
+
+ setbits_le32(&power_regs->hw_power_vddioctrl,
+ POWER_VDDIOCTRL_PWDN_BRNOUT);
+}
+
+static void mx28_power_set_vddio(uint32_t new_target, uint32_t new_brownout)
+{
+ uint32_t cur_target;
+
+ dbg(3, "%s@%d: \n", __func__, __LINE__);
+ new_brownout = (new_target - new_brownout) / 25;
+ if (new_brownout > 7) {
+ dbg(3, "Bad VDDD brownout offset\n");
+ new_brownout = 7;
+ }
+
+ cur_target = readl(&power_regs->hw_power_vddioctrl);
+ cur_target &= POWER_VDDIOCTRL_TRG_MASK;
+ cur_target *= 50; /* 50 mV step*/
+ cur_target += 2800; /* 2800 mV lowest */
+
+ if (new_target == cur_target) {
+ dbg(3, "%s@%d: VDDIO is at %u\n", __func__, __LINE__,
+ cur_target, new_target);
+ return;
+ }
+
+ dbg(3, "%s@%d: stepping VDDIO from %u to %u\n", __func__, __LINE__,
+ cur_target, new_target);
+
+ setbits_le32(&power_regs->hw_power_vddioctrl,
+ POWER_VDDIOCTRL_BO_OFFSET_MASK);
+ clrsetbits_le32(&power_regs->hw_power_vddioctrl,
+ POWER_VDDIOCTRL_TRG_MASK, new_target);
+ while (!(readl(&power_regs->hw_power_sts) &
+ POWER_STS_DC_OK)) {
+ static int loops = 100;
+ early_delay(100);
+ if (--loops < 0) {
+ dprintf("Wait for VDDIO_OK timed out\n");
+ break;
+ }
+ }
+ dbg(3, "%s@%d: Done\n", __func__, __LINE__);
+
+ clrsetbits_le32(&power_regs->hw_power_vddioctrl,
+ POWER_VDDDCTRL_BO_OFFSET_MASK,
+ new_brownout << POWER_VDDDCTRL_BO_OFFSET_OFFSET);
+ dbg(3, "%s@%d: vddioctrl=%x\n", __func__, __LINE__,
+ readl(&power_regs->hw_power_vddioctrl));
+}
+
+static void mx28_power_set_vddd(uint32_t new_target, uint32_t new_brownout)
+{
+ uint32_t cur_target;
+
+ dbg(3, "%s@%d: \n", __func__, __LINE__);
+ new_brownout = (new_target - new_brownout) / 25;
+ if (new_brownout > 7) {
+ dbg(3, "Bad VDDD brownout offset\n");
+ new_brownout = 7;
+ }
+ cur_target = readl(&power_regs->hw_power_vdddctrl);
+ cur_target &= POWER_VDDDCTRL_TRG_MASK;
+ cur_target *= 25; /* 25 mV step*/
+ cur_target += 800; /* 800 mV lowest */
+
+ if (new_target == cur_target) {
+ dbg(3, "%s@%d: VDDD is at %u\n", __func__, __LINE__,
+ cur_target, new_target);
+ return;
+ }
+
+ dbg(3, "%s@%d: stepping VDDD from %u to %u\n", __func__, __LINE__,
+ cur_target, new_target);
+
+ setbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_BO_OFFSET_MASK);
+ clrsetbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_TRG_MASK, new_target);
+ while (!(readl(&power_regs->hw_power_sts) &
+ POWER_STS_DC_OK)) {
+ static int loops = 100;
+ early_delay(100);
+ if (--loops < 0) {
+ dprintf("Wait for VDDD_OK timed out\n");
+ break;
+ }
+ }
+ dbg(3, "%s@%d: Done\n", __func__, __LINE__);
+
+ clrsetbits_le32(&power_regs->hw_power_vdddctrl,
+ POWER_VDDDCTRL_BO_OFFSET_MASK,
+ new_brownout << POWER_VDDDCTRL_BO_OFFSET_OFFSET);
+}
+
+void mx28_power_init(void)
+{
+ dprintf("%s: %s %s\n", __func__, __DATE__, __TIME__);
+
+ mx28_power_clock2xtal();
+ mx28_power_clear_auto_restart();
+ mx28_power_set_linreg();
+
+ mx28_src_power_init();
+ early_delay(10000);
+ mx28_fixed_batt_boot();
+ early_delay(10000);
+ mx28_power_clock2pll();
+ early_delay(10000);
+ early_delay(10000);
+ mx28_switch_vdds_to_dcdc_source();
+ early_delay(10000);
+
+ mx28_enable_output_rail_protection();
+
+ mx28_power_set_vddio(3300, 3150);
+
+ mx28_power_set_vddd(1500, 1325);
+
+ writel(POWER_CTRL_VDDD_BO_IRQ | POWER_CTRL_VDDA_BO_IRQ |
+ POWER_CTRL_VDDIO_BO_IRQ | POWER_CTRL_VDD5V_DROOP_IRQ |
+ POWER_CTRL_VBUS_VALID_IRQ | POWER_CTRL_BATT_BO_IRQ |
+ POWER_CTRL_DCDC4P2_BO_IRQ, &power_regs->hw_power_ctrl_clr);
+
+ dbg(0, "sts=%x\n", readl(&power_regs->hw_power_sts));
+ dbg(0, "vddioctrl=%x\n", readl(&power_regs->hw_power_vddioctrl));
+ dbg(0, "vdddctrl=%x\n", readl(&power_regs->hw_power_vdddctrl));
+ dbg(0, "5vctrl=%x\n", readl(&power_regs->hw_power_5vctrl));
+ dbg(0, "dcdc4p2=%x\n", readl(&power_regs->hw_power_dcdc4p2));
+ dbg(0, "%s@%d: Finished\n", __func__, __LINE__);
+ memdump(0x80044000, 0x60);
+}
+
+#ifdef CONFIG_SPL_MX28_PSWITCH_WAIT
+void mx28_power_wait_pswitch(void)
+{
+ dbg(3, "%s@%d: \n", __func__, __LINE__);
+ while (!(readl(&power_regs->hw_power_sts) & POWER_STS_PSWITCH_MASK))
+ ;
+}
+#endif