All of lore.kernel.org
 help / color / mirror / Atom feed
From: Moriis Ku <saumah@gmail.com>
To: gregkh@linuxfoundation.org
Cc: linux-serial@vger.kernel.org, jason_lee@sunix.com,
	taian.chen@sunix.com, morris_ku@sunix.com, edward.lee@sunix.com,
	Morris Ku <saumah@gmail.com>, Edward Lee <Edward.lee@sunix.com>
Subject: [PATCH] SUNIX SDC serial port driver
Date: Fri, 25 Jun 2021 17:30:11 +0800	[thread overview]
Message-ID: <20210625093011.12659-1-saumah@gmail.com> (raw)

From: Morris Ku <saumah@gmail.com>

Add support for SUNIX SDC serial port

Cc: Taian Chen <taian.chen@sunix.com>
Cc: Morris Ku <morris_ku@sunix.com>
Cc: Edward Lee <Edward.lee@sunix.com>
Signed-off-by: Morris Ku <saumah@gmail.com>
---
 8250_sdc.c | 410 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 410 insertions(+)
 create mode 100644 8250_sdc.c

diff --git a/8250_sdc.c b/8250_sdc.c
new file mode 100644
index 0000000..3ec9ce7
--- /dev/null
+++ b/8250_sdc.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SUNIX SDC serial port driver.
+ *
+ * Copyright (C) 2021, SUNIX Co., Ltd.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/serial_8250.h>
+
+#define DRIVER_NAME		"8250_sdc"
+
+struct sdc8250_port_data {
+	u32 board_id;
+	u8 chl_number;
+	u8 version;
+	u16 tx_fifo_size;
+	u16 rx_fifo_size;
+	u32 significand;
+	u8 exponent;
+	u32 capability;
+
+	int line;
+};
+
+#define SDC_UART_UMR				0x0e
+
+#define SDC_UART_RS232				BIT(0)
+#define SDC_UART_RS422				BIT(1)
+#define SDC_UART_RS485				BIT(2)
+#define SDC_UART_MODE_MASK			GENMASK(2, 0)
+#define SDC_UART_AHDC				BIT(3)
+#define SDC_UART_CS					BIT(4)
+#define SDC_UART_AUTO_RS422485		BIT(5)
+#define SDC_UART_RS422_TERMINATION	BIT(6)
+#define SDC_UART_RS485_TERMINATION	BIT(7)
+
+struct sdc8250_data {
+	struct sdc8250_port_data data;
+	struct uart_8250_port uart;
+	struct device *dev;
+	struct resource *res;
+	int fifosize;
+	unsigned long long clk_rate;
+	u8 umr_suspend;
+};
+
+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);
+	u32 flags = rs485->flags;
+	int ret;
+	u8 umr;
+
+	if (!(flags & SER_RS485_ENABLED)) {
+		if (!(data->data.capability & SDC_UART_RS232)) {
+			dev_err(data->dev, "no RS232 cap.\n");
+			ret = -EINVAL;
+			goto err_out;
+		}
+		umr = SDC_UART_RS232;
+
+	} else if ((flags & SER_RS485_ENABLED) &&
+				(flags & SER_RS485_RX_DURING_TX)) {
+		if (!(data->data.capability & SDC_UART_RS422)) {
+			dev_err(data->dev, "no RS422 cap.\n");
+			ret = -EINVAL;
+			goto err_out;
+		}
+		umr = SDC_UART_RS422;
+
+		if (flags & SER_RS485_TERMINATE_BUS) {
+			if (!(data->data.capability &
+					SDC_UART_RS422_TERMINATION)) {
+				dev_err(data->dev, "no RS422 termination cap.\n");
+				ret = -EINVAL;
+				goto err_out;
+			}
+			umr |= SDC_UART_RS422_TERMINATION;
+		}
+
+	} else if (flags & SER_RS485_ENABLED) {
+		if (!(data->data.capability & SDC_UART_RS485)) {
+			dev_err(data->dev, "no RS485 cap.\n");
+			ret = -EINVAL;
+			goto err_out;
+		}
+		umr = SDC_UART_RS485;
+
+		if (data->data.capability & SDC_UART_AHDC)
+			umr |= SDC_UART_AHDC;
+
+		if (data->data.capability & SDC_UART_CS)
+			umr |= SDC_UART_CS;
+
+		if (flags & SER_RS485_TERMINATE_BUS) {
+			if (!(data->data.capability &
+					SDC_UART_RS485_TERMINATION)) {
+				dev_err(data->dev, "no RS485 termination cap.\n");
+				ret = -EINVAL;
+				goto err_out;
+			}
+			umr |= SDC_UART_RS485_TERMINATION;
+		}
+
+	} else {
+		ret = -EINVAL;
+		goto err_out;
+	}
+
+	outb(umr, p->iobase + SDC_UART_UMR);
+	p->rs485 = *rs485;
+	return 0;
+
+err_out:
+	return ret;
+}
+
+static int sdc8250_read_property(struct sdc8250_port_data *data,
+				struct device *dev)
+{
+	int ret;
+
+	ret = device_property_read_u32(dev, "board_id", &data->board_id);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "chl_number", &data->chl_number);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "version", &data->version);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u16(dev, "tx_fifo_size",
+			&data->tx_fifo_size);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u16(dev, "rx_fifo_size",
+			&data->rx_fifo_size);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u32(dev, "significand", &data->significand);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u8(dev, "exponent", &data->exponent);
+	if (ret < 0)
+		return -EINVAL;
+	ret = device_property_read_u32(dev, "capability", &data->capability);
+	if (ret < 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int sdc8250_rs485_init(struct sdc8250_data *data,
+				struct serial_rs485 *rs485)
+{
+	int ret = 0;
+	u8 umr;
+
+	if (data->data.version != 0)
+		return -EINVAL;
+
+	memset(rs485, 0, sizeof(struct serial_rs485));
+	umr = inb(data->res->start + SDC_UART_UMR);
+	if ((umr & SDC_UART_MODE_MASK) == SDC_UART_RS232) {
+		/* RS232 mode */
+
+	} else if ((umr & SDC_UART_MODE_MASK) == SDC_UART_RS422) {
+		/* RS422 mode */
+		rs485->flags = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX;
+
+		if (umr & SDC_UART_RS422_TERMINATION)
+			rs485->flags |= SER_RS485_TERMINATE_BUS;
+
+	} else if ((umr & SDC_UART_MODE_MASK) == SDC_UART_RS485) {
+		/* RS485 mode */
+		rs485->flags = SER_RS485_ENABLED;
+
+		if (umr & SDC_UART_RS485_TERMINATION)
+			rs485->flags |= SER_RS485_TERMINATE_BUS;
+
+		if (data->data.capability & SDC_UART_AHDC)
+			umr |= SDC_UART_AHDC;
+
+		if (data->data.capability & SDC_UART_CS)
+			umr |= SDC_UART_CS;
+
+		outb(umr, data->res->start + SDC_UART_UMR);
+
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static struct dentry *sdc8250_debugfs;
+#define SDC8250_DEBUGFS_FORMAT		"b%d:uart%d"
+
+static void sdc8250_debugfs_add(struct sdc8250_data *data)
+{
+	struct dentry *dir;
+	char name[32];
+
+	snprintf(name, sizeof(name), SDC8250_DEBUGFS_FORMAT,
+		data->data.board_id, data->data.chl_number);
+	dir = debugfs_create_dir(name, sdc8250_debugfs);
+	debugfs_create_x8("version", 0444, dir, &data->data.version);
+	debugfs_create_x32("capability", 0444, dir, &data->data.capability);
+	debugfs_create_u32("line", 0444, dir, &data->data.line);
+}
+
+static void sdc8250_debugfs_remove(struct sdc8250_data *data)
+{
+	struct dentry *dir;
+	char name[32];
+
+	snprintf(name, sizeof(name), SDC8250_DEBUGFS_FORMAT,
+		data->data.board_id, data->data.chl_number);
+	dir = debugfs_lookup(name, sdc8250_debugfs);
+	debugfs_remove_recursive(dir);
+}
+
+static void sdc8250_debugfs_init(void)
+{
+	sdc8250_debugfs = debugfs_create_dir(DRIVER_NAME, NULL);
+}
+
+static void sdc8250_debugfs_exit(void)
+{
+	debugfs_remove(sdc8250_debugfs);
+}
+#else
+static void sdc8250_debugfs_add(struct sdc8250_data *data) {}
+static void sdc8250_debugfs_remove(struct sdc8250_data *data) {}
+static void sdc8250_debugfs_init(void) {}
+static void sdc8250_debugfs_exit(void) {}
+#endif
+
+static int sdc8250_probe(struct platform_device *pdev)
+{
+	unsigned long long exponent;
+	struct uart_8250_port *up;
+	struct sdc8250_data *data;
+	struct serial_rs485 rs485;
+	struct uart_port *p;
+	struct resource *res;
+	struct device *dev;
+	int irq;
+	int ret;
+	int i;
+
+	dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	if (!res) {
+		dev_err(dev, "no resource 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->data, dev);
+	if (ret < 0) {
+		dev_err(dev, "failed to read property\n");
+		return ret;
+	}
+	data->dev = dev;
+	data->res = res;
+	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 = sdc8250_rs485_init(data, &rs485);
+	if (ret < 0)
+		goto err_out;
+
+	spin_lock_init(&p->lock);
+	p->iotype       = UPIO_PORT;
+	p->iobase       = res->start;
+	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->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_out;
+	}
+	platform_set_drvdata(pdev, data);
+
+	sdc8250_debugfs_add(data);
+	return 0;
+
+err_out:
+	dev_err(dev, "failed to probe, err %d\n", ret);
+	return ret;
+}
+
+static int sdc8250_remove(struct platform_device *pdev)
+{
+	struct sdc8250_data *data = platform_get_drvdata(pdev);
+
+	sdc8250_debugfs_remove(data);
+	serial8250_unregister_port(data->data.line);
+	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_suspend = inb(data->uart.port.iobase + SDC_UART_UMR);
+	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_suspend, data->uart.port.iobase + SDC_UART_UMR);
+	serial8250_resume_port(data->data.line);
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops sdc8250_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(sdc8250_suspend, sdc8250_resume)
+};
+
+static struct platform_driver sdc8250_platform_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.pm = &sdc8250_pm_ops,
+	},
+	.probe = sdc8250_probe,
+	.remove = sdc8250_remove,
+};
+
+static int __init sdc8250_init(void)
+{
+	sdc8250_debugfs_init();
+	platform_driver_register(&sdc8250_platform_driver);
+	return 0;
+}
+module_init(sdc8250_init);
+
+static void __exit sdc8250_exit(void)
+{
+	platform_driver_unregister(&sdc8250_platform_driver);
+	sdc8250_debugfs_exit();
+}
+module_exit(sdc8250_exit);
+
+MODULE_AUTHOR("Jason Lee <jason_lee@sunix.com>");
+MODULE_DESCRIPTION("SUNIX SDC serial port driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
2.20.1


             reply	other threads:[~2021-06-25  9:30 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-06-25  9:30 Moriis Ku [this message]
2021-06-25  9:37 ` [PATCH] SUNIX SDC serial port driver Greg KH
  -- strict thread matches above, loose matches on Subject: below --
2021-06-25  9:29 Moriis Ku
2021-06-25  9:36 ` Greg KH
2021-06-25  9:29 Moriis Ku
2021-06-25  9:36 ` Greg KH

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210625093011.12659-1-saumah@gmail.com \
    --to=saumah@gmail.com \
    --cc=edward.lee@sunix.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jason_lee@sunix.com \
    --cc=linux-serial@vger.kernel.org \
    --cc=morris_ku@sunix.com \
    --cc=taian.chen@sunix.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.