|  | /* | 
|  | * This file is subject to the terms and conditions of the GNU General Public | 
|  | * License.  See the file "COPYING" in the main directory of this archive | 
|  | * for more details. | 
|  | * | 
|  | * Copyright (c) 2005 Silicon Graphics, Inc.  All rights reserved. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/version.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/proc_fs.h> | 
|  | #include <linux/device.h> | 
|  | #include <linux/delay.h> | 
|  | #include <asm/uaccess.h> | 
|  | #include <asm/sn/sn_sal.h> | 
|  | #include <asm/sn/addrs.h> | 
|  | #include <asm/sn/io.h> | 
|  | #include <asm/sn/types.h> | 
|  | #include <asm/sn/shubio.h> | 
|  | #include <asm/sn/tiocx.h> | 
|  | #include <asm/sn/l1.h> | 
|  | #include <asm/sn/module.h> | 
|  | #include "tio.h" | 
|  | #include "xtalk/xwidgetdev.h" | 
|  | #include "xtalk/hubdev.h" | 
|  |  | 
|  | #define CX_DEV_NONE 0 | 
|  | #define DEVICE_NAME "tiocx" | 
|  | #define WIDGET_ID 0 | 
|  | #define TIOCX_DEBUG 0 | 
|  |  | 
|  | #if TIOCX_DEBUG | 
|  | #define DBG(fmt...)    printk(KERN_ALERT fmt) | 
|  | #else | 
|  | #define DBG(fmt...) | 
|  | #endif | 
|  |  | 
|  | struct device_attribute dev_attr_cxdev_control; | 
|  |  | 
|  | /** | 
|  | * tiocx_match - Try to match driver id list with device. | 
|  | * @dev: device pointer | 
|  | * @drv: driver pointer | 
|  | * | 
|  | * Returns 1 if match, 0 otherwise. | 
|  | */ | 
|  | static int tiocx_match(struct device *dev, struct device_driver *drv) | 
|  | { | 
|  | struct cx_dev *cx_dev = to_cx_dev(dev); | 
|  | struct cx_drv *cx_drv = to_cx_driver(drv); | 
|  | const struct cx_device_id *ids = cx_drv->id_table; | 
|  |  | 
|  | if (!ids) | 
|  | return 0; | 
|  |  | 
|  | while (ids->part_num) { | 
|  | if (ids->part_num == cx_dev->cx_id.part_num) | 
|  | return 1; | 
|  | ids++; | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | } | 
|  |  | 
|  | static int tiocx_hotplug(struct device *dev, char **envp, int num_envp, | 
|  | char *buffer, int buffer_size) | 
|  | { | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | static void tiocx_bus_release(struct device *dev) | 
|  | { | 
|  | kfree(to_cx_dev(dev)); | 
|  | } | 
|  |  | 
|  | struct bus_type tiocx_bus_type = { | 
|  | .name = "tiocx", | 
|  | .match = tiocx_match, | 
|  | .hotplug = tiocx_hotplug, | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * cx_device_match - Find cx_device in the id table. | 
|  | * @ids: id table from driver | 
|  | * @cx_device: part/mfg id for the device | 
|  | * | 
|  | */ | 
|  | static const struct cx_device_id *cx_device_match(const struct cx_device_id | 
|  | *ids, | 
|  | struct cx_dev *cx_device) | 
|  | { | 
|  | /* | 
|  | * NOTES: We may want to check for CX_ANY_ID too. | 
|  | *        Do we want to match against nasid too? | 
|  | *        CX_DEV_NONE == 0, if the driver tries to register for | 
|  | *        part/mfg == 0 we should return no-match (NULL) here. | 
|  | */ | 
|  | while (ids->part_num && ids->mfg_num) { | 
|  | if (ids->part_num == cx_device->cx_id.part_num && | 
|  | ids->mfg_num == cx_device->cx_id.mfg_num) | 
|  | return ids; | 
|  | ids++; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_device_probe - Look for matching device. | 
|  | *			Call driver probe routine if found. | 
|  | * @cx_driver: driver table (cx_drv struct) from driver | 
|  | * @cx_device: part/mfg id for the device | 
|  | */ | 
|  | static int cx_device_probe(struct device *dev) | 
|  | { | 
|  | const struct cx_device_id *id; | 
|  | struct cx_drv *cx_drv = to_cx_driver(dev->driver); | 
|  | struct cx_dev *cx_dev = to_cx_dev(dev); | 
|  | int error = 0; | 
|  |  | 
|  | if (!cx_dev->driver && cx_drv->probe) { | 
|  | id = cx_device_match(cx_drv->id_table, cx_dev); | 
|  | if (id) { | 
|  | if ((error = cx_drv->probe(cx_dev, id)) < 0) | 
|  | return error; | 
|  | else | 
|  | cx_dev->driver = cx_drv; | 
|  | } | 
|  | } | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_driver_remove - Remove driver from device struct. | 
|  | * @dev: device | 
|  | */ | 
|  | static int cx_driver_remove(struct device *dev) | 
|  | { | 
|  | struct cx_dev *cx_dev = to_cx_dev(dev); | 
|  | struct cx_drv *cx_drv = cx_dev->driver; | 
|  | if (cx_drv->remove) | 
|  | cx_drv->remove(cx_dev); | 
|  | cx_dev->driver = NULL; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_driver_register - Register the driver. | 
|  | * @cx_driver: driver table (cx_drv struct) from driver | 
|  | * | 
|  | * Called from the driver init routine to register a driver. | 
|  | * The cx_drv struct contains the driver name, a pointer to | 
|  | * a table of part/mfg numbers and a pointer to the driver's | 
|  | * probe/attach routine. | 
|  | */ | 
|  | int cx_driver_register(struct cx_drv *cx_driver) | 
|  | { | 
|  | cx_driver->driver.name = cx_driver->name; | 
|  | cx_driver->driver.bus = &tiocx_bus_type; | 
|  | cx_driver->driver.probe = cx_device_probe; | 
|  | cx_driver->driver.remove = cx_driver_remove; | 
|  |  | 
|  | return driver_register(&cx_driver->driver); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_driver_unregister - Unregister the driver. | 
|  | * @cx_driver: driver table (cx_drv struct) from driver | 
|  | */ | 
|  | int cx_driver_unregister(struct cx_drv *cx_driver) | 
|  | { | 
|  | driver_unregister(&cx_driver->driver); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_device_register - Register a device. | 
|  | * @nasid: device's nasid | 
|  | * @part_num: device's part number | 
|  | * @mfg_num: device's manufacturer number | 
|  | * @hubdev: hub info associated with this device | 
|  | * | 
|  | */ | 
|  | int | 
|  | cx_device_register(nasid_t nasid, int part_num, int mfg_num, | 
|  | struct hubdev_info *hubdev) | 
|  | { | 
|  | struct cx_dev *cx_dev; | 
|  |  | 
|  | cx_dev = kcalloc(1, sizeof(struct cx_dev), GFP_KERNEL); | 
|  | DBG("cx_dev= 0x%p\n", cx_dev); | 
|  | if (cx_dev == NULL) | 
|  | return -ENOMEM; | 
|  |  | 
|  | cx_dev->cx_id.part_num = part_num; | 
|  | cx_dev->cx_id.mfg_num = mfg_num; | 
|  | cx_dev->cx_id.nasid = nasid; | 
|  | cx_dev->hubdev = hubdev; | 
|  |  | 
|  | cx_dev->dev.parent = NULL; | 
|  | cx_dev->dev.bus = &tiocx_bus_type; | 
|  | cx_dev->dev.release = tiocx_bus_release; | 
|  | snprintf(cx_dev->dev.bus_id, BUS_ID_SIZE, "%d.0x%x", | 
|  | cx_dev->cx_id.nasid, cx_dev->cx_id.part_num); | 
|  | device_register(&cx_dev->dev); | 
|  | get_device(&cx_dev->dev); | 
|  |  | 
|  | device_create_file(&cx_dev->dev, &dev_attr_cxdev_control); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_device_unregister - Unregister a device. | 
|  | * @cx_dev: part/mfg id for the device | 
|  | */ | 
|  | int cx_device_unregister(struct cx_dev *cx_dev) | 
|  | { | 
|  | put_device(&cx_dev->dev); | 
|  | device_unregister(&cx_dev->dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * cx_device_reload - Reload the device. | 
|  | * @nasid: device's nasid | 
|  | * @part_num: device's part number | 
|  | * @mfg_num: device's manufacturer number | 
|  | * | 
|  | * Remove the device associated with 'nasid' from device list and then | 
|  | * call device-register with the given part/mfg numbers. | 
|  | */ | 
|  | static int cx_device_reload(struct cx_dev *cx_dev) | 
|  | { | 
|  | device_remove_file(&cx_dev->dev, &dev_attr_cxdev_control); | 
|  | cx_device_unregister(cx_dev); | 
|  | return cx_device_register(cx_dev->cx_id.nasid, cx_dev->cx_id.part_num, | 
|  | cx_dev->cx_id.mfg_num, cx_dev->hubdev); | 
|  | } | 
|  |  | 
|  | static inline uint64_t tiocx_intr_alloc(nasid_t nasid, int widget, | 
|  | u64 sn_irq_info, | 
|  | int req_irq, nasid_t req_nasid, | 
|  | int req_slice) | 
|  | { | 
|  | struct ia64_sal_retval rv; | 
|  | rv.status = 0; | 
|  | rv.v0 = 0; | 
|  |  | 
|  | ia64_sal_oemcall_nolock(&rv, SN_SAL_IOIF_INTERRUPT, | 
|  | SAL_INTR_ALLOC, nasid, | 
|  | widget, sn_irq_info, req_irq, | 
|  | req_nasid, req_slice); | 
|  | return rv.status; | 
|  | } | 
|  |  | 
|  | static inline void tiocx_intr_free(nasid_t nasid, int widget, | 
|  | struct sn_irq_info *sn_irq_info) | 
|  | { | 
|  | struct ia64_sal_retval rv; | 
|  | rv.status = 0; | 
|  | rv.v0 = 0; | 
|  |  | 
|  | ia64_sal_oemcall_nolock(&rv, SN_SAL_IOIF_INTERRUPT, | 
|  | SAL_INTR_FREE, nasid, | 
|  | widget, sn_irq_info->irq_irq, | 
|  | sn_irq_info->irq_cookie, 0, 0); | 
|  | } | 
|  |  | 
|  | struct sn_irq_info *tiocx_irq_alloc(nasid_t nasid, int widget, int irq, | 
|  | nasid_t req_nasid, int slice) | 
|  | { | 
|  | struct sn_irq_info *sn_irq_info; | 
|  | int status; | 
|  | int sn_irq_size = sizeof(struct sn_irq_info); | 
|  |  | 
|  | if ((nasid & 1) == 0) | 
|  | return NULL; | 
|  |  | 
|  | sn_irq_info = kmalloc(sn_irq_size, GFP_KERNEL); | 
|  | if (sn_irq_info == NULL) | 
|  | return NULL; | 
|  |  | 
|  | memset(sn_irq_info, 0x0, sn_irq_size); | 
|  |  | 
|  | status = tiocx_intr_alloc(nasid, widget, __pa(sn_irq_info), irq, | 
|  | req_nasid, slice); | 
|  | if (status) { | 
|  | kfree(sn_irq_info); | 
|  | return NULL; | 
|  | } else { | 
|  | return sn_irq_info; | 
|  | } | 
|  | } | 
|  |  | 
|  | void tiocx_irq_free(struct sn_irq_info *sn_irq_info) | 
|  | { | 
|  | uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge; | 
|  | nasid_t nasid = NASID_GET(bridge); | 
|  | int widget; | 
|  |  | 
|  | if (nasid & 1) { | 
|  | widget = TIO_SWIN_WIDGETNUM(bridge); | 
|  | tiocx_intr_free(nasid, widget, sn_irq_info); | 
|  | kfree(sn_irq_info); | 
|  | } | 
|  | } | 
|  |  | 
|  | uint64_t tiocx_dma_addr(uint64_t addr) | 
|  | { | 
|  | return PHYS_TO_TIODMA(addr); | 
|  | } | 
|  |  | 
|  | uint64_t tiocx_swin_base(int nasid) | 
|  | { | 
|  | return TIO_SWIN_BASE(nasid, TIOCX_CORELET); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(cx_driver_register); | 
|  | EXPORT_SYMBOL(cx_driver_unregister); | 
|  | EXPORT_SYMBOL(cx_device_register); | 
|  | EXPORT_SYMBOL(cx_device_unregister); | 
|  | EXPORT_SYMBOL(tiocx_irq_alloc); | 
|  | EXPORT_SYMBOL(tiocx_irq_free); | 
|  | EXPORT_SYMBOL(tiocx_bus_type); | 
|  | EXPORT_SYMBOL(tiocx_dma_addr); | 
|  | EXPORT_SYMBOL(tiocx_swin_base); | 
|  |  | 
|  | static void tio_conveyor_set(nasid_t nasid, int enable_flag) | 
|  | { | 
|  | uint64_t ice_frz; | 
|  | uint64_t disable_cb = (1ull << 61); | 
|  |  | 
|  | if (!(nasid & 1)) | 
|  | return; | 
|  |  | 
|  | ice_frz = REMOTE_HUB_L(nasid, TIO_ICE_FRZ_CFG); | 
|  | if (enable_flag) { | 
|  | if (!(ice_frz & disable_cb))	/* already enabled */ | 
|  | return; | 
|  | ice_frz &= ~disable_cb; | 
|  | } else { | 
|  | if (ice_frz & disable_cb)	/* already disabled */ | 
|  | return; | 
|  | ice_frz |= disable_cb; | 
|  | } | 
|  | DBG(KERN_ALERT "TIO_ICE_FRZ_CFG= 0x%lx\n", ice_frz); | 
|  | REMOTE_HUB_S(nasid, TIO_ICE_FRZ_CFG, ice_frz); | 
|  | } | 
|  |  | 
|  | #define tio_conveyor_enable(nasid) tio_conveyor_set(nasid, 1) | 
|  | #define tio_conveyor_disable(nasid) tio_conveyor_set(nasid, 0) | 
|  |  | 
|  | static void tio_corelet_reset(nasid_t nasid, int corelet) | 
|  | { | 
|  | if (!(nasid & 1)) | 
|  | return; | 
|  |  | 
|  | REMOTE_HUB_S(nasid, TIO_ICE_PMI_TX_CFG, 1 << corelet); | 
|  | udelay(2000); | 
|  | REMOTE_HUB_S(nasid, TIO_ICE_PMI_TX_CFG, 0); | 
|  | udelay(2000); | 
|  | } | 
|  |  | 
|  | static int tiocx_btchar_get(int nasid) | 
|  | { | 
|  | moduleid_t module_id; | 
|  | geoid_t geoid; | 
|  | int cnodeid; | 
|  |  | 
|  | cnodeid = nasid_to_cnodeid(nasid); | 
|  | geoid = cnodeid_get_geoid(cnodeid); | 
|  | module_id = geo_module(geoid); | 
|  | return MODULE_GET_BTCHAR(module_id); | 
|  | } | 
|  |  | 
|  | static int is_fpga_brick(int nasid) | 
|  | { | 
|  | switch (tiocx_btchar_get(nasid)) { | 
|  | case L1_BRICKTYPE_SA: | 
|  | case L1_BRICKTYPE_ATHENA: | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int bitstream_loaded(nasid_t nasid) | 
|  | { | 
|  | uint64_t cx_credits; | 
|  |  | 
|  | cx_credits = REMOTE_HUB_L(nasid, TIO_ICE_PMI_TX_DYN_CREDIT_STAT_CB3); | 
|  | cx_credits &= TIO_ICE_PMI_TX_DYN_CREDIT_STAT_CB3_CREDIT_CNT_MASK; | 
|  | DBG("cx_credits= 0x%lx\n", cx_credits); | 
|  |  | 
|  | return (cx_credits == 0xf) ? 1 : 0; | 
|  | } | 
|  |  | 
|  | static int tiocx_reload(struct cx_dev *cx_dev) | 
|  | { | 
|  | int part_num = CX_DEV_NONE; | 
|  | int mfg_num = CX_DEV_NONE; | 
|  | nasid_t nasid = cx_dev->cx_id.nasid; | 
|  |  | 
|  | if (bitstream_loaded(nasid)) { | 
|  | uint64_t cx_id; | 
|  |  | 
|  | cx_id = | 
|  | *(volatile int32_t *)(TIO_SWIN_BASE(nasid, TIOCX_CORELET) + | 
|  | WIDGET_ID); | 
|  | part_num = XWIDGET_PART_NUM(cx_id); | 
|  | mfg_num = XWIDGET_MFG_NUM(cx_id); | 
|  | DBG("part= 0x%x, mfg= 0x%x\n", part_num, mfg_num); | 
|  | /* just ignore it if it's a CE */ | 
|  | if (part_num == TIO_CE_ASIC_PARTNUM) | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | cx_dev->cx_id.part_num = part_num; | 
|  | cx_dev->cx_id.mfg_num = mfg_num; | 
|  |  | 
|  | /* | 
|  | * Delete old device and register the new one.  It's ok if | 
|  | * part_num/mfg_num == CX_DEV_NONE.  We want to register | 
|  | * devices in the table even if a bitstream isn't loaded. | 
|  | * That allows use to see that a bitstream isn't loaded via | 
|  | * TIOCX_IOCTL_DEV_LIST. | 
|  | */ | 
|  | return cx_device_reload(cx_dev); | 
|  | } | 
|  |  | 
|  | static ssize_t show_cxdev_control(struct device *dev, struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct cx_dev *cx_dev = to_cx_dev(dev); | 
|  |  | 
|  | return sprintf(buf, "0x%x 0x%x 0x%x %d\n", | 
|  | cx_dev->cx_id.nasid, | 
|  | cx_dev->cx_id.part_num, cx_dev->cx_id.mfg_num, | 
|  | tiocx_btchar_get(cx_dev->cx_id.nasid)); | 
|  | } | 
|  |  | 
|  | static ssize_t store_cxdev_control(struct device *dev, struct device_attribute *attr, const char *buf, | 
|  | size_t count) | 
|  | { | 
|  | int n; | 
|  | struct cx_dev *cx_dev = to_cx_dev(dev); | 
|  |  | 
|  | if (!capable(CAP_SYS_ADMIN)) | 
|  | return -EPERM; | 
|  |  | 
|  | if (count <= 0) | 
|  | return 0; | 
|  |  | 
|  | n = simple_strtoul(buf, NULL, 0); | 
|  |  | 
|  | switch (n) { | 
|  | case 1: | 
|  | tiocx_reload(cx_dev); | 
|  | break; | 
|  | case 3: | 
|  | tio_corelet_reset(cx_dev->cx_id.nasid, TIOCX_CORELET); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | DEVICE_ATTR(cxdev_control, 0644, show_cxdev_control, store_cxdev_control); | 
|  |  | 
|  | static int __init tiocx_init(void) | 
|  | { | 
|  | cnodeid_t cnodeid; | 
|  | int found_tiocx_device = 0; | 
|  |  | 
|  | bus_register(&tiocx_bus_type); | 
|  |  | 
|  | for (cnodeid = 0; cnodeid < MAX_COMPACT_NODES; cnodeid++) { | 
|  | nasid_t nasid; | 
|  |  | 
|  | if ((nasid = cnodeid_to_nasid(cnodeid)) < 0) | 
|  | break;	/* No more nasids .. bail out of loop */ | 
|  |  | 
|  | if ((nasid & 0x1) && is_fpga_brick(nasid)) { | 
|  | struct hubdev_info *hubdev; | 
|  | struct xwidget_info *widgetp; | 
|  |  | 
|  | DBG("Found TIO at nasid 0x%x\n", nasid); | 
|  |  | 
|  | hubdev = | 
|  | (struct hubdev_info *)(NODEPDA(cnodeid)->pdinfo); | 
|  |  | 
|  | widgetp = &hubdev->hdi_xwidget_info[TIOCX_CORELET]; | 
|  |  | 
|  | /* The CE hangs off of the CX port but is not an FPGA */ | 
|  | if (widgetp->xwi_hwid.part_num == TIO_CE_ASIC_PARTNUM) | 
|  | continue; | 
|  |  | 
|  | tio_corelet_reset(nasid, TIOCX_CORELET); | 
|  | tio_conveyor_enable(nasid); | 
|  |  | 
|  | if (cx_device_register | 
|  | (nasid, widgetp->xwi_hwid.part_num, | 
|  | widgetp->xwi_hwid.mfg_num, hubdev) < 0) | 
|  | return -ENXIO; | 
|  | else | 
|  | found_tiocx_device++; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* It's ok if we find zero devices. */ | 
|  | DBG("found_tiocx_device= %d\n", found_tiocx_device); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cx_remove_device(struct device * dev, void * data) | 
|  | { | 
|  | struct cx_dev *cx_dev = to_cx_dev(dev); | 
|  | device_remove_file(dev, &dev_attr_cxdev_control); | 
|  | cx_device_unregister(cx_dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit tiocx_exit(void) | 
|  | { | 
|  | DBG("tiocx_exit\n"); | 
|  |  | 
|  | /* | 
|  | * Unregister devices. | 
|  | */ | 
|  | bus_for_each_dev(&tiocx_bus_type, NULL, NULL, cx_remove_device); | 
|  | bus_unregister(&tiocx_bus_type); | 
|  | } | 
|  |  | 
|  | module_init(tiocx_init); | 
|  | module_exit(tiocx_exit); | 
|  |  | 
|  | /************************************************************************ | 
|  | * Module licensing and description | 
|  | ************************************************************************/ | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_AUTHOR("Bruce Losure <blosure@sgi.com>"); | 
|  | MODULE_DESCRIPTION("TIOCX module"); | 
|  | MODULE_SUPPORTED_DEVICE(DEVICE_NAME); |