1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 1999, 2000 Ralf Baechle (ralf@gnu.org) |
4 | * Copyright (C) 1999, 2000 Silcon Graphics, Inc. |
5 | * Copyright (C) 2004 Christoph Hellwig. |
6 | * |
7 | * Generic XTALK initialization code |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/smp.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/platform_data/sgi-w1.h> |
14 | #include <linux/platform_data/xtalk-bridge.h> |
15 | #include <asm/sn/addrs.h> |
16 | #include <asm/sn/types.h> |
17 | #include <asm/sn/klconfig.h> |
18 | #include <asm/pci/bridge.h> |
19 | #include <asm/xtalk/xtalk.h> |
20 | |
21 | |
22 | #define XBOW_WIDGET_PART_NUM 0x0 |
23 | #define XXBOW_WIDGET_PART_NUM 0xd000 /* Xbow in Xbridge */ |
24 | #define BASE_XBOW_PORT 8 /* Lowest external port */ |
25 | |
26 | static void bridge_platform_create(nasid_t nasid, int widget, int masterwid) |
27 | { |
28 | struct xtalk_bridge_platform_data *bd; |
29 | struct sgi_w1_platform_data *wd; |
30 | struct platform_device *pdev_wd; |
31 | struct platform_device *pdev_bd; |
32 | struct resource w1_res; |
33 | unsigned long offset; |
34 | |
35 | offset = NODE_OFFSET(nasid); |
36 | |
37 | wd = kzalloc(size: sizeof(*wd), GFP_KERNEL); |
38 | if (!wd) { |
39 | pr_warn("xtalk:n%d/%x bridge create out of memory\n" , nasid, widget); |
40 | return; |
41 | } |
42 | |
43 | snprintf(wd->dev_id, sizeof(wd->dev_id), "bridge-%012lx" , |
44 | offset + (widget << SWIN_SIZE_BITS)); |
45 | |
46 | memset(&w1_res, 0, sizeof(w1_res)); |
47 | w1_res.start = offset + (widget << SWIN_SIZE_BITS) + |
48 | offsetof(struct bridge_regs, b_nic); |
49 | w1_res.end = w1_res.start + 3; |
50 | w1_res.flags = IORESOURCE_MEM; |
51 | |
52 | pdev_wd = platform_device_alloc(name: "sgi_w1" , PLATFORM_DEVID_AUTO); |
53 | if (!pdev_wd) { |
54 | pr_warn("xtalk:n%d/%x bridge create out of memory\n" , nasid, widget); |
55 | goto err_kfree_wd; |
56 | } |
57 | if (platform_device_add_resources(pdev: pdev_wd, res: &w1_res, num: 1)) { |
58 | pr_warn("xtalk:n%d/%x bridge failed to add platform resources.\n" , nasid, widget); |
59 | goto err_put_pdev_wd; |
60 | } |
61 | if (platform_device_add_data(pdev: pdev_wd, data: wd, size: sizeof(*wd))) { |
62 | pr_warn("xtalk:n%d/%x bridge failed to add platform data.\n" , nasid, widget); |
63 | goto err_put_pdev_wd; |
64 | } |
65 | if (platform_device_add(pdev: pdev_wd)) { |
66 | pr_warn("xtalk:n%d/%x bridge failed to add platform device.\n" , nasid, widget); |
67 | goto err_put_pdev_wd; |
68 | } |
69 | /* platform_device_add_data() duplicates the data */ |
70 | kfree(objp: wd); |
71 | |
72 | bd = kzalloc(size: sizeof(*bd), GFP_KERNEL); |
73 | if (!bd) { |
74 | pr_warn("xtalk:n%d/%x bridge create out of memory\n" , nasid, widget); |
75 | goto err_unregister_pdev_wd; |
76 | } |
77 | pdev_bd = platform_device_alloc(name: "xtalk-bridge" , PLATFORM_DEVID_AUTO); |
78 | if (!pdev_bd) { |
79 | pr_warn("xtalk:n%d/%x bridge create out of memory\n" , nasid, widget); |
80 | goto err_kfree_bd; |
81 | } |
82 | |
83 | |
84 | bd->bridge_addr = RAW_NODE_SWIN_BASE(nasid, widget); |
85 | bd->intr_addr = BIT_ULL(47) + 0x01800000 + PI_INT_PEND_MOD; |
86 | bd->nasid = nasid; |
87 | bd->masterwid = masterwid; |
88 | |
89 | bd->mem.name = "Bridge PCI MEM" ; |
90 | bd->mem.start = offset + (widget << SWIN_SIZE_BITS) + BRIDGE_DEVIO0; |
91 | bd->mem.end = offset + (widget << SWIN_SIZE_BITS) + SWIN_SIZE - 1; |
92 | bd->mem.flags = IORESOURCE_MEM; |
93 | bd->mem_offset = offset; |
94 | |
95 | bd->io.name = "Bridge PCI IO" ; |
96 | bd->io.start = offset + (widget << SWIN_SIZE_BITS) + BRIDGE_DEVIO0; |
97 | bd->io.end = offset + (widget << SWIN_SIZE_BITS) + SWIN_SIZE - 1; |
98 | bd->io.flags = IORESOURCE_IO; |
99 | bd->io_offset = offset; |
100 | |
101 | if (platform_device_add_data(pdev: pdev_bd, data: bd, size: sizeof(*bd))) { |
102 | pr_warn("xtalk:n%d/%x bridge failed to add platform data.\n" , nasid, widget); |
103 | goto err_put_pdev_bd; |
104 | } |
105 | if (platform_device_add(pdev: pdev_bd)) { |
106 | pr_warn("xtalk:n%d/%x bridge failed to add platform device.\n" , nasid, widget); |
107 | goto err_put_pdev_bd; |
108 | } |
109 | /* platform_device_add_data() duplicates the data */ |
110 | kfree(objp: bd); |
111 | pr_info("xtalk:n%d/%x bridge widget\n" , nasid, widget); |
112 | return; |
113 | |
114 | err_put_pdev_bd: |
115 | platform_device_put(pdev: pdev_bd); |
116 | err_kfree_bd: |
117 | kfree(objp: bd); |
118 | err_unregister_pdev_wd: |
119 | platform_device_unregister(pdev_wd); |
120 | return; |
121 | err_put_pdev_wd: |
122 | platform_device_put(pdev: pdev_wd); |
123 | err_kfree_wd: |
124 | kfree(objp: wd); |
125 | return; |
126 | } |
127 | |
128 | static int probe_one_port(nasid_t nasid, int widget, int masterwid) |
129 | { |
130 | widgetreg_t widget_id; |
131 | xwidget_part_num_t partnum; |
132 | |
133 | widget_id = *(volatile widgetreg_t *) |
134 | (RAW_NODE_SWIN_BASE(nasid, widget) + WIDGET_ID); |
135 | partnum = XWIDGET_PART_NUM(widget_id); |
136 | |
137 | switch (partnum) { |
138 | case BRIDGE_WIDGET_PART_NUM: |
139 | case XBRIDGE_WIDGET_PART_NUM: |
140 | bridge_platform_create(nasid, widget, masterwid); |
141 | break; |
142 | default: |
143 | pr_info("xtalk:n%d/%d unknown widget (0x%x)\n" , |
144 | nasid, widget, partnum); |
145 | break; |
146 | } |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static int xbow_probe(nasid_t nasid) |
152 | { |
153 | lboard_t *brd; |
154 | klxbow_t *xbow_p; |
155 | unsigned masterwid, i; |
156 | |
157 | /* |
158 | * found xbow, so may have multiple bridges |
159 | * need to probe xbow |
160 | */ |
161 | brd = find_lboard((lboard_t *)KL_CONFIG_INFO(nasid), KLTYPE_MIDPLANE8); |
162 | if (!brd) |
163 | return -ENODEV; |
164 | |
165 | xbow_p = (klxbow_t *)find_component(brd, NULL, KLSTRUCT_XBOW); |
166 | if (!xbow_p) |
167 | return -ENODEV; |
168 | |
169 | /* |
170 | * Okay, here's a xbow. Let's arbitrate and find |
171 | * out if we should initialize it. Set enabled |
172 | * hub connected at highest or lowest widget as |
173 | * master. |
174 | */ |
175 | #ifdef WIDGET_A |
176 | i = HUB_WIDGET_ID_MAX + 1; |
177 | do { |
178 | i--; |
179 | } while ((!XBOW_PORT_TYPE_HUB(xbow_p, i)) || |
180 | (!XBOW_PORT_IS_ENABLED(xbow_p, i))); |
181 | #else |
182 | i = HUB_WIDGET_ID_MIN - 1; |
183 | do { |
184 | i++; |
185 | } while ((!XBOW_PORT_TYPE_HUB(xbow_p, i)) || |
186 | (!XBOW_PORT_IS_ENABLED(xbow_p, i))); |
187 | #endif |
188 | |
189 | masterwid = i; |
190 | if (nasid != XBOW_PORT_NASID(xbow_p, i)) |
191 | return 1; |
192 | |
193 | for (i = HUB_WIDGET_ID_MIN; i <= HUB_WIDGET_ID_MAX; i++) { |
194 | if (XBOW_PORT_IS_ENABLED(xbow_p, i) && |
195 | XBOW_PORT_TYPE_IO(xbow_p, i)) |
196 | probe_one_port(nasid, i, masterwid); |
197 | } |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | static void xtalk_probe_node(nasid_t nasid) |
203 | { |
204 | volatile u64 hubreg; |
205 | xwidget_part_num_t partnum; |
206 | widgetreg_t widget_id; |
207 | |
208 | hubreg = REMOTE_HUB_L(nasid, IIO_LLP_CSR); |
209 | |
210 | /* check whether the link is up */ |
211 | if (!(hubreg & IIO_LLP_CSR_IS_UP)) |
212 | return; |
213 | |
214 | widget_id = *(volatile widgetreg_t *) |
215 | (RAW_NODE_SWIN_BASE(nasid, 0x0) + WIDGET_ID); |
216 | partnum = XWIDGET_PART_NUM(widget_id); |
217 | |
218 | switch (partnum) { |
219 | case BRIDGE_WIDGET_PART_NUM: |
220 | bridge_platform_create(nasid, 0x8, 0xa); |
221 | break; |
222 | case XBOW_WIDGET_PART_NUM: |
223 | case XXBOW_WIDGET_PART_NUM: |
224 | pr_info("xtalk:n%d/0 xbow widget\n" , nasid); |
225 | xbow_probe(nasid); |
226 | break; |
227 | default: |
228 | pr_info("xtalk:n%d/0 unknown widget (0x%x)\n" , nasid, partnum); |
229 | break; |
230 | } |
231 | } |
232 | |
233 | static int __init xtalk_init(void) |
234 | { |
235 | nasid_t nasid; |
236 | |
237 | for_each_online_node(nasid) |
238 | xtalk_probe_node(nasid); |
239 | |
240 | return 0; |
241 | } |
242 | arch_initcall(xtalk_init); |
243 | |