330 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2016-2017 Linaro Ltd., Rob Herring <robh@kernel.org>4 */5#include <linux/kernel.h>6#include <linux/serdev.h>7#include <linux/tty.h>8#include <linux/tty_driver.h>9#include <linux/poll.h>10 11#define SERPORT_ACTIVE 112 13struct serport {14 struct tty_port *port;15 struct tty_struct *tty;16 struct tty_driver *tty_drv;17 int tty_idx;18 unsigned long flags;19};20 21/*22 * Callback functions from the tty port.23 */24 25static size_t ttyport_receive_buf(struct tty_port *port, const u8 *cp,26 const u8 *fp, size_t count)27{28 struct serdev_controller *ctrl = port->client_data;29 struct serport *serport = serdev_controller_get_drvdata(ctrl);30 size_t ret;31 32 if (!test_bit(SERPORT_ACTIVE, &serport->flags))33 return 0;34 35 ret = serdev_controller_receive_buf(ctrl, cp, count);36 37 dev_WARN_ONCE(&ctrl->dev, ret > count,38 "receive_buf returns %zu (count = %zu)\n",39 ret, count);40 if (ret > count)41 return count;42 43 return ret;44}45 46static void ttyport_write_wakeup(struct tty_port *port)47{48 struct serdev_controller *ctrl = port->client_data;49 struct serport *serport = serdev_controller_get_drvdata(ctrl);50 struct tty_struct *tty;51 52 tty = tty_port_tty_get(port);53 if (!tty)54 return;55 56 if (test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) &&57 test_bit(SERPORT_ACTIVE, &serport->flags))58 serdev_controller_write_wakeup(ctrl);59 60 /* Wake up any tty_wait_until_sent() */61 wake_up_interruptible(&tty->write_wait);62 63 tty_kref_put(tty);64}65 66static const struct tty_port_client_operations client_ops = {67 .receive_buf = ttyport_receive_buf,68 .write_wakeup = ttyport_write_wakeup,69};70 71/*72 * Callback functions from the serdev core.73 */74 75static ssize_t ttyport_write_buf(struct serdev_controller *ctrl, const u8 *data, size_t len)76{77 struct serport *serport = serdev_controller_get_drvdata(ctrl);78 struct tty_struct *tty = serport->tty;79 80 if (!test_bit(SERPORT_ACTIVE, &serport->flags))81 return 0;82 83 set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);84 return tty->ops->write(serport->tty, data, len);85}86 87static void ttyport_write_flush(struct serdev_controller *ctrl)88{89 struct serport *serport = serdev_controller_get_drvdata(ctrl);90 struct tty_struct *tty = serport->tty;91 92 tty_driver_flush_buffer(tty);93}94 95static int ttyport_write_room(struct serdev_controller *ctrl)96{97 struct serport *serport = serdev_controller_get_drvdata(ctrl);98 struct tty_struct *tty = serport->tty;99 100 return tty_write_room(tty);101}102 103static int ttyport_open(struct serdev_controller *ctrl)104{105 struct serport *serport = serdev_controller_get_drvdata(ctrl);106 struct tty_struct *tty;107 struct ktermios ktermios;108 int ret;109 110 tty = tty_init_dev(serport->tty_drv, serport->tty_idx);111 if (IS_ERR(tty))112 return PTR_ERR(tty);113 serport->tty = tty;114 115 if (!tty->ops->open || !tty->ops->close) {116 ret = -ENODEV;117 goto err_unlock;118 }119 120 ret = tty->ops->open(serport->tty, NULL);121 if (ret)122 goto err_close;123 124 tty_unlock(serport->tty);125 126 /* Bring the UART into a known 8 bits no parity hw fc state */127 ktermios = tty->termios;128 ktermios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |129 INLCR | IGNCR | ICRNL | IXON);130 ktermios.c_oflag &= ~OPOST;131 ktermios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);132 ktermios.c_cflag &= ~(CSIZE | PARENB);133 ktermios.c_cflag |= CS8;134 ktermios.c_cflag |= CRTSCTS;135 /* Hangups are not supported so make sure to ignore carrier detect. */136 ktermios.c_cflag |= CLOCAL;137 tty_set_termios(tty, &ktermios);138 139 set_bit(SERPORT_ACTIVE, &serport->flags);140 141 return 0;142 143err_close:144 tty->ops->close(tty, NULL);145err_unlock:146 tty_unlock(tty);147 tty_release_struct(tty, serport->tty_idx);148 149 return ret;150}151 152static void ttyport_close(struct serdev_controller *ctrl)153{154 struct serport *serport = serdev_controller_get_drvdata(ctrl);155 struct tty_struct *tty = serport->tty;156 157 clear_bit(SERPORT_ACTIVE, &serport->flags);158 159 tty_lock(tty);160 if (tty->ops->close)161 tty->ops->close(tty, NULL);162 tty_unlock(tty);163 164 tty_release_struct(tty, serport->tty_idx);165}166 167static unsigned int ttyport_set_baudrate(struct serdev_controller *ctrl, unsigned int speed)168{169 struct serport *serport = serdev_controller_get_drvdata(ctrl);170 struct tty_struct *tty = serport->tty;171 struct ktermios ktermios = tty->termios;172 173 ktermios.c_cflag &= ~CBAUD;174 tty_termios_encode_baud_rate(&ktermios, speed, speed);175 176 /* tty_set_termios() return not checked as it is always 0 */177 tty_set_termios(tty, &ktermios);178 return ktermios.c_ospeed;179}180 181static void ttyport_set_flow_control(struct serdev_controller *ctrl, bool enable)182{183 struct serport *serport = serdev_controller_get_drvdata(ctrl);184 struct tty_struct *tty = serport->tty;185 struct ktermios ktermios = tty->termios;186 187 if (enable)188 ktermios.c_cflag |= CRTSCTS;189 else190 ktermios.c_cflag &= ~CRTSCTS;191 192 tty_set_termios(tty, &ktermios);193}194 195static int ttyport_set_parity(struct serdev_controller *ctrl,196 enum serdev_parity parity)197{198 struct serport *serport = serdev_controller_get_drvdata(ctrl);199 struct tty_struct *tty = serport->tty;200 struct ktermios ktermios = tty->termios;201 202 ktermios.c_cflag &= ~(PARENB | PARODD | CMSPAR);203 if (parity != SERDEV_PARITY_NONE) {204 ktermios.c_cflag |= PARENB;205 if (parity == SERDEV_PARITY_ODD)206 ktermios.c_cflag |= PARODD;207 }208 209 tty_set_termios(tty, &ktermios);210 211 if ((tty->termios.c_cflag & (PARENB | PARODD | CMSPAR)) !=212 (ktermios.c_cflag & (PARENB | PARODD | CMSPAR)))213 return -EINVAL;214 215 return 0;216}217 218static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout)219{220 struct serport *serport = serdev_controller_get_drvdata(ctrl);221 struct tty_struct *tty = serport->tty;222 223 tty_wait_until_sent(tty, timeout);224}225 226static int ttyport_get_tiocm(struct serdev_controller *ctrl)227{228 struct serport *serport = serdev_controller_get_drvdata(ctrl);229 struct tty_struct *tty = serport->tty;230 231 if (!tty->ops->tiocmget)232 return -EOPNOTSUPP;233 234 return tty->ops->tiocmget(tty);235}236 237static int ttyport_set_tiocm(struct serdev_controller *ctrl, unsigned int set, unsigned int clear)238{239 struct serport *serport = serdev_controller_get_drvdata(ctrl);240 struct tty_struct *tty = serport->tty;241 242 if (!tty->ops->tiocmset)243 return -EOPNOTSUPP;244 245 return tty->ops->tiocmset(tty, set, clear);246}247 248static int ttyport_break_ctl(struct serdev_controller *ctrl, unsigned int break_state)249{250 struct serport *serport = serdev_controller_get_drvdata(ctrl);251 struct tty_struct *tty = serport->tty;252 253 if (!tty->ops->break_ctl)254 return -EOPNOTSUPP;255 256 return tty->ops->break_ctl(tty, break_state);257}258 259static const struct serdev_controller_ops ctrl_ops = {260 .write_buf = ttyport_write_buf,261 .write_flush = ttyport_write_flush,262 .write_room = ttyport_write_room,263 .open = ttyport_open,264 .close = ttyport_close,265 .set_flow_control = ttyport_set_flow_control,266 .set_parity = ttyport_set_parity,267 .set_baudrate = ttyport_set_baudrate,268 .wait_until_sent = ttyport_wait_until_sent,269 .get_tiocm = ttyport_get_tiocm,270 .set_tiocm = ttyport_set_tiocm,271 .break_ctl = ttyport_break_ctl,272};273 274struct device *serdev_tty_port_register(struct tty_port *port,275 struct device *host,276 struct device *parent,277 struct tty_driver *drv, int idx)278{279 struct serdev_controller *ctrl;280 struct serport *serport;281 int ret;282 283 if (!port || !drv || !parent)284 return ERR_PTR(-ENODEV);285 286 ctrl = serdev_controller_alloc(host, parent, sizeof(struct serport));287 if (!ctrl)288 return ERR_PTR(-ENOMEM);289 serport = serdev_controller_get_drvdata(ctrl);290 291 serport->port = port;292 serport->tty_idx = idx;293 serport->tty_drv = drv;294 295 ctrl->ops = &ctrl_ops;296 297 port->client_ops = &client_ops;298 port->client_data = ctrl;299 300 ret = serdev_controller_add(ctrl);301 if (ret)302 goto err_reset_data;303 304 dev_info(&ctrl->dev, "tty port %s%d registered\n", drv->name, idx);305 return &ctrl->dev;306 307err_reset_data:308 port->client_data = NULL;309 port->client_ops = &tty_port_default_client_ops;310 serdev_controller_put(ctrl);311 312 return ERR_PTR(ret);313}314 315int serdev_tty_port_unregister(struct tty_port *port)316{317 struct serdev_controller *ctrl = port->client_data;318 struct serport *serport = serdev_controller_get_drvdata(ctrl);319 320 if (!serport)321 return -ENODEV;322 323 serdev_controller_remove(ctrl);324 port->client_data = NULL;325 port->client_ops = &tty_port_default_client_ops;326 serdev_controller_put(ctrl);327 328 return 0;329}330