[RFC][PATCH 1/1] serdev: Support HS-UART serdev slaves over tty

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds support for TTY interface on Serial Device Bus with
slave devices defined by ACPI HID's INT3511/INT3512.
HS-UART peripheral is found on Intel Atom BayTrail SoC's.

Signed-off-by: Shrirang Bagul <shrirang.bagul@xxxxxxxxxxxxx>
---
 drivers/misc/Kconfig               |  11 ++
 drivers/misc/Makefile              |   1 +
 drivers/misc/intel-hsuart-serdev.c | 332 +++++++++++++++++++++++++++++++++++++
 3 files changed, 344 insertions(+)
 create mode 100644 drivers/misc/intel-hsuart-serdev.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 5d713008749b..c7a87670aa9e 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -499,6 +499,17 @@ config MISC_RTSX
 	tristate
 	default MISC_RTSX_PCI || MISC_RTSX_USB
 
+config HSUART_SERIAL_DEVICE
+	bool "Intel ACPI INT3511/12 Serial Device Bus slave TTY driver"
+	help
+	  Say Y here if you want to use the TTY interface on Serial Device Bus with
+	  slave devices defined by ACPI HID's INT3511/INT3512.
+	  This HS-UART peripheral is found on Intel Atom BayTrail SoC's.
+
+	  If unsure, say Y.
+	depends on ACPI
+	depends on TTY && SERIAL_DEV_BUS = y
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 20be70c3f118..b2285265eec6 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP)	+= aspeed-lpc-snoop.o
 obj-$(CONFIG_PCI_ENDPOINT_TEST)	+= pci_endpoint_test.o
 obj-$(CONFIG_OCXL)		+= ocxl/
 obj-$(CONFIG_MISC_RTSX)		+= cardreader/
+obj-$(CONFIG_HSUART_SERIAL_DEVICE)	+= intel-hsuart-serdev.o
diff --git a/drivers/misc/intel-hsuart-serdev.c b/drivers/misc/intel-hsuart-serdev.c
new file mode 100644
index 000000000000..146c4789f224
--- /dev/null
+++ b/drivers/misc/intel-hsuart-serdev.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Copyright (C) 2018 Shrirang Bagul <shrirang.bagul@xxxxxxxxxxxxx>
+ *
+ *  Serial device bus slave driver for virtual HSUART ports on Intel Atom
+ *  Baytrail SoC.
+ */
+
+#include <linux/acpi.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/serdev.h>
+#include <linux/serial.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+
+#define DRV_NAME "hsuart-tty"
+#define HSUART_NUM_MINORS	2
+
+static DEFINE_IDR(hsuart_minors);
+static DEFINE_MUTEX(table_lock);
+
+struct hsuart_data {
+	struct serdev_device *uart;	/* the uart connected to the chip */
+	struct tty_driver *tty_drv;	/* this is the user space tty */
+	struct device *dev;	/* returned by tty_port_register_device() */
+	struct tty_port port;
+	unsigned int minor;
+	struct mutex mutex;
+};
+
+static struct hsuart_data *get_hsuart_by_minor(unsigned int minor)
+{
+	struct hsuart_data *data;
+
+	mutex_lock(&table_lock);
+	data = idr_find(&hsuart_minors, minor);
+	if (data) {
+		mutex_lock(&data->mutex);
+		tty_port_get(&data->port);
+		mutex_unlock(&data->mutex);
+	}
+	mutex_unlock(&table_lock);
+	return data;
+}
+
+static int alloc_minor(struct hsuart_data *data)
+{
+	int minor;
+
+	mutex_lock(&table_lock);
+	minor = idr_alloc(&hsuart_minors, data, 0, HSUART_NUM_MINORS,
+			GFP_KERNEL);
+	mutex_unlock(&table_lock);
+	if (minor >= 0)
+		data->minor = minor;
+	return minor;
+}
+
+static void release_minor(struct hsuart_data *data)
+{
+	int minor = data->minor;
+
+	data->minor = 0;	/* Maybe should use an invalid value instead */
+	mutex_lock(&table_lock);
+	idr_remove(&hsuart_minors, minor);
+	mutex_unlock(&table_lock);
+}
+
+static int huart_receive_buf(struct serdev_device *serdev,
+		const unsigned char *rxdata,	size_t count)
+{
+	struct hsuart_data *data =
+		(struct hsuart_data *) serdev_device_get_drvdata(serdev);
+	int ret;
+
+	dev_dbg(&data->uart->dev, "push %zu chars to tty port\n", count);
+	ret = tty_insert_flip_string(&data->port, rxdata, count);
+	if (ret != count)
+		dev_dbg(&data->uart->dev, "lost %zu characters\n", count - ret);
+	tty_flip_buffer_push(&data->port);
+
+	/* assume we have processed everything */
+	return count;
+}
+
+static void hsuart_write_wakeup(struct serdev_device *serdev)
+{
+	struct hsuart_data *data =
+		(struct hsuart_data *) serdev_device_get_drvdata(serdev);
+	struct tty_struct *tty = tty_port_tty_get(&data->port);
+
+	if (tty) {
+		tty_wakeup(tty);
+		tty_kref_put(tty);
+	}
+}
+
+static const struct serdev_device_ops hsuart_serdev_client_ops = {
+	.receive_buf = huart_receive_buf,
+	.write_wakeup = hsuart_write_wakeup,
+};
+
+static int hsuart_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	struct hsuart_data *data;
+	int retval;
+
+	data = get_hsuart_by_minor(tty->index);
+	if (!data)
+		return -ENODEV;
+
+	retval = tty_standard_install(driver, tty);
+	if (retval)
+		goto error;
+
+	tty->driver_data = data;
+	return 0;
+error:
+	tty_port_put(&data->port);
+	return retval;
+}
+
+static int hsuart_tty_open(struct tty_struct *tty, struct file *file)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	return tty_port_open(&data->port, tty, file);
+}
+
+static void hsuart_tty_close(struct tty_struct *tty, struct file *file)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	tty_port_close(&data->port, tty, file);
+}
+
+static int hsuart_tty_write(struct tty_struct *tty, const unsigned char *buf,
+			int count)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	return serdev_device_write_buf(data->uart, buf, count);
+}
+
+static int hsuart_tty_write_room(struct tty_struct *tty)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	return serdev_device_write_room(data->uart);
+}
+
+static void hsuart_tty_flush_buffer(struct tty_struct *tty)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	serdev_device_write_flush(data->uart);
+}
+
+static void hsuart_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	serdev_device_wait_until_sent(data->uart, timeout);
+}
+
+static void hsuart_tty_set_termios(struct tty_struct *tty,
+			       struct ktermios *termios_old)
+{
+	struct hsuart_data *data = tty->driver_data;
+	unsigned int speed = tty_get_baud_rate(tty);
+
+	if (C_BAUD(tty) != B0)
+		serdev_device_set_baudrate(data->uart, speed);
+
+	serdev_device_set_flow_control(data->uart, C_CRTSCTS(tty));
+
+	if (C_PARENB(tty)) {
+		if (C_PARODD(tty))
+			serdev_device_set_parity(data->uart,
+					SERDEV_PARITY_ODD);
+		else
+			serdev_device_set_parity(data->uart,
+					SERDEV_PARITY_EVEN);
+	} else {
+		serdev_device_set_parity(data->uart, SERDEV_PARITY_NONE);
+	}
+}
+
+static int hsuart_tty_tiocmget(struct tty_struct *tty)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	return serdev_device_get_tiocm(data->uart);
+}
+
+static int hsuart_tty_tiocmset(struct tty_struct *tty, unsigned int set,
+			   unsigned int clear)
+{
+	struct hsuart_data *data = tty->driver_data;
+
+	return serdev_device_set_tiocm(data->uart, set, clear);
+}
+
+static const struct tty_operations hsuart_tty_ops = {
+	.install = hsuart_tty_install,
+	.open = hsuart_tty_open,
+	.close = hsuart_tty_close,
+	.write = hsuart_tty_write,
+	.write_room = hsuart_tty_write_room,
+	.flush_buffer = hsuart_tty_flush_buffer,
+	.wait_until_sent	= hsuart_tty_wait_until_sent,
+	.set_termios = hsuart_tty_set_termios,
+	.tiocmget = hsuart_tty_tiocmget,
+	.tiocmset = hsuart_tty_tiocmset,
+};
+
+static const struct tty_port_operations hsuart_port_ops = {
+};
+
+static int hsuart_probe(struct serdev_device *serdev)
+{
+	struct hsuart_data *data;
+	int minor;
+	int err = 0;
+
+	data = devm_kzalloc(&serdev->dev, sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	serdev_device_set_drvdata(serdev, data);
+	data->uart = serdev;
+
+	serdev_device_set_client_ops(data->uart, &hsuart_serdev_client_ops);
+	serdev_device_open(data->uart);
+
+	serdev_device_set_baudrate(data->uart, 9600);
+	serdev_device_set_flow_control(data->uart, false);
+
+	/* allocate the tty driver */
+	data->tty_drv = tty_alloc_driver(HSUART_NUM_MINORS, 0);
+	if (!data->tty_drv) {
+		dev_err(&serdev->dev,
+				"failed to allcate tty driver\n");
+		err = -ENOMEM;
+		goto err_ttyfail;
+	}
+
+	/* initialize the tty driver */
+	data->tty_drv->driver_name = DRV_NAME;
+	data->tty_drv->name = "ttyHS";
+	data->tty_drv->major = 0;
+	data->tty_drv->minor_start = 0;
+	data->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
+	data->tty_drv->subtype = SERIAL_TYPE_NORMAL;
+	data->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	data->tty_drv->init_termios = tty_std_termios;
+	data->tty_drv->init_termios.c_cflag = B9600 | CS8 |
+		CREAD | HUPCL | CLOCAL;
+	tty_set_operations(data->tty_drv, &hsuart_tty_ops);
+
+	minor = alloc_minor(data);
+	if (minor < 0) {
+		if (minor == -ENOSPC) {
+			dev_err(&serdev->dev,
+				"no more free minor numbers\n");
+			err = -ENODEV;
+		}
+		goto err_ttyfail;
+	}
+	mutex_init(&data->mutex);
+
+	/* register the tty driver */
+	err = tty_register_driver(data->tty_drv);
+	if (err) {
+		dev_err(&serdev->dev, "tty_register_driver failed(%d)\n", err);
+		put_tty_driver(data->tty_drv);
+		goto err_ttyfail;
+	}
+
+	tty_port_init(&data->port);
+	data->port.ops = &hsuart_port_ops;
+	data->dev = tty_port_register_device(&data->port, data->tty_drv,
+			minor, &serdev->dev);
+
+	return 0;
+
+err_ttyfail:
+	serdev_device_close(data->uart);
+	dev_err(&serdev->dev, "%s failed error: %d\n", __func__, err);
+
+	return err;
+}
+
+static void hsuart_remove(struct serdev_device *serdev)
+{
+	struct hsuart_data *data = serdev_device_get_drvdata(serdev);
+
+	serdev_device_close(data->uart);
+	tty_unregister_device(data->tty_drv, data->minor);
+	release_minor(data);
+
+	tty_unregister_driver(data->tty_drv);
+}
+
+static const struct acpi_device_id hsuart_acpi_match[] = {
+	{"INT3511", 0},
+	{"INT3512", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(acpi, hsuart_acpi_match);
+
+static struct serdev_device_driver hsuart_tty_drv = {
+	.driver		= {
+		.name	= DRV_NAME,
+		.acpi_match_table = ACPI_PTR(hsuart_acpi_match),
+	},
+	.probe	= hsuart_probe,
+	.remove	= hsuart_remove,
+};
+
+module_serdev_device_driver(hsuart_tty_drv);
+
+MODULE_AUTHOR("Shrirang Bagul <shrirang.bagul@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Intel Atom (BayTrail) HS-UART serdev slave tty driver");
+MODULE_LICENSE("GPL v2");
-- 
2.14.1

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux PPP]     [Linux FS]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Linmodem]     [Device Mapper]     [Linux Kernel for ARM]

  Powered by Linux