1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "mos/device/serial.h"
4
5#include <mos/mos_global.h>
6#include <mos/syslog/printk.h>
7
8typedef enum
9{
10 MODEM_DCTS = 1 << 0, // Clear To Send input has changed since last read.
11 MODEM_DDSR = 1 << 1, // Data Set Ready input has changed since last read.
12 MODEM_TERI = 1 << 2, // Ring Indicator input has changed since last read.
13 MODEM_DDCD = 1 << 3, // Data Carrier Detect input has changed since last read.
14 MODEM_CLEAR_TO_SEND = 1 << 4,
15 MODEM_DATA_SET_READY = 1 << 5,
16 MODEM_RING_INDICATOR = 1 << 6,
17 MODEM_DATA_CARRIER_DETECT = 1 << 7,
18} serial_modem_status_t;
19
20typedef enum
21{
22 MODEM_DTR = 1 << 0, // Data Terminal Ready
23 MODEM_RTS = 1 << 1, // Request To Send
24 MODEM_UNUSED_PIN1 = 1 << 2, // Unused
25 MODEM_IRQ = 1 << 3, // Interrupt Request
26 MODEM_LOOP = 1 << 4, // Loopback
27} serial_modem_control_t;
28
29typedef enum
30{
31 LINE_DATA_READY = 1 << 0, // Data ready to be read.
32 LINE_ERR_OVERRUN = 1 << 1, // There has been data lost.
33 LINE_ERR_PARITY = 1 << 2, // Parity error.
34 LINE_ERR_FRAMING = 1 << 3, // Stop bit is missing.
35 LINE_ERR_BREAK = 1 << 4, // Break detected.
36 LINE_TRANSMITR_BUF_EMPTY = 1 << 5, // (transmitter buffer is empty) Data can be sent.
37 LINE_TRANSMITR_EMPTY = 1 << 6, // Transmitter is not doing anything.
38 LINE_ERR_IMPENDING = 1 << 7, // There is an error with a word in the input buffer
39} serial_line_status_t;
40
41static void set_baudrate_divisor(serial_device_t *dev)
42{
43 // Set the most significant bit of the Line Control Register. This is the DLAB bit, and allows access to the divisor registers.
44 u8 reg = dev->driver->read_register(dev, OFFSET_LINE_CONTROL);
45 reg |= 0x80; // set DLAB bit
46
47 dev->driver->write_register(dev, OFFSET_LINE_CONTROL, reg);
48
49 // Send the least significant byte of the divisor value to [PORT + 0].
50 dev->driver->write_register(dev, OFFSET_DLAB_DIVISOR_LSB, dev->baudrate_divisor & 0xFF);
51
52 // Send the most significant byte of the divisor value to [PORT + 1].
53 dev->driver->write_register(dev, OFFSET_DLAB_DIVISOR_MSB, dev->baudrate_divisor >> 8);
54
55 // Clear the most significant bit of the Line Control Register.
56 reg &= ~0x80;
57 dev->driver->write_register(dev, OFFSET_LINE_CONTROL, reg);
58}
59
60static void set_data_bits(serial_device_t *dev)
61{
62 u8 control = dev->driver->read_register(dev, OFFSET_LINE_CONTROL);
63 control &= dev->char_length;
64 dev->driver->write_register(dev, OFFSET_LINE_CONTROL, control);
65}
66
67static void set_stop_bits(serial_device_t *dev)
68{
69 byte_t control = { .byte = dev->driver->read_register(dev, OFFSET_LINE_CONTROL) };
70 control.bits.b1 = dev->stop_bits == STOP_BITS_15_OR_2;
71 dev->driver->write_register(dev, OFFSET_LINE_CONTROL, control.byte);
72}
73
74static void set_parity(serial_device_t *dev, serial_parity_t parity)
75{
76 u8 byte = dev->driver->read_register(dev, OFFSET_LINE_CONTROL);
77 byte |= ((u8) parity) << 3;
78 dev->driver->write_register(dev, OFFSET_LINE_CONTROL, byte);
79}
80
81static void serial_set_interrupts(serial_device_t *dev, int interrupts)
82{
83 char control = dev->driver->read_register(dev, OFFSET_INTERRUPT_ENABLE);
84 control = interrupts;
85 dev->driver->write_register(dev, OFFSET_INTERRUPT_ENABLE, control);
86}
87
88static void serial_set_modem_options(serial_device_t *dev, serial_modem_control_t control, bool enable)
89{
90 byte_t byte = { .byte = dev->driver->read_register(dev, OFFSET_MODEM_CONTROL) };
91 switch (control)
92 {
93 case MODEM_DTR: byte.bits.b0 = enable; break;
94 case MODEM_RTS: byte.bits.b1 = enable; break;
95 case MODEM_UNUSED_PIN1: byte.bits.b2 = enable; break;
96 case MODEM_IRQ: byte.bits.b3 = enable; break;
97 case MODEM_LOOP: byte.bits.b4 = enable; break;
98 }
99 dev->driver->write_register(dev, OFFSET_MODEM_CONTROL, byte.byte);
100}
101
102static char serial_get_line_status(serial_device_t *dev)
103{
104 return dev->driver->read_register(dev, OFFSET_LINE_STATUS);
105}
106
107__maybe_unused static char serial_get_modem_status(serial_device_t *dev)
108{
109 return dev->driver->read_register(dev, OFFSET_MODEM_STATUS);
110}
111
112bool serial_device_setup(serial_device_t *device)
113{
114 serial_set_interrupts(dev: device, interrupts: INTERRUPT_NONE);
115 set_baudrate_divisor(device);
116 set_data_bits(device);
117 set_stop_bits(device);
118 set_parity(dev: device, parity: device->parity);
119
120 serial_set_modem_options(dev: device, control: MODEM_DTR, enable: true);
121 serial_set_modem_options(dev: device, control: MODEM_RTS, enable: true);
122
123 // Try send a byte to the serial port.
124 // If it fails, then the serial port is not connected.
125 {
126 const char challenge = 'H';
127 char response = { 0 };
128 serial_set_modem_options(dev: device, control: MODEM_LOOP, enable: true);
129 serial_device_write(device, data: &challenge, length: 1);
130 serial_device_read(device, data: &response, length: 1);
131 serial_set_modem_options(dev: device, control: MODEM_LOOP, enable: false);
132 if (response != 'H')
133 return false;
134 }
135
136 serial_set_modem_options(dev: device, control: MODEM_IRQ, enable: true);
137 serial_set_interrupts(dev: device, interrupts: INTERRUPT_DATA_AVAILABLE);
138 return true;
139}
140
141bool serial_dev_get_data_ready(serial_device_t *device)
142{
143 return serial_get_line_status(dev: device) & LINE_DATA_READY;
144}
145
146static void serial_dev_wait_ready_to_read(serial_device_t *device)
147{
148 while (!serial_dev_get_data_ready(device))
149 ;
150}
151
152static void serial_dev_wait_ready_to_write(serial_device_t *device)
153{
154 while (!(serial_get_line_status(dev: device) & LINE_TRANSMITR_BUF_EMPTY))
155 ;
156}
157
158int serial_device_write(serial_device_t *device, const char *data, size_t length)
159{
160 size_t i = 0;
161 for (; i < length; i++)
162 {
163 serial_dev_wait_ready_to_write(device);
164 device->driver->write_data(device, data[i]);
165 }
166 return i;
167}
168
169int serial_device_read(serial_device_t *device, char *data, size_t length)
170{
171 for (size_t i = 0; i < length; i++)
172 {
173 serial_dev_wait_ready_to_read(device);
174 data[i] = device->driver->read_data(device);
175 }
176
177 return length;
178}
179