rockbox/firmware/target/arm/s5l8702/ipod6g/serial-ipod6g.c
Cástor Muñoz f6ed4f8306 iPod Classic: use PMU interrupts to detect accessories
- Speed auto detection is launched when an accessory is inserted,
  so the user doesn't need to modify settings to use accessories
  that operates at different speeds (or when the same accessory is
  unplugged and plugged again).

- UART controller is disabled when no accessory is inserted, not
  much powersave but everything counts.

Change-Id: If20c3617c2a87b6277fd7e0270031030c44fa953
2016-05-26 09:05:44 +02:00

249 lines
7.5 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 by Cástor Muñoz
*
* 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 <stdint.h>
#include <stdbool.h>
#include "config.h"
#include "cpu.h"
#include "system.h"
#include "serial.h"
#include "s5l8702.h"
#include "uc870x.h"
/* Define LOGF_ENABLE to enable logf output in this file */
#define LOGF_ENABLE
#include "logf.h"
/* shall include serial HW configuracion for specific target */
#define IPOD6G_UART_CLK_HZ 12000000 /* external OSC0 ??? */
/* This values below are valid with a UCLK of 12MHz */
#define BRDATA_9600 (77) /* 9615 */
#define BRDATA_19200 (38) /* 19231 */
#define BRDATA_28800 (25) /* 28846 */
#define BRDATA_38400 (19 | (0xc330c << 8)) /* 38305 */
#define BRDATA_57600 (12) /* 57692 */
#define BRDATA_115200 (6 | (0xffffff << 8)) /* 114286 */
extern const struct uartc s5l8702_uartc;
#ifdef IPOD_ACCESSORY_PROTOCOL
static void iap_rx_isr(int, char*, char*, uint32_t);
#endif
struct uartc_port ser_port IDATA_ATTR =
{
/* location */
.uartc = &s5l8702_uartc,
.id = 0,
/* configuration */
.rx_trg = UFCON_RX_FIFO_TRG_4,
.tx_trg = UFCON_TX_FIFO_TRG_EMPTY,
.clksel = UCON_CLKSEL_ECLK,
.clkhz = IPOD6G_UART_CLK_HZ,
/* interrupt callbacks */
#ifdef IPOD_ACCESSORY_PROTOCOL
.rx_cb = iap_rx_isr,
#else
.rx_cb = NULL,
#endif
.tx_cb = NULL, /* polling */
};
/*
* serial driver API
*/
int tx_rdy(void)
{
return uartc_port_tx_ready(&ser_port) ? 1 : 0;
}
void tx_writec(unsigned char c)
{
uartc_port_tx_byte(&ser_port, c);
}
#ifndef IPOD_ACCESSORY_PROTOCOL
void serial_setup(void)
{
uartc_port_open(&ser_port);
/* set a default configuration, Tx and Rx modes are
disabled when the port is initialized */
uartc_port_config(&ser_port, ULCON_DATA_BITS_8,
ULCON_PARITY_NONE, ULCON_STOP_BITS_1);
uartc_port_set_bitrate_raw(&ser_port, BRDATA_115200);
/* enable Tx interrupt request or POLLING mode */
uartc_port_set_tx_mode(&ser_port, UCON_MODE_INTREQ);
logf("[%lu] "MODEL_NAME" port %d ready!", USEC_TIMER, ser_port.id);
}
#else /* IPOD_ACCESSORY_PROTOCOL */
#include "kernel.h"
#include "pmu-target.h"
#include "iap.h"
static enum {
ABR_STATUS_LAUNCHED, /* ST_SYNC */
ABR_STATUS_SYNCING, /* ST_SOF */
ABR_STATUS_DONE
} abr_status;
static int bitrate = 0;
static bool acc_plugged = false;
static void serial_acc_tick(void)
{
bool plugged = pmu_accessory_present();
if (acc_plugged != plugged)
{
acc_plugged = plugged;
if (acc_plugged)
{
uartc_open(ser_port.uartc);
uartc_port_open(&ser_port);
/* set a default configuration, Tx and Rx modes are
disabled when the port is initialized */
uartc_port_config(&ser_port, ULCON_DATA_BITS_8,
ULCON_PARITY_NONE, ULCON_STOP_BITS_1);
uartc_port_set_tx_mode(&ser_port, UCON_MODE_INTREQ);
serial_bitrate(bitrate);
}
else
{
uartc_port_close(&ser_port);
uartc_close(ser_port.uartc);
}
}
}
void serial_setup(void)
{
uartc_close(ser_port.uartc);
tick_add_task(serial_acc_tick);
}
void serial_bitrate(int rate)
{
bitrate = rate;
if (!acc_plugged)
return;
logf("[%lu] serial_bitrate(%d)", 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;
}
}
static 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) (IPOD6G_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 */