1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * MEN Chameleon Bus. |
4 | * |
5 | * Copyright (C) 2013 MEN Mikroelektronik GmbH (www.men.de) |
6 | * Author: Johannes Thumshirn <johannes.thumshirn@men.de> |
7 | */ |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/types.h> |
12 | #include <linux/idr.h> |
13 | #include <linux/mcb.h> |
14 | |
15 | static DEFINE_IDA(mcb_ida); |
16 | |
17 | static const struct mcb_device_id *mcb_match_id(const struct mcb_device_id *ids, |
18 | struct mcb_device *dev) |
19 | { |
20 | if (ids) { |
21 | while (ids->device) { |
22 | if (ids->device == dev->id) |
23 | return ids; |
24 | ids++; |
25 | } |
26 | } |
27 | |
28 | return NULL; |
29 | } |
30 | |
31 | static int mcb_match(struct device *dev, struct device_driver *drv) |
32 | { |
33 | struct mcb_driver *mdrv = to_mcb_driver(drv); |
34 | struct mcb_device *mdev = to_mcb_device(dev); |
35 | const struct mcb_device_id *found_id; |
36 | |
37 | found_id = mcb_match_id(ids: mdrv->id_table, dev: mdev); |
38 | if (found_id) |
39 | return 1; |
40 | |
41 | return 0; |
42 | } |
43 | |
44 | static int mcb_uevent(const struct device *dev, struct kobj_uevent_env *env) |
45 | { |
46 | const struct mcb_device *mdev = to_mcb_device(dev); |
47 | int ret; |
48 | |
49 | ret = add_uevent_var(env, format: "MODALIAS=mcb:16z%03d" , mdev->id); |
50 | if (ret) |
51 | return -ENOMEM; |
52 | |
53 | return 0; |
54 | } |
55 | |
56 | static int mcb_probe(struct device *dev) |
57 | { |
58 | struct mcb_driver *mdrv = to_mcb_driver(drv: dev->driver); |
59 | struct mcb_device *mdev = to_mcb_device(dev); |
60 | const struct mcb_device_id *found_id; |
61 | struct module *carrier_mod; |
62 | int ret; |
63 | |
64 | found_id = mcb_match_id(ids: mdrv->id_table, dev: mdev); |
65 | if (!found_id) |
66 | return -ENODEV; |
67 | |
68 | carrier_mod = mdev->dev.parent->driver->owner; |
69 | if (!try_module_get(module: carrier_mod)) |
70 | return -EINVAL; |
71 | |
72 | get_device(dev); |
73 | ret = mdrv->probe(mdev, found_id); |
74 | if (ret) { |
75 | module_put(module: carrier_mod); |
76 | put_device(dev); |
77 | } |
78 | |
79 | return ret; |
80 | } |
81 | |
82 | static void mcb_remove(struct device *dev) |
83 | { |
84 | struct mcb_driver *mdrv = to_mcb_driver(drv: dev->driver); |
85 | struct mcb_device *mdev = to_mcb_device(dev); |
86 | struct module *carrier_mod; |
87 | |
88 | mdrv->remove(mdev); |
89 | |
90 | carrier_mod = mdev->dev.parent->driver->owner; |
91 | module_put(module: carrier_mod); |
92 | |
93 | put_device(dev: &mdev->dev); |
94 | } |
95 | |
96 | static void mcb_shutdown(struct device *dev) |
97 | { |
98 | struct mcb_driver *mdrv = to_mcb_driver(drv: dev->driver); |
99 | struct mcb_device *mdev = to_mcb_device(dev); |
100 | |
101 | if (mdrv && mdrv->shutdown) |
102 | mdrv->shutdown(mdev); |
103 | } |
104 | |
105 | static ssize_t revision_show(struct device *dev, struct device_attribute *attr, |
106 | char *buf) |
107 | { |
108 | struct mcb_bus *bus = to_mcb_bus(dev); |
109 | |
110 | return scnprintf(buf, PAGE_SIZE, fmt: "%d\n" , bus->revision); |
111 | } |
112 | static DEVICE_ATTR_RO(revision); |
113 | |
114 | static ssize_t model_show(struct device *dev, struct device_attribute *attr, |
115 | char *buf) |
116 | { |
117 | struct mcb_bus *bus = to_mcb_bus(dev); |
118 | |
119 | return scnprintf(buf, PAGE_SIZE, fmt: "%c\n" , bus->model); |
120 | } |
121 | static DEVICE_ATTR_RO(model); |
122 | |
123 | static ssize_t minor_show(struct device *dev, struct device_attribute *attr, |
124 | char *buf) |
125 | { |
126 | struct mcb_bus *bus = to_mcb_bus(dev); |
127 | |
128 | return scnprintf(buf, PAGE_SIZE, fmt: "%d\n" , bus->minor); |
129 | } |
130 | static DEVICE_ATTR_RO(minor); |
131 | |
132 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, |
133 | char *buf) |
134 | { |
135 | struct mcb_bus *bus = to_mcb_bus(dev); |
136 | |
137 | return scnprintf(buf, PAGE_SIZE, fmt: "%s\n" , bus->name); |
138 | } |
139 | static DEVICE_ATTR_RO(name); |
140 | |
141 | static struct attribute *mcb_bus_attrs[] = { |
142 | &dev_attr_revision.attr, |
143 | &dev_attr_model.attr, |
144 | &dev_attr_minor.attr, |
145 | &dev_attr_name.attr, |
146 | NULL, |
147 | }; |
148 | |
149 | static const struct attribute_group mcb_carrier_group = { |
150 | .attrs = mcb_bus_attrs, |
151 | }; |
152 | |
153 | static const struct attribute_group *mcb_carrier_groups[] = { |
154 | &mcb_carrier_group, |
155 | NULL, |
156 | }; |
157 | |
158 | |
159 | static struct bus_type mcb_bus_type = { |
160 | .name = "mcb" , |
161 | .match = mcb_match, |
162 | .uevent = mcb_uevent, |
163 | .probe = mcb_probe, |
164 | .remove = mcb_remove, |
165 | .shutdown = mcb_shutdown, |
166 | }; |
167 | |
168 | static struct device_type mcb_carrier_device_type = { |
169 | .name = "mcb-carrier" , |
170 | .groups = mcb_carrier_groups, |
171 | }; |
172 | |
173 | /** |
174 | * __mcb_register_driver() - Register a @mcb_driver at the system |
175 | * @drv: The @mcb_driver |
176 | * @owner: The @mcb_driver's module |
177 | * @mod_name: The name of the @mcb_driver's module |
178 | * |
179 | * Register a @mcb_driver at the system. Perform some sanity checks, if |
180 | * the .probe and .remove methods are provided by the driver. |
181 | */ |
182 | int __mcb_register_driver(struct mcb_driver *drv, struct module *owner, |
183 | const char *mod_name) |
184 | { |
185 | if (!drv->probe || !drv->remove) |
186 | return -EINVAL; |
187 | |
188 | drv->driver.owner = owner; |
189 | drv->driver.bus = &mcb_bus_type; |
190 | drv->driver.mod_name = mod_name; |
191 | |
192 | return driver_register(drv: &drv->driver); |
193 | } |
194 | EXPORT_SYMBOL_NS_GPL(__mcb_register_driver, MCB); |
195 | |
196 | /** |
197 | * mcb_unregister_driver() - Unregister a @mcb_driver from the system |
198 | * @drv: The @mcb_driver |
199 | * |
200 | * Unregister a @mcb_driver from the system. |
201 | */ |
202 | void mcb_unregister_driver(struct mcb_driver *drv) |
203 | { |
204 | driver_unregister(drv: &drv->driver); |
205 | } |
206 | EXPORT_SYMBOL_NS_GPL(mcb_unregister_driver, MCB); |
207 | |
208 | static void mcb_release_dev(struct device *dev) |
209 | { |
210 | struct mcb_device *mdev = to_mcb_device(dev); |
211 | |
212 | mcb_bus_put(bus: mdev->bus); |
213 | kfree(objp: mdev); |
214 | } |
215 | |
216 | /** |
217 | * mcb_device_register() - Register a mcb_device |
218 | * @bus: The @mcb_bus of the device |
219 | * @dev: The @mcb_device |
220 | * |
221 | * Register a specific @mcb_device at a @mcb_bus and the system itself. |
222 | */ |
223 | int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev) |
224 | { |
225 | int ret; |
226 | int device_id; |
227 | |
228 | device_initialize(dev: &dev->dev); |
229 | mcb_bus_get(bus); |
230 | dev->dev.bus = &mcb_bus_type; |
231 | dev->dev.parent = bus->dev.parent; |
232 | dev->dev.release = mcb_release_dev; |
233 | dev->dma_dev = bus->carrier; |
234 | |
235 | device_id = dev->id; |
236 | dev_set_name(dev: &dev->dev, name: "mcb%d-16z%03d-%d:%d:%d" , |
237 | bus->bus_nr, device_id, dev->inst, dev->group, dev->var); |
238 | |
239 | ret = device_add(dev: &dev->dev); |
240 | if (ret < 0) { |
241 | pr_err("Failed registering device 16z%03d on bus mcb%d (%d)\n" , |
242 | device_id, bus->bus_nr, ret); |
243 | goto out; |
244 | } |
245 | |
246 | return 0; |
247 | |
248 | out: |
249 | put_device(dev: &dev->dev); |
250 | |
251 | return ret; |
252 | } |
253 | EXPORT_SYMBOL_NS_GPL(mcb_device_register, MCB); |
254 | |
255 | static void mcb_free_bus(struct device *dev) |
256 | { |
257 | struct mcb_bus *bus = to_mcb_bus(dev); |
258 | |
259 | put_device(dev: bus->carrier); |
260 | ida_free(&mcb_ida, id: bus->bus_nr); |
261 | kfree(objp: bus); |
262 | } |
263 | |
264 | /** |
265 | * mcb_alloc_bus() - Allocate a new @mcb_bus |
266 | * |
267 | * Allocate a new @mcb_bus. |
268 | */ |
269 | struct mcb_bus *mcb_alloc_bus(struct device *carrier) |
270 | { |
271 | struct mcb_bus *bus; |
272 | int bus_nr; |
273 | int rc; |
274 | |
275 | bus = kzalloc(size: sizeof(struct mcb_bus), GFP_KERNEL); |
276 | if (!bus) |
277 | return ERR_PTR(error: -ENOMEM); |
278 | |
279 | bus_nr = ida_alloc(ida: &mcb_ida, GFP_KERNEL); |
280 | if (bus_nr < 0) { |
281 | kfree(objp: bus); |
282 | return ERR_PTR(error: bus_nr); |
283 | } |
284 | |
285 | bus->bus_nr = bus_nr; |
286 | bus->carrier = get_device(dev: carrier); |
287 | |
288 | device_initialize(dev: &bus->dev); |
289 | bus->dev.parent = carrier; |
290 | bus->dev.bus = &mcb_bus_type; |
291 | bus->dev.type = &mcb_carrier_device_type; |
292 | bus->dev.release = mcb_free_bus; |
293 | |
294 | dev_set_name(dev: &bus->dev, name: "mcb:%d" , bus_nr); |
295 | rc = device_add(dev: &bus->dev); |
296 | if (rc) |
297 | goto err_put; |
298 | |
299 | return bus; |
300 | |
301 | err_put: |
302 | put_device(dev: &bus->dev); |
303 | return ERR_PTR(error: rc); |
304 | } |
305 | EXPORT_SYMBOL_NS_GPL(mcb_alloc_bus, MCB); |
306 | |
307 | static int __mcb_devices_unregister(struct device *dev, void *data) |
308 | { |
309 | device_unregister(dev); |
310 | return 0; |
311 | } |
312 | |
313 | static void mcb_devices_unregister(struct mcb_bus *bus) |
314 | { |
315 | bus_for_each_dev(bus: bus->dev.bus, NULL, NULL, fn: __mcb_devices_unregister); |
316 | } |
317 | /** |
318 | * mcb_release_bus() - Free a @mcb_bus |
319 | * @bus: The @mcb_bus to release |
320 | * |
321 | * Release an allocated @mcb_bus from the system. |
322 | */ |
323 | void mcb_release_bus(struct mcb_bus *bus) |
324 | { |
325 | mcb_devices_unregister(bus); |
326 | } |
327 | EXPORT_SYMBOL_NS_GPL(mcb_release_bus, MCB); |
328 | |
329 | /** |
330 | * mcb_bus_put() - Increment refcnt |
331 | * @bus: The @mcb_bus |
332 | * |
333 | * Get a @mcb_bus' ref |
334 | */ |
335 | struct mcb_bus *mcb_bus_get(struct mcb_bus *bus) |
336 | { |
337 | if (bus) |
338 | get_device(dev: &bus->dev); |
339 | |
340 | return bus; |
341 | } |
342 | EXPORT_SYMBOL_NS_GPL(mcb_bus_get, MCB); |
343 | |
344 | /** |
345 | * mcb_bus_put() - Decrement refcnt |
346 | * @bus: The @mcb_bus |
347 | * |
348 | * Release a @mcb_bus' ref |
349 | */ |
350 | void mcb_bus_put(struct mcb_bus *bus) |
351 | { |
352 | if (bus) |
353 | put_device(dev: &bus->dev); |
354 | } |
355 | EXPORT_SYMBOL_NS_GPL(mcb_bus_put, MCB); |
356 | |
357 | /** |
358 | * mcb_alloc_dev() - Allocate a device |
359 | * @bus: The @mcb_bus the device is part of |
360 | * |
361 | * Allocate a @mcb_device and add bus. |
362 | */ |
363 | struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus) |
364 | { |
365 | struct mcb_device *dev; |
366 | |
367 | dev = kzalloc(size: sizeof(struct mcb_device), GFP_KERNEL); |
368 | if (!dev) |
369 | return NULL; |
370 | |
371 | dev->bus = bus; |
372 | |
373 | return dev; |
374 | } |
375 | EXPORT_SYMBOL_NS_GPL(mcb_alloc_dev, MCB); |
376 | |
377 | /** |
378 | * mcb_free_dev() - Free @mcb_device |
379 | * @dev: The device to free |
380 | * |
381 | * Free a @mcb_device |
382 | */ |
383 | void mcb_free_dev(struct mcb_device *dev) |
384 | { |
385 | kfree(objp: dev); |
386 | } |
387 | EXPORT_SYMBOL_NS_GPL(mcb_free_dev, MCB); |
388 | |
389 | static int __mcb_bus_add_devices(struct device *dev, void *data) |
390 | { |
391 | int retval; |
392 | |
393 | retval = device_attach(dev); |
394 | if (retval < 0) { |
395 | dev_err(dev, "Error adding device (%d)\n" , retval); |
396 | return retval; |
397 | } |
398 | |
399 | return 0; |
400 | } |
401 | |
402 | /** |
403 | * mcb_bus_add_devices() - Add devices in the bus' internal device list |
404 | * @bus: The @mcb_bus we add the devices |
405 | * |
406 | * Add devices in the bus' internal device list to the system. |
407 | */ |
408 | void mcb_bus_add_devices(const struct mcb_bus *bus) |
409 | { |
410 | bus_for_each_dev(bus: bus->dev.bus, NULL, NULL, fn: __mcb_bus_add_devices); |
411 | } |
412 | EXPORT_SYMBOL_NS_GPL(mcb_bus_add_devices, MCB); |
413 | |
414 | /** |
415 | * mcb_get_resource() - get a resource for a mcb device |
416 | * @dev: the mcb device |
417 | * @type: the type of resource |
418 | */ |
419 | struct resource *mcb_get_resource(struct mcb_device *dev, unsigned int type) |
420 | { |
421 | if (type == IORESOURCE_MEM) |
422 | return &dev->mem; |
423 | else if (type == IORESOURCE_IRQ) |
424 | return &dev->irq; |
425 | else |
426 | return NULL; |
427 | } |
428 | EXPORT_SYMBOL_NS_GPL(mcb_get_resource, MCB); |
429 | |
430 | /** |
431 | * mcb_request_mem() - Request memory |
432 | * @dev: The @mcb_device the memory is for |
433 | * @name: The name for the memory reference. |
434 | * |
435 | * Request memory for a @mcb_device. If @name is NULL the driver name will |
436 | * be used. |
437 | */ |
438 | struct resource *mcb_request_mem(struct mcb_device *dev, const char *name) |
439 | { |
440 | struct resource *mem; |
441 | u32 size; |
442 | |
443 | if (!name) |
444 | name = dev->dev.driver->name; |
445 | |
446 | size = resource_size(res: &dev->mem); |
447 | |
448 | mem = request_mem_region(dev->mem.start, size, name); |
449 | if (!mem) |
450 | return ERR_PTR(error: -EBUSY); |
451 | |
452 | return mem; |
453 | } |
454 | EXPORT_SYMBOL_NS_GPL(mcb_request_mem, MCB); |
455 | |
456 | /** |
457 | * mcb_release_mem() - Release memory requested by device |
458 | * @dev: The @mcb_device that requested the memory |
459 | * |
460 | * Release memory that was prior requested via @mcb_request_mem(). |
461 | */ |
462 | void mcb_release_mem(struct resource *mem) |
463 | { |
464 | u32 size; |
465 | |
466 | size = resource_size(res: mem); |
467 | release_mem_region(mem->start, size); |
468 | } |
469 | EXPORT_SYMBOL_NS_GPL(mcb_release_mem, MCB); |
470 | |
471 | static int __mcb_get_irq(struct mcb_device *dev) |
472 | { |
473 | struct resource *irq; |
474 | |
475 | irq = mcb_get_resource(dev, IORESOURCE_IRQ); |
476 | |
477 | return irq->start; |
478 | } |
479 | |
480 | /** |
481 | * mcb_get_irq() - Get device's IRQ number |
482 | * @dev: The @mcb_device the IRQ is for |
483 | * |
484 | * Get the IRQ number of a given @mcb_device. |
485 | */ |
486 | int mcb_get_irq(struct mcb_device *dev) |
487 | { |
488 | struct mcb_bus *bus = dev->bus; |
489 | |
490 | if (bus->get_irq) |
491 | return bus->get_irq(dev); |
492 | |
493 | return __mcb_get_irq(dev); |
494 | } |
495 | EXPORT_SYMBOL_NS_GPL(mcb_get_irq, MCB); |
496 | |
497 | static int mcb_init(void) |
498 | { |
499 | return bus_register(bus: &mcb_bus_type); |
500 | } |
501 | |
502 | static void mcb_exit(void) |
503 | { |
504 | ida_destroy(ida: &mcb_ida); |
505 | bus_unregister(bus: &mcb_bus_type); |
506 | } |
507 | |
508 | /* mcb must be initialized after PCI but before the chameleon drivers. |
509 | * That means we must use some initcall between subsys_initcall and |
510 | * device_initcall. |
511 | */ |
512 | fs_initcall(mcb_init); |
513 | module_exit(mcb_exit); |
514 | |
515 | MODULE_DESCRIPTION("MEN Chameleon Bus Driver" ); |
516 | MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>" ); |
517 | MODULE_LICENSE("GPL v2" ); |
518 | |