blob: 85c351ecd3b4378f607bd4c185a65dbe1c776809 [file] [log] [blame]
/*
* Copyright (c) 2013 Qualcomm Atheros, Inc.
*
* 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
*/
/*
* Atheros High Speed UART driver
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/byteorder.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/bitops.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/serial_core.h>
#include <linux/console.h>
#include <atheros.h>
typedef struct {
struct tty_driver *tty;
char buf[1024];
unsigned put,
get,
inited;
} ath_hs_uart_softc_t;
ath_hs_uart_softc_t ath_hs_uart_softc;
void ath_hs_uart_put(uint8_t byte);
void ath_hs_uart_init(void);
static int ath_hs_baud = ATH_HS_UART_BAUD;
static int __init ath_hs_uart_baud_setup(char *str)
{
ath_hs_uart_softc.inited = 0;
get_option(&str, &ath_hs_baud);
ath_hs_uart_init(); // redo the setup
return 0;
}
early_param("hsbaud", ath_hs_uart_baud_setup);
void ath_hs_uart_init(void)
{
u_int32_t data, serial_clk, clk_step, clk_scale;
if (ath_hs_uart_softc.inited) {
return;
}
if (ath_hs_baud < 115200 || ath_hs_baud > 3000000) {
printk( "\nInvalid baudrate (%d) for HS UART. Setting "
__stringify(ATH_HS_UART_BAUD) "\n", ath_hs_baud);
ath_hs_baud = ATH_HS_UART_BAUD;
}
/*
* Formula to calculate clk_step and clk_scale
* temp = (((long)serial_clk)*1310)/131072;
* clk_scale = (temp / (baud_rate));
* temp = (131072*((long)baud_rate));
* clk_step = (temp / (serial_clk)) * (clk_scale + 1);
*/
// UART1 Out of Reset
ath_reg_wr(ATH_RESET,
ath_reg_rd(ATH_RESET) & ~RST_RESET_UART1_RESET_MASK);
// Set UART to operate on 100 MHz
ath_reg_rmw_set(SWITCH_CLOCK_SPARE_ADDRESS,
SWITCH_CLOCK_SPARE_UART1_CLK_SEL_SET(1));
serial_clk = ATH_HS_SERIAL_CLOCK * 1000 * 1000;
//clk_scale = ((serial_clk / 128k) * 1310) / baudrate
clk_scale = ((serial_clk >> 17) * 1310) / ath_hs_baud;
//clk_step = ((128k * 115200 * (clk_scale + 1)) / serial_clk)
// Splitting 128K as 128 * 1024
clk_step = ((128 * (ath_hs_baud / 100) * (clk_scale + 1)) << 10)
/ (serial_clk / 100);
ath_reg_wr(ATH_HS_UART_INT_EN, 0x1);
ath_gpio_set_fn(ATH_HS_UART_TD_GPIO, ATH_HS_UART_TD);
ath_gpio_set_fn(ATH_HS_UART_CTS_GPIO, ATH_HS_UART_RTS);
ath_gpio_config_output(ATH_HS_UART_TD_GPIO);
ath_gpio_config_output(ATH_HS_UART_RTS_GPIO);
ath_gpio_config_input(ATH_HS_UART_RD_GPIO);
ath_gpio_config_input(ATH_HS_UART_CTS_GPIO);
// Enabling UART1_RD - 22, UART1_CTS - 18 as inputs
ath_reg_rmw_clear(GPIO_IN_ENABLE9_ADDRESS,
GPIO_IN_ENABLE9_UART1_CTS_MASK |
GPIO_IN_ENABLE9_UART1_RD_MASK);
ath_reg_rmw_set(GPIO_IN_ENABLE9_ADDRESS,
GPIO_IN_ENABLE9_UART1_CTS_SET(ATH_HS_UART_CTS_GPIO) |
GPIO_IN_ENABLE9_UART1_RD_SET(ATH_HS_UART_RD_GPIO));
// GPIO_IN_ENABLE9
// CLOCK Settings
data = ath_reg_rd(ATH_HS_UART_CLK);
data = (data & 0xff000000) | clk_step | (clk_scale << 16);
ath_reg_wr(ATH_HS_UART_CLK, data);
ath_reg_wr(ATH_HS_UART_CS, 0x2188);
ath_hs_uart_softc.inited = 1;
ath_sys_frequency();
printk("step 0x%x scale 0x%x for %d\n", clk_step, clk_scale, ath_hs_baud);
}
void ath_hs_uart_put(uint8_t byte)
{
u_int32_t tx_data;
if (!ath_hs_uart_softc.inited) {
ath_hs_uart_init();
}
do {
tx_data = ath_reg_rd(UART1_REG_ADDRESS); // UART DATA Reg
} while ((tx_data & 0x200) != 0x200);
tx_data = byte | 0x200;
ath_reg_wr(UART1_REG_ADDRESS, tx_data);
//tx_data = ath_reg_rd(UART1_REG_ADDRESS);
}
uint8_t ath_hs_uart_get_poll(void)
{
uint8_t ch;
u_int32_t rx_data;
do {
rx_data = ath_reg_rd(UART1_REG_ADDRESS); // UART DATA Reg
} while ((rx_data & 0x100) != 0x100);
ch = rx_data & 0xff;
ath_reg_wr(UART1_REG_ADDRESS, 0x100);
return ch;
}
// Set this to 1 to get console prints on HS UART
#define ATH_HS_UART_CONSOLE 0
#if !defined(CONFIG_SERIAL_8250) || (ATH_HS_UART_CONSOLE == 1)
static void
ath_hs_uart_console_write(struct console *co, const char *s, unsigned int count)
{
unsigned int i;
for (i = 0; i < count; i++, s++) {
if (*s == '\n')
ath_hs_uart_put('\r');
ath_hs_uart_put(*s);
}
}
static int __init ath_hs_uart_console_setup(struct console *co, char *options)
{
return 0;
}
static int ath_hs_uart_console_early_setup(void)
{
return update_console_cmdline("uart", 0, "ttyS", 0, NULL);
}
struct tty_driver *ath_hs_uart_console_device(struct console *co, int *index)
{
*index = 0;
return ((ath_hs_uart_softc_t *)co->data)->tty;
}
static struct console ath_hs_uart_console = {
.name = "ttyS",
.write = ath_hs_uart_console_write,
.device = ath_hs_uart_console_device,
.setup = ath_hs_uart_console_setup,
.early_setup = ath_hs_uart_console_early_setup,
.flags = CON_PRINTBUFFER | CON_ENABLED | CON_CONSDEV,
.index = -1,
.data = &ath_hs_uart_softc,
};
#endif /* !defined(CONFIG_SERIAL_8250) || (ATH_HS_UART_CONSOLE == 1) */
static int ath_hs_uart_open(struct tty_struct *tty, struct file *filp)
{
if (tty->index >= tty->driver->num)
return -ENODEV;
return 0;
}
static void ath_hs_uart_close(struct tty_struct *tty, struct file *filp)
{
return;
}
#define hs_get_char(sc) \
({ \
unsigned char c = \
sc->buf[sc->get % sizeof(sc->buf)]; \
sc->get ++; \
c; \
})
#define hs_set_char(sc, c) \
do { \
sc->buf[sc->put % sizeof(sc->buf)] = c; \
sc->put ++; \
} while(0)
static int ath_hs_uart_put_char(struct tty_struct *tty, unsigned char ch)
{
ath_hs_uart_softc_t *sc = tty->driver->driver_state;
hs_set_char(sc, ch);
return 1;
}
static int ath_hs_uart_chars_in_buffer(struct tty_struct *tty)
{
ath_hs_uart_softc_t *sc = tty->driver->driver_state;
return sc->put - sc->get;
}
static int ath_hs_uart_write_room(struct tty_struct *tty)
{
ath_hs_uart_softc_t *sc = tty->driver->driver_state;
return sizeof(sc->buf) - ath_hs_uart_chars_in_buffer(tty);
}
static void ath_hs_uart_flush_chars(struct tty_struct *tty)
{
ath_hs_uart_softc_t *sc = tty->driver->driver_state;
while (sc->get < sc->put) {
ath_hs_uart_put(hs_get_char(sc));
}
return;
}
static int
ath_hs_uart_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case TCFLSH:
tty_buffer_flush(tty);
break;
case TCGETS:
case TCSETS:
case TCXONC:
case TCSETSW:
// allow the default handler to take care
return -ENOIOCTLCMD;
default:
printk("%s: cmd = 0x%x\n", __func__, cmd);
}
return 0;
}
static int ath_hs_uart_break_ctl(struct tty_struct *tty, int break_state)
{
printk("%s called\n", __func__);
return 0;
}
static void ath_hs_uart_wait_until_sent(struct tty_struct *tty, int timeout)
{
//printk("%s called\n", __func__);
udelay(100);
return;
}
static int ath_hs_uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
int i;
ath_hs_uart_softc_t *sc = tty->driver->driver_state;
if (count > (i = ath_hs_uart_write_room(tty))) {
count = i;
}
for (i = 0; i < count; i++) {
hs_set_char(sc, buf[i]);
}
ath_hs_uart_flush_chars(tty);
tty_wakeup(tty);
return i;
}
static const struct tty_operations ath_hs_uart_ops = {
.open = ath_hs_uart_open,
.close = ath_hs_uart_close,
.write = ath_hs_uart_write,
.put_char = ath_hs_uart_put_char,
.flush_chars = ath_hs_uart_flush_chars,
.write_room = ath_hs_uart_write_room,
.chars_in_buffer= ath_hs_uart_chars_in_buffer,
.ioctl = ath_hs_uart_ioctl,
.break_ctl = ath_hs_uart_break_ctl,
.wait_until_sent= ath_hs_uart_wait_until_sent,
};
static irqreturn_t ath_hs_uart_isr(int irq, void *dev_id)
{
ath_hs_uart_softc_t *sc = dev_id;
uint8_t ch = ath_hs_uart_get_poll();
if (sc->tty->ttys[0]) {
tty_insert_flip_char(sc->tty->ttys[0], ch, TTY_NORMAL);
ath_reg_wr(ATH_HS_UART_INT_STATUS, 0xffffffff);
tty_flip_buffer_push(sc->tty->ttys[0]);
} else {
ath_reg_wr(ATH_HS_UART_INT_STATUS, 0xffffffff);
}
return IRQ_HANDLED;
}
#ifdef CONFIG_SERIAL_8250
# define ATH_HS_UART_MINOR (64 + CONFIG_SERIAL_8250_NR_UARTS)
#else
# define ATH_HS_UART_MINOR 64
#endif
#define ATH_HS_UART_NR_UARTS 1
static int __init ath_hs_uart_user_init(void)
{
int ret;
struct tty_driver *tty;
if (!ath_hs_uart_softc.inited) {
return 0;
}
printk("Serial: Atheros High-Speed UART (%d)\n", ath_hs_baud);
tty = alloc_tty_driver(1);
tty->owner = THIS_MODULE;
tty->driver_name = "Atheros hs-uart";
tty->num = ATH_HS_UART_NR_UARTS;
tty->name = "ttyS";
tty->major = TTY_MAJOR;
tty->minor_start = ATH_HS_UART_MINOR;
tty->type = TTY_DRIVER_TYPE_SERIAL;
tty->subtype = SERIAL_TYPE_NORMAL;
tty->init_termios = tty_std_termios;
tty->init_termios.c_cflag = CS8 | CREAD | HUPCL | CLOCAL;
/* Update the c_cflag,c_ispeed,c_ospeed based on configured baudrate */
tty_termios_encode_baud_rate(&tty->init_termios, ath_hs_baud, ath_hs_baud);
tty->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
tty->driver_state = &ath_hs_uart_softc;
tty_set_operations(tty, &ath_hs_uart_ops);
ret = tty_register_driver(tty);
if (ret < 0) {
put_tty_driver(tty);
printk("tty_register_driver failed %d\n", ret);
return ret;
}
ath_hs_uart_softc.tty = tty;
if ((ret = request_irq(ATH_MISC_IRQ_HS_UART, ath_hs_uart_isr,
IRQF_SHARED, "hs-uart", &ath_hs_uart_softc)) < 0) {
printk("request irq failed (%d)\n", ret);
put_tty_driver(tty);
tty_unregister_driver(tty);
return ret;
}
return ret;
}
module_init(ath_hs_uart_user_init);
#if !defined(CONFIG_SERIAL_8250) || (ATH_HS_UART_CONSOLE == 1)
static int __init ath_hs_uart_console_init(void)
{
register_console(&ath_hs_uart_console);
return 0;
}
console_initcall(ath_hs_uart_console_init);
#endif /* !defined(CONFIG_SERIAL_8250) && (ATH_HS_UART_CONSOLE == 1) */