241 lines
		
	
	
	
		
			6.2 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			241 lines
		
	
	
	
		
			6.2 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * 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); |