upd78f0730.c (11267B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Renesas Electronics uPD78F0730 USB to serial converter driver 4 * 5 * Copyright (C) 2014,2016 Maksim Salau <maksim.salau@gmail.com> 6 * 7 * Protocol of the adaptor is described in the application note U19660EJ1V0AN00 8 * μPD78F0730 8-bit Single-Chip Microcontroller 9 * USB-to-Serial Conversion Software 10 * <https://www.renesas.com/en-eu/doc/DocumentServer/026/U19660EJ1V0AN00.pdf> 11 * 12 * The adaptor functionality is limited to the following: 13 * - data bits: 7 or 8 14 * - stop bits: 1 or 2 15 * - parity: even, odd or none 16 * - flow control: none 17 * - baud rates: 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600 18 * - signals: DTR, RTS and BREAK 19 */ 20 21#include <linux/module.h> 22#include <linux/slab.h> 23#include <linux/tty.h> 24#include <linux/usb.h> 25#include <linux/usb/serial.h> 26 27#define DRIVER_DESC "Renesas uPD78F0730 USB to serial converter driver" 28 29#define DRIVER_AUTHOR "Maksim Salau <maksim.salau@gmail.com>" 30 31static const struct usb_device_id id_table[] = { 32 { USB_DEVICE(0x0409, 0x0063) }, /* V850ESJX3-STICK */ 33 { USB_DEVICE(0x045B, 0x0212) }, /* YRPBRL78G13, YRPBRL78G14 */ 34 { USB_DEVICE(0x064B, 0x7825) }, /* Analog Devices EVAL-ADXL362Z-DB */ 35 {} 36}; 37 38MODULE_DEVICE_TABLE(usb, id_table); 39 40/* 41 * Each adaptor is associated with a private structure, that holds the current 42 * state of control signals (DTR, RTS and BREAK). 43 */ 44struct upd78f0730_port_private { 45 struct mutex lock; /* mutex to protect line_signals */ 46 u8 line_signals; 47}; 48 49/* Op-codes of control commands */ 50#define UPD78F0730_CMD_LINE_CONTROL 0x00 51#define UPD78F0730_CMD_SET_DTR_RTS 0x01 52#define UPD78F0730_CMD_SET_XON_XOFF_CHR 0x02 53#define UPD78F0730_CMD_OPEN_CLOSE 0x03 54#define UPD78F0730_CMD_SET_ERR_CHR 0x04 55 56/* Data sizes in UPD78F0730_CMD_LINE_CONTROL command */ 57#define UPD78F0730_DATA_SIZE_7_BITS 0x00 58#define UPD78F0730_DATA_SIZE_8_BITS 0x01 59#define UPD78F0730_DATA_SIZE_MASK 0x01 60 61/* Stop-bit modes in UPD78F0730_CMD_LINE_CONTROL command */ 62#define UPD78F0730_STOP_BIT_1_BIT 0x00 63#define UPD78F0730_STOP_BIT_2_BIT 0x02 64#define UPD78F0730_STOP_BIT_MASK 0x02 65 66/* Parity modes in UPD78F0730_CMD_LINE_CONTROL command */ 67#define UPD78F0730_PARITY_NONE 0x00 68#define UPD78F0730_PARITY_EVEN 0x04 69#define UPD78F0730_PARITY_ODD 0x08 70#define UPD78F0730_PARITY_MASK 0x0C 71 72/* Flow control modes in UPD78F0730_CMD_LINE_CONTROL command */ 73#define UPD78F0730_FLOW_CONTROL_NONE 0x00 74#define UPD78F0730_FLOW_CONTROL_HW 0x10 75#define UPD78F0730_FLOW_CONTROL_SW 0x20 76#define UPD78F0730_FLOW_CONTROL_MASK 0x30 77 78/* Control signal bits in UPD78F0730_CMD_SET_DTR_RTS command */ 79#define UPD78F0730_RTS 0x01 80#define UPD78F0730_DTR 0x02 81#define UPD78F0730_BREAK 0x04 82 83/* Port modes in UPD78F0730_CMD_OPEN_CLOSE command */ 84#define UPD78F0730_PORT_CLOSE 0x00 85#define UPD78F0730_PORT_OPEN 0x01 86 87/* Error character substitution modes in UPD78F0730_CMD_SET_ERR_CHR command */ 88#define UPD78F0730_ERR_CHR_DISABLED 0x00 89#define UPD78F0730_ERR_CHR_ENABLED 0x01 90 91/* 92 * Declaration of command structures 93 */ 94 95/* UPD78F0730_CMD_LINE_CONTROL command */ 96struct upd78f0730_line_control { 97 u8 opcode; 98 __le32 baud_rate; 99 u8 params; 100} __packed; 101 102/* UPD78F0730_CMD_SET_DTR_RTS command */ 103struct upd78f0730_set_dtr_rts { 104 u8 opcode; 105 u8 params; 106}; 107 108/* UPD78F0730_CMD_SET_XON_OFF_CHR command */ 109struct upd78f0730_set_xon_xoff_chr { 110 u8 opcode; 111 u8 xon; 112 u8 xoff; 113}; 114 115/* UPD78F0730_CMD_OPEN_CLOSE command */ 116struct upd78f0730_open_close { 117 u8 opcode; 118 u8 state; 119}; 120 121/* UPD78F0730_CMD_SET_ERR_CHR command */ 122struct upd78f0730_set_err_chr { 123 u8 opcode; 124 u8 state; 125 u8 err_char; 126}; 127 128static int upd78f0730_send_ctl(struct usb_serial_port *port, 129 const void *data, int size) 130{ 131 struct usb_device *usbdev = port->serial->dev; 132 void *buf; 133 int res; 134 135 if (size <= 0 || !data) 136 return -EINVAL; 137 138 buf = kmemdup(data, size, GFP_KERNEL); 139 if (!buf) 140 return -ENOMEM; 141 142 res = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x00, 143 USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, 144 0x0000, 0x0000, buf, size, USB_CTRL_SET_TIMEOUT); 145 146 kfree(buf); 147 148 if (res < 0) { 149 struct device *dev = &port->dev; 150 151 dev_err(dev, "failed to send control request %02x: %d\n", 152 *(u8 *)data, res); 153 154 return res; 155 } 156 157 return 0; 158} 159 160static int upd78f0730_port_probe(struct usb_serial_port *port) 161{ 162 struct upd78f0730_port_private *private; 163 164 private = kzalloc(sizeof(*private), GFP_KERNEL); 165 if (!private) 166 return -ENOMEM; 167 168 mutex_init(&private->lock); 169 usb_set_serial_port_data(port, private); 170 171 return 0; 172} 173 174static void upd78f0730_port_remove(struct usb_serial_port *port) 175{ 176 struct upd78f0730_port_private *private; 177 178 private = usb_get_serial_port_data(port); 179 mutex_destroy(&private->lock); 180 kfree(private); 181} 182 183static int upd78f0730_tiocmget(struct tty_struct *tty) 184{ 185 struct upd78f0730_port_private *private; 186 struct usb_serial_port *port = tty->driver_data; 187 int signals; 188 int res; 189 190 private = usb_get_serial_port_data(port); 191 192 mutex_lock(&private->lock); 193 signals = private->line_signals; 194 mutex_unlock(&private->lock); 195 196 res = ((signals & UPD78F0730_DTR) ? TIOCM_DTR : 0) | 197 ((signals & UPD78F0730_RTS) ? TIOCM_RTS : 0); 198 199 dev_dbg(&port->dev, "%s - res = %x\n", __func__, res); 200 201 return res; 202} 203 204static int upd78f0730_tiocmset(struct tty_struct *tty, 205 unsigned int set, unsigned int clear) 206{ 207 struct usb_serial_port *port = tty->driver_data; 208 struct upd78f0730_port_private *private; 209 struct upd78f0730_set_dtr_rts request; 210 struct device *dev = &port->dev; 211 int res; 212 213 private = usb_get_serial_port_data(port); 214 215 mutex_lock(&private->lock); 216 if (set & TIOCM_DTR) { 217 private->line_signals |= UPD78F0730_DTR; 218 dev_dbg(dev, "%s - set DTR\n", __func__); 219 } 220 if (set & TIOCM_RTS) { 221 private->line_signals |= UPD78F0730_RTS; 222 dev_dbg(dev, "%s - set RTS\n", __func__); 223 } 224 if (clear & TIOCM_DTR) { 225 private->line_signals &= ~UPD78F0730_DTR; 226 dev_dbg(dev, "%s - clear DTR\n", __func__); 227 } 228 if (clear & TIOCM_RTS) { 229 private->line_signals &= ~UPD78F0730_RTS; 230 dev_dbg(dev, "%s - clear RTS\n", __func__); 231 } 232 request.opcode = UPD78F0730_CMD_SET_DTR_RTS; 233 request.params = private->line_signals; 234 235 res = upd78f0730_send_ctl(port, &request, sizeof(request)); 236 mutex_unlock(&private->lock); 237 238 return res; 239} 240 241static void upd78f0730_break_ctl(struct tty_struct *tty, int break_state) 242{ 243 struct upd78f0730_port_private *private; 244 struct usb_serial_port *port = tty->driver_data; 245 struct upd78f0730_set_dtr_rts request; 246 struct device *dev = &port->dev; 247 248 private = usb_get_serial_port_data(port); 249 250 mutex_lock(&private->lock); 251 if (break_state) { 252 private->line_signals |= UPD78F0730_BREAK; 253 dev_dbg(dev, "%s - set BREAK\n", __func__); 254 } else { 255 private->line_signals &= ~UPD78F0730_BREAK; 256 dev_dbg(dev, "%s - clear BREAK\n", __func__); 257 } 258 request.opcode = UPD78F0730_CMD_SET_DTR_RTS; 259 request.params = private->line_signals; 260 261 upd78f0730_send_ctl(port, &request, sizeof(request)); 262 mutex_unlock(&private->lock); 263} 264 265static void upd78f0730_dtr_rts(struct usb_serial_port *port, int on) 266{ 267 struct tty_struct *tty = port->port.tty; 268 unsigned int set = 0; 269 unsigned int clear = 0; 270 271 if (on) 272 set = TIOCM_DTR | TIOCM_RTS; 273 else 274 clear = TIOCM_DTR | TIOCM_RTS; 275 276 upd78f0730_tiocmset(tty, set, clear); 277} 278 279static speed_t upd78f0730_get_baud_rate(struct tty_struct *tty) 280{ 281 const speed_t baud_rate = tty_get_baud_rate(tty); 282 static const speed_t supported[] = { 283 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600 284 }; 285 int i; 286 287 for (i = ARRAY_SIZE(supported) - 1; i >= 0; i--) { 288 if (baud_rate == supported[i]) 289 return baud_rate; 290 } 291 292 /* If the baud rate is not supported, switch to the default one */ 293 tty_encode_baud_rate(tty, 9600, 9600); 294 295 return tty_get_baud_rate(tty); 296} 297 298static void upd78f0730_set_termios(struct tty_struct *tty, 299 struct usb_serial_port *port, 300 struct ktermios *old_termios) 301{ 302 struct device *dev = &port->dev; 303 struct upd78f0730_line_control request; 304 speed_t baud_rate; 305 306 if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) 307 return; 308 309 if (C_BAUD(tty) == B0) 310 upd78f0730_dtr_rts(port, 0); 311 else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) 312 upd78f0730_dtr_rts(port, 1); 313 314 baud_rate = upd78f0730_get_baud_rate(tty); 315 request.opcode = UPD78F0730_CMD_LINE_CONTROL; 316 request.baud_rate = cpu_to_le32(baud_rate); 317 request.params = 0; 318 dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud_rate); 319 320 switch (C_CSIZE(tty)) { 321 case CS7: 322 request.params |= UPD78F0730_DATA_SIZE_7_BITS; 323 dev_dbg(dev, "%s - 7 data bits\n", __func__); 324 break; 325 default: 326 tty->termios.c_cflag &= ~CSIZE; 327 tty->termios.c_cflag |= CS8; 328 dev_warn(dev, "data size is not supported, using 8 bits\n"); 329 fallthrough; 330 case CS8: 331 request.params |= UPD78F0730_DATA_SIZE_8_BITS; 332 dev_dbg(dev, "%s - 8 data bits\n", __func__); 333 break; 334 } 335 336 if (C_PARENB(tty)) { 337 if (C_PARODD(tty)) { 338 request.params |= UPD78F0730_PARITY_ODD; 339 dev_dbg(dev, "%s - odd parity\n", __func__); 340 } else { 341 request.params |= UPD78F0730_PARITY_EVEN; 342 dev_dbg(dev, "%s - even parity\n", __func__); 343 } 344 345 if (C_CMSPAR(tty)) { 346 tty->termios.c_cflag &= ~CMSPAR; 347 dev_warn(dev, "MARK/SPACE parity is not supported\n"); 348 } 349 } else { 350 request.params |= UPD78F0730_PARITY_NONE; 351 dev_dbg(dev, "%s - no parity\n", __func__); 352 } 353 354 if (C_CSTOPB(tty)) { 355 request.params |= UPD78F0730_STOP_BIT_2_BIT; 356 dev_dbg(dev, "%s - 2 stop bits\n", __func__); 357 } else { 358 request.params |= UPD78F0730_STOP_BIT_1_BIT; 359 dev_dbg(dev, "%s - 1 stop bit\n", __func__); 360 } 361 362 if (C_CRTSCTS(tty)) { 363 tty->termios.c_cflag &= ~CRTSCTS; 364 dev_warn(dev, "RTSCTS flow control is not supported\n"); 365 } 366 if (I_IXOFF(tty) || I_IXON(tty)) { 367 tty->termios.c_iflag &= ~(IXOFF | IXON); 368 dev_warn(dev, "XON/XOFF flow control is not supported\n"); 369 } 370 request.params |= UPD78F0730_FLOW_CONTROL_NONE; 371 dev_dbg(dev, "%s - no flow control\n", __func__); 372 373 upd78f0730_send_ctl(port, &request, sizeof(request)); 374} 375 376static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port) 377{ 378 static const struct upd78f0730_open_close request = { 379 .opcode = UPD78F0730_CMD_OPEN_CLOSE, 380 .state = UPD78F0730_PORT_OPEN 381 }; 382 int res; 383 384 res = upd78f0730_send_ctl(port, &request, sizeof(request)); 385 if (res) 386 return res; 387 388 if (tty) 389 upd78f0730_set_termios(tty, port, NULL); 390 391 return usb_serial_generic_open(tty, port); 392} 393 394static void upd78f0730_close(struct usb_serial_port *port) 395{ 396 static const struct upd78f0730_open_close request = { 397 .opcode = UPD78F0730_CMD_OPEN_CLOSE, 398 .state = UPD78F0730_PORT_CLOSE 399 }; 400 401 usb_serial_generic_close(port); 402 upd78f0730_send_ctl(port, &request, sizeof(request)); 403} 404 405static struct usb_serial_driver upd78f0730_device = { 406 .driver = { 407 .owner = THIS_MODULE, 408 .name = "upd78f0730", 409 }, 410 .id_table = id_table, 411 .num_ports = 1, 412 .port_probe = upd78f0730_port_probe, 413 .port_remove = upd78f0730_port_remove, 414 .open = upd78f0730_open, 415 .close = upd78f0730_close, 416 .set_termios = upd78f0730_set_termios, 417 .tiocmget = upd78f0730_tiocmget, 418 .tiocmset = upd78f0730_tiocmset, 419 .dtr_rts = upd78f0730_dtr_rts, 420 .break_ctl = upd78f0730_break_ctl, 421}; 422 423static struct usb_serial_driver * const serial_drivers[] = { 424 &upd78f0730_device, 425 NULL 426}; 427 428module_usb_serial_driver(serial_drivers, id_table); 429 430MODULE_DESCRIPTION(DRIVER_DESC); 431MODULE_AUTHOR(DRIVER_AUTHOR); 432MODULE_LICENSE("GPL v2");