// SPDX-License-Identifier: GPL-2.0+ /** * * Driver for ROCKCHIP RK630 Ethernet PHYs * * Copyright (c) 2020, Rockchip Electronics Co., Ltd * * David Wu * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define RK630_PHY_ID 0x00441400 /* PAGE 0 */ #define REG_MMD_ACCESS_CONTROL 0x0d #define REG_MMD_ACCESS_DATA_ADDRESS 0x0e #define REG_INTERRUPT_STATUS 0X10 #define REG_INTERRUPT_MASK 0X11 #define REG_GLOBAL_CONFIGURATION 0X13 #define REG_MAC_ADDRESS0 0x16 #define REG_MAC_ADDRESS1 0x17 #define REG_MAC_ADDRESS2 0x18 #define REG_PAGE_SEL 0x1F /* PAGE 1 */ #define REG_PAGE1_APS_CTRL 0x12 #define REG_PAGE1_UAPS_CONFIGURE 0X13 #define REG_PAGE1_EEE_CONFIGURE 0x17 /* PAGE 2 */ #define REG_PAGE2_AFE_CTRL 0x18 /* PAGE 6 */ #define REG_PAGE6_ADC_ANONTROL 0x10 #define REG_PAGE6_GAIN_ANONTROL 0x12 #define REG_PAGE6_AFE_RX_CTRL 0x13 #define REG_PAGE6_AFE_TX_CTRL 0x14 #define REG_PAGE6_AFE_DRIVER2 0x15 #define REG_PAGE6_CP_CURRENT 0x17 #define REG_PAGE6_ADC_OP_BIAS 0x18 #define REG_PAGE6_RX_DECTOR 0x19 #define REG_PAGE6_TX_MOS_DRV 0x1B #define REG_PAGE6_AFE_PDCW 0x1c /* PAGE 8 */ #define REG_PAGE8_AFE_CTRL 0x18 #define REG_PAGE8_AUTO_CAL 0x1d /* * Fixed address: * Addr: 1 --- RK630@S40 * 2 --- RV1106@T22 */ #define PHY_ADDR_S40 1 #define PHY_ADDR_T22 2 #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; int wol_irq; 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) { struct rk630_phy_priv *priv = phydev->priv; unsigned int tx_level_100M = T22_TX_LEVEL_100M; unsigned int tx_level_10M = T22_TX_LEVEL_10M; unsigned char *efuse_buf; struct nvmem_cell *cell; size_t len; cell = nvmem_cell_get(&phydev->mdio.dev, "txlevel"); if (IS_ERR(cell)) { phydev_err(phydev, "failed to get txlevel cell: %ld, use default\n", PTR_ERR(cell)); } else { efuse_buf = nvmem_cell_read(cell, &len); nvmem_cell_put(cell); if (!IS_ERR(efuse_buf)) { if (len == 2 && efuse_buf[0] > 0 && efuse_buf[1] > 0) { tx_level_100M = efuse_buf[1]; tx_level_10M = efuse_buf[0]; } kfree(efuse_buf); } else { phydev_err(phydev, "failed to get efuse buf, use default\n"); } } priv->tx_level_100M = tx_level_100M; priv->tx_level_10M = tx_level_10M; } static void rk630_phy_wol_enable(struct phy_device *phydev) { struct net_device *ndev = phydev->attached_dev; u32 value; /* Switch to page 0 */ phy_write(phydev, REG_PAGE_SEL, 0x0000); phy_write(phydev, REG_MAC_ADDRESS0, ((u16)ndev->dev_addr[0] << 8) + ndev->dev_addr[1]); phy_write(phydev, REG_MAC_ADDRESS1, ((u16)ndev->dev_addr[2] << 8) + ndev->dev_addr[3]); phy_write(phydev, REG_MAC_ADDRESS2, ((u16)ndev->dev_addr[4] << 8) + ndev->dev_addr[5]); value = phy_read(phydev, REG_GLOBAL_CONFIGURATION); value |= BIT(8); value &= ~BIT(7); value |= BIT(10); phy_write(phydev, REG_GLOBAL_CONFIGURATION, value); value = phy_read(phydev, REG_INTERRUPT_MASK); value |= BIT(14); phy_write(phydev, REG_INTERRUPT_MASK, value); } static void rk630_phy_wol_disable(struct phy_device *phydev) { u32 value; /* Switch to page 0 */ phy_write(phydev, REG_PAGE_SEL, 0x0000); value = phy_read(phydev, REG_GLOBAL_CONFIGURATION); value &= ~BIT(10); phy_write(phydev, REG_GLOBAL_CONFIGURATION, value); } static void rk630_phy_ieee_set(struct phy_device *phydev, bool enable) { u32 value; /* Switch to page 1 */ phy_write(phydev, REG_PAGE_SEL, 0x0100); value = phy_read(phydev, REG_PAGE1_EEE_CONFIGURE); if (enable) value |= BIT(3); else value &= ~BIT(3); phy_write(phydev, REG_PAGE1_EEE_CONFIGURE, value); /* Switch to page 0 */ phy_write(phydev, REG_PAGE_SEL, 0x0000); } static void rk630_phy_set_aps(struct phy_device *phydev, bool enable) { u32 value; /* Switch to page 1 */ phy_write(phydev, REG_PAGE_SEL, 0x0100); value = phy_read(phydev, REG_PAGE1_APS_CTRL); if (enable) value |= BIT(15); else value &= ~BIT(15); phy_write(phydev, REG_PAGE1_APS_CTRL, value); /* Switch to page 0 */ phy_write(phydev, REG_PAGE_SEL, 0x0000); } static void rk630_phy_set_uaps(struct phy_device *phydev, bool enable) { u32 value; /* Switch to page 1 */ phy_write(phydev, REG_PAGE_SEL, 0x0100); value = phy_read(phydev, REG_PAGE1_UAPS_CONFIGURE); if (enable) value |= BIT(15); else value &= ~BIT(15); phy_write(phydev, REG_PAGE1_UAPS_CONFIGURE, value); /* Switch to page 0 */ 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)); /* Switch to page 1 */ phy_write(phydev, REG_PAGE_SEL, 0x0100); /* Disable APS */ phy_write(phydev, REG_PAGE1_APS_CTRL, 0x4824); /* Switch to page 2 */ phy_write(phydev, REG_PAGE_SEL, 0x0200); /* PHYAFE TRX optimization */ phy_write(phydev, REG_PAGE2_AFE_CTRL, 0x0000); /* Switch to page 6 */ phy_write(phydev, REG_PAGE_SEL, 0x0600); /* PHYAFE TX optimization */ phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, 0x708f); /* PHYAFE RX optimization */ phy_write(phydev, REG_PAGE6_AFE_RX_CTRL, 0xf000); phy_write(phydev, REG_PAGE6_AFE_DRIVER2, 0x1530); /* Switch to page 8 */ phy_write(phydev, REG_PAGE_SEL, 0x0800); /* PHYAFE TRX optimization */ phy_write(phydev, REG_PAGE8_AFE_CTRL, 0x00bc); /* Switch to page 0 */ phy_write(phydev, REG_PAGE_SEL, 0x0000); } static void rk630_phy_t22_config_init(struct phy_device *phydev) { struct rk630_phy_priv *priv = phydev->priv; /* Switch to page 1 */ phy_write(phydev, REG_PAGE_SEL, 0x0100); /* Enable offset clock */ phy_write(phydev, 0x10, 0xfbfe); /* 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 */ phy_write(phydev, REG_PAGE2_AFE_CTRL, 0x0000); /* Switch to page 6 */ phy_write(phydev, REG_PAGE_SEL, 0x0600); /* PHYAFE ADC optimization */ phy_write(phydev, REG_PAGE6_ADC_ANONTROL, 0x5540); /* PHYAFE Gain optimization */ phy_write(phydev, REG_PAGE6_GAIN_ANONTROL, 0x0400); /* PHYAFE EQ optimization */ 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); /* PHYAFE TX optimization */ phy_write(phydev, REG_PAGE6_AFE_DRIVER2, (priv->tx_level_100M << 8) | priv->tx_level_10M); /* PHYAFE CP current optimization */ phy_write(phydev, REG_PAGE6_CP_CURRENT, 0x0575); /* ADC OP BIAS optimization */ phy_write(phydev, REG_PAGE6_ADC_OP_BIAS, 0x0000); /* Rx signal detctor level optimization */ phy_write(phydev, REG_PAGE6_RX_DECTOR, 0x0408); /* PHYAFE PDCW optimization */ phy_write(phydev, REG_PAGE6_AFE_PDCW, 0x8880); /* Add PHY Tx mos drive, reduce power noise/jitter */ phy_write(phydev, REG_PAGE6_TX_MOS_DRV, 0x888e); /* Switch to page 8 */ phy_write(phydev, REG_PAGE_SEL, 0x0800); /* Disable auto-cal */ phy_write(phydev, REG_PAGE8_AUTO_CAL, 0x0844); /* Reatart offset calibration */ phy_write(phydev, 0x13, 0xc096); /* Switch to page 0 */ phy_write(phydev, REG_PAGE_SEL, 0x0000); /* Disable eee mode advertised */ phy_write(phydev, REG_MMD_ACCESS_CONTROL, 0x0007); phy_write(phydev, REG_MMD_ACCESS_DATA_ADDRESS, 0x003c); phy_write(phydev, REG_MMD_ACCESS_CONTROL, 0x4007); phy_write(phydev, REG_MMD_ACCESS_DATA_ADDRESS, 0x0000); } static int rk630_phy_config_init(struct phy_device *phydev) { switch (phydev->mdio.addr) { case PHY_ADDR_S40: rk630_phy_s40_config_init(phydev); /* * Ultra Auto-Power Saving Mode (UAPS) is designed to * save power when cable is not plugged into PHY. */ rk630_phy_set_uaps(phydev, true); break; case PHY_ADDR_T22: rk630_phy_t22_config_init(phydev); rk630_phy_set_aps(phydev, false); rk630_phy_set_uaps(phydev, false); break; default: phydev_err(phydev, "Unsupported address for current phy: %d\n", phydev->mdio.addr); return -EINVAL; } rk630_phy_ieee_set(phydev, true); return 0; } /* 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 (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(7, 6); if (config) val |= GENMASK(7, 6); else val |= BIT(7); phy_write(phydev, REG_PAGE6_AFE_TX_CTRL, val); 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; phy_write(priv->phydev, REG_INTERRUPT_STATUS, BIT(14)); wake_lock_timeout(&priv->wol_wake_lock, msecs_to_jiffies(8000)); return IRQ_HANDLED; } 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); if (!priv) return -ENOMEM; phydev->priv = priv; priv->wol_irq = of_irq_get_byname(phydev->mdio.dev.of_node, "wol_irq"); if (priv->wol_irq == -EPROBE_DEFER) return priv->wol_irq; if (priv->wol_irq > 0) { wake_lock_init(&priv->wol_wake_lock, WAKE_LOCK_SUSPEND, "wol_wake_lock"); ret = devm_request_threaded_irq(&phydev->mdio.dev, priv->wol_irq, NULL, rk630_wol_irq_thread, IRQF_TRIGGER_FALLING | IRQF_SHARED | IRQF_ONESHOT, "wol_irq", priv); if (ret) { wake_lock_destroy(&priv->wol_wake_lock); phydev_err(phydev, "request wol_irq failed: %d\n", ret); return ret; } disable_irq(priv->wol_irq); 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; } static void rk630_phy_remove(struct phy_device *phydev) { struct rk630_phy_priv *priv = phydev->priv; if (priv->wol_irq > 0) wake_lock_destroy(&priv->wol_wake_lock); } static int rk630_phy_suspend(struct phy_device *phydev) { struct rk630_phy_priv *priv = phydev->priv; if (priv->wol_irq > 0) { rk630_phy_wol_enable(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); } static int rk630_phy_resume(struct phy_device *phydev) { struct rk630_phy_priv *priv = phydev->priv; if (priv->wol_irq > 0) { rk630_phy_wol_disable(phydev); phy_write(phydev, REG_INTERRUPT_MASK, 0); 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); } static struct phy_driver rk630_phy_driver[] = { { .phy_id = RK630_PHY_ID, .phy_id_mask = 0xffffffff, .name = "RK630 PHY", .features = PHY_BASIC_FEATURES, .flags = 0, .link_change_notify = rk630_phy_link_change_notify, .probe = rk630_phy_probe, .remove = rk630_phy_remove, .soft_reset = genphy_soft_reset, .config_init = rk630_phy_config_init, .config_aneg = genphy_config_aneg, .read_status = genphy_read_status, .suspend = rk630_phy_suspend, .resume = rk630_phy_resume, }, }; static struct mdio_device_id __maybe_unused rk630_phy_tbl[] = { { RK630_PHY_ID, 0xffffffff }, { } }; MODULE_DEVICE_TABLE(mdio, rk630_phy_tbl); module_phy_driver(rk630_phy_driver); MODULE_AUTHOR("David Wu "); MODULE_DESCRIPTION("Rockchip RK630 Ethernet PHY driver"); MODULE_LICENSE("GPL v2");