1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* central.c: Central FHC driver for Sunfire/Starfire/Wildfire. |
3 | * |
4 | * Copyright (C) 1997, 1999, 2008 David S. Miller (davem@davemloft.net) |
5 | */ |
6 | |
7 | #include <linux/kernel.h> |
8 | #include <linux/types.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/export.h> |
11 | #include <linux/string.h> |
12 | #include <linux/init.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #include <asm/fhc.h> |
17 | #include <asm/upa.h> |
18 | |
19 | struct clock_board { |
20 | void __iomem *clock_freq_regs; |
21 | void __iomem *clock_regs; |
22 | void __iomem *clock_ver_reg; |
23 | int num_slots; |
24 | struct resource leds_resource; |
25 | struct platform_device leds_pdev; |
26 | }; |
27 | |
28 | struct fhc { |
29 | void __iomem *pregs; |
30 | bool central; |
31 | bool jtag_master; |
32 | int board_num; |
33 | struct resource leds_resource; |
34 | struct platform_device leds_pdev; |
35 | }; |
36 | |
37 | static int clock_board_calc_nslots(struct clock_board *p) |
38 | { |
39 | u8 reg = upa_readb(p->clock_regs + CLOCK_STAT1) & 0xc0; |
40 | |
41 | switch (reg) { |
42 | case 0x40: |
43 | return 16; |
44 | |
45 | case 0xc0: |
46 | return 8; |
47 | |
48 | case 0x80: |
49 | reg = 0; |
50 | if (p->clock_ver_reg) |
51 | reg = upa_readb(p->clock_ver_reg); |
52 | if (reg) { |
53 | if (reg & 0x80) |
54 | return 4; |
55 | else |
56 | return 5; |
57 | } |
58 | fallthrough; |
59 | default: |
60 | return 4; |
61 | } |
62 | } |
63 | |
64 | static int clock_board_probe(struct platform_device *op) |
65 | { |
66 | struct clock_board *p = kzalloc(size: sizeof(*p), GFP_KERNEL); |
67 | int err = -ENOMEM; |
68 | |
69 | if (!p) { |
70 | printk(KERN_ERR "clock_board: Cannot allocate struct clock_board\n" ); |
71 | goto out; |
72 | } |
73 | |
74 | p->clock_freq_regs = of_ioremap(&op->resource[0], 0, |
75 | resource_size(res: &op->resource[0]), |
76 | "clock_board_freq" ); |
77 | if (!p->clock_freq_regs) { |
78 | printk(KERN_ERR "clock_board: Cannot map clock_freq_regs\n" ); |
79 | goto out_free; |
80 | } |
81 | |
82 | p->clock_regs = of_ioremap(&op->resource[1], 0, |
83 | resource_size(res: &op->resource[1]), |
84 | "clock_board_regs" ); |
85 | if (!p->clock_regs) { |
86 | printk(KERN_ERR "clock_board: Cannot map clock_regs\n" ); |
87 | goto out_unmap_clock_freq_regs; |
88 | } |
89 | |
90 | if (op->resource[2].flags) { |
91 | p->clock_ver_reg = of_ioremap(&op->resource[2], 0, |
92 | resource_size(res: &op->resource[2]), |
93 | "clock_ver_reg" ); |
94 | if (!p->clock_ver_reg) { |
95 | printk(KERN_ERR "clock_board: Cannot map clock_ver_reg\n" ); |
96 | goto out_unmap_clock_regs; |
97 | } |
98 | } |
99 | |
100 | p->num_slots = clock_board_calc_nslots(p); |
101 | |
102 | p->leds_resource.start = (unsigned long) |
103 | (p->clock_regs + CLOCK_CTRL); |
104 | p->leds_resource.end = p->leds_resource.start; |
105 | p->leds_resource.name = "leds" ; |
106 | |
107 | p->leds_pdev.name = "sunfire-clockboard-leds" ; |
108 | p->leds_pdev.id = -1; |
109 | p->leds_pdev.resource = &p->leds_resource; |
110 | p->leds_pdev.num_resources = 1; |
111 | p->leds_pdev.dev.parent = &op->dev; |
112 | |
113 | err = platform_device_register(&p->leds_pdev); |
114 | if (err) { |
115 | printk(KERN_ERR "clock_board: Could not register LEDS " |
116 | "platform device\n" ); |
117 | goto out_unmap_clock_ver_reg; |
118 | } |
119 | |
120 | printk(KERN_INFO "clock_board: Detected %d slot Enterprise system.\n" , |
121 | p->num_slots); |
122 | |
123 | err = 0; |
124 | out: |
125 | return err; |
126 | |
127 | out_unmap_clock_ver_reg: |
128 | if (p->clock_ver_reg) |
129 | of_iounmap(&op->resource[2], p->clock_ver_reg, |
130 | resource_size(res: &op->resource[2])); |
131 | |
132 | out_unmap_clock_regs: |
133 | of_iounmap(&op->resource[1], p->clock_regs, |
134 | resource_size(res: &op->resource[1])); |
135 | |
136 | out_unmap_clock_freq_regs: |
137 | of_iounmap(&op->resource[0], p->clock_freq_regs, |
138 | resource_size(res: &op->resource[0])); |
139 | |
140 | out_free: |
141 | kfree(objp: p); |
142 | goto out; |
143 | } |
144 | |
145 | static const struct of_device_id clock_board_match[] = { |
146 | { |
147 | .name = "clock-board" , |
148 | }, |
149 | {}, |
150 | }; |
151 | |
152 | static struct platform_driver clock_board_driver = { |
153 | .probe = clock_board_probe, |
154 | .driver = { |
155 | .name = "clock_board" , |
156 | .of_match_table = clock_board_match, |
157 | }, |
158 | }; |
159 | |
160 | static int fhc_probe(struct platform_device *op) |
161 | { |
162 | struct fhc *p = kzalloc(size: sizeof(*p), GFP_KERNEL); |
163 | int err = -ENOMEM; |
164 | u32 reg; |
165 | |
166 | if (!p) { |
167 | printk(KERN_ERR "fhc: Cannot allocate struct fhc\n" ); |
168 | goto out; |
169 | } |
170 | |
171 | if (of_node_name_eq(np: op->dev.of_node->parent, name: "central" )) |
172 | p->central = true; |
173 | |
174 | p->pregs = of_ioremap(&op->resource[0], 0, |
175 | resource_size(res: &op->resource[0]), |
176 | "fhc_pregs" ); |
177 | if (!p->pregs) { |
178 | printk(KERN_ERR "fhc: Cannot map pregs\n" ); |
179 | goto out_free; |
180 | } |
181 | |
182 | if (p->central) { |
183 | reg = upa_readl(p->pregs + FHC_PREGS_BSR); |
184 | p->board_num = ((reg >> 16) & 1) | ((reg >> 12) & 0x0e); |
185 | } else { |
186 | p->board_num = of_getintprop_default(op->dev.of_node, "board#" , -1); |
187 | if (p->board_num == -1) { |
188 | printk(KERN_ERR "fhc: No board# property\n" ); |
189 | goto out_unmap_pregs; |
190 | } |
191 | if (upa_readl(p->pregs + FHC_PREGS_JCTRL) & FHC_JTAG_CTRL_MENAB) |
192 | p->jtag_master = true; |
193 | } |
194 | |
195 | if (!p->central) { |
196 | p->leds_resource.start = (unsigned long) |
197 | (p->pregs + FHC_PREGS_CTRL); |
198 | p->leds_resource.end = p->leds_resource.start; |
199 | p->leds_resource.name = "leds" ; |
200 | |
201 | p->leds_pdev.name = "sunfire-fhc-leds" ; |
202 | p->leds_pdev.id = p->board_num; |
203 | p->leds_pdev.resource = &p->leds_resource; |
204 | p->leds_pdev.num_resources = 1; |
205 | p->leds_pdev.dev.parent = &op->dev; |
206 | |
207 | err = platform_device_register(&p->leds_pdev); |
208 | if (err) { |
209 | printk(KERN_ERR "fhc: Could not register LEDS " |
210 | "platform device\n" ); |
211 | goto out_unmap_pregs; |
212 | } |
213 | } |
214 | reg = upa_readl(p->pregs + FHC_PREGS_CTRL); |
215 | |
216 | if (!p->central) |
217 | reg |= FHC_CONTROL_IXIST; |
218 | |
219 | reg &= ~(FHC_CONTROL_AOFF | |
220 | FHC_CONTROL_BOFF | |
221 | FHC_CONTROL_SLINE); |
222 | |
223 | upa_writel(reg, p->pregs + FHC_PREGS_CTRL); |
224 | upa_readl(p->pregs + FHC_PREGS_CTRL); |
225 | |
226 | reg = upa_readl(p->pregs + FHC_PREGS_ID); |
227 | printk(KERN_INFO "fhc: Board #%d, Version[%x] PartID[%x] Manuf[%x] %s\n" , |
228 | p->board_num, |
229 | (reg & FHC_ID_VERS) >> 28, |
230 | (reg & FHC_ID_PARTID) >> 12, |
231 | (reg & FHC_ID_MANUF) >> 1, |
232 | (p->jtag_master ? |
233 | "(JTAG Master)" : |
234 | (p->central ? "(Central)" : "" ))); |
235 | |
236 | err = 0; |
237 | |
238 | out: |
239 | return err; |
240 | |
241 | out_unmap_pregs: |
242 | of_iounmap(&op->resource[0], p->pregs, resource_size(res: &op->resource[0])); |
243 | |
244 | out_free: |
245 | kfree(objp: p); |
246 | goto out; |
247 | } |
248 | |
249 | static const struct of_device_id fhc_match[] = { |
250 | { |
251 | .name = "fhc" , |
252 | }, |
253 | {}, |
254 | }; |
255 | |
256 | static struct platform_driver fhc_driver = { |
257 | .probe = fhc_probe, |
258 | .driver = { |
259 | .name = "fhc" , |
260 | .of_match_table = fhc_match, |
261 | }, |
262 | }; |
263 | |
264 | static int __init sunfire_init(void) |
265 | { |
266 | (void) platform_driver_register(&fhc_driver); |
267 | (void) platform_driver_register(&clock_board_driver); |
268 | return 0; |
269 | } |
270 | |
271 | fs_initcall(sunfire_init); |
272 | |