linux_dsm_epyc7002/drivers/net/dsa/mv88e6xxx/global2.c
Andrew Lunn dc30c35be7 net: dsa: mv88e6xxx: Implement interrupt support.
The switch can have up to two interrupt controllers. One of these
contains the interrupts from the integrated PHYs, so is useful to
export. The Marvell PHY driver can then be used in interrupt mode,
rather than polling, speeding up PHY handling and reducing load on the
MDIO bus.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-10-17 11:18:08 -04:00

629 lines
14 KiB
C

/*
* Marvell 88E6xxx Switch Global 2 Registers support (device address
* 0x1C)
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016 Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/irqdomain.h>
#include "mv88e6xxx.h"
#include "global2.h"
#define ADDR_GLOBAL2 0x1c
static int mv88e6xxx_g2_read(struct mv88e6xxx_chip *chip, int reg, u16 *val)
{
return mv88e6xxx_read(chip, ADDR_GLOBAL2, reg, val);
}
static int mv88e6xxx_g2_write(struct mv88e6xxx_chip *chip, int reg, u16 val)
{
return mv88e6xxx_write(chip, ADDR_GLOBAL2, reg, val);
}
static int mv88e6xxx_g2_update(struct mv88e6xxx_chip *chip, int reg, u16 update)
{
return mv88e6xxx_update(chip, ADDR_GLOBAL2, reg, update);
}
static int mv88e6xxx_g2_wait(struct mv88e6xxx_chip *chip, int reg, u16 mask)
{
return mv88e6xxx_wait(chip, ADDR_GLOBAL2, reg, mask);
}
/* Offset 0x06: Device Mapping Table register */
static int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip,
int target, int port)
{
u16 val = (target << 8) | (port & 0xf);
return mv88e6xxx_g2_update(chip, GLOBAL2_DEVICE_MAPPING, val);
}
static int mv88e6xxx_g2_set_device_mapping(struct mv88e6xxx_chip *chip)
{
int target, port;
int err;
/* Initialize the routing port to the 32 possible target devices */
for (target = 0; target < 32; ++target) {
port = 0xf;
if (target < DSA_MAX_SWITCHES) {
port = chip->ds->rtable[target];
if (port == DSA_RTABLE_NONE)
port = 0xf;
}
err = mv88e6xxx_g2_device_mapping_write(chip, target, port);
if (err)
break;
}
return err;
}
/* Offset 0x07: Trunk Mask Table register */
static int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
bool hask, u16 mask)
{
const u16 port_mask = BIT(mv88e6xxx_num_ports(chip)) - 1;
u16 val = (num << 12) | (mask & port_mask);
if (hask)
val |= GLOBAL2_TRUNK_MASK_HASK;
return mv88e6xxx_g2_update(chip, GLOBAL2_TRUNK_MASK, val);
}
/* Offset 0x08: Trunk Mapping Table register */
static int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
u16 map)
{
const u16 port_mask = BIT(mv88e6xxx_num_ports(chip)) - 1;
u16 val = (id << 11) | (map & port_mask);
return mv88e6xxx_g2_update(chip, GLOBAL2_TRUNK_MAPPING, val);
}
static int mv88e6xxx_g2_clear_trunk(struct mv88e6xxx_chip *chip)
{
const u16 port_mask = BIT(mv88e6xxx_num_ports(chip)) - 1;
int i, err;
/* Clear all eight possible Trunk Mask vectors */
for (i = 0; i < 8; ++i) {
err = mv88e6xxx_g2_trunk_mask_write(chip, i, false, port_mask);
if (err)
return err;
}
/* Clear all sixteen possible Trunk ID routing vectors */
for (i = 0; i < 16; ++i) {
err = mv88e6xxx_g2_trunk_mapping_write(chip, i, 0);
if (err)
return err;
}
return 0;
}
/* Offset 0x09: Ingress Rate Command register
* Offset 0x0A: Ingress Rate Data register
*/
static int mv88e6xxx_g2_clear_irl(struct mv88e6xxx_chip *chip)
{
int port, err;
/* Init all Ingress Rate Limit resources of all ports */
for (port = 0; port < mv88e6xxx_num_ports(chip); ++port) {
/* XXX newer chips (like 88E6390) have different 2-bit ops */
err = mv88e6xxx_g2_write(chip, GLOBAL2_IRL_CMD,
GLOBAL2_IRL_CMD_OP_INIT_ALL |
(port << 8));
if (err)
break;
/* Wait for the operation to complete */
err = mv88e6xxx_g2_wait(chip, GLOBAL2_IRL_CMD,
GLOBAL2_IRL_CMD_BUSY);
if (err)
break;
}
return err;
}
/* Offset 0x0D: Switch MAC/WoL/WoF register */
static int mv88e6xxx_g2_switch_mac_write(struct mv88e6xxx_chip *chip,
unsigned int pointer, u8 data)
{
u16 val = (pointer << 8) | data;
return mv88e6xxx_g2_update(chip, GLOBAL2_SWITCH_MAC, val);
}
int mv88e6xxx_g2_set_switch_mac(struct mv88e6xxx_chip *chip, u8 *addr)
{
int i, err;
for (i = 0; i < 6; i++) {
err = mv88e6xxx_g2_switch_mac_write(chip, i, addr[i]);
if (err)
break;
}
return err;
}
/* Offset 0x0F: Priority Override Table */
static int mv88e6xxx_g2_pot_write(struct mv88e6xxx_chip *chip, int pointer,
u8 data)
{
u16 val = (pointer << 8) | (data & 0x7);
return mv88e6xxx_g2_update(chip, GLOBAL2_PRIO_OVERRIDE, val);
}
static int mv88e6xxx_g2_clear_pot(struct mv88e6xxx_chip *chip)
{
int i, err;
/* Clear all sixteen possible Priority Override entries */
for (i = 0; i < 16; i++) {
err = mv88e6xxx_g2_pot_write(chip, i, 0);
if (err)
break;
}
return err;
}
/* Offset 0x14: EEPROM Command
* Offset 0x15: EEPROM Data
*/
static int mv88e6xxx_g2_eeprom_wait(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g2_wait(chip, GLOBAL2_EEPROM_CMD,
GLOBAL2_EEPROM_CMD_BUSY |
GLOBAL2_EEPROM_CMD_RUNNING);
}
static int mv88e6xxx_g2_eeprom_cmd(struct mv88e6xxx_chip *chip, u16 cmd)
{
int err;
err = mv88e6xxx_g2_write(chip, GLOBAL2_EEPROM_CMD, cmd);
if (err)
return err;
return mv88e6xxx_g2_eeprom_wait(chip);
}
static int mv88e6xxx_g2_eeprom_read16(struct mv88e6xxx_chip *chip,
u8 addr, u16 *data)
{
u16 cmd = GLOBAL2_EEPROM_CMD_OP_READ | addr;
int err;
err = mv88e6xxx_g2_eeprom_wait(chip);
if (err)
return err;
err = mv88e6xxx_g2_eeprom_cmd(chip, cmd);
if (err)
return err;
return mv88e6xxx_g2_read(chip, GLOBAL2_EEPROM_DATA, data);
}
static int mv88e6xxx_g2_eeprom_write16(struct mv88e6xxx_chip *chip,
u8 addr, u16 data)
{
u16 cmd = GLOBAL2_EEPROM_CMD_OP_WRITE | addr;
int err;
err = mv88e6xxx_g2_eeprom_wait(chip);
if (err)
return err;
err = mv88e6xxx_g2_write(chip, GLOBAL2_EEPROM_DATA, data);
if (err)
return err;
return mv88e6xxx_g2_eeprom_cmd(chip, cmd);
}
int mv88e6xxx_g2_get_eeprom16(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data)
{
unsigned int offset = eeprom->offset;
unsigned int len = eeprom->len;
u16 val;
int err;
eeprom->len = 0;
if (offset & 1) {
err = mv88e6xxx_g2_eeprom_read16(chip, offset >> 1, &val);
if (err)
return err;
*data++ = (val >> 8) & 0xff;
offset++;
len--;
eeprom->len++;
}
while (len >= 2) {
err = mv88e6xxx_g2_eeprom_read16(chip, offset >> 1, &val);
if (err)
return err;
*data++ = val & 0xff;
*data++ = (val >> 8) & 0xff;
offset += 2;
len -= 2;
eeprom->len += 2;
}
if (len) {
err = mv88e6xxx_g2_eeprom_read16(chip, offset >> 1, &val);
if (err)
return err;
*data++ = val & 0xff;
offset++;
len--;
eeprom->len++;
}
return 0;
}
int mv88e6xxx_g2_set_eeprom16(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data)
{
unsigned int offset = eeprom->offset;
unsigned int len = eeprom->len;
u16 val;
int err;
/* Ensure the RO WriteEn bit is set */
err = mv88e6xxx_g2_read(chip, GLOBAL2_EEPROM_CMD, &val);
if (err)
return err;
if (!(val & GLOBAL2_EEPROM_CMD_WRITE_EN))
return -EROFS;
eeprom->len = 0;
if (offset & 1) {
err = mv88e6xxx_g2_eeprom_read16(chip, offset >> 1, &val);
if (err)
return err;
val = (*data++ << 8) | (val & 0xff);
err = mv88e6xxx_g2_eeprom_write16(chip, offset >> 1, val);
if (err)
return err;
offset++;
len--;
eeprom->len++;
}
while (len >= 2) {
val = *data++;
val |= *data++ << 8;
err = mv88e6xxx_g2_eeprom_write16(chip, offset >> 1, val);
if (err)
return err;
offset += 2;
len -= 2;
eeprom->len += 2;
}
if (len) {
err = mv88e6xxx_g2_eeprom_read16(chip, offset >> 1, &val);
if (err)
return err;
val = (val & 0xff00) | *data++;
err = mv88e6xxx_g2_eeprom_write16(chip, offset >> 1, val);
if (err)
return err;
offset++;
len--;
eeprom->len++;
}
return 0;
}
/* Offset 0x18: SMI PHY Command Register
* Offset 0x19: SMI PHY Data Register
*/
static int mv88e6xxx_g2_smi_phy_wait(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g2_wait(chip, GLOBAL2_SMI_PHY_CMD,
GLOBAL2_SMI_PHY_CMD_BUSY);
}
static int mv88e6xxx_g2_smi_phy_cmd(struct mv88e6xxx_chip *chip, u16 cmd)
{
int err;
err = mv88e6xxx_g2_write(chip, GLOBAL2_SMI_PHY_CMD, cmd);
if (err)
return err;
return mv88e6xxx_g2_smi_phy_wait(chip);
}
int mv88e6xxx_g2_smi_phy_read(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 *val)
{
u16 cmd = GLOBAL2_SMI_PHY_CMD_OP_22_READ_DATA | (addr << 5) | reg;
int err;
err = mv88e6xxx_g2_smi_phy_wait(chip);
if (err)
return err;
err = mv88e6xxx_g2_smi_phy_cmd(chip, cmd);
if (err)
return err;
return mv88e6xxx_g2_read(chip, GLOBAL2_SMI_PHY_DATA, val);
}
int mv88e6xxx_g2_smi_phy_write(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 val)
{
u16 cmd = GLOBAL2_SMI_PHY_CMD_OP_22_WRITE_DATA | (addr << 5) | reg;
int err;
err = mv88e6xxx_g2_smi_phy_wait(chip);
if (err)
return err;
err = mv88e6xxx_g2_write(chip, GLOBAL2_SMI_PHY_DATA, val);
if (err)
return err;
return mv88e6xxx_g2_smi_phy_cmd(chip, cmd);
}
static void mv88e6xxx_g2_irq_mask(struct irq_data *d)
{
struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d);
unsigned int n = d->hwirq;
chip->g2_irq.masked |= (1 << n);
}
static void mv88e6xxx_g2_irq_unmask(struct irq_data *d)
{
struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d);
unsigned int n = d->hwirq;
chip->g2_irq.masked &= ~(1 << n);
}
static irqreturn_t mv88e6xxx_g2_irq_thread_fn(int irq, void *dev_id)
{
struct mv88e6xxx_chip *chip = dev_id;
unsigned int nhandled = 0;
unsigned int sub_irq;
unsigned int n;
int err;
u16 reg;
mutex_lock(&chip->reg_lock);
err = mv88e6xxx_g2_read(chip, GLOBAL2_INT_SOURCE, &reg);
mutex_unlock(&chip->reg_lock);
if (err)
goto out;
for (n = 0; n < 16; ++n) {
if (reg & (1 << n)) {
sub_irq = irq_find_mapping(chip->g2_irq.domain, n);
handle_nested_irq(sub_irq);
++nhandled;
}
}
out:
return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE);
}
static void mv88e6xxx_g2_irq_bus_lock(struct irq_data *d)
{
struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d);
mutex_lock(&chip->reg_lock);
}
static void mv88e6xxx_g2_irq_bus_sync_unlock(struct irq_data *d)
{
struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d);
mv88e6xxx_g2_write(chip, GLOBAL2_INT_MASK, ~chip->g2_irq.masked);
mutex_unlock(&chip->reg_lock);
}
static struct irq_chip mv88e6xxx_g2_irq_chip = {
.name = "mv88e6xxx-g2",
.irq_mask = mv88e6xxx_g2_irq_mask,
.irq_unmask = mv88e6xxx_g2_irq_unmask,
.irq_bus_lock = mv88e6xxx_g2_irq_bus_lock,
.irq_bus_sync_unlock = mv88e6xxx_g2_irq_bus_sync_unlock,
};
static int mv88e6xxx_g2_irq_domain_map(struct irq_domain *d,
unsigned int irq,
irq_hw_number_t hwirq)
{
struct mv88e6xxx_chip *chip = d->host_data;
irq_set_chip_data(irq, d->host_data);
irq_set_chip_and_handler(irq, &chip->g2_irq.chip, handle_level_irq);
irq_set_noprobe(irq);
return 0;
}
static const struct irq_domain_ops mv88e6xxx_g2_irq_domain_ops = {
.map = mv88e6xxx_g2_irq_domain_map,
.xlate = irq_domain_xlate_twocell,
};
void mv88e6xxx_g2_irq_free(struct mv88e6xxx_chip *chip)
{
int irq, virq;
for (irq = 0; irq < 16; irq++) {
virq = irq_find_mapping(chip->g2_irq.domain, irq);
irq_dispose_mapping(virq);
}
irq_domain_remove(chip->g2_irq.domain);
}
int mv88e6xxx_g2_irq_setup(struct mv88e6xxx_chip *chip)
{
int device_irq;
int err, irq;
if (!chip->dev->of_node)
return -EINVAL;
chip->g2_irq.domain = irq_domain_add_simple(
chip->dev->of_node, 16, 0, &mv88e6xxx_g2_irq_domain_ops, chip);
if (!chip->g2_irq.domain)
return -ENOMEM;
for (irq = 0; irq < 16; irq++)
irq_create_mapping(chip->g2_irq.domain, irq);
chip->g2_irq.chip = mv88e6xxx_g2_irq_chip;
chip->g2_irq.masked = ~0;
device_irq = irq_find_mapping(chip->g1_irq.domain,
GLOBAL_STATUS_IRQ_DEVICE);
if (device_irq < 0) {
err = device_irq;
goto out;
}
err = devm_request_threaded_irq(chip->dev, device_irq, NULL,
mv88e6xxx_g2_irq_thread_fn,
IRQF_ONESHOT, "mv88e6xxx-g1", chip);
if (err)
goto out;
return 0;
out:
mv88e6xxx_g2_irq_free(chip);
return err;
}
int mv88e6xxx_g2_setup(struct mv88e6xxx_chip *chip)
{
u16 reg;
int err;
if (mv88e6xxx_has(chip, MV88E6XXX_FLAG_G2_MGMT_EN_2X)) {
/* Consider the frames with reserved multicast destination
* addresses matching 01:80:c2:00:00:2x as MGMT.
*/
err = mv88e6xxx_g2_write(chip, GLOBAL2_MGMT_EN_2X, 0xffff);
if (err)
return err;
}
if (mv88e6xxx_has(chip, MV88E6XXX_FLAG_G2_MGMT_EN_0X)) {
/* Consider the frames with reserved multicast destination
* addresses matching 01:80:c2:00:00:0x as MGMT.
*/
err = mv88e6xxx_g2_write(chip, GLOBAL2_MGMT_EN_0X, 0xffff);
if (err)
return err;
}
/* Ignore removed tag data on doubly tagged packets, disable
* flow control messages, force flow control priority to the
* highest, and send all special multicast frames to the CPU
* port at the highest priority.
*/
reg = GLOBAL2_SWITCH_MGMT_FORCE_FLOW_CTRL_PRI | (0x7 << 4);
if (mv88e6xxx_has(chip, MV88E6XXX_FLAG_G2_MGMT_EN_0X) ||
mv88e6xxx_has(chip, MV88E6XXX_FLAG_G2_MGMT_EN_2X))
reg |= GLOBAL2_SWITCH_MGMT_RSVD2CPU | 0x7;
err = mv88e6xxx_g2_write(chip, GLOBAL2_SWITCH_MGMT, reg);
if (err)
return err;
/* Program the DSA routing table. */
err = mv88e6xxx_g2_set_device_mapping(chip);
if (err)
return err;
/* Clear all trunk masks and mapping. */
err = mv88e6xxx_g2_clear_trunk(chip);
if (err)
return err;
if (mv88e6xxx_has(chip, MV88E6XXX_FLAGS_IRL)) {
/* Disable ingress rate limiting by resetting all per port
* ingress rate limit resources to their initial state.
*/
err = mv88e6xxx_g2_clear_irl(chip);
if (err)
return err;
}
if (mv88e6xxx_has(chip, MV88E6XXX_FLAGS_PVT)) {
/* Initialize Cross-chip Port VLAN Table to reset defaults */
err = mv88e6xxx_g2_write(chip, GLOBAL2_PVT_ADDR,
GLOBAL2_PVT_ADDR_OP_INIT_ONES);
if (err)
return err;
}
if (mv88e6xxx_has(chip, MV88E6XXX_FLAG_G2_POT)) {
/* Clear the priority override table. */
err = mv88e6xxx_g2_clear_pot(chip);
if (err)
return err;
}
return 0;
}