summaryrefslogtreecommitdiffstats
path: root/rbutil/mkimxboot/dualboot/dualboot.c
blob: 4aa128e15b1692f2f2bb48b1833c50f4cb01b85b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/***************************************************************************
 *             __________               __   ___.
 *   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;

// 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_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);
}