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 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.