[PATCH 3/4] net: phy: Add GPIO and DT support to NCN26000

From: Jay Monkman
Date: Mon Sep 18 2023 - 03:10:08 EST



This adds GPIO support and devicetree configuration for
the NCN26000 PHY.

Signed-off-by: Jay Monkman <jtm@xxxxxxxxxxxxx>
---
drivers/net/phy/ncn26000.c | 467 ++++++++++++++++++++++++++++++++++++-
1 file changed, 459 insertions(+), 8 deletions(-)

diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index 5680584f659e..d89cbac6f8e3 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -7,17 +7,32 @@
#include <linux/kernel.h>
#include <linux/bitfield.h>
#include <linux/errno.h>
+#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/phy.h>
+#include <linux/property.h>

#include "mdio-open-alliance.h"

#define PHY_ID_NCN26000 0x180FF5A1

+#define TO_TMR_DEFAULT 32
+
#define NCN26000_REG_IRQ_CTL 16
#define NCN26000_REG_IRQ_STATUS 17
+#define NCN26000_REG_DIO_CONFIG 18
+
+// clause 45 vendor specific registers
+#define NCN26000_REG_PHYCFG1_MMD MDIO_MMD_VEND2
+#define NCN26000_REG_PHYCFG1 0x8001
+
+#define NCN26000_REG_PLCAEXT_MMD MDIO_MMD_VEND2
+#define NCN26000_REG_PLCAEXT 0x8002
+
+#define NCN26000_REG_TWEAKS_MMD MDIO_MMD_VEND1
+#define NCN26000_REG_TWEAKS 0x1001

// the NCN26000 maps link_ctrl to BMCR_ANENABLE
#define NCN26000_BCMR_LINK_CTRL_BIT BMCR_ANENABLE
@@ -33,17 +48,392 @@
#define NCN26000_IRQ_PHYSCOL_BIT BIT(5)
#define NCN26000_IRQ_RESET_BIT BIT(15)

-#define TO_TMR_DEFAULT 32
+#define NCN26000_DIO_FN_DISABLE 0x0
+#define NCN26000_DIO_FN_GPIO 0x1
+#define NCN26000_DIO_FN_SFD_TX 0x2
+#define NCN26000_DIO_FN_SFD_RX 0x3
+#define NCN26000_DIO_FN_LED_LINK 0x4
+#define NCN26000_DIO_FN_LED_PLCA 0x5
+#define NCN26000_DIO_FN_LED_TX 0x6
+#define NCN26000_DIO_FN_LED_RX 0x7
+#define NCN26000_DIO_FN_CLK25M 0x8
+#define NCN26000_DIO_FN_SFD_RXTX 0xb
+#define NCN26000_DIO_FN_LED_RXTX 0xf
+
+#define NCN26000_DIO_CFG_SLEW_RATE1 BIT(15)
+#define NCN26000_DIO_CFG_PULL_EN1 BIT(14)
+#define NCN26000_DIO_CFG_PULL_DIR1 BIT(13)
+#define NCN26000_DIO_CFG_FN1(x) ((x) << 9)
+#define NCN26000_DIO_CFG_VAL1 BIT(8)
+#define NCN26000_DIO_CFG_SLEW_RATE0 BIT(7)
+#define NCN26000_DIO_CFG_PULL_EN0 BIT(6)
+#define NCN26000_DIO_CFG_PULL_DIR0 BIT(5)
+#define NCN26000_DIO_CFG_FN0(x) ((x) << 1)
+#define NCN26000_DIO_CFG_VAL0 BIT(0)
+
+#define NCN26000_PHYCFG1_PKT_LOOP BIT(15)
+#define NCN26000_PHYCFG1_ENI BIT(7)
+#define NCN26000_PHYCFG1_UNJAB_TMR BIT(6)
+#define NCN26000_PHYCFG1_NO_COL_MASK BIT(1)
+#define NCN26000_PHYCFG1_RX_DLY BIT(0)
+
+#define NCN26000_PLCAEXT_PRECEDENCE BIT(15)
+#define NCN26000_PLCAEXT_MIIEXTDIS BIT(11)
+
+#define NCN26000_TWEAKS_TX_GAIN(x) ((x) << 14)
+#define NCN26000_TWEAKS_CD_THRESH(x) ((x) << 10)
+#define NCN26000_TWEAKS_ED_THRESH(x) ((x) << 6)
+#define NCN26000_TWEAKS_DIG_SLEW BIT(5)
+#define NCN26000_TWEAKS_CMC_COMP(x) ((x) << 3)
+#define NCN26000_TWEAKS_TX_SLEW BIT(2)
+
+#define NCN26000_TX_GAIN_1V0 0
+#define NCN26000_TX_GAIN_1V1 1
+#define NCN26000_TX_GAIN_0V9 2
+#define NCN26000_TX_GAIN_0V8 3
+#define NCN26000_CD_THRESH_DEFAULT 11
+#define NCN26000_ED_THRESH_DEFAULT 2
+#define NCN26000_DIG_SLEW_FAST 1
+#define NCN26000_DIG_SLEW_SLOW 0
+#define NCN26000_CMC_COMP_0P25 0
+#define NCN26000_CMC_COMP_1P37 1
+#define NCN26000_CMC_COMP_3P00 2
+#define NCN26000_CMC_COMP_4P37 3
+#define NCN26000_TX_SLEW_FAST 1
+#define NCN26000_TX_SLEW_SLOW 0
+
+struct ncn26000_priv {
+ struct device *dev;
+ struct phy_device *phydev;
+ struct gpio_chip gpio_chip;
+ unsigned int gpiomask;
+ u16 diocfg;
+ u16 phycfg0;
+ u16 plcaext;
+ u16 tweaks;
+
+ enum {
+ ENI_AUTO = 0,
+ ENI_FORCE_ON = 1,
+ ENI_FORCE_OFF = 2
+ } eni_mode;
+};
+
+static int ncn26000_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ncn26000_priv *priv = gpiochip_get_data(gc);
+
+ if (offset > 2)
+ return -ENODEV;
+
+ if (priv->gpiomask & (1 << offset))
+ return 0;
+
+ return -EBUSY;
+}
+
+static void ncn26000_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct ncn26000_priv *priv = gpiochip_get_data(gc);
+ u32 dio_reg;
+
+ dio_reg = phy_read(priv->phydev, NCN26000_REG_DIO_CONFIG);
+
+ switch (offset) {
+ case 0:
+ if (!val == !(priv->diocfg & NCN26000_DIO_CFG_VAL0))
+ dio_reg |= NCN26000_DIO_CFG_VAL0;
+ else
+ dio_reg &= ~NCN26000_DIO_CFG_VAL0;
+ break;
+
+ case 1:
+ if (!val == !(priv->diocfg & NCN26000_DIO_CFG_VAL1))
+ dio_reg |= NCN26000_DIO_CFG_VAL1;
+ else
+ dio_reg &= ~NCN26000_DIO_CFG_VAL1;
+ break;
+
+ default:
+ dev_err(priv->dev, "invalid GPIO offset: %d\n", offset);
+ return;
+ }
+
+ phy_write(priv->phydev, NCN26000_REG_DIO_CONFIG, dio_reg);
+}
+
+static int ncn26000_gpio_get_dir(struct gpio_chip *gc, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ncn26000_gpio_dir_out(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ ncn26000_gpio_set(gc, offset, val);
+ return 0;
+}
+
+static int ncn26000_gpio_setup(struct ncn26000_priv *priv)
+{
+ struct gpio_chip *gc = &priv->gpio_chip;
+
+ gc->request = ncn26000_gpio_request;
+ gc->get_direction = ncn26000_gpio_get_dir;
+ gc->direction_output = ncn26000_gpio_dir_out;
+ gc->set = ncn26000_gpio_set;
+ gc->label = "ncn26000-gpio";
+ gc->base = -1;
+ gc->ngpio = 2;
+ gc->parent = priv->dev;
+ gc->owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(priv->dev, gc, priv);
+}
+
+static void ncn26000_tweaks_config(struct ncn26000_priv *priv)
+{
+ u16 val = NCN26000_TWEAKS_CD_THRESH(NCN26000_CD_THRESH_DEFAULT) |
+ NCN26000_TWEAKS_ED_THRESH(NCN26000_ED_THRESH_DEFAULT) |
+ NCN26000_DIG_SLEW_FAST;
+
+ struct device *dev = &priv->phydev->mdio.dev;
+ const char *s;
+ int ret;
+
+ ret = device_property_read_string(dev, "tx-gain", &s);
+ if (!ret) {
+ dev_info(dev, "tx-gain = %s\n", s);
+ if (strcmp(s, "1v1") == 0)
+ val |= NCN26000_TWEAKS_TX_GAIN(NCN26000_TX_GAIN_1V1);
+ else if (strcmp(s, "0v9") == 0)
+ val |= NCN26000_TWEAKS_TX_GAIN(NCN26000_TX_GAIN_0V9);
+ else if (strcmp(s, "0v8") == 0)
+ val |= NCN26000_TWEAKS_TX_GAIN(NCN26000_TX_GAIN_0V8);
+ else if (strcmp(s, "1v0") != 0)
+ dev_err(dev, "tx-gain is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "tx-slew", &s);
+ if (!ret) {
+ dev_info(dev, "tx-slew = %s\n", s);
+ if (strcmp(s, "fast") == 0)
+ val |= NCN26000_TX_SLEW_FAST;
+ else if (strcmp(s, "slow") != 0)
+ dev_err(dev, "tx-slew is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "dig-slew", &s);
+ if (!ret) {
+ dev_info(dev, "dig-slew = %s\n", s);
+ if (strcmp(s, "slow") == 0)
+ val &= ~NCN26000_DIG_SLEW_FAST;
+ else if (strcmp(s, "fast") != 0)
+ dev_err(dev, "dig-slew is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "cmc-comp", &s);
+ if (!ret) {
+ dev_info(dev, "cmc-comp = %s\n", s);
+ if (strcmp(s, "1p37") == 0)
+ val |= NCN26000_TWEAKS_CMC_COMP(NCN26000_CMC_COMP_1P37);
+ else if (strcmp(s, "3p00") == 0)
+ val |= NCN26000_TWEAKS_CMC_COMP(NCN26000_CMC_COMP_3P00);
+ else if (strcmp(s, "4p37") == 0)
+ val |= NCN26000_TWEAKS_CMC_COMP(NCN26000_CMC_COMP_4P37);
+ else if (strcmp(s, "0p25") != 0)
+ dev_err(dev, "cmc-comp is invalid: %s\n", s);
+ }
+
+ priv->tweaks = val;
+}
+
+static void ncn26000_plcaext_config(struct ncn26000_priv *priv)
+{
+ u16 val = NCN26000_PLCAEXT_MIIEXTDIS;
+
+ if (device_property_read_bool(priv->dev, "plca-precedence")) {
+ dev_info(priv->dev, "PLCA precedence mode enabled\n");
+ val |= NCN26000_PLCAEXT_PRECEDENCE;
+ }
+
+ priv->plcaext = val;
+}
+
+static void ncn26000_phycfg0_config(struct ncn26000_priv *priv)
+{
+ u16 val = NCN26000_PHYCFG1_RX_DLY | NCN26000_PHYCFG1_NO_COL_MASK |
+ NCN26000_PHYCFG1_UNJAB_TMR;
+
+ struct device *dev = &priv->phydev->mdio.dev;
+ const char *s;
+ int ret;
+
+ priv->eni_mode = ENI_AUTO;
+ ret = device_property_read_string(dev, "eni-mode", &s);
+ if (!ret) {
+ dev_info(dev, "eni-mode = %s\n", s);
+ if (strcmp(s, "force-on") == 0) {
+ priv->eni_mode = ENI_FORCE_ON;
+ val |= NCN26000_PHYCFG1_ENI;
+ } else if (strcmp(s, "force-off") == 0) {
+ priv->eni_mode = ENI_FORCE_OFF;
+ } else if (strcmp(s, "auto") != 0) {
+ dev_err(dev, "eni-mode is invalid: %s\n", s);
+ }
+ }
+
+ if (device_property_read_bool(priv->dev, "tx-pkt-loop")) {
+ dev_info(priv->dev, "TX packet loop enabled\n");
+ val |= NCN26000_PHYCFG1_PKT_LOOP;
+ }
+
+ if (device_property_read_bool(priv->dev, "unjab-tmr-disable")) {
+ dev_info(priv->dev, "unjab timer disabled\n");
+ val &= ~NCN26000_PHYCFG1_UNJAB_TMR;
+ }
+
+ if (device_property_read_bool(priv->dev, "col-disable")) {
+ dev_info(priv->dev, "ENI collision detect disabled\n");
+ val &= ~NCN26000_PHYCFG1_NO_COL_MASK;
+ }
+
+ if (device_property_read_bool(priv->dev, "no-rx-delay")) {
+ dev_info(priv->dev, "RX delay disabled\n");
+ val &= ~NCN26000_PHYCFG1_RX_DLY;
+ }
+
+ priv->phycfg0 = val;
+}
+
+static void ncn26000_dio_config(struct ncn26000_priv *priv)
+{
+ struct device *dev = &priv->phydev->mdio.dev;
+ const char *s;
+ u16 val = 0;
+ int ret;
+
+ ret = device_property_read_string(dev, "dio0-fn", &s);
+ if (!ret) {
+ dev_info(dev, "dio0-fn = %s\n", s);
+ if (strcmp(s, "sfd-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_SFD_TX);
+ else if (strcmp(s, "sfd-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_SFD_RX);
+ else if (strcmp(s, "sfd-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_SFD_RXTX);
+ else if (strcmp(s, "led-link") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_LINK);
+ else if (strcmp(s, "led-plca") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_PLCA);
+ else if (strcmp(s, "led-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_TX);
+ else if (strcmp(s, "led-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_RX);
+ else if (strcmp(s, "led-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_LED_RXTX);
+ else if (strcmp(s, "clk25m") == 0)
+ val |= NCN26000_DIO_CFG_FN0(NCN26000_DIO_FN_CLK25M);
+ else
+ dev_err(dev, "dio0-fn is invalid: %s\n", s);
+ }
+
+ if (device_property_read_bool(dev, "dio0-pullup"))
+ val |= NCN26000_DIO_CFG_PULL_EN0;
+
+ if (device_property_read_bool(dev, "dio0-active-high"))
+ val |= NCN26000_DIO_CFG_VAL0;
+
+ ret = device_property_read_string(dev, "dio0-slew", &s);
+ if (!ret) {
+ dev_info(dev, "dio0-slew = %s\n", s);
+ if (strcmp(s, "slow") == 0)
+ val |= NCN26000_DIO_CFG_SLEW_RATE0;
+ else if (strcmp(s, "fast") != 0)
+ dev_err(dev, "dio0-slew is invalid: %s\n", s);
+ }
+
+ ret = device_property_read_string(dev, "dio1-fn", &s);
+ if (!ret) {
+ dev_info(dev, "dio1-fn = %s\n", s);
+ if (strcmp(s, "sfd-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_SFD_TX);
+ else if (strcmp(s, "sfd-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_SFD_RX);
+ else if (strcmp(s, "sfd-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_SFD_RXTX);
+ else if (strcmp(s, "led-link") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_LINK);
+ else if (strcmp(s, "led-plca") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_PLCA);
+ else if (strcmp(s, "led-tx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_TX);
+ else if (strcmp(s, "led-rx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_RX);
+ else if (strcmp(s, "led-rxtx") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_LED_RXTX);
+ else if (strcmp(s, "clk25m") == 0)
+ val |= NCN26000_DIO_CFG_FN1(NCN26000_DIO_FN_CLK25M);
+ else
+ dev_err(dev, "dio1-fn is invalid: %s\n", s);
+ }
+
+ if (device_property_read_bool(dev, "dio1-pullup"))
+ val |= NCN26000_DIO_CFG_PULL_EN1;
+
+ if (device_property_read_bool(dev, "dio1-active-high"))
+ val |= NCN26000_DIO_CFG_VAL1;
+
+ ret = device_property_read_string(dev, "dio1-slew", &s);
+ if (!ret) {
+ dev_info(dev, "dio1-slew = %s\n", s);
+ if (strcmp(s, "slow") == 0)
+ val |= NCN26000_DIO_CFG_SLEW_RATE1;
+ else if (strcmp(s, "fast") != 0)
+ dev_err(dev, "dio1-slew is invalid: %s\n", s);
+ }
+
+ priv->diocfg = val;
+}

static int ncn26000_config_init(struct phy_device *phydev)
{
+ struct ncn26000_priv *priv = phydev->priv;
+ int ret;
+
+ // configure the DIOx function
+ ret = phy_write(phydev, NCN26000_REG_DIO_CONFIG, priv->diocfg);
+ if (ret)
+ goto out;
+
+ // configure proprietary PHY features
+ ret = phy_write_mmd(phydev, NCN26000_REG_PHYCFG1_MMD,
+ NCN26000_REG_PHYCFG1, priv->phycfg0);
+ if (ret)
+ goto out;
+
+ // configure the PLCA extensions
+ ret = phy_write_mmd(phydev, NCN26000_REG_PLCAEXT_MMD,
+ NCN26000_REG_PLCAEXT, priv->plcaext);
+ if (ret)
+ goto out;
+
+ // configure additional PHY tweaks
+ ret = phy_write_mmd(phydev, NCN26000_REG_TWEAKS_MMD,
+ NCN26000_REG_TWEAKS, priv->tweaks);
+ if (ret)
+ goto out;
+
/* HW bug workaround: the default value of the PLCA TO_TIMER should be
* 32, where the current version of NCN26000 reports 24. This will be
* fixed in future PHY versions. For the time being, we force the
* correct default here.
*/
- return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
- TO_TMR_DEFAULT);
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
+ TO_TMR_DEFAULT);
+
+out:
+ return ret;
}

static int ncn26000_config_aneg(struct phy_device *phydev)
@@ -100,6 +490,36 @@ static int ncn26000_read_status(struct phy_device *phydev)
return 0;
}

+static int ncn26000_plca_set_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg)
+{
+ struct ncn26000_priv *priv = phydev->priv;
+ int ret;
+
+ /* We capture calls to PLCA set config to intercept PLCA enable/disable
+ * requests and set the proprietary ENI mode accordingly
+ */
+ ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
+ if (ret || plca_cfg->enabled < 0 || priv->eni_mode != ENI_AUTO)
+ goto out;
+
+ ret = phy_read_mmd(phydev, NCN26000_REG_PHYCFG1_MMD,
+ NCN26000_REG_PHYCFG1);
+
+ if (ret < 0)
+ goto out;
+
+ if (plca_cfg->enabled)
+ ret |= NCN26000_PHYCFG1_ENI;
+ else
+ ret &= ~NCN26000_PHYCFG1_ENI;
+
+ ret = phy_write_mmd(phydev, NCN26000_REG_PHYCFG1_MMD,
+ NCN26000_REG_PHYCFG1, ret);
+out:
+ return ret;
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -140,20 +560,51 @@ static int ncn26000_config_intr(struct phy_device *phydev)
return 0;
}

+static int ncn26000_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct ncn26000_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->phydev = phydev;
+ phydev->priv = priv;
+
+ ncn26000_dio_config(priv);
+ if (priv->gpiomask)
+ ncn26000_gpio_setup(priv);
+
+ ncn26000_phycfg0_config(priv);
+ ncn26000_plcaext_config(priv);
+ ncn26000_tweaks_config(priv);
+
+ return 0;
+}
+
+static void ncn26000_remove(struct phy_device *phydev)
+{
+ kfree(phydev->priv);
+}
+
static struct phy_driver ncn26000_driver[] = {
{
PHY_ID_MATCH_MODEL(PHY_ID_NCN26000),
.name = "NCN26000",
.features = PHY_BASIC_T1S_P2MP_FEATURES,
- .config_init = ncn26000_config_init,
- .config_intr = ncn26000_config_intr,
+ .probe = ncn26000_probe,
+ .remove = ncn26000_remove,
+ .config_init = ncn26000_config_init,
+ .config_intr = ncn26000_config_intr,
.config_aneg = ncn26000_config_aneg,
.read_status = ncn26000_read_status,
- .handle_interrupt = ncn26000_handle_interrupt,
+ .handle_interrupt = ncn26000_handle_interrupt,
+ .set_plca_cfg = ncn26000_plca_set_cfg,
.get_plca_cfg = genphy_c45_plca_get_cfg,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
- .soft_reset = genphy_soft_reset,
+ .soft_reset = genphy_soft_reset,
},
};

--
2.40.1