6022d3100a
Several devices, including the Fuze+ have great trouble recovering from very low battery states, even in the presence of USB power. This is partly due to buggy Sigmatel boot stubs and Rockbox bootloader doing unsafe power operations on boot (should be fixed soon). In such a state, it is impossible to boot either the OF and Rockbox, so only the recovery mode is available. With this commit, mkimxboot can now create a very small stub which only does one thing but does it well: setup charging to recover from any situation. It does not provide a fancy charging screen or whatever, screen will just stay black and the device will slowly charge at ~100mA. When the battery is back to a normal level, just unplug and boot normally. Change-Id: Ib50880af85ed1f4f64a7eed0f2221e73c889c351
284 lines
No EOL
8.2 KiB
C
284 lines
No EOL
8.2 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)
|
|
|
|
typedef unsigned long uint32_t;
|
|
|
|
// 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);
|
|
/* don't bother changing the source, we are early enough at boot so that
|
|
* channel x is mapped to source x */
|
|
HW_LRADC_CHn_CLR(src) = BM_OR2(LRADC_CHn, NUM_SAMPLES, ACCUMULATE);
|
|
BF_SETV(LRADC_CTRL2, 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_SETV(LRADC_CTRL0, SCHEDULE, 1 << src);
|
|
while(!BF_RD(LRADC_CTRL1, LRADCx_IRQ(src)));
|
|
return BF_RDn(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_OR2(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 */
|
|
if(read_gpio(0, 9) == 0)
|
|
return BOOT_STOP;
|
|
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
|
|
HW_PINCTRL_PULLn_SET(0) = 1 << 9; // enable pullup on hold key (B0P09)
|
|
/* 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_WRn(LRADC_DELAYn, 0, TRIGGER_LRADCS, 0x80);
|
|
BF_WRn(LRADC_DELAYn, 0, TRIGGER_DELAYS, 0x1);
|
|
BF_WRn(LRADC_DELAYn, 0, DELAY, 200);
|
|
BF_SETn(LRADC_DELAYn, 0, KICK);
|
|
BF_SET(LRADC_CONVERSION, AUTOMATIC);
|
|
BF_WR_V(LRADC_CONVERSION, SCALE_FACTOR, 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)
|
|
);
|
|
}
|
|
}
|
|
|
|
int main(uint32_t arg, uint32_t *result_id)
|
|
{
|
|
if(arg == BOOT_ARG_CHARGE)
|
|
do_charge();
|
|
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);
|
|
} |