forked from adrianomarto/soft_uart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
raspberry_soft_uart.c
executable file
·332 lines (292 loc) · 7.82 KB
/
raspberry_soft_uart.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#include "raspberry_soft_uart.h"
#include "queue.h"
#include <linux/gpio.h>
#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/ktime.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/version.h>
static irq_handler_t handle_rx_start(unsigned int irq, void* device, struct pt_regs* registers);
static enum hrtimer_restart handle_tx(struct hrtimer* timer);
static enum hrtimer_restart handle_rx(struct hrtimer* timer);
static void receive_character(unsigned int character);
static struct queue queue_tx;
static struct tty_struct* current_tty = NULL;
static DEFINE_MUTEX(current_tty_mutex);
static struct hrtimer timer_tx;
static struct hrtimer timer_rx;
static ktime_t period;
static int gpio_tx = 0;
static int gpio_rx = 0;
static int rx_bit_index = -1;
/**
* Initializes the Raspberry Soft UART infrastructure.
* This must be called during the module initialization.
* The GPIO pin used as TX is configured as output.
* The GPIO pin used as RX is configured as input.
* @param gpio_tx GPIO pin used as TX
* @param gpio_rx GPIO pin used as RX
* @return 1 if the initialization is successful. 0 otherwise.
*/
int raspberry_soft_uart_init(const int _gpio_tx, const int _gpio_rx)
{
bool success = true;
mutex_init(¤t_tty_mutex);
// Initializes the TX timer.
hrtimer_init(&timer_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
timer_tx.function = &handle_tx;
// Initializes the RX timer.
hrtimer_init(&timer_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
timer_rx.function = &handle_rx;
// Initializes the GPIO pins.
gpio_tx = _gpio_tx;
gpio_rx = _gpio_rx;
success &= gpio_request(gpio_tx, "soft_uart_tx") == 0;
success &= gpio_direction_output(gpio_tx, 1) == 0;
success &= gpio_request(gpio_rx, "soft_uart_rx") == 0;
success &= gpio_direction_input(gpio_rx) == 0;
// Initializes the interruption.
success &= request_irq(
gpio_to_irq(gpio_rx),
(irq_handler_t) handle_rx_start,
IRQF_TRIGGER_FALLING,
"soft_uart_irq_handler",
NULL) == 0;
disable_irq(gpio_to_irq(gpio_rx));
return success;
}
/**
* Finalizes the Raspberry Soft UART infrastructure.
*/
int raspberry_soft_uart_finalize(void)
{
free_irq(gpio_to_irq(gpio_rx), NULL);
gpio_set_value(gpio_tx, 0);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return 1;
}
/**
* Opens the Soft UART.
* @param tty
* @return 1 if the operation is successful. 0 otherwise.
*/
int raspberry_soft_uart_open(struct tty_struct* tty)
{
int success = 0;
mutex_lock(¤t_tty_mutex);
if (current_tty == NULL)
{
current_tty = tty;
initialize_queue(&queue_tx);
success = 1;
enable_irq(gpio_to_irq(gpio_rx));
}
mutex_unlock(¤t_tty_mutex);
return success;
}
/**
* Closes the Soft UART.
*/
int raspberry_soft_uart_close(void)
{
int success = 0;
mutex_lock(¤t_tty_mutex);
if (current_tty != NULL)
{
disable_irq(gpio_to_irq(gpio_rx));
hrtimer_cancel(&timer_tx);
hrtimer_cancel(&timer_rx);
current_tty = NULL;
success = 1;
}
mutex_unlock(¤t_tty_mutex);
return success;
}
/**
* Sets the Soft UART baudrate.
* @param baudrate desired baudrate
* @return 1 if the operation is successful. 0 otherwise.
*/
int raspberry_soft_uart_set_baudrate(const int baudrate)
{
period = ktime_set(0, 1000000000/baudrate);
gpio_set_debounce(gpio_rx, 1000/baudrate/2);
return 1;
}
/**
* Adds a given string to the TX queue.
* @paran string given string
* @param string_size size of the given string
* @return The amount of characters successfully added to the queue.
*/
int raspberry_soft_uart_send_string(const unsigned char* string, int string_size)
{
int result = enqueue_string(&queue_tx, string, string_size);
// Starts the TX timer if it is not already running.
if (!hrtimer_active(&timer_tx))
{
hrtimer_start(&timer_tx, period, HRTIMER_MODE_REL);
}
return result;
}
/*
* Gets the number of characters that can be added to the TX queue.
* @return number of characters.
*/
int raspberry_soft_uart_get_tx_queue_room(void)
{
return get_queue_room(&queue_tx);
}
/*
* Gets the number of characters in the TX queue.
* @return number of characters.
*/
int raspberry_soft_uart_get_tx_queue_size(void)
{
return get_queue_size(&queue_tx);
}
//-----------------------------------------------------------------------------
// Internals
//-----------------------------------------------------------------------------
/**
* If we are waiting for the RX start bit, then starts the RX timer. Otherwise,
* does nothing.
*/
static irq_handler_t handle_rx_start(unsigned int irq, void* device, struct pt_regs* registers)
{
if (rx_bit_index == -1)
{
hrtimer_start(&timer_rx, ktime_set(0, 0), HRTIMER_MODE_REL);
}
return (irq_handler_t) IRQ_HANDLED;
}
/**
* Dequeues a character from the TX queue and sends it.
*/
static enum hrtimer_restart handle_tx(struct hrtimer* timer)
{
ktime_t current_time = ktime_get();
static unsigned char mode = 0;
static unsigned char data = 0;
static int bit_index = -1;
enum hrtimer_restart result = HRTIMER_NORESTART;
bool must_restart_timer = false;
// Start bit.
if (bit_index == -1)
{
if (get_queue_size(&queue_tx) > 1) {
dequeue_character(&queue_tx, &mode);
if (dequeue_character(&queue_tx, &data))
{
gpio_set_value(gpio_tx, 0);
bit_index++;
must_restart_timer = true;
}
}
}
// Data bits.
else if (0 <= bit_index && bit_index < 8)
{
gpio_set_value(gpio_tx, 1 & (data >> bit_index));
bit_index++;
must_restart_timer = true;
}
// Mode bit.
else if (bit_index == 8)
{
gpio_set_value(gpio_tx, 1 & mode);
bit_index++;
must_restart_timer = true;
}
// Stop bit.
else if (bit_index == 9)
{
gpio_set_value(gpio_tx, 1);
mode = 0;
data = 0;
bit_index = -1;
must_restart_timer = get_queue_size(&queue_tx) > 0;
}
// Restarts the TX timer.
if (must_restart_timer)
{
hrtimer_forward(&timer_tx, current_time, period);
result = HRTIMER_RESTART;
}
return result;
}
/*
* Receives a character and sends it to the kernel.
*/
static enum hrtimer_restart handle_rx(struct hrtimer* timer)
{
ktime_t current_time = ktime_get();
static unsigned int character = 0;
int bit_value = gpio_get_value(gpio_rx);
enum hrtimer_restart result = HRTIMER_NORESTART;
bool must_restart_timer = false;
// Start bit.
if (rx_bit_index == -1)
{
rx_bit_index++;
character = 0;
must_restart_timer = true;
}
// Data bits.
else if (0 <= rx_bit_index && rx_bit_index < 9)
{
if (bit_value == 0)
{
character &= 0x1fdff; //0xfeff;
}
else
{
character |= 0x0200; //0x0100;
}
rx_bit_index++;
character >>= 1;
must_restart_timer = true;
}
// Stop bit.
else if (rx_bit_index == 9)
{
receive_character(character);
rx_bit_index = -1;
}
// Restarts the RX timer.
if (must_restart_timer)
{
hrtimer_forward(&timer_rx, current_time, period);
result = HRTIMER_RESTART;
}
return result;
}
/**
* Adds a given (received) character to the RX buffer, which is managed by the kernel,
* and then flushes (flip) it.
* @param character given character
*/
void receive_character(unsigned int character)
{
unsigned char mode = character >> 8;
unsigned char data = character;
mutex_lock(¤t_tty_mutex);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
if (current_tty != NULL && current_tty->port != NULL)
{
tty_insert_flip_char(current_tty->port, mode, TTY_NORMAL);
tty_insert_flip_char(current_tty->port, data, TTY_NORMAL);
tty_flip_buffer_push(current_tty->port);
}
#else
if (tty != NULL)
{
tty_insert_flip_char(current_tty, mode, TTY_NORMAL);
tty_insert_flip_char(current_tty, data, TTY_NORMAL);
tty_flip_buffer_push(tty);
}
#endif
mutex_unlock(¤t_tty_mutex);
}