forked from mirrors/linux
		
	drivers: Introduce MEN Chameleon Bus
The MCB (MEN Chameleon Bus) is a Bus specific to MEN Mikroelektronik FPGA based devices. It is used to identify MCB based IP-Cores within an FPGA and provide the necessary framework for instantiating drivers for these devices. Signed-off-by: Johannes Thumshirn <johannes.thumshirn@men.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									e5bb7425e5
								
							
						
					
					
						commit
						3764e82e51
					
				
					 10 changed files with 842 additions and 0 deletions
				
			
		|  | @ -5667,6 +5667,12 @@ L:	linux-watchdog@vger.kernel.org | ||||||
| S:	Supported | S:	Supported | ||||||
| F:	drivers/watchdog/mena21_wdt.c | F:	drivers/watchdog/mena21_wdt.c | ||||||
| 
 | 
 | ||||||
|  | MEN CHAMELEON BUS (mcb) | ||||||
|  | M:  	Johannes Thumshirn <johannes.thumshirn@men.de> | ||||||
|  | S:	Supported | ||||||
|  | F:	drivers/mcb/ | ||||||
|  | F:	include/linux/mcb.h | ||||||
|  | 
 | ||||||
| METAG ARCHITECTURE | METAG ARCHITECTURE | ||||||
| M:	James Hogan <james.hogan@imgtec.com> | M:	James Hogan <james.hogan@imgtec.com> | ||||||
| L:	linux-metag@vger.kernel.org | L:	linux-metag@vger.kernel.org | ||||||
|  |  | ||||||
|  | @ -172,4 +172,6 @@ source "drivers/phy/Kconfig" | ||||||
| 
 | 
 | ||||||
| source "drivers/powercap/Kconfig" | source "drivers/powercap/Kconfig" | ||||||
| 
 | 
 | ||||||
|  | source "drivers/mcb/Kconfig" | ||||||
|  | 
 | ||||||
| endmenu | endmenu | ||||||
|  |  | ||||||
|  | @ -156,3 +156,4 @@ obj-$(CONFIG_IPACK_BUS)		+= ipack/ | ||||||
| obj-$(CONFIG_NTB)		+= ntb/ | obj-$(CONFIG_NTB)		+= ntb/ | ||||||
| obj-$(CONFIG_FMC)		+= fmc/ | obj-$(CONFIG_FMC)		+= fmc/ | ||||||
| obj-$(CONFIG_POWERCAP)		+= powercap/ | obj-$(CONFIG_POWERCAP)		+= powercap/ | ||||||
|  | obj-$(CONFIG_MCB)		+= mcb/ | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								drivers/mcb/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								drivers/mcb/Kconfig
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | # | ||||||
|  | # MEN Chameleon Bus (MCB) support | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | menuconfig MCB | ||||||
|  | 	   tristate "MCB support" | ||||||
|  | 	   default m | ||||||
|  | 	   help | ||||||
|  | 
 | ||||||
|  | 	   The MCB (MEN Chameleon Bus) is a Bus specific to MEN Mikroelektronik | ||||||
|  | 	   FPGA based devices. It is used to identify MCB based IP-Cores within | ||||||
|  | 	   an FPGA and provide the necessary framework for instantiating drivers | ||||||
|  | 	   for these devices. | ||||||
|  | 
 | ||||||
|  | 	   If build as a module, the module is called mcb.ko | ||||||
							
								
								
									
										5
									
								
								drivers/mcb/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								drivers/mcb/Makefile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | 
 | ||||||
|  | obj-$(CONFIG_MCB) += mcb.o | ||||||
|  | 
 | ||||||
|  | mcb-y += mcb-core.o | ||||||
|  | mcb-y += mcb-parse.o | ||||||
							
								
								
									
										414
									
								
								drivers/mcb/mcb-core.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										414
									
								
								drivers/mcb/mcb-core.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,414 @@ | ||||||
|  | /*
 | ||||||
|  |  * MEN Chameleon Bus. | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2013 MEN Mikroelektronik GmbH (www.men.de) | ||||||
|  |  * Author: Johannes Thumshirn <johannes.thumshirn@men.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify it | ||||||
|  |  * under the terms of the GNU General Public License as published by the Free | ||||||
|  |  * Software Foundation; version 2 of the License. | ||||||
|  |  */ | ||||||
|  | #include <linux/kernel.h> | ||||||
|  | #include <linux/module.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | #include <linux/types.h> | ||||||
|  | #include <linux/idr.h> | ||||||
|  | #include <linux/mcb.h> | ||||||
|  | 
 | ||||||
|  | static DEFINE_IDA(mcb_ida); | ||||||
|  | 
 | ||||||
|  | static const struct mcb_device_id *mcb_match_id(const struct mcb_device_id *ids, | ||||||
|  | 						struct mcb_device *dev) | ||||||
|  | { | ||||||
|  | 	if (ids) { | ||||||
|  | 		while (ids->device) { | ||||||
|  | 			if (ids->device == dev->id) | ||||||
|  | 				return ids; | ||||||
|  | 			ids++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int mcb_match(struct device *dev, struct device_driver *drv) | ||||||
|  | { | ||||||
|  | 	struct mcb_driver *mdrv = to_mcb_driver(drv); | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 	const struct mcb_device_id *found_id; | ||||||
|  | 
 | ||||||
|  | 	found_id = mcb_match_id(mdrv->id_table, mdev); | ||||||
|  | 	if (found_id) | ||||||
|  | 		return 1; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int mcb_uevent(struct device *dev, struct kobj_uevent_env *env) | ||||||
|  | { | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = add_uevent_var(env, "MODALIAS=mcb:16z%03d", mdev->id); | ||||||
|  | 	if (ret) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int mcb_probe(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct mcb_driver *mdrv = to_mcb_driver(dev->driver); | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 	const struct mcb_device_id *found_id; | ||||||
|  | 
 | ||||||
|  | 	found_id = mcb_match_id(mdrv->id_table, mdev); | ||||||
|  | 	if (!found_id) | ||||||
|  | 		return -ENODEV; | ||||||
|  | 
 | ||||||
|  | 	return mdrv->probe(mdev, found_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int mcb_remove(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct mcb_driver *mdrv = to_mcb_driver(dev->driver); | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 
 | ||||||
|  | 	mdrv->remove(mdev); | ||||||
|  | 
 | ||||||
|  | 	put_device(&mdev->dev); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void mcb_shutdown(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 	struct mcb_driver *mdrv = mdev->driver; | ||||||
|  | 
 | ||||||
|  | 	if (mdrv && mdrv->shutdown) | ||||||
|  | 		mdrv->shutdown(mdev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct bus_type mcb_bus_type = { | ||||||
|  | 	.name = "mcb", | ||||||
|  | 	.match = mcb_match, | ||||||
|  | 	.uevent = mcb_uevent, | ||||||
|  | 	.probe = mcb_probe, | ||||||
|  | 	.remove = mcb_remove, | ||||||
|  | 	.shutdown = mcb_shutdown, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * __mcb_register_driver() - Register a @mcb_driver at the system | ||||||
|  |  * @drv: The @mcb_driver | ||||||
|  |  * @owner: The @mcb_driver's module | ||||||
|  |  * @mod_name: The name of the @mcb_driver's module | ||||||
|  |  * | ||||||
|  |  * Register a @mcb_driver at the system. Perform some sanity checks, if | ||||||
|  |  * the .probe and .remove methods are provided by the driver. | ||||||
|  |  */ | ||||||
|  | int __mcb_register_driver(struct mcb_driver *drv, struct module *owner, | ||||||
|  | 			const char *mod_name) | ||||||
|  | { | ||||||
|  | 	if (!drv->probe || !drv->remove) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	drv->driver.owner = owner; | ||||||
|  | 	drv->driver.bus = &mcb_bus_type; | ||||||
|  | 	drv->driver.mod_name = mod_name; | ||||||
|  | 
 | ||||||
|  | 	return driver_register(&drv->driver); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(__mcb_register_driver); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_unregister_driver() - Unregister a @mcb_driver from the system | ||||||
|  |  * @drv: The @mcb_driver | ||||||
|  |  * | ||||||
|  |  * Unregister a @mcb_driver from the system. | ||||||
|  |  */ | ||||||
|  | void mcb_unregister_driver(struct mcb_driver *drv) | ||||||
|  | { | ||||||
|  | 	driver_unregister(&drv->driver); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_unregister_driver); | ||||||
|  | 
 | ||||||
|  | static void mcb_release_dev(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 
 | ||||||
|  | 	mcb_bus_put(mdev->bus); | ||||||
|  | 	kfree(mdev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_device_register() - Register a mcb_device | ||||||
|  |  * @bus: The @mcb_bus of the device | ||||||
|  |  * @dev: The @mcb_device | ||||||
|  |  * | ||||||
|  |  * Register a specific @mcb_device at a @mcb_bus and the system itself. | ||||||
|  |  */ | ||||||
|  | int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  | 	int device_id; | ||||||
|  | 
 | ||||||
|  | 	device_initialize(&dev->dev); | ||||||
|  | 	dev->dev.bus = &mcb_bus_type; | ||||||
|  | 	dev->dev.parent = bus->dev.parent; | ||||||
|  | 	dev->dev.release = mcb_release_dev; | ||||||
|  | 
 | ||||||
|  | 	device_id = dev->id; | ||||||
|  | 	dev_set_name(&dev->dev, "mcb%d-16z%03d-%d:%d:%d", | ||||||
|  | 		bus->bus_nr, device_id, dev->inst, dev->group, dev->var); | ||||||
|  | 
 | ||||||
|  | 	ret = device_add(&dev->dev); | ||||||
|  | 	if (ret < 0) { | ||||||
|  | 		pr_err("Failed registering device 16z%03d on bus mcb%d (%d)\n", | ||||||
|  | 			device_id, bus->bus_nr, ret); | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | 
 | ||||||
|  | out: | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_device_register); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_alloc_bus() - Allocate a new @mcb_bus | ||||||
|  |  * | ||||||
|  |  * Allocate a new @mcb_bus. | ||||||
|  |  */ | ||||||
|  | struct mcb_bus *mcb_alloc_bus(void) | ||||||
|  | { | ||||||
|  | 	struct mcb_bus *bus; | ||||||
|  | 	int bus_nr; | ||||||
|  | 
 | ||||||
|  | 	bus = kzalloc(sizeof(struct mcb_bus), GFP_KERNEL); | ||||||
|  | 	if (!bus) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	bus_nr = ida_simple_get(&mcb_ida, 0, 0, GFP_KERNEL); | ||||||
|  | 	if (bus_nr < 0) { | ||||||
|  | 		kfree(bus); | ||||||
|  | 		return ERR_PTR(bus_nr); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	INIT_LIST_HEAD(&bus->children); | ||||||
|  | 	bus->bus_nr = bus_nr; | ||||||
|  | 
 | ||||||
|  | 	return bus; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_alloc_bus); | ||||||
|  | 
 | ||||||
|  | static int __mcb_devices_unregister(struct device *dev, void *data) | ||||||
|  | { | ||||||
|  | 	device_unregister(dev); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void mcb_devices_unregister(struct mcb_bus *bus) | ||||||
|  | { | ||||||
|  | 	bus_for_each_dev(&mcb_bus_type, NULL, NULL, __mcb_devices_unregister); | ||||||
|  | } | ||||||
|  | /**
 | ||||||
|  |  * mcb_release_bus() - Free a @mcb_bus | ||||||
|  |  * @bus: The @mcb_bus to release | ||||||
|  |  * | ||||||
|  |  * Release an allocated @mcb_bus from the system. | ||||||
|  |  */ | ||||||
|  | void mcb_release_bus(struct mcb_bus *bus) | ||||||
|  | { | ||||||
|  | 	mcb_devices_unregister(bus); | ||||||
|  | 
 | ||||||
|  | 	ida_simple_remove(&mcb_ida, bus->bus_nr); | ||||||
|  | 
 | ||||||
|  | 	kfree(bus); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_release_bus); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_bus_put() - Increment refcnt | ||||||
|  |  * @bus: The @mcb_bus | ||||||
|  |  * | ||||||
|  |  * Get a @mcb_bus' ref | ||||||
|  |  */ | ||||||
|  | struct mcb_bus *mcb_bus_get(struct mcb_bus *bus) | ||||||
|  | { | ||||||
|  | 	if (bus) | ||||||
|  | 		get_device(&bus->dev); | ||||||
|  | 
 | ||||||
|  | 	return bus; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_bus_get); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_bus_put() - Decrement refcnt | ||||||
|  |  * @bus: The @mcb_bus | ||||||
|  |  * | ||||||
|  |  * Release a @mcb_bus' ref | ||||||
|  |  */ | ||||||
|  | void mcb_bus_put(struct mcb_bus *bus) | ||||||
|  | { | ||||||
|  | 	if (bus) | ||||||
|  | 		put_device(&bus->dev); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_bus_put); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_alloc_dev() - Allocate a device | ||||||
|  |  * @bus: The @mcb_bus the device is part of | ||||||
|  |  * | ||||||
|  |  * Allocate a @mcb_device and add bus. | ||||||
|  |  */ | ||||||
|  | struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus) | ||||||
|  | { | ||||||
|  | 	struct mcb_device *dev; | ||||||
|  | 
 | ||||||
|  | 	dev = kzalloc(sizeof(struct mcb_device), GFP_KERNEL); | ||||||
|  | 	if (!dev) | ||||||
|  | 		return NULL; | ||||||
|  | 
 | ||||||
|  | 	INIT_LIST_HEAD(&dev->bus_list); | ||||||
|  | 	dev->bus = bus; | ||||||
|  | 
 | ||||||
|  | 	return dev; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_alloc_dev); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_free_dev() - Free @mcb_device | ||||||
|  |  * @dev: The device to free | ||||||
|  |  * | ||||||
|  |  * Free a @mcb_device | ||||||
|  |  */ | ||||||
|  | void mcb_free_dev(struct mcb_device *dev) | ||||||
|  | { | ||||||
|  | 	kfree(dev); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_free_dev); | ||||||
|  | 
 | ||||||
|  | static int __mcb_bus_add_devices(struct device *dev, void *data) | ||||||
|  | { | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 	int retval; | ||||||
|  | 
 | ||||||
|  | 	if (mdev->is_added) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	retval = device_attach(dev); | ||||||
|  | 	if (retval < 0) | ||||||
|  | 		dev_err(dev, "Error adding device (%d)\n", retval); | ||||||
|  | 
 | ||||||
|  | 	mdev->is_added = true; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int __mcb_bus_add_child(struct device *dev, void *data) | ||||||
|  | { | ||||||
|  | 	struct mcb_device *mdev = to_mcb_device(dev); | ||||||
|  | 	struct mcb_bus *child; | ||||||
|  | 
 | ||||||
|  | 	BUG_ON(!mdev->is_added); | ||||||
|  | 	child = mdev->subordinate; | ||||||
|  | 
 | ||||||
|  | 	if (child) | ||||||
|  | 		mcb_bus_add_devices(child); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_bus_add_devices() - Add devices in the bus' internal device list | ||||||
|  |  * @bus: The @mcb_bus we add the devices | ||||||
|  |  * | ||||||
|  |  * Add devices in the bus' internal device list to the system. | ||||||
|  |  */ | ||||||
|  | void mcb_bus_add_devices(const struct mcb_bus *bus) | ||||||
|  | { | ||||||
|  | 	bus_for_each_dev(&mcb_bus_type, NULL, NULL, __mcb_bus_add_devices); | ||||||
|  | 	bus_for_each_dev(&mcb_bus_type, NULL, NULL, __mcb_bus_add_child); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_bus_add_devices); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_request_mem() - Request memory | ||||||
|  |  * @dev: The @mcb_device the memory is for | ||||||
|  |  * @name: The name for the memory reference. | ||||||
|  |  * | ||||||
|  |  * Request memory for a @mcb_device. If @name is NULL the driver name will | ||||||
|  |  * be used. | ||||||
|  |  */ | ||||||
|  | struct resource *mcb_request_mem(struct mcb_device *dev, const char *name) | ||||||
|  | { | ||||||
|  | 	struct resource *mem; | ||||||
|  | 	u32 size; | ||||||
|  | 
 | ||||||
|  | 	if (!name) | ||||||
|  | 		name = dev->dev.driver->name; | ||||||
|  | 
 | ||||||
|  | 	size = resource_size(&dev->mem); | ||||||
|  | 
 | ||||||
|  | 	mem = request_mem_region(dev->mem.start, size, name); | ||||||
|  | 	if (!mem) | ||||||
|  | 		return ERR_PTR(-EBUSY); | ||||||
|  | 
 | ||||||
|  | 	return mem; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_request_mem); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_release_mem() - Release memory requested by device | ||||||
|  |  * @dev: The @mcb_device that requested the memory | ||||||
|  |  * | ||||||
|  |  * Release memory that was prior requested via @mcb_request_mem(). | ||||||
|  |  */ | ||||||
|  | void mcb_release_mem(struct resource *mem) | ||||||
|  | { | ||||||
|  | 	u32 size; | ||||||
|  | 
 | ||||||
|  | 	size = resource_size(mem); | ||||||
|  | 	release_mem_region(mem->start, size); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_release_mem); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * mcb_get_irq() - Get device's IRQ number | ||||||
|  |  * @dev: The @mcb_device the IRQ is for | ||||||
|  |  * | ||||||
|  |  * Get the IRQ number of a given @mcb_device. | ||||||
|  |  */ | ||||||
|  | int mcb_get_irq(struct mcb_device *dev) | ||||||
|  | { | ||||||
|  | 	struct resource *irq = &dev->irq; | ||||||
|  | 
 | ||||||
|  | 	return irq->start; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(mcb_get_irq); | ||||||
|  | 
 | ||||||
|  | static int mcb_init(void) | ||||||
|  | { | ||||||
|  | 	return bus_register(&mcb_bus_type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void mcb_exit(void) | ||||||
|  | { | ||||||
|  | 	bus_unregister(&mcb_bus_type); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* mcb must be initialized after PCI but before the chameleon drivers.
 | ||||||
|  |  * That means we must use some initcall between subsys_initcall and | ||||||
|  |  * device_initcall. | ||||||
|  |  */ | ||||||
|  | fs_initcall(mcb_init); | ||||||
|  | module_exit(mcb_exit); | ||||||
|  | 
 | ||||||
|  | MODULE_DESCRIPTION("MEN Chameleon Bus Driver"); | ||||||
|  | MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>"); | ||||||
|  | MODULE_LICENSE("GPL v2"); | ||||||
							
								
								
									
										116
									
								
								drivers/mcb/mcb-internal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								drivers/mcb/mcb-internal.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,116 @@ | ||||||
|  | #ifndef __MCB_INTERNAL | ||||||
|  | #define __MCB_INTERNAL | ||||||
|  | 
 | ||||||
|  | #include <linux/types.h> | ||||||
|  | 
 | ||||||
|  | #define CHAMELEON_FILENAME_LEN		12 | ||||||
|  | #define CHAMELEONV2_MAGIC		0xabce | ||||||
|  | 
 | ||||||
|  | enum chameleon_descriptor_type { | ||||||
|  | 	CHAMELEON_DTYPE_GENERAL = 0x0, | ||||||
|  | 	CHAMELEON_DTYPE_BRIDGE = 0x1, | ||||||
|  | 	CHAMELEON_DTYPE_CPU = 0x2, | ||||||
|  | 	CHAMELEON_DTYPE_BAR = 0x3, | ||||||
|  | 	CHAMELEON_DTYPE_END = 0xf, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum chameleon_bus_type { | ||||||
|  | 	CHAMELEON_BUS_WISHBONE, | ||||||
|  | 	CHAMELEON_BUS_AVALON, | ||||||
|  | 	CHAMELEON_BUS_LPC, | ||||||
|  | 	CHAMELEON_BUS_ISA, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct chameleon_fpga_header | ||||||
|  |  * | ||||||
|  |  * @revision:	Revison of Chameleon table in FPGA | ||||||
|  |  * @model:	Chameleon table model ASCII char | ||||||
|  |  * @minor:	Revision minor | ||||||
|  |  * @bus_type:	Bus type (usually %CHAMELEON_BUS_WISHBONE) | ||||||
|  |  * @magic:	Chameleon header magic number (0xabce for version 2) | ||||||
|  |  * @reserved:	Reserved | ||||||
|  |  * @filename:	Filename of FPGA bitstream | ||||||
|  |  */ | ||||||
|  | struct chameleon_fpga_header { | ||||||
|  | 	u8 revision; | ||||||
|  | 	char model; | ||||||
|  | 	u8 minor; | ||||||
|  | 	u8 bus_type; | ||||||
|  | 	u16 magic; | ||||||
|  | 	u16 reserved; | ||||||
|  | 	/* This one has no '\0' at the end!!! */ | ||||||
|  | 	char filename[CHAMELEON_FILENAME_LEN]; | ||||||
|  | } __packed; | ||||||
|  | #define HEADER_MAGIC_OFFSET 0x4 | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct chameleon_gdd - Chameleon General Device Descriptor | ||||||
|  |  * | ||||||
|  |  * @irq:	the position in the FPGA's IRQ controller vector | ||||||
|  |  * @rev:	the revision of the variant's implementation | ||||||
|  |  * @var:	the variant of the IP core | ||||||
|  |  * @dev:	the device  the IP core is | ||||||
|  |  * @dtype:	device descriptor type | ||||||
|  |  * @bar:	BAR offset that must be added to module offset | ||||||
|  |  * @inst:	the instance number of the device, 0 is first instance | ||||||
|  |  * @group:	the group the device belongs to (0 = no group) | ||||||
|  |  * @reserved:	reserved | ||||||
|  |  * @offset:	beginning of the address window of desired module | ||||||
|  |  * @size:	size of the module's address window | ||||||
|  |  */ | ||||||
|  | struct chameleon_gdd { | ||||||
|  | 	__le32 reg1; | ||||||
|  | 	__le32 reg2; | ||||||
|  | 	__le32 offset; | ||||||
|  | 	__le32 size; | ||||||
|  | 
 | ||||||
|  | } __packed; | ||||||
|  | 
 | ||||||
|  | /* GDD Register 1 fields */ | ||||||
|  | #define GDD_IRQ(x) ((x) & 0x1f) | ||||||
|  | #define GDD_REV(x) (((x) >> 5) & 0x3f) | ||||||
|  | #define GDD_VAR(x) (((x) >> 11) & 0x3f) | ||||||
|  | #define GDD_DEV(x) (((x) >> 18) & 0x3ff) | ||||||
|  | #define GDD_DTY(x) (((x) >> 28) & 0xf) | ||||||
|  | 
 | ||||||
|  | /* GDD Register 2 fields */ | ||||||
|  | #define GDD_BAR(x) ((x) & 0x7) | ||||||
|  | #define GDD_INS(x) (((x) >> 3) & 0x3f) | ||||||
|  | #define GDD_GRP(x) (((x) >> 9) & 0x3f) | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct chameleon_bdd - Chameleon Bridge Device Descriptor | ||||||
|  |  * | ||||||
|  |  * @irq:	the position in the FPGA's IRQ controller vector | ||||||
|  |  * @rev:	the revision of the variant's implementation | ||||||
|  |  * @var:	the variant of the IP core | ||||||
|  |  * @dev:	the device  the IP core is | ||||||
|  |  * @dtype:	device descriptor type | ||||||
|  |  * @bar:	BAR offset that must be added to module offset | ||||||
|  |  * @inst:	the instance number of the device, 0 is first instance | ||||||
|  |  * @dbar:	destination bar from the bus _behind_ the bridge | ||||||
|  |  * @chamoff:	offset within the BAR of the source bus | ||||||
|  |  * @offset: | ||||||
|  |  * @size: | ||||||
|  |  */ | ||||||
|  | struct chameleon_bdd { | ||||||
|  | 	unsigned int irq:6; | ||||||
|  | 	unsigned int rev:6; | ||||||
|  | 	unsigned int var:6; | ||||||
|  | 	unsigned int dev:10; | ||||||
|  | 	unsigned int dtype:4; | ||||||
|  | 	unsigned int bar:3; | ||||||
|  | 	unsigned int inst:6; | ||||||
|  | 	unsigned int dbar:3; | ||||||
|  | 	unsigned int group:6; | ||||||
|  | 	unsigned int reserved:14; | ||||||
|  | 	u32 chamoff; | ||||||
|  | 	u32 offset; | ||||||
|  | 	u32 size; | ||||||
|  | } __packed; | ||||||
|  | 
 | ||||||
|  | int chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase, | ||||||
|  | 			  void __iomem *base); | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
							
								
								
									
										159
									
								
								drivers/mcb/mcb-parse.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								drivers/mcb/mcb-parse.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,159 @@ | ||||||
|  | #include <linux/types.h> | ||||||
|  | #include <linux/ioport.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | #include <linux/export.h> | ||||||
|  | #include <linux/io.h> | ||||||
|  | #include <linux/mcb.h> | ||||||
|  | 
 | ||||||
|  | #include "mcb-internal.h" | ||||||
|  | 
 | ||||||
|  | struct mcb_parse_priv { | ||||||
|  | 	phys_addr_t mapbase; | ||||||
|  | 	void __iomem *base; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #define for_each_chameleon_cell(dtype, p)	\ | ||||||
|  | 	for ((dtype) = get_next_dtype((p));	\ | ||||||
|  | 	     (dtype) != CHAMELEON_DTYPE_END;	\ | ||||||
|  | 	     (dtype) = get_next_dtype((p))) | ||||||
|  | 
 | ||||||
|  | static inline uint32_t get_next_dtype(void __iomem *p) | ||||||
|  | { | ||||||
|  | 	uint32_t dtype; | ||||||
|  | 
 | ||||||
|  | 	dtype = readl(p); | ||||||
|  | 	return dtype >> 28; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int chameleon_parse_bdd(struct mcb_bus *bus, | ||||||
|  | 			phys_addr_t mapbase, | ||||||
|  | 			void __iomem *base) | ||||||
|  | { | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int chameleon_parse_gdd(struct mcb_bus *bus, | ||||||
|  | 			phys_addr_t mapbase, | ||||||
|  | 			void __iomem *base) | ||||||
|  | { | ||||||
|  | 	struct chameleon_gdd __iomem *gdd = | ||||||
|  | 		(struct chameleon_gdd __iomem *) base; | ||||||
|  | 	struct mcb_device *mdev; | ||||||
|  | 	u32 offset; | ||||||
|  | 	u32 size; | ||||||
|  | 	int ret; | ||||||
|  | 	__le32 reg1; | ||||||
|  | 	__le32 reg2; | ||||||
|  | 
 | ||||||
|  | 	mdev = mcb_alloc_dev(bus); | ||||||
|  | 	if (!mdev) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	reg1 = readl(&gdd->reg1); | ||||||
|  | 	reg2 = readl(&gdd->reg2); | ||||||
|  | 	offset = readl(&gdd->offset); | ||||||
|  | 	size = readl(&gdd->size); | ||||||
|  | 
 | ||||||
|  | 	mdev->id = GDD_DEV(reg1); | ||||||
|  | 	mdev->rev = GDD_REV(reg1); | ||||||
|  | 	mdev->var = GDD_VAR(reg1); | ||||||
|  | 	mdev->bar = GDD_BAR(reg1); | ||||||
|  | 	mdev->group = GDD_GRP(reg2); | ||||||
|  | 	mdev->inst = GDD_INS(reg2); | ||||||
|  | 
 | ||||||
|  | 	pr_debug("Found a 16z%03d\n", mdev->id); | ||||||
|  | 
 | ||||||
|  | 	mdev->irq.start = GDD_IRQ(reg1); | ||||||
|  | 	mdev->irq.end = GDD_IRQ(reg1); | ||||||
|  | 	mdev->irq.flags = IORESOURCE_IRQ; | ||||||
|  | 
 | ||||||
|  | 	mdev->mem.start = mapbase + offset; | ||||||
|  | 	mdev->mem.end = mdev->mem.start + size - 1; | ||||||
|  | 	mdev->mem.flags = IORESOURCE_MEM; | ||||||
|  | 
 | ||||||
|  | 	mdev->is_added = false; | ||||||
|  | 
 | ||||||
|  | 	ret = mcb_device_register(bus, mdev); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		goto err; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | 
 | ||||||
|  | err: | ||||||
|  | 	mcb_free_dev(mdev); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase, | ||||||
|  | 			void __iomem *base) | ||||||
|  | { | ||||||
|  | 	char __iomem *p = base; | ||||||
|  | 	struct chameleon_fpga_header *header; | ||||||
|  | 	uint32_t dtype; | ||||||
|  | 	int num_cells = 0; | ||||||
|  | 	int ret = 0; | ||||||
|  | 	u32 hsize; | ||||||
|  | 
 | ||||||
|  | 	hsize = sizeof(struct chameleon_fpga_header); | ||||||
|  | 
 | ||||||
|  | 	header = kzalloc(hsize, GFP_KERNEL); | ||||||
|  | 	if (!header) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	/* Extract header information */ | ||||||
|  | 	memcpy_fromio(header, p, hsize); | ||||||
|  | 	/* We only support chameleon v2 at the moment */ | ||||||
|  | 	header->magic = le16_to_cpu(header->magic); | ||||||
|  | 	if (header->magic != CHAMELEONV2_MAGIC) { | ||||||
|  | 		pr_err("Unsupported chameleon version 0x%x\n", | ||||||
|  | 				header->magic); | ||||||
|  | 		kfree(header); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 	p += hsize; | ||||||
|  | 
 | ||||||
|  | 	pr_debug("header->revision = %d\n", header->revision); | ||||||
|  | 	pr_debug("header->model = 0x%x ('%c')\n", header->model, | ||||||
|  | 		header->model); | ||||||
|  | 	pr_debug("header->minor = %d\n", header->minor); | ||||||
|  | 	pr_debug("header->bus_type = 0x%x\n", header->bus_type); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	pr_debug("header->magic = 0x%x\n", header->magic); | ||||||
|  | 	pr_debug("header->filename = \"%.*s\"\n", CHAMELEON_FILENAME_LEN, | ||||||
|  | 		header->filename); | ||||||
|  | 
 | ||||||
|  | 	for_each_chameleon_cell(dtype, p) { | ||||||
|  | 		switch (dtype) { | ||||||
|  | 		case CHAMELEON_DTYPE_GENERAL: | ||||||
|  | 			ret = chameleon_parse_gdd(bus, mapbase, p); | ||||||
|  | 			if (ret < 0) | ||||||
|  | 				goto out; | ||||||
|  | 			p += sizeof(struct chameleon_gdd); | ||||||
|  | 			break; | ||||||
|  | 		case CHAMELEON_DTYPE_BRIDGE: | ||||||
|  | 			chameleon_parse_bdd(bus, mapbase, p); | ||||||
|  | 			p += sizeof(struct chameleon_bdd); | ||||||
|  | 			break; | ||||||
|  | 		case CHAMELEON_DTYPE_END: | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			pr_err("Invalid chameleon descriptor type 0x%x\n", | ||||||
|  | 				dtype); | ||||||
|  | 			return -EINVAL; | ||||||
|  | 		} | ||||||
|  | 		num_cells++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (num_cells == 0) | ||||||
|  | 		num_cells = -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	kfree(header); | ||||||
|  | 	return num_cells; | ||||||
|  | 
 | ||||||
|  | out: | ||||||
|  | 	kfree(header); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(chameleon_parse_cells); | ||||||
							
								
								
									
										119
									
								
								include/linux/mcb.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								include/linux/mcb.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,119 @@ | ||||||
|  | /*
 | ||||||
|  |  * MEN Chameleon Bus. | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de) | ||||||
|  |  * Author: Johannes Thumshirn <johannes.thumshirn@men.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify it | ||||||
|  |  * under the terms of the GNU General Public License as published by the Free | ||||||
|  |  * Software Foundation; version 2 of the License. | ||||||
|  |  */ | ||||||
|  | #ifndef _LINUX_MCB_H | ||||||
|  | #define _LINUX_MCB_H | ||||||
|  | 
 | ||||||
|  | #include <linux/mod_devicetable.h> | ||||||
|  | #include <linux/device.h> | ||||||
|  | #include <linux/irqreturn.h> | ||||||
|  | 
 | ||||||
|  | struct mcb_driver; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct mcb_bus - MEN Chameleon Bus | ||||||
|  |  * | ||||||
|  |  * @dev: pointer to carrier device | ||||||
|  |  * @children: the child busses | ||||||
|  |  * @bus_nr: mcb bus number | ||||||
|  |  */ | ||||||
|  | struct mcb_bus { | ||||||
|  | 	struct list_head children; | ||||||
|  | 	struct device dev; | ||||||
|  | 	int bus_nr; | ||||||
|  | }; | ||||||
|  | #define to_mcb_bus(b) container_of((b), struct mcb_bus, dev) | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct mcb_device - MEN Chameleon Bus device | ||||||
|  |  * | ||||||
|  |  * @bus_list: internal list handling for bus code | ||||||
|  |  * @dev: device in kernel representation | ||||||
|  |  * @bus: mcb bus the device is plugged to | ||||||
|  |  * @subordinate: subordinate MCBus in case of bridge | ||||||
|  |  * @is_added: flag to check if device is added to bus | ||||||
|  |  * @driver: associated mcb_driver | ||||||
|  |  * @id: mcb device id | ||||||
|  |  * @inst: instance in Chameleon table | ||||||
|  |  * @group: group in Chameleon table | ||||||
|  |  * @var: variant in Chameleon table | ||||||
|  |  * @bar: BAR in Chameleon table | ||||||
|  |  * @rev: revision in Chameleon table | ||||||
|  |  * @irq: IRQ resource | ||||||
|  |  * @memory: memory resource | ||||||
|  |  */ | ||||||
|  | struct mcb_device { | ||||||
|  | 	struct list_head bus_list; | ||||||
|  | 	struct device dev; | ||||||
|  | 	struct mcb_bus *bus; | ||||||
|  | 	struct mcb_bus *subordinate; | ||||||
|  | 	bool is_added; | ||||||
|  | 	struct mcb_driver *driver; | ||||||
|  | 	u16 id; | ||||||
|  | 	int inst; | ||||||
|  | 	int group; | ||||||
|  | 	int var; | ||||||
|  | 	int bar; | ||||||
|  | 	int rev; | ||||||
|  | 	struct resource irq; | ||||||
|  | 	struct resource mem; | ||||||
|  | }; | ||||||
|  | #define to_mcb_device(x) container_of((x), struct mcb_device, dev) | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * struct mcb_driver - MEN Chameleon Bus device driver | ||||||
|  |  * | ||||||
|  |  * @driver: device_driver | ||||||
|  |  * @id_table: mcb id table | ||||||
|  |  * @probe: probe callback | ||||||
|  |  * @remove: remove callback | ||||||
|  |  * @shutdown: shutdown callback | ||||||
|  |  */ | ||||||
|  | struct mcb_driver { | ||||||
|  | 	struct device_driver driver; | ||||||
|  | 	const struct mcb_device_id *id_table; | ||||||
|  | 	int (*probe)(struct mcb_device *mdev, const struct mcb_device_id *id); | ||||||
|  | 	void (*remove)(struct mcb_device *mdev); | ||||||
|  | 	void (*shutdown)(struct mcb_device *mdev); | ||||||
|  | }; | ||||||
|  | #define to_mcb_driver(x) container_of((x), struct mcb_driver, driver) | ||||||
|  | 
 | ||||||
|  | static inline void *mcb_get_drvdata(struct mcb_device *dev) | ||||||
|  | { | ||||||
|  | 	return dev_get_drvdata(&dev->dev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void mcb_set_drvdata(struct mcb_device *dev, void *data) | ||||||
|  | { | ||||||
|  | 	dev_set_drvdata(&dev->dev, data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | extern int __must_check __mcb_register_driver(struct mcb_driver *drv, | ||||||
|  | 					struct module *owner, | ||||||
|  | 					const char *mod_name); | ||||||
|  | #define mcb_register_driver(driver)		\ | ||||||
|  | 	__mcb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) | ||||||
|  | extern void mcb_unregister_driver(struct mcb_driver *driver); | ||||||
|  | #define module_mcb_driver(__mcb_driver)		\ | ||||||
|  | 	module_driver(__mcb_driver, mcb_register_driver, mcb_unregister_driver); | ||||||
|  | extern void mcb_bus_add_devices(const struct mcb_bus *bus); | ||||||
|  | extern int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev); | ||||||
|  | extern struct mcb_bus *mcb_alloc_bus(void); | ||||||
|  | extern struct mcb_bus *mcb_bus_get(struct mcb_bus *bus); | ||||||
|  | extern void mcb_bus_put(struct mcb_bus *bus); | ||||||
|  | extern struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus); | ||||||
|  | extern void mcb_free_dev(struct mcb_device *dev); | ||||||
|  | extern void mcb_release_bus(struct mcb_bus *bus); | ||||||
|  | extern struct resource *mcb_request_mem(struct mcb_device *dev, | ||||||
|  | 					const char *name); | ||||||
|  | extern void mcb_release_mem(struct resource *mem); | ||||||
|  | extern int mcb_get_irq(struct mcb_device *dev); | ||||||
|  | 
 | ||||||
|  | #endif /* _LINUX_MCB_H */ | ||||||
|  | @ -607,4 +607,9 @@ struct rio_device_id { | ||||||
| 	__u16 asm_did, asm_vid; | 	__u16 asm_did, asm_vid; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct mcb_device_id { | ||||||
|  | 	__u16 device; | ||||||
|  | 	kernel_ulong_t driver_data; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| #endif /* LINUX_MOD_DEVICETABLE_H */ | #endif /* LINUX_MOD_DEVICETABLE_H */ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Johannes Thumshirn
						Johannes Thumshirn