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