Commit | Line | Data |
---|---|---|
61e12104 WK |
1 | /* |
2 | * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. | |
3 | * | |
4 | * This software is licensed under the terms of the GNU General Public | |
5 | * License version 2, as published by the Free Software Foundation, and | |
6 | * may be copied, distributed, and modified under those terms. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | */ | |
13 | ||
0ec473b5 JP |
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
15 | ||
61e12104 WK |
16 | #include <linux/kernel.h> |
17 | #include <linux/errno.h> | |
61e12104 WK |
18 | #include <linux/tty.h> |
19 | #include <linux/tty_driver.h> | |
20 | #include <linux/tty_flip.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/usb/cdc.h> | |
24 | #include <linux/serial.h> | |
25 | #include "gdm_tty.h" | |
26 | ||
27 | #define GDM_TTY_MAJOR 0 | |
28 | #define GDM_TTY_MINOR 32 | |
29 | ||
30 | #define ACM_CTRL_DTR 0x01 | |
31 | #define ACM_CTRL_RTS 0x02 | |
32 | #define ACM_CTRL_DSR 0x02 | |
33 | #define ACM_CTRL_RI 0x08 | |
34 | #define ACM_CTRL_DCD 0x01 | |
35 | ||
36 | #define WRITE_SIZE 2048 | |
37 | ||
38 | #define MUX_TX_MAX_SIZE 2048 | |
39 | ||
40 | #define gdm_tty_send(n, d, l, i, c, b) (\ | |
41 | n->tty_dev->send_func(n->tty_dev->priv_dev, d, l, i, c, b)) | |
42 | #define gdm_tty_recv(n, c) (\ | |
43 | n->tty_dev->recv_func(n->tty_dev->priv_dev, c)) | |
44 | #define gdm_tty_send_control(n, r, v, d, l) (\ | |
45 | n->tty_dev->send_control(n->tty_dev->priv_dev, r, v, d, l)) | |
46 | ||
7ee4c1b9 | 47 | #define GDM_TTY_READY(gdm) (gdm && gdm->tty_dev && gdm->port.count) |
61e12104 | 48 | |
7ca1ea66 FW |
49 | static struct tty_driver *gdm_driver[TTY_MAX_COUNT]; |
50 | static struct gdm *gdm_table[TTY_MAX_COUNT][GDM_TTY_MINOR]; | |
7ee4c1b9 | 51 | static DEFINE_MUTEX(gdm_table_lock); |
61e12104 WK |
52 | |
53 | static char *DRIVER_STRING[TTY_MAX_COUNT] = {"GCTATC", "GCTDM"}; | |
54 | static char *DEVICE_STRING[TTY_MAX_COUNT] = {"GCT-ATC", "GCT-DM"}; | |
55 | ||
7ee4c1b9 WK |
56 | static void gdm_port_destruct(struct tty_port *port) |
57 | { | |
58 | struct gdm *gdm = container_of(port, struct gdm, port); | |
59 | ||
60 | mutex_lock(&gdm_table_lock); | |
61 | gdm_table[gdm->index][gdm->minor] = NULL; | |
62 | mutex_unlock(&gdm_table_lock); | |
63 | ||
64 | kfree(gdm); | |
65 | } | |
61e12104 | 66 | |
e16a4884 | 67 | static const struct tty_port_operations gdm_port_ops = { |
7ee4c1b9 | 68 | .destruct = gdm_port_destruct, |
61e12104 WK |
69 | }; |
70 | ||
7ee4c1b9 | 71 | static int gdm_tty_install(struct tty_driver *driver, struct tty_struct *tty) |
61e12104 | 72 | { |
7ee4c1b9 WK |
73 | struct gdm *gdm = NULL; |
74 | int ret; | |
61e12104 | 75 | int i; |
7ee4c1b9 | 76 | int j; |
61e12104 | 77 | |
7ee4c1b9 | 78 | j = GDM_TTY_MINOR; |
61e12104 WK |
79 | for (i = 0; i < TTY_MAX_COUNT; i++) { |
80 | if (!strcmp(tty->driver->driver_name, DRIVER_STRING[i])) { | |
7ee4c1b9 | 81 | j = tty->index; |
61e12104 WK |
82 | break; |
83 | } | |
84 | } | |
85 | ||
7ee4c1b9 | 86 | if (j == GDM_TTY_MINOR) |
61e12104 | 87 | return -ENODEV; |
61e12104 | 88 | |
7ee4c1b9 WK |
89 | mutex_lock(&gdm_table_lock); |
90 | gdm = gdm_table[i][j]; | |
b6f6fd8a | 91 | if (!gdm) { |
bf0373f1 | 92 | mutex_unlock(&gdm_table_lock); |
7ee4c1b9 | 93 | return -ENODEV; |
bf0373f1 | 94 | } |
61e12104 | 95 | |
7ee4c1b9 | 96 | tty_port_get(&gdm->port); |
61e12104 | 97 | |
7ee4c1b9 WK |
98 | ret = tty_standard_install(driver, tty); |
99 | if (ret) { | |
100 | tty_port_put(&gdm->port); | |
bf0373f1 | 101 | mutex_unlock(&gdm_table_lock); |
7ee4c1b9 WK |
102 | return ret; |
103 | } | |
61e12104 | 104 | |
7ee4c1b9 | 105 | tty->driver_data = gdm; |
bf0373f1 | 106 | mutex_unlock(&gdm_table_lock); |
7ee4c1b9 WK |
107 | |
108 | return 0; | |
61e12104 WK |
109 | } |
110 | ||
7ee4c1b9 | 111 | static int gdm_tty_open(struct tty_struct *tty, struct file *filp) |
61e12104 | 112 | { |
7ee4c1b9 | 113 | struct gdm *gdm = tty->driver_data; |
f4ef08f0 | 114 | |
7ee4c1b9 WK |
115 | return tty_port_open(&gdm->port, tty, filp); |
116 | } | |
61e12104 | 117 | |
7ee4c1b9 WK |
118 | static void gdm_tty_cleanup(struct tty_struct *tty) |
119 | { | |
120 | struct gdm *gdm = tty->driver_data; | |
f4ef08f0 | 121 | |
7ee4c1b9 WK |
122 | tty_port_put(&gdm->port); |
123 | } | |
61e12104 | 124 | |
7ee4c1b9 WK |
125 | static void gdm_tty_hangup(struct tty_struct *tty) |
126 | { | |
127 | struct gdm *gdm = tty->driver_data; | |
f4ef08f0 | 128 | |
7ee4c1b9 WK |
129 | tty_port_hangup(&gdm->port); |
130 | } | |
61e12104 | 131 | |
7ee4c1b9 WK |
132 | static void gdm_tty_close(struct tty_struct *tty, struct file *filp) |
133 | { | |
134 | struct gdm *gdm = tty->driver_data; | |
f4ef08f0 | 135 | |
7ee4c1b9 | 136 | tty_port_close(&gdm->port, tty, filp); |
61e12104 WK |
137 | } |
138 | ||
bf0373f1 WK |
139 | static int gdm_tty_recv_complete(void *data, |
140 | int len, | |
141 | int index, | |
142 | struct tty_dev *tty_dev, | |
143 | int complete) | |
61e12104 | 144 | { |
bf0373f1 | 145 | struct gdm *gdm = tty_dev->gdm[index]; |
f4ef08f0 | 146 | |
7ee4c1b9 | 147 | if (!GDM_TTY_READY(gdm)) { |
61e12104 | 148 | if (complete == RECV_PACKET_PROCESS_COMPLETE) |
7ee4c1b9 | 149 | gdm_tty_recv(gdm, gdm_tty_recv_complete); |
61e12104 WK |
150 | return TO_HOST_PORT_CLOSE; |
151 | } | |
152 | ||
7ee4c1b9 WK |
153 | if (data && len) { |
154 | if (tty_buffer_request_room(&gdm->port, len) == len) { | |
155 | tty_insert_flip_string(&gdm->port, data, len); | |
156 | tty_flip_buffer_push(&gdm->port); | |
157 | } else { | |
158 | return TO_HOST_BUFFER_REQUEST_FAIL; | |
159 | } | |
61e12104 WK |
160 | } |
161 | ||
61e12104 | 162 | if (complete == RECV_PACKET_PROCESS_COMPLETE) |
7ee4c1b9 | 163 | gdm_tty_recv(gdm, gdm_tty_recv_complete); |
61e12104 | 164 | |
497a2e02 | 165 | return 0; |
61e12104 WK |
166 | } |
167 | ||
168 | static void gdm_tty_send_complete(void *arg) | |
169 | { | |
2594ca30 | 170 | struct gdm *gdm = arg; |
61e12104 | 171 | |
7ee4c1b9 | 172 | if (!GDM_TTY_READY(gdm)) |
61e12104 WK |
173 | return; |
174 | ||
7ee4c1b9 | 175 | tty_port_tty_wakeup(&gdm->port); |
61e12104 WK |
176 | } |
177 | ||
208b8671 EA |
178 | static int gdm_tty_write(struct tty_struct *tty, const unsigned char *buf, |
179 | int len) | |
61e12104 | 180 | { |
7ee4c1b9 | 181 | struct gdm *gdm = tty->driver_data; |
61e12104 WK |
182 | int remain = len; |
183 | int sent_len = 0; | |
184 | int sending_len = 0; | |
185 | ||
7ee4c1b9 | 186 | if (!GDM_TTY_READY(gdm)) |
61e12104 WK |
187 | return -ENODEV; |
188 | ||
189 | if (!len) | |
190 | return 0; | |
191 | ||
192 | while (1) { | |
208b8671 EA |
193 | sending_len = remain > MUX_TX_MAX_SIZE ? MUX_TX_MAX_SIZE : |
194 | remain; | |
7ee4c1b9 | 195 | gdm_tty_send(gdm, |
ba7f55b7 | 196 | (void *)(buf + sent_len), |
61e12104 | 197 | sending_len, |
7ee4c1b9 | 198 | gdm->index, |
61e12104 | 199 | gdm_tty_send_complete, |
7ee4c1b9 | 200 | gdm |
61e12104 WK |
201 | ); |
202 | sent_len += sending_len; | |
203 | remain -= sending_len; | |
204 | if (remain <= 0) | |
205 | break; | |
206 | } | |
207 | ||
208 | return len; | |
209 | } | |
210 | ||
211 | static int gdm_tty_write_room(struct tty_struct *tty) | |
212 | { | |
7ee4c1b9 | 213 | struct gdm *gdm = tty->driver_data; |
61e12104 | 214 | |
7ee4c1b9 | 215 | if (!GDM_TTY_READY(gdm)) |
61e12104 WK |
216 | return -ENODEV; |
217 | ||
218 | return WRITE_SIZE; | |
219 | } | |
220 | ||
7ee4c1b9 | 221 | int register_lte_tty_device(struct tty_dev *tty_dev, struct device *device) |
61e12104 | 222 | { |
7ee4c1b9 WK |
223 | struct gdm *gdm; |
224 | int i; | |
225 | int j; | |
61e12104 | 226 | |
7ee4c1b9 | 227 | for (i = 0; i < TTY_MAX_COUNT; i++) { |
7b7df122 | 228 | gdm = kmalloc(sizeof(*gdm), GFP_KERNEL); |
7ee4c1b9 WK |
229 | if (!gdm) |
230 | return -ENOMEM; | |
61e12104 | 231 | |
7ee4c1b9 | 232 | mutex_lock(&gdm_table_lock); |
61e12104 | 233 | for (j = 0; j < GDM_TTY_MINOR; j++) { |
7ee4c1b9 | 234 | if (!gdm_table[i][j]) |
61e12104 WK |
235 | break; |
236 | } | |
237 | ||
238 | if (j == GDM_TTY_MINOR) { | |
bf0373f1 | 239 | kfree(gdm); |
7ee4c1b9 WK |
240 | mutex_unlock(&gdm_table_lock); |
241 | return -EINVAL; | |
61e12104 WK |
242 | } |
243 | ||
7ee4c1b9 | 244 | gdm_table[i][j] = gdm; |
7ee4c1b9 | 245 | mutex_unlock(&gdm_table_lock); |
61e12104 | 246 | |
bf0373f1 | 247 | tty_dev->gdm[i] = gdm; |
7ee4c1b9 | 248 | tty_port_init(&gdm->port); |
bf0373f1 | 249 | |
7ee4c1b9 WK |
250 | gdm->port.ops = &gdm_port_ops; |
251 | gdm->index = i; | |
252 | gdm->minor = j; | |
253 | gdm->tty_dev = tty_dev; | |
61e12104 | 254 | |
208b8671 EA |
255 | tty_port_register_device(&gdm->port, gdm_driver[i], |
256 | gdm->minor, device); | |
7ee4c1b9 | 257 | } |
61e12104 WK |
258 | |
259 | for (i = 0; i < MAX_ISSUE_NUM; i++) | |
7ee4c1b9 | 260 | gdm_tty_recv(gdm, gdm_tty_recv_complete); |
61e12104 WK |
261 | |
262 | return 0; | |
263 | } | |
264 | ||
265 | void unregister_lte_tty_device(struct tty_dev *tty_dev) | |
266 | { | |
7ee4c1b9 WK |
267 | struct gdm *gdm; |
268 | struct tty_struct *tty; | |
61e12104 WK |
269 | int i; |
270 | ||
271 | for (i = 0; i < TTY_MAX_COUNT; i++) { | |
bf0373f1 WK |
272 | gdm = tty_dev->gdm[i]; |
273 | if (!gdm) | |
61e12104 WK |
274 | continue; |
275 | ||
7ee4c1b9 | 276 | mutex_lock(&gdm_table_lock); |
bf0373f1 | 277 | gdm_table[gdm->index][gdm->minor] = NULL; |
7ee4c1b9 | 278 | mutex_unlock(&gdm_table_lock); |
61e12104 | 279 | |
7ee4c1b9 WK |
280 | tty = tty_port_tty_get(&gdm->port); |
281 | if (tty) { | |
282 | tty_vhangup(tty); | |
283 | tty_kref_put(tty); | |
61e12104 | 284 | } |
61e12104 | 285 | |
bf0373f1 | 286 | tty_unregister_device(gdm_driver[i], gdm->minor); |
7ee4c1b9 | 287 | tty_port_put(&gdm->port); |
7ee4c1b9 | 288 | } |
61e12104 WK |
289 | } |
290 | ||
291 | static const struct tty_operations gdm_tty_ops = { | |
7ee4c1b9 WK |
292 | .install = gdm_tty_install, |
293 | .open = gdm_tty_open, | |
294 | .close = gdm_tty_close, | |
295 | .cleanup = gdm_tty_cleanup, | |
296 | .hangup = gdm_tty_hangup, | |
297 | .write = gdm_tty_write, | |
298 | .write_room = gdm_tty_write_room, | |
61e12104 WK |
299 | }; |
300 | ||
301 | int register_lte_tty_driver(void) | |
302 | { | |
7ee4c1b9 | 303 | struct tty_driver *tty_driver; |
61e12104 WK |
304 | int i; |
305 | int ret; | |
306 | ||
307 | for (i = 0; i < TTY_MAX_COUNT; i++) { | |
308 | tty_driver = alloc_tty_driver(GDM_TTY_MINOR); | |
7ee4c1b9 | 309 | if (!tty_driver) |
61e12104 | 310 | return -ENOMEM; |
61e12104 WK |
311 | |
312 | tty_driver->owner = THIS_MODULE; | |
313 | tty_driver->driver_name = DRIVER_STRING[i]; | |
314 | tty_driver->name = DEVICE_STRING[i]; | |
315 | tty_driver->major = GDM_TTY_MAJOR; | |
316 | tty_driver->type = TTY_DRIVER_TYPE_SERIAL; | |
317 | tty_driver->subtype = SERIAL_TYPE_NORMAL; | |
208b8671 EA |
318 | tty_driver->flags = TTY_DRIVER_REAL_RAW | |
319 | TTY_DRIVER_DYNAMIC_DEV; | |
61e12104 WK |
320 | tty_driver->init_termios = tty_std_termios; |
321 | tty_driver->init_termios.c_cflag = B9600 | CS8 | HUPCL | CLOCAL; | |
322 | tty_driver->init_termios.c_lflag = ISIG | ICANON | IEXTEN; | |
323 | tty_set_operations(tty_driver, &gdm_tty_ops); | |
324 | ||
325 | ret = tty_register_driver(tty_driver); | |
7ee4c1b9 WK |
326 | if (ret) { |
327 | put_tty_driver(tty_driver); | |
328 | return ret; | |
329 | } | |
61e12104 | 330 | |
7ee4c1b9 | 331 | gdm_driver[i] = tty_driver; |
61e12104 WK |
332 | } |
333 | ||
334 | return ret; | |
335 | } | |
336 | ||
337 | void unregister_lte_tty_driver(void) | |
338 | { | |
339 | struct tty_driver *tty_driver; | |
340 | int i; | |
341 | ||
342 | for (i = 0; i < TTY_MAX_COUNT; i++) { | |
7ee4c1b9 | 343 | tty_driver = gdm_driver[i]; |
61e12104 WK |
344 | if (tty_driver) { |
345 | tty_unregister_driver(tty_driver); | |
346 | put_tty_driver(tty_driver); | |
347 | } | |
348 | } | |
349 | } | |
7ee4c1b9 | 350 |