brintos

brintos / linux-shallow public Read only

0
0
Text · 13.8 KiB · 2ccd13c Raw
559 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 *  Driver for Conexant Digicolor serial ports (USART)4 *5 * Author: Baruch Siach <baruch@tkos.co.il>6 *7 * Copyright (C) 2014 Paradox Innovation Ltd.8 */9 10#include <linux/module.h>11#include <linux/console.h>12#include <linux/serial_core.h>13#include <linux/serial.h>14#include <linux/clk.h>15#include <linux/io.h>16#include <linux/tty.h>17#include <linux/tty_flip.h>18#include <linux/of.h>19#include <linux/platform_device.h>20#include <linux/workqueue.h>21 22#define UA_ENABLE			0x0023#define UA_ENABLE_ENABLE		BIT(0)24 25#define UA_CONTROL			0x0126#define UA_CONTROL_RX_ENABLE		BIT(0)27#define UA_CONTROL_TX_ENABLE		BIT(1)28#define UA_CONTROL_SOFT_RESET		BIT(2)29 30#define UA_STATUS			0x0231#define UA_STATUS_PARITY_ERR		BIT(0)32#define UA_STATUS_FRAME_ERR		BIT(1)33#define UA_STATUS_OVERRUN_ERR		BIT(2)34#define UA_STATUS_TX_READY		BIT(6)35 36#define UA_CONFIG			0x0337#define UA_CONFIG_CHAR_LEN		BIT(0)38#define UA_CONFIG_STOP_BITS		BIT(1)39#define UA_CONFIG_PARITY		BIT(2)40#define UA_CONFIG_ODD_PARITY		BIT(4)41 42#define UA_EMI_REC			0x0443 44#define UA_HBAUD_LO			0x0845#define UA_HBAUD_HI			0x0946 47#define UA_STATUS_FIFO			0x0a48#define UA_STATUS_FIFO_RX_EMPTY		BIT(2)49#define UA_STATUS_FIFO_RX_INT_ALMOST	BIT(3)50#define UA_STATUS_FIFO_TX_FULL		BIT(4)51#define UA_STATUS_FIFO_TX_INT_ALMOST	BIT(7)52 53#define UA_CONFIG_FIFO			0x0b54#define UA_CONFIG_FIFO_RX_THRESH	755#define UA_CONFIG_FIFO_RX_FIFO_MODE	BIT(3)56#define UA_CONFIG_FIFO_TX_FIFO_MODE	BIT(7)57 58#define UA_INTFLAG_CLEAR		0x1c59#define UA_INTFLAG_SET			0x1d60#define UA_INT_ENABLE			0x1e61#define UA_INT_STATUS			0x1f62 63#define UA_INT_TX			BIT(0)64#define UA_INT_RX			BIT(1)65 66#define DIGICOLOR_USART_NR		367 68/*69 * We use the 16 bytes hardware FIFO to buffer Rx traffic. Rx interrupt is70 * only produced when the FIFO is filled more than a certain configurable71 * threshold. Unfortunately, there is no way to set this threshold below half72 * FIFO. This means that we must periodically poll the FIFO status register to73 * see whether there are waiting Rx bytes.74 */75 76struct digicolor_port {77	struct uart_port port;78	struct delayed_work rx_poll_work;79};80 81static struct uart_port *digicolor_ports[DIGICOLOR_USART_NR];82 83static bool digicolor_uart_tx_full(struct uart_port *port)84{85	return !!(readb_relaxed(port->membase + UA_STATUS_FIFO) &86		  UA_STATUS_FIFO_TX_FULL);87}88 89static bool digicolor_uart_rx_empty(struct uart_port *port)90{91	return !!(readb_relaxed(port->membase + UA_STATUS_FIFO) &92		  UA_STATUS_FIFO_RX_EMPTY);93}94 95static void digicolor_uart_stop_tx(struct uart_port *port)96{97	u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE);98 99	int_enable &= ~UA_INT_TX;100	writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE);101}102 103static void digicolor_uart_start_tx(struct uart_port *port)104{105	u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE);106 107	int_enable |= UA_INT_TX;108	writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE);109}110 111static void digicolor_uart_stop_rx(struct uart_port *port)112{113	u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE);114 115	int_enable &= ~UA_INT_RX;116	writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE);117}118 119static void digicolor_rx_poll(struct work_struct *work)120{121	struct digicolor_port *dp =122		container_of(to_delayed_work(work),123			     struct digicolor_port, rx_poll_work);124 125	if (!digicolor_uart_rx_empty(&dp->port))126		/* force RX interrupt */127		writeb_relaxed(UA_INT_RX, dp->port.membase + UA_INTFLAG_SET);128 129	schedule_delayed_work(&dp->rx_poll_work, msecs_to_jiffies(100));130}131 132static void digicolor_uart_rx(struct uart_port *port)133{134	unsigned long flags;135 136	uart_port_lock_irqsave(port, &flags);137 138	while (1) {139		u8 status, ch, ch_flag;140 141		if (digicolor_uart_rx_empty(port))142			break;143 144		ch = readb_relaxed(port->membase + UA_EMI_REC);145		status = readb_relaxed(port->membase + UA_STATUS);146 147		port->icount.rx++;148		ch_flag = TTY_NORMAL;149 150		if (status) {151			if (status & UA_STATUS_PARITY_ERR)152				port->icount.parity++;153			else if (status & UA_STATUS_FRAME_ERR)154				port->icount.frame++;155			else if (status & UA_STATUS_OVERRUN_ERR)156				port->icount.overrun++;157 158			status &= port->read_status_mask;159 160			if (status & UA_STATUS_PARITY_ERR)161				ch_flag = TTY_PARITY;162			else if (status & UA_STATUS_FRAME_ERR)163				ch_flag = TTY_FRAME;164			else if (status & UA_STATUS_OVERRUN_ERR)165				ch_flag = TTY_OVERRUN;166		}167 168		if (status & port->ignore_status_mask)169			continue;170 171		uart_insert_char(port, status, UA_STATUS_OVERRUN_ERR, ch,172				 ch_flag);173	}174 175	uart_port_unlock_irqrestore(port, flags);176 177	tty_flip_buffer_push(&port->state->port);178}179 180static void digicolor_uart_tx(struct uart_port *port)181{182	struct tty_port *tport = &port->state->port;183	unsigned long flags;184	unsigned char c;185 186	if (digicolor_uart_tx_full(port))187		return;188 189	uart_port_lock_irqsave(port, &flags);190 191	if (port->x_char) {192		writeb_relaxed(port->x_char, port->membase + UA_EMI_REC);193		port->icount.tx++;194		port->x_char = 0;195		goto out;196	}197 198	if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) {199		digicolor_uart_stop_tx(port);200		goto out;201	}202 203	while (uart_fifo_get(port, &c)) {204		writeb(c, port->membase + UA_EMI_REC);205 206		if (digicolor_uart_tx_full(port))207			break;208	}209 210	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)211		uart_write_wakeup(port);212 213out:214	uart_port_unlock_irqrestore(port, flags);215}216 217static irqreturn_t digicolor_uart_int(int irq, void *dev_id)218{219	struct uart_port *port = dev_id;220	u8 int_status = readb_relaxed(port->membase + UA_INT_STATUS);221 222	writeb_relaxed(UA_INT_RX | UA_INT_TX,223		       port->membase + UA_INTFLAG_CLEAR);224 225	if (int_status & UA_INT_RX)226		digicolor_uart_rx(port);227	if (int_status & UA_INT_TX)228		digicolor_uart_tx(port);229 230	return IRQ_HANDLED;231}232 233static unsigned int digicolor_uart_tx_empty(struct uart_port *port)234{235	u8 status = readb_relaxed(port->membase + UA_STATUS);236 237	return (status & UA_STATUS_TX_READY) ? TIOCSER_TEMT : 0;238}239 240static unsigned int digicolor_uart_get_mctrl(struct uart_port *port)241{242	return TIOCM_CTS;243}244 245static void digicolor_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)246{247}248 249static void digicolor_uart_break_ctl(struct uart_port *port, int state)250{251}252 253static int digicolor_uart_startup(struct uart_port *port)254{255	struct digicolor_port *dp =256		container_of(port, struct digicolor_port, port);257 258	writeb_relaxed(UA_ENABLE_ENABLE, port->membase + UA_ENABLE);259	writeb_relaxed(UA_CONTROL_SOFT_RESET, port->membase + UA_CONTROL);260	writeb_relaxed(0, port->membase + UA_CONTROL);261 262	writeb_relaxed(UA_CONFIG_FIFO_RX_FIFO_MODE263		       | UA_CONFIG_FIFO_TX_FIFO_MODE | UA_CONFIG_FIFO_RX_THRESH,264		       port->membase + UA_CONFIG_FIFO);265	writeb_relaxed(UA_STATUS_FIFO_RX_INT_ALMOST,266		       port->membase + UA_STATUS_FIFO);267	writeb_relaxed(UA_CONTROL_RX_ENABLE | UA_CONTROL_TX_ENABLE,268		       port->membase + UA_CONTROL);269	writeb_relaxed(UA_INT_TX | UA_INT_RX,270		       port->membase + UA_INT_ENABLE);271 272	schedule_delayed_work(&dp->rx_poll_work, msecs_to_jiffies(100));273 274	return 0;275}276 277static void digicolor_uart_shutdown(struct uart_port *port)278{279	struct digicolor_port *dp =280		container_of(port, struct digicolor_port, port);281 282	writeb_relaxed(0, port->membase + UA_ENABLE);283	cancel_delayed_work_sync(&dp->rx_poll_work);284}285 286static void digicolor_uart_set_termios(struct uart_port *port,287				       struct ktermios *termios,288				       const struct ktermios *old)289{290	unsigned int baud, divisor;291	u8 config = 0;292	unsigned long flags;293 294	/* Mask termios capabilities we don't support */295	termios->c_cflag &= ~CMSPAR;296	termios->c_iflag &= ~(BRKINT | IGNBRK);297 298	/* Limit baud rates so that we don't need the fractional divider */299	baud = uart_get_baud_rate(port, termios, old,300				  port->uartclk / (0x10000*16),301				  port->uartclk / 256);302	divisor = uart_get_divisor(port, baud) - 1;303 304	switch (termios->c_cflag & CSIZE) {305	case CS7:306		break;307	case CS8:308	default:309		config |= UA_CONFIG_CHAR_LEN;310		termios->c_cflag &= ~CSIZE;311		termios->c_cflag |= CS8;312		break;313	}314 315	if (termios->c_cflag & CSTOPB)316		config |= UA_CONFIG_STOP_BITS;317 318	if (termios->c_cflag & PARENB) {319		config |= UA_CONFIG_PARITY;320		if (termios->c_cflag & PARODD)321			config |= UA_CONFIG_ODD_PARITY;322	}323 324	/* Set read status mask */325	port->read_status_mask = UA_STATUS_OVERRUN_ERR;326	if (termios->c_iflag & INPCK)327		port->read_status_mask |= UA_STATUS_PARITY_ERR328			| UA_STATUS_FRAME_ERR;329 330	/* Set status ignore mask */331	port->ignore_status_mask = 0;332	if (!(termios->c_cflag & CREAD))333		port->ignore_status_mask |= UA_STATUS_OVERRUN_ERR334			| UA_STATUS_PARITY_ERR | UA_STATUS_FRAME_ERR;335 336	uart_port_lock_irqsave(port, &flags);337 338	uart_update_timeout(port, termios->c_cflag, baud);339 340	writeb_relaxed(config, port->membase + UA_CONFIG);341	writeb_relaxed(divisor & 0xff, port->membase + UA_HBAUD_LO);342	writeb_relaxed(divisor >> 8, port->membase + UA_HBAUD_HI);343 344	uart_port_unlock_irqrestore(port, flags);345}346 347static const char *digicolor_uart_type(struct uart_port *port)348{349	return (port->type == PORT_DIGICOLOR) ? "DIGICOLOR USART" : NULL;350}351 352static void digicolor_uart_config_port(struct uart_port *port, int flags)353{354	if (flags & UART_CONFIG_TYPE)355		port->type = PORT_DIGICOLOR;356}357 358static void digicolor_uart_release_port(struct uart_port *port)359{360}361 362static int digicolor_uart_request_port(struct uart_port *port)363{364	return 0;365}366 367static const struct uart_ops digicolor_uart_ops = {368	.tx_empty	= digicolor_uart_tx_empty,369	.set_mctrl	= digicolor_uart_set_mctrl,370	.get_mctrl	= digicolor_uart_get_mctrl,371	.stop_tx	= digicolor_uart_stop_tx,372	.start_tx	= digicolor_uart_start_tx,373	.stop_rx	= digicolor_uart_stop_rx,374	.break_ctl	= digicolor_uart_break_ctl,375	.startup	= digicolor_uart_startup,376	.shutdown	= digicolor_uart_shutdown,377	.set_termios	= digicolor_uart_set_termios,378	.type		= digicolor_uart_type,379	.config_port	= digicolor_uart_config_port,380	.release_port	= digicolor_uart_release_port,381	.request_port	= digicolor_uart_request_port,382};383 384static void digicolor_uart_console_putchar(struct uart_port *port, unsigned char ch)385{386	while (digicolor_uart_tx_full(port))387		cpu_relax();388 389	writeb_relaxed(ch, port->membase + UA_EMI_REC);390}391 392static void digicolor_uart_console_write(struct console *co, const char *c,393					 unsigned n)394{395	struct uart_port *port = digicolor_ports[co->index];396	u8 status;397	unsigned long flags;398	int locked = 1;399 400	if (oops_in_progress)401		locked = uart_port_trylock_irqsave(port, &flags);402	else403		uart_port_lock_irqsave(port, &flags);404 405	uart_console_write(port, c, n, digicolor_uart_console_putchar);406 407	if (locked)408		uart_port_unlock_irqrestore(port, flags);409 410	/* Wait for transmitter to become empty */411	do {412		status = readb_relaxed(port->membase + UA_STATUS);413	} while ((status & UA_STATUS_TX_READY) == 0);414}415 416static int digicolor_uart_console_setup(struct console *co, char *options)417{418	int baud = 115200, bits = 8, parity = 'n', flow = 'n';419	struct uart_port *port;420 421	if (co->index < 0 || co->index >= DIGICOLOR_USART_NR)422		return -EINVAL;423 424	port = digicolor_ports[co->index];425	if (!port)426		return -ENODEV;427 428	if (options)429		uart_parse_options(options, &baud, &parity, &bits, &flow);430 431	return uart_set_options(port, co, baud, parity, bits, flow);432}433 434static struct console digicolor_console = {435	.name	= "ttyS",436	.device	= uart_console_device,437	.write	= digicolor_uart_console_write,438	.setup	= digicolor_uart_console_setup,439	.flags	= CON_PRINTBUFFER,440	.index	= -1,441};442 443static struct uart_driver digicolor_uart = {444	.driver_name	= "digicolor-usart",445	.dev_name	= "ttyS",446	.nr		= DIGICOLOR_USART_NR,447};448 449static int digicolor_uart_probe(struct platform_device *pdev)450{451	struct device_node *np = pdev->dev.of_node;452	int irq, ret, index;453	struct digicolor_port *dp;454	struct resource *res;455	struct clk *uart_clk;456 457	if (!np) {458		dev_err(&pdev->dev, "Missing device tree node\n");459		return -ENXIO;460	}461 462	index = of_alias_get_id(np, "serial");463	if (index < 0 || index >= DIGICOLOR_USART_NR)464		return -EINVAL;465 466	dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL);467	if (!dp)468		return -ENOMEM;469 470	uart_clk = devm_clk_get(&pdev->dev, NULL);471	if (IS_ERR(uart_clk))472		return PTR_ERR(uart_clk);473 474	dp->port.membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res);475	if (IS_ERR(dp->port.membase))476		return PTR_ERR(dp->port.membase);477	dp->port.mapbase = res->start;478 479	irq = platform_get_irq(pdev, 0);480	if (irq < 0)481		return irq;482	dp->port.irq = irq;483 484	dp->port.iotype = UPIO_MEM;485	dp->port.uartclk = clk_get_rate(uart_clk);486	dp->port.fifosize = 16;487	dp->port.dev = &pdev->dev;488	dp->port.ops = &digicolor_uart_ops;489	dp->port.line = index;490	dp->port.type = PORT_DIGICOLOR;491	spin_lock_init(&dp->port.lock);492 493	digicolor_ports[index] = &dp->port;494	platform_set_drvdata(pdev, &dp->port);495 496	INIT_DELAYED_WORK(&dp->rx_poll_work, digicolor_rx_poll);497 498	ret = devm_request_irq(&pdev->dev, dp->port.irq, digicolor_uart_int, 0,499			       dev_name(&pdev->dev), &dp->port);500	if (ret)501		return ret;502 503	return uart_add_one_port(&digicolor_uart, &dp->port);504}505 506static void digicolor_uart_remove(struct platform_device *pdev)507{508	struct uart_port *port = platform_get_drvdata(pdev);509 510	uart_remove_one_port(&digicolor_uart, port);511}512 513static const struct of_device_id digicolor_uart_dt_ids[] = {514	{ .compatible = "cnxt,cx92755-usart", },515	{ }516};517MODULE_DEVICE_TABLE(of, digicolor_uart_dt_ids);518 519static struct platform_driver digicolor_uart_platform = {520	.driver = {521		.name		= "digicolor-usart",522		.of_match_table	= of_match_ptr(digicolor_uart_dt_ids),523	},524	.probe	= digicolor_uart_probe,525	.remove_new = digicolor_uart_remove,526};527 528static int __init digicolor_uart_init(void)529{530	int ret;531 532	if (IS_ENABLED(CONFIG_SERIAL_CONEXANT_DIGICOLOR_CONSOLE)) {533		digicolor_uart.cons = &digicolor_console;534		digicolor_console.data = &digicolor_uart;535	}536 537	ret = uart_register_driver(&digicolor_uart);538	if (ret)539		return ret;540 541	ret = platform_driver_register(&digicolor_uart_platform);542	if (ret)543		uart_unregister_driver(&digicolor_uart);544 545	return ret;546}547module_init(digicolor_uart_init);548 549static void __exit digicolor_uart_exit(void)550{551	platform_driver_unregister(&digicolor_uart_platform);552	uart_unregister_driver(&digicolor_uart);553}554module_exit(digicolor_uart_exit);555 556MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");557MODULE_DESCRIPTION("Conexant Digicolor USART serial driver");558MODULE_LICENSE("GPL");559