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++) { |
61e12104 | 228 | |
7b7df122 | 229 | gdm = kmalloc(sizeof(*gdm), GFP_KERNEL); |
7ee4c1b9 WK |
230 | if (!gdm) |
231 | return -ENOMEM; | |
61e12104 | 232 | |
7ee4c1b9 | 233 | mutex_lock(&gdm_table_lock); |
61e12104 | 234 | for (j = 0; j < GDM_TTY_MINOR; j++) { |
7ee4c1b9 | 235 | if (!gdm_table[i][j]) |
61e12104 WK |
236 | break; |
237 | } | |
238 | ||
239 | if (j == GDM_TTY_MINOR) { | |
bf0373f1 | 240 | kfree(gdm); |
7ee4c1b9 WK |
241 | mutex_unlock(&gdm_table_lock); |
242 | return -EINVAL; | |
61e12104 WK |
243 | } |
244 | ||
7ee4c1b9 | 245 | gdm_table[i][j] = gdm; |
7ee4c1b9 | 246 | mutex_unlock(&gdm_table_lock); |
61e12104 | 247 | |
bf0373f1 | 248 | tty_dev->gdm[i] = gdm; |
7ee4c1b9 | 249 | tty_port_init(&gdm->port); |
bf0373f1 | 250 | |
7ee4c1b9 WK |
251 | gdm->port.ops = &gdm_port_ops; |
252 | gdm->index = i; | |
253 | gdm->minor = j; | |
254 | gdm->tty_dev = tty_dev; | |
61e12104 | 255 | |
208b8671 EA |
256 | tty_port_register_device(&gdm->port, gdm_driver[i], |
257 | gdm->minor, device); | |
7ee4c1b9 | 258 | } |
61e12104 WK |
259 | |
260 | for (i = 0; i < MAX_ISSUE_NUM; i++) | |
7ee4c1b9 | 261 | gdm_tty_recv(gdm, gdm_tty_recv_complete); |
61e12104 WK |
262 | |
263 | return 0; | |
264 | } | |
265 | ||
266 | void unregister_lte_tty_device(struct tty_dev *tty_dev) | |
267 | { | |
7ee4c1b9 WK |
268 | struct gdm *gdm; |
269 | struct tty_struct *tty; | |
61e12104 WK |
270 | int i; |
271 | ||
272 | for (i = 0; i < TTY_MAX_COUNT; i++) { | |
bf0373f1 WK |
273 | gdm = tty_dev->gdm[i]; |
274 | if (!gdm) | |
61e12104 WK |
275 | continue; |
276 | ||
7ee4c1b9 | 277 | mutex_lock(&gdm_table_lock); |
bf0373f1 | 278 | gdm_table[gdm->index][gdm->minor] = NULL; |
7ee4c1b9 | 279 | mutex_unlock(&gdm_table_lock); |
61e12104 | 280 | |
7ee4c1b9 WK |
281 | tty = tty_port_tty_get(&gdm->port); |
282 | if (tty) { | |
283 | tty_vhangup(tty); | |
284 | tty_kref_put(tty); | |
61e12104 | 285 | } |
61e12104 | 286 | |
bf0373f1 | 287 | tty_unregister_device(gdm_driver[i], gdm->minor); |
7ee4c1b9 | 288 | tty_port_put(&gdm->port); |
7ee4c1b9 | 289 | } |
61e12104 WK |
290 | } |
291 | ||
292 | static const struct tty_operations gdm_tty_ops = { | |
7ee4c1b9 WK |
293 | .install = gdm_tty_install, |
294 | .open = gdm_tty_open, | |
295 | .close = gdm_tty_close, | |
296 | .cleanup = gdm_tty_cleanup, | |
297 | .hangup = gdm_tty_hangup, | |
298 | .write = gdm_tty_write, | |
299 | .write_room = gdm_tty_write_room, | |
61e12104 WK |
300 | }; |
301 | ||
302 | int register_lte_tty_driver(void) | |
303 | { | |
7ee4c1b9 | 304 | struct tty_driver *tty_driver; |
61e12104 WK |
305 | int i; |
306 | int ret; | |
307 | ||
308 | for (i = 0; i < TTY_MAX_COUNT; i++) { | |
309 | tty_driver = alloc_tty_driver(GDM_TTY_MINOR); | |
7ee4c1b9 | 310 | if (!tty_driver) |
61e12104 | 311 | return -ENOMEM; |
61e12104 WK |
312 | |
313 | tty_driver->owner = THIS_MODULE; | |
314 | tty_driver->driver_name = DRIVER_STRING[i]; | |
315 | tty_driver->name = DEVICE_STRING[i]; | |
316 | tty_driver->major = GDM_TTY_MAJOR; | |
317 | tty_driver->type = TTY_DRIVER_TYPE_SERIAL; | |
318 | tty_driver->subtype = SERIAL_TYPE_NORMAL; | |
208b8671 EA |
319 | tty_driver->flags = TTY_DRIVER_REAL_RAW | |
320 | TTY_DRIVER_DYNAMIC_DEV; | |
61e12104 WK |
321 | tty_driver->init_termios = tty_std_termios; |
322 | tty_driver->init_termios.c_cflag = B9600 | CS8 | HUPCL | CLOCAL; | |
323 | tty_driver->init_termios.c_lflag = ISIG | ICANON | IEXTEN; | |
324 | tty_set_operations(tty_driver, &gdm_tty_ops); | |
325 | ||
326 | ret = tty_register_driver(tty_driver); | |
7ee4c1b9 WK |
327 | if (ret) { |
328 | put_tty_driver(tty_driver); | |
329 | return ret; | |
330 | } | |
61e12104 | 331 | |
7ee4c1b9 | 332 | gdm_driver[i] = tty_driver; |
61e12104 WK |
333 | } |
334 | ||
335 | return ret; | |
336 | } | |
337 | ||
338 | void unregister_lte_tty_driver(void) | |
339 | { | |
340 | struct tty_driver *tty_driver; | |
341 | int i; | |
342 | ||
343 | for (i = 0; i < TTY_MAX_COUNT; i++) { | |
7ee4c1b9 | 344 | tty_driver = gdm_driver[i]; |
61e12104 WK |
345 | if (tty_driver) { |
346 | tty_unregister_driver(tty_driver); | |
347 | put_tty_driver(tty_driver); | |
348 | } | |
349 | } | |
350 | } | |
7ee4c1b9 | 351 |