rockbox/firmware/target/arm/imx233/debug-imx233.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

1406 lines
42 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2011 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 "cpu.h"
#include "system.h"
#include "kernel.h"
#include "dma-imx233.h"
#include "lcd.h"
#include "font.h"
#include "adc.h"
#include "usb.h"
#include "power-imx233.h"
#include "clkctrl-imx233.h"
#include "powermgmt-imx233.h"
#include "rtc-imx233.h"
#include "dualboot-imx233.h"
#include "dcp-imx233.h"
#include "pinctrl-imx233.h"
#include "ocotp-imx233.h"
#include "pwm-imx233.h"
#include "emi-imx233.h"
#include "audioin-imx233.h"
#include "audioout-imx233.h"
#include "timrot-imx233.h"
#include "string.h"
#include "stdio.h"
#include "button.h"
#include "button-imx233.h"
#include "sdmmc-imx233.h"
#include "storage.h"
#include "regs/usbphy.h"
#include "regs/timrot.h"
#include "regs/power.h"
#define ACT_NONE 0
#define ACT_CANCEL 1
#define ACT_OK 2
#define ACT_PREV 3
#define ACT_NEXT 4
#define ACT_LEFT 5
#define ACT_RIGHT 6
#define ACT_REPEAT 0x1000
int xlate_button(int btn)
{
switch(btn)
{
case BUTTON_POWER:
#if defined(BUTTON_BACK)
case BUTTON_BACK:
#elif defined(BUTTON_LEFT)
case BUTTON_LEFT:
#else
#error no key for ACT_CANCEL
#endif
return ACT_CANCEL;
#if defined(BUTTON_SELECT)
case BUTTON_SELECT:
#elif defined(BUTTON_PLAY)
case BUTTON_PLAY:
#elif defined(BUTTON_CENTER)
case BUTTON_CENTER:
#else
#error no key for ACT_OK
#endif
return ACT_OK;
case BUTTON_UP:
return ACT_PREV;
case BUTTON_DOWN:
return ACT_NEXT;
default:
return ACT_NONE;
}
}
int my_get_status(void)
{
return xlate_button(button_status());
}
int my_get_action(int tmo)
{
int btn = button_get_w_tmo(tmo);
while(btn & BUTTON_REL)
btn = button_get_w_tmo(tmo);
bool repeat = btn & BUTTON_REPEAT;
int act = xlate_button(btn & ~BUTTON_REPEAT);
if(repeat)
act |= ACT_REPEAT;
return act;
}
static struct
{
const char *name;
unsigned chan;
} dbg_channels[] =
{
{ "i2c", APB_I2C },
{ "dac", APB_AUDIO_DAC },
{ "ssp1", APB_SSP(1) },
{ "ssp2", APB_SSP(2) },
};
static struct
{
const char *name;
unsigned src;
} dbg_irqs[] =
{
{ "vdd5v", INT_SRC_VDD5V },
{ "dac_dma", INT_SRC_DAC_DMA },
{ "dac_err", INT_SRC_DAC_ERROR },
{ "adc_dma", INT_SRC_ADC_DMA },
{ "adc_err", INT_SRC_ADC_ERROR },
{ "usbctrl", INT_SRC_USB_CTRL },
{ "ssp1_dma", INT_SRC_SSP1_DMA },
{ "ssp1_err", INT_SRC_SSP1_ERROR },
{ "gpio0", INT_SRC_GPIO0 },
{ "gpio1", INT_SRC_GPIO1 },
{ "gpio2", INT_SRC_GPIO2 },
#if IMX233_SUBTARGET >= 3780
{ "ssp2_dma", INT_SRC_SSP2_DMA },
{ "ssp2_err", INT_SRC_SSP2_ERROR },
{ "lcdif_dma", INT_SRC_LCDIF_DMA },
{ "lcdif_err", INT_SRC_LCDIF_ERROR },
{ "dcp", INT_SRC_DCP },
#endif
{ "i2c_dma", INT_SRC_I2C_DMA },
{ "i2c_err", INT_SRC_I2C_ERROR },
{ "timer0", INT_SRC_TIMER(0) },
{ "timer1", INT_SRC_TIMER(1) },
{ "timer2", INT_SRC_TIMER(2) },
{ "timer3", INT_SRC_TIMER(3) },
{ "touch_det", INT_SRC_TOUCH_DETECT },
{ "lradc_ch0", INT_SRC_LRADC_CHx(0) },
{ "lradc_ch1", INT_SRC_LRADC_CHx(1) },
{ "lradc_ch2", INT_SRC_LRADC_CHx(2) },
{ "lradc_ch3", INT_SRC_LRADC_CHx(3) },
{ "lradc_ch4", INT_SRC_LRADC_CHx(4) },
{ "lradc_ch5", INT_SRC_LRADC_CHx(5) },
{ "lradc_ch6", INT_SRC_LRADC_CHx(6) },
{ "lradc_ch7", INT_SRC_LRADC_CHx(7) },
{ "rtc_1msec", INT_SRC_RTC_1MSEC },
};
bool dbg_hw_info_dma(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 25);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
lcd_putsf(0, 0, "S C name bar apb ahb una");
for(unsigned i = 0; i < ARRAYLEN(dbg_channels); i++)
{
struct imx233_dma_info_t info = imx233_dma_get_info(dbg_channels[i].chan, DMA_INFO_ALL);
lcd_putsf(0, i + 1, "%c %c %4s %8x %3x %3x %3x",
info.gated ? 'g' : info.frozen ? 'f' : ' ',
!info.int_enabled ? '-' : info.int_error ? 'e' : info.int_cmdcomplt ? 'c' : ' ',
dbg_channels[i].name, info.bar, info.apb_bytes, info.ahb_bytes,
info.nr_unaligned);
}
lcd_update();
yield();
}
}
bool dbg_hw_info_power(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
struct imx233_power_info_t info = imx233_power_get_info(POWER_INFO_ALL);
int line = 0;
unsigned trg, bo;
bool en;
int linreg;
char buf[16];
lcd_putsf(0, line++, "name value bo linreg");
#define DISP_REGULATOR(name) \
imx233_power_get_regulator(REGULATOR_##name, &trg, &bo); \
imx233_power_get_regulator_linreg(REGULATOR_##name, &en, &linreg); \
if(en) snprintf(buf, sizeof(buf), "%d", linreg); \
else snprintf(buf, sizeof(buf), " "); \
lcd_putsf(0, line++, "%6s %4d %4d %s", #name, trg, bo, buf); \
DISP_REGULATOR(VDDD);
#if IMX233_SUBTARGET >= 3700
DISP_REGULATOR(VDDA);
DISP_REGULATOR(VDDIO);
#endif
#if IMX233_SUBTARGET >= 3780
DISP_REGULATOR(VDDMEM);
#endif
lcd_putsf(0, line++, "dcdc: pll: %d freq: %d", info.dcdc_sel_pllclk, info.dcdc_freqsel);
lcd_putsf(0, line++, "chrg: %d mA / %d mA", info.charge_current, info.stop_current);
lcd_putsf(0, line++, "chrging: %d batadj: %d", info.charging, info.batt_adj);
lcd_putsf(0, line++, "4.2: en: %d dcdc: %d", info._4p2_enable, info._4p2_dcdc);
lcd_putsf(0, line++, "4.2: cmptrip: %d", info._4p2_cmptrip);
lcd_putsf(0, line++, "4.2: dropout: %d", info._4p2_dropout);
lcd_putsf(0, line++, "5v: pwd_4.2_charge: %d", info._5v_pwd_charge_4p2);
lcd_putsf(0, line++, "5v: chrglim: %d mA", info._5v_charge_4p2_limit);
lcd_putsf(0, line++, "5v: dcdc: %d xfer: %d", info._5v_enable_dcdc, info._5v_dcdc_xfer);
lcd_putsf(0, line++, "5v: thr: %d mV", info._5v_vbusvalid_thr);
lcd_putsf(0, line++, "5v: use: %d cmps: %d", info._5v_vbusvalid_detect, info._5v_vbus_cmps);
#if IMX233_SUBTARGET >= 3780
lcd_putsf(0, line++, "pwrup: %x", BF_RD(POWER_STS, PWRUP_SOURCE));
#endif
lcd_update();
yield();
}
}
bool dbg_hw_info_lradc(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 25);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
/* add battery readout in mV, this it is not the direct output of a channel */
lcd_putsf(0, 0, "Battery(mV) %d", _battery_voltage());
for(unsigned i = 0; i < NUM_ADC_CHANNELS; i++)
{
lcd_putsf(0, i + 1, "%s %d", adc_name(i), adc_read(i));
}
lcd_update();
yield();
}
}
static struct
{
enum imx233_clock_t clk;
const char *name;
bool has_enable;
bool has_bypass;
bool has_idiv;
bool has_fdiv;
bool has_freq;
} dbg_clk[] =
{
{ CLK_PLL, "pll", true, false, false, false, true},
{ CLK_XTAL, "xtal", false, false, false, false, true},
#if IMX233_SUBTARGET >= 3700
{ CLK_PIX, "pix", true, true, true, true, true },
#endif
{ CLK_SSP, "ssp", true, true, true, false, true },
{ CLK_IO, "io", false, false, false, true, true },
{ CLK_CPU, "cpu", false, true, true, true, true },
{ CLK_HBUS, "hbus", false, false, true, true, true },
{ CLK_EMI, "emi", false, true, true, true, true },
{ CLK_XBUS, "xbus", false, false, true, false, true }
};
bool dbg_hw_info_clkctrl(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
/* 012345678901234567890123456789 */
#if LCD_WIDTH < 240
lcd_putsf(0, 0, "name en frequency");
#else
lcd_putsf(0, 0, "name en by idiv fdiv frequency");
#endif
for(unsigned i = 0; i < ARRAYLEN(dbg_clk); i++)
{
#define c dbg_clk[i]
lcd_putsf(0, i + 1, "%4s", c.name);
if(c.has_enable)
lcd_putsf(5, i + 1, "%2d", imx233_clkctrl_is_enabled(c.clk));
#if LCD_WIDTH >= 240
#if IMX233_SUBTARGET >= 3700
if(c.has_bypass)
lcd_putsf(8, i + 1, "%2d", imx233_clkctrl_get_bypass(c.clk));
#endif
if(c.has_idiv && imx233_clkctrl_get_div(c.clk) != 0)
lcd_putsf(10, i + 1, "%4d", imx233_clkctrl_get_div(c.clk));
#if IMX233_SUBTARGET >= 3700
if(c.has_fdiv && imx233_clkctrl_get_frac_div(c.clk) != 0)
lcd_putsf(16, i + 1, "%4d", imx233_clkctrl_get_frac_div(c.clk));
#endif
if(c.has_freq)
lcd_putsf(21, i + 1, "%9d", imx233_clkctrl_get_freq(c.clk));
#else /* LCD_WIDTH < 240 */
if(c.has_freq)
lcd_putsf(8, i + 1, "%9d", imx233_clkctrl_get_freq(c.clk));
#endif
#undef c
}
int line = ARRAYLEN(dbg_clk) + 1;
if(!imx233_clkctrl_is_auto_slow_enabled())
lcd_putsf(0, line++, "auto-slow: disabled");
else
lcd_putsf(0, line++, "auto-slow: 1/%d", 1 << imx233_clkctrl_get_auto_slow_div());
lcd_update();
yield();
}
}
bool dbg_hw_info_powermgmt(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
struct imx233_powermgmt_info_t info = imx233_powermgmt_get_info();
lcd_putsf(0, 0, "state: %s",
info.state == DISCHARGING ? "discharging" :
#if CONFIG_CHARGING >= CHARGING_MONITOR
info.state == CHARGE_STATE_DISABLED ? "disabled" :
info.state == CHARGE_STATE_ERROR ? "error" :
#endif
#if CONFIG_CHARGING >= CHARGING_MONITOR
info.state == TRICKLE ? "trickle" :
info.state == TOPOFF ? "topoff" :
info.state == CHARGING ? "charging" :
#endif
"<unknown>");
lcd_putsf(0, 1, "charging tmo: %d", info.charging_timeout);
lcd_putsf(0, 2, "topoff tmo: %d", info.topoff_timeout);
lcd_update();
yield();
}
}
#if IMX233_SUBTARGET >= 3780
/* stmp < 3780 does not have a 4.2V rail and thus cannot do this magic trick */
bool dbg_hw_info_power2(void)
{
lcd_setfont(FONT_SYSFIXED);
bool holding_select = false;
int select_hold_time = 0;
while(1)
{
int button = my_get_action(HZ / 10);
if(button == ACT_NEXT || button == ACT_PREV)
{
lcd_setfont(FONT_UI);
return true;
}
else if(button == ACT_CANCEL)
{
lcd_setfont(FONT_UI);
return false;
}
button = my_get_status();
if(button == ACT_OK && !holding_select)
{
holding_select = true;
select_hold_time = current_tick;
}
else if(button != ACT_OK && holding_select)
{
holding_select = false;
}
/* disable feature if unsafe: we need 4.2 and dcdc fully operational */
bool feat_safe = usb_detect() == USB_INSERTED && BF_RD(POWER_DCDC4P2, ENABLE_DCDC)
&& BF_RD(POWER_DCDC4P2, ENABLE_4P2) && BF_RD(POWER_5VCTRL, ENABLE_DCDC)
&& !BF_RD(POWER_5VCTRL, PWD_CHARGE_4P2);
bool batt_disabled = (BF_RD(POWER_DCDC4P2, DROPOUT_CTRL) == 0xc);
if(holding_select && TIME_AFTER(current_tick, select_hold_time + HZ))
{
if(batt_disabled)
{
BF_CLR(POWER_CHARGE, PWD_BATTCHRG); /* enable charger again */
BF_WR(POWER_DCDC4P2, DROPOUT_CTRL(0xe)); /* select greater, 200 mV drop */
}
else if(feat_safe)
{
BF_WR(POWER_DCDC4P2, DROPOUT_CTRL(0xc)); /* always select 4.2, 200 mV drop */
BF_SET(POWER_CHARGE, PWD_BATTCHRG); /* disable charger */
}
holding_select = false;
/* return to the beginning of the loop to gather more information
* about HW state before displaying it */
continue;
}
lcd_clear_display();
if(!batt_disabled)
{
lcd_putsf(0, 0, "Hold select for 1 sec");
lcd_putsf(0, 1, "to disable battery");
lcd_putsf(0, 1, "and battery charger.");
lcd_putsf(0, 2, "The device will run");
lcd_putsf(0, 3, "entirely from USB.");
lcd_putsf(0, 5, "WARNING");
lcd_putsf(0, 6, "This is a debug");
lcd_putsf(0, 7, "feature !");
if(!feat_safe)
{
lcd_putsf(0, 9, "NOTE: unavailable");
lcd_putsf(0, 10, "Plug USB to enable.");
}
}
else
{
lcd_putsf(0, 0, "Battery is DISABLED.");
lcd_putsf(0, 1, "Hold select for 1 sec");
lcd_putsf(0, 2, "to renable battery.");
lcd_putsf(0, 4, "WARNING");
lcd_putsf(0, 5, "Do not unplug USB !");
}
lcd_update();
yield();
}
}
#endif /* IMX233_SUBTARGET >= 3780 */
bool dbg_hw_info_rtc(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
struct imx233_rtc_info_t info = imx233_rtc_get_info();
int line = 0;
lcd_putsf(0, line++, "seconds: %lu", info.seconds);
lcd_putsf(0, line++, "alarm: %lu", info.alarm);
for(int i = 0; i < 6; i++)
lcd_putsf(0, line++, "persist%d: 0x%lx", i, info.persistent[i]);
lcd_update();
yield();
}
}
#if IMX233_SUBTARGET >= 3780
bool dbg_hw_info_dcp(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
struct imx233_dcp_info_t info = imx233_dcp_get_info(DCP_INFO_ALL);
lcd_putsf(0, 0, "crypto: %d csc: %d", info.has_crypto, info.has_csc);
lcd_putsf(0, 1, "keys: %d channels: %d", info.num_keys, info.num_channels);
lcd_putsf(0, 2, "ciphers: 0x%lx hash: 0x%lx", info.ciphers, info.hashs);
lcd_putsf(0, 3, "gather wr: %d otp rdy: %d ch0merged: %d",
info.gather_writes, info.otp_key_ready, info.ch0_merged);
lcd_putsf(0, 4, "ctx switching: %d caching: %d", info.context_switching,
info.context_caching);
lcd_putsf(0, 5, "ch irq ien en rdy pri sem cmdptr a");
int nr = HW_DCP_NUM_CHANNELS;
for(int i = 0; i < nr; i++)
{
lcd_putsf(0, 6 + i, "%d %d %d %d %d %d %d 0x%08lx %d",
i, info.channel[i].irq, info.channel[i].irq_en, info.channel[i].enable,
info.channel[i].ready, info.channel[i].high_priority,
info.channel[i].sema, info.channel[i].cmdptr, info.channel[i].acquired);
}
lcd_putsf(0, 6 + nr, "csc %d %d %d %d",
info.csc.irq, info.csc.irq_en, info.csc.enable, info.csc.priority);
lcd_update();
yield();
}
}
#else
bool dbg_hw_info_dcp(void)
{
return true;
}
#endif
bool dbg_hw_info_icoll(void)
{
lcd_setfont(FONT_SYSFIXED);
int first_irq = 0;
int dbg_irqs_count = sizeof(dbg_irqs) / sizeof(dbg_irqs[0]);
int line_count = lcd_getheight() / font_get(lcd_getfont())->height;
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
first_irq++;
if(first_irq >= dbg_irqs_count)
first_irq = dbg_irqs_count - 1;
break;
case ACT_PREV:
first_irq--;
if(first_irq < 0)
first_irq = 0;
break;
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
for(int i = first_irq, j = 0; i < dbg_irqs_count && j < line_count; i++, j++)
{
struct imx233_icoll_irq_info_t info = imx233_icoll_get_irq_info(dbg_irqs[i].src);
static char prio[4] = {'-', '+', '^', '!'};
lcd_putsf(0, j, "%c%s", prio[info.priority & 3], dbg_irqs[i].name);
if(info.enabled || info.freq > 0)
lcd_putsf(11, j, "%d", info.freq);
}
lcd_update();
yield();
}
}
bool dbg_hw_info_pinctrl(void)
{
lcd_setfont(FONT_SYSFIXED);
#ifdef IMX233_PINCTRL_DEBUG
unsigned top_user = 0;
#endif
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
#ifdef IMX233_PINCTRL_DEBUG
top_user++;
break;
#endif
case ACT_PREV:
#ifdef IMX233_PINCTRL_DEBUG
if(top_user > 0)
top_user--;
break;
#endif
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
for(int i = 0; i < 4; i++)
lcd_putsf(0, i, "DIN%d = 0x%08x", i, HW_PINCTRL_DINn(i));
#ifdef IMX233_PINCTRL_DEBUG
unsigned cur_line = 6;
unsigned last_line = lcd_getheight() / font_get(lcd_getfont())->height;
unsigned cur_idx = 0;
for(int bank = 0; bank < 4; bank++)
for(int pin = 0; pin < 32; pin++)
{
const char *owner = imx233_pinctrl_blame(bank, pin);
if(owner == NULL)
continue;
if(cur_idx++ >= top_user && cur_line < last_line)
lcd_putsf(0, cur_line++, "B%dP%02d %s", bank, pin, owner);
}
if(cur_idx < top_user)
top_user = cur_idx - 1;
#endif
lcd_update();
yield();
}
}
struct
{
const char *name;
volatile uint32_t *addr;
} dbg_ocotp[] =
{
#if IMX233_SUBTARGET >= 3700
#define E(n,v) { .name = n, .addr = &v }
E("CUST0", HW_OCOTP_CUSTn(0)), E("CUST1", HW_OCOTP_CUSTn(1)),
E("CUST2", HW_OCOTP_CUSTn(2)), E("CUST0", HW_OCOTP_CUSTn(3)),
E("HWCAP0", HW_OCOTP_HWCAPn(0)), E("HWCAP1", HW_OCOTP_HWCAPn(1)),
E("HWCAP2", HW_OCOTP_HWCAPn(2)), E("HWCAP3", HW_OCOTP_HWCAPn(3)),
E("HWCAP4", HW_OCOTP_HWCAPn(4)), E("HWCAP5", HW_OCOTP_HWCAPn(5)),
E("SWCAP", HW_OCOTP_SWCAP), E("CUSTCAP", HW_OCOTP_CUSTCAP),
E("OPS0", HW_OCOTP_OPSn(0)), E("OPS1", HW_OCOTP_OPSn(1)),
E("OPS2", HW_OCOTP_OPSn(2)), E("OPS2", HW_OCOTP_OPSn(3)),
E("UN0", HW_OCOTP_UNn(0)), E("UN1", HW_OCOTP_UNn(1)),
E("UN2", HW_OCOTP_UNn(2)),
E("ROM0", HW_OCOTP_ROMn(0)), E("ROM1", HW_OCOTP_ROMn(1)),
E("ROM2", HW_OCOTP_ROMn(2)), E("ROM3", HW_OCOTP_ROMn(3)),
E("ROM4", HW_OCOTP_ROMn(4)), E("ROM5", HW_OCOTP_ROMn(5)),
E("ROM6", HW_OCOTP_ROMn(6)), E("ROM7", HW_OCOTP_ROMn(7)),
#undef E
#else
#define E(n,v) { .name = n, .addr = &v }
E("LASERFUSE0", HW_RTC_LASERFUSEn(0)),
E("LASERFUSE1", HW_RTC_LASERFUSEn(1)),
E("LASERFUSE2", HW_RTC_LASERFUSEn(2)),
E("LASERFUSE3", HW_RTC_LASERFUSEn(3)),
E("LASERFUSE4", HW_RTC_LASERFUSEn(4)),
E("LASERFUSE5", HW_RTC_LASERFUSEn(5)),
E("LASERFUSE6", HW_RTC_LASERFUSEn(6)),
E("LASERFUSE7", HW_RTC_LASERFUSEn(7)),
E("LASERFUSE8", HW_RTC_LASERFUSEn(8)),
E("LASERFUSE9", HW_RTC_LASERFUSEn(9)),
E("LASERFUSE10", HW_RTC_LASERFUSEn(10)),
E("LASERFUSE11", HW_RTC_LASERFUSEn(11)),
#undef E
#endif
};
bool dbg_hw_info_ocotp(void)
{
lcd_setfont(FONT_SYSFIXED);
unsigned top_user = 0;
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
top_user++;
break;
case ACT_PREV:
if(top_user > 0)
top_user--;
break;
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
unsigned cur_line = 0;
unsigned last_line = lcd_getheight() / font_get(lcd_getfont())->height;
unsigned i = 0;
for(i = 0; i < ARRAYLEN(dbg_ocotp); i++)
{
if(i >= top_user && cur_line < last_line)
{
lcd_putsf(0, cur_line, "%s", dbg_ocotp[i].name);
lcd_putsf(8, cur_line++, "%x", imx233_ocotp_read(dbg_ocotp[i].addr));
}
}
if(i < top_user)
top_user = i - 1;
lcd_update();
yield();
}
}
bool dbg_hw_info_pwm(void)
{
lcd_setfont(FONT_SYSFIXED);
bool detailled_view = false;
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
detailled_view = !detailled_view;
break;
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
int line = 0;
if(detailled_view)
lcd_putsf(0, line++, "c cdiv period active/x inactive");
else
lcd_putsf(0, line++, "pwm");
for(int i = 0; i < IMX233_PWM_NR_CHANNELS; i++)
{
struct imx233_pwm_info_t info = imx233_pwm_get_info(i);
if(!info.enabled)
continue;
if(detailled_view)
{
lcd_putsf(0, line++, "%d %4d %6d %6d/%c %6d/%c", i, info.cdiv,
info.period, info.active, info.active_state,
info.inactive, info.inactive_state);
}
else
{
char *prefix = "";
int freq = imx233_clkctrl_get_freq(CLK_XTAL) * 1000 / info.cdiv / info.period;
if(freq > 1000)
{
prefix = "K";
freq /= 1000;
}
int duty = (info.inactive - info.active) * 100 / info.period;
lcd_putsf(0, line++, "%d @%d %sHz, %d%% %c/%c", i, freq, prefix,
duty, info.active_state, info.inactive_state);
}
}
lcd_update();
yield();
}
}
bool dbg_hw_info_usb(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
BF_SET(USBPHY_CTRL, ENOTGIDDETECT);
BF_SET(USBPHY_CTRL, ENDEVPLUGINDETECT);
break;
case ACT_PREV:
BF_CLR(USBPHY_CTRL, ENOTGIDDETECT);
BF_CLR(USBPHY_CTRL, ENDEVPLUGINDETECT);
break;
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
int line = 0;
#if IMX233_SUBTARGET >= 3700
lcd_putsf(0, line++, "VBUS valid: %d/%d", BF_RD(POWER_STS, VBUSVALID), BF_RD(POWER_STS, VBUSVALID_STATUS));
lcd_putsf(0, line++, "A valid: %d/%d", BF_RD(POWER_STS, AVALID), BF_RD(POWER_STS, AVALID_STATUS));
lcd_putsf(0, line++, "B valid: %d/%d", BF_RD(POWER_STS, BVALID), BF_RD(POWER_STS, BVALID_STATUS));
#else
lcd_putsf(0, line++, "VBUS valid: %d/%d", BF_RD(POWER_STS, VBUSVALID));
lcd_putsf(0, line++, "A valid: %d/%d", BF_RD(POWER_STS, AVALID));
lcd_putsf(0, line++, "B valid: %d/%d", BF_RD(POWER_STS, BVALID));
#endif
lcd_putsf(0, line++, "session end: %d", BF_RD(POWER_STS, SESSEND));
lcd_putsf(0, line++, "dev plugin: %d", BF_RD(USBPHY_STATUS, DEVPLUGIN_STATUS));
lcd_putsf(0, line++, "OTG ID: %d", BF_RD(USBPHY_STATUS, OTGID_STATUS));
lcd_update();
yield();
}
}
bool dbg_hw_info_emi(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
struct imx233_emi_info_t info = imx233_emi_get_info();
int line = 0;
lcd_putsf(0, line++, "EMI");
lcd_putsf(0, line++, "rows: %d", info.rows);
lcd_putsf(0, line++, "columns: %d", info.columns);
lcd_putsf(0, line++, "banks: %d", info.banks);
lcd_putsf(0, line++, "chips: %d", info.chips);
lcd_putsf(0, line++, "size: %d MiB", info.size / 1024 / 1024);
lcd_putsf(0, line++, "cas: %d.%d", info.cas / 2, 5 * (info.cas % 2));
lcd_update();
yield();
}
}
bool dbg_hw_info_audio(void)
{
static const char *hp_sel[2] = {"DAC", "Line1"};
static const char *mux_sel[4] = {"Mic", "Line1", "HP", "Line2"};
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
struct imx233_audioout_info_t out = imx233_audioout_get_info();
struct imx233_audioin_info_t in = imx233_audioin_get_info();
int line = 0;
#define display_sys(st, sys, name) \
if(st.sys) \
{ \
char buffer[64]; \
snprintf(buffer, 64, "%s: ", name); \
for(int i = 0; i < 2; i++) \
{ \
if(st.sys##mute[i]) \
strcat(buffer, "mute"); \
else \
snprintf(buffer + strlen(buffer), 64, "%d.%d", \
/* properly handle negative values ! */ \
st.sys##vol[i] / 10, (10 + (st.sys##vol[i]) % 10) % 10); \
if(i == 0) \
strcat(buffer, " / "); \
else \
strcat(buffer, " dB"); \
} \
lcd_putsf(0, line++, "%s", buffer); \
} \
else \
lcd_putsf(0, line++, "%s: powered down", name);
display_sys(out, dac, "DAC");
display_sys(out, hp, "HP");
display_sys(out, spkr, "SPKR");
display_sys(in, adc, "ADC");
display_sys(in, mux, "MUX");
display_sys(in, mic, "MIC");
#undef display_sys
lcd_putsf(0, line++, "capless: %d", out.capless);
lcd_putsf(0, line++, "HP select: %s", hp_sel[out.hpselect]);
lcd_putsf(0, line++, "MUX select: %s / %s", mux_sel[in.muxselect[0]], mux_sel[in.muxselect[1]]);
lcd_update();
yield();
}
}
bool dbg_hw_info_timrot(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
int line = 0;
for(int i = 0; i < 4; i++)
{
struct imx233_timrot_info_t info = imx233_timrot_get_info(i);
const char *unit = NULL;
const char *unit_prefix = "";
int src_freq = 1;
switch(info.src)
{
case BV_TIMROT_TIMCTRLn_SELECT__NEVER_TICK: src_freq = 0; break;
case BV_TIMROT_TIMCTRLn_SELECT__PWM0: unit = "PWM0"; break;
case BV_TIMROT_TIMCTRLn_SELECT__PWM1: unit = "PWM1"; break;
case BV_TIMROT_TIMCTRLn_SELECT__PWM2: unit = "PWM2"; break;
case BV_TIMROT_TIMCTRLn_SELECT__PWM3: unit = "PWM3"; break;
case BV_TIMROT_TIMCTRLn_SELECT__PWM4: unit = "PWM4"; break;
case BV_TIMROT_TIMCTRLn_SELECT__ROTARYA: unit = "ROTA"; break;
case BV_TIMROT_TIMCTRLn_SELECT__ROTARYB: unit = "ROTB"; break;
case BV_TIMROT_TIMCTRLn_SELECT__32KHZ_XTAL: src_freq = 32000; break;
case BV_TIMROT_TIMCTRLn_SELECT__8KHZ_XTAL: src_freq = 8000; break;
case BV_TIMROT_TIMCTRLn_SELECT__4KHZ_XTAL: src_freq = 4000; break;
case BV_TIMROT_TIMCTRLn_SELECT__1KHZ_XTAL: src_freq = 1000; break;
case BV_TIMROT_TIMCTRLn_SELECT__TICK_ALWAYS:
default: src_freq = 24000000 / (1 << info.prescale); break;
}
int count = info.reload ? info.fixed_count + 1 : info.run_count;
if(src_freq == 0 || count == 0)
continue;
unsigned long long freq;
if(info.reload)
freq = (unsigned long long)src_freq * 1000 / count;
else
freq = count * 1000 / src_freq;
if(freq >= 1000000000)
{
unit_prefix = "M";
freq /= 1000000;
}
else if(freq >= 1000000)
{
unit_prefix = "k";
freq /= 1000;
}
char str[32];
if(freq % 1000)
snprintf(str, sizeof(str), "%lu.%lu", (unsigned long)freq / 1000, (unsigned long)freq % 1000);
else
snprintf(str, sizeof(str), "%lu", (unsigned long)freq / 1000);
char str2[32];
if(unit)
snprintf(str2, sizeof(str2), "%s%s(%s)", unit_prefix, info.reload ? "H" : "", unit);
else
snprintf(str2, sizeof(str2), "%s%s", unit_prefix, info.reload ? "Hz" : "s");
lcd_putsf(0, line++, "%ctimer %d: %s %s", info.polarity ? '+' : '-', i, str, str2);
}
lcd_update();
yield();
}
}
bool dbg_hw_info_button(void)
{
lcd_setfont(FONT_SYSFIXED);
#if IMX233_SUBTARGET >= 3700
int orig_vddio_val, orig_vddio_brownout;
imx233_power_get_regulator(REGULATOR_VDDIO, &orig_vddio_val, &orig_vddio_brownout);
int vddio_val = orig_vddio_val;
int vddio_brownout = orig_vddio_brownout;
#endif
while(1)
{
int btn = my_get_action(0);
switch(btn)
{
#if IMX233_SUBTARGET >= 3700
case ACT_PREV:
vddio_val -= 100; /* mV */
vddio_brownout -= 100; /* mV */
imx233_power_set_regulator(REGULATOR_VDDIO, vddio_val, vddio_brownout);
break;
case ACT_NEXT:
vddio_val += 100; /* mV */
vddio_brownout += 100; /* mV */
imx233_power_set_regulator(REGULATOR_VDDIO, vddio_val, vddio_brownout);
break;
#endif
case ACT_OK:
#if IMX233_SUBTARGET >= 3700
imx233_power_set_regulator(REGULATOR_VDDIO, orig_vddio_val, orig_vddio_brownout);
#endif
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
#if IMX233_SUBTARGET >= 3700
imx233_power_set_regulator(REGULATOR_VDDIO, orig_vddio_val, orig_vddio_brownout);
#endif
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
int line = 0;
#ifdef HAVE_BUTTON_DATA
int data;
btn = button_read_device(&data);
#else
btn = button_read_device();
#endif
lcd_putsf(0, line++, "raw buttons: %x", btn);
#ifdef HAS_BUTTON_HOLD
lcd_putsf(0, line++, "hold: %d", button_hold());
#endif
#ifdef HAVE_HEADPHONE_DETECTION
lcd_putsf(0, line++, "headphones: %d", headphones_inserted());
#endif
#ifdef HAVE_BUTTON_DATA
#ifdef HAVE_TOUCHSCREEN
lcd_putsf(0, line++, "touch: x=%d y=%d", data >> 16, data & 0xffff);
#else
lcd_putsf(0, line++, "data: %d", data);
#endif
#endif
#define MAP imx233_button_map
for(int i = 0; MAP[i].btn != IMX233_BUTTON_END; i++)
{
bool val = imx233_button_read_btn(i);
int raw = imx233_button_read_raw(i);
char type[20];
char path[128];
char flags[128];
if(MAP[i].periph == IMX233_BUTTON_GPIO)
{
snprintf(type, sizeof(type), "gpio");
snprintf(path, sizeof(path), "bank=%d pin=%d", MAP[i].u.gpio.bank, MAP[i].u.gpio.pin);
}
else if(MAP[i].periph == IMX233_BUTTON_LRADC)
{
static const char *op_name[] =
{
[IMX233_BUTTON_EQ] = "eq",
[IMX233_BUTTON_GT] = "gt",
[IMX233_BUTTON_LT] = "lt"
};
char rel_name[20];
snprintf(type, sizeof(type), "adc");
if(MAP[i].u.lradc.relative != -1)
snprintf(rel_name, sizeof(rel_name), " %s", MAP[MAP[i].u.lradc.relative].name);
else
rel_name[0] = 0;
snprintf(path, sizeof(path), "%d %s %d%s %d", MAP[i].u.lradc.src,
op_name[MAP[i].u.lradc.op], MAP[i].u.lradc.value, rel_name,
MAP[i].u.lradc.margin);
}
else if(MAP[i].periph == IMX233_BUTTON_PSWITCH)
{
snprintf(type, sizeof(type), "psw");
snprintf(path, sizeof(path), "level=%d", MAP[i].u.pswitch.level);
}
else
{
snprintf(type, sizeof(type), "unk");
snprintf(path, sizeof(path), "unknown");
}
flags[0] = 0;
if(MAP[i].flags & IMX233_BUTTON_INVERTED)
strcat(flags, " inv");
if(MAP[i].flags & IMX233_BUTTON_PULLUP)
strcat(flags, " pull");
#if LCD_WIDTH <= LCD_HEIGHT
lcd_putsf(0, line++, "%s %d %d/%d %d %s", MAP[i].name, val,
MAP[i].rounds, MAP[i].threshold, raw, type);
lcd_putsf(0, line++, " %s%s", path, flags);
#else
lcd_putsf(0, line++, "%s %d %d/%d %d %s %s%s", MAP[i].name, val,
MAP[i].rounds, MAP[i].threshold, raw, type, path, flags);
#endif
}
#undef MAP
lcd_update();
yield();
}
}
bool dbg_hw_info_sdmmc(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
int line = 0;
#if CONFIG_STORAGE & STORAGE_MMC
int mmc_idx = 0;
#endif
#if CONFIG_STORAGE & STORAGE_SD
int sd_idx = 0;
#endif
for(int drive = 0; drive < storage_num_drives(); drive++)
{
struct sdmmc_info_t info;
if(false) {}
#if CONFIG_STORAGE & STORAGE_MMC
else if(storage_driver_type(drive) == STORAGE_MMC_NUM)
info = imx233_mmc_get_info(mmc_idx++);
#endif
#if CONFIG_STORAGE & STORAGE_SD
else if(storage_driver_type(drive) == STORAGE_SD_NUM)
info = imx233_sd_get_info(sd_idx++);
#endif
else
continue;
lcd_putsf(0, line++, "%s", info.slot_name);
#ifdef HAVE_HOTSWAP
bool removable = storage_removable(info.drive);
bool present = storage_present(info.drive);
if(removable)
lcd_putsf(0, line++, " removable %spresent", present ? "" : "not ");
if(!present)
continue;
#endif
lcd_putsf(0, line++, " bus: %d sbc: %d", info.bus_width, info.has_sbc);
lcd_putsf(0, line++, " hs: %s", info.hs_enabled ? "enabled" :
info.hs_capable ? "disabled" : "not capable");
}
lcd_update();
yield();
}
}
#ifdef HAVE_DUALBOOT_STUB
bool dbg_hw_info_dualboot(void)
{
lcd_setfont(FONT_SYSFIXED);
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
case ACT_PREV:
{
/* only if boot mode is supported... */
if(!imx233_dualboot_get_field(DUALBOOT_CAP_BOOT))
break;
/* change it */
unsigned boot = imx233_dualboot_get_field(DUALBOOT_BOOT);
if(boot == IMX233_BOOT_NORMAL)
boot = IMX233_BOOT_OF;
else if(boot == IMX233_BOOT_OF)
boot = IMX233_BOOT_UPDATER;
else
boot = IMX233_BOOT_NORMAL;
imx233_dualboot_set_field(DUALBOOT_BOOT, boot);
break;
}
case ACT_OK:
lcd_setfont(FONT_UI);
return true;
case ACT_CANCEL:
lcd_setfont(FONT_UI);
return false;
}
lcd_clear_display();
int line = 0;
unsigned cap_boot = imx233_dualboot_get_field(DUALBOOT_CAP_BOOT);
lcd_putsf(0, line++, "cap_boot: %s", cap_boot ? "yes" : "no");
if(cap_boot)
{
unsigned boot = imx233_dualboot_get_field(DUALBOOT_BOOT);
lcd_putsf(0, line++, "boot: %s",
boot == IMX233_BOOT_NORMAL ? "normal"
: boot == IMX233_BOOT_OF ? "of"
: boot == IMX233_BOOT_UPDATER ? "updater" : "?");
}
lcd_update();
yield();
}
}
#endif
static struct
{
const char *name;
bool (*fn)(void);
} debug_screens[] =
{
{"clock", dbg_hw_info_clkctrl},
{"dma", dbg_hw_info_dma},
{"lradc", dbg_hw_info_lradc},
{"power", dbg_hw_info_power},
#if IMX233_SUBTARGET >= 3780
{"power2", dbg_hw_info_power2},
#endif
{"powermgmt", dbg_hw_info_powermgmt},
{"rtc", dbg_hw_info_rtc},
{"dcp", dbg_hw_info_dcp},
{"pinctrl", dbg_hw_info_pinctrl},
{"icoll", dbg_hw_info_icoll},
{"ocotp", dbg_hw_info_ocotp},
{"pwm", dbg_hw_info_pwm},
{"usb", dbg_hw_info_usb},
{"emi", dbg_hw_info_emi},
{"audio", dbg_hw_info_audio},
{"timrot", dbg_hw_info_timrot},
{"button", dbg_hw_info_button},
{"sdmmc", dbg_hw_info_sdmmc},
#ifdef HAVE_DUALBOOT_STUB
{"dualboot", dbg_hw_info_dualboot},
#endif
{"target", dbg_hw_target_info},
};
bool dbg_hw_info(void)
{
int nr_lines = lcd_getheight() / font_get(lcd_getfont())->height;
int len = ARRAYLEN(debug_screens);
int top_visible = 0;
int highlight = 0;
while(1)
{
int button = my_get_action(HZ / 10);
switch(button)
{
case ACT_NEXT:
highlight = (highlight + 1) % len;
break;
case ACT_PREV:
highlight = (highlight + len - 1) % len;
break;
case ACT_OK:
// if the screen returns true, advance to next screen
while(debug_screens[highlight].fn())
highlight = (highlight + 1) % len;
lcd_setfont(FONT_UI);
break;
case ACT_CANCEL:
return false;
}
// adjust top visible if needed
if(highlight < top_visible)
top_visible = highlight;
else if(highlight >= top_visible + nr_lines)
top_visible = highlight - nr_lines + 1;
lcd_clear_display();
for(int i = top_visible; i < len && i < top_visible + nr_lines; i++)
{
if(i == highlight)
{
lcd_set_foreground(LCD_BLACK);
lcd_set_background(LCD_RGBPACK(255, 255, 0));
}
else
{
lcd_set_foreground(LCD_WHITE);
lcd_set_background(LCD_BLACK);
}
lcd_putsf(0, i - top_visible, "%s", debug_screens[i].name);
}
lcd_set_foreground(LCD_WHITE);
lcd_set_background(LCD_BLACK);
lcd_update();
yield();
}
return false;
}
bool dbg_ports(void)
{
return false;
}