/*************************************************************************** * __________ __ ___. * 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); }