Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

Merge branch 'phylink-and-sfp-support'



Russell King says:

====================
phylink and sfp support

This patch series introduces generic support for SFP sockets found on
various Marvell based platforms.  The idea here is to provide common
SFP socket support which can be re-used by network drivers as
appropriate, rather than each network driver having to re-implement
SFP socket support.

SFP sockets typically use other system resources, eg, I2C buses to read
identifying information, and GPIOs to monitor socket state and control
the socket.  Meanwhile, some network drivers drive multiple ethernet
ports from one instantiation of the driver.

It is not desirable to block the initialisation of a network driver
(thus denying other ports from being operational) if the resources
for the SFP socket are not yet available.  This means that an element
of independence between the SFP support code and the driver is
required.

More than that, SFP modules effectively bring hotplug PHYs to
networking - SFP copper modules normally contain a standard PHY
accessed over the I2C bus, and it is desirable to read their state
so network drivers can be appropriately configured.

To add to the complexity, SFP modules can be connected in at least
two places:

1. Directly to the serdes output of a MAC with no intervening PHY.
   For example:

     mvneta ----> SFP socket

2. To a PHY, for example:

     mvpp2 ---> PHY ---> copper
                 |
                 `-----> SFP socket

This code supports both setups, although it's not fully implemented
with scenario (2).

Moreover, the link presented by the SFP module can be one of the
10Gbase-R family (for SFP+ sockets), SGMII or 1000base-X (for SFP
sockets) depending on the module, and network drivers need to
reconfigure themselves accordingly for the link to come up.

For example, if the MAC is configured for SGMII and a fibre module
is plugged in, the link won't come up until the MAC is reconfigured
for 1000base-X mode.

The SFP code manages the SFP socket - detecting the module, reading
the identifying information, and managing the control and status
signals.  Importantly, it disables the SFP module transmitter when
the MAC is down, so that the laser is turned off (but that is not
a guarantee.)

phylink provides the mechanisms necessary to manage the link modes,
based on the SFP module type, and supports hot-plugging of the PHY
without needing the MAC driver to be brought up and down on
transitions.  phylink also supports the classical static PHY and
fixed-link modes.

I currently (but not included in this series) have code to convert
mvneta to use phylink, and the out of tree mvpp2x driver.  I have
nothing for the mvpp2 driver at present as that driver is only
recently becoming functional on 10G hardware, and is missing a lot
of features that are necessary to make things work correctly.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 21e27f2d 73970055
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -106,6 +106,16 @@ config MDIO_HISI_FEMAC
	  This module provides a driver for the MDIO busses found in the
	  Hisilicon SoC that have an Fast Ethernet MAC.

config MDIO_I2C
	tristate
	depends on I2C
	help
	  Support I2C based PHYs.  This provides a MDIO bus bridged
	  to I2C to allow PHYs connected in I2C mode to be accessed
	  using the existing infrastructure.

	  This is library mode.

config MDIO_MOXART
        tristate "MOXA ART MDIO interface support"
        depends on ARCH_MOXART
@@ -159,6 +169,16 @@ menuconfig PHYLIB
	  devices.  This option provides infrastructure for
	  managing PHY devices.

config PHYLINK
	tristate
	depends on NETDEVICES
	select PHYLIB
	select SWPHY
	help
	  PHYlink models the link between the PHY and MAC, allowing fixed
	  configuration links, PHYs, and Serdes links with MAC level
	  autonegotiation modes.

if PHYLIB

config SWPHY
@@ -180,6 +200,11 @@ config LED_TRIGGER_PHY

comment "MII PHY device drivers"

config SFP
	tristate "SFP cage support"
	depends on I2C && PHYLINK
	select MDIO_I2C

config AMD_PHY
	tristate "AMD PHYs"
	---help---
+6 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ endif
libphy-$(CONFIG_SWPHY)		+= swphy.o
libphy-$(CONFIG_LED_TRIGGER_PHY)	+= phy_led_triggers.o

obj-$(CONFIG_PHYLINK)		+= phylink.o
obj-$(CONFIG_PHYLIB)		+= libphy.o

obj-$(CONFIG_MDIO_BCM_IPROC)	+= mdio-bcm-iproc.o
@@ -30,12 +31,17 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
obj-$(CONFIG_MDIO_CAVIUM)	+= mdio-cavium.o
obj-$(CONFIG_MDIO_GPIO)		+= mdio-gpio.o
obj-$(CONFIG_MDIO_HISI_FEMAC)	+= mdio-hisi-femac.o
obj-$(CONFIG_MDIO_I2C)		+= mdio-i2c.o
obj-$(CONFIG_MDIO_MOXART)	+= mdio-moxart.o
obj-$(CONFIG_MDIO_OCTEON)	+= mdio-octeon.o
obj-$(CONFIG_MDIO_SUN4I)	+= mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER)	+= mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE)	+= mdio-xgene.o

obj-$(CONFIG_SFP)		+= sfp.o
sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
obj-y				+= $(sfp-obj-y) $(sfp-obj-m)

obj-$(CONFIG_AMD_PHY)		+= amd.o
obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
obj-$(CONFIG_AT803X_PHY)	+= at803x.o
+109 −0
Original line number Diff line number Diff line
/*
 * MDIO I2C bridge
 *
 * Copyright (C) 2015-2016 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Network PHYs can appear on I2C buses when they are part of SFP module.
 * This driver exposes these PHYs to the networking PHY code, allowing
 * our PHY drivers access to these PHYs, and so allowing configuration
 * of their settings.
 */
#include <linux/i2c.h>
#include <linux/phy.h>

#include "mdio-i2c.h"

/*
 * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
 * specified to be present in SFP modules.  These correspond with PHY
 * addresses 16 and 17.  Disallow access to these "phy" addresses.
 */
static bool i2c_mii_valid_phy_id(int phy_id)
{
	return phy_id != 0x10 && phy_id != 0x11;
}

static unsigned int i2c_mii_phy_addr(int phy_id)
{
	return phy_id + 0x40;
}

static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
{
	struct i2c_adapter *i2c = bus->priv;
	struct i2c_msg msgs[2];
	u8 data[2], dev_addr = reg;
	int bus_addr, ret;

	if (!i2c_mii_valid_phy_id(phy_id))
		return 0xffff;

	bus_addr = i2c_mii_phy_addr(phy_id);
	msgs[0].addr = bus_addr;
	msgs[0].flags = 0;
	msgs[0].len = 1;
	msgs[0].buf = &dev_addr;
	msgs[1].addr = bus_addr;
	msgs[1].flags = I2C_M_RD;
	msgs[1].len = sizeof(data);
	msgs[1].buf = data;

	ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
	if (ret != ARRAY_SIZE(msgs))
		return 0xffff;

	return data[0] << 8 | data[1];
}

static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
{
	struct i2c_adapter *i2c = bus->priv;
	struct i2c_msg msg;
	int ret;
	u8 data[3];

	if (!i2c_mii_valid_phy_id(phy_id))
		return 0;

	data[0] = reg;
	data[1] = val >> 8;
	data[2] = val;

	msg.addr = i2c_mii_phy_addr(phy_id);
	msg.flags = 0;
	msg.len = 3;
	msg.buf = data;

	ret = i2c_transfer(i2c, &msg, 1);

	return ret < 0 ? ret : 0;
}

struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
{
	struct mii_bus *mii;

	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
		return ERR_PTR(-EINVAL);

	mii = mdiobus_alloc();
	if (!mii)
		return ERR_PTR(-ENOMEM);

	snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent));
	mii->parent = parent;
	mii->read = i2c_mii_read;
	mii->write = i2c_mii_write;
	mii->priv = i2c;

	return mii;
}
EXPORT_SYMBOL_GPL(mdio_i2c_alloc);

MODULE_AUTHOR("Russell King");
MODULE_DESCRIPTION("MDIO I2C bridge library");
MODULE_LICENSE("GPL v2");
+19 −0
Original line number Diff line number Diff line
/*
 * MDIO I2C bridge
 *
 * Copyright (C) 2015 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#ifndef MDIO_I2C_H
#define MDIO_I2C_H

struct device;
struct i2c_adapter;
struct mii_bus;

struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);

#endif
+180 −0
Original line number Diff line number Diff line
@@ -9,6 +9,186 @@
#include <linux/export.h>
#include <linux/phy.h>

const char *phy_speed_to_str(int speed)
{
	switch (speed) {
	case SPEED_10:
		return "10Mbps";
	case SPEED_100:
		return "100Mbps";
	case SPEED_1000:
		return "1Gbps";
	case SPEED_2500:
		return "2.5Gbps";
	case SPEED_5000:
		return "5Gbps";
	case SPEED_10000:
		return "10Gbps";
	case SPEED_14000:
		return "14Gbps";
	case SPEED_20000:
		return "20Gbps";
	case SPEED_25000:
		return "25Gbps";
	case SPEED_40000:
		return "40Gbps";
	case SPEED_50000:
		return "50Gbps";
	case SPEED_56000:
		return "56Gbps";
	case SPEED_100000:
		return "100Gbps";
	case SPEED_UNKNOWN:
		return "Unknown";
	default:
		return "Unsupported (update phy-core.c)";
	}
}
EXPORT_SYMBOL_GPL(phy_speed_to_str);

const char *phy_duplex_to_str(unsigned int duplex)
{
	if (duplex == DUPLEX_HALF)
		return "Half";
	if (duplex == DUPLEX_FULL)
		return "Full";
	if (duplex == DUPLEX_UNKNOWN)
		return "Unknown";
	return "Unsupported (update phy-core.c)";
}
EXPORT_SYMBOL_GPL(phy_duplex_to_str);

/* A mapping of all SUPPORTED settings to speed/duplex.  This table
 * must be grouped by speed and sorted in descending match priority
 * - iow, descending speed. */
static const struct phy_setting settings[] = {
	{
		.speed = SPEED_10000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
	},
	{
		.speed = SPEED_10000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
	},
	{
		.speed = SPEED_10000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
	},
	{
		.speed = SPEED_2500,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
	},
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
	},
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
	},
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
	},
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_HALF,
		.bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
	},
	{
		.speed = SPEED_100,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT,
	},
	{
		.speed = SPEED_100,
		.duplex = DUPLEX_HALF,
		.bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT,
	},
	{
		.speed = SPEED_10,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT,
	},
	{
		.speed = SPEED_10,
		.duplex = DUPLEX_HALF,
		.bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT,
	},
};

/**
 * phy_lookup_setting - lookup a PHY setting
 * @speed: speed to match
 * @duplex: duplex to match
 * @mask: allowed link modes
 * @maxbit: bit size of link modes
 * @exact: an exact match is required
 *
 * Search the settings array for a setting that matches the speed and
 * duplex, and which is supported.
 *
 * If @exact is unset, either an exact match or %NULL for no match will
 * be returned.
 *
 * If @exact is set, an exact match, the fastest supported setting at
 * or below the specified speed, the slowest supported setting, or if
 * they all fail, %NULL will be returned.
 */
const struct phy_setting *
phy_lookup_setting(int speed, int duplex, const unsigned long *mask,
		   size_t maxbit, bool exact)
{
	const struct phy_setting *p, *match = NULL, *last = NULL;
	int i;

	for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
		if (p->bit < maxbit && test_bit(p->bit, mask)) {
			last = p;
			if (p->speed == speed && p->duplex == duplex) {
				/* Exact match for speed and duplex */
				match = p;
				break;
			} else if (!exact) {
				if (!match && p->speed <= speed)
					/* Candidate */
					match = p;

				if (p->speed < speed)
					break;
			}
		}
	}

	if (!match && !exact)
		match = last;

	return match;
}
EXPORT_SYMBOL_GPL(phy_lookup_setting);

size_t phy_speeds(unsigned int *speeds, size_t size,
		  unsigned long *mask, size_t maxbit)
{
	size_t count;
	int i;

	for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++)
		if (settings[i].bit < maxbit &&
		    test_bit(settings[i].bit, mask) &&
		    (count == 0 || speeds[count - 1] != settings[i].speed))
			speeds[count++] = settings[i].speed;

	return count;
}

static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
			     u16 regnum)
{
Loading