blob: e53ceea7ec355ffeb083a5413a03d8fbe43ba0c0 [file] [log] [blame]
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -08001/*
2 * Fintek F81232 USB to serial adaptor driver
3 *
4 * Copyright (C) 2012 Greg Kroah-Hartman (gregkh@linuxfoundation.org)
5 * Copyright (C) 2012 Linux Foundation
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published by
9 * the Free Software Foundation.
10 *
11 */
12
13#include <linux/kernel.h>
14#include <linux/errno.h>
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080015#include <linux/slab.h>
16#include <linux/tty.h>
17#include <linux/tty_driver.h>
18#include <linux/tty_flip.h>
19#include <linux/serial.h>
20#include <linux/module.h>
21#include <linux/moduleparam.h>
Peter Hung7139c932015-03-17 17:48:21 +080022#include <linux/mutex.h>
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080023#include <linux/uaccess.h>
24#include <linux/usb.h>
25#include <linux/usb/serial.h>
Peter Hung88850782015-03-17 17:48:20 +080026#include <linux/serial_reg.h>
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080027
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080028static const struct usb_device_id id_table[] = {
29 { USB_DEVICE(0x1934, 0x0706) },
30 { } /* Terminating entry */
31};
32MODULE_DEVICE_TABLE(usb, id_table);
33
Peter Hung87fe5ad2015-03-17 17:48:22 +080034/* USB Control EP parameter */
35#define F81232_REGISTER_REQUEST 0xa0
36#define F81232_GET_REGISTER 0xc0
37
38#define SERIAL_BASE_ADDRESS 0x0120
39#define MODEM_STATUS_REGISTER (0x06 + SERIAL_BASE_ADDRESS)
40
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080041#define CONTROL_DTR 0x01
42#define CONTROL_RTS 0x02
43
44#define UART_STATE 0x08
45#define UART_STATE_TRANSIENT_MASK 0x74
46#define UART_DCD 0x01
47#define UART_DSR 0x02
48#define UART_BREAK_ERROR 0x04
49#define UART_RING 0x08
50#define UART_FRAME_ERROR 0x10
51#define UART_PARITY_ERROR 0x20
52#define UART_OVERRUN_ERROR 0x40
53#define UART_CTS 0x80
54
55struct f81232_private {
Peter Hung7139c932015-03-17 17:48:21 +080056 struct mutex lock;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080057 u8 line_control;
Peter Hungb830d072015-03-17 17:48:19 +080058 u8 modem_status;
Peter Hung87fe5ad2015-03-17 17:48:22 +080059 struct work_struct interrupt_work;
60 struct usb_serial_port *port;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -080061};
62
Peter Hung87fe5ad2015-03-17 17:48:22 +080063static int f81232_get_register(struct usb_serial_port *port, u16 reg, u8 *val)
64{
65 int status;
66 u8 *tmp;
67 struct usb_device *dev = port->serial->dev;
68
69 tmp = kmalloc(sizeof(*val), GFP_KERNEL);
70 if (!tmp)
71 return -ENOMEM;
72
73 status = usb_control_msg(dev,
74 usb_rcvctrlpipe(dev, 0),
75 F81232_REGISTER_REQUEST,
76 F81232_GET_REGISTER,
77 reg,
78 0,
79 tmp,
80 sizeof(*val),
81 USB_CTRL_GET_TIMEOUT);
82 if (status != sizeof(*val)) {
83 dev_err(&port->dev, "%s failed status: %d\n", __func__, status);
84
85 if (status < 0)
86 status = usb_translate_errors(status);
87 else
88 status = -EIO;
89 } else {
90 status = 0;
91 *val = *tmp;
92 }
93
94 kfree(tmp);
95 return status;
96}
97
98static void f81232_read_msr(struct usb_serial_port *port)
99{
100 int status;
101 u8 current_msr;
102 struct tty_struct *tty;
103 struct f81232_private *priv = usb_get_serial_port_data(port);
104
105 mutex_lock(&priv->lock);
106 status = f81232_get_register(port, MODEM_STATUS_REGISTER,
107 &current_msr);
108 if (status) {
109 dev_err(&port->dev, "%s fail, status: %d\n", __func__, status);
110 mutex_unlock(&priv->lock);
111 return;
112 }
113
114 if (!(current_msr & UART_MSR_ANY_DELTA)) {
115 mutex_unlock(&priv->lock);
116 return;
117 }
118
119 priv->modem_status = current_msr;
120
121 if (current_msr & UART_MSR_DCTS)
122 port->icount.cts++;
123 if (current_msr & UART_MSR_DDSR)
124 port->icount.dsr++;
125 if (current_msr & UART_MSR_TERI)
126 port->icount.rng++;
127 if (current_msr & UART_MSR_DDCD) {
128 port->icount.dcd++;
129 tty = tty_port_tty_get(&port->port);
130 if (tty) {
131 usb_serial_handle_dcd_change(port, tty,
132 current_msr & UART_MSR_DCD);
133
134 tty_kref_put(tty);
135 }
136 }
137
138 wake_up_interruptible(&port->port.delta_msr_wait);
139 mutex_unlock(&priv->lock);
140}
141
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800142static void f81232_update_line_status(struct usb_serial_port *port,
143 unsigned char *data,
Peter Hung87fe5ad2015-03-17 17:48:22 +0800144 size_t actual_length)
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800145{
Peter Hung87fe5ad2015-03-17 17:48:22 +0800146 struct f81232_private *priv = usb_get_serial_port_data(port);
147
148 if (!actual_length)
149 return;
150
151 switch (data[0] & 0x07) {
152 case 0x00: /* msr change */
153 dev_dbg(&port->dev, "IIR: MSR Change: %02x\n", data[0]);
154 schedule_work(&priv->interrupt_work);
155 break;
156 case 0x02: /* tx-empty */
157 break;
158 case 0x04: /* rx data available */
159 break;
160 case 0x06: /* lsr change */
161 /* we can forget it. the LSR will read from bulk-in */
162 dev_dbg(&port->dev, "IIR: LSR Change: %02x\n", data[0]);
163 break;
164 }
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800165}
166
167static void f81232_read_int_callback(struct urb *urb)
168{
169 struct usb_serial_port *port = urb->context;
170 unsigned char *data = urb->transfer_buffer;
171 unsigned int actual_length = urb->actual_length;
172 int status = urb->status;
173 int retval;
174
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800175 switch (status) {
176 case 0:
177 /* success */
178 break;
179 case -ECONNRESET:
180 case -ENOENT:
181 case -ESHUTDOWN:
182 /* this urb is terminated, clean up */
Greg Kroah-Hartmana94e9b92012-05-15 16:27:17 -0700183 dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
184 __func__, status);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800185 return;
186 default:
Greg Kroah-Hartmana94e9b92012-05-15 16:27:17 -0700187 dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
188 __func__, status);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800189 goto exit;
190 }
191
Greg Kroah-Hartman59d33f22012-09-18 09:58:57 +0100192 usb_serial_debug_data(&port->dev, __func__,
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800193 urb->actual_length, urb->transfer_buffer);
194
195 f81232_update_line_status(port, data, actual_length);
196
197exit:
198 retval = usb_submit_urb(urb, GFP_ATOMIC);
199 if (retval)
200 dev_err(&urb->dev->dev,
201 "%s - usb_submit_urb failed with result %d\n",
202 __func__, retval);
203}
204
205static void f81232_process_read_urb(struct urb *urb)
206{
207 struct usb_serial_port *port = urb->context;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800208 unsigned char *data = urb->transfer_buffer;
Peter Hung88850782015-03-17 17:48:20 +0800209 char tty_flag;
210 unsigned int i;
211 u8 lsr;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800212
Peter Hung88850782015-03-17 17:48:20 +0800213 /*
214 * When opening the port we get a 1-byte packet with the current LSR,
215 * which we discard.
216 */
217 if ((urb->actual_length < 2) || (urb->actual_length % 2))
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800218 return;
219
Peter Hung88850782015-03-17 17:48:20 +0800220 /* bulk-in data: [LSR(1Byte)+DATA(1Byte)][LSR(1Byte)+DATA(1Byte)]... */
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800221
Peter Hung88850782015-03-17 17:48:20 +0800222 for (i = 0; i < urb->actual_length; i += 2) {
223 tty_flag = TTY_NORMAL;
224 lsr = data[i];
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800225
Peter Hung88850782015-03-17 17:48:20 +0800226 if (lsr & UART_LSR_BRK_ERROR_BITS) {
227 if (lsr & UART_LSR_BI) {
228 tty_flag = TTY_BREAK;
229 port->icount.brk++;
230 usb_serial_handle_break(port);
231 } else if (lsr & UART_LSR_PE) {
232 tty_flag = TTY_PARITY;
233 port->icount.parity++;
234 } else if (lsr & UART_LSR_FE) {
235 tty_flag = TTY_FRAME;
236 port->icount.frame++;
237 }
238
239 if (lsr & UART_LSR_OE) {
240 port->icount.overrun++;
241 tty_insert_flip_char(&port->port, 0,
242 TTY_OVERRUN);
243 }
244 }
245
246 if (port->port.console && port->sysrq) {
247 if (usb_serial_handle_sysrq_char(port, data[i + 1]))
248 continue;
249 }
250
251 tty_insert_flip_char(&port->port, data[i + 1], tty_flag);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800252 }
253
Jiri Slaby2e124b42013-01-03 15:53:06 +0100254 tty_flip_buffer_push(&port->port);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800255}
256
257static int set_control_lines(struct usb_device *dev, u8 value)
258{
259 /* FIXME - Stubbed out for now */
260 return 0;
261}
262
263static void f81232_break_ctl(struct tty_struct *tty, int break_state)
264{
265 /* FIXME - Stubbed out for now */
266
267 /*
268 * break_state = -1 to turn on break, and 0 to turn off break
269 * see drivers/char/tty_io.c to see it used.
270 * last_set_data_urb_value NEVER has the break bit set in it.
271 */
272}
273
274static void f81232_set_termios(struct tty_struct *tty,
275 struct usb_serial_port *port, struct ktermios *old_termios)
276{
277 /* FIXME - Stubbed out for now */
278
279 /* Don't change anything if nothing has changed */
Johan Hovold21886722013-06-10 18:29:37 +0200280 if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800281 return;
282
283 /* Do the real work here... */
Johan Hovold21886722013-06-10 18:29:37 +0200284 if (old_termios)
285 tty_termios_copy_hw(&tty->termios, old_termios);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800286}
287
288static int f81232_tiocmget(struct tty_struct *tty)
289{
290 /* FIXME - Stubbed out for now */
291 return 0;
292}
293
294static int f81232_tiocmset(struct tty_struct *tty,
295 unsigned int set, unsigned int clear)
296{
297 /* FIXME - Stubbed out for now */
298 return 0;
299}
300
301static int f81232_open(struct tty_struct *tty, struct usb_serial_port *port)
302{
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800303 int result;
304
305 /* Setup termios */
306 if (tty)
Johan Hovold21886722013-06-10 18:29:37 +0200307 f81232_set_termios(tty, port, NULL);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800308
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800309 result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
310 if (result) {
311 dev_err(&port->dev, "%s - failed submitting interrupt urb,"
312 " error %d\n", __func__, result);
313 return result;
314 }
315
316 result = usb_serial_generic_open(tty, port);
317 if (result) {
318 usb_kill_urb(port->interrupt_in_urb);
319 return result;
320 }
321
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800322 return 0;
323}
324
325static void f81232_close(struct usb_serial_port *port)
326{
327 usb_serial_generic_close(port);
328 usb_kill_urb(port->interrupt_in_urb);
329}
330
331static void f81232_dtr_rts(struct usb_serial_port *port, int on)
332{
333 struct f81232_private *priv = usb_get_serial_port_data(port);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800334 u8 control;
335
Peter Hung7139c932015-03-17 17:48:21 +0800336 mutex_lock(&priv->lock);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800337 /* Change DTR and RTS */
338 if (on)
339 priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
340 else
341 priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
342 control = priv->line_control;
Peter Hung7139c932015-03-17 17:48:21 +0800343 mutex_unlock(&priv->lock);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800344 set_control_lines(port->serial->dev, control);
345}
346
347static int f81232_carrier_raised(struct usb_serial_port *port)
348{
349 struct f81232_private *priv = usb_get_serial_port_data(port);
Peter Hungb830d072015-03-17 17:48:19 +0800350 if (priv->modem_status & UART_DCD)
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800351 return 1;
352 return 0;
353}
354
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800355static int f81232_ioctl(struct tty_struct *tty,
356 unsigned int cmd, unsigned long arg)
357{
358 struct serial_struct ser;
359 struct usb_serial_port *port = tty->driver_data;
Greg Kroah-Hartmana94e9b92012-05-15 16:27:17 -0700360
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800361 switch (cmd) {
362 case TIOCGSERIAL:
363 memset(&ser, 0, sizeof ser);
364 ser.type = PORT_16654;
Greg Kroah-Hartmane5b1e202013-06-07 11:04:28 -0700365 ser.line = port->minor;
Greg Kroah-Hartman11438322013-06-06 10:32:00 -0700366 ser.port = port->port_number;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800367 ser.baud_base = 460800;
368
369 if (copy_to_user((void __user *)arg, &ser, sizeof ser))
370 return -EFAULT;
371
372 return 0;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800373 default:
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800374 break;
375 }
376 return -ENOIOCTLCMD;
377}
378
Peter Hung87fe5ad2015-03-17 17:48:22 +0800379static void f81232_interrupt_work(struct work_struct *work)
380{
381 struct f81232_private *priv =
382 container_of(work, struct f81232_private, interrupt_work);
383
384 f81232_read_msr(priv->port);
385}
386
Johan Hovold3124d1d2012-10-17 13:34:56 +0200387static int f81232_port_probe(struct usb_serial_port *port)
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800388{
389 struct f81232_private *priv;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800390
Johan Hovold3124d1d2012-10-17 13:34:56 +0200391 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
392 if (!priv)
393 return -ENOMEM;
394
Peter Hung7139c932015-03-17 17:48:21 +0800395 mutex_init(&priv->lock);
Peter Hung87fe5ad2015-03-17 17:48:22 +0800396 INIT_WORK(&priv->interrupt_work, f81232_interrupt_work);
Johan Hovold3124d1d2012-10-17 13:34:56 +0200397
398 usb_set_serial_port_data(port, priv);
399
Johan Hovoldd7be6222013-06-26 16:47:23 +0200400 port->port.drain_delay = 256;
Peter Hung87fe5ad2015-03-17 17:48:22 +0800401 priv->port = port;
Johan Hovoldd7be6222013-06-26 16:47:23 +0200402
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800403 return 0;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800404}
405
Johan Hovold3124d1d2012-10-17 13:34:56 +0200406static int f81232_port_remove(struct usb_serial_port *port)
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800407{
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800408 struct f81232_private *priv;
409
Johan Hovold3124d1d2012-10-17 13:34:56 +0200410 priv = usb_get_serial_port_data(port);
411 kfree(priv);
412
413 return 0;
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800414}
415
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800416static struct usb_serial_driver f81232_device = {
417 .driver = {
418 .owner = THIS_MODULE,
419 .name = "f81232",
420 },
421 .id_table = id_table,
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800422 .num_ports = 1,
423 .bulk_in_size = 256,
424 .bulk_out_size = 256,
425 .open = f81232_open,
426 .close = f81232_close,
427 .dtr_rts = f81232_dtr_rts,
428 .carrier_raised = f81232_carrier_raised,
429 .ioctl = f81232_ioctl,
430 .break_ctl = f81232_break_ctl,
431 .set_termios = f81232_set_termios,
432 .tiocmget = f81232_tiocmget,
433 .tiocmset = f81232_tiocmset,
Johan Hovoldc50db822013-12-29 19:22:58 +0100434 .tiocmiwait = usb_serial_generic_tiocmiwait,
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800435 .process_read_urb = f81232_process_read_urb,
436 .read_int_callback = f81232_read_int_callback,
Johan Hovold3124d1d2012-10-17 13:34:56 +0200437 .port_probe = f81232_port_probe,
438 .port_remove = f81232_port_remove,
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800439};
440
441static struct usb_serial_driver * const serial_drivers[] = {
442 &f81232_device,
443 NULL,
444};
445
Greg Kroah-Hartman68e24112012-05-08 15:46:14 -0700446module_usb_serial_driver(serial_drivers, id_table);
Greg Kroah-Hartmanaac1fc32012-02-28 13:36:35 -0800447
448MODULE_DESCRIPTION("Fintek F81232 USB to serial adaptor driver");
449MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org");
450MODULE_LICENSE("GPL v2");