aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/tty/goldfish.c166
1 files changed, 139 insertions, 27 deletions
diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c
index 011eb5322077..757e17ff7aa4 100644
--- a/drivers/tty/goldfish.c
+++ b/drivers/tty/goldfish.c
@@ -22,6 +22,8 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/goldfish.h>
+#include <linux/mm.h>
+#include <linux/dma-mapping.h>
/* Goldfish tty register's offsets */
#define GOLDFISH_TTY_REG_BYTES_READY 0x04
@@ -29,6 +31,7 @@
#define GOLDFISH_TTY_REG_DATA_PTR 0x10
#define GOLDFISH_TTY_REG_DATA_LEN 0x14
#define GOLDFISH_TTY_REG_DATA_PTR_HIGH 0x18
+#define GOLDFISH_TTY_REG_VERSION 0x20
/* Goldfish tty commands */
#define GOLDFISH_TTY_CMD_INT_DISABLE 0
@@ -43,6 +46,8 @@ struct goldfish_tty {
u32 irq;
int opencount;
struct console console;
+ u32 version;
+ struct device *dev;
};
static DEFINE_MUTEX(goldfish_tty_lock);
@@ -51,24 +56,95 @@ static u32 goldfish_tty_line_count = 8;
static u32 goldfish_tty_current_line_count;
static struct goldfish_tty *goldfish_ttys;
-static void goldfish_tty_do_write(int line, const char *buf, unsigned count)
+static void do_rw_io(struct goldfish_tty *qtty,
+ unsigned long address,
+ unsigned int count,
+ int is_write)
{
unsigned long irq_flags;
- struct goldfish_tty *qtty = &goldfish_ttys[line];
void __iomem *base = qtty->base;
+
spin_lock_irqsave(&qtty->lock, irq_flags);
- gf_write_ptr(buf, base + GOLDFISH_TTY_REG_DATA_PTR,
+ gf_write_ptr((void *)address, base + GOLDFISH_TTY_REG_DATA_PTR,
base + GOLDFISH_TTY_REG_DATA_PTR_HIGH);
writel(count, base + GOLDFISH_TTY_REG_DATA_LEN);
- writel(GOLDFISH_TTY_CMD_WRITE_BUFFER, base + GOLDFISH_TTY_REG_CMD);
+
+ if (is_write)
+ writel(GOLDFISH_TTY_CMD_WRITE_BUFFER,
+ base + GOLDFISH_TTY_REG_CMD);
+ else
+ writel(GOLDFISH_TTY_CMD_READ_BUFFER,
+ base + GOLDFISH_TTY_REG_CMD);
+
spin_unlock_irqrestore(&qtty->lock, irq_flags);
}
+static void goldfish_tty_rw(struct goldfish_tty *qtty,
+ unsigned long addr,
+ unsigned int count,
+ int is_write)
+{
+ dma_addr_t dma_handle;
+ enum dma_data_direction dma_dir;
+
+ dma_dir = (is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (qtty->version > 0) {
+ /*
+ * Goldfish TTY for Ranchu platform uses
+ * physical addresses and DMA for read/write operations
+ */
+ unsigned long addr_end = addr + count;
+
+ while (addr < addr_end) {
+ unsigned long pg_end = (addr & PAGE_MASK) + PAGE_SIZE;
+ unsigned long next =
+ pg_end < addr_end ? pg_end : addr_end;
+ unsigned long avail = next - addr;
+
+ /*
+ * Map the buffer's virtual address to the DMA address
+ * so the buffer can be accessed by the device.
+ */
+ dma_handle = dma_map_single(qtty->dev, (void *)addr,
+ avail, dma_dir);
+
+ if (dma_mapping_error(qtty->dev, dma_handle)) {
+ dev_err(qtty->dev, "tty: DMA mapping error.\n");
+ return;
+ }
+ do_rw_io(qtty, dma_handle, avail, is_write);
+
+ /*
+ * Unmap the previously mapped region after
+ * the completion of the read/write operation.
+ */
+ dma_unmap_single(qtty->dev, dma_handle, avail, dma_dir);
+
+ addr += avail;
+ }
+ } else {
+ /*
+ * Old style Goldfish TTY used on the Goldfish platform
+ * uses virtual addresses.
+ */
+ do_rw_io(qtty, addr, count, is_write);
+ }
+}
+
+static void goldfish_tty_do_write(int line, const char *buf,
+ unsigned int count)
+{
+ struct goldfish_tty *qtty = &goldfish_ttys[line];
+ unsigned long address = (unsigned long)(void *)buf;
+
+ goldfish_tty_rw(qtty, address, count, 1);
+}
+
static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
{
struct goldfish_tty *qtty = dev_id;
void __iomem *base = qtty->base;
- unsigned long irq_flags;
+ unsigned long address;
unsigned char *buf;
u32 count;
@@ -77,12 +153,10 @@ static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
return IRQ_NONE;
count = tty_prepare_flip_string(&qtty->port, &buf, count);
- spin_lock_irqsave(&qtty->lock, irq_flags);
- gf_write_ptr(buf, base + GOLDFISH_TTY_REG_DATA_PTR,
- base + GOLDFISH_TTY_REG_DATA_PTR_HIGH);
- writel(count, base + GOLDFISH_TTY_REG_DATA_LEN);
- writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_REG_CMD);
- spin_unlock_irqrestore(&qtty->lock, irq_flags);
+
+ address = (unsigned long)(void *)buf;
+ goldfish_tty_rw(qtty, address, count, 0);
+
tty_schedule_flip(&qtty->port);
return IRQ_HANDLED;
}
@@ -225,7 +299,7 @@ static void goldfish_tty_delete_driver(void)
static int goldfish_tty_probe(struct platform_device *pdev)
{
struct goldfish_tty *qtty;
- int ret = -EINVAL;
+ int ret = -ENODEV;
struct resource *r;
struct device *ttydev;
void __iomem *base;
@@ -233,16 +307,22 @@ static int goldfish_tty_probe(struct platform_device *pdev)
unsigned int line;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (r == NULL)
- return -EINVAL;
+ if (!r) {
+ pr_err("goldfish_tty: No MEM resource available!\n");
+ return -ENOMEM;
+ }
base = ioremap(r->start, 0x1000);
- if (base == NULL)
- pr_err("goldfish_tty: unable to remap base\n");
+ if (!base) {
+ pr_err("goldfish_tty: Unable to ioremap base!\n");
+ return -ENOMEM;
+ }
r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (r == NULL)
+ if (!r) {
+ pr_err("goldfish_tty: No IRQ resource available!\n");
goto err_unmap;
+ }
irq = r->start;
@@ -253,13 +333,17 @@ static int goldfish_tty_probe(struct platform_device *pdev)
else
line = pdev->id;
- if (line >= goldfish_tty_line_count)
- goto err_create_driver_failed;
+ if (line >= goldfish_tty_line_count) {
+ pr_err("goldfish_tty: Reached maximum tty number of %d.\n",
+ goldfish_tty_current_line_count);
+ ret = -ENOMEM;
+ goto err_unlock;
+ }
if (goldfish_tty_current_line_count == 0) {
ret = goldfish_tty_create_driver();
if (ret)
- goto err_create_driver_failed;
+ goto err_unlock;
}
goldfish_tty_current_line_count++;
@@ -269,17 +353,45 @@ static int goldfish_tty_probe(struct platform_device *pdev)
qtty->port.ops = &goldfish_port_ops;
qtty->base = base;
qtty->irq = irq;
+ qtty->dev = &pdev->dev;
+
+ /*
+ * Goldfish TTY device used by the Goldfish emulator
+ * should identify itself with 0, forcing the driver
+ * to use virtual addresses. Goldfish TTY device
+ * on Ranchu emulator (qemu2) returns 1 here and
+ * driver will use physical addresses.
+ */
+ qtty->version = readl(base + GOLDFISH_TTY_REG_VERSION);
+
+ /*
+ * Goldfish TTY device on Ranchu emulator (qemu2)
+ * will use DMA for read/write IO operations.
+ */
+ if (qtty->version > 0) {
+ /*
+ * Initialize dma_mask to 32-bits.
+ */
+ if (!pdev->dev.dma_mask)
+ pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
+ ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(&pdev->dev, "No suitable DMA available.\n");
+ goto err_dec_line_count;
+ }
+ }
writel(GOLDFISH_TTY_CMD_INT_DISABLE, base + GOLDFISH_TTY_REG_CMD);
ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED,
- "goldfish_tty", qtty);
- if (ret)
- goto err_request_irq_failed;
-
+ "goldfish_tty", qtty);
+ if (ret) {
+ pr_err("goldfish_tty: No IRQ available!\n");
+ goto err_dec_line_count;
+ }
ttydev = tty_port_register_device(&qtty->port, goldfish_tty_driver,
- line, &pdev->dev);
+ line, &pdev->dev);
if (IS_ERR(ttydev)) {
ret = PTR_ERR(ttydev);
goto err_tty_register_device_failed;
@@ -299,11 +411,11 @@ static int goldfish_tty_probe(struct platform_device *pdev)
err_tty_register_device_failed:
free_irq(irq, qtty);
-err_request_irq_failed:
+err_dec_line_count:
goldfish_tty_current_line_count--;
if (goldfish_tty_current_line_count == 0)
goldfish_tty_delete_driver();
-err_create_driver_failed:
+err_unlock:
mutex_unlock(&goldfish_tty_lock);
err_unmap:
iounmap(base);