diff options
Diffstat (limited to 'drivers/tty/goldfish.c')
| -rw-r--r-- | drivers/tty/goldfish.c | 360 |
1 files changed, 253 insertions, 107 deletions
diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index f17d2e4ee2ca..3a9582029005 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -1,20 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2007 Google, Inc. * Copyright (C) 2012 Intel, Inc. - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * + * Copyright (C) 2017 Imagination Technologies Ltd. */ #include <linux/console.h> -#include <linux/init.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/tty.h> @@ -22,20 +13,25 @@ #include <linux/slab.h> #include <linux/io.h> #include <linux/module.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_CMD_INT_DISABLE = 0, - GOLDFISH_TTY_CMD_INT_ENABLE = 1, - GOLDFISH_TTY_CMD_WRITE_BUFFER = 2, - GOLDFISH_TTY_CMD_READ_BUFFER = 3, -}; +#include <linux/mod_devicetable.h> +#include <linux/goldfish.h> +#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; @@ -44,6 +40,8 @@ struct goldfish_tty { u32 irq; int opencount; struct console console; + u32 version; + struct device *dev; }; static DEFINE_MUTEX(goldfish_tty_lock); @@ -52,61 +50,125 @@ 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, + size_t count, bool 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); - writel((u32)buf, base + GOLDFISH_TTY_DATA_PTR); - 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); + gf_iowrite32(count, base + GOLDFISH_TTY_REG_DATA_LEN); + + if (is_write) + gf_iowrite32(GOLDFISH_TTY_CMD_WRITE_BUFFER, + base + GOLDFISH_TTY_REG_CMD); + else + gf_iowrite32(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, + size_t count, bool 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 u8 *buf, size_t count) +{ + struct goldfish_tty *qtty = &goldfish_ttys[line]; + + goldfish_tty_rw(qtty, (unsigned long)buf, count, true); +} + static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id) { - struct platform_device *pdev = dev_id; - struct goldfish_tty *qtty = &goldfish_ttys[pdev->id]; + struct goldfish_tty *qtty = dev_id; void __iomem *base = qtty->base; - unsigned long irq_flags; - unsigned char *buf; + u8 *buf; u32 count; - count = readl(base + GOLDFISH_TTY_BYTES_READY); - if(count == 0) + count = gf_ioread32(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); - writel((u32)buf, base + GOLDFISH_TTY_DATA_PTR); - writel(count, base + GOLDFISH_TTY_DATA_LEN); - writel(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_CMD); - spin_unlock_irqrestore(&qtty->lock, irq_flags); - tty_schedule_flip(&qtty->port); + + goldfish_tty_rw(qtty, (unsigned long)buf, count, false); + + tty_flip_buffer_push(&qtty->port); return IRQ_HANDLED; } 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); + struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, + port); + gf_iowrite32(GOLDFISH_TTY_CMD_INT_ENABLE, qtty->base + GOLDFISH_TTY_REG_CMD); return 0; } 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); + struct goldfish_tty *qtty = container_of(port, struct goldfish_tty, + port); + gf_iowrite32(GOLDFISH_TTY_CMD_INT_DISABLE, qtty->base + GOLDFISH_TTY_REG_CMD); } -static int goldfish_tty_open(struct tty_struct * tty, struct file * filp) +static int goldfish_tty_open(struct tty_struct *tty, struct file *filp) { struct goldfish_tty *qtty = &goldfish_ttys[tty->index]; return tty_port_open(&qtty->port, tty, filp); } -static void goldfish_tty_close(struct tty_struct * tty, struct file * filp) +static void goldfish_tty_close(struct tty_struct *tty, struct file *filp) { tty_port_close(tty->port, tty, filp); } @@ -116,30 +178,33 @@ static void goldfish_tty_hangup(struct tty_struct *tty) tty_port_hangup(tty->port); } -static int goldfish_tty_write(struct tty_struct * tty, const unsigned char *buf, int count) +static ssize_t goldfish_tty_write(struct tty_struct *tty, const u8 *buf, + size_t count) { goldfish_tty_do_write(tty->index, buf, count); return count; } -static int goldfish_tty_write_room(struct tty_struct *tty) +static unsigned int goldfish_tty_write_room(struct tty_struct *tty) { return 0x10000; } -static int goldfish_tty_chars_in_buffer(struct tty_struct *tty) +static unsigned 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 gf_ioread32(base + GOLDFISH_TTY_REG_BYTES_READY); } -static void goldfish_tty_console_write(struct console *co, const char *b, unsigned count) +static void goldfish_tty_console_write(struct console *co, const char *b, + unsigned count) { goldfish_tty_do_write(co->index, b, count); } -static struct tty_driver *goldfish_tty_console_device(struct console *c, int *index) +static struct tty_driver *goldfish_tty_console_device(struct console *c, + int *index) { *index = c->index; return goldfish_tty_driver; @@ -147,19 +212,19 @@ static struct tty_driver *goldfish_tty_console_device(struct console *c, int *in static int goldfish_tty_console_setup(struct console *co, char *options) { - if((unsigned)co->index > goldfish_tty_line_count) + if ((unsigned)co->index >= goldfish_tty_line_count) return -ENODEV; - if(goldfish_ttys[co->index].base == 0) + if (!goldfish_ttys[co->index].base) return -ENODEV; return 0; } -static struct tty_port_operations goldfish_port_ops = { +static const struct tty_port_operations goldfish_port_ops = { .activate = goldfish_tty_activate, .shutdown = goldfish_tty_shutdown }; -static struct tty_operations goldfish_tty_ops = { +static const struct tty_operations goldfish_tty_ops = { .open = goldfish_tty_open, .close = goldfish_tty_close, .hangup = goldfish_tty_hangup, @@ -173,33 +238,36 @@ static int goldfish_tty_create_driver(void) int ret; struct tty_driver *tty; - goldfish_ttys = kzalloc(sizeof(*goldfish_ttys) * goldfish_tty_line_count, GFP_KERNEL); - if(goldfish_ttys == NULL) { + goldfish_ttys = kcalloc(goldfish_tty_line_count, + sizeof(*goldfish_ttys), + GFP_KERNEL); + if (goldfish_ttys == NULL) { ret = -ENOMEM; goto err_alloc_goldfish_ttys_failed; } - tty = alloc_tty_driver(goldfish_tty_line_count); - if(tty == NULL) { - ret = -ENOMEM; - goto err_alloc_tty_driver_failed; + tty = tty_alloc_driver(goldfish_tty_line_count, + TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(tty)) { + ret = PTR_ERR(tty); + goto err_tty_alloc_driver_failed; } tty->driver_name = "goldfish"; tty->name = "ttyGF"; tty->type = TTY_DRIVER_TYPE_SERIAL; tty->subtype = SERIAL_TYPE_NORMAL; tty->init_termios = tty_std_termios; - tty->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; tty_set_operations(tty, &goldfish_tty_ops); ret = tty_register_driver(tty); - if(ret) + if (ret) goto err_tty_register_driver_failed; goldfish_tty_driver = tty; return 0; err_tty_register_driver_failed: - put_tty_driver(tty); -err_alloc_tty_driver_failed: + tty_driver_kref_put(tty); +err_tty_alloc_driver_failed: kfree(goldfish_ttys); goldfish_ttys = NULL; err_alloc_goldfish_ttys_failed: @@ -209,7 +277,7 @@ err_alloc_goldfish_ttys_failed: static void goldfish_tty_delete_driver(void) { tty_unregister_driver(goldfish_tty_driver); - put_tty_driver(goldfish_tty_driver); + tty_driver_kref_put(goldfish_tty_driver); goldfish_tty_driver = NULL; kfree(goldfish_ttys); goldfish_ttys = NULL; @@ -218,55 +286,98 @@ static void goldfish_tty_delete_driver(void) static int goldfish_tty_probe(struct platform_device *pdev) { struct goldfish_tty *qtty; - int ret = -EINVAL; - int i; + int ret = -ENODEV; struct resource *r; struct device *ttydev; void __iomem *base; - u32 irq; + int irq; + 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) + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; goto err_unmap; + } - irq = r->start; + mutex_lock(&goldfish_tty_lock); - if(pdev->id >= goldfish_tty_line_count) - goto err_unmap; + if (pdev->id == PLATFORM_DEVID_NONE) + line = goldfish_tty_current_line_count; + else + line = pdev->id; - mutex_lock(&goldfish_tty_lock); - if(goldfish_tty_current_line_count == 0) { + 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; + if (ret) + goto err_unlock; } goldfish_tty_current_line_count++; - qtty = &goldfish_ttys[pdev->id]; + qtty = &goldfish_ttys[line]; spin_lock_init(&qtty->lock); tty_port_init(&qtty->port); 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 = gf_ioread32(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); - - ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED, "goldfish_tty", pdev); - if(ret) - goto err_request_irq_failed; + gf_iowrite32(GOLDFISH_TTY_CMD_INT_DISABLE, base + GOLDFISH_TTY_REG_CMD); + ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED, + "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, - pdev->id, &pdev->dev); - if(IS_ERR(ttydev)) { + line, &pdev->dev); + if (IS_ERR(ttydev)) { ret = PTR_ERR(ttydev); goto err_tty_register_device_failed; } @@ -276,53 +387,88 @@ static int goldfish_tty_probe(struct platform_device *pdev) qtty->console.device = goldfish_tty_console_device; qtty->console.setup = goldfish_tty_console_setup; qtty->console.flags = CON_PRINTBUFFER; - qtty->console.index = pdev->id; + qtty->console.index = line; register_console(&qtty->console); + platform_set_drvdata(pdev, qtty); mutex_unlock(&goldfish_tty_lock); return 0; - tty_unregister_device(goldfish_tty_driver, i); err_tty_register_device_failed: - free_irq(irq, pdev); -err_request_irq_failed: + free_irq(irq, qtty); +err_dec_line_count: + tty_port_destroy(&qtty->port); goldfish_tty_current_line_count--; - if(goldfish_tty_current_line_count == 0) + 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); return ret; } -static int goldfish_tty_remove(struct platform_device *pdev) +static void goldfish_tty_remove(struct platform_device *pdev) { - struct goldfish_tty *qtty; + struct goldfish_tty *qtty = platform_get_drvdata(pdev); mutex_lock(&goldfish_tty_lock); - qtty = &goldfish_ttys[pdev->id]; unregister_console(&qtty->console); - tty_unregister_device(goldfish_tty_driver, pdev->id); + tty_unregister_device(goldfish_tty_driver, qtty->console.index); iounmap(qtty->base); - qtty->base = 0; - free_irq(qtty->irq, pdev); + qtty->base = NULL; + free_irq(qtty->irq, qtty); + tty_port_destroy(&qtty->port); goldfish_tty_current_line_count--; - if(goldfish_tty_current_line_count == 0) + if (goldfish_tty_current_line_count == 0) goldfish_tty_delete_driver(); mutex_unlock(&goldfish_tty_lock); +} + +#ifdef CONFIG_GOLDFISH_TTY_EARLY_CONSOLE +static void gf_early_console_putchar(struct uart_port *port, unsigned char ch) +{ + gf_iowrite32(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); +#endif + +static const struct of_device_id goldfish_tty_of_match[] = { + { .compatible = "google,goldfish-tty", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, goldfish_tty_of_match); + static struct platform_driver goldfish_tty_platform_driver = { .probe = goldfish_tty_probe, .remove = goldfish_tty_remove, .driver = { - .name = "goldfish_tty" + .name = "goldfish_tty", + .of_match_table = goldfish_tty_of_match, } }; module_platform_driver(goldfish_tty_platform_driver); +MODULE_DESCRIPTION("Goldfish TTY Driver"); MODULE_LICENSE("GPL v2"); |
