diff mbox series

net: phy: TLK10X initial driver submission

Message ID 20180419082816.109338-1-mans.andersson@nibe.se
State Changes Requested, archived
Delegated to: David Miller
Headers show
Series net: phy: TLK10X initial driver submission | expand

Commit Message

Måns Andersson April 19, 2018, 8:28 a.m. UTC
From: Mans Andersson <mans.andersson@nibe.se>

Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.

In addition the TLK10X needs to be removed from DP83848 driver as the
power back off support is added here for this device.

Datasheet:
http://www.ti.com/lit/gpn/tlk106
---
 .../devicetree/bindings/net/ti,tlk10x.txt          |  27 +++
 drivers/net/phy/Kconfig                            |   5 +
 drivers/net/phy/Makefile                           |   1 +
 drivers/net/phy/dp83848.c                          |   3 -
 drivers/net/phy/tlk10x.c                           | 209 +++++++++++++++++++++
 5 files changed, 242 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/net/ti,tlk10x.txt
 create mode 100644 drivers/net/phy/tlk10x.c

Comments

Andrew Lunn April 19, 2018, 12:09 p.m. UTC | #1
On Thu, Apr 19, 2018 at 10:28:16AM +0200, Måns Andersson wrote:
> From: Mans Andersson <mans.andersson@nibe.se>
> 
> Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.
> 
> In addition the TLK10X needs to be removed from DP83848 driver as the
> power back off support is added here for this device.
> 
> Datasheet:
> http://www.ti.com/lit/gpn/tlk106
> ---
>  .../devicetree/bindings/net/ti,tlk10x.txt          |  27 +++
>  drivers/net/phy/Kconfig                            |   5 +
>  drivers/net/phy/Makefile                           |   1 +
>  drivers/net/phy/dp83848.c                          |   3 -
>  drivers/net/phy/tlk10x.c                           | 209 +++++++++++++++++++++
>  5 files changed, 242 insertions(+), 3 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/net/ti,tlk10x.txt
>  create mode 100644 drivers/net/phy/tlk10x.c
> 
> diff --git a/Documentation/devicetree/bindings/net/ti,tlk10x.txt b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> new file mode 100644
> index 0000000..371d0d7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> @@ -0,0 +1,27 @@
> +* Texas Instruments - TLK105 / TLK106 ethernet PHYs
> +
> +Required properties:
> +	- reg - The ID number for the phy, usually a small integer
> +
> +Optional properties:
> +	- ti,power-back-off - Power Back Off Level
> +		Please refer to data sheet chapter 8.6 and TI Application
> +		Note SLLA3228
> +		0 - Normal Operation
> +		1 - Level 1 (up to 140m cable between TLK link partners)
> +		2 - Level 2 (up to 100m cable between TLK link partners)
> +		3 - Level 3 (up to 80m cable between TLK link partners)

Hi Måns

Device tree is all about board properties. In most cases, power back
off is not a board properties, since it depends on the cable length
and the peer board. If however, your board has two PHYs back to back,
say to connect to an Ethernet switch, that would be a valid board
property.

How are you using this?

I know of others who would like such a configuration. Marvell PHYs can
do something similar. I've always suggested adding a PHY tunable. Pass
the cable length in meters and let the PHY driver pick the nearest it
can do, rounding up. The Marvell PHYs also support measuring the cable
length as part of the cable diagnostics. So it would be good to
reserve a configuration value to mean 'auto' - measure the cable and
then pick the best power back off. Quickly scanning the data sheet, i
see that this PHY also has the ability to measure the cable length.

> +static int tlk10x_read(struct phy_device *phydev, int reg)
> +{
> +	if (reg & ~0x1f) {
> +		/* Extended register */
> +		phy_write(phydev, TLK10X_REGCR, 0x001F);
> +		phy_write(phydev, TLK10X_ADDAR, reg);
> +		phy_write(phydev, TLK10X_REGCR, 0x401F);
> +		reg = TLK10X_ADDAR;
> +	}
> +
> +	return phy_read(phydev, reg);
> +}
> +
> +static int tlk10x_write(struct phy_device *phydev, int reg, int val)
> +{
> +	if (reg & ~0x1f) {
> +		/* Extended register */
> +		phy_write(phydev, TLK10X_REGCR, 0x001F);
> +		phy_write(phydev, TLK10X_ADDAR, reg);
> +		phy_write(phydev, TLK10X_REGCR, 0x401F);
> +		reg = TLK10X_ADDAR;
> +	}
> +
> +	return phy_write(phydev, reg, val);
> +}

This looks to be phy_read_mmd() and phy_write_mmd(). If so, please use
them, they get the locking correct.


> +#ifdef CONFIG_OF_MDIO
> +static int tlk10x_of_init(struct phy_device *phydev)
> +{
> +	struct tlk10x_private *tlk10x = phydev->priv;
> +	struct device *dev = &phydev->mdio.dev;
> +	struct device_node *of_node = dev->of_node;
> +	int ret;
> +
> +	if (!of_node)
> +		return 0;
> +
> +	ret = of_property_read_u32(of_node, "ti,power-back-off",
> +				   &tlk10x->pwrbo_level);
> +	if (ret) {
> +		dev_err(dev, "missing ti,power-back-off property");
> +		tlk10x->pwrbo_level = 0;
> +	}

If we decide to accept this, you should do range checking, and return
-EINVAL if the value is out of range.

> +static int tlk10x_config_init(struct phy_device *phydev)
> +{
> +	int ret, reg;
> +	struct tlk10x_private *tlk10x;
> +
> +	ret = genphy_config_init(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (!phydev->priv) {
> +		tlk10x = devm_kzalloc(&phydev->mdio.dev, sizeof(*tlk10x),
> +				      GFP_KERNEL);
> +		if (!tlk10x)
> +			return -ENOMEM;
> +
> +		phydev->priv = tlk10x;
> +		ret = tlk10x_of_init(phydev);
> +		if (ret)
> +			return ret;
> +	} else {
> +		tlk10x = (struct tlk10x_private *)phydev->priv;
> +	}

This allocation should be done in .probe

> +
> +	// Power back off
> +	if (tlk10x->pwrbo_level < 0 || tlk10x->pwrbo_level > 3)
> +		tlk10x->pwrbo_level = 0;
> +	reg = tlk10x_read(phydev, TLK10X_PWRBOCR);
> +	reg = ((reg & ~TLK10X_PWRBOCR_MASK)
> +		| (tlk10x->pwrbo_level << 6));
> +	ret = tlk10x_write(phydev, TLK10X_PWRBOCR, reg);
> +	if (ret < 0) {
> +		dev_err(&phydev->mdio.dev,
> +			"unable to set power back-off (err=%d)\n", ret);
> +		return ret;
> +	}
> +	dev_info(&phydev->mdio.dev, "power back-off set to level %d\n",
> +		 tlk10x->pwrbo_level);
> +
> +	return 0;
> +}

  Andrew
Miguel Ojeda April 19, 2018, 12:24 p.m. UTC | #2
On Thu, Apr 19, 2018 at 10:28 AM, Måns Andersson <mans.andersson@nibe.se> wrote:
> From: Mans Andersson <mans.andersson@nibe.se>
>
> Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.
>

Hi Mans,

Some quick notes.

> In addition the TLK10X needs to be removed from DP83848 driver as the
> power back off support is added here for this device.
>
> Datasheet:
> http://www.ti.com/lit/gpn/tlk106

Missing signature.

> ---
>  .../devicetree/bindings/net/ti,tlk10x.txt          |  27 +++
>  drivers/net/phy/Kconfig                            |   5 +
>  drivers/net/phy/Makefile                           |   1 +
>  drivers/net/phy/dp83848.c                          |   3 -
>  drivers/net/phy/tlk10x.c                           | 209 +++++++++++++++++++++
>  5 files changed, 242 insertions(+), 3 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/net/ti,tlk10x.txt
>  create mode 100644 drivers/net/phy/tlk10x.c
>
> diff --git a/Documentation/devicetree/bindings/net/ti,tlk10x.txt b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> new file mode 100644
> index 0000000..371d0d7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> @@ -0,0 +1,27 @@
> +* Texas Instruments - TLK105 / TLK106 ethernet PHYs
> +
> +Required properties:
> +       - reg - The ID number for the phy, usually a small integer
> +
> +Optional properties:
> +       - ti,power-back-off - Power Back Off Level
> +               Please refer to data sheet chapter 8.6 and TI Application
> +               Note SLLA3228
> +               0 - Normal Operation
> +               1 - Level 1 (up to 140m cable between TLK link partners)
> +               2 - Level 2 (up to 100m cable between TLK link partners)
> +               3 - Level 3 (up to 80m cable between TLK link partners)
> +
> +Default child nodes are standard Ethernet PHY device
> +nodes as described in Documentation/devicetree/bindings/net/phy.txt
> +
> +Example:
> +
> +       ethernet-phy@0 {
> +               reg = <0>;
> +               ti,power-back-off = <2>;
> +       };
> +
> +Datasheets and documentation can be found at:
> +http://www.ti.com/lit/gpn/tlk106
> +http://www.ti.com/lit/an/slla328/slla328.pdf
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index bdfbabb..c980240 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -295,6 +295,11 @@ config DP83867_PHY
>         ---help---
>           Currently supports the DP83867 PHY.
>
> +config TLK10X_PHY
> +       tristate "Texas Instruments TLK10x PHY"
> +       ---help---
> +         Supports the TLK105 and TLK106 PHYs.
> +
>  config FIXED_PHY
>         tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
>         depends on PHYLIB
> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> index 01acbcb..37e4e02 100644
> --- a/drivers/net/phy/Makefile
> +++ b/drivers/net/phy/Makefile
> @@ -79,5 +79,6 @@ obj-$(CONFIG_ROCKCHIP_PHY)    += rockchip.o
>  obj-$(CONFIG_SMSC_PHY)         += smsc.o
>  obj-$(CONFIG_STE10XP)          += ste10Xp.o
>  obj-$(CONFIG_TERANETICS_PHY)   += teranetics.o
> +obj-$(CONFIG_TLK10X_PHY)       += tlk10x.o
>  obj-$(CONFIG_VITESSE_PHY)      += vitesse.o
>  obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
> diff --git a/drivers/net/phy/dp83848.c b/drivers/net/phy/dp83848.c
> index cd09c3a..435f401 100644
> --- a/drivers/net/phy/dp83848.c
> +++ b/drivers/net/phy/dp83848.c
> @@ -19,7 +19,6 @@
>  #define TI_DP83848C_PHY_ID             0x20005ca0
>  #define TI_DP83620_PHY_ID              0x20005ce0
>  #define NS_DP83848C_PHY_ID             0x20005c90
> -#define TLK10X_PHY_ID                  0x2000a210
>
>  /* Registers */
>  #define DP83848_MICR                   0x11 /* MII Interrupt Control Register */
> @@ -78,7 +77,6 @@ static struct mdio_device_id __maybe_unused dp83848_tbl[] = {
>         { TI_DP83848C_PHY_ID, 0xfffffff0 },
>         { NS_DP83848C_PHY_ID, 0xfffffff0 },
>         { TI_DP83620_PHY_ID, 0xfffffff0 },
> -       { TLK10X_PHY_ID, 0xfffffff0 },
>         { }
>  };
>  MODULE_DEVICE_TABLE(mdio, dp83848_tbl);
> @@ -105,7 +103,6 @@ static struct phy_driver dp83848_driver[] = {
>         DP83848_PHY_DRIVER(TI_DP83848C_PHY_ID, "TI DP83848C 10/100 Mbps PHY"),
>         DP83848_PHY_DRIVER(NS_DP83848C_PHY_ID, "NS DP83848C 10/100 Mbps PHY"),
>         DP83848_PHY_DRIVER(TI_DP83620_PHY_ID, "TI DP83620 10/100 Mbps PHY"),
> -       DP83848_PHY_DRIVER(TLK10X_PHY_ID, "TI TLK10X 10/100 Mbps PHY"),
>  };
>  module_phy_driver(dp83848_driver);
>
> diff --git a/drivers/net/phy/tlk10x.c b/drivers/net/phy/tlk10x.c
> new file mode 100644
> index 0000000..1efc81e
> --- /dev/null
> +++ b/drivers/net/phy/tlk10x.c
> @@ -0,0 +1,209 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/**
> + * Driver for the Texas Instruments TLK105 / TLK106
> + *
> + * Copyright (C) 2018 NIBE Industrier AB - http://www.nibe.com
> + *
> + * 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.
> + *
> + * 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.

Since you are using the SPDX id, please remove the license text (which
is actually wrong: it seems you have cut the v2+ version and then
removed the last sentence of the first paragraph? :-).

> + */
> +
> +#include <linux/module.h>
> +#include <linux/phy.h>
> +#include <linux/of.h>
> +
> +#define TLK10X_PHY_ID                  0x2000a210
> +
> +/* Registers */
> +#define TLK10X_MICR                    0x11 /* MII Interrupt Control Reg */
> +#define TLK10X_MISR                    0x12 /* MII Interrupt Status Reg */
> +#define TLK10X_REGCR                   0x0d /* Register Control Register */
> +#define TLK10X_ADDAR                   0x0e /* Data Register */
> +#define TLK10X_PWRBOCR                 0xae /* Power Backoff Register */
> +
> +/* MICR Register Fields */
> +#define TLK10X_MICR_INT_OE             BIT(0) /* Interrupt Output Enable */
> +#define TLK10X_MICR_INTEN              BIT(1) /* Interrupt Enable */
> +
> +/* MISR Register Fields */
> +#define TLK10X_MISR_RHF_INT_EN         BIT(0) /* Receive Error Counter */
> +#define TLK10X_MISR_FHF_INT_EN         BIT(1) /* False Carrier Counter */
> +#define TLK10X_MISR_ANC_INT_EN         BIT(2) /* Auto-negotiation complete */
> +#define TLK10X_MISR_DUP_INT_EN         BIT(3) /* Duplex Status */
> +#define TLK10X_MISR_SPD_INT_EN         BIT(4) /* Speed status */
> +#define TLK10X_MISR_LINK_INT_EN                BIT(5) /* Link status */
> +#define TLK10X_MISR_ED_INT_EN          BIT(6) /* Energy detect */
> +#define TLK10X_MISR_LQM_INT_EN         BIT(7) /* Link Quality Monitor */
> +
> +/* PWRBOCR Register Fields */
> +#define TLK10X_PWRBOCR_MASK            0xe0 /* Power Backoff Mask */
> +
> +#define TLK10X_INT_EN_MASK             \
> +       (TLK10X_MISR_ANC_INT_EN |       \
> +        TLK10X_MISR_DUP_INT_EN |       \
> +        TLK10X_MISR_SPD_INT_EN |       \
> +        TLK10X_MISR_LINK_INT_EN)
> +
> +struct tlk10x_private {
> +       int pwrbo_level;
> +};
> +
> +static int tlk10x_read(struct phy_device *phydev, int reg)
> +{
> +       if (reg & ~0x1f) {

0x1f or ~0x1f should probably have a #define name.

> +               /* Extended register */
> +               phy_write(phydev, TLK10X_REGCR, 0x001F);
> +               phy_write(phydev, TLK10X_ADDAR, reg);
> +               phy_write(phydev, TLK10X_REGCR, 0x401F);
> +               reg = TLK10X_ADDAR;
> +       }
> +
> +       return phy_read(phydev, reg);
> +}
> +
> +static int tlk10x_write(struct phy_device *phydev, int reg, int val)
> +{
> +       if (reg & ~0x1f) {

Ditto.

> +               /* Extended register */
> +               phy_write(phydev, TLK10X_REGCR, 0x001F);
> +               phy_write(phydev, TLK10X_ADDAR, reg);
> +               phy_write(phydev, TLK10X_REGCR, 0x401F);
> +               reg = TLK10X_ADDAR;
> +       }
> +
> +       return phy_write(phydev, reg, val);
> +}
> +
> +#ifdef CONFIG_OF_MDIO

Maybe you want the #ifdef inside.

> +static int tlk10x_of_init(struct phy_device *phydev)
> +{
> +       struct tlk10x_private *tlk10x = phydev->priv;
> +       struct device *dev = &phydev->mdio.dev;
> +       struct device_node *of_node = dev->of_node;
> +       int ret;
> +
> +       if (!of_node)
> +               return 0;
> +
> +       ret = of_property_read_u32(of_node, "ti,power-back-off",
> +                                  &tlk10x->pwrbo_level);
> +       if (ret) {
> +               dev_err(dev, "missing ti,power-back-off property");
> +               tlk10x->pwrbo_level = 0;
> +       }
> +
> +       return 0;
> +}
> +#else
> +static int tlk10x_of_init(struct phy_device *phydev)
> +{
> +       return 0;
> +}
> +#endif /* CONFIG_OF_MDIO */
> +
> +static int tlk10x_config_init(struct phy_device *phydev)
> +{
> +       int ret, reg;
> +       struct tlk10x_private *tlk10x;
> +
> +       ret = genphy_config_init(phydev);
> +       if (ret < 0)
> +               return ret;
> +
> +       if (!phydev->priv) {
> +               tlk10x = devm_kzalloc(&phydev->mdio.dev, sizeof(*tlk10x),
> +                                     GFP_KERNEL);
> +               if (!tlk10x)
> +                       return -ENOMEM;
> +
> +               phydev->priv = tlk10x;
> +               ret = tlk10x_of_init(phydev);
> +               if (ret)
> +                       return ret;
> +       } else {
> +               tlk10x = (struct tlk10x_private *)phydev->priv;
> +       }
> +
> +       // Power back off
> +       if (tlk10x->pwrbo_level < 0 || tlk10x->pwrbo_level > 3)
> +               tlk10x->pwrbo_level = 0;
> +       reg = tlk10x_read(phydev, TLK10X_PWRBOCR);
> +       reg = ((reg & ~TLK10X_PWRBOCR_MASK)
> +               | (tlk10x->pwrbo_level << 6));

Maybe the 6 should have a name, or maybe a bigger macro for this would clarify.

> +       ret = tlk10x_write(phydev, TLK10X_PWRBOCR, reg);
> +       if (ret < 0) {
> +               dev_err(&phydev->mdio.dev,
> +                       "unable to set power back-off (err=%d)\n", ret);
> +               return ret;
> +       }
> +       dev_info(&phydev->mdio.dev, "power back-off set to level %d\n",
> +                tlk10x->pwrbo_level);
> +
> +       return 0;
> +}
> +
> +static int tlk10x_ack_interrupt(struct phy_device *phydev)
> +{
> +       int err = tlk10x_read(phydev, TLK10X_MISR);

Following the style of the rest of the file, shouldn't this be:

    if (err < 0)
        return err;

    return 0;

?

> +
> +       return err < 0 ? err : 0;
> +}
> +
> +static int tlk10x_config_intr(struct phy_device *phydev)
> +{
> +       int control, ret;
> +
> +       control = tlk10x_read(phydev, TLK10X_MICR);
> +       if (control < 0)
> +               return control;
> +
> +       if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
> +               control |= TLK10X_MICR_INT_OE;
> +               control |= TLK10X_MICR_INTEN;
> +
> +               ret = tlk10x_write(phydev, TLK10X_MISR, TLK10X_INT_EN_MASK);
> +               if (ret < 0)
> +                       return ret;
> +       } else {
> +               control &= ~TLK10X_MICR_INTEN;
> +       }
> +
> +       return tlk10x_write(phydev, TLK10X_MICR, control);
> +}
> +
> +static struct phy_driver tlk10x_driver[] = {
> +       {
> +               .phy_id         = TLK10X_PHY_ID,
> +               .phy_id_mask    = 0xfffffff0,
> +               .name           = "TI TLK10X 10/100 Mbps PHY",
> +               .features       = PHY_BASIC_FEATURES,
> +               .flags          = PHY_HAS_INTERRUPT,
> +
> +               .config_init    = tlk10x_config_init,
> +               .soft_reset     = genphy_soft_reset,
> +
> +               /* IRQ related */
> +               .ack_interrupt  = tlk10x_ack_interrupt,
> +               .config_intr    = tlk10x_config_intr,
> +
> +               .suspend        = genphy_suspend,
> +               .resume         = genphy_resume,
> +       },
> +};
> +module_phy_driver(tlk10x_driver);
> +
> +static struct mdio_device_id __maybe_unused tlk10x_tbl[] = {
> +       { TLK10X_PHY_ID, 0xfffffff0 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(mdio, tlk10x_tbl);
> +
> +MODULE_DESCRIPTION("Texas Instruments TLK105 / TLK106 PHY driver");
> +MODULE_AUTHOR("Mans Andersson <mans.andersson@nibe.se>");
> +MODULE_LICENSE("GPL");

Should be "GPL v2".

Cheers,
Miguel
Måns Andersson April 20, 2018, 6:58 a.m. UTC | #3
On Thu, Apr 19, 2018 at 02:09:02PM +0200, Andrew Lunn wrote:
> On Thu, Apr 19, 2018 at 10:28:16AM +0200, Måns Andersson wrote:
> > From: Mans Andersson <mans.andersson@nibe.se>
> > 
> > Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.
> > 
> > In addition the TLK10X needs to be removed from DP83848 driver as the
> > power back off support is added here for this device.
> > 
> > Datasheet:
> > http://www.ti.com/lit/gpn/tlk106
> > ---
> >  .../devicetree/bindings/net/ti,tlk10x.txt          |  27 +++
> >  drivers/net/phy/Kconfig                            |   5 +
> >  drivers/net/phy/Makefile                           |   1 +
> >  drivers/net/phy/dp83848.c                          |   3 -
> >  drivers/net/phy/tlk10x.c                           | 209 +++++++++++++++++++++
> >  5 files changed, 242 insertions(+), 3 deletions(-)
> >  create mode 100644 Documentation/devicetree/bindings/net/ti,tlk10x.txt
> >  create mode 100644 drivers/net/phy/tlk10x.c
> > 
> > diff --git a/Documentation/devicetree/bindings/net/ti,tlk10x.txt b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> > new file mode 100644
> > index 0000000..371d0d7
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> > @@ -0,0 +1,27 @@
> > +* Texas Instruments - TLK105 / TLK106 ethernet PHYs
> > +
> > +Required properties:
> > +	- reg - The ID number for the phy, usually a small integer
> > +
> > +Optional properties:
> > +	- ti,power-back-off - Power Back Off Level
> > +		Please refer to data sheet chapter 8.6 and TI Application
> > +		Note SLLA3228
> > +		0 - Normal Operation
> > +		1 - Level 1 (up to 140m cable between TLK link partners)
> > +		2 - Level 2 (up to 100m cable between TLK link partners)
> > +		3 - Level 3 (up to 80m cable between TLK link partners)
> 
> Hi Måns
> 
> Device tree is all about board properties. In most cases, power back
> off is not a board properties, since it depends on the cable length
> and the peer board. If however, your board has two PHYs back to back,
> say to connect to an Ethernet switch, that would be a valid board
> property.
> 
> How are you using this?
> 
> I know of others who would like such a configuration. Marvell PHYs can
> do something similar. I've always suggested adding a PHY tunable. Pass
> the cable length in meters and let the PHY driver pick the nearest it
> can do, rounding up. The Marvell PHYs also support measuring the cable
> length as part of the cable diagnostics. So it would be good to
> reserve a configuration value to mean 'auto' - measure the cable and
> then pick the best power back off. Quickly scanning the data sheet, i
> see that this PHY also has the ability to measure the cable length.
>

Hi Andrew,

Thanks for your comments, highly appreciated!

I'm using this to lock down the PHY to the IEEE 802.3 100m standard cable
length, as opposed to the extended 150m which the PHY supports in its
default operation (see pg. 2 of the data sheet). The reason why I need this
is that the board has too high EMC emissions when running with the default
operation. For me the setting is therefore used as a board property, i.e.
it's not something that will be changed during operation.
 
> > +static int tlk10x_read(struct phy_device *phydev, int reg)
> > +{
> > +	if (reg & ~0x1f) {
> > +		/* Extended register */
> > +		phy_write(phydev, TLK10X_REGCR, 0x001F);
> > +		phy_write(phydev, TLK10X_ADDAR, reg);
> > +		phy_write(phydev, TLK10X_REGCR, 0x401F);
> > +		reg = TLK10X_ADDAR;
> > +	}
> > +
> > +	return phy_read(phydev, reg);
> > +}
> > +
> > +static int tlk10x_write(struct phy_device *phydev, int reg, int val)
> > +{
> > +	if (reg & ~0x1f) {
> > +		/* Extended register */
> > +		phy_write(phydev, TLK10X_REGCR, 0x001F);
> > +		phy_write(phydev, TLK10X_ADDAR, reg);
> > +		phy_write(phydev, TLK10X_REGCR, 0x401F);
> > +		reg = TLK10X_ADDAR;
> > +	}
> > +
> > +	return phy_write(phydev, reg, val);
> > +}
> 
> This looks to be phy_read_mmd() and phy_write_mmd(). If so, please use
> them, they get the locking correct.
>

Yes, that's correct, will fix that.

> 
> > +#ifdef CONFIG_OF_MDIO
> > +static int tlk10x_of_init(struct phy_device *phydev)
> > +{
> > +	struct tlk10x_private *tlk10x = phydev->priv;
> > +	struct device *dev = &phydev->mdio.dev;
> > +	struct device_node *of_node = dev->of_node;
> > +	int ret;
> > +
> > +	if (!of_node)
> > +		return 0;
> > +
> > +	ret = of_property_read_u32(of_node, "ti,power-back-off",
> > +				   &tlk10x->pwrbo_level);
> > +	if (ret) {
> > +		dev_err(dev, "missing ti,power-back-off property");
> > +		tlk10x->pwrbo_level = 0;
> > +	}
> 
> If we decide to accept this, you should do range checking, and return
> -EINVAL if the value is out of range.

Ok, will fix that.

> 
> > +static int tlk10x_config_init(struct phy_device *phydev)
> > +{
> > +	int ret, reg;
> > +	struct tlk10x_private *tlk10x;
> > +
> > +	ret = genphy_config_init(phydev);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	if (!phydev->priv) {
> > +		tlk10x = devm_kzalloc(&phydev->mdio.dev, sizeof(*tlk10x),
> > +				      GFP_KERNEL);
> > +		if (!tlk10x)
> > +			return -ENOMEM;
> > +
> > +		phydev->priv = tlk10x;
> > +		ret = tlk10x_of_init(phydev);
> > +		if (ret)
> > +			return ret;
> > +	} else {
> > +		tlk10x = (struct tlk10x_private *)phydev->priv;
> > +	}
> 
> This allocation should be done in .probe

Ok, will fix that.

> 
> > +
> > +	// Power back off
> > +	if (tlk10x->pwrbo_level < 0 || tlk10x->pwrbo_level > 3)
> > +		tlk10x->pwrbo_level = 0;
> > +	reg = tlk10x_read(phydev, TLK10X_PWRBOCR);
> > +	reg = ((reg & ~TLK10X_PWRBOCR_MASK)
> > +		| (tlk10x->pwrbo_level << 6));
> > +	ret = tlk10x_write(phydev, TLK10X_PWRBOCR, reg);
> > +	if (ret < 0) {
> > +		dev_err(&phydev->mdio.dev,
> > +			"unable to set power back-off (err=%d)\n", ret);
> > +		return ret;
> > +	}
> > +	dev_info(&phydev->mdio.dev, "power back-off set to level %d\n",
> > +		 tlk10x->pwrbo_level);
> > +
> > +	return 0;
> > +}
> 
>   Andrew

// Måns
Måns Andersson April 20, 2018, 7:07 a.m. UTC | #4
On Thu, Apr 19, 2018 at 02:24:27PM +0200, Miguel Ojeda wrote:
> On Thu, Apr 19, 2018 at 10:28 AM, Måns Andersson <mans.andersson@nibe.se> wrote:
> > From: Mans Andersson <mans.andersson@nibe.se>
> >
> > Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.
> >
> 
> Hi Mans,
> 
> Some quick notes.
> 
> > In addition the TLK10X needs to be removed from DP83848 driver as the
> > power back off support is added here for this device.
> >
> > Datasheet:
> > http://www.ti.com/lit/gpn/tlk106
> 
> Missing signature.

Hi Miguel,

My bad, will fix.

> 
> > ---
> >  .../devicetree/bindings/net/ti,tlk10x.txt          |  27 +++
> >  drivers/net/phy/Kconfig                            |   5 +
> >  drivers/net/phy/Makefile                           |   1 +
> >  drivers/net/phy/dp83848.c                          |   3 -
> >  drivers/net/phy/tlk10x.c                           | 209 +++++++++++++++++++++
> >  5 files changed, 242 insertions(+), 3 deletions(-)
> >  create mode 100644 Documentation/devicetree/bindings/net/ti,tlk10x.txt
> >  create mode 100644 drivers/net/phy/tlk10x.c
> >
> > diff --git a/Documentation/devicetree/bindings/net/ti,tlk10x.txt b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> > new file mode 100644
> > index 0000000..371d0d7
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> > @@ -0,0 +1,27 @@
> > +* Texas Instruments - TLK105 / TLK106 ethernet PHYs
> > +
> > +Required properties:
> > +       - reg - The ID number for the phy, usually a small integer
> > +
> > +Optional properties:
> > +       - ti,power-back-off - Power Back Off Level
> > +               Please refer to data sheet chapter 8.6 and TI Application
> > +               Note SLLA3228
> > +               0 - Normal Operation
> > +               1 - Level 1 (up to 140m cable between TLK link partners)
> > +               2 - Level 2 (up to 100m cable between TLK link partners)
> > +               3 - Level 3 (up to 80m cable between TLK link partners)
> > +
> > +Default child nodes are standard Ethernet PHY device
> > +nodes as described in Documentation/devicetree/bindings/net/phy.txt
> > +
> > +Example:
> > +
> > +       ethernet-phy@0 {
> > +               reg = <0>;
> > +               ti,power-back-off = <2>;
> > +       };
> > +
> > +Datasheets and documentation can be found at:
> > +http://www.ti.com/lit/gpn/tlk106
> > +http://www.ti.com/lit/an/slla328/slla328.pdf
> > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > index bdfbabb..c980240 100644
> > --- a/drivers/net/phy/Kconfig
> > +++ b/drivers/net/phy/Kconfig
> > @@ -295,6 +295,11 @@ config DP83867_PHY
> >         ---help---
> >           Currently supports the DP83867 PHY.
> >
> > +config TLK10X_PHY
> > +       tristate "Texas Instruments TLK10x PHY"
> > +       ---help---
> > +         Supports the TLK105 and TLK106 PHYs.
> > +
> >  config FIXED_PHY
> >         tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
> >         depends on PHYLIB
> > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> > index 01acbcb..37e4e02 100644
> > --- a/drivers/net/phy/Makefile
> > +++ b/drivers/net/phy/Makefile
> > @@ -79,5 +79,6 @@ obj-$(CONFIG_ROCKCHIP_PHY)    += rockchip.o
> >  obj-$(CONFIG_SMSC_PHY)         += smsc.o
> >  obj-$(CONFIG_STE10XP)          += ste10Xp.o
> >  obj-$(CONFIG_TERANETICS_PHY)   += teranetics.o
> > +obj-$(CONFIG_TLK10X_PHY)       += tlk10x.o
> >  obj-$(CONFIG_VITESSE_PHY)      += vitesse.o
> >  obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
> > diff --git a/drivers/net/phy/dp83848.c b/drivers/net/phy/dp83848.c
> > index cd09c3a..435f401 100644
> > --- a/drivers/net/phy/dp83848.c
> > +++ b/drivers/net/phy/dp83848.c
> > @@ -19,7 +19,6 @@
> >  #define TI_DP83848C_PHY_ID             0x20005ca0
> >  #define TI_DP83620_PHY_ID              0x20005ce0
> >  #define NS_DP83848C_PHY_ID             0x20005c90
> > -#define TLK10X_PHY_ID                  0x2000a210
> >
> >  /* Registers */
> >  #define DP83848_MICR                   0x11 /* MII Interrupt Control Register */
> > @@ -78,7 +77,6 @@ static struct mdio_device_id __maybe_unused dp83848_tbl[] = {
> >         { TI_DP83848C_PHY_ID, 0xfffffff0 },
> >         { NS_DP83848C_PHY_ID, 0xfffffff0 },
> >         { TI_DP83620_PHY_ID, 0xfffffff0 },
> > -       { TLK10X_PHY_ID, 0xfffffff0 },
> >         { }
> >  };
> >  MODULE_DEVICE_TABLE(mdio, dp83848_tbl);
> > @@ -105,7 +103,6 @@ static struct phy_driver dp83848_driver[] = {
> >         DP83848_PHY_DRIVER(TI_DP83848C_PHY_ID, "TI DP83848C 10/100 Mbps PHY"),
> >         DP83848_PHY_DRIVER(NS_DP83848C_PHY_ID, "NS DP83848C 10/100 Mbps PHY"),
> >         DP83848_PHY_DRIVER(TI_DP83620_PHY_ID, "TI DP83620 10/100 Mbps PHY"),
> > -       DP83848_PHY_DRIVER(TLK10X_PHY_ID, "TI TLK10X 10/100 Mbps PHY"),
> >  };
> >  module_phy_driver(dp83848_driver);
> >
> > diff --git a/drivers/net/phy/tlk10x.c b/drivers/net/phy/tlk10x.c
> > new file mode 100644
> > index 0000000..1efc81e
> > --- /dev/null
> > +++ b/drivers/net/phy/tlk10x.c
> > @@ -0,0 +1,209 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/**
> > + * Driver for the Texas Instruments TLK105 / TLK106
> > + *
> > + * Copyright (C) 2018 NIBE Industrier AB - http://www.nibe.com
> > + *
> > + * 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.
> > + *
> > + * 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.
> 
> Since you are using the SPDX id, please remove the license text (which
> is actually wrong: it seems you have cut the v2+ version and then
> removed the last sentence of the first paragraph? :-).
>

Ok.
 
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/phy.h>
> > +#include <linux/of.h>
> > +
> > +#define TLK10X_PHY_ID                  0x2000a210
> > +
> > +/* Registers */
> > +#define TLK10X_MICR                    0x11 /* MII Interrupt Control Reg */
> > +#define TLK10X_MISR                    0x12 /* MII Interrupt Status Reg */
> > +#define TLK10X_REGCR                   0x0d /* Register Control Register */
> > +#define TLK10X_ADDAR                   0x0e /* Data Register */
> > +#define TLK10X_PWRBOCR                 0xae /* Power Backoff Register */
> > +
> > +/* MICR Register Fields */
> > +#define TLK10X_MICR_INT_OE             BIT(0) /* Interrupt Output Enable */
> > +#define TLK10X_MICR_INTEN              BIT(1) /* Interrupt Enable */
> > +
> > +/* MISR Register Fields */
> > +#define TLK10X_MISR_RHF_INT_EN         BIT(0) /* Receive Error Counter */
> > +#define TLK10X_MISR_FHF_INT_EN         BIT(1) /* False Carrier Counter */
> > +#define TLK10X_MISR_ANC_INT_EN         BIT(2) /* Auto-negotiation complete */
> > +#define TLK10X_MISR_DUP_INT_EN         BIT(3) /* Duplex Status */
> > +#define TLK10X_MISR_SPD_INT_EN         BIT(4) /* Speed status */
> > +#define TLK10X_MISR_LINK_INT_EN                BIT(5) /* Link status */
> > +#define TLK10X_MISR_ED_INT_EN          BIT(6) /* Energy detect */
> > +#define TLK10X_MISR_LQM_INT_EN         BIT(7) /* Link Quality Monitor */
> > +
> > +/* PWRBOCR Register Fields */
> > +#define TLK10X_PWRBOCR_MASK            0xe0 /* Power Backoff Mask */
> > +
> > +#define TLK10X_INT_EN_MASK             \
> > +       (TLK10X_MISR_ANC_INT_EN |       \
> > +        TLK10X_MISR_DUP_INT_EN |       \
> > +        TLK10X_MISR_SPD_INT_EN |       \
> > +        TLK10X_MISR_LINK_INT_EN)
> > +
> > +struct tlk10x_private {
> > +       int pwrbo_level;
> > +};
> > +
> > +static int tlk10x_read(struct phy_device *phydev, int reg)
> > +{
> > +       if (reg & ~0x1f) {
> 
> 0x1f or ~0x1f should probably have a #define name.
> 

That's true, will fix that.

> > +               /* Extended register */
> > +               phy_write(phydev, TLK10X_REGCR, 0x001F);
> > +               phy_write(phydev, TLK10X_ADDAR, reg);
> > +               phy_write(phydev, TLK10X_REGCR, 0x401F);
> > +               reg = TLK10X_ADDAR;
> > +       }
> > +
> > +       return phy_read(phydev, reg);
> > +}
> > +
> > +static int tlk10x_write(struct phy_device *phydev, int reg, int val)
> > +{
> > +       if (reg & ~0x1f) {
> 
> Ditto.
> 

Ok.

> > +               /* Extended register */
> > +               phy_write(phydev, TLK10X_REGCR, 0x001F);
> > +               phy_write(phydev, TLK10X_ADDAR, reg);
> > +               phy_write(phydev, TLK10X_REGCR, 0x401F);
> > +               reg = TLK10X_ADDAR;
> > +       }
> > +
> > +       return phy_write(phydev, reg, val);
> > +}
> > +
> > +#ifdef CONFIG_OF_MDIO
> 
> Maybe you want the #ifdef inside.
> 

What's the preferred way by the kernel maintainers? I've seen both solutions
used but figured this was the most common.

> > +static int tlk10x_of_init(struct phy_device *phydev)
> > +{
> > +       struct tlk10x_private *tlk10x = phydev->priv;
> > +       struct device *dev = &phydev->mdio.dev;
> > +       struct device_node *of_node = dev->of_node;
> > +       int ret;
> > +
> > +       if (!of_node)
> > +               return 0;
> > +
> > +       ret = of_property_read_u32(of_node, "ti,power-back-off",
> > +                                  &tlk10x->pwrbo_level);
> > +       if (ret) {
> > +               dev_err(dev, "missing ti,power-back-off property");
> > +               tlk10x->pwrbo_level = 0;
> > +       }
> > +
> > +       return 0;
> > +}
> > +#else
> > +static int tlk10x_of_init(struct phy_device *phydev)
> > +{
> > +       return 0;
> > +}
> > +#endif /* CONFIG_OF_MDIO */
> > +
> > +static int tlk10x_config_init(struct phy_device *phydev)
> > +{
> > +       int ret, reg;
> > +       struct tlk10x_private *tlk10x;
> > +
> > +       ret = genphy_config_init(phydev);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       if (!phydev->priv) {
> > +               tlk10x = devm_kzalloc(&phydev->mdio.dev, sizeof(*tlk10x),
> > +                                     GFP_KERNEL);
> > +               if (!tlk10x)
> > +                       return -ENOMEM;
> > +
> > +               phydev->priv = tlk10x;
> > +               ret = tlk10x_of_init(phydev);
> > +               if (ret)
> > +                       return ret;
> > +       } else {
> > +               tlk10x = (struct tlk10x_private *)phydev->priv;
> > +       }
> > +
> > +       // Power back off
> > +       if (tlk10x->pwrbo_level < 0 || tlk10x->pwrbo_level > 3)
> > +               tlk10x->pwrbo_level = 0;
> > +       reg = tlk10x_read(phydev, TLK10X_PWRBOCR);
> > +       reg = ((reg & ~TLK10X_PWRBOCR_MASK)
> > +               | (tlk10x->pwrbo_level << 6));
> 
> Maybe the 6 should have a name, or maybe a bigger macro for this would clarify.
> 

Will look into this.

> > +       ret = tlk10x_write(phydev, TLK10X_PWRBOCR, reg);
> > +       if (ret < 0) {
> > +               dev_err(&phydev->mdio.dev,
> > +                       "unable to set power back-off (err=%d)\n", ret);
> > +               return ret;
> > +       }
> > +       dev_info(&phydev->mdio.dev, "power back-off set to level %d\n",
> > +                tlk10x->pwrbo_level);
> > +
> > +       return 0;
> > +}
> > +
> > +static int tlk10x_ack_interrupt(struct phy_device *phydev)
> > +{
> > +       int err = tlk10x_read(phydev, TLK10X_MISR);
> 
> Following the style of the rest of the file, shouldn't this be:
> 
>     if (err < 0)
>         return err;
> 
>     return 0;
> 
> ?
> 

True, will fix that.

> > +
> > +       return err < 0 ? err : 0;
> > +}
> > +
> > +static int tlk10x_config_intr(struct phy_device *phydev)
> > +{
> > +       int control, ret;
> > +
> > +       control = tlk10x_read(phydev, TLK10X_MICR);
> > +       if (control < 0)
> > +               return control;
> > +
> > +       if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
> > +               control |= TLK10X_MICR_INT_OE;
> > +               control |= TLK10X_MICR_INTEN;
> > +
> > +               ret = tlk10x_write(phydev, TLK10X_MISR, TLK10X_INT_EN_MASK);
> > +               if (ret < 0)
> > +                       return ret;
> > +       } else {
> > +               control &= ~TLK10X_MICR_INTEN;
> > +       }
> > +
> > +       return tlk10x_write(phydev, TLK10X_MICR, control);
> > +}
> > +
> > +static struct phy_driver tlk10x_driver[] = {
> > +       {
> > +               .phy_id         = TLK10X_PHY_ID,
> > +               .phy_id_mask    = 0xfffffff0,
> > +               .name           = "TI TLK10X 10/100 Mbps PHY",
> > +               .features       = PHY_BASIC_FEATURES,
> > +               .flags          = PHY_HAS_INTERRUPT,
> > +
> > +               .config_init    = tlk10x_config_init,
> > +               .soft_reset     = genphy_soft_reset,
> > +
> > +               /* IRQ related */
> > +               .ack_interrupt  = tlk10x_ack_interrupt,
> > +               .config_intr    = tlk10x_config_intr,
> > +
> > +               .suspend        = genphy_suspend,
> > +               .resume         = genphy_resume,
> > +       },
> > +};
> > +module_phy_driver(tlk10x_driver);
> > +
> > +static struct mdio_device_id __maybe_unused tlk10x_tbl[] = {
> > +       { TLK10X_PHY_ID, 0xfffffff0 },
> > +       { }
> > +};
> > +MODULE_DEVICE_TABLE(mdio, tlk10x_tbl);
> > +
> > +MODULE_DESCRIPTION("Texas Instruments TLK105 / TLK106 PHY driver");
> > +MODULE_AUTHOR("Mans Andersson <mans.andersson@nibe.se>");
> > +MODULE_LICENSE("GPL");
> 
> Should be "GPL v2".
> 
> Cheers,
> Miguel

Good catch, will update that as well. Thanks for all the input! I will
get back in a couple of days with an updated patch.
// Måns
Rob Herring (Arm) April 24, 2018, 4:34 p.m. UTC | #5
On Thu, Apr 19, 2018 at 10:28:16AM +0200, Måns Andersson wrote:
> From: Mans Andersson <mans.andersson@nibe.se>
> 
> Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.
> 
> In addition the TLK10X needs to be removed from DP83848 driver as the
> power back off support is added here for this device.
> 
> Datasheet:
> http://www.ti.com/lit/gpn/tlk106
> ---
>  .../devicetree/bindings/net/ti,tlk10x.txt          |  27 +++

Please split bindings to a separate patch.

>  drivers/net/phy/Kconfig                            |   5 +
>  drivers/net/phy/Makefile                           |   1 +
>  drivers/net/phy/dp83848.c                          |   3 -
>  drivers/net/phy/tlk10x.c                           | 209 +++++++++++++++++++++
>  5 files changed, 242 insertions(+), 3 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/net/ti,tlk10x.txt
>  create mode 100644 drivers/net/phy/tlk10x.c
> 
> diff --git a/Documentation/devicetree/bindings/net/ti,tlk10x.txt b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> new file mode 100644
> index 0000000..371d0d7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
> @@ -0,0 +1,27 @@
> +* Texas Instruments - TLK105 / TLK106 ethernet PHYs
> +
> +Required properties:
> +	- reg - The ID number for the phy, usually a small integer

Isn't this the MDIO bus address?

This should have a compatible string too.

> +
> +Optional properties:
> +	- ti,power-back-off - Power Back Off Level
> +		Please refer to data sheet chapter 8.6 and TI Application
> +		Note SLLA3228
> +		0 - Normal Operation
> +		1 - Level 1 (up to 140m cable between TLK link partners)
> +		2 - Level 2 (up to 100m cable between TLK link partners)
> +		3 - Level 3 (up to 80m cable between TLK link partners)
> +
> +Default child nodes are standard Ethernet PHY device
> +nodes as described in Documentation/devicetree/bindings/net/phy.txt
> +
> +Example:
> +
> +	ethernet-phy@0 {
> +		reg = <0>;
> +		ti,power-back-off = <2>;
> +	};
> +
> +Datasheets and documentation can be found at:
> +http://www.ti.com/lit/gpn/tlk106
> +http://www.ti.com/lit/an/slla328/slla328.pdf

Move this to before the properties.

Rob
Florian Fainelli April 24, 2018, 4:52 p.m. UTC | #6
On 04/19/2018 01:28 AM, Måns Andersson wrote:
> From: Mans Andersson <mans.andersson@nibe.se>
> 
> Add suport for the TI TLK105 and TLK106 10/100Mbit ethernet phys.
> 
> In addition the TLK10X needs to be removed from DP83848 driver as the
> power back off support is added here for this device.

I would not think this is a compelling enough reason, you could very
well just adjust the dp83848.c driver just to account for these
properties that you are introducing. More comments below.

[snip]

> +#define TLK10X_INT_EN_MASK		\
> +	(TLK10X_MISR_ANC_INT_EN |	\
> +	 TLK10X_MISR_DUP_INT_EN |	\
> +	 TLK10X_MISR_SPD_INT_EN |	\
> +	 TLK10X_MISR_LINK_INT_EN)
> +
> +struct tlk10x_private {
> +	int pwrbo_level;

unsigned int

> +};
> +
> +static int tlk10x_read(struct phy_device *phydev, int reg)
> +{
> +	if (reg & ~0x1f) {
> +		/* Extended register */
> +		phy_write(phydev, TLK10X_REGCR, 0x001F);
> +		phy_write(phydev, TLK10X_ADDAR, reg);
> +		phy_write(phydev, TLK10X_REGCR, 0x401F);
> +		reg = TLK10X_ADDAR;
> +	}

Humm, this looks a bit fragile, you would likely want to create separate
helper functions for these extended registers and make sure you handle
write failures as well. Also consider making use of the page helpers
from include/linux/phy.h.

> +
> +	return phy_read(phydev, reg);
> +}
> +
> +static int tlk10x_write(struct phy_device *phydev, int reg, int val)
> +{
> +	if (reg & ~0x1f) {
> +		/* Extended register */
> +		phy_write(phydev, TLK10X_REGCR, 0x001F);
> +		phy_write(phydev, TLK10X_ADDAR, reg);
> +		phy_write(phydev, TLK10X_REGCR, 0x401F);
> +		reg = TLK10X_ADDAR;
> +	}

Same here.

> +
> +	return phy_write(phydev, reg, val);
> +}
> +
> +#ifdef CONFIG_OF_MDIO
> +static int tlk10x_of_init(struct phy_device *phydev)
> +{
> +	struct tlk10x_private *tlk10x = phydev->priv;
> +	struct device *dev = &phydev->mdio.dev;
> +	struct device_node *of_node = dev->of_node;
> +	int ret;
> +
> +	if (!of_node)
> +		return 0;
> +
> +	ret = of_property_read_u32(of_node, "ti,power-back-off",
> +				   &tlk10x->pwrbo_level);
> +	if (ret) {
> +		dev_err(dev, "missing ti,power-back-off property");
> +		tlk10x->pwrbo_level = 0;

This should not be necessary, that should be the default with a zero
initialized private data structure.

> +	}
> +
> +	return 0;
> +}
> +#else
> +static int tlk10x_of_init(struct phy_device *phydev)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_OF_MDIO */
> +
> +static int tlk10x_config_init(struct phy_device *phydev)
> +{
> +	int ret, reg;
> +	struct tlk10x_private *tlk10x;
> +
> +	ret = genphy_config_init(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (!phydev->priv) {
> +		tlk10x = devm_kzalloc(&phydev->mdio.dev, sizeof(*tlk10x),
> +				      GFP_KERNEL);
> +		if (!tlk10x)
> +			return -ENOMEM;
> +
> +		phydev->priv = tlk10x;
> +		ret = tlk10x_of_init(phydev);
> +		if (ret)
> +			return ret;
> +	} else {
> +		tlk10x = (struct tlk10x_private *)phydev->priv;
> +	}

You need to implement a probe() function that is responsible for
allocation private memory instead of doing this check.

> +
> +	// Power back off
> +	if (tlk10x->pwrbo_level < 0 || tlk10x->pwrbo_level > 3)
> +		tlk10x->pwrbo_level = 0;

How can you have pwrb_level < 0 when you use of_read_property_u32()?

> +	reg = tlk10x_read(phydev, TLK10X_PWRBOCR);
> +	reg = ((reg & ~TLK10X_PWRBOCR_MASK)
> +		| (tlk10x->pwrbo_level << 6));

One too many levels of parenthesis, the outer ones should not be necessary.

> +	ret = tlk10x_write(phydev, TLK10X_PWRBOCR, reg);
> +	if (ret < 0) {
> +		dev_err(&phydev->mdio.dev,
> +			"unable to set power back-off (err=%d)\n", ret);
> +		return ret;
> +	}
> +	dev_info(&phydev->mdio.dev, "power back-off set to level %d\n",
> +		 tlk10x->pwrbo_level);

config_init() is called often, consider making this a debugging statement.
Andrew Lunn April 24, 2018, 5:52 p.m. UTC | #7
> > +Required properties:
> > +	- reg - The ID number for the phy, usually a small integer
> 
> Isn't this the MDIO bus address?

Hi Rob

Yes. This text has been take direct from the generic PHY binding.
 
> This should have a compatible string too.

Please see Documentation/devicetree/bindings/net/phy.txt

compatible strings are optional. We know what device it is from its ID
registers, which are in a well known location.

	Andrew
diff mbox series

Patch

diff --git a/Documentation/devicetree/bindings/net/ti,tlk10x.txt b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
new file mode 100644
index 0000000..371d0d7
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/ti,tlk10x.txt
@@ -0,0 +1,27 @@ 
+* Texas Instruments - TLK105 / TLK106 ethernet PHYs
+
+Required properties:
+	- reg - The ID number for the phy, usually a small integer
+
+Optional properties:
+	- ti,power-back-off - Power Back Off Level
+		Please refer to data sheet chapter 8.6 and TI Application
+		Note SLLA3228
+		0 - Normal Operation
+		1 - Level 1 (up to 140m cable between TLK link partners)
+		2 - Level 2 (up to 100m cable between TLK link partners)
+		3 - Level 3 (up to 80m cable between TLK link partners)
+
+Default child nodes are standard Ethernet PHY device
+nodes as described in Documentation/devicetree/bindings/net/phy.txt
+
+Example:
+
+	ethernet-phy@0 {
+		reg = <0>;
+		ti,power-back-off = <2>;
+	};
+
+Datasheets and documentation can be found at:
+http://www.ti.com/lit/gpn/tlk106
+http://www.ti.com/lit/an/slla328/slla328.pdf
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index bdfbabb..c980240 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -295,6 +295,11 @@  config DP83867_PHY
 	---help---
 	  Currently supports the DP83867 PHY.
 
+config TLK10X_PHY
+	tristate "Texas Instruments TLK10x PHY"
+	---help---
+	  Supports the TLK105 and TLK106 PHYs.
+
 config FIXED_PHY
 	tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs"
 	depends on PHYLIB
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 01acbcb..37e4e02 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -79,5 +79,6 @@  obj-$(CONFIG_ROCKCHIP_PHY)	+= rockchip.o
 obj-$(CONFIG_SMSC_PHY)		+= smsc.o
 obj-$(CONFIG_STE10XP)		+= ste10Xp.o
 obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
+obj-$(CONFIG_TLK10X_PHY)	+= tlk10x.o
 obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/dp83848.c b/drivers/net/phy/dp83848.c
index cd09c3a..435f401 100644
--- a/drivers/net/phy/dp83848.c
+++ b/drivers/net/phy/dp83848.c
@@ -19,7 +19,6 @@ 
 #define TI_DP83848C_PHY_ID		0x20005ca0
 #define TI_DP83620_PHY_ID		0x20005ce0
 #define NS_DP83848C_PHY_ID		0x20005c90
-#define TLK10X_PHY_ID			0x2000a210
 
 /* Registers */
 #define DP83848_MICR			0x11 /* MII Interrupt Control Register */
@@ -78,7 +77,6 @@  static struct mdio_device_id __maybe_unused dp83848_tbl[] = {
 	{ TI_DP83848C_PHY_ID, 0xfffffff0 },
 	{ NS_DP83848C_PHY_ID, 0xfffffff0 },
 	{ TI_DP83620_PHY_ID, 0xfffffff0 },
-	{ TLK10X_PHY_ID, 0xfffffff0 },
 	{ }
 };
 MODULE_DEVICE_TABLE(mdio, dp83848_tbl);
@@ -105,7 +103,6 @@  static struct phy_driver dp83848_driver[] = {
 	DP83848_PHY_DRIVER(TI_DP83848C_PHY_ID, "TI DP83848C 10/100 Mbps PHY"),
 	DP83848_PHY_DRIVER(NS_DP83848C_PHY_ID, "NS DP83848C 10/100 Mbps PHY"),
 	DP83848_PHY_DRIVER(TI_DP83620_PHY_ID, "TI DP83620 10/100 Mbps PHY"),
-	DP83848_PHY_DRIVER(TLK10X_PHY_ID, "TI TLK10X 10/100 Mbps PHY"),
 };
 module_phy_driver(dp83848_driver);
 
diff --git a/drivers/net/phy/tlk10x.c b/drivers/net/phy/tlk10x.c
new file mode 100644
index 0000000..1efc81e
--- /dev/null
+++ b/drivers/net/phy/tlk10x.c
@@ -0,0 +1,209 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Driver for the Texas Instruments TLK105 / TLK106
+ *
+ * Copyright (C) 2018 NIBE Industrier AB - http://www.nibe.com
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+
+#define TLK10X_PHY_ID			0x2000a210
+
+/* Registers */
+#define TLK10X_MICR			0x11 /* MII Interrupt Control Reg */
+#define TLK10X_MISR			0x12 /* MII Interrupt Status Reg */
+#define TLK10X_REGCR			0x0d /* Register Control Register */
+#define TLK10X_ADDAR			0x0e /* Data Register */
+#define TLK10X_PWRBOCR			0xae /* Power Backoff Register */
+
+/* MICR Register Fields */
+#define TLK10X_MICR_INT_OE		BIT(0) /* Interrupt Output Enable */
+#define TLK10X_MICR_INTEN		BIT(1) /* Interrupt Enable */
+
+/* MISR Register Fields */
+#define TLK10X_MISR_RHF_INT_EN		BIT(0) /* Receive Error Counter */
+#define TLK10X_MISR_FHF_INT_EN		BIT(1) /* False Carrier Counter */
+#define TLK10X_MISR_ANC_INT_EN		BIT(2) /* Auto-negotiation complete */
+#define TLK10X_MISR_DUP_INT_EN		BIT(3) /* Duplex Status */
+#define TLK10X_MISR_SPD_INT_EN		BIT(4) /* Speed status */
+#define TLK10X_MISR_LINK_INT_EN		BIT(5) /* Link status */
+#define TLK10X_MISR_ED_INT_EN		BIT(6) /* Energy detect */
+#define TLK10X_MISR_LQM_INT_EN		BIT(7) /* Link Quality Monitor */
+
+/* PWRBOCR Register Fields */
+#define TLK10X_PWRBOCR_MASK		0xe0 /* Power Backoff Mask */
+
+#define TLK10X_INT_EN_MASK		\
+	(TLK10X_MISR_ANC_INT_EN |	\
+	 TLK10X_MISR_DUP_INT_EN |	\
+	 TLK10X_MISR_SPD_INT_EN |	\
+	 TLK10X_MISR_LINK_INT_EN)
+
+struct tlk10x_private {
+	int pwrbo_level;
+};
+
+static int tlk10x_read(struct phy_device *phydev, int reg)
+{
+	if (reg & ~0x1f) {
+		/* Extended register */
+		phy_write(phydev, TLK10X_REGCR, 0x001F);
+		phy_write(phydev, TLK10X_ADDAR, reg);
+		phy_write(phydev, TLK10X_REGCR, 0x401F);
+		reg = TLK10X_ADDAR;
+	}
+
+	return phy_read(phydev, reg);
+}
+
+static int tlk10x_write(struct phy_device *phydev, int reg, int val)
+{
+	if (reg & ~0x1f) {
+		/* Extended register */
+		phy_write(phydev, TLK10X_REGCR, 0x001F);
+		phy_write(phydev, TLK10X_ADDAR, reg);
+		phy_write(phydev, TLK10X_REGCR, 0x401F);
+		reg = TLK10X_ADDAR;
+	}
+
+	return phy_write(phydev, reg, val);
+}
+
+#ifdef CONFIG_OF_MDIO
+static int tlk10x_of_init(struct phy_device *phydev)
+{
+	struct tlk10x_private *tlk10x = phydev->priv;
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	int ret;
+
+	if (!of_node)
+		return 0;
+
+	ret = of_property_read_u32(of_node, "ti,power-back-off",
+				   &tlk10x->pwrbo_level);
+	if (ret) {
+		dev_err(dev, "missing ti,power-back-off property");
+		tlk10x->pwrbo_level = 0;
+	}
+
+	return 0;
+}
+#else
+static int tlk10x_of_init(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int tlk10x_config_init(struct phy_device *phydev)
+{
+	int ret, reg;
+	struct tlk10x_private *tlk10x;
+
+	ret = genphy_config_init(phydev);
+	if (ret < 0)
+		return ret;
+
+	if (!phydev->priv) {
+		tlk10x = devm_kzalloc(&phydev->mdio.dev, sizeof(*tlk10x),
+				      GFP_KERNEL);
+		if (!tlk10x)
+			return -ENOMEM;
+
+		phydev->priv = tlk10x;
+		ret = tlk10x_of_init(phydev);
+		if (ret)
+			return ret;
+	} else {
+		tlk10x = (struct tlk10x_private *)phydev->priv;
+	}
+
+	// Power back off
+	if (tlk10x->pwrbo_level < 0 || tlk10x->pwrbo_level > 3)
+		tlk10x->pwrbo_level = 0;
+	reg = tlk10x_read(phydev, TLK10X_PWRBOCR);
+	reg = ((reg & ~TLK10X_PWRBOCR_MASK)
+		| (tlk10x->pwrbo_level << 6));
+	ret = tlk10x_write(phydev, TLK10X_PWRBOCR, reg);
+	if (ret < 0) {
+		dev_err(&phydev->mdio.dev,
+			"unable to set power back-off (err=%d)\n", ret);
+		return ret;
+	}
+	dev_info(&phydev->mdio.dev, "power back-off set to level %d\n",
+		 tlk10x->pwrbo_level);
+
+	return 0;
+}
+
+static int tlk10x_ack_interrupt(struct phy_device *phydev)
+{
+	int err = tlk10x_read(phydev, TLK10X_MISR);
+
+	return err < 0 ? err : 0;
+}
+
+static int tlk10x_config_intr(struct phy_device *phydev)
+{
+	int control, ret;
+
+	control = tlk10x_read(phydev, TLK10X_MICR);
+	if (control < 0)
+		return control;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		control |= TLK10X_MICR_INT_OE;
+		control |= TLK10X_MICR_INTEN;
+
+		ret = tlk10x_write(phydev, TLK10X_MISR, TLK10X_INT_EN_MASK);
+		if (ret < 0)
+			return ret;
+	} else {
+		control &= ~TLK10X_MICR_INTEN;
+	}
+
+	return tlk10x_write(phydev, TLK10X_MICR, control);
+}
+
+static struct phy_driver tlk10x_driver[] = {
+	{
+		.phy_id		= TLK10X_PHY_ID,
+		.phy_id_mask	= 0xfffffff0,
+		.name		= "TI TLK10X 10/100 Mbps PHY",
+		.features	= PHY_BASIC_FEATURES,
+		.flags		= PHY_HAS_INTERRUPT,
+
+		.config_init	= tlk10x_config_init,
+		.soft_reset	= genphy_soft_reset,
+
+		/* IRQ related */
+		.ack_interrupt	= tlk10x_ack_interrupt,
+		.config_intr	= tlk10x_config_intr,
+
+		.suspend	= genphy_suspend,
+		.resume		= genphy_resume,
+	},
+};
+module_phy_driver(tlk10x_driver);
+
+static struct mdio_device_id __maybe_unused tlk10x_tbl[] = {
+	{ TLK10X_PHY_ID, 0xfffffff0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, tlk10x_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments TLK105 / TLK106 PHY driver");
+MODULE_AUTHOR("Mans Andersson <mans.andersson@nibe.se>");
+MODULE_LICENSE("GPL");