c74997bdfe
Enable the TX/RX FIFOs present on UARTs in Ingenic SoCs. FIFO sizes vary per device so match these based on the OF compatible string Enabling the FIFOs permits much faster transfer with lower CPU overhead. Tested on Ingenic JZ4780 on the MIPS Ci20 Creator board Signed-off-by: Matt Redfearn <matt.redfearn@imgtec.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
346 lines
8.6 KiB
C
346 lines
8.6 KiB
C
/*
|
|
* Copyright (C) 2010 Lars-Peter Clausen <lars@metafoo.de>
|
|
* Copyright (C) 2015 Imagination Technologies
|
|
*
|
|
* Ingenic SoC UART support
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.,
|
|
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/console.h>
|
|
#include <linux/io.h>
|
|
#include <linux/libfdt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_fdt.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/serial_8250.h>
|
|
#include <linux/serial_core.h>
|
|
#include <linux/serial_reg.h>
|
|
|
|
#include "8250.h"
|
|
|
|
/** ingenic_uart_config: SOC specific config data. */
|
|
struct ingenic_uart_config {
|
|
int tx_loadsz;
|
|
int fifosize;
|
|
};
|
|
|
|
struct ingenic_uart_data {
|
|
struct clk *clk_module;
|
|
struct clk *clk_baud;
|
|
int line;
|
|
};
|
|
|
|
static const struct of_device_id of_match[];
|
|
|
|
#define UART_FCR_UME BIT(4)
|
|
|
|
#define UART_MCR_MDCE BIT(7)
|
|
#define UART_MCR_FCM BIT(6)
|
|
|
|
static struct earlycon_device *early_device;
|
|
|
|
static uint8_t __init early_in(struct uart_port *port, int offset)
|
|
{
|
|
return readl(port->membase + (offset << 2));
|
|
}
|
|
|
|
static void __init early_out(struct uart_port *port, int offset, uint8_t value)
|
|
{
|
|
writel(value, port->membase + (offset << 2));
|
|
}
|
|
|
|
static void __init ingenic_early_console_putc(struct uart_port *port, int c)
|
|
{
|
|
uint8_t lsr;
|
|
|
|
do {
|
|
lsr = early_in(port, UART_LSR);
|
|
} while ((lsr & UART_LSR_TEMT) == 0);
|
|
|
|
early_out(port, UART_TX, c);
|
|
}
|
|
|
|
static void __init ingenic_early_console_write(struct console *console,
|
|
const char *s, unsigned int count)
|
|
{
|
|
uart_console_write(&early_device->port, s, count,
|
|
ingenic_early_console_putc);
|
|
}
|
|
|
|
static void __init ingenic_early_console_setup_clock(struct earlycon_device *dev)
|
|
{
|
|
void *fdt = initial_boot_params;
|
|
const __be32 *prop;
|
|
int offset;
|
|
|
|
offset = fdt_path_offset(fdt, "/ext");
|
|
if (offset < 0)
|
|
return;
|
|
|
|
prop = fdt_getprop(fdt, offset, "clock-frequency", NULL);
|
|
if (!prop)
|
|
return;
|
|
|
|
dev->port.uartclk = be32_to_cpup(prop);
|
|
}
|
|
|
|
static int __init ingenic_early_console_setup(struct earlycon_device *dev,
|
|
const char *opt)
|
|
{
|
|
struct uart_port *port = &dev->port;
|
|
unsigned int baud, divisor;
|
|
|
|
if (!dev->port.membase)
|
|
return -ENODEV;
|
|
|
|
ingenic_early_console_setup_clock(dev);
|
|
|
|
baud = dev->baud ?: 115200;
|
|
divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * baud);
|
|
|
|
early_out(port, UART_IER, 0);
|
|
early_out(port, UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8);
|
|
early_out(port, UART_DLL, 0);
|
|
early_out(port, UART_DLM, 0);
|
|
early_out(port, UART_LCR, UART_LCR_WLEN8);
|
|
early_out(port, UART_FCR, UART_FCR_UME | UART_FCR_CLEAR_XMIT |
|
|
UART_FCR_CLEAR_RCVR | UART_FCR_ENABLE_FIFO);
|
|
early_out(port, UART_MCR, UART_MCR_RTS | UART_MCR_DTR);
|
|
|
|
early_out(port, UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8);
|
|
early_out(port, UART_DLL, divisor & 0xff);
|
|
early_out(port, UART_DLM, (divisor >> 8) & 0xff);
|
|
early_out(port, UART_LCR, UART_LCR_WLEN8);
|
|
|
|
early_device = dev;
|
|
dev->con->write = ingenic_early_console_write;
|
|
|
|
return 0;
|
|
}
|
|
|
|
EARLYCON_DECLARE(jz4740_uart, ingenic_early_console_setup);
|
|
OF_EARLYCON_DECLARE(jz4740_uart, "ingenic,jz4740-uart",
|
|
ingenic_early_console_setup);
|
|
|
|
EARLYCON_DECLARE(jz4775_uart, ingenic_early_console_setup);
|
|
OF_EARLYCON_DECLARE(jz4775_uart, "ingenic,jz4775-uart",
|
|
ingenic_early_console_setup);
|
|
|
|
EARLYCON_DECLARE(jz4780_uart, ingenic_early_console_setup);
|
|
OF_EARLYCON_DECLARE(jz4780_uart, "ingenic,jz4780-uart",
|
|
ingenic_early_console_setup);
|
|
|
|
static void ingenic_uart_serial_out(struct uart_port *p, int offset, int value)
|
|
{
|
|
int ier;
|
|
|
|
switch (offset) {
|
|
case UART_FCR:
|
|
/* UART module enable */
|
|
value |= UART_FCR_UME;
|
|
break;
|
|
|
|
case UART_IER:
|
|
/* Enable receive timeout interrupt with the
|
|
* receive line status interrupt */
|
|
value |= (value & 0x4) << 2;
|
|
break;
|
|
|
|
case UART_MCR:
|
|
/* If we have enabled modem status IRQs we should enable modem
|
|
* mode. */
|
|
ier = p->serial_in(p, UART_IER);
|
|
|
|
if (ier & UART_IER_MSI)
|
|
value |= UART_MCR_MDCE | UART_MCR_FCM;
|
|
else
|
|
value &= ~(UART_MCR_MDCE | UART_MCR_FCM);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
writeb(value, p->membase + (offset << p->regshift));
|
|
}
|
|
|
|
static unsigned int ingenic_uart_serial_in(struct uart_port *p, int offset)
|
|
{
|
|
unsigned int value;
|
|
|
|
value = readb(p->membase + (offset << p->regshift));
|
|
|
|
/* Hide non-16550 compliant bits from higher levels */
|
|
switch (offset) {
|
|
case UART_FCR:
|
|
value &= ~UART_FCR_UME;
|
|
break;
|
|
|
|
case UART_MCR:
|
|
value &= ~(UART_MCR_MDCE | UART_MCR_FCM);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static int ingenic_uart_probe(struct platform_device *pdev)
|
|
{
|
|
struct uart_8250_port uart = {};
|
|
struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
struct ingenic_uart_data *data;
|
|
const struct ingenic_uart_config *cdata;
|
|
const struct of_device_id *match;
|
|
int err, line;
|
|
|
|
match = of_match_device(of_match, &pdev->dev);
|
|
if (!match) {
|
|
dev_err(&pdev->dev, "Error: No device match found\n");
|
|
return -ENODEV;
|
|
}
|
|
cdata = match->data;
|
|
|
|
if (!regs || !irq) {
|
|
dev_err(&pdev->dev, "no registers/irq defined\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&uart.port.lock);
|
|
uart.port.type = PORT_16550A;
|
|
uart.port.flags = UPF_SKIP_TEST | UPF_IOREMAP | UPF_FIXED_TYPE;
|
|
uart.port.iotype = UPIO_MEM;
|
|
uart.port.mapbase = regs->start;
|
|
uart.port.regshift = 2;
|
|
uart.port.serial_out = ingenic_uart_serial_out;
|
|
uart.port.serial_in = ingenic_uart_serial_in;
|
|
uart.port.irq = irq->start;
|
|
uart.port.dev = &pdev->dev;
|
|
uart.port.fifosize = cdata->fifosize;
|
|
uart.tx_loadsz = cdata->tx_loadsz;
|
|
uart.capabilities = UART_CAP_FIFO | UART_CAP_RTOIE;
|
|
|
|
/* Check for a fixed line number */
|
|
line = of_alias_get_id(pdev->dev.of_node, "serial");
|
|
if (line >= 0)
|
|
uart.port.line = line;
|
|
|
|
uart.port.membase = devm_ioremap(&pdev->dev, regs->start,
|
|
resource_size(regs));
|
|
if (!uart.port.membase)
|
|
return -ENOMEM;
|
|
|
|
data->clk_module = devm_clk_get(&pdev->dev, "module");
|
|
if (IS_ERR(data->clk_module)) {
|
|
err = PTR_ERR(data->clk_module);
|
|
if (err != -EPROBE_DEFER)
|
|
dev_err(&pdev->dev,
|
|
"unable to get module clock: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
data->clk_baud = devm_clk_get(&pdev->dev, "baud");
|
|
if (IS_ERR(data->clk_baud)) {
|
|
err = PTR_ERR(data->clk_baud);
|
|
if (err != -EPROBE_DEFER)
|
|
dev_err(&pdev->dev,
|
|
"unable to get baud clock: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = clk_prepare_enable(data->clk_module);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "could not enable module clock: %d\n", err);
|
|
goto out;
|
|
}
|
|
|
|
err = clk_prepare_enable(data->clk_baud);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "could not enable baud clock: %d\n", err);
|
|
goto out_disable_moduleclk;
|
|
}
|
|
uart.port.uartclk = clk_get_rate(data->clk_baud);
|
|
|
|
data->line = serial8250_register_8250_port(&uart);
|
|
if (data->line < 0) {
|
|
err = data->line;
|
|
goto out_disable_baudclk;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, data);
|
|
return 0;
|
|
|
|
out_disable_baudclk:
|
|
clk_disable_unprepare(data->clk_baud);
|
|
out_disable_moduleclk:
|
|
clk_disable_unprepare(data->clk_module);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int ingenic_uart_remove(struct platform_device *pdev)
|
|
{
|
|
struct ingenic_uart_data *data = platform_get_drvdata(pdev);
|
|
|
|
serial8250_unregister_port(data->line);
|
|
clk_disable_unprepare(data->clk_module);
|
|
clk_disable_unprepare(data->clk_baud);
|
|
return 0;
|
|
}
|
|
|
|
static const struct ingenic_uart_config jz4740_uart_config = {
|
|
.tx_loadsz = 8,
|
|
.fifosize = 16,
|
|
};
|
|
|
|
static const struct ingenic_uart_config jz4760_uart_config = {
|
|
.tx_loadsz = 16,
|
|
.fifosize = 32,
|
|
};
|
|
|
|
static const struct ingenic_uart_config jz4780_uart_config = {
|
|
.tx_loadsz = 32,
|
|
.fifosize = 64,
|
|
};
|
|
|
|
static const struct of_device_id of_match[] = {
|
|
{ .compatible = "ingenic,jz4740-uart", .data = &jz4740_uart_config },
|
|
{ .compatible = "ingenic,jz4760-uart", .data = &jz4760_uart_config },
|
|
{ .compatible = "ingenic,jz4775-uart", .data = &jz4760_uart_config },
|
|
{ .compatible = "ingenic,jz4780-uart", .data = &jz4780_uart_config },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, of_match);
|
|
|
|
static struct platform_driver ingenic_uart_platform_driver = {
|
|
.driver = {
|
|
.name = "ingenic-uart",
|
|
.of_match_table = of_match,
|
|
},
|
|
.probe = ingenic_uart_probe,
|
|
.remove = ingenic_uart_remove,
|
|
};
|
|
|
|
module_platform_driver(ingenic_uart_platform_driver);
|
|
|
|
MODULE_AUTHOR("Paul Burton");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Ingenic SoC UART driver");
|