18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * USB ZyXEL omni.net LCD PLUS driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2013,2017 Johan Hovold <johan@kernel.org> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * See Documentation/usb/usb-serial.rst for more information on using this 88c2ecf20Sopenharmony_ci * driver 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Please report both successes and troubles to the author at omninet@kroah.com 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/errno.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/tty.h> 178c2ecf20Sopenharmony_ci#include <linux/tty_driver.h> 188c2ecf20Sopenharmony_ci#include <linux/tty_flip.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 218c2ecf20Sopenharmony_ci#include <linux/usb.h> 228c2ecf20Sopenharmony_ci#include <linux/usb/serial.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define DRIVER_AUTHOR "Alessandro Zummo" 258c2ecf20Sopenharmony_ci#define DRIVER_DESC "USB ZyXEL omni.net LCD PLUS Driver" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define ZYXEL_VENDOR_ID 0x0586 288c2ecf20Sopenharmony_ci#define ZYXEL_OMNINET_ID 0x1000 298c2ecf20Sopenharmony_ci#define ZYXEL_OMNI_56K_PLUS_ID 0x1500 308c2ecf20Sopenharmony_ci/* This one seems to be a re-branded ZyXEL device */ 318c2ecf20Sopenharmony_ci#define BT_IGNITIONPRO_ID 0x2000 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* function prototypes */ 348c2ecf20Sopenharmony_cistatic void omninet_process_read_urb(struct urb *urb); 358c2ecf20Sopenharmony_cistatic int omninet_prepare_write_buffer(struct usb_serial_port *port, 368c2ecf20Sopenharmony_ci void *buf, size_t count); 378c2ecf20Sopenharmony_cistatic int omninet_calc_num_ports(struct usb_serial *serial, 388c2ecf20Sopenharmony_ci struct usb_serial_endpoints *epds); 398c2ecf20Sopenharmony_cistatic int omninet_port_probe(struct usb_serial_port *port); 408c2ecf20Sopenharmony_cistatic int omninet_port_remove(struct usb_serial_port *port); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic const struct usb_device_id id_table[] = { 438c2ecf20Sopenharmony_ci { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNINET_ID) }, 448c2ecf20Sopenharmony_ci { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNI_56K_PLUS_ID) }, 458c2ecf20Sopenharmony_ci { USB_DEVICE(ZYXEL_VENDOR_ID, BT_IGNITIONPRO_ID) }, 468c2ecf20Sopenharmony_ci { } /* Terminating entry */ 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, id_table); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic struct usb_serial_driver zyxel_omninet_device = { 518c2ecf20Sopenharmony_ci .driver = { 528c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 538c2ecf20Sopenharmony_ci .name = "omninet", 548c2ecf20Sopenharmony_ci }, 558c2ecf20Sopenharmony_ci .description = "ZyXEL - omni.net lcd plus usb", 568c2ecf20Sopenharmony_ci .id_table = id_table, 578c2ecf20Sopenharmony_ci .num_bulk_out = 2, 588c2ecf20Sopenharmony_ci .calc_num_ports = omninet_calc_num_ports, 598c2ecf20Sopenharmony_ci .port_probe = omninet_port_probe, 608c2ecf20Sopenharmony_ci .port_remove = omninet_port_remove, 618c2ecf20Sopenharmony_ci .process_read_urb = omninet_process_read_urb, 628c2ecf20Sopenharmony_ci .prepare_write_buffer = omninet_prepare_write_buffer, 638c2ecf20Sopenharmony_ci}; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic struct usb_serial_driver * const serial_drivers[] = { 668c2ecf20Sopenharmony_ci &zyxel_omninet_device, NULL 678c2ecf20Sopenharmony_ci}; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci/* 718c2ecf20Sopenharmony_ci * The protocol. 728c2ecf20Sopenharmony_ci * 738c2ecf20Sopenharmony_ci * The omni.net always exchange 64 bytes of data with the host. The first 748c2ecf20Sopenharmony_ci * four bytes are the control header. 758c2ecf20Sopenharmony_ci * 768c2ecf20Sopenharmony_ci * oh_seq is a sequence number. Don't know if/how it's used. 778c2ecf20Sopenharmony_ci * oh_len is the length of the data bytes in the packet. 788c2ecf20Sopenharmony_ci * oh_xxx Bit-mapped, related to handshaking and status info. 798c2ecf20Sopenharmony_ci * I normally set it to 0x03 in transmitted frames. 808c2ecf20Sopenharmony_ci * 7: Active when the TA is in a CONNECTed state. 818c2ecf20Sopenharmony_ci * 6: unknown 828c2ecf20Sopenharmony_ci * 5: handshaking, unknown 838c2ecf20Sopenharmony_ci * 4: handshaking, unknown 848c2ecf20Sopenharmony_ci * 3: unknown, usually 0 858c2ecf20Sopenharmony_ci * 2: unknown, usually 0 868c2ecf20Sopenharmony_ci * 1: handshaking, unknown, usually set to 1 in transmitted frames 878c2ecf20Sopenharmony_ci * 0: handshaking, unknown, usually set to 1 in transmitted frames 888c2ecf20Sopenharmony_ci * oh_pad Probably a pad byte. 898c2ecf20Sopenharmony_ci * 908c2ecf20Sopenharmony_ci * After the header you will find data bytes if oh_len was greater than zero. 918c2ecf20Sopenharmony_ci */ 928c2ecf20Sopenharmony_cistruct omninet_header { 938c2ecf20Sopenharmony_ci __u8 oh_seq; 948c2ecf20Sopenharmony_ci __u8 oh_len; 958c2ecf20Sopenharmony_ci __u8 oh_xxx; 968c2ecf20Sopenharmony_ci __u8 oh_pad; 978c2ecf20Sopenharmony_ci}; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistruct omninet_data { 1008c2ecf20Sopenharmony_ci __u8 od_outseq; /* Sequence number for bulk_out URBs */ 1018c2ecf20Sopenharmony_ci}; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int omninet_calc_num_ports(struct usb_serial *serial, 1048c2ecf20Sopenharmony_ci struct usb_serial_endpoints *epds) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci /* We need only the second bulk-out for our single-port device. */ 1078c2ecf20Sopenharmony_ci epds->bulk_out[0] = epds->bulk_out[1]; 1088c2ecf20Sopenharmony_ci epds->num_bulk_out = 1; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return 1; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int omninet_port_probe(struct usb_serial_port *port) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct omninet_data *od; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci od = kzalloc(sizeof(*od), GFP_KERNEL); 1188c2ecf20Sopenharmony_ci if (!od) 1198c2ecf20Sopenharmony_ci return -ENOMEM; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci usb_set_serial_port_data(port, od); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci return 0; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic int omninet_port_remove(struct usb_serial_port *port) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct omninet_data *od; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci od = usb_get_serial_port_data(port); 1318c2ecf20Sopenharmony_ci kfree(od); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci return 0; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci#define OMNINET_HEADERLEN 4 1378c2ecf20Sopenharmony_ci#define OMNINET_BULKOUTSIZE 64 1388c2ecf20Sopenharmony_ci#define OMNINET_PAYLOADSIZE (OMNINET_BULKOUTSIZE - OMNINET_HEADERLEN) 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic void omninet_process_read_urb(struct urb *urb) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct usb_serial_port *port = urb->context; 1438c2ecf20Sopenharmony_ci const struct omninet_header *hdr = urb->transfer_buffer; 1448c2ecf20Sopenharmony_ci const unsigned char *data; 1458c2ecf20Sopenharmony_ci size_t data_len; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if (urb->actual_length <= OMNINET_HEADERLEN || !hdr->oh_len) 1488c2ecf20Sopenharmony_ci return; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci data = (char *)urb->transfer_buffer + OMNINET_HEADERLEN; 1518c2ecf20Sopenharmony_ci data_len = min_t(size_t, urb->actual_length - OMNINET_HEADERLEN, 1528c2ecf20Sopenharmony_ci hdr->oh_len); 1538c2ecf20Sopenharmony_ci tty_insert_flip_string(&port->port, data, data_len); 1548c2ecf20Sopenharmony_ci tty_flip_buffer_push(&port->port); 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic int omninet_prepare_write_buffer(struct usb_serial_port *port, 1588c2ecf20Sopenharmony_ci void *buf, size_t count) 1598c2ecf20Sopenharmony_ci{ 1608c2ecf20Sopenharmony_ci struct omninet_data *od = usb_get_serial_port_data(port); 1618c2ecf20Sopenharmony_ci struct omninet_header *header = buf; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci count = min_t(size_t, count, OMNINET_PAYLOADSIZE); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci count = kfifo_out_locked(&port->write_fifo, buf + OMNINET_HEADERLEN, 1668c2ecf20Sopenharmony_ci count, &port->lock); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci header->oh_seq = od->od_outseq++; 1698c2ecf20Sopenharmony_ci header->oh_len = count; 1708c2ecf20Sopenharmony_ci header->oh_xxx = 0x03; 1718c2ecf20Sopenharmony_ci header->oh_pad = 0x00; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci /* always 64 bytes */ 1748c2ecf20Sopenharmony_ci return OMNINET_BULKOUTSIZE; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cimodule_usb_serial_driver(serial_drivers, id_table); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 1808c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 1818c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 182