// SPDX-License-Identifier: GPL-2.0+
/*
* Blackfin On-Chip Sport Emulated UART Driver
*
* Copyright 2006-2009 Analog Devices Inc.
*
* Enter bugs at http://blackfin.uclinux.org/
*/
/*
* This driver and the hardware supported are in term of EE-191 of ADI.
* http://www.analog.com/static/imported-files/application_notes/EE191.pdf
* This application note describe how to implement a UART on a Sharc DSP,
* but this driver is implemented on Blackfin Processor.
* Transmit Frame Sync is not used by this driver to transfer data out.
*/
/* #define DEBUG */
#define DRV_NAME "bfin-sport-uart"
#define DEVICE_NAME "ttySS"
#define pr_fmt(fmt) DRV_NAME ": " fmt
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/gpio.h>
#include <asm/bfin_sport.h>
#include <asm/delay.h>
#include <asm/portmux.h>
#include "bfin_sport_uart.h"
struct sport_uart_port {
struct uart_port port;
int err_irq;
unsigned short csize;
unsigned short rxmask;
unsigned short txmask1;
unsigned short txmask2;
unsigned char stopb;
/* unsigned char parib; */
#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS
int cts_pin;
int rts_pin;
#endif
};
static int sport_uart_tx_chars(struct sport_uart_port *up);
static void sport_stop_tx(struct uart_port *port);
static inline void tx_one_byte(struct sport_uart_port *up, unsigned int value)
{
pr_debug("%s value:%x, mask1=0x%x, mask2=0x%x\n", __func__, value,
up->txmask1, up->txmask2);
/* Place Start and Stop bits */
__asm__ __volatile__ (
"%[val] <<= 1;"
"%[val] = %[val] & %[mask1];"
"%[val] = %[val] | %[mask2];"
: [val]"+d"(value)
: [mask1]"d"(up->txmask1), [mask2]"d"(up->txmask2)
: "ASTAT"
);
pr_debug("%s value:%x\n", __func__, value);
SPORT_PUT_TX(up, value);
}
static inline unsigned char rx_one_byte(struct sport_uart_port *up)
{
unsigned int value;
unsigned char extract;
u32 tmp_mask1, tmp_mask2, tmp_shift, tmp;
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);
/* Extract data */
__asm__ __volatile__ (
"%[extr] = 0;"
"%[mask1] = %[rxmask];"
"%[mask2] = 0x0200(Z);"
"%[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;"
: [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)
: "ASTAT", "LB0", "LC0", "LT0"
);
pr_debug(" extract:%x\n", extract);
return extract;
}
static int sport_uart_setup(struct sport_uart_port *up, int size, int baud_rate)
{
int tclkdiv, rclkdiv;
unsigned int sclk = get_sclk();
/* Set TCR1 and TCR2, TFSR is not enabled for uart */
SPORT_PUT_TCR1(up, (LATFS | ITFS | TFSR | TLSBIT | ITCLK));
SPORT_PUT_TCR2(up, size + 1);
pr_debug("%s TCR1:%x, TCR2:%x\n", __func__, SPORT_GET_TCR1(up), SPORT_GET_TCR2(up));
/* Set RCR1 and RCR2 */
SPORT_PUT_RCR1(up, (RCKFE | LARFS | LRFS | RFSR | IRCLK));
SPORT_PUT_RCR2(up, (size + 1) * 2 - 1);
pr_debug("%s RCR1:%x, RCR2:%x\n", __func__, SPORT_GET_RCR1(up), SPORT_GET_RCR2(up));
tclkdiv = sclk / (2 * baud_rate) - 1;
/* 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;
SPORT_PUT_TCLKDIV(up, tclkdiv);
SPORT_PUT_RCLKDIV(up, rclkdiv);
SSYNC();
pr_debug("%s sclk:%d, baud_rate:%d, tclkdiv:%d, rclkdiv:%d\n",
__func__, sclk, baud_rate, tclkdiv, rclkdiv);
return 0;
}
static irqreturn_t sport_uart_rx_irq(int irq, void *dev_id)
{
struct sport_uart_port *up = dev_id;
struct tty_port *port = &up->port.state->port;
unsigned int ch;
spin_lock(&up->port.lock);
while<