patch-2.4.22 linux-2.4.22/arch/ppc/kernel/ocp.c

Next file: linux-2.4.22/arch/ppc/kernel/open_pic.c
Previous file: linux-2.4.22/arch/ppc/kernel/mpc10x_common.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.21/arch/ppc/kernel/ocp.c linux-2.4.22/arch/ppc/kernel/ocp.c
@@ -0,0 +1,450 @@
+/*
+ * 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 <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 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_init
+ *
+ *      NOTE: Probe is called with ocp_drivers_sem heocp_find_deviceld, 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));
+}
+
+/**
+ *	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 list_head	*entry;
+	struct ocp_device	*dev, *found = NULL;
+
+	DBG(("ocp: ocp_find_device(vendor: %x, function: %x, index: %d)...\n",
+		vendor, function, index));
+
+	down_read(&ocp_devices_sem);
+	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;
+	}
+	up_read(&ocp_devices_sem);
+
+	DBG(("ocp: ocp_find_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 is solely meant to be called by ocp_init
+ *	to add one device from the core static list to the
+ *	device list
+ */
+static void
+ocp_add_one_device(struct ocp_def *def)
+{
+	struct	ocp_device	*dev;
+
+	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL)
+		return;
+	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);
+	list_add_tail(&dev->link, &ocp_devices);
+}
+
+#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_init	-	Init OCP device management
+ *
+ *	This function is meant to be called once, and only once to initialize
+ *	the OCP device management and build the list of devices. Note that
+ *	it can actually be called at any time, it's perfectly legal to
+ *	register drivers before ocp_init() is called
+ */
+int
+ocp_init(void)
+{
+	struct ocp_def	*def;
+
+	/* ocp_init is by default an initcall. If your arch requires this to
+	 * be called earlier, then go on, ocp_init is non-static for that purpose,
+	 * and can safely be called twice
+	 */
+	if (ocp_inited)
+		return 0;
+	ocp_inited = 1;
+
+	DBG(("ocp: ocp_init()...\n"));
+
+	/* Fill the devices list */
+	down_write(&ocp_devices_sem);
+	for (def = core_ocp; def->vendor != OCP_VENDOR_INVALID; def++)
+		ocp_add_one_device(def);
+	up_write(&ocp_devices_sem);
+
+	DBG(("ocp: ocp_init()... added...\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_init()... done.\n"));
+
+	return 0;
+}
+
+__initcall(ocp_init);
+
+EXPORT_SYMBOL(ocp_register_driver);
+EXPORT_SYMBOL(ocp_unregister_driver);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)