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 WriteByte(byte: challenge);
25 const auto response = ReadByte();
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] = ReadByte();
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 WriteByte(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}
76
77void ISerialDevice::SetDataBits()
78{
79 u8 control = read_register(offset: OFFSET_LINE_CONTROL);
80 control &= char_length;
81 write_register(offset: OFFSET_LINE_CONTROL, value: control);
82}
83
84void ISerialDevice::SetStopBits()
85{
86 byte_t control = { .byte = read_register(offset: OFFSET_LINE_CONTROL) };
87 control.bits.b1 = stop_bits == STOP_BITS_15_OR_2;
88 write_register(offset: OFFSET_LINE_CONTROL, value: control.byte);
89}
90
91void ISerialDevice::SetParity()
92{
93 u8 byte = read_register(offset: OFFSET_LINE_CONTROL);
94 byte |= ((u8) parity) << 3;
95 write_register(offset: OFFSET_LINE_CONTROL, value: byte);
96}
97
98void ISerialDevice::SetInterrupts(int interrupts)
99{
100 char control = read_register(offset: OFFSET_INTERRUPT_ENABLE);
101 control = interrupts;
102 write_register(offset: OFFSET_INTERRUPT_ENABLE, value: control);
103}
104
105void ISerialDevice::SetModemOptions(serial_modem_control_t control, bool enable)
106{
107 byte_t byte = { .byte = read_register(offset: OFFSET_MODEM_CONTROL) };
108 switch (control)
109 {
110 case MODEM_DTR: byte.bits.b0 = enable; break;
111 case MODEM_RTS: byte.bits.b1 = enable; break;
112 case MODEM_UNUSED_PIN1: byte.bits.b2 = enable; break;
113 case MODEM_IRQ: byte.bits.b3 = enable; break;
114 case MODEM_LOOP: byte.bits.b4 = enable; break;
115 }
116 write_register(offset: OFFSET_MODEM_CONTROL, value: byte.byte);
117}
118
119char ISerialDevice::GetLineStatus()
120{
121 return read_register(offset: OFFSET_LINE_STATUS);
122}
123
124__maybe_unused char ISerialDevice::GetModelStatus()
125{
126 return read_register(offset: OFFSET_MODEM_STATUS);
127}
128
129bool ISerialDevice::GetDataReady()
130{
131 return GetLineStatus() & LINE_DATA_READY;
132}
133
134void ISerialDevice::WaitReadyToRead()
135{
136 while (!GetDataReady())
137 ;
138}
139
140void ISerialDevice::WaitReadyToWrite()
141{
142 while (!(GetLineStatus() & LINE_TRANSMITR_BUF_EMPTY))
143 ;
144}
145