Commit 0d53072a authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'dsa-mv88e6xxx-port-operation-refine'



Vivien Didelot says:

====================
net: dsa: mv88e6xxx: refine port operations

The Marvell chips have one internal SMI device per port, containing a
set of registers used to configure a port's link, STP state, default
VLAN or addresses database, etc.

This patchset creates port files to implement the port operations as
described in datasheets, and extend the chip ops structure with them.

Patches 1 to 6 implement accessors for port's STP state, port based VLAN
map, default FID, default VID, and 802.1Q mode.

Patches 7 to 11 implement the port's MAC setup of link state, duplex
mode, RGMII delay and speed, all accessed through port's register 0x01.

The new port's MAC setup code is used to re-implement the adjust_link
code and correctly force the link down before changing any of the MAC
settings, as requested by the datasheets.

The port's MAC accessors use values compatible with struct phy_device
(e.g. DUPLEX_FULL) and extend them when needed (e.g. SPEED_MAX).

Changes in v2:

  - Strictly use new _UNFORCED values instead of re-using _UNKNOWN ones.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 5976c5f4 d78343d2
......@@ -2,3 +2,4 @@ obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o
mv88e6xxx-objs := chip.o
mv88e6xxx-objs += global1.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2.o
mv88e6xxx-objs += port.o
This diff is collapsed.
......@@ -61,16 +61,22 @@
#define PORT_PCS_CTRL 0x01
#define PORT_PCS_CTRL_RGMII_DELAY_RXCLK BIT(15)
#define PORT_PCS_CTRL_RGMII_DELAY_TXCLK BIT(14)
#define PORT_PCS_CTRL_FORCE_SPEED BIT(13) /* 6390 */
#define PORT_PCS_CTRL_ALTSPEED BIT(12) /* 6390 */
#define PORT_PCS_CTRL_200BASE BIT(12) /* 6352 */
#define PORT_PCS_CTRL_FC BIT(7)
#define PORT_PCS_CTRL_FORCE_FC BIT(6)
#define PORT_PCS_CTRL_LINK_UP BIT(5)
#define PORT_PCS_CTRL_FORCE_LINK BIT(4)
#define PORT_PCS_CTRL_DUPLEX_FULL BIT(3)
#define PORT_PCS_CTRL_FORCE_DUPLEX BIT(2)
#define PORT_PCS_CTRL_10 0x00
#define PORT_PCS_CTRL_100 0x01
#define PORT_PCS_CTRL_1000 0x02
#define PORT_PCS_CTRL_UNFORCED 0x03
#define PORT_PCS_CTRL_SPEED_MASK (0x03)
#define PORT_PCS_CTRL_SPEED_10 (0x00)
#define PORT_PCS_CTRL_SPEED_100 (0x01)
#define PORT_PCS_CTRL_SPEED_200 (0x02) /* 6065 and non Gb chips */
#define PORT_PCS_CTRL_SPEED_1000 (0x02)
#define PORT_PCS_CTRL_SPEED_10000 (0x03) /* 6390X */
#define PORT_PCS_CTRL_SPEED_UNFORCED (0x03)
#define PORT_PAUSE_CTRL 0x02
#define PORT_SWITCH_ID 0x03
#define PORT_SWITCH_ID_PROD_NUM_6085 0x04a
......@@ -727,6 +733,41 @@ struct mv88e6xxx_ops {
u16 *val);
int (*phy_write)(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 val);
/* RGMII Receive/Transmit Timing Control
* Add delay on PHY_INTERFACE_MODE_RGMII_*ID, no delay otherwise.
*/
int (*port_set_rgmii_delay)(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
#define LINK_FORCED_DOWN 0
#define LINK_FORCED_UP 1
#define LINK_UNFORCED -2
/* Port's MAC link state
* Use LINK_FORCED_UP or LINK_FORCED_DOWN to force link up or down,
* or LINK_UNFORCED for normal link detection.
*/
int (*port_set_link)(struct mv88e6xxx_chip *chip, int port, int link);
#define DUPLEX_UNFORCED -2
/* Port's MAC duplex mode
*
* Use DUPLEX_HALF or DUPLEX_FULL to force half or full duplex,
* or DUPLEX_UNFORCED for normal duplex detection.
*/
int (*port_set_duplex)(struct mv88e6xxx_chip *chip, int port, int dup);
#define SPEED_MAX INT_MAX
#define SPEED_UNFORCED -2
/* Port's MAC speed (in Mbps)
*
* Depending on the chip, 10, 100, 200, 1000, 2500, 10000 are valid.
* Use SPEED_UNFORCED for normal detection, SPEED_MAX for max value.
*/
int (*port_set_speed)(struct mv88e6xxx_chip *chip, int port, int speed);
};
enum stat_type {
......
/*
* Marvell 88E6xxx Switch Port Registers support
*
* 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 "mv88e6xxx.h"
#include "port.h"
int mv88e6xxx_port_read(struct mv88e6xxx_chip *chip, int port, int reg,
u16 *val)
{
int addr = chip->info->port_base_addr + port;
return mv88e6xxx_read(chip, addr, reg, val);
}
int mv88e6xxx_port_write(struct mv88e6xxx_chip *chip, int port, int reg,
u16 val)
{
int addr = chip->info->port_base_addr + port;
return mv88e6xxx_write(chip, addr, reg, val);
}
/* Offset 0x01: MAC (or PCS or Physical) Control Register
*
* Link, Duplex and Flow Control have one force bit, one value bit.
*
* For port's MAC speed, ForceSpd (or SpdValue) bits 1:0 program the value.
* Alternative values require the 200BASE (or AltSpeed) bit 12 set.
* Newer chips need a ForcedSpd bit 13 set to consider the value.
*/
static int mv88e6xxx_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_PCS_CTRL, &reg);
if (err)
return err;
reg &= ~(PORT_PCS_CTRL_RGMII_DELAY_RXCLK |
PORT_PCS_CTRL_RGMII_DELAY_TXCLK);
switch (mode) {
case PHY_INTERFACE_MODE_RGMII_RXID:
reg |= PORT_PCS_CTRL_RGMII_DELAY_RXCLK;
break;
case PHY_INTERFACE_MODE_RGMII_TXID:
reg |= PORT_PCS_CTRL_RGMII_DELAY_TXCLK;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
reg |= PORT_PCS_CTRL_RGMII_DELAY_RXCLK |
PORT_PCS_CTRL_RGMII_DELAY_TXCLK;
break;
default:
/* no delay */
break;
}
err = mv88e6xxx_port_write(chip, port, PORT_PCS_CTRL, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "delay RXCLK %s, TXCLK %s\n",
reg & PORT_PCS_CTRL_RGMII_DELAY_RXCLK ? "yes" : "no",
reg & PORT_PCS_CTRL_RGMII_DELAY_TXCLK ? "yes" : "no");
return 0;
}
int mv88e6352_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
if (port < 5)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_rgmii_delay(chip, port, mode);
}
int mv88e6390_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
if (port != 0)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_rgmii_delay(chip, port, mode);
}
int mv88e6xxx_port_set_link(struct mv88e6xxx_chip *chip, int port, int link)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_PCS_CTRL, &reg);
if (err)
return err;
reg &= ~(PORT_PCS_CTRL_FORCE_LINK | PORT_PCS_CTRL_LINK_UP);
switch (link) {
case LINK_FORCED_DOWN:
reg |= PORT_PCS_CTRL_FORCE_LINK;
break;
case LINK_FORCED_UP:
reg |= PORT_PCS_CTRL_FORCE_LINK | PORT_PCS_CTRL_LINK_UP;
break;
case LINK_UNFORCED:
/* normal link detection */
break;
default:
return -EINVAL;
}
err = mv88e6xxx_port_write(chip, port, PORT_PCS_CTRL, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "%s link %s\n",
reg & PORT_PCS_CTRL_FORCE_LINK ? "Force" : "Unforce",
reg & PORT_PCS_CTRL_LINK_UP ? "up" : "down");
return 0;
}
int mv88e6xxx_port_set_duplex(struct mv88e6xxx_chip *chip, int port, int dup)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_PCS_CTRL, &reg);
if (err)
return err;
reg &= ~(PORT_PCS_CTRL_FORCE_DUPLEX | PORT_PCS_CTRL_DUPLEX_FULL);
switch (dup) {
case DUPLEX_HALF:
reg |= PORT_PCS_CTRL_FORCE_DUPLEX;
break;
case DUPLEX_FULL:
reg |= PORT_PCS_CTRL_FORCE_DUPLEX | PORT_PCS_CTRL_DUPLEX_FULL;
break;
case DUPLEX_UNFORCED:
/* normal duplex detection */
break;
default:
return -EINVAL;
}
err = mv88e6xxx_port_write(chip, port, PORT_PCS_CTRL, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "%s %s duplex\n",
reg & PORT_PCS_CTRL_FORCE_DUPLEX ? "Force" : "Unforce",
reg & PORT_PCS_CTRL_DUPLEX_FULL ? "full" : "half");
return 0;
}
static int mv88e6xxx_port_set_speed(struct mv88e6xxx_chip *chip, int port,
int speed, bool alt_bit, bool force_bit)
{
u16 reg, ctrl;
int err;
switch (speed) {
case 10:
ctrl = PORT_PCS_CTRL_SPEED_10;
break;
case 100:
ctrl = PORT_PCS_CTRL_SPEED_100;
break;
case 200:
if (alt_bit)
ctrl = PORT_PCS_CTRL_SPEED_100 | PORT_PCS_CTRL_ALTSPEED;
else
ctrl = PORT_PCS_CTRL_SPEED_200;
break;
case 1000:
ctrl = PORT_PCS_CTRL_SPEED_1000;
break;
case 2500:
ctrl = PORT_PCS_CTRL_SPEED_1000 | PORT_PCS_CTRL_ALTSPEED;
break;
case 10000:
/* all bits set, fall through... */
case SPEED_UNFORCED:
ctrl = PORT_PCS_CTRL_SPEED_UNFORCED;
break;
default:
return -EOPNOTSUPP;
}
err = mv88e6xxx_port_read(chip, port, PORT_PCS_CTRL, &reg);
if (err)
return err;
reg &= ~PORT_PCS_CTRL_SPEED_MASK;
if (alt_bit)
reg &= ~PORT_PCS_CTRL_ALTSPEED;
if (force_bit) {
reg &= ~PORT_PCS_CTRL_FORCE_SPEED;
if (speed)
ctrl |= PORT_PCS_CTRL_FORCE_SPEED;
}
reg |= ctrl;
err = mv88e6xxx_port_write(chip, port, PORT_PCS_CTRL, reg);
if (err)
return err;
if (speed)
netdev_dbg(chip->ds->ports[port].netdev,
"Speed set to %d Mbps\n", speed);
else
netdev_dbg(chip->ds->ports[port].netdev, "Speed unforced\n");
return 0;
}
/* Support 10, 100, 200 Mbps (e.g. 88E6065 family) */
int mv88e6065_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed)
{
if (speed == SPEED_MAX)
speed = 200;
if (speed > 200)
return -EOPNOTSUPP;
/* Setting 200 Mbps on port 0 to 3 selects 100 Mbps */
return mv88e6xxx_port_set_speed(chip, port, speed, false, false);
}
/* Support 10, 100, 1000 Mbps (e.g. 88E6185 family) */
int mv88e6185_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed)
{
if (speed == SPEED_MAX)
speed = 1000;
if (speed == 200 || speed > 1000)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_speed(chip, port, speed, false, false);
}
/* Support 10, 100, 200, 1000 Mbps (e.g. 88E6352 family) */
int mv88e6352_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed)
{
if (speed == SPEED_MAX)
speed = 1000;
if (speed > 1000)
return -EOPNOTSUPP;
if (speed == 200 && port < 5)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_speed(chip, port, speed, true, false);
}
/* Support 10, 100, 200, 1000, 2500 Mbps (e.g. 88E6390) */
int mv88e6390_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed)
{
if (speed == SPEED_MAX)
speed = port < 9 ? 1000 : 2500;
if (speed > 2500)
return -EOPNOTSUPP;
if (speed == 200 && port != 0)
return -EOPNOTSUPP;
if (speed == 2500 && port < 9)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_speed(chip, port, speed, true, true);
}
/* Support 10, 100, 200, 1000, 2500, 10000 Mbps (e.g. 88E6190X) */
int mv88e6390x_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed)
{
if (speed == SPEED_MAX)
speed = port < 9 ? 1000 : 10000;
if (speed == 200 && port != 0)
return -EOPNOTSUPP;
if (speed >= 2500 && port < 9)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_speed(chip, port, speed, true, true);
}
/* Offset 0x04: Port Control Register */
static const char * const mv88e6xxx_port_state_names[] = {
[PORT_CONTROL_STATE_DISABLED] = "Disabled",
[PORT_CONTROL_STATE_BLOCKING] = "Blocking/Listening",
[PORT_CONTROL_STATE_LEARNING] = "Learning",
[PORT_CONTROL_STATE_FORWARDING] = "Forwarding",
};
int mv88e6xxx_port_set_state(struct mv88e6xxx_chip *chip, int port, u8 state)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_CONTROL, &reg);
if (err)
return err;
reg &= ~PORT_CONTROL_STATE_MASK;
reg |= state;
err = mv88e6xxx_port_write(chip, port, PORT_CONTROL, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "PortState set to %s\n",
mv88e6xxx_port_state_names[state]);
return 0;
}
/* Offset 0x05: Port Control 1 */
/* Offset 0x06: Port Based VLAN Map */
int mv88e6xxx_port_set_vlan_map(struct mv88e6xxx_chip *chip, int port, u16 map)
{
const u16 mask = GENMASK(mv88e6xxx_num_ports(chip) - 1, 0);
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_BASE_VLAN, &reg);
if (err)
return err;
reg &= ~mask;
reg |= map & mask;
err = mv88e6xxx_port_write(chip, port, PORT_BASE_VLAN, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "VLANTable set to %.3x\n",
map);
return 0;
}
int mv88e6xxx_port_get_fid(struct mv88e6xxx_chip *chip, int port, u16 *fid)
{
const u16 upper_mask = (mv88e6xxx_num_databases(chip) - 1) >> 4;
u16 reg;
int err;
/* Port's default FID lower 4 bits are located in reg 0x06, offset 12 */
err = mv88e6xxx_port_read(chip, port, PORT_BASE_VLAN, &reg);
if (err)
return err;
*fid = (reg & 0xf000) >> 12;
/* Port's default FID upper bits are located in reg 0x05, offset 0 */
if (upper_mask) {
err = mv88e6xxx_port_read(chip, port, PORT_CONTROL_1, &reg);
if (err)
return err;
*fid |= (reg & upper_mask) << 4;
}
return 0;
}
int mv88e6xxx_port_set_fid(struct mv88e6xxx_chip *chip, int port, u16 fid)
{
const u16 upper_mask = (mv88e6xxx_num_databases(chip) - 1) >> 4;
u16 reg;
int err;
if (fid >= mv88e6xxx_num_databases(chip))
return -EINVAL;
/* Port's default FID lower 4 bits are located in reg 0x06, offset 12 */
err = mv88e6xxx_port_read(chip, port, PORT_BASE_VLAN, &reg);
if (err)
return err;
reg &= 0x0fff;
reg |= (fid & 0x000f) << 12;
err = mv88e6xxx_port_write(chip, port, PORT_BASE_VLAN, reg);
if (err)
return err;
/* Port's default FID upper bits are located in reg 0x05, offset 0 */
if (upper_mask) {
err = mv88e6xxx_port_read(chip, port, PORT_CONTROL_1, &reg);
if (err)
return err;
reg &= ~upper_mask;
reg |= (fid >> 4) & upper_mask;
err = mv88e6xxx_port_write(chip, port, PORT_CONTROL_1, reg);
if (err)
return err;
}
netdev_dbg(chip->ds->ports[port].netdev, "FID set to %u\n", fid);
return 0;
}
/* Offset 0x07: Default Port VLAN ID & Priority */
int mv88e6xxx_port_get_pvid(struct mv88e6xxx_chip *chip, int port, u16 *pvid)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_DEFAULT_VLAN, &reg);
if (err)
return err;
*pvid = reg & PORT_DEFAULT_VLAN_MASK;
return 0;
}
int mv88e6xxx_port_set_pvid(struct mv88e6xxx_chip *chip, int port, u16 pvid)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_DEFAULT_VLAN, &reg);
if (err)
return err;
reg &= ~PORT_DEFAULT_VLAN_MASK;
reg |= pvid & PORT_DEFAULT_VLAN_MASK;
err = mv88e6xxx_port_write(chip, port, PORT_DEFAULT_VLAN, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "DefaultVID set to %u\n",
pvid);
return 0;
}
/* Offset 0x08: Port Control 2 Register */
static const char * const mv88e6xxx_port_8021q_mode_names[] = {
[PORT_CONTROL_2_8021Q_DISABLED] = "Disabled",
[PORT_CONTROL_2_8021Q_FALLBACK] = "Fallback",
[PORT_CONTROL_2_8021Q_CHECK] = "Check",
[PORT_CONTROL_2_8021Q_SECURE] = "Secure",
};
int mv88e6xxx_port_set_8021q_mode(struct mv88e6xxx_chip *chip, int port,
u16 mode)
{
u16 reg;
int err;
err = mv88e6xxx_port_read(chip, port, PORT_CONTROL_2, &reg);
if (err)
return err;
reg &= ~PORT_CONTROL_2_8021Q_MASK;
reg |= mode & PORT_CONTROL_2_8021Q_MASK;
err = mv88e6xxx_port_write(chip, port, PORT_CONTROL_2, reg);
if (err)
return err;
netdev_dbg(chip->ds->ports[port].netdev, "802.1QMode set to %s\n",
mv88e6xxx_port_8021q_mode_names[mode]);
return 0;
}
/*
* Marvell 88E6xxx Switch Port Registers support
*
* 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.
*/
#ifndef _MV88E6XXX_PORT_H
#define _MV88E6XXX_PORT_H
#include "mv88e6xxx.h"
int mv88e6xxx_port_read(struct mv88e6xxx_chip *chip, int port, int reg,
u16 *val);
int mv88e6xxx_port_write(struct mv88e6xxx_chip *chip, int port, int reg,
u16 val);
int mv88e6352_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6390_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6xxx_port_set_link(struct mv88e6xxx_chip *chip, int port, int link);
int mv88e6xxx_port_set_duplex(struct mv88e6xxx_chip *chip, int port, int dup);
int mv88e6065_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed);
int mv88e6185_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed);
int mv88e6352_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed);
int mv88e6390_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed);
int mv88e6390x_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed);
int mv88e6xxx_port_set_state(struct mv88e6xxx_chip *chip, int port, u8 state);
int mv88e6xxx_port_set_vlan_map(struct mv88e6xxx_chip *chip, int port, u16 map);
int mv88e6xxx_port_get_fid(struct mv88e6xxx_chip *chip, int port, u16 *fid);
int mv88e6xxx_port_set_fid(struct mv88e6xxx_chip *chip, int port, u16 fid);
int mv88e6xxx_port_get_pvid(struct mv88e6xxx_chip *chip, int port, u16 *pvid);
int mv88e6xxx_port_set_pvid(struct mv88e6xxx_chip *chip, int port, u16 pvid);