2008-04-30 15:52:12 +08:00
|
|
|
/*
|
2009-12-10 04:31:28 +08:00
|
|
|
* Blackfin On-Chip Sport Emulated UART Driver
|
2008-04-30 15:52:12 +08:00
|
|
|
*
|
2009-12-10 04:31:28 +08:00
|
|
|
* Copyright 2006-2009 Analog Devices Inc.
|
2008-04-30 15:52:12 +08:00
|
|
|
*
|
2009-12-10 04:31:28 +08:00
|
|
|
* Enter bugs at http://blackfin.uclinux.org/
|
2008-04-30 15:52:12 +08:00
|
|
|
*
|
2009-12-10 04:31:28 +08:00
|
|
|
* Licensed under the GPL-2 or later.
|
2008-04-30 15:52:12 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This driver and the hardware supported are in term of EE-191 of ADI.
|
2010-10-18 17:03:14 +08:00
|
|
|
* http://www.analog.com/static/imported-files/application_notes/EE191.pdf
|
2008-04-30 15:52:12 +08:00
|
|
|
* This application note describe how to implement a UART on a Sharc DSP,
|
|
|
|
* but this driver is implemented on Blackfin Processor.
|
2009-12-10 04:31:28 +08:00
|
|
|
* Transmit Frame Sync is not used by this driver to transfer data out.
|
2008-04-30 15:52:12 +08:00
|
|
|
*/
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
/* #define DEBUG */
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
#define DRV_NAME "bfin-sport-uart"
|
|
|
|
#define DEVICE_NAME "ttySS"
|
|
|
|
#define pr_fmt(fmt) DRV_NAME ": " fmt
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/ioport.h>
|
2009-12-10 04:31:28 +08:00
|
|
|
#include <linux/io.h>
|
2008-04-30 15:52:12 +08:00
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/console.h>
|
|
|
|
#include <linux/sysrq.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2008-04-30 15:52:12 +08:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/tty.h>
|
|
|
|
#include <linux/tty_flip.h>
|
|
|
|
#include <linux/serial_core.h>
|
2014-09-03 05:39:18 +08:00
|
|
|
#include <linux/gpio.h>
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2010-03-10 01:25:38 +08:00
|
|
|
#include <asm/bfin_sport.h>
|
2008-04-30 15:52:12 +08:00
|
|
|
#include <asm/delay.h>
|
|
|
|
#include <asm/portmux.h>
|
|
|
|
|
|
|
|
#include "bfin_sport_uart.h"
|
|
|
|
|
|
|
|
struct sport_uart_port {
|
|
|
|
struct uart_port port;
|
|
|
|
int err_irq;
|
2009-12-10 04:31:28 +08:00
|
|
|
unsigned short csize;
|
|
|
|
unsigned short rxmask;
|
|
|
|
unsigned short txmask1;
|
|
|
|
unsigned short txmask2;
|
|
|
|
unsigned char stopb;
|
|
|
|
/* unsigned char parib; */
|
2010-03-10 01:25:33 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
int cts_pin;
|
|
|
|
int rts_pin;
|
|
|
|
#endif
|
2008-04-30 15:52:12 +08:00
|
|
|
};
|
|
|
|
|
2010-03-10 01:25:37 +08:00
|
|
|
static int sport_uart_tx_chars(struct sport_uart_port *up);
|
2008-04-30 15:52:12 +08:00
|
|
|
static void sport_stop_tx(struct uart_port *port);
|
|
|
|
|
|
|
|
static inline void tx_one_byte(struct sport_uart_port *up, unsigned int value)
|
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
pr_debug("%s value:%x, mask1=0x%x, mask2=0x%x\n", __func__, value,
|
|
|
|
up->txmask1, up->txmask2);
|
|
|
|
|
|
|
|
/* Place Start and Stop bits */
|
2009-06-11 20:37:11 +08:00
|
|
|
__asm__ __volatile__ (
|
2009-12-10 04:31:28 +08:00
|
|
|
"%[val] <<= 1;"
|
|
|
|
"%[val] = %[val] & %[mask1];"
|
|
|
|
"%[val] = %[val] | %[mask2];"
|
|
|
|
: [val]"+d"(value)
|
|
|
|
: [mask1]"d"(up->txmask1), [mask2]"d"(up->txmask2)
|
|
|
|
: "ASTAT"
|
2009-06-11 20:37:11 +08:00
|
|
|
);
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s value:%x\n", __func__, value);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
SPORT_PUT_TX(up, value);
|
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
static inline unsigned char rx_one_byte(struct sport_uart_port *up)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
unsigned int value;
|
|
|
|
unsigned char extract;
|
2009-06-11 20:37:11 +08:00
|
|
|
u32 tmp_mask1, tmp_mask2, tmp_shift, tmp;
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
if ((up->csize + up->stopb) > 7)
|
|
|
|
value = SPORT_GET_RX32(up);
|
|
|
|
else
|
|
|
|
value = SPORT_GET_RX(up);
|
|
|
|
|
|
|
|
pr_debug("%s value:%x, cs=%d, mask=0x%x\n", __func__, value,
|
|
|
|
up->csize, up->rxmask);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
/* Extract data */
|
2009-06-11 20:37:11 +08:00
|
|
|
__asm__ __volatile__ (
|
|
|
|
"%[extr] = 0;"
|
2009-12-10 04:31:28 +08:00
|
|
|
"%[mask1] = %[rxmask];"
|
|
|
|
"%[mask2] = 0x0200(Z);"
|
2009-06-11 20:37:11 +08:00
|
|
|
"%[shift] = 0;"
|
|
|
|
"LSETUP(.Lloop_s, .Lloop_e) LC0 = %[lc];"
|
|
|
|
".Lloop_s:"
|
|
|
|
"%[tmp] = extract(%[val], %[mask1].L)(Z);"
|
|
|
|
"%[tmp] <<= %[shift];"
|
|
|
|
"%[extr] = %[extr] | %[tmp];"
|
|
|
|
"%[mask1] = %[mask1] - %[mask2];"
|
|
|
|
".Lloop_e:"
|
|
|
|
"%[shift] += 1;"
|
2009-12-10 04:31:28 +08:00
|
|
|
: [extr]"=&d"(extract), [shift]"=&d"(tmp_shift), [tmp]"=&d"(tmp),
|
|
|
|
[mask1]"=&d"(tmp_mask1), [mask2]"=&d"(tmp_mask2)
|
|
|
|
: [val]"d"(value), [rxmask]"d"(up->rxmask), [lc]"a"(up->csize)
|
2009-06-11 20:37:11 +08:00
|
|
|
: "ASTAT", "LB0", "LC0", "LT0"
|
|
|
|
);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
pr_debug(" extract:%x\n", extract);
|
|
|
|
return extract;
|
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
static int sport_uart_setup(struct sport_uart_port *up, int size, int baud_rate)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
int tclkdiv, rclkdiv;
|
|
|
|
unsigned int sclk = get_sclk();
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
/* Set TCR1 and TCR2, TFSR is not enabled for uart */
|
2010-08-29 04:32:55 +08:00
|
|
|
SPORT_PUT_TCR1(up, (LATFS | ITFS | TFSR | TLSBIT | ITCLK));
|
2009-12-10 04:31:28 +08:00
|
|
|
SPORT_PUT_TCR2(up, size + 1);
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s TCR1:%x, TCR2:%x\n", __func__, SPORT_GET_TCR1(up), SPORT_GET_TCR2(up));
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
/* Set RCR1 and RCR2 */
|
|
|
|
SPORT_PUT_RCR1(up, (RCKFE | LARFS | LRFS | RFSR | IRCLK));
|
2009-12-10 04:31:28 +08:00
|
|
|
SPORT_PUT_RCR2(up, (size + 1) * 2 - 1);
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s RCR1:%x, RCR2:%x\n", __func__, SPORT_GET_RCR1(up), SPORT_GET_RCR2(up));
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
tclkdiv = sclk / (2 * baud_rate) - 1;
|
2010-10-17 06:22:34 +08:00
|
|
|
/* The actual uart baud rate of devices vary between +/-2%. The sport
|
|
|
|
* RX sample rate should be faster than the double of the worst case,
|
|
|
|
* otherwise, wrong data are received. So, set sport RX clock to be
|
|
|
|
* 3% faster.
|
|
|
|
*/
|
|
|
|
rclkdiv = sclk / (2 * baud_rate * 2 * 97 / 100) - 1;
|
2008-04-30 15:52:12 +08:00
|
|
|
SPORT_PUT_TCLKDIV(up, tclkdiv);
|
|
|
|
SPORT_PUT_RCLKDIV(up, rclkdiv);
|
|
|
|
SSYNC();
|
2009-12-10 04:31:28 +08:00
|
|
|
pr_debug("%s sclk:%d, baud_rate:%d, tclkdiv:%d, rclkdiv:%d\n",
|
|
|
|
__func__, sclk, baud_rate, tclkdiv, rclkdiv);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t sport_uart_rx_irq(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = dev_id;
|
2013-01-03 22:53:03 +08:00
|
|
|
struct tty_port *port = &up->port.state->port;
|
2008-04-30 15:52:12 +08:00
|
|
|
unsigned int ch;
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
spin_lock(&up->port.lock);
|
|
|
|
|
|
|
|
while (SPORT_GET_STAT(up) & RXNE) {
|
2008-04-30 15:52:12 +08:00
|
|
|
ch = rx_one_byte(up);
|
|
|
|
up->port.icount.rx++;
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
if (!uart_handle_sysrq_char(&up->port, ch))
|
2013-01-03 22:53:03 +08:00
|
|
|
tty_insert_flip_char(port, ch, TTY_NORMAL);
|
2009-12-10 04:31:28 +08:00
|
|
|
}
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
spin_unlock(&up->port.lock);
|
|
|
|
|
2013-08-19 22:44:11 +08:00
|
|
|
/* XXX this won't deadlock with lowlat? */
|
|
|
|
tty_flip_buffer_push(port);
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t sport_uart_tx_irq(int irq, void *dev_id)
|
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
struct sport_uart_port *up = dev_id;
|
|
|
|
|
|
|
|
spin_lock(&up->port.lock);
|
|
|
|
sport_uart_tx_chars(up);
|
|
|
|
spin_unlock(&up->port.lock);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t sport_uart_err_irq(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = dev_id;
|
|
|
|
unsigned int stat = SPORT_GET_STAT(up);
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
spin_lock(&up->port.lock);
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
/* Overflow in RX FIFO */
|
|
|
|
if (stat & ROVF) {
|
|
|
|
up->port.icount.overrun++;
|
2013-01-03 22:53:03 +08:00
|
|
|
tty_insert_flip_char(&up->port.state->port, 0, TTY_OVERRUN);
|
2008-04-30 15:52:12 +08:00
|
|
|
SPORT_PUT_STAT(up, ROVF); /* Clear ROVF bit */
|
|
|
|
}
|
|
|
|
/* These should not happen */
|
|
|
|
if (stat & (TOVF | TUVF | RUVF)) {
|
2009-12-10 04:31:28 +08:00
|
|
|
pr_err("SPORT Error:%s %s %s\n",
|
|
|
|
(stat & TOVF) ? "TX overflow" : "",
|
|
|
|
(stat & TUVF) ? "TX underflow" : "",
|
|
|
|
(stat & RUVF) ? "RX underflow" : "");
|
2008-04-30 15:52:12 +08:00
|
|
|
SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN);
|
|
|
|
SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) & ~RSPEN);
|
|
|
|
}
|
|
|
|
SSYNC();
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
spin_unlock(&up->port.lock);
|
2013-01-03 22:53:03 +08:00
|
|
|
/* XXX we don't push the overrun bit to TTY? */
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2010-03-10 01:25:33 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
static unsigned int sport_get_mctrl(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
if (up->cts_pin < 0)
|
|
|
|
return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
|
|
|
|
|
|
|
|
/* CTS PIN is negative assertive. */
|
|
|
|
if (SPORT_UART_GET_CTS(up))
|
|
|
|
return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
|
|
|
|
else
|
|
|
|
return TIOCM_DSR | TIOCM_CAR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
if (up->rts_pin < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* RTS PIN is negative assertive. */
|
|
|
|
if (mctrl & TIOCM_RTS)
|
|
|
|
SPORT_UART_ENABLE_RTS(up);
|
|
|
|
else
|
|
|
|
SPORT_UART_DISABLE_RTS(up);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle any change of modem status signal.
|
|
|
|
*/
|
|
|
|
static irqreturn_t sport_mctrl_cts_int(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)dev_id;
|
|
|
|
unsigned int status;
|
|
|
|
|
|
|
|
status = sport_get_mctrl(&up->port);
|
|
|
|
uart_handle_cts_change(&up->port, status & TIOCM_CTS);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static unsigned int sport_get_mctrl(struct uart_port *port)
|
|
|
|
{
|
|
|
|
pr_debug("%s enter\n", __func__);
|
|
|
|
return TIOCM_CTS | TIOCM_CD | TIOCM_DSR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
|
|
{
|
|
|
|
pr_debug("%s enter\n", __func__);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
/* Reqeust IRQ, Setup clock */
|
|
|
|
static int sport_startup(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
2009-12-10 04:31:28 +08:00
|
|
|
int ret;
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2009-12-10 04:31:28 +08:00
|
|
|
ret = request_irq(up->port.irq, sport_uart_rx_irq, 0,
|
|
|
|
"SPORT_UART_RX", up);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(port->dev, "unable to request SPORT RX interrupt\n");
|
|
|
|
return ret;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
ret = request_irq(up->port.irq+1, sport_uart_tx_irq, 0,
|
|
|
|
"SPORT_UART_TX", up);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(port->dev, "unable to request SPORT TX interrupt\n");
|
2008-04-30 15:52:12 +08:00
|
|
|
goto fail1;
|
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
ret = request_irq(up->err_irq, sport_uart_err_irq, 0,
|
|
|
|
"SPORT_UART_STATUS", up);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(port->dev, "unable to request SPORT status interrupt\n");
|
2008-04-30 15:52:12 +08:00
|
|
|
goto fail2;
|
|
|
|
}
|
|
|
|
|
2010-03-10 01:25:33 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
if (up->cts_pin >= 0) {
|
|
|
|
if (request_irq(gpio_to_irq(up->cts_pin),
|
|
|
|
sport_mctrl_cts_int,
|
|
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
|
2011-09-22 16:59:15 +08:00
|
|
|
0, "BFIN_SPORT_UART_CTS", up)) {
|
2010-03-10 01:25:33 +08:00
|
|
|
up->cts_pin = -1;
|
2011-04-24 11:38:19 +08:00
|
|
|
dev_info(port->dev, "Unable to attach BlackFin UART over SPORT CTS interrupt. So, disable it.\n");
|
2010-03-10 01:25:33 +08:00
|
|
|
}
|
|
|
|
}
|
2011-12-06 15:16:34 +08:00
|
|
|
if (up->rts_pin >= 0) {
|
|
|
|
if (gpio_request(up->rts_pin, DRV_NAME)) {
|
|
|
|
dev_info(port->dev, "fail to request RTS PIN at GPIO_%d\n", up->rts_pin);
|
|
|
|
up->rts_pin = -1;
|
|
|
|
} else
|
|
|
|
gpio_direction_output(up->rts_pin, 0);
|
|
|
|
}
|
2010-03-10 01:25:33 +08:00
|
|
|
#endif
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
return 0;
|
2009-12-10 04:31:28 +08:00
|
|
|
fail2:
|
|
|
|
free_irq(up->port.irq+1, up);
|
|
|
|
fail1:
|
|
|
|
free_irq(up->port.irq, up);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
return ret;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
2010-03-10 01:25:37 +08:00
|
|
|
/*
|
|
|
|
* sport_uart_tx_chars
|
|
|
|
*
|
|
|
|
* ret 1 means need to enable sport.
|
|
|
|
* ret 0 means do nothing.
|
|
|
|
*/
|
|
|
|
static int sport_uart_tx_chars(struct sport_uart_port *up)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-09-20 04:13:28 +08:00
|
|
|
struct circ_buf *xmit = &up->port.state->xmit;
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
if (SPORT_GET_STAT(up) & TXF)
|
2010-03-10 01:25:37 +08:00
|
|
|
return 0;
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
if (up->port.x_char) {
|
|
|
|
tx_one_byte(up, up->port.x_char);
|
|
|
|
up->port.icount.tx++;
|
|
|
|
up->port.x_char = 0;
|
2010-03-10 01:25:37 +08:00
|
|
|
return 1;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {
|
2010-03-10 01:25:29 +08:00
|
|
|
/* The waiting loop to stop SPORT TX from TX interrupt is
|
|
|
|
* too long. This may block SPORT RX interrupts and cause
|
|
|
|
* RX FIFO overflow. So, do stop sport TX only after the last
|
|
|
|
* char in TX FIFO is moved into the shift register.
|
|
|
|
*/
|
|
|
|
if (SPORT_GET_STAT(up) & TXHRE)
|
|
|
|
sport_stop_tx(&up->port);
|
2010-03-10 01:25:37 +08:00
|
|
|
return 0;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
while(!(SPORT_GET_STAT(up) & TXF) && !uart_circ_empty(xmit)) {
|
|
|
|
tx_one_byte(up, xmit->buf[xmit->tail]);
|
|
|
|
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE -1);
|
|
|
|
up->port.icount.tx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
|
|
uart_write_wakeup(&up->port);
|
2010-03-10 01:25:37 +08:00
|
|
|
|
|
|
|
return 1;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int sport_tx_empty(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
unsigned int stat;
|
|
|
|
|
|
|
|
stat = SPORT_GET_STAT(up);
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s stat:%04x\n", __func__, stat);
|
2008-04-30 15:52:12 +08:00
|
|
|
if (stat & TXHRE) {
|
|
|
|
return TIOCSER_TEMT;
|
|
|
|
} else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_stop_tx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2010-03-10 01:25:37 +08:00
|
|
|
if (!(SPORT_GET_TCR1(up) & TSPEN))
|
|
|
|
return;
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
/* Although the hold register is empty, last byte is still in shift
|
2009-12-10 04:31:28 +08:00
|
|
|
* register and not sent out yet. So, put a dummy data into TX FIFO.
|
|
|
|
* Then, sport tx stops when last byte is shift out and the dummy
|
|
|
|
* data is moved into the shift register.
|
|
|
|
*/
|
|
|
|
SPORT_PUT_TX(up, 0xffff);
|
|
|
|
while (!(SPORT_GET_STAT(up) & TXHRE))
|
|
|
|
cpu_relax();
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) & ~TSPEN));
|
|
|
|
SSYNC();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_start_tx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2009-12-10 04:31:28 +08:00
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
/* Write data into SPORT FIFO before enable SPROT to transmit */
|
2010-03-10 01:25:37 +08:00
|
|
|
if (sport_uart_tx_chars(up)) {
|
|
|
|
/* Enable transmit, then an interrupt will generated */
|
|
|
|
SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN));
|
|
|
|
SSYNC();
|
|
|
|
}
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s exit\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_stop_rx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
/* Disable sport to stop rx */
|
|
|
|
SPORT_PUT_RCR1(up, (SPORT_GET_RCR1(up) & ~RSPEN));
|
|
|
|
SSYNC();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_break_ctl(struct uart_port *port, int break_state)
|
|
|
|
{
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_shutdown(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
dev_dbg(port->dev, "%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
/* Disable sport */
|
|
|
|
SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) & ~TSPEN));
|
|
|
|
SPORT_PUT_RCR1(up, (SPORT_GET_RCR1(up) & ~RSPEN));
|
|
|
|
SSYNC();
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
free_irq(up->port.irq, up);
|
|
|
|
free_irq(up->port.irq+1, up);
|
2008-04-30 15:52:12 +08:00
|
|
|
free_irq(up->err_irq, up);
|
2010-03-10 01:25:33 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
if (up->cts_pin >= 0)
|
|
|
|
free_irq(gpio_to_irq(up->cts_pin), up);
|
2011-12-06 15:16:34 +08:00
|
|
|
if (up->rts_pin >= 0)
|
|
|
|
gpio_free(up->rts_pin);
|
2010-03-10 01:25:33 +08:00
|
|
|
#endif
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *sport_type(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2009-12-10 04:31:28 +08:00
|
|
|
return up->port.type == PORT_BFIN_SPORT ? "BFIN-SPORT-UART" : NULL;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_release_port(struct uart_port *port)
|
|
|
|
{
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int sport_request_port(struct uart_port *port)
|
|
|
|
{
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_config_port(struct uart_port *port, int flags)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
up->port.type = PORT_BFIN_SPORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sport_verify_port(struct uart_port *port, struct serial_struct *ser)
|
|
|
|
{
|
2009-01-02 21:49:13 +08:00
|
|
|
pr_debug("%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
static void sport_set_termios(struct uart_port *port,
|
|
|
|
struct ktermios *termios, struct ktermios *old)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
unsigned long flags;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
pr_debug("%s enter, c_cflag:%08x\n", __func__, termios->c_cflag);
|
|
|
|
|
2014-06-16 21:17:10 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
if (old == NULL && up->cts_pin != -1)
|
|
|
|
termios->c_cflag |= CRTSCTS;
|
|
|
|
else if (up->cts_pin == -1)
|
|
|
|
termios->c_cflag &= ~CRTSCTS;
|
|
|
|
#endif
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
switch (termios->c_cflag & CSIZE) {
|
|
|
|
case CS8:
|
|
|
|
up->csize = 8;
|
|
|
|
break;
|
|
|
|
case CS7:
|
|
|
|
up->csize = 7;
|
|
|
|
break;
|
|
|
|
case CS6:
|
|
|
|
up->csize = 6;
|
|
|
|
break;
|
|
|
|
case CS5:
|
|
|
|
up->csize = 5;
|
|
|
|
break;
|
|
|
|
default:
|
2014-11-10 14:46:35 +08:00
|
|
|
pr_warn("requested word length not supported\n");
|
|
|
|
break;
|
2009-12-10 04:31:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (termios->c_cflag & CSTOPB) {
|
|
|
|
up->stopb = 1;
|
|
|
|
}
|
|
|
|
if (termios->c_cflag & PARENB) {
|
2014-11-10 14:46:35 +08:00
|
|
|
pr_warn("PAREN bit is not supported yet\n");
|
2009-12-10 04:31:28 +08:00
|
|
|
/* up->parib = 1; */
|
|
|
|
}
|
|
|
|
|
2010-03-10 01:25:34 +08:00
|
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
|
|
|
2010-03-10 01:25:36 +08:00
|
|
|
port->read_status_mask = 0;
|
2009-12-10 04:31:28 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Characters to ignore
|
|
|
|
*/
|
|
|
|
port->ignore_status_mask = 0;
|
|
|
|
|
|
|
|
/* RX extract mask */
|
|
|
|
up->rxmask = 0x01 | (((up->csize + up->stopb) * 2 - 1) << 0x8);
|
|
|
|
/* TX masks, 8 bit data and 1 bit stop for example:
|
|
|
|
* mask1 = b#0111111110
|
|
|
|
* mask2 = b#1000000000
|
|
|
|
*/
|
|
|
|
for (i = 0, up->txmask1 = 0; i < up->csize; i++)
|
|
|
|
up->txmask1 |= (1<<i);
|
|
|
|
up->txmask2 = (1<<i);
|
|
|
|
if (up->stopb) {
|
|
|
|
++i;
|
|
|
|
up->txmask2 |= (1<<i);
|
|
|
|
}
|
|
|
|
up->txmask1 <<= 1;
|
|
|
|
up->txmask2 <<= 1;
|
|
|
|
/* uart baud rate */
|
|
|
|
port->uartclk = uart_get_baud_rate(port, termios, old, 0, get_sclk()/16);
|
|
|
|
|
|
|
|
/* Disable UART */
|
|
|
|
SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN);
|
|
|
|
SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) & ~RSPEN);
|
|
|
|
|
|
|
|
sport_uart_setup(up, up->csize + up->stopb, port->uartclk);
|
|
|
|
|
|
|
|
/* driver TX line high after config, one dummy data is
|
|
|
|
* necessary to stop sport after shift one byte
|
|
|
|
*/
|
|
|
|
SPORT_PUT_TX(up, 0xffff);
|
|
|
|
SPORT_PUT_TX(up, 0xffff);
|
|
|
|
SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN));
|
|
|
|
SSYNC();
|
|
|
|
while (!(SPORT_GET_STAT(up) & TXHRE))
|
|
|
|
cpu_relax();
|
|
|
|
SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN);
|
|
|
|
SSYNC();
|
|
|
|
|
|
|
|
/* Port speed changed, update the per-port timeout. */
|
|
|
|
uart_update_timeout(port, termios->c_cflag, port->uartclk);
|
|
|
|
|
|
|
|
/* Enable sport rx */
|
|
|
|
SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) | RSPEN);
|
|
|
|
SSYNC();
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
|
|
}
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
struct uart_ops sport_uart_ops = {
|
|
|
|
.tx_empty = sport_tx_empty,
|
|
|
|
.set_mctrl = sport_set_mctrl,
|
|
|
|
.get_mctrl = sport_get_mctrl,
|
|
|
|
.stop_tx = sport_stop_tx,
|
|
|
|
.start_tx = sport_start_tx,
|
|
|
|
.stop_rx = sport_stop_rx,
|
|
|
|
.break_ctl = sport_break_ctl,
|
|
|
|
.startup = sport_startup,
|
|
|
|
.shutdown = sport_shutdown,
|
|
|
|
.set_termios = sport_set_termios,
|
|
|
|
.type = sport_type,
|
|
|
|
.release_port = sport_release_port,
|
|
|
|
.request_port = sport_request_port,
|
|
|
|
.config_port = sport_config_port,
|
|
|
|
.verify_port = sport_verify_port,
|
|
|
|
};
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
#define BFIN_SPORT_UART_MAX_PORTS 4
|
|
|
|
|
|
|
|
static struct sport_uart_port *bfin_sport_uart_ports[BFIN_SPORT_UART_MAX_PORTS];
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE
|
2010-03-10 01:25:32 +08:00
|
|
|
#define CLASS_BFIN_SPORT_CONSOLE "bfin-sport-console"
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
static int __init
|
|
|
|
sport_uart_console_setup(struct console *co, char *options)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up;
|
|
|
|
int baud = 57600;
|
|
|
|
int bits = 8;
|
|
|
|
int parity = 'n';
|
2010-03-10 01:25:33 +08:00
|
|
|
# ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
int flow = 'r';
|
|
|
|
# else
|
2009-12-10 04:31:28 +08:00
|
|
|
int flow = 'n';
|
2010-03-10 01:25:33 +08:00
|
|
|
# endif
|
2009-12-10 04:31:28 +08:00
|
|
|
|
|
|
|
/* Check whether an invalid uart number has been specified */
|
|
|
|
if (co->index < 0 || co->index >= BFIN_SPORT_UART_MAX_PORTS)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
up = bfin_sport_uart_ports[co->index];
|
|
|
|
if (!up)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (options)
|
|
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
|
|
|
|
|
|
return uart_set_options(&up->port, co, baud, parity, bits, flow);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sport_uart_console_putchar(struct uart_port *port, int ch)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = (struct sport_uart_port *)port;
|
|
|
|
|
|
|
|
while (SPORT_GET_STAT(up) & TXF)
|
|
|
|
barrier();
|
|
|
|
|
|
|
|
tx_one_byte(up, ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interrupts are disabled on entering
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
sport_uart_console_write(struct console *co, const char *s, unsigned int count)
|
|
|
|
{
|
|
|
|
struct sport_uart_port *up = bfin_sport_uart_ports[co->index];
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&up->port.lock, flags);
|
|
|
|
|
|
|
|
if (SPORT_GET_TCR1(up) & TSPEN)
|
|
|
|
uart_console_write(&up->port, s, count, sport_uart_console_putchar);
|
|
|
|
else {
|
|
|
|
/* dummy data to start sport */
|
|
|
|
while (SPORT_GET_STAT(up) & TXF)
|
|
|
|
barrier();
|
|
|
|
SPORT_PUT_TX(up, 0xffff);
|
|
|
|
/* Enable transmit, then an interrupt will generated */
|
|
|
|
SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN));
|
|
|
|
SSYNC();
|
|
|
|
|
|
|
|
uart_console_write(&up->port, s, count, sport_uart_console_putchar);
|
|
|
|
|
|
|
|
/* Although the hold register is empty, last byte is still in shift
|
|
|
|
* register and not sent out yet. So, put a dummy data into TX FIFO.
|
|
|
|
* Then, sport tx stops when last byte is shift out and the dummy
|
|
|
|
* data is moved into the shift register.
|
|
|
|
*/
|
|
|
|
while (SPORT_GET_STAT(up) & TXF)
|
|
|
|
barrier();
|
|
|
|
SPORT_PUT_TX(up, 0xffff);
|
|
|
|
while (!(SPORT_GET_STAT(up) & TXHRE))
|
|
|
|
barrier();
|
|
|
|
|
|
|
|
/* Stop sport tx transfer */
|
|
|
|
SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) & ~TSPEN));
|
|
|
|
SSYNC();
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
2009-12-10 04:31:28 +08:00
|
|
|
|
|
|
|
spin_unlock_irqrestore(&up->port.lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct uart_driver sport_uart_reg;
|
|
|
|
|
|
|
|
static struct console sport_uart_console = {
|
|
|
|
.name = DEVICE_NAME,
|
|
|
|
.write = sport_uart_console_write,
|
|
|
|
.device = uart_console_device,
|
|
|
|
.setup = sport_uart_console_setup,
|
|
|
|
.flags = CON_PRINTBUFFER,
|
|
|
|
.index = -1,
|
|
|
|
.data = &sport_uart_reg,
|
2008-04-30 15:52:12 +08:00
|
|
|
};
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
#define SPORT_UART_CONSOLE (&sport_uart_console)
|
|
|
|
#else
|
|
|
|
#define SPORT_UART_CONSOLE NULL
|
|
|
|
#endif /* CONFIG_SERIAL_BFIN_SPORT_CONSOLE */
|
|
|
|
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
static struct uart_driver sport_uart_reg = {
|
|
|
|
.owner = THIS_MODULE,
|
2009-12-10 04:31:28 +08:00
|
|
|
.driver_name = DRV_NAME,
|
|
|
|
.dev_name = DEVICE_NAME,
|
2008-04-30 15:52:12 +08:00
|
|
|
.major = 204,
|
|
|
|
.minor = 84,
|
2009-12-10 04:31:28 +08:00
|
|
|
.nr = BFIN_SPORT_UART_MAX_PORTS,
|
|
|
|
.cons = SPORT_UART_CONSOLE,
|
2008-04-30 15:52:12 +08:00
|
|
|
};
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int sport_uart_suspend(struct device *dev)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
struct sport_uart_port *sport = dev_get_drvdata(dev);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
dev_dbg(dev, "%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
if (sport)
|
|
|
|
uart_suspend_port(&sport_uart_reg, &sport->port);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
static int sport_uart_resume(struct device *dev)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
struct sport_uart_port *sport = dev_get_drvdata(dev);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
dev_dbg(dev, "%s enter\n", __func__);
|
2008-04-30 15:52:12 +08:00
|
|
|
if (sport)
|
|
|
|
uart_resume_port(&sport_uart_reg, &sport->port);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
static struct dev_pm_ops bfin_sport_uart_dev_pm_ops = {
|
|
|
|
.suspend = sport_uart_suspend,
|
|
|
|
.resume = sport_uart_resume,
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2012-11-20 02:21:50 +08:00
|
|
|
static int sport_uart_probe(struct platform_device *pdev)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
struct resource *res;
|
|
|
|
struct sport_uart_port *sport;
|
|
|
|
int ret = 0;
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
dev_dbg(&pdev->dev, "%s enter\n", __func__);
|
|
|
|
|
|
|
|
if (pdev->id < 0 || pdev->id >= BFIN_SPORT_UART_MAX_PORTS) {
|
|
|
|
dev_err(&pdev->dev, "Wrong sport uart platform device id.\n");
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bfin_sport_uart_ports[pdev->id] == NULL) {
|
|
|
|
bfin_sport_uart_ports[pdev->id] =
|
2010-03-10 01:25:35 +08:00
|
|
|
kzalloc(sizeof(struct sport_uart_port), GFP_KERNEL);
|
2009-12-10 04:31:28 +08:00
|
|
|
sport = bfin_sport_uart_ports[pdev->id];
|
|
|
|
if (!sport) {
|
|
|
|
dev_err(&pdev->dev,
|
2010-03-10 01:25:35 +08:00
|
|
|
"Fail to malloc sport_uart_port\n");
|
2009-12-10 04:31:28 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2013-09-09 13:09:20 +08:00
|
|
|
ret = peripheral_request_list(dev_get_platdata(&pdev->dev),
|
|
|
|
DRV_NAME);
|
2009-12-10 04:31:28 +08:00
|
|
|
if (ret) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Fail to request SPORT peripherals\n");
|
|
|
|
goto out_error_free_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_init(&sport->port.lock);
|
|
|
|
sport->port.fifosize = SPORT_TX_FIFO_SIZE,
|
|
|
|
sport->port.ops = &sport_uart_ops;
|
|
|
|
sport->port.line = pdev->id;
|
|
|
|
sport->port.iotype = UPIO_MEM;
|
|
|
|
sport->port.flags = UPF_BOOT_AUTOCONF;
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
if (res == NULL) {
|
|
|
|
dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto out_error_free_peripherals;
|
|
|
|
}
|
|
|
|
|
2010-05-11 15:10:23 +08:00
|
|
|
sport->port.membase = ioremap(res->start, resource_size(res));
|
2009-12-10 04:31:28 +08:00
|
|
|
if (!sport->port.membase) {
|
|
|
|
dev_err(&pdev->dev, "Cannot map sport IO\n");
|
|
|
|
ret = -ENXIO;
|
|
|
|
goto out_error_free_peripherals;
|
|
|
|
}
|
2010-03-10 01:25:31 +08:00
|
|
|
sport->port.mapbase = res->start;
|
2009-12-10 04:31:28 +08:00
|
|
|
|
|
|
|
sport->port.irq = platform_get_irq(pdev, 0);
|
2011-01-17 18:08:52 +08:00
|
|
|
if ((int)sport->port.irq < 0) {
|
2009-12-10 04:31:28 +08:00
|
|
|
dev_err(&pdev->dev, "No sport RX/TX IRQ specified\n");
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto out_error_unmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
sport->err_irq = platform_get_irq(pdev, 1);
|
|
|
|
if (sport->err_irq < 0) {
|
|
|
|
dev_err(&pdev->dev, "No sport status IRQ specified\n");
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto out_error_unmap;
|
|
|
|
}
|
2010-03-10 01:25:33 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
|
|
|
if (res == NULL)
|
|
|
|
sport->cts_pin = -1;
|
2014-06-16 21:17:10 +08:00
|
|
|
else
|
2010-03-10 01:25:33 +08:00
|
|
|
sport->cts_pin = res->start;
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IO, 1);
|
|
|
|
if (res == NULL)
|
|
|
|
sport->rts_pin = -1;
|
|
|
|
else
|
|
|
|
sport->rts_pin = res->start;
|
|
|
|
#endif
|
2009-12-10 04:31:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE
|
|
|
|
if (!is_early_platform_device(pdev)) {
|
|
|
|
#endif
|
|
|
|
sport = bfin_sport_uart_ports[pdev->id];
|
|
|
|
sport->port.dev = &pdev->dev;
|
|
|
|
dev_set_drvdata(&pdev->dev, sport);
|
|
|
|
ret = uart_add_one_port(&sport_uart_reg, &sport->port);
|
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (sport) {
|
|
|
|
out_error_unmap:
|
|
|
|
iounmap(sport->port.membase);
|
|
|
|
out_error_free_peripherals:
|
2013-09-09 13:09:20 +08:00
|
|
|
peripheral_free_list(dev_get_platdata(&pdev->dev));
|
2009-12-10 04:31:28 +08:00
|
|
|
out_error_free_mem:
|
|
|
|
kfree(sport);
|
|
|
|
bfin_sport_uart_ports[pdev->id] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2008-04-30 15:52:12 +08:00
|
|
|
}
|
|
|
|
|
2012-11-20 02:26:18 +08:00
|
|
|
static int sport_uart_remove(struct platform_device *pdev)
|
2008-04-30 15:52:12 +08:00
|
|
|
{
|
2009-12-10 04:31:28 +08:00
|
|
|
struct sport_uart_port *sport = platform_get_drvdata(pdev);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
dev_dbg(&pdev->dev, "%s enter\n", __func__);
|
|
|
|
dev_set_drvdata(&pdev->dev, NULL);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
if (sport) {
|
2008-04-30 15:52:12 +08:00
|
|
|
uart_remove_one_port(&sport_uart_reg, &sport->port);
|
2009-12-10 04:31:28 +08:00
|
|
|
iounmap(sport->port.membase);
|
2013-09-09 13:09:20 +08:00
|
|
|
peripheral_free_list(dev_get_platdata(&pdev->dev));
|
2009-12-10 04:31:28 +08:00
|
|
|
kfree(sport);
|
|
|
|
bfin_sport_uart_ports[pdev->id] = NULL;
|
|
|
|
}
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct platform_driver sport_uart_driver = {
|
|
|
|
.probe = sport_uart_probe,
|
2012-11-20 02:21:34 +08:00
|
|
|
.remove = sport_uart_remove,
|
2008-04-30 15:52:12 +08:00
|
|
|
.driver = {
|
|
|
|
.name = DRV_NAME,
|
2009-12-10 04:31:28 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
.pm = &bfin_sport_uart_dev_pm_ops,
|
|
|
|
#endif
|
2008-04-30 15:52:12 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE
|
2013-08-08 19:40:50 +08:00
|
|
|
static struct early_platform_driver early_sport_uart_driver __initdata = {
|
2010-03-10 01:25:32 +08:00
|
|
|
.class_str = CLASS_BFIN_SPORT_CONSOLE,
|
2009-12-10 04:31:28 +08:00
|
|
|
.pdrv = &sport_uart_driver,
|
|
|
|
.requested_id = EARLY_PLATFORM_ID_UNSET,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init sport_uart_rs_console_init(void)
|
|
|
|
{
|
|
|
|
early_platform_driver_register(&early_sport_uart_driver, DRV_NAME);
|
|
|
|
|
2010-03-10 01:25:32 +08:00
|
|
|
early_platform_driver_probe(CLASS_BFIN_SPORT_CONSOLE,
|
|
|
|
BFIN_SPORT_UART_MAX_PORTS, 0);
|
2009-12-10 04:31:28 +08:00
|
|
|
|
|
|
|
register_console(&sport_uart_console);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
console_initcall(sport_uart_rs_console_init);
|
|
|
|
#endif
|
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
static int __init sport_uart_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2010-03-10 01:25:31 +08:00
|
|
|
pr_info("Blackfin uart over sport driver\n");
|
2009-12-10 04:31:28 +08:00
|
|
|
|
2008-04-30 15:52:12 +08:00
|
|
|
ret = uart_register_driver(&sport_uart_reg);
|
2009-12-10 04:31:28 +08:00
|
|
|
if (ret) {
|
|
|
|
pr_err("failed to register %s:%d\n",
|
2008-04-30 15:52:12 +08:00
|
|
|
sport_uart_reg.driver_name, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = platform_driver_register(&sport_uart_driver);
|
2009-12-10 04:31:28 +08:00
|
|
|
if (ret) {
|
|
|
|
pr_err("failed to register sport uart driver:%d\n", ret);
|
2008-04-30 15:52:12 +08:00
|
|
|
uart_unregister_driver(&sport_uart_reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2009-12-10 04:31:28 +08:00
|
|
|
module_init(sport_uart_init);
|
2008-04-30 15:52:12 +08:00
|
|
|
|
|
|
|
static void __exit sport_uart_exit(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&sport_uart_driver);
|
|
|
|
uart_unregister_driver(&sport_uart_reg);
|
|
|
|
}
|
|
|
|
module_exit(sport_uart_exit);
|
|
|
|
|
2009-12-10 04:31:28 +08:00
|
|
|
MODULE_AUTHOR("Sonic Zhang, Roy Huang");
|
|
|
|
MODULE_DESCRIPTION("Blackfin serial over SPORT driver");
|
2008-04-30 15:52:12 +08:00
|
|
|
MODULE_LICENSE("GPL");
|