summaryrefslogtreecommitdiffstats
path: root/firmware/target/arm/s5l8702/ipod6g/serial-6g.c
blob: c90180142f37fdbbf360149adb7206d531b86594 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/***************************************************************************
 *             __________               __   ___.
 *   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 "uc87xx.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 */