1
0
Fork 0
alistair23-linux/drivers/tty/serial/cpm_uart/cpm_uart_core.c

1485 lines
35 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-06 10:11:51 -07:00
// 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>
mm: remove include/linux/bootmem.h Move remaining definitions and declarations from include/linux/bootmem.h into include/linux/memblock.h and remove the redundant header. The includes were replaced with the semantic patch below and then semi-automated removal of duplicated '#include <linux/memblock.h> @@ @@ - #include <linux/bootmem.h> + #include <linux/memblock.h> [sfr@canb.auug.org.au: dma-direct: fix up for the removal of linux/bootmem.h] Link: http://lkml.kernel.org/r/20181002185342.133d1680@canb.auug.org.au [sfr@canb.auug.org.au: powerpc: fix up for removal of linux/bootmem.h] Link: http://lkml.kernel.org/r/20181005161406.73ef8727@canb.auug.org.au [sfr@canb.auug.org.au: x86/kaslr, ACPI/NUMA: fix for linux/bootmem.h removal] Link: http://lkml.kernel.org/r/20181008190341.5e396491@canb.auug.org.au Link: http://lkml.kernel.org/r/1536927045-23536-30-git-send-email-rppt@linux.vnet.ibm.com Signed-off-by: Mike Rapoport <rppt@linux.vnet.ibm.com> Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au> Acked-by: Michal Hocko <mhocko@suse.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Chris Zankel <chris@zankel.net> Cc: "David S. Miller" <davem@davemloft.net> Cc: Geert Uytterhoeven <geert@linux-m68k.org> Cc: Greentime Hu <green.hu@gmail.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Guan Xuetao <gxt@pku.edu.cn> Cc: Ingo Molnar <mingo@redhat.com> Cc: "James E.J. Bottomley" <jejb@parisc-linux.org> Cc: Jonas Bonn <jonas@southpole.se> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Ley Foon Tan <lftan@altera.com> Cc: Mark Salter <msalter@redhat.com> Cc: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: Matt Turner <mattst88@gmail.com> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Michal Simek <monstr@monstr.eu> Cc: Palmer Dabbelt <palmer@sifive.com> Cc: Paul Burton <paul.burton@mips.com> Cc: Richard Kuo <rkuo@codeaurora.org> Cc: Richard Weinberger <richard@nod.at> Cc: Rich Felker <dalias@libc.org> Cc: Russell King <linux@armlinux.org.uk> Cc: Serge Semin <fancer.lancer@gmail.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Tony Luck <tony.luck@intel.com> Cc: Vineet Gupta <vgupta@synopsys.com> Cc: Yoshinori Sato <ysato@users.sourceforge.jp> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-30 16:09:49 -06:00
#include <linux/memblock.h>
#include <linux/dma-mapping.h>
#include <linux/fs_uart_pd.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/delay.h>
#include <asm/fs_pd.h>
#include <asm/udbg.h>
#if defined(CONFIG_SERIAL_CPM_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#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_init_smc(struct uart_cpm_port *pinfo);
static void cpm_uart_init_scc(struct uart_cpm_port *pinfo);
static void cpm_uart_initbd(struct uart_cpm_port *pinfo);
/**************************************************************/
#define HW_BUF_SPD_THRESHOLD 2400
/*
* 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] >= 0)
gpio_set_value(pinfo->gpios[GPIO_RTS], !(mctrl & TIOCM_RTS));
if (pinfo->gpios[GPIO_DTR] >= 0)
gpio_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] >= 0) {
if (gpio_get_value(pinfo->gpios[GPIO_CTS]))
mctrl &= ~TIOCM_CTS;
}
if (pinfo->gpios[GPIO_DSR] >= 0) {
if (gpio_get_value(pinfo->gpios[GPIO_DSR]))
mctrl &= ~TIOCM_DSR;
}
if (pinfo->gpios[GPIO_DCD] >= 0) {
if (gpio_get_value(pinfo->gpios[GPIO_DCD]))
mctrl &= ~TIOCM_CAR;
}
if (pinfo->gpios[GPIO_RI] >= 0) {
if (!gpio_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
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
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
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
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;
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
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;
}
#ifdef SUPPORT_SYSRQ
port->sysrq = 0;
#endif
goto error_return;
}
/*
* Asynchron mode interrupt handler
*/
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
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)
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
cpm_uart_int_rx(port);
if (events & SMCM_TX)
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
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)
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
cpm_uart_int_rx(port);
if (events & UART_SCCM_TX)
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 07:55:46 -06:00
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,
struct ktermios *old)
{
int baud;
unsigned long flags;
u16 cval, scval, prev_mode;
int bits, sbits;
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 ||
(pinfo->port.state && pinfo->port.state->port.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;
/* Character length programmed into the mode register is the
* sum of: 1 start bit, number of data bits, 0 or 1 parity bit,
* 1 or 2 stop bits, minus 1.
* The value 'bits' counts this for us.
*/
cval = 0;
scval = 0;
/* byte size */
switch (termios->c_cflag & CSIZE) {
case CS5:
bits = 5;
break;
case CS6:
bits = 6;
break;
case CS7:
bits = 7;
break;
case CS8:
bits = 8;
break;
/* Never happens, but GCC is too dumb to figure it out */
default:
bits = 8;
break;
}
sbits = bits - 5;
if (termios->c_cflag & CSTOPB) {
cval |= SMCMR_SL; /* Two stops */
scval |= SCU_PSMR_SL;
bits++;
}
if (termios->c_cflag & PARENB) {
cval |= SMCMR_PEN;
scval |= SCU_PSMR_PEN;
bits++;
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;
spin_lock_irqsave(&port->lock, flags);
/* Start bit has not been added (so don't, because we would just
* subtract it later), and we need to add one for the number of
* stops bits (there is always at least one).
*/
bits++;
if (IS_SMC(pinfo)) {
/*
* 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. */
out_be16(&smcp->smc_smcmr, smcr_mk_clen(bits) | cval |
SMCMR_SM_UART | prev_mode);
} else {
out_be16(&pinfo->sccup->scc_genscc.scc_mrblr, pinfo->rx_fifosize);
out_be16(&pinfo->sccup->scc_maxidl, maxidl);
out_be16(&sccp->scc_psmr, (sbits << 12) | scval);
}
if (pinfo->clk)
clk_set_rate(pinfo->clk, baud);
else
cpm_set_brg(pinfo->brg - 1, baud);
spin_unlock_irqrestore(&port->lock, 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);
[PATCH] ppc32: cpm_uart: fix xchar sending while using SCC as uart and as serial console at same time I got this: [ 138.214258] Oops: kernel access of bad area, sig: 11 [#1] [ 138.218832] PREEMPT [ 138.221021] NIP: C0105C48 LR: C0105E60 SP: C03D5D10 REGS: c03d5c60 TRAP: 0300 Not tainted [ 138.229280] MSR: 00009032 EE: 1 PR: 0 FP: 0 ME: 1 IR/DR: 11 [ 138.234713] DAR: 00000000, DSISR: C0000000 [ 138.238745] TASK = c0349420[693] 'sh' THREAD: c03d4000 [ 138.243754] Last syscall: 6 [ 138.246402] GPR00: FEFFFFFF C03D5D10 C0349420 C01FB094 00000011 00000000 C1ECFBBC C01F24B0 [ 138.254602] GPR08: FF002820 00000000 FF0028C0 00000000 19133615 A0CBCD5E 02000300 00000000 [ 138.262804] GPR16: 00000000 01FF9E4C 00000000 7FA9A770 00000000 00000000 1003E2A8 00000000 [ 138.271003] GPR24: 100562F4 7F9B6EF4 C0210000 C02A5338 C01FB094 00000000 C01FB094 C1F14574 [ 138.279376] NIP [c0105c48] cpm_uart_tx_pump+0x4c/0x22c [ 138.284419] LR [c0105e60] cpm_uart_start_tx+0x38/0xb0 [ 138.289361] Call trace: [ 138.291762] [c0105e60] cpm_uart_start_tx+0x38/0xb0 [ 138.296547] [c010277c] uart_send_xchar+0x88/0x118 [ 138.301244] [c01029a0] uart_unthrottle+0x6c/0x138 [ 138.305942] [c00ece10] check_unthrottle+0x60/0x64 [ 138.310641] [c00ecec4] reset_buffer_flags+0xb0/0x138 [ 138.315595] [c00ecf64] n_tty_flush_buffer+0x18/0x78 [ 138.320465] [c00e81b0] tty_ldisc_flush+0x64/0x7c [ 138.325078] [c010410c] uart_close+0xf0/0x2c8 [ 138.329348] [c00e9c48] release_dev+0x724/0x8d4 [ 138.333790] [c00e9e18] tty_release+0x20/0x3c [ 138.338061] [c006e544] __fput+0x178/0x1e0 [ 138.342076] [c006c43c] filp_close+0x54/0xac [ 138.346261] [c0002d90] ret_from_syscall+0x0/0x44 [ 138.352386] note: sh[693] exited with preempt_count 2 a easy way to reproduce it is log into the system using ssh and do: cat >/dev/ttyCPM0 then, switch to minicom and write some stuff on it back to ssh, a control C produce the oops this happens because uart_close calls uart_shutdown which frees xmit.buf, currently used by xchar sending in cpm_uart_tx_pump(), which seems wrong. the attached patch fixes the oops and also fixes xchar sending. Signed-off-by: Paul Mackerras <paulus@samba.org>
2005-12-29 14:18:49 -07:00
*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) &&
xmit->tail != xmit->head) {
count = 0;
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
while (count < pinfo->tx_fifosize) {
*p++ = xmit->buf[xmit->tail];
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
count++;
if (xmit->head == xmit->tail)
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.
*/
cpm_set_scc_fcr(sup);
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.
*/
cpm_set_smc_fcr(up);
/* 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);
}
/*
* 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);
}
#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
};
struct uart_cpm_port cpm_uart_ports[UART_NR];
static int cpm_uart_init_port(struct device_node *np,
struct uart_cpm_port *pinfo)
{
const u32 *data;
void __iomem *mem, *pram;
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.iotype = UPIO_MEM;
pinfo->port.fifosize = pinfo->tx_nrfifos * pinfo->tx_fifosize;
spin_lock_init(&pinfo->port.lock);
pinfo->port.irq = irq_of_parse_and_map(np, 0);
if (pinfo->port.irq == NO_IRQ) {
ret = -EINVAL;
goto out_pram;
}
for (i = 0; i < NUM_GPIOS; i++) {
int gpio;
pinfo->gpios[i] = -1;
gpio = of_get_gpio(np, i);
if (gpio_is_valid(gpio)) {
ret = gpio_request(gpio, "cpm_uart");
if (ret) {
pr_err("can't request gpio #%d: %d\n", i, ret);
continue;
}
if (i == GPIO_RTS || i == GPIO_DTR)
ret = gpio_direction_output(gpio, 0);
else
ret = gpio_direction_input(gpio);
if (ret) {
pr_err("can't set direction for gpio #%d: %d\n",
i, ret);
gpio_free(gpio);
continue;
}
pinfo->gpios[i] = gpio;
}
}
#ifdef CONFIG_PPC_EARLY_DEBUG_CPM
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;
int nolock = oops_in_progress;
if (unlikely(nolock)) {
local_irq_save(flags);
} else {
spin_lock_irqsave(&pinfo->port.lock, flags);
}
cpm_uart_early_write(pinfo, s, count, true);
if (unlikely(nolock)) {
local_irq_restore(flags);
} else {
spin_unlock_irqrestore(&pinfo->port.lock, 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 {
if ((baud = uart_baudrate()) == -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);
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)
{
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;
ret = cpm_uart_init_port(ofdev->dev.of_node, pinfo);
if (ret)
return ret;
return uart_add_one_port(&cpm_reg, &pinfo->port);
}
static int cpm_uart_remove(struct platform_device *ofdev)
{
struct uart_cpm_port *pinfo = platform_get_drvdata(ofdev);
return 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 = 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);