Commit 969337a4 authored by David S. Miller's avatar David S. Miller

Merge branch 'net-ipq4019-rate'

Christian Marangi says:

====================
net: mdio-ipq4019: fix wrong default MDC rate

This was a long journey to arrive and discover this problem.

To not waste too much char, there is a race problem with PHY and driver
probe. This was observed with Aquantia PHY firmware loading.

With some hacks the race problem was workarounded but an interesting
thing was notice. It took more than a minute for the firmware to load
via MDIO.

This was strange as the same operation was done by UBoot in at max 5
second and the same data was loaded.

A similar problem was observed on a mtk board that also had an
Aquantia PHY where the load was very slow. It was notice that the cause
was the MDIO bus running at a very low speed and the firmware
was missing a property (present in mtk sdk) that set the right frequency
to the MDIO bus.

It was fun to find that THE VERY SAME PROBLEM is present on IPQ in a
different form. The MDIO apply internally a division to the feed clock
resulting in the bus running at 390KHz instead of 6.25Mhz.

Searching around the web for some documentation and some include and
analyzing the uboot codeflow resulted in the divider being set wrongly
at /256 instead of /16 as the value was actually never set.
Applying the value restore the original load time for the Aquantia PHY.

This series mainly handle this by adding support for the "clock-frequency"
property.

Changes v3:
- Add Reviewed-by tag
- Fix english grammar error in comment
- Drop DTS patch
Changes v2:
- Use DIV_ROUND_UP
- Introduce logic to chose a default value for 802.3 spec 2.5MHz
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 747056a9 bdce82e9
......@@ -44,6 +44,21 @@ properties:
items:
- const: gcc_mdio_ahb_clk
clock-frequency:
description:
The MDIO bus clock that must be output by the MDIO bus hardware, if
absent, the default hardware values are used.
MDC rate is feed by an external clock (fixed 100MHz) and is divider
internally. The default divider is /256 resulting in the default rate
applied of 390KHz.
To follow 802.3 standard that instruct up to 2.5MHz by default, if
this property is not declared and the divider is set to /256, by
default 1.5625Mhz is select.
enum: [ 390625, 781250, 1562500, 3125000, 6250000, 12500000 ]
default: 1562500
required:
- compatible
- reg
......
......@@ -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 mii_bus *bus, int mii_id, int regnum,
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 *bus)
return ret;
ret = clk_prepare_enable(priv->mdio_clk);
if (ret == 0)
mdelay(10);
if (ret)
return ret;
return ret;
mdelay(10);
/* 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 platform_device *pdev)
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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment