blob: 6d28dd757e4e3934f0a0c2fdf4cdfd8b15805b60 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "hal/hal_uart.h"
#include "bsp/bsp.h"
#include "mcu/mips_hal.h"
#include <assert.h>
#include <stdlib.h>
#include <mips/cpu.h>
#include <mips/hal.h>
#include "mcu/gic.h"
static const uint32_t UART_0_INT_NO = 24;
static const uint32_t UART_1_INT_NO = 25;
/* base values for the below functions */
static uint32_t* const UART_0_BASE = (uint32_t* const)0xb8101400;
static uint32_t* const UART_1_BASE = (uint32_t* const)0xb8101500;
static const uint32_t UART_CLOCK_FREQ = 1843200;
enum e_uart_regs {
CR_UART_RBR_THR_DLL = 0,
CR_UART_IER_DLH,
CR_UART_IIR_FCR,
CR_UART_LCR,
CR_UART_MCR,
CR_UART_LSR,
CR_UART_MSR,
CR_UART_SCRATCH,
CR_UART_SOFT_RESET,
CR_UART_ACC_BUF_STATUS
};
enum e_uart_lsr_bits {
CR_UART_LSR_DR = 1,
CR_UART_LSR_OE = 1 << 1,
CR_UART_LSR_PE = 1 << 2,
CR_UART_LSR_FE = 1 << 3,
CR_UART_LSR_BI = 1 << 4,
CR_UART_LSR_THRE = 1 << 5,
CR_UART_LSR_TEMT = 1 << 6,
CR_UART_LSR_RXFER = 1 << 7
};
enum e_uart_lcr_bits {
CR_UART_LCR_WLS = 3,
CR_UART_LCR_STOPB = 1 << 2,
CR_UART_LCR_PEN = 1 << 3,
CR_UART_LCR_EPS = 1 << 4,
CR_UART_LCR_STPR = 1 << 5,
CR_UART_LCR_BREAK = 1 << 6,
CR_UART_LCR_DLAB = 1 << 7
};
static inline uint32_t*
uart_reg(int port, enum e_uart_regs offset)
{
switch (port) {
case 0:
return UART_0_BASE + offset;
case 1:
return UART_1_BASE + offset;
}
return 0;
}
static inline uint32_t
uart_reg_read(int port, enum e_uart_regs offset)
{
return *uart_reg(port, offset);
}
static inline void
uart_reg_write(int port, enum e_uart_regs offset, uint32_t data)
{
*uart_reg(port, offset) = data;
}
struct hal_uart {
volatile uint8_t u_rx_stall:1;
volatile uint8_t u_rx_data;
hal_uart_rx_char u_rx_func;
hal_uart_tx_char u_tx_func;
hal_uart_tx_done u_tx_done;
void *u_func_arg;
};
static struct hal_uart uarts[UART_CNT];
int
hal_uart_init_cbs(int port, hal_uart_tx_char tx_func, hal_uart_tx_done tx_done,
hal_uart_rx_char rx_func, void *arg)
{
uarts[port].u_rx_func = rx_func;
uarts[port].u_tx_func = tx_func;
uarts[port].u_tx_done = tx_done;
uarts[port].u_func_arg = arg;
return 0;
}
static void
uart_irq_handler(int port)
{
uint8_t lsr = uart_reg_read(port, CR_UART_LSR);
if (lsr & CR_UART_LSR_RXFER) {
/* receive error */
if (lsr & CR_UART_LSR_BI) {
/* break */
}
if (lsr & CR_UART_LSR_FE) {
/* framing error */
}
if (lsr & CR_UART_LSR_PE) {
/* parity */
}
}
if (lsr & CR_UART_LSR_DR) {
/* data ready */
uarts[port].u_rx_data = uart_reg_read(port, CR_UART_RBR_THR_DLL);
int c = uarts[port].u_rx_func(uarts[port].u_func_arg,
uarts[port].u_rx_data);
if (c < 0) {
/* disable rx interrupt */
uart_reg_write(port, CR_UART_IER_DLH, uart_reg_read(1,
CR_UART_IER_DLH) & ~1);
uarts[port].u_rx_stall = 1;
}
}
if (lsr & CR_UART_LSR_THRE) {
/* transmit holding reg empty */
int c = uarts[port].u_tx_func(uarts[port].u_func_arg);
if (c < 0) {
/* disable tx interrupt */
uart_reg_write(port, CR_UART_IER_DLH, uart_reg_read(1,
CR_UART_IER_DLH) & ~2);
/* call tx done cb */
if (uarts[port].u_tx_done) {
uarts[port].u_tx_done(uarts[port].u_func_arg);
}
} else {
/* write char out */
uart_reg_write(port, CR_UART_RBR_THR_DLL, (uint32_t)c & 0xff);
}
}
}
void __attribute__((interrupt, keep_interrupts_masked))
_mips_isr_hw0(void)
{
uart_irq_handler(0);
}
void __attribute__((interrupt, keep_interrupts_masked))
_mips_isr_hw1(void)
{
uart_irq_handler(1);
}
void
hal_uart_start_rx(int port)
{
if (uarts[port].u_rx_stall) {
/* recover saved data */
reg_t sr;
__HAL_DISABLE_INTERRUPTS(sr);
int c = uarts[port].u_rx_func(uarts[port].u_func_arg,
uarts[port].u_rx_data);
if (c >= 0) {
uarts[port].u_rx_stall = 0;
/* enable rx interrupt */
uart_reg_write(port, CR_UART_IER_DLH, 1);
}
__HAL_ENABLE_INTERRUPTS(sr);
}
}
void
hal_uart_start_tx(int port)
{
uart_reg_write(port, CR_UART_IER_DLH, uart_reg_read(port,
CR_UART_IER_DLH) | 2);
}
void
hal_uart_blocking_tx(int port, uint8_t data)
{
/* wait for transmit holding register to be empty */
while(!(uart_reg_read(port, CR_UART_LSR) & CR_UART_LSR_THRE)) {
}
/* write to transmit holding register */
uart_reg_write(port, CR_UART_RBR_THR_DLL, data);
/* wait for transmit holding register to be empty */
while(!(uart_reg_read(port, CR_UART_LSR) & CR_UART_LSR_THRE)) {
}
}
int
hal_uart_init(int port, void *arg)
{
return 0;
}
int
hal_uart_config(int port, int32_t baudrate, uint8_t databits, uint8_t stopbits,
enum hal_uart_parity parity, enum hal_uart_flow_ctl flow_ctl)
{
/* XXX: flow control currently unsupported */
(void) flow_ctl;
uarts[port].u_rx_stall = 0;
/* work out divisor */
uint16_t divisor = (UART_CLOCK_FREQ + (baudrate << 3)) / (baudrate << 4);
/* write to divisor regs */
uart_reg_write(port, CR_UART_LCR, CR_UART_LCR_DLAB);
uart_reg_write(port, CR_UART_RBR_THR_DLL, divisor & 0xff);
uart_reg_write(port, CR_UART_IER_DLH, divisor >> 8);
/* write to config regs */
if ((databits < 5) || (databits > 8) || (stopbits < 1) || (stopbits > 2)) {
return -1;
}
uint8_t value = ((databits - 5) & 3) | ((stopbits << 1) & (1 << 2));
switch (parity) {
case HAL_UART_PARITY_NONE:
break;
case HAL_UART_PARITY_ODD:
value |= 1 << 3;
break;
case HAL_UART_PARITY_EVEN:
value |= (1 << 3) | (1 << 4);
break;
default:
return -1;
}
uart_reg_write(port, CR_UART_LCR, value);
uart_reg_write(port, CR_UART_MCR, 0);
/* init gic, there shouldn't be any harm in calling this multiple times */
if (gic_init() != 0) {
return -1;
}
switch(port) {
case 0:
/* map UART0 to HW interrupt 0 */
gic_map(UART_0_INT_NO, 0, port);
gic_interrupt_active_high(UART_0_INT_NO);
/* enable UART0 interrupt */
gic_interrupt_set(UART_0_INT_NO);
break;
case 1:
/* map UART1 to HW interrupt 1 */
gic_map(UART_1_INT_NO, 0, port);
gic_interrupt_active_high(UART_1_INT_NO);
/* enable UART1 interrupt */
gic_interrupt_set(UART_1_INT_NO);
break;
default:
return -1;
}
/* enable rx interrupt */
uart_reg_write(port, CR_UART_IER_DLH, 1);
return 0;
}
int
hal_uart_close(int port)
{
/* disable gic interrupts */
switch(port) {
case 0:
/* unmap UART0 from HW interrupt 0 */
gic_unmap(UART_0_INT_NO, port);
/* disable UART0 interrupt */
gic_interrupt_reset(UART_0_INT_NO);
break;
case 1:
/* unmap UART1 from HW interrupt 1 */
gic_unmap(UART_1_INT_NO, port);
/* disable UART1 interrupt */
gic_interrupt_reset(UART_1_INT_NO);
break;
default:
return -1;
}
/* disable uart interrupts */
uart_reg_write(port, CR_UART_IER_DLH, 0);
uart_reg_write(port, CR_UART_MCR, 0);
return 0;
}