]>
Commit | Line | Data |
---|---|---|
61e115a5 MB |
1 | /* |
2 | * Sonics Silicon Backplane | |
3 | * PCMCIA-Hostbus related functions | |
4 | * | |
5 | * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | |
6 | * Copyright 2007 Michael Buesch <mb@bu3sch.de> | |
7 | * | |
8 | * Licensed under the GNU/GPL. See COPYING for details. | |
9 | */ | |
10 | ||
11 | #include <linux/ssb/ssb.h> | |
12 | #include <linux/delay.h> | |
409f2435 | 13 | #include <linux/io.h> |
61e115a5 MB |
14 | |
15 | #include <pcmcia/cs_types.h> | |
16 | #include <pcmcia/cs.h> | |
17 | #include <pcmcia/cistpl.h> | |
18 | #include <pcmcia/ciscode.h> | |
19 | #include <pcmcia/ds.h> | |
20 | #include <pcmcia/cisreg.h> | |
21 | ||
22 | #include "ssb_private.h" | |
23 | ||
24 | ||
25 | /* Define the following to 1 to enable a printk on each coreswitch. */ | |
26 | #define SSB_VERBOSE_PCMCIACORESWITCH_DEBUG 0 | |
27 | ||
28 | ||
29 | int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus, | |
30 | u8 coreidx) | |
31 | { | |
32 | struct pcmcia_device *pdev = bus->host_pcmcia; | |
33 | int err; | |
34 | int attempts = 0; | |
35 | u32 cur_core; | |
36 | conf_reg_t reg; | |
37 | u32 addr; | |
38 | u32 read_addr; | |
39 | ||
40 | addr = (coreidx * SSB_CORE_SIZE) + SSB_ENUM_BASE; | |
41 | while (1) { | |
42 | reg.Action = CS_WRITE; | |
43 | reg.Offset = 0x2E; | |
44 | reg.Value = (addr & 0x0000F000) >> 12; | |
45 | err = pcmcia_access_configuration_register(pdev, ®); | |
46 | if (err != CS_SUCCESS) | |
47 | goto error; | |
48 | reg.Offset = 0x30; | |
49 | reg.Value = (addr & 0x00FF0000) >> 16; | |
50 | err = pcmcia_access_configuration_register(pdev, ®); | |
51 | if (err != CS_SUCCESS) | |
52 | goto error; | |
53 | reg.Offset = 0x32; | |
54 | reg.Value = (addr & 0xFF000000) >> 24; | |
55 | err = pcmcia_access_configuration_register(pdev, ®); | |
56 | if (err != CS_SUCCESS) | |
57 | goto error; | |
58 | ||
59 | read_addr = 0; | |
60 | ||
61 | reg.Action = CS_READ; | |
62 | reg.Offset = 0x2E; | |
63 | err = pcmcia_access_configuration_register(pdev, ®); | |
64 | if (err != CS_SUCCESS) | |
65 | goto error; | |
60d78c44 | 66 | read_addr |= ((u32)(reg.Value & 0x0F)) << 12; |
61e115a5 MB |
67 | reg.Offset = 0x30; |
68 | err = pcmcia_access_configuration_register(pdev, ®); | |
69 | if (err != CS_SUCCESS) | |
70 | goto error; | |
60d78c44 | 71 | read_addr |= ((u32)reg.Value) << 16; |
61e115a5 MB |
72 | reg.Offset = 0x32; |
73 | err = pcmcia_access_configuration_register(pdev, ®); | |
74 | if (err != CS_SUCCESS) | |
75 | goto error; | |
60d78c44 | 76 | read_addr |= ((u32)reg.Value) << 24; |
61e115a5 MB |
77 | |
78 | cur_core = (read_addr - SSB_ENUM_BASE) / SSB_CORE_SIZE; | |
79 | if (cur_core == coreidx) | |
80 | break; | |
81 | ||
82 | if (attempts++ > SSB_BAR0_MAX_RETRIES) | |
83 | goto error; | |
84 | udelay(10); | |
85 | } | |
86 | ||
87 | return 0; | |
88 | error: | |
89 | ssb_printk(KERN_ERR PFX "Failed to switch to core %u\n", coreidx); | |
90 | return -ENODEV; | |
91 | } | |
92 | ||
93 | int ssb_pcmcia_switch_core(struct ssb_bus *bus, | |
94 | struct ssb_device *dev) | |
95 | { | |
96 | int err; | |
97 | unsigned long flags; | |
98 | ||
99 | #if SSB_VERBOSE_PCMCIACORESWITCH_DEBUG | |
100 | ssb_printk(KERN_INFO PFX | |
101 | "Switching to %s core, index %d\n", | |
102 | ssb_core_name(dev->id.coreid), | |
103 | dev->core_index); | |
104 | #endif | |
105 | ||
106 | spin_lock_irqsave(&bus->bar_lock, flags); | |
107 | err = ssb_pcmcia_switch_coreidx(bus, dev->core_index); | |
108 | if (!err) | |
109 | bus->mapped_device = dev; | |
110 | spin_unlock_irqrestore(&bus->bar_lock, flags); | |
111 | ||
112 | return err; | |
113 | } | |
114 | ||
115 | int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg) | |
116 | { | |
117 | int attempts = 0; | |
118 | unsigned long flags; | |
119 | conf_reg_t reg; | |
120 | int res, err = 0; | |
121 | ||
122 | SSB_WARN_ON((seg != 0) && (seg != 1)); | |
123 | reg.Offset = 0x34; | |
124 | reg.Function = 0; | |
125 | spin_lock_irqsave(&bus->bar_lock, flags); | |
126 | while (1) { | |
127 | reg.Action = CS_WRITE; | |
128 | reg.Value = seg; | |
129 | res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); | |
130 | if (unlikely(res != CS_SUCCESS)) | |
131 | goto error; | |
132 | reg.Value = 0xFF; | |
133 | reg.Action = CS_READ; | |
134 | res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); | |
135 | if (unlikely(res != CS_SUCCESS)) | |
136 | goto error; | |
137 | ||
138 | if (reg.Value == seg) | |
139 | break; | |
140 | ||
141 | if (unlikely(attempts++ > SSB_BAR0_MAX_RETRIES)) | |
142 | goto error; | |
143 | udelay(10); | |
144 | } | |
145 | bus->mapped_pcmcia_seg = seg; | |
146 | out_unlock: | |
147 | spin_unlock_irqrestore(&bus->bar_lock, flags); | |
148 | return err; | |
149 | error: | |
150 | ssb_printk(KERN_ERR PFX "Failed to switch pcmcia segment\n"); | |
151 | err = -ENODEV; | |
152 | goto out_unlock; | |
153 | } | |
154 | ||
60d78c44 MB |
155 | static int select_core_and_segment(struct ssb_device *dev, |
156 | u16 *offset) | |
61e115a5 | 157 | { |
60d78c44 | 158 | struct ssb_bus *bus = dev->bus; |
61e115a5 | 159 | int err; |
60d78c44 MB |
160 | u8 need_segment; |
161 | ||
162 | if (*offset >= 0x800) { | |
163 | *offset -= 0x800; | |
164 | need_segment = 1; | |
165 | } else | |
166 | need_segment = 0; | |
61e115a5 MB |
167 | |
168 | if (unlikely(dev != bus->mapped_device)) { | |
169 | err = ssb_pcmcia_switch_core(bus, dev); | |
170 | if (unlikely(err)) | |
171 | return err; | |
172 | } | |
60d78c44 MB |
173 | if (unlikely(need_segment != bus->mapped_pcmcia_seg)) { |
174 | err = ssb_pcmcia_switch_segment(bus, need_segment); | |
61e115a5 MB |
175 | if (unlikely(err)) |
176 | return err; | |
177 | } | |
61e115a5 MB |
178 | |
179 | return 0; | |
180 | } | |
181 | ||
182 | static u16 ssb_pcmcia_read16(struct ssb_device *dev, u16 offset) | |
183 | { | |
184 | struct ssb_bus *bus = dev->bus; | |
61e115a5 | 185 | |
60d78c44 | 186 | if (unlikely(select_core_and_segment(dev, &offset))) |
61e115a5 | 187 | return 0xFFFF; |
61e115a5 | 188 | |
60d78c44 | 189 | return readw(bus->mmio + offset); |
61e115a5 MB |
190 | } |
191 | ||
192 | static u32 ssb_pcmcia_read32(struct ssb_device *dev, u16 offset) | |
193 | { | |
194 | struct ssb_bus *bus = dev->bus; | |
60d78c44 | 195 | u32 lo, hi; |
61e115a5 | 196 | |
60d78c44 | 197 | if (unlikely(select_core_and_segment(dev, &offset))) |
61e115a5 | 198 | return 0xFFFFFFFF; |
60d78c44 MB |
199 | lo = readw(bus->mmio + offset); |
200 | hi = readw(bus->mmio + offset + 2); | |
61e115a5 | 201 | |
60d78c44 | 202 | return (lo | (hi << 16)); |
61e115a5 MB |
203 | } |
204 | ||
205 | static void ssb_pcmcia_write16(struct ssb_device *dev, u16 offset, u16 value) | |
206 | { | |
207 | struct ssb_bus *bus = dev->bus; | |
208 | ||
60d78c44 | 209 | if (unlikely(select_core_and_segment(dev, &offset))) |
61e115a5 MB |
210 | return; |
211 | writew(value, bus->mmio + offset); | |
212 | } | |
213 | ||
214 | static void ssb_pcmcia_write32(struct ssb_device *dev, u16 offset, u32 value) | |
215 | { | |
216 | struct ssb_bus *bus = dev->bus; | |
217 | ||
60d78c44 | 218 | if (unlikely(select_core_and_segment(dev, &offset))) |
61e115a5 | 219 | return; |
60d78c44 MB |
220 | writeb((value & 0xFF000000) >> 24, bus->mmio + offset + 3); |
221 | writeb((value & 0x00FF0000) >> 16, bus->mmio + offset + 2); | |
222 | writeb((value & 0x0000FF00) >> 8, bus->mmio + offset + 1); | |
223 | writeb((value & 0x000000FF) >> 0, bus->mmio + offset + 0); | |
61e115a5 MB |
224 | } |
225 | ||
226 | /* Not "static", as it's used in main.c */ | |
227 | const struct ssb_bus_ops ssb_pcmcia_ops = { | |
228 | .read16 = ssb_pcmcia_read16, | |
229 | .read32 = ssb_pcmcia_read32, | |
230 | .write16 = ssb_pcmcia_write16, | |
231 | .write32 = ssb_pcmcia_write32, | |
232 | }; | |
233 | ||
234 | int ssb_pcmcia_get_invariants(struct ssb_bus *bus, | |
235 | struct ssb_init_invariants *iv) | |
236 | { | |
237 | //TODO | |
238 | return 0; | |
239 | } | |
240 | ||
241 | int ssb_pcmcia_init(struct ssb_bus *bus) | |
242 | { | |
243 | conf_reg_t reg; | |
244 | int err; | |
245 | ||
246 | if (bus->bustype != SSB_BUSTYPE_PCMCIA) | |
247 | return 0; | |
248 | ||
249 | /* Switch segment to a known state and sync | |
250 | * bus->mapped_pcmcia_seg with hardware state. */ | |
251 | ssb_pcmcia_switch_segment(bus, 0); | |
252 | ||
253 | /* Init IRQ routing */ | |
254 | reg.Action = CS_READ; | |
255 | reg.Function = 0; | |
256 | if (bus->chip_id == 0x4306) | |
257 | reg.Offset = 0x00; | |
258 | else | |
259 | reg.Offset = 0x80; | |
260 | err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); | |
261 | if (err != CS_SUCCESS) | |
262 | goto error; | |
263 | reg.Action = CS_WRITE; | |
264 | reg.Value |= 0x04 | 0x01; | |
265 | err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); | |
266 | if (err != CS_SUCCESS) | |
267 | goto error; | |
268 | ||
269 | return 0; | |
270 | error: | |
271 | return -ENODEV; | |
272 | } |