diff mbox series

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

Message ID ZQf1Nx+2amtUZulX@lopingdog.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net: Add NCN26010 driver | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Guessed tree name to be net-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1340 this patch: 1340
netdev/cc_maintainers warning 7 maintainers not CCed: hkallweit1@gmail.com linux@armlinux.org.uk andrew@lunn.ch pabeni@redhat.com davem@davemloft.net edumazet@google.com kuba@kernel.org
netdev/build_clang success Errors and warnings before: 1363 this patch: 1363
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1363 this patch: 1363
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Jay Monkman Sept. 18, 2023, 6:59 a.m. UTC
This adds GPIO support and devicetree configuration for
the NCN26000 PHY.

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

Comments

Vadim Fedorenko Sept. 18, 2023, 10:40 a.m. UTC | #1
On 18/09/2023 07:59, Jay Monkman wrote:
> 
> This adds GPIO support and devicetree configuration for
> the NCN26000 PHY.
> 
> Signed-off-by: Jay Monkman <jtm@lopingdog.com>
> ---
>   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))

it's better to use BIT(offset) here

> +		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))

		     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))

		     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,
>   	},
>   };
>
Andrew Lunn Sept. 18, 2023, 4:18 p.m. UTC | #2
On Mon, Sep 18, 2023 at 01:59:03AM -0500, Jay Monkman wrote:
> 
> This adds GPIO support and devicetree configuration for
> the NCN26000 PHY.

Please Cc: the GPIO maintainers. They have specialist knowledge that
netdev reviews like me don't have. You probably want to separate this
out into a patch of its own, since you don't want to be spamming GPIO
people with a MAC driver etc.

> +// clause 45 vendor specific registers
> +#define NCN26000_REG_PHYCFG1_MMD	MDIO_MMD_VEND2

Please use MDIO_MMD_VEND2 directly, so it is clear you are in vendor
space.

> +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;

Can that happen? I would expect the GPIO core to perform this
validation?

> +
> +	if (priv->gpiomask & (1 << offset))
> +		return 0;
> +
> +	return -EBUSY;

Is this function even needed? All it seems to do is validation. No
resources are actually reserved.

> +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);

You are doing a read/modify/write here. How does locking work?

> +static int ncn26000_gpio_get_dir(struct gpio_chip *gc, unsigned int offset)
> +{
> +	return GPIO_LINE_DIRECTION_OUT;
> +}

So they are all GPO? No GPI or GPIO?

> +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);
> +}

Am i right in saying that the rest of this patch has nothing to do
with GPIOs? Please split it up into multiple patches.

     Andrew
Andrew Lunn Sept. 18, 2023, 4:19 p.m. UTC | #3
On Mon, Sep 18, 2023 at 11:40:36AM +0100, Vadim Fedorenko wrote:
> > +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))
> 
> it's better to use BIT(offset) here

Hi Vadim

When replying, please trim the email to just the relevant text.

     Thanks
	Andrew
diff mbox series

Patch

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,
 	},
 };