summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Glass <sjg@chromium.org>2019-12-19 17:58:18 -0700
committerBin Meng <bmeng.cn@gmail.com>2020-02-04 01:19:13 +0800
commit62cbde4c4e462e5147d07cf1424492bb39e7bf94 (patch)
tree2faa4e220f1f8102f815c99eb573073484cc68d0
parent31a790bee939e227dfc7e6a6a323b2b13180707f (diff)
serial: ns16550: Support run-time configuration
At present this driver uses an assortment of CONFIG options to control how it accesses the hardware. This is painful for platforms that are supposed to be controlled by a device tree or a previous-stage bootloader. Add a new CONFIG option to enable fully dynamic configuration. This controls register spacing, size, offset and endianness. Signed-off-by: Simon Glass <sjg@chromium.org> Reviewed-by: Aiden Park <aiden.park@intel.com> Tested-by: Aiden Park <aiden.park@intel.com> Reviewed-by: Bin Meng <bmeng.cn@gmail.com> [bmeng: squashed in http://patchwork.ozlabs.org/patch/1232929/] Signed-off-by: Bin Meng <bmeng.cn@gmail.com>
-rw-r--r--drivers/serial/Kconfig21
-rw-r--r--drivers/serial/ns16550.c79
-rw-r--r--include/ns16550.h16
3 files changed, 107 insertions, 9 deletions
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index bd95f70b61..1147654082 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -601,6 +601,27 @@ config SYS_NS16550
be used. It can be a constant or a function to get clock, eg,
get_serial_clock().
+config NS16550_DYNAMIC
+ bool "Allow NS16550 to be configured at runtime"
+ default y if SYS_COREBOOT || SYS_SLIMBOOTLOADER
+ help
+ Enable this option to allow device-tree control of the driver.
+
+ Normally this driver is controlled by the following options:
+
+ CONFIG_SYS_NS16550_PORT_MAPPED - indicates that port I/O is used for
+ access. If not enabled, then the UART is memory-mapped.
+ CONFIG_SYS_NS16550_MEM32 - if memory-mapped, indicates that 32-bit
+ access should be used (instead of 8-bit)
+ CONFIG_SYS_NS16550_REG_SIZE - indicates register width and also
+ endianness. If positive, big-endian access is used. If negative,
+ little-endian is used.
+
+ It is not a good practice for a driver to be statically configured,
+ since it prevents the same driver being used for different types of
+ UARTs in a system. This option avoids this problem at the cost of a
+ slightly increased code size.
+
config INTEL_MID_SERIAL
bool "Intel MID platform UART support"
depends on DM_SERIAL && OF_CONTROL
diff --git a/drivers/serial/ns16550.c b/drivers/serial/ns16550.c
index 9851663dc5..31f6cfe421 100644
--- a/drivers/serial/ns16550.c
+++ b/drivers/serial/ns16550.c
@@ -93,19 +93,79 @@ static inline int serial_in_shift(void *addr, int shift)
#define CONFIG_SYS_NS16550_CLK 0
#endif
+/*
+ * Use this #ifdef for now since many platforms don't define in(), out(),
+ * out_le32(), etc. but we don't have #defines to indicate this.
+ *
+ * TODO(sjg@chromium.org): Add CONFIG options to indicate what I/O is available
+ * on a platform
+ */
+#ifdef CONFIG_NS16550_DYNAMIC
+static void serial_out_dynamic(struct ns16550_platdata *plat, u8 *addr,
+ int value)
+{
+ if (plat->flags & NS16550_FLAG_IO) {
+ outb(value, addr);
+ } else if (plat->reg_width == 4) {
+ if (plat->flags & NS16550_FLAG_ENDIAN) {
+ if (plat->flags & NS16550_FLAG_BE)
+ out_be32(addr, value);
+ else
+ out_le32(addr, value);
+ } else {
+ writel(value, addr);
+ }
+ } else if (plat->flags & NS16550_FLAG_BE) {
+ writeb(value, addr + (1 << plat->reg_shift) - 1);
+ } else {
+ writeb(value, addr);
+ }
+}
+
+static int serial_in_dynamic(struct ns16550_platdata *plat, u8 *addr)
+{
+ if (plat->flags & NS16550_FLAG_IO) {
+ return inb(addr);
+ } else if (plat->reg_width == 4) {
+ if (plat->flags & NS16550_FLAG_ENDIAN) {
+ if (plat->flags & NS16550_FLAG_BE)
+ return in_be32(addr);
+ else
+ return in_le32(addr);
+ } else {
+ return readl(addr);
+ }
+ } else if (plat->flags & NS16550_FLAG_BE) {
+ return readb(addr + (1 << plat->reg_shift) - 1);
+ } else {
+ return readb(addr);
+ }
+}
+#else
+static inline void serial_out_dynamic(struct ns16550_platdata *plat, u8 *addr,
+ int value)
+{
+}
+
+static inline int serial_in_dynamic(struct ns16550_platdata *plat, u8 *addr)
+{
+ return 0;
+}
+
+#endif /* CONFIG_NS16550_DYNAMIC */
+
static void ns16550_writeb(NS16550_t port, int offset, int value)
{
struct ns16550_platdata *plat = port->plat;
unsigned char *addr;
offset *= 1 << plat->reg_shift;
- addr = (unsigned char *)plat->base + offset;
+ addr = (unsigned char *)plat->base + offset + plat->reg_offset;
- /*
- * As far as we know it doesn't make sense to support selection of
- * these options at run-time, so use the existing CONFIG options.
- */
- serial_out_shift(addr + plat->reg_offset, plat->reg_shift, value);
+ if (IS_ENABLED(CONFIG_NS16550_DYNAMIC))
+ serial_out_dynamic(plat, addr, value);
+ else
+ serial_out_shift(addr, plat->reg_shift, value);
}
static int ns16550_readb(NS16550_t port, int offset)
@@ -114,9 +174,12 @@ static int ns16550_readb(NS16550_t port, int offset)
unsigned char *addr;
offset *= 1 << plat->reg_shift;
- addr = (unsigned char *)plat->base + offset;
+ addr = (unsigned char *)plat->base + offset + plat->reg_offset;
- return serial_in_shift(addr + plat->reg_offset, plat->reg_shift);
+ if (IS_ENABLED(CONFIG_NS16550_DYNAMIC))
+ return serial_in_dynamic(plat, addr);
+ else
+ return serial_in_shift(addr, plat->reg_shift);
}
static u32 ns16550_getfcr(NS16550_t port)
diff --git a/include/ns16550.h b/include/ns16550.h
index 701efeea85..18c9077755 100644
--- a/include/ns16550.h
+++ b/include/ns16550.h
@@ -31,6 +31,9 @@
#define CONFIG_SYS_NS16550_REG_SIZE (-1)
#endif
+#ifdef CONFIG_NS16550_DYNAMIC
+#define UART_REG(x) unsigned char x
+#else
#if !defined(CONFIG_SYS_NS16550_REG_SIZE) || (CONFIG_SYS_NS16550_REG_SIZE == 0)
#error "Please define NS16550 registers size."
#elif defined(CONFIG_SYS_NS16550_MEM32) && !defined(CONFIG_DM_SERIAL)
@@ -44,14 +47,24 @@
unsigned char x; \
unsigned char postpad_##x[-CONFIG_SYS_NS16550_REG_SIZE - 1];
#endif
+#endif /* CONFIG_NS16550_DYNAMIC */
+
+enum ns16550_flags {
+ NS16550_FLAG_IO = 1 << 0, /* Use I/O access (else mem-mapped) */
+ NS16550_FLAG_ENDIAN = 1 << 1, /* Use out_le/be_32() */
+ NS16550_FLAG_BE = 1 << 2, /* Big-endian access (else little) */
+};
/**
* struct ns16550_platdata - information about a NS16550 port
*
* @base: Base register address
- * @reg_width: IO accesses size of registers (in bytes)
+ * @reg_width: IO accesses size of registers (in bytes, 1 or 4)
* @reg_shift: Shift size of registers (0=byte, 1=16bit, 2=32bit...)
+ * @reg_offset: Offset to start of registers (normally 0)
* @clock: UART base clock speed in Hz
+ * @fcr: Offset of FCR register (normally UART_FCR_DEFVAL)
+ * @flags: A few flags (enum ns16550_flags)
* @bdf: PCI slot/function (pci_dev_t)
*/
struct ns16550_platdata {
@@ -61,6 +74,7 @@ struct ns16550_platdata {
int reg_offset;
int clock;
u32 fcr;
+ int flags;
#if defined(CONFIG_PCI) && defined(CONFIG_SPL)
int bdf;
#endif