1 /* arch/arm/mach-goldfish/pdev_bus.c
2 **
3 ** Copyright (C) 2007 Google, Inc.
4 **
5 ** This software is licensed under the terms of the GNU General Public
6 ** License version 2, as published by the Free Software Foundation, and
7 ** may be copied, distributed, and modified under those terms.
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ** GNU General Public License for more details.
13 **
14 */
15
16 #include <linux/kernel.h>
17 #include <linux/init.h>
18 #include <linux/interrupt.h>
19 #include <linux/irq.h>
20 #include <linux/platform_device.h>
21
22 #include <mach/hardware.h>
23 #include <asm/io.h>
24
25 #include <asm/mach-goldfish/irq.h>
26
27 #define PDEV_BUS_OP_DONE (0x00)
28 #define PDEV_BUS_OP_REMOVE_DEV (0x04)
29 #define PDEV_BUS_OP_ADD_DEV (0x08)
30
31 #define PDEV_BUS_OP_INIT (0x00)
32
33 #define PDEV_BUS_OP (0x00)
34 #define PDEV_BUS_GET_NAME (0x04)
35 #define PDEV_BUS_NAME_LEN (0x08)
36 #define PDEV_BUS_ID (0x0c)
37 #define PDEV_BUS_IO_BASE (0x10)
38 #define PDEV_BUS_IO_SIZE (0x14)
39 #define PDEV_BUS_IRQ (0x18)
40 #define PDEV_BUS_IRQ_COUNT (0x1c)
41
42 struct pdev_bus_dev {
43 struct list_head list;
44 struct platform_device pdev;
45 struct resource resources[0];
46 };
47
48 static void goldfish_pdev_worker(struct work_struct *work);
49
50 static uint32_t pdev_bus_base;
51 static uint32_t pdev_bus_irq;
52 static LIST_HEAD(pdev_bus_new_devices);
53 static LIST_HEAD(pdev_bus_registered_devices);
54 static LIST_HEAD(pdev_bus_removed_devices);
55 static DECLARE_WORK(pdev_bus_worker, goldfish_pdev_worker);
56
57
goldfish_pdev_worker(struct work_struct * work)58 static void goldfish_pdev_worker(struct work_struct *work)
59 {
60 int ret;
61 struct pdev_bus_dev *pos, *n;
62
63 list_for_each_entry_safe(pos, n, &pdev_bus_removed_devices, list) {
64 list_del(&pos->list);
65 platform_device_unregister(&pos->pdev);
66 kfree(pos);
67 }
68 list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
69 list_del(&pos->list);
70 ret = platform_device_register(&pos->pdev);
71 if (ret)
72 printk(KERN_ERR "goldfish_pdev_worker failed to register device, %s\n", pos->pdev.name);
73 else
74 printk(KERN_ERR "goldfish_pdev_worker registered %s\n", pos->pdev.name);
75 list_add_tail(&pos->list, &pdev_bus_registered_devices);
76 }
77 }
78
goldfish_pdev_remove(void)79 static void goldfish_pdev_remove(void)
80 {
81 struct pdev_bus_dev *pos, *n;
82 uint32_t base;
83
84 base = readl(pdev_bus_base + PDEV_BUS_IO_BASE);
85
86 list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
87 if (pos->resources[0].start == base) {
88 list_del(&pos->list);
89 kfree(pos);
90 return;
91 }
92 }
93 list_for_each_entry_safe(pos, n, &pdev_bus_registered_devices, list) {
94 if (pos->resources[0].start == base) {
95 list_del(&pos->list);
96 list_add_tail(&pos->list, &pdev_bus_removed_devices);
97 schedule_work(&pdev_bus_worker);
98 return;
99 }
100 };
101 printk(KERN_ERR "goldfish_pdev_remove could not find device at %x\n", base);
102 }
103
goldfish_new_pdev(void)104 static int goldfish_new_pdev(void)
105 {
106 struct pdev_bus_dev *dev;
107 uint32_t name_len;
108 uint32_t irq = -1, irq_count;
109 int resource_count = 2;
110 uint32_t base;
111 char *name;
112
113 base = readl(pdev_bus_base + PDEV_BUS_IO_BASE);
114
115 irq_count = readl(pdev_bus_base + PDEV_BUS_IRQ_COUNT);
116 name_len = readl(pdev_bus_base + PDEV_BUS_NAME_LEN);
117 if (irq_count)
118 resource_count++;
119
120 dev = kzalloc(sizeof(*dev) + sizeof(struct resource) * resource_count + name_len + 1, GFP_ATOMIC);
121 if (dev == NULL)
122 return -ENOMEM;
123
124 dev->pdev.num_resources = resource_count;
125 dev->pdev.resource = (struct resource *)(dev + 1);
126 dev->pdev.name = name = (char *)(dev->pdev.resource + resource_count);
127 dev->pdev.dev.coherent_dma_mask = ~0;
128
129 writel((unsigned long)name, pdev_bus_base + PDEV_BUS_GET_NAME);
130 name[name_len] = '\0';
131 dev->pdev.id = readl(pdev_bus_base + PDEV_BUS_ID);
132 dev->pdev.resource[0].start = base;
133 dev->pdev.resource[0].end = base + readl(pdev_bus_base + PDEV_BUS_IO_SIZE) - 1;
134 dev->pdev.resource[0].flags = IORESOURCE_MEM;
135 if (irq_count) {
136 irq = readl(pdev_bus_base + PDEV_BUS_IRQ);
137 dev->pdev.resource[1].start = GOLDFISH_IRQ_BASE+irq;
138 dev->pdev.resource[1].end = GOLDFISH_IRQ_BASE+irq + irq_count - 1;
139 dev->pdev.resource[1].flags = IORESOURCE_IRQ;
140 }
141
142 printk(KERN_INFO "goldfish_new_pdev %s at %x irq %d\n", name, base, irq);
143 list_add_tail(&dev->list, &pdev_bus_new_devices);
144 schedule_work(&pdev_bus_worker);
145
146 return 0;
147 }
148
goldfish_pdev_bus_interrupt(int irq,void * dev_id)149 static irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *dev_id)
150 {
151 irqreturn_t ret = IRQ_NONE;
152 while (1) {
153 uint32_t op = readl(pdev_bus_base + PDEV_BUS_OP);
154 switch (op) {
155 case PDEV_BUS_OP_DONE:
156 return IRQ_NONE;
157
158 case PDEV_BUS_OP_REMOVE_DEV:
159 goldfish_pdev_remove();
160 break;
161
162 case PDEV_BUS_OP_ADD_DEV:
163 goldfish_new_pdev();
164 break;
165 }
166 ret = IRQ_HANDLED;
167 }
168 }
169
goldfish_pdev_bus_probe(struct platform_device * pdev)170 static int __devinit goldfish_pdev_bus_probe(struct platform_device *pdev)
171 {
172 int ret;
173 struct resource *r;
174 r = platform_get_resource(pdev, IORESOURCE_IO, 0);
175 if (r == NULL)
176 return -EINVAL;
177 pdev_bus_base = IO_ADDRESS(r->start);
178
179 r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
180 if (r == NULL)
181 return -EINVAL;
182 pdev_bus_irq = r->start;
183
184 ret = request_irq(pdev_bus_irq, goldfish_pdev_bus_interrupt, IRQF_SHARED, "goldfish_pdev_bus", pdev);
185 if (ret)
186 goto err_request_irq_failed;
187
188 writel(PDEV_BUS_OP_INIT, pdev_bus_base + PDEV_BUS_OP);
189
190 err_request_irq_failed:
191 return ret;
192 }
193
goldfish_pdev_bus_remove(struct platform_device * pdev)194 static int __devexit goldfish_pdev_bus_remove(struct platform_device *pdev)
195 {
196 free_irq(pdev_bus_irq, pdev);
197 return 0;
198 }
199
200 static struct platform_driver goldfish_pdev_bus_driver = {
201 .probe = goldfish_pdev_bus_probe,
202 .remove = __devexit_p(goldfish_pdev_bus_remove),
203 .driver = {
204 .name = "goldfish_pdev_bus"
205 }
206 };
207
goldfish_pdev_bus_init(void)208 static int __init goldfish_pdev_bus_init(void)
209 {
210 return platform_driver_register(&goldfish_pdev_bus_driver);
211 }
212
goldfish_pdev_bus_exit(void)213 static void __exit goldfish_pdev_bus_exit(void)
214 {
215 platform_driver_unregister(&goldfish_pdev_bus_driver);
216 }
217
218 module_init(goldfish_pdev_bus_init);
219 module_exit(goldfish_pdev_bus_exit);
220
221