forked from Minki/linux
f5b556c94c
This makes the ath79 bootconsole behave the same way as the generic 8250 bootconsole. Also waiting for TEMT (transmit buffer is empty) instead of just THRE (transmit buffer is not full) ensures that all characters have been transmitted before the real serial driver starts reconfiguring the serial controller (which would sometimes result in garbage being transmitted.) This change does not cause a visible performance loss. In addition, this seems to fix a hang observed in certain configurations on many AR7xxx/AR9xxx SoCs during autoconfig of the real serial driver. A more complete follow-up patch will disable 8250 autoconfig for ath79 altogether (the serial controller is detected as a 16550A, which is not fully compatible with the ath79 serial, and the autoconfig may lead to undefined behavior on ath79.) Cc: <stable@vger.kernel.org> Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
102 lines
2.4 KiB
C
102 lines
2.4 KiB
C
/*
|
|
* Atheros AR7XXX/AR9XXX SoC early printk support
|
|
*
|
|
* Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org>
|
|
* Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/serial_reg.h>
|
|
#include <asm/addrspace.h>
|
|
|
|
#include <asm/mach-ath79/ath79.h>
|
|
#include <asm/mach-ath79/ar71xx_regs.h>
|
|
#include <asm/mach-ath79/ar933x_uart.h>
|
|
|
|
static void (*_prom_putchar) (unsigned char);
|
|
|
|
static inline void prom_putchar_wait(void __iomem *reg, u32 mask, u32 val)
|
|
{
|
|
u32 t;
|
|
|
|
do {
|
|
t = __raw_readl(reg);
|
|
if ((t & mask) == val)
|
|
break;
|
|
} while (1);
|
|
}
|
|
|
|
#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
|
|
|
|
static void prom_putchar_ar71xx(unsigned char ch)
|
|
{
|
|
void __iomem *base = (void __iomem *)(KSEG1ADDR(AR71XX_UART_BASE));
|
|
|
|
prom_putchar_wait(base + UART_LSR * 4, BOTH_EMPTY, BOTH_EMPTY);
|
|
__raw_writel(ch, base + UART_TX * 4);
|
|
prom_putchar_wait(base + UART_LSR * 4, BOTH_EMPTY, BOTH_EMPTY);
|
|
}
|
|
|
|
static void prom_putchar_ar933x(unsigned char ch)
|
|
{
|
|
void __iomem *base = (void __iomem *)(KSEG1ADDR(AR933X_UART_BASE));
|
|
|
|
prom_putchar_wait(base + AR933X_UART_DATA_REG, AR933X_UART_DATA_TX_CSR,
|
|
AR933X_UART_DATA_TX_CSR);
|
|
__raw_writel(AR933X_UART_DATA_TX_CSR | ch, base + AR933X_UART_DATA_REG);
|
|
prom_putchar_wait(base + AR933X_UART_DATA_REG, AR933X_UART_DATA_TX_CSR,
|
|
AR933X_UART_DATA_TX_CSR);
|
|
}
|
|
|
|
static void prom_putchar_dummy(unsigned char ch)
|
|
{
|
|
/* nothing to do */
|
|
}
|
|
|
|
static void prom_putchar_init(void)
|
|
{
|
|
void __iomem *base;
|
|
u32 id;
|
|
|
|
base = (void __iomem *)(KSEG1ADDR(AR71XX_RESET_BASE));
|
|
id = __raw_readl(base + AR71XX_RESET_REG_REV_ID);
|
|
id &= REV_ID_MAJOR_MASK;
|
|
|
|
switch (id) {
|
|
case REV_ID_MAJOR_AR71XX:
|
|
case REV_ID_MAJOR_AR7240:
|
|
case REV_ID_MAJOR_AR7241:
|
|
case REV_ID_MAJOR_AR7242:
|
|
case REV_ID_MAJOR_AR913X:
|
|
case REV_ID_MAJOR_AR9341:
|
|
case REV_ID_MAJOR_AR9342:
|
|
case REV_ID_MAJOR_AR9344:
|
|
case REV_ID_MAJOR_QCA9556:
|
|
case REV_ID_MAJOR_QCA9558:
|
|
_prom_putchar = prom_putchar_ar71xx;
|
|
break;
|
|
|
|
case REV_ID_MAJOR_AR9330:
|
|
case REV_ID_MAJOR_AR9331:
|
|
_prom_putchar = prom_putchar_ar933x;
|
|
break;
|
|
|
|
default:
|
|
_prom_putchar = prom_putchar_dummy;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void prom_putchar(unsigned char ch)
|
|
{
|
|
if (!_prom_putchar)
|
|
prom_putchar_init();
|
|
|
|
_prom_putchar(ch);
|
|
}
|