162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ip30-xtalk.c - Very basic Crosstalk (XIO) detection support.
462306a36Sopenharmony_ci *   Copyright (C) 2004-2007 Stanislaw Skowronek <skylark@unaligned.org>
562306a36Sopenharmony_ci *   Copyright (C) 2009 Johannes Dickgreber <tanzy@gmx.de>
662306a36Sopenharmony_ci *   Copyright (C) 2007, 2014-2016 Joshua Kinard <kumba@gentoo.org>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/init.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci#include <linux/platform_data/sgi-w1.h>
1362306a36Sopenharmony_ci#include <linux/platform_data/xtalk-bridge.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <asm/xtalk/xwidget.h>
1662306a36Sopenharmony_ci#include <asm/pci/bridge.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define IP30_SWIN_BASE(widget) \
1962306a36Sopenharmony_ci		(0x0000000010000000 | (((unsigned long)(widget)) << 24))
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define IP30_RAW_SWIN_BASE(widget)	(IO_BASE + IP30_SWIN_BASE(widget))
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define IP30_SWIN_SIZE		(1 << 24)
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define IP30_WIDGET_XBOW        _AC(0x0, UL)    /* XBow is always 0 */
2662306a36Sopenharmony_ci#define IP30_WIDGET_HEART       _AC(0x8, UL)    /* HEART is always 8 */
2762306a36Sopenharmony_ci#define IP30_WIDGET_PCI_BASE    _AC(0xf, UL)    /* BaseIO PCI is always 15 */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define XTALK_NODEV             0xffffffff
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define XBOW_REG_LINK_STAT_0    0x114
3262306a36Sopenharmony_ci#define XBOW_REG_LINK_BLK_SIZE  0x40
3362306a36Sopenharmony_ci#define XBOW_REG_LINK_ALIVE     0x80000000
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define HEART_INTR_ADDR		0x00000080
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define xtalk_read	__raw_readl
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic void bridge_platform_create(int widget, int masterwid)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct xtalk_bridge_platform_data *bd;
4262306a36Sopenharmony_ci	struct sgi_w1_platform_data *wd;
4362306a36Sopenharmony_ci	struct platform_device *pdev_wd;
4462306a36Sopenharmony_ci	struct platform_device *pdev_bd;
4562306a36Sopenharmony_ci	struct resource w1_res;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	wd = kzalloc(sizeof(*wd), GFP_KERNEL);
4862306a36Sopenharmony_ci	if (!wd) {
4962306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge create out of memory\n", widget);
5062306a36Sopenharmony_ci		return;
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	snprintf(wd->dev_id, sizeof(wd->dev_id), "bridge-%012lx",
5462306a36Sopenharmony_ci		 IP30_SWIN_BASE(widget));
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	memset(&w1_res, 0, sizeof(w1_res));
5762306a36Sopenharmony_ci	w1_res.start = IP30_SWIN_BASE(widget) +
5862306a36Sopenharmony_ci				offsetof(struct bridge_regs, b_nic);
5962306a36Sopenharmony_ci	w1_res.end = w1_res.start + 3;
6062306a36Sopenharmony_ci	w1_res.flags = IORESOURCE_MEM;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	pdev_wd = platform_device_alloc("sgi_w1", PLATFORM_DEVID_AUTO);
6362306a36Sopenharmony_ci	if (!pdev_wd) {
6462306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge create out of memory\n", widget);
6562306a36Sopenharmony_ci		goto err_kfree_wd;
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci	if (platform_device_add_resources(pdev_wd, &w1_res, 1)) {
6862306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge failed to add platform resources.\n", widget);
6962306a36Sopenharmony_ci		goto err_put_pdev_wd;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci	if (platform_device_add_data(pdev_wd, wd, sizeof(*wd))) {
7262306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge failed to add platform data.\n", widget);
7362306a36Sopenharmony_ci		goto err_put_pdev_wd;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci	if (platform_device_add(pdev_wd)) {
7662306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge failed to add platform device.\n", widget);
7762306a36Sopenharmony_ci		goto err_put_pdev_wd;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci	/* platform_device_add_data() duplicates the data */
8062306a36Sopenharmony_ci	kfree(wd);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	bd = kzalloc(sizeof(*bd), GFP_KERNEL);
8362306a36Sopenharmony_ci	if (!bd) {
8462306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge create out of memory\n", widget);
8562306a36Sopenharmony_ci		goto err_unregister_pdev_wd;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci	pdev_bd = platform_device_alloc("xtalk-bridge", PLATFORM_DEVID_AUTO);
8862306a36Sopenharmony_ci	if (!pdev_bd) {
8962306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge create out of memory\n", widget);
9062306a36Sopenharmony_ci		goto err_kfree_bd;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	bd->bridge_addr	= IP30_RAW_SWIN_BASE(widget);
9462306a36Sopenharmony_ci	bd->intr_addr	= HEART_INTR_ADDR;
9562306a36Sopenharmony_ci	bd->nasid	= 0;
9662306a36Sopenharmony_ci	bd->masterwid	= masterwid;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	bd->mem.name	= "Bridge PCI MEM";
9962306a36Sopenharmony_ci	bd->mem.start	= IP30_SWIN_BASE(widget) + BRIDGE_DEVIO0;
10062306a36Sopenharmony_ci	bd->mem.end	= IP30_SWIN_BASE(widget) + IP30_SWIN_SIZE - 1;
10162306a36Sopenharmony_ci	bd->mem.flags	= IORESOURCE_MEM;
10262306a36Sopenharmony_ci	bd->mem_offset	= IP30_SWIN_BASE(widget);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	bd->io.name	= "Bridge PCI IO";
10562306a36Sopenharmony_ci	bd->io.start	= IP30_SWIN_BASE(widget) + BRIDGE_DEVIO0;
10662306a36Sopenharmony_ci	bd->io.end	= IP30_SWIN_BASE(widget) + IP30_SWIN_SIZE - 1;
10762306a36Sopenharmony_ci	bd->io.flags	= IORESOURCE_IO;
10862306a36Sopenharmony_ci	bd->io_offset	= IP30_SWIN_BASE(widget);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (platform_device_add_data(pdev_bd, bd, sizeof(*bd))) {
11162306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge failed to add platform data.\n", widget);
11262306a36Sopenharmony_ci		goto err_put_pdev_bd;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci	if (platform_device_add(pdev_bd)) {
11562306a36Sopenharmony_ci		pr_warn("xtalk:%x bridge failed to add platform device.\n", widget);
11662306a36Sopenharmony_ci		goto err_put_pdev_bd;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci	/* platform_device_add_data() duplicates the data */
11962306a36Sopenharmony_ci	kfree(bd);
12062306a36Sopenharmony_ci	pr_info("xtalk:%x bridge widget\n", widget);
12162306a36Sopenharmony_ci	return;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cierr_put_pdev_bd:
12462306a36Sopenharmony_ci	platform_device_put(pdev_bd);
12562306a36Sopenharmony_cierr_kfree_bd:
12662306a36Sopenharmony_ci	kfree(bd);
12762306a36Sopenharmony_cierr_unregister_pdev_wd:
12862306a36Sopenharmony_ci	platform_device_unregister(pdev_wd);
12962306a36Sopenharmony_ci	return;
13062306a36Sopenharmony_cierr_put_pdev_wd:
13162306a36Sopenharmony_ci	platform_device_put(pdev_wd);
13262306a36Sopenharmony_cierr_kfree_wd:
13362306a36Sopenharmony_ci	kfree(wd);
13462306a36Sopenharmony_ci	return;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic unsigned int __init xbow_widget_active(s8 wid)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	unsigned int link_stat;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	link_stat = xtalk_read((void *)(IP30_RAW_SWIN_BASE(IP30_WIDGET_XBOW) +
14262306a36Sopenharmony_ci					XBOW_REG_LINK_STAT_0 +
14362306a36Sopenharmony_ci					XBOW_REG_LINK_BLK_SIZE *
14462306a36Sopenharmony_ci					(wid - 8)));
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	return (link_stat & XBOW_REG_LINK_ALIVE) ? 1 : 0;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic void __init xtalk_init_widget(s8 wid, s8 masterwid)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	xwidget_part_num_t partnum;
15262306a36Sopenharmony_ci	widgetreg_t widget_id;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (!xbow_widget_active(wid))
15562306a36Sopenharmony_ci		return;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	widget_id = xtalk_read((void *)(IP30_RAW_SWIN_BASE(wid) + WIDGET_ID));
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	partnum = XWIDGET_PART_NUM(widget_id);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	switch (partnum) {
16262306a36Sopenharmony_ci	case BRIDGE_WIDGET_PART_NUM:
16362306a36Sopenharmony_ci	case XBRIDGE_WIDGET_PART_NUM:
16462306a36Sopenharmony_ci		bridge_platform_create(wid, masterwid);
16562306a36Sopenharmony_ci		break;
16662306a36Sopenharmony_ci	default:
16762306a36Sopenharmony_ci		pr_info("xtalk:%x unknown widget (0x%x)\n", wid, partnum);
16862306a36Sopenharmony_ci		break;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic int __init ip30_xtalk_init(void)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	int i;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	/*
17762306a36Sopenharmony_ci	 * Walk widget IDs backwards so that BaseIO is probed first.  This
17862306a36Sopenharmony_ci	 * ensures that the BaseIO IOC3 is always detected as eth0.
17962306a36Sopenharmony_ci	 */
18062306a36Sopenharmony_ci	for (i = IP30_WIDGET_PCI_BASE; i > IP30_WIDGET_HEART; i--)
18162306a36Sopenharmony_ci		xtalk_init_widget(i, IP30_WIDGET_HEART);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	return 0;
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ciarch_initcall(ip30_xtalk_init);
187