linux-stable/drivers/tty/serial/mcf.c
Uwe Kleine-König cd1d7071f5 serial: mcf: Convert to platform remove callback returning void
The .remove() callback for a platform driver returns an int which makes
many driver authors wrongly assume it's possible to do error handling by
returning an error code. However the value returned is ignored (apart
from emitting a warning) and this typically results in resource leaks.

To improve here there is a quest to make the remove callback return
void. In the first step of this quest all drivers are converted to
.remove_new(), which already returns void. Eventually after all drivers
are converted, .remove_new() will be renamed to .remove().

Trivially convert this driver from always returning zero in the remove
callback to the void returning variant.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Link: https://lore.kernel.org/r/20231110152927.70601-23-u.kleine-koenig@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-11-23 19:12:32 +00:00

689 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/****************************************************************************/
/*
* mcf.c -- Freescale ColdFire UART driver
*
* (C) Copyright 2003-2007, Greg Ungerer <gerg@uclinux.org>
*/
/****************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <asm/coldfire.h>
#include <asm/mcfsim.h>
#include <asm/mcfuart.h>
#include <asm/nettel.h>
/****************************************************************************/
/*
* Some boards implement the DTR/DCD lines using GPIO lines, most
* don't. Dummy out the access macros for those that don't. Those
* that do should define these macros somewhere in there board
* specific inlude files.
*/
#if !defined(mcf_getppdcd)
#define mcf_getppdcd(p) (1)
#endif
#if !defined(mcf_getppdtr)
#define mcf_getppdtr(p) (1)
#endif
#if !defined(mcf_setppdtr)
#define mcf_setppdtr(p, v) do { } while (0)
#endif
/****************************************************************************/
/*
* Local per-uart structure.
*/
struct mcf_uart {
struct uart_port port;
unsigned int sigs; /* Local copy of line sigs */
unsigned char imr; /* Local IMR mirror */
};
/****************************************************************************/
static unsigned int mcf_tx_empty(struct uart_port *port)
{
return (readb(port->membase + MCFUART_USR) & MCFUART_USR_TXEMPTY) ?
TIOCSER_TEMT : 0;
}
/****************************************************************************/
static unsigned int mcf_get_mctrl(struct uart_port *port)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
unsigned int sigs;
sigs = (readb(port->membase + MCFUART_UIPR) & MCFUART_UIPR_CTS) ?
0 : TIOCM_CTS;
sigs |= (pp->sigs & TIOCM_RTS);
sigs |= (mcf_getppdcd(port->line) ? TIOCM_CD : 0);
sigs |= (mcf_getppdtr(port->line) ? TIOCM_DTR : 0);
return sigs;
}
/****************************************************************************/
static void mcf_set_mctrl(struct uart_port *port, unsigned int sigs)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
pp->sigs = sigs;
mcf_setppdtr(port->line, (sigs & TIOCM_DTR));
if (sigs & TIOCM_RTS)
writeb(MCFUART_UOP_RTS, port->membase + MCFUART_UOP1);
else
writeb(MCFUART_UOP_RTS, port->membase + MCFUART_UOP0);
}
/****************************************************************************/
static void mcf_start_tx(struct uart_port *port)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
if (port->rs485.flags & SER_RS485_ENABLED) {
/* Enable Transmitter */
writeb(MCFUART_UCR_TXENABLE, port->membase + MCFUART_UCR);
/* Manually assert RTS */
writeb(MCFUART_UOP_RTS, port->membase + MCFUART_UOP1);
}
pp->imr |= MCFUART_UIR_TXREADY;
writeb(pp->imr, port->membase + MCFUART_UIMR);
}
/****************************************************************************/
static void mcf_stop_tx(struct uart_port *port)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
pp->imr &= ~MCFUART_UIR_TXREADY;
writeb(pp->imr, port->membase + MCFUART_UIMR);
}
/****************************************************************************/
static void mcf_stop_rx(struct uart_port *port)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
pp->imr &= ~MCFUART_UIR_RXREADY;
writeb(pp->imr, port->membase + MCFUART_UIMR);
}
/****************************************************************************/
static void mcf_break_ctl(struct uart_port *port, int break_state)
{
unsigned long flags;
uart_port_lock_irqsave(port, &flags);
if (break_state == -1)
writeb(MCFUART_UCR_CMDBREAKSTART, port->membase + MCFUART_UCR);
else
writeb(MCFUART_UCR_CMDBREAKSTOP, port->membase + MCFUART_UCR);
uart_port_unlock_irqrestore(port, flags);
}
/****************************************************************************/
static int mcf_startup(struct uart_port *port)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
unsigned long flags;
uart_port_lock_irqsave(port, &flags);
/* Reset UART, get it into known state... */
writeb(MCFUART_UCR_CMDRESETRX, port->membase + MCFUART_UCR);
writeb(MCFUART_UCR_CMDRESETTX, port->membase + MCFUART_UCR);
/* Enable the UART transmitter and receiver */
writeb(MCFUART_UCR_RXENABLE | MCFUART_UCR_TXENABLE,
port->membase + MCFUART_UCR);
/* Enable RX interrupts now */
pp->imr = MCFUART_UIR_RXREADY;
writeb(pp->imr, port->membase + MCFUART_UIMR);
uart_port_unlock_irqrestore(port, flags);
return 0;
}
/****************************************************************************/
static void mcf_shutdown(struct uart_port *port)
{
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
unsigned long flags;
uart_port_lock_irqsave(port, &flags);
/* Disable all interrupts now */
pp->imr = 0;
writeb(pp->imr, port->membase + MCFUART_UIMR);
/* Disable UART transmitter and receiver */
writeb(MCFUART_UCR_CMDRESETRX, port->membase + MCFUART_UCR);
writeb(MCFUART_UCR_CMDRESETTX, port->membase + MCFUART_UCR);
uart_port_unlock_irqrestore(port, flags);
}
/****************************************************************************/
static void mcf_set_termios(struct uart_port *port, struct ktermios *termios,
const struct ktermios *old)
{
unsigned long flags;
unsigned int baud, baudclk;
#if defined(CONFIG_M5272)
unsigned int baudfr;
#endif
unsigned char mr1, mr2;
baud = uart_get_baud_rate(port, termios, old, 0, 230400);
#if defined(CONFIG_M5272)
baudclk = (MCF_BUSCLK / baud) / 32;
baudfr = (((MCF_BUSCLK / baud) + 1) / 2) % 16;
#else
baudclk = ((MCF_BUSCLK / baud) + 16) / 32;
#endif
mr1 = MCFUART_MR1_RXIRQRDY | MCFUART_MR1_RXERRCHAR;
mr2 = 0;
switch (termios->c_cflag & CSIZE) {
case CS5: mr1 |= MCFUART_MR1_CS5; break;
case CS6: mr1 |= MCFUART_MR1_CS6; break;
case CS7: mr1 |= MCFUART_MR1_CS7; break;
case CS8:
default: mr1 |= MCFUART_MR1_CS8; break;
}
if (termios->c_cflag & PARENB) {
if (termios->c_cflag & CMSPAR) {
if (termios->c_cflag & PARODD)
mr1 |= MCFUART_MR1_PARITYMARK;
else
mr1 |= MCFUART_MR1_PARITYSPACE;
} else {
if (termios->c_cflag & PARODD)
mr1 |= MCFUART_MR1_PARITYODD;
else
mr1 |= MCFUART_MR1_PARITYEVEN;
}
} else {
mr1 |= MCFUART_MR1_PARITYNONE;
}
/*
* FIXME: port->read_status_mask and port->ignore_status_mask
* need to be initialized based on termios settings for
* INPCK, IGNBRK, IGNPAR, PARMRK, BRKINT
*/
if (termios->c_cflag & CSTOPB)
mr2 |= MCFUART_MR2_STOP2;
else
mr2 |= MCFUART_MR2_STOP1;
if (termios->c_cflag & CRTSCTS) {
mr1 |= MCFUART_MR1_RXRTS;
mr2 |= MCFUART_MR2_TXCTS;
}
uart_port_lock_irqsave(port, &flags);
if (port->rs485.flags & SER_RS485_ENABLED) {
dev_dbg(port->dev, "Setting UART to RS485\n");
mr2 |= MCFUART_MR2_TXRTS;
}
uart_update_timeout(port, termios->c_cflag, baud);
writeb(MCFUART_UCR_CMDRESETRX, port->membase + MCFUART_UCR);
writeb(MCFUART_UCR_CMDRESETTX, port->membase + MCFUART_UCR);
writeb(MCFUART_UCR_CMDRESETMRPTR, port->membase + MCFUART_UCR);
writeb(mr1, port->membase + MCFUART_UMR);
writeb(mr2, port->membase + MCFUART_UMR);
writeb((baudclk & 0xff00) >> 8, port->membase + MCFUART_UBG1);
writeb((baudclk & 0xff), port->membase + MCFUART_UBG2);
#if defined(CONFIG_M5272)
writeb((baudfr & 0x0f), port->membase + MCFUART_UFPD);
#endif
writeb(MCFUART_UCSR_RXCLKTIMER | MCFUART_UCSR_TXCLKTIMER,
port->membase + MCFUART_UCSR);
writeb(MCFUART_UCR_RXENABLE | MCFUART_UCR_TXENABLE,
port->membase + MCFUART_UCR);
uart_port_unlock_irqrestore(port, flags);
}
/****************************************************************************/
static void mcf_rx_chars(struct mcf_uart *pp)
{
struct uart_port *port = &pp->port;
u8 status, ch, flag;
while ((status = readb(port->membase + MCFUART_USR)) & MCFUART_USR_RXREADY) {
ch = readb(port->membase + MCFUART_URB);
flag = TTY_NORMAL;
port->icount.rx++;
if (status & MCFUART_USR_RXERR) {
writeb(MCFUART_UCR_CMDRESETERR,
port->membase + MCFUART_UCR);
if (status & MCFUART_USR_RXBREAK) {
port->icount.brk++;
if (uart_handle_break(port))
continue;
} else if (status & MCFUART_USR_RXPARITY) {
port->icount.parity++;
} else if (status & MCFUART_USR_RXOVERRUN) {
port->icount.overrun++;
} else if (status & MCFUART_USR_RXFRAMING) {
port->icount.frame++;
}
status &= port->read_status_mask;
if (status & MCFUART_USR_RXBREAK)
flag = TTY_BREAK;
else if (status & MCFUART_USR_RXPARITY)
flag = TTY_PARITY;
else if (status & MCFUART_USR_RXFRAMING)
flag = TTY_FRAME;
}
if (uart_handle_sysrq_char(port, ch))
continue;
uart_insert_char(port, status, MCFUART_USR_RXOVERRUN, ch, flag);
}
tty_flip_buffer_push(&port->state->port);
}
/****************************************************************************/
static void mcf_tx_chars(struct mcf_uart *pp)
{
struct uart_port *port = &pp->port;
bool pending;
u8 ch;
pending = uart_port_tx(port, ch,
readb(port->membase + MCFUART_USR) & MCFUART_USR_TXREADY,
writeb(ch, port->membase + MCFUART_UTB));
/* Disable TX to negate RTS automatically */
if (!pending && (port->rs485.flags & SER_RS485_ENABLED))
writeb(MCFUART_UCR_TXDISABLE, port->membase + MCFUART_UCR);
}
/****************************************************************************/
static irqreturn_t mcf_interrupt(int irq, void *data)
{
struct uart_port *port = data;
struct mcf_uart *pp = container_of(port, struct mcf_uart, port);
unsigned int isr;
irqreturn_t ret = IRQ_NONE;
isr = readb(port->membase + MCFUART_UISR) & pp->imr;
uart_port_lock(port);
if (isr & MCFUART_UIR_RXREADY) {
mcf_rx_chars(pp);
ret = IRQ_HANDLED;
}
if (isr & MCFUART_UIR_TXREADY) {
mcf_tx_chars(pp);
ret = IRQ_HANDLED;
}
uart_port_unlock(port);
return ret;
}
/****************************************************************************/
static void mcf_config_port(struct uart_port *port, int flags)
{
port->type = PORT_MCF;
port->fifosize = MCFUART_TXFIFOSIZE;
/* Clear mask, so no surprise interrupts. */
writeb(0, port->membase + MCFUART_UIMR);
if (request_irq(port->irq, mcf_interrupt, 0, "UART", port))
printk(KERN_ERR "MCF: unable to attach ColdFire UART %d "
"interrupt vector=%d\n", port->line, port->irq);
}
/****************************************************************************/
static const char *mcf_type(struct uart_port *port)
{
return (port->type == PORT_MCF) ? "ColdFire UART" : NULL;
}
/****************************************************************************/
static int mcf_request_port(struct uart_port *port)
{
/* UARTs always present */
return 0;
}
/****************************************************************************/
static void mcf_release_port(struct uart_port *port)
{
/* Nothing to release... */
}
/****************************************************************************/
static int mcf_verify_port(struct uart_port *port, struct serial_struct *ser)
{
if ((ser->type != PORT_UNKNOWN) && (ser->type != PORT_MCF))
return -EINVAL;
return 0;
}
/****************************************************************************/
/* Enable or disable the RS485 support */
static int mcf_config_rs485(struct uart_port *port, struct ktermios *termios,
struct serial_rs485 *rs485)
{
unsigned char mr1, mr2;
/* Get mode registers */
mr1 = readb(port->membase + MCFUART_UMR);
mr2 = readb(port->membase + MCFUART_UMR);
if (rs485->flags & SER_RS485_ENABLED) {
dev_dbg(port->dev, "Setting UART to RS485\n");
/* Automatically negate RTS after TX completes */
mr2 |= MCFUART_MR2_TXRTS;
} else {
dev_dbg(port->dev, "Setting UART to RS232\n");
mr2 &= ~MCFUART_MR2_TXRTS;
}
writeb(mr1, port->membase + MCFUART_UMR);
writeb(mr2, port->membase + MCFUART_UMR);
return 0;
}
static const struct serial_rs485 mcf_rs485_supported = {
.flags = SER_RS485_ENABLED | SER_RS485_RTS_AFTER_SEND,
};
/****************************************************************************/
/*
* Define the basic serial functions we support.
*/
static const struct uart_ops mcf_uart_ops = {
.tx_empty = mcf_tx_empty,
.get_mctrl = mcf_get_mctrl,
.set_mctrl = mcf_set_mctrl,
.start_tx = mcf_start_tx,
.stop_tx = mcf_stop_tx,
.stop_rx = mcf_stop_rx,
.break_ctl = mcf_break_ctl,
.startup = mcf_startup,
.shutdown = mcf_shutdown,
.set_termios = mcf_set_termios,
.type = mcf_type,
.request_port = mcf_request_port,
.release_port = mcf_release_port,
.config_port = mcf_config_port,
.verify_port = mcf_verify_port,
};
static struct mcf_uart mcf_ports[4];
#define MCF_MAXPORTS ARRAY_SIZE(mcf_ports)
/****************************************************************************/
#if defined(CONFIG_SERIAL_MCF_CONSOLE)
/****************************************************************************/
int __init early_mcf_setup(struct mcf_platform_uart *platp)
{
struct uart_port *port;
int i;
for (i = 0; ((i < MCF_MAXPORTS) && (platp[i].mapbase)); i++) {
port = &mcf_ports[i].port;
port->line = i;
port->type = PORT_MCF;
port->mapbase = platp[i].mapbase;
port->membase = (platp[i].membase) ? platp[i].membase :
(unsigned char __iomem *) port->mapbase;
port->iotype = SERIAL_IO_MEM;
port->irq = platp[i].irq;
port->uartclk = MCF_BUSCLK;
port->flags = UPF_BOOT_AUTOCONF;
port->rs485_config = mcf_config_rs485;
port->rs485_supported = mcf_rs485_supported;
port->ops = &mcf_uart_ops;
}
return 0;
}
/****************************************************************************/
static void mcf_console_putc(struct console *co, const char c)
{
struct uart_port *port = &(mcf_ports + co->index)->port;
int i;
for (i = 0; (i < 0x10000); i++) {
if (readb(port->membase + MCFUART_USR) & MCFUART_USR_TXREADY)
break;
}
writeb(c, port->membase + MCFUART_UTB);
for (i = 0; (i < 0x10000); i++) {
if (readb(port->membase + MCFUART_USR) & MCFUART_USR_TXREADY)
break;
}
}
/****************************************************************************/
static void mcf_console_write(struct console *co, const char *s, unsigned int count)
{
for (; (count); count--, s++) {
mcf_console_putc(co, *s);
if (*s == '\n')
mcf_console_putc(co, '\r');
}
}
/****************************************************************************/
static int __init mcf_console_setup(struct console *co, char *options)
{
struct uart_port *port;
int baud = CONFIG_SERIAL_MCF_BAUDRATE;
int bits = 8;
int parity = 'n';
int flow = 'n';
if ((co->index < 0) || (co->index >= MCF_MAXPORTS))
co->index = 0;
port = &mcf_ports[co->index].port;
if (port->membase == 0)
return -ENODEV;
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
return uart_set_options(port, co, baud, parity, bits, flow);
}
/****************************************************************************/
static struct uart_driver mcf_driver;
static struct console mcf_console = {
.name = "ttyS",
.write = mcf_console_write,
.device = uart_console_device,
.setup = mcf_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &mcf_driver,
};
static int __init mcf_console_init(void)
{
register_console(&mcf_console);
return 0;
}
console_initcall(mcf_console_init);
#define MCF_CONSOLE &mcf_console
/****************************************************************************/
#else
/****************************************************************************/
#define MCF_CONSOLE NULL
/****************************************************************************/
#endif /* CONFIG_SERIAL_MCF_CONSOLE */
/****************************************************************************/
/*
* Define the mcf UART driver structure.
*/
static struct uart_driver mcf_driver = {
.owner = THIS_MODULE,
.driver_name = "mcf",
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = 64,
.nr = MCF_MAXPORTS,
.cons = MCF_CONSOLE,
};
/****************************************************************************/
static int mcf_probe(struct platform_device *pdev)
{
struct mcf_platform_uart *platp = dev_get_platdata(&pdev->dev);
struct uart_port *port;
int i;
for (i = 0; ((i < MCF_MAXPORTS) && (platp[i].mapbase)); i++) {
port = &mcf_ports[i].port;
port->line = i;
port->type = PORT_MCF;
port->mapbase = platp[i].mapbase;
port->membase = (platp[i].membase) ? platp[i].membase :
(unsigned char __iomem *) platp[i].mapbase;
port->dev = &pdev->dev;
port->iotype = SERIAL_IO_MEM;
port->irq = platp[i].irq;
port->uartclk = MCF_BUSCLK;
port->ops = &mcf_uart_ops;
port->flags = UPF_BOOT_AUTOCONF;
port->rs485_config = mcf_config_rs485;
port->rs485_supported = mcf_rs485_supported;
port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_MCF_CONSOLE);
uart_add_one_port(&mcf_driver, port);
}
return 0;
}
/****************************************************************************/
static void mcf_remove(struct platform_device *pdev)
{
struct uart_port *port;
int i;
for (i = 0; (i < MCF_MAXPORTS); i++) {
port = &mcf_ports[i].port;
if (port)
uart_remove_one_port(&mcf_driver, port);
}
}
/****************************************************************************/
static struct platform_driver mcf_platform_driver = {
.probe = mcf_probe,
.remove_new = mcf_remove,
.driver = {
.name = "mcfuart",
},
};
/****************************************************************************/
static int __init mcf_init(void)
{
int rc;
printk("ColdFire internal UART serial driver\n");
rc = uart_register_driver(&mcf_driver);
if (rc)
return rc;
rc = platform_driver_register(&mcf_platform_driver);
if (rc) {
uart_unregister_driver(&mcf_driver);
return rc;
}
return 0;
}
/****************************************************************************/
static void __exit mcf_exit(void)
{
platform_driver_unregister(&mcf_platform_driver);
uart_unregister_driver(&mcf_driver);
}
/****************************************************************************/
module_init(mcf_init);
module_exit(mcf_exit);
MODULE_AUTHOR("Greg Ungerer <gerg@uclinux.org>");
MODULE_DESCRIPTION("Freescale ColdFire UART driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mcfuart");
/****************************************************************************/