1543 lines
37 KiB
C
Executable File
1543 lines
37 KiB
C
Executable File
/*
|
|
* drivers/net/phy/motorcomm.c
|
|
*
|
|
* Driver for Motorcomm PHYs
|
|
*
|
|
* Author: Leilei Zhao <leilei.zhao@motorcomm.com>
|
|
*
|
|
* 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 <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/motorcomm_phy.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#ifndef LINUX_VERSION_CODE
|
|
#include <linux/version.h>
|
|
#else
|
|
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
|
|
#endif
|
|
/*for wol, 20210604*/
|
|
#include <linux/netdevice.h>
|
|
|
|
#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 yt8511_config_out_125m(struct mii_bus *bus, int phy_id);
|
|
static int ytphy_mii_wr_ext(struct mii_bus *bus, int phy_id, u32 regnum, u16 val);
|
|
|
|
static 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;
|
|
}
|
|
|
|
#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 yt8511_config_out_125m(struct mii_bus *bus, int phy_id)
|
|
{
|
|
int ret;
|
|
int val;
|
|
|
|
ret = ytphy_mii_wr_ext(bus, phy_id, 0xa012, 0xd0);
|
|
mdelay(100);
|
|
val = ytphy_mii_rd_ext(bus, phy_id, 0xa012);
|
|
|
|
if(val != 0xd0)
|
|
{
|
|
printk("yt8511_config_out_125m error\n");
|
|
return -1;
|
|
}
|
|
|
|
/* 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[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, phy_id, 0xc, val);
|
|
//printk("yt8511_config_out_125m, phy clk out, val=%#08x\n",val);
|
|
|
|
//ret = ytphy_mii_wr_ext(bus, phy_id, 0xa012, 0x70);
|
|
|
|
#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);
|
|
|
|
|