18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2016-2017 Linaro Ltd., Rob Herring <robh@kernel.org> 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci#include <linux/kernel.h> 68c2ecf20Sopenharmony_ci#include <linux/serdev.h> 78c2ecf20Sopenharmony_ci#include <linux/tty.h> 88c2ecf20Sopenharmony_ci#include <linux/tty_driver.h> 98c2ecf20Sopenharmony_ci#include <linux/poll.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define SERPORT_ACTIVE 1 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cistruct serport { 148c2ecf20Sopenharmony_ci struct tty_port *port; 158c2ecf20Sopenharmony_ci struct tty_struct *tty; 168c2ecf20Sopenharmony_ci struct tty_driver *tty_drv; 178c2ecf20Sopenharmony_ci int tty_idx; 188c2ecf20Sopenharmony_ci unsigned long flags; 198c2ecf20Sopenharmony_ci}; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* 228c2ecf20Sopenharmony_ci * Callback functions from the tty port. 238c2ecf20Sopenharmony_ci */ 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic int ttyport_receive_buf(struct tty_port *port, const unsigned char *cp, 268c2ecf20Sopenharmony_ci const unsigned char *fp, size_t count) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct serdev_controller *ctrl = port->client_data; 298c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 308c2ecf20Sopenharmony_ci int ret; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci if (!test_bit(SERPORT_ACTIVE, &serport->flags)) 338c2ecf20Sopenharmony_ci return 0; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci ret = serdev_controller_receive_buf(ctrl, cp, count); 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci dev_WARN_ONCE(&ctrl->dev, ret < 0 || ret > count, 388c2ecf20Sopenharmony_ci "receive_buf returns %d (count = %zu)\n", 398c2ecf20Sopenharmony_ci ret, count); 408c2ecf20Sopenharmony_ci if (ret < 0) 418c2ecf20Sopenharmony_ci return 0; 428c2ecf20Sopenharmony_ci else if (ret > count) 438c2ecf20Sopenharmony_ci return count; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci return ret; 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic void ttyport_write_wakeup(struct tty_port *port) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci struct serdev_controller *ctrl = port->client_data; 518c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 528c2ecf20Sopenharmony_ci struct tty_struct *tty; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci tty = tty_port_tty_get(port); 558c2ecf20Sopenharmony_ci if (!tty) 568c2ecf20Sopenharmony_ci return; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci if (test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && 598c2ecf20Sopenharmony_ci test_bit(SERPORT_ACTIVE, &serport->flags)) 608c2ecf20Sopenharmony_ci serdev_controller_write_wakeup(ctrl); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* Wake up any tty_wait_until_sent() */ 638c2ecf20Sopenharmony_ci wake_up_interruptible(&tty->write_wait); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci tty_kref_put(tty); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic const struct tty_port_client_operations client_ops = { 698c2ecf20Sopenharmony_ci .receive_buf = ttyport_receive_buf, 708c2ecf20Sopenharmony_ci .write_wakeup = ttyport_write_wakeup, 718c2ecf20Sopenharmony_ci}; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci/* 748c2ecf20Sopenharmony_ci * Callback functions from the serdev core. 758c2ecf20Sopenharmony_ci */ 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int ttyport_write_buf(struct serdev_controller *ctrl, const unsigned char *data, size_t len) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 808c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci if (!test_bit(SERPORT_ACTIVE, &serport->flags)) 838c2ecf20Sopenharmony_ci return 0; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); 868c2ecf20Sopenharmony_ci return tty->ops->write(serport->tty, data, len); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic void ttyport_write_flush(struct serdev_controller *ctrl) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 928c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci tty_driver_flush_buffer(tty); 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic int ttyport_write_room(struct serdev_controller *ctrl) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 1008c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci return tty_write_room(tty); 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic int ttyport_open(struct serdev_controller *ctrl) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 1088c2ecf20Sopenharmony_ci struct tty_struct *tty; 1098c2ecf20Sopenharmony_ci struct ktermios ktermios; 1108c2ecf20Sopenharmony_ci int ret; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci tty = tty_init_dev(serport->tty_drv, serport->tty_idx); 1138c2ecf20Sopenharmony_ci if (IS_ERR(tty)) 1148c2ecf20Sopenharmony_ci return PTR_ERR(tty); 1158c2ecf20Sopenharmony_ci serport->tty = tty; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (!tty->ops->open || !tty->ops->close) { 1188c2ecf20Sopenharmony_ci ret = -ENODEV; 1198c2ecf20Sopenharmony_ci goto err_unlock; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci ret = tty->ops->open(serport->tty, NULL); 1238c2ecf20Sopenharmony_ci if (ret) 1248c2ecf20Sopenharmony_ci goto err_close; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci tty_unlock(serport->tty); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci /* Bring the UART into a known 8 bits no parity hw fc state */ 1298c2ecf20Sopenharmony_ci ktermios = tty->termios; 1308c2ecf20Sopenharmony_ci ktermios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | 1318c2ecf20Sopenharmony_ci INLCR | IGNCR | ICRNL | IXON); 1328c2ecf20Sopenharmony_ci ktermios.c_oflag &= ~OPOST; 1338c2ecf20Sopenharmony_ci ktermios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 1348c2ecf20Sopenharmony_ci ktermios.c_cflag &= ~(CSIZE | PARENB); 1358c2ecf20Sopenharmony_ci ktermios.c_cflag |= CS8; 1368c2ecf20Sopenharmony_ci ktermios.c_cflag |= CRTSCTS; 1378c2ecf20Sopenharmony_ci /* Hangups are not supported so make sure to ignore carrier detect. */ 1388c2ecf20Sopenharmony_ci ktermios.c_cflag |= CLOCAL; 1398c2ecf20Sopenharmony_ci tty_set_termios(tty, &ktermios); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci set_bit(SERPORT_ACTIVE, &serport->flags); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci return 0; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cierr_close: 1468c2ecf20Sopenharmony_ci tty->ops->close(tty, NULL); 1478c2ecf20Sopenharmony_cierr_unlock: 1488c2ecf20Sopenharmony_ci tty_unlock(tty); 1498c2ecf20Sopenharmony_ci tty_release_struct(tty, serport->tty_idx); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return ret; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic void ttyport_close(struct serdev_controller *ctrl) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 1578c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci clear_bit(SERPORT_ACTIVE, &serport->flags); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci tty_lock(tty); 1628c2ecf20Sopenharmony_ci if (tty->ops->close) 1638c2ecf20Sopenharmony_ci tty->ops->close(tty, NULL); 1648c2ecf20Sopenharmony_ci tty_unlock(tty); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci tty_release_struct(tty, serport->tty_idx); 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic unsigned int ttyport_set_baudrate(struct serdev_controller *ctrl, unsigned int speed) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 1728c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 1738c2ecf20Sopenharmony_ci struct ktermios ktermios = tty->termios; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci ktermios.c_cflag &= ~CBAUD; 1768c2ecf20Sopenharmony_ci tty_termios_encode_baud_rate(&ktermios, speed, speed); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* tty_set_termios() return not checked as it is always 0 */ 1798c2ecf20Sopenharmony_ci tty_set_termios(tty, &ktermios); 1808c2ecf20Sopenharmony_ci return ktermios.c_ospeed; 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic void ttyport_set_flow_control(struct serdev_controller *ctrl, bool enable) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 1868c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 1878c2ecf20Sopenharmony_ci struct ktermios ktermios = tty->termios; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (enable) 1908c2ecf20Sopenharmony_ci ktermios.c_cflag |= CRTSCTS; 1918c2ecf20Sopenharmony_ci else 1928c2ecf20Sopenharmony_ci ktermios.c_cflag &= ~CRTSCTS; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci tty_set_termios(tty, &ktermios); 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic int ttyport_set_parity(struct serdev_controller *ctrl, 1988c2ecf20Sopenharmony_ci enum serdev_parity parity) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 2018c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 2028c2ecf20Sopenharmony_ci struct ktermios ktermios = tty->termios; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci ktermios.c_cflag &= ~(PARENB | PARODD | CMSPAR); 2058c2ecf20Sopenharmony_ci if (parity != SERDEV_PARITY_NONE) { 2068c2ecf20Sopenharmony_ci ktermios.c_cflag |= PARENB; 2078c2ecf20Sopenharmony_ci if (parity == SERDEV_PARITY_ODD) 2088c2ecf20Sopenharmony_ci ktermios.c_cflag |= PARODD; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci tty_set_termios(tty, &ktermios); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if ((tty->termios.c_cflag & (PARENB | PARODD | CMSPAR)) != 2148c2ecf20Sopenharmony_ci (ktermios.c_cflag & (PARENB | PARODD | CMSPAR))) 2158c2ecf20Sopenharmony_ci return -EINVAL; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci return 0; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 2238c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci tty_wait_until_sent(tty, timeout); 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic int ttyport_get_tiocm(struct serdev_controller *ctrl) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 2318c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (!tty->ops->tiocmget) 2348c2ecf20Sopenharmony_ci return -ENOTSUPP; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return tty->ops->tiocmget(tty); 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic int ttyport_set_tiocm(struct serdev_controller *ctrl, unsigned int set, unsigned int clear) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 2428c2ecf20Sopenharmony_ci struct tty_struct *tty = serport->tty; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (!tty->ops->tiocmset) 2458c2ecf20Sopenharmony_ci return -ENOTSUPP; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci return tty->ops->tiocmset(tty, set, clear); 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic const struct serdev_controller_ops ctrl_ops = { 2518c2ecf20Sopenharmony_ci .write_buf = ttyport_write_buf, 2528c2ecf20Sopenharmony_ci .write_flush = ttyport_write_flush, 2538c2ecf20Sopenharmony_ci .write_room = ttyport_write_room, 2548c2ecf20Sopenharmony_ci .open = ttyport_open, 2558c2ecf20Sopenharmony_ci .close = ttyport_close, 2568c2ecf20Sopenharmony_ci .set_flow_control = ttyport_set_flow_control, 2578c2ecf20Sopenharmony_ci .set_parity = ttyport_set_parity, 2588c2ecf20Sopenharmony_ci .set_baudrate = ttyport_set_baudrate, 2598c2ecf20Sopenharmony_ci .wait_until_sent = ttyport_wait_until_sent, 2608c2ecf20Sopenharmony_ci .get_tiocm = ttyport_get_tiocm, 2618c2ecf20Sopenharmony_ci .set_tiocm = ttyport_set_tiocm, 2628c2ecf20Sopenharmony_ci}; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_cistruct device *serdev_tty_port_register(struct tty_port *port, 2658c2ecf20Sopenharmony_ci struct device *parent, 2668c2ecf20Sopenharmony_ci struct tty_driver *drv, int idx) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct serdev_controller *ctrl; 2698c2ecf20Sopenharmony_ci struct serport *serport; 2708c2ecf20Sopenharmony_ci int ret; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci if (!port || !drv || !parent) 2738c2ecf20Sopenharmony_ci return ERR_PTR(-ENODEV); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci ctrl = serdev_controller_alloc(parent, sizeof(struct serport)); 2768c2ecf20Sopenharmony_ci if (!ctrl) 2778c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2788c2ecf20Sopenharmony_ci serport = serdev_controller_get_drvdata(ctrl); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci serport->port = port; 2818c2ecf20Sopenharmony_ci serport->tty_idx = idx; 2828c2ecf20Sopenharmony_ci serport->tty_drv = drv; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci ctrl->ops = &ctrl_ops; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci port->client_ops = &client_ops; 2878c2ecf20Sopenharmony_ci port->client_data = ctrl; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci ret = serdev_controller_add(ctrl); 2908c2ecf20Sopenharmony_ci if (ret) 2918c2ecf20Sopenharmony_ci goto err_reset_data; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci dev_info(&ctrl->dev, "tty port %s%d registered\n", drv->name, idx); 2948c2ecf20Sopenharmony_ci return &ctrl->dev; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cierr_reset_data: 2978c2ecf20Sopenharmony_ci port->client_data = NULL; 2988c2ecf20Sopenharmony_ci port->client_ops = &tty_port_default_client_ops; 2998c2ecf20Sopenharmony_ci serdev_controller_put(ctrl); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci return ERR_PTR(ret); 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ciint serdev_tty_port_unregister(struct tty_port *port) 3058c2ecf20Sopenharmony_ci{ 3068c2ecf20Sopenharmony_ci struct serdev_controller *ctrl = port->client_data; 3078c2ecf20Sopenharmony_ci struct serport *serport = serdev_controller_get_drvdata(ctrl); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci if (!serport) 3108c2ecf20Sopenharmony_ci return -ENODEV; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci serdev_controller_remove(ctrl); 3138c2ecf20Sopenharmony_ci port->client_data = NULL; 3148c2ecf20Sopenharmony_ci port->client_ops = &tty_port_default_client_ops; 3158c2ecf20Sopenharmony_ci serdev_controller_put(ctrl); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci return 0; 3188c2ecf20Sopenharmony_ci} 319