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
next 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox