mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-10 07:00:48 +00:00
d19993c40c
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-12-u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1611 lines
39 KiB
C
1611 lines
39 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Driver for CPM (SCC/SMC) serial ports; core driver
|
|
*
|
|
* Based on arch/ppc/cpm2_io/uart.c by Dan Malek
|
|
* Based on ppc8xx.c by Thomas Gleixner
|
|
* Based on drivers/serial/amba.c by Russell King
|
|
*
|
|
* Maintainer: Kumar Gala (galak@kernel.crashing.org) (CPM2)
|
|
* Pantelis Antoniou (panto@intracom.gr) (CPM1)
|
|
*
|
|
* Copyright (C) 2004, 2007 Freescale Semiconductor, Inc.
|
|
* (C) 2004 Intracom, S.A.
|
|
* (C) 2005-2006 MontaVista Software, Inc.
|
|
* Vitaly Bordug <vbordug@ru.mvista.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/init.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/console.h>
|
|
#include <linux/sysrq.h>
|
|
#include <linux/device.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/clk.h>
|
|
|
|
#include <sysdev/fsl_soc.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/delay.h>
|
|
#include <asm/udbg.h>
|
|
|
|
#include <linux/serial_core.h>
|
|
#include <linux/kernel.h>
|
|
|
|
#include "cpm_uart.h"
|
|
|
|
|
|
/**************************************************************/
|
|
|
|
static int cpm_uart_tx_pump(struct uart_port *port);
|
|
static void cpm_uart_initbd(struct uart_cpm_port *pinfo);
|
|
|
|
/**************************************************************/
|
|
|
|
#define HW_BUF_SPD_THRESHOLD 2400
|
|
|
|
static void cpm_line_cr_cmd(struct uart_cpm_port *port, int cmd)
|
|
{
|
|
cpm_command(port->command, cmd);
|
|
}
|
|
|
|
/*
|
|
* Check, if transmit buffers are processed
|
|
*/
|
|
static unsigned int cpm_uart_tx_empty(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
cbd_t __iomem *bdp = pinfo->tx_bd_base;
|
|
int ret = 0;
|
|
|
|
while (1) {
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_READY)
|
|
break;
|
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP) {
|
|
ret = TIOCSER_TEMT;
|
|
break;
|
|
}
|
|
bdp++;
|
|
}
|
|
|
|
pr_debug("CPM uart[%d]:tx_empty: %d\n", port->line, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cpm_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
|
|
if (pinfo->gpios[GPIO_RTS])
|
|
gpiod_set_value(pinfo->gpios[GPIO_RTS], !(mctrl & TIOCM_RTS));
|
|
|
|
if (pinfo->gpios[GPIO_DTR])
|
|
gpiod_set_value(pinfo->gpios[GPIO_DTR], !(mctrl & TIOCM_DTR));
|
|
}
|
|
|
|
static unsigned int cpm_uart_get_mctrl(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
unsigned int mctrl = TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
|
|
|
|
if (pinfo->gpios[GPIO_CTS]) {
|
|
if (gpiod_get_value(pinfo->gpios[GPIO_CTS]))
|
|
mctrl &= ~TIOCM_CTS;
|
|
}
|
|
|
|
if (pinfo->gpios[GPIO_DSR]) {
|
|
if (gpiod_get_value(pinfo->gpios[GPIO_DSR]))
|
|
mctrl &= ~TIOCM_DSR;
|
|
}
|
|
|
|
if (pinfo->gpios[GPIO_DCD]) {
|
|
if (gpiod_get_value(pinfo->gpios[GPIO_DCD]))
|
|
mctrl &= ~TIOCM_CAR;
|
|
}
|
|
|
|
if (pinfo->gpios[GPIO_RI]) {
|
|
if (!gpiod_get_value(pinfo->gpios[GPIO_RI]))
|
|
mctrl |= TIOCM_RNG;
|
|
}
|
|
|
|
return mctrl;
|
|
}
|
|
|
|
/*
|
|
* Stop transmitter
|
|
*/
|
|
static void cpm_uart_stop_tx(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
|
|
|
pr_debug("CPM uart[%d]:stop tx\n", port->line);
|
|
|
|
if (IS_SMC(pinfo))
|
|
clrbits8(&smcp->smc_smcm, SMCM_TX);
|
|
else
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_TX);
|
|
}
|
|
|
|
/*
|
|
* Start transmitter
|
|
*/
|
|
static void cpm_uart_start_tx(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
|
|
|
pr_debug("CPM uart[%d]:start tx\n", port->line);
|
|
|
|
if (IS_SMC(pinfo)) {
|
|
if (in_8(&smcp->smc_smcm) & SMCM_TX)
|
|
return;
|
|
} else {
|
|
if (in_be16(&sccp->scc_sccm) & UART_SCCM_TX)
|
|
return;
|
|
}
|
|
|
|
if (cpm_uart_tx_pump(port) != 0) {
|
|
if (IS_SMC(pinfo)) {
|
|
setbits8(&smcp->smc_smcm, SMCM_TX);
|
|
} else {
|
|
setbits16(&sccp->scc_sccm, UART_SCCM_TX);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stop receiver
|
|
*/
|
|
static void cpm_uart_stop_rx(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
|
|
|
pr_debug("CPM uart[%d]:stop rx\n", port->line);
|
|
|
|
if (IS_SMC(pinfo))
|
|
clrbits8(&smcp->smc_smcm, SMCM_RX);
|
|
else
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_RX);
|
|
}
|
|
|
|
/*
|
|
* Generate a break.
|
|
*/
|
|
static void cpm_uart_break_ctl(struct uart_port *port, int break_state)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
|
|
pr_debug("CPM uart[%d]:break ctrl, break_state: %d\n", port->line,
|
|
break_state);
|
|
|
|
if (break_state)
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
|
else
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_RESTART_TX);
|
|
}
|
|
|
|
/*
|
|
* Transmit characters, refill buffer descriptor, if possible
|
|
*/
|
|
static void cpm_uart_int_tx(struct uart_port *port)
|
|
{
|
|
pr_debug("CPM uart[%d]:TX INT\n", port->line);
|
|
|
|
cpm_uart_tx_pump(port);
|
|
}
|
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
static int serial_polled;
|
|
#endif
|
|
|
|
/*
|
|
* Receive characters
|
|
*/
|
|
static void cpm_uart_int_rx(struct uart_port *port)
|
|
{
|
|
int i;
|
|
unsigned char ch;
|
|
u8 *cp;
|
|
struct tty_port *tport = &port->state->port;
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
cbd_t __iomem *bdp;
|
|
u16 status;
|
|
unsigned int flg;
|
|
|
|
pr_debug("CPM uart[%d]:RX INT\n", port->line);
|
|
|
|
/* Just loop through the closed BDs and copy the characters into
|
|
* the buffer.
|
|
*/
|
|
bdp = pinfo->rx_cur;
|
|
for (;;) {
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
if (unlikely(serial_polled)) {
|
|
serial_polled = 0;
|
|
return;
|
|
}
|
|
#endif
|
|
/* get status */
|
|
status = in_be16(&bdp->cbd_sc);
|
|
/* If this one is empty, return happy */
|
|
if (status & BD_SC_EMPTY)
|
|
break;
|
|
|
|
/* get number of characters, and check spce in flip-buffer */
|
|
i = in_be16(&bdp->cbd_datlen);
|
|
|
|
/* If we have not enough room in tty flip buffer, then we try
|
|
* later, which will be the next rx-interrupt or a timeout
|
|
*/
|
|
if (tty_buffer_request_room(tport, i) < i) {
|
|
printk(KERN_WARNING "No room in flip buffer\n");
|
|
return;
|
|
}
|
|
|
|
/* get pointer */
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
|
|
|
/* loop through the buffer */
|
|
while (i-- > 0) {
|
|
ch = *cp++;
|
|
port->icount.rx++;
|
|
flg = TTY_NORMAL;
|
|
|
|
if (status &
|
|
(BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_OV))
|
|
goto handle_error;
|
|
if (uart_handle_sysrq_char(port, ch))
|
|
continue;
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
if (unlikely(serial_polled)) {
|
|
serial_polled = 0;
|
|
return;
|
|
}
|
|
#endif
|
|
error_return:
|
|
tty_insert_flip_char(tport, ch, flg);
|
|
|
|
} /* End while (i--) */
|
|
|
|
/* This BD is ready to be used again. Clear status. get next */
|
|
clrbits16(&bdp->cbd_sc, BD_SC_BR | BD_SC_FR | BD_SC_PR |
|
|
BD_SC_OV | BD_SC_ID);
|
|
setbits16(&bdp->cbd_sc, BD_SC_EMPTY);
|
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
|
bdp = pinfo->rx_bd_base;
|
|
else
|
|
bdp++;
|
|
|
|
} /* End for (;;) */
|
|
|
|
/* Write back buffer pointer */
|
|
pinfo->rx_cur = bdp;
|
|
|
|
/* activate BH processing */
|
|
tty_flip_buffer_push(tport);
|
|
|
|
return;
|
|
|
|
/* Error processing */
|
|
|
|
handle_error:
|
|
/* Statistics */
|
|
if (status & BD_SC_BR)
|
|
port->icount.brk++;
|
|
if (status & BD_SC_PR)
|
|
port->icount.parity++;
|
|
if (status & BD_SC_FR)
|
|
port->icount.frame++;
|
|
if (status & BD_SC_OV)
|
|
port->icount.overrun++;
|
|
|
|
/* Mask out ignored conditions */
|
|
status &= port->read_status_mask;
|
|
|
|
/* Handle the remaining ones */
|
|
if (status & BD_SC_BR)
|
|
flg = TTY_BREAK;
|
|
else if (status & BD_SC_PR)
|
|
flg = TTY_PARITY;
|
|
else if (status & BD_SC_FR)
|
|
flg = TTY_FRAME;
|
|
|
|
/* overrun does not affect the current character ! */
|
|
if (status & BD_SC_OV) {
|
|
ch = 0;
|
|
flg = TTY_OVERRUN;
|
|
/* We skip this buffer */
|
|
/* CHECK: Is really nothing senseful there */
|
|
/* ASSUMPTION: it contains nothing valid */
|
|
i = 0;
|
|
}
|
|
port->sysrq = 0;
|
|
goto error_return;
|
|
}
|
|
|
|
/*
|
|
* Asynchron mode interrupt handler
|
|
*/
|
|
static irqreturn_t cpm_uart_int(int irq, void *data)
|
|
{
|
|
u8 events;
|
|
struct uart_port *port = data;
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
|
|
|
pr_debug("CPM uart[%d]:IRQ\n", port->line);
|
|
|
|
if (IS_SMC(pinfo)) {
|
|
events = in_8(&smcp->smc_smce);
|
|
out_8(&smcp->smc_smce, events);
|
|
if (events & SMCM_BRKE)
|
|
uart_handle_break(port);
|
|
if (events & SMCM_RX)
|
|
cpm_uart_int_rx(port);
|
|
if (events & SMCM_TX)
|
|
cpm_uart_int_tx(port);
|
|
} else {
|
|
events = in_be16(&sccp->scc_scce);
|
|
out_be16(&sccp->scc_scce, events);
|
|
if (events & UART_SCCM_BRKE)
|
|
uart_handle_break(port);
|
|
if (events & UART_SCCM_RX)
|
|
cpm_uart_int_rx(port);
|
|
if (events & UART_SCCM_TX)
|
|
cpm_uart_int_tx(port);
|
|
}
|
|
return (events) ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
|
|
static int cpm_uart_startup(struct uart_port *port)
|
|
{
|
|
int retval;
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
|
|
pr_debug("CPM uart[%d]:startup\n", port->line);
|
|
|
|
/* If the port is not the console, make sure rx is disabled. */
|
|
if (!(pinfo->flags & FLAG_CONSOLE)) {
|
|
/* Disable UART rx */
|
|
if (IS_SMC(pinfo)) {
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN);
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX);
|
|
} else {
|
|
clrbits32(&pinfo->sccp->scc_gsmrl, SCC_GSMRL_ENR);
|
|
clrbits16(&pinfo->sccp->scc_sccm, UART_SCCM_RX);
|
|
}
|
|
cpm_uart_initbd(pinfo);
|
|
if (IS_SMC(pinfo)) {
|
|
out_be32(&pinfo->smcup->smc_rstate, 0);
|
|
out_be32(&pinfo->smcup->smc_tstate, 0);
|
|
out_be16(&pinfo->smcup->smc_rbptr,
|
|
in_be16(&pinfo->smcup->smc_rbase));
|
|
out_be16(&pinfo->smcup->smc_tbptr,
|
|
in_be16(&pinfo->smcup->smc_tbase));
|
|
} else {
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
|
}
|
|
}
|
|
/* Install interrupt handler. */
|
|
retval = request_irq(port->irq, cpm_uart_int, 0, "cpm_uart", port);
|
|
if (retval)
|
|
return retval;
|
|
|
|
/* Startup rx-int */
|
|
if (IS_SMC(pinfo)) {
|
|
setbits8(&pinfo->smcp->smc_smcm, SMCM_RX);
|
|
setbits16(&pinfo->smcp->smc_smcmr, (SMCMR_REN | SMCMR_TEN));
|
|
} else {
|
|
setbits16(&pinfo->sccp->scc_sccm, UART_SCCM_RX);
|
|
setbits32(&pinfo->sccp->scc_gsmrl, (SCC_GSMRL_ENR | SCC_GSMRL_ENT));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline void cpm_uart_wait_until_send(struct uart_cpm_port *pinfo)
|
|
{
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
schedule_timeout(pinfo->wait_closing);
|
|
}
|
|
|
|
/*
|
|
* Shutdown the uart
|
|
*/
|
|
static void cpm_uart_shutdown(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
|
|
pr_debug("CPM uart[%d]:shutdown\n", port->line);
|
|
|
|
/* free interrupt handler */
|
|
free_irq(port->irq, port);
|
|
|
|
/* If the port is not the console, disable Rx and Tx. */
|
|
if (!(pinfo->flags & FLAG_CONSOLE)) {
|
|
/* Wait for all the BDs marked sent */
|
|
while(!cpm_uart_tx_empty(port)) {
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
schedule_timeout(2);
|
|
}
|
|
|
|
if (pinfo->wait_closing)
|
|
cpm_uart_wait_until_send(pinfo);
|
|
|
|
/* Stop uarts */
|
|
if (IS_SMC(pinfo)) {
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
clrbits16(&smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
|
clrbits8(&smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
} else {
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
|
clrbits32(&sccp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_TX | UART_SCCM_RX);
|
|
}
|
|
|
|
/* Shut them really down and reinit buffer descriptors */
|
|
if (IS_SMC(pinfo)) {
|
|
out_be16(&pinfo->smcup->smc_brkcr, 0);
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
|
} else {
|
|
out_be16(&pinfo->sccup->scc_brkcr, 0);
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_GRA_STOP_TX);
|
|
}
|
|
|
|
cpm_uart_initbd(pinfo);
|
|
}
|
|
}
|
|
|
|
static void cpm_uart_set_termios(struct uart_port *port,
|
|
struct ktermios *termios,
|
|
const struct ktermios *old)
|
|
{
|
|
int baud;
|
|
unsigned long flags;
|
|
u16 cval, scval, prev_mode;
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
|
int maxidl;
|
|
|
|
pr_debug("CPM uart[%d]:set_termios\n", port->line);
|
|
|
|
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
|
|
if (baud < HW_BUF_SPD_THRESHOLD || port->flags & UPF_LOW_LATENCY)
|
|
pinfo->rx_fifosize = 1;
|
|
else
|
|
pinfo->rx_fifosize = RX_BUF_SIZE;
|
|
|
|
/* MAXIDL is the timeout after which a receive buffer is closed
|
|
* when not full if no more characters are received.
|
|
* We calculate it from the baudrate so that the duration is
|
|
* always the same at standard rates: about 4ms.
|
|
*/
|
|
maxidl = baud / 2400;
|
|
if (maxidl < 1)
|
|
maxidl = 1;
|
|
if (maxidl > 0x10)
|
|
maxidl = 0x10;
|
|
|
|
cval = 0;
|
|
scval = 0;
|
|
|
|
if (termios->c_cflag & CSTOPB) {
|
|
cval |= SMCMR_SL; /* Two stops */
|
|
scval |= SCU_PSMR_SL;
|
|
}
|
|
|
|
if (termios->c_cflag & PARENB) {
|
|
cval |= SMCMR_PEN;
|
|
scval |= SCU_PSMR_PEN;
|
|
if (!(termios->c_cflag & PARODD)) {
|
|
cval |= SMCMR_PM_EVEN;
|
|
scval |= (SCU_PSMR_REVP | SCU_PSMR_TEVP);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update the timeout
|
|
*/
|
|
uart_update_timeout(port, termios->c_cflag, baud);
|
|
|
|
/*
|
|
* Set up parity check flag
|
|
*/
|
|
port->read_status_mask = (BD_SC_EMPTY | BD_SC_OV);
|
|
if (termios->c_iflag & INPCK)
|
|
port->read_status_mask |= BD_SC_FR | BD_SC_PR;
|
|
if ((termios->c_iflag & BRKINT) || (termios->c_iflag & PARMRK))
|
|
port->read_status_mask |= BD_SC_BR;
|
|
|
|
/*
|
|
* Characters to ignore
|
|
*/
|
|
port->ignore_status_mask = 0;
|
|
if (termios->c_iflag & IGNPAR)
|
|
port->ignore_status_mask |= BD_SC_PR | BD_SC_FR;
|
|
if (termios->c_iflag & IGNBRK) {
|
|
port->ignore_status_mask |= BD_SC_BR;
|
|
/*
|
|
* If we're ignore parity and break indicators, ignore
|
|
* overruns too. (For real raw support).
|
|
*/
|
|
if (termios->c_iflag & IGNPAR)
|
|
port->ignore_status_mask |= BD_SC_OV;
|
|
}
|
|
/*
|
|
* !!! ignore all characters if CREAD is not set
|
|
*/
|
|
if ((termios->c_cflag & CREAD) == 0)
|
|
port->read_status_mask &= ~BD_SC_EMPTY;
|
|
|
|
uart_port_lock_irqsave(port, &flags);
|
|
|
|
if (IS_SMC(pinfo)) {
|
|
unsigned int bits = tty_get_frame_size(termios->c_cflag);
|
|
|
|
/*
|
|
* MRBLR can be changed while an SMC/SCC is operating only
|
|
* if it is done in a single bus cycle with one 16-bit move
|
|
* (not two 8-bit bus cycles back-to-back). This occurs when
|
|
* the cp shifts control to the next RxBD, so the change does
|
|
* not take effect immediately. To guarantee the exact RxBD
|
|
* on which the change occurs, change MRBLR only while the
|
|
* SMC/SCC receiver is disabled.
|
|
*/
|
|
out_be16(&pinfo->smcup->smc_mrblr, pinfo->rx_fifosize);
|
|
out_be16(&pinfo->smcup->smc_maxidl, maxidl);
|
|
|
|
/* Set the mode register. We want to keep a copy of the
|
|
* enables, because we want to put them back if they were
|
|
* present.
|
|
*/
|
|
prev_mode = in_be16(&smcp->smc_smcmr) & (SMCMR_REN | SMCMR_TEN);
|
|
/* Output in *one* operation, so we don't interrupt RX/TX if they
|
|
* were already enabled.
|
|
* Character length programmed into the register is frame bits minus 1.
|
|
*/
|
|
out_be16(&smcp->smc_smcmr, smcr_mk_clen(bits - 1) | cval |
|
|
SMCMR_SM_UART | prev_mode);
|
|
} else {
|
|
unsigned int bits = tty_get_char_size(termios->c_cflag);
|
|
|
|
out_be16(&pinfo->sccup->scc_genscc.scc_mrblr, pinfo->rx_fifosize);
|
|
out_be16(&pinfo->sccup->scc_maxidl, maxidl);
|
|
out_be16(&sccp->scc_psmr, (UART_LCR_WLEN(bits) << 12) | scval);
|
|
}
|
|
|
|
if (pinfo->clk)
|
|
clk_set_rate(pinfo->clk, baud);
|
|
else
|
|
cpm_setbrg(pinfo->brg - 1, baud);
|
|
uart_port_unlock_irqrestore(port, flags);
|
|
}
|
|
|
|
static const char *cpm_uart_type(struct uart_port *port)
|
|
{
|
|
pr_debug("CPM uart[%d]:uart_type\n", port->line);
|
|
|
|
return port->type == PORT_CPM ? "CPM UART" : NULL;
|
|
}
|
|
|
|
/*
|
|
* verify the new serial_struct (for TIOCSSERIAL).
|
|
*/
|
|
static int cpm_uart_verify_port(struct uart_port *port,
|
|
struct serial_struct *ser)
|
|
{
|
|
int ret = 0;
|
|
|
|
pr_debug("CPM uart[%d]:verify_port\n", port->line);
|
|
|
|
if (ser->type != PORT_UNKNOWN && ser->type != PORT_CPM)
|
|
ret = -EINVAL;
|
|
if (ser->irq < 0 || ser->irq >= nr_irqs)
|
|
ret = -EINVAL;
|
|
if (ser->baud_base < 9600)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Transmit characters, refill buffer descriptor, if possible
|
|
*/
|
|
static int cpm_uart_tx_pump(struct uart_port *port)
|
|
{
|
|
cbd_t __iomem *bdp;
|
|
u8 *p;
|
|
int count;
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
/* Handle xon/xoff */
|
|
if (port->x_char) {
|
|
/* Pick next descriptor and fill from buffer */
|
|
bdp = pinfo->tx_cur;
|
|
|
|
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
|
|
|
*p++ = port->x_char;
|
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
|
/* Get next BD. */
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
|
bdp = pinfo->tx_bd_base;
|
|
else
|
|
bdp++;
|
|
pinfo->tx_cur = bdp;
|
|
|
|
port->icount.tx++;
|
|
port->x_char = 0;
|
|
return 1;
|
|
}
|
|
|
|
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
|
|
cpm_uart_stop_tx(port);
|
|
return 0;
|
|
}
|
|
|
|
/* Pick next descriptor and fill from buffer */
|
|
bdp = pinfo->tx_cur;
|
|
|
|
while (!(in_be16(&bdp->cbd_sc) & BD_SC_READY) && !uart_circ_empty(xmit)) {
|
|
count = 0;
|
|
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
|
while (count < pinfo->tx_fifosize) {
|
|
*p++ = xmit->buf[xmit->tail];
|
|
uart_xmit_advance(port, 1);
|
|
count++;
|
|
if (uart_circ_empty(xmit))
|
|
break;
|
|
}
|
|
out_be16(&bdp->cbd_datlen, count);
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
|
/* Get next BD. */
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
|
bdp = pinfo->tx_bd_base;
|
|
else
|
|
bdp++;
|
|
}
|
|
pinfo->tx_cur = bdp;
|
|
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
uart_write_wakeup(port);
|
|
|
|
if (uart_circ_empty(xmit)) {
|
|
cpm_uart_stop_tx(port);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* init buffer descriptors
|
|
*/
|
|
static void cpm_uart_initbd(struct uart_cpm_port *pinfo)
|
|
{
|
|
int i;
|
|
u8 *mem_addr;
|
|
cbd_t __iomem *bdp;
|
|
|
|
pr_debug("CPM uart[%d]:initbd\n", pinfo->port.line);
|
|
|
|
/* Set the physical address of the host memory
|
|
* buffers in the buffer descriptors, and the
|
|
* virtual address for us to work with.
|
|
*/
|
|
mem_addr = pinfo->mem_addr;
|
|
bdp = pinfo->rx_cur = pinfo->rx_bd_base;
|
|
for (i = 0; i < (pinfo->rx_nrfifos - 1); i++, bdp++) {
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
out_be16(&bdp->cbd_sc, BD_SC_EMPTY | BD_SC_INTRPT);
|
|
mem_addr += pinfo->rx_fifosize;
|
|
}
|
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
out_be16(&bdp->cbd_sc, BD_SC_WRAP | BD_SC_EMPTY | BD_SC_INTRPT);
|
|
|
|
/* Set the physical address of the host memory
|
|
* buffers in the buffer descriptors, and the
|
|
* virtual address for us to work with.
|
|
*/
|
|
mem_addr = pinfo->mem_addr + L1_CACHE_ALIGN(pinfo->rx_nrfifos * pinfo->rx_fifosize);
|
|
bdp = pinfo->tx_cur = pinfo->tx_bd_base;
|
|
for (i = 0; i < (pinfo->tx_nrfifos - 1); i++, bdp++) {
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
out_be16(&bdp->cbd_sc, BD_SC_INTRPT);
|
|
mem_addr += pinfo->tx_fifosize;
|
|
}
|
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
out_be16(&bdp->cbd_sc, BD_SC_WRAP | BD_SC_INTRPT);
|
|
}
|
|
|
|
static void cpm_uart_init_scc(struct uart_cpm_port *pinfo)
|
|
{
|
|
scc_t __iomem *scp;
|
|
scc_uart_t __iomem *sup;
|
|
|
|
pr_debug("CPM uart[%d]:init_scc\n", pinfo->port.line);
|
|
|
|
scp = pinfo->sccp;
|
|
sup = pinfo->sccup;
|
|
|
|
/* Store address */
|
|
out_be16(&pinfo->sccup->scc_genscc.scc_rbase,
|
|
(u8 __iomem *)pinfo->rx_bd_base - DPRAM_BASE);
|
|
out_be16(&pinfo->sccup->scc_genscc.scc_tbase,
|
|
(u8 __iomem *)pinfo->tx_bd_base - DPRAM_BASE);
|
|
|
|
/* Set up the uart parameters in the
|
|
* parameter ram.
|
|
*/
|
|
|
|
out_8(&sup->scc_genscc.scc_rfcr, CPMFCR_GBL | CPMFCR_EB);
|
|
out_8(&sup->scc_genscc.scc_tfcr, CPMFCR_GBL | CPMFCR_EB);
|
|
|
|
out_be16(&sup->scc_genscc.scc_mrblr, pinfo->rx_fifosize);
|
|
out_be16(&sup->scc_maxidl, 0x10);
|
|
out_be16(&sup->scc_brkcr, 1);
|
|
out_be16(&sup->scc_parec, 0);
|
|
out_be16(&sup->scc_frmec, 0);
|
|
out_be16(&sup->scc_nosec, 0);
|
|
out_be16(&sup->scc_brkec, 0);
|
|
out_be16(&sup->scc_uaddr1, 0);
|
|
out_be16(&sup->scc_uaddr2, 0);
|
|
out_be16(&sup->scc_toseq, 0);
|
|
out_be16(&sup->scc_char1, 0x8000);
|
|
out_be16(&sup->scc_char2, 0x8000);
|
|
out_be16(&sup->scc_char3, 0x8000);
|
|
out_be16(&sup->scc_char4, 0x8000);
|
|
out_be16(&sup->scc_char5, 0x8000);
|
|
out_be16(&sup->scc_char6, 0x8000);
|
|
out_be16(&sup->scc_char7, 0x8000);
|
|
out_be16(&sup->scc_char8, 0x8000);
|
|
out_be16(&sup->scc_rccm, 0xc0ff);
|
|
|
|
/* Send the CPM an initialize command.
|
|
*/
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
|
|
|
/* Set UART mode, 8 bit, no parity, one stop.
|
|
* Enable receive and transmit.
|
|
*/
|
|
out_be32(&scp->scc_gsmrh, 0);
|
|
out_be32(&scp->scc_gsmrl,
|
|
SCC_GSMRL_MODE_UART | SCC_GSMRL_TDCR_16 | SCC_GSMRL_RDCR_16);
|
|
|
|
/* Enable rx interrupts and clear all pending events. */
|
|
out_be16(&scp->scc_sccm, 0);
|
|
out_be16(&scp->scc_scce, 0xffff);
|
|
out_be16(&scp->scc_dsr, 0x7e7e);
|
|
out_be16(&scp->scc_psmr, 0x3000);
|
|
|
|
setbits32(&scp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
|
}
|
|
|
|
static void cpm_uart_init_smc(struct uart_cpm_port *pinfo)
|
|
{
|
|
smc_t __iomem *sp;
|
|
smc_uart_t __iomem *up;
|
|
|
|
pr_debug("CPM uart[%d]:init_smc\n", pinfo->port.line);
|
|
|
|
sp = pinfo->smcp;
|
|
up = pinfo->smcup;
|
|
|
|
/* Store address */
|
|
out_be16(&pinfo->smcup->smc_rbase,
|
|
(u8 __iomem *)pinfo->rx_bd_base - DPRAM_BASE);
|
|
out_be16(&pinfo->smcup->smc_tbase,
|
|
(u8 __iomem *)pinfo->tx_bd_base - DPRAM_BASE);
|
|
|
|
/*
|
|
* In case SMC is being relocated...
|
|
*/
|
|
out_be16(&up->smc_rbptr, in_be16(&pinfo->smcup->smc_rbase));
|
|
out_be16(&up->smc_tbptr, in_be16(&pinfo->smcup->smc_tbase));
|
|
out_be32(&up->smc_rstate, 0);
|
|
out_be32(&up->smc_tstate, 0);
|
|
out_be16(&up->smc_brkcr, 1); /* number of break chars */
|
|
out_be16(&up->smc_brkec, 0);
|
|
|
|
/* Set up the uart parameters in the
|
|
* parameter ram.
|
|
*/
|
|
out_8(&up->smc_rfcr, CPMFCR_GBL | CPMFCR_EB);
|
|
out_8(&up->smc_tfcr, CPMFCR_GBL | CPMFCR_EB);
|
|
|
|
/* Using idle character time requires some additional tuning. */
|
|
out_be16(&up->smc_mrblr, pinfo->rx_fifosize);
|
|
out_be16(&up->smc_maxidl, 0x10);
|
|
out_be16(&up->smc_brklen, 0);
|
|
out_be16(&up->smc_brkec, 0);
|
|
out_be16(&up->smc_brkcr, 1);
|
|
|
|
/* Set UART mode, 8 bit, no parity, one stop.
|
|
* Enable receive and transmit.
|
|
*/
|
|
out_be16(&sp->smc_smcmr, smcr_mk_clen(9) | SMCMR_SM_UART);
|
|
|
|
/* Enable only rx interrupts clear all pending events. */
|
|
out_8(&sp->smc_smcm, 0);
|
|
out_8(&sp->smc_smce, 0xff);
|
|
|
|
setbits16(&sp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
|
}
|
|
|
|
/*
|
|
* Allocate DP-Ram and memory buffers. We need to allocate a transmit and
|
|
* receive buffer descriptors from dual port ram, and a character
|
|
* buffer area from host mem. If we are allocating for the console we need
|
|
* to do it from bootmem
|
|
*/
|
|
static int cpm_uart_allocbuf(struct uart_cpm_port *pinfo, unsigned int is_con)
|
|
{
|
|
int dpmemsz, memsz;
|
|
u8 __iomem *dp_mem;
|
|
unsigned long dp_offset;
|
|
u8 *mem_addr;
|
|
dma_addr_t dma_addr = 0;
|
|
|
|
pr_debug("CPM uart[%d]:allocbuf\n", pinfo->port.line);
|
|
|
|
dpmemsz = sizeof(cbd_t) * (pinfo->rx_nrfifos + pinfo->tx_nrfifos);
|
|
dp_offset = cpm_muram_alloc(dpmemsz, 8);
|
|
if (IS_ERR_VALUE(dp_offset)) {
|
|
pr_err("%s: could not allocate buffer descriptors\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dp_mem = cpm_muram_addr(dp_offset);
|
|
|
|
memsz = L1_CACHE_ALIGN(pinfo->rx_nrfifos * pinfo->rx_fifosize) +
|
|
L1_CACHE_ALIGN(pinfo->tx_nrfifos * pinfo->tx_fifosize);
|
|
if (IS_ENABLED(CONFIG_CPM1) && is_con) {
|
|
/* was hostalloc but changed cause it blows away the */
|
|
/* large tlb mapping when pinning the kernel area */
|
|
mem_addr = (u8 __force *)cpm_muram_addr(cpm_muram_alloc(memsz, 8));
|
|
dma_addr = cpm_muram_dma((void __iomem *)mem_addr);
|
|
} else if (is_con) {
|
|
mem_addr = kzalloc(memsz, GFP_NOWAIT);
|
|
dma_addr = virt_to_bus(mem_addr);
|
|
} else {
|
|
mem_addr = dma_alloc_coherent(pinfo->port.dev, memsz, &dma_addr,
|
|
GFP_KERNEL);
|
|
}
|
|
|
|
if (!mem_addr) {
|
|
cpm_muram_free(dp_offset);
|
|
pr_err("%s: could not allocate coherent memory\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pinfo->dp_addr = dp_offset;
|
|
pinfo->mem_addr = mem_addr;
|
|
pinfo->dma_addr = dma_addr;
|
|
pinfo->mem_size = memsz;
|
|
|
|
pinfo->rx_buf = mem_addr;
|
|
pinfo->tx_buf = pinfo->rx_buf + L1_CACHE_ALIGN(pinfo->rx_nrfifos
|
|
* pinfo->rx_fifosize);
|
|
|
|
pinfo->rx_bd_base = (cbd_t __iomem *)dp_mem;
|
|
pinfo->tx_bd_base = pinfo->rx_bd_base + pinfo->rx_nrfifos;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cpm_uart_freebuf(struct uart_cpm_port *pinfo)
|
|
{
|
|
dma_free_coherent(pinfo->port.dev, L1_CACHE_ALIGN(pinfo->rx_nrfifos *
|
|
pinfo->rx_fifosize) +
|
|
L1_CACHE_ALIGN(pinfo->tx_nrfifos *
|
|
pinfo->tx_fifosize), (void __force *)pinfo->mem_addr,
|
|
pinfo->dma_addr);
|
|
|
|
cpm_muram_free(pinfo->dp_addr);
|
|
}
|
|
|
|
/*
|
|
* Initialize port. This is called from early_console stuff
|
|
* so we have to be careful here !
|
|
*/
|
|
static int cpm_uart_request_port(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
int ret;
|
|
|
|
pr_debug("CPM uart[%d]:request port\n", port->line);
|
|
|
|
if (pinfo->flags & FLAG_CONSOLE)
|
|
return 0;
|
|
|
|
if (IS_SMC(pinfo)) {
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
|
} else {
|
|
clrbits16(&pinfo->sccp->scc_sccm, UART_SCCM_TX | UART_SCCM_RX);
|
|
clrbits32(&pinfo->sccp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
|
}
|
|
|
|
ret = cpm_uart_allocbuf(pinfo, 0);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
cpm_uart_initbd(pinfo);
|
|
if (IS_SMC(pinfo))
|
|
cpm_uart_init_smc(pinfo);
|
|
else
|
|
cpm_uart_init_scc(pinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cpm_uart_release_port(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
|
|
if (!(pinfo->flags & FLAG_CONSOLE))
|
|
cpm_uart_freebuf(pinfo);
|
|
}
|
|
|
|
/*
|
|
* Configure/autoconfigure the port.
|
|
*/
|
|
static void cpm_uart_config_port(struct uart_port *port, int flags)
|
|
{
|
|
pr_debug("CPM uart[%d]:config_port\n", port->line);
|
|
|
|
if (flags & UART_CONFIG_TYPE) {
|
|
port->type = PORT_CPM;
|
|
cpm_uart_request_port(port);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_CONSOLE_POLL) || defined(CONFIG_SERIAL_CPM_CONSOLE)
|
|
/*
|
|
* Write a string to the serial port
|
|
* Note that this is called with interrupts already disabled
|
|
*/
|
|
static void cpm_uart_early_write(struct uart_cpm_port *pinfo,
|
|
const char *string, u_int count, bool handle_linefeed)
|
|
{
|
|
unsigned int i;
|
|
cbd_t __iomem *bdp, *bdbase;
|
|
unsigned char *cpm_outp_addr;
|
|
|
|
/* Get the address of the host memory buffer.
|
|
*/
|
|
bdp = pinfo->tx_cur;
|
|
bdbase = pinfo->tx_bd_base;
|
|
|
|
/*
|
|
* Now, do each character. This is not as bad as it looks
|
|
* since this is a holding FIFO and not a transmitting FIFO.
|
|
* We could add the complexity of filling the entire transmit
|
|
* buffer, but we would just wait longer between accesses......
|
|
*/
|
|
for (i = 0; i < count; i++, string++) {
|
|
/* Wait for transmitter fifo to empty.
|
|
* Ready indicates output is ready, and xmt is doing
|
|
* that, not that it is ready for us to send.
|
|
*/
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
|
;
|
|
|
|
/* Send the character out.
|
|
* If the buffer address is in the CPM DPRAM, don't
|
|
* convert it.
|
|
*/
|
|
cpm_outp_addr = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr),
|
|
pinfo);
|
|
*cpm_outp_addr = *string;
|
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
|
bdp = bdbase;
|
|
else
|
|
bdp++;
|
|
|
|
/* if a LF, also do CR... */
|
|
if (handle_linefeed && *string == 10) {
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
|
;
|
|
|
|
cpm_outp_addr = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr),
|
|
pinfo);
|
|
*cpm_outp_addr = 13;
|
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
|
bdp = bdbase;
|
|
else
|
|
bdp++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Finally, Wait for transmitter & holding register to empty
|
|
* and restore the IER
|
|
*/
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
|
;
|
|
|
|
pinfo->tx_cur = bdp;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
/* Serial polling routines for writing and reading from the uart while
|
|
* in an interrupt or debug context.
|
|
*/
|
|
|
|
#define GDB_BUF_SIZE 512 /* power of 2, please */
|
|
|
|
static char poll_buf[GDB_BUF_SIZE];
|
|
static char *pollp;
|
|
static int poll_chars;
|
|
|
|
static int poll_wait_key(char *obuf, struct uart_cpm_port *pinfo)
|
|
{
|
|
u_char c, *cp;
|
|
volatile cbd_t *bdp;
|
|
int i;
|
|
|
|
/* Get the address of the host memory buffer.
|
|
*/
|
|
bdp = pinfo->rx_cur;
|
|
if (bdp->cbd_sc & BD_SC_EMPTY)
|
|
return NO_POLL_CHAR;
|
|
|
|
/* If the buffer address is in the CPM DPRAM, don't
|
|
* convert it.
|
|
*/
|
|
cp = cpm2cpu_addr(bdp->cbd_bufaddr, pinfo);
|
|
|
|
if (obuf) {
|
|
i = c = bdp->cbd_datlen;
|
|
while (i-- > 0)
|
|
*obuf++ = *cp++;
|
|
} else
|
|
c = *cp;
|
|
bdp->cbd_sc &= ~(BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_OV | BD_SC_ID);
|
|
bdp->cbd_sc |= BD_SC_EMPTY;
|
|
|
|
if (bdp->cbd_sc & BD_SC_WRAP)
|
|
bdp = pinfo->rx_bd_base;
|
|
else
|
|
bdp++;
|
|
pinfo->rx_cur = (cbd_t *)bdp;
|
|
|
|
return (int)c;
|
|
}
|
|
|
|
static int cpm_get_poll_char(struct uart_port *port)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
|
|
if (!serial_polled) {
|
|
serial_polled = 1;
|
|
poll_chars = 0;
|
|
}
|
|
if (poll_chars <= 0) {
|
|
int ret = poll_wait_key(poll_buf, pinfo);
|
|
|
|
if (ret == NO_POLL_CHAR)
|
|
return ret;
|
|
poll_chars = ret;
|
|
pollp = poll_buf;
|
|
}
|
|
poll_chars--;
|
|
return *pollp++;
|
|
}
|
|
|
|
static void cpm_put_poll_char(struct uart_port *port,
|
|
unsigned char c)
|
|
{
|
|
struct uart_cpm_port *pinfo =
|
|
container_of(port, struct uart_cpm_port, port);
|
|
static char ch[2];
|
|
|
|
ch[0] = (char)c;
|
|
cpm_uart_early_write(pinfo, ch, 1, false);
|
|
}
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_CONSOLE
|
|
static struct uart_port *udbg_port;
|
|
|
|
static void udbg_cpm_putc(char c)
|
|
{
|
|
if (c == '\n')
|
|
cpm_put_poll_char(udbg_port, '\r');
|
|
cpm_put_poll_char(udbg_port, c);
|
|
}
|
|
|
|
static int udbg_cpm_getc_poll(void)
|
|
{
|
|
int c = cpm_get_poll_char(udbg_port);
|
|
|
|
return c == NO_POLL_CHAR ? -1 : c;
|
|
}
|
|
|
|
static int udbg_cpm_getc(void)
|
|
{
|
|
int c;
|
|
|
|
while ((c = udbg_cpm_getc_poll()) == -1)
|
|
cpu_relax();
|
|
return c;
|
|
}
|
|
#endif /* CONFIG_SERIAL_CPM_CONSOLE */
|
|
|
|
#endif /* CONFIG_CONSOLE_POLL */
|
|
|
|
static const struct uart_ops cpm_uart_pops = {
|
|
.tx_empty = cpm_uart_tx_empty,
|
|
.set_mctrl = cpm_uart_set_mctrl,
|
|
.get_mctrl = cpm_uart_get_mctrl,
|
|
.stop_tx = cpm_uart_stop_tx,
|
|
.start_tx = cpm_uart_start_tx,
|
|
.stop_rx = cpm_uart_stop_rx,
|
|
.break_ctl = cpm_uart_break_ctl,
|
|
.startup = cpm_uart_startup,
|
|
.shutdown = cpm_uart_shutdown,
|
|
.set_termios = cpm_uart_set_termios,
|
|
.type = cpm_uart_type,
|
|
.release_port = cpm_uart_release_port,
|
|
.request_port = cpm_uart_request_port,
|
|
.config_port = cpm_uart_config_port,
|
|
.verify_port = cpm_uart_verify_port,
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
.poll_get_char = cpm_get_poll_char,
|
|
.poll_put_char = cpm_put_poll_char,
|
|
#endif
|
|
};
|
|
|
|
static struct uart_cpm_port cpm_uart_ports[UART_NR];
|
|
|
|
static void __iomem *cpm_uart_map_pram(struct uart_cpm_port *port,
|
|
struct device_node *np)
|
|
{
|
|
void __iomem *pram;
|
|
unsigned long offset;
|
|
struct resource res;
|
|
resource_size_t len;
|
|
|
|
/* Don't remap parameter RAM if it has already been initialized
|
|
* during console setup.
|
|
*/
|
|
if (IS_SMC(port) && port->smcup)
|
|
return port->smcup;
|
|
else if (!IS_SMC(port) && port->sccup)
|
|
return port->sccup;
|
|
|
|
if (of_address_to_resource(np, 1, &res))
|
|
return NULL;
|
|
|
|
len = resource_size(&res);
|
|
pram = ioremap(res.start, len);
|
|
if (!pram)
|
|
return NULL;
|
|
|
|
if (!IS_ENABLED(CONFIG_CPM2) || !IS_SMC(port))
|
|
return pram;
|
|
|
|
if (len != 2) {
|
|
pr_warn("cpm_uart[%d]: device tree references "
|
|
"SMC pram, using boot loader/wrapper pram mapping. "
|
|
"Please fix your device tree to reference the pram "
|
|
"base register instead.\n",
|
|
port->port.line);
|
|
return pram;
|
|
}
|
|
|
|
offset = cpm_muram_alloc(64, 64);
|
|
out_be16(pram, offset);
|
|
iounmap(pram);
|
|
return cpm_muram_addr(offset);
|
|
}
|
|
|
|
static void cpm_uart_unmap_pram(struct uart_cpm_port *port, void __iomem *pram)
|
|
{
|
|
if (!IS_ENABLED(CONFIG_CPM2) || !IS_SMC(port))
|
|
iounmap(pram);
|
|
}
|
|
|
|
static int cpm_uart_init_port(struct device_node *np,
|
|
struct uart_cpm_port *pinfo)
|
|
{
|
|
const u32 *data;
|
|
void __iomem *mem, *pram;
|
|
struct device *dev = pinfo->port.dev;
|
|
int len;
|
|
int ret;
|
|
int i;
|
|
|
|
data = of_get_property(np, "clock", NULL);
|
|
if (data) {
|
|
struct clk *clk = clk_get(NULL, (const char*)data);
|
|
if (!IS_ERR(clk))
|
|
pinfo->clk = clk;
|
|
}
|
|
if (!pinfo->clk) {
|
|
data = of_get_property(np, "fsl,cpm-brg", &len);
|
|
if (!data || len != 4) {
|
|
printk(KERN_ERR "CPM UART %pOFn has no/invalid "
|
|
"fsl,cpm-brg property.\n", np);
|
|
return -EINVAL;
|
|
}
|
|
pinfo->brg = *data;
|
|
}
|
|
|
|
data = of_get_property(np, "fsl,cpm-command", &len);
|
|
if (!data || len != 4) {
|
|
printk(KERN_ERR "CPM UART %pOFn has no/invalid "
|
|
"fsl,cpm-command property.\n", np);
|
|
return -EINVAL;
|
|
}
|
|
pinfo->command = *data;
|
|
|
|
mem = of_iomap(np, 0);
|
|
if (!mem)
|
|
return -ENOMEM;
|
|
|
|
if (of_device_is_compatible(np, "fsl,cpm1-scc-uart") ||
|
|
of_device_is_compatible(np, "fsl,cpm2-scc-uart")) {
|
|
pinfo->sccp = mem;
|
|
pinfo->sccup = pram = cpm_uart_map_pram(pinfo, np);
|
|
} else if (of_device_is_compatible(np, "fsl,cpm1-smc-uart") ||
|
|
of_device_is_compatible(np, "fsl,cpm2-smc-uart")) {
|
|
pinfo->flags |= FLAG_SMC;
|
|
pinfo->smcp = mem;
|
|
pinfo->smcup = pram = cpm_uart_map_pram(pinfo, np);
|
|
} else {
|
|
ret = -ENODEV;
|
|
goto out_mem;
|
|
}
|
|
|
|
if (!pram) {
|
|
ret = -ENOMEM;
|
|
goto out_mem;
|
|
}
|
|
|
|
pinfo->tx_nrfifos = TX_NUM_FIFO;
|
|
pinfo->tx_fifosize = TX_BUF_SIZE;
|
|
pinfo->rx_nrfifos = RX_NUM_FIFO;
|
|
pinfo->rx_fifosize = RX_BUF_SIZE;
|
|
|
|
pinfo->port.uartclk = ppc_proc_freq;
|
|
pinfo->port.mapbase = (unsigned long)mem;
|
|
pinfo->port.type = PORT_CPM;
|
|
pinfo->port.ops = &cpm_uart_pops;
|
|
pinfo->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_CPM_CONSOLE);
|
|
pinfo->port.iotype = UPIO_MEM;
|
|
pinfo->port.fifosize = pinfo->tx_nrfifos * pinfo->tx_fifosize;
|
|
spin_lock_init(&pinfo->port.lock);
|
|
|
|
for (i = 0; i < NUM_GPIOS; i++) {
|
|
struct gpio_desc *gpiod;
|
|
|
|
pinfo->gpios[i] = NULL;
|
|
|
|
gpiod = devm_gpiod_get_index_optional(dev, NULL, i, GPIOD_ASIS);
|
|
|
|
if (IS_ERR(gpiod)) {
|
|
ret = PTR_ERR(gpiod);
|
|
goto out_pram;
|
|
}
|
|
|
|
if (gpiod) {
|
|
if (i == GPIO_RTS || i == GPIO_DTR)
|
|
ret = gpiod_direction_output(gpiod, 0);
|
|
else
|
|
ret = gpiod_direction_input(gpiod);
|
|
if (ret) {
|
|
pr_err("can't set direction for gpio #%d: %d\n",
|
|
i, ret);
|
|
continue;
|
|
}
|
|
pinfo->gpios[i] = gpiod;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_PPC_EARLY_DEBUG_CPM
|
|
#if defined(CONFIG_CONSOLE_POLL) && defined(CONFIG_SERIAL_CPM_CONSOLE)
|
|
if (!udbg_port)
|
|
#endif
|
|
udbg_putc = NULL;
|
|
#endif
|
|
|
|
return cpm_uart_request_port(&pinfo->port);
|
|
|
|
out_pram:
|
|
cpm_uart_unmap_pram(pinfo, pram);
|
|
out_mem:
|
|
iounmap(mem);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_CONSOLE
|
|
/*
|
|
* Print a string to the serial port trying not to disturb
|
|
* any possible real use of the port...
|
|
*
|
|
* Note that this is called with interrupts already disabled
|
|
*/
|
|
static void cpm_uart_console_write(struct console *co, const char *s,
|
|
u_int count)
|
|
{
|
|
struct uart_cpm_port *pinfo = &cpm_uart_ports[co->index];
|
|
unsigned long flags;
|
|
|
|
if (unlikely(oops_in_progress)) {
|
|
local_irq_save(flags);
|
|
cpm_uart_early_write(pinfo, s, count, true);
|
|
local_irq_restore(flags);
|
|
} else {
|
|
uart_port_lock_irqsave(&pinfo->port, &flags);
|
|
cpm_uart_early_write(pinfo, s, count, true);
|
|
uart_port_unlock_irqrestore(&pinfo->port, flags);
|
|
}
|
|
}
|
|
|
|
|
|
static int __init cpm_uart_console_setup(struct console *co, char *options)
|
|
{
|
|
int baud = 38400;
|
|
int bits = 8;
|
|
int parity = 'n';
|
|
int flow = 'n';
|
|
int ret;
|
|
struct uart_cpm_port *pinfo;
|
|
struct uart_port *port;
|
|
|
|
struct device_node *np;
|
|
int i = 0;
|
|
|
|
if (co->index >= UART_NR) {
|
|
printk(KERN_ERR "cpm_uart: console index %d too high\n",
|
|
co->index);
|
|
return -ENODEV;
|
|
}
|
|
|
|
for_each_node_by_type(np, "serial") {
|
|
if (!of_device_is_compatible(np, "fsl,cpm1-smc-uart") &&
|
|
!of_device_is_compatible(np, "fsl,cpm1-scc-uart") &&
|
|
!of_device_is_compatible(np, "fsl,cpm2-smc-uart") &&
|
|
!of_device_is_compatible(np, "fsl,cpm2-scc-uart"))
|
|
continue;
|
|
|
|
if (i++ == co->index)
|
|
break;
|
|
}
|
|
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
pinfo = &cpm_uart_ports[co->index];
|
|
|
|
pinfo->flags |= FLAG_CONSOLE;
|
|
port = &pinfo->port;
|
|
|
|
ret = cpm_uart_init_port(np, pinfo);
|
|
of_node_put(np);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (options) {
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
} else {
|
|
baud = get_baudrate();
|
|
if (baud == -1)
|
|
baud = 9600;
|
|
}
|
|
|
|
if (IS_SMC(pinfo)) {
|
|
out_be16(&pinfo->smcup->smc_brkcr, 0);
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
|
} else {
|
|
out_be16(&pinfo->sccup->scc_brkcr, 0);
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_GRA_STOP_TX);
|
|
clrbits16(&pinfo->sccp->scc_sccm, UART_SCCM_TX | UART_SCCM_RX);
|
|
clrbits32(&pinfo->sccp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
|
}
|
|
|
|
ret = cpm_uart_allocbuf(pinfo, 1);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
cpm_uart_initbd(pinfo);
|
|
|
|
if (IS_SMC(pinfo))
|
|
cpm_uart_init_smc(pinfo);
|
|
else
|
|
cpm_uart_init_scc(pinfo);
|
|
|
|
uart_set_options(port, co, baud, parity, bits, flow);
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_RESTART_TX);
|
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
if (!udbg_port) {
|
|
udbg_port = &pinfo->port;
|
|
udbg_putc = udbg_cpm_putc;
|
|
udbg_getc = udbg_cpm_getc;
|
|
udbg_getc_poll = udbg_cpm_getc_poll;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct uart_driver cpm_reg;
|
|
static struct console cpm_scc_uart_console = {
|
|
.name = "ttyCPM",
|
|
.write = cpm_uart_console_write,
|
|
.device = uart_console_device,
|
|
.setup = cpm_uart_console_setup,
|
|
.flags = CON_PRINTBUFFER,
|
|
.index = -1,
|
|
.data = &cpm_reg,
|
|
};
|
|
|
|
static int __init cpm_uart_console_init(void)
|
|
{
|
|
cpm_muram_init();
|
|
register_console(&cpm_scc_uart_console);
|
|
return 0;
|
|
}
|
|
|
|
console_initcall(cpm_uart_console_init);
|
|
|
|
#define CPM_UART_CONSOLE &cpm_scc_uart_console
|
|
#else
|
|
#define CPM_UART_CONSOLE NULL
|
|
#endif
|
|
|
|
static struct uart_driver cpm_reg = {
|
|
.owner = THIS_MODULE,
|
|
.driver_name = "ttyCPM",
|
|
.dev_name = "ttyCPM",
|
|
.major = SERIAL_CPM_MAJOR,
|
|
.minor = SERIAL_CPM_MINOR,
|
|
.cons = CPM_UART_CONSOLE,
|
|
.nr = UART_NR,
|
|
};
|
|
|
|
static int probe_index;
|
|
|
|
static int cpm_uart_probe(struct platform_device *ofdev)
|
|
{
|
|
int index = probe_index++;
|
|
struct uart_cpm_port *pinfo = &cpm_uart_ports[index];
|
|
int ret;
|
|
|
|
pinfo->port.line = index;
|
|
|
|
if (index >= UART_NR)
|
|
return -ENODEV;
|
|
|
|
platform_set_drvdata(ofdev, pinfo);
|
|
|
|
/* initialize the device pointer for the port */
|
|
pinfo->port.dev = &ofdev->dev;
|
|
|
|
pinfo->port.irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
|
|
if (!pinfo->port.irq)
|
|
return -EINVAL;
|
|
|
|
ret = cpm_uart_init_port(ofdev->dev.of_node, pinfo);
|
|
if (!ret)
|
|
return uart_add_one_port(&cpm_reg, &pinfo->port);
|
|
|
|
irq_dispose_mapping(pinfo->port.irq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cpm_uart_remove(struct platform_device *ofdev)
|
|
{
|
|
struct uart_cpm_port *pinfo = platform_get_drvdata(ofdev);
|
|
|
|
uart_remove_one_port(&cpm_reg, &pinfo->port);
|
|
}
|
|
|
|
static const struct of_device_id cpm_uart_match[] = {
|
|
{
|
|
.compatible = "fsl,cpm1-smc-uart",
|
|
},
|
|
{
|
|
.compatible = "fsl,cpm1-scc-uart",
|
|
},
|
|
{
|
|
.compatible = "fsl,cpm2-smc-uart",
|
|
},
|
|
{
|
|
.compatible = "fsl,cpm2-scc-uart",
|
|
},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, cpm_uart_match);
|
|
|
|
static struct platform_driver cpm_uart_driver = {
|
|
.driver = {
|
|
.name = "cpm_uart",
|
|
.of_match_table = cpm_uart_match,
|
|
},
|
|
.probe = cpm_uart_probe,
|
|
.remove_new = cpm_uart_remove,
|
|
};
|
|
|
|
static int __init cpm_uart_init(void)
|
|
{
|
|
int ret = uart_register_driver(&cpm_reg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = platform_driver_register(&cpm_uart_driver);
|
|
if (ret)
|
|
uart_unregister_driver(&cpm_reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit cpm_uart_exit(void)
|
|
{
|
|
platform_driver_unregister(&cpm_uart_driver);
|
|
uart_unregister_driver(&cpm_reg);
|
|
}
|
|
|
|
module_init(cpm_uart_init);
|
|
module_exit(cpm_uart_exit);
|
|
|
|
MODULE_AUTHOR("Kumar Gala/Antoniou Pantelis");
|
|
MODULE_DESCRIPTION("CPM SCC/SMC port driver $Revision: 0.01 $");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_CHARDEV(SERIAL_CPM_MAJOR, SERIAL_CPM_MINOR);
|