1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "mos/device/serial.hpp"
4
5#include <mos/mos_global.h>
6#include <mos/syslog/printk.hpp>
7
8bool ISerialDevice::setup()
9{
10 SetInterrupts(INTERRUPT_NONE);
11 SetBaudrateDivisor();
12 SetDataBits();
13 SetStopBits();
14 SetParity();
15
16 SetModemOptions(control: MODEM_DTR, enable: true);
17 SetModemOptions(control: MODEM_RTS, enable: true);
18
19 // Try send a byte to the serial port.
20 // If it fails, then the serial port is not connected.
21 {
22 const char challenge = 'H';
23 SetModemOptions(control: MODEM_LOOP, enable: true);
24 write_byte(byte: challenge);
25 const auto response = read_byte();
26 SetModemOptions(control: MODEM_LOOP, enable: false);
27 if (response != 'H')
28 return false;
29 }
30
31 SetModemOptions(control: MODEM_IRQ, enable: true);
32 SetInterrupts(INTERRUPT_DATA_AVAILABLE);
33 return true;
34}
35
36int ISerialDevice::read_data(char *data, size_t length)
37{
38 for (size_t i = 0; i < length; i++)
39 {
40 WaitReadyToRead();
41 data[i] = read_byte();
42 }
43
44 return length;
45}
46
47int ISerialDevice::write_data(const char *data, size_t length)
48{
49 for (size_t i = 0; i < length; i++)
50 {
51 WaitReadyToWrite();
52 write_byte(byte: data[i]);
53 }
54
55 return length;
56}
57
58void ISerialDevice::SetBaudrateDivisor()
59{
60 // Set the most significant bit of the Line Control Register. This is the DLAB bit, and allows access to the divisor registers.
61 u8 reg = read_register(offset: OFFSET_LINE_CONTROL);
62 reg |= 0x80; // set DLAB bit
63
64 write_register(offset: OFFSET_LINE_CONTROL, value: reg);
65
66 // Send the least significant byte of the divisor value to [PORT + 0].
67 write_register(offset: OFFSET_DLAB_DIVISOR_LSB, value: baudrate_divisor & 0xFF);
68
69 // Send the most significant byte of the divisor value to [PORT + 1].
70 write_register(offset: OFFSET_DLAB_DIVISOR_MSB, value: baudrate_divisor >> 8);
71
72 // Clear the most significant bit of the Line Control Register.
73 reg &= ~0x80;
74 write_register(offset: OFFSET_LINE_CONTROL, value: reg);
75}
76void ISerialDevice::SetDataBits()
77{
78 u8 control = read_register(offset: OFFSET_LINE_CONTROL);
79 control &= char_length;
80 write_register(offset: OFFSET_LINE_CONTROL, value: control);
81}
82void ISerialDevice::SetStopBits()
83{
84 byte_t control = { .byte = read_register(offset: OFFSET_LINE_CONTROL) };
85 control.bits.b1 = stop_bits == STOP_BITS_15_OR_2;
86 write_register(offset: OFFSET_LINE_CONTROL, value: control.byte);
87}
88void ISerialDevice::SetParity()
89{
90 u8 byte = read_register(offset: OFFSET_LINE_CONTROL);
91 byte |= ((u8) parity) << 3;
92 write_register(offset: OFFSET_LINE_CONTROL, value: byte);
93}
94void ISerialDevice::SetInterrupts(int interrupts)
95{
96 char control = read_register(offset: OFFSET_INTERRUPT_ENABLE);
97 control = interrupts;
98 write_register(offset: OFFSET_INTERRUPT_ENABLE, value: control);
99}
100void ISerialDevice::SetModemOptions(serial_modem_control_t control, bool enable)
101{
102 byte_t byte = { .byte = read_register(offset: OFFSET_MODEM_CONTROL) };
103 switch (control)
104 {
105 case MODEM_DTR: byte.bits.b0 = enable; break;
106 case MODEM_RTS: byte.bits.b1 = enable; break;
107 case MODEM_UNUSED_PIN1: byte.bits.b2 = enable; break;
108 case MODEM_IRQ: byte.bits.b3 = enable; break;
109 case MODEM_LOOP: byte.bits.b4 = enable; break;
110 }
111 write_register(offset: OFFSET_MODEM_CONTROL, value: byte.byte);
112}
113char ISerialDevice::GetLineStatus()
114{
115 return read_register(offset: OFFSET_LINE_STATUS);
116}
117__maybe_unused char ISerialDevice::GetModelStatus()
118{
119 return read_register(offset: OFFSET_MODEM_STATUS);
120}
121bool ISerialDevice::GetDataReady()
122{
123 return GetLineStatus() & LINE_DATA_READY;
124}
125void ISerialDevice::WaitReadyToRead()
126{
127 while (!GetDataReady())
128 ;
129}
130void ISerialDevice::WaitReadyToWrite()
131{
132 while (!(GetLineStatus() & LINE_TRANSMITR_BUF_EMPTY))
133 ;
134}
135