OpenCloudOS-Kernel/drivers/tty/serial/bcm63xx_uart.c

923 lines
21 KiB
C
Raw Normal View History

tty: add SPDX identifiers to all remaining files in drivers/tty/ It's good to have SPDX identifiers in all files to make it easier to audit the kernel tree for correct licenses. Update the drivers/tty files files with the correct SPDX license identifier based on the license text in the file itself. The SPDX identifier is a legally binding shorthand, which can be used instead of the full boiler plate text. This work is based on a script and data from Thomas Gleixner, Philippe Ombredanne, and Kate Stewart. Cc: Jiri Slaby <jslaby@suse.com> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Chris Metcalf <cmetcalf@mellanox.com> Cc: Jiri Kosina <jikos@kernel.org> Cc: David Sterba <dsterba@suse.com> Cc: James Hogan <jhogan@kernel.org> Cc: Rob Herring <robh@kernel.org> Cc: Eric Anholt <eric@anholt.net> Cc: Stefan Wahren <stefan.wahren@i2se.com> Cc: Florian Fainelli <f.fainelli@gmail.com> Cc: Ray Jui <rjui@broadcom.com> Cc: Scott Branden <sbranden@broadcom.com> Cc: bcm-kernel-feedback-list@broadcom.com Cc: "James E.J. Bottomley" <jejb@parisc-linux.org> Cc: Helge Deller <deller@gmx.de> Cc: Joachim Eastwood <manabian@gmail.com> Cc: Matthias Brugger <matthias.bgg@gmail.com> Cc: Masahiro Yamada <yamada.masahiro@socionext.com> Cc: Tobias Klauser <tklauser@distanz.ch> Cc: Russell King <linux@armlinux.org.uk> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: Richard Genoud <richard.genoud@gmail.com> Cc: Alexander Shiyan <shc_work@mail.ru> Cc: Baruch Siach <baruch@tkos.co.il> Cc: "Maciej W. Rozycki" <macro@linux-mips.org> Cc: "Uwe Kleine-König" <kernel@pengutronix.de> Cc: Pat Gefre <pfg@sgi.com> Cc: "Guilherme G. Piccoli" <gpiccoli@linux.vnet.ibm.com> Cc: Jason Wessel <jason.wessel@windriver.com> Cc: Vladimir Zapolskiy <vz@mleia.com> Cc: Sylvain Lemieux <slemieux.tyco@gmail.com> Cc: Carlo Caione <carlo@caione.org> Cc: Kevin Hilman <khilman@baylibre.com> Cc: Liviu Dudau <liviu.dudau@arm.com> Cc: Sudeep Holla <sudeep.holla@arm.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Andy Gross <andy.gross@linaro.org> Cc: David Brown <david.brown@linaro.org> Cc: "Andreas Färber" <afaerber@suse.de> Cc: Kevin Cernekee <cernekee@gmail.com> Cc: Laxman Dewangan <ldewangan@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: Jonathan Hunter <jonathanh@nvidia.com> Cc: Barry Song <baohua@kernel.org> Cc: Patrice Chotard <patrice.chotard@st.com> Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com> Cc: Alexandre Torgue <alexandre.torgue@st.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Peter Korsgaard <jacmet@sunsite.dk> Cc: Timur Tabi <timur@tabi.org> Cc: Tony Prisk <linux@prisktech.co.nz> Cc: Michal Simek <michal.simek@xilinx.com> Cc: "Sören Brinkmann" <soren.brinkmann@xilinx.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Kate Stewart <kstewart@linuxfoundation.org> Cc: Philippe Ombredanne <pombredanne@nexb.com> Cc: Jiri Slaby <jslaby@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-11-07 01:11:51 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Derived from many drivers using generic_serial interface.
*
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
*
* Serial driver for BCM63xx integrated UART.
*
* Hardware flow control was _not_ tested since I only have RX/TX on
* my board.
*/
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/clk.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/sysrq.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/serial_bcm63xx.h>
#include <linux/io.h>
#include <linux/of.h>
#define BCM63XX_NR_UARTS 2
static struct uart_port ports[BCM63XX_NR_UARTS];
/*
* rx interrupt mask / stat
*
* mask:
* - rx fifo full
* - rx fifo above threshold
* - rx fifo not empty for too long
*/
#define UART_RX_INT_MASK (UART_IR_MASK(UART_IR_RXOVER) | \
UART_IR_MASK(UART_IR_RXTHRESH) | \
UART_IR_MASK(UART_IR_RXTIMEOUT))
#define UART_RX_INT_STAT (UART_IR_STAT(UART_IR_RXOVER) | \
UART_IR_STAT(UART_IR_RXTHRESH) | \
UART_IR_STAT(UART_IR_RXTIMEOUT))
/*
* tx interrupt mask / stat
*
* mask:
* - tx fifo empty
* - tx fifo below threshold
*/
#define UART_TX_INT_MASK (UART_IR_MASK(UART_IR_TXEMPTY) | \
UART_IR_MASK(UART_IR_TXTRESH))
#define UART_TX_INT_STAT (UART_IR_STAT(UART_IR_TXEMPTY) | \
UART_IR_STAT(UART_IR_TXTRESH))
/*
* external input interrupt
*
* mask: any edge on CTS, DCD
*/
#define UART_EXTINP_INT_MASK (UART_EXTINP_IRMASK(UART_EXTINP_IR_CTS) | \
UART_EXTINP_IRMASK(UART_EXTINP_IR_DCD))
/*
* handy uart register accessor
*/
static inline unsigned int bcm_uart_readl(struct uart_port *port,
unsigned int offset)
{
return __raw_readl(port->membase + offset);
}
static inline void bcm_uart_writel(struct uart_port *port,
unsigned int value, unsigned int offset)
{
__raw_writel(value, port->membase + offset);
}
/*
* serial core request to check if uart tx fifo is empty
*/
static unsigned int bcm_uart_tx_empty(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_IR_REG);
return (val & UART_IR_STAT(UART_IR_TXEMPTY)) ? 1 : 0;
}
/*
* serial core request to set RTS and DTR pin state and loopback mode
*/
static void bcm_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
unsigned int val;
val = bcm_uart_readl(port, UART_MCTL_REG);
val &= ~(UART_MCTL_DTR_MASK | UART_MCTL_RTS_MASK);
/* invert of written value is reflected on the pin */
if (!(mctrl & TIOCM_DTR))
val |= UART_MCTL_DTR_MASK;
if (!(mctrl & TIOCM_RTS))
val |= UART_MCTL_RTS_MASK;
bcm_uart_writel(port, val, UART_MCTL_REG);
val = bcm_uart_readl(port, UART_CTL_REG);
if (mctrl & TIOCM_LOOP)
val |= UART_CTL_LOOPBACK_MASK;
else
val &= ~UART_CTL_LOOPBACK_MASK;
bcm_uart_writel(port, val, UART_CTL_REG);
}
/*
* serial core request to return RI, CTS, DCD and DSR pin state
*/
static unsigned int bcm_uart_get_mctrl(struct uart_port *port)
{
unsigned int val, mctrl;
mctrl = 0;
val = bcm_uart_readl(port, UART_EXTINP_REG);
if (val & UART_EXTINP_RI_MASK)
mctrl |= TIOCM_RI;
if (val & UART_EXTINP_CTS_MASK)
mctrl |= TIOCM_CTS;
if (val & UART_EXTINP_DCD_MASK)
mctrl |= TIOCM_CD;
if (val & UART_EXTINP_DSR_MASK)
mctrl |= TIOCM_DSR;
return mctrl;
}
/*
* serial core request to disable tx ASAP (used for flow control)
*/
static void bcm_uart_stop_tx(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_CTL_REG);
val &= ~(UART_CTL_TXEN_MASK);
bcm_uart_writel(port, val, UART_CTL_REG);
val = bcm_uart_readl(port, UART_IR_REG);
val &= ~UART_TX_INT_MASK;
bcm_uart_writel(port, val, UART_IR_REG);
}
/*
* serial core request to (re)enable tx
*/
static void bcm_uart_start_tx(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_IR_REG);
val |= UART_TX_INT_MASK;
bcm_uart_writel(port, val, UART_IR_REG);
val = bcm_uart_readl(port, UART_CTL_REG);
val |= UART_CTL_TXEN_MASK;
bcm_uart_writel(port, val, UART_CTL_REG);
}
/*
* serial core request to stop rx, called before port shutdown
*/
static void bcm_uart_stop_rx(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_IR_REG);
val &= ~UART_RX_INT_MASK;
bcm_uart_writel(port, val, UART_IR_REG);
}
/*
* serial core request to enable modem status interrupt reporting
*/
static void bcm_uart_enable_ms(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_IR_REG);
val |= UART_IR_MASK(UART_IR_EXTIP);
bcm_uart_writel(port, val, UART_IR_REG);
}
/*
* serial core request to start/stop emitting break char
*/
static void bcm_uart_break_ctl(struct uart_port *port, int ctl)
{
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&port->lock, flags);
val = bcm_uart_readl(port, UART_CTL_REG);
if (ctl)
val |= UART_CTL_XMITBRK_MASK;
else
val &= ~UART_CTL_XMITBRK_MASK;
bcm_uart_writel(port, val, UART_CTL_REG);
spin_unlock_irqrestore(&port->lock, flags);
}
/*
* return port type in string format
*/
static const char *bcm_uart_type(struct uart_port *port)
{
return (port->type == PORT_BCM63XX) ? "bcm63xx_uart" : NULL;
}
/*
* read all chars in rx fifo and send them to core
*/
static void bcm_uart_do_rx(struct uart_port *port)
{
struct tty_port *tty_port = &port->state->port;
unsigned int max_count;
/* limit number of char read in interrupt, should not be
* higher than fifo size anyway since we're much faster than
* serial port */
max_count = 32;
do {
unsigned int iestat, c, cstat;
char flag;
/* get overrun/fifo empty information from ier
* register */
iestat = bcm_uart_readl(port, UART_IR_REG);
if (unlikely(iestat & UART_IR_STAT(UART_IR_RXOVER))) {
unsigned int val;
/* fifo reset is required to clear
* interrupt */
val = bcm_uart_readl(port, UART_CTL_REG);
val |= UART_CTL_RSTRXFIFO_MASK;
bcm_uart_writel(port, val, UART_CTL_REG);
port->icount.overrun++;
tty_insert_flip_char(tty_port, 0, TTY_OVERRUN);
}
if (!(iestat & UART_IR_STAT(UART_IR_RXNOTEMPTY)))
break;
cstat = c = bcm_uart_readl(port, UART_FIFO_REG);
port->icount.rx++;
flag = TTY_NORMAL;
c &= 0xff;
if (unlikely((cstat & UART_FIFO_ANYERR_MASK))) {
/* do stats first */
if (cstat & UART_FIFO_BRKDET_MASK) {
port->icount.brk++;
if (uart_handle_break(port))
continue;
}
if (cstat & UART_FIFO_PARERR_MASK)
port->icount.parity++;
if (cstat & UART_FIFO_FRAMEERR_MASK)
port->icount.frame++;
/* update flag wrt read_status_mask */
cstat &= port->read_status_mask;
if (cstat & UART_FIFO_BRKDET_MASK)
flag = TTY_BREAK;
if (cstat & UART_FIFO_FRAMEERR_MASK)
flag = TTY_FRAME;
if (cstat & UART_FIFO_PARERR_MASK)
flag = TTY_PARITY;
}
if (uart_handle_sysrq_char(port, c))
continue;
if ((cstat & port->ignore_status_mask) == 0)
tty_insert_flip_char(tty_port, c, flag);
} while (--max_count);
tty_flip_buffer_push(tty_port);
}
/*
* fill tx fifo with chars to send, stop when fifo is about to be full
* or when all chars have been sent.
*/
static void bcm_uart_do_tx(struct uart_port *port)
{
struct circ_buf *xmit;
unsigned int val, max_count;
if (port->x_char) {
bcm_uart_writel(port, port->x_char, UART_FIFO_REG);
port->icount.tx++;
port->x_char = 0;
return;
}
if (uart_tx_stopped(port)) {
bcm_uart_stop_tx(port);
return;
}
xmit = &port->state->xmit;
if (uart_circ_empty(xmit))
goto txq_empty;
val = bcm_uart_readl(port, UART_MCTL_REG);
val = (val & UART_MCTL_TXFIFOFILL_MASK) >> UART_MCTL_TXFIFOFILL_SHIFT;
max_count = port->fifosize - val;
while (max_count--) {
unsigned int c;
c = xmit->buf[xmit->tail];
bcm_uart_writel(port, c, UART_FIFO_REG);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
if (uart_circ_empty(xmit))
break;
}
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port);
if (uart_circ_empty(xmit))
goto txq_empty;
return;
txq_empty:
/* nothing to send, disable transmit interrupt */
val = bcm_uart_readl(port, UART_IR_REG);
val &= ~UART_TX_INT_MASK;
bcm_uart_writel(port, val, UART_IR_REG);
return;
}
/*
* process uart interrupt
*/
static irqreturn_t bcm_uart_interrupt(int irq, void *dev_id)
{
struct uart_port *port;
unsigned int irqstat;
port = dev_id;
spin_lock(&port->lock);
irqstat = bcm_uart_readl(port, UART_IR_REG);
if (irqstat & UART_RX_INT_STAT)
bcm_uart_do_rx(port);
if (irqstat & UART_TX_INT_STAT)
bcm_uart_do_tx(port);
if (irqstat & UART_IR_MASK(UART_IR_EXTIP)) {
unsigned int estat;
estat = bcm_uart_readl(port, UART_EXTINP_REG);
if (estat & UART_EXTINP_IRSTAT(UART_EXTINP_IR_CTS))
uart_handle_cts_change(port,
estat & UART_EXTINP_CTS_MASK);
if (estat & UART_EXTINP_IRSTAT(UART_EXTINP_IR_DCD))
uart_handle_dcd_change(port,
estat & UART_EXTINP_DCD_MASK);
}
spin_unlock(&port->lock);
return IRQ_HANDLED;
}
/*
* enable rx & tx operation on uart
*/
static void bcm_uart_enable(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_CTL_REG);
val |= (UART_CTL_BRGEN_MASK | UART_CTL_TXEN_MASK | UART_CTL_RXEN_MASK);
bcm_uart_writel(port, val, UART_CTL_REG);
}
/*
* disable rx & tx operation on uart
*/
static void bcm_uart_disable(struct uart_port *port)
{
unsigned int val;
val = bcm_uart_readl(port, UART_CTL_REG);
val &= ~(UART_CTL_BRGEN_MASK | UART_CTL_TXEN_MASK |
UART_CTL_RXEN_MASK);
bcm_uart_writel(port, val, UART_CTL_REG);
}
/*
* clear all unread data in rx fifo and unsent data in tx fifo
*/
static void bcm_uart_flush(struct uart_port *port)
{
unsigned int val;
/* empty rx and tx fifo */
val = bcm_uart_readl(port, UART_CTL_REG);
val |= UART_CTL_RSTRXFIFO_MASK | UART_CTL_RSTTXFIFO_MASK;
bcm_uart_writel(port, val, UART_CTL_REG);
/* read any pending char to make sure all irq status are
* cleared */
(void)bcm_uart_readl(port, UART_FIFO_REG);
}
/*
* serial core request to initialize uart and start rx operation
*/
static int bcm_uart_startup(struct uart_port *port)
{
unsigned int val;
int ret;
/* mask all irq and flush port */
bcm_uart_disable(port);
bcm_uart_writel(port, 0, UART_IR_REG);
bcm_uart_flush(port);
/* clear any pending external input interrupt */
(void)bcm_uart_readl(port, UART_EXTINP_REG);
/* set rx/tx fifo thresh to fifo half size */
val = bcm_uart_readl(port, UART_MCTL_REG);
val &= ~(UART_MCTL_RXFIFOTHRESH_MASK | UART_MCTL_TXFIFOTHRESH_MASK);
val |= (port->fifosize / 2) << UART_MCTL_RXFIFOTHRESH_SHIFT;
val |= (port->fifosize / 2) << UART_MCTL_TXFIFOTHRESH_SHIFT;
bcm_uart_writel(port, val, UART_MCTL_REG);
/* set rx fifo timeout to 1 char time */
val = bcm_uart_readl(port, UART_CTL_REG);
val &= ~UART_CTL_RXTMOUTCNT_MASK;
val |= 1 << UART_CTL_RXTMOUTCNT_SHIFT;
bcm_uart_writel(port, val, UART_CTL_REG);
/* report any edge on dcd and cts */
val = UART_EXTINP_INT_MASK;
val |= UART_EXTINP_DCD_NOSENSE_MASK;
val |= UART_EXTINP_CTS_NOSENSE_MASK;
bcm_uart_writel(port, val, UART_EXTINP_REG);
/* register irq and enable rx interrupts */
ret = request_irq(port->irq, bcm_uart_interrupt, 0,
dev_name(port->dev), port);
if (ret)
return ret;
bcm_uart_writel(port, UART_RX_INT_MASK, UART_IR_REG);
bcm_uart_enable(port);
return 0;
}
/*
* serial core request to flush & disable uart
*/
static void bcm_uart_shutdown(struct uart_port *port)
{
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
bcm_uart_writel(port, 0, UART_IR_REG);
spin_unlock_irqrestore(&port->lock, flags);
bcm_uart_disable(port);
bcm_uart_flush(port);
free_irq(port->irq, port);
}
/*
* serial core request to change current uart setting
*/
static void bcm_uart_set_termios(struct uart_port *port,
struct ktermios *new,
struct ktermios *old)
{
unsigned int ctl, baud, quot, ier;
unsigned long flags;
int tries;
spin_lock_irqsave(&port->lock, flags);
/* Drain the hot tub fully before we power it off for the winter. */
for (tries = 3; !bcm_uart_tx_empty(port) && tries; tries--)
mdelay(10);
/* disable uart while changing speed */
bcm_uart_disable(port);
bcm_uart_flush(port);
/* update Control register */
ctl = bcm_uart_readl(port, UART_CTL_REG);
ctl &= ~UART_CTL_BITSPERSYM_MASK;
switch (new->c_cflag & CSIZE) {
case CS5:
ctl |= (0 << UART_CTL_BITSPERSYM_SHIFT);
break;
case CS6:
ctl |= (1 << UART_CTL_BITSPERSYM_SHIFT);
break;
case CS7:
ctl |= (2 << UART_CTL_BITSPERSYM_SHIFT);
break;
default:
ctl |= (3 << UART_CTL_BITSPERSYM_SHIFT);
break;
}
ctl &= ~UART_CTL_STOPBITS_MASK;
if (new->c_cflag & CSTOPB)
ctl |= UART_CTL_STOPBITS_2;
else
ctl |= UART_CTL_STOPBITS_1;
ctl &= ~(UART_CTL_RXPAREN_MASK | UART_CTL_TXPAREN_MASK);
if (new->c_cflag & PARENB)
ctl |= (UART_CTL_RXPAREN_MASK | UART_CTL_TXPAREN_MASK);
ctl &= ~(UART_CTL_RXPAREVEN_MASK | UART_CTL_TXPAREVEN_MASK);
if (new->c_cflag & PARODD)
ctl |= (UART_CTL_RXPAREVEN_MASK | UART_CTL_TXPAREVEN_MASK);
bcm_uart_writel(port, ctl, UART_CTL_REG);
/* update Baudword register */
baud = uart_get_baud_rate(port, new, old, 0, port->uartclk / 16);
quot = uart_get_divisor(port, baud) - 1;
bcm_uart_writel(port, quot, UART_BAUD_REG);
/* update Interrupt register */
ier = bcm_uart_readl(port, UART_IR_REG);
ier &= ~UART_IR_MASK(UART_IR_EXTIP);
if (UART_ENABLE_MS(port, new->c_cflag))
ier |= UART_IR_MASK(UART_IR_EXTIP);
bcm_uart_writel(port, ier, UART_IR_REG);
/* update read/ignore mask */
port->read_status_mask = UART_FIFO_VALID_MASK;
if (new->c_iflag & INPCK) {
port->read_status_mask |= UART_FIFO_FRAMEERR_MASK;
port->read_status_mask |= UART_FIFO_PARERR_MASK;
}
serial: Fix IGNBRK handling If IGNBRK is set without either BRKINT or PARMRK set, some uart drivers send a 0x00 byte for BREAK without the TTYBREAK flag to the line discipline, when it should send either nothing or the TTYBREAK flag set. This happens because the read_status_mask masks out the BI condition, which uart_insert_char() then interprets as a normal 0x00 byte. SUS v3 is clear regarding the meaning of IGNBRK; Section 11.2.2, General Terminal Interface - Input Modes, states: "If IGNBRK is set, a break condition detected on input shall be ignored; that is, not put on the input queue and therefore not read by any process." Fix read_status_mask to include the BI bit if IGNBRK is set; the lsr status retains the BI bit if a BREAK is recv'd, which is subsequently ignored in uart_insert_char() when masked with the ignore_status_mask. Affected drivers: 8250 - all serial_txx9 mfd amba-pl010 amba-pl011 atmel_serial bfin_uart dz ip22zilog max310x mxs-auart netx-serial pnx8xxx_uart pxa sb1250-duart sccnxp serial_ks8695 sirfsoc_uart st-asc vr41xx_siu zs sunzilog fsl_lpuart sunsab ucc_uart bcm63xx_uart sunsu efm32-uart pmac_zilog mpsc msm_serial m32r_sio Unaffected drivers: omap-serial rp2 sa1100 imx icom Annotated for fixes: altera_uart mcf Drivers without break detection: 21285 xilinx-uartps altera_jtaguart apbuart arc-uart clps711x max3100 uartlite msm_serial_hs nwpserial lantiq vt8500_serial Unknown: samsung mpc52xx_uart bfin_sport_uart cpm_uart/core Fixes: Bugzilla #71651, '8250_core.c incorrectly handles IGNBRK flag' Reported-by: Ivan <athlon_@mail.ru> Signed-off-by: Peter Hurley <peter@hurleysoftware.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2014-06-16 20:10:41 +08:00
if (new->c_iflag & (IGNBRK | BRKINT))
port->read_status_mask |= UART_FIFO_BRKDET_MASK;
port->ignore_status_mask = 0;
if (new->c_iflag & IGNPAR)
port->ignore_status_mask |= UART_FIFO_PARERR_MASK;
if (new->c_iflag & IGNBRK)
port->ignore_status_mask |= UART_FIFO_BRKDET_MASK;
if (!(new->c_cflag & CREAD))
port->ignore_status_mask |= UART_FIFO_VALID_MASK;
uart_update_timeout(port, new->c_cflag, baud);
bcm_uart_enable(port);
spin_unlock_irqrestore(&port->lock, flags);
}
/*
* serial core request to claim uart iomem
*/
static int bcm_uart_request_port(struct uart_port *port)
{
/* UARTs always present */
return 0;
}
/*
* serial core request to release uart iomem
*/
static void bcm_uart_release_port(struct uart_port *port)
{
/* Nothing to release ... */
}
/*
* serial core request to do any port required autoconfiguration
*/
static void bcm_uart_config_port(struct uart_port *port, int flags)
{
if (flags & UART_CONFIG_TYPE) {
if (bcm_uart_request_port(port))
return;
port->type = PORT_BCM63XX;
}
}
/*
* serial core request to check that port information in serinfo are
* suitable
*/
static int bcm_uart_verify_port(struct uart_port *port,
struct serial_struct *serinfo)
{
if (port->type != PORT_BCM63XX)
return -EINVAL;
if (port->irq != serinfo->irq)
return -EINVAL;
if (port->iotype != serinfo->io_type)
return -EINVAL;
if (port->mapbase != (unsigned long)serinfo->iomem_base)
return -EINVAL;
return 0;
}
/* serial core callbacks */
static const struct uart_ops bcm_uart_ops = {
.tx_empty = bcm_uart_tx_empty,
.get_mctrl = bcm_uart_get_mctrl,
.set_mctrl = bcm_uart_set_mctrl,
.start_tx = bcm_uart_start_tx,
.stop_tx = bcm_uart_stop_tx,
.stop_rx = bcm_uart_stop_rx,
.enable_ms = bcm_uart_enable_ms,
.break_ctl = bcm_uart_break_ctl,
.startup = bcm_uart_startup,
.shutdown = bcm_uart_shutdown,
.set_termios = bcm_uart_set_termios,
.type = bcm_uart_type,
.release_port = bcm_uart_release_port,
.request_port = bcm_uart_request_port,
.config_port = bcm_uart_config_port,
.verify_port = bcm_uart_verify_port,
};
#ifdef CONFIG_SERIAL_BCM63XX_CONSOLE
static void wait_for_xmitr(struct uart_port *port)
{
unsigned int tmout;
/* Wait up to 10ms for the character(s) to be sent. */
tmout = 10000;
while (--tmout) {
unsigned int val;
val = bcm_uart_readl(port, UART_IR_REG);
if (val & UART_IR_STAT(UART_IR_TXEMPTY))
break;
udelay(1);
}
/* Wait up to 1s for flow control if necessary */
if (port->flags & UPF_CONS_FLOW) {
tmout = 1000000;
while (--tmout) {
unsigned int val;
val = bcm_uart_readl(port, UART_EXTINP_REG);
if (val & UART_EXTINP_CTS_MASK)
break;
udelay(1);
}
}
}
/*
* output given char
*/
serial: make uart_console_write->putchar()'s character an unsigned char Currently, uart_console_write->putchar's second parameter (the character) is of type int. It makes little sense, provided uart_console_write() accepts the input string as "const char *s" and passes its content -- the characters -- to putchar(). So switch the character's type to unsigned char. We don't use char as that is signed on some platforms. That would cause troubles for drivers which (implicitly) cast the char to u16 when writing to the device. Sign extension would happen in that case and the value written would be completely different to the provided char. DZ is an example of such a driver -- on MIPS, it uses u16 for dz_out in dz_console_putchar(). Note we do the char -> uchar conversion implicitly in uart_console_write(). Provided we do not change size of the data type, sign extension does not happen there, so the problem is void. This makes the types consistent and unified with the rest of the uart layer, which uses unsigned char in most places already. One exception is xmit_buf, but that is going to be converted later. Cc: Paul Cercueil <paul@crapouillou.net> Cc: Tobias Klauser <tklauser@distanz.ch> Cc: Russell King <linux@armlinux.org.uk> Cc: Vineet Gupta <vgupta@kernel.org> Cc: Nicolas Ferre <nicolas.ferre@microchip.com> Cc: Alexandre Belloni <alexandre.belloni@bootlin.com> Cc: Ludovic Desroches <ludovic.desroches@microchip.com> Cc: Florian Fainelli <f.fainelli@gmail.com> Cc: bcm-kernel-feedback-list@broadcom.com Cc: Alexander Shiyan <shc_work@mail.ru> Cc: Baruch Siach <baruch@tkos.co.il> Cc: "Maciej W. Rozycki" <macro@orcam.me.uk> Cc: Paul Walmsley <paul.walmsley@sifive.com> Cc: Palmer Dabbelt <palmer@dabbelt.com> Cc: Albert Ou <aou@eecs.berkeley.edu> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Pengutronix Kernel Team <kernel@pengutronix.de> Cc: Fabio Estevam <festevam@gmail.com> Cc: NXP Linux Team <linux-imx@nxp.com> Cc: Karol Gugala <kgugala@antmicro.com> Cc: Mateusz Holenko <mholenko@antmicro.com> Cc: Vladimir Zapolskiy <vz@mleia.com> Cc: Neil Armstrong <narmstrong@baylibre.com> Cc: Kevin Hilman <khilman@baylibre.com> Cc: Jerome Brunet <jbrunet@baylibre.com> Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com> Cc: Taichi Sugaya <sugaya.taichi@socionext.com> Cc: Takao Orito <orito.takao@socionext.com> Cc: Liviu Dudau <liviu.dudau@arm.com> Cc: Sudeep Holla <sudeep.holla@arm.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: "Andreas Färber" <afaerber@suse.de> Cc: Manivannan Sadhasivam <mani@kernel.org> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Andy Gross <agross@kernel.org> Cc: Bjorn Andersson <bjorn.andersson@linaro.org> Cc: Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com> Cc: Orson Zhai <orsonzhai@gmail.com> Cc: Baolin Wang <baolin.wang7@gmail.com> Cc: Chunyan Zhang <zhang.lyra@gmail.com> Cc: Patrice Chotard <patrice.chotard@foss.st.com> Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com> Cc: Alexandre Torgue <alexandre.torgue@foss.st.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Peter Korsgaard <peter@korsgaard.com> Cc: Michal Simek <michal.simek@xilinx.com> Acked-by: Richard Genoud <richard.genoud@gmail.com> [atmel_serial] Acked-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Acked-by: Paul Cercueil <paul@crapouillou.net> Acked-by: Neil Armstrong <narmstrong@baylibre.com> # meson_serial Signed-off-by: Jiri Slaby <jslaby@suse.cz> Link: https://lore.kernel.org/r/20220303080831.21783-1-jslaby@suse.cz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-03-03 16:08:31 +08:00
static void bcm_console_putchar(struct uart_port *port, unsigned char ch)
{
wait_for_xmitr(port);
bcm_uart_writel(port, ch, UART_FIFO_REG);
}
/*
* console core request to output given string
*/
static void bcm_console_write(struct console *co, const char *s,
unsigned int count)
{
struct uart_port *port;
unsigned long flags;
int locked;
port = &ports[co->index];
local_irq_save(flags);
if (port->sysrq) {
/* bcm_uart_interrupt() already took the lock */
locked = 0;
} else if (oops_in_progress) {
locked = spin_trylock(&port->lock);
} else {
spin_lock(&port->lock);
locked = 1;
}
/* call helper to deal with \r\n */
uart_console_write(port, s, count, bcm_console_putchar);
/* and wait for char to be transmitted */
wait_for_xmitr(port);
if (locked)
spin_unlock(&port->lock);
local_irq_restore(flags);
}
/*
* console core request to setup given console, find matching uart
* port and setup it.
*/
static int bcm_console_setup(struct console *co, char *options)
{
struct uart_port *port;
int baud = 9600;
int bits = 8;
int parity = 'n';
int flow = 'n';
if (co->index < 0 || co->index >= BCM63XX_NR_UARTS)
return -EINVAL;
port = &ports[co->index];
if (!port->membase)
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 bcm_uart_driver;
static struct console bcm63xx_console = {
.name = "ttyS",
.write = bcm_console_write,
.device = uart_console_device,
.setup = bcm_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &bcm_uart_driver,
};
static int __init bcm63xx_console_init(void)
{
register_console(&bcm63xx_console);
return 0;
}
console_initcall(bcm63xx_console_init);
static void bcm_early_write(struct console *con, const char *s, unsigned n)
{
struct earlycon_device *dev = con->data;
uart_console_write(&dev->port, s, n, bcm_console_putchar);
wait_for_xmitr(&dev->port);
}
static int __init bcm_early_console_setup(struct earlycon_device *device,
const char *opt)
{
if (!device->port.membase)
return -ENODEV;
device->con->write = bcm_early_write;
return 0;
}
OF_EARLYCON_DECLARE(bcm63xx_uart, "brcm,bcm6345-uart", bcm_early_console_setup);
#define BCM63XX_CONSOLE (&bcm63xx_console)
#else
#define BCM63XX_CONSOLE NULL
#endif /* CONFIG_SERIAL_BCM63XX_CONSOLE */
static struct uart_driver bcm_uart_driver = {
.owner = THIS_MODULE,
.driver_name = "bcm63xx_uart",
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = 64,
.nr = BCM63XX_NR_UARTS,
.cons = BCM63XX_CONSOLE,
};
/*
* platform driver probe/remove callback
*/
static int bcm_uart_probe(struct platform_device *pdev)
{
struct resource *res_mem;
struct uart_port *port;
struct clk *clk;
int ret;
if (pdev->dev.of_node) {
pdev->id = of_alias_get_id(pdev->dev.of_node, "serial");
if (pdev->id < 0)
pdev->id = of_alias_get_id(pdev->dev.of_node, "uart");
}
if (pdev->id < 0 || pdev->id >= BCM63XX_NR_UARTS)
return -EINVAL;
port = &ports[pdev->id];
if (port->membase)
return -EBUSY;
memset(port, 0, sizeof(*port));
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res_mem)
return -ENODEV;
port->mapbase = res_mem->start;
port->membase = devm_ioremap_resource(&pdev->dev, res_mem);
if (IS_ERR(port->membase))
return PTR_ERR(port->membase);
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
port->irq = ret;
clk = clk_get(&pdev->dev, "refclk");
if (IS_ERR(clk) && pdev->dev.of_node)
clk = of_clk_get(pdev->dev.of_node, 0);
if (IS_ERR(clk))
return -ENODEV;
port->iotype = UPIO_MEM;
port->ops = &bcm_uart_ops;
port->flags = UPF_BOOT_AUTOCONF;
port->dev = &pdev->dev;
port->fifosize = 16;
port->uartclk = clk_get_rate(clk) / 2;
port->line = pdev->id;
port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_BCM63XX_CONSOLE);
clk_put(clk);
ret = uart_add_one_port(&bcm_uart_driver, port);
if (ret) {
ports[pdev->id].membase = NULL;
return ret;
}
platform_set_drvdata(pdev, port);
return 0;
}
static int bcm_uart_remove(struct platform_device *pdev)
{
struct uart_port *port;
port = platform_get_drvdata(pdev);
uart_remove_one_port(&bcm_uart_driver, port);
/* mark port as free */
ports[pdev->id].membase = NULL;
return 0;
}
static const struct of_device_id bcm63xx_of_match[] = {
{ .compatible = "brcm,bcm6345-uart" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcm63xx_of_match);
/*
* platform driver stuff
*/
static struct platform_driver bcm_uart_platform_driver = {
.probe = bcm_uart_probe,
.remove = bcm_uart_remove,
.driver = {
.name = "bcm63xx_uart",
.of_match_table = bcm63xx_of_match,
},
};
static int __init bcm_uart_init(void)
{
int ret;
ret = uart_register_driver(&bcm_uart_driver);
if (ret)
return ret;
ret = platform_driver_register(&bcm_uart_platform_driver);
if (ret)
uart_unregister_driver(&bcm_uart_driver);
return ret;
}
static void __exit bcm_uart_exit(void)
{
platform_driver_unregister(&bcm_uart_platform_driver);
uart_unregister_driver(&bcm_uart_driver);
}
module_init(bcm_uart_init);
module_exit(bcm_uart_exit);
MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
MODULE_DESCRIPTION("Broadcom 63xx integrated uart driver");
MODULE_LICENSE("GPL");