tty: add a DesignWare 8250 driver
The Synopsys DesignWare 8250 is an 8250 that has an extra interrupt that gets raised when writing to the LCR when busy. To handle this we need special serial_out, serial_in and handle_irq methods. Add a new platform driver that uses these accessors. Cc: Alan Cox <alan@linux.intel.com> Cc: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Jamie Iles <jamie@jamieiles.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
c7a1bdc5c9
commit
7d4008ebb1
4 changed files with 227 additions and 0 deletions
|
@ -0,0 +1,25 @@
|
|||
* Synopsys DesignWare ABP UART
|
||||
|
||||
Required properties:
|
||||
- compatible : "snps,dw-apb-uart"
|
||||
- reg : offset and length of the register set for the device.
|
||||
- interrupts : should contain uart interrupt.
|
||||
- clock-frequency : the input clock frequency for the UART.
|
||||
|
||||
Optional properties:
|
||||
- reg-shift : quantity to shift the register offsets by. If this property is
|
||||
not present then the register offsets are not shifted.
|
||||
- reg-io-width : the size (in bytes) of the IO accesses that should be
|
||||
performed on the device. If this property is not present then single byte
|
||||
accesses are used.
|
||||
|
||||
Example:
|
||||
|
||||
uart@80230000 {
|
||||
compatible = "snps,dw-apb-uart";
|
||||
reg = <0x80230000 0x100>;
|
||||
clock-frequency = <3686400>;
|
||||
interrupts = <10>;
|
||||
reg-shift = <2>;
|
||||
reg-io-width = <4>;
|
||||
};
|
194
drivers/tty/serial/8250_dw.c
Normal file
194
drivers/tty/serial/8250_dw.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Synopsys DesignWare 8250 driver.
|
||||
*
|
||||
* Copyright 2011 Picochip, Jamie Iles.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The Synopsys DesignWare 8250 has an extra feature whereby it detects if the
|
||||
* LCR is written whilst busy. If it is, then a busy detect interrupt is
|
||||
* raised, the LCR needs to be rewritten and the uart status register read.
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/serial_8250.h>
|
||||
#include <linux/serial_core.h>
|
||||
#include <linux/serial_reg.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct dw8250_data {
|
||||
int last_lcr;
|
||||
int line;
|
||||
};
|
||||
|
||||
static void dw8250_serial_out(struct uart_port *p, int offset, int value)
|
||||
{
|
||||
struct dw8250_data *d = p->private_data;
|
||||
|
||||
if (offset == UART_LCR)
|
||||
d->last_lcr = value;
|
||||
|
||||
offset <<= p->regshift;
|
||||
writeb(value, p->membase + offset);
|
||||
}
|
||||
|
||||
static unsigned int dw8250_serial_in(struct uart_port *p, int offset)
|
||||
{
|
||||
offset <<= p->regshift;
|
||||
|
||||
return readb(p->membase + offset);
|
||||
}
|
||||
|
||||
static void dw8250_serial_out32(struct uart_port *p, int offset, int value)
|
||||
{
|
||||
struct dw8250_data *d = p->private_data;
|
||||
|
||||
if (offset == UART_LCR)
|
||||
d->last_lcr = value;
|
||||
|
||||
offset <<= p->regshift;
|
||||
writel(value, p->membase + offset);
|
||||
}
|
||||
|
||||
static unsigned int dw8250_serial_in32(struct uart_port *p, int offset)
|
||||
{
|
||||
offset <<= p->regshift;
|
||||
|
||||
return readl(p->membase + offset);
|
||||
}
|
||||
|
||||
/* Offset for the DesignWare's UART Status Register. */
|
||||
#define UART_USR 0x1f
|
||||
|
||||
static int dw8250_handle_irq(struct uart_port *p)
|
||||
{
|
||||
struct dw8250_data *d = p->private_data;
|
||||
unsigned int iir = p->serial_in(p, UART_IIR);
|
||||
|
||||
if (serial8250_handle_irq(p, iir)) {
|
||||
return 1;
|
||||
} else if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
|
||||
/* Clear the USR and write the LCR again. */
|
||||
(void)p->serial_in(p, UART_USR);
|
||||
p->serial_out(p, d->last_lcr, UART_LCR);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit dw8250_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct uart_port port = {};
|
||||
struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
u32 val;
|
||||
struct dw8250_data *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;
|
||||
port.private_data = data;
|
||||
|
||||
spin_lock_init(&port.lock);
|
||||
port.mapbase = regs->start;
|
||||
port.irq = irq->start;
|
||||
port.handle_irq = dw8250_handle_irq;
|
||||
port.type = PORT_8250;
|
||||
port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF | UPF_IOREMAP |
|
||||
UPF_FIXED_PORT | UPF_FIXED_TYPE;
|
||||
port.dev = &pdev->dev;
|
||||
|
||||
port.iotype = UPIO_MEM;
|
||||
port.serial_in = dw8250_serial_in;
|
||||
port.serial_out = dw8250_serial_out;
|
||||
if (!of_property_read_u32(np, "reg-io-width", &val)) {
|
||||
switch (val) {
|
||||
case 1:
|
||||
break;
|
||||
case 4:
|
||||
port.iotype = UPIO_MEM32;
|
||||
port.serial_in = dw8250_serial_in32;
|
||||
port.serial_out = dw8250_serial_out32;
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "unsupported reg-io-width (%u)\n",
|
||||
val);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!of_property_read_u32(np, "reg-shift", &val))
|
||||
port.regshift = val;
|
||||
|
||||
if (of_property_read_u32(np, "clock-frequency", &val)) {
|
||||
dev_err(&pdev->dev, "no clock-frequency property set\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
port.uartclk = val;
|
||||
|
||||
data->line = serial8250_register_port(&port);
|
||||
if (data->line < 0)
|
||||
return data->line;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit dw8250_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dw8250_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
serial8250_unregister_port(data->line);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dw8250_match[] = {
|
||||
{ .compatible = "snps,dw-apb-uart" },
|
||||
{ /* Sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dw8250_match);
|
||||
|
||||
static struct platform_driver dw8250_platform_driver = {
|
||||
.driver = {
|
||||
.name = "dw-apb-uart",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = dw8250_match,
|
||||
},
|
||||
.probe = dw8250_probe,
|
||||
.remove = __devexit_p(dw8250_remove),
|
||||
};
|
||||
|
||||
static int __init dw8250_init(void)
|
||||
{
|
||||
return platform_driver_register(&dw8250_platform_driver);
|
||||
}
|
||||
module_init(dw8250_init);
|
||||
|
||||
static void __exit dw8250_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&dw8250_platform_driver);
|
||||
}
|
||||
module_exit(dw8250_exit);
|
||||
|
||||
MODULE_AUTHOR("Jamie Iles");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver");
|
|
@ -267,6 +267,13 @@ config SERIAL_8250_RM9K
|
|||
port hardware found on MIPS RM9122 and similar processors.
|
||||
If unsure, say N.
|
||||
|
||||
config SERIAL_8250_DW
|
||||
tristate "Support for Synopsys DesignWare 8250 quirks"
|
||||
depends on SERIAL_8250 && OF
|
||||
help
|
||||
Selecting this option will enable handling of the extra features
|
||||
present in the Synopsys DesignWare APB UART.
|
||||
|
||||
comment "Non-8250 serial port support"
|
||||
|
||||
config SERIAL_AMBA_PL010
|
||||
|
|
|
@ -28,6 +28,7 @@ obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_boca.o
|
|||
obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554) += 8250_exar_st16c554.o
|
||||
obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o
|
||||
obj-$(CONFIG_SERIAL_8250_MCA) += 8250_mca.o
|
||||
obj-$(CONFIG_SERIAL_8250_DW) += 8250_dw.o
|
||||
obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
|
||||
obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
|
||||
obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
|
||||
|
|
Loading…
Reference in a new issue