/*************************************************************************** * __________ __ ___. * 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 "led-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 ""); 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(); int line = 0; for(int i = first_irq; i < dbg_irqs_count && line < line_count; i++) { struct imx233_icoll_irq_info_t info = imx233_icoll_get_irq_info(dbg_irqs[i].src); static char prio[4] = {'-', '+', '^', '!'}; if(info.enabled || info.freq > 0) { lcd_putsf(0, line, "%c%s", prio[info.priority & 3], dbg_irqs[i].name); lcd_putsf(11, line, "%d %d %d", info.freq, info.max_time, info.total_time); line++; } } 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(); } } static void get_pwm_freq_duty(int chan, int *freq, int *duty) { struct imx233_pwm_info_t info = imx233_pwm_get_info(chan); *freq = imx233_clkctrl_get_freq(CLK_XTAL) * 1000 / info.cdiv / info.period; *duty = (info.inactive - info.active) * 100 / info.period; } 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 { int freq, duty; get_pwm_freq_duty(i, &freq, &duty); const char *prefix = ""; if(freq > 1000) { prefix = "K"; freq /= 1000; } 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(); } } static const char *get_led_col(enum imx233_led_color_t col) { switch(col) { case LED_RED: return "red"; case LED_GREEN: return "green"; case LED_BLUE: return "blue"; default: return "unknown"; } } bool dbg_hw_info_led(void) { lcd_setfont(FONT_SYSFIXED); int cur_led = 0, cur_chan = 0; bool nr_leds = imx233_led_get_count(); struct imx233_led_t *leds = imx233_led_get_info(); bool prev_pending = false; bool next_pending = false; bool editing = false; while(1) { int button = my_get_action(HZ / 10); switch(button) { case ACT_NEXT: if(nr_leds > 0 && !editing) { cur_chan++; if(cur_chan == leds[cur_led].nr_chan) { cur_chan = 0; cur_led = (cur_led + 1) % nr_leds; } } else next_pending = true; break; case ACT_PREV: if(nr_leds > 0 && !editing) { cur_chan--; if(cur_chan == -1) { cur_led = (cur_led + nr_leds - 1) % nr_leds; cur_chan = leds[cur_led].nr_chan - 1; } } else prev_pending = true; break; case ACT_OK: editing = !editing; break; case ACT_CANCEL: lcd_setfont(FONT_UI); return false; } lcd_clear_display(); int line = 0; if(nr_leds == 0) lcd_putsf(0, line++, "This device has no LED!"); for(int led = 0; led < imx233_led_get_count(); led++) { lcd_putsf(0, line++, "LED %d:", led); for(int chan = 0; chan < leds[led].nr_chan; chan++) { /* read current configuration */ char buffer[64]; bool use_pwm = false; int duty = 0, freq = 1; bool on = false; if(leds[led].chan[chan].has_pwm && imx233_pwm_is_enabled(leds[led].chan[chan].pwm_chan)) { get_pwm_freq_duty(leds[led].chan[chan].pwm_chan, &freq, &duty); /* assume active is high and inactive is low */ snprintf(buffer, sizeof(buffer), "%d Hz, %d%%", freq, duty); use_pwm = true; } else { on = imx233_pinctrl_get_gpio(leds[led].chan[chan].gpio_bank, leds[led].chan[chan].gpio_pin); snprintf(buffer, sizeof(buffer), "%s", on ? "on" : "off"); } if(cur_led == led && cur_chan == chan) lcd_set_foreground(editing ? LCD_RGBPACK(255, 0, 0) : LCD_RGBPACK(0, 0, 255)); lcd_putsf(0, line++, " %s: %s", get_led_col(leds[led].chan[chan].color), buffer); lcd_set_foreground(LCD_WHITE); /* do edit */ if(cur_led != led || cur_chan != chan || !editing) continue; if(!next_pending && !prev_pending) continue; bool inc = next_pending; next_pending = false; prev_pending = false; if(use_pwm) { duty += inc ? 10 : -10; if(duty < 0) duty = 0; if(duty > 100) duty = 100; imx233_led_set_pwm(cur_led, cur_chan, freq, duty); } else imx233_led_set(cur_led, cur_chan, !on); } } 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}, {"led", dbg_hw_info_led}, #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; }