File Index
Files and directories
Sign in
FILE

8250_keba.c

File preview with syntax highlighting.

linux-6.19.14/drivers/tty/serial/8250/8250_keba.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Copyright (C) 2025 KEBA Industrial Automation GmbH
  4 *
  5 * Driver for KEBA UART FPGA IP core
  6 */
  7
  8#include <linux/auxiliary_bus.h>
  9#include <linux/device.h>
 10#include <linux/io.h>
 11#include <linux/misc/keba.h>
 12#include <linux/module.h>
 13
 14#include "8250.h"
 15
 16#define KUART "kuart"
 17
 18/* flags */
 19#define KUART_RS485		BIT(0)
 20#define KUART_USE_CAPABILITY	BIT(1)
 21
 22/* registers */
 23#define KUART_VERSION		0x0000
 24#define KUART_REVISION		0x0001
 25#define KUART_CAPABILITY	0x0002
 26#define KUART_CONTROL		0x0004
 27#define KUART_BASE		0x000C
 28#define KUART_REGSHIFT		2
 29#define KUART_CLK		1843200
 30
 31/* mode flags */
 32enum kuart_mode {
 33	KUART_MODE_NONE = 0,
 34	KUART_MODE_RS485,
 35	KUART_MODE_RS422,
 36	KUART_MODE_RS232
 37};
 38
 39/* capability flags */
 40#define KUART_CAPABILITY_NONE	BIT(KUART_MODE_NONE)
 41#define KUART_CAPABILITY_RS485	BIT(KUART_MODE_RS485)
 42#define KUART_CAPABILITY_RS422	BIT(KUART_MODE_RS422)
 43#define KUART_CAPABILITY_RS232	BIT(KUART_MODE_RS232)
 44#define KUART_CAPABILITY_MASK	GENMASK(3, 0)
 45
 46/* Additional Control Register DTR line configuration */
 47#define UART_ACR_DTRLC_MASK		0x18
 48#define UART_ACR_DTRLC_COMPAT		0x00
 49#define UART_ACR_DTRLC_ENABLE_LOW	0x10
 50
 51struct kuart {
 52	struct keba_uart_auxdev *auxdev;
 53	void __iomem *base;
 54	unsigned int line;
 55
 56	unsigned int flags;
 57	u8 capability;
 58	enum kuart_mode mode;
 59};
 60
 61static void kuart_set_phy_mode(struct kuart *kuart, enum kuart_mode mode)
 62{
 63	iowrite8(mode, kuart->base + KUART_CONTROL);
 64}
 65
 66static void kuart_enhanced_mode(struct uart_8250_port *up, bool enable)
 67{
 68	u8 lcr, efr;
 69
 70	/* backup LCR register */
 71	lcr = serial_in(up, UART_LCR);
 72
 73	/* enable 650 compatible register set (EFR, ...) */
 74	serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
 75
 76	/* enable/disable enhanced mode with indexed control registers */
 77	efr = serial_in(up, UART_EFR);
 78	if (enable)
 79		efr |= UART_EFR_ECB;
 80	else
 81		efr &= ~UART_EFR_ECB;
 82	serial_out(up, UART_EFR, efr);
 83
 84	/* disable 650 compatible register set, restore LCR */
 85	serial_out(up, UART_LCR, lcr);
 86}
 87
 88static void kuart_dtr_line_config(struct uart_8250_port *up, u8 dtrlc)
 89{
 90	u8 acr;
 91
 92	/* set index register to 0 to access ACR register */
 93	serial_out(up, UART_SCR, UART_ACR);
 94
 95	/* set value register to 0x10 writing DTR mode (1,0) */
 96	acr = serial_in(up, UART_LSR);
 97	acr &= ~UART_ACR_DTRLC_MASK;
 98	acr |= dtrlc;
 99	serial_out(up, UART_LSR, acr);
100}
101
102static int kuart_rs485_config(struct uart_port *port, struct ktermios *termios,
103			      struct serial_rs485 *rs485)
104{
105	struct uart_8250_port *up = up_to_u8250p(port);
106	struct kuart *kuart = port->private_data;
107	enum kuart_mode mode;
108	u8 dtrlc;
109
110	if (rs485->flags & SER_RS485_ENABLED) {
111		if (rs485->flags & SER_RS485_MODE_RS422)
112			mode = KUART_MODE_RS422;
113		else
114			mode = KUART_MODE_RS485;
115	} else {
116		mode = KUART_MODE_RS232;
117	}
118
119	if (mode == kuart->mode)
120		return 0;
121
122	if (kuart->flags & KUART_USE_CAPABILITY) {
123		/* deactivate physical interface, break before make */
124		kuart_set_phy_mode(kuart, KUART_MODE_NONE);
125	}
126
127	if (mode == KUART_MODE_RS485) {
128		/*
129		 * Set DTR line configuration of 95x UART to DTR mode (1,0).
130		 * In this mode the DTR pin drives the active-low enable pin of
131		 * an external RS485 buffer. The DTR pin will be forced low
132		 * whenever the transmitter is not empty, otherwise DTR pin is
133		 * high.
134		 */
135		dtrlc = UART_ACR_DTRLC_ENABLE_LOW;
136	} else {
137		/*
138		 * Set DTR line configuration of 95x UART to DTR mode (0,0).
139		 * In this mode the DTR pin is compatible with 16C450, 16C550,
140		 * 16C650 and 16c670 (i.e. normal).
141		 */
142		dtrlc = UART_ACR_DTRLC_COMPAT;
143	}
144
145	kuart_enhanced_mode(up, true);
146	kuart_dtr_line_config(up, dtrlc);
147	kuart_enhanced_mode(up, false);
148
149	if (kuart->flags & KUART_USE_CAPABILITY) {
150		/* activate selected physical interface */
151		kuart_set_phy_mode(kuart, mode);
152	}
153
154	kuart->mode = mode;
155
156	return 0;
157}
158
159static int kuart_probe(struct auxiliary_device *auxdev,
160		       const struct auxiliary_device_id *id)
161{
162	struct device *dev = &auxdev->dev;
163	struct uart_8250_port uart = {};
164	struct resource res;
165	struct kuart *kuart;
166	int retval;
167
168	kuart = devm_kzalloc(dev, sizeof(*kuart), GFP_KERNEL);
169	if (!kuart)
170		return -ENOMEM;
171	kuart->auxdev = container_of(auxdev, struct keba_uart_auxdev, auxdev);
172	kuart->flags = id->driver_data;
173	auxiliary_set_drvdata(auxdev, kuart);
174
175	/*
176	 * map only memory in front of UART registers, UART registers will be
177	 * mapped by serial port
178	 */
179	res = kuart->auxdev->io;
180	res.end = res.start + KUART_BASE - 1;
181	kuart->base = devm_ioremap_resource(dev, &res);
182	if (IS_ERR(kuart->base))
183		return PTR_ERR(kuart->base);
184
185	if (kuart->flags & KUART_USE_CAPABILITY) {
186		/*
187		 * supported modes are read from capability register, at least
188		 * one mode other than none must be supported
189		 */
190		kuart->capability = ioread8(kuart->base + KUART_CAPABILITY) &
191				    KUART_CAPABILITY_MASK;
192		if ((kuart->capability & ~KUART_CAPABILITY_NONE) == 0)
193			return -EIO;
194	}
195
196	spin_lock_init(&uart.port.lock);
197	uart.port.dev = dev;
198	uart.port.mapbase = kuart->auxdev->io.start + KUART_BASE;
199	uart.port.irq = kuart->auxdev->irq;
200	uart.port.uartclk = KUART_CLK;
201	uart.port.private_data = kuart;
202
203	/* 8 bit registers are 32 bit aligned => shift register offset */
204	uart.port.iotype = UPIO_MEM32;
205	uart.port.regshift = KUART_REGSHIFT;
206
207	/*
208	 * UART mixes 16550, 16750 and 16C950 (for RS485) standard => auto
209	 * configuration works best
210	 */
211	uart.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_IOREMAP;
212
213	/*
214	 * UART supports RS485, RS422 and RS232 with switching of physical
215	 * interface
216	 */
217	uart.port.rs485_config = kuart_rs485_config;
218	if (kuart->flags & KUART_RS485) {
219		uart.port.rs485_supported.flags = SER_RS485_ENABLED |
220						  SER_RS485_RTS_ON_SEND;
221		uart.port.rs485.flags = SER_RS485_ENABLED |
222					SER_RS485_RTS_ON_SEND;
223	}
224	if (kuart->flags & KUART_USE_CAPABILITY) {
225		/* default mode priority is RS485 > RS422 > RS232 */
226		if (kuart->capability & KUART_CAPABILITY_RS422) {
227			uart.port.rs485_supported.flags |= SER_RS485_ENABLED |
228							   SER_RS485_RTS_ON_SEND |
229							   SER_RS485_MODE_RS422;
230			uart.port.rs485.flags = SER_RS485_ENABLED |
231						SER_RS485_RTS_ON_SEND |
232						SER_RS485_MODE_RS422;
233		}
234		if (kuart->capability & KUART_CAPABILITY_RS485) {
235			uart.port.rs485_supported.flags |= SER_RS485_ENABLED |
236							   SER_RS485_RTS_ON_SEND;
237			uart.port.rs485.flags = SER_RS485_ENABLED |
238						SER_RS485_RTS_ON_SEND;
239		}
240	}
241
242	retval = serial8250_register_8250_port(&uart);
243	if (retval < 0) {
244		dev_err(&auxdev->dev, "UART registration failed!\n");
245		return retval;
246	}
247	kuart->line = retval;
248
249	return 0;
250}
251
252static void kuart_remove(struct auxiliary_device *auxdev)
253{
254	struct kuart *kuart = auxiliary_get_drvdata(auxdev);
255
256	if (kuart->flags & KUART_USE_CAPABILITY)
257		kuart_set_phy_mode(kuart, KUART_MODE_NONE);
258
259	serial8250_unregister_port(kuart->line);
260}
261
262static const struct auxiliary_device_id kuart_devtype_aux[] = {
263	{ .name = "keba.rs485-uart", .driver_data = KUART_RS485 },
264	{ .name = "keba.rs232-uart", .driver_data = 0 },
265	{ .name = "keba.uart", .driver_data = KUART_USE_CAPABILITY },
266	{}
267};
268MODULE_DEVICE_TABLE(auxiliary, kuart_devtype_aux);
269
270static struct auxiliary_driver kuart_driver_aux = {
271	.name = KUART,
272	.id_table = kuart_devtype_aux,
273	.probe  = kuart_probe,
274	.remove = kuart_remove,
275};
276module_auxiliary_driver(kuart_driver_aux);
277
278MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
279MODULE_DESCRIPTION("KEBA 8250 serial port driver");
280MODULE_LICENSE("GPL");
~/ Read-only file preview.