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", },  	{},  | 
