root/arch/mips/sgi-ip27/ip27-xtalk.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 1999, 2000 Ralf Baechle (ralf@gnu.org)
 * Copyright (C) 1999, 2000 Silcon Graphics, Inc.
 * Copyright (C) 2004 Christoph Hellwig.
 *
 * Generic XTALK initialization code
 */

#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/platform_device.h>
#include <linux/platform_data/sgi-w1.h>
#include <linux/platform_data/xtalk-bridge.h>
#include <asm/sn/addrs.h>
#include <asm/sn/types.h>
#include <asm/sn/klconfig.h>
#include <asm/pci/bridge.h>
#include <asm/xtalk/xtalk.h>


#define XBOW_WIDGET_PART_NUM    0x0
#define XXBOW_WIDGET_PART_NUM   0xd000  /* Xbow in Xbridge */
#define BASE_XBOW_PORT          8     /* Lowest external port */

static void bridge_platform_create(nasid_t nasid, int widget, int masterwid)
{
        struct xtalk_bridge_platform_data *bd;
        struct sgi_w1_platform_data *wd;
        struct platform_device *pdev_wd;
        struct platform_device *pdev_bd;
        struct resource w1_res;
        unsigned long offset;

        offset = NODE_OFFSET(nasid);

        wd = kzalloc_obj(*wd);
        if (!wd) {
                pr_warn("xtalk:n%d/%x bridge create out of memory\n", nasid, widget);
                return;
        }

        snprintf(wd->dev_id, sizeof(wd->dev_id), "bridge-%012lx",
                 offset + (widget << SWIN_SIZE_BITS));

        memset(&w1_res, 0, sizeof(w1_res));
        w1_res.start = offset + (widget << SWIN_SIZE_BITS) +
                                offsetof(struct bridge_regs, b_nic);
        w1_res.end = w1_res.start + 3;
        w1_res.flags = IORESOURCE_MEM;

        pdev_wd = platform_device_alloc("sgi_w1", PLATFORM_DEVID_AUTO);
        if (!pdev_wd) {
                pr_warn("xtalk:n%d/%x bridge create out of memory\n", nasid, widget);
                goto err_kfree_wd;
        }
        if (platform_device_add_resources(pdev_wd, &w1_res, 1)) {
                pr_warn("xtalk:n%d/%x bridge failed to add platform resources.\n", nasid, widget);
                goto err_put_pdev_wd;
        }
        if (platform_device_add_data(pdev_wd, wd, sizeof(*wd))) {
                pr_warn("xtalk:n%d/%x bridge failed to add platform data.\n", nasid, widget);
                goto err_put_pdev_wd;
        }
        if (platform_device_add(pdev_wd)) {
                pr_warn("xtalk:n%d/%x bridge failed to add platform device.\n", nasid, widget);
                goto err_put_pdev_wd;
        }
        /* platform_device_add_data() duplicates the data */
        kfree(wd);

        bd = kzalloc_obj(*bd);
        if (!bd) {
                pr_warn("xtalk:n%d/%x bridge create out of memory\n", nasid, widget);
                goto err_unregister_pdev_wd;
        }
        pdev_bd = platform_device_alloc("xtalk-bridge", PLATFORM_DEVID_AUTO);
        if (!pdev_bd) {
                pr_warn("xtalk:n%d/%x bridge create out of memory\n", nasid, widget);
                goto err_kfree_bd;
        }


        bd->bridge_addr = RAW_NODE_SWIN_BASE(nasid, widget);
        bd->intr_addr   = BIT_ULL(47) + 0x01800000 + PI_INT_PEND_MOD;
        bd->nasid       = nasid;
        bd->masterwid   = masterwid;

        bd->mem.name    = "Bridge PCI MEM";
        bd->mem.start   = offset + (widget << SWIN_SIZE_BITS) + BRIDGE_DEVIO0;
        bd->mem.end     = offset + (widget << SWIN_SIZE_BITS) + SWIN_SIZE - 1;
        bd->mem.flags   = IORESOURCE_MEM;
        bd->mem_offset  = offset;

        bd->io.name     = "Bridge PCI IO";
        bd->io.start    = offset + (widget << SWIN_SIZE_BITS) + BRIDGE_DEVIO0;
        bd->io.end      = offset + (widget << SWIN_SIZE_BITS) + SWIN_SIZE - 1;
        bd->io.flags    = IORESOURCE_IO;
        bd->io_offset   = offset;

        if (platform_device_add_data(pdev_bd, bd, sizeof(*bd))) {
                pr_warn("xtalk:n%d/%x bridge failed to add platform data.\n", nasid, widget);
                goto err_put_pdev_bd;
        }
        if (platform_device_add(pdev_bd)) {
                pr_warn("xtalk:n%d/%x bridge failed to add platform device.\n", nasid, widget);
                goto err_put_pdev_bd;
        }
        /* platform_device_add_data() duplicates the data */
        kfree(bd);
        pr_info("xtalk:n%d/%x bridge widget\n", nasid, widget);
        return;

err_put_pdev_bd:
        platform_device_put(pdev_bd);
err_kfree_bd:
        kfree(bd);
err_unregister_pdev_wd:
        platform_device_unregister(pdev_wd);
        return;
err_put_pdev_wd:
        platform_device_put(pdev_wd);
err_kfree_wd:
        kfree(wd);
        return;
}

static int probe_one_port(nasid_t nasid, int widget, int masterwid)
{
        widgetreg_t             widget_id;
        xwidget_part_num_t      partnum;

        widget_id = *(volatile widgetreg_t *)
                (RAW_NODE_SWIN_BASE(nasid, widget) + WIDGET_ID);
        partnum = XWIDGET_PART_NUM(widget_id);

        switch (partnum) {
        case BRIDGE_WIDGET_PART_NUM:
        case XBRIDGE_WIDGET_PART_NUM:
                bridge_platform_create(nasid, widget, masterwid);
                break;
        default:
                pr_info("xtalk:n%d/%d unknown widget (0x%x)\n",
                        nasid, widget, partnum);
                break;
        }

        return 0;
}

static int xbow_probe(nasid_t nasid)
{
        lboard_t *brd;
        klxbow_t *xbow_p;
        unsigned masterwid, i;

        /*
         * found xbow, so may have multiple bridges
         * need to probe xbow
         */
        brd = find_lboard((lboard_t *)KL_CONFIG_INFO(nasid), KLTYPE_MIDPLANE8);
        if (!brd)
                return -ENODEV;

        xbow_p = (klxbow_t *)find_component(brd, NULL, KLSTRUCT_XBOW);
        if (!xbow_p)
                return -ENODEV;

        /*
         * Okay, here's a xbow. Let's arbitrate and find
         * out if we should initialize it. Set enabled
         * hub connected at highest or lowest widget as
         * master.
         */
#ifdef WIDGET_A
        i = HUB_WIDGET_ID_MAX + 1;
        do {
                i--;
        } while ((!XBOW_PORT_TYPE_HUB(xbow_p, i)) ||
                 (!XBOW_PORT_IS_ENABLED(xbow_p, i)));
#else
        i = HUB_WIDGET_ID_MIN - 1;
        do {
                i++;
        } while ((!XBOW_PORT_TYPE_HUB(xbow_p, i)) ||
                 (!XBOW_PORT_IS_ENABLED(xbow_p, i)));
#endif

        masterwid = i;
        if (nasid != XBOW_PORT_NASID(xbow_p, i))
                return 1;

        for (i = HUB_WIDGET_ID_MIN; i <= HUB_WIDGET_ID_MAX; i++) {
                if (XBOW_PORT_IS_ENABLED(xbow_p, i) &&
                    XBOW_PORT_TYPE_IO(xbow_p, i))
                        probe_one_port(nasid, i, masterwid);
        }

        return 0;
}

static void xtalk_probe_node(nasid_t nasid)
{
        volatile u64            hubreg;
        xwidget_part_num_t      partnum;
        widgetreg_t             widget_id;

        hubreg = REMOTE_HUB_L(nasid, IIO_LLP_CSR);

        /* check whether the link is up */
        if (!(hubreg & IIO_LLP_CSR_IS_UP))
                return;

        widget_id = *(volatile widgetreg_t *)
                       (RAW_NODE_SWIN_BASE(nasid, 0x0) + WIDGET_ID);
        partnum = XWIDGET_PART_NUM(widget_id);

        switch (partnum) {
        case BRIDGE_WIDGET_PART_NUM:
                bridge_platform_create(nasid, 0x8, 0xa);
                break;
        case XBOW_WIDGET_PART_NUM:
        case XXBOW_WIDGET_PART_NUM:
                pr_info("xtalk:n%d/0 xbow widget\n", nasid);
                xbow_probe(nasid);
                break;
        default:
                pr_info("xtalk:n%d/0 unknown widget (0x%x)\n", nasid, partnum);
                break;
        }
}

static int __init xtalk_init(void)
{
        nasid_t nasid;

        for_each_online_node(nasid)
                xtalk_probe_node(nasid);

        return 0;
}
arch_initcall(xtalk_init);