|
@@ -22,6 +22,8 @@
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
#include <linux/goldfish.h>
|
|
#include <linux/goldfish.h>
|
|
|
|
+#include <linux/mm.h>
|
|
|
|
+#include <linux/dma-mapping.h>
|
|
|
|
|
|
/* Goldfish tty register's offsets */
|
|
/* Goldfish tty register's offsets */
|
|
#define GOLDFISH_TTY_REG_BYTES_READY 0x04
|
|
#define GOLDFISH_TTY_REG_BYTES_READY 0x04
|
|
@@ -29,6 +31,7 @@
|
|
#define GOLDFISH_TTY_REG_DATA_PTR 0x10
|
|
#define GOLDFISH_TTY_REG_DATA_PTR 0x10
|
|
#define GOLDFISH_TTY_REG_DATA_LEN 0x14
|
|
#define GOLDFISH_TTY_REG_DATA_LEN 0x14
|
|
#define GOLDFISH_TTY_REG_DATA_PTR_HIGH 0x18
|
|
#define GOLDFISH_TTY_REG_DATA_PTR_HIGH 0x18
|
|
|
|
+#define GOLDFISH_TTY_REG_VERSION 0x20
|
|
|
|
|
|
/* Goldfish tty commands */
|
|
/* Goldfish tty commands */
|
|
#define GOLDFISH_TTY_CMD_INT_DISABLE 0
|
|
#define GOLDFISH_TTY_CMD_INT_DISABLE 0
|
|
@@ -43,6 +46,8 @@ struct goldfish_tty {
|
|
u32 irq;
|
|
u32 irq;
|
|
int opencount;
|
|
int opencount;
|
|
struct console console;
|
|
struct console console;
|
|
|
|
+ u32 version;
|
|
|
|
+ struct device *dev;
|
|
};
|
|
};
|
|
|
|
|
|
static DEFINE_MUTEX(goldfish_tty_lock);
|
|
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 u32 goldfish_tty_current_line_count;
|
|
static struct goldfish_tty *goldfish_ttys;
|
|
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;
|
|
unsigned long irq_flags;
|
|
- struct goldfish_tty *qtty = &goldfish_ttys[line];
|
|
|
|
void __iomem *base = qtty->base;
|
|
void __iomem *base = qtty->base;
|
|
|
|
+
|
|
spin_lock_irqsave(&qtty->lock, irq_flags);
|
|
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);
|
|
base + GOLDFISH_TTY_REG_DATA_PTR_HIGH);
|
|
writel(count, base + GOLDFISH_TTY_REG_DATA_LEN);
|
|
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);
|
|
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)
|
|
static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
|
|
{
|
|
{
|
|
struct goldfish_tty *qtty = dev_id;
|
|
struct goldfish_tty *qtty = dev_id;
|
|
void __iomem *base = qtty->base;
|
|
void __iomem *base = qtty->base;
|
|
- unsigned long irq_flags;
|
|
|
|
|
|
+ unsigned long address;
|
|
unsigned char *buf;
|
|
unsigned char *buf;
|
|
u32 count;
|
|
u32 count;
|
|
|
|
|
|
@@ -77,12 +153,10 @@ static irqreturn_t goldfish_tty_interrupt(int irq, void *dev_id)
|
|
return IRQ_NONE;
|
|
return IRQ_NONE;
|
|
|
|
|
|
count = tty_prepare_flip_string(&qtty->port, &buf, count);
|
|
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);
|
|
tty_schedule_flip(&qtty->port);
|
|
return IRQ_HANDLED;
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
@@ -225,7 +299,7 @@ static void goldfish_tty_delete_driver(void)
|
|
static int goldfish_tty_probe(struct platform_device *pdev)
|
|
static int goldfish_tty_probe(struct platform_device *pdev)
|
|
{
|
|
{
|
|
struct goldfish_tty *qtty;
|
|
struct goldfish_tty *qtty;
|
|
- int ret = -EINVAL;
|
|
|
|
|
|
+ int ret = -ENODEV;
|
|
struct resource *r;
|
|
struct resource *r;
|
|
struct device *ttydev;
|
|
struct device *ttydev;
|
|
void __iomem *base;
|
|
void __iomem *base;
|
|
@@ -233,16 +307,22 @@ static int goldfish_tty_probe(struct platform_device *pdev)
|
|
unsigned int line;
|
|
unsigned int line;
|
|
|
|
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
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);
|
|
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);
|
|
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;
|
|
goto err_unmap;
|
|
|
|
+ }
|
|
|
|
|
|
irq = r->start;
|
|
irq = r->start;
|
|
|
|
|
|
@@ -253,13 +333,17 @@ static int goldfish_tty_probe(struct platform_device *pdev)
|
|
else
|
|
else
|
|
line = pdev->id;
|
|
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) {
|
|
if (goldfish_tty_current_line_count == 0) {
|
|
ret = goldfish_tty_create_driver();
|
|
ret = goldfish_tty_create_driver();
|
|
if (ret)
|
|
if (ret)
|
|
- goto err_create_driver_failed;
|
|
|
|
|
|
+ goto err_unlock;
|
|
}
|
|
}
|
|
goldfish_tty_current_line_count++;
|
|
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->port.ops = &goldfish_port_ops;
|
|
qtty->base = base;
|
|
qtty->base = base;
|
|
qtty->irq = irq;
|
|
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);
|
|
writel(GOLDFISH_TTY_CMD_INT_DISABLE, base + GOLDFISH_TTY_REG_CMD);
|
|
|
|
|
|
ret = request_irq(irq, goldfish_tty_interrupt, IRQF_SHARED,
|
|
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,
|
|
ttydev = tty_port_register_device(&qtty->port, goldfish_tty_driver,
|
|
- line, &pdev->dev);
|
|
|
|
|
|
+ line, &pdev->dev);
|
|
if (IS_ERR(ttydev)) {
|
|
if (IS_ERR(ttydev)) {
|
|
ret = PTR_ERR(ttydev);
|
|
ret = PTR_ERR(ttydev);
|
|
goto err_tty_register_device_failed;
|
|
goto err_tty_register_device_failed;
|
|
@@ -299,11 +411,11 @@ static int goldfish_tty_probe(struct platform_device *pdev)
|
|
|
|
|
|
err_tty_register_device_failed:
|
|
err_tty_register_device_failed:
|
|
free_irq(irq, qtty);
|
|
free_irq(irq, qtty);
|
|
-err_request_irq_failed:
|
|
|
|
|
|
+err_dec_line_count:
|
|
goldfish_tty_current_line_count--;
|
|
goldfish_tty_current_line_count--;
|
|
if (goldfish_tty_current_line_count == 0)
|
|
if (goldfish_tty_current_line_count == 0)
|
|
goldfish_tty_delete_driver();
|
|
goldfish_tty_delete_driver();
|
|
-err_create_driver_failed:
|
|
|
|
|
|
+err_unlock:
|
|
mutex_unlock(&goldfish_tty_lock);
|
|
mutex_unlock(&goldfish_tty_lock);
|
|
err_unmap:
|
|
err_unmap:
|
|
iounmap(base);
|
|
iounmap(base);
|