diff options
author | Yangbo Lu <yangbo.lu@nxp.com> | 2017-09-27 15:31:31 +0800 |
---|---|---|
committer | John Crispin <john@phrozen.org> | 2017-10-07 23:13:23 +0200 |
commit | 8fdda1cc1033e2bd0d048188af5167faffbf9b38 (patch) | |
tree | be12aa762b013e30476e2bd784398a0b802bbc71 /target/linux/layerscape/patches-4.9/703-phy-support-layerscape.patch | |
parent | 19951bbf57da87093f7bde25bad41571fbdaf4d9 (diff) | |
download | upstream-8fdda1cc1033e2bd0d048188af5167faffbf9b38.tar.gz upstream-8fdda1cc1033e2bd0d048188af5167faffbf9b38.tar.bz2 upstream-8fdda1cc1033e2bd0d048188af5167faffbf9b38.zip |
layerscape: add linux 4.9 support
This patch is to add linux 4.9 support for layerscape.
All these kernel patches are from NXP LSDK 1709 release.
Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
Diffstat (limited to 'target/linux/layerscape/patches-4.9/703-phy-support-layerscape.patch')
-rw-r--r-- | target/linux/layerscape/patches-4.9/703-phy-support-layerscape.patch | 1776 |
1 files changed, 1776 insertions, 0 deletions
diff --git a/target/linux/layerscape/patches-4.9/703-phy-support-layerscape.patch b/target/linux/layerscape/patches-4.9/703-phy-support-layerscape.patch new file mode 100644 index 0000000000..0e8e3695f3 --- /dev/null +++ b/target/linux/layerscape/patches-4.9/703-phy-support-layerscape.patch @@ -0,0 +1,1776 @@ +From be07319b9897738a4ab1501880b7dd9be26eba66 Mon Sep 17 00:00:00 2001 +From: Yangbo Lu <yangbo.lu@nxp.com> +Date: Mon, 25 Sep 2017 11:54:28 +0800 +Subject: [PATCH] phy: support layerscape + +This is a integrated patch for layerscape mdio-phy support. + +Signed-off-by: Bogdan Purcareata <bogdan.purcareata@nxp.com> +Signed-off-by: Zhang Ying-22455 <ying.zhang22455@nxp.com> +Signed-off-by: costi <constantin.tudor@freescale.com> +Signed-off-by: Madalin Bucur <madalin.bucur@freescale.com> +Signed-off-by: Shaohui Xie <Shaohui.Xie@nxp.com> +Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com> +--- + drivers/net/phy/Kconfig | 11 + + drivers/net/phy/Makefile | 2 + + drivers/net/phy/aquantia.c | 28 + + drivers/net/phy/cortina.c | 118 ++++ + drivers/net/phy/fsl_backplane.c | 1358 +++++++++++++++++++++++++++++++++++++++ + drivers/net/phy/phy.c | 23 +- + drivers/net/phy/phy_device.c | 6 +- + drivers/net/phy/swphy.c | 1 + + include/linux/phy.h | 4 + + 9 files changed, 1544 insertions(+), 7 deletions(-) + create mode 100644 drivers/net/phy/cortina.c + create mode 100644 drivers/net/phy/fsl_backplane.c + +diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig +index 30a3a2f5..3521c1ac 100644 +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -89,6 +89,12 @@ config MDIO_BUS_MUX_MMIOREG + config MDIO_CAVIUM + tristate + ++config MDIO_FSL_BACKPLANE ++ tristate "Support for backplane on Freescale XFI interface" ++ depends on OF_MDIO ++ help ++ This module provides a driver for Freescale XFI's backplane. ++ + config MDIO_GPIO + tristate "GPIO lib-based bitbanged MDIO buses" + depends on MDIO_BITBANG && GPIOLIB +@@ -298,6 +304,11 @@ config CICADA_PHY + ---help--- + Currently supports the cis8204 + ++config CORTINA_PHY ++ tristate "Cortina EDC CDR 10G Ethernet PHY" ++ ---help--- ++ Currently supports the CS4340 phy. ++ + config DAVICOM_PHY + tristate "Davicom PHYs" + ---help--- +diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile +index 93a68fcd..ef3ec265 100644 +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -30,6 +30,7 @@ obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC) += mdio-mux-bcm-iproc.o + obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o + obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o + obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o ++obj-$(CONFIG_MDIO_FSL_BACKPLANE) += fsl_backplane.o + obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o + obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o + obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o +@@ -48,6 +49,7 @@ obj-$(CONFIG_BCM_CYGNUS_PHY) += bcm-cygnus.o + obj-$(CONFIG_BCM_NET_PHYLIB) += bcm-phy-lib.o + obj-$(CONFIG_BROADCOM_PHY) += broadcom.o + obj-$(CONFIG_CICADA_PHY) += cicada.o ++obj-$(CONFIG_CORTINA_PHY) += cortina.o + obj-$(CONFIG_DAVICOM_PHY) += davicom.o + obj-$(CONFIG_DP83640_PHY) += dp83640.o + obj-$(CONFIG_DP83848_PHY) += dp83848.o +diff --git a/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c +index 09b0b0aa..e8ae50e1 100644 +--- a/drivers/net/phy/aquantia.c ++++ b/drivers/net/phy/aquantia.c +@@ -21,6 +21,8 @@ + #define PHY_ID_AQ1202 0x03a1b445 + #define PHY_ID_AQ2104 0x03a1b460 + #define PHY_ID_AQR105 0x03a1b4a2 ++#define PHY_ID_AQR106 0x03a1b4d0 ++#define PHY_ID_AQR107 0x03a1b4e0 + #define PHY_ID_AQR405 0x03a1b4b0 + + #define PHY_AQUANTIA_FEATURES (SUPPORTED_10000baseT_Full | \ +@@ -153,6 +155,30 @@ static struct phy_driver aquantia_driver[] = { + .ack_interrupt = aquantia_ack_interrupt, + .read_status = aquantia_read_status, + }, ++{ ++ .phy_id = PHY_ID_AQR106, ++ .phy_id_mask = 0xfffffff0, ++ .name = "Aquantia AQR106", ++ .features = PHY_AQUANTIA_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .aneg_done = aquantia_aneg_done, ++ .config_aneg = aquantia_config_aneg, ++ .config_intr = aquantia_config_intr, ++ .ack_interrupt = aquantia_ack_interrupt, ++ .read_status = aquantia_read_status, ++}, ++{ ++ .phy_id = PHY_ID_AQR107, ++ .phy_id_mask = 0xfffffff0, ++ .name = "Aquantia AQR107", ++ .features = PHY_AQUANTIA_FEATURES, ++ .flags = PHY_HAS_INTERRUPT, ++ .aneg_done = aquantia_aneg_done, ++ .config_aneg = aquantia_config_aneg, ++ .config_intr = aquantia_config_intr, ++ .ack_interrupt = aquantia_ack_interrupt, ++ .read_status = aquantia_read_status, ++}, + { + .phy_id = PHY_ID_AQR405, + .phy_id_mask = 0xfffffff0, +@@ -173,6 +199,8 @@ static struct mdio_device_id __maybe_unused aquantia_tbl[] = { + { PHY_ID_AQ1202, 0xfffffff0 }, + { PHY_ID_AQ2104, 0xfffffff0 }, + { PHY_ID_AQR105, 0xfffffff0 }, ++ { PHY_ID_AQR106, 0xfffffff0 }, ++ { PHY_ID_AQR107, 0xfffffff0 }, + { PHY_ID_AQR405, 0xfffffff0 }, + { } + }; +diff --git a/drivers/net/phy/cortina.c b/drivers/net/phy/cortina.c +new file mode 100644 +index 00000000..72f4228a +--- /dev/null ++++ b/drivers/net/phy/cortina.c +@@ -0,0 +1,118 @@ ++/* ++ * Copyright 2017 NXP ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * CORTINA is a registered trademark of Cortina Systems, Inc. ++ * ++ */ ++#include <linux/module.h> ++#include <linux/phy.h> ++ ++#define PHY_ID_CS4340 0x13e51002 ++ ++#define VILLA_GLOBAL_CHIP_ID_LSB 0x0 ++#define VILLA_GLOBAL_CHIP_ID_MSB 0x1 ++ ++#define VILLA_GLOBAL_GPIO_1_INTS 0x017 ++ ++static int cortina_read_reg(struct phy_device *phydev, u16 regnum) ++{ ++ return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, ++ MII_ADDR_C45 | regnum); ++} ++ ++static int cortina_config_aneg(struct phy_device *phydev) ++{ ++ phydev->supported = SUPPORTED_10000baseT_Full; ++ phydev->advertising = SUPPORTED_10000baseT_Full; ++ ++ return 0; ++} ++ ++static int cortina_read_status(struct phy_device *phydev) ++{ ++ int gpio_int_status, ret = 0; ++ ++ gpio_int_status = cortina_read_reg(phydev, VILLA_GLOBAL_GPIO_1_INTS); ++ if (gpio_int_status < 0) { ++ ret = gpio_int_status; ++ goto err; ++ } ++ ++ if (gpio_int_status & 0x8) { ++ /* up when edc_convergedS set */ ++ phydev->speed = SPEED_10000; ++ phydev->duplex = DUPLEX_FULL; ++ phydev->link = 1; ++ } else { ++ phydev->link = 0; ++ } ++ ++err: ++ return ret; ++} ++ ++static int cortina_soft_reset(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int cortina_probe(struct phy_device *phydev) ++{ ++ u32 phy_id = 0; ++ int id_lsb = 0, id_msb = 0; ++ ++ /* Read device id from phy registers. */ ++ id_lsb = cortina_read_reg(phydev, VILLA_GLOBAL_CHIP_ID_LSB); ++ if (id_lsb < 0) ++ return -ENXIO; ++ ++ phy_id = id_lsb << 16; ++ ++ id_msb = cortina_read_reg(phydev, VILLA_GLOBAL_CHIP_ID_MSB); ++ if (id_msb < 0) ++ return -ENXIO; ++ ++ phy_id |= id_msb; ++ ++ /* Make sure the device tree binding matched the driver with the ++ * right device. ++ */ ++ if (phy_id != phydev->drv->phy_id) { ++ phydev_err(phydev, "Error matching phy with %s driver\n", ++ phydev->drv->name); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static struct phy_driver cortina_driver[] = { ++{ ++ .phy_id = PHY_ID_CS4340, ++ .phy_id_mask = 0xffffffff, ++ .name = "Cortina CS4340", ++ .config_aneg = cortina_config_aneg, ++ .read_status = cortina_read_status, ++ .soft_reset = cortina_soft_reset, ++ .probe = cortina_probe, ++}, ++}; ++ ++module_phy_driver(cortina_driver); ++ ++static struct mdio_device_id __maybe_unused cortina_tbl[] = { ++ { PHY_ID_CS4340, 0xffffffff}, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(mdio, cortina_tbl); +diff --git a/drivers/net/phy/fsl_backplane.c b/drivers/net/phy/fsl_backplane.c +new file mode 100644 +index 00000000..76865261 +--- /dev/null ++++ b/drivers/net/phy/fsl_backplane.c +@@ -0,0 +1,1358 @@ ++/* Freescale backplane driver. ++ * Author: Shaohui Xie <Shaohui.Xie@freescale.com> ++ * ++ * Copyright 2015 Freescale Semiconductor, Inc. ++ * ++ * Licensed under the GPL-2 or later. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/mii.h> ++#include <linux/mdio.h> ++#include <linux/ethtool.h> ++#include <linux/phy.h> ++#include <linux/io.h> ++#include <linux/of.h> ++#include <linux/of_net.h> ++#include <linux/of_address.h> ++#include <linux/of_platform.h> ++#include <linux/timer.h> ++#include <linux/delay.h> ++#include <linux/workqueue.h> ++ ++/* XFI PCS Device Identifier */ ++#define FSL_PCS_PHY_ID 0x0083e400 ++ ++/* Freescale KR PMD registers */ ++#define FSL_KR_PMD_CTRL 0x96 ++#define FSL_KR_PMD_STATUS 0x97 ++#define FSL_KR_LP_CU 0x98 ++#define FSL_KR_LP_STATUS 0x99 ++#define FSL_KR_LD_CU 0x9a ++#define FSL_KR_LD_STATUS 0x9b ++ ++/* Freescale KR PMD defines */ ++#define PMD_RESET 0x1 ++#define PMD_STATUS_SUP_STAT 0x4 ++#define PMD_STATUS_FRAME_LOCK 0x2 ++#define TRAIN_EN 0x3 ++#define TRAIN_DISABLE 0x1 ++#define RX_STAT 0x1 ++ ++#define FSL_KR_RX_LINK_STAT_MASK 0x1000 ++#define FSL_XFI_PCS_10GR_SR1 0x20 ++ ++/* Freescale KX PCS mode register */ ++#define FSL_PCS_IF_MODE 0x8014 ++ ++/* Freescale KX PCS mode register init value */ ++#define IF_MODE_INIT 0x8 ++ ++/* Freescale KX/KR AN registers */ ++#define FSL_AN_AD1 0x11 ++#define FSL_AN_BP_STAT 0x30 ++ ++/* Freescale KX/KR AN registers defines */ ++#define AN_CTRL_INIT 0x1200 ++#define KX_AN_AD1_INIT 0x25 ++#define KR_AN_AD1_INIT 0x85 ++#define AN_LNK_UP_MASK 0x4 ++#define KR_AN_MASK 0x8 ++#define TRAIN_FAIL 0x8 ++ ++/* C(-1) */ ++#define BIN_M1 0 ++/* C(1) */ ++#define BIN_LONG 1 ++#define BIN_M1_SEL 6 ++#define BIN_Long_SEL 7 ++#define CDR_SEL_MASK 0x00070000 ++#define BIN_SNAPSHOT_NUM 5 ++#define BIN_M1_THRESHOLD 3 ++#define BIN_LONG_THRESHOLD 2 ++ ++#define PRE_COE_SHIFT 22 ++#define POST_COE_SHIFT 16 ++#define ZERO_COE_SHIFT 8 ++ ++#define PRE_COE_MAX 0x0 ++#define PRE_COE_MIN 0x8 ++#define POST_COE_MAX 0x0 ++#define POST_COE_MIN 0x10 ++#define ZERO_COE_MAX 0x30 ++#define ZERO_COE_MIN 0x0 ++ ++#define TECR0_INIT 0x24200000 ++#define RATIO_PREQ 0x3 ++#define RATIO_PST1Q 0xd ++#define RATIO_EQ 0x20 ++ ++#define GCR0_RESET_MASK 0x600000 ++#define GCR1_SNP_START_MASK 0x00000040 ++#define GCR1_CTL_SNP_START_MASK 0x00002000 ++#define GCR1_REIDL_TH_MASK 0x00700000 ++#define GCR1_REIDL_EX_SEL_MASK 0x000c0000 ++#define GCR1_REIDL_ET_MAS_MASK 0x00004000 ++#define TECR0_AMP_RED_MASK 0x0000003f ++ ++#define RECR1_CTL_SNP_DONE_MASK 0x00000002 ++#define RECR1_SNP_DONE_MASK 0x00000004 ++#define TCSR1_SNP_DATA_MASK 0x0000ffc0 ++#define TCSR1_SNP_DATA_SHIFT 6 ++#define TCSR1_EQ_SNPBIN_SIGN_MASK 0x100 ++ ++#define RECR1_GAINK2_MASK 0x0f000000 ++#define RECR1_GAINK2_SHIFT 24 ++#define RECR1_GAINK3_MASK 0x000f0000 ++#define RECR1_GAINK3_SHIFT 16 ++#define RECR1_OFFSET_MASK 0x00003f80 ++#define RECR1_OFFSET_SHIFT 7 ++#define RECR1_BLW_MASK 0x00000f80 ++#define RECR1_BLW_SHIFT 7 ++#define EYE_CTRL_SHIFT 12 ++#define BASE_WAND_SHIFT 10 ++ ++#define XGKR_TIMEOUT 1050 ++ ++#define INCREMENT 1 ++#define DECREMENT 2 ++#define TIMEOUT_LONG 3 ++#define TIMEOUT_M1 3 ++ ++#define RX_READY_MASK 0x8000 ++#define PRESET_MASK 0x2000 ++#define INIT_MASK 0x1000 ++#define COP1_MASK 0x30 ++#define COP1_SHIFT 4 ++#define COZ_MASK 0xc ++#define COZ_SHIFT 2 ++#define COM1_MASK 0x3 ++#define COM1_SHIFT 0 ++#define REQUEST_MASK 0x3f ++#define LD_ALL_MASK (PRESET_MASK | INIT_MASK | \ ++ COP1_MASK | COZ_MASK | COM1_MASK) ++ ++#define NEW_ALGORITHM_TRAIN_TX ++#ifdef NEW_ALGORITHM_TRAIN_TX ++#define FORCE_INC_COP1_NUMBER 0 ++#define FORCE_INC_COM1_NUMBER 1 ++#endif ++ ++#define VAL_INVALID 0xff ++ ++static const u32 preq_table[] = {0x0, 0x1, 0x3, 0x5, ++ 0x7, 0x9, 0xb, 0xc, VAL_INVALID}; ++static const u32 pst1q_table[] = {0x0, 0x1, 0x3, 0x5, 0x7, ++ 0x9, 0xb, 0xd, 0xf, 0x10, VAL_INVALID}; ++ ++enum backplane_mode { ++ PHY_BACKPLANE_1000BASE_KX, ++ PHY_BACKPLANE_10GBASE_KR, ++ PHY_BACKPLANE_INVAL ++}; ++ ++enum coe_filed { ++ COE_COP1, ++ COE_COZ, ++ COE_COM ++}; ++ ++enum coe_update { ++ COE_NOTUPDATED, ++ COE_UPDATED, ++ COE_MIN, ++ COE_MAX, ++ COE_INV ++}; ++ ++enum train_state { ++ DETECTING_LP, ++ TRAINED, ++}; ++ ++struct per_lane_ctrl_status { ++ __be32 gcr0; /* 0x.000 - General Control Register 0 */ ++ __be32 gcr1; /* 0x.004 - General Control Register 1 */ ++ __be32 gcr2; /* 0x.008 - General Control Register 2 */ ++ __be32 resv1; /* 0x.00C - Reserved */ ++ __be32 recr0; /* 0x.010 - Receive Equalization Control Register 0 */ ++ __be32 recr1; /* 0x.014 - Receive Equalization Control Register 1 */ ++ __be32 tecr0; /* 0x.018 - Transmit Equalization Control Register 0 */ ++ __be32 resv2; /* 0x.01C - Reserved */ ++ __be32 tlcr0; /* 0x.020 - TTL Control Register 0 */ ++ __be32 tlcr1; /* 0x.024 - TTL Control Register 1 */ ++ __be32 tlcr2; /* 0x.028 - TTL Control Register 2 */ ++ __be32 tlcr3; /* 0x.02C - TTL Control Register 3 */ ++ __be32 tcsr0; /* 0x.030 - Test Control/Status Register 0 */ ++ __be32 tcsr1; /* 0x.034 - Test Control/Status Register 1 */ ++ __be32 tcsr2; /* 0x.038 - Test Control/Status Register 2 */ ++ __be32 tcsr3; /* 0x.03C - Test Control/Status Register 3 */ ++}; ++ ++struct tx_condition { ++ bool bin_m1_late_early; ++ bool bin_long_late_early; ++ bool bin_m1_stop; ++ bool bin_long_stop; ++ bool tx_complete; ++ bool sent_init; ++ int m1_min_max_cnt; ++ int long_min_max_cnt; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ int pre_inc; ++ int post_inc; ++#endif ++}; ++ ++struct fsl_xgkr_inst { ++ void *reg_base; ++ struct phy_device *phydev; ++ struct tx_condition tx_c; ++ struct delayed_work xgkr_wk; ++ enum train_state state; ++ u32 ld_update; ++ u32 ld_status; ++ u32 ratio_preq; ++ u32 ratio_pst1q; ++ u32 adpt_eq; ++}; ++ ++static void tx_condition_init(struct tx_condition *tx_c) ++{ ++ tx_c->bin_m1_late_early = true; ++ tx_c->bin_long_late_early = false; ++ tx_c->bin_m1_stop = false; ++ tx_c->bin_long_stop = false; ++ tx_c->tx_complete = false; ++ tx_c->sent_init = false; ++ tx_c->m1_min_max_cnt = 0; ++ tx_c->long_min_max_cnt = 0; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ tx_c->pre_inc = FORCE_INC_COM1_NUMBER; ++ tx_c->post_inc = FORCE_INC_COP1_NUMBER; ++#endif ++} ++ ++void tune_tecr0(struct fsl_xgkr_inst *inst) ++{ ++ struct per_lane_ctrl_status *reg_base = inst->reg_base; ++ u32 val; ++ ++ val = TECR0_INIT | ++ inst->adpt_eq << ZERO_COE_SHIFT | ++ inst->ratio_preq << PRE_COE_SHIFT | ++ inst->ratio_pst1q << POST_COE_SHIFT; ++ ++ /* reset the lane */ ++ iowrite32(ioread32(®_base->gcr0) & ~GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++ iowrite32(val, ®_base->tecr0); ++ udelay(1); ++ /* unreset the lane */ ++ iowrite32(ioread32(®_base->gcr0) | GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++} ++ ++static void start_lt(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_CTRL, TRAIN_EN); ++} ++ ++static void stop_lt(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_CTRL, TRAIN_DISABLE); ++} ++ ++static void reset_gcr0(struct fsl_xgkr_inst *inst) ++{ ++ struct per_lane_ctrl_status *reg_base = inst->reg_base; ++ ++ iowrite32(ioread32(®_base->gcr0) & ~GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++ iowrite32(ioread32(®_base->gcr0) | GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++} ++ ++void lane_set_1gkx(void *reg) ++{ ++ struct per_lane_ctrl_status *reg_base = reg; ++ u32 val; ++ ++ /* reset the lane */ ++ iowrite32(ioread32(®_base->gcr0) & ~GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++ ++ /* set gcr1 for 1GKX */ ++ val = ioread32(®_base->gcr1); ++ val &= ~(GCR1_REIDL_TH_MASK | GCR1_REIDL_EX_SEL_MASK | ++ GCR1_REIDL_ET_MAS_MASK); ++ iowrite32(val, ®_base->gcr1); ++ udelay(1); ++ ++ /* set tecr0 for 1GKX */ ++ val = ioread32(®_base->tecr0); ++ val &= ~TECR0_AMP_RED_MASK; ++ iowrite32(val, ®_base->tecr0); ++ udelay(1); ++ ++ /* unreset the lane */ ++ iowrite32(ioread32(®_base->gcr0) | GCR0_RESET_MASK, ++ ®_base->gcr0); ++ udelay(1); ++} ++ ++static void reset_lt(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, PMD_RESET); ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_CTRL, TRAIN_DISABLE); ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LD_CU, 0); ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LD_STATUS, 0); ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_STATUS, 0); ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_CU, 0); ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_STATUS, 0); ++} ++ ++static void start_xgkr_state_machine(struct delayed_work *work) ++{ ++ queue_delayed_work(system_power_efficient_wq, work, ++ msecs_to_jiffies(XGKR_TIMEOUT)); ++} ++ ++static void start_xgkr_an(struct phy_device *phydev) ++{ ++ struct fsl_xgkr_inst *inst; ++ ++ reset_lt(phydev); ++ phy_write_mmd(phydev, MDIO_MMD_AN, FSL_AN_AD1, KR_AN_AD1_INIT); ++ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, AN_CTRL_INIT); ++ ++ inst = phydev->priv; ++ ++ /* start state machine*/ ++ start_xgkr_state_machine(&inst->xgkr_wk); ++} ++ ++static void start_1gkx_an(struct phy_device *phydev) ++{ ++ phy_write_mmd(phydev, MDIO_MMD_PCS, FSL_PCS_IF_MODE, IF_MODE_INIT); ++ phy_write_mmd(phydev, MDIO_MMD_AN, FSL_AN_AD1, KX_AN_AD1_INIT); ++ phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); ++ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, AN_CTRL_INIT); ++} ++ ++static void ld_coe_status(struct fsl_xgkr_inst *inst) ++{ ++ phy_write_mmd(inst->phydev, MDIO_MMD_PMAPMD, ++ FSL_KR_LD_STATUS, inst->ld_status); ++} ++ ++static void ld_coe_update(struct fsl_xgkr_inst *inst) ++{ ++ dev_dbg(&inst->phydev->mdio.dev, "sending request: %x\n", inst->ld_update); ++ phy_write_mmd(inst->phydev, MDIO_MMD_PMAPMD, ++ FSL_KR_LD_CU, inst->ld_update); ++} ++ ++static void init_inst(struct fsl_xgkr_inst *inst, int reset) ++{ ++ if (reset) { ++ inst->ratio_preq = RATIO_PREQ; ++ inst->ratio_pst1q = RATIO_PST1Q; ++ inst->adpt_eq = RATIO_EQ; ++ tune_tecr0(inst); ++ } ++ ++ tx_condition_init(&inst->tx_c); ++ inst->state = DETECTING_LP; ++ inst->ld_status &= RX_READY_MASK; ++ ld_coe_status(inst); ++ inst->ld_update = 0; ++ inst->ld_status &= ~RX_READY_MASK; ++ ld_coe_status(inst); ++} ++ ++#ifdef NEW_ALGORITHM_TRAIN_TX ++static int get_median_gaink2(u32 *reg) ++{ ++ int gaink2_snap_shot[BIN_SNAPSHOT_NUM]; ++ u32 rx_eq_snp; ++ struct per_lane_ctrl_status *reg_base; ++ int timeout; ++ int i, j, tmp, pos; ++ ++ reg_base = (struct per_lane_ctrl_status *)reg; ++ ++ for (i = 0; i < BIN_SNAPSHOT_NUM; i++) { ++ /* wait RECR1_CTL_SNP_DONE_MASK has cleared */ ++ timeout = 100; ++ while (ioread32(®_base->recr1) & ++ RECR1_CTL_SNP_DONE_MASK) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* start snap shot */ ++ iowrite32((ioread32(®_base->gcr1) | ++ GCR1_CTL_SNP_START_MASK), ++ ®_base->gcr1); ++ ++ /* wait for SNP done */ ++ timeout = 100; ++ while (!(ioread32(®_base->recr1) & ++ RECR1_CTL_SNP_DONE_MASK)) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* read and save the snap shot */ ++ rx_eq_snp = ioread32(®_base->recr1); ++ gaink2_snap_shot[i] = (rx_eq_snp & RECR1_GAINK2_MASK) >> ++ RECR1_GAINK2_SHIFT; ++ ++ /* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */ ++ iowrite32((ioread32(®_base->gcr1) & ++ ~GCR1_CTL_SNP_START_MASK), ++ ®_base->gcr1); ++ } ++ ++ /* get median of the 5 snap shot */ ++ for (i = 0; i < BIN_SNAPSHOT_NUM - 1; i++) { ++ tmp = gaink2_snap_shot[i]; ++ pos = i; ++ for (j = i + 1; j < BIN_SNAPSHOT_NUM; j++) { ++ if (gaink2_snap_shot[j] < tmp) { ++ tmp = gaink2_snap_shot[j]; ++ pos = j; ++ } ++ } ++ ++ gaink2_snap_shot[pos] = gaink2_snap_shot[i]; ++ gaink2_snap_shot[i] = tmp; ++ } ++ ++ return gaink2_snap_shot[2]; ++} ++#endif ++ ++static bool is_bin_early(int bin_sel, void *reg) ++{ ++ bool early = false; ++ int bin_snap_shot[BIN_SNAPSHOT_NUM]; ++ int i, negative_count = 0; ++ struct per_lane_ctrl_status *reg_base = reg; ++ int timeout; ++ ++ for (i = 0; i < BIN_SNAPSHOT_NUM; i++) { ++ /* wait RECR1_SNP_DONE_MASK has cleared */ ++ timeout = 100; ++ while ((ioread32(®_base->recr1) & RECR1_SNP_DONE_MASK)) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* set TCSR1[CDR_SEL] to BinM1/BinLong */ ++ if (bin_sel == BIN_M1) { ++ iowrite32((ioread32(®_base->tcsr1) & ++ ~CDR_SEL_MASK) | BIN_M1_SEL, ++ ®_base->tcsr1); ++ } else { ++ iowrite32((ioread32(®_base->tcsr1) & ++ ~CDR_SEL_MASK) | BIN_Long_SEL, ++ ®_base->tcsr1); ++ } ++ ++ /* start snap shot */ ++ iowrite32(ioread32(®_base->gcr1) | GCR1_SNP_START_MASK, ++ ®_base->gcr1); ++ ++ /* wait for SNP done */ ++ timeout = 100; ++ while (!(ioread32(®_base->recr1) & RECR1_SNP_DONE_MASK)) { ++ udelay(1); ++ timeout--; ++ if (timeout == 0) ++ break; ++ } ++ ++ /* read and save the snap shot */ ++ bin_snap_shot[i] = (ioread32(®_base->tcsr1) & ++ TCSR1_SNP_DATA_MASK) >> TCSR1_SNP_DATA_SHIFT; ++ if (bin_snap_shot[i] & TCSR1_EQ_SNPBIN_SIGN_MASK) ++ negative_count++; ++ ++ /* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */ ++ iowrite32(ioread32(®_base->gcr1) & ~GCR1_SNP_START_MASK, ++ ®_base->gcr1); ++ } ++ ++ if (((bin_sel == BIN_M1) && (negative_count > BIN_M1_THRESHOLD)) || ++ ((bin_sel == BIN_LONG && (negative_count > BIN_LONG_THRESHOLD)))) { ++ early = true; ++ } ++ ++ return early; ++} ++ ++static void train_tx(struct fsl_xgkr_inst *inst) ++{ ++ struct phy_device *phydev = inst->phydev; ++ struct tx_condition *tx_c = &inst->tx_c; ++ bool bin_m1_early, bin_long_early; ++ u32 lp_status, old_ld_update; ++ u32 status_cop1, status_coz, status_com1; ++ u32 req_cop1, req_coz, req_com1, req_preset, req_init; ++ u32 temp; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ u32 median_gaink2; ++#endif ++ ++recheck: ++ if (tx_c->bin_long_stop && tx_c->bin_m1_stop) { ++ tx_c->tx_complete = true; ++ inst->ld_status |= RX_READY_MASK; ++ ld_coe_status(inst); ++ /* tell LP we are ready */ ++ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, ++ FSL_KR_PMD_STATUS, RX_STAT); ++ return; ++ } ++ ++ /* We start by checking the current LP status. If we got any responses, ++ * we can clear up the appropriate update request so that the ++ * subsequent code may easily issue new update requests if needed. ++ */ ++ lp_status = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_STATUS) & ++ REQUEST_MASK; ++ status_cop1 = (lp_status & COP1_MASK) >> COP1_SHIFT; ++ status_coz = (lp_status & COZ_MASK) >> COZ_SHIFT; ++ status_com1 = (lp_status & COM1_MASK) >> COM1_SHIFT; ++ ++ old_ld_update = inst->ld_update; ++ req_cop1 = (old_ld_update & COP1_MASK) >> COP1_SHIFT; ++ req_coz = (old_ld_update & COZ_MASK) >> COZ_SHIFT; ++ req_com1 = (old_ld_update & COM1_MASK) >> COM1_SHIFT; ++ req_preset = old_ld_update & PRESET_MASK; ++ req_init = old_ld_update & INIT_MASK; ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.1 ++ * We may clear PRESET when all coefficients show UPDATED or MAX. ++ */ ++ if (req_preset) { ++ if ((status_cop1 == COE_UPDATED || status_cop1 == COE_MAX) && ++ (status_coz == COE_UPDATED || status_coz == COE_MAX) && ++ (status_com1 == COE_UPDATED || status_com1 == COE_MAX)) { ++ inst->ld_update &= ~PRESET_MASK; ++ } ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.2 ++ * We may clear INITIALIZE when no coefficients show NOT UPDATED. ++ */ ++ if (req_init) { ++ if (status_cop1 != COE_NOTUPDATED && ++ status_coz != COE_NOTUPDATED && ++ status_com1 != COE_NOTUPDATED) { ++ inst->ld_update &= ~INIT_MASK; ++ } ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.2 ++ * we send initialize to the other side to ensure default settings ++ * for the LP. Naturally, we should do this only once. ++ */ ++ if (!tx_c->sent_init) { ++ if (!lp_status && !(old_ld_update & (LD_ALL_MASK))) { ++ inst->ld_update = INIT_MASK; ++ tx_c->sent_init = true; ++ } ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.3 ++ * We set coefficient requests to HOLD when we get the information ++ * about any updates On clearing our prior response, we also update ++ * our internal status. ++ */ ++ if (status_cop1 != COE_NOTUPDATED) { ++ if (req_cop1) { ++ inst->ld_update &= ~COP1_MASK; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ if (tx_c->post_inc) { ++ if (req_cop1 == INCREMENT && ++ status_cop1 == COE_MAX) { ++ tx_c->post_inc = 0; ++ tx_c->bin_long_stop = true; ++ tx_c->bin_m1_stop = true; ++ } else { ++ tx_c->post_inc -= 1; ++ } ++ ++ ld_coe_update(inst); ++ goto recheck; ++ } ++#endif ++ if ((req_cop1 == DECREMENT && status_cop1 == COE_MIN) || ++ (req_cop1 == INCREMENT && status_cop1 == COE_MAX)) { ++ dev_dbg(&inst->phydev->mdio.dev, "COP1 hit limit %s", ++ (status_cop1 == COE_MIN) ? ++ "DEC MIN" : "INC MAX"); ++ tx_c->long_min_max_cnt++; ++ if (tx_c->long_min_max_cnt >= TIMEOUT_LONG) { ++ tx_c->bin_long_stop = true; ++ ld_coe_update(inst); ++ goto recheck; ++ } ++ } ++ } ++ } ++ ++ if (status_coz != COE_NOTUPDATED) { ++ if (req_coz) ++ inst->ld_update &= ~COZ_MASK; ++ } ++ ++ if (status_com1 != COE_NOTUPDATED) { ++ if (req_com1) { ++ inst->ld_update &= ~COM1_MASK; ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ if (tx_c->pre_inc) { ++ if (req_com1 == INCREMENT && ++ status_com1 == COE_MAX) ++ tx_c->pre_inc = 0; ++ else ++ tx_c->pre_inc -= 1; ++ ++ ld_coe_update(inst); ++ goto recheck; ++ } ++#endif ++ /* Stop If we have reached the limit for a parameter. */ ++ if ((req_com1 == DECREMENT && status_com1 == COE_MIN) || ++ (req_com1 == INCREMENT && status_com1 == COE_MAX)) { ++ dev_dbg(&inst->phydev->mdio.dev, "COM1 hit limit %s", ++ (status_com1 == COE_MIN) ? ++ "DEC MIN" : "INC MAX"); ++ tx_c->m1_min_max_cnt++; ++ if (tx_c->m1_min_max_cnt >= TIMEOUT_M1) { ++ tx_c->bin_m1_stop = true; ++ ld_coe_update(inst); ++ goto recheck; ++ } ++ } ++ } ++ } ++ ++ if (old_ld_update != inst->ld_update) { ++ ld_coe_update(inst); ++ /* Redo these status checks and updates until we have no more ++ * changes, to speed up the overall process. ++ */ ++ goto recheck; ++ } ++ ++ /* Do nothing if we have pending request. */ ++ if ((req_coz || req_com1 || req_cop1)) ++ return; ++ else if (lp_status) ++ /* No pending request but LP status was not reverted to ++ * not updated. ++ */ ++ return; ++ ++#ifdef NEW_ALGORITHM_TRAIN_TX ++ if (!(inst->ld_update & (PRESET_MASK | INIT_MASK))) { ++ if (tx_c->pre_inc) { ++ inst->ld_update = INCREMENT << COM1_SHIFT; ++ ld_coe_update(inst); ++ return; ++ } ++ ++ if (status_cop1 != COE_MAX) { ++ median_gaink2 = get_median_gaink2(inst->reg_base); ++ if (median_gaink2 == 0xf) { ++ tx_c->post_inc = 1; ++ } else { ++ /* Gaink2 median lower than "F" */ ++ tx_c->bin_m1_stop = true; ++ tx_c->bin_long_stop = true; ++ goto recheck; ++ } ++ } else { ++ /* C1 MAX */ ++ tx_c->bin_m1_stop = true; ++ tx_c->bin_long_stop = true; ++ goto recheck; ++ } ++ ++ if (tx_c->post_inc) { ++ inst->ld_update = INCREMENT << COP1_SHIFT; ++ ld_coe_update(inst); ++ return; ++ } ++ } ++#endif ++ ++ /* snapshot and select bin */ ++ bin_m1_early = is_bin_early(BIN_M1, inst->reg_base); ++ bin_long_early = is_bin_early(BIN_LONG, inst->reg_base); ++ ++ if (!tx_c->bin_m1_stop && !tx_c->bin_m1_late_early && bin_m1_early) { ++ tx_c->bin_m1_stop = true; ++ goto recheck; ++ } ++ ++ if (!tx_c->bin_long_stop && ++ tx_c->bin_long_late_early && !bin_long_early) { ++ tx_c->bin_long_stop = true; ++ goto recheck; ++ } ++ ++ /* IEEE802.3-2008, 72.6.10.2.3.3 ++ * We only request coefficient updates when no PRESET/INITIALIZE is ++ * pending. We also only request coefficient updates when the ++ * corresponding status is NOT UPDATED and nothing is pending. ++ */ ++ if (!(inst->ld_update & (PRESET_MASK | INIT_MASK))) { ++ if (!tx_c->bin_long_stop) { ++ /* BinM1 correction means changing COM1 */ ++ if (!status_com1 && !(inst->ld_update & COM1_MASK)) { ++ /* Avoid BinM1Late by requesting an ++ * immediate decrement. ++ */ ++ if (!bin_m1_early) { ++ /* request decrement c(-1) */ ++ temp = DECREMENT << COM1_SHIFT; ++ inst->ld_update = temp; ++ ld_coe_update(inst); ++ tx_c->bin_m1_late_early = bin_m1_early; ++ return; ++ } ++ } ++ ++ /* BinLong correction means changing COP1 */ ++ if (!status_cop1 && !(inst->ld_update & COP1_MASK)) { ++ /* Locate BinLong transition point (if any) ++ * while avoiding BinM1Late. ++ */ ++ if (bin_long_early) { ++ /* request increment c(1) */ ++ temp = INCREMENT << COP1_SHIFT; ++ inst->ld_update = temp; ++ } else { ++ /* request decrement c(1) */ ++ temp = DECREMENT << COP1_SHIFT; ++ inst->ld_update = temp; ++ } ++ ++ ld_coe_update(inst); ++ tx_c->bin_long_late_early = bin_long_early; ++ } ++ /* We try to finish BinLong before we do BinM1 */ ++ return; ++ } ++ ++ if (!tx_c->bin_m1_stop) { ++ /* BinM1 correction means changing COM1 */ ++ if (!status_com1 && !(inst->ld_update & COM1_MASK)) { ++ /* Locate BinM1 transition point (if any) */ ++ if (bin_m1_early) { ++ /* request increment c(-1) */ ++ temp = INCREMENT << COM1_SHIFT; ++ inst->ld_update = temp; ++ } else { ++ /* request decrement c(-1) */ ++ temp = DECREMENT << COM1_SHIFT; ++ inst->ld_update = temp; ++ } ++ ++ ld_coe_update(inst); ++ tx_c->bin_m1_late_early = bin_m1_early; ++ } ++ } ++ } ++} ++ ++static int is_link_up(struct phy_device *phydev) ++{ ++ int val; ++ ++ phy_read_mmd(phydev, MDIO_MMD_PCS, FSL_XFI_PCS_10GR_SR1); ++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, FSL_XFI_PCS_10GR_SR1); ++ ++ return (val & FSL_KR_RX_LINK_STAT_MASK) ? 1 : 0; ++} ++ ++static int is_link_training_fail(struct phy_device *phydev) ++{ ++ int val; ++ int timeout = 100; ++ ++ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_STATUS); ++ if (!(val & TRAIN_FAIL) && (val & RX_STAT)) { ++ /* check LNK_STAT for sure */ ++ while (timeout--) { ++ if (is_link_up(phydev)) ++ return 0; ++ ++ usleep_range(100, 500); ++ } ++ } ++ ++ return 1; ++} ++ ++static int check_rx(struct phy_device *phydev) ++{ ++ return phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_STATUS) & ++ RX_READY_MASK; ++} ++ ++/* Coefficient values have hardware restrictions */ ++static int is_ld_valid(struct fsl_xgkr_inst *inst) ++{ ++ u32 ratio_pst1q = inst->ratio_pst1q; ++ u32 adpt_eq = inst->adpt_eq; ++ u32 ratio_preq = inst->ratio_preq; ++ ++ if ((ratio_pst1q + adpt_eq + ratio_preq) > 48) ++ return 0; ++ ++ if (((ratio_pst1q + adpt_eq + ratio_preq) * 4) >= ++ ((adpt_eq - ratio_pst1q - ratio_preq) * 17)) ++ return 0; ++ ++ if (ratio_preq > ratio_pst1q) ++ return 0; ++ ++ if (ratio_preq > 8) ++ return 0; ++ ++ if (adpt_eq < 26) ++ return 0; ++ ++ if (ratio_pst1q > 16) ++ return 0; ++ ++ return 1; ++} ++ ++static int is_value_allowed(const u32 *val_table, u32 val) ++{ ++ int i; ++ ++ for (i = 0;; i++) { ++ if (*(val_table + i) == VAL_INVALID) ++ return 0; ++ if (*(val_table + i) == val) ++ return 1; ++ } ++} ++ ++static int inc_dec(struct fsl_xgkr_inst *inst, int field, int request) ++{ ++ u32 ld_limit[3], ld_coe[3], step[3]; ++ ++ ld_coe[0] = inst->ratio_pst1q; ++ ld_coe[1] = inst->adpt_eq; ++ ld_coe[2] = inst->ratio_preq; ++ ++ /* Information specific to the Freescale SerDes for 10GBase-KR: ++ * Incrementing C(+1) means *decrementing* RATIO_PST1Q ++ * Incrementing C(0) means incrementing ADPT_EQ ++ * Incrementing C(-1) means *decrementing* RATIO_PREQ ++ */ ++ step[0] = -1; ++ step[1] = 1; ++ step[2] = -1; ++ ++ switch (request) { ++ case INCREMENT: ++ ld_limit[0] = POST_COE_MAX; ++ ld_limit[1] = ZERO_COE_MAX; ++ ld_limit[2] = PRE_COE_MAX; ++ if (ld_coe[field] != ld_limit[field]) ++ ld_coe[field] += step[field]; ++ else ++ /* MAX */ ++ return 2; ++ break; ++ case DECREMENT: ++ ld_limit[0] = POST_COE_MIN; ++ ld_limit[1] = ZERO_COE_MIN; ++ ld_limit[2] = PRE_COE_MIN; ++ if (ld_coe[field] != ld_limit[field]) ++ ld_coe[field] -= step[field]; ++ else ++ /* MIN */ ++ return 1; ++ break; ++ default: ++ break; ++ } ++ ++ if (is_ld_valid(inst)) { ++ /* accept new ld */ ++ inst->ratio_pst1q = ld_coe[0]; ++ inst->adpt_eq = ld_coe[1]; ++ inst->ratio_preq = ld_coe[2]; ++ /* only some values for preq and pst1q can be used. ++ * for preq: 0x0, 0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xc. ++ * for pst1q: 0x0, 0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xd, 0xf, 0x10. ++ */ ++ if (!is_value_allowed((const u32 *)&preq_table, ld_coe[2])) { ++ dev_dbg(&inst->phydev->mdio.dev, ++ "preq skipped value: %d\n", ld_coe[2]); ++ return 0; ++ } ++ ++ if (!is_value_allowed((const u32 *)&pst1q_table, ld_coe[0])) { ++ dev_dbg(&inst->phydev->mdio.dev, ++ "pst1q skipped value: %d\n", ld_coe[0]); ++ return 0; ++ } ++ ++ tune_tecr0(inst); ++ } else { ++ if (request == DECREMENT) ++ /* MIN */ ++ return 1; ++ if (request == INCREMENT) ++ /* MAX */ ++ return 2; ++ } ++ ++ return 0; ++} ++ ++static void min_max_updated(struct fsl_xgkr_inst *inst, int field, int new_ld) ++{ ++ u32 ld_coe[] = {COE_UPDATED, COE_MIN, COE_MAX}; ++ u32 mask, val; ++ ++ switch (field) { ++ case COE_COP1: ++ mask = COP1_MASK; ++ val = ld_coe[new_ld] << COP1_SHIFT; ++ break; ++ case COE_COZ: ++ mask = COZ_MASK; ++ val = ld_coe[new_ld] << COZ_SHIFT; ++ break; ++ case COE_COM: ++ mask = COM1_MASK; ++ val = ld_coe[new_ld] << COM1_SHIFT; ++ break; ++ default: ++ return; ++ } ++ ++ inst->ld_status &= ~mask; ++ inst->ld_status |= val; ++} ++ ++static void check_request(struct fsl_xgkr_inst *inst, int request) ++{ ++ int cop1_req, coz_req, com_req; ++ int old_status, new_ld_sta; ++ ++ cop1_req = (request & COP1_MASK) >> COP1_SHIFT; ++ coz_req = (request & COZ_MASK) >> COZ_SHIFT; ++ com_req = (request & COM1_MASK) >> COM1_SHIFT; ++ ++ /* IEEE802.3-2008, 72.6.10.2.5 ++ * Ensure we only act on INCREMENT/DECREMENT when we are in NOT UPDATED ++ */ ++ old_status = inst->ld_status; ++ ++ if (cop1_req && !(inst->ld_status & COP1_MASK)) { ++ new_ld_sta = inc_dec(inst, COE_COP1, cop1_req); ++ min_max_updated(inst, COE_COP1, new_ld_sta); ++ } ++ ++ if (coz_req && !(inst->ld_status & COZ_MASK)) { ++ new_ld_sta = inc_dec(inst, COE_COZ, coz_req); ++ min_max_updated(inst, COE_COZ, new_ld_sta); ++ } ++ ++ if (com_req && !(inst->ld_status & COM1_MASK)) { ++ new_ld_sta = inc_dec(inst, COE_COM, com_req); ++ min_max_updated(inst, COE_COM, new_ld_sta); ++ } ++ ++ if (old_status != inst->ld_status) ++ ld_coe_status(inst); ++} ++ ++static void preset(struct fsl_xgkr_inst *inst) ++{ ++ /* These are all MAX values from the IEEE802.3 perspective. */ ++ inst->ratio_pst1q = POST_COE_MAX; ++ inst->adpt_eq = ZERO_COE_MAX; ++ inst->ratio_preq = PRE_COE_MAX; ++ ++ tune_tecr0(inst); ++ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK); ++ inst->ld_status |= COE_MAX << COP1_SHIFT | ++ COE_MAX << COZ_SHIFT | ++ COE_MAX << COM1_SHIFT; ++ ld_coe_status(inst); ++} ++ ++static void initialize(struct fsl_xgkr_inst *inst) ++{ ++ inst->ratio_preq = RATIO_PREQ; ++ inst->ratio_pst1q = RATIO_PST1Q; ++ inst->adpt_eq = RATIO_EQ; ++ ++ tune_tecr0(inst); ++ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK); ++ inst->ld_status |= COE_UPDATED << COP1_SHIFT | ++ COE_UPDATED << COZ_SHIFT | ++ COE_UPDATED << COM1_SHIFT; ++ ld_coe_status(inst); ++} ++ ++static void train_rx(struct fsl_xgkr_inst *inst) ++{ ++ struct phy_device *phydev = inst->phydev; ++ int request, old_ld_status; ++ ++ /* get request from LP */ ++ request = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_CU) & ++ (LD_ALL_MASK); ++ old_ld_status = inst->ld_status; ++ ++ /* IEEE802.3-2008, 72.6.10.2.5 ++ * Ensure we always go to NOT UDPATED for status reporting in ++ * response to HOLD requests. ++ * IEEE802.3-2008, 72.6.10.2.3.1/2 ++ * ... but only if PRESET/INITIALIZE are not active to ensure ++ * we keep status until they are released. ++ */ ++ if (!(request & (PRESET_MASK | INIT_MASK))) { ++ if (!(request & COP1_MASK)) ++ inst->ld_status &= ~COP1_MASK; ++ ++ if (!(request & COZ_MASK)) ++ inst->ld_status &= ~COZ_MASK; ++ ++ if (!(request & COM1_MASK)) ++ inst->ld_status &= ~COM1_MASK; ++ ++ if (old_ld_status != inst->ld_status) ++ ld_coe_status(inst); ++ } ++ ++ /* As soon as the LP shows ready, no need to do any more updates. */ ++ if (check_rx(phydev)) { ++ /* LP receiver is ready */ ++ if (inst->ld_status & (COP1_MASK | COZ_MASK | COM1_MASK)) { ++ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK); ++ ld_coe_status(inst); ++ } ++ } else { ++ /* IEEE802.3-2008, 72.6.10.2.3.1/2 ++ * only act on PRESET/INITIALIZE if all status is NOT UPDATED. ++ */ ++ if (request & (PRESET_MASK | INIT_MASK)) { ++ if (!(inst->ld_status & ++ (COP1_MASK | COZ_MASK | COM1_MASK))) { ++ if (request & PRESET_MASK) ++ preset(inst); ++ ++ if (request & INIT_MASK) ++ initialize(inst); ++ } ++ } ++ ++ /* LP Coefficient are not in HOLD */ ++ if (request & REQUEST_MASK) ++ check_request(inst, request & REQUEST_MASK); ++ } ++} ++ ++static void xgkr_start_train(struct phy_device *phydev) ++{ ++ struct fsl_xgkr_inst *inst = phydev->priv; ++ struct tx_condition *tx_c = &inst->tx_c; ++ int val = 0, i; ++ int lt_state; ++ unsigned long dead_line; ++ int rx_ok, tx_ok; ++ ++ init_inst(inst, 0); ++ start_lt(phydev); ++ ++ for (i = 0; i < 2;) { ++ dead_line = jiffies + msecs_to_jiffies(500); ++ while (time_before(jiffies, dead_line)) { ++ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, ++ FSL_KR_PMD_STATUS); ++ if (val & TRAIN_FAIL) { ++ /* LT failed already, reset lane to avoid ++ * it run into hanging, then start LT again. ++ */ ++ reset_gcr0(inst); ++ start_lt(phydev); ++ } else if ((val & PMD_STATUS_SUP_STAT) && ++ (val & PMD_STATUS_FRAME_LOCK)) ++ break; ++ usleep_range(100, 500); ++ } ++ ++ if (!((val & PMD_STATUS_FRAME_LOCK) && ++ (val & PMD_STATUS_SUP_STAT))) { ++ i++; ++ continue; ++ } ++ ++ /* init process */ ++ rx_ok = false; ++ tx_ok = false; ++ /* the LT should be finished in 500ms, failed or OK. */ ++ dead_line = jiffies + msecs_to_jiffies(500); ++ ++ while (time_before(jiffies, dead_line)) { ++ /* check if the LT is already failed */ ++ lt_state = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, ++ FSL_KR_PMD_STATUS); ++ if (lt_state & TRAIN_FAIL) { ++ reset_gcr0(inst); ++ break; ++ } ++ ++ rx_ok = check_rx(phydev); ++ tx_ok = tx_c->tx_complete; ++ ++ if (rx_ok && tx_ok) ++ break; ++ ++ if (!rx_ok) ++ train_rx(inst); ++ ++ if (!tx_ok) ++ train_tx(inst); ++ ++ usleep_range(100, 500); ++ } ++ ++ i++; ++ /* check LT result */ ++ if (is_link_training_fail(phydev)) { ++ init_inst(inst, 0); ++ continue; ++ } else { ++ stop_lt(phydev); ++ inst->state = TRAINED; ++ break; ++ } ++ } ++} ++ ++static void xgkr_state_machine(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct fsl_xgkr_inst *inst = container_of(dwork, ++ struct fsl_xgkr_inst, ++ xgkr_wk); ++ struct phy_device *phydev = inst->phydev; ++ int an_state; ++ bool needs_train = false; ++ ++ mutex_lock(&phydev->lock); ++ ++ switch (inst->state) { ++ case DETECTING_LP: ++ phy_read_mmd(phydev, MDIO_MMD_AN, FSL_AN_BP_STAT); ++ an_state = phy_read_mmd(phydev, MDIO_MMD_AN, FSL_AN_BP_STAT); ++ if ((an_state & KR_AN_MASK)) ++ needs_train = true; ++ break; ++ case TRAINED: ++ if (!is_link_up(phydev)) { ++ dev_info(&phydev->mdio.dev, ++ "Detect hotplug, restart training\n"); ++ init_inst(inst, 1); ++ start_xgkr_an(phydev); ++ inst->state = DETECTING_LP; ++ } ++ break; ++ } ++ ++ if (needs_train) ++ xgkr_start_train(phydev); ++ ++ mutex_unlock(&phydev->lock); ++ queue_delayed_work(system_power_efficient_wq, &inst->xgkr_wk, ++ msecs_to_jiffies(XGKR_TIMEOUT)); ++} ++ ++static int fsl_backplane_probe(struct phy_device *phydev) ++{ ++ struct fsl_xgkr_inst *xgkr_inst; ++ struct device_node *phy_node, *lane_node; ++ struct resource res_lane; ++ const char *bm; ++ int ret; ++ int bp_mode; ++ u32 lane[2]; ++ ++ phy_node = phydev->mdio.dev.of_node; ++ bp_mode = of_property_read_string(phy_node, "backplane-mode", &bm); ++ if (bp_mode < 0) ++ return 0; ++ ++ if (!strcasecmp(bm, "1000base-kx")) { ++ bp_mode = PHY_BACKPLANE_1000BASE_KX; ++ } else if (!strcasecmp(bm, "10gbase-kr")) { ++ bp_mode = PHY_BACKPLANE_10GBASE_KR; ++ } else { ++ dev_err(&phydev->mdio.dev, "Unknown backplane-mode\n"); ++ return -EINVAL; ++ } ++ ++ lane_node = of_parse_phandle(phy_node, "fsl,lane-handle", 0); ++ if (!lane_node) { ++ dev_err(&phydev->mdio.dev, "parse fsl,lane-handle failed\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_address_to_resource(lane_node, 0, &res_lane); ++ if (ret) { ++ dev_err(&phydev->mdio.dev, "could not obtain memory map\n"); ++ return ret; ++ } ++ ++ of_node_put(lane_node); ++ ret = of_property_read_u32_array(phy_node, "fsl,lane-reg", ++ (u32 *)&lane, 2); ++ if (ret) { ++ dev_err(&phydev->mdio.dev, "could not get fsl,lane-reg\n"); ++ return -EINVAL; ++ } ++ ++ phydev->priv = devm_ioremap_nocache(&phydev->mdio.dev, ++ res_lane.start + lane[0], ++ lane[1]); ++ if (!phydev->priv) { ++ dev_err(&phydev->mdio.dev, "ioremap_nocache failed\n"); ++ return -ENOMEM; ++ } ++ ++ if (bp_mode == PHY_BACKPLANE_1000BASE_KX) { ++ phydev->speed = SPEED_1000; ++ /* configure the lane for 1000BASE-KX */ ++ lane_set_1gkx(phydev->priv); ++ return 0; ++ } ++ ++ xgkr_inst = devm_kzalloc(&phydev->mdio.dev, ++ sizeof(*xgkr_inst), GFP_KERNEL); ++ if (!xgkr_inst) ++ return -ENOMEM; ++ ++ xgkr_inst->reg_base = phydev->priv; ++ xgkr_inst->phydev = phydev; ++ phydev->priv = xgkr_inst; ++ ++ if (bp_mode == PHY_BACKPLANE_10GBASE_KR) { ++ phydev->speed = SPEED_10000; ++ INIT_DELAYED_WORK(&xgkr_inst->xgkr_wk, xgkr_state_machine); ++ } ++ ++ return 0; ++} ++ ++static int fsl_backplane_aneg_done(struct phy_device *phydev) ++{ ++ return 1; ++} ++ ++static int fsl_backplane_config_aneg(struct phy_device *phydev) ++{ ++ if (phydev->speed == SPEED_10000) { ++ phydev->supported |= SUPPORTED_10000baseKR_Full; ++ start_xgkr_an(phydev); ++ } else if (phydev->speed == SPEED_1000) { ++ phydev->supported |= SUPPORTED_1000baseKX_Full; ++ start_1gkx_an(phydev); ++ } ++ ++ phydev->advertising = phydev->supported; ++ phydev->duplex = 1; ++ ++ return 0; ++} ++ ++static int fsl_backplane_suspend(struct phy_device *phydev) ++{ ++ if (phydev->speed == SPEED_10000) { ++ struct fsl_xgkr_inst *xgkr_inst = phydev->priv; ++ ++ cancel_delayed_work_sync(&xgkr_inst->xgkr_wk); ++ } ++ return 0; ++} ++ ++static int fsl_backplane_resume(struct phy_device *phydev) ++{ ++ if (phydev->speed == SPEED_10000) { ++ struct fsl_xgkr_inst *xgkr_inst = phydev->priv; ++ ++ init_inst(xgkr_inst, 1); ++ queue_delayed_work(system_power_efficient_wq, ++ &xgkr_inst->xgkr_wk, ++ msecs_to_jiffies(XGKR_TIMEOUT)); ++ } ++ return 0; ++} ++ ++static int fsl_backplane_read_status(struct phy_device *phydev) ++{ ++ if (is_link_up(phydev)) ++ phydev->link = 1; ++ else ++ phydev->link = 0; ++ ++ return 0; ++} ++ ++static struct phy_driver fsl_backplane_driver[] = { ++ { ++ .phy_id = FSL_PCS_PHY_ID, ++ .name = "Freescale Backplane", ++ .phy_id_mask = 0xffffffff, ++ .features = SUPPORTED_Backplane | SUPPORTED_Autoneg | ++ SUPPORTED_MII, ++ .probe = fsl_backplane_probe, ++ .aneg_done = fsl_backplane_aneg_done, ++ .config_aneg = fsl_backplane_config_aneg, ++ .read_status = fsl_backplane_read_status, ++ .suspend = fsl_backplane_suspend, ++ .resume = fsl_backplane_resume, ++ }, ++}; ++ ++module_phy_driver(fsl_backplane_driver); ++ ++static struct mdio_device_id __maybe_unused freescale_tbl[] = { ++ { FSL_PCS_PHY_ID, 0xffffffff }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, freescale_tbl); ++ ++MODULE_DESCRIPTION("Freescale Backplane driver"); ++MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c +index f3e64a89..42cdd5b7 100644 +--- a/drivers/net/phy/phy.c ++++ b/drivers/net/phy/phy.c +@@ -585,7 +585,7 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd) + return 0; + + case SIOCSHWTSTAMP: +- if (phydev->drv->hwtstamp) ++ if (phydev->drv && phydev->drv->hwtstamp) + return phydev->drv->hwtstamp(phydev, ifr); + /* fall through */ + +@@ -610,6 +610,9 @@ static int phy_start_aneg_priv(struct phy_device *phydev, bool sync) + bool trigger = 0; + int err; + ++ if (!phydev->drv) ++ return -EIO; ++ + mutex_lock(&phydev->lock); + + if (AUTONEG_DISABLE == phydev->autoneg) +@@ -1009,7 +1012,7 @@ void phy_state_machine(struct work_struct *work) + + old_state = phydev->state; + +- if (phydev->drv->link_change_notify) ++ if (phydev->drv && phydev->drv->link_change_notify) + phydev->drv->link_change_notify(phydev); + + switch (phydev->state) { +@@ -1311,6 +1314,9 @@ EXPORT_SYMBOL(phy_write_mmd_indirect); + */ + int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) + { ++ if (!phydev->drv) ++ return -EIO; ++ + /* According to 802.3az,the EEE is supported only in full duplex-mode. + * Also EEE feature is active when core is operating with MII, GMII + * or RGMII (all kinds). Internal PHYs are also allowed to proceed and +@@ -1388,6 +1394,9 @@ EXPORT_SYMBOL(phy_init_eee); + */ + int phy_get_eee_err(struct phy_device *phydev) + { ++ if (!phydev->drv) ++ return -EIO; ++ + return phy_read_mmd_indirect(phydev, MDIO_PCS_EEE_WK_ERR, MDIO_MMD_PCS); + } + EXPORT_SYMBOL(phy_get_eee_err); +@@ -1404,6 +1413,9 @@ int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_eee *data) + { + int val; + ++ if (!phydev->drv) ++ return -EIO; ++ + /* Get Supported EEE */ + val = phy_read_mmd_indirect(phydev, MDIO_PCS_EEE_ABLE, MDIO_MMD_PCS); + if (val < 0) +@@ -1437,6 +1449,9 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) + { + int val = ethtool_adv_to_mmd_eee_adv_t(data->advertised); + ++ if (!phydev->drv) ++ return -EIO; ++ + /* Mask prohibited EEE modes */ + val &= ~phydev->eee_broken_modes; + +@@ -1448,7 +1463,7 @@ EXPORT_SYMBOL(phy_ethtool_set_eee); + + int phy_ethtool_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) + { +- if (phydev->drv->set_wol) ++ if (phydev->drv && phydev->drv->set_wol) + return phydev->drv->set_wol(phydev, wol); + + return -EOPNOTSUPP; +@@ -1457,7 +1472,7 @@ EXPORT_SYMBOL(phy_ethtool_set_wol); + + void phy_ethtool_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) + { +- if (phydev->drv->get_wol) ++ if (phydev->drv && phydev->drv->get_wol) + phydev->drv->get_wol(phydev, wol); + } + EXPORT_SYMBOL(phy_ethtool_get_wol); +diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c +index 5fdc491e..039f9664 100644 +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -1046,7 +1046,7 @@ int phy_suspend(struct phy_device *phydev) + if (wol.wolopts) + return -EBUSY; + +- if (phydrv->suspend) ++ if (phydev->drv && phydrv->suspend) + ret = phydrv->suspend(phydev); + + if (ret) +@@ -1063,7 +1063,7 @@ int phy_resume(struct phy_device *phydev) + struct phy_driver *phydrv = to_phy_driver(phydev->mdio.dev.driver); + int ret = 0; + +- if (phydrv->resume) ++ if (phydev->drv && phydrv->resume) + ret = phydrv->resume(phydev); + + if (ret) +@@ -1726,7 +1726,7 @@ static int phy_remove(struct device *dev) + phydev->state = PHY_DOWN; + mutex_unlock(&phydev->lock); + +- if (phydev->drv->remove) ++ if (phydev->drv && phydev->drv->remove) + phydev->drv->remove(phydev); + phydev->drv = NULL; + +diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c +index 34f58f23..52ddddbe 100644 +--- a/drivers/net/phy/swphy.c ++++ b/drivers/net/phy/swphy.c +@@ -77,6 +77,7 @@ static const struct swmii_regs duplex[] = { + static int swphy_decode_speed(int speed) + { + switch (speed) { ++ case 10000: + case 1000: + return SWMII_SPEED_1000; + case 100: +diff --git a/include/linux/phy.h b/include/linux/phy.h +index 850c8b51..5f253f1a 100644 +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -81,6 +81,7 @@ typedef enum { + PHY_INTERFACE_MODE_MOCA, + PHY_INTERFACE_MODE_QSGMII, + PHY_INTERFACE_MODE_TRGMII, ++ PHY_INTERFACE_MODE_SGMII_2500, + PHY_INTERFACE_MODE_MAX, + } phy_interface_t; + +@@ -784,6 +785,9 @@ int phy_stop_interrupts(struct phy_device *phydev); + + static inline int phy_read_status(struct phy_device *phydev) + { ++ if (!phydev->drv) ++ return -EIO; ++ + return phydev->drv->read_status(phydev); + } + +-- +2.14.1 + |