Compare commits

...

9 Commits

Author SHA1 Message Date
7e0984e70f Tweak config-5.4 to admit the VSC848X phy 2021-02-16 03:42:47 +00:00
ad6a039808 Boot OF_I2C || O2_SPI dependency 2021-02-16 03:38:33 +00:00
34ee966cba Further cleanup 2021-02-16 03:27:39 +00:00
8d73f4e647 Fixup patches 2021-02-16 03:06:35 +00:00
4f8715bdbf Add further patches 2021-02-16 02:05:16 +00:00
d803c68954 Fixup spacing, again 2021-02-16 01:50:30 +00:00
b5d1f3cbc3 Fixup spacing 2021-02-16 01:47:43 +00:00
15977758ba Rebase patch on newer kernel 2021-02-16 01:44:01 +00:00
319b41636f Add vsc phy 2021-02-16 01:19:54 +00:00
4 changed files with 1191 additions and 2 deletions

View File

@@ -159,6 +159,7 @@ CONFIG_HZ_250=y
CONFIG_HZ_PERIODIC=y
CONFIG_IMAGE_CMDLINE_HACK=y
CONFIG_I2C=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_OCTEON=y
CONFIG_INITRAMFS_SOURCE=""
CONFIG_IRQCHIP=y
@@ -205,8 +206,10 @@ CONFIG_MTD_PHYSMAP=y
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y
CONFIG_NEED_DMA_MAP_STATE=y
CONFIG_NET_DEVLINK=y
CONFIG_NET_DSA=y
CONFIG_NET_FLOW_LIMIT=y
CONFIG_NET_SWITCHDEV=y
CONFIG_NLS=y
CONFIG_NLS_CODEPAGE_437=y
CONFIG_NLS_ISO8859_1=y
@@ -227,22 +230,25 @@ CONFIG_OF_GPIO=y
CONFIG_OF_IRQ=y
CONFIG_OF_KOBJ=y
CONFIG_OF_MDIO=y
CONFIG_OF_MEMORY_ACCESSOR=y
CONFIG_OF_NET=y
CONFIG_PADATA=y
# CONFIG_PARTITION_ADVANCED is not set
CONFIG_PCI=y
CONFIG_PCI_DOMAINS=y
CONFIG_PCI_DRIVERS_LEGACY=y
CONFIG_PCIEAER=y
CONFIG_PCIEPORTBUS=y
CONFIG_PCI_DOMAINS=y
CONFIG_PCI_DRIVERS_LEGACY=y
CONFIG_PERF_USE_VMALLOC=y
CONFIG_PGTABLE_LEVELS=3
CONFIG_PHYLIB=y
CONFIG_PHYLINK=y
CONFIG_PHYS_ADDR_T_64BIT=y
CONFIG_POSIX_MQUEUE=y
CONFIG_POSIX_MQUEUE_SYSCTL=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_QUEUED_SPINLOCKS=y
CONFIG_RAS=y
CONFIG_RCU_NEED_SEGCBLIST=y
CONFIG_RCU_STALL_COMMON=y
CONFIG_RELAY=y
@@ -260,6 +266,7 @@ CONFIG_SPARSEMEM=y
CONFIG_SPARSEMEM_STATIC=y
CONFIG_SPI=y
CONFIG_SPI_MASTER=y
CONFIG_SPI_MEM=y
CONFIG_SPI_OCTEON=y
CONFIG_SRCU=y
CONFIG_SWIOTLB=y
@@ -295,6 +302,7 @@ CONFIG_USE_OF=y
CONFIG_VFAT_FS=y
CONFIG_VITESSE_PHY=y
CONFIG_VM_EVENT_COUNTERS=y
CONFIG_VSC848X_PHY=y
CONFIG_WATCHDOG_CORE=y
CONFIG_WEAK_ORDERING=y
CONFIG_XPS=y

View File

@@ -0,0 +1,829 @@
From: Abhishek Paliwal <abhishek.paliwal@aricent.com>
Date: Fri, 13 Feb 2015 15:04:55 +0530
Subject: netdev/phy: Add driver for Vitesse vsc848x single, dual and quad 10G
phys
From: David Daney <david.daney@cavium.com>
These phys implement the standard IEEE 802.3 clause 45 registers but
require additional configuration. Some of these registers in the multi-phy
devices are shared among all phys such as the GPIO registers.
Additionally, this PHY does not automatically access the SFP+ serial EEPROM so
it is up to the PHY driver to parse it and change certain parameters in the
PHY according to the type of module installed and the length of the cable, if
copper.
This module has support for the vsc8488, vsc8486 and vsc8484 Vitesse devices
but thus far has only been tested with the vsc8488 dual PHY.
netdev/phy: Clean up structure names in vsc848x.c
Cut-and-paste snafu left some bad names, no functional change.
Signed-off-by: David Daney <david.daney@cavium.com>
Signed-off-by: Aaron Williams <aaron.williams@cavium.com>
Signed-off-by: Leonid Rosenboim <lrosenboim@caviumnetworks.com>
Signed-off-by: Abhishek Paliwal <abhishek.paliwal@aricent.com>
[Rebased on kernel 5.4 by Martin Kennedy <hurricos@gmail.com>]
Signed-off-by: Martin Kennedy <hurricos@gmail.com>
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -54,6 +54,13 @@ config VITESSE_PHY
---help---
Currently supports the vsc8244
+config VSC848X_PHY
+ tristate "Drivers for the Vitesse 10G PHYs"
+ depends on OF_MEMORY_ACCESSOR
+ help
+ Driver for Vitesse vsc848x single, dual and quad 10G PHY devices.
+ Currently supports the vsc8488, vsc8486 and vsc8484 chips
+
config SMSC_PHY
tristate "Drivers for SMSC PHYs"
---help---
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -108,4 +108,5 @@
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
+obj-$(CONFIG_VSC848X_PHY) += vsc848x.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
--- /dev/null
+++ b/drivers/net/phy/vsc848x.c
@@ -0,0 +1,774 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2012 Cavium, Inc.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/of_mdio.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/phy.h>
+#include <linux/memory.h>
+#include <linux/mutex.h>
+#include <linux/of_memory_accessor.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+
+#define PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x01000a)
+#define PMA_TXOUTCTRL2 (MII_ADDR_C45 | 0x018014)
+#define EDC_EYE_QUALITY (MII_ADDR_C45 | 0x018034)
+/* EDC Firmware State Machine Status and Lib Force
+ * 15: library force enable
+ * 14:8 - Library number
+ * 7:5 - N/A
+ * 4 - State force
+ * 3:0 - FW state (1=reset, 2=wait for alarm to clear, 3 = convergence,
+ * 4 = tracking, 5 = freeze)
+ */
+#define EDC_FW_SM_STATUS (MII_ADDR_C45 | 0x018036)
+
+#define BASER_PCS_STATUS (MII_ADDR_C45 | 0x030020)
+#define XGXS_LANE_STATUS (MII_ADDR_C45 | 0x040018)
+#define EWIS_INTR_PEND1 (MII_ADDR_C45 | 0x02EE00)
+#define EWIS_INTR_MASKA_1 (MII_ADDR_C45 | 0x02EE01)
+#define EWIS_INTR_MASKB_1 (MII_ADDR_C45 | 0x02EE02)
+#define EWIS_INTR_STAT2 (MII_ADDR_C45 | 0x02EE03)
+#define EWIS_INTR_PEND2 (MII_ADDR_C45 | 0x02EE04)
+#define EWIS_INTR_MASKA_2 (MII_ADDR_C45 | 0x02EE05)
+#define EWIS_INTR_MASKB_2 (MII_ADDR_C45 | 0x02EE06)
+#define EWIS_FAULT_MASK (MII_ADDR_C45 | 0x02EE07)
+#define EWIS_INTR_PEND3 (MII_ADDR_C45 | 0x02EE08)
+#define EWIS_INTR_MASKA_3 (MII_ADDR_C45 | 0x02EE09)
+#define EWIS_INTR_MASKB_3 (MII_ADDR_C45 | 0x02EE0A)
+
+/* Device ID
+ * 15:0 - device ID
+ */
+#define GBL_DEVICE_ID (MII_ADDR_C45 | 0x1e0000)
+/* Device revision
+ * 15:04 - reserved
+ * 03:00 - revision ID
+ */
+#define GBL_DEVICE_REVISION (MII_ADDR_C45 | 0x1e0001)
+/* Block Level Software Reset
+ * 15:14 - reserved
+ * 13: - software reset EDC 1 (1 = reset, autoclears)
+ * 12: - software reset EDC 0 (1 = reset, autoclears)
+ * 11:10 - reserved
+ * 09: - Software reset channel 1 (1 = reset, autoclears)
+ * 08: - Software reset channel 0 (1 = reset, autoclears)
+ * 07: - Microprocessor reset (0 = normal operation, 1 = reset)
+ * 06: - Software reset BIU (1 = reset, autoclears)
+ * 05: - Software reset TWS slave (1 = reset, autoclears)
+ * 04: - Software reset TWS master (1 = reset, autoclears)
+ * 03: - Software reset MDIO (1 = reset, autoclears)
+ * 02: - Software reset UART (1 = reset, autoclears)
+ * 01: - Global register reset (1 = reset, autoclears)
+ * 00: - Software reset chip (1 = reset, autoclears)
+ */
+#define GBL_BLOCK_LVL_SW_RESET (MII_ADDR_C45 | 0x1e0002)
+#define GBL_GPIO_0_CONFIG1_STATUS (MII_ADDR_C45 | 0x1e0100)
+#define GBL_GPIO_0_CONFIG2 (MII_ADDR_C45 | 0x1e0101)
+#define GBL_DEVICE_ID (MII_ADDR_C45 | 0x1e0000)
+#define GBL_FW_CHECKSUM (MII_ADDR_C45 | 0x1e7fe0)
+#define GBL_FW_WATCHDOG (MII_ADDR_C45 | 0x1e7fe1)
+#define GBL_FW_VERSION (MII_ADDR_C45 | 0x1e7fe2)
+#define GBL_FW_VAR_ACC_CTRL (MII_ADDR_C45 | 0x1e7fe3)
+#define GBL_FW_VAR_ACC_DATA (MII_ADDR_C45 | 0x1e7fe4)
+
+/* The Vitesse VSC848X series are 10G PHYs.
+ *
+ * Some of these devices contain multiple PHYs in a single package and
+ * some features are controlled by a global set of registers shared between
+ * all of the PHY devices. Because of this a nexus is used to handle all
+ * of the PHYs on the same device.
+ *
+ * Unlike some PHY devices, it is up to the driver to read the SFP module
+ * serial EEPROM in order to put the PHY into the right mode. The VSC848X
+ * does not provide an I2C interface so the PHY driver relies on the
+ * external AT24 I2C EEPROM driver to read the module whenever it is inserted.
+ *
+ */
+
+/* Enable LOPC detection (see 0x5B for target state)
+ * 15:12 - channel 3
+ * 11:08 - channel 2
+ * 07:04 - channel 1
+ * 03:00 - channel 0
+ * 1 = enable (default), 0 = disable
+ */
+#define FW_VAR_ENABLE_LOPC 0x58
+/* While in tracking mode, go to this state in response to LOPC assertion
+ * 1 = reset, 2 = wait (default), 3 = converging, 4 = tracking, 5 = freeze
+ */
+#define FW_VAR_LOPC_ASSERT_MODE 0x5B
+/* While in freeze mode, enable state transition upon deassertion of LOPC (see
+ * 0x61 for target state)
+ * 1 - reset, 2 = wait, 3 = converging, 4 = tracking (default), 5 = freeze
+ */
+#define FW_VAR_FREEZE_DEASSERT_MODE 0x61
+/* Current functional mode
+ * See VITESSE_FUNC_MODE_XXX below for values
+ * NOTE: When the firmware is done servicing the mode change request, bit 4
+ * will be set to 1.
+ */
+#define FW_VAR_FUNCTIONAL_MODE 0x94
+/* Current state of graded SPSA process
+ * 3: channel 3
+ * 2: channel 2
+ * 1: channel 1
+ * 0: channel 0
+ * 1 = busy, 2 = done
+ */
+#define FW_VAR_GRADED_SPSA_STATE 0x95
+/* BerScore at start of SPSA cycle */
+#define FW_VAR_BERSCORE_START 0x96
+/* BerScore at end of SPSA cycle */
+#define FW_VAR_BERSCORE_END 0x97
+/* Enable/Disable aggressive track phase on entering tracking state
+ * 15:12 - channel 3
+ * 11:08 - channel 2
+ * 07:04 - channel 1
+ * 03:00 - channel 0
+ * 0 = disable, 1 = enable (default)
+ */
+#define FW_VAR_AGG_TRACKING 0xAF
+
+/* Modes for the PHY firmware */
+#define VITESSE_FUNC_MODE_LIMITING 2 /* Optical */
+#define VITESSE_FUNC_MODE_COPPER 3 /* Copper */
+#define VITESSE_FUNC_MODE_LINEAR 4
+#define VITESSE_FUNC_MODE_KR 5
+#define VITESSE_FUNC_MODE_ZR 7
+#define VITESSE_FUNC_MODE_1G 8
+
+
+struct vsc848x_nexus_mdiobus {
+ struct mii_bus *mii_bus;
+ struct mii_bus *parent_mii_bus;
+ int reg_offset;
+ struct mutex lock; /* Lock used for global register sequences */
+ int phy_irq[PHY_MAX_ADDR];
+};
+
+struct vsc848x_phy_info {
+ int sfp_conn; /* Module connected? */
+ int tx_en_gpio; /* GPIO that enables transmit */
+ int mod_abs_gpio; /* Module Absent GPIO line */
+ int tx_fault_gpio; /* TX Fault GPIO line */
+ int inta_gpio, intb_gpio; /* Interrupt GPIO line (output) */
+ uint8_t mode; /* Mode for module */
+ uint8_t channel; /* channel in multi-phy devices */
+ struct device_node *sfp_node; /* EEPROM NODE for SFP */
+ struct memory_accessor *macc; /* memory access routines for EEPROM */
+ struct vsc848x_nexus_mdiobus *nexus; /* Nexus for lock */
+};
+
+/**
+ * Maps GPIO lines to the global GPIO config registers.
+ *
+ * Please see the data sheet since the configuration for each GPIO line is
+ * different.
+ */
+static const struct {
+ uint32_t config1_status_reg;
+ uint32_t config2_reg;
+} vcs848x_gpio_to_reg[12] = {
+ { (MII_ADDR_C45 | 0x1e0100), (MII_ADDR_C45 | 0x1e0101) }, /* 0 */
+ { (MII_ADDR_C45 | 0x1e0102), (MII_ADDR_C45 | 0x1e0103) }, /* 1 */
+ { (MII_ADDR_C45 | 0x1e0104), (MII_ADDR_C45 | 0x1e0105) }, /* 2 */
+ { (MII_ADDR_C45 | 0x1e0106), (MII_ADDR_C45 | 0x1e0107) }, /* 3 */
+ { (MII_ADDR_C45 | 0x1e0108), (MII_ADDR_C45 | 0x1e0109) }, /* 4 */
+ { (MII_ADDR_C45 | 0x1e010A), (MII_ADDR_C45 | 0x1e010B) }, /* 5 */
+ { (MII_ADDR_C45 | 0x1e0124), (MII_ADDR_C45 | 0x1e0125) }, /* 6 */
+ { (MII_ADDR_C45 | 0x1e0126), (MII_ADDR_C45 | 0x1e0127) }, /* 7 */
+ { (MII_ADDR_C45 | 0x1e0128), (MII_ADDR_C45 | 0x1e0129) }, /* 8 */
+ { (MII_ADDR_C45 | 0x1e012a), (MII_ADDR_C45 | 0x1e012b) }, /* 9 */
+ { (MII_ADDR_C45 | 0x1e012c), (MII_ADDR_C45 | 0x1e012d) }, /* 10 */
+ { (MII_ADDR_C45 | 0x1e012e), (MII_ADDR_C45 | 0x1e012f) }, /* 11 */
+};
+
+static int vsc848x_probe(struct phy_device *phydev)
+{
+ struct vsc848x_phy_info *dev_info;
+ int ret;
+
+ dev_info = devm_kzalloc(&phydev->dev, sizeof(*dev_info), GFP_KERNEL);
+ if (dev_info == NULL)
+ return -ENOMEM;
+
+ phydev->priv = dev_info;
+ dev_info->mode = VITESSE_FUNC_MODE_LIMITING; /* Default to optical */
+ phydev->priv = dev_info;
+ dev_info->nexus = phydev->bus->priv;
+
+ ret = of_property_read_u32(phydev->dev.of_node, "mod_abs",
+ &dev_info->mod_abs_gpio);
+ if (ret) {
+ dev_err(&phydev->dev, "%s has invalid mod_abs address\n",
+ phydev->dev.of_node->full_name);
+ return ret;
+ }
+
+ ret = of_property_read_u32(phydev->dev.of_node, "tx_fault",
+ &dev_info->tx_fault_gpio);
+ if (ret) {
+ dev_err(&phydev->dev, "%s has invalid tx_fault address\n",
+ phydev->dev.of_node->full_name);
+ return ret;
+ }
+
+ ret = of_property_read_u32(phydev->dev.of_node, "inta",
+ &dev_info->inta_gpio);
+ if (ret)
+ dev_info->inta_gpio = -1;
+
+ ret = of_property_read_u32(phydev->dev.of_node, "intb",
+ &dev_info->intb_gpio);
+ if (ret)
+ dev_info->intb_gpio = -1;
+
+ ret = of_property_read_u32(phydev->dev.of_node, "txon",
+ &dev_info->tx_en_gpio);
+ if (ret) {
+ dev_err(&phydev->dev, "%s has invalid txon gpio address\n",
+ phydev->dev.of_node->full_name);
+ return -ENXIO;
+ }
+
+ dev_info->sfp_node = of_parse_phandle(phydev->dev.of_node,
+ "sfp-eeprom", 0);
+ if (!dev_info->sfp_node) {
+ dev_err(&phydev->dev, "%s has invalid sfp-eeprom node\n",
+ phydev->dev.of_node->full_name);
+ return -ENXIO;
+ }
+
+ dev_info->macc = of_memory_accessor_get(dev_info->sfp_node);
+
+ ret = phy_read(phydev, GBL_DEVICE_ID);
+ if (ret < 0) {
+ dev_err(&phydev->dev, "%s error reading PHY\n",
+ phydev->dev.of_node->full_name);
+ return ret;
+ }
+
+ /* Check how many devices are in the package to figure out the channel
+ * number.
+ */
+ switch (ret) {
+ case 0x8487: /* Single */
+ case 0x8486:
+ dev_info->channel = 0;
+ break;
+ case 0x8488: /* Dual */
+ dev_info->channel = phydev->addr & 1;
+ break;
+ case 0x8484: /* Quad */
+ dev_info->channel = phydev->addr & 3;
+ break;
+ default:
+ dev_err(&phydev->dev, "%s Unknown Vitesse PHY model %04x\n",
+ phydev->dev.of_node->full_name, ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vsc848x_remove(struct phy_device *phydev)
+{
+ struct vsc848x_phy_info *dev_info = phydev->priv;
+
+ dev_info(&phydev->dev, "%s Exiting\n", phydev->dev.of_node->full_name);
+
+ of_memory_accessor_put(dev_info->sfp_node);
+
+ kfree(dev_info);
+}
+
+static int vsc848x_config_init(struct phy_device *phydev)
+{
+ phydev->supported = SUPPORTED_10000baseR_FEC;
+ phydev->advertising = ADVERTISED_10000baseR_FEC;
+ phydev->state = PHY_NOLINK;
+
+ return 0;
+}
+
+static int vsc848x_config_aneg(struct phy_device *phydev)
+{
+ return -EINVAL;
+}
+
+static int vsc848x_write_global_var(struct phy_device *phydev, uint8_t channel,
+ uint8_t addr, uint16_t value)
+{
+ struct vsc848x_phy_info *dev_info = phydev->priv;
+ int timeout = 1000;
+ int ret = 0;
+
+ mutex_lock(&(dev_info->nexus->lock));
+
+ /* Wait for firmware download to complete */
+ timeout = 100000;
+ do {
+ ret = phy_read(phydev, MII_ADDR_C45 | 0x1e7fe0);
+ if (ret < 0)
+ goto error;
+ if (ret == 3)
+ break;
+ udelay(100);
+ } while (timeout-- > 0);
+ if (timeout <= 0) {
+ dev_err(&phydev->dev, "%s Timeout waiting for PHY firmware to load\n",
+ phydev->dev.of_node->full_name);
+ ret = -EIO;
+ goto error;
+ }
+
+ do {
+ ret = phy_read(phydev, (MII_ADDR_C45 | 0x1e7fe3));
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ break;
+ mdelay(1);
+ } while (timeout-- > 0);
+ if (timeout <= 0) {
+ dev_err(&phydev->dev, "%s timed out waiting to write global\n",
+ phydev->dev.of_node->full_name);
+ ret = -EIO;
+ goto error;
+ }
+ ret = phy_write(phydev, (MII_ADDR_C45 | 0x1e7fe4), value);
+ if (ret < 0)
+ goto error;
+
+ ret = phy_write(phydev, (MII_ADDR_C45 | 0x1e7fe3),
+ 0x8000 | ((channel & 3) << 8) | addr);
+ if (ret < 0)
+ goto error;
+
+ /* Wait for value to be written */
+ do {
+ ret = phy_read(phydev, (MII_ADDR_C45 | 0x1e7fe3));
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ break;
+ mdelay(1);
+ } while (timeout-- > 0);
+ if (timeout <= 0) {
+ dev_err(&phydev->dev, "%s timed out waiting to write global\n",
+ phydev->dev.of_node->full_name);
+ ret = -EIO;
+ goto error;
+ }
+ ret = 0;
+
+error:
+ mutex_unlock(&(dev_info->nexus->lock));
+
+ return ret;
+}
+
+/**
+ * Dumps out the contents of the SFP EEPROM when errors are detected
+ *
+ * @param eeprom - contents of SFP+ EEPROM
+ */
+static void dump_sfp_eeprom(const uint8_t eeprom[64])
+{
+ int addr = 0;
+ int i;
+ char line[17];
+ line[16] = '\0';
+
+ pr_info("SFP+ EEPROM contents:\n");
+ while (addr < 64) {
+ pr_info(" %02x: ", addr);
+ for (i = 0; i < 16; i++)
+ pr_cont("%02x ", eeprom[addr + i]);
+ for (i = 0; i < 16; i++) {
+ if (!isprint(eeprom[addr + i]) ||
+ eeprom[addr + i] >= 0x80)
+ line[i] = '.';
+ else
+ line[i] = eeprom[addr + i];
+ }
+ pr_cont(" %s\n", line);
+ addr += 16;
+ }
+ pr_info("\n");
+}
+
+/**
+ * Read the SFP+ module EEPROM and program the Vitesse PHY accordingly.
+ *
+ * @param phydev - Phy device
+ *
+ * @returns 0 for success, error otherwise.
+ */
+static int vsc848x_read_sfp(struct phy_device *phydev)
+{
+ struct vsc848x_phy_info *dev_info = phydev->priv;
+ uint8_t sfp_buffer[64];
+ ssize_t size;
+ uint8_t csum;
+ uint8_t mode = VITESSE_FUNC_MODE_LIMITING;
+ const char *mode_str = "Unknown";
+ int i;
+ int ret = 0;
+
+ /* For details on the SFP+ EEPROM contents see the SFF-8472
+ * Diagnostic Monitoring Interface for Optical Transceivers.
+ *
+ * This is based on revision 11.1, October 26, 2012.
+ */
+ if (!dev_info->macc) {
+ dev_info->macc = of_memory_accessor_get(dev_info->sfp_node);
+ if (!dev_info->macc) {
+ dev_info(&phydev->dev,
+ "Could not connect to sfp memory accessor %s\n",
+ dev_info->sfp_node->full_name);
+ return -ENODEV;
+ }
+ }
+ size = dev_info->macc->read(dev_info->macc,
+ (char *)sfp_buffer, 0, sizeof(sfp_buffer));
+ if (size != sizeof(sfp_buffer)) {
+ dev_err(&phydev->dev, "%s cannot read SFP module EEPROM\n",
+ phydev->dev.of_node->full_name);
+ return -ENODEV;
+ }
+
+ /* Validate SFP checksum */
+ csum = 0;
+ for (i = 0; i < 63; i++)
+ csum += sfp_buffer[i];
+ if (csum != sfp_buffer[63]) {
+ dev_err(&phydev->dev, "%s SFP EEPROM checksum bad, calculated 0x%02x, should be 0x%02x\n",
+ phydev->dev.of_node->full_name, csum, sfp_buffer[63]);
+ dump_sfp_eeprom(sfp_buffer);
+ return -ENXIO;
+ }
+
+ /* Make sure it's a SFP or SFP+ module */
+ if (sfp_buffer[0] != 3) {
+ dev_err(&phydev->dev, "%s module is not SFP or SFP+\n",
+ phydev->dev.of_node->full_name);
+ dump_sfp_eeprom(sfp_buffer);
+ return -ENXIO;
+ }
+
+ /* Check connector type */
+ switch (sfp_buffer[2]) {
+ case 0x01: /* SC */
+ mode = VITESSE_FUNC_MODE_LIMITING;
+ break;
+ case 0x07: /* LC */
+ mode = VITESSE_FUNC_MODE_LIMITING;
+ break;
+ case 0x0B: /* Optical pigtail */
+ mode = VITESSE_FUNC_MODE_LIMITING;
+ break;
+ case 0x21: /* Copper pigtail */
+ case 0x22: /* RJ45 */
+ mode = VITESSE_FUNC_MODE_COPPER;
+ break;
+ default:
+ dev_err(&phydev->dev, "%s Unknown Connector Type 0x%x\n",
+ phydev->dev.of_node->full_name, sfp_buffer[2]);
+ dump_sfp_eeprom(sfp_buffer);
+ return -EINVAL;
+ }
+
+ if (mode == VITESSE_FUNC_MODE_LIMITING) {
+ if (mode_str[3] & 0x10)
+ mode_str = "10GBase-SR";
+ else if (mode_str[3] & 0x20)
+ mode_str = "10GBase-LR";
+ else if (mode_str[3] & 0x40)
+ mode_str = "10GBase-LRM";
+ else if (mode_str[3] & 0x80)
+ mode_str = "10GBase-ER";
+ else
+ dev_err(&phydev->dev, "%s unknown SFP compatibility\n"
+ "type ID: 0x%02x, extended ID: 0x%02x, Connector type code: 0x%02x\n"
+ "Transceiver compatibility code: (%02x) %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ phydev->dev.of_node->full_name, sfp_buffer[0],
+ sfp_buffer[1], sfp_buffer[2], sfp_buffer[36],
+ sfp_buffer[3], sfp_buffer[4], sfp_buffer[5],
+ sfp_buffer[6], sfp_buffer[7], sfp_buffer[8],
+ sfp_buffer[9], sfp_buffer[10]);
+ } else if (mode == VITESSE_FUNC_MODE_COPPER) {
+ if (sfp_buffer[8] & 0x4) {
+ mode_str = "10G Passive Copper";
+ } else if (sfp_buffer[8] & 0x8) {
+ mode_str = "10G Active Copper";
+ mode = VITESSE_FUNC_MODE_LIMITING;
+ } else {
+ dev_err(&phydev->dev, "%s Unknown SFP+ copper cable capability 0x%02x\n"
+ "Transceiver compatibility code: (%02x) %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ phydev->dev.of_node->full_name, sfp_buffer[8],
+ sfp_buffer[36], sfp_buffer[3], sfp_buffer[4],
+ sfp_buffer[5], sfp_buffer[6], sfp_buffer[7],
+ sfp_buffer[8], sfp_buffer[9], sfp_buffer[10]);
+ return -EINVAL;
+ }
+ } else {
+ dev_err(&phydev->dev, "%s Unsupported phy mode %d\n",
+ phydev->dev.of_node->full_name, mode);
+ dump_sfp_eeprom(sfp_buffer);
+ }
+
+ vsc848x_write_global_var(phydev, dev_info->channel, 0x94, mode);
+
+ /* Adjust PMA_TXOUTCTRL2 based on cable length. Vitesse recommends
+ * 0x1606 for copper cable lengths 5M and longer.
+ *
+ * The default value is 0x1300.
+ */
+ if (mode == VITESSE_FUNC_MODE_COPPER) {
+ if (sfp_buffer[18] >= 5)
+ ret = phy_write(phydev, PMA_TXOUTCTRL2, 0x1606);
+ else
+ ret = phy_write(phydev, PMA_TXOUTCTRL2, 0x1300);
+ if (ret)
+ return ret;
+ }
+
+ /* Reset the state machine */
+ ret = phy_write(phydev, MII_ADDR_C45 | 0x18034, 0x11);
+
+ dev_info(&phydev->dev, "%s configured for %s\n",
+ phydev->dev.of_node->full_name, mode_str);
+
+ return ret;
+}
+
+static int vsc848x_read_status(struct phy_device *phydev)
+{
+ struct vsc848x_phy_info *dev_info = phydev->priv;
+ int rx_signal_detect;
+ int pcs_status;
+ int xgxs_lane_status;
+ int value;
+ int sfp_conn;
+ int ret;
+
+ /* Check if a module is plugged in */
+ value = phy_read(phydev, vcs848x_gpio_to_reg[dev_info->mod_abs_gpio]
+ .config1_status_reg);
+ if (value < 0)
+ return value;
+
+ sfp_conn = !(value & 0x400);
+
+ if (sfp_conn != dev_info->sfp_conn) {
+ /* We detect a module being plugged in */
+ if (sfp_conn) {
+ ret = vsc848x_read_sfp(phydev);
+ if (ret < 0)
+ goto no_link;
+ dev_info->sfp_conn = sfp_conn;
+ } else {
+ dev_info(&phydev->dev, "%s module unplugged\n",
+ phydev->dev.of_node->full_name);
+ dev_info->sfp_conn = sfp_conn;
+ goto no_link;
+ }
+ }
+
+ rx_signal_detect = phy_read(phydev, PMD_RX_SIGNAL_DETECT);
+ if (rx_signal_detect < 0)
+ return rx_signal_detect;
+
+ if ((rx_signal_detect & 1) == 0)
+ goto no_link;
+
+ pcs_status = phy_read(phydev, BASER_PCS_STATUS);
+ if (pcs_status < 0)
+ return pcs_status;
+
+ if ((pcs_status & 1) == 0)
+ goto no_link;
+
+ xgxs_lane_status = phy_read(phydev, XGXS_LANE_STATUS);
+ if (xgxs_lane_status < 0)
+ return xgxs_lane_status;
+
+ if ((xgxs_lane_status & 0x1000) == 0)
+ goto no_link;
+
+ phydev->speed = 10000;
+ phydev->link = 1;
+ phydev->duplex = 1;
+ return 0;
+no_link:
+ phydev->link = 0;
+ return 0;
+}
+
+static struct of_device_id vsc848x_match[] = {
+ {
+ .compatible = "vitesse,vsc8488",
+ },
+ {
+ .compatible = "vitesse,vsc8486",
+ },
+ {
+ .compatible = "vitesse,vsc8484",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, vsc848x_match);
+
+static struct phy_driver vsc848x_phy_driver = {
+ .phy_id = 0x00070400,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Vitesse VSC848X",
+ .config_init = vsc848x_config_init,
+ .probe = vsc848x_probe,
+ .remove = vsc848x_remove,
+ .config_aneg = vsc848x_config_aneg,
+ .read_status = vsc848x_read_status,
+ .driver = {
+ .owner = THIS_MODULE,
+ .of_match_table = vsc848x_match,
+ },
+};
+
+/* Phy nexus support below. */
+
+static int vsc848x_nexus_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+ struct vsc848x_nexus_mdiobus *p = bus->priv;
+ return p->parent_mii_bus->read(p->parent_mii_bus,
+ phy_id + p->reg_offset,
+ regnum);
+}
+
+static int vsc848x_nexus_write(struct mii_bus *bus, int phy_id,
+ int regnum, u16 val)
+{
+ struct vsc848x_nexus_mdiobus *p = bus->priv;
+ return p->parent_mii_bus->write(p->parent_mii_bus,
+ phy_id + p->reg_offset,
+ regnum, val);
+}
+
+static int vsc848x_nexus_probe(struct platform_device *pdev)
+{
+ struct vsc848x_nexus_mdiobus *bus;
+ const char *bus_id;
+ int len;
+ int err = 0;
+
+ bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+ if (!bus)
+ return -ENOMEM;
+
+ bus->parent_mii_bus = container_of(pdev->dev.parent,
+ struct mii_bus, dev);
+
+ /* The PHY nexux must have a reg property in the range [0-31] */
+ err = of_property_read_u32(pdev->dev.of_node, "reg", &bus->reg_offset);
+ if (err) {
+ dev_err(&pdev->dev, "%s has invalid PHY address\n",
+ pdev->dev.of_node->full_name);
+ return err;
+ }
+
+ bus->mii_bus = mdiobus_alloc();
+ if (!bus->mii_bus)
+ return -ENOMEM;
+
+ bus->mii_bus->priv = bus;
+ bus->mii_bus->irq = bus->phy_irq;
+ bus->mii_bus->name = "vsc848x_nexus";
+ bus_id = bus->parent_mii_bus->id;
+ len = strlen(bus_id);
+ if (len > MII_BUS_ID_SIZE - 4)
+ bus_id += len - (MII_BUS_ID_SIZE - 4);
+ snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%s:%02x",
+ bus_id, bus->reg_offset);
+ bus->mii_bus->parent = &pdev->dev;
+
+ bus->mii_bus->read = vsc848x_nexus_read;
+ bus->mii_bus->write = vsc848x_nexus_write;
+ mutex_init(&bus->lock);
+
+ dev_set_drvdata(&pdev->dev, bus);
+
+ err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node);
+ if (err) {
+ dev_err(&pdev->dev, "Error registering with device tree\n");
+ goto fail_register;
+ }
+
+ return 0;
+
+fail_register:
+ dev_err(&pdev->dev, "Failed to register\n");
+ mdiobus_free(bus->mii_bus);
+ return err;
+}
+
+static int vsc848x_nexus_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct of_device_id vsc848x_nexus_match[] = {
+ {
+ .compatible = "vitesse,vsc8488-nexus",
+ },
+ {
+ .compatible = "vitesse,vsc8486-nexus",
+ },
+ {
+ .compatible = "vitesse,vsc8484-nexus",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, vsc848x_nexus_match);
+
+static struct platform_driver vsc848x_nexus_driver = {
+ .driver = {
+ .name = "vsc848x-nexus",
+ .owner = THIS_MODULE,
+ .of_match_table = vsc848x_nexus_match,
+ },
+ .probe = vsc848x_nexus_probe,
+ .remove = vsc848x_nexus_remove,
+};
+
+static int __init vsc848x_mod_init(void)
+{
+ int rv;
+
+ rv = platform_driver_register(&vsc848x_nexus_driver);
+ if (rv)
+ return rv;
+
+ rv = phy_driver_register(&vsc848x_phy_driver);
+
+ return rv;
+}
+module_init(vsc848x_mod_init);
+
+static void __exit vsc848x_mod_exit(void)
+{
+ phy_driver_unregister(&vsc848x_phy_driver);
+ platform_driver_unregister(&vsc848x_nexus_driver);
+}
+module_exit(vsc848x_mod_exit);
+
+MODULE_DESCRIPTION("Driver for Vitesse VSC848X PHY");
+MODULE_AUTHOR("David Daney and Aaron Williams");
+MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,310 @@
From: Abhishek Paliwal <abhishek.paliwal@aricent.com>
Date: Fri, 13 Feb 2015 12:47:36 +0530
Subject: of: Add of_memory_accessor to map device tree node to memory accessor
functions.
From: Aaron Williams <aaron.williams@cavium.com>
Currently there is no easy way to map a device tree node to a memory
accessor function for devices like I2C EEPROMs. For example, the Vitesse
vsc848x 10G PHY driver needs to be able to use the I2C at24 serial EEPROM
memory accessor function in order to read the SFP+ eeprom.
This provides a way where the vsc848x module can parse its device tree and
easily gain the accessor functions for the eeprom through a phandle.
This may be useful for any module which provides memory accessor functions.
Signed-off-by: Aaron Williams <aaron.williams@cavium.com>
Signed-off-by: Leonid Rosenboim <lrosenboim@caviumnetworks.com>
Signed-off-by: Abhishek Paliwal <abhishek.paliwal@aricent.com>
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -107,4 +107,10 @@
# arches should select this if DMA is coherent by default for OF devices
bool
+config OF_MEMORY_ACCESSOR
+ def_bool y
+ help
+ OpenFirmware memory accessor support for accessing devices like
+ i2c and SPI eeproms.
+
endif # OF
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -16,3 +16,4 @@
obj-$(CONFIG_OF_NUMA) += of_numa.o
obj-$(CONFIG_OF_UNITTEST) += unittest-data/
+obj-$(CONFIG_OF_MEMORY_ACCESSOR) += of_memory_accessor.o
--- /dev/null
+++ b/drivers/of/of_memory_accessor.c
@@ -0,0 +1,192 @@
+/*
+ * Memory accessor OF helpers
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2012 Cavium Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of_memory_accessor.h>
+#include <linux/list.h>
+#include <linux/memory.h>
+
+struct of_macc_entry {
+ struct list_head list;
+ struct device *dev;
+ struct memory_accessor *macc;
+ int ref;
+};
+
+static DEFINE_MUTEX(lock);
+static LIST_HEAD(macc_list);
+
+/**
+ * Adds a mapping of a device node to a memory accessor
+ *
+ * @param[in] dev - device
+ * @param[in] macc - memory accessor
+ *
+ * @returns 0 for success or -ENOMEM
+ */
+int of_memory_accessor_register(struct device *dev,
+ struct memory_accessor *macc)
+{
+ struct of_macc_entry *mentry;
+
+ mentry = kmalloc(sizeof(*mentry), GFP_KERNEL);
+ if (mentry == NULL)
+ return -ENOMEM;
+
+ mentry->dev = dev;
+ mentry->macc = macc;
+ mentry->ref = 0;
+
+ mutex_lock(&lock);
+
+ list_add(&(mentry->list), &macc_list);
+
+ mutex_unlock(&lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(of_memory_accessor_register);
+
+/**
+ * removes the mapping of a device node to a memory accessor
+ *
+ * @param[in] devnode - device node to remove
+ *
+ * @returns 0 for success or -ENODEV if device node not found, -EBUSY if still
+ * in use
+ */
+
+int of_memory_accessor_remove(struct device *dev)
+{
+ struct of_macc_entry *mentry;
+ struct list_head *pos, *q;
+ int ret = -ENODEV;
+
+ mutex_lock(&lock);
+
+ list_for_each_safe(pos, q, &macc_list) {
+ mentry = list_entry(pos, struct of_macc_entry, list);
+ if (mentry->dev == dev) {
+ if (mentry->ref > 0) {
+ ret = -EBUSY;
+ goto done;
+ }
+ list_del(pos);
+ kfree(mentry);
+ ret = 0;
+ goto done;
+ }
+ }
+
+ /* Not found */
+done:
+ mutex_unlock(&lock);
+ return ret;
+}
+EXPORT_SYMBOL(of_memory_accessor_remove);
+
+/**
+ * Returns the memory accessor for a device node and increments a reference
+ * count
+ *
+ * @param[in] devnode - device node to look up
+ *
+ * @returns memory accessor for device node or NULL if none found.
+ */
+struct memory_accessor *
+of_memory_accessor_get(const struct device_node *devnode)
+{
+ struct of_macc_entry *mentry;
+ struct list_head *pos;
+ struct memory_accessor *macc = NULL;
+
+ mutex_lock(&lock);
+
+ list_for_each(pos, &macc_list) {
+ mentry = list_entry(pos, struct of_macc_entry, list);
+ if (mentry->dev->of_node == devnode) {
+ macc = mentry->macc;
+ if (!mentry->ref) {
+ if (!try_module_get(mentry->dev->driver->owner)) {
+ macc = NULL;
+ pr_info("Warning: module for %s not found!",
+ mentry->dev->of_node->full_name);
+ }
+ }
+ mentry->ref++;
+ goto done;
+ }
+ }
+done:
+ mutex_unlock(&lock);
+ return macc;
+}
+EXPORT_SYMBOL(of_memory_accessor_get);
+
+/**
+ * Decrements the reference count for the memory accessor attached to the
+ * device node.
+ *
+ * @param[in] devnode - device node to look up
+ *
+ * @returns 0 for success or -ENODEV if the device node was not found.
+ */
+int of_memory_accessor_put(const struct device_node *devnode)
+{
+ struct of_macc_entry *mentry;
+ struct list_head *pos;
+ int ret = -ENODEV;
+
+ mutex_lock(&lock);
+ list_for_each(pos, &macc_list) {
+ mentry = list_entry(pos, struct of_macc_entry, list);
+ if (mentry->dev->of_node == devnode) {
+ if (mentry->ref > 0)
+ mentry->ref--;
+ if (!mentry->ref)
+ module_put(mentry->dev->driver->owner);
+
+ module_put(THIS_MODULE);
+ ret = 0;
+ goto done;
+ }
+ }
+done:
+ mutex_unlock(&lock);
+ return ret;
+}
+EXPORT_SYMBOL(of_memory_accessor_put);
+
+static void __exit of_memory_accessor_exit(void)
+{
+ struct of_macc_entry *mentry;
+ struct list_head *pos, *q;
+
+ list_for_each_safe(pos, q, &macc_list) {
+ mentry = list_entry(pos, struct of_macc_entry, list);
+ if (mentry->ref)
+ module_put(mentry->dev->driver->owner);
+ list_del(pos);
+ kfree(mentry);
+ }
+
+ /* Not found */
+ mutex_destroy(&lock);
+ list_del(&macc_list);
+}
+module_exit(of_memory_accessor_exit);
+
+MODULE_DESCRIPTION("Driver for mapping device nodes to memory accessors");
+MODULE_AUTHOR("Aaron Williams");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/include/linux/of_memory_accessor.h
@@ -0,0 +1,71 @@
+#ifndef _LINUX_OF_MEMORY_ACCESSOR_H
+#define _LINUX_OF_MEMORY_ACCESSOR_H
+/*
+ * Memory accessor OF helpers
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2012 Cavium Inc.
+ */
+
+#include <linux/of.h>
+#include <linux/memory.h>
+
+/**
+ * Adds a mapping of a device node to a memory accessor
+ *
+ * @param[in] dev - device
+ * @param[in] macc - memory accessor
+ *
+ * @returns 0 for success or -ENOMEM
+ */
+#ifdef CONFIG_OF_MEMORY_ACCESSOR
+int of_memory_accessor_register(struct device *dev,
+ struct memory_accessor *macc);
+#else
+static inline int of_memory_accessor_register(struct device *dev,
+ struct memory_accessor *macc)
+{
+ return 0;
+}
+#endif
+
+/**
+ * removes the mapping of a device node to a memory accessor
+ *
+ * @param[in] devnode - device node to remove
+ *
+ * @returns 0 for success or 1 if device node not found
+ */
+#ifdef CONFIG_OF_MEMORY_ACCESSOR
+int of_memory_accessor_remove(struct device *dev);
+#else
+static inline int of_memory_accessor_remove(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+/**
+ * Returns the memory accessor for a device node
+ *
+ * @param[in] devnode - device node to look up
+ *
+ * @returns memory accessor for device node or NULL if none found.
+ */
+struct memory_accessor *
+of_memory_accessor_get(const struct device_node *devnode);
+
+/**
+ * Decrements the reference count for the memory accessor attached to the
+ * device node.
+ *
+ * @param[in] devnode - device node to look up
+ *
+ * @returns 0 for success or -1 if the device node was not found.
+ */
+int of_memory_accessor_put(const struct device_node *devnode);
+
+#endif /* _LINUX_OF_MEMORY_ACCESSOR_H */

View File

@@ -0,0 +1,42 @@
From: Abhishek Paliwal <abhishek.paliwal@aricent.com>
Date: Fri, 13 Feb 2015 12:48:16 +0530
Subject: misc/at24: Register memory accessor functions with of_memory_accessor
From: Aaron Williams <aaron.williams@cavium.com>
The at24 module will now register its memory accessor functions with its
device tree entry so that other modules may call these functions based on
the device tree node.
Signed-off-by: Aaron Williams <aaron.williams@cavium.com>
Signed-off-by: Leonid Rosenboim <lrosenboim@caviumnetworks.com>
Signed-off-by: Abhishek Paliwal <abhishek.paliwal@aricent.com>
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -23,6 +23,7 @@
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
+#include <linux/of_memory_accessor.h>
/* Address pointer is 16 bit. */
#define AT24_FLAG_ADDR16 BIT(7)
@@ -720,6 +721,9 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
byte_len, client->name,
writable ? "writable" : "read-only", at24->write_max);
+ if (client->dev.of_node)
+ of_memory_accessor_register(&client->dev, &at24->macc);
+
return 0;
}
@@ -728,5 +732,8 @@ static int at24_remove(struct i2c_client *client)
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
+ if (client->dev.of_node)
+ of_memory_accessor_remove(&client->dev);
+
return 0;
}