diff options
author | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
---|---|---|
committer | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
commit | 849369d6c66d3054688672f97d31fceb8e8230fb (patch) | |
tree | 6135abc790ca67dedbe07c39806591e70eda81ce /drivers/rapidio | |
download | linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2 linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip |
initial_commit
Diffstat (limited to 'drivers/rapidio')
-rw-r--r-- | drivers/rapidio/Kconfig | 34 | ||||
-rw-r--r-- | drivers/rapidio/Makefile | 8 | ||||
-rw-r--r-- | drivers/rapidio/rio-access.c | 175 | ||||
-rw-r--r-- | drivers/rapidio/rio-driver.c | 226 | ||||
-rw-r--r-- | drivers/rapidio/rio-scan.c | 1314 | ||||
-rw-r--r-- | drivers/rapidio/rio-sysfs.c | 287 | ||||
-rw-r--r-- | drivers/rapidio/rio.c | 1197 | ||||
-rw-r--r-- | drivers/rapidio/rio.h | 72 | ||||
-rw-r--r-- | drivers/rapidio/switches/Kconfig | 35 | ||||
-rw-r--r-- | drivers/rapidio/switches/Makefile | 9 | ||||
-rw-r--r-- | drivers/rapidio/switches/idt_gen2.c | 430 | ||||
-rw-r--r-- | drivers/rapidio/switches/idtcps.c | 149 | ||||
-rw-r--r-- | drivers/rapidio/switches/tsi500.c | 78 | ||||
-rw-r--r-- | drivers/rapidio/switches/tsi568.c | 146 | ||||
-rw-r--r-- | drivers/rapidio/switches/tsi57x.c | 318 |
15 files changed, 4478 insertions, 0 deletions
diff --git a/drivers/rapidio/Kconfig b/drivers/rapidio/Kconfig new file mode 100644 index 00000000..070211a5 --- /dev/null +++ b/drivers/rapidio/Kconfig @@ -0,0 +1,34 @@ +# +# RapidIO configuration +# +config RAPIDIO_DISC_TIMEOUT + int "Discovery timeout duration (seconds)" + depends on RAPIDIO + default "30" + ---help--- + Amount of time a discovery node waits for a host to complete + enumeration before giving up. + +config RAPIDIO_ENABLE_RX_TX_PORTS + bool "Enable RapidIO Input/Output Ports" + depends on RAPIDIO + ---help--- + The RapidIO specification describes a Output port transmit + enable and a Input port receive enable. The recommended state + for Input ports and Output ports should be disabled. When + this switch is set the RapidIO subsystem will enable all + ports for Input/Output direction to allow other traffic + than Maintenance transfers. + +source "drivers/rapidio/switches/Kconfig" + +config RAPIDIO_DEBUG + bool "RapidIO subsystem debug messages" + depends on RAPIDIO + help + Say Y here if you want the RapidIO subsystem to produce a bunch of + debug messages to the system log. Select this if you are having a + problem with the RapidIO subsystem and want to see more of what is + going on. + + If you are unsure about this, say N here. diff --git a/drivers/rapidio/Makefile b/drivers/rapidio/Makefile new file mode 100644 index 00000000..89b8eca8 --- /dev/null +++ b/drivers/rapidio/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for RapidIO interconnect services +# +obj-y += rio.o rio-access.o rio-driver.o rio-scan.o rio-sysfs.o + +obj-$(CONFIG_RAPIDIO) += switches/ + +subdir-ccflags-$(CONFIG_RAPIDIO_DEBUG) := -DDEBUG diff --git a/drivers/rapidio/rio-access.c b/drivers/rapidio/rio-access.c new file mode 100644 index 00000000..a3824bac --- /dev/null +++ b/drivers/rapidio/rio-access.c @@ -0,0 +1,175 @@ +/* + * RapidIO configuration space access support + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/rio.h> +#include <linux/module.h> + +/* + * These interrupt-safe spinlocks protect all accesses to RIO + * configuration space and doorbell access. + */ +static DEFINE_SPINLOCK(rio_config_lock); +static DEFINE_SPINLOCK(rio_doorbell_lock); + +/* + * Wrappers for all RIO configuration access functions. They just check + * alignment, do locking and call the low-level functions pointed to + * by rio_mport->ops. + */ + +#define RIO_8_BAD 0 +#define RIO_16_BAD (offset & 1) +#define RIO_32_BAD (offset & 3) + +/** + * RIO_LOP_READ - Generate rio_local_read_config_* functions + * @size: Size of configuration space read (8, 16, 32 bits) + * @type: C type of value argument + * @len: Length of configuration space read (1, 2, 4 bytes) + * + * Generates rio_local_read_config_* functions used to access + * configuration space registers on the local device. + */ +#define RIO_LOP_READ(size,type,len) \ +int __rio_local_read_config_##size \ + (struct rio_mport *mport, u32 offset, type *value) \ +{ \ + int res; \ + unsigned long flags; \ + u32 data = 0; \ + if (RIO_##size##_BAD) return RIO_BAD_SIZE; \ + spin_lock_irqsave(&rio_config_lock, flags); \ + res = mport->ops->lcread(mport, mport->id, offset, len, &data); \ + *value = (type)data; \ + spin_unlock_irqrestore(&rio_config_lock, flags); \ + return res; \ +} + +/** + * RIO_LOP_WRITE - Generate rio_local_write_config_* functions + * @size: Size of configuration space write (8, 16, 32 bits) + * @type: C type of value argument + * @len: Length of configuration space write (1, 2, 4 bytes) + * + * Generates rio_local_write_config_* functions used to access + * configuration space registers on the local device. + */ +#define RIO_LOP_WRITE(size,type,len) \ +int __rio_local_write_config_##size \ + (struct rio_mport *mport, u32 offset, type value) \ +{ \ + int res; \ + unsigned long flags; \ + if (RIO_##size##_BAD) return RIO_BAD_SIZE; \ + spin_lock_irqsave(&rio_config_lock, flags); \ + res = mport->ops->lcwrite(mport, mport->id, offset, len, value);\ + spin_unlock_irqrestore(&rio_config_lock, flags); \ + return res; \ +} + +RIO_LOP_READ(8, u8, 1) +RIO_LOP_READ(16, u16, 2) +RIO_LOP_READ(32, u32, 4) +RIO_LOP_WRITE(8, u8, 1) +RIO_LOP_WRITE(16, u16, 2) +RIO_LOP_WRITE(32, u32, 4) + +EXPORT_SYMBOL_GPL(__rio_local_read_config_8); +EXPORT_SYMBOL_GPL(__rio_local_read_config_16); +EXPORT_SYMBOL_GPL(__rio_local_read_config_32); +EXPORT_SYMBOL_GPL(__rio_local_write_config_8); +EXPORT_SYMBOL_GPL(__rio_local_write_config_16); +EXPORT_SYMBOL_GPL(__rio_local_write_config_32); + +/** + * RIO_OP_READ - Generate rio_mport_read_config_* functions + * @size: Size of configuration space read (8, 16, 32 bits) + * @type: C type of value argument + * @len: Length of configuration space read (1, 2, 4 bytes) + * + * Generates rio_mport_read_config_* functions used to access + * configuration space registers on the local device. + */ +#define RIO_OP_READ(size,type,len) \ +int rio_mport_read_config_##size \ + (struct rio_mport *mport, u16 destid, u8 hopcount, u32 offset, type *value) \ +{ \ + int res; \ + unsigned long flags; \ + u32 data = 0; \ + if (RIO_##size##_BAD) return RIO_BAD_SIZE; \ + spin_lock_irqsave(&rio_config_lock, flags); \ + res = mport->ops->cread(mport, mport->id, destid, hopcount, offset, len, &data); \ + *value = (type)data; \ + spin_unlock_irqrestore(&rio_config_lock, flags); \ + return res; \ +} + +/** + * RIO_OP_WRITE - Generate rio_mport_write_config_* functions + * @size: Size of configuration space write (8, 16, 32 bits) + * @type: C type of value argument + * @len: Length of configuration space write (1, 2, 4 bytes) + * + * Generates rio_mport_write_config_* functions used to access + * configuration space registers on the local device. + */ +#define RIO_OP_WRITE(size,type,len) \ +int rio_mport_write_config_##size \ + (struct rio_mport *mport, u16 destid, u8 hopcount, u32 offset, type value) \ +{ \ + int res; \ + unsigned long flags; \ + if (RIO_##size##_BAD) return RIO_BAD_SIZE; \ + spin_lock_irqsave(&rio_config_lock, flags); \ + res = mport->ops->cwrite(mport, mport->id, destid, hopcount, offset, len, value); \ + spin_unlock_irqrestore(&rio_config_lock, flags); \ + return res; \ +} + +RIO_OP_READ(8, u8, 1) +RIO_OP_READ(16, u16, 2) +RIO_OP_READ(32, u32, 4) +RIO_OP_WRITE(8, u8, 1) +RIO_OP_WRITE(16, u16, 2) +RIO_OP_WRITE(32, u32, 4) + +EXPORT_SYMBOL_GPL(rio_mport_read_config_8); +EXPORT_SYMBOL_GPL(rio_mport_read_config_16); +EXPORT_SYMBOL_GPL(rio_mport_read_config_32); +EXPORT_SYMBOL_GPL(rio_mport_write_config_8); +EXPORT_SYMBOL_GPL(rio_mport_write_config_16); +EXPORT_SYMBOL_GPL(rio_mport_write_config_32); + +/** + * rio_mport_send_doorbell - Send a doorbell message + * + * @mport: RIO master port + * @destid: RIO device destination ID + * @data: Doorbell message data + * + * Send a doorbell message to a RIO device. The doorbell message + * has a 16-bit info field provided by the data argument. + */ +int rio_mport_send_doorbell(struct rio_mport *mport, u16 destid, u16 data) +{ + int res; + unsigned long flags; + + spin_lock_irqsave(&rio_doorbell_lock, flags); + res = mport->ops->dsend(mport, mport->id, destid, data); + spin_unlock_irqrestore(&rio_doorbell_lock, flags); + + return res; +} + +EXPORT_SYMBOL_GPL(rio_mport_send_doorbell); diff --git a/drivers/rapidio/rio-driver.c b/drivers/rapidio/rio-driver.c new file mode 100644 index 00000000..0f4a53bd --- /dev/null +++ b/drivers/rapidio/rio-driver.c @@ -0,0 +1,226 @@ +/* + * RapidIO driver support + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/rio.h> +#include <linux/rio_ids.h> + +#include "rio.h" + +/** + * rio_match_device - Tell if a RIO device has a matching RIO device id structure + * @id: the RIO device id structure to match against + * @rdev: the RIO device structure to match against + * + * Used from driver probe and bus matching to check whether a RIO device + * matches a device id structure provided by a RIO driver. Returns the + * matching &struct rio_device_id or %NULL if there is no match. + */ +static const struct rio_device_id *rio_match_device(const struct rio_device_id + *id, + const struct rio_dev *rdev) +{ + while (id->vid || id->asm_vid) { + if (((id->vid == RIO_ANY_ID) || (id->vid == rdev->vid)) && + ((id->did == RIO_ANY_ID) || (id->did == rdev->did)) && + ((id->asm_vid == RIO_ANY_ID) + || (id->asm_vid == rdev->asm_vid)) + && ((id->asm_did == RIO_ANY_ID) + || (id->asm_did == rdev->asm_did))) + return id; + id++; + } + return NULL; +} + +/** + * rio_dev_get - Increments the reference count of the RIO device structure + * + * @rdev: RIO device being referenced + * + * Each live reference to a device should be refcounted. + * + * Drivers for RIO devices should normally record such references in + * their probe() methods, when they bind to a device, and release + * them by calling rio_dev_put(), in their disconnect() methods. + */ +struct rio_dev *rio_dev_get(struct rio_dev *rdev) +{ + if (rdev) + get_device(&rdev->dev); + + return rdev; +} + +/** + * rio_dev_put - Release a use of the RIO device structure + * + * @rdev: RIO device being disconnected + * + * Must be called when a user of a device is finished with it. + * When the last user of the device calls this function, the + * memory of the device is freed. + */ +void rio_dev_put(struct rio_dev *rdev) +{ + if (rdev) + put_device(&rdev->dev); +} + +/** + * rio_device_probe - Tell if a RIO device structure has a matching RIO device id structure + * @dev: the RIO device structure to match against + * + * return 0 and set rio_dev->driver when drv claims rio_dev, else error + */ +static int rio_device_probe(struct device *dev) +{ + struct rio_driver *rdrv = to_rio_driver(dev->driver); + struct rio_dev *rdev = to_rio_dev(dev); + int error = -ENODEV; + const struct rio_device_id *id; + + if (!rdev->driver && rdrv->probe) { + if (!rdrv->id_table) + return error; + id = rio_match_device(rdrv->id_table, rdev); + rio_dev_get(rdev); + if (id) + error = rdrv->probe(rdev, id); + if (error >= 0) { + rdev->driver = rdrv; + error = 0; + } else + rio_dev_put(rdev); + } + return error; +} + +/** + * rio_device_remove - Remove a RIO device from the system + * + * @dev: the RIO device structure to match against + * + * Remove a RIO device from the system. If it has an associated + * driver, then run the driver remove() method. Then update + * the reference count. + */ +static int rio_device_remove(struct device *dev) +{ + struct rio_dev *rdev = to_rio_dev(dev); + struct rio_driver *rdrv = rdev->driver; + + if (rdrv) { + if (rdrv->remove) + rdrv->remove(rdev); + rdev->driver = NULL; + } + + rio_dev_put(rdev); + + return 0; +} + +/** + * rio_register_driver - register a new RIO driver + * @rdrv: the RIO driver structure to register + * + * Adds a &struct rio_driver to the list of registered drivers. + * Returns a negative value on error, otherwise 0. If no error + * occurred, the driver remains registered even if no device + * was claimed during registration. + */ +int rio_register_driver(struct rio_driver *rdrv) +{ + /* initialize common driver fields */ + rdrv->driver.name = rdrv->name; + rdrv->driver.bus = &rio_bus_type; + + /* register with core */ + return driver_register(&rdrv->driver); +} + +/** + * rio_unregister_driver - unregister a RIO driver + * @rdrv: the RIO driver structure to unregister + * + * Deletes the &struct rio_driver from the list of registered RIO + * drivers, gives it a chance to clean up by calling its remove() + * function for each device it was responsible for, and marks those + * devices as driverless. + */ +void rio_unregister_driver(struct rio_driver *rdrv) +{ + driver_unregister(&rdrv->driver); +} + +/** + * rio_match_bus - Tell if a RIO device structure has a matching RIO driver device id structure + * @dev: the standard device structure to match against + * @drv: the standard driver structure containing the ids to match against + * + * Used by a driver to check whether a RIO device present in the + * system is in its list of supported devices. Returns 1 if + * there is a matching &struct rio_device_id or 0 if there is + * no match. + */ +static int rio_match_bus(struct device *dev, struct device_driver *drv) +{ + struct rio_dev *rdev = to_rio_dev(dev); + struct rio_driver *rdrv = to_rio_driver(drv); + const struct rio_device_id *id = rdrv->id_table; + const struct rio_device_id *found_id; + + if (!id) + goto out; + + found_id = rio_match_device(id, rdev); + + if (found_id) + return 1; + + out:return 0; +} + +struct device rio_bus = { + .init_name = "rapidio", +}; + +struct bus_type rio_bus_type = { + .name = "rapidio", + .match = rio_match_bus, + .dev_attrs = rio_dev_attrs, + .probe = rio_device_probe, + .remove = rio_device_remove, +}; + +/** + * rio_bus_init - Register the RapidIO bus with the device model + * + * Registers the RIO bus device and RIO bus type with the Linux + * device model. + */ +static int __init rio_bus_init(void) +{ + if (device_register(&rio_bus) < 0) + printk("RIO: failed to register RIO bus device\n"); + return bus_register(&rio_bus_type); +} + +postcore_initcall(rio_bus_init); + +EXPORT_SYMBOL_GPL(rio_register_driver); +EXPORT_SYMBOL_GPL(rio_unregister_driver); +EXPORT_SYMBOL_GPL(rio_bus_type); +EXPORT_SYMBOL_GPL(rio_dev_get); +EXPORT_SYMBOL_GPL(rio_dev_put); diff --git a/drivers/rapidio/rio-scan.c b/drivers/rapidio/rio-scan.c new file mode 100644 index 00000000..ebe77dd8 --- /dev/null +++ b/drivers/rapidio/rio-scan.c @@ -0,0 +1,1314 @@ +/* + * RapidIO enumeration and discovery support + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * Copyright 2009 Integrated Device Technology, Inc. + * Alex Bounine <alexandre.bounine@idt.com> + * - Added Port-Write/Error Management initialization and handling + * + * Copyright 2009 Sysgo AG + * Thomas Moll <thomas.moll@sysgo.com> + * - Added Input- Output- enable functionality, to allow full communication + * + * 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. + */ + +#include <linux/types.h> +#include <linux/kernel.h> + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include <linux/rio_regs.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/slab.h> + +#include "rio.h" + +LIST_HEAD(rio_devices); +static LIST_HEAD(rio_switches); + +static void rio_enum_timeout(unsigned long); + +static void rio_init_em(struct rio_dev *rdev); + +DEFINE_SPINLOCK(rio_global_list_lock); + +static int next_destid = 0; +static int next_net = 0; +static int next_comptag = 1; + +static struct timer_list rio_enum_timer = +TIMER_INITIALIZER(rio_enum_timeout, 0, 0); + +static int rio_mport_phys_table[] = { + RIO_EFB_PAR_EP_ID, + RIO_EFB_PAR_EP_REC_ID, + RIO_EFB_SER_EP_ID, + RIO_EFB_SER_EP_REC_ID, + -1, +}; + +/** + * rio_get_device_id - Get the base/extended device id for a device + * @port: RIO master port + * @destid: Destination ID of device + * @hopcount: Hopcount to device + * + * Reads the base/extended device id from a device. Returns the + * 8/16-bit device ID. + */ +static u16 rio_get_device_id(struct rio_mport *port, u16 destid, u8 hopcount) +{ + u32 result; + + rio_mport_read_config_32(port, destid, hopcount, RIO_DID_CSR, &result); + + return RIO_GET_DID(port->sys_size, result); +} + +/** + * rio_set_device_id - Set the base/extended device id for a device + * @port: RIO master port + * @destid: Destination ID of device + * @hopcount: Hopcount to device + * @did: Device ID value to be written + * + * Writes the base/extended device id from a device. + */ +static void rio_set_device_id(struct rio_mport *port, u16 destid, u8 hopcount, u16 did) +{ + rio_mport_write_config_32(port, destid, hopcount, RIO_DID_CSR, + RIO_SET_DID(port->sys_size, did)); +} + +/** + * rio_local_set_device_id - Set the base/extended device id for a port + * @port: RIO master port + * @did: Device ID value to be written + * + * Writes the base/extended device id from a device. + */ +static void rio_local_set_device_id(struct rio_mport *port, u16 did) +{ + rio_local_write_config_32(port, RIO_DID_CSR, RIO_SET_DID(port->sys_size, + did)); +} + +/** + * rio_clear_locks- Release all host locks and signal enumeration complete + * @port: Master port to issue transaction + * + * Marks the component tag CSR on each device with the enumeration + * complete flag. When complete, it then release the host locks on + * each device. Returns 0 on success or %-EINVAL on failure. + */ +static int rio_clear_locks(struct rio_mport *port) +{ + struct rio_dev *rdev; + u32 result; + int ret = 0; + + /* Release host device id locks */ + rio_local_write_config_32(port, RIO_HOST_DID_LOCK_CSR, + port->host_deviceid); + rio_local_read_config_32(port, RIO_HOST_DID_LOCK_CSR, &result); + if ((result & 0xffff) != 0xffff) { + printk(KERN_INFO + "RIO: badness when releasing host lock on master port, result %8.8x\n", + result); + ret = -EINVAL; + } + list_for_each_entry(rdev, &rio_devices, global_list) { + rio_write_config_32(rdev, RIO_HOST_DID_LOCK_CSR, + port->host_deviceid); + rio_read_config_32(rdev, RIO_HOST_DID_LOCK_CSR, &result); + if ((result & 0xffff) != 0xffff) { + printk(KERN_INFO + "RIO: badness when releasing host lock on vid %4.4x did %4.4x\n", + rdev->vid, rdev->did); + ret = -EINVAL; + } + + /* Mark device as discovered and enable master */ + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_GEN_CTL_CSR, + &result); + result |= RIO_PORT_GEN_DISCOVERED | RIO_PORT_GEN_MASTER; + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_GEN_CTL_CSR, + result); + } + + return ret; +} + +/** + * rio_enum_host- Set host lock and initialize host destination ID + * @port: Master port to issue transaction + * + * Sets the local host master port lock and destination ID register + * with the host device ID value. The host device ID value is provided + * by the platform. Returns %0 on success or %-1 on failure. + */ +static int rio_enum_host(struct rio_mport *port) +{ + u32 result; + + /* Set master port host device id lock */ + rio_local_write_config_32(port, RIO_HOST_DID_LOCK_CSR, + port->host_deviceid); + + rio_local_read_config_32(port, RIO_HOST_DID_LOCK_CSR, &result); + if ((result & 0xffff) != port->host_deviceid) + return -1; + + /* Set master port destid and init destid ctr */ + rio_local_set_device_id(port, port->host_deviceid); + + if (next_destid == port->host_deviceid) + next_destid++; + + return 0; +} + +/** + * rio_device_has_destid- Test if a device contains a destination ID register + * @port: Master port to issue transaction + * @src_ops: RIO device source operations + * @dst_ops: RIO device destination operations + * + * Checks the provided @src_ops and @dst_ops for the necessary transaction + * capabilities that indicate whether or not a device will implement a + * destination ID register. Returns 1 if true or 0 if false. + */ +static int rio_device_has_destid(struct rio_mport *port, int src_ops, + int dst_ops) +{ + u32 mask = RIO_OPS_READ | RIO_OPS_WRITE | RIO_OPS_ATOMIC_TST_SWP | RIO_OPS_ATOMIC_INC | RIO_OPS_ATOMIC_DEC | RIO_OPS_ATOMIC_SET | RIO_OPS_ATOMIC_CLR; + + return !!((src_ops | dst_ops) & mask); +} + +/** + * rio_release_dev- Frees a RIO device struct + * @dev: LDM device associated with a RIO device struct + * + * Gets the RIO device struct associated a RIO device struct. + * The RIO device struct is freed. + */ +static void rio_release_dev(struct device *dev) +{ + struct rio_dev *rdev; + + rdev = to_rio_dev(dev); + kfree(rdev); +} + +/** + * rio_is_switch- Tests if a RIO device has switch capabilities + * @rdev: RIO device + * + * Gets the RIO device Processing Element Features register + * contents and tests for switch capabilities. Returns 1 if + * the device is a switch or 0 if it is not a switch. + * The RIO device struct is freed. + */ +static int rio_is_switch(struct rio_dev *rdev) +{ + if (rdev->pef & RIO_PEF_SWITCH) + return 1; + return 0; +} + +/** + * rio_switch_init - Sets switch operations for a particular vendor switch + * @rdev: RIO device + * @do_enum: Enumeration/Discovery mode flag + * + * Searches the RIO switch ops table for known switch types. If the vid + * and did match a switch table entry, then call switch initialization + * routine to setup switch-specific routines. + */ +static void rio_switch_init(struct rio_dev *rdev, int do_enum) +{ + struct rio_switch_ops *cur = __start_rio_switch_ops; + struct rio_switch_ops *end = __end_rio_switch_ops; + + while (cur < end) { + if ((cur->vid == rdev->vid) && (cur->did == rdev->did)) { + pr_debug("RIO: calling init routine for %s\n", + rio_name(rdev)); + cur->init_hook(rdev, do_enum); + break; + } + cur++; + } + + if ((cur >= end) && (rdev->pef & RIO_PEF_STD_RT)) { + pr_debug("RIO: adding STD routing ops for %s\n", + rio_name(rdev)); + rdev->rswitch->add_entry = rio_std_route_add_entry; + rdev->rswitch->get_entry = rio_std_route_get_entry; + rdev->rswitch->clr_table = rio_std_route_clr_table; + } + + if (!rdev->rswitch->add_entry || !rdev->rswitch->get_entry) + printk(KERN_ERR "RIO: missing routing ops for %s\n", + rio_name(rdev)); +} + +/** + * rio_add_device- Adds a RIO device to the device model + * @rdev: RIO device + * + * Adds the RIO device to the global device list and adds the RIO + * device to the RIO device list. Creates the generic sysfs nodes + * for an RIO device. + */ +static int __devinit rio_add_device(struct rio_dev *rdev) +{ + int err; + + err = device_add(&rdev->dev); + if (err) + return err; + + spin_lock(&rio_global_list_lock); + list_add_tail(&rdev->global_list, &rio_devices); + spin_unlock(&rio_global_list_lock); + + rio_create_sysfs_dev_files(rdev); + + return 0; +} + +/** + * rio_enable_rx_tx_port - enable input receiver and output transmitter of + * given port + * @port: Master port associated with the RIO network + * @local: local=1 select local port otherwise a far device is reached + * @destid: Destination ID of the device to check host bit + * @hopcount: Number of hops to reach the target + * @port_num: Port (-number on switch) to enable on a far end device + * + * Returns 0 or 1 from on General Control Command and Status Register + * (EXT_PTR+0x3C) + */ +inline int rio_enable_rx_tx_port(struct rio_mport *port, + int local, u16 destid, + u8 hopcount, u8 port_num) { +#ifdef CONFIG_RAPIDIO_ENABLE_RX_TX_PORTS + u32 regval; + u32 ext_ftr_ptr; + + /* + * enable rx input tx output port + */ + pr_debug("rio_enable_rx_tx_port(local = %d, destid = %d, hopcount = " + "%d, port_num = %d)\n", local, destid, hopcount, port_num); + + ext_ftr_ptr = rio_mport_get_physefb(port, local, destid, hopcount); + + if (local) { + rio_local_read_config_32(port, ext_ftr_ptr + + RIO_PORT_N_CTL_CSR(0), + ®val); + } else { + if (rio_mport_read_config_32(port, destid, hopcount, + ext_ftr_ptr + RIO_PORT_N_CTL_CSR(port_num), ®val) < 0) + return -EIO; + } + + if (regval & RIO_PORT_N_CTL_P_TYP_SER) { + /* serial */ + regval = regval | RIO_PORT_N_CTL_EN_RX_SER + | RIO_PORT_N_CTL_EN_TX_SER; + } else { + /* parallel */ + regval = regval | RIO_PORT_N_CTL_EN_RX_PAR + | RIO_PORT_N_CTL_EN_TX_PAR; + } + + if (local) { + rio_local_write_config_32(port, ext_ftr_ptr + + RIO_PORT_N_CTL_CSR(0), regval); + } else { + if (rio_mport_write_config_32(port, destid, hopcount, + ext_ftr_ptr + RIO_PORT_N_CTL_CSR(port_num), regval) < 0) + return -EIO; + } +#endif + return 0; +} + +/** + * rio_setup_device- Allocates and sets up a RIO device + * @net: RIO network + * @port: Master port to send transactions + * @destid: Current destination ID + * @hopcount: Current hopcount + * @do_enum: Enumeration/Discovery mode flag + * + * Allocates a RIO device and configures fields based on configuration + * space contents. If device has a destination ID register, a destination + * ID is either assigned in enumeration mode or read from configuration + * space in discovery mode. If the device has switch capabilities, then + * a switch is allocated and configured appropriately. Returns a pointer + * to a RIO device on success or NULL on failure. + * + */ +static struct rio_dev __devinit *rio_setup_device(struct rio_net *net, + struct rio_mport *port, u16 destid, + u8 hopcount, int do_enum) +{ + int ret = 0; + struct rio_dev *rdev; + struct rio_switch *rswitch = NULL; + int result, rdid; + size_t size; + u32 swpinfo = 0; + + size = sizeof(struct rio_dev); + if (rio_mport_read_config_32(port, destid, hopcount, + RIO_PEF_CAR, &result)) + return NULL; + + if (result & (RIO_PEF_SWITCH | RIO_PEF_MULTIPORT)) { + rio_mport_read_config_32(port, destid, hopcount, + RIO_SWP_INFO_CAR, &swpinfo); + if (result & RIO_PEF_SWITCH) { + size += (RIO_GET_TOTAL_PORTS(swpinfo) * + sizeof(rswitch->nextdev[0])) + sizeof(*rswitch); + } + } + + rdev = kzalloc(size, GFP_KERNEL); + if (!rdev) + return NULL; + + rdev->net = net; + rdev->pef = result; + rdev->swpinfo = swpinfo; + rio_mport_read_config_32(port, destid, hopcount, RIO_DEV_ID_CAR, + &result); + rdev->did = result >> 16; + rdev->vid = result & 0xffff; + rio_mport_read_config_32(port, destid, hopcount, RIO_DEV_INFO_CAR, + &rdev->device_rev); + rio_mport_read_config_32(port, destid, hopcount, RIO_ASM_ID_CAR, + &result); + rdev->asm_did = result >> 16; + rdev->asm_vid = result & 0xffff; + rio_mport_read_config_32(port, destid, hopcount, RIO_ASM_INFO_CAR, + &result); + rdev->asm_rev = result >> 16; + if (rdev->pef & RIO_PEF_EXT_FEATURES) { + rdev->efptr = result & 0xffff; + rdev->phys_efptr = rio_mport_get_physefb(port, 0, destid, + hopcount); + + rdev->em_efptr = rio_mport_get_feature(port, 0, destid, + hopcount, RIO_EFB_ERR_MGMNT); + } + + rio_mport_read_config_32(port, destid, hopcount, RIO_SRC_OPS_CAR, + &rdev->src_ops); + rio_mport_read_config_32(port, destid, hopcount, RIO_DST_OPS_CAR, + &rdev->dst_ops); + + if (do_enum) { + /* Assign component tag to device */ + if (next_comptag >= 0x10000) { + pr_err("RIO: Component Tag Counter Overflow\n"); + goto cleanup; + } + rio_mport_write_config_32(port, destid, hopcount, + RIO_COMPONENT_TAG_CSR, next_comptag); + rdev->comp_tag = next_comptag++; + } else { + rio_mport_read_config_32(port, destid, hopcount, + RIO_COMPONENT_TAG_CSR, + &rdev->comp_tag); + } + + if (rio_device_has_destid(port, rdev->src_ops, rdev->dst_ops)) { + if (do_enum) { + rio_set_device_id(port, destid, hopcount, next_destid); + rdev->destid = next_destid++; + if (next_destid == port->host_deviceid) + next_destid++; + } else + rdev->destid = rio_get_device_id(port, destid, hopcount); + + rdev->hopcount = 0xff; + } else { + /* Switch device has an associated destID which + * will be adjusted later + */ + rdev->destid = destid; + rdev->hopcount = hopcount; + } + + /* If a PE has both switch and other functions, show it as a switch */ + if (rio_is_switch(rdev)) { + rswitch = rdev->rswitch; + rswitch->switchid = rdev->comp_tag & RIO_CTAG_UDEVID; + rswitch->port_ok = 0; + rswitch->route_table = kzalloc(sizeof(u8)* + RIO_MAX_ROUTE_ENTRIES(port->sys_size), + GFP_KERNEL); + if (!rswitch->route_table) + goto cleanup; + /* Initialize switch route table */ + for (rdid = 0; rdid < RIO_MAX_ROUTE_ENTRIES(port->sys_size); + rdid++) + rswitch->route_table[rdid] = RIO_INVALID_ROUTE; + dev_set_name(&rdev->dev, "%02x:s:%04x", rdev->net->id, + rswitch->switchid); + rio_switch_init(rdev, do_enum); + + if (do_enum && rswitch->clr_table) + rswitch->clr_table(port, destid, hopcount, + RIO_GLOBAL_TABLE); + + list_add_tail(&rswitch->node, &rio_switches); + + } else { + if (do_enum) + /*Enable Input Output Port (transmitter reviever)*/ + rio_enable_rx_tx_port(port, 0, destid, hopcount, 0); + + dev_set_name(&rdev->dev, "%02x:e:%04x", rdev->net->id, + rdev->destid); + } + + rdev->dev.bus = &rio_bus_type; + rdev->dev.parent = &rio_bus; + + device_initialize(&rdev->dev); + rdev->dev.release = rio_release_dev; + rio_dev_get(rdev); + + rdev->dma_mask = DMA_BIT_MASK(32); + rdev->dev.dma_mask = &rdev->dma_mask; + rdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + if (rdev->dst_ops & RIO_DST_OPS_DOORBELL) + rio_init_dbell_res(&rdev->riores[RIO_DOORBELL_RESOURCE], + 0, 0xffff); + + ret = rio_add_device(rdev); + if (ret) + goto cleanup; + + return rdev; + +cleanup: + if (rio_is_switch(rdev)) + kfree(rswitch->route_table); + + kfree(rdev); + return NULL; +} + +/** + * rio_sport_is_active- Tests if a switch port has an active connection. + * @port: Master port to send transaction + * @destid: Associated destination ID for switch + * @hopcount: Hopcount to reach switch + * @sport: Switch port number + * + * Reads the port error status CSR for a particular switch port to + * determine if the port has an active link. Returns + * %RIO_PORT_N_ERR_STS_PORT_OK if the port is active or %0 if it is + * inactive. + */ +static int +rio_sport_is_active(struct rio_mport *port, u16 destid, u8 hopcount, int sport) +{ + u32 result = 0; + u32 ext_ftr_ptr; + + ext_ftr_ptr = rio_mport_get_efb(port, 0, destid, hopcount, 0); + + while (ext_ftr_ptr) { + rio_mport_read_config_32(port, destid, hopcount, + ext_ftr_ptr, &result); + result = RIO_GET_BLOCK_ID(result); + if ((result == RIO_EFB_SER_EP_FREE_ID) || + (result == RIO_EFB_SER_EP_FREE_ID_V13P) || + (result == RIO_EFB_SER_EP_FREC_ID)) + break; + + ext_ftr_ptr = rio_mport_get_efb(port, 0, destid, hopcount, + ext_ftr_ptr); + } + + if (ext_ftr_ptr) + rio_mport_read_config_32(port, destid, hopcount, + ext_ftr_ptr + + RIO_PORT_N_ERR_STS_CSR(sport), + &result); + + return result & RIO_PORT_N_ERR_STS_PORT_OK; +} + +/** + * rio_lock_device - Acquires host device lock for specified device + * @port: Master port to send transaction + * @destid: Destination ID for device/switch + * @hopcount: Hopcount to reach switch + * @wait_ms: Max wait time in msec (0 = no timeout) + * + * Attepts to acquire host device lock for specified device + * Returns 0 if device lock acquired or EINVAL if timeout expires. + */ +static int +rio_lock_device(struct rio_mport *port, u16 destid, u8 hopcount, int wait_ms) +{ + u32 result; + int tcnt = 0; + + /* Attempt to acquire device lock */ + rio_mport_write_config_32(port, destid, hopcount, + RIO_HOST_DID_LOCK_CSR, port->host_deviceid); + rio_mport_read_config_32(port, destid, hopcount, + RIO_HOST_DID_LOCK_CSR, &result); + + while (result != port->host_deviceid) { + if (wait_ms != 0 && tcnt == wait_ms) { + pr_debug("RIO: timeout when locking device %x:%x\n", + destid, hopcount); + return -EINVAL; + } + + /* Delay a bit */ + mdelay(1); + tcnt++; + /* Try to acquire device lock again */ + rio_mport_write_config_32(port, destid, + hopcount, + RIO_HOST_DID_LOCK_CSR, + port->host_deviceid); + rio_mport_read_config_32(port, destid, + hopcount, + RIO_HOST_DID_LOCK_CSR, &result); + } + + return 0; +} + +/** + * rio_unlock_device - Releases host device lock for specified device + * @port: Master port to send transaction + * @destid: Destination ID for device/switch + * @hopcount: Hopcount to reach switch + * + * Returns 0 if device lock released or EINVAL if fails. + */ +static int +rio_unlock_device(struct rio_mport *port, u16 destid, u8 hopcount) +{ + u32 result; + + /* Release device lock */ + rio_mport_write_config_32(port, destid, + hopcount, + RIO_HOST_DID_LOCK_CSR, + port->host_deviceid); + rio_mport_read_config_32(port, destid, hopcount, + RIO_HOST_DID_LOCK_CSR, &result); + if ((result & 0xffff) != 0xffff) { + pr_debug("RIO: badness when releasing device lock %x:%x\n", + destid, hopcount); + return -EINVAL; + } + + return 0; +} + +/** + * rio_route_add_entry- Add a route entry to a switch routing table + * @rdev: RIO device + * @table: Routing table ID + * @route_destid: Destination ID to be routed + * @route_port: Port number to be routed + * @lock: lock switch device flag + * + * Calls the switch specific add_entry() method to add a route entry + * on a switch. The route table can be specified using the @table + * argument if a switch has per port routing tables or the normal + * use is to specific all tables (or the global table) by passing + * %RIO_GLOBAL_TABLE in @table. Returns %0 on success or %-EINVAL + * on failure. + */ +static int +rio_route_add_entry(struct rio_dev *rdev, + u16 table, u16 route_destid, u8 route_port, int lock) +{ + int rc; + + if (lock) { + rc = rio_lock_device(rdev->net->hport, rdev->destid, + rdev->hopcount, 1000); + if (rc) + return rc; + } + + rc = rdev->rswitch->add_entry(rdev->net->hport, rdev->destid, + rdev->hopcount, table, + route_destid, route_port); + if (lock) + rio_unlock_device(rdev->net->hport, rdev->destid, + rdev->hopcount); + + return rc; +} + +/** + * rio_route_get_entry- Read a route entry in a switch routing table + * @rdev: RIO device + * @table: Routing table ID + * @route_destid: Destination ID to be routed + * @route_port: Pointer to read port number into + * @lock: lock switch device flag + * + * Calls the switch specific get_entry() method to read a route entry + * in a switch. The route table can be specified using the @table + * argument if a switch has per port routing tables or the normal + * use is to specific all tables (or the global table) by passing + * %RIO_GLOBAL_TABLE in @table. Returns %0 on success or %-EINVAL + * on failure. + */ +static int +rio_route_get_entry(struct rio_dev *rdev, u16 table, + u16 route_destid, u8 *route_port, int lock) +{ + int rc; + + if (lock) { + rc = rio_lock_device(rdev->net->hport, rdev->destid, + rdev->hopcount, 1000); + if (rc) + return rc; + } + + rc = rdev->rswitch->get_entry(rdev->net->hport, rdev->destid, + rdev->hopcount, table, + route_destid, route_port); + if (lock) + rio_unlock_device(rdev->net->hport, rdev->destid, + rdev->hopcount); + + return rc; +} + +/** + * rio_get_host_deviceid_lock- Reads the Host Device ID Lock CSR on a device + * @port: Master port to send transaction + * @hopcount: Number of hops to the device + * + * Used during enumeration to read the Host Device ID Lock CSR on a + * RIO device. Returns the value of the lock register. + */ +static u16 rio_get_host_deviceid_lock(struct rio_mport *port, u8 hopcount) +{ + u32 result; + + rio_mport_read_config_32(port, RIO_ANY_DESTID(port->sys_size), hopcount, + RIO_HOST_DID_LOCK_CSR, &result); + + return (u16) (result & 0xffff); +} + +/** + * rio_enum_peer- Recursively enumerate a RIO network through a master port + * @net: RIO network being enumerated + * @port: Master port to send transactions + * @hopcount: Number of hops into the network + * @prev: Previous RIO device connected to the enumerated one + * @prev_port: Port on previous RIO device + * + * Recursively enumerates a RIO network. Transactions are sent via the + * master port passed in @port. + */ +static int __devinit rio_enum_peer(struct rio_net *net, struct rio_mport *port, + u8 hopcount, struct rio_dev *prev, int prev_port) +{ + int port_num; + int cur_destid; + int sw_destid; + int sw_inport; + struct rio_dev *rdev; + u16 destid; + u32 regval; + int tmp; + + if (rio_mport_chk_dev_access(port, + RIO_ANY_DESTID(port->sys_size), hopcount)) { + pr_debug("RIO: device access check failed\n"); + return -1; + } + + if (rio_get_host_deviceid_lock(port, hopcount) == port->host_deviceid) { + pr_debug("RIO: PE already discovered by this host\n"); + /* + * Already discovered by this host. Add it as another + * link to the existing device. + */ + rio_mport_read_config_32(port, RIO_ANY_DESTID(port->sys_size), + hopcount, RIO_COMPONENT_TAG_CSR, ®val); + + if (regval) { + rdev = rio_get_comptag((regval & 0xffff), NULL); + + if (rdev && prev && rio_is_switch(prev)) { + pr_debug("RIO: redundant path to %s\n", + rio_name(rdev)); + prev->rswitch->nextdev[prev_port] = rdev; + } + } + + return 0; + } + + /* Attempt to acquire device lock */ + rio_mport_write_config_32(port, RIO_ANY_DESTID(port->sys_size), + hopcount, + RIO_HOST_DID_LOCK_CSR, port->host_deviceid); + while ((tmp = rio_get_host_deviceid_lock(port, hopcount)) + < port->host_deviceid) { + /* Delay a bit */ + mdelay(1); + /* Attempt to acquire device lock again */ + rio_mport_write_config_32(port, RIO_ANY_DESTID(port->sys_size), + hopcount, + RIO_HOST_DID_LOCK_CSR, + port->host_deviceid); + } + + if (rio_get_host_deviceid_lock(port, hopcount) > port->host_deviceid) { + pr_debug( + "RIO: PE locked by a higher priority host...retreating\n"); + return -1; + } + + /* Setup new RIO device */ + rdev = rio_setup_device(net, port, RIO_ANY_DESTID(port->sys_size), + hopcount, 1); + if (rdev) { + /* Add device to the global and bus/net specific list. */ + list_add_tail(&rdev->net_list, &net->devices); + rdev->prev = prev; + if (prev && rio_is_switch(prev)) + prev->rswitch->nextdev[prev_port] = rdev; + } else + return -1; + + if (rio_is_switch(rdev)) { + sw_inport = RIO_GET_PORT_NUM(rdev->swpinfo); + rio_route_add_entry(rdev, RIO_GLOBAL_TABLE, + port->host_deviceid, sw_inport, 0); + rdev->rswitch->route_table[port->host_deviceid] = sw_inport; + + for (destid = 0; destid < next_destid; destid++) { + if (destid == port->host_deviceid) + continue; + rio_route_add_entry(rdev, RIO_GLOBAL_TABLE, + destid, sw_inport, 0); + rdev->rswitch->route_table[destid] = sw_inport; + } + + pr_debug( + "RIO: found %s (vid %4.4x did %4.4x) with %d ports\n", + rio_name(rdev), rdev->vid, rdev->did, + RIO_GET_TOTAL_PORTS(rdev->swpinfo)); + sw_destid = next_destid; + for (port_num = 0; + port_num < RIO_GET_TOTAL_PORTS(rdev->swpinfo); + port_num++) { + /*Enable Input Output Port (transmitter reviever)*/ + rio_enable_rx_tx_port(port, 0, + RIO_ANY_DESTID(port->sys_size), + hopcount, port_num); + + if (sw_inport == port_num) { + rdev->rswitch->port_ok |= (1 << port_num); + continue; + } + + cur_destid = next_destid; + + if (rio_sport_is_active + (port, RIO_ANY_DESTID(port->sys_size), hopcount, + port_num)) { + pr_debug( + "RIO: scanning device on port %d\n", + port_num); + rdev->rswitch->port_ok |= (1 << port_num); + rio_route_add_entry(rdev, RIO_GLOBAL_TABLE, + RIO_ANY_DESTID(port->sys_size), + port_num, 0); + + if (rio_enum_peer(net, port, hopcount + 1, + rdev, port_num) < 0) + return -1; + + /* Update routing tables */ + if (next_destid > cur_destid) { + for (destid = cur_destid; + destid < next_destid; destid++) { + if (destid == port->host_deviceid) + continue; + rio_route_add_entry(rdev, + RIO_GLOBAL_TABLE, + destid, + port_num, + 0); + rdev->rswitch-> + route_table[destid] = + port_num; + } + } + } else { + /* If switch supports Error Management, + * set PORT_LOCKOUT bit for unused port + */ + if (rdev->em_efptr) + rio_set_port_lockout(rdev, port_num, 1); + + rdev->rswitch->port_ok &= ~(1 << port_num); + } + } + + /* Direct Port-write messages to the enumeratiing host */ + if ((rdev->src_ops & RIO_SRC_OPS_PORT_WRITE) && + (rdev->em_efptr)) { + rio_write_config_32(rdev, + rdev->em_efptr + RIO_EM_PW_TGT_DEVID, + (port->host_deviceid << 16) | + (port->sys_size << 15)); + } + + rio_init_em(rdev); + + /* Check for empty switch */ + if (next_destid == sw_destid) { + next_destid++; + if (next_destid == port->host_deviceid) + next_destid++; + } + + rdev->destid = sw_destid; + } else + pr_debug("RIO: found %s (vid %4.4x did %4.4x)\n", + rio_name(rdev), rdev->vid, rdev->did); + + return 0; +} + +/** + * rio_enum_complete- Tests if enumeration of a network is complete + * @port: Master port to send transaction + * + * Tests the Component Tag CSR for non-zero value (enumeration + * complete flag). Return %1 if enumeration is complete or %0 if + * enumeration is incomplete. + */ +static int rio_enum_complete(struct rio_mport *port) +{ + u32 regval; + + rio_local_read_config_32(port, port->phys_efptr + RIO_PORT_GEN_CTL_CSR, + ®val); + return (regval & RIO_PORT_GEN_MASTER) ? 1 : 0; +} + +/** + * rio_disc_peer- Recursively discovers a RIO network through a master port + * @net: RIO network being discovered + * @port: Master port to send transactions + * @destid: Current destination ID in network + * @hopcount: Number of hops into the network + * @prev: previous rio_dev + * @prev_port: previous port number + * + * Recursively discovers a RIO network. Transactions are sent via the + * master port passed in @port. + */ +static int __devinit +rio_disc_peer(struct rio_net *net, struct rio_mport *port, u16 destid, + u8 hopcount, struct rio_dev *prev, int prev_port) +{ + u8 port_num, route_port; + struct rio_dev *rdev; + u16 ndestid; + + /* Setup new RIO device */ + if ((rdev = rio_setup_device(net, port, destid, hopcount, 0))) { + /* Add device to the global and bus/net specific list. */ + list_add_tail(&rdev->net_list, &net->devices); + rdev->prev = prev; + if (prev && rio_is_switch(prev)) + prev->rswitch->nextdev[prev_port] = rdev; + } else + return -1; + + if (rio_is_switch(rdev)) { + /* Associated destid is how we accessed this switch */ + rdev->destid = destid; + + pr_debug( + "RIO: found %s (vid %4.4x did %4.4x) with %d ports\n", + rio_name(rdev), rdev->vid, rdev->did, + RIO_GET_TOTAL_PORTS(rdev->swpinfo)); + for (port_num = 0; + port_num < RIO_GET_TOTAL_PORTS(rdev->swpinfo); + port_num++) { + if (RIO_GET_PORT_NUM(rdev->swpinfo) == port_num) + continue; + + if (rio_sport_is_active + (port, destid, hopcount, port_num)) { + pr_debug( + "RIO: scanning device on port %d\n", + port_num); + + rio_lock_device(port, destid, hopcount, 1000); + + for (ndestid = 0; + ndestid < RIO_ANY_DESTID(port->sys_size); + ndestid++) { + rio_route_get_entry(rdev, + RIO_GLOBAL_TABLE, + ndestid, + &route_port, 0); + if (route_port == port_num) + break; + } + + if (ndestid == RIO_ANY_DESTID(port->sys_size)) + continue; + rio_unlock_device(port, destid, hopcount); + if (rio_disc_peer(net, port, ndestid, + hopcount + 1, rdev, port_num) < 0) + return -1; + } + } + } else + pr_debug("RIO: found %s (vid %4.4x did %4.4x)\n", + rio_name(rdev), rdev->vid, rdev->did); + + return 0; +} + +/** + * rio_mport_is_active- Tests if master port link is active + * @port: Master port to test + * + * Reads the port error status CSR for the master port to + * determine if the port has an active link. Returns + * %RIO_PORT_N_ERR_STS_PORT_OK if the master port is active + * or %0 if it is inactive. + */ +static int rio_mport_is_active(struct rio_mport *port) +{ + u32 result = 0; + u32 ext_ftr_ptr; + int *entry = rio_mport_phys_table; + + do { + if ((ext_ftr_ptr = + rio_mport_get_feature(port, 1, 0, 0, *entry))) + break; + } while (*++entry >= 0); + + if (ext_ftr_ptr) + rio_local_read_config_32(port, + ext_ftr_ptr + + RIO_PORT_N_ERR_STS_CSR(port->index), + &result); + + return result & RIO_PORT_N_ERR_STS_PORT_OK; +} + +/** + * rio_alloc_net- Allocate and configure a new RIO network + * @port: Master port associated with the RIO network + * + * Allocates a RIO network structure, initializes per-network + * list heads, and adds the associated master port to the + * network list of associated master ports. Returns a + * RIO network pointer on success or %NULL on failure. + */ +static struct rio_net __devinit *rio_alloc_net(struct rio_mport *port) +{ + struct rio_net *net; + + net = kzalloc(sizeof(struct rio_net), GFP_KERNEL); + if (net) { + INIT_LIST_HEAD(&net->node); + INIT_LIST_HEAD(&net->devices); + INIT_LIST_HEAD(&net->mports); + list_add_tail(&port->nnode, &net->mports); + net->hport = port; + net->id = next_net++; + } + return net; +} + +/** + * rio_update_route_tables- Updates route tables in switches + * @port: Master port associated with the RIO network + * + * For each enumerated device, ensure that each switch in a system + * has correct routing entries. Add routes for devices that where + * unknown dirung the first enumeration pass through the switch. + */ +static void rio_update_route_tables(struct rio_mport *port) +{ + struct rio_dev *rdev, *swrdev; + struct rio_switch *rswitch; + u8 sport; + u16 destid; + + list_for_each_entry(rdev, &rio_devices, global_list) { + + destid = rdev->destid; + + list_for_each_entry(rswitch, &rio_switches, node) { + + if (rio_is_switch(rdev) && (rdev->rswitch == rswitch)) + continue; + + if (RIO_INVALID_ROUTE == rswitch->route_table[destid]) { + swrdev = sw_to_rio_dev(rswitch); + + /* Skip if destid ends in empty switch*/ + if (swrdev->destid == destid) + continue; + + sport = RIO_GET_PORT_NUM(swrdev->swpinfo); + + if (rswitch->add_entry) { + rio_route_add_entry(swrdev, + RIO_GLOBAL_TABLE, destid, + sport, 0); + rswitch->route_table[destid] = sport; + } + } + } + } +} + +/** + * rio_init_em - Initializes RIO Error Management (for switches) + * @rdev: RIO device + * + * For each enumerated switch, call device-specific error management + * initialization routine (if supplied by the switch driver). + */ +static void rio_init_em(struct rio_dev *rdev) +{ + if (rio_is_switch(rdev) && (rdev->em_efptr) && + (rdev->rswitch->em_init)) { + rdev->rswitch->em_init(rdev); + } +} + +/** + * rio_pw_enable - Enables/disables port-write handling by a master port + * @port: Master port associated with port-write handling + * @enable: 1=enable, 0=disable + */ +static void rio_pw_enable(struct rio_mport *port, int enable) +{ + if (port->ops->pwenable) + port->ops->pwenable(port, enable); +} + +/** + * rio_enum_mport- Start enumeration through a master port + * @mport: Master port to send transactions + * + * Starts the enumeration process. If somebody has enumerated our + * master port device, then give up. If not and we have an active + * link, then start recursive peer enumeration. Returns %0 if + * enumeration succeeds or %-EBUSY if enumeration fails. + */ +int __devinit rio_enum_mport(struct rio_mport *mport) +{ + struct rio_net *net = NULL; + int rc = 0; + + printk(KERN_INFO "RIO: enumerate master port %d, %s\n", mport->id, + mport->name); + /* If somebody else enumerated our master port device, bail. */ + if (rio_enum_host(mport) < 0) { + printk(KERN_INFO + "RIO: master port %d device has been enumerated by a remote host\n", + mport->id); + rc = -EBUSY; + goto out; + } + + /* If master port has an active link, allocate net and enum peers */ + if (rio_mport_is_active(mport)) { + if (!(net = rio_alloc_net(mport))) { + printk(KERN_ERR "RIO: failed to allocate new net\n"); + rc = -ENOMEM; + goto out; + } + + /* Enable Input Output Port (transmitter reviever) */ + rio_enable_rx_tx_port(mport, 1, 0, 0, 0); + + /* Set component tag for host */ + rio_local_write_config_32(mport, RIO_COMPONENT_TAG_CSR, + next_comptag++); + + if (rio_enum_peer(net, mport, 0, NULL, 0) < 0) { + /* A higher priority host won enumeration, bail. */ + printk(KERN_INFO + "RIO: master port %d device has lost enumeration to a remote host\n", + mport->id); + rio_clear_locks(mport); + rc = -EBUSY; + goto out; + } + rio_update_route_tables(mport); + rio_clear_locks(mport); + rio_pw_enable(mport, 1); + } else { + printk(KERN_INFO "RIO: master port %d link inactive\n", + mport->id); + rc = -EINVAL; + } + + out: + return rc; +} + +/** + * rio_build_route_tables- Generate route tables from switch route entries + * + * For each switch device, generate a route table by copying existing + * route entries from the switch. + */ +static void rio_build_route_tables(void) +{ + struct rio_dev *rdev; + int i; + u8 sport; + + list_for_each_entry(rdev, &rio_devices, global_list) + if (rio_is_switch(rdev)) { + rio_lock_device(rdev->net->hport, rdev->destid, + rdev->hopcount, 1000); + for (i = 0; + i < RIO_MAX_ROUTE_ENTRIES(rdev->net->hport->sys_size); + i++) { + if (rio_route_get_entry(rdev, + RIO_GLOBAL_TABLE, i, &sport, 0) < 0) + continue; + rdev->rswitch->route_table[i] = sport; + } + + rio_unlock_device(rdev->net->hport, + rdev->destid, + rdev->hopcount); + } +} + +/** + * rio_enum_timeout- Signal that enumeration timed out + * @data: Address of timeout flag. + * + * When the enumeration complete timer expires, set a flag that + * signals to the discovery process that enumeration did not + * complete in a sane amount of time. + */ +static void rio_enum_timeout(unsigned long data) +{ + /* Enumeration timed out, set flag */ + *(int *)data = 1; +} + +/** + * rio_disc_mport- Start discovery through a master port + * @mport: Master port to send transactions + * + * Starts the discovery process. If we have an active link, + * then wait for the signal that enumeration is complete. + * When enumeration completion is signaled, start recursive + * peer discovery. Returns %0 if discovery succeeds or %-EBUSY + * on failure. + */ +int __devinit rio_disc_mport(struct rio_mport *mport) +{ + struct rio_net *net = NULL; + int enum_timeout_flag = 0; + + printk(KERN_INFO "RIO: discover master port %d, %s\n", mport->id, + mport->name); + + /* If master port has an active link, allocate net and discover peers */ + if (rio_mport_is_active(mport)) { + if (!(net = rio_alloc_net(mport))) { + printk(KERN_ERR "RIO: Failed to allocate new net\n"); + goto bail; + } + + pr_debug("RIO: wait for enumeration complete..."); + + rio_enum_timer.expires = + jiffies + CONFIG_RAPIDIO_DISC_TIMEOUT * HZ; + rio_enum_timer.data = (unsigned long)&enum_timeout_flag; + add_timer(&rio_enum_timer); + while (!rio_enum_complete(mport)) { + mdelay(1); + if (enum_timeout_flag) { + del_timer_sync(&rio_enum_timer); + goto timeout; + } + } + del_timer_sync(&rio_enum_timer); + + pr_debug("done\n"); + + /* Read DestID assigned by enumerator */ + rio_local_read_config_32(mport, RIO_DID_CSR, + &mport->host_deviceid); + mport->host_deviceid = RIO_GET_DID(mport->sys_size, + mport->host_deviceid); + + if (rio_disc_peer(net, mport, RIO_ANY_DESTID(mport->sys_size), + 0, NULL, 0) < 0) { + printk(KERN_INFO + "RIO: master port %d device has failed discovery\n", + mport->id); + goto bail; + } + + rio_build_route_tables(); + } + + return 0; + + timeout: + pr_debug("timeout\n"); + bail: + return -EBUSY; +} diff --git a/drivers/rapidio/rio-sysfs.c b/drivers/rapidio/rio-sysfs.c new file mode 100644 index 00000000..4dbe3609 --- /dev/null +++ b/drivers/rapidio/rio-sysfs.c @@ -0,0 +1,287 @@ +/* + * RapidIO sysfs attributes and support + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/stat.h> +#include <linux/capability.h> + +#include "rio.h" + +/* Sysfs support */ +#define rio_config_attr(field, format_string) \ +static ssize_t \ +field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct rio_dev *rdev = to_rio_dev(dev); \ + \ + return sprintf(buf, format_string, rdev->field); \ +} \ + +rio_config_attr(did, "0x%04x\n"); +rio_config_attr(vid, "0x%04x\n"); +rio_config_attr(device_rev, "0x%08x\n"); +rio_config_attr(asm_did, "0x%04x\n"); +rio_config_attr(asm_vid, "0x%04x\n"); +rio_config_attr(asm_rev, "0x%04x\n"); +rio_config_attr(destid, "0x%04x\n"); +rio_config_attr(hopcount, "0x%02x\n"); + +static ssize_t routes_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct rio_dev *rdev = to_rio_dev(dev); + char *str = buf; + int i; + + for (i = 0; i < RIO_MAX_ROUTE_ENTRIES(rdev->net->hport->sys_size); + i++) { + if (rdev->rswitch->route_table[i] == RIO_INVALID_ROUTE) + continue; + str += + sprintf(str, "%04x %02x\n", i, + rdev->rswitch->route_table[i]); + } + + return (str - buf); +} + +static ssize_t lprev_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rio_dev *rdev = to_rio_dev(dev); + + return sprintf(buf, "%s\n", + (rdev->prev) ? rio_name(rdev->prev) : "root"); +} + +static ssize_t lnext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rio_dev *rdev = to_rio_dev(dev); + char *str = buf; + int i; + + if (rdev->pef & RIO_PEF_SWITCH) { + for (i = 0; i < RIO_GET_TOTAL_PORTS(rdev->swpinfo); i++) { + if (rdev->rswitch->nextdev[i]) + str += sprintf(str, "%s\n", + rio_name(rdev->rswitch->nextdev[i])); + else + str += sprintf(str, "null\n"); + } + } + + return str - buf; +} + +struct device_attribute rio_dev_attrs[] = { + __ATTR_RO(did), + __ATTR_RO(vid), + __ATTR_RO(device_rev), + __ATTR_RO(asm_did), + __ATTR_RO(asm_vid), + __ATTR_RO(asm_rev), + __ATTR_RO(lprev), + __ATTR_RO(destid), + __ATTR_NULL, +}; + +static DEVICE_ATTR(routes, S_IRUGO, routes_show, NULL); +static DEVICE_ATTR(lnext, S_IRUGO, lnext_show, NULL); +static DEVICE_ATTR(hopcount, S_IRUGO, hopcount_show, NULL); + +static ssize_t +rio_read_config(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct rio_dev *dev = + to_rio_dev(container_of(kobj, struct device, kobj)); + unsigned int size = 0x100; + loff_t init_off = off; + u8 *data = (u8 *) buf; + + /* Several chips lock up trying to read undefined config space */ + if (capable(CAP_SYS_ADMIN)) + size = RIO_MAINT_SPACE_SZ; + + if (off >= size) + return 0; + if (off + count > size) { + size -= off; + count = size; + } else { + size = count; + } + + if ((off & 1) && size) { + u8 val; + rio_read_config_8(dev, off, &val); + data[off - init_off] = val; + off++; + size--; + } + + if ((off & 3) && size > 2) { + u16 val; + rio_read_config_16(dev, off, &val); + data[off - init_off] = (val >> 8) & 0xff; + data[off - init_off + 1] = val & 0xff; + off += 2; + size -= 2; + } + + while (size > 3) { + u32 val; + rio_read_config_32(dev, off, &val); + data[off - init_off] = (val >> 24) & 0xff; + data[off - init_off + 1] = (val >> 16) & 0xff; + data[off - init_off + 2] = (val >> 8) & 0xff; + data[off - init_off + 3] = val & 0xff; + off += 4; + size -= 4; + } + + if (size >= 2) { + u16 val; + rio_read_config_16(dev, off, &val); + data[off - init_off] = (val >> 8) & 0xff; + data[off - init_off + 1] = val & 0xff; + off += 2; + size -= 2; + } + + if (size > 0) { + u8 val; + rio_read_config_8(dev, off, &val); + data[off - init_off] = val; + off++; + --size; + } + + return count; +} + +static ssize_t +rio_write_config(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct rio_dev *dev = + to_rio_dev(container_of(kobj, struct device, kobj)); + unsigned int size = count; + loff_t init_off = off; + u8 *data = (u8 *) buf; + + if (off >= RIO_MAINT_SPACE_SZ) + return 0; + if (off + count > RIO_MAINT_SPACE_SZ) { + size = RIO_MAINT_SPACE_SZ - off; + count = size; + } + + if ((off & 1) && size) { + rio_write_config_8(dev, off, data[off - init_off]); + off++; + size--; + } + + if ((off & 3) && (size > 2)) { + u16 val = data[off - init_off + 1]; + val |= (u16) data[off - init_off] << 8; + rio_write_config_16(dev, off, val); + off += 2; + size -= 2; + } + + while (size > 3) { + u32 val = data[off - init_off + 3]; + val |= (u32) data[off - init_off + 2] << 8; + val |= (u32) data[off - init_off + 1] << 16; + val |= (u32) data[off - init_off] << 24; + rio_write_config_32(dev, off, val); + off += 4; + size -= 4; + } + + if (size >= 2) { + u16 val = data[off - init_off + 1]; + val |= (u16) data[off - init_off] << 8; + rio_write_config_16(dev, off, val); + off += 2; + size -= 2; + } + + if (size) { + rio_write_config_8(dev, off, data[off - init_off]); + off++; + --size; + } + + return count; +} + +static struct bin_attribute rio_config_attr = { + .attr = { + .name = "config", + .mode = S_IRUGO | S_IWUSR, + }, + .size = RIO_MAINT_SPACE_SZ, + .read = rio_read_config, + .write = rio_write_config, +}; + +/** + * rio_create_sysfs_dev_files - create RIO specific sysfs files + * @rdev: device whose entries should be created + * + * Create files when @rdev is added to sysfs. + */ +int rio_create_sysfs_dev_files(struct rio_dev *rdev) +{ + int err = 0; + + err = device_create_bin_file(&rdev->dev, &rio_config_attr); + + if (!err && (rdev->pef & RIO_PEF_SWITCH)) { + err |= device_create_file(&rdev->dev, &dev_attr_routes); + err |= device_create_file(&rdev->dev, &dev_attr_lnext); + err |= device_create_file(&rdev->dev, &dev_attr_hopcount); + if (!err && rdev->rswitch->sw_sysfs) + err = rdev->rswitch->sw_sysfs(rdev, RIO_SW_SYSFS_CREATE); + } + + if (err) + pr_warning("RIO: Failed to create attribute file(s) for %s\n", + rio_name(rdev)); + + return err; +} + +/** + * rio_remove_sysfs_dev_files - cleanup RIO specific sysfs files + * @rdev: device whose entries we should free + * + * Cleanup when @rdev is removed from sysfs. + */ +void rio_remove_sysfs_dev_files(struct rio_dev *rdev) +{ + device_remove_bin_file(&rdev->dev, &rio_config_attr); + if (rdev->pef & RIO_PEF_SWITCH) { + device_remove_file(&rdev->dev, &dev_attr_routes); + device_remove_file(&rdev->dev, &dev_attr_lnext); + device_remove_file(&rdev->dev, &dev_attr_hopcount); + if (rdev->rswitch->sw_sysfs) + rdev->rswitch->sw_sysfs(rdev, RIO_SW_SYSFS_REMOVE); + } +} diff --git a/drivers/rapidio/rio.c b/drivers/rapidio/rio.c new file mode 100644 index 00000000..86c9a091 --- /dev/null +++ b/drivers/rapidio/rio.c @@ -0,0 +1,1197 @@ +/* + * RapidIO interconnect services + * (RapidIO Interconnect Specification, http://www.rapidio.org) + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * Copyright 2009 Integrated Device Technology, Inc. + * Alex Bounine <alexandre.bounine@idt.com> + * - Added Port-Write/Error Management initialization and handling + * + * 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. + */ + +#include <linux/types.h> +#include <linux/kernel.h> + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include <linux/rio_regs.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include "rio.h" + +static LIST_HEAD(rio_mports); +static unsigned char next_portid; + +/** + * rio_local_get_device_id - Get the base/extended device id for a port + * @port: RIO master port from which to get the deviceid + * + * Reads the base/extended device id from the local device + * implementing the master port. Returns the 8/16-bit device + * id. + */ +u16 rio_local_get_device_id(struct rio_mport *port) +{ + u32 result; + + rio_local_read_config_32(port, RIO_DID_CSR, &result); + + return (RIO_GET_DID(port->sys_size, result)); +} + +/** + * rio_request_inb_mbox - request inbound mailbox service + * @mport: RIO master port from which to allocate the mailbox resource + * @dev_id: Device specific pointer to pass on event + * @mbox: Mailbox number to claim + * @entries: Number of entries in inbound mailbox queue + * @minb: Callback to execute when inbound message is received + * + * Requests ownership of an inbound mailbox resource and binds + * a callback function to the resource. Returns %0 on success. + */ +int rio_request_inb_mbox(struct rio_mport *mport, + void *dev_id, + int mbox, + int entries, + void (*minb) (struct rio_mport * mport, void *dev_id, int mbox, + int slot)) +{ + int rc = -ENOSYS; + struct resource *res; + + if (mport->ops->open_inb_mbox == NULL) + goto out; + + res = kmalloc(sizeof(struct resource), GFP_KERNEL); + + if (res) { + rio_init_mbox_res(res, mbox, mbox); + + /* Make sure this mailbox isn't in use */ + if ((rc = + request_resource(&mport->riores[RIO_INB_MBOX_RESOURCE], + res)) < 0) { + kfree(res); + goto out; + } + + mport->inb_msg[mbox].res = res; + + /* Hook the inbound message callback */ + mport->inb_msg[mbox].mcback = minb; + + rc = mport->ops->open_inb_mbox(mport, dev_id, mbox, entries); + } else + rc = -ENOMEM; + + out: + return rc; +} + +/** + * rio_release_inb_mbox - release inbound mailbox message service + * @mport: RIO master port from which to release the mailbox resource + * @mbox: Mailbox number to release + * + * Releases ownership of an inbound mailbox resource. Returns 0 + * if the request has been satisfied. + */ +int rio_release_inb_mbox(struct rio_mport *mport, int mbox) +{ + if (mport->ops->close_inb_mbox) { + mport->ops->close_inb_mbox(mport, mbox); + + /* Release the mailbox resource */ + return release_resource(mport->inb_msg[mbox].res); + } else + return -ENOSYS; +} + +/** + * rio_request_outb_mbox - request outbound mailbox service + * @mport: RIO master port from which to allocate the mailbox resource + * @dev_id: Device specific pointer to pass on event + * @mbox: Mailbox number to claim + * @entries: Number of entries in outbound mailbox queue + * @moutb: Callback to execute when outbound message is sent + * + * Requests ownership of an outbound mailbox resource and binds + * a callback function to the resource. Returns 0 on success. + */ +int rio_request_outb_mbox(struct rio_mport *mport, + void *dev_id, + int mbox, + int entries, + void (*moutb) (struct rio_mport * mport, void *dev_id, int mbox, int slot)) +{ + int rc = -ENOSYS; + struct resource *res; + + if (mport->ops->open_outb_mbox == NULL) + goto out; + + res = kmalloc(sizeof(struct resource), GFP_KERNEL); + + if (res) { + rio_init_mbox_res(res, mbox, mbox); + + /* Make sure this outbound mailbox isn't in use */ + if ((rc = + request_resource(&mport->riores[RIO_OUTB_MBOX_RESOURCE], + res)) < 0) { + kfree(res); + goto out; + } + + mport->outb_msg[mbox].res = res; + + /* Hook the inbound message callback */ + mport->outb_msg[mbox].mcback = moutb; + + rc = mport->ops->open_outb_mbox(mport, dev_id, mbox, entries); + } else + rc = -ENOMEM; + + out: + return rc; +} + +/** + * rio_release_outb_mbox - release outbound mailbox message service + * @mport: RIO master port from which to release the mailbox resource + * @mbox: Mailbox number to release + * + * Releases ownership of an inbound mailbox resource. Returns 0 + * if the request has been satisfied. + */ +int rio_release_outb_mbox(struct rio_mport *mport, int mbox) +{ + if (mport->ops->close_outb_mbox) { + mport->ops->close_outb_mbox(mport, mbox); + + /* Release the mailbox resource */ + return release_resource(mport->outb_msg[mbox].res); + } else + return -ENOSYS; +} + +/** + * rio_setup_inb_dbell - bind inbound doorbell callback + * @mport: RIO master port to bind the doorbell callback + * @dev_id: Device specific pointer to pass on event + * @res: Doorbell message resource + * @dinb: Callback to execute when doorbell is received + * + * Adds a doorbell resource/callback pair into a port's + * doorbell event list. Returns 0 if the request has been + * satisfied. + */ +static int +rio_setup_inb_dbell(struct rio_mport *mport, void *dev_id, struct resource *res, + void (*dinb) (struct rio_mport * mport, void *dev_id, u16 src, u16 dst, + u16 info)) +{ + int rc = 0; + struct rio_dbell *dbell; + + if (!(dbell = kmalloc(sizeof(struct rio_dbell), GFP_KERNEL))) { + rc = -ENOMEM; + goto out; + } + + dbell->res = res; + dbell->dinb = dinb; + dbell->dev_id = dev_id; + + list_add_tail(&dbell->node, &mport->dbells); + + out: + return rc; +} + +/** + * rio_request_inb_dbell - request inbound doorbell message service + * @mport: RIO master port from which to allocate the doorbell resource + * @dev_id: Device specific pointer to pass on event + * @start: Doorbell info range start + * @end: Doorbell info range end + * @dinb: Callback to execute when doorbell is received + * + * Requests ownership of an inbound doorbell resource and binds + * a callback function to the resource. Returns 0 if the request + * has been satisfied. + */ +int rio_request_inb_dbell(struct rio_mport *mport, + void *dev_id, + u16 start, + u16 end, + void (*dinb) (struct rio_mport * mport, void *dev_id, u16 src, + u16 dst, u16 info)) +{ + int rc = 0; + + struct resource *res = kmalloc(sizeof(struct resource), GFP_KERNEL); + + if (res) { + rio_init_dbell_res(res, start, end); + + /* Make sure these doorbells aren't in use */ + if ((rc = + request_resource(&mport->riores[RIO_DOORBELL_RESOURCE], + res)) < 0) { + kfree(res); + goto out; + } + + /* Hook the doorbell callback */ + rc = rio_setup_inb_dbell(mport, dev_id, res, dinb); + } else + rc = -ENOMEM; + + out: + return rc; +} + +/** + * rio_release_inb_dbell - release inbound doorbell message service + * @mport: RIO master port from which to release the doorbell resource + * @start: Doorbell info range start + * @end: Doorbell info range end + * + * Releases ownership of an inbound doorbell resource and removes + * callback from the doorbell event list. Returns 0 if the request + * has been satisfied. + */ +int rio_release_inb_dbell(struct rio_mport *mport, u16 start, u16 end) +{ + int rc = 0, found = 0; + struct rio_dbell *dbell; + + list_for_each_entry(dbell, &mport->dbells, node) { + if ((dbell->res->start == start) && (dbell->res->end == end)) { + found = 1; + break; + } + } + + /* If we can't find an exact match, fail */ + if (!found) { + rc = -EINVAL; + goto out; + } + + /* Delete from list */ + list_del(&dbell->node); + + /* Release the doorbell resource */ + rc = release_resource(dbell->res); + + /* Free the doorbell event */ + kfree(dbell); + + out: + return rc; +} + +/** + * rio_request_outb_dbell - request outbound doorbell message range + * @rdev: RIO device from which to allocate the doorbell resource + * @start: Doorbell message range start + * @end: Doorbell message range end + * + * Requests ownership of a doorbell message range. Returns a resource + * if the request has been satisfied or %NULL on failure. + */ +struct resource *rio_request_outb_dbell(struct rio_dev *rdev, u16 start, + u16 end) +{ + struct resource *res = kmalloc(sizeof(struct resource), GFP_KERNEL); + + if (res) { + rio_init_dbell_res(res, start, end); + + /* Make sure these doorbells aren't in use */ + if (request_resource(&rdev->riores[RIO_DOORBELL_RESOURCE], res) + < 0) { + kfree(res); + res = NULL; + } + } + + return res; +} + +/** + * rio_release_outb_dbell - release outbound doorbell message range + * @rdev: RIO device from which to release the doorbell resource + * @res: Doorbell resource to be freed + * + * Releases ownership of a doorbell message range. Returns 0 if the + * request has been satisfied. + */ +int rio_release_outb_dbell(struct rio_dev *rdev, struct resource *res) +{ + int rc = release_resource(res); + + kfree(res); + + return rc; +} + +/** + * rio_request_inb_pwrite - request inbound port-write message service + * @rdev: RIO device to which register inbound port-write callback routine + * @pwcback: Callback routine to execute when port-write is received + * + * Binds a port-write callback function to the RapidIO device. + * Returns 0 if the request has been satisfied. + */ +int rio_request_inb_pwrite(struct rio_dev *rdev, + int (*pwcback)(struct rio_dev *rdev, union rio_pw_msg *msg, int step)) +{ + int rc = 0; + + spin_lock(&rio_global_list_lock); + if (rdev->pwcback != NULL) + rc = -ENOMEM; + else + rdev->pwcback = pwcback; + + spin_unlock(&rio_global_list_lock); + return rc; +} +EXPORT_SYMBOL_GPL(rio_request_inb_pwrite); + +/** + * rio_release_inb_pwrite - release inbound port-write message service + * @rdev: RIO device which registered for inbound port-write callback + * + * Removes callback from the rio_dev structure. Returns 0 if the request + * has been satisfied. + */ +int rio_release_inb_pwrite(struct rio_dev *rdev) +{ + int rc = -ENOMEM; + + spin_lock(&rio_global_list_lock); + if (rdev->pwcback) { + rdev->pwcback = NULL; + rc = 0; + } + + spin_unlock(&rio_global_list_lock); + return rc; +} +EXPORT_SYMBOL_GPL(rio_release_inb_pwrite); + +/** + * rio_mport_get_physefb - Helper function that returns register offset + * for Physical Layer Extended Features Block. + * @port: Master port to issue transaction + * @local: Indicate a local master port or remote device access + * @destid: Destination ID of the device + * @hopcount: Number of switch hops to the device + */ +u32 +rio_mport_get_physefb(struct rio_mport *port, int local, + u16 destid, u8 hopcount) +{ + u32 ext_ftr_ptr; + u32 ftr_header; + + ext_ftr_ptr = rio_mport_get_efb(port, local, destid, hopcount, 0); + + while (ext_ftr_ptr) { + if (local) + rio_local_read_config_32(port, ext_ftr_ptr, + &ftr_header); + else + rio_mport_read_config_32(port, destid, hopcount, + ext_ftr_ptr, &ftr_header); + + ftr_header = RIO_GET_BLOCK_ID(ftr_header); + switch (ftr_header) { + + case RIO_EFB_SER_EP_ID_V13P: + case RIO_EFB_SER_EP_REC_ID_V13P: + case RIO_EFB_SER_EP_FREE_ID_V13P: + case RIO_EFB_SER_EP_ID: + case RIO_EFB_SER_EP_REC_ID: + case RIO_EFB_SER_EP_FREE_ID: + case RIO_EFB_SER_EP_FREC_ID: + + return ext_ftr_ptr; + + default: + break; + } + + ext_ftr_ptr = rio_mport_get_efb(port, local, destid, + hopcount, ext_ftr_ptr); + } + + return ext_ftr_ptr; +} + +/** + * rio_get_comptag - Begin or continue searching for a RIO device by component tag + * @comp_tag: RIO component tag to match + * @from: Previous RIO device found in search, or %NULL for new search + * + * Iterates through the list of known RIO devices. If a RIO device is + * found with a matching @comp_tag, a pointer to its device + * structure is returned. Otherwise, %NULL is returned. A new search + * is initiated by passing %NULL to the @from argument. Otherwise, if + * @from is not %NULL, searches continue from next device on the global + * list. + */ +struct rio_dev *rio_get_comptag(u32 comp_tag, struct rio_dev *from) +{ + struct list_head *n; + struct rio_dev *rdev; + + spin_lock(&rio_global_list_lock); + n = from ? from->global_list.next : rio_devices.next; + + while (n && (n != &rio_devices)) { + rdev = rio_dev_g(n); + if (rdev->comp_tag == comp_tag) + goto exit; + n = n->next; + } + rdev = NULL; +exit: + spin_unlock(&rio_global_list_lock); + return rdev; +} + +/** + * rio_set_port_lockout - Sets/clears LOCKOUT bit (RIO EM 1.3) for a switch port. + * @rdev: Pointer to RIO device control structure + * @pnum: Switch port number to set LOCKOUT bit + * @lock: Operation : set (=1) or clear (=0) + */ +int rio_set_port_lockout(struct rio_dev *rdev, u32 pnum, int lock) +{ + u32 regval; + + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_CTL_CSR(pnum), + ®val); + if (lock) + regval |= RIO_PORT_N_CTL_LOCKOUT; + else + regval &= ~RIO_PORT_N_CTL_LOCKOUT; + + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_CTL_CSR(pnum), + regval); + return 0; +} + +/** + * rio_chk_dev_route - Validate route to the specified device. + * @rdev: RIO device failed to respond + * @nrdev: Last active device on the route to rdev + * @npnum: nrdev's port number on the route to rdev + * + * Follows a route to the specified RIO device to determine the last available + * device (and corresponding RIO port) on the route. + */ +static int +rio_chk_dev_route(struct rio_dev *rdev, struct rio_dev **nrdev, int *npnum) +{ + u32 result; + int p_port, rc = -EIO; + struct rio_dev *prev = NULL; + + /* Find switch with failed RIO link */ + while (rdev->prev && (rdev->prev->pef & RIO_PEF_SWITCH)) { + if (!rio_read_config_32(rdev->prev, RIO_DEV_ID_CAR, &result)) { + prev = rdev->prev; + break; + } + rdev = rdev->prev; + } + + if (prev == NULL) + goto err_out; + + p_port = prev->rswitch->route_table[rdev->destid]; + + if (p_port != RIO_INVALID_ROUTE) { + pr_debug("RIO: link failed on [%s]-P%d\n", + rio_name(prev), p_port); + *nrdev = prev; + *npnum = p_port; + rc = 0; + } else + pr_debug("RIO: failed to trace route to %s\n", rio_name(rdev)); +err_out: + return rc; +} + +/** + * rio_mport_chk_dev_access - Validate access to the specified device. + * @mport: Master port to send transactions + * @destid: Device destination ID in network + * @hopcount: Number of hops into the network + */ +int +rio_mport_chk_dev_access(struct rio_mport *mport, u16 destid, u8 hopcount) +{ + int i = 0; + u32 tmp; + + while (rio_mport_read_config_32(mport, destid, hopcount, + RIO_DEV_ID_CAR, &tmp)) { + i++; + if (i == RIO_MAX_CHK_RETRY) + return -EIO; + mdelay(1); + } + + return 0; +} + +/** + * rio_chk_dev_access - Validate access to the specified device. + * @rdev: Pointer to RIO device control structure + */ +static int rio_chk_dev_access(struct rio_dev *rdev) +{ + return rio_mport_chk_dev_access(rdev->net->hport, + rdev->destid, rdev->hopcount); +} + +/** + * rio_get_input_status - Sends a Link-Request/Input-Status control symbol and + * returns link-response (if requested). + * @rdev: RIO devive to issue Input-status command + * @pnum: Device port number to issue the command + * @lnkresp: Response from a link partner + */ +static int +rio_get_input_status(struct rio_dev *rdev, int pnum, u32 *lnkresp) +{ + u32 regval; + int checkcount; + + if (lnkresp) { + /* Read from link maintenance response register + * to clear valid bit */ + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_MNT_RSP_CSR(pnum), + ®val); + udelay(50); + } + + /* Issue Input-status command */ + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_MNT_REQ_CSR(pnum), + RIO_MNT_REQ_CMD_IS); + + /* Exit if the response is not expected */ + if (lnkresp == NULL) + return 0; + + checkcount = 3; + while (checkcount--) { + udelay(50); + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_MNT_RSP_CSR(pnum), + ®val); + if (regval & RIO_PORT_N_MNT_RSP_RVAL) { + *lnkresp = regval; + return 0; + } + } + + return -EIO; +} + +/** + * rio_clr_err_stopped - Clears port Error-stopped states. + * @rdev: Pointer to RIO device control structure + * @pnum: Switch port number to clear errors + * @err_status: port error status (if 0 reads register from device) + */ +static int rio_clr_err_stopped(struct rio_dev *rdev, u32 pnum, u32 err_status) +{ + struct rio_dev *nextdev = rdev->rswitch->nextdev[pnum]; + u32 regval; + u32 far_ackid, far_linkstat, near_ackid; + + if (err_status == 0) + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ERR_STS_CSR(pnum), + &err_status); + + if (err_status & RIO_PORT_N_ERR_STS_PW_OUT_ES) { + pr_debug("RIO_EM: servicing Output Error-Stopped state\n"); + /* + * Send a Link-Request/Input-Status control symbol + */ + if (rio_get_input_status(rdev, pnum, ®val)) { + pr_debug("RIO_EM: Input-status response timeout\n"); + goto rd_err; + } + + pr_debug("RIO_EM: SP%d Input-status response=0x%08x\n", + pnum, regval); + far_ackid = (regval & RIO_PORT_N_MNT_RSP_ASTAT) >> 5; + far_linkstat = regval & RIO_PORT_N_MNT_RSP_LSTAT; + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ACK_STS_CSR(pnum), + ®val); + pr_debug("RIO_EM: SP%d_ACK_STS_CSR=0x%08x\n", pnum, regval); + near_ackid = (regval & RIO_PORT_N_ACK_INBOUND) >> 24; + pr_debug("RIO_EM: SP%d far_ackID=0x%02x far_linkstat=0x%02x" \ + " near_ackID=0x%02x\n", + pnum, far_ackid, far_linkstat, near_ackid); + + /* + * If required, synchronize ackIDs of near and + * far sides. + */ + if ((far_ackid != ((regval & RIO_PORT_N_ACK_OUTSTAND) >> 8)) || + (far_ackid != (regval & RIO_PORT_N_ACK_OUTBOUND))) { + /* Align near outstanding/outbound ackIDs with + * far inbound. + */ + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ACK_STS_CSR(pnum), + (near_ackid << 24) | + (far_ackid << 8) | far_ackid); + /* Align far outstanding/outbound ackIDs with + * near inbound. + */ + far_ackid++; + if (nextdev) + rio_write_config_32(nextdev, + nextdev->phys_efptr + + RIO_PORT_N_ACK_STS_CSR(RIO_GET_PORT_NUM(nextdev->swpinfo)), + (far_ackid << 24) | + (near_ackid << 8) | near_ackid); + else + pr_debug("RIO_EM: Invalid nextdev pointer (NULL)\n"); + } +rd_err: + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ERR_STS_CSR(pnum), + &err_status); + pr_debug("RIO_EM: SP%d_ERR_STS_CSR=0x%08x\n", pnum, err_status); + } + + if ((err_status & RIO_PORT_N_ERR_STS_PW_INP_ES) && nextdev) { + pr_debug("RIO_EM: servicing Input Error-Stopped state\n"); + rio_get_input_status(nextdev, + RIO_GET_PORT_NUM(nextdev->swpinfo), NULL); + udelay(50); + + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ERR_STS_CSR(pnum), + &err_status); + pr_debug("RIO_EM: SP%d_ERR_STS_CSR=0x%08x\n", pnum, err_status); + } + + return (err_status & (RIO_PORT_N_ERR_STS_PW_OUT_ES | + RIO_PORT_N_ERR_STS_PW_INP_ES)) ? 1 : 0; +} + +/** + * rio_inb_pwrite_handler - process inbound port-write message + * @pw_msg: pointer to inbound port-write message + * + * Processes an inbound port-write message. Returns 0 if the request + * has been satisfied. + */ +int rio_inb_pwrite_handler(union rio_pw_msg *pw_msg) +{ + struct rio_dev *rdev; + u32 err_status, em_perrdet, em_ltlerrdet; + int rc, portnum; + + rdev = rio_get_comptag((pw_msg->em.comptag & RIO_CTAG_UDEVID), NULL); + if (rdev == NULL) { + /* Device removed or enumeration error */ + pr_debug("RIO: %s No matching device for CTag 0x%08x\n", + __func__, pw_msg->em.comptag); + return -EIO; + } + + pr_debug("RIO: Port-Write message from %s\n", rio_name(rdev)); + +#ifdef DEBUG_PW + { + u32 i; + for (i = 0; i < RIO_PW_MSG_SIZE/sizeof(u32);) { + pr_debug("0x%02x: %08x %08x %08x %08x\n", + i*4, pw_msg->raw[i], pw_msg->raw[i + 1], + pw_msg->raw[i + 2], pw_msg->raw[i + 3]); + i += 4; + } + } +#endif + + /* Call an external service function (if such is registered + * for this device). This may be the service for endpoints that send + * device-specific port-write messages. End-point messages expected + * to be handled completely by EP specific device driver. + * For switches rc==0 signals that no standard processing required. + */ + if (rdev->pwcback != NULL) { + rc = rdev->pwcback(rdev, pw_msg, 0); + if (rc == 0) + return 0; + } + + portnum = pw_msg->em.is_port & 0xFF; + + /* Check if device and route to it are functional: + * Sometimes devices may send PW message(s) just before being + * powered down (or link being lost). + */ + if (rio_chk_dev_access(rdev)) { + pr_debug("RIO: device access failed - get link partner\n"); + /* Scan route to the device and identify failed link. + * This will replace device and port reported in PW message. + * PW message should not be used after this point. + */ + if (rio_chk_dev_route(rdev, &rdev, &portnum)) { + pr_err("RIO: Route trace for %s failed\n", + rio_name(rdev)); + return -EIO; + } + pw_msg = NULL; + } + + /* For End-point devices processing stops here */ + if (!(rdev->pef & RIO_PEF_SWITCH)) + return 0; + + if (rdev->phys_efptr == 0) { + pr_err("RIO_PW: Bad switch initialization for %s\n", + rio_name(rdev)); + return 0; + } + + /* + * Process the port-write notification from switch + */ + if (rdev->rswitch->em_handle) + rdev->rswitch->em_handle(rdev, portnum); + + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ERR_STS_CSR(portnum), + &err_status); + pr_debug("RIO_PW: SP%d_ERR_STS_CSR=0x%08x\n", portnum, err_status); + + if (err_status & RIO_PORT_N_ERR_STS_PORT_OK) { + + if (!(rdev->rswitch->port_ok & (1 << portnum))) { + rdev->rswitch->port_ok |= (1 << portnum); + rio_set_port_lockout(rdev, portnum, 0); + /* Schedule Insertion Service */ + pr_debug("RIO_PW: Device Insertion on [%s]-P%d\n", + rio_name(rdev), portnum); + } + + /* Clear error-stopped states (if reported). + * Depending on the link partner state, two attempts + * may be needed for successful recovery. + */ + if (err_status & (RIO_PORT_N_ERR_STS_PW_OUT_ES | + RIO_PORT_N_ERR_STS_PW_INP_ES)) { + if (rio_clr_err_stopped(rdev, portnum, err_status)) + rio_clr_err_stopped(rdev, portnum, 0); + } + } else { /* if (err_status & RIO_PORT_N_ERR_STS_PORT_UNINIT) */ + + if (rdev->rswitch->port_ok & (1 << portnum)) { + rdev->rswitch->port_ok &= ~(1 << portnum); + rio_set_port_lockout(rdev, portnum, 1); + + rio_write_config_32(rdev, + rdev->phys_efptr + + RIO_PORT_N_ACK_STS_CSR(portnum), + RIO_PORT_N_ACK_CLEAR); + + /* Schedule Extraction Service */ + pr_debug("RIO_PW: Device Extraction on [%s]-P%d\n", + rio_name(rdev), portnum); + } + } + + rio_read_config_32(rdev, + rdev->em_efptr + RIO_EM_PN_ERR_DETECT(portnum), &em_perrdet); + if (em_perrdet) { + pr_debug("RIO_PW: RIO_EM_P%d_ERR_DETECT=0x%08x\n", + portnum, em_perrdet); + /* Clear EM Port N Error Detect CSR */ + rio_write_config_32(rdev, + rdev->em_efptr + RIO_EM_PN_ERR_DETECT(portnum), 0); + } + + rio_read_config_32(rdev, + rdev->em_efptr + RIO_EM_LTL_ERR_DETECT, &em_ltlerrdet); + if (em_ltlerrdet) { + pr_debug("RIO_PW: RIO_EM_LTL_ERR_DETECT=0x%08x\n", + em_ltlerrdet); + /* Clear EM L/T Layer Error Detect CSR */ + rio_write_config_32(rdev, + rdev->em_efptr + RIO_EM_LTL_ERR_DETECT, 0); + } + + /* Clear remaining error bits and Port-Write Pending bit */ + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ERR_STS_CSR(portnum), + err_status); + + return 0; +} +EXPORT_SYMBOL_GPL(rio_inb_pwrite_handler); + +/** + * rio_mport_get_efb - get pointer to next extended features block + * @port: Master port to issue transaction + * @local: Indicate a local master port or remote device access + * @destid: Destination ID of the device + * @hopcount: Number of switch hops to the device + * @from: Offset of current Extended Feature block header (if 0 starts + * from ExtFeaturePtr) + */ +u32 +rio_mport_get_efb(struct rio_mport *port, int local, u16 destid, + u8 hopcount, u32 from) +{ + u32 reg_val; + + if (from == 0) { + if (local) + rio_local_read_config_32(port, RIO_ASM_INFO_CAR, + ®_val); + else + rio_mport_read_config_32(port, destid, hopcount, + RIO_ASM_INFO_CAR, ®_val); + return reg_val & RIO_EXT_FTR_PTR_MASK; + } else { + if (local) + rio_local_read_config_32(port, from, ®_val); + else + rio_mport_read_config_32(port, destid, hopcount, + from, ®_val); + return RIO_GET_BLOCK_ID(reg_val); + } +} + +/** + * rio_mport_get_feature - query for devices' extended features + * @port: Master port to issue transaction + * @local: Indicate a local master port or remote device access + * @destid: Destination ID of the device + * @hopcount: Number of switch hops to the device + * @ftr: Extended feature code + * + * Tell if a device supports a given RapidIO capability. + * Returns the offset of the requested extended feature + * block within the device's RIO configuration space or + * 0 in case the device does not support it. Possible + * values for @ftr: + * + * %RIO_EFB_PAR_EP_ID LP/LVDS EP Devices + * + * %RIO_EFB_PAR_EP_REC_ID LP/LVDS EP Recovery Devices + * + * %RIO_EFB_PAR_EP_FREE_ID LP/LVDS EP Free Devices + * + * %RIO_EFB_SER_EP_ID LP/Serial EP Devices + * + * %RIO_EFB_SER_EP_REC_ID LP/Serial EP Recovery Devices + * + * %RIO_EFB_SER_EP_FREE_ID LP/Serial EP Free Devices + */ +u32 +rio_mport_get_feature(struct rio_mport * port, int local, u16 destid, + u8 hopcount, int ftr) +{ + u32 asm_info, ext_ftr_ptr, ftr_header; + + if (local) + rio_local_read_config_32(port, RIO_ASM_INFO_CAR, &asm_info); + else + rio_mport_read_config_32(port, destid, hopcount, + RIO_ASM_INFO_CAR, &asm_info); + + ext_ftr_ptr = asm_info & RIO_EXT_FTR_PTR_MASK; + + while (ext_ftr_ptr) { + if (local) + rio_local_read_config_32(port, ext_ftr_ptr, + &ftr_header); + else + rio_mport_read_config_32(port, destid, hopcount, + ext_ftr_ptr, &ftr_header); + if (RIO_GET_BLOCK_ID(ftr_header) == ftr) + return ext_ftr_ptr; + if (!(ext_ftr_ptr = RIO_GET_BLOCK_PTR(ftr_header))) + break; + } + + return 0; +} + +/** + * rio_get_asm - Begin or continue searching for a RIO device by vid/did/asm_vid/asm_did + * @vid: RIO vid to match or %RIO_ANY_ID to match all vids + * @did: RIO did to match or %RIO_ANY_ID to match all dids + * @asm_vid: RIO asm_vid to match or %RIO_ANY_ID to match all asm_vids + * @asm_did: RIO asm_did to match or %RIO_ANY_ID to match all asm_dids + * @from: Previous RIO device found in search, or %NULL for new search + * + * Iterates through the list of known RIO devices. If a RIO device is + * found with a matching @vid, @did, @asm_vid, @asm_did, the reference + * count to the device is incrememted and a pointer to its device + * structure is returned. Otherwise, %NULL is returned. A new search + * is initiated by passing %NULL to the @from argument. Otherwise, if + * @from is not %NULL, searches continue from next device on the global + * list. The reference count for @from is always decremented if it is + * not %NULL. + */ +struct rio_dev *rio_get_asm(u16 vid, u16 did, + u16 asm_vid, u16 asm_did, struct rio_dev *from) +{ + struct list_head *n; + struct rio_dev *rdev; + + WARN_ON(in_interrupt()); + spin_lock(&rio_global_list_lock); + n = from ? from->global_list.next : rio_devices.next; + + while (n && (n != &rio_devices)) { + rdev = rio_dev_g(n); + if ((vid == RIO_ANY_ID || rdev->vid == vid) && + (did == RIO_ANY_ID || rdev->did == did) && + (asm_vid == RIO_ANY_ID || rdev->asm_vid == asm_vid) && + (asm_did == RIO_ANY_ID || rdev->asm_did == asm_did)) + goto exit; + n = n->next; + } + rdev = NULL; + exit: + rio_dev_put(from); + rdev = rio_dev_get(rdev); + spin_unlock(&rio_global_list_lock); + return rdev; +} + +/** + * rio_get_device - Begin or continue searching for a RIO device by vid/did + * @vid: RIO vid to match or %RIO_ANY_ID to match all vids + * @did: RIO did to match or %RIO_ANY_ID to match all dids + * @from: Previous RIO device found in search, or %NULL for new search + * + * Iterates through the list of known RIO devices. If a RIO device is + * found with a matching @vid and @did, the reference count to the + * device is incrememted and a pointer to its device structure is returned. + * Otherwise, %NULL is returned. A new search is initiated by passing %NULL + * to the @from argument. Otherwise, if @from is not %NULL, searches + * continue from next device on the global list. The reference count for + * @from is always decremented if it is not %NULL. + */ +struct rio_dev *rio_get_device(u16 vid, u16 did, struct rio_dev *from) +{ + return rio_get_asm(vid, did, RIO_ANY_ID, RIO_ANY_ID, from); +} + +/** + * rio_std_route_add_entry - Add switch route table entry using standard + * registers defined in RIO specification rev.1.3 + * @mport: Master port to issue transaction + * @destid: Destination ID of the device + * @hopcount: Number of switch hops to the device + * @table: routing table ID (global or port-specific) + * @route_destid: destID entry in the RT + * @route_port: destination port for specified destID + */ +int rio_std_route_add_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 route_port) +{ + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, + (u32)route_destid); + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, + (u32)route_port); + } + + udelay(10); + return 0; +} + +/** + * rio_std_route_get_entry - Read switch route table entry (port number) + * associated with specified destID using standard registers defined in RIO + * specification rev.1.3 + * @mport: Master port to issue transaction + * @destid: Destination ID of the device + * @hopcount: Number of switch hops to the device + * @table: routing table ID (global or port-specific) + * @route_destid: destID entry in the RT + * @route_port: returned destination port for specified destID + */ +int rio_std_route_get_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 *route_port) +{ + u32 result; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, route_destid); + rio_mport_read_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, &result); + + *route_port = (u8)result; + } + + return 0; +} + +/** + * rio_std_route_clr_table - Clear swotch route table using standard registers + * defined in RIO specification rev.1.3. + * @mport: Master port to issue transaction + * @destid: Destination ID of the device + * @hopcount: Number of switch hops to the device + * @table: routing table ID (global or port-specific) + */ +int rio_std_route_clr_table(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table) +{ + u32 max_destid = 0xff; + u32 i, pef, id_inc = 1, ext_cfg = 0; + u32 port_sel = RIO_INVALID_ROUTE; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_read_config_32(mport, destid, hopcount, + RIO_PEF_CAR, &pef); + + if (mport->sys_size) { + rio_mport_read_config_32(mport, destid, hopcount, + RIO_SWITCH_RT_LIMIT, + &max_destid); + max_destid &= RIO_RT_MAX_DESTID; + } + + if (pef & RIO_PEF_EXT_RT) { + ext_cfg = 0x80000000; + id_inc = 4; + port_sel = (RIO_INVALID_ROUTE << 24) | + (RIO_INVALID_ROUTE << 16) | + (RIO_INVALID_ROUTE << 8) | + RIO_INVALID_ROUTE; + } + + for (i = 0; i <= max_destid;) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, + ext_cfg | i); + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, + port_sel); + i += id_inc; + } + } + + udelay(10); + return 0; +} + +static void rio_fixup_device(struct rio_dev *dev) +{ +} + +static int __devinit rio_init(void) +{ + struct rio_dev *dev = NULL; + + while ((dev = rio_get_device(RIO_ANY_ID, RIO_ANY_ID, dev)) != NULL) { + rio_fixup_device(dev); + } + return 0; +} + +int __devinit rio_init_mports(void) +{ + struct rio_mport *port; + + list_for_each_entry(port, &rio_mports, node) { + if (port->host_deviceid >= 0) + rio_enum_mport(port); + else + rio_disc_mport(port); + } + + rio_init(); + + return 0; +} + +device_initcall_sync(rio_init_mports); + +static int hdids[RIO_MAX_MPORTS + 1]; + +static int rio_get_hdid(int index) +{ + if (!hdids[0] || hdids[0] <= index || index >= RIO_MAX_MPORTS) + return -1; + + return hdids[index + 1]; +} + +static int rio_hdid_setup(char *str) +{ + (void)get_options(str, ARRAY_SIZE(hdids), hdids); + return 1; +} + +__setup("riohdid=", rio_hdid_setup); + +int rio_register_mport(struct rio_mport *port) +{ + if (next_portid >= RIO_MAX_MPORTS) { + pr_err("RIO: reached specified max number of mports\n"); + return 1; + } + + port->id = next_portid++; + port->host_deviceid = rio_get_hdid(port->id); + list_add_tail(&port->node, &rio_mports); + return 0; +} + +EXPORT_SYMBOL_GPL(rio_local_get_device_id); +EXPORT_SYMBOL_GPL(rio_get_device); +EXPORT_SYMBOL_GPL(rio_get_asm); +EXPORT_SYMBOL_GPL(rio_request_inb_dbell); +EXPORT_SYMBOL_GPL(rio_release_inb_dbell); +EXPORT_SYMBOL_GPL(rio_request_outb_dbell); +EXPORT_SYMBOL_GPL(rio_release_outb_dbell); +EXPORT_SYMBOL_GPL(rio_request_inb_mbox); +EXPORT_SYMBOL_GPL(rio_release_inb_mbox); +EXPORT_SYMBOL_GPL(rio_request_outb_mbox); +EXPORT_SYMBOL_GPL(rio_release_outb_mbox); diff --git a/drivers/rapidio/rio.h b/drivers/rapidio/rio.h new file mode 100644 index 00000000..b1af414f --- /dev/null +++ b/drivers/rapidio/rio.h @@ -0,0 +1,72 @@ +/* + * RapidIO interconnect services + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/rio.h> + +#define RIO_MAX_CHK_RETRY 3 + +/* Functions internal to the RIO core code */ + +extern u32 rio_mport_get_feature(struct rio_mport *mport, int local, u16 destid, + u8 hopcount, int ftr); +extern u32 rio_mport_get_physefb(struct rio_mport *port, int local, + u16 destid, u8 hopcount); +extern u32 rio_mport_get_efb(struct rio_mport *port, int local, u16 destid, + u8 hopcount, u32 from); +extern int rio_mport_chk_dev_access(struct rio_mport *mport, u16 destid, + u8 hopcount); +extern int rio_create_sysfs_dev_files(struct rio_dev *rdev); +extern int rio_enum_mport(struct rio_mport *mport); +extern int rio_disc_mport(struct rio_mport *mport); +extern int rio_std_route_add_entry(struct rio_mport *mport, u16 destid, + u8 hopcount, u16 table, u16 route_destid, + u8 route_port); +extern int rio_std_route_get_entry(struct rio_mport *mport, u16 destid, + u8 hopcount, u16 table, u16 route_destid, + u8 *route_port); +extern int rio_std_route_clr_table(struct rio_mport *mport, u16 destid, + u8 hopcount, u16 table); +extern int rio_set_port_lockout(struct rio_dev *rdev, u32 pnum, int lock); +extern struct rio_dev *rio_get_comptag(u32 comp_tag, struct rio_dev *from); + +/* Structures internal to the RIO core code */ +extern struct device_attribute rio_dev_attrs[]; +extern spinlock_t rio_global_list_lock; + +extern struct rio_switch_ops __start_rio_switch_ops[]; +extern struct rio_switch_ops __end_rio_switch_ops[]; + +/* Helpers internal to the RIO core code */ +#define DECLARE_RIO_SWITCH_SECTION(section, name, vid, did, init_hook) \ + static const struct rio_switch_ops __rio_switch_##name __used \ + __section(section) = { vid, did, init_hook }; + +/** + * DECLARE_RIO_SWITCH_INIT - Registers switch initialization routine + * @vid: RIO vendor ID + * @did: RIO device ID + * @init_hook: Callback that performs switch-specific initialization + * + * Manipulating switch route tables and error management in RIO + * is switch specific. This registers a switch by vendor and device ID with + * initialization callback for setting up switch operations and (if required) + * hardware initialization. A &struct rio_switch_ops is initialized with + * pointer to the init routine and placed into a RIO-specific kernel section. + */ +#define DECLARE_RIO_SWITCH_INIT(vid, did, init_hook) \ + DECLARE_RIO_SWITCH_SECTION(.rio_switch_ops, vid##did, \ + vid, did, init_hook) + +#define RIO_GET_DID(size, x) (size ? (x & 0xffff) : ((x & 0x00ff0000) >> 16)) +#define RIO_SET_DID(size, x) (size ? (x & 0xffff) : ((x & 0x000000ff) << 16)) diff --git a/drivers/rapidio/switches/Kconfig b/drivers/rapidio/switches/Kconfig new file mode 100644 index 00000000..f47fee5d --- /dev/null +++ b/drivers/rapidio/switches/Kconfig @@ -0,0 +1,35 @@ +# +# RapidIO switches configuration +# +config RAPIDIO_TSI57X + bool "IDT Tsi57x SRIO switches support" + depends on RAPIDIO + ---help--- + Includes support for IDT Tsi57x family of serial RapidIO switches. + +config RAPIDIO_CPS_XX + bool "IDT CPS-xx SRIO switches support" + depends on RAPIDIO + ---help--- + Includes support for IDT CPS-16/12/10/8 serial RapidIO switches. + +config RAPIDIO_TSI568 + bool "Tsi568 SRIO switch support" + depends on RAPIDIO + default n + ---help--- + Includes support for IDT Tsi568 serial RapidIO switch. + +config RAPIDIO_CPS_GEN2 + bool "IDT CPS Gen.2 SRIO switch support" + depends on RAPIDIO + default n + ---help--- + Includes support for ITD CPS Gen.2 serial RapidIO switches. + +config RAPIDIO_TSI500 + bool "Tsi500 Parallel RapidIO switch support" + depends on RAPIDIO + default n + ---help--- + Includes support for IDT Tsi500 parallel RapidIO switch. diff --git a/drivers/rapidio/switches/Makefile b/drivers/rapidio/switches/Makefile new file mode 100644 index 00000000..c4d3acc3 --- /dev/null +++ b/drivers/rapidio/switches/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for RIO switches +# + +obj-$(CONFIG_RAPIDIO_TSI57X) += tsi57x.o +obj-$(CONFIG_RAPIDIO_CPS_XX) += idtcps.o +obj-$(CONFIG_RAPIDIO_TSI568) += tsi568.o +obj-$(CONFIG_RAPIDIO_TSI500) += tsi500.o +obj-$(CONFIG_RAPIDIO_CPS_GEN2) += idt_gen2.o diff --git a/drivers/rapidio/switches/idt_gen2.c b/drivers/rapidio/switches/idt_gen2.c new file mode 100644 index 00000000..043ee313 --- /dev/null +++ b/drivers/rapidio/switches/idt_gen2.c @@ -0,0 +1,430 @@ +/* + * IDT CPS Gen.2 Serial RapidIO switch family support + * + * Copyright 2010 Integrated Device Technology, Inc. + * Alexandre Bounine <alexandre.bounine@idt.com> + * + * 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. + */ + +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include <linux/delay.h> +#include "../rio.h" + +#define LOCAL_RTE_CONF_DESTID_SEL 0x010070 +#define LOCAL_RTE_CONF_DESTID_SEL_PSEL 0x0000001f + +#define IDT_LT_ERR_REPORT_EN 0x03100c + +#define IDT_PORT_ERR_REPORT_EN(n) (0x031044 + (n)*0x40) +#define IDT_PORT_ERR_REPORT_EN_BC 0x03ff04 + +#define IDT_PORT_ISERR_REPORT_EN(n) (0x03104C + (n)*0x40) +#define IDT_PORT_ISERR_REPORT_EN_BC 0x03ff0c +#define IDT_PORT_INIT_TX_ACQUIRED 0x00000020 + +#define IDT_LANE_ERR_REPORT_EN(n) (0x038010 + (n)*0x100) +#define IDT_LANE_ERR_REPORT_EN_BC 0x03ff10 + +#define IDT_DEV_CTRL_1 0xf2000c +#define IDT_DEV_CTRL_1_GENPW 0x02000000 +#define IDT_DEV_CTRL_1_PRSTBEH 0x00000001 + +#define IDT_CFGBLK_ERR_CAPTURE_EN 0x020008 +#define IDT_CFGBLK_ERR_REPORT 0xf20014 +#define IDT_CFGBLK_ERR_REPORT_GENPW 0x00000002 + +#define IDT_AUX_PORT_ERR_CAP_EN 0x020000 +#define IDT_AUX_ERR_REPORT_EN 0xf20018 +#define IDT_AUX_PORT_ERR_LOG_I2C 0x00000002 +#define IDT_AUX_PORT_ERR_LOG_JTAG 0x00000001 + +#define IDT_ISLTL_ADDRESS_CAP 0x021014 + +#define IDT_RIO_DOMAIN 0xf20020 +#define IDT_RIO_DOMAIN_MASK 0x000000ff + +#define IDT_PW_INFO_CSR 0xf20024 + +#define IDT_SOFT_RESET 0xf20040 +#define IDT_SOFT_RESET_REQ 0x00030097 + +#define IDT_I2C_MCTRL 0xf20050 +#define IDT_I2C_MCTRL_GENPW 0x04000000 + +#define IDT_JTAG_CTRL 0xf2005c +#define IDT_JTAG_CTRL_GENPW 0x00000002 + +#define IDT_LANE_CTRL(n) (0xff8000 + (n)*0x100) +#define IDT_LANE_CTRL_BC 0xffff00 +#define IDT_LANE_CTRL_GENPW 0x00200000 +#define IDT_LANE_DFE_1_BC 0xffff18 +#define IDT_LANE_DFE_2_BC 0xffff1c + +#define IDT_PORT_OPS(n) (0xf40004 + (n)*0x100) +#define IDT_PORT_OPS_GENPW 0x08000000 +#define IDT_PORT_OPS_PL_ELOG 0x00000040 +#define IDT_PORT_OPS_LL_ELOG 0x00000020 +#define IDT_PORT_OPS_LT_ELOG 0x00000010 +#define IDT_PORT_OPS_BC 0xf4ff04 + +#define IDT_PORT_ISERR_DET(n) (0xf40008 + (n)*0x100) + +#define IDT_ERR_CAP 0xfd0000 +#define IDT_ERR_CAP_LOG_OVERWR 0x00000004 + +#define IDT_ERR_RD 0xfd0004 + +#define IDT_DEFAULT_ROUTE 0xde +#define IDT_NO_ROUTE 0xdf + +static int +idtg2_route_add_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 route_port) +{ + /* + * Select routing table to update + */ + if (table == RIO_GLOBAL_TABLE) + table = 0; + else + table++; + + if (route_port == RIO_INVALID_ROUTE) + route_port = IDT_DEFAULT_ROUTE; + + rio_mport_write_config_32(mport, destid, hopcount, + LOCAL_RTE_CONF_DESTID_SEL, table); + + /* + * Program destination port for the specified destID + */ + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, + (u32)route_destid); + + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, + (u32)route_port); + udelay(10); + + return 0; +} + +static int +idtg2_route_get_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 *route_port) +{ + u32 result; + + /* + * Select routing table to read + */ + if (table == RIO_GLOBAL_TABLE) + table = 0; + else + table++; + + rio_mport_write_config_32(mport, destid, hopcount, + LOCAL_RTE_CONF_DESTID_SEL, table); + + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, + route_destid); + + rio_mport_read_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, &result); + + if (IDT_DEFAULT_ROUTE == (u8)result || IDT_NO_ROUTE == (u8)result) + *route_port = RIO_INVALID_ROUTE; + else + *route_port = (u8)result; + + return 0; +} + +static int +idtg2_route_clr_table(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table) +{ + u32 i; + + /* + * Select routing table to read + */ + if (table == RIO_GLOBAL_TABLE) + table = 0; + else + table++; + + rio_mport_write_config_32(mport, destid, hopcount, + LOCAL_RTE_CONF_DESTID_SEL, table); + + for (i = RIO_STD_RTE_CONF_EXTCFGEN; + i <= (RIO_STD_RTE_CONF_EXTCFGEN | 0xff);) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, i); + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, + (IDT_DEFAULT_ROUTE << 24) | (IDT_DEFAULT_ROUTE << 16) | + (IDT_DEFAULT_ROUTE << 8) | IDT_DEFAULT_ROUTE); + i += 4; + } + + return 0; +} + + +static int +idtg2_set_domain(struct rio_mport *mport, u16 destid, u8 hopcount, + u8 sw_domain) +{ + /* + * Switch domain configuration operates only at global level + */ + rio_mport_write_config_32(mport, destid, hopcount, + IDT_RIO_DOMAIN, (u32)sw_domain); + return 0; +} + +static int +idtg2_get_domain(struct rio_mport *mport, u16 destid, u8 hopcount, + u8 *sw_domain) +{ + u32 regval; + + /* + * Switch domain configuration operates only at global level + */ + rio_mport_read_config_32(mport, destid, hopcount, + IDT_RIO_DOMAIN, ®val); + + *sw_domain = (u8)(regval & 0xff); + + return 0; +} + +static int +idtg2_em_init(struct rio_dev *rdev) +{ + u32 regval; + int i, tmp; + + /* + * This routine performs device-specific initialization only. + * All standard EM configuration should be performed at upper level. + */ + + pr_debug("RIO: %s [%d:%d]\n", __func__, rdev->destid, rdev->hopcount); + + /* Set Port-Write info CSR: PRIO=3 and CRF=1 */ + rio_write_config_32(rdev, IDT_PW_INFO_CSR, 0x0000e000); + + /* + * Configure LT LAYER error reporting. + */ + + /* Enable standard (RIO.p8) error reporting */ + rio_write_config_32(rdev, IDT_LT_ERR_REPORT_EN, + REM_LTL_ERR_ILLTRAN | REM_LTL_ERR_UNSOLR | + REM_LTL_ERR_UNSUPTR); + + /* Use Port-Writes for LT layer error reporting. + * Enable per-port reset + */ + rio_read_config_32(rdev, IDT_DEV_CTRL_1, ®val); + rio_write_config_32(rdev, IDT_DEV_CTRL_1, + regval | IDT_DEV_CTRL_1_GENPW | IDT_DEV_CTRL_1_PRSTBEH); + + /* + * Configure PORT error reporting. + */ + + /* Report all RIO.p8 errors supported by device */ + rio_write_config_32(rdev, IDT_PORT_ERR_REPORT_EN_BC, 0x807e8037); + + /* Configure reporting of implementation specific errors/events */ + rio_write_config_32(rdev, IDT_PORT_ISERR_REPORT_EN_BC, + IDT_PORT_INIT_TX_ACQUIRED); + + /* Use Port-Writes for port error reporting and enable error logging */ + tmp = RIO_GET_TOTAL_PORTS(rdev->swpinfo); + for (i = 0; i < tmp; i++) { + rio_read_config_32(rdev, IDT_PORT_OPS(i), ®val); + rio_write_config_32(rdev, + IDT_PORT_OPS(i), regval | IDT_PORT_OPS_GENPW | + IDT_PORT_OPS_PL_ELOG | + IDT_PORT_OPS_LL_ELOG | + IDT_PORT_OPS_LT_ELOG); + } + /* Overwrite error log if full */ + rio_write_config_32(rdev, IDT_ERR_CAP, IDT_ERR_CAP_LOG_OVERWR); + + /* + * Configure LANE error reporting. + */ + + /* Disable line error reporting */ + rio_write_config_32(rdev, IDT_LANE_ERR_REPORT_EN_BC, 0); + + /* Use Port-Writes for lane error reporting (when enabled) + * (do per-lane update because lanes may have different configuration) + */ + tmp = (rdev->did == RIO_DID_IDTCPS1848) ? 48 : 16; + for (i = 0; i < tmp; i++) { + rio_read_config_32(rdev, IDT_LANE_CTRL(i), ®val); + rio_write_config_32(rdev, IDT_LANE_CTRL(i), + regval | IDT_LANE_CTRL_GENPW); + } + + /* + * Configure AUX error reporting. + */ + + /* Disable JTAG and I2C Error capture */ + rio_write_config_32(rdev, IDT_AUX_PORT_ERR_CAP_EN, 0); + + /* Disable JTAG and I2C Error reporting/logging */ + rio_write_config_32(rdev, IDT_AUX_ERR_REPORT_EN, 0); + + /* Disable Port-Write notification from JTAG */ + rio_write_config_32(rdev, IDT_JTAG_CTRL, 0); + + /* Disable Port-Write notification from I2C */ + rio_read_config_32(rdev, IDT_I2C_MCTRL, ®val); + rio_write_config_32(rdev, IDT_I2C_MCTRL, regval & ~IDT_I2C_MCTRL_GENPW); + + /* + * Configure CFG_BLK error reporting. + */ + + /* Disable Configuration Block error capture */ + rio_write_config_32(rdev, IDT_CFGBLK_ERR_CAPTURE_EN, 0); + + /* Disable Port-Writes for Configuration Block error reporting */ + rio_read_config_32(rdev, IDT_CFGBLK_ERR_REPORT, ®val); + rio_write_config_32(rdev, IDT_CFGBLK_ERR_REPORT, + regval & ~IDT_CFGBLK_ERR_REPORT_GENPW); + + /* set TVAL = ~50us */ + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_LINKTO_CTL_CSR, 0x8e << 8); + + return 0; +} + +static int +idtg2_em_handler(struct rio_dev *rdev, u8 portnum) +{ + u32 regval, em_perrdet, em_ltlerrdet; + + rio_read_config_32(rdev, + rdev->em_efptr + RIO_EM_LTL_ERR_DETECT, &em_ltlerrdet); + if (em_ltlerrdet) { + /* Service Logical/Transport Layer Error(s) */ + if (em_ltlerrdet & REM_LTL_ERR_IMPSPEC) { + /* Implementation specific error reported */ + rio_read_config_32(rdev, + IDT_ISLTL_ADDRESS_CAP, ®val); + + pr_debug("RIO: %s Implementation Specific LTL errors" \ + " 0x%x @(0x%x)\n", + rio_name(rdev), em_ltlerrdet, regval); + + /* Clear implementation specific address capture CSR */ + rio_write_config_32(rdev, IDT_ISLTL_ADDRESS_CAP, 0); + + } + } + + rio_read_config_32(rdev, + rdev->em_efptr + RIO_EM_PN_ERR_DETECT(portnum), &em_perrdet); + if (em_perrdet) { + /* Service Port-Level Error(s) */ + if (em_perrdet & REM_PED_IMPL_SPEC) { + /* Implementation Specific port error reported */ + + /* Get IS errors reported */ + rio_read_config_32(rdev, + IDT_PORT_ISERR_DET(portnum), ®val); + + pr_debug("RIO: %s Implementation Specific Port" \ + " errors 0x%x\n", rio_name(rdev), regval); + + /* Clear all implementation specific events */ + rio_write_config_32(rdev, + IDT_PORT_ISERR_DET(portnum), 0); + } + } + + return 0; +} + +static ssize_t +idtg2_show_errlog(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct rio_dev *rdev = to_rio_dev(dev); + ssize_t len = 0; + u32 regval; + + while (!rio_read_config_32(rdev, IDT_ERR_RD, ®val)) { + if (!regval) /* 0 = end of log */ + break; + len += snprintf(buf + len, PAGE_SIZE - len, + "%08x\n", regval); + if (len >= (PAGE_SIZE - 10)) + break; + } + + return len; +} + +static DEVICE_ATTR(errlog, S_IRUGO, idtg2_show_errlog, NULL); + +static int idtg2_sysfs(struct rio_dev *rdev, int create) +{ + struct device *dev = &rdev->dev; + int err = 0; + + if (create == RIO_SW_SYSFS_CREATE) { + /* Initialize sysfs entries */ + err = device_create_file(dev, &dev_attr_errlog); + if (err) + dev_err(dev, "Unable create sysfs errlog file\n"); + } else + device_remove_file(dev, &dev_attr_errlog); + + return err; +} + +static int idtg2_switch_init(struct rio_dev *rdev, int do_enum) +{ + pr_debug("RIO: %s for %s\n", __func__, rio_name(rdev)); + rdev->rswitch->add_entry = idtg2_route_add_entry; + rdev->rswitch->get_entry = idtg2_route_get_entry; + rdev->rswitch->clr_table = idtg2_route_clr_table; + rdev->rswitch->set_domain = idtg2_set_domain; + rdev->rswitch->get_domain = idtg2_get_domain; + rdev->rswitch->em_init = idtg2_em_init; + rdev->rswitch->em_handle = idtg2_em_handler; + rdev->rswitch->sw_sysfs = idtg2_sysfs; + + if (do_enum) { + /* Ensure that default routing is disabled on startup */ + rio_write_config_32(rdev, + RIO_STD_RTE_DEFAULT_PORT, IDT_NO_ROUTE); + } + + return 0; +} + +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS1848, idtg2_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS1616, idtg2_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTVPS1616, idtg2_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTSPS1616, idtg2_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS1432, idtg2_switch_init); diff --git a/drivers/rapidio/switches/idtcps.c b/drivers/rapidio/switches/idtcps.c new file mode 100644 index 00000000..d06ee2d4 --- /dev/null +++ b/drivers/rapidio/switches/idtcps.c @@ -0,0 +1,149 @@ +/* + * IDT CPS RapidIO switches support + * + * Copyright 2009-2010 Integrated Device Technology, Inc. + * Alexandre Bounine <alexandre.bounine@idt.com> + * + * 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. + */ + +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include "../rio.h" + +#define CPS_DEFAULT_ROUTE 0xde +#define CPS_NO_ROUTE 0xdf + +#define IDTCPS_RIO_DOMAIN 0xf20020 + +static int +idtcps_route_add_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 route_port) +{ + u32 result; + + if (route_port == RIO_INVALID_ROUTE) + route_port = CPS_DEFAULT_ROUTE; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, route_destid); + + rio_mport_read_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, &result); + + result = (0xffffff00 & result) | (u32)route_port; + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, result); + } + + return 0; +} + +static int +idtcps_route_get_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 *route_port) +{ + u32 result; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, route_destid); + + rio_mport_read_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, &result); + + if (CPS_DEFAULT_ROUTE == (u8)result || + CPS_NO_ROUTE == (u8)result) + *route_port = RIO_INVALID_ROUTE; + else + *route_port = (u8)result; + } + + return 0; +} + +static int +idtcps_route_clr_table(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table) +{ + u32 i; + + if (table == RIO_GLOBAL_TABLE) { + for (i = 0x80000000; i <= 0x800000ff;) { + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_DESTID_SEL_CSR, i); + rio_mport_write_config_32(mport, destid, hopcount, + RIO_STD_RTE_CONF_PORT_SEL_CSR, + (CPS_DEFAULT_ROUTE << 24) | + (CPS_DEFAULT_ROUTE << 16) | + (CPS_DEFAULT_ROUTE << 8) | CPS_DEFAULT_ROUTE); + i += 4; + } + } + + return 0; +} + +static int +idtcps_set_domain(struct rio_mport *mport, u16 destid, u8 hopcount, + u8 sw_domain) +{ + /* + * Switch domain configuration operates only at global level + */ + rio_mport_write_config_32(mport, destid, hopcount, + IDTCPS_RIO_DOMAIN, (u32)sw_domain); + return 0; +} + +static int +idtcps_get_domain(struct rio_mport *mport, u16 destid, u8 hopcount, + u8 *sw_domain) +{ + u32 regval; + + /* + * Switch domain configuration operates only at global level + */ + rio_mport_read_config_32(mport, destid, hopcount, + IDTCPS_RIO_DOMAIN, ®val); + + *sw_domain = (u8)(regval & 0xff); + + return 0; +} + +static int idtcps_switch_init(struct rio_dev *rdev, int do_enum) +{ + pr_debug("RIO: %s for %s\n", __func__, rio_name(rdev)); + rdev->rswitch->add_entry = idtcps_route_add_entry; + rdev->rswitch->get_entry = idtcps_route_get_entry; + rdev->rswitch->clr_table = idtcps_route_clr_table; + rdev->rswitch->set_domain = idtcps_set_domain; + rdev->rswitch->get_domain = idtcps_get_domain; + rdev->rswitch->em_init = NULL; + rdev->rswitch->em_handle = NULL; + + if (do_enum) { + /* set TVAL = ~50us */ + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_LINKTO_CTL_CSR, 0x8e << 8); + /* Ensure that default routing is disabled on startup */ + rio_write_config_32(rdev, + RIO_STD_RTE_DEFAULT_PORT, CPS_NO_ROUTE); + } + + return 0; +} + +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS6Q, idtcps_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS8, idtcps_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS10Q, idtcps_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS12, idtcps_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDTCPS16, idtcps_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_IDT, RIO_DID_IDT70K200, idtcps_switch_init); diff --git a/drivers/rapidio/switches/tsi500.c b/drivers/rapidio/switches/tsi500.c new file mode 100644 index 00000000..914eddd5 --- /dev/null +++ b/drivers/rapidio/switches/tsi500.c @@ -0,0 +1,78 @@ +/* + * RapidIO Tsi500 switch support + * + * Copyright 2009-2010 Integrated Device Technology, Inc. + * Alexandre Bounine <alexandre.bounine@idt.com> + * - Modified switch operations initialization. + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include "../rio.h" + +static int +tsi500_route_add_entry(struct rio_mport *mport, u16 destid, u8 hopcount, u16 table, u16 route_destid, u8 route_port) +{ + int i; + u32 offset = 0x10000 + 0xa00 + ((route_destid / 2)&~0x3); + u32 result; + + if (table == 0xff) { + rio_mport_read_config_32(mport, destid, hopcount, offset, &result); + result &= ~(0xf << (4*(route_destid & 0x7))); + for (i=0;i<4;i++) + rio_mport_write_config_32(mport, destid, hopcount, offset + (0x20000*i), result | (route_port << (4*(route_destid & 0x7)))); + } + else { + rio_mport_read_config_32(mport, destid, hopcount, offset + (0x20000*table), &result); + result &= ~(0xf << (4*(route_destid & 0x7))); + rio_mport_write_config_32(mport, destid, hopcount, offset + (0x20000*table), result | (route_port << (4*(route_destid & 0x7)))); + } + + return 0; +} + +static int +tsi500_route_get_entry(struct rio_mport *mport, u16 destid, u8 hopcount, u16 table, u16 route_destid, u8 *route_port) +{ + int ret = 0; + u32 offset = 0x10000 + 0xa00 + ((route_destid / 2)&~0x3); + u32 result; + + if (table == 0xff) + rio_mport_read_config_32(mport, destid, hopcount, offset, &result); + else + rio_mport_read_config_32(mport, destid, hopcount, offset + (0x20000*table), &result); + + result &= 0xf << (4*(route_destid & 0x7)); + *route_port = result >> (4*(route_destid & 0x7)); + if (*route_port > 3) + ret = -1; + + return ret; +} + +static int tsi500_switch_init(struct rio_dev *rdev, int do_enum) +{ + pr_debug("RIO: %s for %s\n", __func__, rio_name(rdev)); + rdev->rswitch->add_entry = tsi500_route_add_entry; + rdev->rswitch->get_entry = tsi500_route_get_entry; + rdev->rswitch->clr_table = NULL; + rdev->rswitch->set_domain = NULL; + rdev->rswitch->get_domain = NULL; + rdev->rswitch->em_init = NULL; + rdev->rswitch->em_handle = NULL; + + return 0; +} + +DECLARE_RIO_SWITCH_INIT(RIO_VID_TUNDRA, RIO_DID_TSI500, tsi500_switch_init); diff --git a/drivers/rapidio/switches/tsi568.c b/drivers/rapidio/switches/tsi568.c new file mode 100644 index 00000000..3994c00a --- /dev/null +++ b/drivers/rapidio/switches/tsi568.c @@ -0,0 +1,146 @@ +/* + * RapidIO Tsi568 switch support + * + * Copyright 2009-2010 Integrated Device Technology, Inc. + * Alexandre Bounine <alexandre.bounine@idt.com> + * - Added EM support + * - Modified switch operations initialization. + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include <linux/delay.h> +#include "../rio.h" + +/* Global (broadcast) route registers */ +#define SPBC_ROUTE_CFG_DESTID 0x10070 +#define SPBC_ROUTE_CFG_PORT 0x10074 + +/* Per port route registers */ +#define SPP_ROUTE_CFG_DESTID(n) (0x11070 + 0x100*n) +#define SPP_ROUTE_CFG_PORT(n) (0x11074 + 0x100*n) + +#define TSI568_SP_MODE(n) (0x11004 + 0x100*n) +#define TSI568_SP_MODE_PW_DIS 0x08000000 + +static int +tsi568_route_add_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 route_port) +{ + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_DESTID, route_destid); + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_PORT, route_port); + } else { + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_DESTID(table), + route_destid); + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_PORT(table), route_port); + } + + udelay(10); + + return 0; +} + +static int +tsi568_route_get_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 *route_port) +{ + int ret = 0; + u32 result; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_DESTID, route_destid); + rio_mport_read_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_PORT, &result); + } else { + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_DESTID(table), + route_destid); + rio_mport_read_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_PORT(table), &result); + } + + *route_port = result; + if (*route_port > 15) + ret = -1; + + return ret; +} + +static int +tsi568_route_clr_table(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table) +{ + u32 route_idx; + u32 lut_size; + + lut_size = (mport->sys_size) ? 0x1ff : 0xff; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_DESTID, 0x80000000); + for (route_idx = 0; route_idx <= lut_size; route_idx++) + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_PORT, + RIO_INVALID_ROUTE); + } else { + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_DESTID(table), + 0x80000000); + for (route_idx = 0; route_idx <= lut_size; route_idx++) + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_PORT(table), + RIO_INVALID_ROUTE); + } + + return 0; +} + +static int +tsi568_em_init(struct rio_dev *rdev) +{ + u32 regval; + int portnum; + + pr_debug("TSI568 %s [%d:%d]\n", __func__, rdev->destid, rdev->hopcount); + + /* Make sure that Port-Writes are disabled (for all ports) */ + for (portnum = 0; + portnum < RIO_GET_TOTAL_PORTS(rdev->swpinfo); portnum++) { + rio_read_config_32(rdev, TSI568_SP_MODE(portnum), ®val); + rio_write_config_32(rdev, TSI568_SP_MODE(portnum), + regval | TSI568_SP_MODE_PW_DIS); + } + + return 0; +} + +static int tsi568_switch_init(struct rio_dev *rdev, int do_enum) +{ + pr_debug("RIO: %s for %s\n", __func__, rio_name(rdev)); + rdev->rswitch->add_entry = tsi568_route_add_entry; + rdev->rswitch->get_entry = tsi568_route_get_entry; + rdev->rswitch->clr_table = tsi568_route_clr_table; + rdev->rswitch->set_domain = NULL; + rdev->rswitch->get_domain = NULL; + rdev->rswitch->em_init = tsi568_em_init; + rdev->rswitch->em_handle = NULL; + + return 0; +} + +DECLARE_RIO_SWITCH_INIT(RIO_VID_TUNDRA, RIO_DID_TSI568, tsi568_switch_init); diff --git a/drivers/rapidio/switches/tsi57x.c b/drivers/rapidio/switches/tsi57x.c new file mode 100644 index 00000000..db8b8028 --- /dev/null +++ b/drivers/rapidio/switches/tsi57x.c @@ -0,0 +1,318 @@ +/* + * RapidIO Tsi57x switch family support + * + * Copyright 2009-2010 Integrated Device Technology, Inc. + * Alexandre Bounine <alexandre.bounine@idt.com> + * - Added EM support + * - Modified switch operations initialization. + * + * Copyright 2005 MontaVista Software, Inc. + * Matt Porter <mporter@kernel.crashing.org> + * + * 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. + */ + +#include <linux/rio.h> +#include <linux/rio_drv.h> +#include <linux/rio_ids.h> +#include <linux/delay.h> +#include "../rio.h" + +/* Global (broadcast) route registers */ +#define SPBC_ROUTE_CFG_DESTID 0x10070 +#define SPBC_ROUTE_CFG_PORT 0x10074 + +/* Per port route registers */ +#define SPP_ROUTE_CFG_DESTID(n) (0x11070 + 0x100*n) +#define SPP_ROUTE_CFG_PORT(n) (0x11074 + 0x100*n) + +#define TSI578_SP_MODE(n) (0x11004 + n*0x100) +#define TSI578_SP_MODE_GLBL 0x10004 +#define TSI578_SP_MODE_PW_DIS 0x08000000 +#define TSI578_SP_MODE_LUT_512 0x01000000 + +#define TSI578_SP_CTL_INDEP(n) (0x13004 + n*0x100) +#define TSI578_SP_LUT_PEINF(n) (0x13010 + n*0x100) +#define TSI578_SP_CS_TX(n) (0x13014 + n*0x100) +#define TSI578_SP_INT_STATUS(n) (0x13018 + n*0x100) + +#define TSI578_GLBL_ROUTE_BASE 0x10078 + +static int +tsi57x_route_add_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 route_port) +{ + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_DESTID, route_destid); + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_PORT, route_port); + } else { + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_DESTID(table), route_destid); + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_PORT(table), route_port); + } + + udelay(10); + + return 0; +} + +static int +tsi57x_route_get_entry(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table, u16 route_destid, u8 *route_port) +{ + int ret = 0; + u32 result; + + if (table == RIO_GLOBAL_TABLE) { + /* Use local RT of the ingress port to avoid possible + race condition */ + rio_mport_read_config_32(mport, destid, hopcount, + RIO_SWP_INFO_CAR, &result); + table = (result & RIO_SWP_INFO_PORT_NUM_MASK); + } + + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_DESTID(table), route_destid); + rio_mport_read_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_PORT(table), &result); + + *route_port = (u8)result; + if (*route_port > 15) + ret = -1; + + return ret; +} + +static int +tsi57x_route_clr_table(struct rio_mport *mport, u16 destid, u8 hopcount, + u16 table) +{ + u32 route_idx; + u32 lut_size; + + lut_size = (mport->sys_size) ? 0x1ff : 0xff; + + if (table == RIO_GLOBAL_TABLE) { + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_DESTID, 0x80000000); + for (route_idx = 0; route_idx <= lut_size; route_idx++) + rio_mport_write_config_32(mport, destid, hopcount, + SPBC_ROUTE_CFG_PORT, + RIO_INVALID_ROUTE); + } else { + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_DESTID(table), 0x80000000); + for (route_idx = 0; route_idx <= lut_size; route_idx++) + rio_mport_write_config_32(mport, destid, hopcount, + SPP_ROUTE_CFG_PORT(table) , RIO_INVALID_ROUTE); + } + + return 0; +} + +static int +tsi57x_set_domain(struct rio_mport *mport, u16 destid, u8 hopcount, + u8 sw_domain) +{ + u32 regval; + + /* + * Switch domain configuration operates only at global level + */ + + /* Turn off flat (LUT_512) mode */ + rio_mport_read_config_32(mport, destid, hopcount, + TSI578_SP_MODE_GLBL, ®val); + rio_mport_write_config_32(mport, destid, hopcount, TSI578_SP_MODE_GLBL, + regval & ~TSI578_SP_MODE_LUT_512); + /* Set switch domain base */ + rio_mport_write_config_32(mport, destid, hopcount, + TSI578_GLBL_ROUTE_BASE, + (u32)(sw_domain << 24)); + return 0; +} + +static int +tsi57x_get_domain(struct rio_mport *mport, u16 destid, u8 hopcount, + u8 *sw_domain) +{ + u32 regval; + + /* + * Switch domain configuration operates only at global level + */ + rio_mport_read_config_32(mport, destid, hopcount, + TSI578_GLBL_ROUTE_BASE, ®val); + + *sw_domain = (u8)(regval >> 24); + + return 0; +} + +static int +tsi57x_em_init(struct rio_dev *rdev) +{ + u32 regval; + int portnum; + + pr_debug("TSI578 %s [%d:%d]\n", __func__, rdev->destid, rdev->hopcount); + + for (portnum = 0; + portnum < RIO_GET_TOTAL_PORTS(rdev->swpinfo); portnum++) { + /* Make sure that Port-Writes are enabled (for all ports) */ + rio_read_config_32(rdev, + TSI578_SP_MODE(portnum), ®val); + rio_write_config_32(rdev, + TSI578_SP_MODE(portnum), + regval & ~TSI578_SP_MODE_PW_DIS); + + /* Clear all pending interrupts */ + rio_read_config_32(rdev, + rdev->phys_efptr + + RIO_PORT_N_ERR_STS_CSR(portnum), + ®val); + rio_write_config_32(rdev, + rdev->phys_efptr + + RIO_PORT_N_ERR_STS_CSR(portnum), + regval & 0x07120214); + + rio_read_config_32(rdev, + TSI578_SP_INT_STATUS(portnum), ®val); + rio_write_config_32(rdev, + TSI578_SP_INT_STATUS(portnum), + regval & 0x000700bd); + + /* Enable all interrupts to allow ports to send a port-write */ + rio_read_config_32(rdev, + TSI578_SP_CTL_INDEP(portnum), ®val); + rio_write_config_32(rdev, + TSI578_SP_CTL_INDEP(portnum), + regval | 0x000b0000); + + /* Skip next (odd) port if the current port is in x4 mode */ + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_CTL_CSR(portnum), + ®val); + if ((regval & RIO_PORT_N_CTL_PWIDTH) == RIO_PORT_N_CTL_PWIDTH_4) + portnum++; + } + + /* set TVAL = ~50us */ + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_LINKTO_CTL_CSR, 0x9a << 8); + + return 0; +} + +static int +tsi57x_em_handler(struct rio_dev *rdev, u8 portnum) +{ + struct rio_mport *mport = rdev->net->hport; + u32 intstat, err_status; + int sendcount, checkcount; + u8 route_port; + u32 regval; + + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_ERR_STS_CSR(portnum), + &err_status); + + if ((err_status & RIO_PORT_N_ERR_STS_PORT_OK) && + (err_status & (RIO_PORT_N_ERR_STS_PW_OUT_ES | + RIO_PORT_N_ERR_STS_PW_INP_ES))) { + /* Remove any queued packets by locking/unlocking port */ + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_CTL_CSR(portnum), + ®val); + if (!(regval & RIO_PORT_N_CTL_LOCKOUT)) { + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_CTL_CSR(portnum), + regval | RIO_PORT_N_CTL_LOCKOUT); + udelay(50); + rio_write_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_CTL_CSR(portnum), + regval); + } + + /* Read from link maintenance response register to clear + * valid bit + */ + rio_read_config_32(rdev, + rdev->phys_efptr + RIO_PORT_N_MNT_RSP_CSR(portnum), + ®val); + + /* Send a Packet-Not-Accepted/Link-Request-Input-Status control + * symbol to recover from IES/OES + */ + sendcount = 3; + while (sendcount) { + rio_write_config_32(rdev, + TSI578_SP_CS_TX(portnum), 0x40fc8000); + checkcount = 3; + while (checkcount--) { + udelay(50); + rio_read_config_32(rdev, + rdev->phys_efptr + + RIO_PORT_N_MNT_RSP_CSR(portnum), + ®val); + if (regval & RIO_PORT_N_MNT_RSP_RVAL) + goto exit_es; + } + + sendcount--; + } + } + +exit_es: + /* Clear implementation specific error status bits */ + rio_read_config_32(rdev, TSI578_SP_INT_STATUS(portnum), &intstat); + pr_debug("TSI578[%x:%x] SP%d_INT_STATUS=0x%08x\n", + rdev->destid, rdev->hopcount, portnum, intstat); + + if (intstat & 0x10000) { + rio_read_config_32(rdev, + TSI578_SP_LUT_PEINF(portnum), ®val); + regval = (mport->sys_size) ? (regval >> 16) : (regval >> 24); + route_port = rdev->rswitch->route_table[regval]; + pr_debug("RIO: TSI578[%s] P%d LUT Parity Error (destID=%d)\n", + rio_name(rdev), portnum, regval); + tsi57x_route_add_entry(mport, rdev->destid, rdev->hopcount, + RIO_GLOBAL_TABLE, regval, route_port); + } + + rio_write_config_32(rdev, TSI578_SP_INT_STATUS(portnum), + intstat & 0x000700bd); + + return 0; +} + +static int tsi57x_switch_init(struct rio_dev *rdev, int do_enum) +{ + pr_debug("RIO: %s for %s\n", __func__, rio_name(rdev)); + rdev->rswitch->add_entry = tsi57x_route_add_entry; + rdev->rswitch->get_entry = tsi57x_route_get_entry; + rdev->rswitch->clr_table = tsi57x_route_clr_table; + rdev->rswitch->set_domain = tsi57x_set_domain; + rdev->rswitch->get_domain = tsi57x_get_domain; + rdev->rswitch->em_init = tsi57x_em_init; + rdev->rswitch->em_handle = tsi57x_em_handler; + + if (do_enum) { + /* Ensure that default routing is disabled on startup */ + rio_write_config_32(rdev, RIO_STD_RTE_DEFAULT_PORT, + RIO_INVALID_ROUTE); + } + + return 0; +} + +DECLARE_RIO_SWITCH_INIT(RIO_VID_TUNDRA, RIO_DID_TSI572, tsi57x_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_TUNDRA, RIO_DID_TSI574, tsi57x_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_TUNDRA, RIO_DID_TSI577, tsi57x_switch_init); +DECLARE_RIO_SWITCH_INIT(RIO_VID_TUNDRA, RIO_DID_TSI578, tsi57x_switch_init); |