Skip to content

Commit

Permalink
MIPS: SGI-IP27: abstract chipset irq from bridge
Browse files Browse the repository at this point in the history
Bridge ASIC is widely used in different SGI systems, but the connected
chipset is either HUB, HEART or BEDROCK. This commit switches to
irq domain hierarchy for hub and bridge interrupts to get bridge
setup out of hub interrupt code.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
[paul.burton@mips.com:
  Resolve conflict with commit 69a07a4 ("MIPS: SGI-IP27: rework HUB
  interrupts").]
Signed-off-by: Paul Burton <paul.burton@mips.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: James Hogan <jhogan@kernel.org>
Cc: linux-mips@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
  • Loading branch information
Thomas Bogendoerfer authored and Paul Burton committed May 9, 2019
1 parent a57140e commit e6308b6
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 119 deletions.
1 change: 1 addition & 0 deletions arch/mips/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ config SGI_IP27
select SYS_HAS_EARLY_PRINTK
select HAVE_PCI
select IRQ_MIPS_CPU
select IRQ_DOMAIN_HIERARCHY
select NR_CPUS_DEFAULT_64
select PCI_DRIVERS_GENERIC
select PCI_XTALK_BRIDGE
Expand Down
3 changes: 1 addition & 2 deletions arch/mips/include/asm/pci/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ struct bridge_controller {
struct bridge_regs *base;
unsigned long baddr;
unsigned long intr_addr;
struct irq_domain *domain;
unsigned int pci_int[8];
nasid_t nasid;
};
Expand All @@ -819,6 +820,4 @@ struct bridge_controller {
#define bridge_clr(bc, reg, val) \
__raw_writel(__raw_readl(&bc->base->reg) & ~(val), &bc->base->reg)

extern int request_bridge_irq(struct bridge_controller *bc, int pin);

#endif /* _ASM_PCI_BRIDGE_H */
11 changes: 11 additions & 0 deletions arch/mips/include/asm/sn/irq_alloc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_SN_IRQ_ALLOC_H
#define __ASM_SN_IRQ_ALLOC_H

struct irq_alloc_info {
void *ctrl;
nasid_t nasid;
int pin;
};

#endif /* __ASM_SN_IRQ_ALLOC_H */
178 changes: 168 additions & 10 deletions arch/mips/pci/pci-xtalk-bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#include <asm/pci/bridge.h>
#include <asm/paccess.h>
#include <asm/sn/intr.h>
#include <asm/sn/irq_alloc.h>

/*
* Most of the IOC3 PCI config register aren't present
Expand Down Expand Up @@ -310,6 +310,135 @@ static struct pci_ops bridge_pci_ops = {
.write = pci_write_config,
};

struct bridge_irq_chip_data {
struct bridge_controller *bc;
nasid_t nasid;
};

static int bridge_set_affinity(struct irq_data *d, const struct cpumask *mask,
bool force)
{
#ifdef CONFIG_NUMA
struct bridge_irq_chip_data *data = d->chip_data;
int bit = d->parent_data->hwirq;
int pin = d->hwirq;
nasid_t nasid;
int ret, cpu;

ret = irq_chip_set_affinity_parent(d, mask, force);
if (ret >= 0) {
cpu = cpumask_first_and(mask, cpu_online_mask);
nasid = COMPACT_TO_NASID_NODEID(cpu_to_node(cpu));
bridge_write(data->bc, b_int_addr[pin].addr,
(((data->bc->intr_addr >> 30) & 0x30000) |
bit | (nasid << 8)));
bridge_read(data->bc, b_wid_tflush);
}
return ret;
#else
return irq_chip_set_affinity_parent(d, mask, force);
#endif
}

struct irq_chip bridge_irq_chip = {
.name = "BRIDGE",
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_set_affinity = bridge_set_affinity
};

static int bridge_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
struct bridge_irq_chip_data *data;
struct irq_alloc_info *info = arg;
int ret;

if (nr_irqs > 1 || !info)
return -EINVAL;

data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;

ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
if (ret >= 0) {
data->bc = info->ctrl;
data->nasid = info->nasid;
irq_domain_set_info(domain, virq, info->pin, &bridge_irq_chip,
data, handle_level_irq, NULL, NULL);
} else {
kfree(data);
}

return ret;
}

static void bridge_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs)
{
struct irq_data *irqd = irq_domain_get_irq_data(domain, virq);

if (nr_irqs)
return;

kfree(irqd->chip_data);
irq_domain_free_irqs_top(domain, virq, nr_irqs);
}

static int bridge_domain_activate(struct irq_domain *domain,
struct irq_data *irqd, bool reserve)
{
struct bridge_irq_chip_data *data = irqd->chip_data;
struct bridge_controller *bc = data->bc;
int bit = irqd->parent_data->hwirq;
int pin = irqd->hwirq;
u32 device;

bridge_write(bc, b_int_addr[pin].addr,
(((bc->intr_addr >> 30) & 0x30000) |
bit | (data->nasid << 8)));
bridge_set(bc, b_int_enable, (1 << pin));
bridge_set(bc, b_int_enable, 0x7ffffe00); /* more stuff in int_enable */

/*
* Enable sending of an interrupt clear packt to the hub on a high to
* low transition of the interrupt pin.
*
* IRIX sets additional bits in the address which are documented as
* reserved in the bridge docs.
*/
bridge_set(bc, b_int_mode, (1UL << pin));

/*
* We assume the bridge to have a 1:1 mapping between devices
* (slots) and intr pins.
*/
device = bridge_read(bc, b_int_device);
device &= ~(7 << (pin*3));
device |= (pin << (pin*3));
bridge_write(bc, b_int_device, device);

bridge_read(bc, b_wid_tflush);
return 0;
}

static void bridge_domain_deactivate(struct irq_domain *domain,
struct irq_data *irqd)
{
struct bridge_irq_chip_data *data = irqd->chip_data;

bridge_clr(data->bc, b_int_enable, (1 << irqd->hwirq));
bridge_read(data->bc, b_wid_tflush);
}

static const struct irq_domain_ops bridge_domain_ops = {
.alloc = bridge_domain_alloc,
.free = bridge_domain_free,
.activate = bridge_domain_activate,
.deactivate = bridge_domain_deactivate
};

/*
* All observed requests have pin == 1. We could have a global here, that
* gets incremented and returned every time - unfortunately, pci_map_irq
Expand All @@ -322,11 +451,16 @@ static struct pci_ops bridge_pci_ops = {
static int bridge_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
struct bridge_controller *bc = BRIDGE_CONTROLLER(dev->bus);
struct irq_alloc_info info;
int irq;

irq = bc->pci_int[slot];
if (irq == -1) {
irq = request_bridge_irq(bc, slot);
info.ctrl = bc;
info.nasid = bc->nasid;
info.pin = slot;

irq = irq_domain_alloc_irqs(bc->domain, 1, bc->nasid, &info);
if (irq < 0)
return irq;

Expand All @@ -337,18 +471,34 @@ static int bridge_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)

static int bridge_probe(struct platform_device *pdev)
{
struct xtalk_bridge_platform_data *bd = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
struct bridge_controller *bc;
struct pci_host_bridge *host;
struct irq_domain *domain, *parent;
struct fwnode_handle *fn;
int slot;
int err;
struct xtalk_bridge_platform_data *bd = dev_get_platdata(&pdev->dev);

parent = irq_get_default_host();
if (!parent)
return -ENODEV;
fn = irq_domain_alloc_named_fwnode("BRIDGE");
if (!fn)
return -ENOMEM;
domain = irq_domain_create_hierarchy(parent, 0, 8, fn,
&bridge_domain_ops, NULL);
irq_domain_free_fwnode(fn);
if (!domain)
return -ENOMEM;

pci_set_flags(PCI_PROBE_ONLY);

host = devm_pci_alloc_host_bridge(dev, sizeof(*bc));
if (!host)
return -ENOMEM;
if (!host) {
err = -ENOMEM;
goto err_remove_domain;
}

bc = pci_host_bridge_priv(host);

Expand All @@ -357,15 +507,15 @@ static int bridge_probe(struct platform_device *pdev)
bc->busn.end = 0xff;
bc->busn.flags = IORESOURCE_BUS;

bc->domain = domain;

pci_add_resource_offset(&host->windows, &bd->mem, bd->mem_offset);
pci_add_resource_offset(&host->windows, &bd->io, bd->io_offset);
pci_add_resource(&host->windows, &bc->busn);

err = devm_request_pci_bus_resources(dev, &host->windows);
if (err < 0) {
pci_free_resource_list(&host->windows);
return err;
}
if (err < 0)
goto err_free_resource;

bc->nasid = bd->nasid;

Expand Down Expand Up @@ -419,20 +569,28 @@ static int bridge_probe(struct platform_device *pdev)

err = pci_scan_root_bus_bridge(host);
if (err < 0)
return err;
goto err_free_resource;

pci_bus_claim_resources(host->bus);
pci_bus_add_devices(host->bus);

platform_set_drvdata(pdev, host->bus);

return 0;

err_free_resource:
pci_free_resource_list(&host->windows);
err_remove_domain:
irq_domain_remove(domain);
return err;
}

static int bridge_remove(struct platform_device *pdev)
{
struct pci_bus *bus = platform_get_drvdata(pdev);
struct bridge_controller *bc = BRIDGE_CONTROLLER(bus);

irq_domain_remove(bc->domain);
pci_lock_rescan_remove();
pci_stop_root_bus(bus);
pci_remove_root_bus(bus);
Expand Down
Loading

0 comments on commit e6308b6

Please sign in to comment.