OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [arch/] [ppc/] [kernel/] [ocp.c] - Rev 1765

Compare with Previous | Blame | View Log

/*
 * ocp.c
 *
 *      (c) Benjamin Herrenschmidt (benh@kernel.crashing.org)
 *          Mipsys - France
 *
 "          Derived from work (c) Armin Kuster akuster@pacbell.net
 *
 *
 * 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;  either version 2 of the  License, or (at your
 *  option) any later version.
 *
 *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR   IMPLIED
 *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT,  INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
 *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the  GNU General Public License along
 *  with this program; if not, write  to the Free Software Foundation, Inc.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
 
#include <linux/module.h>
#include <linux/config.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/bootmem.h>
#include <asm/io.h>
#include <asm/ocp.h>
#include <asm/errno.h>
#include <asm/rwsem.h>
#include <asm/semaphore.h>
 
//#define DBG(x)	printk x
#define DBG(x)
 
extern int mem_init_done;
 
extern struct ocp_def core_ocp[];	/* Static list of devices, provided by
					   CPU core */
 
LIST_HEAD(ocp_devices);			/* List of all OCP devices */
LIST_HEAD(ocp_drivers);			/* List of all OCP drivers */
DECLARE_RWSEM(ocp_devices_sem);		/* Global semaphores for those lists */
DECLARE_MUTEX(ocp_drivers_sem);		/* Global semaphores for those lists */
 
static int ocp_inited;
 
/**
 *	ocp_driver_match	-	Match one driver to one device
 *	@drv: driver to match
 *	@dev: device to match
 *
 *	This function returns 0 if the driver and device don't match
 */
static int
ocp_driver_match(struct ocp_driver *drv, struct ocp_device *dev)
{
	const struct ocp_device_id *ids = drv->id_table;
 
	if (!ids)
		return 0;
 
	while (ids->vendor || ids->function) {
		if ((ids->vendor == OCP_ANY_ID
		     || ids->vendor == dev->def->vendor)
		    && (ids->function == OCP_ANY_ID
			|| ids->function == dev->def->function))
		        return 1;
		ids++;
	}
	return 0;
}
 
 
/**
 *	ocp_bind_drivers	-	Match all drivers with all devices
 *	@candidate: driver beeing registered
 *
 *	This function is called on driver registration and device discovery,
 *	it redo the matching of all "driverless" devices with all possible
 *	driver candidates.
 *	The driver beeing registered can be optionally passed in, in which
 *	case, the function will return -ENODEV is no match have been found
 *	or if all matches failed with a different code than -EAGAIN
 */
static int
ocp_bind_drivers(struct ocp_driver *candidate)
{
	struct list_head	*deventry, *drventry;
	struct ocp_device	*dev;
	struct ocp_driver	*drv;
	int			one_again, one_match;
	int			count = 0;
 
	DBG(("ocp: binding drivers...\n"));
	do {
		/* We re-do the match loop if we had a sucess match and got one -EAGAIN
		 */
		one_match = one_again = 0;
		down_read(&ocp_devices_sem);
		list_for_each(deventry, &ocp_devices) {
			dev = list_entry(deventry, struct ocp_device, link);
			if (dev->driver != NULL)
				continue;
			DBG(("ocp: device %s unmatched, trying to match...\n", dev->name));
			list_for_each(drventry, &ocp_drivers) {
				drv = list_entry(drventry, struct ocp_driver, link);
				if (ocp_driver_match(drv, dev)) {
					int rc;
 
					/* Hrm... shall we set dev->driver after or before ? */
					DBG(("ocp: match with driver %s, calling probe...\n", drv->name));
					rc = drv->probe(dev);
					DBG(("ocp: probe result: %d\n", rc));
					if (rc == 0) {
						/* Driver matched, next device */
						dev->driver = drv;
						one_match = 1;
						if (drv == candidate)
							count++;
						break;
					} else if (rc == -EAGAIN) {
						/* Driver matched but asked for later call, next device */
						one_again = 1;
						if (drv == candidate)
							count++;
						break;
					}
				}
			}
		}
		up_read(&ocp_devices_sem);
	} while(one_match && one_again);
	DBG(("ocp: binding drivers... done.\n"));
 
	return count;
}
 
/**
 *	ocp_register_driver	-	Register an OCP driver
 *	@drv: pointer to statically defined ocp_driver structure
 *
 *	The driver's probe() callback is called either recursively
 *	by this function or upon later call of ocp_driver_init
 *
 *      NOTE: Probe is called with ocp_drivers_sem held, it shouldn't
 *      call ocp_register/unregister_driver on his own code path.
 *      however, it _can_ call ocp_find_device().
 *
 *	NOTE2: Detection of devices is a 2 pass step on this implementation,
 *	hotswap isn't supported. First, all OCP devices are put in the device
 *	list, _then_ all drivers are probed on each match.
 *
 *      NOTE3: Drivers are allowed to return -EAGAIN from the probe() routine.
 *      this will cause them to be called again for this specific device as
 *	soon as another device have been probed or another driver registered.
 *	this, gives a simple way for a driver like EMAC to wait for another driver,
 *	like MAL to be up. There is potentially a small race if MAL happens to
 *	unregister, but this is hopefully never happening.
 *
 *	This function returns a count of how many devices actually matched
 *	(wether the probe routine returned 0 or -EAGAIN, a different error
 *	code isn't considered as a match).
 */
 
int
ocp_register_driver(struct ocp_driver *drv)
{
	int	rc = 0;
 
	DBG(("ocp: ocp_register_driver(%s)...\n", drv->name));
 
	/* Add to driver list */
	down(&ocp_drivers_sem);
	list_add_tail(&drv->link, &ocp_drivers);
 
	/* Check matching devices */
	rc = ocp_bind_drivers(drv);
 
	up(&ocp_drivers_sem);
 
	DBG(("ocp: ocp_register_driver(%s)... done, count: %d.\n", drv->name, rc));
 
	return rc;
}
 
/**
 *	ocp_unregister_driver	-	Unregister an OCP driver
 *	@drv: pointer to statically defined ocp_driver structure
 *
 *	The driver's remove() callback is called recursively
 *	by this function for any device already registered
 */
 
void
ocp_unregister_driver(struct ocp_driver *drv)
{
	struct ocp_device	*dev;
	struct list_head	*entry;
 
	DBG(("ocp: ocp_unregister_driver(%s)...\n", drv->name));
 
	/* Call remove() routine for all devices using it */
	down(&ocp_drivers_sem);
	down_read(&ocp_devices_sem);
	list_for_each(entry, &ocp_devices) {
		dev = list_entry(entry, struct ocp_device, link);
		if (dev->driver == drv) {
			drv->remove(dev);
			dev->driver = NULL;
			dev->drvdata = NULL;
		}
	}
	up_read(&ocp_devices_sem);
 
	/* Unlink driver structure */
	list_del_init(&drv->link);
	up(&ocp_drivers_sem);
 
	DBG(("ocp: ocp_unregister_driver(%s)... done.\n", drv->name));
}
 
/* Core of ocp_find_device(). Caller must hold ocp_devices_sem */
static struct ocp_device *
__ocp_find_device(unsigned int vendor, unsigned int function, int index)
{
	struct list_head	*entry;
	struct ocp_device	*dev, *found = NULL;
 
	DBG(("ocp: __ocp_find_device(vendor: %x, function: %x, index: %d)...\n", vendor, function, index));
 
	list_for_each(entry, &ocp_devices) {
		dev = list_entry(entry, struct ocp_device, link);
		if (vendor != OCP_ANY_ID && vendor != dev->def->vendor)
			continue;
		if (function != OCP_ANY_ID && function != dev->def->function)
			continue;
		if (index != OCP_ANY_INDEX && index != dev->def->index)
			continue;
		found = dev;
		break;
	}
 
	DBG(("ocp: __ocp_find_device(vendor: %x, function: %x, index: %d)... done\n", vendor, function, index));
 
	return found;
}
 
/**
 *	ocp_find_device	-	Find a device by function & index
 *      @vendor: vendor ID of the device (or OCP_ANY_ID)
 *	@function: function code of the device (or OCP_ANY_ID)
 *	@idx: index of the device (or OCP_ANY_INDEX)
 *
 *	This function allows a lookup of a given function by it's
 *	index, it's typically used to find the MAL or ZMII associated
 *	with an EMAC or similar horrors.
 *      You can pass vendor, though you usually want OCP_ANY_ID there...
 */
struct ocp_device *
ocp_find_device(unsigned int vendor, unsigned int function, int index)
{
	struct ocp_device	*dev;
 
	down_read(&ocp_devices_sem);
	dev = __ocp_find_device(vendor, function, index);
	up_read(&ocp_devices_sem);
 
	return dev;
}
 
/**
 *	ocp_get_one_device -	Find a def by function & index
 *      @vendor: vendor ID of the device (or OCP_ANY_ID)
 *	@function: function code of the device (or OCP_ANY_ID)
 *	@idx: index of the device (or OCP_ANY_INDEX)
 *
 *	This function allows a lookup of a given ocp_def by it's
 *	vendor, function, and index.  The main purpose for is to
 *	allow modification of the def before binding to the driver
 */
struct ocp_def *
ocp_get_one_device(unsigned int vendor, unsigned int function, int index)
{
	struct ocp_device	*dev;
	struct ocp_def		*found = NULL;
 
	DBG(("ocp: ocp_get_one_device(vendor: %x, function: %x, index: %d)...\n",
		vendor, function, index));
 
	dev = ocp_find_device(vendor, function, index);
 
	if (dev) 
		found = dev->def;
 
	DBG(("ocp: ocp_get_one_device(vendor: %x, function: %x, index: %d)... done.\n",
		vendor, function, index));
 
	return found;
}
 
/**
 *	ocp_add_one_device	-	Add a device
 *	@def: static device definition structure
 *
 *	This function adds a device definition to the
 *	device list. It may only be called before
 *	ocp_driver_init() and will return an error
 *	otherwise.
 */
int
ocp_add_one_device(struct ocp_def *def)
{
	struct	ocp_device	*dev;
 
	DBG(("ocp: ocp_add_one_device(vendor: %x, function: %x, index: %d)...\n", vendor, function, index));
 
	/* Can't be called after ocp driver init */
	if (ocp_inited)
		return 1;
 
	if (mem_init_done)
		dev = kmalloc(sizeof(*dev), GFP_KERNEL);
	else
		dev = alloc_bootmem(sizeof(*dev));
 
	if (dev == NULL)
		return 1;
	memset(dev, 0, sizeof(*dev));
	dev->def = def;
	dev->current_state = 4;
	sprintf(dev->name, "OCP device %04x:%04x:%04x",
		dev->def->vendor, dev->def->function, dev->def->index);
	down_write(&ocp_devices_sem);
	list_add_tail(&dev->link, &ocp_devices);
	up_write(&ocp_devices_sem);
 
	DBG(("ocp: ocp_add_one_device(vendor: %x, function: %x, index: %d)...done.\n", vendor, function, index));
 
	return 0;
}
 
/**
 *	ocp_remove_one_device -	Remove a device by function & index
 *      @vendor: vendor ID of the device (or OCP_ANY_ID)
 *	@function: function code of the device (or OCP_ANY_ID)
 *	@idx: index of the device (or OCP_ANY_INDEX)
 *
 *	This function allows removal of a given function by its
 *	index. It may only be called before ocp_driver_init()
 *	and will return an error otherwise.
 */
int
ocp_remove_one_device(unsigned int vendor, unsigned int function, int index)
{
	struct ocp_device *dev;
	int	rc = 0;
 
	DBG(("ocp: ocp_remove_one_device(vendor: %x, function: %x, index: %d)...\n", vendor, function, index));
 
	/* Can't be called after ocp driver init */
	if (ocp_inited)
		return 1;
 
	down_write(&ocp_devices_sem);
	dev = __ocp_find_device(vendor, function, index);
	if (dev != NULL)
		list_del((struct list_head *)dev);
	else
		rc = 1;
	up_write(&ocp_devices_sem);
 
	DBG(("ocp: ocp_remove_one_device(vendor: %x, function: %x, index: %d)... done.\n", vendor, function, index));
 
	return rc;
}
 
#ifdef CONFIG_PM
/**
 * OCP Power management..
 *
 * This needs to be done centralized, so that we power manage PCI
 * devices in the right order: we should not shut down PCI bridges
 * before we've shut down the devices behind them, and we should
 * not wake up devices before we've woken up the bridge to the
 * device.. Eh?
 *
 * We do not touch devices that don't have a driver that exports
 * a suspend/resume function. That is just too dangerous. If the default
 * PCI suspend/resume functions work for a device, the driver can
 * easily implement them (ie just have a suspend function that calls
 * the pci_set_power_state() function).
 *
 * BenH: Implementation here couldn't work properly. This version
 *       slightly modified and _might_ be more useable, but real
 *       PM support will probably have to wait for 2.5
 */
 
static int ocp_pm_save_state_device(struct ocp_device *dev, u32 state)
{
	int error = 0;
	if (dev) {
		struct ocp_driver *driver = dev->driver;
		if (driver && driver->save_state)
			error = driver->save_state(dev,state);
	}
	return error;
}
 
static int ocp_pm_suspend_device(struct ocp_device *dev, u32 state)
{
	int error = 0;
	if (dev) {
		struct ocp_driver *driver = dev->driver;
		if (driver && driver->suspend)
			error = driver->suspend(dev,state);
	}
	return error;
}
 
static int ocp_pm_resume_device(struct ocp_device *dev)
{
	int error = 0;
	if (dev) {
		struct ocp_driver *driver = dev->driver;
		if (driver && driver->resume)
			error = driver->resume(dev);
	}
	return error;
}
 
static int
ocp_pm_callback(struct pm_dev *pm_device, pm_request_t rqst, void *data)
{
	int error = 0;
	struct list_head	*entry;
	struct ocp_device	*dev;
 
	down(&ocp_drivers_sem);
	down_read(&ocp_devices_sem);
 
	list_for_each(entry, &ocp_devices) {
		dev = list_entry(entry, struct ocp_device, link);
		switch (rqst) {
		case PM_SAVE_STATE:
			error = ocp_pm_save_state_device(dev, 3);
			break;
		case PM_SUSPEND:
			error = ocp_pm_suspend_device(dev, 3);
			break;
		case PM_RESUME:
			error = ocp_pm_resume_device(dev);
			break;
		default: break;
		}
		if (error)
			break;
	}
	return error;
}
 
/*
 * Is this ever used ?
 */
void
ppc4xx_cpm_fr(u32 bits, int val)
{
	unsigned long flags;
 
	save_flags(flags);
	cli();
 
	if (val)
		mtdcr(DCRN_CPMFR, mfdcr(DCRN_CPMFR) | bits);
	else
		mtdcr(DCRN_CPMFR, mfdcr(DCRN_CPMFR) & ~bits);
 
	restore_flags(flags);
}
#endif /* CONFIG_PM */
 
/**
 *	ocp_early_init	-	Init OCP device management
 *
 *	This function builds the list of devices before setup_arch. 
 *	This allows platform code to modify the device lists before
 *	they are bound to drivers (changes to paddr, removing devices
 *	etc)
 */
int __init
ocp_early_init(void)
{
	struct ocp_def	*def;
 
	DBG(("ocp: ocp_early_init()...\n"));
 
	/* Fill the devices list */
	for (def = core_ocp; def->vendor != OCP_VENDOR_INVALID; def++)
		ocp_add_one_device(def);
 
	DBG(("ocp: ocp_early_init()... done.\n"));
 
	return 0;
}
 
/**
 *	ocp_driver_init	-	Init OCP device management
 *
 *	This function is meant to be called once, and only once to initialize
 *	the OCP device management. Note that it can actually be called at any 
 *	time, it's perfectly legal to register drivers before 
 *	ocp_driver_init() is called
 */
int
ocp_driver_init(void)
{
	/* ocp_driver_init is by default an initcall. If your arch requires 
	 * this to be called earlier, then go on, ocp_driver_init is 
	 * non-static for that purpose, and can safely be called twice
	 */
	if (ocp_inited)
		return 0;
	ocp_inited = 1;
 
	DBG(("ocp: ocp_driver_init()...\n"));
 
	/* Call drivers probes */
	down(&ocp_drivers_sem);
	ocp_bind_drivers(NULL);
	up(&ocp_drivers_sem);
 
#ifdef CONFIG_PM
	pm_register(PM_SYS_DEV, 0, ocp_pm_callback);
#endif
 
	DBG(("ocp: ocp_driver_init()... done.\n"));
 
	return 0;
}
 
__initcall(ocp_driver_init);
 
EXPORT_SYMBOL(ocp_find_device);
EXPORT_SYMBOL(ocp_register_driver);
EXPORT_SYMBOL(ocp_unregister_driver);
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2025 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.