Merge branch 'dsa-next'

Guenter Roeck says:

====================
net: dsa: Fixes and enhancements

Patch 01/15 addresses a bug indicated by an an annoying and unhelpful
log message.

Patches 02/15 and 03/15 are minor enhancements, adding support for
known switch revisions.

Patches 04/15 and 05/15 add support for MV88E6352 and MV88E6176.

Patch 06/15 adds support for hardware monitoring, specifically for
reporting the chip temperature, to the dsa subsystem.

Patches 07/15 and 08/15 implement hardware monitoring for MV88E6352,
MV88E6176, MV88E6123, MV88E6161, and MV88E6165.

Patch 09/15 and 10/15 add support for EEPROM access to the DSA subsystem.

Patch 11/15 implements EEPROM access for MV88E6352 and MV88E6176.

Patch 12/15 adds support for reading switch registers to the DSA
subsystem.

Patches 13/15 amd 14/15 implement support for reading switch registers
to the drivers for MV88E6352, MV88E6176, MV88E6123, MV88E6161, and MV88E6165.

Patch 15/15 adds support for reading additional RMON registers to the drivers
for  MV88E6352, MV88E6176, MV88E6123, MV88E6161, and MV88E6165.

The series was tested on top of v3.18-rc2 in an x86 system with MV88E6352.
Testing in systems with 88E6131, 88E6060 and MV88E6165 was done earlier
(I don't have access to those systems right now). The series was also build
tested using my build system at http://server.roeck-us.net:8010/builders.
Look into the 'dsa' column for build results.

The series merges cleanly into net-next as of today (10/29).

v3:
- Fix bug in eeprom patches seen if devicetree is enabled:
  eeprom-length property is attached to switch devicetree node,
  not to dsa node, and there was a compile error.
v2:
- Made reporting chip temperatures through the hwmon subsystem optional
  with new Kconfig option
- Changed the hwmon chip name to <network device name>_dsa<index>
- Made EEPROM presence and size configurable through platform and devicetree
  data
- Various minor changes and fixes (see individual patches for details)
====================

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2014-10-30 14:54:21 -04:00
commit d3627795d0
17 changed files with 1201 additions and 18 deletions

View File

@ -10,7 +10,7 @@ Required properties:
- dsa,ethernet : Should be a phandle to a valid Ethernet device node
- dsa,mii-bus : Should be a phandle to a valid MDIO bus device node
Optionnal properties:
Optional properties:
- interrupts : property with a value describing the switch
interrupt number (not supported by the driver)
@ -23,6 +23,13 @@ Each of these switch child nodes should have the following required properties:
- #address-cells : Must be 1
- #size-cells : Must be 0
A switch child node has the following optional property:
- eeprom-length : Set to the length of an EEPROM connected to the
switch. Must be set if the switch can not detect
the presence and/or size of a connected EEPROM,
otherwise optional.
A switch may have multiple "port" children nodes
Each port children node must have the following mandatory properties:

View File

@ -5848,6 +5848,11 @@ M: Russell King <rmk+kernel@arm.linux.org.uk>
S: Maintained
F: drivers/gpu/drm/armada/
MARVELL 88E6352 DSA support
M: Guenter Roeck <linux@roeck-us.net>
S: Maintained
F: drivers/net/dsa/mv88e6352.c
MARVELL GIGABIT ETHERNET DRIVERS (skge/sky2)
M: Mirko Lindner <mlindner@marvell.com>
M: Stephen Hemminger <stephen@networkplumber.org>

View File

@ -45,6 +45,15 @@ config NET_DSA_MV88E6171
This enables support for the Marvell 88E6171 ethernet switch
chip.
config NET_DSA_MV88E6352
tristate "Marvell 88E6176/88E6352 ethernet switch chip support"
select NET_DSA
select NET_DSA_MV88E6XXX
select NET_DSA_TAG_EDSA
---help---
This enables support for the Marvell 88E6176 and 88E6352 ethernet
switch chips.
config NET_DSA_BCM_SF2
tristate "Broadcom Starfighter 2 Ethernet switch support"
depends on HAS_IOMEM

View File

@ -7,6 +7,9 @@ endif
ifdef CONFIG_NET_DSA_MV88E6131
mv88e6xxx_drv-y += mv88e6131.o
endif
ifdef CONFIG_NET_DSA_MV88E6352
mv88e6xxx_drv-y += mv88e6352.o
endif
ifdef CONFIG_NET_DSA_MV88E6171
mv88e6xxx_drv-y += mv88e6171.o
endif

View File

@ -69,8 +69,11 @@ static char *mv88e6060_probe(struct device *host_dev, int sw_addr)
ret = mdiobus_read(bus, sw_addr + REG_PORT(0), 0x03);
if (ret >= 0) {
ret &= 0xfff0;
if (ret == 0x0600)
return "Marvell 88E6060 (A0)";
if (ret == 0x0601 || ret == 0x0602)
return "Marvell 88E6060 (B0)";
if ((ret & 0xfff0) == 0x0600)
return "Marvell 88E6060";
}

View File

@ -291,6 +291,54 @@ static int mv88e6123_61_65_setup_port(struct dsa_switch *ds, int p)
return 0;
}
#ifdef CONFIG_NET_DSA_HWMON
static int mv88e6123_61_65_get_temp(struct dsa_switch *ds, int *temp)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int ret;
int val;
*temp = 0;
mutex_lock(&ps->phy_mutex);
ret = mv88e6xxx_phy_write(ds, 0x0, 0x16, 0x6);
if (ret < 0)
goto error;
/* Enable temperature sensor */
ret = mv88e6xxx_phy_read(ds, 0x0, 0x1a);
if (ret < 0)
goto error;
ret = mv88e6xxx_phy_write(ds, 0x0, 0x1a, ret | (1 << 5));
if (ret < 0)
goto error;
/* Wait for temperature to stabilize */
usleep_range(10000, 12000);
val = mv88e6xxx_phy_read(ds, 0x0, 0x1a);
if (val < 0) {
ret = val;
goto error;
}
/* Disable temperature sensor */
ret = mv88e6xxx_phy_write(ds, 0x0, 0x1a, ret & ~(1 << 5));
if (ret < 0)
goto error;
*temp = ((val & 0x1f) - 5) * 5;
error:
mv88e6xxx_phy_write(ds, 0x0, 0x16, 0x0);
mutex_unlock(&ps->phy_mutex);
return ret;
}
#endif /* CONFIG_NET_DSA_HWMON */
static int mv88e6123_61_65_setup(struct dsa_switch *ds)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
@ -299,6 +347,7 @@ static int mv88e6123_61_65_setup(struct dsa_switch *ds)
mutex_init(&ps->smi_mutex);
mutex_init(&ps->stats_mutex);
mutex_init(&ps->phy_mutex);
ret = mv88e6123_61_65_switch_reset(ds);
if (ret < 0)
@ -329,16 +378,28 @@ static int mv88e6123_61_65_port_to_phy_addr(int port)
static int
mv88e6123_61_65_phy_read(struct dsa_switch *ds, int port, int regnum)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int addr = mv88e6123_61_65_port_to_phy_addr(port);
return mv88e6xxx_phy_read(ds, addr, regnum);
int ret;
mutex_lock(&ps->phy_mutex);
ret = mv88e6xxx_phy_read(ds, addr, regnum);
mutex_unlock(&ps->phy_mutex);
return ret;
}
static int
mv88e6123_61_65_phy_write(struct dsa_switch *ds,
int port, int regnum, u16 val)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int addr = mv88e6123_61_65_port_to_phy_addr(port);
return mv88e6xxx_phy_write(ds, addr, regnum, val);
int ret;
mutex_lock(&ps->phy_mutex);
ret = mv88e6xxx_phy_write(ds, addr, regnum, val);
mutex_unlock(&ps->phy_mutex);
return ret;
}
static struct mv88e6xxx_hw_stat mv88e6123_61_65_hw_stats[] = {
@ -372,6 +433,9 @@ static struct mv88e6xxx_hw_stat mv88e6123_61_65_hw_stats[] = {
{ "hist_256_511bytes", 4, 0x0b, },
{ "hist_512_1023bytes", 4, 0x0c, },
{ "hist_1024_max_bytes", 4, 0x0d, },
{ "sw_in_discards", 4, 0x110, },
{ "sw_in_filtered", 2, 0x112, },
{ "sw_out_filtered", 2, 0x113, },
};
static void
@ -406,6 +470,11 @@ struct dsa_switch_driver mv88e6123_61_65_switch_driver = {
.get_strings = mv88e6123_61_65_get_strings,
.get_ethtool_stats = mv88e6123_61_65_get_ethtool_stats,
.get_sset_count = mv88e6123_61_65_get_sset_count,
#ifdef CONFIG_NET_DSA_HWMON
.get_temp = mv88e6123_61_65_get_temp,
#endif
.get_regs_len = mv88e6xxx_get_regs_len,
.get_regs = mv88e6xxx_get_regs,
};
MODULE_ALIAS("platform:mv88e6123");

View File

@ -21,6 +21,7 @@
#define ID_6085 0x04a0
#define ID_6095 0x0950
#define ID_6131 0x1060
#define ID_6131_B2 0x1066
static char *mv88e6131_probe(struct device *host_dev, int sw_addr)
{
@ -32,12 +33,15 @@ static char *mv88e6131_probe(struct device *host_dev, int sw_addr)
ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), 0x03);
if (ret >= 0) {
ret &= 0xfff0;
if (ret == ID_6085)
int ret_masked = ret & 0xfff0;
if (ret_masked == ID_6085)
return "Marvell 88E6085";
if (ret == ID_6095)
if (ret_masked == ID_6095)
return "Marvell 88E6095/88E6095F";
if (ret == ID_6131)
if (ret == ID_6131_B2)
return "Marvell 88E6131 (B2)";
if (ret_masked == ID_6131)
return "Marvell 88E6131";
}

788
drivers/net/dsa/mv88e6352.c Normal file
View File

@ -0,0 +1,788 @@
/*
* net/dsa/mv88e6352.c - Marvell 88e6352 switch chip support
*
* Copyright (c) 2014 Guenter Roeck
*
* Derived from mv88e6123_61_65.c
* Copyright (c) 2008-2009 Marvell Semiconductor
*
* 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/delay.h>
#include <linux/jiffies.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/platform_device.h>
#include <linux/phy.h>
#include <net/dsa.h>
#include "mv88e6xxx.h"
static int mv88e6352_wait(struct dsa_switch *ds, int reg, u16 mask)
{
unsigned long timeout = jiffies + HZ / 10;
while (time_before(jiffies, timeout)) {
int ret;
ret = REG_READ(REG_GLOBAL2, reg);
if (ret < 0)
return ret;
if (!(ret & mask))
return 0;
usleep_range(1000, 2000);
}
return -ETIMEDOUT;
}
static inline int mv88e6352_phy_wait(struct dsa_switch *ds)
{
return mv88e6352_wait(ds, 0x18, 0x8000);
}
static inline int mv88e6352_eeprom_load_wait(struct dsa_switch *ds)
{
return mv88e6352_wait(ds, 0x14, 0x0800);
}
static inline int mv88e6352_eeprom_busy_wait(struct dsa_switch *ds)
{
return mv88e6352_wait(ds, 0x14, 0x8000);
}
static int __mv88e6352_phy_read(struct dsa_switch *ds, int addr, int regnum)
{
int ret;
REG_WRITE(REG_GLOBAL2, 0x18, 0x9800 | (addr << 5) | regnum);
ret = mv88e6352_phy_wait(ds);
if (ret < 0)
return ret;
return REG_READ(REG_GLOBAL2, 0x19);
}
static int __mv88e6352_phy_write(struct dsa_switch *ds, int addr, int regnum,
u16 val)
{
REG_WRITE(REG_GLOBAL2, 0x19, val);
REG_WRITE(REG_GLOBAL2, 0x18, 0x9400 | (addr << 5) | regnum);
return mv88e6352_phy_wait(ds);
}
static char *mv88e6352_probe(struct device *host_dev, int sw_addr)
{
struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev);
int ret;
if (bus == NULL)
return NULL;
ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), 0x03);
if (ret >= 0) {
if ((ret & 0xfff0) == 0x1760)
return "Marvell 88E6176";
if (ret == 0x3521)
return "Marvell 88E6352 (A0)";
if (ret == 0x3522)
return "Marvell 88E6352 (A1)";
if ((ret & 0xfff0) == 0x3520)
return "Marvell 88E6352";
}
return NULL;
}
static int mv88e6352_switch_reset(struct dsa_switch *ds)
{
unsigned long timeout;
int ret;
int i;
/* Set all ports to the disabled state. */
for (i = 0; i < 7; i++) {
ret = REG_READ(REG_PORT(i), 0x04);
REG_WRITE(REG_PORT(i), 0x04, ret & 0xfffc);
}
/* Wait for transmit queues to drain. */
usleep_range(2000, 4000);
/* Reset the switch. Keep PPU active (bit 14, undocumented).
* The PPU needs to be active to support indirect phy register
* accesses through global registers 0x18 and 0x19.
*/
REG_WRITE(REG_GLOBAL, 0x04, 0xc000);
/* Wait up to one second for reset to complete. */
timeout = jiffies + 1 * HZ;
while (time_before(jiffies, timeout)) {
ret = REG_READ(REG_GLOBAL, 0x00);
if ((ret & 0x8800) == 0x8800)
break;
usleep_range(1000, 2000);
}
if (time_after(jiffies, timeout))
return -ETIMEDOUT;
return 0;
}
static int mv88e6352_setup_global(struct dsa_switch *ds)
{
int ret;
int i;
/* Discard packets with excessive collisions,
* mask all interrupt sources, enable PPU (bit 14, undocumented).
*/
REG_WRITE(REG_GLOBAL, 0x04, 0x6000);
/* Set the default address aging time to 5 minutes, and
* enable address learn messages to be sent to all message
* ports.
*/
REG_WRITE(REG_GLOBAL, 0x0a, 0x0148);
/* Configure the priority mapping registers. */
ret = mv88e6xxx_config_prio(ds);
if (ret < 0)
return ret;
/* Configure the upstream port, and configure the upstream
* port as the port to which ingress and egress monitor frames
* are to be sent.
*/
REG_WRITE(REG_GLOBAL, 0x1a, (dsa_upstream_port(ds) * 0x1110));
/* Disable remote management for now, and set the switch's
* DSA device number.
*/
REG_WRITE(REG_GLOBAL, 0x1c, ds->index & 0x1f);
/* Send all frames with destination addresses matching
* 01:80:c2:00:00:2x to the CPU port.
*/
REG_WRITE(REG_GLOBAL2, 0x02, 0xffff);
/* Send all frames with destination addresses matching
* 01:80:c2:00:00:0x to the CPU port.
*/
REG_WRITE(REG_GLOBAL2, 0x03, 0xffff);
/* Disable the loopback filter, disable flow control
* messages, disable flood broadcast override, disable
* removing of provider tags, disable ATU age violation
* interrupts, disable tag flow control, force flow
* control priority to the highest, and send all special
* multicast frames to the CPU at the highest priority.
*/
REG_WRITE(REG_GLOBAL2, 0x05, 0x00ff);
/* Program the DSA routing table. */
for (i = 0; i < 32; i++) {
int nexthop = 0x1f;
if (i != ds->index && i < ds->dst->pd->nr_chips)
nexthop = ds->pd->rtable[i] & 0x1f;
REG_WRITE(REG_GLOBAL2, 0x06, 0x8000 | (i << 8) | nexthop);
}
/* Clear all trunk masks. */
for (i = 0; i < 8; i++)
REG_WRITE(REG_GLOBAL2, 0x07, 0x8000 | (i << 12) | 0x7f);
/* Clear all trunk mappings. */
for (i = 0; i < 16; i++)
REG_WRITE(REG_GLOBAL2, 0x08, 0x8000 | (i << 11));
/* Disable ingress rate limiting by resetting all ingress
* rate limit registers to their initial state.
*/
for (i = 0; i < 7; i++)
REG_WRITE(REG_GLOBAL2, 0x09, 0x9000 | (i << 8));
/* Initialise cross-chip port VLAN table to reset defaults. */
REG_WRITE(REG_GLOBAL2, 0x0b, 0x9000);
/* Clear the priority override table. */
for (i = 0; i < 16; i++)
REG_WRITE(REG_GLOBAL2, 0x0f, 0x8000 | (i << 8));
/* @@@ initialise AVB (22/23) watchdog (27) sdet (29) registers */
return 0;
}
static int mv88e6352_setup_port(struct dsa_switch *ds, int p)
{
int addr = REG_PORT(p);
u16 val;
/* MAC Forcing register: don't force link, speed, duplex
* or flow control state to any particular values on physical
* ports, but force the CPU port and all DSA ports to 1000 Mb/s
* full duplex.
*/
if (dsa_is_cpu_port(ds, p) || ds->dsa_port_mask & (1 << p))
REG_WRITE(addr, 0x01, 0x003e);
else
REG_WRITE(addr, 0x01, 0x0003);
/* Do not limit the period of time that this port can be
* paused for by the remote end or the period of time that
* this port can pause the remote end.
*/
REG_WRITE(addr, 0x02, 0x0000);
/* Port Control: disable Drop-on-Unlock, disable Drop-on-Lock,
* disable Header mode, enable IGMP/MLD snooping, disable VLAN
* tunneling, determine priority by looking at 802.1p and IP
* priority fields (IP prio has precedence), and set STP state
* to Forwarding.
*
* If this is the CPU link, use DSA or EDSA tagging depending
* on which tagging mode was configured.
*
* If this is a link to another switch, use DSA tagging mode.
*
* If this is the upstream port for this switch, enable
* forwarding of unknown unicasts and multicasts.
*/
val = 0x0433;
if (dsa_is_cpu_port(ds, p)) {
if (ds->dst->tag_protocol == DSA_TAG_PROTO_EDSA)
val |= 0x3300;
else
val |= 0x0100;
}
if (ds->dsa_port_mask & (1 << p))
val |= 0x0100;
if (p == dsa_upstream_port(ds))
val |= 0x000c;
REG_WRITE(addr, 0x04, val);
/* Port Control 1: disable trunking. Also, if this is the
* CPU port, enable learn messages to be sent to this port.
*/
REG_WRITE(addr, 0x05, dsa_is_cpu_port(ds, p) ? 0x8000 : 0x0000);
/* Port based VLAN map: give each port its own address
* database, allow the CPU port to talk to each of the 'real'
* ports, and allow each of the 'real' ports to only talk to
* the upstream port.
*/
val = (p & 0xf) << 12;
if (dsa_is_cpu_port(ds, p))
val |= ds->phys_port_mask;
else
val |= 1 << dsa_upstream_port(ds);
REG_WRITE(addr, 0x06, val);
/* Default VLAN ID and priority: don't set a default VLAN
* ID, and set the default packet priority to zero.
*/
REG_WRITE(addr, 0x07, 0x0000);
/* Port Control 2: don't force a good FCS, set the maximum
* frame size to 10240 bytes, don't let the switch add or
* strip 802.1q tags, don't discard tagged or untagged frames
* on this port, do a destination address lookup on all
* received packets as usual, disable ARP mirroring and don't
* send a copy of all transmitted/received frames on this port
* to the CPU.
*/
REG_WRITE(addr, 0x08, 0x2080);
/* Egress rate control: disable egress rate control. */
REG_WRITE(addr, 0x09, 0x0001);
/* Egress rate control 2: disable egress rate control. */
REG_WRITE(addr, 0x0a, 0x0000);
/* Port Association Vector: when learning source addresses
* of packets, add the address to the address database using
* a port bitmap that has only the bit for this port set and
* the other bits clear.
*/
REG_WRITE(addr, 0x0b, 1 << p);
/* Port ATU control: disable limiting the number of address
* database entries that this port is allowed to use.
*/
REG_WRITE(addr, 0x0c, 0x0000);
/* Priority Override: disable DA, SA and VTU priority override. */
REG_WRITE(addr, 0x0d, 0x0000);
/* Port Ethertype: use the Ethertype DSA Ethertype value. */
REG_WRITE(addr, 0x0f, ETH_P_EDSA);
/* Tag Remap: use an identity 802.1p prio -> switch prio
* mapping.
*/
REG_WRITE(addr, 0x18, 0x3210);
/* Tag Remap 2: use an identity 802.1p prio -> switch prio
* mapping.
*/
REG_WRITE(addr, 0x19, 0x7654);
return 0;
}
#ifdef CONFIG_NET_DSA_HWMON
static int mv88e6352_phy_page_read(struct dsa_switch *ds,
int port, int page, int reg)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int ret;
mutex_lock(&ps->phy_mutex);
ret = __mv88e6352_phy_write(ds, port, 0x16, page);
if (ret < 0)
goto error;
ret = __mv88e6352_phy_read(ds, port, reg);
error:
__mv88e6352_phy_write(ds, port, 0x16, 0x0);
mutex_unlock(&ps->phy_mutex);
return ret;
}
static int mv88e6352_phy_page_write(struct dsa_switch *ds,
int port, int page, int reg, int val)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int ret;
mutex_lock(&ps->phy_mutex);
ret = __mv88e6352_phy_write(ds, port, 0x16, page);
if (ret < 0)
goto error;
ret = __mv88e6352_phy_write(ds, port, reg, val);
error:
__mv88e6352_phy_write(ds, port, 0x16, 0x0);
mutex_unlock(&ps->phy_mutex);
return ret;
}
static int mv88e6352_get_temp(struct dsa_switch *ds, int *temp)
{
int ret;
*temp = 0;
ret = mv88e6352_phy_page_read(ds, 0, 6, 27);
if (ret < 0)
return ret;
*temp = (ret & 0xff) - 25;
return 0;
}
static int mv88e6352_get_temp_limit(struct dsa_switch *ds, int *temp)
{
int ret;
*temp = 0;
ret = mv88e6352_phy_page_read(ds, 0, 6, 26);
if (ret < 0)
return ret;
*temp = (((ret >> 8) & 0x1f) * 5) - 25;
return 0;
}
static int mv88e6352_set_temp_limit(struct dsa_switch *ds, int temp)
{
int ret;
ret = mv88e6352_phy_page_read(ds, 0, 6, 26);
if (ret < 0)
return ret;
temp = clamp_val(DIV_ROUND_CLOSEST(temp, 5) + 5, 0, 0x1f);
return mv88e6352_phy_page_write(ds, 0, 6, 26,
(ret & 0xe0ff) | (temp << 8));
}
static int mv88e6352_get_temp_alarm(struct dsa_switch *ds, bool *alarm)
{
int ret;
*alarm = false;
ret = mv88e6352_phy_page_read(ds, 0, 6, 26);
if (ret < 0)
return ret;
*alarm = !!(ret & 0x40);
return 0;
}
#endif /* CONFIG_NET_DSA_HWMON */
static int mv88e6352_setup(struct dsa_switch *ds)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int ret;
int i;
mutex_init(&ps->smi_mutex);
mutex_init(&ps->stats_mutex);
mutex_init(&ps->phy_mutex);
mutex_init(&ps->eeprom_mutex);
ps->id = REG_READ(REG_PORT(0), 0x03) & 0xfff0;
ret = mv88e6352_switch_reset(ds);
if (ret < 0)
return ret;
/* @@@ initialise vtu and atu */
ret = mv88e6352_setup_global(ds);
if (ret < 0)
return ret;
for (i = 0; i < 7; i++) {
ret = mv88e6352_setup_port(ds, i);
if (ret < 0)
return ret;
}
return 0;
}
static int mv88e6352_port_to_phy_addr(int port)
{
if (port >= 0 && port <= 4)
return port;
return -EINVAL;
}
static int
mv88e6352_phy_read(struct dsa_switch *ds, int port, int regnum)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int addr = mv88e6352_port_to_phy_addr(port);
int ret;
if (addr < 0)
return addr;
mutex_lock(&ps->phy_mutex);
ret = __mv88e6352_phy_read(ds, addr, regnum);
mutex_unlock(&ps->phy_mutex);
return ret;
}
static int
mv88e6352_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int addr = mv88e6352_port_to_phy_addr(port);
int ret;
if (addr < 0)
return addr;
mutex_lock(&ps->phy_mutex);
ret = __mv88e6352_phy_write(ds, addr, regnum, val);
mutex_unlock(&ps->phy_mutex);
return ret;
}
static struct mv88e6xxx_hw_stat mv88e6352_hw_stats[] = {
{ "in_good_octets", 8, 0x00, },
{ "in_bad_octets", 4, 0x02, },
{ "in_unicast", 4, 0x04, },
{ "in_broadcasts", 4, 0x06, },
{ "in_multicasts", 4, 0x07, },
{ "in_pause", 4, 0x16, },
{ "in_undersize", 4, 0x18, },
{ "in_fragments", 4, 0x19, },
{ "in_oversize", 4, 0x1a, },
{ "in_jabber", 4, 0x1b, },
{ "in_rx_error", 4, 0x1c, },
{ "in_fcs_error", 4, 0x1d, },
{ "out_octets", 8, 0x0e, },
{ "out_unicast", 4, 0x10, },
{ "out_broadcasts", 4, 0x13, },
{ "out_multicasts", 4, 0x12, },
{ "out_pause", 4, 0x15, },
{ "excessive", 4, 0x11, },
{ "collisions", 4, 0x1e, },
{ "deferred", 4, 0x05, },
{ "single", 4, 0x14, },
{ "multiple", 4, 0x17, },
{ "out_fcs_error", 4, 0x03, },
{ "late", 4, 0x1f, },
{ "hist_64bytes", 4, 0x08, },
{ "hist_65_127bytes", 4, 0x09, },
{ "hist_128_255bytes", 4, 0x0a, },
{ "hist_256_511bytes", 4, 0x0b, },
{ "hist_512_1023bytes", 4, 0x0c, },
{ "hist_1024_max_bytes", 4, 0x0d, },
{ "sw_in_discards", 4, 0x110, },
{ "sw_in_filtered", 2, 0x112, },
{ "sw_out_filtered", 2, 0x113, },
};
static int mv88e6352_read_eeprom_word(struct dsa_switch *ds, int addr)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int ret;
mutex_lock(&ps->eeprom_mutex);
ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x14,
0xc000 | (addr & 0xff));
if (ret < 0)
goto error;
ret = mv88e6352_eeprom_busy_wait(ds);
if (ret < 0)
goto error;
ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2, 0x15);
error:
mutex_unlock(&ps->eeprom_mutex);
return ret;
}
static int mv88e6352_get_eeprom(struct dsa_switch *ds,
struct ethtool_eeprom *eeprom, u8 *data)
{
int offset;
int len;
int ret;
offset = eeprom->offset;
len = eeprom->len;
eeprom->len = 0;
eeprom->magic = 0xc3ec4951;
ret = mv88e6352_eeprom_load_wait(ds);
if (ret < 0)
return ret;
if (offset & 1) {
int word;
word = mv88e6352_read_eeprom_word(ds, offset >> 1);
if (word < 0)
return word;
*data++ = (word >> 8) & 0xff;
offset++;
len--;
eeprom->len++;
}
while (len >= 2) {
int word;
word = mv88e6352_read_eeprom_word(ds, offset >> 1);
if (word < 0)
return word;
*data++ = word & 0xff;
*data++ = (word >> 8) & 0xff;
offset += 2;
len -= 2;
eeprom->len += 2;
}
if (len) {
int word;
word = mv88e6352_read_eeprom_word(ds, offset >> 1);
if (word < 0)
return word;
*data++ = word & 0xff;
offset++;
len--;
eeprom->len++;
}
return 0;
}
static int mv88e6352_eeprom_is_readonly(struct dsa_switch *ds)
{
int ret;
ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2, 0x14);
if (ret < 0)
return ret;
if (!(ret & 0x0400))
return -EROFS;
return 0;
}
static int mv88e6352_write_eeprom_word(struct dsa_switch *ds, int addr,
u16 data)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
int ret;
mutex_lock(&ps->eeprom_mutex);
ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x15, data);
if (ret < 0)
goto error;
ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x14,
0xb000 | (addr & 0xff));
if (ret < 0)
goto error;
ret = mv88e6352_eeprom_busy_wait(ds);
error:
mutex_unlock(&ps->eeprom_mutex);
return ret;
}
static int mv88e6352_set_eeprom(struct dsa_switch *ds,
struct ethtool_eeprom *eeprom, u8 *data)
{
int offset;
int ret;
int len;
if (eeprom->magic != 0xc3ec4951)
return -EINVAL;
ret = mv88e6352_eeprom_is_readonly(ds);
if (ret)
return ret;
offset = eeprom->offset;
len = eeprom->len;
eeprom->len = 0;
ret = mv88e6352_eeprom_load_wait(ds);
if (ret < 0)
return ret;
if (offset & 1) {
int word;
word = mv88e6352_read_eeprom_word(ds, offset >> 1);
if (word < 0)
return word;
word = (*data++ << 8) | (word & 0xff);
ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word);
if (ret < 0)
return ret;
offset++;
len--;
eeprom->len++;
}
while (len >= 2) {
int word;
word = *data++;
word |= *data++ << 8;
ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word);
if (ret < 0)
return ret;
offset += 2;
len -= 2;
eeprom->len += 2;
}
if (len) {
int word;
word = mv88e6352_read_eeprom_word(ds, offset >> 1);
if (word < 0)
return word;
word = (word & 0xff00) | *data++;
ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word);
if (ret < 0)
return ret;
offset++;
len--;
eeprom->len++;
}
return 0;
}
static void
mv88e6352_get_strings(struct dsa_switch *ds, int port, uint8_t *data)
{
mv88e6xxx_get_strings(ds, ARRAY_SIZE(mv88e6352_hw_stats),
mv88e6352_hw_stats, port, data);
}
static void
mv88e6352_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
{
mv88e6xxx_get_ethtool_stats(ds, ARRAY_SIZE(mv88e6352_hw_stats),
mv88e6352_hw_stats, port, data);
}
static int mv88e6352_get_sset_count(struct dsa_switch *ds)
{
return ARRAY_SIZE(mv88e6352_hw_stats);
}
struct dsa_switch_driver mv88e6352_switch_driver = {
.tag_protocol = DSA_TAG_PROTO_EDSA,
.priv_size = sizeof(struct mv88e6xxx_priv_state),
.probe = mv88e6352_probe,
.setup = mv88e6352_setup,
.set_addr = mv88e6xxx_set_addr_indirect,
.phy_read = mv88e6352_phy_read,
.phy_write = mv88e6352_phy_write,
.poll_link = mv88e6xxx_poll_link,
.get_strings = mv88e6352_get_strings,
.get_ethtool_stats = mv88e6352_get_ethtool_stats,
.get_sset_count = mv88e6352_get_sset_count,
#ifdef CONFIG_NET_DSA_HWMON
.get_temp = mv88e6352_get_temp,
.get_temp_limit = mv88e6352_get_temp_limit,
.set_temp_limit = mv88e6352_set_temp_limit,
.get_temp_alarm = mv88e6352_get_temp_alarm,
#endif
.get_eeprom = mv88e6352_get_eeprom,
.set_eeprom = mv88e6352_set_eeprom,
.get_regs_len = mv88e6xxx_get_regs_len,
.get_regs = mv88e6xxx_get_regs,
};
MODULE_ALIAS("platform:mv88e6352");

View File

@ -485,20 +485,60 @@ void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds,
for (i = 0; i < nr_stats; i++) {
struct mv88e6xxx_hw_stat *s = stats + i;
u32 low;
u32 high;
u32 high = 0;
if (s->reg >= 0x100) {
int ret;
ret = mv88e6xxx_reg_read(ds, REG_PORT(port),
s->reg - 0x100);
if (ret < 0)
goto error;
low = ret;
if (s->sizeof_stat == 4) {
ret = mv88e6xxx_reg_read(ds, REG_PORT(port),
s->reg - 0x100 + 1);
if (ret < 0)
goto error;
high = ret;
}
data[i] = (((u64)high) << 16) | low;
continue;
}
mv88e6xxx_stats_read(ds, s->reg, &low);
if (s->sizeof_stat == 8)
mv88e6xxx_stats_read(ds, s->reg + 1, &high);
else
high = 0;
data[i] = (((u64)high) << 32) | low;
}
error:
mutex_unlock(&ps->stats_mutex);
}
int mv88e6xxx_get_regs_len(struct dsa_switch *ds, int port)
{
return 32 * sizeof(u16);
}
void mv88e6xxx_get_regs(struct dsa_switch *ds, int port,
struct ethtool_regs *regs, void *_p)
{
u16 *p = _p;
int i;
regs->version = 0;
memset(p, 0xff, 32 * sizeof(u16));
for (i = 0; i < 32; i++) {
int ret;
ret = mv88e6xxx_reg_read(ds, REG_PORT(port), i);
if (ret >= 0)
p[i] = ret;
}
}
static int __init mv88e6xxx_init(void)
{
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6131)
@ -507,6 +547,9 @@ static int __init mv88e6xxx_init(void)
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6123_61_65)
register_switch_driver(&mv88e6123_61_65_switch_driver);
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6352)
register_switch_driver(&mv88e6352_switch_driver);
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6171)
register_switch_driver(&mv88e6171_switch_driver);
#endif

View File

@ -37,6 +37,17 @@ struct mv88e6xxx_priv_state {
*/
struct mutex stats_mutex;
/* This mutex serializes phy access for chips with
* indirect phy addressing. It is unused for chips
* with direct phy access.
*/
struct mutex phy_mutex;
/* This mutex serializes eeprom access for chips with
* eeprom support.
*/
struct mutex eeprom_mutex;
int id; /* switch product id */
};
@ -67,9 +78,13 @@ void mv88e6xxx_get_strings(struct dsa_switch *ds,
void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds,
int nr_stats, struct mv88e6xxx_hw_stat *stats,
int port, uint64_t *data);
int mv88e6xxx_get_regs_len(struct dsa_switch *ds, int port);
void mv88e6xxx_get_regs(struct dsa_switch *ds, int port,
struct ethtool_regs *regs, void *_p);
extern struct dsa_switch_driver mv88e6131_switch_driver;
extern struct dsa_switch_driver mv88e6123_61_65_switch_driver;
extern struct dsa_switch_driver mv88e6352_switch_driver;
extern struct dsa_switch_driver mv88e6171_switch_driver;
#define REG_READ(addr, reg) \

View File

@ -38,6 +38,9 @@ struct dsa_chip_data {
struct device *host_dev;
int sw_addr;
/* set to size of eeprom if supported by the switch */
int eeprom_len;
/* Device tree node pointer for this specific switch chip
* used during switch setup in case additional properties
* and resources needs to be used
@ -139,6 +142,14 @@ struct dsa_switch {
*/
struct device *master_dev;
#ifdef CONFIG_NET_DSA_HWMON
/*
* Hardware monitoring information
*/
char hwmon_name[IFNAMSIZ + 8];
struct device *hwmon_dev;
#endif
/*
* Slave mii_bus and devices for the individual ports.
*/
@ -242,6 +253,28 @@ struct dsa_switch_driver {
struct ethtool_eee *e);
int (*get_eee)(struct dsa_switch *ds, int port,
struct ethtool_eee *e);
#ifdef CONFIG_NET_DSA_HWMON
/* Hardware monitoring */
int (*get_temp)(struct dsa_switch *ds, int *temp);
int (*get_temp_limit)(struct dsa_switch *ds, int *temp);
int (*set_temp_limit)(struct dsa_switch *ds, int temp);
int (*get_temp_alarm)(struct dsa_switch *ds, bool *alarm);
#endif
/* EEPROM access */
int (*get_eeprom_len)(struct dsa_switch *ds);
int (*get_eeprom)(struct dsa_switch *ds,
struct ethtool_eeprom *eeprom, u8 *data);
int (*set_eeprom)(struct dsa_switch *ds,
struct ethtool_eeprom *eeprom, u8 *data);
/*
* Register access.
*/
int (*get_regs_len)(struct dsa_switch *ds, int port);
void (*get_regs)(struct dsa_switch *ds, int port,
struct ethtool_regs *regs, void *p);
};
void register_switch_driver(struct dsa_switch_driver *type);

View File

@ -11,6 +11,17 @@ config NET_DSA
if NET_DSA
config NET_DSA_HWMON
bool "Distributed Switch Architecture HWMON support"
default y
depends on HWMON && !(NET_DSA=y && HWMON=m)
---help---
Say Y if you want to expose thermal sensor data on switches supported
by the Distributed Switch Architecture.
Some of those switches contain thermal sensors. This data is available
via the hwmon sysfs interface and exposes the onboard sensors.
# tagging formats
config NET_DSA_TAG_BRCM
bool

View File

@ -9,6 +9,9 @@
* (at your option) any later version.
*/
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/list.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
@ -17,6 +20,7 @@
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#include <linux/sysfs.h>
#include "dsa_priv.h"
char dsa_driver_version[] = "0.1";
@ -71,6 +75,104 @@ dsa_switch_probe(struct device *host_dev, int sw_addr, char **_name)
return ret;
}
/* hwmon support ************************************************************/
#ifdef CONFIG_NET_DSA_HWMON
static ssize_t temp1_input_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dsa_switch *ds = dev_get_drvdata(dev);
int temp, ret;
ret = ds->drv->get_temp(ds, &temp);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", temp * 1000);
}
static DEVICE_ATTR_RO(temp1_input);
static ssize_t temp1_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dsa_switch *ds = dev_get_drvdata(dev);
int temp, ret;
ret = ds->drv->get_temp_limit(ds, &temp);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", temp * 1000);
}
static ssize_t temp1_max_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct dsa_switch *ds = dev_get_drvdata(dev);
int temp, ret;
ret = kstrtoint(buf, 0, &temp);
if (ret < 0)
return ret;
ret = ds->drv->set_temp_limit(ds, DIV_ROUND_CLOSEST(temp, 1000));
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR(temp1_max, S_IRUGO, temp1_max_show, temp1_max_store);
static ssize_t temp1_max_alarm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dsa_switch *ds = dev_get_drvdata(dev);
bool alarm;
int ret;
ret = ds->drv->get_temp_alarm(ds, &alarm);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", alarm);
}
static DEVICE_ATTR_RO(temp1_max_alarm);
static struct attribute *dsa_hwmon_attrs[] = {
&dev_attr_temp1_input.attr, /* 0 */
&dev_attr_temp1_max.attr, /* 1 */
&dev_attr_temp1_max_alarm.attr, /* 2 */
NULL
};
static umode_t dsa_hwmon_attrs_visible(struct kobject *kobj,
struct attribute *attr, int index)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct dsa_switch *ds = dev_get_drvdata(dev);
struct dsa_switch_driver *drv = ds->drv;
umode_t mode = attr->mode;
if (index == 1) {
if (!drv->get_temp_limit)
mode = 0;
else if (drv->set_temp_limit)
mode |= S_IWUSR;
} else if (index == 2 && !drv->get_temp_alarm) {
mode = 0;
}
return mode;
}
static const struct attribute_group dsa_hwmon_group = {
.attrs = dsa_hwmon_attrs,
.is_visible = dsa_hwmon_attrs_visible,
};
__ATTRIBUTE_GROUPS(dsa_hwmon);
#endif /* CONFIG_NET_DSA_HWMON */
/* basic switch operations **************************************************/
static struct dsa_switch *
@ -225,6 +327,31 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
ds->ports[i] = slave_dev;
}
#ifdef CONFIG_NET_DSA_HWMON
/* If the switch provides a temperature sensor,
* register with hardware monitoring subsystem.
* Treat registration error as non-fatal and ignore it.
*/
if (drv->get_temp) {
const char *netname = netdev_name(dst->master_netdev);
char hname[IFNAMSIZ + 1];
int i, j;
/* Create valid hwmon 'name' attribute */
for (i = j = 0; i < IFNAMSIZ && netname[i]; i++) {
if (isalnum(netname[i]))
hname[j++] = netname[i];
}
hname[j] = '\0';
scnprintf(ds->hwmon_name, sizeof(ds->hwmon_name), "%s_dsa%d",
hname, index);
ds->hwmon_dev = hwmon_device_register_with_groups(NULL,
ds->hwmon_name, ds, dsa_hwmon_groups);
if (IS_ERR(ds->hwmon_dev))
ds->hwmon_dev = NULL;
}
#endif /* CONFIG_NET_DSA_HWMON */
return ds;
out_free:
@ -236,6 +363,10 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
static void dsa_switch_destroy(struct dsa_switch *ds)
{
#ifdef CONFIG_NET_DSA_HWMON
if (ds->hwmon_dev)
hwmon_device_unregister(ds->hwmon_dev);
#endif
}
#ifdef CONFIG_PM_SLEEP
@ -444,6 +575,7 @@ static int dsa_of_probe(struct platform_device *pdev)
const char *port_name;
int chip_index, port_index;
const unsigned int *sw_addr, *port_reg;
u32 eeprom_len;
int ret;
mdio = of_parse_phandle(np, "dsa,mii-bus", 0);
@ -495,6 +627,9 @@ static int dsa_of_probe(struct platform_device *pdev)
if (cd->sw_addr > PHY_MAX_ADDR)
continue;
if (!of_property_read_u32(np, "eeprom-length", &eeprom_len))
cd->eeprom_len = eeprom_len;
for_each_available_child_of_node(child, port) {
port_reg = of_get_property(port, "reg", NULL);
if (!port_reg)

View File

@ -249,6 +249,27 @@ static void dsa_slave_get_drvinfo(struct net_device *dev,
strlcpy(drvinfo->bus_info, "platform", sizeof(drvinfo->bus_info));
}
static int dsa_slave_get_regs_len(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_regs_len)
return ds->drv->get_regs_len(ds, p->port);
return -EOPNOTSUPP;
}
static void
dsa_slave_get_regs(struct net_device *dev, struct ethtool_regs *regs, void *_p)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_regs)
ds->drv->get_regs(ds, p->port, regs, _p);
}
static int dsa_slave_nway_reset(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
@ -271,6 +292,44 @@ static u32 dsa_slave_get_link(struct net_device *dev)
return -EOPNOTSUPP;
}
static int dsa_slave_get_eeprom_len(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->pd->eeprom_len)
return ds->pd->eeprom_len;
if (ds->drv->get_eeprom_len)
return ds->drv->get_eeprom_len(ds);
return 0;
}
static int dsa_slave_get_eeprom(struct net_device *dev,
struct ethtool_eeprom *eeprom, u8 *data)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_eeprom)
return ds->drv->get_eeprom(ds, eeprom, data);
return -EOPNOTSUPP;
}
static int dsa_slave_set_eeprom(struct net_device *dev,
struct ethtool_eeprom *eeprom, u8 *data)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->set_eeprom)
return ds->drv->set_eeprom(ds, eeprom, data);
return -EOPNOTSUPP;
}
static void dsa_slave_get_strings(struct net_device *dev,
uint32_t stringset, uint8_t *data)
{
@ -385,8 +444,13 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
.get_settings = dsa_slave_get_settings,
.set_settings = dsa_slave_set_settings,
.get_drvinfo = dsa_slave_get_drvinfo,
.get_regs_len = dsa_slave_get_regs_len,
.get_regs = dsa_slave_get_regs,
.nway_reset = dsa_slave_nway_reset,
.get_link = dsa_slave_get_link,
.get_eeprom_len = dsa_slave_get_eeprom_len,
.get_eeprom = dsa_slave_get_eeprom,
.set_eeprom = dsa_slave_set_eeprom,
.get_strings = dsa_slave_get_strings,
.get_ethtool_stats = dsa_slave_get_ethtool_stats,
.get_sset_count = dsa_slave_get_sset_count,

View File

@ -63,8 +63,6 @@ static netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev)
dsa_header[3] = 0x00;
}
skb->protocol = htons(ETH_P_DSA);
skb->dev = p->parent->dst->master_netdev;
dev_queue_xmit(skb);

View File

@ -76,8 +76,6 @@ static netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev)
edsa_header[7] = 0x00;
}
skb->protocol = htons(ETH_P_EDSA);
skb->dev = p->parent->dst->master_netdev;
dev_queue_xmit(skb);

View File

@ -57,8 +57,6 @@ static netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev)
trailer[2] = 0x10;
trailer[3] = 0x00;
nskb->protocol = htons(ETH_P_TRAILER);
nskb->dev = p->parent->dst->master_netdev;
dev_queue_xmit(nskb);