kolibrios/drivers/serial/uart16550.inc
2025-05-22 21:26:05 +05:00

409 lines
9.4 KiB
PHP
Executable File

BASE_FREQ = 1843200
BASE_DIV = 16
THR_REG = 0 ; transtitter/reciever
IER_REG = 1 ; interrupt enable
IIR_REG = 2 ; interrupt info
FCR_REG = 2 ; FIFO control
LCR_REG = 3 ; line control
MCR_REG = 4 ; modem control
LSR_REG = 5 ; line status
MSR_REG = 6 ; modem status
SCR_REG = 7 ; scratch
DLL_REG = THR_REG ; divisor latch (LSB)
DLM_REG = IER_REG ; divisor latch (MSB)
LCR_5BIT = 0x00
LCR_6BIT = 0x01
LCR_7BIT = 0x02
LCR_8BIT = 0x03
LCR_STOP_1 = 0x00
LCR_STOP_2 = 0x04
LCR_PARITY = 0x08
LCR_EVEN = 0x10
LCR_STICK = 0x20
LCR_BREAK = 0x40
LCR_DLAB = 0x80
LSR_DR = 0x01 ; data ready
LSR_OE = 0x02 ; overrun error
LSR_PE = 0x04 ; parity error
LSR_FE = 0x08 ; framing error
LSR_BI = 0x10 ; break interrupt
LSR_THRE = 0x20 ; transmitter holding empty
LSR_TEMT = 0x40 ; transmitter empty
LSR_FER = 0x80 ; FIFO error
FCR_EFIFO = 0x01 ; enable FIFO
FCR_CRB = 0x02 ; clear reciever FIFO
FCR_CXMIT = 0x04 ; clear transmitter FIFO
FCR_RDY = 0x08 ; set RXRDY and TXRDY pins
FCR_FIFO_1 = 0x00 ; 1 byte trigger
FCR_FIFO_4 = 0x40 ; 4 bytes trigger
FCR_FIFO_8 = 0x80 ; 8 bytes trigger
FCR_FIFO_14 = 0xC0 ; 14 bytes trigger
IIR_INTR = 0x01 ; 1= no interrupts
IIR_IID = 0x0E ; interrupt source mask
IER_RDAI = 0x01 ; reciever data interrupt
IER_THRI = 0x02 ; transmitter empty interrupt
IER_LSI = 0x04 ; line status interrupt
IER_MSI = 0x08 ; modem status interrupt
MCR_DTR = 0x01 ; 0-> DTR=1, 1-> DTR=0
MCR_RTS = 0x02 ; 0-> RTS=1, 1-> RTS=0
MCR_OUT1 = 0x04 ; 0-> OUT1=1, 1-> OUT1=0
MCR_OUT2 = 0x08 ; 0-> OUT2=1, 1-> OUT2=0; enable intr
MCR_LOOP = 0x10 ; lopback mode
MSR_DCTS = 0x01 ; delta clear to send
MSR_DDSR = 0x02 ; delta data set redy
MSR_TERI = 0x04 ; trailinh edge of ring
MSR_DDCD = 0x08 ; delta carrier detect
MSR_CTS = 0x10
MSR_DSR = 0x20
MSR_RI = 0x40
MSR_DCD = 0x80
MCR_TEST_MASK = MCR_DTR or MCR_RTS or MCR_OUT1 or MCR_OUT2 or MCR_LOOP
MSR_CHECK_MASK = MSR_CTS or MSR_DSR or MSR_RI or MSR_DCD
struct DRV_DATA
io_addr dd ? ; base address of io port
port dd ? ; serial port descriptor
ends
; dx = base io
; al = result
macro rd_reg reg
{
push edx
add dx, reg
in al, dx
pop edx
}
; dx = base io
; al = new value
macro wr_reg reg
{
push edx
add dx, reg
out dx, al
pop edx
}
; dx = port
; ax = divisor value
proc uart_set_baud
push eax
rd_reg LCR_REG
or al, LCR_DLAB
wr_reg LCR_REG
pop eax
wr_reg DLL_REG
shr ax, 8
wr_reg DLM_REG
rd_reg LCR_REG
and al, 0x7f
wr_reg LCR_REG
ret
endp
proc uart_probe stdcall uses ebx esi edi, io_addr:dword, irqn:dword
xor ebx, ebx ; 0 = reserve
mov ecx, [io_addr]
lea edx, [ecx + 7]
push ebp
invoke ReservePortArea
pop ebp
test eax, eax
jnz .err
mov edx, [io_addr]
; enable loopback
mov al, MCR_LOOP
wr_reg MCR_REG
; read status
rd_reg MSR_REG
and al, MSR_CHECK_MASK
test al, al
jnz .free_port
; set test signals
mov al, MCR_TEST_MASK
wr_reg MCR_REG
; check signals
rd_reg MSR_REG
and al, MSR_CHECK_MASK
cmp al, MSR_CHECK_MASK
jnz .free_port
DEBUGF L_DBG, "uart16550: found serial port %x\n", [io_addr]
; initialize port
xor ax, ax
wr_reg MCR_REG
wr_reg IER_REG
wr_reg LCR_REG
wr_reg FCR_REG
mov eax, sizeof.DRV_DATA
invoke Kmalloc
test eax, eax
jz .free_port
mov edi, eax
mov eax, [io_addr]
mov [edi + DRV_DATA.io_addr], eax
invoke AttachIntHandler, [irqn], uart_int_handler, edi
test eax, eax
jz .free_desc
; register port
lea ecx, [uart_drv]
mov edx, edi
call add_port
test eax, eax
jz .free_desc ; TODO detach_int_handler?
; save port handle
mov [edi + DRV_DATA.port], eax
ret
.free_desc:
mov eax, edi
invoke Kfree
.free_port:
xor ebx, ebx
inc ebx ; 1 = release
mov ecx, [io_addr]
lea edx, [ecx + 7]
push ebp
invoke ReservePortArea
pop ebp
.err:
ret
endp
proc uart_int_handler c uses ebx esi edi, data:dword
locals
.buf db ?
endl
mov edi, [data]
mov edx, [edi + DRV_DATA.io_addr]
xor ebx, ebx
.read_iir:
rd_reg IIR_REG
test al, IIR_INTR
jnz .exit
inc ebx
and ax, IIR_IID
shr ax, 1
; check source
test ax, ax
jz .modem
cmp ax, 1
jz .xmit
cmp ax, 2
jz .recv
cmp ax, 3
jz .status
jmp .exit
.modem:
; read MSR for clear interrupt
rd_reg MSR_REG
jmp .read_iir
.xmit:
push edx
mov eax, [edi + DRV_DATA.port]
mov edx, SERIAL_EVT_TXE
mov ecx, 1
lea esi, [.buf]
call handle_event
pop edx
test eax, eax
jz .no_data
mov al, [.buf]
wr_reg THR_REG
jmp .read_iir
.no_data:
; disable THR empty interrupt
rd_reg IER_REG
and ax, not IER_THRI
wr_reg IER_REG
jmp .read_iir
.recv:
; read byte
rd_reg THR_REG
push edx
mov [.buf], al
mov eax, [edi + DRV_DATA.port]
mov edx, SERIAL_EVT_RXNE
mov ecx, 1
lea esi, [.buf]
call handle_event
pop edx
; check for more recevied bytes
rd_reg LSR_REG
test al, LSR_DR
jnz .recv
jmp .read_iir
.status:
rd_reg LSR_REG
jmp .read_iir
.fifo:
jmp .read_iir
.exit:
xchg eax, ebx
ret
endp
proc uart_startup stdcall, data:dword, conf:dword
DEBUGF L_DBG, "uart16550: startup %x %x\n", [data], [conf]
; enable and reset fifo, 1 byte trigger level
mov ecx, [data]
mov edx, [ecx + DRV_DATA.io_addr]
mov ax, FCR_EFIFO or FCR_CRB or FCR_CXMIT
wr_reg FCR_REG
; configure at startup
stdcall uart_reconf, [data], [conf]
ret
endp
proc uart_shutdown stdcall, data:dword
DEBUGF L_DBG, "uart16550: shutdown %x\n", [data]
; disable interrupts
mov ecx, [data]
mov edx, [ecx + DRV_DATA.io_addr]
xor ax, ax
wr_reg IER_REG
ret
endp
proc uart_reconf stdcall uses ebx esi, data:dword, conf:dword
locals
divisor dw ?
lcr dw ?
endl
; calc divisor = BASE_FREQ / BASE_DIV / baudrate
mov esi, [conf]
xor edx, edx
mov eax, BASE_FREQ / BASE_DIV
div [esi + SP_CONF.baudrate]
test edx, edx
jnz .fail
test eax, eax
jz .fail
mov [divisor], ax
; calc word size
xor eax, eax
mov al, [esi + SP_CONF.word_size]
cmp al, 8
ja .fail
sub al, 5
jb .fail
mov [lcr], ax
; calc parity
mov al, [esi + SP_CONF.parity]
xor bx, bx
cmp al, SERIAL_CONF_PARITY_NONE
je .parity_ok
or bl, LCR_PARITY
cmp al, SERIAL_CONF_PARITY_ODD
je .parity_ok
or bl, LCR_EVEN
cmp al, SERIAL_CONF_PARITY_EVEN
je .parity_ok
mov bl, LCR_STICK or LCR_PARITY
cmp al, SERIAL_CONF_PARITY_MARK
je .parity_ok
or bl, LCR_EVEN
cmp al, SERIAL_CONF_PARITY_SPACE
jne .fail
.parity_ok:
mov [lcr], bx
; calc stop bits
mov bx, LCR_STOP_1
mov al, [esi + SP_CONF.stop_bits]
cmp al, SERIAL_CONF_STOP_BITS_1
je .stop_bits_ok
or bx, LCR_STOP_2
cmp al, SERIAL_CONF_STOP_BITS_2
jne .fail
.stop_bits_ok:
or [lcr], bx
mov esi, [data]
mov edx, [esi + DRV_DATA.io_addr]
spin_lock_irqsave
rd_reg IER_REG
and ax, IER_RDAI or IER_LSI
wr_reg IER_REG
spin_unlock_irqrestore
mov ax, [divisor]
call uart_set_baud
mov bx, [lcr]
wr_reg LCR_REG
mov al, MCR_DTR or MCR_OUT1 or MCR_OUT2
wr_reg MCR_REG
; enable rx interrupt
mov al, IER_RDAI or IER_LSI
wr_reg IER_REG
xor eax, eax
ret
.fail:
mov eax, SERIAL_API_ERR_CONF
ret
endp
proc uart_tx stdcall, data:dword
mov ecx, [data]
mov edx, [ecx + DRV_DATA.io_addr]
spin_lock_irqsave
rd_reg IER_REG
or ax, IER_THRI
wr_reg IER_REG
spin_unlock_irqrestore
ret
endp
align 4
uart_drv:
dd uart_drv_end - uart_drv
dd uart_startup
dd uart_shutdown
dd uart_reconf
dd uart_tx
uart_drv_end: