Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/phy/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 106 kB image not shown  

Quelle  marvell.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0+
/*
 * drivers/net/phy/marvell.c
 *
 * Driver for Marvell PHYs
 *
 * Author: Andy Fleming
 *
 * Copyright (c) 2004 Freescale Semiconductor, Inc.
 *
 * Copyright (c) 2013 Michael Stapelberg <michael@stapelberg.de>
 */

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/hwmon.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/phy.h>
#include <linux/marvell_phy.h>
#include <linux/bitfield.h>
#include <linux/of.h>
#include <linux/sfp.h>

#include <linux/io.h>
#include <asm/irq.h>
#include <linux/uaccess.h>

#define MII_MARVELL_PHY_PAGE  22
#define MII_MARVELL_COPPER_PAGE  0x00
#define MII_MARVELL_FIBER_PAGE  0x01
#define MII_MARVELL_MSCR_PAGE  0x02
#define MII_MARVELL_LED_PAGE  0x03
#define MII_MARVELL_VCT5_PAGE  0x05
#define MII_MARVELL_MISC_TEST_PAGE 0x06
#define MII_MARVELL_VCT7_PAGE  0x07
#define MII_MARVELL_WOL_PAGE  0x11
#define MII_MARVELL_MODE_PAGE  0x12

#define MII_M1011_IEVENT  0x13
#define MII_M1011_IEVENT_CLEAR  0x0000

#define MII_M1011_IMASK   0x12
#define MII_M1011_IMASK_INIT  0x6400
#define MII_M1011_IMASK_CLEAR  0x0000

#define MII_M1011_PHY_SCR   0x10
#define MII_M1011_PHY_SCR_DOWNSHIFT_EN  BIT(11)
#define MII_M1011_PHY_SCR_DOWNSHIFT_MASK GENMASK(14, 12)
#define MII_M1011_PHY_SCR_DOWNSHIFT_MAX  8
#define MII_M1011_PHY_SCR_MDI   (0x0 << 5)
#define MII_M1011_PHY_SCR_MDI_X   (0x1 << 5)
#define MII_M1011_PHY_SCR_AUTO_CROSS  (0x3 << 5)

#define MII_M1011_PHY_SSR   0x11
#define MII_M1011_PHY_SSR_DOWNSHIFT  BIT(5)

#define MII_M1111_PHY_LED_CONTROL 0x18
#define MII_M1111_PHY_LED_DIRECT 0x4100
#define MII_M1111_PHY_LED_COMBINE 0x411c
#define MII_M1111_PHY_EXT_CR  0x14
#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK GENMASK(11, 9)
#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_MAX 8
#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN BIT(8)
#define MII_M1111_RGMII_RX_DELAY BIT(7)
#define MII_M1111_RGMII_TX_DELAY BIT(1)
#define MII_M1111_PHY_EXT_SR  0x1b

#define MII_M1111_HWCFG_MODE_MASK  0xf
#define MII_M1111_HWCFG_MODE_FIBER_RGMII 0x3
#define MII_M1111_HWCFG_MODE_SGMII_NO_CLK 0x4
#define MII_M1111_HWCFG_MODE_RTBI  0x7
#define MII_M1111_HWCFG_MODE_COPPER_1000X_AN 0x8
#define MII_M1111_HWCFG_MODE_COPPER_RTBI 0x9
#define MII_M1111_HWCFG_MODE_COPPER_RGMII 0xb
#define MII_M1111_HWCFG_MODE_COPPER_1000X_NOAN 0xc
#define MII_M1111_HWCFG_SERIAL_AN_BYPASS BIT(12)
#define MII_M1111_HWCFG_FIBER_COPPER_RES BIT(13)
#define MII_M1111_HWCFG_FIBER_COPPER_AUTO BIT(15)

#define MII_88E1121_PHY_MSCR_REG 21
#define MII_88E1121_PHY_MSCR_RX_DELAY BIT(5)
#define MII_88E1121_PHY_MSCR_TX_DELAY BIT(4)
#define MII_88E1121_PHY_MSCR_DELAY_MASK (BIT(5) | BIT(4))

#define MII_88E1121_MISC_TEST    0x1a
#define MII_88E1510_MISC_TEST_TEMP_THRESHOLD_MASK 0x1f00
#define MII_88E1510_MISC_TEST_TEMP_THRESHOLD_SHIFT 8
#define MII_88E1510_MISC_TEST_TEMP_IRQ_EN  BIT(7)
#define MII_88E1510_MISC_TEST_TEMP_IRQ   BIT(6)
#define MII_88E1121_MISC_TEST_TEMP_SENSOR_EN  BIT(5)
#define MII_88E1121_MISC_TEST_TEMP_MASK   0x1f

#define MII_88E1510_TEMP_SENSOR  0x1b
#define MII_88E1510_TEMP_SENSOR_MASK 0xff

#define MII_88E1540_COPPER_CTRL3 0x1a
#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK GENMASK(11, 10)
#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS 0
#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS 1
#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS 2
#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS 3
#define MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN  BIT(9)

#define MII_88E6390_MISC_TEST  0x1b
#define MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE_SAMPLE_1S (0x0 << 14)
#define MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE  (0x1 << 14)
#define MII_88E6390_MISC_TEST_TEMP_SENSOR_ENABLE_ONESHOT (0x2 << 14)
#define MII_88E6390_MISC_TEST_TEMP_SENSOR_DISABLE  (0x3 << 14)
#define MII_88E6390_MISC_TEST_TEMP_SENSOR_MASK   (0x3 << 14)
#define MII_88E6393_MISC_TEST_SAMPLES_2048 (0x0 << 11)
#define MII_88E6393_MISC_TEST_SAMPLES_4096 (0x1 << 11)
#define MII_88E6393_MISC_TEST_SAMPLES_8192 (0x2 << 11)
#define MII_88E6393_MISC_TEST_SAMPLES_16384 (0x3 << 11)
#define MII_88E6393_MISC_TEST_SAMPLES_MASK (0x3 << 11)
#define MII_88E6393_MISC_TEST_RATE_2_3MS (0x5 << 8)
#define MII_88E6393_MISC_TEST_RATE_6_4MS (0x6 << 8)
#define MII_88E6393_MISC_TEST_RATE_11_9MS (0x7 << 8)
#define MII_88E6393_MISC_TEST_RATE_MASK  (0x7 << 8)

#define MII_88E6390_TEMP_SENSOR  0x1c
#define MII_88E6393_TEMP_SENSOR_THRESHOLD_MASK 0xff00
#define MII_88E6393_TEMP_SENSOR_THRESHOLD_SHIFT 8
#define MII_88E6390_TEMP_SENSOR_MASK  0xff
#define MII_88E6390_TEMP_SENSOR_SAMPLES  10

#define MII_88E1318S_PHY_MSCR1_REG 16
#define MII_88E1318S_PHY_MSCR1_PAD_ODD BIT(6)

/* Copper Specific Interrupt Enable Register */
#define MII_88E1318S_PHY_CSIER    0x12
/* WOL Event Interrupt Enable */
#define MII_88E1318S_PHY_CSIER_WOL_EIE   BIT(7)

#define MII_88E1318S_PHY_LED_FUNC  0x10
#define MII_88E1318S_PHY_LED_FUNC_OFF  (0x8)
#define MII_88E1318S_PHY_LED_FUNC_ON  (0x9)
#define MII_88E1318S_PHY_LED_FUNC_HI_Z  (0xa)
#define MII_88E1318S_PHY_LED_FUNC_BLINK  (0xb)
#define MII_88E1318S_PHY_LED_TCR  0x12
#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11)

/* Magic Packet MAC address registers */
#define MII_88E1318S_PHY_MAGIC_PACKET_WORD2  0x17
#define MII_88E1318S_PHY_MAGIC_PACKET_WORD1  0x18
#define MII_88E1318S_PHY_MAGIC_PACKET_WORD0  0x19

#define MII_88E1318S_PHY_WOL_CTRL    0x10
#define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS  BIT(12)
#define MII_88E1318S_PHY_WOL_CTRL_LINK_UP_ENABLE  BIT(13)
#define MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE BIT(14)

#define MII_PHY_LED_CTRL         16
#define MII_88E1121_PHY_LED_DEF  0x0030
#define MII_88E1510_PHY_LED_DEF  0x1177
#define MII_88E1510_PHY_LED0_LINK_LED1_ACTIVE 0x1040

#define MII_M1011_PHY_STATUS  0x11
#define MII_M1011_PHY_STATUS_1000 0x8000
#define MII_M1011_PHY_STATUS_100 0x4000
#define MII_M1011_PHY_STATUS_SPD_MASK 0xc000
#define MII_M1011_PHY_STATUS_FULLDUPLEX 0x2000
#define MII_M1011_PHY_STATUS_RESOLVED 0x0800
#define MII_M1011_PHY_STATUS_LINK 0x0400
#define MII_M1011_PHY_STATUS_MDIX BIT(6)

#define MII_88E3016_PHY_SPEC_CTRL 0x10
#define MII_88E3016_DISABLE_SCRAMBLER 0x0200
#define MII_88E3016_AUTO_MDIX_CROSSOVER 0x0030

#define MII_88E1510_GEN_CTRL_REG_1  0x14
#define MII_88E1510_GEN_CTRL_REG_1_MODE_MASK 0x7
#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII 0x0 /* RGMII to copper */
#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */
/* RGMII to 1000BASE-X */
#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X 0x2
/* RGMII to 100BASE-FX */
#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX 0x3
/* RGMII to SGMII */
#define MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII 0x4
#define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */

#define MII_88E1510_MSCR_2  0x15

#define MII_VCT5_TX_RX_MDI0_COUPLING 0x10
#define MII_VCT5_TX_RX_MDI1_COUPLING 0x11
#define MII_VCT5_TX_RX_MDI2_COUPLING 0x12
#define MII_VCT5_TX_RX_MDI3_COUPLING 0x13
#define MII_VCT5_TX_RX_AMPLITUDE_MASK 0x7f00
#define MII_VCT5_TX_RX_AMPLITUDE_SHIFT 8
#define MII_VCT5_TX_RX_COUPLING_POSITIVE_REFLECTION BIT(15)

#define MII_VCT5_CTRL    0x17
#define MII_VCT5_CTRL_ENABLE    BIT(15)
#define MII_VCT5_CTRL_COMPLETE    BIT(14)
#define MII_VCT5_CTRL_TX_SAME_CHANNEL   (0x0 << 11)
#define MII_VCT5_CTRL_TX0_CHANNEL   (0x4 << 11)
#define MII_VCT5_CTRL_TX1_CHANNEL   (0x5 << 11)
#define MII_VCT5_CTRL_TX2_CHANNEL   (0x6 << 11)
#define MII_VCT5_CTRL_TX3_CHANNEL   (0x7 << 11)
#define MII_VCT5_CTRL_SAMPLES_2    (0x0 << 8)
#define MII_VCT5_CTRL_SAMPLES_4    (0x1 << 8)
#define MII_VCT5_CTRL_SAMPLES_8    (0x2 << 8)
#define MII_VCT5_CTRL_SAMPLES_16   (0x3 << 8)
#define MII_VCT5_CTRL_SAMPLES_32   (0x4 << 8)
#define MII_VCT5_CTRL_SAMPLES_64   (0x5 << 8)
#define MII_VCT5_CTRL_SAMPLES_128   (0x6 << 8)
#define MII_VCT5_CTRL_SAMPLES_DEFAULT   (0x6 << 8)
#define MII_VCT5_CTRL_SAMPLES_256   (0x7 << 8)
#define MII_VCT5_CTRL_SAMPLES_SHIFT   8
#define MII_VCT5_CTRL_MODE_MAXIMUM_PEEK   (0x0 << 6)
#define MII_VCT5_CTRL_MODE_FIRST_LAST_PEEK  (0x1 << 6)
#define MII_VCT5_CTRL_MODE_OFFSET   (0x2 << 6)
#define MII_VCT5_CTRL_SAMPLE_POINT   (0x3 << 6)
#define MII_VCT5_CTRL_PEEK_HYST_DEFAULT   3

#define MII_VCT5_SAMPLE_POINT_DISTANCE  0x18
#define MII_VCT5_SAMPLE_POINT_DISTANCE_MAX 511
#define MII_VCT5_TX_PULSE_CTRL   0x1c
#define MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN BIT(12)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS (0x0 << 10)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_96nS  (0x1 << 10)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_64nS  (0x2 << 10)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS  (0x3 << 10)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_SHIFT 10
#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_1000mV (0x0 << 8)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_750mV (0x1 << 8)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_500mV (0x2 << 8)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_250mV (0x3 << 8)
#define MII_VCT5_TX_PULSE_CTRL_PULSE_AMPLITUDE_SHIFT 8
#define MII_VCT5_TX_PULSE_CTRL_MAX_AMP   BIT(7)
#define MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV  (0x6 << 0)

/* For TDR measurements less than 11 meters, a short pulse should be
 * used.
 */

#define TDR_SHORT_CABLE_LENGTH 11

#define MII_VCT7_PAIR_0_DISTANCE 0x10
#define MII_VCT7_PAIR_1_DISTANCE 0x11
#define MII_VCT7_PAIR_2_DISTANCE 0x12
#define MII_VCT7_PAIR_3_DISTANCE 0x13

#define MII_VCT7_RESULTS 0x14
#define MII_VCT7_RESULTS_PAIR3_MASK 0xf000
#define MII_VCT7_RESULTS_PAIR2_MASK 0x0f00
#define MII_VCT7_RESULTS_PAIR1_MASK 0x00f0
#define MII_VCT7_RESULTS_PAIR0_MASK 0x000f
#define MII_VCT7_RESULTS_PAIR3_SHIFT 12
#define MII_VCT7_RESULTS_PAIR2_SHIFT 8
#define MII_VCT7_RESULTS_PAIR1_SHIFT 4
#define MII_VCT7_RESULTS_PAIR0_SHIFT 0
#define MII_VCT7_RESULTS_INVALID 0
#define MII_VCT7_RESULTS_OK  1
#define MII_VCT7_RESULTS_OPEN  2
#define MII_VCT7_RESULTS_SAME_SHORT 3
#define MII_VCT7_RESULTS_CROSS_SHORT 4
#define MII_VCT7_RESULTS_BUSY  9

#define MII_VCT7_CTRL  0x15
#define MII_VCT7_CTRL_RUN_NOW   BIT(15)
#define MII_VCT7_CTRL_RUN_ANEG   BIT(14)
#define MII_VCT7_CTRL_DISABLE_CROSS  BIT(13)
#define MII_VCT7_CTRL_RUN_AFTER_BREAK_LINK BIT(12)
#define MII_VCT7_CTRL_IN_PROGRESS  BIT(11)
#define MII_VCT7_CTRL_METERS   BIT(10)
#define MII_VCT7_CTRL_CENTIMETERS  0

#define MII_VCT_TXPINS   0x1A
#define MII_VCT_RXPINS   0x1B
#define MII_VCT_SR   0x1C
#define MII_VCT_TXPINS_ENVCT  BIT(15)
#define MII_VCT_TXRXPINS_VCTTST  GENMASK(14, 13)
#define MII_VCT_TXRXPINS_VCTTST_SHIFT 13
#define MII_VCT_TXRXPINS_VCTTST_OK 0
#define MII_VCT_TXRXPINS_VCTTST_SHORT 1
#define MII_VCT_TXRXPINS_VCTTST_OPEN 2
#define MII_VCT_TXRXPINS_VCTTST_FAIL 3
#define MII_VCT_TXRXPINS_AMPRFLN GENMASK(12, 8)
#define MII_VCT_TXRXPINS_AMPRFLN_SHIFT 8
#define MII_VCT_TXRXPINS_DISTRFLN GENMASK(7, 0)
#define MII_VCT_TXRXPINS_DISTRFLN_MAX 0xff

#define M88E3082_PAIR_A  BIT(0)
#define M88E3082_PAIR_B  BIT(1)

#define LPA_PAUSE_FIBER  0x180
#define LPA_PAUSE_ASYM_FIBER 0x100

#define NB_FIBER_STATS 1
#define NB_STAT_MAX 3

MODULE_DESCRIPTION("Marvell PHY driver");
MODULE_AUTHOR("Andy Fleming");
MODULE_LICENSE("GPL");

struct marvell_hw_stat {
 const char *string;
 u8 page;
 u8 reg;
 u8 bits;
};

static const struct marvell_hw_stat marvell_hw_stats[] = {
 { "phy_receive_errors_copper", 0, 21, 16},
 { "phy_idle_errors", 0, 10, 8 },
 { "phy_receive_errors_fiber", 1, 21, 16},
};

static_assert(ARRAY_SIZE(marvell_hw_stats) <= NB_STAT_MAX);

/* "simple" stat list + corresponding marvell_get_*_simple functions are used
 * on PHYs without a page register
 */

struct marvell_hw_stat_simple {
 const char *string;
 u8 reg;
 u8 bits;
};

static const struct marvell_hw_stat_simple marvell_hw_stats_simple[] = {
 { "phy_receive_errors", 21, 16},
};

static_assert(ARRAY_SIZE(marvell_hw_stats_simple) <= NB_STAT_MAX);

enum {
 M88E3082_VCT_OFF,
 M88E3082_VCT_PHASE1,
 M88E3082_VCT_PHASE2,
};

struct marvell_priv {
 u64 stats[NB_STAT_MAX];
 char *hwmon_name;
 struct device *hwmon_dev;
 bool cable_test_tdr;
 u32 first;
 u32 last;
 u32 step;
 s8 pair;
 u8 vct_phase;
};

static int marvell_read_page(struct phy_device *phydev)
{
 return __phy_read(phydev, MII_MARVELL_PHY_PAGE);
}

static int marvell_write_page(struct phy_device *phydev, int page)
{
 return __phy_write(phydev, MII_MARVELL_PHY_PAGE, page);
}

static int marvell_set_page(struct phy_device *phydev, int page)
{
 return phy_write(phydev, MII_MARVELL_PHY_PAGE, page);
}

static int marvell_ack_interrupt(struct phy_device *phydev)
{
 int err;

 /* Clear the interrupts by reading the reg */
 err = phy_read(phydev, MII_M1011_IEVENT);

 if (err < 0)
  return err;

 return 0;
}

static int marvell_config_intr(struct phy_device *phydev)
{
 int err;

 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
  err = marvell_ack_interrupt(phydev);
  if (err)
   return err;

  err = phy_write(phydev, MII_M1011_IMASK,
    MII_M1011_IMASK_INIT);
 } else {
  err = phy_write(phydev, MII_M1011_IMASK,
    MII_M1011_IMASK_CLEAR);
  if (err)
   return err;

  err = marvell_ack_interrupt(phydev);
 }

 return err;
}

static irqreturn_t marvell_handle_interrupt(struct phy_device *phydev)
{
 int irq_status;

 irq_status = phy_read(phydev, MII_M1011_IEVENT);
 if (irq_status < 0) {
  phy_error(phydev);
  return IRQ_NONE;
 }

 if (!(irq_status & MII_M1011_IMASK_INIT))
  return IRQ_NONE;

 phy_trigger_machine(phydev);

 return IRQ_HANDLED;
}

static int marvell_set_polarity(struct phy_device *phydev, int polarity)
{
 u16 val;

 switch (polarity) {
 case ETH_TP_MDI:
  val = MII_M1011_PHY_SCR_MDI;
  break;
 case ETH_TP_MDI_X:
  val = MII_M1011_PHY_SCR_MDI_X;
  break;
 case ETH_TP_MDI_AUTO:
 case ETH_TP_MDI_INVALID:
 default:
  val = MII_M1011_PHY_SCR_AUTO_CROSS;
  break;
 }

 return phy_modify_changed(phydev, MII_M1011_PHY_SCR,
      MII_M1011_PHY_SCR_AUTO_CROSS, val);
}

static int marvell_config_aneg(struct phy_device *phydev)
{
 int changed = 0;
 int err;

 err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
 if (err < 0)
  return err;

 changed = err;

 err = phy_write(phydev, MII_M1111_PHY_LED_CONTROL,
   MII_M1111_PHY_LED_DIRECT);
 if (err < 0)
  return err;

 err = genphy_config_aneg(phydev);
 if (err < 0)
  return err;

 if (phydev->autoneg != AUTONEG_ENABLE || changed) {
  /* A write to speed/duplex bits (that is performed by
 * genphy_config_aneg() call above) must be followed by
 * a software reset. Otherwise, the write has no effect.
 */

  err = genphy_soft_reset(phydev);
  if (err < 0)
   return err;
 }

 return 0;
}

static int m88e1101_config_aneg(struct phy_device *phydev)
{
 int err;

 /* This Marvell PHY has an errata which requires
 * that certain registers get written in order
 * to restart autonegotiation
 */

 err = genphy_soft_reset(phydev);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1d, 0x1f);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1e, 0x200c);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1d, 0x5);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1e, 0);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1e, 0x100);
 if (err < 0)
  return err;

 return marvell_config_aneg(phydev);
}

#if IS_ENABLED(CONFIG_OF_MDIO)
/* Set and/or override some configuration registers based on the
 * marvell,reg-init property stored in the of_node for the phydev.
 *
 * marvell,reg-init = <reg-page reg mask value>,...;
 *
 * There may be one or more sets of <reg-page reg mask value>:
 *
 * reg-page: which register bank to use.
 * reg: the register.
 * mask: if non-zero, ANDed with existing register value.
 * value: ORed with the masked value and written to the regiser.
 *
 */

static int marvell_of_reg_init(struct phy_device *phydev)
{
 const __be32 *paddr;
 int len, i, saved_page, current_page, ret = 0;

 if (!phydev->mdio.dev.of_node)
  return 0;

 paddr = of_get_property(phydev->mdio.dev.of_node,
    "marvell,reg-init", &len);
 if (!paddr || len < (4 * sizeof(*paddr)))
  return 0;

 saved_page = phy_save_page(phydev);
 if (saved_page < 0)
  goto err;
 current_page = saved_page;

 len /= sizeof(*paddr);
 for (i = 0; i < len - 3; i += 4) {
  u16 page = be32_to_cpup(paddr + i);
  u16 reg = be32_to_cpup(paddr + i + 1);
  u16 mask = be32_to_cpup(paddr + i + 2);
  u16 val_bits = be32_to_cpup(paddr + i + 3);
  int val;

  if (page != current_page) {
   current_page = page;
   ret = marvell_write_page(phydev, page);
   if (ret < 0)
    goto err;
  }

  val = 0;
  if (mask) {
   val = __phy_read(phydev, reg);
   if (val < 0) {
    ret = val;
    goto err;
   }
   val &= mask;
  }
  val |= val_bits;

  ret = __phy_write(phydev, reg, val);
  if (ret < 0)
   goto err;
 }
err:
 return phy_restore_page(phydev, saved_page, ret);
}
#else
static int marvell_of_reg_init(struct phy_device *phydev)
{
 return 0;
}
#endif /* CONFIG_OF_MDIO */

static int m88e1121_config_aneg_rgmii_delays(struct phy_device *phydev)
{
 int mscr;

 if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
  mscr = MII_88E1121_PHY_MSCR_RX_DELAY |
         MII_88E1121_PHY_MSCR_TX_DELAY;
 else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID)
  mscr = MII_88E1121_PHY_MSCR_RX_DELAY;
 else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
  mscr = MII_88E1121_PHY_MSCR_TX_DELAY;
 else
  mscr = 0;

 return phy_modify_paged_changed(phydev, MII_MARVELL_MSCR_PAGE,
     MII_88E1121_PHY_MSCR_REG,
     MII_88E1121_PHY_MSCR_DELAY_MASK, mscr);
}

static int m88e1121_config_aneg(struct phy_device *phydev)
{
 int changed = 0;
 int err = 0;

 if (phy_interface_is_rgmii(phydev)) {
  err = m88e1121_config_aneg_rgmii_delays(phydev);
  if (err < 0)
   return err;
 }

 changed = err;

 err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
 if (err < 0)
  return err;

 changed |= err;

 err = genphy_config_aneg(phydev);
 if (err < 0)
  return err;

 if (phydev->autoneg != AUTONEG_ENABLE || changed) {
  /* A software reset is used to ensure a "commit" of the
 * changes is done.
 */

  err = genphy_soft_reset(phydev);
  if (err < 0)
   return err;
 }

 return 0;
}

static int m88e1318_config_aneg(struct phy_device *phydev)
{
 int err;

 err = phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE,
          MII_88E1318S_PHY_MSCR1_REG,
          0, MII_88E1318S_PHY_MSCR1_PAD_ODD);
 if (err < 0)
  return err;

 return m88e1121_config_aneg(phydev);
}

/**
 * linkmode_adv_to_fiber_adv_t
 * @advertise: the linkmode advertisement settings
 *
 * A small helper function that translates linkmode advertisement
 * settings to phy autonegotiation advertisements for the MII_ADV
 * register for fiber link.
 */

static inline u32 linkmode_adv_to_fiber_adv_t(unsigned long *advertise)
{
 u32 result = 0;

 if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, advertise))
  result |= ADVERTISE_1000XHALF;
 if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, advertise))
  result |= ADVERTISE_1000XFULL;

 if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertise) &&
     linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertise))
  result |= ADVERTISE_1000XPSE_ASYM;
 else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertise))
  result |= ADVERTISE_1000XPAUSE;

 return result;
}

/**
 * marvell_config_aneg_fiber - restart auto-negotiation or write BMCR
 * @phydev: target phy_device struct
 *
 * Description: If auto-negotiation is enabled, we configure the
 *   advertising, and then restart auto-negotiation.  If it is not
 *   enabled, then we write the BMCR. Adapted for fiber link in
 *   some Marvell's devices.
 */

static int marvell_config_aneg_fiber(struct phy_device *phydev)
{
 int changed = 0;
 int err;
 u16 adv;

 if (phydev->autoneg != AUTONEG_ENABLE)
  return genphy_setup_forced(phydev);

 /* Only allow advertising what this PHY supports */
 linkmode_and(phydev->advertising, phydev->advertising,
       phydev->supported);

 adv = linkmode_adv_to_fiber_adv_t(phydev->advertising);

 /* Setup fiber advertisement */
 err = phy_modify_changed(phydev, MII_ADVERTISE,
     ADVERTISE_1000XHALF | ADVERTISE_1000XFULL |
     ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM,
     adv);
 if (err < 0)
  return err;
 if (err > 0)
  changed = 1;

 return genphy_check_and_restart_aneg(phydev, changed);
}

static unsigned int m88e1111_inband_caps(struct phy_device *phydev,
      phy_interface_t interface)
{
 /* In 1000base-X and SGMII modes, the inband mode can be changed
 * through the Fibre page BMCR ANENABLE bit.
 */

 if (interface == PHY_INTERFACE_MODE_1000BASEX ||
     interface == PHY_INTERFACE_MODE_SGMII)
  return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE |
         LINK_INBAND_BYPASS;

 return 0;
}

static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
{
 u16 extsr, bmcr;
 int err;

 if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
     phydev->interface != PHY_INTERFACE_MODE_SGMII)
  return -EINVAL;

 if (modes == LINK_INBAND_BYPASS)
  extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
 else
  extsr = 0;

 if (modes == LINK_INBAND_DISABLE)
  bmcr = 0;
 else
  bmcr = BMCR_ANENABLE;

 err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
    MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
 if (err < 0)
  return extsr;

 return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
    BMCR_ANENABLE, bmcr);
}

static int m88e1111_config_aneg(struct phy_device *phydev)
{
 int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
 int err;

 if (extsr < 0)
  return extsr;

 /* If not using SGMII or copper 1000BaseX modes, use normal process.
 * Steps below are only required for these modes.
 */

 if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
     (extsr & MII_M1111_HWCFG_MODE_MASK) !=
     MII_M1111_HWCFG_MODE_COPPER_1000X_AN)
  return marvell_config_aneg(phydev);

 err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 if (err < 0)
  goto error;

 /* Configure the copper link first */
 err = marvell_config_aneg(phydev);
 if (err < 0)
  goto error;

 /* Then the fiber link */
 err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
 if (err < 0)
  goto error;

 if (phydev->interface == PHY_INTERFACE_MODE_SGMII)
  /* Do not touch the fiber advertisement if we're in copper->sgmii mode.
 * Just ensure that SGMII-side autonegotiation is enabled.
 * If we switched from some other mode to SGMII it may not be.
 */

  err = genphy_check_and_restart_aneg(phydev, false);
 else
  err = marvell_config_aneg_fiber(phydev);
 if (err < 0)
  goto error;

 return marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);

error:
 marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 return err;
}

static int m88e1510_config_aneg(struct phy_device *phydev)
{
 int err;

 err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 if (err < 0)
  goto error;

 /* Configure the copper link first */
 err = m88e1318_config_aneg(phydev);
 if (err < 0)
  goto error;

 /* Do not touch the fiber page if we're in copper->sgmii mode */
 if (phydev->interface == PHY_INTERFACE_MODE_SGMII)
  return 0;

 /* Then the fiber link */
 err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
 if (err < 0)
  goto error;

 err = marvell_config_aneg_fiber(phydev);
 if (err < 0)
  goto error;

 return marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);

error:
 marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 return err;
}

static void marvell_config_led(struct phy_device *phydev)
{
 u16 def_config;
 int err;

 switch (MARVELL_PHY_FAMILY_ID(phydev->phy_id)) {
 /* Default PHY LED config: LED[0] .. Link, LED[1] .. Activity */
 case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1121R):
 case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1318S):
  def_config = MII_88E1121_PHY_LED_DEF;
  break;
 /* Default PHY LED config:
 * LED[0] .. 1000Mbps Link
 * LED[1] .. 100Mbps Link
 * LED[2] .. Blink, Activity
 */

 case MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E1510):
  if (phydev->dev_flags & MARVELL_PHY_LED0_LINK_LED1_ACTIVE)
   def_config = MII_88E1510_PHY_LED0_LINK_LED1_ACTIVE;
  else
   def_config = MII_88E1510_PHY_LED_DEF;
  break;
 default:
  return;
 }

 err = phy_write_paged(phydev, MII_MARVELL_LED_PAGE, MII_PHY_LED_CTRL,
         def_config);
 if (err < 0)
  phydev_warn(phydev, "Fail to config marvell phy LED.\n");
}

static int marvell_config_init(struct phy_device *phydev)
{
 /* Set default LED */
 marvell_config_led(phydev);

 /* Set registers from marvell,reg-init DT property */
 return marvell_of_reg_init(phydev);
}

static int m88e3016_config_init(struct phy_device *phydev)
{
 int ret;

 /* Enable Scrambler and Auto-Crossover */
 ret = phy_modify(phydev, MII_88E3016_PHY_SPEC_CTRL,
    MII_88E3016_DISABLE_SCRAMBLER,
    MII_88E3016_AUTO_MDIX_CROSSOVER);
 if (ret < 0)
  return ret;

 return marvell_config_init(phydev);
}

static int m88e1111_config_init_hwcfg_mode(struct phy_device *phydev,
        u16 mode,
        int fibre_copper_auto)
{
 if (fibre_copper_auto)
  mode |= MII_M1111_HWCFG_FIBER_COPPER_AUTO;

 return phy_modify(phydev, MII_M1111_PHY_EXT_SR,
     MII_M1111_HWCFG_MODE_MASK |
     MII_M1111_HWCFG_FIBER_COPPER_AUTO |
     MII_M1111_HWCFG_FIBER_COPPER_RES,
     mode);
}

static int m88e1111_config_init_rgmii_delays(struct phy_device *phydev)
{
 int delay;

 switch (phydev->interface) {
 case PHY_INTERFACE_MODE_RGMII_ID:
  delay = MII_M1111_RGMII_RX_DELAY | MII_M1111_RGMII_TX_DELAY;
  break;
 case PHY_INTERFACE_MODE_RGMII_RXID:
  delay = MII_M1111_RGMII_RX_DELAY;
  break;
 case PHY_INTERFACE_MODE_RGMII_TXID:
  delay = MII_M1111_RGMII_TX_DELAY;
  break;
 default:
  delay = 0;
  break;
 }

 return phy_modify(phydev, MII_M1111_PHY_EXT_CR,
     MII_M1111_RGMII_RX_DELAY | MII_M1111_RGMII_TX_DELAY,
     delay);
}

static int m88e1111_config_init_rgmii(struct phy_device *phydev)
{
 int temp;
 int err;

 err = m88e1111_config_init_rgmii_delays(phydev);
 if (err < 0)
  return err;

 temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
 if (temp < 0)
  return temp;

 temp &= ~(MII_M1111_HWCFG_MODE_MASK);

 if (temp & MII_M1111_HWCFG_FIBER_COPPER_RES)
  temp |= MII_M1111_HWCFG_MODE_FIBER_RGMII;
 else
  temp |= MII_M1111_HWCFG_MODE_COPPER_RGMII;

 return phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
}

static int m88e1111_config_init_sgmii(struct phy_device *phydev)
{
 int err;

 err = m88e1111_config_init_hwcfg_mode(
  phydev,
  MII_M1111_HWCFG_MODE_SGMII_NO_CLK,
  MII_M1111_HWCFG_FIBER_COPPER_AUTO);
 if (err < 0)
  return err;

 /* make sure copper is selected */
 return marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
}

static int m88e1111_config_init_rtbi(struct phy_device *phydev)
{
 int err;

 err = m88e1111_config_init_rgmii_delays(phydev);
 if (err < 0)
  return err;

 err = m88e1111_config_init_hwcfg_mode(
  phydev,
  MII_M1111_HWCFG_MODE_RTBI,
  MII_M1111_HWCFG_FIBER_COPPER_AUTO);
 if (err < 0)
  return err;

 /* soft reset */
 err = genphy_soft_reset(phydev);
 if (err < 0)
  return err;

 return m88e1111_config_init_hwcfg_mode(
  phydev,
  MII_M1111_HWCFG_MODE_RTBI,
  MII_M1111_HWCFG_FIBER_COPPER_AUTO);
}

static int m88e1111_config_init_1000basex(struct phy_device *phydev)
{
 int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
 int err, mode;

 if (extsr < 0)
  return extsr;

 /* If using copper mode, ensure 1000BaseX auto-negotiation is enabled.
 * FIXME: this does not actually enable 1000BaseX auto-negotiation if
 * it was previously disabled in the Fiber BMCR!
 */

 mode = extsr & MII_M1111_HWCFG_MODE_MASK;
 if (mode == MII_M1111_HWCFG_MODE_COPPER_1000X_NOAN) {
  err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
     MII_M1111_HWCFG_MODE_MASK |
     MII_M1111_HWCFG_SERIAL_AN_BYPASS,
     MII_M1111_HWCFG_MODE_COPPER_1000X_AN |
     MII_M1111_HWCFG_SERIAL_AN_BYPASS);
  if (err < 0)
   return err;
 }
 return 0;
}

static int m88e1111_config_init(struct phy_device *phydev)
{
 int err;

 if (phy_interface_is_rgmii(phydev)) {
  err = m88e1111_config_init_rgmii(phydev);
  if (err < 0)
   return err;
 }

 if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
  err = m88e1111_config_init_sgmii(phydev);
  if (err < 0)
   return err;
 }

 if (phydev->interface == PHY_INTERFACE_MODE_RTBI) {
  err = m88e1111_config_init_rtbi(phydev);
  if (err < 0)
   return err;
 }

 if (phydev->interface == PHY_INTERFACE_MODE_1000BASEX) {
  err = m88e1111_config_init_1000basex(phydev);
  if (err < 0)
   return err;
 }

 err = marvell_of_reg_init(phydev);
 if (err < 0)
  return err;

 err = genphy_soft_reset(phydev);
 if (err < 0)
  return err;

 if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
  /* If the HWCFG_MODE was changed from another mode (such as
 * 1000BaseX) to SGMII, the state of the support bits may have
 * also changed now that the PHY has been reset.
 * Update the PHY abilities accordingly.
 */

  err = genphy_read_abilities(phydev);
  linkmode_or(phydev->advertising, phydev->advertising,
       phydev->supported);
 }
 return err;
}

static int m88e1111_get_downshift(struct phy_device *phydev, u8 *data)
{
 int val, cnt, enable;

 val = phy_read(phydev, MII_M1111_PHY_EXT_CR);
 if (val < 0)
  return val;

 enable = FIELD_GET(MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN, val);
 cnt = FIELD_GET(MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, val) + 1;

 *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE;

 return 0;
}

static int m88e1111_set_downshift(struct phy_device *phydev, u8 cnt)
{
 int val, err;

 if (cnt > MII_M1111_PHY_EXT_CR_DOWNSHIFT_MAX)
  return -E2BIG;

 if (!cnt) {
  err = phy_clear_bits(phydev, MII_M1111_PHY_EXT_CR,
         MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN);
 } else {
  val = MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN;
  val |= FIELD_PREP(MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, cnt - 1);

  err = phy_modify(phydev, MII_M1111_PHY_EXT_CR,
     MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN |
     MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK,
     val);
 }

 if (err < 0)
  return err;

 return genphy_soft_reset(phydev);
}

static int m88e1111_get_tunable(struct phy_device *phydev,
    struct ethtool_tunable *tuna, void *data)
{
 switch (tuna->id) {
 case ETHTOOL_PHY_DOWNSHIFT:
  return m88e1111_get_downshift(phydev, data);
 default:
  return -EOPNOTSUPP;
 }
}

static int m88e1111_set_tunable(struct phy_device *phydev,
    struct ethtool_tunable *tuna, const void *data)
{
 switch (tuna->id) {
 case ETHTOOL_PHY_DOWNSHIFT:
  return m88e1111_set_downshift(phydev, *(const u8 *)data);
 default:
  return -EOPNOTSUPP;
 }
}

static int m88e1011_get_downshift(struct phy_device *phydev, u8 *data)
{
 int val, cnt, enable;

 val = phy_read(phydev, MII_M1011_PHY_SCR);
 if (val < 0)
  return val;

 enable = FIELD_GET(MII_M1011_PHY_SCR_DOWNSHIFT_EN, val);
 cnt = FIELD_GET(MII_M1011_PHY_SCR_DOWNSHIFT_MASK, val) + 1;

 *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE;

 return 0;
}

static int m88e1011_set_downshift(struct phy_device *phydev, u8 cnt)
{
 int val, err;

 if (cnt > MII_M1011_PHY_SCR_DOWNSHIFT_MAX)
  return -E2BIG;

 if (!cnt) {
  err = phy_clear_bits(phydev, MII_M1011_PHY_SCR,
         MII_M1011_PHY_SCR_DOWNSHIFT_EN);
 } else {
  val = MII_M1011_PHY_SCR_DOWNSHIFT_EN;
  val |= FIELD_PREP(MII_M1011_PHY_SCR_DOWNSHIFT_MASK, cnt - 1);

  err = phy_modify(phydev, MII_M1011_PHY_SCR,
     MII_M1011_PHY_SCR_DOWNSHIFT_EN |
     MII_M1011_PHY_SCR_DOWNSHIFT_MASK,
     val);
 }

 if (err < 0)
  return err;

 return genphy_soft_reset(phydev);
}

static int m88e1011_get_tunable(struct phy_device *phydev,
    struct ethtool_tunable *tuna, void *data)
{
 switch (tuna->id) {
 case ETHTOOL_PHY_DOWNSHIFT:
  return m88e1011_get_downshift(phydev, data);
 default:
  return -EOPNOTSUPP;
 }
}

static int m88e1011_set_tunable(struct phy_device *phydev,
    struct ethtool_tunable *tuna, const void *data)
{
 switch (tuna->id) {
 case ETHTOOL_PHY_DOWNSHIFT:
  return m88e1011_set_downshift(phydev, *(const u8 *)data);
 default:
  return -EOPNOTSUPP;
 }
}

static int m88e1112_config_init(struct phy_device *phydev)
{
 int err;

 err = m88e1011_set_downshift(phydev, 3);
 if (err < 0)
  return err;

 return m88e1111_config_init(phydev);
}

static int m88e1111gbe_config_init(struct phy_device *phydev)
{
 int err;

 err = m88e1111_set_downshift(phydev, 3);
 if (err < 0)
  return err;

 return m88e1111_config_init(phydev);
}

static int marvell_1011gbe_config_init(struct phy_device *phydev)
{
 int err;

 err = m88e1011_set_downshift(phydev, 3);
 if (err < 0)
  return err;

 return marvell_config_init(phydev);
}
static int m88e1116r_config_init(struct phy_device *phydev)
{
 int err;

 err = genphy_soft_reset(phydev);
 if (err < 0)
  return err;

 msleep(500);

 err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 if (err < 0)
  return err;

 err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
 if (err < 0)
  return err;

 err = m88e1011_set_downshift(phydev, 8);
 if (err < 0)
  return err;

 if (phy_interface_is_rgmii(phydev)) {
  err = m88e1121_config_aneg_rgmii_delays(phydev);
  if (err < 0)
   return err;
 }

 err = genphy_soft_reset(phydev);
 if (err < 0)
  return err;

 return marvell_config_init(phydev);
}

static int m88e1318_config_init(struct phy_device *phydev)
{
 if (phy_interrupt_is_valid(phydev)) {
  int err = phy_modify_paged(
   phydev, MII_MARVELL_LED_PAGE,
   MII_88E1318S_PHY_LED_TCR,
   MII_88E1318S_PHY_LED_TCR_FORCE_INT,
   MII_88E1318S_PHY_LED_TCR_INTn_ENABLE |
   MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW);
  if (err < 0)
   return err;
 }

 return marvell_config_init(phydev);
}

static int m88e1510_config_init(struct phy_device *phydev)
{
 static const struct {
  u16 reg17, reg16;
 } errata_vals[] = {
  { 0x214b, 0x2144 },
  { 0x0c28, 0x2146 },
  { 0xb233, 0x214d },
  { 0xcc0c, 0x2159 },
 };
 int err;
 int i;

 /* As per Marvell Release Notes - Alaska 88E1510/88E1518/88E1512/
 * 88E1514 Rev A0, Errata Section 5.1:
 * If EEE is intended to be used, the following register writes
 * must be done once after every hardware reset.
 */

 err = marvell_set_page(phydev, 0x00FF);
 if (err < 0)
  return err;

 for (i = 0; i < ARRAY_SIZE(errata_vals); ++i) {
  err = phy_write(phydev, 17, errata_vals[i].reg17);
  if (err)
   return err;
  err = phy_write(phydev, 16, errata_vals[i].reg16);
  if (err)
   return err;
 }

 err = marvell_set_page(phydev, 0x00FB);
 if (err < 0)
  return err;
 err = phy_write(phydev, 07, 0xC00D);
 if (err < 0)
  return err;
 err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 if (err < 0)
  return err;

 /* SGMII-to-Copper mode initialization */
 if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
  /* Select page 18 */
  err = marvell_set_page(phydev, 18);
  if (err < 0)
   return err;

  /* In reg 20, write MODE[2:0] = 0x1 (SGMII to Copper) */
  err = phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1,
     MII_88E1510_GEN_CTRL_REG_1_MODE_MASK,
     MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII);
  if (err < 0)
   return err;

  /* PHY reset is necessary after changing MODE[2:0] */
  err = phy_set_bits(phydev, MII_88E1510_GEN_CTRL_REG_1,
       MII_88E1510_GEN_CTRL_REG_1_RESET);
  if (err < 0)
   return err;

  /* Reset page selection */
  err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
  if (err < 0)
   return err;
 }
 err = m88e1011_set_downshift(phydev, 3);
 if (err < 0)
  return err;

 return m88e1318_config_init(phydev);
}

static int m88e1118_config_aneg(struct phy_device *phydev)
{
 int err;

 err = marvell_set_polarity(phydev, phydev->mdix_ctrl);
 if (err < 0)
  return err;

 err = genphy_config_aneg(phydev);
 if (err < 0)
  return err;

 return genphy_soft_reset(phydev);
}

static int m88e1118_config_init(struct phy_device *phydev)
{
 u16 leds;
 int err;

 /* Enable 1000 Mbit */
 err = phy_write_paged(phydev, MII_MARVELL_MSCR_PAGE,
         MII_88E1121_PHY_MSCR_REG, 0x1070);
 if (err < 0)
  return err;

 if (phy_interface_is_rgmii(phydev)) {
  err = m88e1121_config_aneg_rgmii_delays(phydev);
  if (err < 0)
   return err;
 }

 /* Adjust LED Control */
 if (phydev->dev_flags & MARVELL_PHY_M1118_DNS323_LEDS)
  leds = 0x1100;
 else
  leds = 0x021e;

 err = phy_write_paged(phydev, MII_MARVELL_LED_PAGE, 0x10, leds);
 if (err < 0)
  return err;

 err = marvell_of_reg_init(phydev);
 if (err < 0)
  return err;

 /* Reset page register */
 err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 if (err < 0)
  return err;

 return genphy_soft_reset(phydev);
}

static int m88e1149_config_init(struct phy_device *phydev)
{
 int err;

 /* Change address */
 err = marvell_set_page(phydev, MII_MARVELL_MSCR_PAGE);
 if (err < 0)
  return err;

 /* Enable 1000 Mbit */
 err = phy_write(phydev, 0x15, 0x1048);
 if (err < 0)
  return err;

 err = marvell_of_reg_init(phydev);
 if (err < 0)
  return err;

 /* Reset address */
 err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 if (err < 0)
  return err;

 return genphy_soft_reset(phydev);
}

static int m88e1145_config_init_rgmii(struct phy_device *phydev)
{
 int err;

 err = m88e1111_config_init_rgmii_delays(phydev);
 if (err < 0)
  return err;

 if (phydev->dev_flags & MARVELL_PHY_M1145_FLAGS_RESISTANCE) {
  err = phy_write(phydev, 0x1d, 0x0012);
  if (err < 0)
   return err;

  err = phy_modify(phydev, 0x1e, 0x0fc0,
     2 << 9 | /* 36 ohm */
     2 << 6); /* 39 ohm */
  if (err < 0)
   return err;

  err = phy_write(phydev, 0x1d, 0x3);
  if (err < 0)
   return err;

  err = phy_write(phydev, 0x1e, 0x8000);
 }
 return err;
}

static int m88e1145_config_init_sgmii(struct phy_device *phydev)
{
 return m88e1111_config_init_hwcfg_mode(
  phydev, MII_M1111_HWCFG_MODE_SGMII_NO_CLK,
  MII_M1111_HWCFG_FIBER_COPPER_AUTO);
}

static int m88e1145_config_init(struct phy_device *phydev)
{
 int err;

 /* Take care of errata E0 & E1 */
 err = phy_write(phydev, 0x1d, 0x001b);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1e, 0x418f);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1d, 0x0016);
 if (err < 0)
  return err;

 err = phy_write(phydev, 0x1e, 0xa2da);
 if (err < 0)
  return err;

 if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
  err = m88e1145_config_init_rgmii(phydev);
  if (err < 0)
   return err;
 }

 if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
  err = m88e1145_config_init_sgmii(phydev);
  if (err < 0)
   return err;
 }
 err = m88e1111_set_downshift(phydev, 3);
 if (err < 0)
  return err;

 err = marvell_of_reg_init(phydev);
 if (err < 0)
  return err;

 return 0;
}

static int m88e1540_get_fld(struct phy_device *phydev, u8 *msecs)
{
 int val;

 val = phy_read(phydev, MII_88E1540_COPPER_CTRL3);
 if (val < 0)
  return val;

 if (!(val & MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN)) {
  *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF;
  return 0;
 }

 val = FIELD_GET(MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);

 switch (val) {
 case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS:
  *msecs = 0;
  break;
 case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS:
  *msecs = 10;
  break;
 case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS:
  *msecs = 20;
  break;
 case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS:
  *msecs = 40;
  break;
 default:
  return -EINVAL;
 }

 return 0;
}

static int m88e1540_set_fld(struct phy_device *phydev, const u8 *msecs)
{
 int val, ret;

 if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
  return phy_clear_bits(phydev, MII_88E1540_COPPER_CTRL3,
          MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN);

 /* According to the Marvell data sheet EEE must be disabled for
 * Fast Link Down detection to work properly
 */

 if (phydev->eee_cfg.eee_enabled) {
  phydev_warn(phydev, "Fast Link Down detection requires EEE to be disabled!\n");
  return -EBUSY;
 }

 if (*msecs <= 5)
  val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS;
 else if (*msecs <= 15)
  val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS;
 else if (*msecs <= 30)
  val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS;
 else
  val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS;

 val = FIELD_PREP(MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);

 ret = phy_modify(phydev, MII_88E1540_COPPER_CTRL3,
    MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
 if (ret)
  return ret;

 return phy_set_bits(phydev, MII_88E1540_COPPER_CTRL3,
       MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN);
}

static int m88e1540_get_tunable(struct phy_device *phydev,
    struct ethtool_tunable *tuna, void *data)
{
 switch (tuna->id) {
 case ETHTOOL_PHY_FAST_LINK_DOWN:
  return m88e1540_get_fld(phydev, data);
 case ETHTOOL_PHY_DOWNSHIFT:
  return m88e1011_get_downshift(phydev, data);
 default:
  return -EOPNOTSUPP;
 }
}

static int m88e1540_set_tunable(struct phy_device *phydev,
    struct ethtool_tunable *tuna, const void *data)
{
 switch (tuna->id) {
 case ETHTOOL_PHY_FAST_LINK_DOWN:
  return m88e1540_set_fld(phydev, data);
 case ETHTOOL_PHY_DOWNSHIFT:
  return m88e1011_set_downshift(phydev, *(const u8 *)data);
 default:
  return -EOPNOTSUPP;
 }
}

/* The VOD can be out of specification on link up. Poke an
 * undocumented register, in an undocumented page, with a magic value
 * to fix this.
 */

static int m88e6390_errata(struct phy_device *phydev)
{
 int err;

 err = phy_write(phydev, MII_BMCR,
   BMCR_ANENABLE | BMCR_SPEED1000 | BMCR_FULLDPLX);
 if (err)
  return err;

 usleep_range(300, 400);

 err = phy_write_paged(phydev, 0xf8, 0x08, 0x36);
 if (err)
  return err;

 return genphy_soft_reset(phydev);
}

static int m88e6390_config_aneg(struct phy_device *phydev)
{
 int err;

 err = m88e6390_errata(phydev);
 if (err)
  return err;

 return m88e1510_config_aneg(phydev);
}

/**
 * fiber_lpa_mod_linkmode_lpa_t
 * @advertising: the linkmode advertisement settings
 * @lpa: value of the MII_LPA register for fiber link
 *
 * A small helper function that translates MII_LPA bits to linkmode LP
 * advertisement settings. Other bits in advertising are left
 * unchanged.
 */

static void fiber_lpa_mod_linkmode_lpa_t(unsigned long *advertising, u32 lpa)
{
 linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
    advertising, lpa & LPA_1000XHALF);

 linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
    advertising, lpa & LPA_1000XFULL);
}

static int marvell_read_status_page_an(struct phy_device *phydev,
           int fiber, int status)
{
 int lpa;
 int err;

 if (!(status & MII_M1011_PHY_STATUS_RESOLVED)) {
  phydev->link = 0;
  return 0;
 }

 if (status & MII_M1011_PHY_STATUS_FULLDUPLEX)
  phydev->duplex = DUPLEX_FULL;
 else
  phydev->duplex = DUPLEX_HALF;

 switch (status & MII_M1011_PHY_STATUS_SPD_MASK) {
 case MII_M1011_PHY_STATUS_1000:
  phydev->speed = SPEED_1000;
  break;

 case MII_M1011_PHY_STATUS_100:
  phydev->speed = SPEED_100;
  break;

 default:
  phydev->speed = SPEED_10;
  break;
 }

 if (!fiber) {
  err = genphy_read_lpa(phydev);
  if (err < 0)
   return err;

  phy_resolve_aneg_pause(phydev);
 } else {
  lpa = phy_read(phydev, MII_LPA);
  if (lpa < 0)
   return lpa;

  /* The fiber link is only 1000M capable */
  fiber_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa);

  if (phydev->duplex == DUPLEX_FULL) {
   if (!(lpa & LPA_PAUSE_FIBER)) {
    phydev->pause = 0;
    phydev->asym_pause = 0;
   } else if ((lpa & LPA_PAUSE_ASYM_FIBER)) {
    phydev->pause = 1;
    phydev->asym_pause = 1;
   } else {
    phydev->pause = 1;
    phydev->asym_pause = 0;
   }
  }
 }

 return 0;
}

/* marvell_read_status_page
 *
 * Description:
 *   Check the link, then figure out the current state
 *   by comparing what we advertise with what the link partner
 *   advertises.  Start by checking the gigabit possibilities,
 *   then move on to 10/100.
 */

static int marvell_read_status_page(struct phy_device *phydev, int page)
{
 int status;
 int fiber;
 int err;

 status = phy_read(phydev, MII_M1011_PHY_STATUS);
 if (status < 0)
  return status;

 /* Use the generic register for copper link status,
 * and the PHY status register for fiber link status.
 */

 if (page == MII_MARVELL_FIBER_PAGE) {
  phydev->link = !!(status & MII_M1011_PHY_STATUS_LINK);
 } else {
  err = genphy_update_link(phydev);
  if (err)
   return err;
 }

 if (page == MII_MARVELL_FIBER_PAGE)
  fiber = 1;
 else
  fiber = 0;

 linkmode_zero(phydev->lp_advertising);
 phydev->pause = 0;
 phydev->asym_pause = 0;
 phydev->speed = SPEED_UNKNOWN;
 phydev->duplex = DUPLEX_UNKNOWN;
 phydev->port = fiber ? PORT_FIBRE : PORT_TP;

 if (fiber) {
  phydev->mdix = ETH_TP_MDI_INVALID;
 } else {
  /* The MDI-X state is set regardless of Autoneg being enabled
 * and reflects forced MDI-X state as well as auto resolution
 */

  if (status & MII_M1011_PHY_STATUS_RESOLVED)
   phydev->mdix = status & MII_M1011_PHY_STATUS_MDIX ?
    ETH_TP_MDI_X : ETH_TP_MDI;
  else
   phydev->mdix = ETH_TP_MDI_INVALID;
 }

 if (phydev->autoneg == AUTONEG_ENABLE)
  err = marvell_read_status_page_an(phydev, fiber, status);
 else
  err = genphy_read_status_fixed(phydev);

 return err;
}

/* marvell_read_status
 *
 * Some Marvell's phys have two modes: fiber and copper.
 * Both need status checked.
 * Description:
 *   First, check the fiber link and status.
 *   If the fiber link is down, check the copper link and status which
 *   will be the default value if both link are down.
 */

static int marvell_read_status(struct phy_device *phydev)
{
 int err;

 /* Check the fiber mode first */
 if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
         phydev->supported) &&
     phydev->interface != PHY_INTERFACE_MODE_SGMII) {
  err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
  if (err < 0)
   goto error;

  err = marvell_read_status_page(phydev, MII_MARVELL_FIBER_PAGE);
  if (err < 0)
   goto error;

  /* If the fiber link is up, it is the selected and
 * used link. In this case, we need to stay in the
 * fiber page. Please to be careful about that, avoid
 * to restore Copper page in other functions which
 * could break the behaviour for some fiber phy like
 * 88E1512.
 */

  if (phydev->link)
   return 0;

  /* If fiber link is down, check and save copper mode state */
  err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
  if (err < 0)
   goto error;
 }

 return marvell_read_status_page(phydev, MII_MARVELL_COPPER_PAGE);

error:
 marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 return err;
}

/* marvell_suspend
 *
 * Some Marvell's phys have two modes: fiber and copper.
 * Both need to be suspended
 */

static int marvell_suspend(struct phy_device *phydev)
{
 int err;

 /* Suspend the fiber mode first */
 if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
         phydev->supported)) {
  err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
  if (err < 0)
   goto error;

  /* With the page set, use the generic suspend */
  err = genphy_suspend(phydev);
  if (err < 0)
   goto error;

  /* Then, the copper link */
  err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
  if (err < 0)
   goto error;
 }

 /* With the page set, use the generic suspend */
 return genphy_suspend(phydev);

error:
 marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 return err;
}

/* marvell_resume
 *
 * Some Marvell's phys have two modes: fiber and copper.
 * Both need to be resumed
 */

static int marvell_resume(struct phy_device *phydev)
{
 int err;

 /* Resume the fiber mode first */
 if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
         phydev->supported)) {
  err = marvell_set_page(phydev, MII_MARVELL_FIBER_PAGE);
  if (err < 0)
   goto error;

  /* With the page set, use the generic resume */
  err = genphy_resume(phydev);
  if (err < 0)
   goto error;

  /* Then, the copper link */
  err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
  if (err < 0)
   goto error;
 }

 /* With the page set, use the generic resume */
 return genphy_resume(phydev);

error:
 marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE);
 return err;
}

/* m88e1510_resume
 *
 * The 88e1510 PHY has an erratum where the phy downshift counter is not cleared
 * after phy being suspended(BMCR_PDOWN set) and then later resumed(BMCR_PDOWN
 * cleared). This can cause the link to intermittently downshift to a lower speed.
 *
 * Disabling and re-enabling the downshift feature clears the counter, allowing
 * the PHY to retry gigabit link negotiation up to the programmed retry count
 * before downshifting. This behavior has been observed on copper links.
 */

static int m88e1510_resume(struct phy_device *phydev)
{
 int err;
 u8 cnt = 0;

 err = marvell_resume(phydev);
 if (err < 0)
  return err;

 /* read downshift counter value */
 err = m88e1011_get_downshift(phydev, &cnt);
 if (err < 0)
  return err;

 if (cnt) {
  /* downshift disabled */
  err = m88e1011_set_downshift(phydev, 0);
  if (err < 0)
   return err;

  /* downshift enabled, with previous counter value */
  err = m88e1011_set_downshift(phydev, cnt);
 }

 return err;
}

static int marvell_aneg_done(struct phy_device *phydev)
{
 int retval = phy_read(phydev, MII_M1011_PHY_STATUS);

 return (retval < 0) ? retval : (retval & MII_M1011_PHY_STATUS_RESOLVED);
}

static void m88e1318_get_wol(struct phy_device *phydev,
        struct ethtool_wolinfo *wol)
{
 int ret;

 wol->supported = WAKE_MAGIC | WAKE_PHY;
 wol->wolopts = 0;

 ret = phy_read_paged(phydev, MII_MARVELL_WOL_PAGE,
        MII_88E1318S_PHY_WOL_CTRL);
 if (ret < 0)
  return;

 if (ret & MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE)
  wol->wolopts |= WAKE_MAGIC;

 if (ret & MII_88E1318S_PHY_WOL_CTRL_LINK_UP_ENABLE)
  wol->wolopts |= WAKE_PHY;
}

static int m88e1318_set_wol(struct phy_device *phydev,
       struct ethtool_wolinfo *wol)
{
 int err = 0, oldpage;

 oldpage = phy_save_page(phydev);
 if (oldpage < 0)
  goto error;

 if (wol->wolopts & (WAKE_MAGIC | WAKE_PHY)) {
  /* Explicitly switch to page 0x00, just to be sure */
  err = marvell_write_page(phydev, MII_MARVELL_COPPER_PAGE);
  if (err < 0)
   goto error;

  /* If WOL event happened once, the LED[2] interrupt pin
 * will not be cleared unless we reading the interrupt status
 * register. If interrupts are in use, the normal interrupt
 * handling will clear the WOL event. Clear the WOL event
 * before enabling it if !phy_interrupt_is_valid()
 */

  if (!phy_interrupt_is_valid(phydev))
   __phy_read(phydev, MII_M1011_IEVENT);

  /* Enable the WOL interrupt */
  err = __phy_set_bits(phydev, MII_88E1318S_PHY_CSIER,
         MII_88E1318S_PHY_CSIER_WOL_EIE);
  if (err < 0)
   goto error;

  err = marvell_write_page(phydev, MII_MARVELL_LED_PAGE);
  if (err < 0)
   goto error;

  /* Setup LED[2] as interrupt pin (active low) */
  err = __phy_modify(phydev, MII_88E1318S_PHY_LED_TCR,
       MII_88E1318S_PHY_LED_TCR_FORCE_INT,
       MII_88E1318S_PHY_LED_TCR_INTn_ENABLE |
       MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW);
  if (err < 0)
   goto error;
 }

 if (wol->wolopts & WAKE_MAGIC) {
  err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
  if (err < 0)
   goto error;

  /* Store the device address for the magic packet */
  err = __phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD2,
    ((phydev->attached_dev->dev_addr[5] << 8) |
     phydev->attached_dev->dev_addr[4]));
  if (err < 0)
   goto error;
  err = __phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD1,
    ((phydev->attached_dev->dev_addr[3] << 8) |
     phydev->attached_dev->dev_addr[2]));
  if (err < 0)
   goto error;
  err = __phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD0,
    ((phydev->attached_dev->dev_addr[1] << 8) |
     phydev->attached_dev->dev_addr[0]));
  if (err < 0)
   goto error;

  /* Clear WOL status and enable magic packet matching */
  err = __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
         MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS |
         MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE);
  if (err < 0)
   goto error;
 } else {
  err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
  if (err < 0)
   goto error;

  /* Clear WOL status and disable magic packet matching */
  err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL,
       MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE,
       MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
  if (err < 0)
   goto error;
 }

 if (wol->wolopts & WAKE_PHY) {
  err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
  if (err < 0)
   goto error;

  /* Clear WOL status and enable link up event */
  err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL, 0,
       MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS |
       MII_88E1318S_PHY_WOL_CTRL_LINK_UP_ENABLE);
  if (err < 0)
   goto error;
 } else {
  err = marvell_write_page(phydev, MII_MARVELL_WOL_PAGE);
  if (err < 0)
   goto error;

  /* Clear WOL status and disable link up event */
  err = __phy_modify(phydev, MII_88E1318S_PHY_WOL_CTRL,
       MII_88E1318S_PHY_WOL_CTRL_LINK_UP_ENABLE,
       MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
  if (err < 0)
   goto error;
 }

error:
 return phy_restore_page(phydev, oldpage, err);
}

static int marvell_get_sset_count(struct phy_device *phydev)
{
 if (linkmode_test_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
         phydev->supported))
  return ARRAY_SIZE(marvell_hw_stats);
 else
  return ARRAY_SIZE(marvell_hw_stats) - NB_FIBER_STATS;
}

static int marvell_get_sset_count_simple(struct phy_device *phydev)
{
 return ARRAY_SIZE(marvell_hw_stats_simple);
}

static void marvell_get_strings(struct phy_device *phydev, u8 *data)
{
 int count = marvell_get_sset_count(phydev);
 int i;

 for (i = 0; i < count; i++)
  ethtool_puts(&data, marvell_hw_stats[i].string);
}

static void marvell_get_strings_simple(struct phy_device *phydev, u8 *data)
{
 int count = marvell_get_sset_count_simple(phydev);
 int i;

 for (i = 0; i < count; i++)
  ethtool_puts(&data, marvell_hw_stats_simple[i].string);
}

static u64 marvell_get_stat(struct phy_device *phydev, int i)
{
 struct marvell_hw_stat stat = marvell_hw_stats[i];
 struct marvell_priv *priv = phydev->priv;
 int val;
 u64 ret;

 val = phy_read_paged(phydev, stat.page, stat.reg);
 if (val < 0) {
  ret = U64_MAX;
 } else {
  val = val & ((1 << stat.bits) - 1);
  priv->stats[i] += val;
  ret = priv->stats[i];
 }

 return ret;
}

static u64 marvell_get_stat_simple(struct phy_device *phydev, int i)
{
 struct marvell_hw_stat_simple stat = marvell_hw_stats_simple[i];
 struct marvell_priv *priv = phydev->priv;
 int val;
 u64 ret;

 val = phy_read(phydev, stat.reg);
 if (val < 0) {
  ret = U64_MAX;
 } else {
  val = val & ((1 << stat.bits) - 1);
  priv->stats[i] += val;
  ret = priv->stats[i];
 }

 return ret;
}

static void marvell_get_stats(struct phy_device *phydev,
         struct ethtool_stats *stats, u64 *data)
{
 int count = marvell_get_sset_count(phydev);
 int i;

 for (i = 0; i < count; i++)
  data[i] = marvell_get_stat(phydev, i);
}

static void marvell_get_stats_simple(struct phy_device *phydev,
         struct ethtool_stats *stats, u64 *data)
{
 int count = marvell_get_sset_count_simple(phydev);
 int i;

 for (i = 0; i < count; i++)
  data[i] = marvell_get_stat_simple(phydev, i);
}

static int m88e1510_loopback(struct phy_device *phydev, bool enable, int speed)
{
 u16 bmcr_ctl, mscr2_ctl = 0;
 int err;

 if (!enable)
  return genphy_loopback(phydev, enable, 0);

 if (speed == SPEED_10 || speed == SPEED_100 || speed == SPEED_1000)
  phydev->speed = speed;
 else if (speed)
  return -EINVAL;

 bmcr_ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);

 err = phy_write(phydev, MII_BMCR, bmcr_ctl);
 if (err < 0)
  return err;

 if (phydev->speed == SPEED_1000)
  mscr2_ctl = BMCR_SPEED1000;
 else if (phydev->speed == SPEED_100)
  mscr2_ctl = BMCR_SPEED100;

 err = phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE,
          MII_88E1510_MSCR_2, BMCR_SPEED1000 |
          BMCR_SPEED100, mscr2_ctl);
 if (err < 0)
  return err;

 /* Need soft reset to have speed configuration takes effect */
 err = genphy_soft_reset(phydev);
 if (err < 0)
  return err;

 err = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
    BMCR_LOOPBACK);

 if (!err) {
  /*
 * It takes some time for PHY device to switch into loopback
 * mode.
 */

  msleep(1000);
 }
 return err;
}

static int marvell_vct5_wait_complete(struct phy_device *phydev)
{
 int i;
 int val;

 for (i = 0; i < 32; i++) {
  val = __phy_read(phydev, MII_VCT5_CTRL);
  if (val < 0)
   return val;

  if (val & MII_VCT5_CTRL_COMPLETE)
   return 0;
 }

 phydev_err(phydev, "Timeout while waiting for cable test to finish\n");
 return -ETIMEDOUT;
}

static int marvell_vct5_amplitude(struct phy_device *phydev, int pair)
{
 int amplitude;
 int val;
 int reg;

 reg = MII_VCT5_TX_RX_MDI0_COUPLING + pair;
 val = __phy_read(phydev, reg);

 if (val < 0)
  return 0;

 amplitude = (val & MII_VCT5_TX_RX_AMPLITUDE_MASK) >>
  MII_VCT5_TX_RX_AMPLITUDE_SHIFT;

 if (!(val & MII_VCT5_TX_RX_COUPLING_POSITIVE_REFLECTION))
  amplitude = -amplitude;

 return 1000 * amplitude / 128;
}

static u32 marvell_vct5_distance2cm(int distance)
{
 return distance * 805 / 10;
}

static u32 marvell_vct5_cm2distance(int cm)
{
 return cm * 10 / 805;
}

static int marvell_vct5_amplitude_distance(struct phy_device *phydev,
        int distance, int pair)
{
 u16 reg;
 int err;
 int mV;
 int i;

 err = __phy_write(phydev, MII_VCT5_SAMPLE_POINT_DISTANCE,
     distance);
 if (err)
  return err;

 reg = MII_VCT5_CTRL_ENABLE |
  MII_VCT5_CTRL_TX_SAME_CHANNEL |
  MII_VCT5_CTRL_SAMPLES_DEFAULT |
  MII_VCT5_CTRL_SAMPLE_POINT |
  MII_VCT5_CTRL_PEEK_HYST_DEFAULT;
 err = __phy_write(phydev, MII_VCT5_CTRL, reg);
 if (err)
  return err;

 err = marvell_vct5_wait_complete(phydev);
 if (err)
  return err;

 for (i = 0; i < 4; i++) {
  if (pair != PHY_PAIR_ALL && i != pair)
   continue;

  mV = marvell_vct5_amplitude(phydev, i);
  ethnl_cable_test_amplitude(phydev, i, mV);
 }

 return 0;
}

static int marvell_vct5_amplitude_graph(struct phy_device *phydev)
{
 struct marvell_priv *priv = phydev->priv;
 int distance;
 u16 width;
 int page;
 int err;
 u16 reg;

 if (priv->first <= TDR_SHORT_CABLE_LENGTH)
  width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS;
 else
  width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS;

 reg = MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV |
  MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN |
  MII_VCT5_TX_PULSE_CTRL_MAX_AMP | width;

 err = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE,
         MII_VCT5_TX_PULSE_CTRL, reg);
 if (err)
  return err;

 /* Reading the TDR data is very MDIO heavy. We need to optimize
 * access to keep the time to a minimum. So lock the bus once,
 * and don't release it until complete. We can then avoid having
 * to change the page for every access, greatly speeding things
 * up.
 */

 page = phy_select_page(phydev, MII_MARVELL_VCT5_PAGE);
 if (page < 0)
  goto restore_page;

 for (distance = priv->first;
      distance <= priv->last;
      distance += priv->step) {
  err = marvell_vct5_amplitude_distance(phydev, distance,
            priv->pair);
  if (err)
   goto restore_page;

  if (distance > TDR_SHORT_CABLE_LENGTH &&
      width == MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_32nS) {
   width = MII_VCT5_TX_PULSE_CTRL_PULSE_WIDTH_128nS;
   reg = MII_VCT5_TX_PULSE_CTRL_GT_140m_46_86mV |
    MII_VCT5_TX_PULSE_CTRL_DONT_WAIT_LINK_DOWN |
    MII_VCT5_TX_PULSE_CTRL_MAX_AMP | width;
   err = __phy_write(phydev, MII_VCT5_TX_PULSE_CTRL, reg);
   if (err)
    goto restore_page;
  }
 }

restore_page:
 return phy_restore_page(phydev, page, err);
}

static int marvell_cable_test_start_common(struct phy_device *phydev)
{
 int bmcr, bmsr, ret;

 /* If auto-negotiation is enabled, but not complete, the cable
 * test never completes. So disable auto-neg.
 */

 bmcr = phy_read(phydev, MII_BMCR);
 if (bmcr < 0)
  return bmcr;

 bmsr = phy_read(phydev, MII_BMSR);

 if (bmsr < 0)
  return bmsr;

 if (bmcr & BMCR_ANENABLE) {
  ret =  phy_clear_bits(phydev, MII_BMCR, BMCR_ANENABLE);
  if (ret < 0)
   return ret;
  ret = genphy_soft_reset(phydev);
  if (ret < 0)
   return ret;
 }

 /* If the link is up, allow it some time to go down */
 if (bmsr & BMSR_LSTATUS)
  msleep(1500);

 return 0;
}

static int marvell_vct7_cable_test_start(struct phy_device *phydev)
{
 struct marvell_priv *priv = phydev->priv;
 int ret;

 ret = marvell_cable_test_start_common(phydev);
 if (ret)
  return ret;

 priv->cable_test_tdr = false;

 /* Reset the VCT5 API control to defaults, otherwise
 * VCT7 does not work correctly.
 */

 ret = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE,
         MII_VCT5_CTRL,
         MII_VCT5_CTRL_TX_SAME_CHANNEL |
         MII_VCT5_CTRL_SAMPLES_DEFAULT |
         MII_VCT5_CTRL_MODE_MAXIMUM_PEEK |
         MII_VCT5_CTRL_PEEK_HYST_DEFAULT);
 if (ret)
  return ret;

 ret = phy_write_paged(phydev, MII_MARVELL_VCT5_PAGE,
         MII_VCT5_SAMPLE_POINT_DISTANCE, 0);
 if (ret)
  return ret;

 return phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE,
          MII_VCT7_CTRL,
          MII_VCT7_CTRL_RUN_NOW |
          MII_VCT7_CTRL_CENTIMETERS);
}

static int marvell_vct5_cable_test_tdr_start(struct phy_device *phydev,
          const struct phy_tdr_config *cfg)
{
 struct marvell_priv *priv = phydev->priv;
 int ret;

 priv->cable_test_tdr = true;
 priv->first = marvell_vct5_cm2distance(cfg->first);
 priv->last = marvell_vct5_cm2distance(cfg->last);
 priv->step = marvell_vct5_cm2distance(cfg->step);
 priv->pair = cfg->pair;

 if (priv->first > MII_VCT5_SAMPLE_POINT_DISTANCE_MAX)
  return -EINVAL;

 if (priv->last > MII_VCT5_SAMPLE_POINT_DISTANCE_MAX)
  return -EINVAL;

 /* Disable  VCT7 */
 ret = phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE,
         MII_VCT7_CTRL, 0);
 if (ret)
  return ret;

 ret = marvell_cable_test_start_common(phydev);
 if (ret)
  return ret;

 ret = ethnl_cable_test_pulse(phydev, 1000);
 if (ret)
  return ret;

 return ethnl_cable_test_step(phydev,
         marvell_vct5_distance2cm(priv->first),
         marvell_vct5_distance2cm(priv->last),
         marvell_vct5_distance2cm(priv->step));
}

static int marvell_vct7_distance_to_length(int distance, bool meter)
{
 if (meter)
  distance *= 100;

 return distance;
}

static bool marvell_vct7_distance_valid(int result)
{
 switch (result) {
 case MII_VCT7_RESULTS_OPEN:
 case MII_VCT7_RESULTS_SAME_SHORT:
 case MII_VCT7_RESULTS_CROSS_SHORT:
  return true;
 }
 return false;
}

static int marvell_vct7_report_length(struct phy_device *phydev,
          int pair, bool meter)
{
 int length;
 int ret;

 ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
        MII_VCT7_PAIR_0_DISTANCE + pair);
 if (ret < 0)
  return ret;

 length = marvell_vct7_distance_to_length(ret, meter);

 ethnl_cable_test_fault_length(phydev, pair, length);

 return 0;
}

static int marvell_vct7_cable_test_report_trans(int result)
{
 switch (result) {
 case MII_VCT7_RESULTS_OK:
  return ETHTOOL_A_CABLE_RESULT_CODE_OK;
 case MII_VCT7_RESULTS_OPEN:
  return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
 case MII_VCT7_RESULTS_SAME_SHORT:
  return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
 case MII_VCT7_RESULTS_CROSS_SHORT:
  return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
 default:
  return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
 }
}

static int marvell_vct7_cable_test_report(struct phy_device *phydev)
{
 int pair0, pair1, pair2, pair3;
 bool meter;
 int ret;

 ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
        MII_VCT7_RESULTS);
 if (ret < 0)
  return ret;

 pair3 = (ret & MII_VCT7_RESULTS_PAIR3_MASK) >>
  MII_VCT7_RESULTS_PAIR3_SHIFT;
 pair2 = (ret & MII_VCT7_RESULTS_PAIR2_MASK) >>
  MII_VCT7_RESULTS_PAIR2_SHIFT;
 pair1 = (ret & MII_VCT7_RESULTS_PAIR1_MASK) >>
  MII_VCT7_RESULTS_PAIR1_SHIFT;
 pair0 = (ret & MII_VCT7_RESULTS_PAIR0_MASK) >>
  MII_VCT7_RESULTS_PAIR0_SHIFT;

 ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
    marvell_vct7_cable_test_report_trans(pair0));
 ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
    marvell_vct7_cable_test_report_trans(pair1));
 ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
    marvell_vct7_cable_test_report_trans(pair2));
 ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
    marvell_vct7_cable_test_report_trans(pair3));

 ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE, MII_VCT7_CTRL);
 if (ret < 0)
  return ret;

 meter = ret & MII_VCT7_CTRL_METERS;

 if (marvell_vct7_distance_valid(pair0))
  marvell_vct7_report_length(phydev, 0, meter);
 if (marvell_vct7_distance_valid(pair1))
  marvell_vct7_report_length(phydev, 1, meter);
 if (marvell_vct7_distance_valid(pair2))
  marvell_vct7_report_length(phydev, 2, meter);
 if (marvell_vct7_distance_valid(pair3))
  marvell_vct7_report_length(phydev, 3, meter);

 return 0;
}

static int marvell_vct7_cable_test_get_status(struct phy_device *phydev,
           bool *finished)
{
 struct marvell_priv *priv = phydev->priv;
 int ret;

 if (priv->cable_test_tdr) {
  ret = marvell_vct5_amplitude_graph(phydev);
  *finished = true;
  return ret;
 }

 *finished = false;

 ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
        MII_VCT7_CTRL);

 if (ret < 0)
  return ret;

 if (!(ret & MII_VCT7_CTRL_IN_PROGRESS)) {
  *finished = true;

  return marvell_vct7_cable_test_report(phydev);
 }

 return 0;
}

static int m88e3082_vct_cable_test_start(struct phy_device *phydev)
{
 struct marvell_priv *priv = phydev->priv;
 int ret;

 /* It needs some magic workarounds described in VCT manual for this PHY.
 */

 ret = phy_write(phydev, 29, 0x0003);
 if (ret < 0)
  return ret;

 ret = phy_write(phydev, 30, 0x6440);
 if (ret < 0)
  return ret;

 if (priv->vct_phase == M88E3082_VCT_PHASE1) {
  ret = phy_write(phydev, 29, 0x000a);
  if (ret < 0)
   return ret;

  ret = phy_write(phydev, 30, 0x0002);
  if (ret < 0)
   return ret;
 }

 ret = phy_write(phydev, MII_BMCR,
   BMCR_RESET | BMCR_SPEED100 | BMCR_FULLDPLX);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.22 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.