123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- From bdce82e960d1205d118662f575cec39379984e34 Mon Sep 17 00:00:00 2001
- From: Christian Marangi <[email protected]>
- Date: Wed, 31 Jan 2024 03:26:04 +0100
- Subject: [PATCH] net: mdio: ipq4019: add support for clock-frequency property
- The IPQ4019 MDIO internally divide the clock feed by AHB based on the
- MDIO_MODE reg. On reset or power up, the default value for the
- divider is 0xff that reflect the divider set to /256.
- This makes the MDC run at a very low rate, that is, considering AHB is
- always fixed to 100Mhz, a value of 390KHz.
- This hasn't have been a problem as MDIO wasn't used for time sensitive
- operation, it is now that on IPQ807x is usually mounted with PHY that
- requires MDIO to load their firmware (example Aquantia PHY).
- To handle this problem and permit to set the correct designed MDC
- frequency for the SoC add support for the standard "clock-frequency"
- property for the MDIO node.
- The divider supports value from /1 to /256 and the common value are to
- set it to /16 to reflect 6.25Mhz or to /8 on newer platform to reflect
- 12.5Mhz.
- To scan if the requested rate is supported by the divider, loop with
- each supported divider and stop when the requested rate match the final
- rate with the current divider. An error is returned if the rate doesn't
- match any value.
- On MDIO reset, the divider is restored to the requested value to prevent
- any kind of downclocking caused by the divider reverting to a default
- value.
- To follow 802.3 spec of 2.5MHz of default value, if divider is set at
- /256 and "clock-frequency" is not set in DT, assume nobody set the
- divider and try to find the closest MDC rate to 2.5MHz. (in the case of
- AHB set to 100MHz, it's 1.5625MHz)
- While at is also document other bits of the MDIO_MODE reg to have a
- clear idea of what is actually applied there.
- Documentation of some BITs is skipped as they are marked as reserved and
- their usage is not clear (RES 11:9 GENPHY 16:13 RES1 19:17)
- Signed-off-by: Christian Marangi <[email protected]>
- Reviewed-by: Andrew Lunn <[email protected]>
- Signed-off-by: David S. Miller <[email protected]>
- ---
- drivers/net/mdio/mdio-ipq4019.c | 109 ++++++++++++++++++++++++++++++--
- 1 file changed, 103 insertions(+), 6 deletions(-)
- --- a/drivers/net/mdio/mdio-ipq4019.c
- +++ b/drivers/net/mdio/mdio-ipq4019.c
- @@ -14,6 +14,20 @@
- #include <linux/clk.h>
-
- #define MDIO_MODE_REG 0x40
- +#define MDIO_MODE_MDC_MODE BIT(12)
- +/* 0 = Clause 22, 1 = Clause 45 */
- +#define MDIO_MODE_C45 BIT(8)
- +#define MDIO_MODE_DIV_MASK GENMASK(7, 0)
- +#define MDIO_MODE_DIV(x) FIELD_PREP(MDIO_MODE_DIV_MASK, (x) - 1)
- +#define MDIO_MODE_DIV_1 0x0
- +#define MDIO_MODE_DIV_2 0x1
- +#define MDIO_MODE_DIV_4 0x3
- +#define MDIO_MODE_DIV_8 0x7
- +#define MDIO_MODE_DIV_16 0xf
- +#define MDIO_MODE_DIV_32 0x1f
- +#define MDIO_MODE_DIV_64 0x3f
- +#define MDIO_MODE_DIV_128 0x7f
- +#define MDIO_MODE_DIV_256 0xff
- #define MDIO_ADDR_REG 0x44
- #define MDIO_DATA_WRITE_REG 0x48
- #define MDIO_DATA_READ_REG 0x4c
- @@ -26,9 +40,6 @@
- #define MDIO_CMD_ACCESS_CODE_C45_WRITE 1
- #define MDIO_CMD_ACCESS_CODE_C45_READ 2
-
- -/* 0 = Clause 22, 1 = Clause 45 */
- -#define MDIO_MODE_C45 BIT(8)
- -
- #define IPQ4019_MDIO_TIMEOUT 10000
- #define IPQ4019_MDIO_SLEEP 10
-
- @@ -41,6 +52,7 @@ struct ipq4019_mdio_data {
- void __iomem *membase;
- void __iomem *eth_ldo_rdy;
- struct clk *mdio_clk;
- + unsigned int mdc_rate;
- };
-
- static int ipq4019_mdio_wait_busy(struct mii_bus *bus)
- @@ -203,6 +215,38 @@ static int ipq4019_mdio_write_c22(struct
- return 0;
- }
-
- +static int ipq4019_mdio_set_div(struct ipq4019_mdio_data *priv)
- +{
- + unsigned long ahb_rate;
- + int div;
- + u32 val;
- +
- + /* If we don't have a clock for AHB use the fixed value */
- + ahb_rate = IPQ_MDIO_CLK_RATE;
- + if (priv->mdio_clk)
- + ahb_rate = clk_get_rate(priv->mdio_clk);
- +
- + /* MDC rate is ahb_rate/(MDIO_MODE_DIV + 1)
- + * While supported, internal documentation doesn't
- + * assure correct functionality of the MDIO bus
- + * with divider of 1, 2 or 4.
- + */
- + for (div = 8; div <= 256; div *= 2) {
- + /* The requested rate is supported by the div */
- + if (priv->mdc_rate == DIV_ROUND_UP(ahb_rate, div)) {
- + val = readl(priv->membase + MDIO_MODE_REG);
- + val &= ~MDIO_MODE_DIV_MASK;
- + val |= MDIO_MODE_DIV(div);
- + writel(val, priv->membase + MDIO_MODE_REG);
- +
- + return 0;
- + }
- + }
- +
- + /* The requested rate is not supported */
- + return -EINVAL;
- +}
- +
- static int ipq_mdio_reset(struct mii_bus *bus)
- {
- struct ipq4019_mdio_data *priv = bus->priv;
- @@ -225,10 +269,58 @@ static int ipq_mdio_reset(struct mii_bus
- return ret;
-
- ret = clk_prepare_enable(priv->mdio_clk);
- - if (ret == 0)
- - mdelay(10);
- + if (ret)
- + return ret;
- +
- + mdelay(10);
-
- - return ret;
- + /* Restore MDC rate */
- + return ipq4019_mdio_set_div(priv);
- +}
- +
- +static void ipq4019_mdio_select_mdc_rate(struct platform_device *pdev,
- + struct ipq4019_mdio_data *priv)
- +{
- + unsigned long ahb_rate;
- + int div;
- + u32 val;
- +
- + /* MDC rate defined in DT, we don't have to decide a default value */
- + if (!of_property_read_u32(pdev->dev.of_node, "clock-frequency",
- + &priv->mdc_rate))
- + return;
- +
- + /* If we don't have a clock for AHB use the fixed value */
- + ahb_rate = IPQ_MDIO_CLK_RATE;
- + if (priv->mdio_clk)
- + ahb_rate = clk_get_rate(priv->mdio_clk);
- +
- + /* Check what is the current div set */
- + val = readl(priv->membase + MDIO_MODE_REG);
- + div = FIELD_GET(MDIO_MODE_DIV_MASK, val);
- +
- + /* div is not set to the default value of /256
- + * Probably someone changed that (bootloader, other drivers)
- + * Keep this and don't overwrite it.
- + */
- + if (div != MDIO_MODE_DIV_256) {
- + priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div + 1);
- + return;
- + }
- +
- + /* If div is /256 assume nobody have set this value and
- + * try to find one MDC rate that is close the 802.3 spec of
- + * 2.5MHz
- + */
- + for (div = 256; div >= 8; div /= 2) {
- + /* Stop as soon as we found a divider that
- + * reached the closest value to 2.5MHz
- + */
- + if (DIV_ROUND_UP(ahb_rate, div) > 2500000)
- + break;
- +
- + priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div);
- + }
- }
-
- static int ipq4019_mdio_probe(struct platform_device *pdev)
- @@ -252,6 +344,11 @@ static int ipq4019_mdio_probe(struct pla
- if (IS_ERR(priv->mdio_clk))
- return PTR_ERR(priv->mdio_clk);
-
- + ipq4019_mdio_select_mdc_rate(pdev, priv);
- + ret = ipq4019_mdio_set_div(priv);
- + if (ret)
- + return ret;
- +
- /* The platform resource is provided on the chipset IPQ5018 */
- /* This resource is optional */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|