summaryrefslogtreecommitdiffstats
path: root/target/linux/brcm47xx/patches-2.6.30/816-ssb_fix_irq_setup.patch
blob: 05c36517d768ede27fe8e443192ba0c5c9a6e3c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
the current ssb irq setup (in ssb_mipscore_init) have some problem :
it configure some device on some irq without checking that the irq is not taken by an other device.

For example in my case PCI host is on irq 0 and IPSEC on irq 3.
The current code :
- store in dev->irq that IPSEC irq is 3+2
- do a set_irq 0->3 on PCI host

But now IPSEC irq is not routed anymore to the mips code and dev->irq is wrong. This cause problem described in [1].

This patch try to solve the problem by making set_irq configure the device we want to take the irq on the shared irq0.
The previous example become :
- store in dev->irq that IPSEC irq is 3+2
- do a set_irq 0->3 on PCI host :
  - irq 3 is already taken by IPSEC. do a set_irq 3->0 on IPSEC


I also added some code to print the irq configuration before and after irq setup to allow easier debugging. And I add extra checking in ssb_mips_irq to report device without irq or device with not routed irq.


[1] http://www.danm.de/files/src/bcm5365p/REPORTED_DEVICES

Signed-off-by: Matthieu CASTET <castet.matthieu@free.fr> 
--- a/drivers/ssb/driver_mipscore.c
+++ b/drivers/ssb/driver_mipscore.c
@@ -49,29 +49,54 @@ static const u32 ipsflag_irq_shift[] = {
 
 static inline u32 ssb_irqflag(struct ssb_device *dev)
 {
-	return ssb_read32(dev, SSB_TPSFLAG) & SSB_TPSFLAG_BPFLAG;
+	u32 tpsflag = ssb_read32(dev, SSB_TPSFLAG);
+	if (tpsflag)
+		return ssb_read32(dev, SSB_TPSFLAG) & SSB_TPSFLAG_BPFLAG;
+	else
+		/* not irq supported */
+		return 0x3f;
+}
+
+static struct ssb_device *find_device(struct ssb_device *rdev, int irqflag)
+{
+	struct ssb_bus *bus = rdev->bus;
+	int i;
+	for (i = 0; i < bus->nr_devices; i++) {
+		struct ssb_device *dev;
+		dev = &(bus->devices[i]);
+		if (ssb_irqflag(dev) == irqflag)
+			return dev;
+	}
+	return NULL;
 }
 
 /* Get the MIPS IRQ assignment for a specified device.
  * If unassigned, 0 is returned.
+ * If disabled, 5 is returned.
+ * If not supported, 6 is returned.
  */
 unsigned int ssb_mips_irq(struct ssb_device *dev)
 {
 	struct ssb_bus *bus = dev->bus;
+	struct ssb_device *mdev = bus->mipscore.dev;
 	u32 irqflag;
 	u32 ipsflag;
 	u32 tmp;
 	unsigned int irq;
 
 	irqflag = ssb_irqflag(dev);
+	if (irqflag == 0x3f)
+		return 6;
 	ipsflag = ssb_read32(bus->mipscore.dev, SSB_IPSFLAG);
 	for (irq = 1; irq <= 4; irq++) {
 		tmp = ((ipsflag & ipsflag_irq_mask[irq]) >> ipsflag_irq_shift[irq]);
 		if (tmp == irqflag)
 			break;
 	}
-	if (irq	== 5)
-		irq = 0;
+	if (irq	== 5) {
+		if ((1 << irqflag) & ssb_read32(mdev, SSB_INTVEC))
+			irq = 0;
+	}
 
 	return irq;
 }
@@ -97,25 +122,56 @@ static void set_irq(struct ssb_device *d
 	struct ssb_device *mdev = bus->mipscore.dev;
 	u32 irqflag = ssb_irqflag(dev);
 
+	BUG_ON(oldirq == 6);
+
 	dev->irq = irq + 2;
 
-	ssb_dprintk(KERN_INFO PFX
-		    "set_irq: core 0x%04x, irq %d => %d\n",
-		    dev->id.coreid, oldirq, irq);
 	/* clear the old irq */
 	if (oldirq == 0)
 		ssb_write32(mdev, SSB_INTVEC, (~(1 << irqflag) & ssb_read32(mdev, SSB_INTVEC)));
-	else
+	else if (oldirq != 5)
 		clear_irq(bus, oldirq);
 
 	/* assign the new one */
 	if (irq == 0) {
 		ssb_write32(mdev, SSB_INTVEC, ((1 << irqflag) | ssb_read32(mdev, SSB_INTVEC)));
 	} else {
+		u32 ipsflag = ssb_read32(mdev, SSB_IPSFLAG);
+		if ((ipsflag & ipsflag_irq_mask[irq]) != ipsflag_irq_mask[irq]) {
+			u32 oldipsflag = (ipsflag & ipsflag_irq_mask[irq]) >> ipsflag_irq_shift[irq];
+			struct ssb_device *olddev = find_device(dev, oldipsflag);
+			if (olddev)
+				set_irq(olddev, 0);
+		}
 		irqflag <<= ipsflag_irq_shift[irq];
-		irqflag |= (ssb_read32(mdev, SSB_IPSFLAG) & ~ipsflag_irq_mask[irq]);
+		irqflag |= (ipsflag & ~ipsflag_irq_mask[irq]);
 		ssb_write32(mdev, SSB_IPSFLAG, irqflag);
 	}
+	ssb_dprintk(KERN_INFO PFX
+		    "set_irq: core 0x%04x, irq %d => %d\n",
+		    dev->id.coreid, oldirq+2, irq+2);
+}
+
+static void print_irq(struct ssb_device *dev, unsigned int irq)
+{
+	int i;
+	static const char *irq_name[] = {"2(S)", "3", "4", "5", "6", "D", "I"};
+	ssb_dprintk(KERN_INFO PFX
+		"core 0x%04x, irq :", dev->id.coreid);
+	for (i = 0; i <= 6; i++) {
+		ssb_dprintk(" %s%s", irq_name[i], i==irq?"*":" ");
+	}
+	ssb_dprintk("\n");
+}
+
+static void dump_irq(struct ssb_bus *bus)
+{
+	int i;
+	for (i = 0; i < bus->nr_devices; i++) {
+		struct ssb_device *dev;
+		dev = &(bus->devices[i]);
+		print_irq(dev, ssb_mips_irq(dev));
+	}
 }
 
 static void ssb_mips_serial_init(struct ssb_mipscore *mcore)
@@ -197,18 +253,26 @@ void ssb_mipscore_init(struct ssb_mipsco
 	else if (bus->chipco.dev)
 		ssb_chipco_timing_init(&bus->chipco, ns);
 
+	dump_irq(bus);
 	/* Assign IRQs to all cores on the bus, start with irq line 2, because serial usually takes 1 */
 	for (irq = 2, i = 0; i < bus->nr_devices; i++) {
+		int mips_irq;
 		dev = &(bus->devices[i]);
-		dev->irq = ssb_mips_irq(dev) + 2;
+		mips_irq = ssb_mips_irq(dev);
+		if (mips_irq > 4)
+			dev->irq = 0;
+		else
+			dev->irq = mips_irq + 2;
+		if (dev->irq > 5)
+			continue;
 		switch (dev->id.coreid) {
 		case SSB_DEV_USB11_HOST:
 			/* shouldn't need a separate irq line for non-4710, most of them have a proper
 			 * external usb controller on the pci */
 			if ((bus->chip_id == 0x4710) && (irq <= 4)) {
 				set_irq(dev, irq++);
-				break;
 			}
+			break;
 			/* fallthrough */
 		case SSB_DEV_PCI:
 		case SSB_DEV_ETHERNET:
@@ -222,6 +286,8 @@ void ssb_mipscore_init(struct ssb_mipsco
 			}
 		}
 	}
+	ssb_dprintk(KERN_INFO PFX "after irq reconfiguration\n");
+	dump_irq(bus);
 
 	ssb_mips_serial_init(mcore);
 	ssb_mips_flash_detect(mcore);