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
- Lines: 451
- Date:
2003-08-25 04:44:40.000000000 -0700
- Orig file:
linux-2.4.21/arch/ppc/kernel/ocp.c
- Orig date:
1969-12-31 16:00:00.000000000 -0800
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)