From bf7e43d9fe0afc1e5b3bce16191dc51598818d47 Mon Sep 17 00:00:00 2001 From: David Wu Date: Fri, 25 Aug 2023 20:44:30 +0800 Subject: [PATCH] net: phy: RK630: Add dynamically adjusting the configuration Adjustments occur at two cases: - No linkup within 5s; - The number of packets received within 10 seconds is less than 3, or the packet loss rate is greater than 15%. Signed-off-by: David Wu Change-Id: I8b48bb4377ddb4231fa6f26b4373695fd85cbb26 --- drivers/net/phy/rk630phy.c | 394 +++++++++++++++++++++++++++++++++++-- 1 file changed, 381 insertions(+), 13 deletions(-) diff --git a/drivers/net/phy/rk630phy.c b/drivers/net/phy/rk630phy.c index de76196712cb..a4abd835c9e1 100644 --- a/drivers/net/phy/rk630phy.c +++ b/drivers/net/phy/rk630phy.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #define RK630_PHY_ID 0x00441400 @@ -71,6 +72,39 @@ #define T22_TX_LEVEL_100M 0x2d #define T22_TX_LEVEL_10M 0x32 +/* Long network cable parameters */ +#define RX_DETECT_SCHEDULE_TIME 500 /* ms */ +#define RX_DETECT_INIT_WAIT_TIME 2000 /* ms */ + +#define RX_DETECT_MAX_COUNT (5000 / RX_DETECT_SCHEDULE_TIME) +#define ALL_RX_DETECT_MAX_COUNT (2 * RX_DETECT_MAX_COUNT) + +#define LINKED_MAX_COUNT (10000 / RX_DETECT_SCHEDULE_TIME) +#define ALL_LINKED_MAX_COUNT (2 * LINKED_MAX_COUNT) + +#define RX_PACKET_RECEIVED_COUNTS 3 /* packets */ +#define RX_PACKET_RECEIVED_LOST 15 /* percent */ + +#define RX_SIGNAL_DETECT_TEMP 85000 + +struct rk630_phy_switched { + /* record state */ + bool config; + bool config_mode_10M; + bool finished; + + /* detected process */ + unsigned int detected_count; + bool config_rx_signal; + int old_link; + + /* linked process */ + unsigned int linked_count; + int rx_pkt_cnt; + int rx_crc_err_cnt; + int lost_percent; +}; + struct rk630_phy_priv { struct phy_device *phydev; bool ieee; @@ -78,6 +112,13 @@ struct rk630_phy_priv { struct wake_lock wol_wake_lock; int tx_level_100M; int tx_level_10M; + + struct rk630_phy_switched switched; + /* mutex protect variables between notify thread and delayed work */ + struct mutex lock; + struct delayed_work service_task; + struct thermal_zone_device *tz; + bool disable_switch; }; static void rk630_phy_t22_get_tx_level_from_efuse(struct phy_device *phydev) @@ -192,6 +233,17 @@ static void rk630_phy_set_uaps(struct phy_device *phydev, bool enable) phy_write(phydev, REG_PAGE_SEL, 0x0000); } +static bool rk630_phy_rx_signal_detected(struct phy_device *phydev) +{ + u32 value; + + /* Switch to page 0 */ + phy_write(phydev, REG_PAGE_SEL, 0x0000); + value = phy_read(phydev, 25); + + return (value & BIT(15)) ? false : true; +} + static void rk630_phy_s40_config_init(struct phy_device *phydev) { phy_write(phydev, 0, phy_read(phydev, 0) & ~BIT(13)); @@ -229,8 +281,8 @@ static void rk630_phy_t22_config_init(struct phy_device *phydev) phy_write(phydev, REG_PAGE_SEL, 0x0100); /* Enable offset clock */ phy_write(phydev, 0x10, 0xfbfe); - /* Disable APS */ - phy_write(phydev, REG_PAGE1_APS_CTRL, 0x4824); + /* Disable APS & Rx detected time 2s, default is 4s */ + phy_write(phydev, REG_PAGE1_APS_CTRL, 0x4822); /* Switch to page 2 */ phy_write(phydev, REG_PAGE_SEL, 0x0200); /* PHYAFE TRX optimization */ @@ -242,7 +294,15 @@ static void rk630_phy_t22_config_init(struct phy_device *phydev) /* PHYAFE Gain optimization */ phy_write(phydev, REG_PAGE6_GAIN_ANONTROL, 0x0400); /* PHYAFE EQ optimization */ - phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, 0x1088); + + if (priv->disable_switch) { + /* Rx detected default threshold 160 mv */ + phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, 0x1088); + } else { + /* Rx detected threshold 260 mv */ + phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, 0x10c8); + priv->switched.config_rx_signal = true; + } if (priv->tx_level_100M <= 0 || priv->tx_level_10M <= 0) rk630_phy_t22_get_tx_level_from_efuse(phydev); @@ -305,23 +365,305 @@ static int rk630_phy_config_init(struct phy_device *phydev) return 0; } -static void rk630_link_change_notify(struct phy_device *phydev) +/* config0(default) and config1(0x555e) switched for 100/10M speed */ +static bool rk630_phy_switch_config(struct phy_device *phydev, bool config) { + struct rk630_phy_priv *priv = phydev->priv; + + if (priv->switched.config != config) { + int val; + + val = config ? 0x555e : 0x5540; + phy_write(priv->phydev, REG_PAGE_SEL, 0x0600); + phy_write(priv->phydev, REG_PAGE6_ADC_ANONTROL, val); + phy_write(priv->phydev, REG_PAGE_SEL, 0x0000); + priv->switched.config = config; + + return true; + } + + return false; +} + +/* 10M speed configuration */ +static void rk630_phy_10m_switch_config(struct phy_device *phydev, bool config) +{ + struct rk630_phy_priv *priv = phydev->priv; unsigned int val; - if (phydev->state == PHY_RUNNING || phydev->state == PHY_NOLINK) { - /* Switch to page 6 */ - phy_write(phydev, REG_PAGE_SEL, 0x0600); + if (config == priv->switched.config_mode_10M) + return; + + phy_write(phydev, REG_PAGE_SEL, 0x0600); + val = phy_read(phydev, REG_PAGE6_AFE_TX_CTRL); + val &= ~GENMASK(14, 13); + if (config && !priv->switched.config_mode_10M) + val |= BIT(13); + + priv->switched.config_mode_10M = config; + phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, val); + phy_write(priv->phydev, REG_PAGE_SEL, 0x0000); +} + +static void rk630_phy_switch_rx_signal_config(struct phy_device *phydev, + bool config) +{ + struct rk630_phy_priv *priv = phydev->priv; + + if (priv->switched.config_rx_signal != config) { + int val; + + phy_write(priv->phydev, REG_PAGE_SEL, 0x0600); val = phy_read(phydev, REG_PAGE6_AFE_TX_CTRL); - val &= ~GENMASK(14, 13); - if (phydev->speed == SPEED_10 && phydev->link) - val |= BIT(13); + val &= ~GENMASK(7, 6); + if (config) + val |= GENMASK(7, 6); + else + val |= BIT(7); phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, val); - /* Switch to page 0 */ - phy_write(phydev, REG_PAGE_SEL, 0x0000); + phy_write(priv->phydev, REG_PAGE_SEL, 0x0000); + + priv->switched.config_rx_signal = config; } } +static void rk630_phy_packet_statistics(struct phy_device *phydev, + int *total_cnt, int *crc_err_cnt) +{ + struct rk630_phy_priv *priv = phydev->priv; + + phy_write(priv->phydev, REG_PAGE_SEL, 0x0900); + *total_cnt = phy_read(priv->phydev, 0x1b) << 16; + *total_cnt |= phy_read(priv->phydev, 0x1c); + *crc_err_cnt = phy_read(priv->phydev, 0x1d) << 16; + *crc_err_cnt |= phy_read(priv->phydev, 0x1e); + phy_write(phydev, REG_PAGE_SEL, 0x0000); +} + +static bool rk630_phy_switch_config_by_packets(struct phy_device *phydev) +{ + struct rk630_phy_priv *priv = phydev->priv; + int rx_pkt_cnt, rx_crc_err_cnt; + int total_cnt, total_crc_err_cnt; + int lost_percent; + + rk630_phy_packet_statistics(phydev, &total_cnt, &total_crc_err_cnt); + + rx_pkt_cnt = total_cnt - priv->switched.rx_pkt_cnt; + rx_crc_err_cnt = total_crc_err_cnt - priv->switched.rx_crc_err_cnt; + + priv->switched.rx_pkt_cnt = total_cnt; + priv->switched.rx_crc_err_cnt = total_crc_err_cnt; + + /* less than the minimal received packets during some time */ + if (rx_pkt_cnt < RX_PACKET_RECEIVED_COUNTS) + return true; + + /* Percents packets lost is not good during some time */ + lost_percent = (rx_crc_err_cnt * 100 / rx_pkt_cnt) > RX_PACKET_RECEIVED_LOST; + + /* Just compare with config0's packet lost, update config if it is better + * than config0. + */ + if (((rx_crc_err_cnt * 100 / rx_pkt_cnt) > RX_PACKET_RECEIVED_LOST) && + lost_percent > priv->switched.lost_percent) { + /* Only save config0 lost percent */ + if (!priv->switched.config) + priv->switched.lost_percent = lost_percent; + return true; + } + + /* Only save config0 lost percent */ + if (!priv->switched.config) + priv->switched.lost_percent = lost_percent; + + return false; +} + +static void rk630_phy_service_task(struct work_struct *work) +{ + struct rk630_phy_priv *priv = container_of(work, struct rk630_phy_priv, + service_task.work); + unsigned int delay_time; + int ret, temp; + + mutex_lock(&priv->lock); + if (priv->disable_switch) { + mutex_unlock(&priv->lock); + return; + } + + if (!priv->phydev->link) { + bool signal_detected; + + signal_detected = rk630_phy_rx_signal_detected(priv->phydev); + + /* Read signal */ + if (!signal_detected) { + /* Slow schedule work for 2 * SCHEDULE_TIME, if no signal */ + priv->switched.detected_count = 0; + priv->switched.lost_percent = 0; + priv->switched.finished = false; + priv->switched.linked_count = 0; + delay_time = 2 * RX_DETECT_SCHEDULE_TIME; + /* Goto default config if no rj45 signal plugin */ + rk630_phy_switch_config(priv->phydev, false); + + /* Also go to 10M default config */ + rk630_phy_10m_switch_config(priv->phydev, false); + } else { + priv->switched.detected_count++; + /* Fast schedule work for 1 * SCHEDULE_TIME, if signal + * detected. + */ + delay_time = RX_DETECT_SCHEDULE_TIME; + if (priv->switched.detected_count == RX_DETECT_MAX_COUNT && + !priv->switched.finished) { + /* After it, there is no link, Might be a long cable, + * config1 switched to get better performance during + * some time. + */ + rk630_phy_switch_config(priv->phydev, true); + } else if (priv->switched.detected_count == ALL_RX_DETECT_MAX_COUNT && + !priv->switched.finished) { + /* After another detect, we lost the last chance, + * go back to default config0. + */ + rk630_phy_switch_config(priv->phydev, false); + priv->switched.finished = true; + } else if (priv->switched.detected_count > ALL_RX_DETECT_MAX_COUNT || + priv->switched.finished) { + /* Slow schedule work for 2 * SCHEDULE_TIME, if + * detected finish. + */ + delay_time = 2 * RX_DETECT_SCHEDULE_TIME; + } + } + } else { + /* Detect the packet count and crc error count statistics */ + priv->switched.linked_count++; + /* Fast schedule work for 1 * SCHEDULE_TIME, if linkup detected */ + delay_time = RX_DETECT_SCHEDULE_TIME; + if (priv->switched.linked_count == LINKED_MAX_COUNT && + !priv->switched.finished) { + if (rk630_phy_switch_config_by_packets(priv->phydev)) { + /* Config1 switched to get better performance */ + rk630_phy_switch_config(priv->phydev, true); + + /* Also go to 10M default config */ + if (priv->switched.config && priv->phydev->speed == SPEED_10) + rk630_phy_10m_switch_config(priv->phydev, true); + } + } else if (priv->switched.linked_count == ALL_LINKED_MAX_COUNT && + !priv->switched.finished) { + /* If config switched, we lost the last chance, return to + * default config0. + */ + if (rk630_phy_switch_config_by_packets(priv->phydev)) { + rk630_phy_switch_config(priv->phydev, false); + rk630_phy_10m_switch_config(priv->phydev, false); + } + priv->switched.finished = true; + } else if (priv->switched.linked_count > ALL_LINKED_MAX_COUNT || + priv->switched.finished) { + /* Slow schedule work for 2 * SCHEDULE_TIME, if linkup + * detected finish. + */ + delay_time = 2 * RX_DETECT_SCHEDULE_TIME; + } + } + + if (priv->tz) { + ret = thermal_zone_get_temp(priv->tz, &temp); + if (ret || temp == THERMAL_TEMP_INVALID) + phydev_err(priv->phydev, + "failed to read out thermal zone (%d)\n", ret); + else + rk630_phy_switch_rx_signal_config(priv->phydev, + (temp > RX_SIGNAL_DETECT_TEMP) ? false : true); + } + + schedule_delayed_work(&priv->service_task, msecs_to_jiffies(delay_time)); + mutex_unlock(&priv->lock); +} + +static void rk630_phy_link_change_notify(struct phy_device *phydev) +{ + struct rk630_phy_priv *priv = phydev->priv; + + if (phydev->mdio.addr == PHY_ADDR_T22) { + mutex_lock(&priv->lock); + if (priv->disable_switch) { + mutex_unlock(&priv->lock); + return; + } + + if (priv->switched.old_link && !phydev->link) { + priv->switched.old_link = 0; + priv->switched.linked_count = 0; + schedule_delayed_work(&priv->service_task, + msecs_to_jiffies(RX_DETECT_SCHEDULE_TIME)); + } else if (!priv->switched.old_link && phydev->link) { + /* If linked, keep current config, but if the linked is + * 10M speed, and config1 has been enabled, also switched + * the 10M config. + */ + if (priv->switched.config && phydev->speed == SPEED_10) + rk630_phy_10m_switch_config(phydev, true); + + priv->switched.old_link = 1; + priv->switched.detected_count = 0; + /* Record base packet statistics to compare later, if linked */ + if (!priv->switched.linked_count) + rk630_phy_packet_statistics(priv->phydev, + &priv->switched.rx_pkt_cnt, + &priv->switched.rx_crc_err_cnt); + schedule_delayed_work(&priv->service_task, + msecs_to_jiffies(RX_DETECT_SCHEDULE_TIME)); + } + mutex_unlock(&priv->lock); + } +} + +static ssize_t rk630_phy_disable_switch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct phy_device *phydev = to_phy_device(dev); + struct rk630_phy_priv *priv = phydev->priv; + int ret; + bool disabled; + + ret = kstrtobool(buf, &disabled); + if (ret) + return count; + + mutex_lock(&priv->lock); + if (disabled) { + cancel_delayed_work_sync(&priv->service_task); + + /* Save to default config */ + rk630_phy_10m_switch_config(priv->phydev, false); + rk630_phy_switch_rx_signal_config(priv->phydev, false); + rk630_phy_switch_config(priv->phydev, false); + + memset(&priv->switched, 0, sizeof(struct rk630_phy_switched)); + } else { + priv->switched.old_link = phydev->link; + /* Rx detected threshold 260 mv */ + rk630_phy_switch_rx_signal_config(priv->phydev, true); + + schedule_delayed_work(&priv->service_task, + msecs_to_jiffies(RX_DETECT_INIT_WAIT_TIME)); + } + priv->disable_switch = disabled; + dev_info(dev, "rk630 phy disable switch to %s\n", disabled ? "true" : "false"); + mutex_unlock(&priv->lock); + + return count; +} +static DEVICE_ATTR_WO(rk630_phy_disable_switch); + static irqreturn_t rk630_wol_irq_thread(int irq, void *dev_id) { struct rk630_phy_priv *priv = (struct rk630_phy_priv *)dev_id; @@ -334,6 +676,7 @@ static irqreturn_t rk630_wol_irq_thread(int irq, void *dev_id) static int rk630_phy_probe(struct phy_device *phydev) { struct rk630_phy_priv *priv; + const char *tz_name; int ret; priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); @@ -362,6 +705,23 @@ static int rk630_phy_probe(struct phy_device *phydev) enable_irq_wake(priv->wol_irq); } + mutex_init(&priv->lock); + INIT_DELAYED_WORK(&priv->service_task, rk630_phy_service_task); + + priv->disable_switch = of_property_read_bool(phydev->mdio.dev.of_node, + "rk630,phy-disable-switch"); + of_property_read_string(phydev->mdio.dev.of_node, "rockchip,thermal-zone", + &tz_name); + priv->tz = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR(priv->tz)) { + pr_warn("Error getting thermal zone, not yet ready?\n"); + priv->tz = NULL; + } + + ret = device_create_file(&phydev->mdio.dev, &dev_attr_rk630_phy_disable_switch); + if (ret) + return ret; + priv->phydev = phydev; return 0; @@ -384,6 +744,10 @@ static int rk630_phy_suspend(struct phy_device *phydev) phy_write(phydev, REG_INTERRUPT_MASK, BIT(14)); enable_irq(priv->wol_irq); } + + if (!priv->disable_switch) + cancel_delayed_work_sync(&priv->service_task); + return genphy_suspend(phydev); } @@ -397,6 +761,10 @@ static int rk630_phy_resume(struct phy_device *phydev) disable_irq(priv->wol_irq); } + if (!priv->disable_switch) + schedule_delayed_work(&priv->service_task, + msecs_to_jiffies(RX_DETECT_INIT_WAIT_TIME)); + return genphy_resume(phydev); } @@ -407,7 +775,7 @@ static struct phy_driver rk630_phy_driver[] = { .name = "RK630 PHY", .features = PHY_BASIC_FEATURES, .flags = 0, - .link_change_notify = rk630_link_change_notify, + .link_change_notify = rk630_phy_link_change_notify, .probe = rk630_phy_probe, .remove = rk630_phy_remove, .soft_reset = genphy_soft_reset,