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