summaryrefslogtreecommitdiff
path: root/drivers/tty/serdev/serdev-ttyport.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serdev/serdev-ttyport.c')
-rw-r--r--drivers/tty/serdev/serdev-ttyport.c129
1 files changed, 91 insertions, 38 deletions
diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c
index 302018d67efa..bab1b143b8a6 100644
--- a/drivers/tty/serdev/serdev-ttyport.c
+++ b/drivers/tty/serdev/serdev-ttyport.c
@@ -1,14 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016-2017 Linaro Ltd., Rob Herring <robh@kernel.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 and
- * only version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/serdev.h>
@@ -30,28 +22,45 @@ struct serport {
* Callback functions from the tty port.
*/
-static int ttyport_receive_buf(struct tty_port *port, const unsigned char *cp,
- const unsigned char *fp, size_t count)
+static size_t ttyport_receive_buf(struct tty_port *port, const u8 *cp,
+ const u8 *fp, size_t count)
{
struct serdev_controller *ctrl = port->client_data;
struct serport *serport = serdev_controller_get_drvdata(ctrl);
+ size_t ret;
if (!test_bit(SERPORT_ACTIVE, &serport->flags))
return 0;
- return serdev_controller_receive_buf(ctrl, cp, count);
+ ret = serdev_controller_receive_buf(ctrl, cp, count);
+
+ dev_WARN_ONCE(&ctrl->dev, ret > count,
+ "receive_buf returns %zu (count = %zu)\n",
+ ret, count);
+ if (ret > count)
+ return count;
+
+ return ret;
}
static void ttyport_write_wakeup(struct tty_port *port)
{
struct serdev_controller *ctrl = port->client_data;
struct serport *serport = serdev_controller_get_drvdata(ctrl);
+ struct tty_struct *tty;
- if (test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &port->tty->flags) &&
+ tty = tty_port_tty_get(port);
+ if (!tty)
+ return;
+
+ if (test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) &&
test_bit(SERPORT_ACTIVE, &serport->flags))
serdev_controller_write_wakeup(ctrl);
- wake_up_interruptible_poll(&port->tty->write_wait, POLLOUT);
+ /* Wake up any tty_wait_until_sent() */
+ wake_up_interruptible(&tty->write_wait);
+
+ tty_kref_put(tty);
}
static const struct tty_port_client_operations client_ops = {
@@ -63,7 +72,7 @@ static const struct tty_port_client_operations client_ops = {
* Callback functions from the serdev core.
*/
-static int ttyport_write_buf(struct serdev_controller *ctrl, const unsigned char *data, size_t len)
+static ssize_t ttyport_write_buf(struct serdev_controller *ctrl, const u8 *data, size_t len)
{
struct serport *serport = serdev_controller_get_drvdata(ctrl);
struct tty_struct *tty = serport->tty;
@@ -83,29 +92,28 @@ static void ttyport_write_flush(struct serdev_controller *ctrl)
tty_driver_flush_buffer(tty);
}
-static int ttyport_write_room(struct serdev_controller *ctrl)
-{
- struct serport *serport = serdev_controller_get_drvdata(ctrl);
- struct tty_struct *tty = serport->tty;
-
- return tty_write_room(tty);
-}
-
static int ttyport_open(struct serdev_controller *ctrl)
{
struct serport *serport = serdev_controller_get_drvdata(ctrl);
struct tty_struct *tty;
struct ktermios ktermios;
+ int ret;
tty = tty_init_dev(serport->tty_drv, serport->tty_idx);
if (IS_ERR(tty))
return PTR_ERR(tty);
serport->tty = tty;
- if (tty->ops->open)
- tty->ops->open(serport->tty, NULL);
- else
- tty_port_open(serport->port, tty, NULL);
+ if (!tty->ops->open || !tty->ops->close) {
+ ret = -ENODEV;
+ goto err_unlock;
+ }
+
+ ret = tty->ops->open(serport->tty, NULL);
+ if (ret)
+ goto err_close;
+
+ tty_unlock(serport->tty);
/* Bring the UART into a known 8 bits no parity hw fc state */
ktermios = tty->termios;
@@ -116,12 +124,21 @@ static int ttyport_open(struct serdev_controller *ctrl)
ktermios.c_cflag &= ~(CSIZE | PARENB);
ktermios.c_cflag |= CS8;
ktermios.c_cflag |= CRTSCTS;
+ /* Hangups are not supported so make sure to ignore carrier detect. */
+ ktermios.c_cflag |= CLOCAL;
tty_set_termios(tty, &ktermios);
set_bit(SERPORT_ACTIVE, &serport->flags);
- tty_unlock(serport->tty);
return 0;
+
+err_close:
+ tty->ops->close(tty, NULL);
+err_unlock:
+ tty_unlock(tty);
+ tty_release_struct(tty, serport->tty_idx);
+
+ return ret;
}
static void ttyport_close(struct serdev_controller *ctrl)
@@ -131,8 +148,10 @@ static void ttyport_close(struct serdev_controller *ctrl)
clear_bit(SERPORT_ACTIVE, &serport->flags);
+ tty_lock(tty);
if (tty->ops->close)
tty->ops->close(tty, NULL);
+ tty_unlock(tty);
tty_release_struct(tty, serport->tty_idx);
}
@@ -165,6 +184,29 @@ static void ttyport_set_flow_control(struct serdev_controller *ctrl, bool enable
tty_set_termios(tty, &ktermios);
}
+static int ttyport_set_parity(struct serdev_controller *ctrl,
+ enum serdev_parity parity)
+{
+ struct serport *serport = serdev_controller_get_drvdata(ctrl);
+ struct tty_struct *tty = serport->tty;
+ struct ktermios ktermios = tty->termios;
+
+ ktermios.c_cflag &= ~(PARENB | PARODD | CMSPAR);
+ if (parity != SERDEV_PARITY_NONE) {
+ ktermios.c_cflag |= PARENB;
+ if (parity == SERDEV_PARITY_ODD)
+ ktermios.c_cflag |= PARODD;
+ }
+
+ tty_set_termios(tty, &ktermios);
+
+ if ((tty->termios.c_cflag & (PARENB | PARODD | CMSPAR)) !=
+ (ktermios.c_cflag & (PARENB | PARODD | CMSPAR)))
+ return -EINVAL;
+
+ return 0;
+}
+
static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout)
{
struct serport *serport = serdev_controller_get_drvdata(ctrl);
@@ -179,9 +221,9 @@ static int ttyport_get_tiocm(struct serdev_controller *ctrl)
struct tty_struct *tty = serport->tty;
if (!tty->ops->tiocmget)
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
- return tty->driver->ops->tiocmget(tty);
+ return tty->ops->tiocmget(tty);
}
static int ttyport_set_tiocm(struct serdev_controller *ctrl, unsigned int set, unsigned int clear)
@@ -190,29 +232,41 @@ static int ttyport_set_tiocm(struct serdev_controller *ctrl, unsigned int set, u
struct tty_struct *tty = serport->tty;
if (!tty->ops->tiocmset)
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
+
+ return tty->ops->tiocmset(tty, set, clear);
+}
+
+static int ttyport_break_ctl(struct serdev_controller *ctrl, unsigned int break_state)
+{
+ struct serport *serport = serdev_controller_get_drvdata(ctrl);
+ struct tty_struct *tty = serport->tty;
+
+ if (!tty->ops->break_ctl)
+ return -EOPNOTSUPP;
- return tty->driver->ops->tiocmset(tty, set, clear);
+ return tty->ops->break_ctl(tty, break_state);
}
static const struct serdev_controller_ops ctrl_ops = {
.write_buf = ttyport_write_buf,
.write_flush = ttyport_write_flush,
- .write_room = ttyport_write_room,
.open = ttyport_open,
.close = ttyport_close,
.set_flow_control = ttyport_set_flow_control,
+ .set_parity = ttyport_set_parity,
.set_baudrate = ttyport_set_baudrate,
.wait_until_sent = ttyport_wait_until_sent,
.get_tiocm = ttyport_get_tiocm,
.set_tiocm = ttyport_set_tiocm,
+ .break_ctl = ttyport_break_ctl,
};
struct device *serdev_tty_port_register(struct tty_port *port,
+ struct device *host,
struct device *parent,
struct tty_driver *drv, int idx)
{
- const struct tty_port_client_operations *old_ops;
struct serdev_controller *ctrl;
struct serport *serport;
int ret;
@@ -220,7 +274,7 @@ struct device *serdev_tty_port_register(struct tty_port *port,
if (!port || !drv || !parent)
return ERR_PTR(-ENODEV);
- ctrl = serdev_controller_alloc(parent, sizeof(struct serport));
+ ctrl = serdev_controller_alloc(host, parent, sizeof(struct serport));
if (!ctrl)
return ERR_PTR(-ENOMEM);
serport = serdev_controller_get_drvdata(ctrl);
@@ -231,7 +285,6 @@ struct device *serdev_tty_port_register(struct tty_port *port,
ctrl->ops = &ctrl_ops;
- old_ops = port->client_ops;
port->client_ops = &client_ops;
port->client_data = ctrl;
@@ -244,7 +297,7 @@ struct device *serdev_tty_port_register(struct tty_port *port,
err_reset_data:
port->client_data = NULL;
- port->client_ops = old_ops;
+ port->client_ops = &tty_port_default_client_ops;
serdev_controller_put(ctrl);
return ERR_PTR(ret);
@@ -259,8 +312,8 @@ int serdev_tty_port_unregister(struct tty_port *port)
return -ENODEV;
serdev_controller_remove(ctrl);
- port->client_ops = NULL;
port->client_data = NULL;
+ port->client_ops = &tty_port_default_client_ops;
serdev_controller_put(ctrl);
return 0;