diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/platform/Makefile | 1 | ||||
-rw-r--r-- | drivers/platform/goldfish/Makefile | 4 | ||||
-rw-r--r-- | drivers/platform/goldfish/pdev_bus.c | 240 |
3 files changed, 245 insertions, 0 deletions
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index b17c16ce54ad..8a44a4cd6d1e 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_OLPC) += olpc/ +obj-$(CONFIG_GOLDFISH) += goldfish/ diff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile new file mode 100644 index 000000000000..6c591f6b2707 --- /dev/null +++ b/drivers/platform/goldfish/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for Goldfish platform specific drivers +# +obj-$(CONFIG_GOLDFISH) += pdev_bus.o diff --git a/drivers/platform/goldfish/pdev_bus.c b/drivers/platform/goldfish/pdev_bus.c new file mode 100644 index 000000000000..92cc4cfafde5 --- /dev/null +++ b/drivers/platform/goldfish/pdev_bus.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (C) 2011 Intel, Inc. + * Copyright (C) 2013 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. + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/io.h> + +#define PDEV_BUS_OP_DONE (0x00) +#define PDEV_BUS_OP_REMOVE_DEV (0x04) +#define PDEV_BUS_OP_ADD_DEV (0x08) + +#define PDEV_BUS_OP_INIT (0x00) + +#define PDEV_BUS_OP (0x00) +#define PDEV_BUS_GET_NAME (0x04) +#define PDEV_BUS_NAME_LEN (0x08) +#define PDEV_BUS_ID (0x0c) +#define PDEV_BUS_IO_BASE (0x10) +#define PDEV_BUS_IO_SIZE (0x14) +#define PDEV_BUS_IRQ (0x18) +#define PDEV_BUS_IRQ_COUNT (0x1c) + +struct pdev_bus_dev { + struct list_head list; + struct platform_device pdev; + struct resource resources[0]; +}; + +static void goldfish_pdev_worker(struct work_struct *work); + +static void __iomem *pdev_bus_base; +static unsigned long pdev_bus_addr; +static unsigned long pdev_bus_len; +static u32 pdev_bus_irq; +static LIST_HEAD(pdev_bus_new_devices); +static LIST_HEAD(pdev_bus_registered_devices); +static LIST_HEAD(pdev_bus_removed_devices); +static DECLARE_WORK(pdev_bus_worker, goldfish_pdev_worker); + + +static void goldfish_pdev_worker(struct work_struct *work) +{ + int ret; + struct pdev_bus_dev *pos, *n; + + list_for_each_entry_safe(pos, n, &pdev_bus_removed_devices, list) { + list_del(&pos->list); + platform_device_unregister(&pos->pdev); + kfree(pos); + } + list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) { + list_del(&pos->list); + ret = platform_device_register(&pos->pdev); + if (ret) + pr_err("goldfish_pdev_worker failed to register device, %s\n", + pos->pdev.name); + list_add_tail(&pos->list, &pdev_bus_registered_devices); + } +} + +static void goldfish_pdev_remove(void) +{ + struct pdev_bus_dev *pos, *n; + u32 base; + + base = readl(pdev_bus_base + PDEV_BUS_IO_BASE); + + list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) { + if (pos->resources[0].start == base) { + list_del(&pos->list); + kfree(pos); + return; + } + } + list_for_each_entry_safe(pos, n, &pdev_bus_registered_devices, list) { + if (pos->resources[0].start == base) { + list_del(&pos->list); + list_add_tail(&pos->list, &pdev_bus_removed_devices); + schedule_work(&pdev_bus_worker); + return; + } + }; + pr_err("goldfish_pdev_remove could not find device at %x\n", base); +} + +static int goldfish_new_pdev(void) +{ + struct pdev_bus_dev *dev; + u32 name_len; + u32 irq = -1, irq_count; + int resource_count = 2; + u32 base; + char *name; + + base = readl(pdev_bus_base + PDEV_BUS_IO_BASE); + + irq_count = readl(pdev_bus_base + PDEV_BUS_IRQ_COUNT); + name_len = readl(pdev_bus_base + PDEV_BUS_NAME_LEN); + if (irq_count) + resource_count++; + + dev = kzalloc(sizeof(*dev) + + sizeof(struct resource) * resource_count + + name_len + 1 + sizeof(*dev->pdev.dev.dma_mask), GFP_ATOMIC); + if (dev == NULL) + return -ENOMEM; + + dev->pdev.num_resources = resource_count; + dev->pdev.resource = (struct resource *)(dev + 1); + dev->pdev.name = name = (char *)(dev->pdev.resource + resource_count); + dev->pdev.dev.coherent_dma_mask = ~0; + dev->pdev.dev.dma_mask = (void *)(dev->pdev.name + name_len + 1); + *dev->pdev.dev.dma_mask = ~0; + + writel((unsigned long)name, pdev_bus_base + PDEV_BUS_GET_NAME); + name[name_len] = '\0'; + dev->pdev.id = readl(pdev_bus_base + PDEV_BUS_ID); + dev->pdev.resource[0].start = base; + dev->pdev.resource[0].end = base + + readl(pdev_bus_base + PDEV_BUS_IO_SIZE) - 1; + dev->pdev.resource[0].flags = IORESOURCE_MEM; + if (irq_count) { + irq = readl(pdev_bus_base + PDEV_BUS_IRQ); + dev->pdev.resource[1].start = irq; + dev->pdev.resource[1].end = irq + irq_count - 1; + dev->pdev.resource[1].flags = IORESOURCE_IRQ; + } + + pr_debug("goldfish_new_pdev %s at %x irq %d\n", name, base, irq); + list_add_tail(&dev->list, &pdev_bus_new_devices); + schedule_work(&pdev_bus_worker); + + return 0; +} + +static irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_NONE; + while (1) { + u32 op = readl(pdev_bus_base + PDEV_BUS_OP); + switch (op) { + case PDEV_BUS_OP_DONE: + return IRQ_NONE; + + case PDEV_BUS_OP_REMOVE_DEV: + goldfish_pdev_remove(); + break; + + case PDEV_BUS_OP_ADD_DEV: + goldfish_new_pdev(); + break; + } + ret = IRQ_HANDLED; + } + return ret; +} + +static int goldfish_pdev_bus_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) + return -EINVAL; + + pdev_bus_addr = r->start; + pdev_bus_len = resource_size(r); + + if (request_mem_region(pdev_bus_addr, pdev_bus_len, "goldfish")) { + dev_err(&pdev->dev, "unable to reserve Goldfish MMIO.\n"); + return -EBUSY; + } + + pdev_bus_base = ioremap(pdev_bus_addr, pdev_bus_len); + if (pdev_bus_base == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "unable to map Goldfish MMIO.\n"); + goto free_resources; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r == NULL) { + ret = -ENOENT; + goto free_map; + } + + pdev_bus_irq = r->start; + + ret = request_irq(pdev_bus_irq, goldfish_pdev_bus_interrupt, + IRQF_SHARED, "goldfish_pdev_bus", pdev); + if (ret) { + dev_err(&pdev->dev, "unable to request Goldfish IRQ\n"); + goto free_map; + } + + writel(PDEV_BUS_OP_INIT, pdev_bus_base + PDEV_BUS_OP); + return 0; + +free_map: + iounmap(pdev_bus_base); +free_resources: + release_mem_region(pdev_bus_addr, pdev_bus_len); + return ret; +} + +static int goldfish_pdev_bus_remove(struct platform_device *pdev) +{ + iounmap(pdev_bus_base); + free_irq(pdev_bus_irq, pdev); + release_mem_region(pdev_bus_addr, pdev_bus_len); + return 0; +} + +static struct platform_driver goldfish_pdev_bus_driver = { + .probe = goldfish_pdev_bus_probe, + .remove = goldfish_pdev_bus_remove, + .driver = { + .name = "goldfish_pdev_bus" + } +}; + +module_platform_driver(goldfish_pdev_bus_driver); |