From 9a4cd2eaee7d389f2fa6e0d79f0f6ea526f7ef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1stor=20Mu=C3=B1oz?= Date: Sat, 14 May 2016 14:24:49 +0200 Subject: [PATCH] iPod Nano2G: add IPOD_ACCESSORY_PROTOCOL Change-Id: I78a19972624504bc802d96b9b8e9cec132164c2c --- firmware/export/config/ipodnano2g.h | 9 +- firmware/export/s5l8700.h | 52 ++++++-- .../arm/s5l8700/ipodnano2g/serial-nano2g.c | 113 ++++++++++++++++++ firmware/target/arm/s5l8700/system-s5l8700.c | 12 +- firmware/target/arm/s5l8700/uart-s5l8701.c | 17 ++- 5 files changed, 183 insertions(+), 20 deletions(-) diff --git a/firmware/export/config/ipodnano2g.h b/firmware/export/config/ipodnano2g.h index a9c7239056..8e8f40f56c 100644 --- a/firmware/export/config/ipodnano2g.h +++ b/firmware/export/config/ipodnano2g.h @@ -231,9 +231,14 @@ /* Define this if you can switch on/off the accessory power supply */ #define HAVE_ACCESSORY_SUPPLY -//#define IPOD_ACCESSORY_PROTOCOL -#ifdef LOGF_SERIAL + +/* Serial */ +#ifndef BOOTLOADER #define HAVE_SERIAL +/* Disable iAP when LOGF_SERIAL is enabled to avoid conflicts */ +#ifndef LOGF_SERIAL +#define IPOD_ACCESSORY_PROTOCOL +#endif #endif /* Define this, if you can switch on/off the lineout */ diff --git a/firmware/export/s5l8700.h b/firmware/export/s5l8700.h index e8497ff337..3444919bc3 100644 --- a/firmware/export/s5l8700.h +++ b/firmware/export/s5l8700.h @@ -133,33 +133,61 @@ #define PWRCONEXT (*(REG32_PTR_T)(0x3C500040)) /* Clock power control register 2 */ /* 06. INTERRUPT CONTROLLER UNIT */ +#if CONFIG_CPU==S5L8700 #define SRCPND (*(REG32_PTR_T)(0x39C00000)) /* Indicates the interrupt request status. */ #define INTMOD (*(REG32_PTR_T)(0x39C00004)) /* Interrupt mode register. */ #define INTMSK (*(REG32_PTR_T)(0x39C00008)) /* Determines which interrupt source is masked. The */ -#if CONFIG_CPU==S5L8701 -#define INTMSK_TIMERA (1<<5) -#define INTMSK_TIMERB (1<<5) -#define INTMSK_TIMERC (1<<5) -#define INTMSK_TIMERD (1<<5) -#define INTMSK_ECC (1<<19) -#define INTMSK_USB_OTG (1<<16) -#define INTMSK_UART0 (0) /* Unknown */ -#define INTMSK_UART1 (1<<12) -#define INTMSK_UART2 (1<<7) -#else #define INTMSK_TIMERA (1<<5) #define INTMSK_TIMERB (1<<7) #define INTMSK_TIMERC (1<<8) #define INTMSK_TIMERD (1<<9) #define INTMSK_UART0 (1<<22) #define INTMSK_UART1 (1<<14) -#endif #define PRIORITY (*(REG32_PTR_T)(0x39C0000C)) /* IRQ priority control register */ #define INTPND (*(REG32_PTR_T)(0x39C00010)) /* Indicates the interrupt request status. */ #define INTOFFSET (*(REG32_PTR_T)(0x39C00014)) /* Indicates the IRQ interrupt request source */ #define EINTPOL (*(REG32_PTR_T)(0x39C00018)) /* Indicates external interrupt polarity */ #define EINTPEND (*(REG32_PTR_T)(0x39C0001C)) /* Indicates whether external interrupts are pending. */ #define EINTMSK (*(REG32_PTR_T)(0x39C00020)) /* Indicates whether external interrupts are masked */ +#else /* S5L8701 */ +#define SRCPND (*(REG32_PTR_T)(0x39C00000)) /* Indicates the interrupt request status. */ +#define INTMOD (*(REG32_PTR_T)(0x39C00004)) /* Interrupt mode register. */ +#define INTMSK (*(REG32_PTR_T)(0x39C00008)) /* Determines which interrupt source is masked. The */ +#define INTMSK_EINTG0 (1<<1) +#define INTMSK_EINTG1 (1<<2) +#define INTMSK_EINTG2 (1<<3) +#define INTMSK_EINTG3 (1<<4) +#define INTMSK_TIMERA (1<<5) +#define INTMSK_TIMERB (1<<5) +#define INTMSK_TIMERC (1<<5) +#define INTMSK_TIMERD (1<<5) +#define INTMSK_ECC (1<<19) +#define INTMSK_USB_OTG (1<<16) +#define INTMSK_UART0 (0) /* (AFAIK) no IRQ to ICU, uses EINTG0 */ +#define INTMSK_UART1 (1<<12) +#define INTMSK_UART2 (1<<7) +#define PRIORITY (*(REG32_PTR_T)(0x39C0000C)) /* IRQ priority control register */ +#define INTPND (*(REG32_PTR_T)(0x39C00010)) /* Indicates the interrupt request status. */ +#define INTOFFSET (*(REG32_PTR_T)(0x39C00014)) /* Indicates the IRQ interrupt request source */ +/* + * s5l8701 GPIO (External) Interrupt Controller. + * + * At first glance it looks very similar to gpio-s5l8702, but + * not fully tested, so this information could be wrong. + * + * Group0[31:10] Not used + * [9] UART0 IRQ + * [8] VBUS + * [7:0] PDAT1 + * Group1[31:0] PDAT5:PDAT4:PDAT3:PDAT2 + * Group2[31:0] PDAT11:PDAT10:PDAT7:PDAT6 + * Group3[31:0] PDAT15:PDAT14:PDAT13:PDAT12 + */ +#define GPIOIC_INTLEVEL(g) (*(REG32_PTR_T)(0x39C00018 + 4*(g))) +#define GPIOIC_INTSTAT(g) (*(REG32_PTR_T)(0x39C00028 + 4*(g))) +#define GPIOIC_INTEN(g) (*(REG32_PTR_T)(0x39C00038 + 4*(g))) +#define GPIOIC_INTTYPE(g) (*(REG32_PTR_T)(0x39C00048 + 4*(g))) +#endif /* 07. MEMORY INTERFACE UNIT (MIU) */ diff --git a/firmware/target/arm/s5l8700/ipodnano2g/serial-nano2g.c b/firmware/target/arm/s5l8700/ipodnano2g/serial-nano2g.c index 487984fbe3..5ff0b0ca25 100644 --- a/firmware/target/arm/s5l8700/ipodnano2g/serial-nano2g.c +++ b/firmware/target/arm/s5l8700/ipodnano2g/serial-nano2g.c @@ -47,6 +47,9 @@ extern const struct uartc s5l8701_uartc0; +#ifdef IPOD_ACCESSORY_PROTOCOL +void iap_rx_isr(int, char*, char*, uint32_t); +#endif struct uartc_port ser_port IDATA_ATTR = { @@ -61,7 +64,11 @@ struct uartc_port ser_port IDATA_ATTR = .clkhz = NANO2G_UART_CLK_HZ, /* interrupt callbacks */ +#ifdef IPOD_ACCESSORY_PROTOCOL + .rx_cb = iap_rx_isr, +#else .rx_cb = NULL, +#endif .tx_cb = NULL, /* polling */ }; @@ -94,3 +101,109 @@ void tx_writec(unsigned char c) { uartc_port_tx_byte(&ser_port, c); } + + +#ifdef IPOD_ACCESSORY_PROTOCOL +#include "iap.h" + +static enum { + ABR_STATUS_LAUNCHED, /* ST_SYNC */ + ABR_STATUS_SYNCING, /* ST_SOF */ + ABR_STATUS_DONE +} abr_status; + +void serial_bitrate(int rate) +{ + logf("[%lu] serial_bitrate(%d)", (uint32_t)USEC_TIMER, rate); + + if (rate == 0) { + /* Using auto-bitrate (ABR) to detect accessory Tx speed: + * + * + Here: + * - Disable Rx logic to clean the FIFO and the shift + * register, thus no Rx data interrupts are generated. + * - Launch ABR and wait for a low pulse in Rx line. + * + * + In ISR, when a low pulse is detected (ideally it is the + * start bit of 0xff): + * - Calculate and configure detected speed. + * - Enable Rx to verify that the next received data frame + * is 0x55 or 0xff: + * - If so, it's assumed bit rate is correctly detected, + * it will not be modified until speed is changed using + * RB options menu. + * - If not, reset iAP state machine and launch a new ABR. + */ + uartc_port_set_rx_mode(&ser_port, UCON_MODE_DISABLED); + uartc_port_abr_start(&ser_port); + abr_status = ABR_STATUS_LAUNCHED; + } + else { + uint32_t brdata; + if (rate == 57600) brdata = BRDATA_57600; + else if (rate == 38400) brdata = BRDATA_38400; + else if (rate == 19200) brdata = BRDATA_19200; + else brdata = BRDATA_9600; + uartc_port_abr_stop(&ser_port); /* abort ABR if already launched */ + uartc_port_set_bitrate_raw(&ser_port, brdata); + uartc_port_set_rx_mode(&ser_port, UCON_MODE_INTREQ); + abr_status = ABR_STATUS_DONE; + } +} + +void iap_rx_isr(int len, char *data, char *err, uint32_t abr_cnt) +{ + /* ignore Rx errors, upper layer will discard bad packets */ + (void) err; + + static int sync_retry; + + if (abr_status == ABR_STATUS_LAUNCHED) { + /* autobauding */ + if (abr_cnt) { + #define BR2CNT(s) (NANO2G_UART_CLK_HZ / (unsigned)(s)) + if (abr_cnt < BR2CNT(57600*1.1) || abr_cnt > BR2CNT(9600*0.9)) { + /* detected speed out of range, relaunch ABR */ + uartc_port_abr_start(&ser_port); + return; + } + /* valid speed detected, select it */ + uint32_t brdata; + if (abr_cnt < BR2CNT(48000)) brdata = BRDATA_57600; + else if (abr_cnt < BR2CNT(33600)) brdata = BRDATA_38400; + else if (abr_cnt < BR2CNT(24000)) brdata = BRDATA_28800; + else if (abr_cnt < BR2CNT(14400)) brdata = BRDATA_19200; + else brdata = BRDATA_9600; + + /* set detected speed */ + uartc_port_set_bitrate_raw(&ser_port, brdata); + uartc_port_set_rx_mode(&ser_port, UCON_MODE_INTREQ); + + /* enter SOF state */ + iap_getc(0xff); + + abr_status = ABR_STATUS_SYNCING; + sync_retry = 2; /* we are expecting [0xff] 0x55 */ + } + } + + /* process received data */ + while (len--) + { + bool sync_done = !iap_getc(*data++); + + if (abr_status == ABR_STATUS_SYNCING) + { + if (sync_done) { + abr_status = ABR_STATUS_DONE; + } + else if (--sync_retry == 0) { + /* invalid speed detected, relaunch ABR + discarding remaining data (if any) */ + serial_bitrate(0); + break; + } + } + } +} +#endif /* IPOD_ACCESSORY_PROTOCOL */ diff --git a/firmware/target/arm/s5l8700/system-s5l8700.c b/firmware/target/arm/s5l8700/system-s5l8700.c index 728fea0432..226464fb08 100644 --- a/firmware/target/arm/s5l8700/system-s5l8700.c +++ b/firmware/target/arm/s5l8700/system-s5l8700.c @@ -85,6 +85,10 @@ default_interrupt(INT_ADC); #if CONFIG_CPU==S5L8701 default_interrupt(INT_UNK); default_interrupt(INT_UART2); +default_interrupt(EINT_G0); +default_interrupt(EINT_G1); +default_interrupt(EINT_G2); +default_interrupt(EINT_G3); #endif @@ -99,8 +103,8 @@ void INT_TIMER(void) #if CONFIG_CPU==S5L8701 static void (* const irqvector[])(void) = -{ /* still 90% unverified and probably incorrect */ - EXT0,EXT1,EXT2,EINT_VBUS,EINTG,INT_TIMER,INT_WDT,INT_UART2, +{ /* still 80% unverified and probably incorrect */ + EXT0,EINT_G0,EINT_G1,EINT_G2,EINT_G3,INT_TIMER,INT_WDT,INT_UART2, INT_UNK,INT_UNK,INT_DMA,INT_ALARM_RTC,INT_UART1,INT_UNK,INT_UNK,INT_USB_HOST, INT_USB_FUNC,INT_LCDC_0,INT_LCDC_1,INT_CALM,INT_ATA,INT_UNK,INT_SPDIF_OUT,INT_ECC, INT_SDCI,INT_LCD,INT_WHEEL,INT_IIC,RESERVED2,INT_MSTICK,INT_ADC_WAKEUP,INT_ADC @@ -117,8 +121,8 @@ static void (* const irqvector[])(void) = #if CONFIG_CPU==S5L8701 static const char * const irqname[] = -{ /* still 90% unverified and probably incorrect */ - "EXT0","EXT1","EXT2","EINT_VBUS","EINTG","INT_TIMER","INT_WDT","INT_UART2", +{ /* still 80% unverified and probably incorrect */ + "EXT0","EINT_G0","EINT_G1","EINT_G2","EINT_G3","INT_TIMER","INT_WDT","INT_UART2", "INT_UNK1","INT_UNK2","INT_DMA","INT_ALARM_RTC","INT_UART1","INT_UNK3","INT_UNK4","INT_USB_HOST", "INT_USB_FUNC","INT_LCDC_0","INT_LCDC_1","INT_CALM","INT_ATA","INT_UNK5","INT_SPDIF_OUT","INT_ECC", "INT_SDCI","INT_LCD","INT_WHEEL","INT_IIC","Reserved","INT_MSTICK","INT_ADC_WAKEUP","INT_ADC" diff --git a/firmware/target/arm/s5l8700/uart-s5l8701.c b/firmware/target/arm/s5l8700/uart-s5l8701.c index 00c9322e47..b9fb1632ac 100644 --- a/firmware/target/arm/s5l8700/uart-s5l8701.c +++ b/firmware/target/arm/s5l8700/uart-s5l8701.c @@ -68,7 +68,7 @@ static uint8_t clockgate_uartc[S5L8701_N_UARTC] = { CLOCKGATE_UARTC0, CLOCKGATE_UARTC1, CLOCKGATE_UARTC2 }; static int intmsk_uart[S5L8701_N_UARTC] = { - INTMSK_UART0, INTMSK_UART1, INTMSK_UART2 }; + INTMSK_EINTG0, INTMSK_UART1, INTMSK_UART2 }; /* * Device level functions specific to S5L8701 @@ -107,6 +107,7 @@ void uart_target_disable_gpio(int uart_id, int port_id) void uart_target_enable_irq(int uart_id, int port_id) { (void) port_id; + if (uart_id == 0) GPIOIC_INTEN(0) = 0x200; INTMSK |= intmsk_uart[uart_id]; } @@ -114,11 +115,13 @@ void uart_target_disable_irq(int uart_id, int port_id) { (void) port_id; INTMSK &= ~intmsk_uart[uart_id]; + if (uart_id == 0) GPIOIC_INTEN(0) = 0; } void uart_target_clear_irq(int uart_id, int port_id) { (void) port_id; + if (uart_id == 0) GPIOIC_INTSTAT(0) = 0x200; SRCPND |= intmsk_uart[uart_id]; } @@ -135,10 +138,20 @@ void uart_target_disable_clocks(int uart_id) /* * ISRs */ -void ICODE_ATTR INT_UART0(void) + +/* On Nano2G, PORT0 interrupts are not used when iAP is disabled */ +#if !defined(IPOD_NANO2G) || defined(IPOD_ACCESSORY_PROTOCOL) +/* + * UART0 IRQ is connected to EINTG0, this is a quick patch, a "real" + * EINT handler will be needed if in future we use more than one IRQ + * on this group. + */ +void ICODE_ATTR EINT_G0(void) { + GPIOIC_INTSTAT(0) = 0x200; /* clear external IRQ */ uartc_callback(&s5l8701_uartc0, 0); } +#endif /* UARTC1,2 not used on Nano2G */ #ifndef IPOD_NANO2G