2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* linux/drivers/serial/cpm_uart.c
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
2005-11-14 00:06:30 +00:00
|
|
|
* Maintainer: Kumar Gala (galak@kernel.crashing.org) (CPM2)
|
2005-04-16 22:20:36 +00:00
|
|
|
* Pantelis Antoniou (panto@intracom.gr) (CPM1)
|
2005-08-09 17:08:00 +00:00
|
|
|
*
|
2007-07-17 22:59:06 +00:00
|
|
|
* Copyright (C) 2004, 2007 Freescale Semiconductor, Inc.
|
2005-04-16 22:20:36 +00:00
|
|
|
* (C) 2004 Intracom, S.A.
|
2006-04-29 19:06:00 +00:00
|
|
|
* (C) 2005-2006 MontaVista Software, Inc.
|
2008-06-12 12:53:48 +00:00
|
|
|
* Vitaly Bordug <vbordug@ru.mvista.com>
|
2005-04-16 22:20:36 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/tty.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/bootmem.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
2006-04-25 16:26:41 +00:00
|
|
|
#include <linux/fs_uart_pd.h>
|
2008-06-12 13:05:09 +00:00
|
|
|
#include <linux/of_platform.h>
|
2008-07-24 16:36:37 +00:00
|
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/of_gpio.h>
|
2008-07-28 08:42:16 +00:00
|
|
|
#include <linux/clk.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#include <asm/delay.h>
|
2006-09-21 13:27:15 +00:00
|
|
|
#include <asm/fs_pd.h>
|
2007-07-17 22:59:06 +00:00
|
|
|
#include <asm/udbg.h>
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
#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);
|
|
|
|
|
|
|
|
/**************************************************************/
|
|
|
|
|
|
|
|
/*
|
2005-08-09 17:08:00 +00:00
|
|
|
* Check, if transmit buffers are processed
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
static unsigned int cpm_uart_tx_empty(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
cbd_t __iomem *bdp = pinfo->tx_bd_base;
|
2005-04-16 22:20:36 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
while (1) {
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_READY)
|
2005-04-16 22:20:36 +00:00
|
|
|
break;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP) {
|
2005-04-16 22:20:36 +00:00
|
|
|
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)
|
|
|
|
{
|
2008-07-24 16:36:37 +00:00
|
|
|
struct uart_cpm_port *pinfo = (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));
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int cpm_uart_get_mctrl(struct uart_port *port)
|
|
|
|
{
|
2008-07-24 16:36:37 +00:00
|
|
|
struct uart_cpm_port *pinfo = (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;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stop transmitter
|
|
|
|
*/
|
2005-08-31 09:12:14 +00:00
|
|
|
static void cpm_uart_stop_tx(struct uart_port *port)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:stop tx\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo))
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits8(&smcp->smc_smcm, SMCM_TX);
|
2005-04-16 22:20:36 +00:00
|
|
|
else
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_TX);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start transmitter
|
|
|
|
*/
|
2005-08-31 09:12:14 +00:00
|
|
|
static void cpm_uart_start_tx(struct uart_port *port)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:start tx\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_8(&smcp->smc_smcm) & SMCM_TX)
|
2005-04-16 22:20:36 +00:00
|
|
|
return;
|
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&sccp->scc_sccm) & UART_SCCM_TX)
|
2005-04-16 22:20:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cpm_uart_tx_pump(port) != 0) {
|
2005-08-09 17:08:00 +00:00
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 20:53:07 +00:00
|
|
|
setbits8(&smcp->smc_smcm, SMCM_TX);
|
2005-08-09 17:08:00 +00:00
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
setbits16(&sccp->scc_sccm, UART_SCCM_TX);
|
2005-08-09 17:08:00 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-08-09 17:08:00 +00:00
|
|
|
* Stop receiver
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
static void cpm_uart_stop_rx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:stop rx\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo))
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits8(&smcp->smc_smcm, SMCM_RX);
|
2005-04-16 22:20:36 +00:00
|
|
|
else
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_RX);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Enable Modem status interrupts
|
|
|
|
*/
|
|
|
|
static void cpm_uart_enable_ms(struct uart_port *port)
|
|
|
|
{
|
|
|
|
pr_debug("CPM uart[%d]:enable ms\n", port->line);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-08-09 17:08:00 +00:00
|
|
|
* Generate a break.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
static void cpm_uart_break_ctl(struct uart_port *port, int break_state)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:break ctrl, break_state: %d\n", port->line,
|
|
|
|
break_state);
|
|
|
|
|
|
|
|
if (break_state)
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
2005-04-16 22:20:36 +00:00
|
|
|
else
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_RESTART_TX);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 13:55:46 +00:00
|
|
|
static void cpm_uart_int_tx(struct uart_port *port)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
pr_debug("CPM uart[%d]:TX INT\n", port->line);
|
|
|
|
|
|
|
|
cpm_uart_tx_pump(port);
|
|
|
|
}
|
|
|
|
|
2008-07-23 16:30:16 +00:00
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
|
|
static int serial_polled;
|
|
|
|
#endif
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* 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 13:55:46 +00:00
|
|
|
static void cpm_uart_int_rx(struct uart_port *port)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
int i;
|
2007-07-24 20:53:07 +00:00
|
|
|
unsigned char ch;
|
|
|
|
u8 *cp;
|
2008-07-22 14:25:07 +00:00
|
|
|
struct tty_struct *tty = port->info->port.tty;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
cbd_t __iomem *bdp;
|
2005-04-16 22:20:36 +00:00
|
|
|
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 (;;) {
|
2008-07-23 16:30:16 +00:00
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
|
|
if (unlikely(serial_polled)) {
|
|
|
|
serial_polled = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2005-04-16 22:20:36 +00:00
|
|
|
/* get status */
|
2007-07-24 20:53:07 +00:00
|
|
|
status = in_be16(&bdp->cbd_sc);
|
2005-04-16 22:20:36 +00:00
|
|
|
/* If this one is empty, return happy */
|
|
|
|
if (status & BD_SC_EMPTY)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* get number of characters, and check spce in flip-buffer */
|
2007-07-24 20:53:07 +00:00
|
|
|
i = in_be16(&bdp->cbd_datlen);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2005-08-09 17:08:00 +00:00
|
|
|
/* If we have not enough room in tty flip buffer, then we try
|
2005-04-16 22:20:36 +00:00
|
|
|
* later, which will be the next rx-interrupt or a timeout
|
|
|
|
*/
|
2006-02-08 21:40:13 +00:00
|
|
|
if(tty_buffer_request_room(tty, i) < i) {
|
|
|
|
printk(KERN_WARNING "No room in flip buffer\n");
|
|
|
|
return;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* get pointer */
|
2007-07-24 20:53:07 +00:00
|
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* 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 13:55:46 +00:00
|
|
|
if (uart_handle_sysrq_char(port, ch))
|
2005-04-16 22:20:36 +00:00
|
|
|
continue;
|
2008-07-23 16:30:16 +00:00
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
|
|
if (unlikely(serial_polled)) {
|
|
|
|
serial_polled = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2005-04-16 22:20:36 +00:00
|
|
|
error_return:
|
2006-02-08 21:40:13 +00:00
|
|
|
tty_insert_flip_char(tty, ch, flg);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
} /* End while (i--) */
|
|
|
|
|
|
|
|
/* This BD is ready to be used again. Clear status. get next */
|
2007-07-24 20:53:07 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-16 22:20:36 +00:00
|
|
|
bdp = pinfo->rx_bd_base;
|
|
|
|
else
|
|
|
|
bdp++;
|
2005-08-09 17:08:00 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
} /* End for (;;) */
|
|
|
|
|
|
|
|
/* Write back buffer pointer */
|
2007-07-24 20:53:07 +00:00
|
|
|
pinfo->rx_cur = bdp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* activate BH processing */
|
|
|
|
tty_flip_buffer_push(tty);
|
|
|
|
|
|
|
|
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 13:55:46 +00:00
|
|
|
static irqreturn_t cpm_uart_int(int irq, void *data)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
u8 events;
|
2008-02-06 09:36:20 +00:00
|
|
|
struct uart_port *port = data;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:IRQ\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 20:53:07 +00:00
|
|
|
events = in_8(&smcp->smc_smce);
|
|
|
|
out_8(&smcp->smc_smce, events);
|
2005-04-16 22:20:36 +00:00
|
|
|
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 13:55:46 +00:00
|
|
|
cpm_uart_int_rx(port);
|
2005-04-16 22:20:36 +00:00
|
|
|
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 13:55:46 +00:00
|
|
|
cpm_uart_int_tx(port);
|
2005-04-16 22:20:36 +00:00
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
events = in_be16(&sccp->scc_scce);
|
|
|
|
out_be16(&sccp->scc_scce, events);
|
2005-04-16 22:20:36 +00:00
|
|
|
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 13:55:46 +00:00
|
|
|
cpm_uart_int_rx(port);
|
2005-04-16 22:20:36 +00:00
|
|
|
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 13:55:46 +00:00
|
|
|
cpm_uart_int_tx(port);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
return (events) ? IRQ_HANDLED : IRQ_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpm_uart_startup(struct uart_port *port)
|
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:startup\n", port->line);
|
|
|
|
|
|
|
|
/* 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)) {
|
2007-07-24 20:53:07 +00:00
|
|
|
setbits8(&pinfo->smcp->smc_smcm, SMCM_RX);
|
|
|
|
setbits16(&pinfo->smcp->smc_smcmr, (SMCMR_REN | SMCMR_TEN));
|
2005-04-16 22:20:36 +00:00
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
setbits16(&pinfo->sccp->scc_sccm, UART_SCCM_RX);
|
|
|
|
setbits32(&pinfo->sccp->scc_gsmrl, (SCC_GSMRL_ENR | SCC_GSMRL_ENT));
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2005-08-09 17:08:00 +00:00
|
|
|
if (!(pinfo->flags & FLAG_CONSOLE))
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-08-09 17:08:00 +00:00
|
|
|
inline void cpm_uart_wait_until_send(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
2005-09-03 22:55:37 +00:00
|
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
|
|
schedule_timeout(pinfo->wait_closing);
|
2005-08-09 17:08:00 +00:00
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* Shutdown the uart
|
|
|
|
*/
|
|
|
|
static void cpm_uart_shutdown(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (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)) {
|
2005-08-09 17:08:00 +00:00
|
|
|
/* Wait for all the BDs marked sent */
|
2005-09-03 22:55:37 +00:00
|
|
|
while(!cpm_uart_tx_empty(port)) {
|
|
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
2005-08-09 17:08:00 +00:00
|
|
|
schedule_timeout(2);
|
2005-09-03 22:55:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pinfo->wait_closing)
|
2005-08-09 17:08:00 +00:00
|
|
|
cpm_uart_wait_until_send(pinfo);
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/* Stop uarts */
|
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
clrbits16(&smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
|
|
|
clrbits8(&smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
2005-04-16 22:20:36 +00:00
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Shut them really down and reinit buffer descriptors */
|
2008-07-24 04:29:50 +00:00
|
|
|
if (IS_SMC(pinfo)) {
|
|
|
|
out_be16(&pinfo->smcup->smc_brkcr, 0);
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
2008-07-24 04:29:50 +00:00
|
|
|
} else {
|
|
|
|
out_be16(&pinfo->sccup->scc_brkcr, 0);
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_GRA_STOP_TX);
|
2008-07-24 04:29:50 +00:00
|
|
|
}
|
2006-04-29 18:32:44 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
cpm_uart_initbd(pinfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_set_termios(struct uart_port *port,
|
2007-05-08 17:19:21 +00:00
|
|
|
struct ktermios *termios,
|
|
|
|
struct ktermios *old)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
int baud;
|
|
|
|
unsigned long flags;
|
|
|
|
u16 cval, scval, prev_mode;
|
|
|
|
int bits, sbits;
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:set_termios\n", port->line);
|
|
|
|
|
|
|
|
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-02 08:58:45 +00:00
|
|
|
/*
|
|
|
|
* Update the timeout
|
|
|
|
*/
|
|
|
|
uart_update_timeout(port, termios->c_cflag, baud);
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* Set up parity check flag
|
|
|
|
*/
|
|
|
|
#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
|
|
|
|
|
|
|
|
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;
|
2005-08-09 17:08:00 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
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)) {
|
|
|
|
/* Set the mode register. We want to keep a copy of the
|
|
|
|
* enables, because we want to put them back if they were
|
|
|
|
* present.
|
|
|
|
*/
|
2008-07-24 04:29:50 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&sccp->scc_psmr, (sbits << 12) | scval);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2008-07-28 08:42:16 +00:00
|
|
|
if (pinfo->clk)
|
|
|
|
clk_set_rate(pinfo->clk, baud);
|
|
|
|
else
|
|
|
|
cpm_set_brg(pinfo->brg - 1, baud);
|
2005-04-16 22:20:36 +00:00
|
|
|
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)
|
|
|
|
{
|
2007-07-24 20:53:07 +00:00
|
|
|
cbd_t __iomem *bdp;
|
|
|
|
u8 *p;
|
2005-04-16 22:20:36 +00:00
|
|
|
int count;
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
|
|
|
struct circ_buf *xmit = &port->info->xmit;
|
|
|
|
|
|
|
|
/* Handle xon/xoff */
|
|
|
|
if (port->x_char) {
|
|
|
|
/* Pick next descriptor and fill from buffer */
|
|
|
|
bdp = pinfo->tx_cur;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-08-09 17:08:00 +00:00
|
|
|
|
2005-12-29 21:18:49 +00:00
|
|
|
*p++ = port->x_char;
|
2007-07-24 20:53:07 +00:00
|
|
|
|
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
2005-04-16 22:20:36 +00:00
|
|
|
/* Get next BD. */
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-16 22:20:36 +00:00
|
|
|
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)) {
|
2005-08-31 09:12:14 +00:00
|
|
|
cpm_uart_stop_tx(port);
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Pick next descriptor and fill from buffer */
|
|
|
|
bdp = pinfo->tx_cur;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
while (!(in_be16(&bdp->cbd_sc) & BD_SC_READY) &&
|
|
|
|
xmit->tail != xmit->head) {
|
2005-04-16 22:20:36 +00:00
|
|
|
count = 0;
|
2007-07-24 20:53:07 +00:00
|
|
|
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
|
|
|
}
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&bdp->cbd_datlen, count);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
2005-04-16 22:20:36 +00:00
|
|
|
/* Get next BD. */
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-16 22:20:36 +00:00
|
|
|
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)) {
|
2005-08-31 09:12:14 +00:00
|
|
|
cpm_uart_stop_tx(port);
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* init buffer descriptors
|
|
|
|
*/
|
|
|
|
static void cpm_uart_initbd(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u8 *mem_addr;
|
2007-07-24 20:53:07 +00:00
|
|
|
cbd_t __iomem *bdp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
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++) {
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_EMPTY | BD_SC_INTRPT);
|
2005-04-16 22:20:36 +00:00
|
|
|
mem_addr += pinfo->rx_fifosize;
|
|
|
|
}
|
2005-08-09 17:08:00 +00:00
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_WRAP | BD_SC_EMPTY | BD_SC_INTRPT);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* 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++) {
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_INTRPT);
|
2005-04-16 22:20:36 +00:00
|
|
|
mem_addr += pinfo->tx_fifosize;
|
|
|
|
}
|
2005-08-09 17:08:00 +00:00
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_WRAP | BD_SC_INTRPT);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_init_scc(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
2007-07-24 20:53:07 +00:00
|
|
|
scc_t __iomem *scp;
|
|
|
|
scc_uart_t __iomem *sup;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:init_scc\n", pinfo->port.line);
|
|
|
|
|
|
|
|
scp = pinfo->sccp;
|
|
|
|
sup = pinfo->sccup;
|
|
|
|
|
|
|
|
/* Store address */
|
2007-07-24 20:53:07 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Set up the uart parameters in the
|
|
|
|
* parameter ram.
|
|
|
|
*/
|
|
|
|
|
|
|
|
cpm_set_scc_fcr(sup);
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&sup->scc_genscc.scc_mrblr, pinfo->rx_fifosize);
|
|
|
|
out_be16(&sup->scc_maxidl, pinfo->rx_fifosize);
|
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Send the CPM an initialize command.
|
|
|
|
*/
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Set UART mode, 8 bit, no parity, one stop.
|
|
|
|
* Enable receive and transmit.
|
|
|
|
*/
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be32(&scp->scc_gsmrh, 0);
|
|
|
|
out_be32(&scp->scc_gsmrl,
|
|
|
|
SCC_GSMRL_MODE_UART | SCC_GSMRL_TDCR_16 | SCC_GSMRL_RDCR_16);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Enable rx interrupts and clear all pending events. */
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&scp->scc_sccm, 0);
|
|
|
|
out_be16(&scp->scc_scce, 0xffff);
|
|
|
|
out_be16(&scp->scc_dsr, 0x7e7e);
|
|
|
|
out_be16(&scp->scc_psmr, 0x3000);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
setbits32(&scp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_init_smc(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
2007-07-24 20:53:07 +00:00
|
|
|
smc_t __iomem *sp;
|
|
|
|
smc_uart_t __iomem *up;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:init_smc\n", pinfo->port.line);
|
|
|
|
|
|
|
|
sp = pinfo->smcp;
|
|
|
|
up = pinfo->smcup;
|
|
|
|
|
|
|
|
/* Store address */
|
2007-07-24 20:53:07 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* In case SMC1 is being relocated...
|
|
|
|
*/
|
|
|
|
#if defined (CONFIG_I2C_SPI_SMC1_UCODE_PATCH)
|
2007-07-24 20:53:07 +00:00
|
|
|
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);
|
2005-04-16 22:20:36 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Set up the uart parameters in the
|
|
|
|
* parameter ram.
|
|
|
|
*/
|
|
|
|
cpm_set_smc_fcr(up);
|
|
|
|
|
|
|
|
/* Using idle charater time requires some additional tuning. */
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&up->smc_mrblr, pinfo->rx_fifosize);
|
|
|
|
out_be16(&up->smc_maxidl, pinfo->rx_fifosize);
|
|
|
|
out_be16(&up->smc_brklen, 0);
|
|
|
|
out_be16(&up->smc_brkec, 0);
|
|
|
|
out_be16(&up->smc_brkcr, 1);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2007-07-17 22:59:06 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Set UART mode, 8 bit, no parity, one stop.
|
|
|
|
* Enable receive and transmit.
|
|
|
|
*/
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&sp->smc_smcmr, smcr_mk_clen(9) | SMCMR_SM_UART);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Enable only rx interrupts clear all pending events. */
|
2007-07-24 20:53:07 +00:00
|
|
|
out_8(&sp->smc_smcm, 0);
|
|
|
|
out_8(&sp->smc_smce, 0xff);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
setbits16(&sp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 = (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)) {
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
2005-04-16 22:20:36 +00:00
|
|
|
} else {
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits16(&pinfo->sccp->scc_sccm, UART_SCCM_TX | UART_SCCM_RX);
|
|
|
|
clrbits32(&pinfo->sccp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = cpm_uart_allocbuf(pinfo, 0);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
cpm_uart_initbd(pinfo);
|
2005-08-09 17:08:00 +00:00
|
|
|
if (IS_SMC(pinfo))
|
|
|
|
cpm_uart_init_smc(pinfo);
|
|
|
|
else
|
|
|
|
cpm_uart_init_scc(pinfo);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_release_port(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (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);
|
|
|
|
}
|
|
|
|
}
|
2008-07-23 16:30:16 +00:00
|
|
|
|
|
|
|
#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;
|
|
|
|
while (bdp->cbd_sc & BD_SC_EMPTY)
|
|
|
|
;
|
|
|
|
|
|
|
|
/* 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 = (struct uart_cpm_port *)port;
|
|
|
|
|
|
|
|
if (!serial_polled) {
|
|
|
|
serial_polled = 1;
|
|
|
|
poll_chars = 0;
|
|
|
|
}
|
|
|
|
if (poll_chars <= 0) {
|
|
|
|
poll_chars = poll_wait_key(poll_buf, pinfo);
|
|
|
|
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 = (struct uart_cpm_port *)port;
|
|
|
|
static char ch[2];
|
|
|
|
|
|
|
|
ch[0] = (char)c;
|
|
|
|
cpm_uart_early_write(pinfo->port.line, ch, 1);
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_CONSOLE_POLL */
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
static 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,
|
|
|
|
.enable_ms = cpm_uart_enable_ms,
|
|
|
|
.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,
|
2008-07-23 16:30:16 +00:00
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
|
|
.poll_get_char = cpm_get_poll_char,
|
|
|
|
.poll_put_char = cpm_put_poll_char,
|
|
|
|
#endif
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2007-07-17 22:59:06 +00:00
|
|
|
struct uart_cpm_port cpm_uart_ports[UART_NR];
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
static int cpm_uart_init_port(struct device_node *np,
|
|
|
|
struct uart_cpm_port *pinfo)
|
2007-07-17 22:59:06 +00:00
|
|
|
{
|
|
|
|
const u32 *data;
|
2007-07-24 20:53:07 +00:00
|
|
|
void __iomem *mem, *pram;
|
2007-07-17 22:59:06 +00:00
|
|
|
int len;
|
|
|
|
int ret;
|
2008-07-24 16:36:37 +00:00
|
|
|
int i;
|
2007-07-17 22:59:06 +00:00
|
|
|
|
2008-07-28 08:42:16 +00:00
|
|
|
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 %s has no/invalid "
|
|
|
|
"fsl,cpm-brg property.\n", np->name);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
pinfo->brg = *data;
|
2007-07-17 22:59:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
data = of_get_property(np, "fsl,cpm-command", &len);
|
|
|
|
if (!data || len != 4) {
|
|
|
|
printk(KERN_ERR "CPM UART %s has no/invalid "
|
|
|
|
"fsl,cpm-command property.\n", np->name);
|
|
|
|
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;
|
2008-04-10 15:01:27 +00:00
|
|
|
pinfo->sccup = pram = cpm_uart_map_pram(pinfo, np);
|
2007-07-17 22:59:06 +00:00
|
|
|
} 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;
|
2008-04-10 15:01:27 +00:00
|
|
|
pinfo->smcup = pram = cpm_uart_map_pram(pinfo, np);
|
2007-07-17 22:59:06 +00:00
|
|
|
} else {
|
|
|
|
ret = -ENODEV;
|
2008-04-10 15:01:27 +00:00
|
|
|
goto out_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pram) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_mem;
|
2007-07-17 22:59:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2008-07-02 08:58:45 +00:00
|
|
|
pinfo->port.fifosize = pinfo->tx_nrfifos * pinfo->tx_fifosize;
|
2007-07-17 22:59:06 +00:00
|
|
|
spin_lock_init(&pinfo->port.lock);
|
|
|
|
|
|
|
|
pinfo->port.irq = of_irq_to_resource(np, 0, NULL);
|
|
|
|
if (pinfo->port.irq == NO_IRQ) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out_pram;
|
|
|
|
}
|
|
|
|
|
2008-07-24 16:36:37 +00:00
|
|
|
for (i = 0; i < NUM_GPIOS; i++)
|
|
|
|
pinfo->gpios[i] = of_get_gpio(np, i);
|
|
|
|
|
2007-07-17 22:59:06 +00:00
|
|
|
return cpm_uart_request_port(&pinfo->port);
|
|
|
|
|
|
|
|
out_pram:
|
2008-04-10 15:01:27 +00:00
|
|
|
cpm_uart_unmap_pram(pinfo, pram);
|
2007-07-17 22:59:06 +00:00
|
|
|
out_mem:
|
|
|
|
iounmap(mem);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
#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)
|
|
|
|
{
|
2007-07-17 22:59:06 +00:00
|
|
|
struct uart_cpm_port *pinfo = &cpm_uart_ports[co->index];
|
2005-04-16 22:20:36 +00:00
|
|
|
unsigned int i;
|
2007-07-24 20:53:07 +00:00
|
|
|
cbd_t __iomem *bdp, *bdbase;
|
|
|
|
unsigned char *cp;
|
2008-05-20 19:39:17 +00:00
|
|
|
unsigned long flags;
|
|
|
|
int nolock = oops_in_progress;
|
|
|
|
|
|
|
|
if (unlikely(nolock)) {
|
|
|
|
local_irq_save(flags);
|
|
|
|
} else {
|
|
|
|
spin_lock_irqsave(&pinfo->port.lock, flags);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* 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++, s++) {
|
|
|
|
/* 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.
|
|
|
|
*/
|
2007-07-24 20:53:07 +00:00
|
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
/* Send the character out.
|
|
|
|
* If the buffer address is in the CPM DPRAM, don't
|
|
|
|
* convert it.
|
|
|
|
*/
|
2007-07-24 20:53:07 +00:00
|
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-16 22:20:36 +00:00
|
|
|
*cp = *s;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-16 22:20:36 +00:00
|
|
|
bdp = bdbase;
|
|
|
|
else
|
|
|
|
bdp++;
|
|
|
|
|
|
|
|
/* if a LF, also do CR... */
|
|
|
|
if (*s == 10) {
|
2007-07-24 20:53:07 +00:00
|
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-16 22:20:36 +00:00
|
|
|
*cp = 13;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
|
|
|
|
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-16 22:20:36 +00:00
|
|
|
bdp = bdbase;
|
|
|
|
else
|
|
|
|
bdp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally, Wait for transmitter & holding register to empty
|
|
|
|
* and restore the IER
|
|
|
|
*/
|
2007-07-24 20:53:07 +00:00
|
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
;
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
pinfo->tx_cur = bdp;
|
2008-05-20 19:39:17 +00:00
|
|
|
|
|
|
|
if (unlikely(nolock)) {
|
|
|
|
local_irq_restore(flags);
|
|
|
|
} else {
|
|
|
|
spin_unlock_irqrestore(&pinfo->port.lock, flags);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-04-25 16:26:41 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
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;
|
2007-07-17 22:59:06 +00:00
|
|
|
struct uart_cpm_port *pinfo;
|
|
|
|
struct uart_port *port;
|
|
|
|
|
|
|
|
struct device_node *np = NULL;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
if (co->index >= UART_NR) {
|
|
|
|
printk(KERN_ERR "cpm_uart: console index %d too high\n",
|
|
|
|
co->index);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
np = of_find_node_by_type(np, "serial");
|
|
|
|
if (!np)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
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"))
|
|
|
|
i--;
|
|
|
|
} while (i++ != co->index);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
if (options) {
|
|
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
|
|
} else {
|
2006-09-21 13:27:15 +00:00
|
|
|
if ((baud = uart_baudrate()) == -1)
|
2005-04-16 22:20:36 +00:00
|
|
|
baud = 9600;
|
|
|
|
}
|
|
|
|
|
2007-07-17 22:59:06 +00:00
|
|
|
#ifdef CONFIG_PPC_EARLY_DEBUG_CPM
|
|
|
|
udbg_putc = NULL;
|
|
|
|
#endif
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
if (IS_SMC(pinfo)) {
|
2008-07-24 04:29:50 +00:00
|
|
|
out_be16(&pinfo->smcup->smc_brkcr, 0);
|
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
2005-04-16 22:20:36 +00:00
|
|
|
} else {
|
2008-07-24 04:29:50 +00:00
|
|
|
out_be16(&pinfo->sccup->scc_brkcr, 0);
|
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_GRA_STOP_TX);
|
2007-07-24 20:53:07 +00:00
|
|
|
clrbits16(&pinfo->sccp->scc_sccm, UART_SCCM_TX | UART_SCCM_RX);
|
|
|
|
clrbits32(&pinfo->sccp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2007-07-17 23:09:33 +00:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_RESTART_TX);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-08-09 17:08:02 +00:00
|
|
|
static struct uart_driver cpm_reg;
|
2005-04-16 22:20:36 +00:00
|
|
|
static struct console cpm_scc_uart_console = {
|
2005-08-09 17:08:02 +00:00
|
|
|
.name = "ttyCPM",
|
|
|
|
.write = cpm_uart_console_write,
|
|
|
|
.device = uart_console_device,
|
|
|
|
.setup = cpm_uart_console_setup,
|
|
|
|
.flags = CON_PRINTBUFFER,
|
|
|
|
.index = -1,
|
2005-04-16 22:20:36 +00:00
|
|
|
.data = &cpm_reg,
|
|
|
|
};
|
|
|
|
|
2007-07-24 20:53:07 +00:00
|
|
|
static int __init cpm_uart_console_init(void)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-04-25 16:26:41 +00:00
|
|
|
register_console(&cpm_scc_uart_console);
|
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2007-07-17 22:59:06 +00:00
|
|
|
.nr = UART_NR,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int probe_index;
|
|
|
|
|
|
|
|
static int __devinit cpm_uart_probe(struct of_device *ofdev,
|
|
|
|
const struct of_device_id *match)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
|
|
|
dev_set_drvdata(&ofdev->dev, pinfo);
|
|
|
|
|
|
|
|
ret = cpm_uart_init_port(ofdev->node, pinfo);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2008-09-12 15:42:56 +00:00
|
|
|
/* initialize the device pointer for the port */
|
|
|
|
pinfo->port.dev = &ofdev->dev;
|
|
|
|
|
2007-07-17 22:59:06 +00:00
|
|
|
return uart_add_one_port(&cpm_reg, &pinfo->port);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __devexit cpm_uart_remove(struct of_device *ofdev)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = dev_get_drvdata(&ofdev->dev);
|
|
|
|
return uart_remove_one_port(&cpm_reg, &pinfo->port);
|
|
|
|
}
|
|
|
|
|
|
|
|
static 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",
|
|
|
|
},
|
|
|
|
{}
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
2007-07-17 22:59:06 +00:00
|
|
|
|
|
|
|
static struct of_platform_driver cpm_uart_driver = {
|
|
|
|
.name = "cpm_uart",
|
|
|
|
.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 = of_register_platform_driver(&cpm_uart_driver);
|
|
|
|
if (ret)
|
|
|
|
uart_unregister_driver(&cpm_reg);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit cpm_uart_exit(void)
|
|
|
|
{
|
|
|
|
of_unregister_platform_driver(&cpm_uart_driver);
|
|
|
|
uart_unregister_driver(&cpm_reg);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
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);
|