/* * drivers/net/phy/motorcomm.c * * Driver for Motorcomm PHYs * * Author: Leilei Zhao * * Copyright (c) 2019 Motorcomm, Inc. * * 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, or (at your * option) any later version. * * Support : Motorcomm Phys: * Giga phys: yt8511, yt8521 * 100/10 Phys : yt8512, yt8512b, yt8510 * Automotive 100Mb Phys : yt8010 * Automotive 100/10 hyper range Phys: yt8510 */ #include #include #include #include #include #include #include #ifndef LINUX_VERSION_CODE #include #else #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) #endif /*for wol, 20210604*/ #include #include "yt8614-phy.h" /**** configuration section begin ***********/ /* if system depends on ethernet packet to restore from sleep, please define this macro to 1 * otherwise, define it to 0. */ #define SYS_WAKEUP_BASED_ON_ETH_PKT 1 /* to enable system WOL of phy, please define this macro to 1 * otherwise, define it to 0. */ #define YTPHY_ENABLE_WOL 0 /* some GMAC need clock input from PHY, for eg., 125M, please enable this macro * by degault, it is set to 0 * NOTE: this macro will need macro SYS_WAKEUP_BASED_ON_ETH_PKT to set to 1 */ #define GMAC_CLOCK_INPUT_NEEDED 1 #define YT8521_PHY_MODE_FIBER 1 //fiber mode only #define YT8521_PHY_MODE_UTP 2 //utp mode only #define YT8521_PHY_MODE_POLL 3 //fiber and utp, poll mode /* please make choice according to system design * for Fiber only system, please define YT8521_PHY_MODE_CURR 1 * for UTP only system, please define YT8521_PHY_MODE_CURR 2 * for combo system, please define YT8521_PHY_MODE_CURR 3 */ #define YT8521_PHY_MODE_CURR 3 /**** configuration section end ***********/ /* no need to change below */ #if (YTPHY_ENABLE_WOL) #undef SYS_WAKEUP_BASED_ON_ETH_PKT #define SYS_WAKEUP_BASED_ON_ETH_PKT 1 #endif /* workaround for 8521 fiber 100m mode */ static int link_mode_8521 = 0; //0: no link; 1: utp; 32: fiber. traced that 1000m fiber uses 32. static int link_mode_8614[4] = {0}; //0: no link; 1: utp; 32: fiber. traced that 1000m fiber uses 32. /* for multiple port phy, base phy address */ static unsigned int yt_mport_base_phy_addr = 0xff; //0xff: invalid; for 8618 static unsigned int yt_mport_base_phy_addr_8614 = 0xff; //0xff: invalid; int phy_yt8531_led_fixup(struct mii_bus *bus, int addr); int yt8511_config_out_125m(struct mii_bus *bus, int phy_id); #if ( LINUX_VERSION_CODE > KERNEL_VERSION(5,0,0) ) int genphy_config_init(struct phy_device *phydev) { int ret; //printk (KERN_INFO "yzhang..read phyaddr=%d, phyid=%08x\n",phydev->mdio.addr, phydev->phy_id); if(phydev->phy_id == 0x4f51e91b) { //printk (KERN_INFO "yzhang..get YT8511, abt to set 125m clk out, phyaddr=%d, phyid=%08x\n",phydev->mdio.addr, phydev->phy_id); ret = yt8511_config_out_125m(phydev->mdio.bus, phydev->mdio.addr); //printk (KERN_INFO "yzhang..8511 set 125m clk out, reg=%#04x\n",phydev->mdio.bus->read(phydev->mdio.bus,phydev->mdio.addr,0x1f)/*double check as delay*/); if (ret<0) printk (KERN_INFO "yzhang..failed to set 125m clk out, ret=%d\n",ret); phy_yt8531_led_fixup(phydev->mdio.bus, phydev->mdio.addr); } return genphy_read_abilities(phydev); } #endif #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) static int ytphy_config_init(struct phy_device *phydev) { return 0; } #endif static int ytphy_read_ext(struct phy_device *phydev, u32 regnum) { int ret; int val; ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum); if (ret < 0) return ret; val = phy_read(phydev, REG_DEBUG_DATA); return val; } static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val) { int ret; ret = phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum); if (ret < 0) return ret; ret = phy_write(phydev, REG_DEBUG_DATA, val); return ret; } static int yt8010_config_aneg(struct phy_device *phydev) { phydev->speed = SPEED_100; return 0; } static int yt8512_clk_init(struct phy_device *phydev) { int ret; int val; val = ytphy_read_ext(phydev, YT8512_EXTREG_AFE_PLL); if (val < 0) return val; val |= YT8512_CONFIG_PLL_REFCLK_SEL_EN; ret = ytphy_write_ext(phydev, YT8512_EXTREG_AFE_PLL, val); if (ret < 0) return ret; val = ytphy_read_ext(phydev, YT8512_EXTREG_EXTEND_COMBO); if (val < 0) return val; val |= YT8512_CONTROL1_RMII_EN; ret = ytphy_write_ext(phydev, YT8512_EXTREG_EXTEND_COMBO, val); if (ret < 0) return ret; val = phy_read(phydev, MII_BMCR); if (val < 0) return val; val |= YT_SOFTWARE_RESET; ret = phy_write(phydev, MII_BMCR, val); return ret; } static int yt8512_led_init(struct phy_device *phydev) { int ret; int val; int mask; val = ytphy_read_ext(phydev, YT8512_EXTREG_LED0); if (val < 0) return val; val |= YT8512_LED0_ACT_BLK_IND; mask = YT8512_LED0_DIS_LED_AN_TRY | YT8512_LED0_BT_BLK_EN | YT8512_LED0_HT_BLK_EN | YT8512_LED0_COL_BLK_EN | YT8512_LED0_BT_ON_EN; val &= ~mask; ret = ytphy_write_ext(phydev, YT8512_EXTREG_LED0, val); if (ret < 0) return ret; val = ytphy_read_ext(phydev, YT8512_EXTREG_LED1); if (val < 0) return val; val |= YT8512_LED1_BT_ON_EN; mask = YT8512_LED1_TXACT_BLK_EN | YT8512_LED1_RXACT_BLK_EN; val &= ~mask; ret = ytphy_write_ext(phydev, YT8512_LED1_BT_ON_EN, val); return ret; } static int yt8512_config_init(struct phy_device *phydev) { int ret; int val; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) ret = ytphy_config_init(phydev); #else ret = genphy_config_init(phydev); #endif if (ret < 0) return ret; ret = yt8512_clk_init(phydev); if (ret < 0) return ret; ret = yt8512_led_init(phydev); /* disable auto sleep */ val = ytphy_read_ext(phydev, YT8512_EXTREG_SLEEP_CONTROL1); if (val < 0) return val; val &= (~BIT(YT8512_EN_SLEEP_SW_BIT)); ret = ytphy_write_ext(phydev, YT8512_EXTREG_SLEEP_CONTROL1, val); if (ret < 0) return ret; return ret; } static int yt8512_read_status(struct phy_device *phydev) { int ret; int val; int speed, speed_mode, duplex; ret = genphy_update_link(phydev); if (ret) return ret; val = phy_read(phydev, REG_PHY_SPEC_STATUS); if (val < 0) return val; duplex = (val & YT8512_DUPLEX) >> YT8512_DUPLEX_BIT; speed_mode = (val & YT8512_SPEED_MODE) >> YT8512_SPEED_MODE_BIT; switch (speed_mode) { case 0: speed = SPEED_10; break; case 1: speed = SPEED_100; break; case 2: case 3: default: #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) speed = -1; #else speed = SPEED_UNKNOWN; #endif break; } phydev->speed = speed; phydev->duplex = duplex; return 0; } #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #else #if 0 int yt8521_soft_reset(struct phy_device *phydev) { int ret; ytphy_write_ext(phydev, 0xa000, 0); ret = genphy_soft_reset(phydev); if (ret < 0) return ret; ytphy_write_ext(phydev, 0xa000, 2); ret = genphy_soft_reset(phydev); if (ret < 0) { ytphy_write_ext(phydev, 0xa000, 0); return ret; } return 0; } #else /* qingsong feedback 2 genphy_soft_reset will cause problem. * and this is the reduction version */ int yt8521_soft_reset(struct phy_device *phydev) { int ret, val; val = ytphy_read_ext(phydev, 0xa001); ytphy_write_ext(phydev, 0xa001, (val & ~0x8000)); ret = genphy_soft_reset(phydev); if (ret < 0) return ret; return 0; } #endif #endif #if GMAC_CLOCK_INPUT_NEEDED static int ytphy_mii_rd_ext(struct mii_bus *bus, int phy_id, u32 regnum) { int ret; int val; ret = bus->write(bus, phy_id, REG_DEBUG_ADDR_OFFSET, regnum); if (ret < 0) return ret; val = bus->read(bus, phy_id, REG_DEBUG_DATA); return val; } static int ytphy_mii_wr_ext(struct mii_bus *bus, int phy_id, u32 regnum, u16 val) { int ret; ret = bus->write(bus, phy_id, REG_DEBUG_ADDR_OFFSET, regnum); if (ret < 0) return ret; ret = bus->write(bus, phy_id, REG_DEBUG_DATA, val); return ret; } int yt8511_config_dis_txdelay(struct mii_bus *bus, int phy_id) { int ret; int val; /* disable auto sleep */ val = ytphy_mii_rd_ext(bus, phy_id, 0x27); if (val < 0) return val; val &= (~BIT(15)); ret = ytphy_mii_wr_ext(bus, phy_id, 0x27, val); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ val = ytphy_mii_rd_ext(bus, phy_id, 0xc); if (val < 0) return val; /* ext reg 0xc b[7:4] Tx Delay time = 150ps * N - 250ps */ val &= ~(0xf << 4); ret = ytphy_mii_wr_ext(bus, phy_id, 0xc, val); printk("yt8511_config_dis_txdelay..phy txdelay, val=%#08x\n",val); return ret; } int phy_yt8531_led_fixup(struct mii_bus *bus, int addr) { //printk("%s in\n", __func__); ytphy_mii_wr_ext(bus, addr, 0xa00d, 0x670); ytphy_mii_wr_ext(bus, addr, 0xa00e, 0x2070); ytphy_mii_wr_ext(bus, addr, 0xa00f, 0x7e); return 0; } int yt8511_config_out_125m(struct mii_bus *bus, int addr) { int ret; int val; mdelay(100); ret = ytphy_mii_wr_ext(bus, addr, 0xa012, 0xd0); mdelay(100); val = ytphy_mii_rd_ext(bus, addr, 0xa012); if(val != 0xd0) { printk("yt8511_config_out_125m error\n"); return -1; } /* disable auto sleep */ val = ytphy_mii_rd_ext(bus, addr, 0x27); if (val < 0) return val; val &= (~BIT(15)); ret = ytphy_mii_wr_ext(bus, addr, 0x27, val); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ val = ytphy_mii_rd_ext(bus, addr, 0xc); if (val < 0) return val; /* ext reg 0xc.b[2:1] 00-----25M from pll; 01---- 25M from xtl;(default) 10-----62.5M from pll; 11----125M from pll(here set to this value) */ val |= (3 << 1); ret = ytphy_mii_wr_ext(bus, addr, 0xc, val); //printk("yt8511_config_out_125m, phy clk out, val=%#08x\n",val); #if 0 /* for customer, please enable it based on demand. * configure to master */ val = bus->read(bus, phy_id, 0x9/*master/slave config reg*/); val |= (0x3<<11); //to be manual config and force to be master ret = bus->write(bus, phy_id, 0x9, val); //take effect until phy soft reset if (ret < 0) return ret; printk("yt8511_config_out_125m, phy to be master, val=%#08x\n",val); #endif return ret; } EXPORT_SYMBOL(yt8511_config_out_125m); static int yt8511_config_init(struct phy_device *phydev) { int ret; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) ret = ytphy_config_init(phydev); #else ret = genphy_config_init(phydev); #endif return ret; } #endif /*GMAC_CLOCK_INPUT_NEEDED*/ #if (YTPHY_ENABLE_WOL) static int ytphy_switch_reg_space(struct phy_device *phydev, int space) { int ret; if (space == YTPHY_REG_SPACE_UTP){ ret = ytphy_write_ext(phydev, 0xa000, 0); }else{ ret = ytphy_write_ext(phydev, 0xa000, 2); } return ret; } static int ytphy_wol_en_cfg(struct phy_device *phydev, ytphy_wol_cfg_t wol_cfg) { int ret=0; int val=0; val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG); if (val < 0) return val; if(wol_cfg.enable) { val |= YTPHY_WOL_CFG_EN; if(wol_cfg.type == YTPHY_WOL_TYPE_LEVEL) { val &= ~YTPHY_WOL_CFG_TYPE; val &= ~YTPHY_WOL_CFG_INTR_SEL; } else if(wol_cfg.type == YTPHY_WOL_TYPE_PULSE) { val |= YTPHY_WOL_CFG_TYPE; val |= YTPHY_WOL_CFG_INTR_SEL; if(wol_cfg.width == YTPHY_WOL_WIDTH_84MS) { val &= ~YTPHY_WOL_CFG_WIDTH1; val &= ~YTPHY_WOL_CFG_WIDTH2; } else if(wol_cfg.width == YTPHY_WOL_WIDTH_168MS) { val |= YTPHY_WOL_CFG_WIDTH1; val &= ~YTPHY_WOL_CFG_WIDTH2; } else if(wol_cfg.width == YTPHY_WOL_WIDTH_336MS) { val &= ~YTPHY_WOL_CFG_WIDTH1; val |= YTPHY_WOL_CFG_WIDTH2; } else if(wol_cfg.width == YTPHY_WOL_WIDTH_672MS) { val |= YTPHY_WOL_CFG_WIDTH1; val |= YTPHY_WOL_CFG_WIDTH2; } } } else { val &= ~YTPHY_WOL_CFG_EN; val &= ~YTPHY_WOL_CFG_INTR_SEL; } ret = ytphy_write_ext(phydev, YTPHY_WOL_CFG_REG, val); if (ret < 0) return ret; return 0; } static void ytphy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { int val = 0; wol->supported = WAKE_MAGIC; wol->wolopts = 0; val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG); if (val < 0) return; if (val & YTPHY_WOL_CFG_EN) wol->wolopts |= WAKE_MAGIC; return; } static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { int ret, pre_page, val; ytphy_wol_cfg_t wol_cfg; struct net_device *p_attached_dev = phydev->attached_dev; memset(&wol_cfg,0,sizeof(ytphy_wol_cfg_t)); pre_page = ytphy_read_ext(phydev, 0xa000); if (pre_page < 0) return pre_page; /* Switch to phy UTP page */ ret = ytphy_switch_reg_space(phydev, YTPHY_REG_SPACE_UTP); if (ret < 0) return ret; if (wol->wolopts & WAKE_MAGIC) { /* Enable the WOL interrupt */ val = phy_read(phydev, YTPHY_UTP_INTR_REG); val |= YTPHY_WOL_INTR; ret = phy_write(phydev, YTPHY_UTP_INTR_REG, val); if (ret < 0) return ret; /* Set the WOL config */ wol_cfg.enable = 1; //enable wol_cfg.type= YTPHY_WOL_TYPE_PULSE; wol_cfg.width= YTPHY_WOL_WIDTH_672MS; ret = ytphy_wol_en_cfg(phydev, wol_cfg); if (ret < 0) return ret; /* Store the device address for the magic packet */ ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR2, ((p_attached_dev->dev_addr[0] << 8) | p_attached_dev->dev_addr[1])); if (ret < 0) return ret; ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR1, ((p_attached_dev->dev_addr[2] << 8) | p_attached_dev->dev_addr[3])); if (ret < 0) return ret; ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR0, ((p_attached_dev->dev_addr[4] << 8) | p_attached_dev->dev_addr[5])); if (ret < 0) return ret; } else { wol_cfg.enable = 0; //disable wol_cfg.type= YTPHY_WOL_TYPE_MAX; wol_cfg.width= YTPHY_WOL_WIDTH_MAX; ret = ytphy_wol_en_cfg(phydev, wol_cfg); if (ret < 0) return ret; } /* Recover to previous register space page */ ret = ytphy_switch_reg_space(phydev, pre_page); if (ret < 0) return ret; return 0; } #endif /*(YTPHY_ENABLE_WOL)*/ static int yt8521_config_init(struct phy_device *phydev) { int ret; int val; phydev->irq = PHY_POLL; ytphy_write_ext(phydev, 0xa000, 0); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) ret = ytphy_config_init(phydev); #else ret = genphy_config_init(phydev); #endif if (ret < 0) return ret; /* disable auto sleep */ val = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1); if (val < 0) return val; val &= (~BIT(YT8521_EN_SLEEP_SW_BIT)); ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, val); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ ret = ytphy_write_ext(phydev, 0xa000, 0); if (ret < 0) return ret; val = ytphy_read_ext(phydev, 0xc); if (val < 0) return val; val &= ~(1 << 12); ret = ytphy_write_ext(phydev, 0xc, val); if (ret < 0) return ret; printk (KERN_INFO "yt8521_config_init, 8521 init call out.\n"); return ret; } /* * for fiber mode, there is no 10M speed mode and * this function is for this purpose. */ static int yt8521_adjust_status(struct phy_device *phydev, int val, int is_utp) { int speed_mode, duplex; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) int speed = -1; #else int speed = SPEED_UNKNOWN; #endif duplex = (val & YT8512_DUPLEX) >> YT8521_DUPLEX_BIT; speed_mode = (val & YT8521_SPEED_MODE) >> YT8521_SPEED_MODE_BIT; switch (speed_mode) { case 0: if (is_utp) speed = SPEED_10; break; case 1: speed = SPEED_100; break; case 2: speed = SPEED_1000; break; case 3: break; default: #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) speed = -1; #else speed = SPEED_UNKNOWN; #endif break; } phydev->speed = speed; phydev->duplex = duplex; //printk (KERN_INFO "yt8521_adjust_status call out,regval=0x%04x,mode=%s,speed=%dm...\n", val,is_utp?"utp":"fiber", phydev->speed); return 0; } /* * for fiber mode, when speed is 100M, there is no definition for autonegotiation, and * this function handles this case and return 1 per linux kernel's polling. */ int yt8521_aneg_done (struct phy_device *phydev) { //printk("yt8521_aneg_done callin,speed=%dm,linkmoded=%d\n", phydev->speed,link_mode_8521); if((32 == link_mode_8521) && (SPEED_100 == phydev->speed)) { return 1/*link_mode_8521*/; } #if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) ) return genphy_aneg_done(phydev); #else return 1; #endif } static int yt8521_read_status(struct phy_device *phydev) { int ret; volatile int val, yt8521_fiber_latch_val, yt8521_fiber_curr_val; volatile int link; int link_utp = 0, link_fiber = 0; #if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) /* reading UTP */ ret = ytphy_write_ext(phydev, 0xa000, 0); if (ret < 0) return ret; val = phy_read(phydev, REG_PHY_SPEC_STATUS); if (val < 0) return val; link = val & (BIT(YT8521_LINK_STATUS_BIT)); if (link) { link_utp = 1; link_mode_8521 = 1; yt8521_adjust_status(phydev, val, 1); } else { link_utp = 0; } #endif //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) #if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) /* reading Fiber */ ret = ytphy_write_ext(phydev, 0xa000, 2); if (ret < 0) return ret; val = phy_read(phydev, REG_PHY_SPEC_STATUS); if (val < 0) return val; //note: below debug information is used to check multiple PHy ports. //printk (KERN_INFO "yt8521_read_status, fiber status=%04x,macbase=0x%08lx\n", val,(unsigned long)phydev->attached_dev); /* for fiber, from 1000m to 100m, there is not link down from 0x11, and check reg 1 to identify such case * this is important for Linux kernel for that, missing linkdown event will cause problem. */ yt8521_fiber_latch_val = phy_read(phydev, MII_BMSR); yt8521_fiber_curr_val = phy_read(phydev, MII_BMSR); link = val & (BIT(YT8521_LINK_STATUS_BIT)); if((link) && (yt8521_fiber_latch_val != yt8521_fiber_curr_val)) { link = 0; printk (KERN_INFO "yt8521_read_status, fiber link down detect,latch=%04x,curr=%04x\n", yt8521_fiber_latch_val,yt8521_fiber_curr_val); } if (link) { link_fiber = 1; yt8521_adjust_status(phydev, val, 0); link_mode_8521 = 32; //fiber mode } else { link_fiber = 0; } #endif //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) if (link_utp || link_fiber) { phydev->link = 1; } else { phydev->link = 0; link_mode_8521 = 0; } #if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) if (link_utp) { ytphy_write_ext(phydev, 0xa000, 0); } #endif //printk (KERN_INFO "yzhang..8521 read status call out,link=%d,linkmode=%d\n", phydev->link, link_mode_8521 ); return 0; } int yt8521_suspend(struct phy_device *phydev) { #if !(SYS_WAKEUP_BASED_ON_ETH_PKT) int value; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_lock(&phydev->lock); #else /* no need lock in 4.19 */ #endif ytphy_write_ext(phydev, 0xa000, 0); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); ytphy_write_ext(phydev, 0xa000, 2); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); ytphy_write_ext(phydev, 0xa000, 0); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_unlock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ return 0; } int yt8521_resume(struct phy_device *phydev) { #if !(SYS_WAKEUP_BASED_ON_ETH_PKT) int value; int ret; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_lock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif ytphy_write_ext(phydev, 0xa000, 0); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); /* disable auto sleep */ value = ytphy_read_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1); if (value < 0) return value; value &= (~BIT(YT8521_EN_SLEEP_SW_BIT)); ret = ytphy_write_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1, value); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ value = ytphy_read_ext(phydev, 0xc); if (value < 0) return value; value &= ~(1 << 12); ret = ytphy_write_ext(phydev, 0xc, value); if (ret < 0) return ret; ytphy_write_ext(phydev, 0xa000, 2); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); #if (YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) ytphy_write_ext(phydev, 0xa000, 0); #endif #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_unlock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ return 0; } #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #else int yt8618_soft_reset(struct phy_device *phydev) { int ret; ytphy_write_ext(phydev, 0xa000, 0); ret = genphy_soft_reset(phydev); if (ret < 0) return ret; return 0; } int yt8614_soft_reset(struct phy_device *phydev) { int ret; /* utp */ ytphy_write_ext(phydev, 0xa000, 0); ret = genphy_soft_reset(phydev); if (ret < 0) return ret; /* qsgmii */ ytphy_write_ext(phydev, 0xa000, 2); ret = genphy_soft_reset(phydev); if (ret < 0) { ytphy_write_ext(phydev, 0xa000, 0); //back to utp mode return ret; } /* sgmii */ ytphy_write_ext(phydev, 0xa000, 3); ret = genphy_soft_reset(phydev); if (ret < 0) { ytphy_write_ext(phydev, 0xa000, 0); //back to utp mode return ret; } return 0; } #endif static int yt8618_config_init(struct phy_device *phydev) { int ret; int val; phydev->irq = PHY_POLL; if(0xff == yt_mport_base_phy_addr) /* by default, we think the first phy should be the base phy addr. for mul */ #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) { yt_mport_base_phy_addr = phydev->addr; }else if (yt_mport_base_phy_addr > phydev->addr) { printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%d, cur=%d\n", yt_mport_base_phy_addr, phydev->addr); } #else { yt_mport_base_phy_addr = phydev->mdio.addr; }else if (yt_mport_base_phy_addr > phydev->mdio.addr) { printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%d, cur=%d\n", yt_mport_base_phy_addr, phydev->mdio.addr); } #endif ytphy_write_ext(phydev, 0xa000, 0); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) ret = ytphy_config_init(phydev); #else ret = genphy_config_init(phydev); #endif if (ret < 0) return ret; /* for utp to optimize signal */ ret = ytphy_write_ext(phydev, 0x41, 0x33); if (ret < 0) return ret; ret = ytphy_write_ext(phydev, 0x42, 0x66); if (ret < 0) return ret; ret = ytphy_write_ext(phydev, 0x43, 0xaa); if (ret < 0) return ret; ret = ytphy_write_ext(phydev, 0x44, 0xd0d); if (ret < 0) return ret; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) if((phydev->addr > yt_mport_base_phy_addr) && ((2 == phydev->addr - yt_mport_base_phy_addr) || (5 == phydev->addr - yt_mport_base_phy_addr))) #else if((phydev->mdio.addr > yt_mport_base_phy_addr) && ((2 == phydev->mdio.addr - yt_mport_base_phy_addr) || (5 == phydev->mdio.addr - yt_mport_base_phy_addr))) #endif { ret = ytphy_write_ext(phydev, 0x44, 0x2929); if (ret < 0) return ret; } val = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, val | BMCR_RESET); printk (KERN_INFO "yt8618_config_init call out.\n"); return ret; } static int yt8614_config_init(struct phy_device *phydev) { int ret = 0; phydev->irq = PHY_POLL; if(0xff == yt_mport_base_phy_addr_8614) /* by default, we think the first phy should be the base phy addr. for mul */ #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) { yt_mport_base_phy_addr_8614 = (unsigned int)phydev->addr; }else if (yt_mport_base_phy_addr_8614 > (unsigned int)phydev->addr) { printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%u, cur=%d\n", yt_mport_base_phy_addr_8614, phydev->addr); } #else { yt_mport_base_phy_addr_8614 = (unsigned int)phydev->mdio.addr; }else if (yt_mport_base_phy_addr_8614 > (unsigned int)phydev->mdio.addr) { printk (KERN_INFO "yzhang..8618 init, phy address mismatch, base=%u, cur=%d\n", yt_mport_base_phy_addr_8614, phydev->mdio.addr); } #endif return ret; } #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #define yt8614_get_port_from_phydev(phydev) ((0xff == yt_mport_base_phy_addr_8614) && (yt_mport_base_phy_addr_8614 <= (phydev)->addr) ? 0 : (unsigned int)((phydev)->addr) - yt_mport_base_phy_addr_8614) #else #define yt8614_get_port_from_phydev(phydev) ((0xff == yt_mport_base_phy_addr_8614) && (yt_mport_base_phy_addr_8614 <= (phydev)->mdio.addr) ? 0 : (unsigned int)((phydev)->mdio.addr) - yt_mport_base_phy_addr_8614) #endif int yt8618_aneg_done (struct phy_device *phydev) { return genphy_aneg_done(phydev); } int yt8614_aneg_done (struct phy_device *phydev) { int port = yt8614_get_port_from_phydev(phydev); /*it should be used for 8614 fiber*/ if((32 == link_mode_8614[port]) && (SPEED_100 == phydev->speed)) { return 1; } return genphy_aneg_done(phydev); } static int yt8614_read_status(struct phy_device *phydev) { //int i; int ret; volatile int val, yt8614_fiber_latch_val, yt8614_fiber_curr_val; volatile int link; int link_utp = 0, link_fiber = 0; int port = yt8614_get_port_from_phydev(phydev); #if (YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) /* switch to utp and reading regs */ ret = ytphy_write_ext(phydev, 0xa000, 0); if (ret < 0) return ret; val = phy_read(phydev, REG_PHY_SPEC_STATUS); if (val < 0) return val; link = val & (BIT(YT8521_LINK_STATUS_BIT)); if (link) { link_utp = 1; // here is same as 8521 and re-use the function; yt8521_adjust_status(phydev, val, 1); } else { link_utp = 0; } #endif //(YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) #if (YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) /* reading Fiber/sgmii */ ret = ytphy_write_ext(phydev, 0xa000, 3); if (ret < 0) return ret; val = phy_read(phydev, REG_PHY_SPEC_STATUS); if (val < 0) return val; //printk (KERN_INFO "yzhang..8614 read fiber status=%04x,macbase=0x%08lx\n", val,(unsigned long)phydev->attached_dev); /* for fiber, from 1000m to 100m, there is not link down from 0x11, and check reg 1 to identify such case */ yt8614_fiber_latch_val = phy_read(phydev, MII_BMSR); yt8614_fiber_curr_val = phy_read(phydev, MII_BMSR); link = val & (BIT(YT8521_LINK_STATUS_BIT)); if((link) && (yt8614_fiber_latch_val != yt8614_fiber_curr_val)) { link = 0; printk (KERN_INFO "yt8614_read_status, fiber link down detect,latch=%04x,curr=%04x\n", yt8614_fiber_latch_val,yt8614_fiber_curr_val); } if (link) { link_fiber = 1; yt8521_adjust_status(phydev, val, 0); link_mode_8614[port] = 32; //fiber mode } else { link_fiber = 0; } #endif //(YT8521_PHY_MODE_CURR != YT8521_PHY_MODE_UTP) if (link_utp || link_fiber) { phydev->link = 1; } else { phydev->link = 0; link_mode_8614[port] = 0; } #if (YT8614_PHY_MODE_CURR != YT8521_PHY_MODE_FIBER) if (link_utp) { ytphy_write_ext(phydev, 0xa000, 0); } #endif //printk (KERN_INFO "yt8614_read_status call out,link=%d,linkmode=%d\n", phydev->link, link_mode_8614[port] ); return 0; } static int yt8618_read_status(struct phy_device *phydev) { int ret; volatile int val; //maybe for 8614 yt8521_fiber_latch_val, yt8521_fiber_curr_val; volatile int link; int link_utp = 0, link_fiber = 0; /* switch to utp and reading regs */ ret = ytphy_write_ext(phydev, 0xa000, 0); if (ret < 0) return ret; val = phy_read(phydev, REG_PHY_SPEC_STATUS); if (val < 0) return val; link = val & (BIT(YT8521_LINK_STATUS_BIT)); if (link) { link_utp = 1; yt8521_adjust_status(phydev, val, 1); } else { link_utp = 0; } if (link_utp || link_fiber) { phydev->link = 1; } else { phydev->link = 0; } return 0; } int yt8618_suspend(struct phy_device *phydev) { #if !(SYS_WAKEUP_BASED_ON_ETH_PKT) int value; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_lock(&phydev->lock); #else /* no need lock in 4.19 */ #endif ytphy_write_ext(phydev, 0xa000, 0); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_unlock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ return 0; } int yt8618_resume(struct phy_device *phydev) { #if !(SYS_WAKEUP_BASED_ON_ETH_PKT) int value; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_lock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif ytphy_write_ext(phydev, 0xa000, 0); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_unlock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ return 0; } int yt8614_suspend(struct phy_device *phydev) { #if !(SYS_WAKEUP_BASED_ON_ETH_PKT) int value; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_lock(&phydev->lock); #else /* no need lock in 4.19 */ #endif ytphy_write_ext(phydev, 0xa000, 0); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); ytphy_write_ext(phydev, 0xa000, 3); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); ytphy_write_ext(phydev, 0xa000, 0); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_unlock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ return 0; } int yt8614_resume(struct phy_device *phydev) { #if !(SYS_WAKEUP_BASED_ON_ETH_PKT) int value; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_lock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif ytphy_write_ext(phydev, 0xa000, 0); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); ytphy_write_ext(phydev, 0xa000, 3); value = phy_read(phydev, MII_BMCR); phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); ytphy_write_ext(phydev, 0xa000, 0); #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) mutex_unlock(&phydev->lock); #else /* no need lock/unlock in 4.19 */ #endif #endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/ return 0; } static struct phy_driver ytphy_drvs[] = { { .phy_id = PHY_ID_YT8010, .name = "YT8010 Automotive Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES, .flags = PHY_HAS_INTERRUPT, #endif .config_aneg = yt8010_config_aneg, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) .config_init = ytphy_config_init, #else .config_init = genphy_config_init, #endif .read_status = genphy_read_status, }, { .phy_id = PHY_ID_YT8510, .name = "YT8510 100/10Mb Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES, .flags = PHY_HAS_INTERRUPT, #endif .config_aneg = genphy_config_aneg, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) .config_init = ytphy_config_init, #else .config_init = genphy_config_init, #endif .read_status = genphy_read_status, }, { .phy_id = PHY_ID_YT8511, .name = "YT8511 Gigabit Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_GBIT_FEATURES, .flags = PHY_HAS_INTERRUPT, #endif .config_aneg = genphy_config_aneg, #if GMAC_CLOCK_INPUT_NEEDED .config_init = yt8511_config_init, #else #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) .config_init = ytphy_config_init, #else .config_init = genphy_config_init, #endif #endif .read_status = genphy_read_status, .suspend = genphy_suspend, .resume = genphy_resume, }, { .phy_id = PHY_ID_YT8512, .name = "YT8512 Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES, .flags = PHY_HAS_INTERRUPT, #endif .config_aneg = genphy_config_aneg, .config_init = yt8512_config_init, .read_status = yt8512_read_status, .suspend = genphy_suspend, .resume = genphy_resume, }, { .phy_id = PHY_ID_YT8512B, .name = "YT8512B Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES, .flags = PHY_HAS_INTERRUPT, #endif .config_aneg = genphy_config_aneg, .config_init = yt8512_config_init, .read_status = yt8512_read_status, .suspend = genphy_suspend, .resume = genphy_resume, }, { .phy_id = PHY_ID_YT8521, .name = "YT8521 Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES, #endif .flags = PHY_POLL, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #else .soft_reset = yt8521_soft_reset, #endif .config_aneg = genphy_config_aneg, #if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) ) .aneg_done = yt8521_aneg_done, #endif .config_init = yt8521_config_init, .read_status = yt8521_read_status, .suspend = yt8521_suspend, .resume = yt8521_resume, #if (YTPHY_ENABLE_WOL) .get_wol = &ytphy_get_wol, .set_wol = &ytphy_set_wol, #endif },{ /* same as 8521 */ .phy_id = PHY_ID_YT8531S, .name = "YT8531S Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES, #endif .flags = PHY_POLL, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #else .soft_reset = yt8521_soft_reset, #endif .config_aneg = genphy_config_aneg, #if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) ) .aneg_done = yt8521_aneg_done, #endif .config_init = yt8521_config_init, .read_status = yt8521_read_status, .suspend = yt8521_suspend, .resume = yt8521_resume, #if (YTPHY_ENABLE_WOL) .get_wol = &ytphy_get_wol, .set_wol = &ytphy_set_wol, #endif }, { /* same as 8511 */ .phy_id = PHY_ID_YT8531, .name = "YT8531 Gigabit Ethernet", .phy_id_mask = MOTORCOMM_PHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES, .flags = PHY_HAS_INTERRUPT, #endif .config_aneg = genphy_config_aneg, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) .config_init = ytphy_config_init, #else .config_init = genphy_config_init, #endif .read_status = genphy_read_status, .suspend = genphy_suspend, .resume = genphy_resume, #if (YTPHY_ENABLE_WOL) .get_wol = &ytphy_get_wol, .set_wol = &ytphy_set_wol, #endif }, { .phy_id = PHY_ID_YT8618, .name = "YT8618 Ethernet", .phy_id_mask = MOTORCOMM_MPHY_ID_MASK, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES, #endif .flags = PHY_POLL, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #else .soft_reset = yt8618_soft_reset, #endif .config_aneg = genphy_config_aneg, #if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) ) .aneg_done = yt8618_aneg_done, #endif .config_init = yt8618_config_init, .read_status = yt8618_read_status, .suspend = yt8618_suspend, .resume = yt8618_resume, }, { .phy_id = PHY_ID_YT8614, .name = "YT8614 Ethernet", .phy_id_mask = MOTORCOMM_MPHY_ID_MASK_8614, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ) .features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES, #endif .flags = PHY_POLL, #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) #else .soft_reset = yt8614_soft_reset, #endif .config_aneg = genphy_config_aneg, #if ( LINUX_VERSION_CODE > KERNEL_VERSION(3,11,0) ) .aneg_done = yt8614_aneg_done, #endif .config_init = yt8614_config_init, .read_status = yt8614_read_status, .suspend = yt8614_suspend, .resume = yt8614_resume, }, }; #if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) static int ytphy_drivers_register(struct phy_driver* phy_drvs, int size) { int i, j; int ret; for (i = 0; i < size; i++) { ret = phy_driver_register(&phy_drvs[i]); if (ret) goto err; } return 0; err: for (j = 0; j < i; j++) phy_driver_unregister(&phy_drvs[j]); return ret; } static void ytphy_drivers_unregister(struct phy_driver* phy_drvs, int size) { int i; for (i = 0; i < size; i++) { phy_driver_unregister(&phy_drvs[i]); } } static int __init ytphy_init(void) { printk("motorcomm phy register\n"); return ytphy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs)); } static void __exit ytphy_exit(void) { printk("motorcomm phy unregister\n"); ytphy_drivers_unregister(ytphy_drvs, ARRAY_SIZE(ytphy_drvs)); } module_init(ytphy_init); module_exit(ytphy_exit); #else /* for linux 4.x */ module_phy_driver(ytphy_drvs); #endif MODULE_DESCRIPTION("Motorcomm PHY driver"); MODULE_AUTHOR("Leilei Zhao"); MODULE_LICENSE("GPL"); static struct mdio_device_id __maybe_unused motorcomm_tbl[] = { { PHY_ID_YT8010, MOTORCOMM_PHY_ID_MASK }, { PHY_ID_YT8510, MOTORCOMM_PHY_ID_MASK }, { PHY_ID_YT8511, MOTORCOMM_PHY_ID_MASK }, { PHY_ID_YT8512, MOTORCOMM_PHY_ID_MASK }, { PHY_ID_YT8512B, MOTORCOMM_PHY_ID_MASK }, { PHY_ID_YT8521, MOTORCOMM_PHY_ID_MASK }, { PHY_ID_YT8531S, MOTORCOMM_PHY_ID_8531_MASK }, { PHY_ID_YT8531, MOTORCOMM_PHY_ID_8531_MASK }, { PHY_ID_YT8618, MOTORCOMM_MPHY_ID_MASK }, { PHY_ID_YT8614, MOTORCOMM_MPHY_ID_MASK_8614 }, { } }; MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);