blob: a5ef28f82f7aeb0cb00242b4fbe003b9260df311 [file] [log] [blame]
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +02001/*
2 * Industry-pack bus support functions.
3 *
4 * (C) 2011 Samuel Iglesias Gonsalvez <siglesia@cern.ch>, CERN
5 * (C) 2012 Samuel Iglesias Gonsalvez <siglesias@igalia.com>, Igalia
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
Samuel Iglesias Gonsalvez416289b2012-05-11 10:17:13 +02009 * Software Foundation; version 2 of the License.
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020010 */
11
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020012#include <linux/module.h>
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +020013#include <linux/slab.h>
Samuel Iglesias Gonsalvez3b86bb22012-05-25 10:03:01 +020014#include <linux/idr.h>
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020015#include "ipack.h"
16
17#define to_ipack_dev(device) container_of(device, struct ipack_device, dev)
18#define to_ipack_driver(drv) container_of(drv, struct ipack_driver, driver)
19
Samuel Iglesias Gonsalvez3b86bb22012-05-25 10:03:01 +020020static DEFINE_IDA(ipack_ida);
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020021
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +020022static void ipack_device_release(struct device *dev)
23{
24 struct ipack_device *device = to_ipack_dev(dev);
Jens Taprogge187e4782012-09-04 17:01:14 +020025 kfree(device->id);
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +020026 kfree(device);
27}
28
Jens Taprogge4aa09d42012-09-04 17:01:19 +020029static inline const struct ipack_device_id *
30ipack_match_one_device(const struct ipack_device_id *id,
31 const struct ipack_device *device)
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020032{
Jens Taprogge4aa09d42012-09-04 17:01:19 +020033 if ((id->format == IPACK_ANY_ID || id->format == device->id_format) &&
34 (id->vendor == IPACK_ANY_ID || id->vendor == device->id_vendor) &&
35 (id->device == IPACK_ANY_ID || id->device == device->id_device))
36 return id;
37 return NULL;
38}
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020039
Jens Taprogge4aa09d42012-09-04 17:01:19 +020040static const struct ipack_device_id *
41ipack_match_id(const struct ipack_device_id *ids, struct ipack_device *idev)
42{
43 if (ids) {
44 while (ids->vendor || ids->device) {
45 if (ipack_match_one_device(ids, idev))
46 return ids;
47 ids++;
48 }
49 }
50 return NULL;
51}
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020052
Jens Taprogge4aa09d42012-09-04 17:01:19 +020053static int ipack_bus_match(struct device *dev, struct device_driver *drv)
54{
55 struct ipack_device *idev = to_ipack_dev(dev);
56 struct ipack_driver *idrv = to_ipack_driver(drv);
57 const struct ipack_device_id *found_id;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020058
Jens Taprogge4aa09d42012-09-04 17:01:19 +020059 found_id = ipack_match_id(idrv->id_table, idev);
60 if (found_id) {
61 idev->driver = idrv;
62 return 1;
63 }
64
65 return 0;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +020066}
67
68static int ipack_bus_probe(struct device *device)
69{
70 struct ipack_device *dev = to_ipack_dev(device);
71
72 if (!dev->driver->ops->probe)
73 return -EINVAL;
74
75 return dev->driver->ops->probe(dev);
76}
77
78static int ipack_bus_remove(struct device *device)
79{
80 struct ipack_device *dev = to_ipack_dev(device);
81
82 if (!dev->driver->ops->remove)
83 return -EINVAL;
84
85 dev->driver->ops->remove(dev);
86 return 0;
87}
88
Jens Taprogge35eb97b2012-09-04 17:01:20 +020089#ifdef CONFIG_HOTPLUG
90
91static int ipack_uevent(struct device *dev, struct kobj_uevent_env *env)
92{
93 struct ipack_device *idev;
94
95 if (!dev)
96 return -ENODEV;
97
98 idev = to_ipack_dev(dev);
99
100 if (add_uevent_var(env,
101 "MODALIAS=ipack:f%02Xv%08Xd%08X", idev->id_format,
102 idev->id_vendor, idev->id_device))
103 return -ENOMEM;
104
105 return 0;
106}
107
108#else /* !CONFIG_HOTPLUG */
109
110#define ipack_uevent NULL
111
112#endif /* !CONFIG_HOTPLUG */
113
Jens Taproggee8ed3272012-09-04 17:01:15 +0200114#define ipack_device_attr(field, format_string) \
115static ssize_t \
116field##_show(struct device *dev, struct device_attribute *attr, \
117 char *buf) \
118{ \
119 struct ipack_device *idev = to_ipack_dev(dev); \
120 return sprintf(buf, format_string, idev->field); \
121}
122
123static ssize_t
124id_vendor_show(struct device *dev, struct device_attribute *attr, char *buf)
125{
126 struct ipack_device *idev = to_ipack_dev(dev);
127 switch (idev->id_format) {
128 case IPACK_ID_VERSION_1:
129 return sprintf(buf, "0x%02x\n", idev->id_vendor);
130 case IPACK_ID_VERSION_2:
131 return sprintf(buf, "0x%06x\n", idev->id_vendor);
132 default:
133 return -EIO;
134 }
135}
136
137static ssize_t
138id_device_show(struct device *dev, struct device_attribute *attr, char *buf)
139{
140 struct ipack_device *idev = to_ipack_dev(dev);
141 switch (idev->id_format) {
142 case IPACK_ID_VERSION_1:
143 return sprintf(buf, "0x%02x\n", idev->id_device);
144 case IPACK_ID_VERSION_2:
145 return sprintf(buf, "0x%04x\n", idev->id_device);
146 default:
147 return -EIO;
148 }
149}
150
Jens Taprogge35eb97b2012-09-04 17:01:20 +0200151static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
152 char *buf)
153{
154 struct ipack_device *idev = to_ipack_dev(dev);
155
156 return sprintf(buf, "ipac:f%02Xv%08Xd%08X", idev->id_format,
157 idev->id_vendor, idev->id_device);
158}
159
Jens Taproggee8ed3272012-09-04 17:01:15 +0200160ipack_device_attr(id_format, "0x%hhu\n");
161
162static struct device_attribute ipack_dev_attrs[] = {
163 __ATTR_RO(id_device),
164 __ATTR_RO(id_format),
165 __ATTR_RO(id_vendor),
Jens Taprogge35eb97b2012-09-04 17:01:20 +0200166 __ATTR_RO(modalias),
Jens Taproggee8ed3272012-09-04 17:01:15 +0200167};
168
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200169static struct bus_type ipack_bus_type = {
Jens Taproggee8ed3272012-09-04 17:01:15 +0200170 .name = "ipack",
171 .probe = ipack_bus_probe,
172 .match = ipack_bus_match,
173 .remove = ipack_bus_remove,
174 .dev_attrs = ipack_dev_attrs,
Jens Taprogge35eb97b2012-09-04 17:01:20 +0200175 .uevent = ipack_uevent,
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200176};
177
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200178struct ipack_bus_device *ipack_bus_register(struct device *parent, int slots,
179 struct ipack_bus_ops *ops)
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200180{
181 int bus_nr;
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200182 struct ipack_bus_device *bus;
183
184 bus = kzalloc(sizeof(struct ipack_bus_device), GFP_KERNEL);
185 if (!bus)
186 return NULL;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200187
Samuel Iglesias Gonsalvez3b86bb22012-05-25 10:03:01 +0200188 bus_nr = ida_simple_get(&ipack_ida, 0, 0, GFP_KERNEL);
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200189 if (bus_nr < 0) {
190 kfree(bus);
191 return NULL;
192 }
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200193
194 bus->bus_nr = bus_nr;
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200195 bus->parent = parent;
196 bus->slots = slots;
197 bus->ops = ops;
198 return bus;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200199}
200EXPORT_SYMBOL_GPL(ipack_bus_register);
201
202int ipack_bus_unregister(struct ipack_bus_device *bus)
203{
Samuel Iglesias Gonsalvez3b86bb22012-05-25 10:03:01 +0200204 ida_simple_remove(&ipack_ida, bus->bus_nr);
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200205 kfree(bus);
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200206 return 0;
207}
208EXPORT_SYMBOL_GPL(ipack_bus_unregister);
209
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200210int ipack_driver_register(struct ipack_driver *edrv, struct module *owner,
211 char *name)
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200212{
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200213 edrv->driver.owner = owner;
214 edrv->driver.name = name;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200215 edrv->driver.bus = &ipack_bus_type;
216 return driver_register(&edrv->driver);
217}
218EXPORT_SYMBOL_GPL(ipack_driver_register);
219
220void ipack_driver_unregister(struct ipack_driver *edrv)
221{
222 driver_unregister(&edrv->driver);
223}
224EXPORT_SYMBOL_GPL(ipack_driver_unregister);
225
Jens Taproggee8ed3272012-09-04 17:01:15 +0200226static void ipack_parse_id1(struct ipack_device *dev)
227{
228 u8 *id = dev->id;
229
230 dev->id_vendor = id[4];
231 dev->id_device = id[5];
232}
233
234static void ipack_parse_id2(struct ipack_device *dev)
235{
236 __be16 *id = (__be16 *) dev->id;
237
238 dev->id_vendor = ((be16_to_cpu(id[3]) & 0xff) << 16)
239 + be16_to_cpu(id[4]);
240 dev->id_device = be16_to_cpu(id[5]);
241}
242
Jens Taprogge187e4782012-09-04 17:01:14 +0200243static int ipack_device_read_id(struct ipack_device *dev)
244{
245 u8 __iomem *idmem;
246 int i;
247 int ret = 0;
248
249 ret = dev->bus->ops->map_space(dev, 0, IPACK_ID_SPACE);
250 if (ret) {
251 dev_err(&dev->dev, "error mapping memory\n");
252 return ret;
253 }
254 idmem = dev->id_space.address;
255
256 /* Determine ID PROM Data Format. If we find the ids "IPAC" or "IPAH"
257 * we are dealing with a IndustryPack format 1 device. If we detect
258 * "VITA4 " (16 bit big endian formatted) we are dealing with a
259 * IndustryPack format 2 device */
260 if ((ioread8(idmem + 1) == 'I') &&
261 (ioread8(idmem + 3) == 'P') &&
262 (ioread8(idmem + 5) == 'A') &&
263 ((ioread8(idmem + 7) == 'C') ||
264 (ioread8(idmem + 7) == 'H'))) {
265 dev->id_format = IPACK_ID_VERSION_1;
266 dev->id_avail = ioread8(idmem + 0x15);
267 if ((dev->id_avail < 0x0c) || (dev->id_avail > 0x40)) {
268 dev_warn(&dev->dev, "invalid id size");
269 dev->id_avail = 0x0c;
270 }
271 } else if ((ioread8(idmem + 0) == 'I') &&
272 (ioread8(idmem + 1) == 'V') &&
273 (ioread8(idmem + 2) == 'A') &&
274 (ioread8(idmem + 3) == 'T') &&
275 (ioread8(idmem + 4) == ' ') &&
276 (ioread8(idmem + 5) == '4')) {
277 dev->id_format = IPACK_ID_VERSION_2;
278 dev->id_avail = ioread16be(idmem + 0x16);
279 if ((dev->id_avail < 0x1a) || (dev->id_avail > 0x40)) {
280 dev_warn(&dev->dev, "invalid id size");
281 dev->id_avail = 0x1a;
282 }
283 } else {
284 dev->id_format = IPACK_ID_VERSION_INVALID;
285 dev->id_avail = 0;
286 }
287
288 if (!dev->id_avail) {
289 ret = -ENODEV;
290 goto out;
291 }
292
293 /* Obtain the amount of memory required to store a copy of the complete
294 * ID ROM contents */
295 dev->id = kmalloc(dev->id_avail, GFP_KERNEL);
296 if (!dev->id) {
297 dev_err(&dev->dev, "dev->id alloc failed.\n");
298 ret = -ENOMEM;
299 goto out;
300 }
301 for (i = 0; i < dev->id_avail; i++) {
302 if (dev->id_format == IPACK_ID_VERSION_1)
303 dev->id[i] = ioread8(idmem + (i << 1) + 1);
304 else
305 dev->id[i] = ioread8(idmem + i);
306 }
307
Jens Taproggee8ed3272012-09-04 17:01:15 +0200308 /* now we can finally work with the copy */
309 switch (dev->id_format) {
310 case IPACK_ID_VERSION_1:
311 ipack_parse_id1(dev);
312 break;
313 case IPACK_ID_VERSION_2:
314 ipack_parse_id2(dev);
315 break;
316 }
317
Jens Taprogge187e4782012-09-04 17:01:14 +0200318out:
319 dev->bus->ops->unmap_space(dev, IPACK_ID_SPACE);
320
321 return ret;
322}
323
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200324struct ipack_device *ipack_device_register(struct ipack_bus_device *bus,
325 int slot, int irqv)
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200326{
327 int ret;
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200328 struct ipack_device *dev;
329
330 dev = kzalloc(sizeof(struct ipack_device), GFP_KERNEL);
331 if (!dev)
332 return NULL;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200333
334 dev->dev.bus = &ipack_bus_type;
335 dev->dev.release = ipack_device_release;
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200336 dev->dev.parent = bus->parent;
337 dev->slot = slot;
338 dev->bus_nr = bus->bus_nr;
339 dev->irq = irqv;
340 dev->bus = bus;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200341 dev_set_name(&dev->dev,
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200342 "ipack-dev.%u.%u", dev->bus_nr, dev->slot);
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200343
Jens Taprogge187e4782012-09-04 17:01:14 +0200344 ret = ipack_device_read_id(dev);
345 if (ret < 0) {
346 dev_err(&dev->dev, "error reading device id section.\n");
347 kfree(dev);
348 return NULL;
349 }
350
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200351 ret = device_register(&dev->dev);
352 if (ret < 0) {
Jens Taprogge187e4782012-09-04 17:01:14 +0200353 kfree(dev->id);
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200354 kfree(dev);
355 return NULL;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200356 }
357
Samuel Iglesias Gonsalvezec440332012-05-18 11:10:05 +0200358 return dev;
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200359}
360EXPORT_SYMBOL_GPL(ipack_device_register);
361
362void ipack_device_unregister(struct ipack_device *dev)
363{
364 device_unregister(&dev->dev);
365}
366EXPORT_SYMBOL_GPL(ipack_device_unregister);
367
368static int __init ipack_init(void)
369{
Samuel Iglesias Gonsalvez3b86bb22012-05-25 10:03:01 +0200370 ida_init(&ipack_ida);
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200371 return bus_register(&ipack_bus_type);
372}
373
374static void __exit ipack_exit(void)
375{
376 bus_unregister(&ipack_bus_type);
Samuel Iglesias Gonsalvez3b86bb22012-05-25 10:03:01 +0200377 ida_destroy(&ipack_ida);
Samuel Iglesias Gonsalvezd3465872012-05-09 15:27:19 +0200378}
379
380module_init(ipack_init);
381module_exit(ipack_exit);
382
383MODULE_AUTHOR("Samuel Iglesias Gonsalvez <siglesias@igalia.com>");
384MODULE_LICENSE("GPL");
385MODULE_DESCRIPTION("Industry-pack bus core");