The switch contains an interrupt controller. This then has a chained device interrupt controller, which contains interrupts from the embedded PHYs. Add support for these interrupt controllers. --- drivers/net/dsa/mv88e6xxx.c | 312 ++++++++++++++++++++++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx.h | 22 ++++ 2 files changed, 334 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index 5f07524083c3..2301dfbc3582 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c @@ -21,6 +21,9 @@ #include <linux/netdevice.h> #include <linux/gpio/consumer.h> #include <linux/phy.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> #include <net/dsa.h> #include <net/switchdev.h> #include "mv88e6xxx.h" @@ -2536,6 +2539,315 @@ int mv88e6xxx_setup_ports(struct dsa_switch *ds) return 0; } +static int mv88e6xxx_nirqs(struct dsa_switch *ds) +{ + int nirqs = 8; + + if (mv88e6xxx_6165_family(ds) || + mv88e6xxx_6351_family(ds) || + mv88e6xxx_6352_family(ds)) + nirqs = 9; + + return nirqs; +} + +static void mv88e6xxx_switch_irq_mask(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + unsigned int n = d->hwirq; + + ps->switch_irq_masked |= (1 << n); +} + +static void mv88e6xxx_switch_irq_unmask(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + unsigned int n = d->hwirq; + + ps->switch_irq_masked &= ~(1 << n); +} + +static irqreturn_t mv88e6xxx_switch_irq_thread_fn(int irq, void *dev_id) +{ + struct dsa_switch *ds = (struct dsa_switch *)dev_id; + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + unsigned nhandled = 0; + unsigned sub_irq; + unsigned n; + int val; + + val = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_STATUS); + if (val < 0) + goto out; + + for (n = 0; n < ps->switch_nirqs; ++n) { + if (val & (1 << n)) { + sub_irq = irq_find_mapping(ps->switch_irq_domain, n); + handle_nested_irq(sub_irq); + ++nhandled; + } + } +out: + return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); +} + +static void mv88e6xxx_switch_irq_bus_lock(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + mutex_lock(&ps->smi_mutex); +} + +static void mv88e6xxx_switch_irq_bus_sync_unlock(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u16 mask = GENMASK(ps->switch_nirqs, 0); + int reg, ret; + + reg = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_CONTROL); + if (reg < 0) + goto out; + + reg &= ~mask; + reg |= (~ps->switch_irq_masked & mask); + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_CONTROL, reg); + if (ret < 0) + goto out; + +out: + mutex_unlock(&ps->smi_mutex); +} + +static struct irq_chip mv88e6xxx_switch_irq_chip = { + .name = "mv88e6xxx-switch", + .irq_mask = mv88e6xxx_switch_irq_mask, + .irq_unmask = mv88e6xxx_switch_irq_unmask, + .irq_bus_lock = mv88e6xxx_switch_irq_bus_lock, + .irq_bus_sync_unlock = mv88e6xxx_switch_irq_bus_sync_unlock, +}; + +static int mv88e6xxx_switch_irq_domain_map(struct irq_domain *d, + unsigned int irq, + irq_hw_number_t hwirq) +{ + struct dsa_switch *ds = d->host_data; + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &ps->switch_irq_chip, handle_level_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops mv88e6xxx_switch_irq_domain_ops = { + .map = mv88e6xxx_switch_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +int mv88e6xxx_setup_switch_irq(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + u16 mask = GENMASK(ps->switch_nirqs, 0); + int err, irq, reg; + + ps->switch_nirqs = mv88e6xxx_nirqs(ds); + ps->switch_irq_domain = irq_domain_add_simple( + NULL, ps->switch_nirqs, 0, + &mv88e6xxx_switch_irq_domain_ops, ds); + if (!ps->switch_irq_domain) + return -ENOMEM; + + for (irq = 0; irq < ps->switch_nirqs; irq++) + irq_create_mapping(ps->switch_irq_domain, irq); + + ps->switch_irq_chip = mv88e6xxx_switch_irq_chip; + ps->switch_irq_masked = ~0; + + reg = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_CONTROL); + if (reg < 0) { + err = reg; + goto out; + } + + reg &= ~mask; + + err = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_CONTROL, reg); + if (err < 0) + goto out; + + /* Reading the interrupt status clears (most of) them */ + reg = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_STATUS); + if (reg < 0) { + err = reg; + goto out; + } + + err = request_threaded_irq(ds->pd->irq, NULL, + mv88e6xxx_switch_irq_thread_fn, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + "mv88e6xxx-switch", ds); + + if (err) + goto out; + + return 0; + +out: + irq_domain_remove(ps->switch_irq_domain); + return err; +} + +static void mv88e6xxx_device_irq_mask(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + unsigned int n = d->hwirq; + + ps->device_irq_masked |= (1 << n); +} + +static void mv88e6xxx_device_irq_unmask(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + unsigned int n = d->hwirq; + + ps->device_irq_masked &= ~(1 << n); +} + +static irqreturn_t mv88e6xxx_device_irq_thread_fn(int irq, void *dev_id) +{ + struct dsa_switch *ds = (struct dsa_switch *)dev_id; + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + unsigned nhandled = 0; + unsigned sub_irq; + unsigned n; + int val; + + val = _mv88e6xxx_reg_read(ds, REG_GLOBAL2, GLOBAL2_INT_SOURCE); + if (val < 0) + goto out; + + for (n = 0; n < 16; ++n) { + if (val & (1 << n)) { + sub_irq = irq_find_mapping(ps->device_irq_domain, n); + handle_nested_irq(sub_irq); + ++nhandled; + } + } +out: + return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); +} + +static void mv88e6xxx_device_irq_bus_lock(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + mutex_lock(&ps->smi_mutex); +} + +static void mv88e6xxx_device_irq_bus_sync_unlock(struct irq_data *d) +{ + struct dsa_switch *ds = irq_data_get_irq_chip_data(d); + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + int ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL2, GLOBAL2_INT_MASK, + ~ps->device_irq_masked); + if (ret < 0) + goto out; + +out: + mutex_unlock(&ps->smi_mutex); +} + +static struct irq_chip mv88e6xxx_device_irq_chip = { + .name = "mv88e6xxx-device", + .irq_mask = mv88e6xxx_device_irq_mask, + .irq_unmask = mv88e6xxx_device_irq_unmask, + .irq_bus_lock = mv88e6xxx_device_irq_bus_lock, + .irq_bus_sync_unlock = mv88e6xxx_device_irq_bus_sync_unlock, +}; + +static int mv88e6xxx_device_irq_domain_map(struct irq_domain *d, + unsigned int irq, + irq_hw_number_t hwirq) +{ + struct dsa_switch *ds = d->host_data; + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &ps->device_irq_chip, handle_level_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops mv88e6xxx_device_irq_domain_ops = { + .map = mv88e6xxx_device_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +int mv88e6xxx_setup_device_irq(struct dsa_switch *ds) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + struct device_node *np = ds->pd->of_node; + int device_irq; + int err, irq; + + ps->device_irq_domain = irq_domain_add_simple( + np, 16, 0, &mv88e6xxx_device_irq_domain_ops, ds); + if (!ps->device_irq_domain) + return -ENOMEM; + + for (irq = 0; irq < 16; irq++) + irq_create_mapping(ps->device_irq_domain, irq); + + ps->device_irq_chip = mv88e6xxx_device_irq_chip; + ps->device_irq_masked = ~0; + + device_irq = irq_find_mapping(ps->switch_irq_domain, + GLOBAL_STATUS_IRQ_DEVICE); + if (device_irq < 0) { + err = device_irq; + goto out; + } + + err = request_threaded_irq(device_irq, NULL, + mv88e6xxx_device_irq_thread_fn, + IRQF_ONESHOT, "mv88e6xxx-device", ds); + if (err) + goto out; + + return 0; +out: + irq_domain_remove(ps->device_irq_domain); + return err; +} + +int mv88e6xxx_setup_irqs(struct dsa_switch *ds) +{ + int err; + + if (ds->pd->irq) { + err = mv88e6xxx_setup_switch_irq(ds); + if (err) + return err; + + if (mv88e6xxx_6165_family(ds) || mv88e6xxx_6351_family(ds) || + mv88e6xxx_6352_family(ds)) + return mv88e6xxx_setup_device_irq(ds); + } + return 0; +} + int mv88e6xxx_setup_common(struct dsa_switch *ds) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h index 3425616987ed..bb97fdc21e0e 100644 --- a/drivers/net/dsa/mv88e6xxx.h +++ b/drivers/net/dsa/mv88e6xxx.h @@ -12,6 +12,7 @@ #define __MV88E6XXX_H #include <linux/if_vlan.h> +#include <linux/irq.h> #ifndef UINT64_MAX #define UINT64_MAX (u64)(~((u64)0)) @@ -185,6 +186,15 @@ #define GLOBAL_STATUS_PPU_INITIALIZING (0x1 << 14) #define GLOBAL_STATUS_PPU_DISABLED (0x2 << 14) #define GLOBAL_STATUS_PPU_POLLING (0x3 << 14) +#define GLOBAL_STATUS_IRQ_AVB 8 +#define GLOBAL_STATUS_IRQ_DEVICE 7 +#define GLOBAL_STATUS_IRQ_STATS 6 +#define GLOBAL_STATUS_IRQ_VTU_PROBLEM 5 +#define GLOBAL_STATUS_IRQ_VTU_DONE 4 +#define GLOBAL_STATUS_IRQ_ATU_PROBLEM 3 +#define GLOBAL_STATUS_IRQ_ATU_DONE 2 +#define GLOBAL_STATUS_IRQ_TCAM_DONE 1 +#define GLOBAL_STATUS_IRQ_EEPROM_DONE 0 #define GLOBAL_MAC_01 0x01 #define GLOBAL_MAC_23 0x02 #define GLOBAL_MAC_45 0x03 @@ -428,6 +438,17 @@ struct mv88e6xxx_priv_state { DECLARE_BITMAP(port_state_update_mask, DSA_MAX_PORTS); + /* Main switch interrupt controller */ + u16 switch_irq_masked; + struct irq_chip switch_irq_chip; + struct irq_domain *switch_irq_domain; + unsigned switch_nirqs; + + /* Device interrupt controller */ + u16 device_irq_masked; + struct irq_chip device_irq_chip; + struct irq_domain *device_irq_domain; + struct work_struct bridge_work; }; @@ -451,6 +472,7 @@ char *mv88e6xxx_lookup_name(struct device *host_dev, int sw_addr, int mv88e6xxx_setup_ports(struct dsa_switch *ds); int mv88e6xxx_setup_common(struct dsa_switch *ds); int mv88e6xxx_setup_global(struct dsa_switch *ds); +int mv88e6xxx_setup_irqs(struct dsa_switch *ds); int mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg); int mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg, u16 val); int mv88e6xxx_set_addr_direct(struct dsa_switch *ds, u8 *addr); -- 2.7.0