2005-04-17 00:20:36 +02: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 01:06:30 +01:00
|
|
|
* Maintainer: Kumar Gala (galak@kernel.crashing.org) (CPM2)
|
2005-04-17 00:20:36 +02:00
|
|
|
* Pantelis Antoniou (panto@intracom.gr) (CPM1)
|
2005-08-09 19:08:00 +02:00
|
|
|
*
|
2007-07-18 00:59:06 +02:00
|
|
|
* Copyright (C) 2004, 2007 Freescale Semiconductor, Inc.
|
2005-04-17 00:20:36 +02:00
|
|
|
* (C) 2004 Intracom, S.A.
|
2006-04-29 21:06:00 +02:00
|
|
|
* (C) 2005-2006 MontaVista Software, Inc.
|
|
|
|
* Vitaly Bordug <vbordug@ru.mvista.com>
|
2005-04-17 00:20:36 +02: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 18:26:41 +02:00
|
|
|
#include <linux/fs_uart_pd.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
#include <asm/delay.h>
|
2006-09-21 15:27:15 +02:00
|
|
|
#include <asm/fs_pd.h>
|
2007-07-18 00:59:06 +02:00
|
|
|
#include <asm/udbg.h>
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_CPM_NEW_BINDING
|
|
|
|
#include <linux/of_platform.h>
|
|
|
|
#endif
|
2005-04-17 00:20:36 +02: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);
|
|
|
|
|
|
|
|
/**************************************************************/
|
|
|
|
|
2007-07-18 00:59:06 +02:00
|
|
|
#ifndef CONFIG_PPC_CPM_NEW_BINDING
|
|
|
|
/* Track which ports are configured as uarts */
|
|
|
|
int cpm_uart_port_map[UART_NR];
|
|
|
|
/* How many ports did we config as uarts */
|
|
|
|
int cpm_uart_nr;
|
2005-08-09 19:08:00 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
/* Place-holder for board-specific stuff */
|
|
|
|
struct platform_device* __attribute__ ((weak)) __init
|
|
|
|
early_uart_get_pdev(int index)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-04-29 21:06:00 +02:00
|
|
|
static void cpm_uart_count(void)
|
2006-04-25 18:26:41 +02:00
|
|
|
{
|
|
|
|
cpm_uart_nr = 0;
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_SMC1
|
|
|
|
cpm_uart_port_map[cpm_uart_nr++] = UART_SMC1;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_SMC2
|
|
|
|
cpm_uart_port_map[cpm_uart_nr++] = UART_SMC2;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_SCC1
|
|
|
|
cpm_uart_port_map[cpm_uart_nr++] = UART_SCC1;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_SCC2
|
|
|
|
cpm_uart_port_map[cpm_uart_nr++] = UART_SCC2;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_SCC3
|
|
|
|
cpm_uart_port_map[cpm_uart_nr++] = UART_SCC3;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_SCC4
|
|
|
|
cpm_uart_port_map[cpm_uart_nr++] = UART_SCC4;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2006-04-29 21:06:00 +02:00
|
|
|
/* Get UART number by its id */
|
|
|
|
static int cpm_uart_id2nr(int id)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
if (id < UART_NR) {
|
|
|
|
for (i=0; i<UART_NR; i++) {
|
|
|
|
if (cpm_uart_port_map[i] == id)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* not found or invalid argument */
|
|
|
|
return -1;
|
|
|
|
}
|
2007-07-18 00:59:06 +02:00
|
|
|
#endif
|
2006-04-29 21:06:00 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/*
|
2005-08-09 19:08:00 +02:00
|
|
|
* Check, if transmit buffers are processed
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
cbd_t __iomem *bdp = pinfo->tx_bd_base;
|
2005-04-17 00:20:36 +02:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
while (1) {
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_READY)
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP) {
|
2005-04-17 00:20:36 +02: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)
|
|
|
|
{
|
|
|
|
/* Whee. Do nothing. */
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int cpm_uart_get_mctrl(struct uart_port *port)
|
|
|
|
{
|
|
|
|
/* Whee. Do nothing. */
|
|
|
|
return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stop transmitter
|
|
|
|
*/
|
2005-08-31 11:12:14 +02:00
|
|
|
static void cpm_uart_stop_tx(struct uart_port *port)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 22:53:07 +02:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:stop tx\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo))
|
2007-07-24 22:53:07 +02:00
|
|
|
clrbits8(&smcp->smc_smcm, SMCM_TX);
|
2005-04-17 00:20:36 +02:00
|
|
|
else
|
2007-07-24 22:53:07 +02:00
|
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_TX);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start transmitter
|
|
|
|
*/
|
2005-08-31 11:12:14 +02:00
|
|
|
static void cpm_uart_start_tx(struct uart_port *port)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 22:53:07 +02:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:start tx\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_8(&smcp->smc_smcm) & SMCM_TX)
|
2005-04-17 00:20:36 +02:00
|
|
|
return;
|
|
|
|
} else {
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&sccp->scc_sccm) & UART_SCCM_TX)
|
2005-04-17 00:20:36 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cpm_uart_tx_pump(port) != 0) {
|
2005-08-09 19:08:00 +02:00
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 22:53:07 +02:00
|
|
|
setbits8(&smcp->smc_smcm, SMCM_TX);
|
2005-08-09 19:08:00 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02:00
|
|
|
setbits16(&sccp->scc_sccm, UART_SCCM_TX);
|
2005-08-09 19:08:00 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-08-09 19:08:00 +02:00
|
|
|
* Stop receiver
|
2005-04-17 00:20:36 +02:00
|
|
|
*/
|
|
|
|
static void cpm_uart_stop_rx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 22:53:07 +02:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:stop rx\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo))
|
2007-07-24 22:53:07 +02:00
|
|
|
clrbits8(&smcp->smc_smcm, SMCM_RX);
|
2005-04-17 00:20:36 +02:00
|
|
|
else
|
2007-07-24 22:53:07 +02:00
|
|
|
clrbits16(&sccp->scc_sccm, UART_SCCM_RX);
|
2005-04-17 00:20:36 +02: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 19:08:00 +02:00
|
|
|
* Generate a break.
|
2005-04-17 00:20:36 +02: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-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
2005-04-17 00:20:36 +02:00
|
|
|
else
|
2007-07-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_RESTART_TX);
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
static void cpm_uart_int_tx(struct uart_port *port)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
pr_debug("CPM uart[%d]:TX INT\n", port->line);
|
|
|
|
|
|
|
|
cpm_uart_tx_pump(port);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 15:55:46 +02:00
|
|
|
static void cpm_uart_int_rx(struct uart_port *port)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
int i;
|
2007-07-24 22:53:07 +02:00
|
|
|
unsigned char ch;
|
|
|
|
u8 *cp;
|
2005-04-17 00:20:36 +02:00
|
|
|
struct tty_struct *tty = port->info->tty;
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 22:53:07 +02:00
|
|
|
cbd_t __iomem *bdp;
|
2005-04-17 00:20:36 +02: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 (;;) {
|
|
|
|
/* get status */
|
2007-07-24 22:53:07 +02:00
|
|
|
status = in_be16(&bdp->cbd_sc);
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
i = in_be16(&bdp->cbd_datlen);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2005-08-09 19:08:00 +02:00
|
|
|
/* If we have not enough room in tty flip buffer, then we try
|
2005-04-17 00:20:36 +02:00
|
|
|
* later, which will be the next rx-interrupt or a timeout
|
|
|
|
*/
|
2006-02-08 22:40:13 +01:00
|
|
|
if(tty_buffer_request_room(tty, i) < i) {
|
|
|
|
printk(KERN_WARNING "No room in flip buffer\n");
|
|
|
|
return;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* get pointer */
|
2007-07-24 22:53:07 +02:00
|
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
if (uart_handle_sysrq_char(port, ch))
|
2005-04-17 00:20:36 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
error_return:
|
2006-02-08 22:40:13 +01:00
|
|
|
tty_insert_flip_char(tty, ch, flg);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
} /* End while (i--) */
|
|
|
|
|
|
|
|
/* This BD is ready to be used again. Clear status. get next */
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-17 00:20:36 +02:00
|
|
|
bdp = pinfo->rx_bd_base;
|
|
|
|
else
|
|
|
|
bdp++;
|
2005-08-09 19:08:00 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
} /* End for (;;) */
|
|
|
|
|
|
|
|
/* Write back buffer pointer */
|
2007-07-24 22:53:07 +02:00
|
|
|
pinfo->rx_cur = bdp;
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
static irqreturn_t cpm_uart_int(int irq, void *data)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
u8 events;
|
|
|
|
struct uart_port *port = (struct uart_port *)data;
|
|
|
|
struct uart_cpm_port *pinfo = (struct uart_cpm_port *)port;
|
2007-07-24 22:53:07 +02:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:IRQ\n", port->line);
|
|
|
|
|
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 22:53:07 +02:00
|
|
|
events = in_8(&smcp->smc_smce);
|
|
|
|
out_8(&smcp->smc_smce, events);
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
cpm_uart_int_rx(port);
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
cpm_uart_int_tx(port);
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02:00
|
|
|
events = in_be16(&sccp->scc_scce);
|
|
|
|
out_be16(&sccp->scc_scce, events);
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
cpm_uart_int_rx(port);
|
2005-04-17 00:20:36 +02: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 15:55:46 +02:00
|
|
|
cpm_uart_int_tx(port);
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
setbits8(&pinfo->smcp->smc_smcm, SMCM_RX);
|
|
|
|
setbits16(&pinfo->smcp->smc_smcmr, (SMCMR_REN | SMCMR_TEN));
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02:00
|
|
|
setbits16(&pinfo->sccp->scc_sccm, UART_SCCM_RX);
|
|
|
|
setbits32(&pinfo->sccp->scc_gsmrl, (SCC_GSMRL_ENR | SCC_GSMRL_ENT));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2005-08-09 19:08:00 +02:00
|
|
|
if (!(pinfo->flags & FLAG_CONSOLE))
|
2007-07-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-08-09 19:08:00 +02:00
|
|
|
inline void cpm_uart_wait_until_send(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
2005-09-04 00:55:37 +02:00
|
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
|
|
schedule_timeout(pinfo->wait_closing);
|
2005-08-09 19:08:00 +02:00
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02: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 19:08:00 +02:00
|
|
|
/* Wait for all the BDs marked sent */
|
2005-09-04 00:55:37 +02:00
|
|
|
while(!cpm_uart_tx_empty(port)) {
|
|
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
2005-08-09 19:08:00 +02:00
|
|
|
schedule_timeout(2);
|
2005-09-04 00:55:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pinfo->wait_closing)
|
2005-08-09 19:08:00 +02:00
|
|
|
cpm_uart_wait_until_send(pinfo);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Stop uarts */
|
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Shut them really down and reinit buffer descriptors */
|
2006-04-29 20:32:44 +02:00
|
|
|
if (IS_SMC(pinfo))
|
2007-07-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_STOP_TX);
|
2006-04-29 20:32:44 +02:00
|
|
|
else
|
2007-07-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_GRA_STOP_TX);
|
2006-04-29 20:32:44 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
cpm_uart_initbd(pinfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_set_termios(struct uart_port *port,
|
2007-05-08 19:19:21 +02:00
|
|
|
struct ktermios *termios,
|
|
|
|
struct ktermios *old)
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
smc_t __iomem *smcp = pinfo->smcp;
|
|
|
|
scc_t __iomem *sccp = pinfo->sccp;
|
2005-04-17 00:20:36 +02: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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 19:08:00 +02:00
|
|
|
|
2005-04-17 00:20:36 +02: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.
|
|
|
|
*/
|
2007-07-24 22:53:07 +02:00
|
|
|
prev_mode = in_be16(&smcp->smc_smcmr);
|
|
|
|
out_be16(&smcp->smc_smcmr, smcr_mk_clen(bits) | cval | SMCMR_SM_UART);
|
|
|
|
setbits16(&smcp->smc_smcmr, (prev_mode & (SMCMR_REN | SMCMR_TEN)));
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02:00
|
|
|
out_be16(&sccp->scc_psmr, (sbits << 12) | scval);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2007-07-24 22:53:07 +02:00
|
|
|
cbd_t __iomem *bdp;
|
|
|
|
u8 *p;
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-08-09 19:08:00 +02:00
|
|
|
|
2005-12-29 22:18:49 +01:00
|
|
|
*p++ = port->x_char;
|
2007-07-24 22:53:07 +02:00
|
|
|
|
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Get next BD. */
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-17 00:20:36 +02: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 11:12:14 +02:00
|
|
|
cpm_uart_stop_tx(port);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Pick next descriptor and fill from buffer */
|
|
|
|
bdp = pinfo->tx_cur;
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
while (!(in_be16(&bdp->cbd_sc) & BD_SC_READY) &&
|
|
|
|
xmit->tail != xmit->head) {
|
2005-04-17 00:20:36 +02:00
|
|
|
count = 0;
|
2007-07-24 22:53:07 +02:00
|
|
|
p = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
out_be16(&bdp->cbd_datlen, count);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
2005-04-17 00:20:36 +02:00
|
|
|
/* Get next BD. */
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-17 00:20:36 +02: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 11:12:14 +02:00
|
|
|
cpm_uart_stop_tx(port);
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
cbd_t __iomem *bdp;
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_EMPTY | BD_SC_INTRPT);
|
2005-04-17 00:20:36 +02:00
|
|
|
mem_addr += pinfo->rx_fifosize;
|
|
|
|
}
|
2005-08-09 19:08:00 +02:00
|
|
|
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_INTRPT);
|
2005-04-17 00:20:36 +02:00
|
|
|
mem_addr += pinfo->tx_fifosize;
|
|
|
|
}
|
2005-08-09 19:08:00 +02:00
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
out_be32(&bdp->cbd_bufaddr, cpu2cpm_addr(mem_addr, pinfo));
|
|
|
|
out_be16(&bdp->cbd_sc, BD_SC_WRAP | BD_SC_INTRPT);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_init_scc(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
2007-07-24 22:53:07 +02:00
|
|
|
scc_t __iomem *scp;
|
|
|
|
scc_uart_t __iomem *sup;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:init_scc\n", pinfo->port.line);
|
|
|
|
|
|
|
|
scp = pinfo->sccp;
|
|
|
|
sup = pinfo->sccup;
|
|
|
|
|
|
|
|
/* Store address */
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Set up the uart parameters in the
|
|
|
|
* parameter ram.
|
|
|
|
*/
|
|
|
|
|
|
|
|
cpm_set_scc_fcr(sup);
|
|
|
|
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Send the CPM an initialize command.
|
|
|
|
*/
|
2007-07-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Set UART mode, 8 bit, no parity, one stop.
|
|
|
|
* Enable receive and transmit.
|
|
|
|
*/
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Enable rx interrupts and clear all pending events. */
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
setbits32(&scp->scc_gsmrl, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cpm_uart_init_smc(struct uart_cpm_port *pinfo)
|
|
|
|
{
|
2007-07-24 22:53:07 +02:00
|
|
|
smc_t __iomem *sp;
|
|
|
|
smc_uart_t __iomem *up;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
pr_debug("CPM uart[%d]:init_smc\n", pinfo->port.line);
|
|
|
|
|
|
|
|
sp = pinfo->smcp;
|
|
|
|
up = pinfo->smcup;
|
|
|
|
|
|
|
|
/* Store address */
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* In case SMC1 is being relocated...
|
|
|
|
*/
|
|
|
|
#if defined (CONFIG_I2C_SPI_SMC1_UCODE_PATCH)
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02: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 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
|
2007-07-18 00:59:06 +02:00
|
|
|
cpm_line_cr_cmd(pinfo, CPM_CR_INIT_TRX);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Set UART mode, 8 bit, no parity, one stop.
|
|
|
|
* Enable receive and transmit.
|
|
|
|
*/
|
2007-07-24 22:53:07 +02:00
|
|
|
out_be16(&sp->smc_smcmr, smcr_mk_clen(9) | SMCMR_SM_UART);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* Enable only rx interrupts clear all pending events. */
|
2007-07-24 22:53:07 +02:00
|
|
|
out_8(&sp->smc_smcm, 0);
|
|
|
|
out_8(&sp->smc_smce, 0xff);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
setbits16(&sp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = cpm_uart_allocbuf(pinfo, 0);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
cpm_uart_initbd(pinfo);
|
2005-08-09 19:08:00 +02:00
|
|
|
if (IS_SMC(pinfo))
|
|
|
|
cpm_uart_init_smc(pinfo);
|
|
|
|
else
|
|
|
|
cpm_uart_init_scc(pinfo);
|
2005-04-17 00:20:36 +02: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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2007-07-18 00:59:06 +02:00
|
|
|
#ifdef CONFIG_PPC_CPM_NEW_BINDING
|
|
|
|
struct uart_cpm_port cpm_uart_ports[UART_NR];
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
static int cpm_uart_init_port(struct device_node *np,
|
|
|
|
struct uart_cpm_port *pinfo)
|
2007-07-18 00:59:06 +02:00
|
|
|
{
|
|
|
|
const u32 *data;
|
2007-07-24 22:53:07 +02:00
|
|
|
void __iomem *mem, *pram;
|
2007-07-18 00:59:06 +02:00
|
|
|
int len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
pram = of_iomap(np, 1);
|
|
|
|
if (!pram) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
} 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;
|
|
|
|
} else {
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto out_pram;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cpm_uart_request_port(&pinfo->port);
|
|
|
|
|
|
|
|
out_pram:
|
|
|
|
iounmap(pram);
|
|
|
|
out_mem:
|
|
|
|
iounmap(mem);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
struct uart_cpm_port cpm_uart_ports[UART_NR] = {
|
|
|
|
[UART_SMC1] = {
|
|
|
|
.port = {
|
|
|
|
.irq = SMC1_IRQ,
|
|
|
|
.ops = &cpm_uart_pops,
|
2006-02-05 11:48:10 +01:00
|
|
|
.iotype = UPIO_MEM,
|
2007-05-08 09:30:10 +02:00
|
|
|
.lock = __SPIN_LOCK_UNLOCKED(cpm_uart_ports[UART_SMC1].port.lock),
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
.flags = FLAG_SMC,
|
|
|
|
.tx_nrfifos = TX_NUM_FIFO,
|
|
|
|
.tx_fifosize = TX_BUF_SIZE,
|
2005-08-09 19:08:00 +02:00
|
|
|
.rx_nrfifos = RX_NUM_FIFO,
|
2005-04-17 00:20:36 +02:00
|
|
|
.rx_fifosize = RX_BUF_SIZE,
|
|
|
|
.set_lineif = smc1_lineif,
|
|
|
|
},
|
|
|
|
[UART_SMC2] = {
|
|
|
|
.port = {
|
|
|
|
.irq = SMC2_IRQ,
|
|
|
|
.ops = &cpm_uart_pops,
|
2006-02-05 11:48:10 +01:00
|
|
|
.iotype = UPIO_MEM,
|
2007-05-08 09:30:10 +02:00
|
|
|
.lock = __SPIN_LOCK_UNLOCKED(cpm_uart_ports[UART_SMC2].port.lock),
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
.flags = FLAG_SMC,
|
|
|
|
.tx_nrfifos = TX_NUM_FIFO,
|
|
|
|
.tx_fifosize = TX_BUF_SIZE,
|
2005-08-09 19:08:00 +02:00
|
|
|
.rx_nrfifos = RX_NUM_FIFO,
|
2005-04-17 00:20:36 +02:00
|
|
|
.rx_fifosize = RX_BUF_SIZE,
|
|
|
|
.set_lineif = smc2_lineif,
|
|
|
|
#ifdef CONFIG_SERIAL_CPM_ALT_SMC2
|
|
|
|
.is_portb = 1,
|
|
|
|
#endif
|
|
|
|
},
|
|
|
|
[UART_SCC1] = {
|
|
|
|
.port = {
|
|
|
|
.irq = SCC1_IRQ,
|
|
|
|
.ops = &cpm_uart_pops,
|
2006-02-05 11:48:10 +01:00
|
|
|
.iotype = UPIO_MEM,
|
2007-05-08 09:30:10 +02:00
|
|
|
.lock = __SPIN_LOCK_UNLOCKED(cpm_uart_ports[UART_SCC1].port.lock),
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
.tx_nrfifos = TX_NUM_FIFO,
|
|
|
|
.tx_fifosize = TX_BUF_SIZE,
|
2005-08-09 19:08:00 +02:00
|
|
|
.rx_nrfifos = RX_NUM_FIFO,
|
2005-04-17 00:20:36 +02:00
|
|
|
.rx_fifosize = RX_BUF_SIZE,
|
|
|
|
.set_lineif = scc1_lineif,
|
2005-08-09 19:08:00 +02:00
|
|
|
.wait_closing = SCC_WAIT_CLOSING,
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
[UART_SCC2] = {
|
|
|
|
.port = {
|
|
|
|
.irq = SCC2_IRQ,
|
|
|
|
.ops = &cpm_uart_pops,
|
2006-02-05 11:48:10 +01:00
|
|
|
.iotype = UPIO_MEM,
|
2007-05-08 09:30:10 +02:00
|
|
|
.lock = __SPIN_LOCK_UNLOCKED(cpm_uart_ports[UART_SCC2].port.lock),
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
.tx_nrfifos = TX_NUM_FIFO,
|
|
|
|
.tx_fifosize = TX_BUF_SIZE,
|
2005-08-09 19:08:00 +02:00
|
|
|
.rx_nrfifos = RX_NUM_FIFO,
|
2005-04-17 00:20:36 +02:00
|
|
|
.rx_fifosize = RX_BUF_SIZE,
|
|
|
|
.set_lineif = scc2_lineif,
|
2005-08-09 19:08:00 +02:00
|
|
|
.wait_closing = SCC_WAIT_CLOSING,
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
[UART_SCC3] = {
|
|
|
|
.port = {
|
|
|
|
.irq = SCC3_IRQ,
|
|
|
|
.ops = &cpm_uart_pops,
|
2006-02-05 11:48:10 +01:00
|
|
|
.iotype = UPIO_MEM,
|
2007-05-08 09:30:10 +02:00
|
|
|
.lock = __SPIN_LOCK_UNLOCKED(cpm_uart_ports[UART_SCC3].port.lock),
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
.tx_nrfifos = TX_NUM_FIFO,
|
|
|
|
.tx_fifosize = TX_BUF_SIZE,
|
2005-08-09 19:08:00 +02:00
|
|
|
.rx_nrfifos = RX_NUM_FIFO,
|
2005-04-17 00:20:36 +02:00
|
|
|
.rx_fifosize = RX_BUF_SIZE,
|
|
|
|
.set_lineif = scc3_lineif,
|
2005-08-09 19:08:00 +02:00
|
|
|
.wait_closing = SCC_WAIT_CLOSING,
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
[UART_SCC4] = {
|
|
|
|
.port = {
|
|
|
|
.irq = SCC4_IRQ,
|
|
|
|
.ops = &cpm_uart_pops,
|
2006-02-05 11:48:10 +01:00
|
|
|
.iotype = UPIO_MEM,
|
2007-05-08 09:30:10 +02:00
|
|
|
.lock = __SPIN_LOCK_UNLOCKED(cpm_uart_ports[UART_SCC4].port.lock),
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
.tx_nrfifos = TX_NUM_FIFO,
|
|
|
|
.tx_fifosize = TX_BUF_SIZE,
|
2005-08-09 19:08:00 +02:00
|
|
|
.rx_nrfifos = RX_NUM_FIFO,
|
2005-04-17 00:20:36 +02:00
|
|
|
.rx_fifosize = RX_BUF_SIZE,
|
|
|
|
.set_lineif = scc4_lineif,
|
2005-08-09 19:08:00 +02:00
|
|
|
.wait_closing = SCC_WAIT_CLOSING,
|
2005-04-17 00:20:36 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
int cpm_uart_drv_get_platform_data(struct platform_device *pdev, int is_con)
|
|
|
|
{
|
|
|
|
struct resource *r;
|
|
|
|
struct fs_uart_platform_info *pdata = pdev->dev.platform_data;
|
POWERPC: Bring the fs_no calculation to the relevant SoC enumeration
The fs_no mean used to be fs_enet driver driven, hence it was an
enumeration across all the possible fs_enet "users" in the SoC. Now, with
QE on the pipeline, and to make DTS descriptions more clear, fs_no features
relevant SoC part number, with additional field to describe the SoC type.
Another reason for that is now not only fs_enet is going to utilize those
stuff. There might be UART, HLDC, and even USB, so to prevent confusion and
be ready for upcoming OF_device transfer, fs_enet and cpm_uart drivers were
updated in that concern, as well as the relevant DTS.
Signed-off-by: Vitaly Bordug <vbordug@ru.mvista.com>
2006-09-21 20:38:05 +02:00
|
|
|
int idx; /* It is UART_SMCx or UART_SCCx index */
|
2006-04-25 18:26:41 +02:00
|
|
|
struct uart_cpm_port *pinfo;
|
|
|
|
int line;
|
|
|
|
u32 mem, pram;
|
|
|
|
|
POWERPC: Bring the fs_no calculation to the relevant SoC enumeration
The fs_no mean used to be fs_enet driver driven, hence it was an
enumeration across all the possible fs_enet "users" in the SoC. Now, with
QE on the pipeline, and to make DTS descriptions more clear, fs_no features
relevant SoC part number, with additional field to describe the SoC type.
Another reason for that is now not only fs_enet is going to utilize those
stuff. There might be UART, HLDC, and even USB, so to prevent confusion and
be ready for upcoming OF_device transfer, fs_enet and cpm_uart drivers were
updated in that concern, as well as the relevant DTS.
Signed-off-by: Vitaly Bordug <vbordug@ru.mvista.com>
2006-09-21 20:38:05 +02:00
|
|
|
idx = pdata->fs_no = fs_uart_get_id(pdata);
|
|
|
|
|
2006-04-29 21:06:00 +02:00
|
|
|
line = cpm_uart_id2nr(idx);
|
|
|
|
if(line < 0) {
|
|
|
|
printk(KERN_ERR"%s(): port %d is not registered", __FUNCTION__, idx);
|
POWERPC: Bring the fs_no calculation to the relevant SoC enumeration
The fs_no mean used to be fs_enet driver driven, hence it was an
enumeration across all the possible fs_enet "users" in the SoC. Now, with
QE on the pipeline, and to make DTS descriptions more clear, fs_no features
relevant SoC part number, with additional field to describe the SoC type.
Another reason for that is now not only fs_enet is going to utilize those
stuff. There might be UART, HLDC, and even USB, so to prevent confusion and
be ready for upcoming OF_device transfer, fs_enet and cpm_uart drivers were
updated in that concern, as well as the relevant DTS.
Signed-off-by: Vitaly Bordug <vbordug@ru.mvista.com>
2006-09-21 20:38:05 +02:00
|
|
|
return -EINVAL;
|
2006-04-29 21:06:00 +02:00
|
|
|
}
|
2006-04-25 18:26:41 +02:00
|
|
|
|
|
|
|
pinfo = (struct uart_cpm_port *) &cpm_uart_ports[idx];
|
|
|
|
|
|
|
|
pinfo->brg = pdata->brg;
|
|
|
|
|
|
|
|
if (!is_con) {
|
|
|
|
pinfo->port.line = line;
|
|
|
|
pinfo->port.flags = UPF_BOOT_AUTOCONF;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs")))
|
|
|
|
return -EINVAL;
|
2006-09-21 15:27:15 +02:00
|
|
|
mem = (u32)ioremap(r->start, r->end - r->start + 1);
|
2006-04-25 18:26:41 +02:00
|
|
|
|
|
|
|
if (!(r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pram")))
|
|
|
|
return -EINVAL;
|
2006-09-21 15:27:15 +02:00
|
|
|
pram = (u32)ioremap(r->start, r->end - r->start + 1);
|
2006-04-25 18:26:41 +02:00
|
|
|
|
|
|
|
if(idx > fsid_smc2_uart) {
|
|
|
|
pinfo->sccp = (scc_t *)mem;
|
|
|
|
pinfo->sccup = (scc_uart_t *)pram;
|
|
|
|
} else {
|
|
|
|
pinfo->smcp = (smc_t *)mem;
|
|
|
|
pinfo->smcup = (smc_uart_t *)pram;
|
|
|
|
}
|
|
|
|
pinfo->tx_nrfifos = pdata->tx_num_fifo;
|
|
|
|
pinfo->tx_fifosize = pdata->tx_buf_size;
|
|
|
|
|
|
|
|
pinfo->rx_nrfifos = pdata->rx_num_fifo;
|
|
|
|
pinfo->rx_fifosize = pdata->rx_buf_size;
|
|
|
|
|
|
|
|
pinfo->port.uartclk = pdata->uart_clk;
|
|
|
|
pinfo->port.mapbase = (unsigned long)mem;
|
|
|
|
pinfo->port.irq = platform_get_irq(pdev, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2007-07-18 00:59:06 +02:00
|
|
|
#endif
|
2006-04-25 18:26:41 +02:00
|
|
|
|
2005-04-17 00:20:36 +02: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-18 00:59:06 +02:00
|
|
|
#ifdef CONFIG_PPC_CPM_NEW_BINDING
|
|
|
|
struct uart_cpm_port *pinfo = &cpm_uart_ports[co->index];
|
|
|
|
#else
|
2005-04-17 00:20:36 +02:00
|
|
|
struct uart_cpm_port *pinfo =
|
|
|
|
&cpm_uart_ports[cpm_uart_port_map[co->index]];
|
2007-07-18 00:59:06 +02:00
|
|
|
#endif
|
2005-04-17 00:20:36 +02:00
|
|
|
unsigned int i;
|
2007-07-24 22:53:07 +02:00
|
|
|
cbd_t __iomem *bdp, *bdbase;
|
|
|
|
unsigned char *cp;
|
2005-04-17 00:20:36 +02: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 22:53:07 +02:00
|
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
2005-04-17 00:20:36 +02:00
|
|
|
;
|
|
|
|
|
|
|
|
/* Send the character out.
|
|
|
|
* If the buffer address is in the CPM DPRAM, don't
|
|
|
|
* convert it.
|
|
|
|
*/
|
2007-07-24 22:53:07 +02:00
|
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-17 00:20:36 +02:00
|
|
|
*cp = *s;
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
out_be16(&bdp->cbd_datlen, 1);
|
|
|
|
setbits16(&bdp->cbd_sc, BD_SC_READY);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
if (in_be16(&bdp->cbd_sc) & BD_SC_WRAP)
|
2005-04-17 00:20:36 +02:00
|
|
|
bdp = bdbase;
|
|
|
|
else
|
|
|
|
bdp++;
|
|
|
|
|
|
|
|
/* if a LF, also do CR... */
|
|
|
|
if (*s == 10) {
|
2007-07-24 22:53:07 +02:00
|
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
2005-04-17 00:20:36 +02:00
|
|
|
;
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
cp = cpm2cpu_addr(in_be32(&bdp->cbd_bufaddr), pinfo);
|
2005-04-17 00:20:36 +02:00
|
|
|
*cp = 13;
|
|
|
|
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02:00
|
|
|
bdp = bdbase;
|
|
|
|
else
|
|
|
|
bdp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finally, Wait for transmitter & holding register to empty
|
|
|
|
* and restore the IER
|
|
|
|
*/
|
2007-07-24 22:53:07 +02:00
|
|
|
while ((in_be16(&bdp->cbd_sc) & BD_SC_READY) != 0)
|
2005-04-17 00:20:36 +02:00
|
|
|
;
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
pinfo->tx_cur = bdp;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
|
2005-04-17 00:20:36 +02: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-18 00:59:06 +02:00
|
|
|
struct uart_cpm_port *pinfo;
|
|
|
|
struct uart_port *port;
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_CPM_NEW_BINDING
|
|
|
|
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;
|
|
|
|
|
|
|
|
#else
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
struct fs_uart_platform_info *pdata;
|
|
|
|
struct platform_device* pdev = early_uart_get_pdev(co->index);
|
|
|
|
|
|
|
|
if (!pdev) {
|
|
|
|
pr_info("cpm_uart: console: compat mode\n");
|
|
|
|
/* compatibility - will be cleaned up */
|
|
|
|
cpm_uart_init_portdesc();
|
2006-05-24 19:40:18 +02:00
|
|
|
}
|
2006-04-25 18:26:41 +02:00
|
|
|
|
2006-05-24 19:40:18 +02:00
|
|
|
port =
|
|
|
|
(struct uart_port *)&cpm_uart_ports[cpm_uart_port_map[co->index]];
|
|
|
|
pinfo = (struct uart_cpm_port *)port;
|
|
|
|
if (!pdev) {
|
2006-04-25 18:26:41 +02:00
|
|
|
if (pinfo->set_lineif)
|
|
|
|
pinfo->set_lineif(pinfo);
|
|
|
|
} else {
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
|
|
if (pdata)
|
|
|
|
if (pdata->init_ioports)
|
2006-09-21 20:38:05 +02:00
|
|
|
pdata->init_ioports(pdata);
|
2006-04-25 18:26:41 +02:00
|
|
|
|
|
|
|
cpm_uart_drv_get_platform_data(pdev, 1);
|
|
|
|
}
|
2005-08-09 19:08:00 +02:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
pinfo->flags |= FLAG_CONSOLE;
|
2007-07-18 00:59:06 +02:00
|
|
|
#endif
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (options) {
|
|
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
|
|
} else {
|
2006-09-21 15:27:15 +02:00
|
|
|
if ((baud = uart_baudrate()) == -1)
|
2005-04-17 00:20:36 +02:00
|
|
|
baud = 9600;
|
|
|
|
}
|
|
|
|
|
2007-07-18 00:59:06 +02:00
|
|
|
#ifdef CONFIG_PPC_EARLY_DEBUG_CPM
|
|
|
|
udbg_putc = NULL;
|
|
|
|
#endif
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (IS_SMC(pinfo)) {
|
2007-07-24 22:53:07 +02:00
|
|
|
clrbits8(&pinfo->smcp->smc_smcm, SMCM_RX | SMCM_TX);
|
|
|
|
clrbits16(&pinfo->smcp->smc_smcmr, SMCMR_REN | SMCMR_TEN);
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
2007-07-24 22:53:07 +02: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-17 00:20:36 +02: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);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-08-09 19:08:02 +02:00
|
|
|
static struct uart_driver cpm_reg;
|
2005-04-17 00:20:36 +02:00
|
|
|
static struct console cpm_scc_uart_console = {
|
2005-08-09 19:08:02 +02:00
|
|
|
.name = "ttyCPM",
|
|
|
|
.write = cpm_uart_console_write,
|
|
|
|
.device = uart_console_device,
|
|
|
|
.setup = cpm_uart_console_setup,
|
|
|
|
.flags = CON_PRINTBUFFER,
|
|
|
|
.index = -1,
|
2005-04-17 00:20:36 +02:00
|
|
|
.data = &cpm_reg,
|
|
|
|
};
|
|
|
|
|
2007-07-24 22:53:07 +02:00
|
|
|
static int __init cpm_uart_console_init(void)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2006-04-25 18:26:41 +02:00
|
|
|
register_console(&cpm_scc_uart_console);
|
|
|
|
return 0;
|
2005-04-17 00:20:36 +02: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-18 00:59:06 +02:00
|
|
|
.nr = UART_NR,
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_PPC_CPM_NEW_BINDING
|
|
|
|
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;
|
|
|
|
|
|
|
|
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-17 00:20:36 +02:00
|
|
|
};
|
2007-07-18 00:59:06 +02: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);
|
|
|
|
}
|
|
|
|
#else
|
2006-04-25 18:26:41 +02:00
|
|
|
static int cpm_uart_drv_probe(struct device *dev)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2006-04-25 18:26:41 +02:00
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
struct fs_uart_platform_info *pdata;
|
|
|
|
int ret = -ENODEV;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
if(!pdev) {
|
|
|
|
printk(KERN_ERR"CPM UART: platform data missing!\n");
|
2005-04-17 00:20:36 +02:00
|
|
|
return ret;
|
2006-04-25 18:26:41 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
pdata = pdev->dev.platform_data;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
if ((ret = cpm_uart_drv_get_platform_data(pdev, 0)))
|
2005-04-17 00:20:36 +02:00
|
|
|
return ret;
|
|
|
|
|
POWERPC: Bring the fs_no calculation to the relevant SoC enumeration
The fs_no mean used to be fs_enet driver driven, hence it was an
enumeration across all the possible fs_enet "users" in the SoC. Now, with
QE on the pipeline, and to make DTS descriptions more clear, fs_no features
relevant SoC part number, with additional field to describe the SoC type.
Another reason for that is now not only fs_enet is going to utilize those
stuff. There might be UART, HLDC, and even USB, so to prevent confusion and
be ready for upcoming OF_device transfer, fs_enet and cpm_uart drivers were
updated in that concern, as well as the relevant DTS.
Signed-off-by: Vitaly Bordug <vbordug@ru.mvista.com>
2006-09-21 20:38:05 +02:00
|
|
|
pr_debug("cpm_uart_drv_probe: Adding CPM UART %d\n", cpm_uart_id2nr(pdata->fs_no));
|
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
if (pdata->init_ioports)
|
2006-09-21 20:38:05 +02:00
|
|
|
pdata->init_ioports(pdata);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
ret = uart_add_one_port(&cpm_reg, &cpm_uart_ports[pdata->fs_no].port);
|
|
|
|
|
|
|
|
return ret;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
static int cpm_uart_drv_remove(struct device *dev)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
struct fs_uart_platform_info *pdata = pdev->dev.platform_data;
|
|
|
|
|
|
|
|
pr_debug("cpm_uart_drv_remove: Removing CPM UART %d\n",
|
2006-04-29 21:06:00 +02:00
|
|
|
cpm_uart_id2nr(pdata->fs_no));
|
2006-04-25 18:26:41 +02:00
|
|
|
|
|
|
|
uart_remove_one_port(&cpm_reg, &cpm_uart_ports[pdata->fs_no].port);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_driver cpm_smc_uart_driver = {
|
|
|
|
.name = "fsl-cpm-smc:uart",
|
|
|
|
.bus = &platform_bus_type,
|
|
|
|
.probe = cpm_uart_drv_probe,
|
|
|
|
.remove = cpm_uart_drv_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct device_driver cpm_scc_uart_driver = {
|
|
|
|
.name = "fsl-cpm-scc:uart",
|
|
|
|
.bus = &platform_bus_type,
|
|
|
|
.probe = cpm_uart_drv_probe,
|
|
|
|
.remove = cpm_uart_drv_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
This is supposed to match uart devices on platform bus,
|
|
|
|
*/
|
|
|
|
static int match_is_uart (struct device* dev, void* data)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2006-04-25 18:26:41 +02:00
|
|
|
struct platform_device* pdev = container_of(dev, struct platform_device, dev);
|
|
|
|
int ret = 0;
|
|
|
|
/* this was setfunc as uart */
|
|
|
|
if(strstr(pdev->name,":uart")) {
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int cpm_uart_init(void) {
|
|
|
|
|
|
|
|
int ret;
|
2005-04-17 00:20:36 +02:00
|
|
|
int i;
|
2006-04-25 18:26:41 +02:00
|
|
|
struct device *dev;
|
|
|
|
printk(KERN_INFO "Serial: CPM driver $Revision: 0.02 $\n");
|
|
|
|
|
|
|
|
/* lookup the bus for uart devices */
|
|
|
|
dev = bus_find_device(&platform_bus_type, NULL, 0, match_is_uart);
|
|
|
|
|
|
|
|
/* There are devices on the bus - all should be OK */
|
|
|
|
if (dev) {
|
|
|
|
cpm_uart_count();
|
|
|
|
cpm_reg.nr = cpm_uart_nr;
|
|
|
|
|
|
|
|
if (!(ret = uart_register_driver(&cpm_reg))) {
|
|
|
|
if ((ret = driver_register(&cpm_smc_uart_driver))) {
|
|
|
|
uart_unregister_driver(&cpm_reg);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if ((ret = driver_register(&cpm_scc_uart_driver))) {
|
|
|
|
driver_unregister(&cpm_scc_uart_driver);
|
|
|
|
uart_unregister_driver(&cpm_reg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* No capable platform devices found - falling back to legacy mode */
|
|
|
|
pr_info("cpm_uart: WARNING: no UART devices found on platform bus!\n");
|
|
|
|
pr_info(
|
|
|
|
"cpm_uart: the driver will guess configuration, but this mode is no longer supported.\n");
|
2006-11-01 14:08:13 +01:00
|
|
|
|
|
|
|
/* Don't run this again, if the console driver did it already */
|
|
|
|
if (cpm_uart_nr == 0)
|
|
|
|
cpm_uart_init_portdesc();
|
2006-04-25 18:26:41 +02:00
|
|
|
|
|
|
|
cpm_reg.nr = cpm_uart_nr;
|
|
|
|
ret = uart_register_driver(&cpm_reg);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
for (i = 0; i < cpm_uart_nr; i++) {
|
|
|
|
int con = cpm_uart_port_map[i];
|
|
|
|
cpm_uart_ports[con].port.line = i;
|
|
|
|
cpm_uart_ports[con].port.flags = UPF_BOOT_AUTOCONF;
|
2006-11-01 14:08:13 +01:00
|
|
|
if (cpm_uart_ports[con].set_lineif)
|
|
|
|
cpm_uart_ports[con].set_lineif(&cpm_uart_ports[con]);
|
2006-04-25 18:26:41 +02:00
|
|
|
uart_add_one_port(&cpm_reg, &cpm_uart_ports[con].port);
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
}
|
2006-04-25 18:26:41 +02:00
|
|
|
return ret;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-04-25 18:26:41 +02:00
|
|
|
static void __exit cpm_uart_exit(void)
|
|
|
|
{
|
|
|
|
driver_unregister(&cpm_scc_uart_driver);
|
|
|
|
driver_unregister(&cpm_smc_uart_driver);
|
2005-04-17 00:20:36 +02:00
|
|
|
uart_unregister_driver(&cpm_reg);
|
|
|
|
}
|
2007-07-18 00:59:06 +02:00
|
|
|
#endif
|
2005-04-17 00:20:36 +02: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);
|