/* * swuart/swuart.c - Software-implemented UART for UBB * * Written 2012 by Werner Almesberger * Copyright 2012 Werner Almesberger * * 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. */ #include #include #include #include #include #include #define TIMER 7 static uint32_t tx_mask, rx_mask; static uint32_t ticks; /* ----- Hardware timer ---------------------------------------------------- */ /* * We run the hardware timer at PCLK/16 = 7 MHz. This gives us 60 cycles at * 115200 bps and sets the lowest data rate to 106.8 bps. */ #define PCLK (112*1000*1000) /* 112 MHz */ #define TCLK (PCLK/16) /* 7 MHz */ static void get_timer(uint32_t cycle) { TSCR = 1 << TIMER; /* enable clock to timer */ TDFR(TIMER) = cycle-1; /* cycle = one bit time */ TCSR(TIMER) = (2 << 3) | 1; /* count at PCLK/16 */ } static void reset_timer(void) { TECR = 1 << TIMER; /* disable timer */ TCNT(TIMER) = 0; /* reset timer */ TFCR = 0x10001 << TIMER; /* clear flags */ TESR = 1 << TIMER; /* enable timer */ } static void set_half_cycle(uint32_t cycle) { uint32_t t; t = (uint16_t) TCNT(TIMER)+(cycle >> 1); /* TCNT+cycle/2 mod cycle-1 */ if (t >= cycle-1) t -= cycle-1; TDHR(TIMER) = t; TFCR = 0x10000 << TIMER; /* clear half flags */ } static void release_timer(void) { TECR = 1 << TIMER; /* disable timer */ TFCR = 0x10001 << TIMER; /* clear flags */ TSSR = 1 << TIMER; /* disable clock to timer */ } static inline bool timer_full(void) { if (!(TFR & (1 << TIMER))) return 0; TFCR = 1 << TIMER; return 1; } static inline bool timer_half(void) { if (!(TFR & (0x10000 << TIMER))) return 0; TFCR = 0x10000 << TIMER; return 1; } /* ----- Enable/disable interrupts ----------------------------------------- */ static uint32_t old_icmr; static void disable_interrupts(void) { old_icmr = ICMR; ICMSR = 0xffffffff; } static void enable_interrupts(void) { ICMCR = ~old_icmr; } /* ----- Transmit a block -------------------------------------------------- */ static struct swuart_err errors; static int trx(uint8_t *out, int out_size, uint8_t *in, int in_size, unsigned wait_bits, unsigned idle_bits) { unsigned tx, rx = 0, idle = 0; int tx_bits = 0, rx_bits = 0; int in_len = 0; reset_timer(); while (1) { full: /* timer has just crossed FULL */ if (tx_bits) { if (tx & 1) SET(tx_mask); else CLR(tx_mask); tx >>= 1; tx_bits--; } else { if (out_size) { CLR(tx_mask); tx = *out++ | 0x100; out_size--; tx_bits = 9; } else { if (idle++ == wait_bits) return in_len; } } /* wait for reception (if any) */ if (rx_bits) { while (!timer_half()); } else { while (!timer_full()) if (!PIN(rx_mask)) { set_half_cycle(ticks); rx_bits = 10; break; } if (!rx_bits) continue; while (!timer_half()) if (timer_full()) goto full; } /* timer has just crossed HALF */ rx = (rx >> 1) | (PIN(rx_mask) ? 0x200 : 0); if (!--rx_bits) { if (rx & 1) { errors.glitch++; } else if (!(rx & 0x200)) { errors.framing++; } else if (in_len == in_size) { errors.overflow++; } else { *in++ = rx >> 1; in_len++; idle = 0; wait_bits = idle_bits; } } while (!timer_full()); } } int swuart_trx(void *out, int out_size, void *in, int in_size, int wait_bits, int idle_bits) { int got; disable_interrupts(); get_timer(ticks); got = trx(out, out_size, in, in_size, wait_bits, idle_bits); release_timer(); enable_interrupts(); return got; } /* ----- Access error counters --------------------------------------------- */ unsigned swuart_get_errors(struct swuart_err *res) { unsigned sum = errors.glitch+errors.framing+errors.overflow; if (res) *res = errors; return sum; } void swuart_clear_errors(void) { memset(&errors, 0, sizeof(errors)); } /* ----- Open/close the software UART -------------------------------------- */ int swuart_open(uint32_t tx, uint32_t rx, int bps) { /* * Make sure code and rx/tx buffers are locked into memory before we * disable interrupts in swuart_trx. */ if (mlockall(MCL_CURRENT | MCL_FUTURE)) return -1; ticks = TCLK/bps-1; tx_mask = tx; rx_mask = rx; OUT(tx); SET(tx); IN(rx); return 0; } void swuart_close(void) { munlockall(); }