Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Chaitanya Vadrevu 1528 96.28% 1 16.67%
Andy Shevchenko 59 3.72% 5 83.33%
Total 1587 6


// SPDX-License-Identifier: GPL-2.0+
/*
 * NI 16550 UART Driver
 *
 * The National Instruments (NI) 16550 is a UART that is compatible with the
 * TL16C550C and OX16C950B register interfaces, but has additional functions
 * for RS-485 transceiver control. This driver implements support for the
 * additional functionality on top of the standard serial8250 core.
 *
 * Copyright 2012-2023 National Instruments Corporation
 */

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/serial_core.h>
#include <linux/types.h>

#include "8250.h"

/* Extra bits in UART_ACR */
#define NI16550_ACR_AUTO_DTR_EN			BIT(4)

/* TFS - TX FIFO Size */
#define NI16550_TFS_OFFSET	0x0C
/* RFS - RX FIFO Size */
#define NI16550_RFS_OFFSET	0x0D

/* PMR - Port Mode Register */
#define NI16550_PMR_OFFSET	0x0E
/* PMR[1:0] - Port Capabilities */
#define NI16550_PMR_CAP_MASK		GENMASK(1, 0)
#define NI16550_PMR_NOT_IMPL		FIELD_PREP(NI16550_PMR_CAP_MASK, 0) /* not implemented */
#define NI16550_PMR_CAP_RS232		FIELD_PREP(NI16550_PMR_CAP_MASK, 1) /* RS-232 capable */
#define NI16550_PMR_CAP_RS485		FIELD_PREP(NI16550_PMR_CAP_MASK, 2) /* RS-485 capable */
#define NI16550_PMR_CAP_DUAL		FIELD_PREP(NI16550_PMR_CAP_MASK, 3) /* dual-port */
/* PMR[4] - Interface Mode */
#define NI16550_PMR_MODE_MASK		GENMASK(4, 4)
#define NI16550_PMR_MODE_RS232		FIELD_PREP(NI16550_PMR_MODE_MASK, 0) /* currently 232 */
#define NI16550_PMR_MODE_RS485		FIELD_PREP(NI16550_PMR_MODE_MASK, 1) /* currently 485 */

/* PCR - Port Control Register */
/*
 * Wire Mode      | Tx enabled?          | Rx enabled?
 * ---------------|----------------------|--------------------------
 * PCR_RS422      | Always               | Always
 * PCR_ECHO_RS485 | When DTR asserted    | Always
 * PCR_DTR_RS485  | When DTR asserted    | Disabled when TX enabled
 * PCR_AUTO_RS485 | When data in TX FIFO | Disabled when TX enabled
 */
#define NI16550_PCR_OFFSET	0x0F
#define NI16550_PCR_WIRE_MODE_MASK		GENMASK(1, 0)
#define NI16550_PCR_RS422			FIELD_PREP(NI16550_PCR_WIRE_MODE_MASK, 0)
#define NI16550_PCR_ECHO_RS485			FIELD_PREP(NI16550_PCR_WIRE_MODE_MASK, 1)
#define NI16550_PCR_DTR_RS485			FIELD_PREP(NI16550_PCR_WIRE_MODE_MASK, 2)
#define NI16550_PCR_AUTO_RS485			FIELD_PREP(NI16550_PCR_WIRE_MODE_MASK, 3)
#define NI16550_PCR_TXVR_ENABLE_BIT		BIT(3)
#define NI16550_PCR_RS485_TERMINATION_BIT	BIT(6)

/* flags for ni16550_device_info */
#define NI_HAS_PMR		BIT(0)

struct ni16550_device_info {
	u32 uartclk;
	u8 prescaler;
	u8 flags;
};

struct ni16550_data {
	int line;
	struct clk *clk;
};

static int ni16550_enable_transceivers(struct uart_port *port)
{
	u8 pcr;

	pcr = port->serial_in(port, NI16550_PCR_OFFSET);
	pcr |= NI16550_PCR_TXVR_ENABLE_BIT;
	dev_dbg(port->dev, "enable transceivers: write pcr: 0x%02x\n", pcr);
	port->serial_out(port, NI16550_PCR_OFFSET, pcr);

	return 0;
}

static int ni16550_disable_transceivers(struct uart_port *port)
{
	u8 pcr;

	pcr = serial_port_in(port, NI16550_PCR_OFFSET);
	pcr &= ~NI16550_PCR_TXVR_ENABLE_BIT;
	dev_dbg(port->dev, "disable transceivers: write pcr: 0x%02x\n", pcr);
	serial_port_out(port, NI16550_PCR_OFFSET, pcr);

	return 0;
}

static int ni16550_rs485_config(struct uart_port *port,
				struct ktermios *termios,
				struct serial_rs485 *rs485)
{
	struct uart_8250_port *up = container_of(port, struct uart_8250_port, port);
	u8 pcr;

	pcr = serial_port_in(port, NI16550_PCR_OFFSET);
	pcr &= ~NI16550_PCR_WIRE_MODE_MASK;

	if ((rs485->flags & SER_RS485_MODE_RS422) ||
	    !(rs485->flags & SER_RS485_ENABLED)) {
		/* RS-422 */
		pcr |= NI16550_PCR_RS422;
		up->acr &= ~NI16550_ACR_AUTO_DTR_EN;
	} else {
		/* RS-485 2-wire Auto */
		pcr |= NI16550_PCR_AUTO_RS485;
		up->acr |= NI16550_ACR_AUTO_DTR_EN;
	}

	dev_dbg(port->dev, "config rs485: write pcr: 0x%02x, acr: %02x\n", pcr, up->acr);
	serial_port_out(port, NI16550_PCR_OFFSET, pcr);
	serial_icr_write(up, UART_ACR, up->acr);

	return 0;
}

static bool is_pmr_rs232_mode(struct uart_8250_port *up)
{
	u8 pmr = serial_in(up, NI16550_PMR_OFFSET);
	u8 pmr_mode = pmr & NI16550_PMR_MODE_MASK;
	u8 pmr_cap = pmr & NI16550_PMR_CAP_MASK;

	/*
	 * If the PMR is not implemented, then by default NI UARTs are
	 * connected to RS-485 transceivers
	 */
	if (pmr_cap == NI16550_PMR_NOT_IMPL)
		return false;

	if (pmr_cap == NI16550_PMR_CAP_DUAL)
		/*
		 * If the port is dual-mode capable, then read the mode bit
		 * to know the current mode
		 */
		return pmr_mode == NI16550_PMR_MODE_RS232;
	/*
	 * If it is not dual-mode capable, then decide based on the
	 * capability
	 */
	return pmr_cap == NI16550_PMR_CAP_RS232;
}

static void ni16550_config_prescaler(struct uart_8250_port *up,
				     u8 prescaler)
{
	/*
	 * Page in the Enhanced Mode Registers
	 * Sets EFR[4] for Enhanced Mode.
	 */
	u8 lcr_value;
	u8 efr_value;

	lcr_value = serial_in(up, UART_LCR);
	serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);

	efr_value = serial_in(up, UART_EFR);
	efr_value |= UART_EFR_ECB;

	serial_out(up, UART_EFR, efr_value);

	/* Page out the Enhanced Mode Registers */
	serial_out(up, UART_LCR, lcr_value);

	/* Set prescaler to CPR register. */
	serial_out(up, UART_SCR, UART_CPR);
	serial_out(up, UART_ICR, prescaler);
}

static const struct serial_rs485 ni16550_rs485_supported = {
	.flags = SER_RS485_ENABLED | SER_RS485_MODE_RS422 | SER_RS485_RTS_ON_SEND |
		 SER_RS485_RTS_AFTER_SEND,
	/*
	 * delay_rts_* and RX_DURING_TX are not supported.
	 *
	 * RTS_{ON,AFTER}_SEND are supported, but ignored; the transceiver
	 * is connected in only one way and we don't need userspace to tell
	 * us, but want to retain compatibility with applications that do.
	 */
};

static void ni16550_rs485_setup(struct uart_port *port)
{
	port->rs485_config = ni16550_rs485_config;
	port->rs485_supported = ni16550_rs485_supported;
	/*
	 * The hardware comes up by default in 2-wire auto mode and we
	 * set the flags to represent that
	 */
	port->rs485.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND;
}

static int ni16550_port_startup(struct uart_port *port)
{
	int ret;

	ret = serial8250_do_startup(port);
	if (ret)
		return ret;

	return ni16550_enable_transceivers(port);
}

static void ni16550_port_shutdown(struct uart_port *port)
{
	ni16550_disable_transceivers(port);

	serial8250_do_shutdown(port);
}

static int ni16550_get_regs(struct platform_device *pdev,
			    struct uart_port *port)
{
	struct resource *regs;

	regs = platform_get_mem_or_io(pdev, 0);
	if (!regs)
		return dev_err_probe(&pdev->dev, -EINVAL, "no registers defined\n");

	switch (resource_type(regs)) {
	case IORESOURCE_IO:
		port->iotype = UPIO_PORT;
		port->iobase = regs->start;

		return 0;
	case IORESOURCE_MEM:
		port->iotype = UPIO_MEM;
		port->mapbase = regs->start;
		port->mapsize = resource_size(regs);
		port->flags |= UPF_IOREMAP;

		return 0;
	default:
		return -EINVAL;
	}
}

/*
 * Very old implementations don't have the TFS or RFS registers
 * defined, so we may read all-0s or all-1s. For such devices,
 * assume a FIFO size of 128.
 */
static u8 ni16550_read_fifo_size(struct uart_8250_port *uart, int reg)
{
	u8 value = serial_in(uart, reg);

	if (value == 0x00 || value == 0xFF)
		return 128;

	return value;
}

static void ni16550_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
	struct uart_8250_port *up = up_to_u8250p(port);

	up->mcr |= UART_MCR_CLKSEL;
	serial8250_do_set_mctrl(port, mctrl);
}

static int ni16550_probe(struct platform_device *pdev)
{
	const struct ni16550_device_info *info;
	struct device *dev = &pdev->dev;
	struct uart_8250_port uart = {};
	unsigned int txfifosz, rxfifosz;
	unsigned int prescaler;
	struct ni16550_data *data;
	const char *portmode;
	bool rs232_property;
	int ret;

	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	spin_lock_init(&uart.port.lock);

	ret = ni16550_get_regs(pdev, &uart.port);
	if (ret < 0)
		return ret;

	/* early setup so that serial_in()/serial_out() work */
	serial8250_set_defaults(&uart);

	info = device_get_match_data(dev);

	uart.port.dev		= dev;
	uart.port.flags		= UPF_BOOT_AUTOCONF | UPF_FIXED_PORT | UPF_FIXED_TYPE;
	uart.port.startup	= ni16550_port_startup;
	uart.port.shutdown	= ni16550_port_shutdown;

	/*
	 * Hardware instantiation of FIFO sizes are held in registers.
	 */
	txfifosz = ni16550_read_fifo_size(&uart, NI16550_TFS_OFFSET);
	rxfifosz = ni16550_read_fifo_size(&uart, NI16550_RFS_OFFSET);

	dev_dbg(dev, "NI 16550 has TX FIFO size %u, RX FIFO size %u\n",
		txfifosz, rxfifosz);

	uart.port.type		= PORT_16550A;
	uart.port.fifosize	= txfifosz;
	uart.tx_loadsz		= txfifosz;
	uart.fcr		= UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10;
	uart.capabilities	= UART_CAP_FIFO | UART_CAP_AFE | UART_CAP_EFR;

	/*
	 * Declaration of the base clock frequency can come from one of:
	 * - static declaration in this driver (for older ACPI IDs)
	 * - a "clock-frequency" ACPI
	 */
	uart.port.uartclk = info->uartclk;

	ret = uart_read_port_properties(&uart.port);
	if (ret)
		return ret;

	if (!uart.port.uartclk) {
		data->clk = devm_clk_get_enabled(dev, NULL);
		if (!IS_ERR(data->clk))
			uart.port.uartclk = clk_get_rate(data->clk);
	}

	if (!uart.port.uartclk)
		return dev_err_probe(dev, -ENODEV, "unable to determine clock frequency!\n");

	prescaler = info->prescaler;
	device_property_read_u32(dev, "clock-prescaler", &prescaler);
	if (prescaler) {
		uart.port.set_mctrl = ni16550_set_mctrl;
		ni16550_config_prescaler(&uart, (u8)prescaler);
	}

	/*
	 * The determination of whether or not this is an RS-485 or RS-232 port
	 * can come from the PMR (if present), otherwise we're solely an RS-485
	 * port.
	 *
	 * This is a device-specific property, and there are old devices in the
	 * field using "transceiver" as an ACPI property, so we have to check
	 * for that as well.
	 */
	if (!device_property_read_string(dev, "transceiver", &portmode)) {
		rs232_property = strncmp(portmode, "RS-232", 6) == 0;

		dev_dbg(dev, "port is in %s mode (via device property)\n",
			rs232_property ? "RS-232" : "RS-485");
	} else if (info->flags & NI_HAS_PMR) {
		rs232_property = is_pmr_rs232_mode(&uart);

		dev_dbg(dev, "port is in %s mode (via PMR)\n",
			rs232_property ? "RS-232" : "RS-485");
	} else {
		rs232_property = 0;

		dev_dbg(dev, "port is fixed as RS-485\n");
	}

	if (!rs232_property) {
		/*
		 * Neither the 'transceiver' property nor the PMR indicate
		 * that this is an RS-232 port, so it must be an RS-485 one.
		 */
		ni16550_rs485_setup(&uart.port);
	}

	ret = serial8250_register_8250_port(&uart);
	if (ret < 0)
		return ret;
	data->line = ret;

	platform_set_drvdata(pdev, data);
	return 0;
}

static void ni16550_remove(struct platform_device *pdev)
{
	struct ni16550_data *data = platform_get_drvdata(pdev);

	serial8250_unregister_port(data->line);
}

/* NI 16550 RS-485 Interface */
static const struct ni16550_device_info nic7750 = {
	.uartclk = 33333333,
};

/* NI CVS-145x RS-485 Interface */
static const struct ni16550_device_info nic7772 = {
	.uartclk = 1843200,
	.flags = NI_HAS_PMR,
};

/* NI cRIO-904x RS-485 Interface */
static const struct ni16550_device_info nic792b = {
	/* Sets UART clock rate to 22.222 MHz with 1.125 prescale */
	.uartclk = 22222222,
	.prescaler = 0x09,
};

/* NI sbRIO 96x8 RS-232/485 Interfaces */
static const struct ni16550_device_info nic7a69 = {
	/* Set UART clock rate to 29.629 MHz with 1.125 prescale */
	.uartclk = 29629629,
	.prescaler = 0x09,
};

static const struct acpi_device_id ni16550_acpi_match[] = {
	{ "NIC7750",	(kernel_ulong_t)&nic7750 },
	{ "NIC7772",	(kernel_ulong_t)&nic7772 },
	{ "NIC792B",	(kernel_ulong_t)&nic792b },
	{ "NIC7A69",	(kernel_ulong_t)&nic7a69 },
	{ }
};
MODULE_DEVICE_TABLE(acpi, ni16550_acpi_match);

static struct platform_driver ni16550_driver = {
	.driver = {
		.name = "ni16550",
		.acpi_match_table = ni16550_acpi_match,
	},
	.probe = ni16550_probe,
	.remove = ni16550_remove,
};

module_platform_driver(ni16550_driver);

MODULE_AUTHOR("Emerson Electric Co.");
MODULE_DESCRIPTION("NI 16550 Driver");
MODULE_LICENSE("GPL");