diff options
Diffstat (limited to 'drivers/tty/goldfish.c')
-rw-r--r-- | drivers/tty/goldfish.c | 234 |
1 files changed, 185 insertions, 49 deletions
diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index 996bd473dd03..381e981dee06 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2007 Google, Inc. * Copyright (C) 2012 Intel, Inc. + * Copyright (C) 2017 Imagination Technologies Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -22,21 +23,23 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/goldfish.h> - -enum { - GOLDFISH_TTY_PUT_CHAR = 0x00, - GOLDFISH_TTY_BYTES_READY = 0x04, - GOLDFISH_TTY_CMD = 0x08, - - GOLDFISH_TTY_DATA_PTR = 0x10, - GOLDFISH_TTY_DATA_LEN = 0x14, - GOLDFISH_TTY_DATA_PTR_HIGH = 0x18, - - GOLDFISH_TTY_CMD_INT_DISABLE = 0, - GOLDFISH_TTY_CMD_INT_ENABLE = 1, - GOLDFISH_TTY_CMD_WRITE_BUFFER = 2, - GOLDFISH_TTY_CMD_READ_BUFFER = 3, -}; +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/serial_core.h> + +/* Goldfish tty register's offsets */ +#define GOLDFISH_TTY_REG_BYTES_READY 0x04 +#define GOLDFISH_TTY_REG_CMD 0x08 +#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 +#define GOLDFISH_TTY_CMD_INT_ENABLE 1 +#define GOLDFISH_TTY_CMD_WRITE_BUFFER 2 +#define GOLDFISH_TTY_CMD_READ_BUFFER 3 struct goldfish_tty { struct tty_port port; @@ -45,6 +48,8 @@ struct goldfish_tty { u32 irq; int opencount; struct console console; + u32 version; + struct device *dev; }; static DEFINE_MUTEX(goldfish_tty_lock); @@ -53,38 +58,107 @@ 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_DATA_PTR, - base + GOLDFISH_TTY_DATA_PTR_HIGH); - writel(count, base + GOLDFISH_TTY_DATA_LEN); - writel(GOLDFISH_TTY_CMD_WRITE_BUFFER, base + GOLDFISH_TTY_CMD); + 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); + + 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; - count = readl(base + GOLDFISH_TTY_BYTES_READY); + count = readl(base + GOLDFISH_TTY_REG_BYTES_READY); if (count == 0) 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_DATA_PTR, - base + GOLDFISH_TTY_DATA_PTR_HIGH); - writel(count, base + GOLDFISH_TTY_DATA_LEN); - writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_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; } @@ -93,7 +167,7 @@ static int goldfish_tty_activate(struct tty_port *port, struct tty_struct *tty) { struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, port); - writel(GOLDFISH_TTY_CMD_INT_ENABLE, qtty->base + GOLDFISH_TTY_CMD); + writel(GOLDFISH_TTY_CMD_INT_ENABLE, qtty->base + GOLDFISH_TTY_REG_CMD); return 0; } @@ -101,7 +175,7 @@ static void goldfish_tty_shutdown(struct tty_port *port) { struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, port); - writel(GOLDFISH_TTY_CMD_INT_DISABLE, qtty->base + GOLDFISH_TTY_CMD); + writel(GOLDFISH_TTY_CMD_INT_DISABLE, qtty->base + GOLDFISH_TTY_REG_CMD); } static int goldfish_tty_open(struct tty_struct *tty, struct file *filp) @@ -136,7 +210,7 @@ static int goldfish_tty_chars_in_buffer(struct tty_struct *tty) { struct goldfish_tty *qtty = &goldfish_ttys[tty->index]; void __iomem *base = qtty->base; - return readl(base + GOLDFISH_TTY_BYTES_READY); + return readl(base + GOLDFISH_TTY_REG_BYTES_READY); } static void goldfish_tty_console_write(struct console *co, const char *b, @@ -227,7 +301,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; @@ -235,16 +309,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; @@ -255,13 +335,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++; @@ -271,17 +355,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_CMD); + 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; @@ -301,11 +413,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); @@ -330,6 +442,30 @@ static int goldfish_tty_remove(struct platform_device *pdev) return 0; } +static void gf_early_console_putchar(struct uart_port *port, int ch) +{ + __raw_writel(ch, port->membase); +} + +static void gf_early_write(struct console *con, const char *s, unsigned int n) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, s, n, gf_early_console_putchar); +} + +static int __init gf_earlycon_setup(struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = gf_early_write; + return 0; +} + +OF_EARLYCON_DECLARE(early_gf_tty, "google,goldfish-tty", gf_earlycon_setup); + static const struct of_device_id goldfish_tty_of_match[] = { { .compatible = "google,goldfish-tty", }, {}, |