rockbox/rbutil/mkimxboot/dualboot/dualboot.c
Amaury Pouly a983859291 imx233: add capability to boot OF or updater instead of Rockbox
This commit adds the necessary code in the dualboot stub (bootloader) to
let rockbox control the boot process. In particular, rockbox can now choose
if the next boot will be normal (boot rockbox or OF on magic key), to OF
or to updater.

The intents (to be added in follow-up commits) are:
1) Let the user more easily reboot to the OF. On some targets it is not trivial,
especially in USB mode.
2) Automatically reboot to updater when the user drop firmware.sb at the root
of the drive (currently, the user needs to do that in OF USB mode)
3) Document this OF magic

Change-Id: I86df651dec048c318c6a22de74abb8c6b41aa9ad
2016-12-12 12:03:08 +01:00

323 lines
9.4 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2013 by Amaury Pouly
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "regs/pinctrl.h"
#include "regs/power.h"
#include "regs/lradc.h"
#include "regs/digctl.h"
#include "regs/clkctrl.h"
#define BOOT_ROM_CONTINUE 0 /* continue boot */
#define BOOT_ROM_SECTION 1 /* switch to new section *result_id */
#define BOOT_ARG_CHARGE ('c' | 'h' << 8 | 'r' << 16 | 'g' << 24)
/** additional defines */
#define BP_LRADC_CTRL4_LRADCxSELECT(x) (4 * (x))
#define BM_LRADC_CTRL4_LRADCxSELECT(x) (0xf << (4 * (x)))
typedef unsigned long uint32_t;
/* we include the dualboot rtc code directly */
#include "dualboot-imx233.h"
#include "dualboot-imx233.c"
// target specific boot context
enum context_t
{
CONTEXT_NORMAL, /* normal boot */
CONTEXT_USB, /* USB plugged boot */
CONTEXT_RTC, /* RTC wake up boot */
};
// target specific boot decision
enum boot_t
{
BOOT_STOP, /* power down */
BOOT_ROCK, /* boot to Rockbox */
BOOT_OF, /* boot to OF */
};
/**
* Helper functions
*/
static inline int __attribute__((always_inline)) read_gpio(int bank, int pin)
{
return (HW_PINCTRL_DINn(bank) >> pin) & 1;
}
static inline int __attribute__((always_inline)) read_pswitch(void)
{
#if IMX233_SUBTARGET >= 3700
return BF_RD(POWER_STS, PSWITCH);
#else
return BF_RD(DIGCTL_STATUS, PSWITCH);
#endif
}
/* only works for channels <=7, always divide by 2, never accumulates */
static inline void __attribute__((always_inline)) setup_lradc(int src)
{
BF_CLR(LRADC_CTRL0, SFTRST);
BF_CLR(LRADC_CTRL0, CLKGATE);
#if IMX233_SUBTARGET >= 3700
HW_LRADC_CTRL4_CLR = BM_LRADC_CTRL4_LRADCxSELECT(src);
HW_LRADC_CTRL4_SET = src << BP_LRADC_CTRL4_LRADCxSELECT(src);
#endif
HW_LRADC_CHn_CLR(src) = BM_OR(LRADC_CHn, NUM_SAMPLES, ACCUMULATE);
BF_WR(LRADC_CTRL2_SET, DIVIDE_BY_TWO(1 << src));
}
#define BP_LRADC_CTRL1_LRADCx_IRQ(x) (x)
#define BM_LRADC_CTRL1_LRADCx_IRQ(x) (1 << (x))
static inline int __attribute__((always_inline)) read_lradc(int src)
{
BF_CLR(LRADC_CTRL1, LRADCx_IRQ(src));
BF_WR(LRADC_CTRL0_SET, SCHEDULE(1 << src));
while(!BF_RD(LRADC_CTRL1, LRADCx_IRQ(src)));
return BF_RD(LRADC_CHn(src), VALUE);
}
static inline void __attribute__((noreturn)) power_down()
{
#ifdef SANSA_FUZEPLUS
/* B0P09: this pin seems to be important to shutdown the hardware properly */
HW_PINCTRL_MUXSELn_SET(0) = 3 << 18;
HW_PINCTRL_DOEn(0) = 1 << 9;
HW_PINCTRL_DOUTn(0) = 1 << 9;
#endif
/* power down */
HW_POWER_RESET = BM_OR(POWER_RESET, UNLOCK, PWD);
while(1);
}
/**
* Boot decision functions
*/
#if defined(SANSA_FUZEPLUS)
static enum boot_t boot_decision(enum context_t context)
{
/* if volume down is hold, boot to OF */
if(!read_gpio(1, 30))
return BOOT_OF;
/* on normal boot, make sure power button is hold long enough */
if(context == CONTEXT_NORMAL)
{
// monitor PSWITCH
int count = 0;
for(int i = 0; i < 550000; i++)
if(read_pswitch() == 1)
count++;
if(count < 400000)
return BOOT_STOP;
}
return BOOT_ROCK;
}
#elif defined(CREATIVE_ZENXFI2)
static int boot_decision(int context)
{
/* We are lacking buttons on the Zen X-Fi2 because on USB, the select button
* enters recovery mode ! So we can only use power but power is used to power up
* on normal boots and then select is free ! Thus use a non-uniform scheme:
* - normal boot/RTC:
* - no key: Rockbox
* - select: OF
* - USB boot:
* - no key: Rockbox
* - power: OF
*/
if(context == CONTEXT_USB)
return read_pswitch() == 1 ? BOOT_OF : BOOT_ROCK;
else
return !read_gpio(0, 14) ? BOOT_OF : BOOT_ROCK;
}
#elif defined(CREATIVE_ZENXFI3)
static int boot_decision(int context)
{
/* if volume down is hold, boot to OF */
return !read_gpio(2, 7) ? BOOT_OF : BOOT_ROCK;
}
#elif defined(SONY_NWZE360) || defined(SONY_NWZE370)
static int local_decision(void)
{
/* read keys and pswitch */
int val = read_lradc(0);
/* if hold is on, power off
* if back is pressed, boot to OF
* if play is pressed, boot RB
* otherwise power off */
#ifdef SONY_NWZE360
if(read_gpio(0, 9) == 0)
return BOOT_STOP;
#endif
if(val >= 1050 && val < 1150)
return BOOT_OF;
if(val >= 1420 && val < 1520)
return BOOT_ROCK;
return BOOT_STOP;
}
static int boot_decision(int context)
{
setup_lradc(0); // setup LRADC channel 0 to read keys
#ifdef SONY_NWZE360
HW_PINCTRL_PULLn_SET(0) = 1 << 9; // enable pullup on hold key (B0P09)
#endif
/* make a decision */
int decision = local_decision();
/* in USB or alarm context, stick to it */
if(context == CONTEXT_USB || context == CONTEXT_RTC)
{
/* never power down so replace power off decision by rockbox */
return decision == BOOT_STOP ? BOOT_ROCK : decision;
}
/* otherwise start a 1 second timeout. Any decision change
* will result in power down */
uint32_t tmo = HW_DIGCTL_MICROSECONDS + 1000000;
while(HW_DIGCTL_MICROSECONDS < tmo)
{
int new_dec = local_decision();
if(new_dec != decision)
return BOOT_STOP;
}
return decision;
}
#elif defined(CREATIVE_ZENXFISTYLE)
static int boot_decision(int context)
{
setup_lradc(2); // setup LRADC channel 2 to read keys
/* make a decision */
int val = read_lradc(2);
/* boot to OF if left is hold
* NOTE: VDDIO is set to 3.1V initially and the resistor ladder is wired to
* VDDIO so these values are not the same as in the main binary which is
* calibrated for VDDIO=3.3V */
if(val >= 815 && val < 915)
return BOOT_OF;
return BOOT_ROCK;
}
#else
#warning You should define a target specific boot decision function
static int boot_decision(int context)
{
return BOOT_ROCK;
}
#endif
/**
* Context functions
*/
static inline enum context_t get_context(void)
{
#if IMX233_SUBTARGET >= 3780
/* On the imx233 it's easy because we know the power up source */
unsigned pwrup_src = BF_RD(POWER_STS, PWRUP_SOURCE);
if(pwrup_src & (1 << 5))
return CONTEXT_USB;
else if(pwrup_src & (1 << 4))
return CONTEXT_RTC;
else
return CONTEXT_NORMAL;
#else
/* On the other targets, we need to poke a few more registers */
#endif
}
/**
* Charging function
*/
static inline void do_charge(void)
{
BF_CLR(LRADC_CTRL0, SFTRST);
BF_CLR(LRADC_CTRL0, CLKGATE);
BF_WR(LRADC_DELAYn(0), TRIGGER_LRADCS(0x80));
BF_WR(LRADC_DELAYn(0), TRIGGER_DELAYS(0x1));
BF_WR(LRADC_DELAYn(0), DELAY(200));
BF_SET(LRADC_DELAYn(0), KICK);
BF_SET(LRADC_CONVERSION, AUTOMATIC);
BF_WR(LRADC_CONVERSION, SCALE_FACTOR_V(LI_ION));
BF_WR(POWER_CHARGE, STOP_ILIMIT(1));
BF_WR(POWER_CHARGE, BATTCHRG_I(0x10));
BF_CLR(POWER_CHARGE, PWD_BATTCHRG);
#if IMX233_SUBTARGET >= 3780
BF_WR(POWER_DCDC4P2, ENABLE_4P2(1));
BF_CLR(POWER_5VCTRL, PWD_CHARGE_4P2);
BF_WR(POWER_5VCTRL, CHARGE_4P2_ILIMIT(0x10));
#endif
while(1)
{
BF_WR(CLKCTRL_CPU, INTERRUPT_WAIT(1));
asm volatile (
"mcr p15, 0, %0, c7, c0, 4 \n" /* Wait for interrupt */
"nop\n" /* Datasheet unclear: "The lr sent to handler points here after RTI"*/
"nop\n"
: : "r"(0)
);
}
}
static void set_updater_bits(void)
{
/* The OF will continue to updater if we clear 18 of PERSISTENT1.
* See dualboot-imx233.c in firmware/ for more explanation */
HW_RTC_PERSISTENT1_CLR = 1 << 18;
}
int main(uint32_t arg, uint32_t *result_id)
{
if(arg == BOOT_ARG_CHARGE)
do_charge();
/* tell rockbox that we can handle boot mode */
imx233_dualboot_set_field(DUALBOOT_CAP_BOOT, 1);
/* if we were asked to boot in a special mode, do so */
unsigned boot_mode = imx233_dualboot_get_field(DUALBOOT_BOOT);
/* clear boot mode to avoid any loop */
imx233_dualboot_set_field(DUALBOOT_BOOT, IMX233_BOOT_NORMAL);
switch(boot_mode)
{
case IMX233_BOOT_UPDATER:
set_updater_bits();
/* fallthrough */
case IMX233_BOOT_OF:
/* continue booting */
return BOOT_ROM_CONTINUE;
case IMX233_BOOT_NORMAL:
default:
break;
}
/* normal boot */
switch(boot_decision(get_context()))
{
case BOOT_ROCK:
*result_id = arg;
return BOOT_ROM_SECTION;
case BOOT_OF:
return BOOT_ROM_CONTINUE;
case BOOT_STOP:
default:
power_down();
}
}
int __attribute__((section(".start"))) start(uint32_t arg, uint32_t *result_id)
{
return main(arg, result_id);
}