Skip to content

Commit 1ce849c

Browse files
maciej-w-rozyckiKAGA-KOKO
authored andcommitted
x86/PCI: Add support for the ALi M1487 (IBC) PIRQ router
The ALi M1487 ISA Bus Controller (IBC), a part of the ALi FinALi 486 chipset, implements PCI interrupt steering with a PIRQ router[1] in the form of four 4-bit mappings, spread across two PCI INTx Routing Table Mapping Registers, available in the port I/O space accessible indirectly via the index/data register pair at 0x22/0x23, located at indices 0x42 and 0x43 for the INT1/INT2 and INT3/INT4 lines respectively. Additionally there is a separate PCI INTx Sensitivity Register at index 0x44 in the same port I/O space, whose bits 3:0 select the trigger mode for INT[4:1] lines respectively[2]. Manufacturer's documentation says that this register has to be set consistently with the relevant ELCR register[3]. Add a router-specific hook then and use it to handle this register. Accesses to the port I/O space concerned here need to be unlocked by writing the value of 0xc5 to the Lock Register at index 0x03 beforehand[4]. Do so then and then lock access after use for safety. The IBC is implemented as a peer bridge on the host bus rather than a southbridge on PCI and therefore it does not itself appear in the PCI configuration space. It is complemented by the M1489 Cache-Memory PCI Controller (CMP) host-to-PCI bridge, so use that device's identification for determining the presence of the IBC. References: [1] "M1489/M1487: 486 PCI Chip Set", Version 1.2, Acer Laboratories Inc., July 1997, Section 4: "Configuration Registers", pp. 76-77 [2] same, p. 77 [3] same, Section 5: "M1489/M1487 Software Programming Guide", pp. 99-100 [4] same, Section 4: "Configuration Registers", p. 37 Signed-off-by: Maciej W. Rozycki <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent fb6a040 commit 1ce849c

File tree

2 files changed

+153
-2
lines changed

2 files changed

+153
-2
lines changed

arch/x86/pci/irq.c

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
#include <linux/dmi.h>
1414
#include <linux/io.h>
1515
#include <linux/smp.h>
16+
#include <linux/spinlock.h>
1617
#include <asm/io_apic.h>
1718
#include <linux/irq.h>
1819
#include <linux/acpi.h>
20+
21+
#include <asm/pc-conf-reg.h>
1922
#include <asm/pci_x86.h>
2023

2124
#define PIRQ_SIGNATURE (('$' << 0) + ('P' << 8) + ('I' << 16) + ('R' << 24))
@@ -47,6 +50,8 @@ struct irq_router {
4750
int (*get)(struct pci_dev *router, struct pci_dev *dev, int pirq);
4851
int (*set)(struct pci_dev *router, struct pci_dev *dev, int pirq,
4952
int new);
53+
int (*lvl)(struct pci_dev *router, struct pci_dev *dev, int pirq,
54+
int irq);
5055
};
5156

5257
struct irq_router_handler {
@@ -169,6 +174,139 @@ void elcr_set_level_irq(unsigned int irq)
169174
}
170175
}
171176

177+
/*
178+
* PIRQ routing for the M1487 ISA Bus Controller (IBC) ASIC used
179+
* with the ALi FinALi 486 chipset. The IBC is not decoded in the
180+
* PCI configuration space, so we identify it by the accompanying
181+
* M1489 Cache-Memory PCI Controller (CMP) ASIC.
182+
*
183+
* There are four 4-bit mappings provided, spread across two PCI
184+
* INTx Routing Table Mapping Registers, available in the port I/O
185+
* space accessible indirectly via the index/data register pair at
186+
* 0x22/0x23, located at indices 0x42 and 0x43 for the INT1/INT2
187+
* and INT3/INT4 lines respectively. The INT1/INT3 and INT2/INT4
188+
* lines are mapped in the low and the high 4-bit nibble of the
189+
* corresponding register as follows:
190+
*
191+
* 0000 : Disabled
192+
* 0001 : IRQ9
193+
* 0010 : IRQ3
194+
* 0011 : IRQ10
195+
* 0100 : IRQ4
196+
* 0101 : IRQ5
197+
* 0110 : IRQ7
198+
* 0111 : IRQ6
199+
* 1000 : Reserved
200+
* 1001 : IRQ11
201+
* 1010 : Reserved
202+
* 1011 : IRQ12
203+
* 1100 : Reserved
204+
* 1101 : IRQ14
205+
* 1110 : Reserved
206+
* 1111 : IRQ15
207+
*
208+
* In addition to the usual ELCR register pair there is a separate
209+
* PCI INTx Sensitivity Register at index 0x44 in the same port I/O
210+
* space, whose bits 3:0 select the trigger mode for INT[4:1] lines
211+
* respectively. Any bit set to 1 causes interrupts coming on the
212+
* corresponding line to be passed to ISA as edge-triggered and
213+
* otherwise they are passed as level-triggered. Manufacturer's
214+
* documentation says this register has to be set consistently with
215+
* the relevant ELCR register.
216+
*
217+
* Accesses to the port I/O space concerned here need to be unlocked
218+
* by writing the value of 0xc5 to the Lock Register at index 0x03
219+
* beforehand. Any other value written to said register prevents
220+
* further accesses from reaching the register file, except for the
221+
* Lock Register being written with 0xc5 again.
222+
*
223+
* References:
224+
*
225+
* "M1489/M1487: 486 PCI Chip Set", Version 1.2, Acer Laboratories
226+
* Inc., July 1997
227+
*/
228+
229+
#define PC_CONF_FINALI_LOCK 0x03u
230+
#define PC_CONF_FINALI_PCI_INTX_RT1 0x42u
231+
#define PC_CONF_FINALI_PCI_INTX_RT2 0x43u
232+
#define PC_CONF_FINALI_PCI_INTX_SENS 0x44u
233+
234+
#define PC_CONF_FINALI_LOCK_KEY 0xc5u
235+
236+
static u8 read_pc_conf_nybble(u8 base, u8 index)
237+
{
238+
u8 reg = base + (index >> 1);
239+
u8 x;
240+
241+
x = pc_conf_get(reg);
242+
return index & 1 ? x >> 4 : x & 0xf;
243+
}
244+
245+
static void write_pc_conf_nybble(u8 base, u8 index, u8 val)
246+
{
247+
u8 reg = base + (index >> 1);
248+
u8 x;
249+
250+
x = pc_conf_get(reg);
251+
x = index & 1 ? (x & 0x0f) | (val << 4) : (x & 0xf0) | val;
252+
pc_conf_set(reg, x);
253+
}
254+
255+
static int pirq_finali_get(struct pci_dev *router, struct pci_dev *dev,
256+
int pirq)
257+
{
258+
static const u8 irqmap[16] = {
259+
0, 9, 3, 10, 4, 5, 7, 6, 0, 11, 0, 12, 0, 14, 0, 15
260+
};
261+
unsigned long flags;
262+
u8 x;
263+
264+
raw_spin_lock_irqsave(&pc_conf_lock, flags);
265+
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
266+
x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1)];
267+
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
268+
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
269+
return x;
270+
}
271+
272+
static int pirq_finali_set(struct pci_dev *router, struct pci_dev *dev,
273+
int pirq, int irq)
274+
{
275+
static const u8 irqmap[16] = {
276+
0, 0, 0, 2, 4, 5, 7, 6, 0, 1, 3, 9, 11, 0, 13, 15
277+
};
278+
u8 val = irqmap[irq];
279+
unsigned long flags;
280+
281+
if (!val)
282+
return 0;
283+
284+
raw_spin_lock_irqsave(&pc_conf_lock, flags);
285+
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
286+
write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1, val);
287+
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
288+
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
289+
return 1;
290+
}
291+
292+
static int pirq_finali_lvl(struct pci_dev *router, struct pci_dev *dev,
293+
int pirq, int irq)
294+
{
295+
u8 mask = ~(1u << (pirq - 1));
296+
unsigned long flags;
297+
u8 trig;
298+
299+
elcr_set_level_irq(irq);
300+
raw_spin_lock_irqsave(&pc_conf_lock, flags);
301+
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
302+
trig = pc_conf_get(PC_CONF_FINALI_PCI_INTX_SENS);
303+
trig &= mask;
304+
pc_conf_set(PC_CONF_FINALI_PCI_INTX_SENS, trig);
305+
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
306+
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
307+
return 1;
308+
}
309+
172310
/*
173311
* Common IRQ routing practice: nibbles in config space,
174312
* offset by some magic constant.
@@ -745,6 +883,12 @@ static __init int ite_router_probe(struct irq_router *r, struct pci_dev *router,
745883
static __init int ali_router_probe(struct irq_router *r, struct pci_dev *router, u16 device)
746884
{
747885
switch (device) {
886+
case PCI_DEVICE_ID_AL_M1489:
887+
r->name = "FinALi";
888+
r->get = pirq_finali_get;
889+
r->set = pirq_finali_set;
890+
r->lvl = pirq_finali_lvl;
891+
return 1;
748892
case PCI_DEVICE_ID_AL_M1533:
749893
case PCI_DEVICE_ID_AL_M1563:
750894
r->name = "ALI";
@@ -968,11 +1112,17 @@ static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
9681112
} else if (r->get && (irq = r->get(pirq_router_dev, dev, pirq)) && \
9691113
((!(pci_probe & PCI_USE_PIRQ_MASK)) || ((1 << irq) & mask))) {
9701114
msg = "found";
971-
elcr_set_level_irq(irq);
1115+
if (r->lvl)
1116+
r->lvl(pirq_router_dev, dev, pirq, irq);
1117+
else
1118+
elcr_set_level_irq(irq);
9721119
} else if (newirq && r->set &&
9731120
(dev->class >> 8) != PCI_CLASS_DISPLAY_VGA) {
9741121
if (r->set(pirq_router_dev, dev, pirq, newirq)) {
975-
elcr_set_level_irq(newirq);
1122+
if (r->lvl)
1123+
r->lvl(pirq_router_dev, dev, pirq, newirq);
1124+
else
1125+
elcr_set_level_irq(newirq);
9761126
msg = "assigned";
9771127
irq = newirq;
9781128
}

include/linux/pci_ids.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,7 @@
11211121
#define PCI_DEVICE_ID_3COM_3CR990SVR 0x990a
11221122

11231123
#define PCI_VENDOR_ID_AL 0x10b9
1124+
#define PCI_DEVICE_ID_AL_M1489 0x1489
11241125
#define PCI_DEVICE_ID_AL_M1533 0x1533
11251126
#define PCI_DEVICE_ID_AL_M1535 0x1535
11261127
#define PCI_DEVICE_ID_AL_M1541 0x1541

0 commit comments

Comments
 (0)