pinephone-keyboard/firmware/main.c
Ondrej Jirman 31c41b7d9e Drop I2C bit functions, to make timing easier to modify
I2C to the charger now has clean 100kHz timing. One read command now takes
400us, and one write command takes 300us to execute.
2021-09-05 02:26:55 +02:00

2183 lines
45 KiB
C

/**
* Pinephone Keyboard Firmware
*
* Copyright (C) 2021 Ondřej Jirman <megi@xff.cz>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <string.h>
#include <em85f684a.h>
#ifndef CONFIG_FLASH_ENABLE
#define CONFIG_FLASH_ENABLE 1
#endif
#ifndef CONFIG_DEBUG_LOG
#define CONFIG_DEBUG_LOG 1
#endif
#ifndef CONFIG_USB_STACK
#define CONFIG_USB_STACK 1
#endif
#ifndef CONFIG_SELFTEST
#define CONFIG_SELFTEST 1
#endif
#ifndef CONFIG_STOCK_FW
#define CONFIG_STOCK_FW 1
#endif
#define USB_DEBUG 0
#define BIT(n) (1u << (n))
// {{{ Debug logging
#if CONFIG_DEBUG_LOG
// debug logging needs to have all variables volatile, since they
// can be accessed from interrupts
//
// any access to these variables has to happen with interrupts disabled
static volatile uint8_t __xdata log_buffer[1024];
// end = start => empty buffer
// end can never equal start on a filled buffer
// end points to the last char if end != start
static volatile uint16_t log_start = 0;
static volatile uint16_t log_end = 0;
// putc needs to disable interrupts (thus it's marked __critical)
// it's possible to call putc in any context
static void putc(char c) __critical
{
log_end = (log_end + 1) % 1024;
if (log_end == log_start) {
// overflow, just push the start in front of us
log_start = (log_start + 1) % 1024;
}
log_buffer[log_end] = c;
}
static void puts(const char* s)
{
while (*s)
putc(*s++);
}
static void put_uint(uint16_t value)
{
char buf[6];
char *p = &buf[6 - 1];
*p = '\0';
if (!value)
*--p = '0';
while (value) {
*--p = '0' + value % 10;
value /= 10;
}
puts(p);
}
static void put_hex_n(uint8_t nibble)
{
char c;
nibble &= 0xf;
if (nibble < 10)
c = '0' + nibble;
else
c = 'a' + (nibble - 10);
putc(c);
}
static void put_hex_b(uint8_t hex)
{
put_hex_n(hex >> 4);
put_hex_n(hex);
}
static void put_hex_w(uint16_t hex)
{
put_hex_b(hex >> 8);
put_hex_b(hex);
}
#else
#define putc(a)
#define puts(a)
#define put_hex_b(a)
#define put_hex_w(a)
#define put_hex_n(a)
#define put_uint(a)
#endif
// }}}
// {{{ Global flags
static __bit jump_to_usb_bootloader = 0;
// }}}
// {{{ Interrupt forwarding
#if CONFIG_STOCK_FW
// Stock firmware translates all interrupts to inerrupt vectors at 0x4000
// if IRAM location 0xff contains 0. Otherwise, it call interrupt handlers
// in stock FW.
#pragma noiv
uint8_t __idata __at(0xff) stock_flag = 1;
//XXX: check if we want to jump to user FW
// cleanup after stock firmware and jump to user firmware
static void jmp_to_user_fw(void) __naked
{
PAGESW = 0;
// disable all interrupts
IE = 0;
P0_EIE1 = 0;
P0_EIE2 = 0;
P0_EIE3 = 0;
// disable timers
TCON = 0;
TMOD = 0;
PSW1 = 0;
// disable I2C B
P0_I2CBCR1 = 0x00;
P0_I2CBCR2 = 0x20;
P0_I2CBINT = 0;
// disable watchdog
P0_WDTCR = 0x07; // disable watchdog ~1s
P0_WDTKEY = 0xb1; // disable watchdog
// reset powerdown/reset registers
P0_DEVPD1 = 0;
P0_DEVPD2 = 0;
P0_DEVPD3 = 0;
P0_PRST = 0;
// disable USB and clear irq flags
PAGESW = 1;
P1_PHYTEST0 &= ~BIT(6); // phy disable
P1_UDCCTRL &= ~BIT(6); // udc disable
P1_UDCINT0STA = 0;
P1_UDCINT1STA = 0;
P1_UDCINT2STA = 0;
P1_UDCINT0EN = 0;
P1_UDCINT1EN = 0;
P1_UDCINT2EN = 0;
// disable pullups, set all pins to input
P1_PHCON2 = 0;
P1_P9M0 = 0xffu;
PAGESW = 0;
P0_PHCON0 = 0;
P0_PHCON1 = 0;
P0_P6M0 = 0xffu;
P0_P8M0 = 0xffu;
P0_ICEN = 0;
stock_flag = 0;
__asm__ ("ljmp 0x4000");
}
#endif
// }}}
// {{{ Timers/delays
// timers clock is 2 MHz so we need to wait for 2000 ticks to get delay of 1ms
#define T0_SET_TIMEOUT(n) { \
TL0 = 0x00; \
TH0 = (0x10000u - (n)) >> 8; \
TL0 = (0x10000u - (n)) & 0xff; \
}
#define T1_SET_TIMEOUT(n) { \
TL1 = 0x00; \
TH1 = (0x10000u - (n)) >> 8; \
TL1 = (0x10000u - (n)) & 0xff; \
}
#define delay_us(n) { \
TL0 = 0x00; \
TF0 = 0; \
TH0 = (0x10000u - 2 * (n)) >> 8; \
TL0 = (0x10000u - 2 * (n)) & 0xff; \
while (!TF0); \
}
static volatile __bit run_timed_tasks = 0;
// we use this interrupt as a scheduling tick (wakeup from sleep)
void timer1_interrupt(void) __interrupt(IRQ_TIMER1) __using(1)
{
run_timed_tasks = 1;
// 20 ms
T1_SET_TIMEOUT(40000);
TF1 = 0;
}
// }}}
// {{{ Original USB bootloader integration
static void usb_bootloader_jump(void) __naked
{
PAGESW = 0;
// disable all interrupts
IE = 0;
P0_EIE1 = 0;
P0_EIE2 = 0;
P0_EIE3 = 0;
// disable timers
TCON = 0;
TMOD = 0;
PSW1 = 0;
// disable I2C B
P0_I2CBCR1 = 0x00;
P0_I2CBCR2 = 0x20;
P0_I2CBINT = 0;
// disable watchdog
P0_WDTCR = 0x07; // disable watchdog ~1s
P0_WDTKEY = 0xb1; // disable watchdog
// reset powerdown/reset registers
P0_DEVPD1 = 0;
P0_DEVPD2 = 0;
P0_DEVPD3 = 0;
P0_PRST = 0;
// disable USB and clear irq flags
PAGESW = 1;
P1_PHYTEST0 &= ~BIT(6); // phy disable
P1_UDCCTRL &= ~BIT(6); // udc disable
P1_UDCINT0STA = 0;
P1_UDCINT1STA = 0;
P1_UDCINT2STA = 0;
P1_UDCINT0EN = 0;
P1_UDCINT1EN = 0;
P1_UDCINT2EN = 0;
// disable pullups, set all pins to input
P1_PHCON2 = 0;
P1_P9M0 = 0xffu;
PAGESW = 0;
P0_PHCON0 = 0;
P0_PHCON1 = 0;
P0_P6M0 = 0xffu;
P0_P8M0 = 0xffu;
P0_ICEN = 0;
__asm__("mov r6,#0x5a");
__asm__("mov r7,#0xe7");
__asm__("ljmp 0x0118");
}
// }}}
// {{{ GPIO change interrupt
// we use this interrupt for wakeup from sleep on input change on port 6
static volatile __bit p6_changed = 0;
void pinchange_interrupt(void) __interrupt(IRQ_PINCHANGE) __using(1)
{
uint8_t saved_page = PAGESW;
PAGESW = 0;
// change flag
if (P0_ICEN & BIT(1)) {
p6_changed = 1;
}
// disable port 6 change detection
P0_ICEN = 0;
ICIE = 0;
PAGESW = saved_page;
}
// }}}
// {{{ Key scanning
static __bit scan_active = 0;
// Keyboard has 12 columns and 6 rows directly connected to GPIOs.
//
// C1 P95
// C2 P96
// C3 P97
// C4 P50
// C5 P51
// C6 P52
// C7 P53
// C8 P54
// C9 P55
// C10 P56
// C11 P57
// C12 P80 (also USB IAP trigger when pulled low)
//
// R1 P60
// R2 P61
// R3 P62
// R4 P63
// R5 P64
// R6 P65
//
// INT P90
// SCL P92
// SDA P93
//
// We will want to keep keyboard controller asleep unless some key is
// pressed. If a key is pressed, the controller will continuously scan
// for further pressed keys. When all keys are released, the controller
// can go back to sleep.
//
// For this to work, we'll use port 6 ability to wake up the controller
// on change.
//
// During sleep:
// - all columns will be set to low state
// - all rows will have pull-up enabled
// - when user presses any key, row state will change to low and
// the controller will wake up
//
// During active state:
// - all columns will be put to hi-Z state, except for the currently
// scanned one, which will be in low state
// - state of rows will be read, and will indicate state of keys
// in the selected column (0 = pressed, 1 = not pressed)
//
// De-bouncing:
// - scanning will happen in 5ms intervals and only if the two
// consecutive scans match, will the result be considered valid
//
// Configure GPIO for keyboard key scanning
//
// Switch to idle state
//
// In this state we can use keyscan_idle_is_pressed() to detect whether
// any key is pressed, and switch to active mode via keyscan_active().
//
static void keyscan_idle(void)
{
// enable output low on all columns (P9[7:5] P5[7:0] P8[0])
PAGESW = 0;
P5 = 0;
P8 &= 0xfe;
P9 &= 0x1f;
P0_P5M0 = 0x00;
P0_P8M0 &= 0xfeu;
PAGESW = 1;
P1_P9M0 &= 0x1fu;
// delay a bit for things to stabilize
delay_us(10);
p6_changed = 0;
scan_active = 0;
}
static uint8_t keyscan_idle_is_pressed(void)
{
return ~P6 & 0x3f;
}
//
// Switch to active mode.
//
// In this state, we can call keyscan_scan() to perform a scan.
//
static void keyscan_active(void)
{
// put all columns to hi-Z (P9[7:5] P5[7:0] P8[0])
PAGESW = 0;
P5 = 0;
P8 &= 0xfe;
P9 &= 0x1f;
// make all columns an input (hi-Z) in preparation for individual
// column scanning
P0_P5M0 = ~0x00u;
P0_P8M0 |= ~0xfeu;
PAGESW = 1;
P1_P9M0 |= ~0x1fu;
scan_active = 1;
}
// 12 byte storage required
static uint8_t keyscan_scan(uint8_t* res)
{
uint8_t pin, mask = 0, row;
// for each column:
// - output low on column
// - wait (for voltage to stabilize)
// - read rows
// - turn column back to hi-Z
PAGESW = 1;
for (pin = 5; pin <= 7; pin++) {
P1_P9M0 &= ~BIT(pin);
delay_us(3);
row = ~P6 & 0x3f;
mask |= row;
*res++ = row;
P1_P9M0 |= BIT(pin);
}
PAGESW = 0;
for (pin = 0; pin <= 7; pin++) {
P0_P5M0 &= ~BIT(pin);
delay_us(3);
row = ~P6 & 0x3f;
mask |= row;
*res++ = row;
P0_P5M0 |= BIT(pin);
}
P0_P8M0 &= ~BIT(0);
delay_us(3);
row = ~P6 & 0x3f;
mask |= row;
*res++ = row;
P0_P8M0 |= BIT(0);
return mask;
}
// }}}
// {{{ Enternal interrupt control
static void ext_int_assert(void)
{
// POGO INT pin
P90 = 0;
PAGESW = 1;
P1_P9M0 &= ~BIT(0);
}
static void ext_int_deassert(void)
{
// POGO INT pin
P90 = 0;
PAGESW = 1;
P1_P9M0 |= BIT(0);
}
// }}}
// {{{ CRC-8
static const uint8_t crc8_0x7_table[] = {
0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d,
0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,
0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,
0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd,
0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea,
0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,
0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a,
0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a,
0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,
0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4,
0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44,
0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,
0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,
0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63,
0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13,
0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,
0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,
0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3
};
static uint8_t crc8(const uint8_t *pdata, size_t nbytes)
{
unsigned int idx;
uint8_t crc = 0xff;
while (nbytes--) {
idx = crc ^ *pdata;
crc = crc8_0x7_table[idx];
pdata++;
}
return crc;
}
// }}}
// {{{ Public I2C register interface
#include "registers.h"
#define REG_SYS(n) ctl_regs[REG_SYS_##n - REG_SYS_CONFIG]
// all the variables are volatile because they can be accessed
// from interrupt context of I2C interrupt handler
static volatile uint8_t __idata ro_regs[REG_KEYMATRIX_STATE_END + 1] = {
[REG_DEVID_K] = 'K',
[REG_DEVID_B] = 'B',
[REG_FW_REVISION] = FW_REVISION,
[REG_FW_FEATURES] =
#if CONFIG_USB_STACK && CONFIG_DEBUG_LOG
REG_FW_FEATURES_USB_DEBUGGER |
#endif
#if CONFIG_FLASH_ENABLE
REG_FW_FEATURES_FLASHING_MODE |
#endif
#if CONFIG_SELFTEST
REG_FW_FEATURES_SELF_TEST |
#endif
#if CONFIG_STOCK_FW
REG_FW_FEATURES_STOCK_FW |
#endif
0,
[REG_KEYMATRIX_SIZE] = 0xc6, // 12 x 6
};
static volatile uint8_t ctl_regs[5] = {0, 0, 0, 0, 0};
static volatile __bit sys_cmd_run = 0;
static volatile uint8_t reg_addr = 0;
// }}}
// {{{ Flashing
static volatile uint8_t __code __at(0x3fff) app_flag;
#if CONFIG_FLASH_ENABLE
// all the variables are volatile because they can be accessed
// from interrupt context of I2C interrupt handler
static volatile uint8_t flash_regs[5] = {0, 0, 0, 0, 0};
// this is where the HW expects the data for a code ROM page
static volatile uint8_t __xdata __at(0x780) flash_content[128];
static volatile uint8_t __xdata __at(0x700) flash_content2[128];
// used to signal that flashing command should be executed and
// to block further writes to flashing registers via I2C
static volatile __bit flash_cmd_run = 0;
#define REG_FLASH(n) flash_regs[REG_FLASH_##n - REG_FLASH_ADDR_L]
static void user_app_flag_set(uint8_t flag) __critical
{
if (app_flag == flag)
return;
for (uint8_t i = 0; i < 128; i++)
flash_content2[i] = 0xff;
flash_content2[0x7fu] = flag;
PAGESW = 0;
uint8_t ckcon_saved = CKCON1;
CKCON1 = (CKCON1 & ~0x06u) | (1 << 1); // set HS pre-divider to /4
// unlock
P0_FLCR &= ~BIT(2);
P0_FLKEY = 0xA9;
P0_FLKEY = 0x7F;
__asm__ (
// dptr0 = source ptr, dptr1 = dest ptr
"push psw\n"
"push _DPL\n"
"push _DPH\n"
"push ar7\n"
"push A\n"
"mov _DPL1,#0x80\n"
"mov _DPH1,#0x3f\n"
"mov A,#_flash_content2\n"
"mov _DPL,A\n"
"mov A,#(_flash_content2 >> 8)\n"
"mov _DPH,A\n"
"mov r7,#0\n" // counter
"00002$: movx a,@dptr\n"
"inc dptr\n"
"orl _PCON,#0x08\n" // select dptr1
"movx @dptr,a\n"
"inc dptr\n"
"anl _PCON,#0xf7\n" // select dptr0
"inc r7\n"
"cjne r7,#0x80,00002$\n"
"pop A\n"
"pop ar7\n"
"pop _DPH\n"
"pop _DPL\n"
"pop psw\n"
);
P0_FLCR |= BIT(0) | BIT(1);
__asm__("nop");
while (P0_FLCR & (BIT(0) | BIT(1))); // wait until programming is done
CKCON1 = ckcon_saved;
}
static void exec_flashing_command(void)
{
if (!flash_cmd_run)
return;
// skip normal result codes
if (REG_FLASH(CMD) == 0 || REG_FLASH(CMD) == 0xff)
goto out;
// return error if the unlock magic is incorrect
if (REG_FLASH(UNLOCK) != REG_FLASH_UNLOCK_MAGIC)
goto err;
if (REG_FLASH(CMD) == REG_FLASH_CMD_READ_ROM) {
// does not need to be in critical section, because I2C access
// is prevented via flash_cmd_run
__asm__ (
// dptr0 = CODE ptr, dptr1 = XRAM ptr
"push psw\n"
"push _DPL\n"
"push _DPH\n"
"push ar7\n"
"push A\n"
"mov _DPL,_flash_regs\n"
"mov _DPH,(_flash_regs + 1)\n"
"mov A,#_flash_content\n"
"mov _DPL1,A\n"
"mov A,#(_flash_content >> 8)\n"
"mov _DPH1,A\n"
"mov r7,#0\n" // counter
"00001$: mov A,r7\n"
"movc a,@a+dptr\n"
"orl _PCON,#0x08\n" // select dptr1
"movx @dptr,a\n"
"inc dptr\n"
"anl _PCON,#0xf7\n" // select dptr0
"inc r7\n"
"cjne r7,#0x80,00001$\n"
"pop A\n"
"pop ar7\n"
"pop _DPH\n"
"pop _DPL\n"
"pop psw\n"
);
REG_FLASH(CRC8) = crc8(flash_content, 128);
} else if (REG_FLASH(CMD) == REG_FLASH_CMD_WRITE_ROM) {
// does not need to be in critical section, because I2C access
// is prevented via flash_cmd_run
if (REG_FLASH(CRC8) != crc8(flash_content, 128))
goto err;
if (REG_FLASH(ADDR_H) < 0x40 || REG_FLASH(ADDR_H) >= 0x80)
goto err;
if ((REG_FLASH(ADDR_L) % 128) != 0)
goto err;
user_app_flag_set(0xff);
// Burn the code, we need to disable interrupts during write
//
// 1) unlock by writing 0xa9 0x7f to FLKEY
// 2) set HS pre-divider to /4
// 3) FLCR |= BIT(2) (if writing options)
// 4) MOVX data to area starting from the ROM address we want to
// write
// 5) FLCR |= BIT(0) | BIT(1);
// 6) nop and wait for FLCR bits to clear
// 7) FLCR &= ~BIT(2), restore pre-divider (cleanup)
//
// FLCR:
// bit 0 = WE
// bit 1 = EE
// bit 2 = MEMSP
// bit 7 = EPEN (protection)
//
__critical {
PAGESW = 0;
uint8_t ckcon_saved = CKCON1;
CKCON1 = (CKCON1 & ~0x06u) | (1 << 1); // set HS pre-divider to /4
// unlock
P0_FLCR &= ~BIT(2);
P0_FLKEY = 0xA9;
P0_FLKEY = 0x7F;
__asm__ (
// dptr0 = source ptr, dptr1 = dest ptr
"push psw\n"
"push _DPL\n"
"push _DPH\n"
"push ar7\n"
"push A\n"
"mov _DPL1,_flash_regs\n"
"mov _DPH1,(_flash_regs + 1)\n"
"mov A,#_flash_content\n"
"mov _DPL,A\n"
"mov A,#(_flash_content >> 8)\n"
"mov _DPH,A\n"
"mov r7,#0\n" // counter
"00002$: movx a,@dptr\n"
"inc dptr\n"
"orl _PCON,#0x08\n" // select dptr1
"movx @dptr,a\n"
"inc dptr\n"
"anl _PCON,#0xf7\n" // select dptr0
"inc r7\n"
"cjne r7,#0x80,00002$\n"
"pop A\n"
"pop ar7\n"
"pop _DPH\n"
"pop _DPL\n"
"pop psw\n"
);
P0_FLCR |= BIT(0) | BIT(1);
__asm__("nop");
while (P0_FLCR & (BIT(0) | BIT(1))); // wait until programming is done
CKCON1 = ckcon_saved;
}
} else if (REG_FLASH(CMD) == REG_FLASH_CMD_COMMIT) {
user_app_flag_set(1);
} else if (REG_FLASH(CMD) == REG_FLASH_CMD_ERASE_ROM) {
user_app_flag_set(0xff);
} else {
goto err;
}
REG_FLASH(CMD) = 0;
out:
REG_FLASH(UNLOCK) = 0;
flash_cmd_run = 0;
return;
err:
REG_FLASH(CMD) = 0xff;
goto out;
}
#endif
// }}}
// {{{ Self-tests
#if CONFIG_SELFTEST
static void set_column_input(uint8_t col)
{
if (col <= 2)
PAGESW = 1;
else
PAGESW = 0;
switch (col) {
case 0: P1_P9M0 |= BIT(5); break;
case 1: P1_P9M0 |= BIT(6); break;
case 2: P1_P9M0 |= BIT(7); break;
case 3: P0_P5M0 |= BIT(0); break;
case 4: P0_P5M0 |= BIT(1); break;
case 5: P0_P5M0 |= BIT(2); break;
case 6: P0_P5M0 |= BIT(3); break;
case 7: P0_P5M0 |= BIT(4); break;
case 8: P0_P5M0 |= BIT(5); break;
case 9: P0_P5M0 |= BIT(6); break;
case 10: P0_P5M0 |= BIT(7); break;
case 11: P0_P8M0 |= BIT(0); break;
}
}
static void set_column_output(uint8_t col)
{
if (col <= 2)
PAGESW = 1;
else
PAGESW = 0;
switch (col) {
case 0: P1_P9M0 &= ~BIT(5); break;
case 1: P1_P9M0 &= ~BIT(6); break;
case 2: P1_P9M0 &= ~BIT(7); break;
case 3: P0_P5M0 &= ~BIT(0); break;
case 4: P0_P5M0 &= ~BIT(1); break;
case 5: P0_P5M0 &= ~BIT(2); break;
case 6: P0_P5M0 &= ~BIT(3); break;
case 7: P0_P5M0 &= ~BIT(4); break;
case 8: P0_P5M0 &= ~BIT(5); break;
case 9: P0_P5M0 &= ~BIT(6); break;
case 10: P0_P5M0 &= ~BIT(7); break;
case 11: P0_P8M0 &= ~BIT(0); break;
}
}
static __bit get_column_value(uint8_t col)
{
switch (col) {
case 0: return P95;
case 1: return P96;
case 2: return P97;
case 3: return P50;
case 4: return P51;
case 5: return P52;
case 6: return P53;
case 7: return P54;
case 8: return P55;
case 9: return P56;
case 10: return P57;
case 11: return P80;
default: return 0;
}
}
static void set_column_value(uint8_t col, __bit val)
{
switch (col) {
case 0: P95 = val; break;
case 1: P96 = val; break;
case 2: P97 = val; break;
case 3: P50 = val; break;
case 4: P51 = val; break;
case 5: P52 = val; break;
case 6: P53 = val; break;
case 7: P54 = val; break;
case 8: P55 = val; break;
case 9: P56 = val; break;
case 10: P57 = val; break;
case 11: P80 = val; break;
}
}
static void self_test_run(void)
{
PAGESW = 0;
// all rows pull-up already as a defauklt config, so set all columns
// to hi-Z first
P0_P5M0 = ~0x00u;
P0_P8M0 |= ~0xfeu;
PAGESW = 1;
P1_P9M0 |= ~0x1fu;
// for each column:
// - output low
// - read other columns
// - turn column back to hi-Z
// data output:
// - list of columns shorted together in 2 byte sequences as a bitmask
// - first byte: cols 12-9 aligned to LSB
// - second byte: cols 8-1
// - terminated by two-byte 00 00 sequence
puts("column self-test:\n");
for (uint8_t c1 = 0; c1 < 12; c1++) {
set_column_output(c1);
for (uint8_t c2 = c1 + 1; c2 < 12; c2++) {
set_column_value(c1, 0);
__bit a = get_column_value(c2);
set_column_value(c1, 1);
__bit b = get_column_value(c2);
if (!a && b) {
// column-column short found
puts("c-c short: ");
put_uint(c1);
puts(" ");
put_uint(c2);
puts("\n");
}
}
set_column_input(c1);
}
puts("done\n");
}
#endif
// }}}
// {{{ Charger I2C bitbanging
#define CHARGER_ADDR 0x75u
// Inspired by: https://calcium3000.wordpress.com/2016/08/19/i2c-bit-banging-tutorial-part-i/
// TODO: recognize clock-stretching (probably not necessary, charger doesn't use it)
#define CHG_SCL BIT(5)
#define CHG_SDA BIT(6)
#define CHG_INT BIT(7)
#define CHG_PORT P8
#define CHG_RELEASE_SCL P0_P8M0 |= CHG_SCL
#define CHG_PULL_SCL P0_P8M0 &= ~CHG_SCL
#define CHG_RELEASE_SDA P0_P8M0 |= CHG_SDA
#define CHG_PULL_SDA P0_P8M0 &= ~CHG_SDA
void i2c_init(void)
{
PAGESW = 1;
P1_PHCON2 |= BIT(3);
PAGESW = 0;
P0_P8M0 |= CHG_SCL | CHG_SDA | CHG_INT; // set SCL/SDA to input (release I2C bus)
P8 &= ~(CHG_SCL | CHG_SDA); // set SCL/SDA to output low when changed to output mode
}
/*
// returns 1 on busy/timeout, abort needed because the bus is locked
static __bit poll_scl_busy(void)
{
// timeout for clock stretching by the slave
T0_SET_TIMEOUT(2 * 250); // 250us
while (!TF0)
if (CHG_PORT & CHG_SCL)
return 0;
return 1;
}
*/
static void i2c_start_condition(void)
{
CHG_RELEASE_SDA;
delay_us(5);
CHG_RELEASE_SCL;
delay_us(5);
CHG_PULL_SDA;
delay_us(5);
CHG_PULL_SCL;
delay_us(5);
}
static void i2c_stop_condition(void)
{
CHG_PULL_SDA;
delay_us(5);
CHG_RELEASE_SCL;
delay_us(5);
CHG_RELEASE_SDA;
delay_us(5);
}
static __bit i2c_write_byte(uint8_t data)
{
uint8_t i;
__bit ack = 1;
// write data
for (i = 0; i < 8; i++) {
// write the most-significant bit
if (data & 0x80)
CHG_RELEASE_SDA;
else
CHG_PULL_SDA;
delay_us(1);
CHG_RELEASE_SCL;
delay_us(5);
CHG_PULL_SCL;
delay_us(1);
data <<= 1;
}
delay_us(1);
// read ack bit
CHG_RELEASE_SDA;
CHG_RELEASE_SCL;
delay_us(3);
if (CHG_PORT & CHG_SDA)
ack = 0;
CHG_PULL_SCL;
delay_us(2);
return ack;
}
static uint8_t i2c_read_byte(__bit ack)
{
uint8_t data = 0;
uint8_t i;
CHG_RELEASE_SDA;
// read data
for (i = 0; i < 8; i++) {
data <<= 1;
CHG_RELEASE_SCL;
delay_us(4);
if (CHG_PORT & CHG_SDA)
data |= 1;
CHG_PULL_SCL;
delay_us(2);
}
// send ack
if (ack)
CHG_PULL_SDA;
else
CHG_RELEASE_SDA;
delay_us(1);
CHG_RELEASE_SCL;
delay_us(5);
CHG_PULL_SCL;
delay_us(1);
return data;
}
__bit i2c_write_reg(uint8_t reg, uint8_t data)
{
__bit ok = 0;
PAGESW = 0;
i2c_start_condition();
if (!i2c_write_byte(CHARGER_ADDR << 1))
goto stop;
if (!i2c_write_byte(reg))
goto stop;
if (!i2c_write_byte(data))
goto stop;
ok = 1;
stop:
i2c_stop_condition();
return ok;
}
__bit i2c_read_reg(uint8_t reg, uint8_t* data)
{
__bit ok = 0;
PAGESW = 0;
i2c_start_condition();
if (!i2c_write_byte(CHARGER_ADDR << 1))
goto stop;
if (!i2c_write_byte(reg))
goto stop;
// repeated start
i2c_start_condition();
if (!i2c_write_byte((CHARGER_ADDR << 1) | 0x01))
goto stop;
*data = i2c_read_byte(0);
ok = 1;
stop:
i2c_stop_condition();
return ok;
}
static __bit charger_is_woke(void)
{
return P87;
}
static uint8_t charger_read(void)
{
if (!charger_is_woke())
return 0xff;
if (!i2c_read_reg(REG_SYS(CHG_ADDR), &REG_SYS(CHG_DATA)))
return 0xff;
return 0;
}
static uint8_t charger_write(void)
{
if (!charger_is_woke())
return 0xff;
if (!i2c_write_reg(REG_SYS(CHG_ADDR), REG_SYS(CHG_DATA)))
return 0xff;
return 0;
}
static void charger_init(void)
{
i2c_init();
}
// }}}
// {{{ System commands
static void exec_system_command(void)
{
if (!sys_cmd_run)
return;
if (REG_SYS(COMMAND) == 0 || REG_SYS(COMMAND) == 0xff)
goto out_done;
if (REG_SYS(COMMAND) == REG_SYS_COMMAND_MCU_RESET) {
RSTSC &= ~BIT(7);
RSTSC |= BIT(7);
#if CONFIG_SELFTEST
} else if (REG_SYS(COMMAND) == REG_SYS_COMMAND_SELFTEST) {
self_test_run();
#endif
} else if (REG_SYS(COMMAND) == REG_SYS_COMMAND_USB_IAP) {
jump_to_usb_bootloader = 1;
} else if (REG_SYS(COMMAND) == REG_SYS_COMMAND_CHG_READ) {
REG_SYS(COMMAND) = charger_read();
goto out_done;
} else if (REG_SYS(COMMAND) == REG_SYS_COMMAND_CHG_WRITE) {
REG_SYS(COMMAND) = charger_write();
goto out_done;
} else {
REG_SYS(COMMAND) = 0xff;
goto out_done;
}
REG_SYS(COMMAND) = 0x00;
out_done:
sys_cmd_run = 0;
}
// }}}
// {{{ I2C register access
// only call this in interrupt context from regbank 1!
static uint8_t reg_get_value(void) __using(1)
{
#if CONFIG_DEBUG_LOG
// read from this register reads the next byte of log buffer
// (register address is not advanced!)
if (reg_addr == 0xff) {
if (log_start != log_end) {
log_start = (log_start + 1) % 1024;
return log_buffer[log_start]; // push data to fifo
}
goto none;
}
#endif
if (reg_addr <= REG_KEYMATRIX_STATE_END) {
return ro_regs[reg_addr];
} else if (reg_addr < REG_SYS_CONFIG) {
goto none;
} else if (reg_addr <= REG_SYS_USER_APP_BLOCK) {
return ctl_regs[reg_addr - REG_SYS_CONFIG];
#if CONFIG_FLASH_ENABLE
} else if (reg_addr < REG_FLASH_DATA_START) {
goto none;
} else if (reg_addr <= REG_FLASH_DATA_END) {
return flash_content[reg_addr - REG_FLASH_DATA_START];
} else if (reg_addr <= REG_FLASH_CMD) {
return flash_regs[reg_addr - REG_FLASH_ADDR_L];
#endif
}
none:
return 0;
}
// only call this in interrupt context from regbank 1!
static void reg_set_value(uint8_t val) __using(1)
{
if (reg_addr < REG_SYS_CONFIG) {
return;
} else if (reg_addr <= REG_SYS_USER_APP_BLOCK) {
if (reg_addr == REG_SYS_COMMAND) {
if (sys_cmd_run)
return;
sys_cmd_run = 1;
}
ctl_regs[reg_addr - REG_SYS_CONFIG] = val;
#if CONFIG_FLASH_ENABLE
} else if (reg_addr < REG_FLASH_DATA_START) {
return;
} else if (reg_addr <= REG_FLASH_DATA_END) {
if (flash_cmd_run)
return;
flash_content[reg_addr - REG_FLASH_DATA_START] = val;
} else if (reg_addr <= REG_FLASH_CMD) {
if (flash_cmd_run)
return;
flash_regs[reg_addr - REG_FLASH_ADDR_L] = val;
if (reg_addr == REG_FLASH_CMD)
flash_cmd_run = 1;
#endif
}
}
/*
* Host write transaction: sending 01 02 03 04 to device at 0x15 (0x2a == 0x15 << 1)
*
* int=60 CR1=8c CR2=af DATA_PRE=2a rx
* int=60 CR1=8e CR2=af DATA_PRE=01 rx
* int=60 CR1=8e CR2=af DATA_PRE=02 rx
* int=60 CR1=8e CR2=af DATA_PRE=03 rx
* int=60 CR1=8e CR2=af DATA_PRE=04 rx
* int=70 CR1=0c CR2=2f DATA_PRE=04 stop
*
* Host read transaction: receiving 4 bytes
*
* int=a0 CR1=8d CR2=af tx
* int=a0 CR1=8d CR2=af tx
* int=a0 CR1=8d CR2=af tx
* int=a0 CR1=89 CR2=af tx NACK from host (last read byte)
* int=b0 CR1=08 CR2=2f stop STOP condition reported
*
* CR1:
* 7: STROBE/PEND (RX/TX: not set on stop IRQ, even though RXSF/TXSF is also set)
* 3: SAR_EMPTY (RX/TX: always set)
* 2: ACK (RX: always set)
* (TX: set on all except on the last TX byte)
* 1: FULL (RX: not set on first RX byte, which is a device address)
* (TX: always not set)
* 0: EMPTY (RX: always not set)
* (TX: alwyas set except after stop IRQ)
*
* CR2:
* 7: I2C busy flag (RX/TX: not set after stop IRQ)
* 6: ?
* 5: SW_RESET
* 4: BBF
*
* I2CBINT:
* 7: TXSF
* 6: RXSF
* 5: STP_IEN
* 4: STOPF
*
* Powerdown is only possible after the stop bit. Wakeup only happens
* on address match.
*/
#define I2C_ADDR 0x15
static volatile uint8_t i2c_n = 0;
// interrupt needs to be enabled for wakeup from powerdown to work
void i2c_b_interrupt(void) __interrupt(IRQ_I2CB) __using(1)
{
uint8_t saved_page = PAGESW;
PAGESW = 0;
uint8_t intf = P0_I2CBINT;
uint8_t cr1 = P0_I2CBCR1;
// handle stop condition
if (intf & BIT(4)) {
i2c_n = 0;
P0_I2CBINT &= ~(BIT(4) | BIT(6) | BIT(7));
goto out_restore_page;
}
// handle TX (byte to be sent to master - this is timing sensitive!)
if (intf & BIT(7)) {
// previous TX was the last byte
if (!(cr1 & BIT(2)))
goto tx_ack;
P0_I2CBDB = reg_get_value();
if (reg_addr != 0xff)
reg_addr++;
i2c_n++;
tx_ack:
P0_I2CBINT &= ~BIT(7);
goto out_restore_page;
}
// handle RX (byte received from master)
if (intf & BIT(6)) {
uint8_t tmp = P0_I2CBDB;
// first RX byte is device address, determined by !FULL flag
if (!(cr1 & BIT(1)))
goto rx_ack;
// set address
if (i2c_n++ == 0) {
reg_addr = tmp;
goto rx_ack;
}
// set reg data
reg_set_value(tmp);
reg_addr++;
rx_ack:
P0_I2CBINT &= ~BIT(6);
goto out_restore_page;
}
out_restore_page:
P0_I2CBCR1 &= ~BIT(7); // clear data pending
PAGESW = saved_page;
}
//
// Slave mode I2C for communication with the SoC
//
// - address is 0x15
// - 400kHz
// - interrupts are used to handle tx/rx/end of transaction (stop bit)
//
void i2c_slave_init(void)
{
PAGESW = 0;
// setup I2C B for slave mode
//P0_I2CBCR1 = 0x20;
//P0_I2CBCR2 = 0x07 << 1 | 0x01; // 400kHz mode, enable I2C B controller, enable
P0_I2CBCR1 = 0x00;
P0_I2CBCR2 = 0x07 << 1 | BIT(0); // 100kHz mode, enable I2C B controller, enable
// setup I2C address
P0_I2CBDAH = 0;
P0_I2CBDAL = I2C_ADDR;
P0_I2CBINT = BIT(5); // enable I2C B stop interrupt
P0_EIE3 |= BIT(5); // enable I2C B interrupt
}
// }}}
// {{{ USB debugging interface
#if CONFIG_USB_STACK
#if USB_DEBUG
#define usb_putc(a) putc(a)
#define usb_puts(a) puts(a)
#define usb_put_hex_b(a) put_hex_b(a)
#define usb_put_hex_w(a) put_hex_w(a)
#define usb_put_hex_n(a) put_hex_n(a)
#define usb_put_uint(a) put_uint(a)
#else
#define usb_putc(a)
#define usb_puts(a)
#define usb_put_hex_b(a)
#define usb_put_hex_w(a)
#define usb_put_hex_n(a)
#define usb_put_uint(a)
#endif
#define USB_ID(w) (uint16_t)w & 0xff, ((uint16_t)w >> 8)
#define USB_BCD(a, b) b, a
static const uint8_t usb_desc_device[] ={
18, // bLength
1, // bDescriptorType
USB_BCD(0x2, 0x0), // bcdUSB
0xff, // bDeviceClass
0, // bDeviceSubClass
0xff, // bDeviceProtocol
64, // bMaxPacketSize0
USB_ID(0x04f3), // idVendor
USB_ID(0xb001), // idProduct
USB_BCD(0x1, 0x0), // bcdDevice
1, // iManfacturer
2, // iProduct
0, // iSerialNumber
1, // bNumConfgurations
};
#define USB_EP_OUT(addr, attr, maxsize, interval) \
7, 5, addr, attr, USB_ID(maxsize), interval
#define USB_EP_IN(addr, attr, maxsize, interval) \
USB_EP_OUT((addr) | 0x80, attr, maxsize, interval)
static const uint8_t usb_desc_config[] = {
9, // bLength
2, // bDescriptorType
USB_ID(sizeof(usb_desc_config)),// bTotolLength
1, // bNumInterfaces
1, // bConfigurationValue
0, // iConfiguration string index
BIT(7) // must be set // bmAttributes
| BIT(6) // self power
| BIT(5) // remote wakeup
,
100, // bMaxPower
// Interface 0
9, // bLength
4, // bDescriptorType
0, // bInterfaceNumber
0, // bAlternateSetting
4, // bNumEndpoints
0xff, // bInterfaceClass
0, // bInterfaceSubClass
0xff, // bInterfaceProtocol
0, // iInterface
USB_EP_OUT(1, 3, 64, 1), // request
USB_EP_IN(2, 3, 64, 1), // response
USB_EP_IN(3, 3, 64, 1), // debug logging output
USB_EP_IN(4, 3, 64, 1), // key status changes
};
static const uint8_t usb_string_lang[] = {
4, 3,
USB_ID(0x0409),
};
static const uint8_t usb_string_manufacturer[] = {
4 * 2 + 2,
3,
'm', 0,
'e', 0,
'g', 0,
'i', 0,
};
static const uint8_t usb_string_product[] = {
5 * 2 + 2,
3,
'd', 0,
'e', 0,
'b', 0,
'u', 0,
'g', 0,
};
static const uint8_t * const usb_strings[] = {
usb_string_lang,
usb_string_manufacturer,
usb_string_product,
};
static uint16_t usb_ep0_in_remaining = 0;
static uint8_t const* usb_ep0_in_ptr;
static uint8_t usb_command_status = 0;
static uint8_t usb_command[8];
static uint8_t usb_response[8];
static volatile __bit usb_key_change = 0;
static void usb_tasks(void) __using(1)
{
uint8_t buf[8];
uint8_t saved_page = PAGESW;
PAGESW = 1;
// handle reset request
if (P1_UDCINT0STA & BIT(5)) {
P1_USBCTRL |= BIT(5);
P1_USBCTRL &= ~BIT(5);
// clear EP0-3 buffers
P1_UDCEPBUF0CTRL |= 0x55u;
P1_UDCEPBUF0CTRL &= ~0x55u;
// clear EP4
P1_UDCEPBUF1CTRL |= BIT(0);
P1_UDCEPBUF1CTRL &= ~BIT(0);
// clear EP0 / EP1 in buffers
P1_UDCBUFSTA &= ~(BIT(0) | BIT(1));
//XXX: what about others?
//XXX: reset software variables...
usb_puts("usb rst\n");
// ack reset request
P1_UDCINT0STA &= ~BIT(5);
}
// ep0 setup request received
if (P1_UDCINT0STA & BIT(1)) {
usb_puts("ep0 su: ");
// buf: bReqType bReq wVal(l/h) wIndex wLength
for (uint8_t i = 0; i < 8; i++) {
buf[i] = P1_UDCEP0BUFDATA;
usb_put_hex_b(buf[i]);
}
usb_puts("\n");
//P1_UDCEPBUF0CTRL |= BIT(0);
//P1_UDCEPBUF0CTRL &= ~BIT(0);
// how much data to send to ep0 in
usb_ep0_in_remaining = (((uint16_t)buf[7] << 8) | buf[6]);
uint16_t in0_len = 0;
// standard commands
if (buf[0] == 0x80) {
// GET_DESCRIPTOR
if (buf[1] == 0x06) {
if (buf[3] == 1) {
// device desc: 80 06 00 01 00 00
if (buf[2] == 0) {
usb_ep0_in_ptr = usb_desc_device;
in0_len = sizeof(usb_desc_device);
goto ack_ep0_setup;
}
} else if (buf[3] == 2) {
// cfg desc: 80 06 00 02 00 00
if (buf[2] == 0) {
usb_ep0_in_ptr = usb_desc_config;
in0_len = sizeof(usb_desc_config);
goto ack_ep0_setup;
}
} else if (buf[3] == 3) {
// string desc: 80 06 str_index 03 00 00
if (buf[2] < sizeof(usb_strings) / sizeof(usb_strings[0])) {
usb_ep0_in_ptr = usb_strings[buf[2]];
in0_len = usb_ep0_in_ptr[0];
goto ack_ep0_setup;
}
}
}
}
usb_ep0_in_remaining = 0;
P1_UDCCTRL |= BIT(4); // stall control endpoint req
ack_ep0_setup:
if (in0_len < usb_ep0_in_remaining)
usb_ep0_in_remaining = in0_len;
// ack
P1_UDCINT0STA &= ~BIT(1);
}
// USB host initiated EP0 IN transfer
if (P1_UDCINT1STA & BIT(0)) {
// ack interrupt
P1_UDCINT1STA &= ~BIT(0);
// check if we're ready to send to ep0
if (!(P1_UDCEPBUF0CTRL & BIT(1)) && (P1_UDCBUFSTA & BIT(0))) {
// if ep0 in buffer not empty, clear it first
//if (!(P1_UDCBUFSTA & BIT(0))) {
// clear ep0 buffer
//P1_UDCEPBUF0CTRL |= BIT(0);
//P1_UDCEPBUF0CTRL &= ~BIT(0);
//}
usb_puts("ep0 in: ptr=");
usb_put_hex_w((uint16_t)usb_ep0_in_ptr);
usb_puts(" rem=");
usb_put_hex_w(usb_ep0_in_remaining);
usb_puts(" ");
for (uint8_t n = 0; n < 64; n++) {
// push data to EP0 in (max 64 bytes)
if (usb_ep0_in_remaining > 0) {
usb_ep0_in_remaining--;
usb_put_hex_b(*usb_ep0_in_ptr);
P1_UDCEP0BUFDATA = *usb_ep0_in_ptr++;
} else {
break;
}
}
usb_puts("\n");
// confirm sending data
P1_UDCEPBUF0CTRL |= BIT(1);
}
}
// data received on ep0 out
if (P1_UDCINT1STA & BIT(1)) {
usb_puts("ep0 out\n");
// we don't handle any control transfers that send us data
// reset ep0 buf
P1_UDCEPBUF0CTRL |= BIT(0);
P1_UDCEPBUF0CTRL &= ~BIT(0);
// ack interrupt
P1_UDCINT1STA &= ~BIT(1);
}
// does not happen, EP1 IN is not configured on host
if (P1_UDCINT1STA & BIT(2)) {
P1_UDCINT1STA &= ~BIT(2);
}
// data received on ep1 out (command endpoint)
if (P1_UDCINT1STA & BIT(3)) {
// read data from ep1 fifo
uint8_t bytes = P1_UDCEP1DATAOUTCNT + 1;
for (uint8_t i = 0; i < 8; i++)
usb_command[i] = P1_UDCEP1BUFDATA;
usb_command_status = 1;
usb_puts("ep1 out\n");
P1_UDCINT1STA &= ~BIT(3);
// clear the rest
P1_UDCEPBUF0CTRL |= BIT(2);
P1_UDCEPBUF0CTRL &= ~BIT(2);
//do {
//P1_USBCTRL |= BIT(6);
//} while(!(P1_USBCTRL & BIT(6)));
}
// process USB commands
if (usb_command_status == 1) {
// what command the response is for
usb_response[0] = usb_command[0];
// success = 0, error code otherwise
usb_response[1] = 0x00;
if (usb_command[0] == 0x01) {
// bootloader mode
jump_to_usb_bootloader = 1;
} else {
// command unknown
usb_response[1] = 1;
}
usb_command_status = 2;
}
// USB host initiated EP2 IN transfer
if (P1_UDCINT1STA & BIT(4)) {
// send out response to last command on ep2 in
if (usb_command_status == 2 && !(P1_UDCEPBUF0CTRL & BIT(5))) {
P1_UDCEP2DATAINCNT = 8 - 1; // how much bytes to send
for (uint8_t i = 0; i < 8; i++)
P1_UDCEP2BUFDATA = usb_response[i];
P1_UDCEPBUF0CTRL |= BIT(5); // EP2 data ready
usb_command_status = 0;
}
// ack
P1_UDCINT1STA &= ~BIT(4);
}
// USB host initiated EP3 IN transfer
if (P1_UDCINT1STA & BIT(6)) {
#if CONFIG_DEBUG_LOG
// all log_* variables need to be accessed with interrupts
// disabled
__critical {
// push printf debug buffer to ep3 in
if (!(P1_UDCEPBUF0CTRL & BIT(7)) && log_start != log_end) {
uint8_t cnt = 0;
while (cnt < 64 && log_start != log_end) {
log_start = (log_start + 1) % 1024;
P1_UDCEP3BUFDATA = log_buffer[log_start]; // push data to fifo
cnt++;
}
P1_UDCEP3DATAINCNT = cnt - 1;
P1_UDCEPBUF0CTRL |= BIT(7); // EP3 data ready
}
}
#endif
// ack
P1_UDCINT1STA &= ~BIT(6);
}
// USB host initiated EP4 IN transfer
if (P1_UDCINT2STA & BIT(2)) {
// push key change events to ep4 in
if (!(P1_UDCEPBUF1CTRL & BIT(1)) && usb_key_change) {
for (uint8_t i = 0; i < 12; i++)
P1_UDCEP4BUFDATA = ro_regs[i + REG_KEYMATRIX_STATE];
P1_UDCEP4DATAINCNT = 12 - 1;
P1_UDCEPBUF1CTRL |= BIT(1); // EP4 data ready
usb_key_change = 0;
}
// ack
P1_UDCINT2STA &= ~BIT(2);
}
// suspend request
if (P1_UDCINT0STA & BIT(6)) {
usb_puts("usb suspend\n");
// host requests suspend, we satisfy it
// clear device resume request bit, we can set it later to wake
// the host / resume USB activity
P1_UDCCTRL &= ~BIT(5);
// ack
P1_UDCINT0STA &= ~BIT(6);
}
// resume request
if (P1_UDCINT0STA & BIT(3)) {
usb_puts("usb resume\n");
// ack
P1_UDCINT0STA &= ~BIT(3);
}
PAGESW = saved_page;
}
void usb_interrupt(void) __interrupt(IRQ_USB) __using(1)
{
usb_tasks();
}
enum {
UDC_EP_CONTROL = 0,
UDC_EP_ISO,
UDC_EP_BULK,
UDC_EP_INTERRUPT,
};
#define UDC_EP_CONF(conf, intf, alt, type) \
(conf << 6) | (intf << 4) | (alt << 2) | type
#define UDC_EP_OUT_CONF(ep1, ep2, ep3, ep4) \
ep4 | (ep3 << 2) | (ep2 << 4) | (ep1 << 6)
static const uint8_t udc_config[5] = {
UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT),
UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT),
UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT),
UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT),
UDC_EP_OUT_CONF(UDC_EP_INTERRUPT, UDC_EP_INTERRUPT, UDC_EP_INTERRUPT, UDC_EP_INTERRUPT),
};
static void usb_init(void)
{
PAGESW = 1;
P1_UDCCTRL |= BIT(6); // udc enable
// wait for UDC to complete initialization
while (!(P1_UDCCTRL & BIT(1)));
__asm__("nop");
// setup USB EP depths
P1_UDCEP1BUFDEPTH = 64 - 1;
P1_UDCEP2BUFDEPTH = 64 - 1;
P1_UDCEP3BUFDEPTH = 64 - 1;
P1_UDCEP4BUFDEPTH = 64 - 1;
__asm__("nop");
__asm__("nop");
// configure UDC
for (uint8_t i = 0; i < 4; i++) {
P1_UDCCFDATA = udc_config[i];
while (!(P1_UDCCFSTA & BIT(7)));
while (P1_UDCCFSTA & BIT(7));
}
P1_UDCCFDATA = udc_config[4];
while (!(P1_UDCCFSTA & BIT(6)));
// enable USB EPRDY
P1_USBCTRL |= BIT(6);
P1_UDCEPCTRL = 0xf;
P1_UDCINT0STA = 0;
P1_UDCINT1STA = 0;
P1_UDCINT2STA = 0;
P1_UDCINT0EN = BIT(5) | BIT(1) | BIT(6) | BIT(3);
P1_UDCINT1EN = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(6);
P1_UDCINT2EN = BIT(2);
//P1_UDCINT0EN = 0;
//P1_UDCINT1EN = 0;
//P1_UDCINT2EN = 0;
// enable phy, wakeup enable
P1_PHYTEST0 |= BIT(5) | BIT(6);
__asm__("nop");
__asm__("nop");
PAGESW = 0;
// enable USB interrupts
P0_EIE2 |= BIT(2);
}
#endif
static void usb_disable(void)
{
// reset phy/usb
PAGESW = 1;
P1_PHYTEST0 &= ~(BIT(6) | BIT(5)); // phy disable
P1_UDCCTRL &= ~BIT(6); // udc disable
}
// }}}
extern uint8_t _start__stack[];
void main(void)
{
static uint8_t keys[12];
SP = (uint8_t)_start__stack;
PAGESW = 0;
// setup interrupts
EA = 0;
IE = 0;
P0_EIE1 = 0;
P0_EIE2 = 0;
P0_EIE3 = 0;
// set CPU clock to normal (high frequency) mode
// [7] = power down HS clock in low speed mode - 1: yes 0: no
// [2:1] = high speed clock pre-divider - 1: /4 2: /2 3: /1
// [0] = cpu clock mode 1: high speed mode 0: low speed mode
CKCON1 = (CKCON1 & ~0x87u) | 0x07; // 0x87
// set timer 1 and timer 0 clock source to sysclk/12 (2 MHz)
CKCON0 = 0x00;
// wait until high speed clock is stable
while (!(CKCON0 & BIT(1)));
// set both timers to 16-bit counter modes
TMOD = 0x11;
// enable both timers
TCON = 0x50;
// protect FLASH from 0x0000 to 0x4000 from being accessed by code at 0x4000+
P0_EPPOINTL = 0x80;
// setup watchdog (timer base is 8ms, prescaler sets up timeout /128 = ~1s)
// P0_WDTCR = 0x87; // enable watchdog ~1s
// P0_WDTKEY = 0x4e; // reset watchdog
P0_WDTCR = 0x07; // disable watchdog ~1s
P0_WDTKEY = 0xb1; // disable watchdog
// power down unused peripherlas
P0_DEVPD1 |= BIT(6) | BIT(5) | BIT(3) | BIT(1); // PWM A, timer 3, SPI, LVD
P0_DEVPD2 |= BIT(6) | BIT(3) | BIT(0); // PWM C, PWM B, I2C A
P0_DEVPD3 |= BIT(2) | BIT(1) | BIT(0); // PWM E, PWM D, PWM F
// keep UART, SPI, and I2C A in reset
//P0_PRST |= BIT(0) | BIT(2) | BIT(3);
// enable pullups only all port 6 pins and make those pins into input
PAGESW = 0;
P0_PHCON0 = 0x00;
P0_PHCON1 = 0xffu; // port 6 pull-up enable
P0_P6M0 = 0xff; // port 6 input
PAGESW = 1;
P1_PHCON2 = 0x00;
// enable auto-tuning internal RC oscillator based on USB SOF packets
P1_IRCCTRL &= ~BIT(1); // disable manual trim
#if CONFIG_STOCK_FW
puts("ppkb firmware " FW_REVISION_STR " (stock)\n");
#else
puts("ppkb firmware " FW_REVISION_STR " (user)\n");
#endif
charger_init();
i2c_slave_init();
T1_SET_TIMEOUT(40000);
usb_disable();
#if !CONFIG_USB_STACK
PAGESW = 1;
// GPIO on USB pins
P1_USBCTRL &= ~BIT(7);
// turn off PLL48
P1_UDCCTRL |= BIT(0);
// turn off unused USB resources (phy power down, PLL48 powerdown
P1_USBCTRL |= BIT(0) | BIT(1);
// enable auto-tuning internal RC oscillator based on USB SOF packets
P1_IRCCTRL |= BIT(1); // enable manual trim
#endif
#if CONFIG_FLASH_ENABLE
for (uint8_t i = 0; i < 128; i++)
flash_content[i] = 0;
#endif
// enable interrupts
ET1 = 1;
EA = 1;
ext_int_deassert();
keyscan_idle();
__bit usb_initialized = 0;
__bit user_app_checked = 0;
uint16_t ticks = 0;
while (1) {
// execute I2C system/flashing commands, once the I2C
// transaction ends, as soon as possible
if (i2c_n == 0) {
exec_system_command();
#if CONFIG_FLASH_ENABLE
exec_flashing_command();
#endif
}
// if we were asked to jump to USB IAP, do it
if (jump_to_usb_bootloader)
__asm__ ("ljmp _usb_bootloader_jump");
// get current system config
uint8_t cfg = REG_SYS(CONFIG);
// if the 20ms timer did not expire yet, check if we can
// powerdown, otherwise busyloop
if (!run_timed_tasks) {
#if CONFIG_USB_STACK
PAGESW = 1;
__bit usb_suspended = !!(P1_UDCCTRL & BIT(2));
#endif
PAGESW = 0;
__bit i2c_idle = !(P0_I2CBCR2 & BIT(7)) && i2c_n == 0;
// if USB is suspended by host and I2C has no activity,
// and we're not in active scanning mode, power down the MCU
if (i2c_idle && !scan_active
&& !p6_changed
#if CONFIG_USB_STACK
&& usb_initialized && usb_suspended
#endif
#if CONFIG_STOCK_FW
&& user_app_checked
#endif
) {
// go to idle CPU mode when there's nothing to
// do, any interrupt will wake us
//PCON |= BIT(0);
// enable interrupt whenever P6 is different
// from the current value (which would be
// whenever some key is pressed, because by
// default all pins on P6 are pulled high)
//
// input change detection works by comparing the
// pin state against the P6 latch for output
p6_changed = 0;
P6 = P6;
P0_ICEN = BIT(5);
ICIE = 1;
// power down (timers don't work in power-down)
PCON |= BIT(1) | BIT(0);
__asm__("nop");
// we may not be woken up only by IC interrupt, so
// disable IC interrupts after each wakeup
ICIE = 0;
#if CONFIG_USB_STACK
// if we were woken up by USB host, USBCTRL.5
// will be set, clear it
PAGESW = 1;
if (!(P1_UDCCTRL & BIT(2)))
P1_USBCTRL &= ~BIT(5);
#endif
}
continue;
}
// every 20ms we will get here to perform some timed tasks
ticks++;
run_timed_tasks = 0;
#if CONFIG_STOCK_FW
// after 300ms check if we should jump to user firmware
if (!user_app_checked && ticks > 400 / 20) {
if (app_flag == 1 && REG_SYS(USER_APP_BLOCK) != REG_SYS_USER_APP_BLOCK_MAGIC)
jmp_to_user_fw();
user_app_checked = 1;
}
#endif
#if CONFIG_USB_STACK
// after 500ms, init usb
if (!usb_initialized && ticks > 500 / 20) {
usb_init();
usb_initialized = 1;
}
#endif
// do nothing if scanning is blocked
if (cfg & REG_SYS_CONFIG_SCAN_BLOCK) {
if (scan_active)
keyscan_idle();
continue;
}
// if active scanning is not active and port 6 change was
// detected, and some key is still pressed, enter active
// scanning mode
if (!scan_active && keyscan_idle_is_pressed())
keyscan_active();
// if we're in active scanning, scan the keys, and report
// new state
if (scan_active) {
uint8_t active_rows = keyscan_scan(keys);
// check for changes
if (memcmp(ro_regs + REG_KEYMATRIX_STATE, keys, 12)) {
// update regs
__critical {
memcpy(ro_regs + REG_KEYMATRIX_STATE, keys, 12);
ro_regs[REG_KEYMATRIX_STATE_CRC8] = crc8(ro_regs + REG_KEYMATRIX_STATE, 12);
}
// signal interrupt
ext_int_assert();
delay_us(100);
ext_int_deassert();
#if CONFIG_USB_STACK
usb_key_change = 1;
// USB wakeup
PAGESW = 1;
if (P1_UDCCTRL & BIT(2)) {
P1_UDCCTRL |= BIT(5);
P1_UDCCTRL &= ~BIT(5);
}
#endif
// Check for Pine + F + # being held during powerup
if ((keys[0] & BIT(2)) && (keys[4] & BIT(2))) {
// H - stay in main firmware
if (keys[6] & BIT(2)) {
#if CONFIG_STOCK_FW
user_app_checked = 1;
#else
RSTSC &= ~BIT(7);
RSTSC |= BIT(7);
#endif
}
// B - jump to USB bootloader
if (keys[5] & BIT(3))
jump_to_usb_bootloader = 1;
}
}
if (!active_rows)
keyscan_idle();
}
}
}