summaryrefslogtreecommitdiff
path: root/firmware/target/arm/uc870x.c
diff options
context:
space:
mode:
authorCástor Muñoz <cmvidal@gmail.com>2016-05-12 06:47:38 +0200
committerCástor Muñoz <cmvidal@gmail.com>2016-05-13 23:21:42 +0200
commit8fb67f48ab57770c3233352de17846a8a773192a (patch)
treefcf00f022dcd297c10ab92df8b85021f55e96f6f /firmware/target/arm/uc870x.c
parent2a1e9eb8a8f50f636f86988de1f0cd1b3acf55bb (diff)
downloadrockbox-8fb67f48ab57770c3233352de17846a8a773192a.tar.gz
rockbox-8fb67f48ab57770c3233352de17846a8a773192a.zip
iPod Classic: updates for uc8702 driver
- Small rework on the UC8702 UART controller to make it compatible with other s5l870x SOCs. Files moved and renamed, many conditional code added to deal with capabilities and 'features' of the different CPUs. - A couple of optimizacions that should not affect the functionality. Change-Id: I705169f7e8b18d5d1da642f81ffc31c4089780a6
Diffstat (limited to 'firmware/target/arm/uc870x.c')
-rw-r--r--firmware/target/arm/uc870x.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/firmware/target/arm/uc870x.c b/firmware/target/arm/uc870x.c
new file mode 100644
index 0000000000..9537c15e1a
--- /dev/null
+++ b/firmware/target/arm/uc870x.c
@@ -0,0 +1,506 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Cástor Muñoz
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <stdint.h>
22#include <stdbool.h>
23
24#include "config.h"
25#include "system.h"
26
27#include "uart-target.h"
28#include "uc870x.h"
29
30
31/*
32 * UC870x: UART controller for s5l870x
33 */
34
35/* Rx related masks */
36#if CONFIG_CPU == S5L8700
37#define UTRSTAT_RX_RELATED_INTS (UTRSTAT_RX_INT_BIT | UTRSTAT_ERR_INT_BIT)
38#define UCON_RX_RELATED_INTS (UCON_RX_INT_BIT | UCON_ERR_INT_BIT)
39
40#elif CONFIG_CPU == S5L8701
41#define UTRSTAT_RX_RELATED_INTS \
42 (UTRSTAT_RX_INT_BIT | UTRSTAT_ERR_INT_BIT | UTRSTAT_AUTOBR_INT_BIT)
43#define UCON_RX_RELATED_INTS \
44 (UCON_RX_INT_BIT | UCON_ERR_INT_BIT | UCON_AUTOBR_INT_BIT)
45
46#else /* CONFIG_CPU == S5L8702 */
47#define UTRSTAT_RX_RELATED_INTS \
48 (UTRSTAT_RX_INT_BIT | UTRSTAT_ERR_INT_BIT | \
49 UTRSTAT_AUTOBR_INT_BIT | UTRSTAT_RX_TOUT_INT_BIT)
50#define UCON_RX_RELATED_INTS \
51 (UCON_RX_INT_BIT | UCON_ERR_INT_BIT | \
52 UCON_AUTOBR_INT_BIT | UCON_RX_TOUT_INT_BIT)
53#endif
54
55#define UART_PORT_BASE(u,i) (((u)->baddr) + (u)->port_off * (i))
56
57/* Initialization */
58static void uartc_reset_port_id(const struct uartc* uartc, int port_id)
59{
60 uart_target_disable_irq(uartc->id, port_id);
61 uart_target_disable_gpio(uartc->id, port_id);
62
63 /* set port registers to default reset values */
64 uint32_t baddr = UART_PORT_BASE(uartc, port_id);
65 UCON(baddr) = 0;
66 ULCON(baddr) = 0;
67 UMCON(baddr) = 0;
68 UFCON(baddr) = UFCON_RX_FIFO_RST_BIT | UFCON_TX_FIFO_RST_BIT;
69 UTRSTAT(baddr) = ~0; /* clear all interrupts */
70 UBRDIV(baddr) = 0;
71#if CONFIG_CPU == S5L8702
72 UBRCONTX(baddr) = 0;
73 UBRCONRX(baddr) = 0;
74#endif
75
76 uartc->port_l[port_id] = (void*)0;
77}
78
79static void uartc_reset(const struct uartc* uartc)
80{
81 for (int port_id = 0; port_id < uartc->n_ports; port_id++)
82 uartc_reset_port_id(uartc, port_id);
83}
84
85void uartc_open(const struct uartc *uartc)
86{
87 uart_target_enable_clocks(uartc->id);
88 uartc_reset(uartc);
89}
90
91void uartc_close(const struct uartc *uartc)
92{
93 uartc_reset(uartc);
94 uart_target_disable_clocks(uartc->id);
95}
96
97void uartc_port_open(struct uartc_port *port)
98{
99 const struct uartc *uartc = port->uartc;
100 uint32_t baddr = UART_PORT_BASE(uartc, port->id);
101
102 uart_target_enable_gpio(uartc->id, port->id);
103
104 /* disable Tx/Rx and mask all interrupts */
105 UCON(baddr) = 0;
106
107 /* clear all interrupts */
108 UTRSTAT(baddr) = ~0;
109
110 /* configure registers */
111 UFCON(baddr) = UFCON_FIFO_ENABLE_BIT
112 | UFCON_RX_FIFO_RST_BIT
113 | UFCON_TX_FIFO_RST_BIT
114 | ((port->rx_trg & UFCON_RX_FIFO_TRG_MASK) << UFCON_RX_FIFO_TRG_POS)
115 | ((port->tx_trg & UFCON_TX_FIFO_TRG_MASK) << UFCON_TX_FIFO_TRG_POS);
116
117 UMCON(baddr) = UMCON_RTS_BIT; /* activate nRTS (low level) */
118
119 UCON(baddr) = (UCON_MODE_DISABLED << UCON_RX_MODE_POS)
120 | (UCON_MODE_DISABLED << UCON_TX_MODE_POS)
121 | ((port->clksel & UCON_CLKSEL_MASK) << UCON_CLKSEL_POS)
122 | (port->rx_cb ? UCON_RX_RELATED_INTS|UCON_RX_TOUT_EN_BIT : 0)
123 | (port->tx_cb ? UCON_TX_INT_BIT : 0);
124
125 /* init and register port struct */
126 port->baddr = baddr;
127 port->utrstat_int_mask = (port->rx_cb ? UTRSTAT_RX_RELATED_INTS : 0)
128 | (port->tx_cb ? UTRSTAT_TX_INT_BIT : 0);
129#if CONFIG_CPU != S5L8700
130 port->abr_aborted = 0;
131#endif
132 uartc->port_l[port->id] = port;
133
134 /* enable interrupts */
135 uart_target_clear_irq(uartc->id, port->id);
136 /*if (port->utrstat_int_mask)*/
137 uart_target_enable_irq(uartc->id, port->id);
138}
139
140void uartc_port_close(struct uartc_port *port)
141{
142 uartc_reset_port_id(port->uartc, port->id);
143}
144
145/* Configuration */
146void uartc_port_config(struct uartc_port *port,
147 uint8_t data_bits, uint8_t parity, uint8_t stop_bits)
148{
149 ULCON(port->baddr) = ((parity & ULCON_PARITY_MASK) << ULCON_PARITY_POS)
150 | ((stop_bits & ULCON_STOP_BITS_MASK) << ULCON_STOP_BITS_POS)
151 | ((data_bits & ULCON_DATA_BITS_MASK) << ULCON_DATA_BITS_POS);
152}
153
154/* set bitrate using precalculated values */
155void uartc_port_set_bitrate_raw(struct uartc_port *port, uint32_t brdata)
156{
157 uint32_t baddr = port->baddr;
158 UBRDIV(baddr) = brdata & 0xff;
159#if CONFIG_CPU == S5L8702
160 UBRCONRX(baddr) = brdata >> 8;
161 UBRCONTX(baddr) = brdata >> 8;
162#endif
163}
164
165#if 0
166/* calculate values to set real bitrate as close as possible to the
167 requested speed */
168void uartc_port_set_bitrate(struct uartc_port *port, unsigned int speed)
169{
170 int uclk = port->clkhz;
171
172 /* Real baud width in UCLK/16 ticks: trunc(UCLK/(16*speed) + 0.5) */
173 int brdiv = (uclk + (speed << 3)) / (speed << 4);
174
175 uint32_t brdata = brdiv - 1;
176
177#if CONFIG_CPU == S5L8702
178 /* Fine adjust:
179 *
180 * Along the whole frame, insert/remove "jittered" bauds when needed
181 * to minimize frame lenght accumulated error.
182 *
183 * jitter_width: "jittered" bauds are 1/16 wider/narrower than normal
184 * bauds, so step is 1/16 of real baud width = brdiv (in UCLK ticks)
185 *
186 * baud_err_width: it is the difference between theoric width and real
187 * width = CLK/speed - brdiv*16 (in UCLK ticks)
188 *
189 * Previous widths are scaled by 'speed' factor to simplify operations
190 * and preserve precision using integer operations.
191 */
192 int jitter_width = brdiv * speed;
193 int baud_err_width = uclk - (jitter_width << 4);
194
195 int jitter_incdec = UBRCON_JITTER_INC;
196 if (baud_err_width < 0) {
197 baud_err_width = -baud_err_width;
198 jitter_incdec = UBRCON_JITTER_DEC;
199 }
200
201 int err_width = 0;
202 uint32_t brcon = 0;
203 for (int bit = 0; bit < UC_FRAME_MAX_LEN; bit++) {
204 err_width += baud_err_width;
205 /* adjust to the nearest width */
206 if (jitter_width < (err_width << 1)) {
207 brcon |= jitter_incdec << UBRCON_JITTER_POS(bit);
208 err_width -= jitter_width;
209 }
210 }
211
212 brdata |= (brcon << 8);
213#endif /* CONFIG_CPU == S5L8702 */
214
215 uartc_port_set_rawbr(port, brdata);
216}
217#endif
218
219/* Select Tx/Rx modes: disabling Tx/Rx resets HW, including
220 FIFOs and shift registers */
221void uartc_port_set_rx_mode(struct uartc_port *port, uint32_t mode)
222{
223 UCON(port->baddr) = (mode << UCON_RX_MODE_POS) |
224 (_UCON_RD(port->baddr) & ~(UCON_RX_MODE_MASK << UCON_RX_MODE_POS));
225}
226
227void uartc_port_set_tx_mode(struct uartc_port *port, uint32_t mode)
228{
229 UCON(port->baddr) = (mode << UCON_TX_MODE_POS) |
230 (_UCON_RD(port->baddr) & ~(UCON_TX_MODE_MASK << UCON_TX_MODE_POS));
231}
232
233/* Transmit */
234bool uartc_port_tx_ready(struct uartc_port *port)
235{
236 return (UTRSTAT(port->baddr) & UTRSTAT_TXBUF_EMPTY_BIT);
237}
238
239void uartc_port_tx_byte(struct uartc_port *port, uint8_t ch)
240{
241 UTXH(port->baddr) = ch;
242#ifdef UC870X_DEBUG
243 port->n_tx_bytes++;
244#endif
245}
246
247void uartc_port_send_byte(struct uartc_port *port, uint8_t ch)
248{
249 /* wait for transmit buffer empty */
250 while (!uartc_port_tx_ready(port));
251 uartc_port_tx_byte(port, ch);
252}
253
254/* Receive */
255bool uartc_port_rx_ready(struct uartc_port *port)
256{
257 /* test receive buffer data ready */
258 return (UTRSTAT(port->baddr) & UTRSTAT_RXBUF_RDY_BIT);
259}
260
261uint8_t uartc_port_rx_byte(struct uartc_port *port)
262{
263 return URXH(port->baddr) & 0xff;
264}
265
266uint8_t uartc_port_read_byte(struct uartc_port *port)
267{
268 while (!uartc_port_rx_ready(port));
269 return uartc_port_rx_byte(port);
270}
271
272#if CONFIG_CPU != S5L8700
273/* Autobauding */
274static inline int uartc_port_abr_status(struct uartc_port *port)
275{
276 return UABRSTAT(port->baddr) & UABRSTAT_STATUS_MASK;
277}
278
279void uartc_port_abr_start(struct uartc_port *port)
280{
281 port->abr_aborted = 0;
282 UCON(port->baddr) = _UCON_RD(port->baddr) | UCON_AUTOBR_START_BIT;
283}
284
285void uartc_port_abr_stop(struct uartc_port *port)
286{
287 if (uartc_port_abr_status(port) == UABRSTAT_STATUS_COUNTING)
288 /* There is no known way to stop the HW once COUNTING is
289 * in progress.
290 * If we disable AUTOBR_START_BIT now, COUNTING is not
291 * aborted, instead the HW will launch interrupts for
292 * every new rising edge detected while AUTOBR_START_BIT
293 * remains disabled.
294 * If AUTOBR_START_BIT is enabled, the HW will stop by
295 * itself when a rising edge is detected.
296 * So, do not disable AUTOBR_START_BIT and wait for the
297 * next rising edge.
298 */
299 port->abr_aborted = 1;
300 else
301 UCON(port->baddr) = _UCON_RD(port->baddr) & ~UCON_AUTOBR_START_BIT;
302}
303#endif /* CONFIG_CPU != S5L8700 */
304
305/* ISR */
306void ICODE_ATTR uartc_callback(const struct uartc* uartc, int port_id)
307{
308 struct uartc_port *port = uartc->port_l[port_id];
309 uint32_t baddr = port->baddr;
310
311 /* filter registered interrupts */
312 uint32_t ints = UTRSTAT(baddr) & port->utrstat_int_mask;
313
314 /* clear interrupts, events ocurring while processing
315 this ISR will be processed in the next call */
316 UTRSTAT(baddr) = ints;
317
318 if (ints & UTRSTAT_RX_RELATED_INTS)
319 {
320 int len = 0;
321#if CONFIG_CPU != S5L8700
322 uint32_t abr_cnt = 0;
323
324 if (ints & UTRSTAT_AUTOBR_INT_BIT)
325 {
326 if (uartc_port_abr_status(port) == UABRSTAT_STATUS_COUNTING) {
327 #ifdef UC870X_DEBUG
328 if (_UCON_RD(baddr) & UCON_AUTOBR_START_BIT) port->n_abnormal0++;
329 else port->n_abnormal1++;
330 #endif
331 /* try to fix abnormal situations */
332 UCON(baddr) = _UCON_RD(baddr) | UCON_AUTOBR_START_BIT;
333 }
334 else if (!port->abr_aborted)
335 abr_cnt = UABRCNT(baddr);
336 }
337
338 if (ints & (UTRSTAT_RX_RELATED_INTS ^ UTRSTAT_AUTOBR_INT_BIT))
339#endif /* CONFIG_CPU != S5L8700 */
340 {
341 /* get FIFO count */
342 uint32_t ufstat = UFSTAT(baddr);
343 len = (ufstat & UFSTAT_RX_FIFO_CNT_MASK) |
344 ((ufstat & UFSTAT_RX_FIFO_FULL_BIT) >> (8 - 4));
345
346 for (int i = 0; i < len; i++) {
347 /* must read error status first, then data */
348 port->rx_err[i] = UERSTAT(baddr);
349 port->rx_data[i] = URXH(baddr);
350 }
351 }
352
353 /* 'len' might be zero due to RX_TOUT interrupts are
354 * raised by the hardware even when RX FIFO is empty.
355 * When overrun, it is marked on the first error:
356 * overrun = len ? (rx_err[0] & UERSTAT_OVERRUN_BIT) : 0
357 */
358#if CONFIG_CPU == S5L8700
359 port->rx_cb(len, port->rx_data, port->rx_err);
360#else
361 /* 'abr_cnt' is zero when no ABR interrupt exists */
362 port->rx_cb(len, port->rx_data, port->rx_err, abr_cnt);
363#endif
364
365#ifdef UC870X_DEBUG
366 if (len) {
367 port->n_rx_bytes += len;
368 if (port->rx_err[0] & UERSTAT_OVERRUN_BIT)
369 port->n_ovr_err++;
370 for (int i = 0; i < len; i++) {
371 if (port->rx_err[i] & UERSTAT_PARITY_ERR_BIT)
372 port->n_parity_err++;
373 if (port->rx_err[i] & UERSTAT_FRAME_ERR_BIT)
374 port->n_frame_err++;
375 if (port->rx_err[i] & UERSTAT_BREAK_DETECT_BIT)
376 port->n_break_detect++;
377 }
378 }
379#endif
380 }
381
382#if 0
383 /* not used and not tested */
384 if (ints & UTRSTAT_TX_INT_BIT)
385 {
386 port->tx_cb(UART_FIFO_SIZE - ((UFSTAT(baddr) & \
387 UFSTAT_TX_FIFO_CNT_MASK) >> UFSTAT_TX_FIFO_CNT_POS));
388 }
389#endif
390}
391
392
393#ifdef UC870X_DEBUG
394/*#define LOGF_ENABLE*/
395#include "logf.h"
396
397#if CONFIG_CPU == S5L8702
398static int get_bitrate(int uclk, int brdiv, int brcon, int frame_len)
399{
400 logf("get_bitrate(%d, %d, 0x%08x, %d)", uclk, brdiv, brcon, frame_len);
401
402 int avg_speed;
403 int speed_sum = 0;
404 unsigned int frame_width = 0; /* in UCLK clock ticks */
405
406 /* calculate resulting speed for every frame len */
407 for (int bit = 0; bit < frame_len; bit++)
408 {
409 frame_width += brdiv * 16;
410
411 int incdec = ((brcon >> UBRCON_JITTER_POS(bit)) & UBRCON_JITTER_MASK);
412 if (incdec == UBRCON_JITTER_INC) frame_width += brdiv;
413 else if (incdec == UBRCON_JITTER_DEC) frame_width -= brdiv;
414
415 /* speed = truncate((UCLK / (real_frame_width / NBITS)) + 0.5)
416 XXX: overflows for big UCLK */
417 int speed = (((uclk*(bit+1))<<1) + frame_width) / (frame_width<<1);
418 speed_sum += speed;
419 logf(" %d: %c %d", bit, ((incdec == UBRCON_JITTER_INC) ? 'i' :
420 ((incdec == UBRCON_JITTER_DEC) ? 'd' : '.')), speed);
421 }
422
423 /* average of the speed for all frame lengths */
424 avg_speed = speed_sum / frame_len;
425 logf(" avg speed = %d", avg_speed);
426
427 return avg_speed;
428}
429#endif /* CONFIG_CPU == S5L8702 */
430
431void uartc_port_get_line_info(struct uartc_port *port,
432 int *tx_status, int *rx_status,
433 int *tx_speed, int *rx_speed, char *line_cfg)
434{
435 uint32_t baddr = port->baddr;
436
437 uint32_t ucon = _UCON_RD(baddr);
438 if (tx_status)
439 *tx_status = ((ucon >> UCON_TX_MODE_POS) & UCON_TX_MODE_MASK) ? 1 : 0;
440 if (rx_status)
441 *rx_status = ((ucon >> UCON_RX_MODE_POS) & UCON_RX_MODE_MASK) ? 1 : 0;
442
443 uint32_t ulcon = ULCON(baddr);
444 int n_data = ((ulcon >> ULCON_DATA_BITS_POS) & ULCON_DATA_BITS_MASK) + 5;
445 int n_stop = ((ulcon >> ULCON_STOP_BITS_POS) & ULCON_STOP_BITS_MASK) + 1;
446 int parity = (ulcon >> ULCON_PARITY_POS) & ULCON_PARITY_MASK;
447
448 uint32_t brdiv = UBRDIV(baddr) + 1;
449#if CONFIG_CPU == S5L8702
450 int frame_len = 1 + n_data + (parity ? 1 : 0) + n_stop;
451 if (tx_speed)
452 *tx_speed = get_bitrate(port->clkhz, brdiv, UBRCONTX(baddr), frame_len);
453 if (rx_speed)
454 *rx_speed = get_bitrate(port->clkhz, brdiv, UBRCONRX(baddr), frame_len);
455#else
456 /* speed = truncate(UCLK/(16*brdiv) + 0.5) */
457 int speed = (port->clkhz + (brdiv << 3)) / (brdiv << 4);
458 if (tx_speed) *tx_speed = speed;
459 if (rx_speed) *rx_speed = speed;
460#endif
461
462 if (line_cfg) {
463 line_cfg[0] = '0' + n_data;
464 line_cfg[1] = ((parity == ULCON_PARITY_NONE) ? 'N' :
465 ((parity == ULCON_PARITY_EVEN) ? 'E' :
466 ((parity == ULCON_PARITY_ODD) ? 'O' :
467 ((parity == ULCON_PARITY_FORCE_1) ? 'M' :
468 ((parity == ULCON_PARITY_FORCE_0) ? 'S' : '?')))));
469 line_cfg[2] = '0' + n_stop;
470 line_cfg[3] = '\0';
471 }
472}
473
474#if CONFIG_CPU != S5L8700
475/* Autobauding */
476int uartc_port_get_abr_info(struct uartc_port *port, uint32_t *abr_cnt)
477{
478 int status;
479 uint32_t abr_status;
480
481 int flags = disable_irq_save();
482
483 abr_status = uartc_port_abr_status(port);
484
485 if (_UCON_RD(port->baddr) & UCON_AUTOBR_START_BIT) {
486 if (abr_status == UABRSTAT_STATUS_COUNTING)
487 status = ABR_INFO_ST_COUNTING; /* waiting for rising edge */
488 else
489 status = ABR_INFO_ST_LAUNCHED; /* waiting for falling edge */
490 }
491 else {
492 if (abr_status == UABRSTAT_STATUS_COUNTING)
493 status = ABR_INFO_ST_ABNORMAL;
494 else
495 status = ABR_INFO_ST_IDLE;
496 }
497
498 if (abr_cnt)
499 *abr_cnt = UABRCNT(port->baddr);
500
501 restore_irq(flags);
502
503 return status;
504}
505#endif /* CONFIG_CPU != S5L8700 */
506#endif /* UC870X_DEBUG */