[PATCH] serial:Add SUNIX SDC serial port driver

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

 



From: Morris Ku <saumah@xxxxxxxxx>

SUNIX SDC serial port driver.

Cc: Jason Lee <jason_lee@xxxxxxxxx>
Cc: Taian Chen <taian.chen@xxxxxxxxx>
Signed-off-by: Morris Ku <saumah@xxxxxxxxx>
---
 8250_sdc.c | 519 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 519 insertions(+)
 create mode 100644 8250_sdc.c

diff --git a/drivers/tty/serial/8250_sdc.c b/8250_sdc.c
new file mode 100644
index 0000000..85ff351
--- /dev/null
+++ b/drivers/tty/serial//8250_sdc.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SUNIX SDC serial port driver.
+ *
+ * Copyright (C) 2021, SUNIX Co., Ltd.
+ *
+ * Based on Synopsys DesignWare 8250 driver written by
+ * - Copyright 2011 Picochip, Jamie Iles.
+ * - Copyright 2013 Intel Corporation
+ */
+
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_platform.h>
+#include <linux/debugfs.h>
+#include <linux/serial_8250.h>
+
+struct sdc8250_port_data {
+	int bus_number;
+	int device_number;
+	int irq;
+
+	unsigned char number;
+	unsigned char version;
+	unsigned char resource_cap;
+	unsigned char event_type;
+
+	unsigned short tx_fifo_size;
+	unsigned short rx_fifo_size;
+	unsigned int significand;
+	unsigned char exponent;
+	unsigned char rs232_cap;
+	unsigned char rs422_cap;
+	unsigned char rs485_cap;
+	unsigned char ahdc_cap;
+	unsigned char cs_cap;
+	unsigned char rs422_end_cap;
+	unsigned char rs485_end_cap;
+
+	int line;
+};
+
+#define UART_SDC_V0_UMER				0x0e
+
+struct sdc8250_data {
+	struct sdc8250_port_data data;
+	struct uart_8250_port uart;
+	struct device *device;
+	char device_name[50];
+	struct dentry *debugfs;
+	struct debugfs_blob_wrapper debugfs_blob_device_name;
+
+	struct resource *regs;
+	int fifosize;
+	unsigned long long clk_rate;
+	unsigned char umr;
+};
+
+static struct dentry *sdc_serial_debugfs;
+
+static inline struct sdc8250_data *to_sdc8250_data(
+				struct sdc8250_port_data *data)
+{
+	return container_of(data, struct sdc8250_data, data);
+}
+
+static void sdc8250_do_pm(struct uart_port *p, unsigned int state,
+				unsigned int old)
+{
+	if (!state)
+		pm_runtime_get_sync(p->dev);
+
+	serial8250_do_pm(p, state, old);
+
+	if (state)
+		pm_runtime_put_sync_suspend(p->dev);
+}
+
+static int sdc8250_rs485_config(struct uart_port *p,
+				struct serial_rs485 *rs485)
+{
+	struct sdc8250_data *data = to_sdc8250_data(p->private_data);
+	int ret = 0;
+	unsigned char umr = 0;
+	unsigned char umr_check = 0;
+
+	if (rs485->flags & SER_RS485_ENABLED) {
+		if (rs485->flags & SER_RS485_RX_DURING_TX) {
+			// rs422
+			if (data->data.rs422_cap == 0x01) {
+				umr = 0x02;
+
+				if (rs485->flags & SER_RS485_TERMINATE_BUS) {
+					if (data->data.rs422_end_cap == 0x01) {
+						umr |= 0x40;
+					} else {
+						dev_err(p->dev, "no RS422 termination cap.\n");
+						ret = -EINVAL;
+					}
+				}
+			} else {
+				dev_err(p->dev, "no RS422 cap.\n");
+				ret = -EINVAL;
+			}
+		} else {
+			// rs485
+			if (data->data.rs485_cap == 0x01) {
+				umr = 0x04;
+
+				if (data->data.ahdc_cap == 0x01)
+					umr |= 0x08;
+
+				if (data->data.cs_cap == 0x01)
+					umr |= 0x10;
+
+				if (rs485->flags & SER_RS485_TERMINATE_BUS) {
+					if (data->data.rs485_end_cap == 0x01) {
+						umr |= 0x80;
+					} else {
+						dev_err(p->dev, "no RS485 termination cap.\n");
+						ret = -EINVAL;
+					}
+				}
+			} else {
+				dev_err(p->dev, "no RS485 cap.\n");
+				ret = -EINVAL;
+			}
+		}
+	} else {
+		// rs232
+		if (data->data.rs232_cap == 0x01) {
+			umr = 0x01;
+		} else {
+			dev_err(p->dev, "no rs232 cap.\n");
+			ret = -EINVAL;
+		}
+	}
+
+	if (ret == 0) {
+		do {
+			outb(umr, p->iobase + UART_SDC_V0_UMER);
+			umr_check = inb(p->iobase + UART_SDC_V0_UMER);
+			if (umr != umr_check) {
+				ret = -EIO;
+				break;
+			}
+
+			data->umr = umr;
+			p->rs485 = *rs485;
+
+		} while (false);
+	}
+
+	return ret;
+}
+
+static int sdc8250_debugfs_add(struct sdc8250_data *data)
+{
+	struct dentry *root_dir;
+	char root_name[20];
+
+	memset(root_name, 0, sizeof(root_name));
+	sprintf(root_name, "ttyS%d", data->data.line);
+	root_dir = debugfs_create_dir(root_name, sdc_serial_debugfs);
+	if (IS_ERR(root_dir))
+		return PTR_ERR(root_dir);
+
+	debugfs_create_u32("bus_number", 0644, root_dir,
+		&data->data.bus_number);
+	debugfs_create_u32("device_number", 0644, root_dir,
+		&data->data.device_number);
+	debugfs_create_u32("irq", 0644, root_dir, &data->data.irq);
+	debugfs_create_u8("number", 0644, root_dir, &data->data.number);
+	debugfs_create_u8("version", 0644, root_dir, &data->data.version);
+	debugfs_create_u8("resource_cap", 0644, root_dir,
+		&data->data.resource_cap);
+	debugfs_create_u8("event_type", 0644, root_dir,
+		&data->data.event_type);
+	debugfs_create_u16("tx_fifo_size", 0644, root_dir,
+		&data->data.tx_fifo_size);
+	debugfs_create_u16("rx_fifo_size", 0644, root_dir,
+		&data->data.rx_fifo_size);
+	debugfs_create_u32("significand", 0644, root_dir,
+		&data->data.significand);
+	debugfs_create_u8("exponent", 0644, root_dir,
+		&data->data.exponent);
+	debugfs_create_u8("rs232_cap", 0644, root_dir, &data->data.rs232_cap);
+	debugfs_create_u8("rs422_cap", 0644, root_dir, &data->data.rs422_cap);
+	debugfs_create_u8("rs485_cap", 0644, root_dir, &data->data.rs485_cap);
+	debugfs_create_u8("ahdc_cap", 0644, root_dir, &data->data.ahdc_cap);
+	debugfs_create_u8("cs_cap", 0644, root_dir, &data->data.cs_cap);
+	debugfs_create_u8("rs422_end_cap", 0644, root_dir,
+		&data->data.rs422_end_cap);
+	debugfs_create_u8("rs485_end_cap", 0644, root_dir,
+		&data->data.rs485_end_cap);
+	debugfs_create_u32("line", 0644, root_dir, &data->data.line);
+
+	memset(data->device_name, 0, sizeof(data->device_name));
+	sprintf(data->device_name, "%s", dev_name(data->device));
+	data->device_name[strlen(data->device_name)] = 0x0a;
+	data->debugfs_blob_device_name.data = data->device_name;
+	data->debugfs_blob_device_name.size = strlen(data->device_name) + 1;
+	debugfs_create_blob("device_name", 0644, root_dir,
+		&data->debugfs_blob_device_name);
+
+	data->debugfs = root_dir;
+	return 0;
+}
+
+static void sdc8250_debugfs_remove(struct sdc8250_data *data)
+{
+	debugfs_remove_recursive(data->debugfs);
+}
+
+static int sdc8250_read_property(struct sdc8250_data *data,
+				struct device *dev)
+{
+	int ret;
+
+	ret = device_property_read_u32(dev, "bus_number",
+				&data->data.bus_number);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u32(dev, "device_number",
+				&data->data.device_number);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u32(dev, "irq", &data->data.irq);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "number", &data->data.number);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "version", &data->data.version);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "resource_cap",
+				&data->data.resource_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "event_type",
+				&data->data.event_type);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u16(dev, "tx_fifo_size",
+				&data->data.tx_fifo_size);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u16(dev, "rx_fifo_size",
+				&data->data.rx_fifo_size);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u32(dev, "significand",
+				&data->data.significand);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "exponent",
+				&data->data.exponent);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "rs232_cap", &data->data.rs232_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "rs422_cap", &data->data.rs422_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "rs485_cap", &data->data.rs485_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "ahdc_cap", &data->data.ahdc_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "cs_cap", &data->data.cs_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "rs422_end_cap",
+				&data->data.rs422_end_cap);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "rs485_end_cap",
+				&data->data.rs485_end_cap);
+	if (ret < 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int config_rs422485(struct sdc8250_data *data,
+				struct serial_rs485 *rs485)
+{
+	int ret = 0;
+	unsigned char umr_check = 0;
+
+	memset(rs485, 0, sizeof(struct serial_rs485));
+	if (data->data.version == 0x00) {
+		data->umr = inb(data->regs->start + UART_SDC_V0_UMER);
+		if ((data->umr & 0x07) == 0x01) {
+			// rs232 mode
+
+		} else if ((data->umr & 0x07) == 0x02) {
+			// rs422 mode
+			rs485->flags =
+				SER_RS485_ENABLED | SER_RS485_RX_DURING_TX;
+
+			if (data->umr & 0x40)
+				rs485->flags |= SER_RS485_TERMINATE_BUS;
+
+		} else if ((data->umr & 0x07) == 0x04) {
+			// rs485 mode
+			rs485->flags = SER_RS485_ENABLED;
+
+			if (data->umr & 0x80)
+				rs485->flags |= SER_RS485_TERMINATE_BUS;
+
+			if (data->data.ahdc_cap == 0x01)
+				data->umr |= 0x08;
+
+			if (data->data.cs_cap == 0x01)
+				data->umr |= 0x10;
+
+			outb(data->umr, data->regs->start + UART_SDC_V0_UMER);
+			umr_check = inb(data->regs->start + UART_SDC_V0_UMER);
+
+			if (data->umr != umr_check)
+				ret = -EIO;
+
+		} else {
+			ret = -EINVAL;
+		}
+	} else {
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static int sdc8250_probe(struct platform_device *pdev)
+{
+	int ret;
+	int i;
+	struct uart_8250_port *up = NULL;
+	struct uart_port *p = NULL;
+	struct sdc8250_data *data;
+	struct device *dev = &pdev->dev;
+	struct resource *regs = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	int irq;
+	unsigned long long exponent;
+	struct serial_rs485 rs485;
+
+	if (!regs) {
+		dev_err(dev, "no registers defined\n");
+		return -EINVAL;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = sdc8250_read_property(data, dev);
+	if (ret < 0) {
+		dev_err(dev, "failed to read property\n");
+		return ret;
+	}
+	data->device = dev;
+	data->regs = regs;
+	up = &data->uart;
+	p = &up->port;
+
+	data->fifosize = data->data.rx_fifo_size;
+	if (data->fifosize > data->data.tx_fifo_size)
+		data->fifosize = data->data.tx_fifo_size;
+
+	exponent = 1;
+	for (i = 0; i < data->data.exponent; i++)
+		exponent *= 10;
+	data->clk_rate = data->data.significand * exponent;
+
+	ret = config_rs422485(data, &rs485);
+	if (ret < 0)
+		goto err_do_nothing;
+
+	spin_lock_init(&p->lock);
+	p->iotype       = UPIO_PORT;
+	p->iobase       = regs->start;
+	p->mapbase      = 0;
+	p->membase      = NULL;
+	p->regshift     = 0;
+
+	p->irq          = irq;
+	p->type         = PORT_SUNIX;
+	p->flags        = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE;
+	p->fifosize     = data->fifosize;
+	p->dev          = dev;
+	p->uartclk      = data->clk_rate;
+	p->private_data = &data->data;
+
+	p->pm           = sdc8250_do_pm;
+	p->rs485_config = sdc8250_rs485_config;
+	memcpy(&p->rs485, &rs485, sizeof(rs485));
+
+	data->data.line = serial8250_register_8250_port(up);
+	if (data->data.line < 0) {
+		ret = data->data.line;
+		goto err_do_nothing;
+	}
+
+	ret = sdc8250_debugfs_add(data);
+	if (ret)
+		dev_warn(dev, "failed to create debugfs entries\n");
+
+	platform_set_drvdata(pdev, data);
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	return 0;
+
+err_do_nothing:
+	dev_err(dev, "failed to register, error:%d\n", ret);
+	return ret;
+}
+
+static int sdc8250_remove(struct platform_device *pdev)
+{
+	struct sdc8250_data *data = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+
+	pm_runtime_get_sync(dev);
+
+	sdc8250_debugfs_remove(data);
+	serial8250_unregister_port(data->data.line);
+
+	pm_runtime_disable(dev);
+	pm_runtime_put_noidle(dev);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sdc8250_suspend(struct device *dev)
+{
+	struct sdc8250_data *data = dev_get_drvdata(dev);
+
+	// save umr
+	data->umr = inb(data->uart.port.iobase + UART_SDC_V0_UMER);
+
+	serial8250_suspend_port(data->data.line);
+	return 0;
+}
+
+static int sdc8250_resume(struct device *dev)
+{
+	struct sdc8250_data *data = dev_get_drvdata(dev);
+
+	// restore umr
+	outb(data->umr, data->uart.port.iobase + UART_SDC_V0_UMER);
+
+	serial8250_resume_port(data->data.line);
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int sdc8250_runtime_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static int sdc8250_runtime_resume(struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops sdc8250_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(sdc8250_suspend, sdc8250_resume)
+	SET_RUNTIME_PM_OPS(sdc8250_runtime_suspend,
+		sdc8250_runtime_resume, NULL)
+};
+
+static const struct of_device_id sdc8250_of_match[] = {
+	{ .compatible = "sunix,sdc_serial" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sdc8250_of_match);
+
+static struct platform_driver sdc8250_platform_driver = {
+	.driver = {
+		.name = "8250_sdc",
+		.pm = &sdc8250_pm_ops,
+		.of_match_table = sdc8250_of_match,
+	},
+	.probe = sdc8250_probe,
+	.remove = sdc8250_remove,
+};
+
+static int __init sdc8250_init(void)
+{
+	sdc_serial_debugfs = debugfs_create_dir("sdc_serial", NULL);
+	platform_driver_register(&sdc8250_platform_driver);
+	return 0;
+}
+module_init(sdc8250_init);
+
+static void __exit sdc8250_exit(void)
+{
+	platform_driver_unregister(&sdc8250_platform_driver);
+	debugfs_remove(sdc_serial_debugfs);
+}
+module_exit(sdc8250_exit);
+
+MODULE_AUTHOR("Jason Lee <jason_lee@xxxxxxxxx>");
+MODULE_DESCRIPTION("SUNIX SDC serial port driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("platform:8250_sdc");
+
+
-- 
2.20.1




[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