一、硬件确认
1.1、phy硬件

二、驱动配置
2.1、驱动位置
linux-4.9/drivers/net/ethernet/allwinner
2.1.1 gmac驱动
路径 :linux-4.9/drivers/net/ethernet/allwinner/sunxi-gmac.h
/*
* linux/drivers/net/ethernet/allwinner/sunxi_gmac.h
*
* Copyright © 2016-2018, fuzhaoke
* Author: fuzhaoke <fuzhaoke@allwinnertech.com>
*
* This file is provided under a dual BSD/GPL license. When using or
* redistributing this file, you may do so under either license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __SUNXI_GETH_H__
#define __SUNXI_GETH_H__
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/module.h>
#include <linux/init.h>
/* GETH_FRAME_FILTER register value */
#define GETH_FRAME_FILTER_PR 0x00000001 /* Promiscuous Mode */
#define GETH_FRAME_FILTER_HUC 0x00000002 /* Hash Unicast */
#define GETH_FRAME_FILTER_HMC 0x00000004 /* Hash Multicast */
#define GETH_FRAME_FILTER_DAIF 0x00000008 /* DA Inverse Filtering */
#define GETH_FRAME_FILTER_PM 0x00000010 /* Pass all multicast */
#define GETH_FRAME_FILTER_DBF 0x00000020 /* Disable Broadcast frames */
#define GETH_FRAME_FILTER_SAIF 0x00000100 /* Inverse Filtering */
#define GETH_FRAME_FILTER_SAF 0x00000200 /* Source Address Filter */
#define GETH_FRAME_FILTER_HPF 0x00000400 /* Hash or perfect Filter */
#define GETH_FRAME_FILTER_RA 0x80000000 /* Receive all mode */
/* Default tx descriptor */
#define TX_SINGLE_DESC0 0x80000000
#define TX_SINGLE_DESC1 0x63000000
/* Default rx descriptor */
#define RX_SINGLE_DESC0 0x80000000
#define RX_SINGLE_DESC1 0x83000000
typedef union {
struct {
/* TDES0 */
unsigned int deferred:1; /* Deferred bit (only half-duplex) */
unsigned int under_err:1; /* Underflow error */
unsigned int ex_deferral:1; /* Excessive deferral */
unsigned int coll_cnt:4; /* Collision count */
unsigned int vlan_tag:1; /* VLAN Frame */
unsigned int ex_coll:1; /* Excessive collision */
unsigned int late_coll:1; /* Late collision */
unsigned int no_carr:1; /* No carrier */
unsigned int loss_carr:1; /* Loss of collision */
unsigned int ipdat_err:1; /* IP payload error */
unsigned int frm_flu:1; /* Frame flushed */
unsigned int jab_timeout:1; /* Jabber timeout */
unsigned int err_sum:1; /* Error summary */
unsigned int iphead_err:1; /* IP header error */
unsigned int ttss:1; /* Transmit time stamp status */
unsigned int reserved0:13;
unsigned int own:1; /* Own bit. CPU:0, DMA:1 */
} tx;
/* bits 5 7 0 | Frame status
* ----------------------------------------------------------
* 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects)
* 1 0 0 | IPv4/6 No CSUM errorS.
* 1 0 1 | IPv4/6 CSUM PAYLOAD error
* 1 1 0 | IPv4/6 CSUM IP HR error
* 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS
* 0 0 1 | IPv4/6 unsupported IP PAYLOAD
* 0 1 1 | COE bypassed.. no IPv4/6 frame
* 0 1 0 | Reserved.
*/
struct {
/* RDES0 */
unsigned int chsum_err:1; /* Payload checksum error */
unsigned int crc_err:1; /* CRC error */
unsigned int dribbling:1; /* Dribble bit error */
unsigned int mii_err:1; /* Received error (bit3) */
unsigned int recv_wt:1; /* Received watchdog timeout */
unsigned int frm_type:1; /* Frame type */
unsigned int late_coll:1; /* Late Collision */
unsigned int ipch_err:1; /* IPv header checksum error (bit7) */
unsigned int last_desc:1; /* Laset descriptor */
unsigned int first_desc:1; /* First descriptor */
unsigned int vlan_tag:1; /* VLAN Tag */
unsigned int over_err:1; /* Overflow error (bit11) */
unsigned int len_err:1; /* Length error */
unsigned int sou_filter:1; /* Source address filter fail */
unsigned int desc_err:1; /* Descriptor error */
unsigned int err_sum:1; /* Error summary (bit15) */
unsigned int frm_len:14; /* Frame length */
unsigned int des_filter:1; /* Destination address filter fail */
unsigned int own:1; /* Own bit. CPU:0, DMA:1 */
#define RX_PKT_OK 0x7FFFB77C
#define RX_LEN 0x3FFF0000
} rx;
unsigned int all;
} desc0_u;
typedef union {
struct {
/* TDES1 */
unsigned int buf1_size:11; /* Transmit buffer1 size */
unsigned int buf2_size:11; /* Transmit buffer2 size */
unsigned int ttse:1; /* Transmit time stamp enable */
unsigned int dis_pad:1; /* Disable pad (bit23) */
unsigned int adr_chain:1; /* Second address chained */
unsigned int end_ring:1; /* Transmit end of ring */
unsigned int crc_dis:1; /* Disable CRC */
unsigned int cic:2; /* Checksum insertion control (bit27:28) */
unsigned int first_sg:1; /* First Segment */
unsigned int last_seg:1; /* Last Segment */
unsigned int interrupt:1; /* Interrupt on completion */
} tx;
struct {
/* RDES1 */
unsigned int buf1_size:11; /* Received buffer1 size */
unsigned int buf2_size:11; /* Received buffer2 size */
unsigned int reserved1:2;
unsigned int adr_chain:1; /* Second address chained */
unsigned int end_ring:1; /* Received end of ring */
unsigned int reserved2:5;
unsigned int dis_ic:1; /* Disable interrupt on completion */
} rx;
unsigned int all;
} desc1_u;
typedef struct dma_desc {
desc0_u desc0;
desc1_u desc1;
/* The address of buffers */
unsigned int desc2;
/* Next desc's address */
unsigned int desc3;
} __attribute__((packed)) dma_desc_t;
enum rx_frame_status { /* IPC status */
good_frame = 0,
discard_frame = 1,
csum_none = 2,
llc_snap = 4,
};
enum tx_dma_irq_status {
tx_hard_error = 1,
tx_hard_error_bump_tc = 2,
handle_tx_rx = 3,
};
struct geth_extra_stats {
/* Transmit errors */
unsigned long tx_underflow;
unsigned long tx_carrier;
unsigned long tx_losscarrier;
unsigned long vlan_tag;
unsigned long tx_deferred;
unsigned long tx_vlan;
unsigned long tx_jabber;
unsigned long tx_frame_flushed;
unsigned long tx_payload_error;
unsigned long tx_ip_header_error;
/* Receive errors */
unsigned long rx_desc;
unsigned long sa_filter_fail;
unsigned long overflow_error;
unsigned long ipc_csum_error;
unsigned long rx_collision;
unsigned long rx_crc;
unsigned long dribbling_bit;
unsigned long rx_length;
unsigned long rx_mii;
unsigned long rx_multicast;
unsigned long rx_gmac_overflow;
unsigned long rx_watchdog;
unsigned long da_rx_filter_fail;
unsigned long sa_rx_filter_fail;
unsigned long rx_missed_cntr;
unsigned long rx_overflow_cntr;
unsigned long rx_vlan;
/* Tx/Rx IRQ errors */
unsigned long tx_undeflow_irq;
unsigned long tx_process_stopped_irq;
unsigned long tx_jabber_irq;
unsigned long rx_overflow_irq;
unsigned long rx_buf_unav_irq;
unsigned long rx_process_stopped_irq;
unsigned long rx_watchdog_irq;
unsigned long tx_early_irq;
unsigned long fatal_bus_error_irq;
/* Extra info */
unsigned long threshold;
unsigned long tx_pkt_n;
unsigned long rx_pkt_n;
unsigned long poll_n;
unsigned long sched_timer_n;
unsigned long normal_irq_n;
};
int sunxi_mdio_read(void *, int, int);
int sunxi_mdio_write(void *, int, int, unsigned short);
int sunxi_mdio_reset(void *);
void sunxi_set_link_mode(void *iobase, int duplex, int speed);
void sunxi_int_disable(void *);
int sunxi_int_status(void *, struct geth_extra_stats *x);
int sunxi_mac_init(void *, int txmode, int rxmode);
void sunxi_set_umac(void *, unsigned char *, int);
void sunxi_mac_enable(void *);
void sunxi_mac_disable(void *);
void sunxi_tx_poll(void *);
void sunxi_int_enable(void *);
void sunxi_start_rx(void *, unsigned long);
void sunxi_start_tx(void *, unsigned long);
void sunxi_stop_tx(void *);
void sunxi_stop_rx(void *);
void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high);
void sunxi_set_filter(void *iobase, unsigned long flags);
void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause);
void sunxi_mac_loopback(void *iobase, int enable);
void desc_buf_set(struct dma_desc *p, unsigned long paddr, int size);
void desc_set_own(struct dma_desc *p);
void desc_init_chain(struct dma_desc *p, unsigned long paddr, unsigned int size);
void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert);
void desc_init(struct dma_desc *p);
int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x);
int desc_buf_get_len(struct dma_desc *desc);
int desc_buf_get_addr(struct dma_desc *desc);
int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x);
int desc_get_own(struct dma_desc *desc);
int desc_get_tx_ls(struct dma_desc *desc);
int desc_rx_frame_len(struct dma_desc *desc);
int sunxi_mac_reset(void *iobase, void (*mdelay)(int), int n);
int sunxi_geth_register(void *iobase, int version, unsigned int div);
#if defined(CONFIG_SUNXI_EPHY)
extern int ephy_is_enable(void);
#endif
#if defined(CONFIG_ARCH_SUN8IW3) \
|| defined(CONFIG_ARCH_SUN9IW1) \
|| defined(CONFIG_ARCH_SUN7I)
#define HW_VERSION 0
#else
#define HW_VERSION 1
#endif
#endif
路径:linux-4.9/drivers/net/ethernet/allwinner/sunxi-gmac.c
/*
* linux/drivers/net/ethernet/allwinner/sunxi_gmac.c
*
* Copyright © 2016-2018, fuzhaoke
* Author: fuzhaoke <fuzhaoke@allwinnertech.com>
*
* This file is provided under a dual BSD/GPL license. When using or
* redistributing this file, you may do so under either license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/mii.h>
#include <linux/gpio.h>
#include <linux/crc32.h>
#include <linux/skbuff.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <linux/err.h>
#include <linux/scatterlist.h>
#include <linux/regulator/consumer.h>
#include <linux/of_net.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include <linux/sunxi-sid.h>
#include <linux/sunxi-gpio.h>
#include "sunxi-gmac.h"
#define DMA_DESC_RX 256
#define DMA_DESC_TX 256
#define BUDGET (dma_desc_rx / 4)
#define TX_THRESH (dma_desc_tx / 4)
#define HASH_TABLE_SIZE 64
#define MAX_BUF_SZ (SZ_2K - 1)
#define POWER_CHAN_NUM 3
#undef PKT_DEBUG
#undef DESC_PRINT
#define circ_cnt(head, tail, size) (((head) > (tail)) ? \
((head) - (tail)) : \
((head) - (tail)) & ((size) - 1))
#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size))
#define circ_inc(n, s) (((n) + 1) % (s))
#define GETH_MAC_ADDRESS "00:00:00:00:00:00"
static char *mac_str = GETH_MAC_ADDRESS;
module_param(mac_str, charp, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)");
static int rxmode = 1;
module_param(rxmode, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(rxmode, "DMA threshold control value");
static int txmode = 1;
module_param(txmode, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(txmode, "DMA threshold control value");
static int pause = 0x400;
module_param(pause, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(pause, "Flow Control Pause Time");
#define TX_TIMEO 5000
static int watchdog = TX_TIMEO;
module_param(watchdog, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds");
static int dma_desc_rx = DMA_DESC_RX;
module_param(dma_desc_rx, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(watchdog, "The number of receive's descriptors");
static int dma_desc_tx = DMA_DESC_TX;
module_param(dma_desc_tx, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(watchdog, "The number of transmit's descriptors");
/* - 0: Flow Off
* - 1: Rx Flow
* - 2: Tx Flow
* - 3: Rx & Tx Flow
*/
static int flow_ctrl;
module_param(flow_ctrl, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]");
static unsigned long tx_delay;
module_param(tx_delay, ulong, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(tx_delay, "Adjust transmit clock delay, value: 0~7");
static unsigned long rx_delay;
module_param(rx_delay, ulong, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(rx_delay, "Adjust receive clock delay, value: 0~31");
/* whether using ephy_clk */
static int g_use_ephy_clk;
static int g_phy_addr;
struct geth_priv {
struct dma_desc *dma_tx;
struct sk_buff **tx_sk;
unsigned int tx_clean;
unsigned int tx_dirty;
dma_addr_t dma_tx_phy;
unsigned long buf_sz;
struct dma_desc *dma_rx;
struct sk_buff **rx_sk;
unsigned int rx_clean;
unsigned int rx_dirty;
dma_addr_t dma_rx_phy;
struct net_device *ndev;
struct device *dev;
struct napi_struct napi;
struct geth_extra_stats xstats;
struct mii_bus *mii;
int link;
int speed;
int duplex;
#define INT_PHY 0
#define EXT_PHY 1
int phy_ext;
int phy_interface;
void __iomem *base;
void __iomem *base_phy;
struct clk *geth_clk;
struct clk *ephy_clk;
struct pinctrl *pinctrl;
struct regulator *gmac_power[POWER_CHAN_NUM];
bool is_suspend;
int phyrst;
u8 rst_active_low;
/* definition spinlock */
spinlock_t lock;
spinlock_t tx_lock;
/* resume work */
struct work_struct eth_work;
struct regulator *avcc;
};
static u64 geth_dma_mask = DMA_BIT_MASK(32);
void sunxi_udelay(int n)
{
udelay(n);
}
static int geth_stop(struct net_device *ndev);
static int geth_open(struct net_device *ndev);
static void geth_tx_complete(struct geth_priv *priv);
static void geth_rx_refill(struct net_device *ndev);
#ifdef CONFIG_GETH_ATTRS
static ssize_t adjust_bgs_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int value = 0;
u32 efuse_value;
struct net_device *ndev = to_net_dev(dev);
struct geth_priv *priv = netdev_priv(ndev);
if (priv->phy_ext == INT_PHY) {
value = readl(priv->base_phy) >> 28;
if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
pr_err("get PHY efuse fail!\n");
else
#if defined(CONFIG_ARCH_SUN50IW2)
value = value - ((efuse_value >> 24) & 0x0F);
#else
pr_warn("miss config come from efuse!\n");
#endif
}
return sprintf(buf, "bgs: %d\n", value);
}
static ssize_t adjust_bgs_write(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int out = 0;
struct net_device *ndev = to_net_dev(dev);
struct geth_priv *priv = netdev_priv(ndev);
u32 clk_value = readl(priv->base_phy);
u32 efuse_value;
out = simple_strtoul(buf, NULL, 10);
if (priv->phy_ext == INT_PHY) {
clk_value &= ~(0xF << 28);
if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
pr_err("get PHY efuse fail!\n");
else
#if defined(CONFIG_ARCH_SUN50IW2)
clk_value |= (((efuse_value >> 24) & 0x0F) + out) << 28;
#else
pr_warn("miss config come from efuse!\n");
#endif
}
writel(clk_value, priv->base_phy);
return count;
}
static struct device_attribute adjust_reg[] = {
__ATTR(adjust_bgs, 0664, adjust_bgs_show, adjust_bgs_write),
};
static int geth_create_attrs(struct net_device *ndev)
{
int j, ret;
for (j = 0; j < ARRAY_SIZE(adjust_reg); j++) {
ret = device_create_file(&ndev->dev, &adjust_reg[j]);
if (ret)
goto sysfs_failed;
}
goto succeed;
sysfs_failed:
while (j--)
device_remove_file(&ndev->dev, &adjust_reg[j]);
succeed:
return ret;
}
#endif
#ifdef DEBUG
static void desc_print(struct dma_desc *desc, int size)
{
#ifdef DESC_PRINT
int i;
for (i = 0; i < size; i++) {
u32 *x = (u32 *)(desc + i);
pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n",
i, (unsigned long)(&desc[i]),
x[0], x[1], x[2], x[3]);
}
pr_info("\n");
#endif
}
#endif
static ssize_t gphy_test_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct net_device *ndev = dev_get_drvdata(dev);
if (!dev) {
pr_err("Argment is invalid\n");
return 0;
}
if (!ndev) {
pr_err("Net device is null\n");
return 0;
}
return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n"
"0 - Normal Mode\n"
"1 - Transmit Jitter Test\n"
"2 - Transmit Jitter Test(MASTER mode)\n"
"3 - Transmit Jitter Test(SLAVE mode)\n"
"4 - Transmit Distortion Test\n\n");
}
static ssize_t gphy_test_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct geth_priv *priv = netdev_priv(ndev);
u16 value = 0;
int ret = 0;
u16 data = 0;
if (!dev) {
pr_err("Argument is invalid\n");
return count;
}
if (!ndev) {
pr_err("Net device is null\n");
return count;
}
data = sunxi_mdio_read(priv->base, g_phy_addr, MII_CTRL1000);
ret = kstrtou16(buf, 0, &value);
if (ret)
return ret;
if (value >= 0 && value <= 4) {
data &= ~(0x7 << 13);
data |= value << 13;
sunxi_mdio_write(priv->base, g_phy_addr, MII_CTRL1000, data);
pr_info("Set MII_CTRL1000(0x09) Reg: 0x%x\n", data);
} else {
pr_info("unknown value (%d)\n", value);
}
return count;
}
static DEVICE_ATTR(gphy_test, 0664, gphy_test_show, gphy_test_store);
static int geth_power_on(struct geth_priv *priv)
{
int value;
int i;
value = readl(priv->base_phy);
if (priv->phy_ext == INT_PHY) {
value |= (1 << 15);
value &= ~(1 << 16);
value |= (3 << 17);
printk("INT_PHY, power up\n");
} else {
printk("EXT_PHY, regulator up,bit 16 18 set\n");
value &= ~(1 << 15);
/*
value &= ~(1 << 16);
value &= ~(3 << 17);
value |= (1 << 17);
*/
for (i = 0; i < POWER_CHAN_NUM; i++) {
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
continue;
if (regulator_enable(priv->gmac_power[i]) != 0) {
pr_err("gmac-power%d enable error\n", i);
return -EINVAL;
}
}
}
writel(value, priv->base_phy);
return 0;
}
static void geth_power_off(struct geth_priv *priv)
{
int value;
int i;
if (priv->phy_ext == INT_PHY) {
value = readl(priv->base_phy);
value |= (1 << 16);
writel(value, priv->base_phy);
} else {
for (i = 0; i < POWER_CHAN_NUM; i++) {
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
continue;
regulator_disable(priv->gmac_power[i]);
}
}
}
/* PHY interface operations */
static int geth_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
{
struct net_device *ndev = bus->priv;
struct geth_priv *priv = netdev_priv(ndev);
return (int)sunxi_mdio_read(priv->base, phyaddr, phyreg);
}
static int geth_mdio_write(struct mii_bus *bus, int phyaddr,
int phyreg, u16 data)
{
struct net_device *ndev = bus->priv;
struct geth_priv *priv = netdev_priv(ndev);
sunxi_mdio_write(priv->base, phyaddr, phyreg, data);
return 0;
}
static int geth_mdio_reset(struct mii_bus *bus)
{
struct net_device *ndev = bus->priv;
struct geth_priv *priv = netdev_priv(ndev);
return sunxi_mdio_reset(priv->base);
}
static void geth_adjust_link(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
struct phy_device *phydev = ndev->phydev;
unsigned long flags;
int new_state = 0;
if (!phydev)
return;
printk("geth_adjust_link link %d, phydev->speeds %d , priv->speed %d\n", phydev->link, phydev->speed, priv->speed);
spin_lock_irqsave(&priv->lock, flags);
if (phydev->link) {
/* Now we make sure that we can be in full duplex mode.
* If not, we operate in half-duplex mode.
*/
if (phydev->duplex != priv->duplex) {
new_state = 1;
priv->duplex = phydev->duplex;
}
/* Flow Control operation */
if (phydev->pause)
sunxi_flow_ctrl(priv->base, phydev->duplex,
flow_ctrl, pause);
printk("geth_adjust_link phydev->speeds %d , %d\n", phydev->speed, priv->speed);
if (phydev->speed != priv->speed) {
new_state = 1;
priv->speed = phydev->speed;
}
if (priv->link == 0) {
new_state = 1;
priv->link = phydev->link;
}
if (new_state){
printk("sunxi_set_link_mode %p , %d, %d\n", priv->base, priv->duplex, priv->speed);
sunxi_set_link_mode(priv->base, priv->duplex, priv->speed);
}
#ifdef LOOPBACK_DEBUG
phydev->state = PHY_FORCING;
#endif
} else if (priv->link != phydev->link) {
new_state = 1;
priv->link = 0;
priv->speed = 0;
priv->duplex = -1;
}
if (new_state)
phy_print_status(phydev);
spin_unlock_irqrestore(&priv->lock, flags);
}
#define AT803X_REG_CHIP_CONFIG 0x1f
#define AT803X_BT_BX_REG_SEL 0x8000
#define AT803X_RG_AUDO_MDET 0x800B
#define AT803X_BX1000_RGMII_75 0x0003
#define AT803X_CONFIG_AUTO_NEGOTIATION BIT(12)
static int at803x_mode_config(struct phy_device *phydev){
int ccr;
phy_write(phydev, AT803X_REG_CHIP_CONFIG, AT803X_BT_BX_REG_SEL);
ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
printk("1120 at803x_parse_dt AT803X_REG_CHIP_CONFIG 0x%x\n", ccr);
return 0;
}
static int ar8031_fix_up(struct phy_device *phydev)
{
unsigned short val;
printk("ar8031 0528 2126 fix up, 125MHz\n");
/* Ar8031 phy SmartEEE feature cause link status generates glitch,
* which cause ethernet link down/up issue, so disable SmartEEE
*/
phy_write(phydev, 0xd, 0x3);
phy_write(phydev, 0xe, 0x805d);
phy_write(phydev, 0xd, 0x4003);
val = phy_read(phydev, 0xe);
val &= ~(0x1 << 8);
phy_write(phydev, 0xe, val);
printk("ar8031 0x805d 0x%x\n", val);
#if 1
/* To enable AR8031 ouput a 125MHz clk from CLK_25M */
phy_write(phydev, 0xd, 0x7);
phy_write(phydev, 0xe, 0x8016);
phy_write(phydev, 0xd, 0x4007);
val = phy_read(phydev, 0xe);
printk("ar8031 0x8016 default 0x%x\n", val);
val &= 0xffe3;
val |= 0x18;
/*
#define AT803X_CLK_OUT_STRENGTH_MASK GENMASK(8, 7) //bit8 bit7
#define AT803X_CLK_OUT_STRENGTH_FULL 0
#define AT803X_CLK_OUT_STRENGTH_HALF 1
#define AT803X_CLK_OUT_STRENGTH_QUARTER 2
val &= 0xFE7F;
*/
phy_write(phydev, 0xe, val);
printk("ar8031 0x8016 0x%x\n", val);
/* Introduce tx clock delay */
/*phy_write(phydev, 0x1d, 0x5);
val = phy_read(phydev, 0x1e);
val |= 0x0100;
phy_write(phydev, 0x1e, val);*/
#define AT803X_DEBUG_REG_1F 0x1F
#define AT803X_DEBUG_PLL_ON BIT(2)
#define AT803X_DEBUG_RGMII_1V8 BIT(3)
#define AT803X_DEBUG_ADDR 0x1D
#define AT803X_DEBUG_DATA 0x1E
phy_write(phydev, 0x1d, 0x1F);
val = phy_read(phydev, 0x1e);
val |= AT803X_DEBUG_PLL_ON ;
phy_write(phydev, 0x1e, val);
#endif
/*check phy power*/
//val = phy_read(phydev, 0x0);
//if (val & BMCR_PDOWN)
//phy_write(phydev, 0x0, (val & ~BMCR_PDOWN));
at803x_mode_config(phydev);
return 0;
}
static int geth_phy_init(struct net_device *ndev)
{
int value;
struct mii_bus *new_bus;
struct geth_priv *priv = netdev_priv(ndev);
struct phy_device *phydev = ndev->phydev;
printk("geth_phy_init 1\n");
if (priv->is_suspend && phydev)
goto resume;
/* Fixup the phy interface type */
if (priv->phy_ext == INT_PHY) {
priv->phy_interface = PHY_INTERFACE_MODE_MII;
} else {
/* If config gpio to reset the phy device, we should reset it */
if (gpio_is_valid(priv->phyrst)) {
gpio_direction_output(priv->phyrst,
priv->rst_active_low);
msleep(50);
gpio_direction_output(priv->phyrst,
!priv->rst_active_low);
msleep(50);
}
}
new_bus = mdiobus_alloc();
if (!new_bus) {
netdev_err(ndev, "Failed to alloc new mdio bus\n");
return -ENOMEM;
}
new_bus->name = dev_name(priv->dev);
new_bus->read = &geth_mdio_read;
new_bus->write = &geth_mdio_write;
new_bus->reset = &geth_mdio_reset;
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, 0);
new_bus->parent = priv->dev;
new_bus->priv = ndev;
if (mdiobus_register(new_bus)) {
pr_err("%s: Cannot register as MDIO bus\n", new_bus->name);
goto reg_fail;
}
priv->mii = new_bus;
{
int addr;
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
struct phy_device *phydev_tmp = mdiobus_get_phy(new_bus, addr);
if (phydev_tmp && (phydev_tmp->phy_id != 0x00)) {
phydev = phydev_tmp;
g_phy_addr = addr;
break;
}
}
}
if (!phydev) {
netdev_err(ndev, "No PHY found!\n");
goto err;
}else{
netdev_err(ndev, "PHY found!\n");
}
phy_write(phydev, MII_BMCR, BMCR_RESET);
while (BMCR_RESET & phy_read(phydev, MII_BMCR))
msleep(30);
netdev_err(ndev, "MII_BMCR poweron , MII_BMCR 0x%x\n", phy_read(phydev, MII_BMCR));
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN));
netdev_err(ndev, "1534 MII_BMCR 0x%x\n", phy_read(phydev, MII_BMCR));
phydev->irq = PHY_POLL;
//ar8031_fix_up(phydev);
netdev_err(ndev, "~BMCR_PDOWN 1411\n");
value = phy_connect_direct(ndev, phydev, &geth_adjust_link, priv->phy_interface);
if (value) {
netdev_err(ndev, "Could not attach to PHY\n");
goto err;
} else {
netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n",
ndev->name, phydev->interface, phydev->phy_id,
phydev->mdio.addr, "poll", dev_name(&phydev->mdio.dev));
}
netdev_err(ndev, "phy_connect_direct\n");
phydev->supported &= PHY_GBIT_FEATURES;
phydev->advertising = phydev->supported;
resume:
if (priv->phy_ext == INT_PHY) {
/* EPHY Initial */
phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
phy_write(phydev, 0x12, 0x4824); /* Disable APS */
phy_write(phydev, 0x1f, 0x0200); /* switchto page 2 */
phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
phy_write(phydev, 0x1f, 0x0600); /* switchto page 6 */
phy_write(phydev, 0x14, 0x708F); /* PHYAFE TX optimization */
phy_write(phydev, 0x19, 0x0000);
phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */
phy_write(phydev, 0x15, 0x1530);
phy_write(phydev, 0x1f, 0x0800); /* switch to page 8 */
phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
phy_write(phydev, 0x1f, 0x0100); /* switchto page 1 */
/* reg 0x17 bit3,set 0 to disable iEEE */
phy_write(phydev, 0x17, phy_read(phydev, 0x17) & (~(1<<3)));
phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
}
if (priv->is_suspend)
phy_init_hw(phydev);
return 0;
err:
mdiobus_unregister(new_bus);
reg_fail:
mdiobus_free(new_bus);
return -EINVAL;
}
static int geth_phy_release(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
struct phy_device *phydev = ndev->phydev;
int value = 0;
/* Stop and disconnect the PHY */
if (phydev)
phy_stop(phydev);
priv->link = PHY_DOWN;
priv->speed = 0;
priv->duplex = -1;
if (priv->is_suspend)
return 0;
if (phydev) {
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));
phy_disconnect(phydev);
ndev->phydev = NULL;
}
if (priv->mii) {
mdiobus_unregister(priv->mii);
priv->mii->priv = NULL;
mdiobus_free(priv->mii);
priv->mii = NULL;
}
return 0;
}
static void geth_rx_refill(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
struct dma_desc *desc;
struct sk_buff *sk = NULL;
dma_addr_t paddr;
while (circ_space(priv->rx_clean, priv->rx_dirty, dma_desc_rx) > 0) {
int entry = priv->rx_clean;
/* Find the dirty's desc and clean it */
desc = priv->dma_rx + entry;
if (priv->rx_sk[entry] == NULL) {
sk = netdev_alloc_skb_ip_align(ndev, priv->buf_sz);
if (unlikely(sk == NULL))
break;
priv->rx_sk[entry] = sk;
paddr = dma_map_single(priv->dev, sk->data,
priv->buf_sz, DMA_FROM_DEVICE);
desc_buf_set(desc, paddr, priv->buf_sz);
}
/* sync memery */
wmb();
desc_set_own(desc);
priv->rx_clean = circ_inc(priv->rx_clean, dma_desc_rx);
}
}
/* geth_dma_desc_init - initialize the RX/TX descriptor list
* @ndev: net device structure
* Description: initialize the list for dma.
*/
static int geth_dma_desc_init(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
unsigned int buf_sz;
priv->rx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_rx,
GFP_KERNEL);
if (!priv->rx_sk)
return -ENOMEM;
priv->tx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_tx,
GFP_KERNEL);
if (!priv->tx_sk)
goto tx_sk_err;
/* Set the size of buffer depend on the MTU & max buf size */
buf_sz = MAX_BUF_SZ;
priv->dma_tx = dma_alloc_coherent(priv->dev,
dma_desc_tx *
sizeof(struct dma_desc),
&priv->dma_tx_phy,
GFP_KERNEL);
if (!priv->dma_tx)
goto dma_tx_err;
priv->dma_rx = dma_alloc_coherent(priv->dev,
dma_desc_rx *
sizeof(struct dma_desc),
&priv->dma_rx_phy,
GFP_KERNEL);
if (!priv->dma_rx)
goto dma_rx_err;
priv->buf_sz = buf_sz;
return 0;
dma_rx_err:
dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
priv->dma_tx, priv->dma_tx_phy);
dma_tx_err:
kfree(priv->tx_sk);
tx_sk_err:
kfree(priv->rx_sk);
return -ENOMEM;
}
static void geth_free_rx_sk(struct geth_priv *priv)
{
int i;
for (i = 0; i < dma_desc_rx; i++) {
if (priv->rx_sk[i] != NULL) {
struct dma_desc *desc = priv->dma_rx + i;
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
desc_buf_get_len(desc),
DMA_FROM_DEVICE);
dev_kfree_skb_any(priv->rx_sk[i]);
priv->rx_sk[i] = NULL;
}
}
}
static void geth_free_tx_sk(struct geth_priv *priv)
{
int i;
for (i = 0; i < dma_desc_tx; i++) {
if (priv->tx_sk[i] != NULL) {
struct dma_desc *desc = priv->dma_tx + i;
if (desc_buf_get_addr(desc))
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
desc_buf_get_len(desc),
DMA_TO_DEVICE);
dev_kfree_skb_any(priv->tx_sk[i]);
priv->tx_sk[i] = NULL;
}
}
}
static void geth_free_dma_desc(struct geth_priv *priv)
{
/* Free the region of consistent memory previously allocated for the DMA */
dma_free_coherent(priv->dev, dma_desc_tx * sizeof(struct dma_desc),
priv->dma_tx, priv->dma_tx_phy);
dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
priv->dma_rx, priv->dma_rx_phy);
kfree(priv->rx_sk);
kfree(priv->tx_sk);
}
#ifdef CONFIG_PM
static int geth_select_gpio_state(struct pinctrl *pctrl, char *name)
{
int ret = 0;
struct pinctrl_state *pctrl_state = NULL;
pctrl_state = pinctrl_lookup_state(pctrl, name);
if (IS_ERR(pctrl_state)) {
pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n",
name, pctrl_state);
return -EINVAL;
}
ret = pinctrl_select_state(pctrl, pctrl_state);
if (ret < 0)
pr_err("gmac pinctrl_select_state(%s) failed! return %d\n",
name, ret);
return ret;
}
static int geth_suspend(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct geth_priv *priv = netdev_priv(ndev);
cancel_work_sync(&priv->eth_work);
if (!ndev || !netif_running(ndev))
return 0;
priv->is_suspend = true;
spin_lock(&priv->lock);
netif_device_detach(ndev);
spin_unlock(&priv->lock);
geth_stop(ndev);
if (priv->phy_ext == EXT_PHY)
geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP);
return 0;
}
static void geth_resume_work(struct work_struct *work)
{
struct geth_priv *priv = container_of(work, struct geth_priv, eth_work);
struct net_device *ndev = priv->ndev;
int ret = 0;
if (!netif_running(ndev))
return;
if (priv->phy_ext == EXT_PHY)
geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT);
spin_lock(&priv->lock);
netif_device_attach(ndev);
spin_unlock(&priv->lock);
#if defined(CONFIG_SUNXI_EPHY)
if (!ephy_is_enable()) {
pr_info("[geth_resume] ephy is not enable, waiting...\n");
msleep(2000);
if (!ephy_is_enable()) {
netdev_err(ndev, "Wait for ephy resume timeout.\n");
return;
}
}
#endif
ret = geth_open(ndev);
if (!ret)
priv->is_suspend = false;
}
static void geth_resume(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct geth_priv *priv = netdev_priv(ndev);
schedule_work(&priv->eth_work);
}
static int geth_freeze(struct device *dev)
{
return 0;
}
static int geth_restore(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops geth_pm_ops = {
.complete = geth_resume,
.prepare = geth_suspend,
.suspend = NULL,
.resume = NULL,
.freeze = geth_freeze,
.restore = geth_restore,
};
#else
static const struct dev_pm_ops geth_pm_ops;
#endif /* CONFIG_PM */
#define sunxi_get_soc_chipid(x) {}
static void geth_chip_hwaddr(u8 *addr)
{
#define MD5_SIZE 16
#define CHIP_SIZE 16
struct crypto_ahash *tfm;
struct ahash_request *req;
struct scatterlist sg;
u8 result[MD5_SIZE];
u8 chipid[CHIP_SIZE];
int i = 0;
int ret = -1;
memset(chipid, 0, sizeof(chipid));
memset(result, 0, sizeof(result));
sunxi_get_soc_chipid((u8 *)chipid);
tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm)) {
pr_err("Failed to alloc md5\n");
return;
}
req = ahash_request_alloc(tfm, GFP_KERNEL);
if (!req)
goto out;
ahash_request_set_callback(req, 0, NULL, NULL);
ret = crypto_ahash_init(req);
if (ret) {
pr_err("crypto_ahash_init() failed\n");
goto out;
}
sg_init_one(&sg, chipid, sizeof(chipid) - 1);
ahash_request_set_crypt(req, &sg, result, sizeof(chipid) - 1);
ret = crypto_ahash_update(req);
if (ret) {
pr_err("crypto_ahash_update() failed for id\n");
goto out;
}
ret = crypto_ahash_final(req);
if (ret) {
pr_err("crypto_ahash_final() failed for result\n");
goto out;
}
ahash_request_free(req);
/* Choose md5 result's [0][2][4][6][8][10] byte as mac address */
for (i = 0; i < ETH_ALEN; i++)
addr[i] = result[2 * i];
addr[0] &= 0xfe; /* clear multicast bit */
addr[0] |= 0x02; /* set local assignment bit (IEEE802) */
out:
crypto_free_ahash(tfm);
}
static void geth_check_addr(struct net_device *ndev, unsigned char *mac)
{
int i;
char *p = mac;
if (!is_valid_ether_addr(ndev->dev_addr)) {
for (i = 0; i < ETH_ALEN; i++, p++)
ndev->dev_addr[i] = simple_strtoul(p, &p, 16);
if (!is_valid_ether_addr(ndev->dev_addr))
geth_chip_hwaddr(ndev->dev_addr);
if (!is_valid_ether_addr(ndev->dev_addr)) {
random_ether_addr(ndev->dev_addr);
pr_warn("%s: Use random mac address\n", ndev->name);
}
}
}
static void geth_clk_enable(struct geth_priv *priv)
{
int phy_interface = 0;
u32 clk_value;
u32 efuse_value;
if (clk_prepare_enable(priv->geth_clk)){
pr_err("try to enable geth_clk failed!\n");
}else{
printk("priv->geth_clk enable rate %p\n", priv->geth_clk);
}
if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk)
&& !IS_ERR_OR_NULL(priv->ephy_clk)) {
if (clk_prepare_enable(priv->ephy_clk))
pr_err("try to enable ephy_clk failed!\n");
}
phy_interface = priv->phy_interface;
clk_value = readl(priv->base_phy);
if (phy_interface == PHY_INTERFACE_MODE_RGMII)
clk_value |= 0x00000004;
else
clk_value &= (~0x00000004);
printk("phy_interface 0x%x\n",phy_interface);
clk_value &= (~0x00002003);
if (phy_interface == PHY_INTERFACE_MODE_RGMII
|| phy_interface == PHY_INTERFACE_MODE_GMII){
printk(" PHY_INTERFACE_MODE_RGMII CLK125M to RGMII\n");
clk_value |= 0x00000002;
}
else if (phy_interface == PHY_INTERFACE_MODE_RMII){
printk(" PHY_INTERFACE_MODE_RMII\n");
clk_value |= 0x00002001;
}
if (priv->phy_ext == INT_PHY) {
if (0 != sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value))
pr_err("get PHY efuse fail!\n");
else
#if defined(CONFIG_ARCH_SUN50IW2)
clk_value |= (((efuse_value >> 24) & 0x0F) + 3) << 28;
#else
pr_warn("miss config come from efuse!\n");
#endif
}
/* Adjust Tx/Rx clock delay */
clk_value &= ~(0x07 << 10);
clk_value |= ((tx_delay & 0x07) << 10);
clk_value &= ~(0x1F << 5);
clk_value |= ((rx_delay & 0x1F) << 5);
writel(clk_value, priv->base_phy);
}
static void geth_clk_disable(struct geth_priv *priv)
{
if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk)
&& !IS_ERR_OR_NULL(priv->ephy_clk))
clk_disable_unprepare(priv->ephy_clk);
clk_disable_unprepare(priv->geth_clk);
}
static void geth_tx_err(struct geth_priv *priv)
{
netif_stop_queue(priv->ndev);
sunxi_stop_tx(priv->base);
geth_free_tx_sk(priv);
memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
priv->tx_dirty = 0;
priv->tx_clean = 0;
sunxi_start_tx(priv->base, priv->dma_tx_phy);
priv->ndev->stats.tx_errors++;
netif_wake_queue(priv->ndev);
}
static inline void geth_schedule(struct geth_priv *priv)
{
if (likely(napi_schedule_prep(&priv->napi))) {
sunxi_int_disable(priv->base);
__napi_schedule(&priv->napi);
}
}
static irqreturn_t geth_interrupt(int irq, void *dev_id)
{
struct net_device *ndev = (struct net_device *)dev_id;
struct geth_priv *priv = netdev_priv(ndev);
int status;
if (unlikely(!ndev)) {
pr_err("%s: invalid ndev pointer\n", __func__);
return IRQ_NONE;
}
status = sunxi_int_status(priv->base, (void *)(&priv->xstats));
if (likely(status == handle_tx_rx))
geth_schedule(priv);
else if (unlikely(status == tx_hard_error_bump_tc))
netdev_info(ndev, "Do nothing for bump tc\n");
else if (unlikely(status == tx_hard_error))
geth_tx_err(priv);
else
netdev_info(ndev, "Do nothing.....\n");
return IRQ_HANDLED;
}
extern int gpioInit(struct platform_device *pdev, const char* name);
static int geth_open(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
struct platform_device *pdev = container_of(priv->dev, struct platform_device, dev);
int ret = 0;
printk("geth_open \n");
ret = geth_dma_desc_init(ndev);
if (ret) {
ret = -EINVAL;
return ret;
}
/******phy reset, that can be discovered *****/
printk("geth_probe reset set high, gpioInit, RXCK will start\n");
//gpioInit(pdev, "phy_reset");
ret = geth_power_on(priv);
if (ret) {
netdev_err(ndev, "Power on is failed\n");
ret = -EINVAL;
}
geth_clk_enable(priv);
//netif_carrier_off(ndev);
ret = geth_phy_init(ndev);
if (ret)
goto err;
ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000);
if (ret) {
netdev_err(ndev, "Initialize hardware error\n");
goto desc_err;
}else{
netdev_err(ndev, "Initialize hardware success\n");
}
sunxi_mac_init(priv->base, txmode, rxmode);
sunxi_set_umac(priv->base, ndev->dev_addr, 0);
/*
if (!priv->is_suspend) {
ret = geth_dma_desc_init(ndev);
if (ret) {
ret = -EINVAL;
goto desc_err;
}
}
*/
memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
memset(priv->dma_rx, 0, dma_desc_rx * sizeof(struct dma_desc));
desc_init_chain(priv->dma_rx, (unsigned long)priv->dma_rx_phy, dma_desc_rx);
desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
priv->rx_clean = 0;
priv->rx_dirty = 0;
priv->tx_clean = 0;
priv->tx_dirty = 0;
geth_rx_refill(ndev);
/* Extra statistics */
memset(&priv->xstats, 0, sizeof(struct geth_extra_stats));
if (ndev->phydev)
phy_start(ndev->phydev);
sunxi_start_rx(priv->base, (unsigned long)((struct dma_desc *)
priv->dma_rx_phy + priv->rx_dirty));
sunxi_start_tx(priv->base, (unsigned long)((struct dma_desc *)
priv->dma_tx_phy + priv->tx_clean));
/* Enable the Rx/Tx */
sunxi_mac_enable(priv->base);
netif_carrier_on(ndev);
napi_enable(&priv->napi);
netif_start_queue(ndev);
/* Enable the Rx/Tx */
// sunxi_mac_enable(priv->base);
return 0;
desc_err:
geth_phy_release(ndev);
err:
geth_clk_disable(priv);
if (priv->is_suspend)
napi_enable(&priv->napi);
geth_power_off(priv);
power_err:
geth_free_dma_desc(priv);
return ret;
}
static int geth_stop(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
netif_stop_queue(ndev);
napi_disable(&priv->napi);
netif_carrier_off(ndev);
/* Release PHY resources */
geth_phy_release(ndev);
/* Disable Rx/Tx */
sunxi_mac_disable(priv->base);
geth_clk_disable(priv);
geth_power_off(priv);
netif_tx_lock_bh(ndev);
/* Release the DMA TX/RX socket buffers */
geth_free_rx_sk(priv);
geth_free_tx_sk(priv);
netif_tx_unlock_bh(ndev);
/* Ensure that hareware have been stopped */
if (!priv->is_suspend)
geth_free_dma_desc(priv);
return 0;
}
static void geth_tx_complete(struct geth_priv *priv)
{
unsigned int entry = 0;
struct sk_buff *skb = NULL;
struct dma_desc *desc = NULL;
int tx_stat;
spin_lock(&priv->tx_lock);
while (circ_cnt(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > 0) {
entry = priv->tx_clean;
desc = priv->dma_tx + entry;
/* Check if the descriptor is owned by the DMA. */
if (desc_get_own(desc))
break;
/* Verify tx error by looking at the last segment */
if (desc_get_tx_ls(desc)) {
tx_stat = desc_get_tx_status(desc, (void *)(&priv->xstats));
if (likely(!tx_stat))
priv->ndev->stats.tx_packets++;
else
priv->ndev->stats.tx_errors++;
}
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
desc_buf_get_len(desc), DMA_TO_DEVICE);
skb = priv->tx_sk[entry];
priv->tx_sk[entry] = NULL;
desc_init(desc);
/* Find next dirty desc */
priv->tx_clean = circ_inc(entry, dma_desc_tx);
if (unlikely(skb == NULL))
continue;
dev_kfree_skb(skb);
}
if (unlikely(netif_queue_stopped(priv->ndev)) &&
circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
TX_THRESH) {
netif_wake_queue(priv->ndev);
}
spin_unlock(&priv->tx_lock);
}
static netdev_tx_t geth_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
unsigned int entry;
struct dma_desc *desc, *first;
unsigned int len, tmp_len = 0;
int i, csum_insert;
int nfrags = skb_shinfo(skb)->nr_frags;
dma_addr_t paddr;
spin_lock(&priv->tx_lock);
if (unlikely(circ_space(priv->tx_dirty, priv->tx_clean,
dma_desc_tx) < (nfrags + 1))) {
if (!netif_queue_stopped(ndev)) {
netdev_err(ndev, "%s: BUG! Tx Ring full when queue awake\n", __func__);
netif_stop_queue(ndev);
}
spin_unlock(&priv->tx_lock);
return NETDEV_TX_BUSY;
}
csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL);
entry = priv->tx_dirty;
first = priv->dma_tx + entry;
desc = priv->dma_tx + entry;
len = skb_headlen(skb);
priv->tx_sk[entry] = skb;
#ifdef PKT_DEBUG
printk("======TX PKT DATA: ============\n");
/* dump the packet */
print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
16, 1, skb->data, 64, true);
#endif
/* Every desc max size is 2K */
while (len != 0) {
desc = priv->dma_tx + entry;
tmp_len = ((len > MAX_BUF_SZ) ? MAX_BUF_SZ : len);
paddr = dma_map_single(priv->dev, skb->data, tmp_len, DMA_TO_DEVICE);
if (dma_mapping_error(priv->dev, paddr)) {
dev_kfree_skb(skb);
return -EIO;
}
desc_buf_set(desc, paddr, tmp_len);
/* Don't set the first's own bit, here */
if (first != desc) {
priv->tx_sk[entry] = NULL;
desc_set_own(desc);
}
entry = circ_inc(entry, dma_desc_tx);
len -= tmp_len;
}
for (i = 0; i < nfrags; i++) {
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
len = skb_frag_size(frag);
desc = priv->dma_tx + entry;
paddr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE);
if (dma_mapping_error(priv->dev, paddr)) {
dev_kfree_skb(skb);
return -EIO;
}
desc_buf_set(desc, paddr, len);
desc_set_own(desc);
priv->tx_sk[entry] = NULL;
entry = circ_inc(entry, dma_desc_tx);
}
ndev->stats.tx_bytes += skb->len;
priv->tx_dirty = entry;
desc_tx_close(first, desc, csum_insert);
desc_set_own(first);
spin_unlock(&priv->tx_lock);
if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) <=
(MAX_SKB_FRAGS + 1)) {
netif_stop_queue(ndev);
if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
TX_THRESH)
netif_wake_queue(ndev);
}
#ifdef DEBUG
printk("=======TX Descriptor DMA: 0x%08llx\n", priv->dma_tx_phy);
printk("Tx pointor: dirty: %d, clean: %d\n", priv->tx_dirty, priv->tx_clean);
desc_print(priv->dma_tx, dma_desc_tx);
#endif
sunxi_tx_poll(priv->base);
geth_tx_complete(priv);
return NETDEV_TX_OK;
}
static int geth_rx(struct geth_priv *priv, int limit)
{
unsigned int rxcount = 0;
unsigned int entry;
struct dma_desc *desc;
struct sk_buff *skb;
int status;
int frame_len;
while (rxcount < limit) {
entry = priv->rx_dirty;
desc = priv->dma_rx + entry;
if (desc_get_own(desc))
break;
rxcount++;
priv->rx_dirty = circ_inc(priv->rx_dirty, dma_desc_rx);
/* Get length & status from hardware */
frame_len = desc_rx_frame_len(desc);
status = desc_get_rx_status(desc, (void *)(&priv->xstats));
netdev_dbg(priv->ndev, "Rx frame size %d, status: %d\n",
frame_len, status);
skb = priv->rx_sk[entry];
if (unlikely(!skb)) {
netdev_err(priv->ndev, "Skb is null\n");
priv->ndev->stats.rx_dropped++;
break;
}
#ifdef PKT_DEBUG
printk("======RX PKT DATA: ============\n");
/* dump the packet */
print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
16, 1, skb->data, 64, true);
#endif
if (status == discard_frame) {
netdev_dbg(priv->ndev, "Get error pkt\n");
priv->ndev->stats.rx_errors++;
continue;
}
if (unlikely(status != llc_snap))
frame_len -= ETH_FCS_LEN;
priv->rx_sk[entry] = NULL;
skb_put(skb, frame_len);
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
desc_buf_get_len(desc), DMA_FROM_DEVICE);
skb->protocol = eth_type_trans(skb, priv->ndev);
skb->ip_summed = CHECKSUM_UNNECESSARY;
napi_gro_receive(&priv->napi, skb);
priv->ndev->stats.rx_packets++;
priv->ndev->stats.rx_bytes += frame_len;
}
#ifdef DEBUG
if (rxcount > 0) {
printk("======RX Descriptor DMA: 0x%08llx=\n", priv->dma_rx_phy);
printk("RX pointor: dirty: %d, clean: %d\n", priv->rx_dirty, priv->rx_clean);
desc_print(priv->dma_rx, dma_desc_rx);
}
#endif
geth_rx_refill(priv->ndev);
return rxcount;
}
static int geth_poll(struct napi_struct *napi, int budget)
{
struct geth_priv *priv = container_of(napi, struct geth_priv, napi);
int work_done = 0;
geth_tx_complete(priv);
work_done = geth_rx(priv, budget);
if (work_done < budget) {
napi_complete(napi);
sunxi_int_enable(priv->base);
}
return work_done;
}
static int geth_change_mtu(struct net_device *ndev, int new_mtu)
{
int max_mtu;
if (netif_running(ndev)) {
pr_err("%s: must be stopped to change its MTU\n", ndev->name);
return -EBUSY;
}
max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);
if ((new_mtu < 46) || (new_mtu > max_mtu)) {
pr_err("%s: invalid MTU, max MTU is: %d\n", ndev->name, max_mtu);
return -EINVAL;
}
ndev->mtu = new_mtu;
netdev_update_features(ndev);
return 0;
}
static netdev_features_t geth_fix_features(struct net_device *ndev,
netdev_features_t features)
{
return features;
}
static void geth_set_rx_mode(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
unsigned int value = 0;
pr_debug("%s: # mcasts %d, # unicast %d\n",
__func__, netdev_mc_count(ndev), netdev_uc_count(ndev));
spin_lock(&priv->lock);
if (ndev->flags & IFF_PROMISC) {
value = GETH_FRAME_FILTER_PR;
} else if ((netdev_mc_count(ndev) > HASH_TABLE_SIZE) ||
(ndev->flags & IFF_ALLMULTI)) {
value = GETH_FRAME_FILTER_PM; /* pass all multi */
sunxi_hash_filter(priv->base, ~0UL, ~0UL);
} else if (!netdev_mc_empty(ndev)) {
u32 mc_filter[2];
struct netdev_hw_addr *ha;
/* Hash filter for multicast */
value = GETH_FRAME_FILTER_HMC;
memset(mc_filter, 0, sizeof(mc_filter));
netdev_for_each_mc_addr(ha, ndev) {
/* The upper 6 bits of the calculated CRC are used to
* index the contens of the hash table
*/
int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26;
/* The most significant bit determines the register to
* use (H/L) while the other 5 bits determine the bit
* within the register.
*/
mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
}
sunxi_hash_filter(priv->base, mc_filter[0], mc_filter[1]);
}
/* Handle multiple unicast addresses (perfect filtering)*/
if (netdev_uc_count(ndev) > 16) {
/* Switch to promiscuous mode is more than 8 addrs are required */
value |= GETH_FRAME_FILTER_PR;
} else {
int reg = 1;
struct netdev_hw_addr *ha;
netdev_for_each_uc_addr(ha, ndev) {
sunxi_set_umac(priv->base, ha->addr, reg);
reg++;
}
}
#ifdef FRAME_FILTER_DEBUG
/* Enable Receive all mode (to debug filtering_fail errors) */
value |= GETH_FRAME_FILTER_RA;
#endif
sunxi_set_filter(priv->base, value);
spin_unlock(&priv->lock);
}
static void geth_tx_timeout(struct net_device *ndev)
{
struct geth_priv *priv = netdev_priv(ndev);
geth_tx_err(priv);
}
static int geth_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
{
if (!netif_running(ndev))
return -EINVAL;
if (!ndev->phydev)
return -EINVAL;
return phy_mii_ioctl(ndev->phydev, rq, cmd);
}
/* Configuration changes (passed on by ifconfig) */
static int geth_config(struct net_device *ndev, struct ifmap *map)
{
if (ndev->flags & IFF_UP) /* can't act on a running interface */
return -EBUSY;
/* Don't allow changing the I/O address */
if (map->base_addr != ndev->base_addr) {
pr_warn("%s: can't change I/O address\n", ndev->name);
return -EOPNOTSUPP;
}
/* Don't allow changing the IRQ */
if (map->irq != ndev->irq) {
pr_warn("%s: can't change IRQ number %d\n", ndev->name, ndev->irq);
return -EOPNOTSUPP;
}
return 0;
}
static int geth_set_mac_address(struct net_device *ndev, void *p)
{
struct geth_priv *priv = netdev_priv(ndev);
struct sockaddr *addr = p;
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len);
sunxi_set_umac(priv->base, ndev->dev_addr, 0);
return 0;
}
int geth_set_features(struct net_device *ndev, netdev_features_t features)
{
struct geth_priv *priv = netdev_priv(ndev);
if (features & NETIF_F_LOOPBACK && netif_running(ndev))
sunxi_mac_loopback(priv->base, 1);
else
sunxi_mac_loopback(priv->base, 0);
return 0;
}
#ifdef CONFIG_NET_POLL_CONTROLLER
/* Polling receive - used by NETCONSOLE and other diagnostic tools
* to allow network I/O with interrupts disabled.
*/
static void geth_poll_controller(struct net_device *dev)
{
disable_irq(dev->irq);
geth_interrupt(dev->irq, dev);
enable_irq(dev->irq);
}
#endif
static const struct net_device_ops geth_netdev_ops = {
.ndo_init = NULL,
.ndo_open = geth_open,
.ndo_start_xmit = geth_xmit,
.ndo_stop = geth_stop,
.ndo_change_mtu = geth_change_mtu,
.ndo_fix_features = geth_fix_features,
.ndo_set_rx_mode = geth_set_rx_mode,
.ndo_tx_timeout = geth_tx_timeout,
.ndo_do_ioctl = geth_ioctl,
.ndo_set_config = geth_config,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = geth_poll_controller,
#endif
.ndo_set_mac_address = geth_set_mac_address,
.ndo_set_features = geth_set_features,
};
static int geth_check_if_running(struct net_device *ndev)
{
if (!netif_running(ndev))
return -EBUSY;
return 0;
}
static int geth_get_sset_count(struct net_device *netdev, int sset)
{
int len;
switch (sset) {
case ETH_SS_STATS:
len = 0;
return len;
default:
return -EOPNOTSUPP;
}
}
static int geth_ethtool_getsettings(struct net_device *ndev,
struct ethtool_cmd *cmd)
{
struct geth_priv *priv = netdev_priv(ndev);
struct phy_device *phy = ndev->phydev;
int rc;
if (phy == NULL) {
netdev_err(ndev, "%s: %s: PHY is not registered\n",
__func__, ndev->name);
return -ENODEV;
}
if (!netif_running(ndev)) {
pr_err("%s: interface is disabled: we cannot track "
"link speed / duplex setting\n", ndev->name);
return -EBUSY;
}
cmd->transceiver = XCVR_INTERNAL;
spin_lock_irq(&priv->lock);
rc = phy_ethtool_gset(phy, cmd);
spin_unlock_irq(&priv->lock);
return rc;
}
static int geth_ethtool_setsettings(struct net_device *ndev,
struct ethtool_cmd *cmd)
{
struct geth_priv *priv = netdev_priv(ndev);
struct phy_device *phy = ndev->phydev;
int rc;
spin_lock(&priv->lock);
rc = phy_ethtool_sset(phy, cmd);
spin_unlock(&priv->lock);
return rc;
}
static void geth_ethtool_getdrvinfo(struct net_device *ndev,
struct ethtool_drvinfo *info)
{
strlcpy(info->driver, "sunxi_geth", sizeof(info->driver));
#define DRV_MODULE_VERSION "SUNXI Gbgit driver V1.1"
strcpy(info->version, DRV_MODULE_VERSION);
info->fw_version[0] = '\0';
}
static const struct ethtool_ops geth_ethtool_ops = {
.begin = geth_check_if_running,
.get_settings = geth_ethtool_getsettings,
.set_settings = geth_ethtool_setsettings,
.get_link = ethtool_op_get_link,
.get_pauseparam = NULL,
.set_pauseparam = NULL,
.get_ethtool_stats = NULL,
.get_strings = NULL,
.get_wol = NULL,
.set_wol = NULL,
.get_sset_count = geth_get_sset_count,
.get_drvinfo = geth_ethtool_getdrvinfo,
};
/* config hardware resource */
static int geth_hw_init(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);
struct geth_priv *priv = netdev_priv(ndev);
struct device_node *np = pdev->dev.of_node;
int ret = 0;
struct resource *res;
u32 value;
struct gpio_config cfg;
const char *gmac_power;
char power[20];
int i;
#ifdef CONFIG_SUNXI_EXT_PHY
priv->phy_ext = EXT_PHY;
#else
priv->phy_ext = INT_PHY;
#endif
/* config memery resource */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (unlikely(!res)) {
pr_err("%s: ERROR: get gmac memory failed", __func__);
return -ENODEV;
}
printk("sunxi-gmac geth_hw_init\n");
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (!priv->base) {
pr_err("%s: ERROR: gmac memory mapping failed", __func__);
return -ENOMEM;
}
printk("sunxi-gmac geth_hw_init base 0x%x\n", priv->base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (unlikely(!res)) {
pr_err("%s: ERROR: get phy memory failed", __func__);
ret = -ENODEV;
goto mem_err;
}
priv->base_phy = devm_ioremap_resource(&pdev->dev, res);
if (unlikely(!priv->base_phy)) {
pr_err("%s: ERROR: phy memory mapping failed", __func__);
ret = -ENOMEM;
goto mem_err;
}
/* config IRQ */
ndev->irq = platform_get_irq_byname(pdev, "gmacirq");
if (ndev->irq == -ENXIO) {
pr_err("%s: ERROR: MAC IRQ not found\n", __func__);
ret = -ENXIO;
goto irq_err;
}
ret = request_irq(ndev->irq, geth_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev);
if (unlikely(ret < 0)) {
pr_err("Could not request irq %d, error: %d\n", ndev->irq, ret);
goto irq_err;
}
/* config clock */
priv->geth_clk = of_clk_get_by_name(np, "gmac");
if (unlikely(!priv->geth_clk || IS_ERR(priv->geth_clk))) {
pr_err("Get gmac clock failed!\n");
ret = -EINVAL;
goto clk_err;
}
if (INT_PHY == priv->phy_ext) {
priv->ephy_clk = of_clk_get_by_name(np, "ephy");
if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
pr_err("Get ephy clock failed!\n");
ret = -EINVAL;
goto clk_err;
}
}
#if defined(CONFIG_ARCH_SUN8IW12) || defined(CONFIG_ARCH_SUN50IW9)
else {
if (!of_property_read_u32(np, "use_ephy25m", &g_use_ephy_clk)
&& g_use_ephy_clk) {
priv->ephy_clk = of_clk_get_by_name(np, "ephy");
if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
pr_err("Get ephy clk failed!\n");
ret = -EINVAL;
goto clk_err;
}
}
}
#endif
/* config power regulator */
if (EXT_PHY == priv->phy_ext) {
for (i = 0; i < POWER_CHAN_NUM; i++) {
snprintf(power, 15, "gmac-power%d", i);
ret = of_property_read_string(np, power, &gmac_power);
if (ret) {
priv->gmac_power[i] = NULL;
pr_info("gmac-power%d: NULL\n", i);
continue;
}
priv->gmac_power[i] = regulator_get(NULL, gmac_power);
if (IS_ERR(priv->gmac_power[i])) {
pr_err("gmac-power%d get error!\n", i);
ret = -EINVAL;
goto clk_err;
}
}
}
/* config other parameters */
priv->phy_interface = of_get_phy_mode(np);
if (priv->phy_interface != PHY_INTERFACE_MODE_MII &&
priv->phy_interface != PHY_INTERFACE_MODE_RGMII &&
priv->phy_interface != PHY_INTERFACE_MODE_RMII) {
pr_err("Not support phy type!\n");
priv->phy_interface = PHY_INTERFACE_MODE_MII;
}
if (!of_property_read_u32(np, "tx-delay", &value))
tx_delay = value;
if (!of_property_read_u32(np, "rx-delay", &value))
rx_delay = value;
/* config pinctrl */
if (EXT_PHY == priv->phy_ext) {
priv->phyrst = of_get_named_gpio_flags(np, "phy-rst", 0, (enum of_gpio_flags *)&cfg);
priv->rst_active_low = (cfg.data == OF_GPIO_ACTIVE_LOW) ? 1 : 0;
if (gpio_is_valid(priv->phyrst)) {
if (gpio_request(priv->phyrst, "phy-rst") < 0) {
pr_err("gmac gpio request fail!\n");
ret = -EINVAL;
goto pin_err;
}
}
priv->pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR_OR_NULL(priv->pinctrl)) {
pr_err("gmac pinctrl error!\n");
priv->pinctrl = NULL;
ret = -EINVAL;
goto pin_err;
}
}
return 0;
pin_err:
if (EXT_PHY == priv->phy_ext) {
for (i = 0; i < POWER_CHAN_NUM; i++) {
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
continue;
regulator_put(priv->gmac_power[i]);
}
}
clk_err:
free_irq(ndev->irq, ndev);
irq_err:
devm_iounmap(&pdev->dev, priv->base_phy);
mem_err:
devm_iounmap(&pdev->dev, priv->base);
return ret;
}
static void geth_hw_release(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);
struct geth_priv *priv = netdev_priv(ndev);
int i;
devm_iounmap(&pdev->dev, (priv->base_phy));
devm_iounmap(&pdev->dev, priv->base);
free_irq(ndev->irq, ndev);
if (priv->geth_clk)
clk_put(priv->geth_clk);
if (EXT_PHY == priv->phy_ext) {
for (i = 0; i < POWER_CHAN_NUM; i++) {
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
continue;
regulator_put(priv->gmac_power[i]);
}
if (!IS_ERR_OR_NULL(priv->pinctrl))
devm_pinctrl_put(priv->pinctrl);
if (gpio_is_valid(priv->phyrst))
gpio_free(priv->phyrst);
}
if (!IS_ERR_OR_NULL(priv->ephy_clk))
clk_put(priv->ephy_clk);
}
extern int gpioInit(struct platform_device *pdev, const char* name);
/**
* geth_probe
* @pdev: platform device pointer
* Description: the driver is initialized through platform_device.
*/
static int geth_probe(struct platform_device *pdev)
{
int ret = 0;
struct net_device *ndev = NULL;
struct geth_priv *priv;
struct gpio_desc *gpiod_reset;
#ifdef CONFIG_OF
pdev->dev.dma_mask = &geth_dma_mask;
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
#endif
printk("sunxi-gmac probe\n");
ndev = alloc_etherdev(sizeof(struct geth_priv));
if (!ndev) {
dev_err(&pdev->dev, "could not allocate device.\n");
return -ENOMEM;
}
SET_NETDEV_DEV(ndev, &pdev->dev);
priv = netdev_priv(ndev);
platform_set_drvdata(pdev, ndev);
priv->avcc = regulator_get(&pdev->dev, "avcc");
if (IS_ERR(priv->avcc)) {
pr_err("[%s]:get geth avcc failed\n", __func__);
priv->avcc = NULL;
} else {
ret = regulator_enable(priv->avcc);
if (ret) {
pr_err("[%s]:geth avcc enable failed!\n", __func__);
//goto err_put_regulator;
}else{
pr_err("[%s]:geth avcc enable success!\n", __func__);
}
}
/******phy reset, that can be discovered *****/
printk("geth_probe reset set high, gpioInit\n");
gpioInit(pdev, "phy_reset");
/******phy reset, that can be discovered end*****/
/* Must set private data to pdev, before call it */
ret = geth_hw_init(pdev);
if (0 != ret) {
pr_err("geth_hw_init fail!\n");
goto hw_err;
}
/* setup the netdevice, fill the field of netdevice */
ether_setup(ndev);
ndev->netdev_ops = &geth_netdev_ops;
netdev_set_default_ethtool_ops(ndev, &geth_ethtool_ops);
ndev->base_addr = (unsigned long)priv->base;
priv->ndev = ndev;
priv->dev = &pdev->dev;
/* TODO: support the VLAN frames */
ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM |
NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM;
ndev->features |= ndev->hw_features;
ndev->hw_features |= NETIF_F_LOOPBACK;
ndev->priv_flags |= IFF_UNICAST_FLT;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
netif_napi_add(ndev, &priv->napi, geth_poll, BUDGET);
spin_lock_init(&priv->lock);
spin_lock_init(&priv->tx_lock);
printk("sunxi-gmac probe 1\n");
/* The last val is mdc clock ratio */
sunxi_geth_register((void *)ndev->base_addr, HW_VERSION, 0x03);
ret = register_netdev(ndev);
if (ret) {
netif_napi_del(&priv->napi);
pr_err("Error: Register %s failed\n", ndev->name);
goto reg_err;
}
/* Before open the device, the mac address should be set */
geth_check_addr(ndev, mac_str);
#ifdef CONFIG_GETH_ATTRS
geth_create_attrs(ndev);
#endif
device_create_file(&pdev->dev, &dev_attr_gphy_test);
device_enable_async_suspend(&pdev->dev);
#ifdef CONFIG_PM
INIT_WORK(&priv->eth_work, geth_resume_work);
#endif
printk("sunxi-gmac probe end\n");
return 0;
reg_err:
geth_hw_release(pdev);
hw_err:
platform_set_drvdata(pdev, NULL);
free_netdev(ndev);
return ret;
}
static int geth_remove(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);
struct geth_priv *priv = netdev_priv(ndev);
device_remove_file(&pdev->dev, &dev_attr_gphy_test);
netif_napi_del(&priv->napi);
unregister_netdev(ndev);
geth_hw_release(pdev);
platform_set_drvdata(pdev, NULL);
free_netdev(ndev);
return 0;
}
static const struct of_device_id geth_of_match[] = {
{.compatible = "allwinner,sunxi-gmac" },
{},
};
//MODULE_DEVICE_TABLE(of, geth_of_match);
static struct platform_driver geth_driver = {
.probe = geth_probe,
.remove = geth_remove,
.driver = {
.name = "sunxi-gmac",
.owner = THIS_MODULE,
.pm = &geth_pm_ops,
.of_match_table = geth_of_match,
},
};
module_platform_driver(geth_driver);
/*
static int __init sunxi_gmac_init(void)
{
printk("sunxi_gmac_init\n");
return platform_driver_register(&geth_driver);
}
static void __exit sunxi_gmac_exit(void)
{
printk("sunxi_gmac_exit\n");
platform_driver_unregister(&geth_driver);
}
module_init(sunxi_gmac_init);
module_exit(sunxi_gmac_exit);
#ifndef MODULE
static int __init set_mac_addr(char *str)
{
char *p = str;
if (str && strlen(str))
memcpy(mac_str, p, 18);
return 0;
}
__setup("mac_addr=", set_mac_addr);
#endif
*/
MODULE_DESCRIPTION("Allwinner Gigabit Ethernet driver");
MODULE_AUTHOR("fuzhaoke <fuzhaoke@allwinnertech.com>");
MODULE_LICENSE("Dual BSD/GPL");
2.1.2 phy 驱动
路径:kernel/linux-4.9/drivers/net/phy/motorcomm.c
// SPDX-License-Identifier: GPL-2.0+
/*
* drivers/net/phy/motorcomm.c
*
* Driver for Motorcomm PHYs
*
* Author: yinghong.zhang<yinghong.zhang@motor-comm.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, yt8531, yt8614, yt8618
* 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/of.h>
#include <linux/clk.h>
#ifndef LINUX_VERSION_CODE
#include <linux/version.h>
#else
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
#endif
/* for wol feature */
#include <linux/netdevice.h>
#define YT_LINUX_MAJOR 2
#define YT_LINUX_MINOR 2
#define YT_LINUX_SUBVERSION 21925
#define YT_LINUX_VERSIONID "2.2.21925"
/********************************************
**** 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 feature of phy, please define this macro to 1
* otherwise, define it to 0.
*/
#define YTPHY_WOL_FEATURE_ENABLE 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 0
/* the max number of yt8521 chip on pcb board
* the most case is only 1 chip per board, but
* by default, we support up to 8.
*/
#define YTPHY_BOARD_MAX_NUM_OF_CHIP_8521 8
#define YTPHY_BOARD_MAX_NUM_OF_CHIP_8614 4
/* for YT8531 package A xtal init config */
#define YTPHY8531A_XTAL_INIT (1)
/**** configuration section end ***********
******************************************/
/* no need to change below */
#define MOTORCOMM_PHY_ID_MASK 0xffffffff
#define PHY_ID_YT8010 0x00000309
#define PHY_ID_YT8010AS 0x4f51eb19
#define PHY_ID_YT8011 0x4f51eb01
#define PHY_ID_YT8510 0x00000109
#define PHY_ID_YT8511 0x0000010a
#define PHY_ID_YT8512 0x00000128
#define PHY_ID_YT8522 0x4f51e928
#define PHY_ID_YT8521 0x0000011a
#define PHY_ID_YT8531S 0x4f51e91a
#define PHY_ID_YT8531 0x4f51e91b
#define PHY_ID_YT8614 0x4f51e899
#define PHY_ID_YT8614Q 0x4f51e8a9
#define PHY_ID_YT8618 0x0000e889
#define PHY_ID_YT8821 0x4f51ea19
#define REG_PHY_SPEC_STATUS 0x11
#define REG_DEBUG_ADDR_OFFSET 0x1e
#define REG_DEBUG_DATA 0x1f
#define REG_MII_MMD_CTRL 0x0D /* MMD access control register */
#define REG_MII_MMD_DATA 0x0E /* MMD access data register */
#define YT8512_EXTREG_LED0 0x40c0
#define YT8512_EXTREG_LED1 0x40c3
#define YT8512_EXTREG_SLEEP_CONTROL1 0x2027
#define YT_SOFTWARE_RESET 0x8000
#define YT8512_LED0_ACT_BLK_IND 0x1000
#define YT8512_LED0_DIS_LED_AN_TRY 0x0001
#define YT8512_LED0_BT_BLK_EN 0x0002
#define YT8512_LED0_HT_BLK_EN 0x0004
#define YT8512_LED0_COL_BLK_EN 0x0008
#define YT8512_LED0_BT_ON_EN 0x0010
#define YT8512_LED1_BT_ON_EN 0x0010
#define YT8512_LED1_TXACT_BLK_EN 0x0100
#define YT8512_LED1_RXACT_BLK_EN 0x0200
#define YT8512_SPEED_MODE 0xc000
#define YT8512_DUPLEX 0x2000
#define YT8512_SPEED_MODE_BIT 14
#define YT8512_DUPLEX_BIT 13
#define YT8512_EN_SLEEP_SW_BIT 15
#define YT8522_TX_CLK_DELAY 0x4210
#define YT8522_ANAGLOG_IF_CTRL 0x4008
#define YT8522_DAC_CTRL 0x2057
#define YT8522_INTERPOLATOR_FILTER_1 0x14
#define YT8522_INTERPOLATOR_FILTER_2 0x15
#define YT8522_EXTENDED_COMBO_CTRL_1 0x4000
#define YT8521_EXTREG_SLEEP_CONTROL1 0x27
#define YT8521_EN_SLEEP_SW_BIT 15
#define YTXXXX_SPEED_MODE 0xc000
#define YTXXXX_DUPLEX 0x2000
#define YTXXXX_SPEED_MODE_BIT 14
#define YTXXXX_DUPLEX_BIT 13
#define YTXXXX_LINK_STATUS_BIT 10
/* based on yt8521 wol feature config register */
#define YTPHY_UTP_INTR_REG 0x12
/* WOL Feature Event Interrupt Enable */
#define YTPHY_WOL_FEATURE_INTR BIT(6)
/* Magic Packet MAC address registers */
#define YTPHY_WOL_FEATURE_MACADDR2_4_MAGIC_PACKET 0xa007
#define YTPHY_WOL_FEATURE_MACADDR1_4_MAGIC_PACKET 0xa008
#define YTPHY_WOL_FEATURE_MACADDR0_4_MAGIC_PACKET 0xa009
#define YTPHY_WOL_FEATURE_REG_CFG 0xa00a
#define YTPHY_WOL_FEATURE_TYPE_CFG BIT(0) /* WOL TYPE Config */
#define YTPHY_WOL_FEATURE_ENABLE_CFG BIT(3) /* WOL Enable Config */
#define YTPHY_WOL_FEATURE_INTR_SEL_CFG BIT(6) /* WOL Event Interrupt Enable Config */
#define YTPHY_WOL_FEATURE_WIDTH1_CFG BIT(1) /* WOL Pulse Width Config */
#define YTPHY_WOL_FEATURE_WIDTH2_CFG BIT(2) /* WOL Pulse Width Config */
#define YTPHY_REG_SPACE_UTP 0
#define YTPHY_REG_SPACE_FIBER 2
enum ytphy_wol_feature_trigger_type_e {
YTPHY_WOL_FEATURE_PULSE_TRIGGER,
YTPHY_WOL_FEATURE_LEVEL_TRIGGER,
YTPHY_WOL_FEATURE_TRIGGER_TYPE_MAX
};
enum ytphy_wol_feature_pulse_width_e {
YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH,
YTPHY_WOL_FEATURE_336MS_PULSE_WIDTH,
YTPHY_WOL_FEATURE_168MS_PULSE_WIDTH,
YTPHY_WOL_FEATURE_84MS_PULSE_WIDTH,
YTPHY_WOL_FEATURE_PULSE_WIDTH_MAX
};
struct ytphy_wol_feature_cfg {
bool enable;
int type;
int width;
};
#if (YTPHY_WOL_FEATURE_ENABLE)
#undef SYS_WAKEUP_BASED_ON_ETH_PKT
#define SYS_WAKEUP_BASED_ON_ETH_PKT 1
#endif
struct yt8xxx_priv {
u8 polling_mode;
u8 chip_mode;
};
/* polling mode */
#define YT_PHY_MODE_FIBER 1 //fiber mode only
#define YT_PHY_MODE_UTP 2 //utp mode only
#define YT_PHY_MODE_POLL (YT_PHY_MODE_FIBER | YT_PHY_MODE_UTP)
/* support automatically check polling mode for yt8521
* 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 YTPHY_861X_ABC_VER 0
#if (YTPHY_861X_ABC_VER)
static int yt8614_get_port_from_phydev(struct phy_device *phydev);
#endif
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
static int ytphy_config_init(struct phy_device *phydev)
{
int val;
val = phy_read(phydev, 3);
return 0;
}
#endif
#if (KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE)
static inline void phy_lock_mdio_bus(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
mutex_lock(&phydev->bus->mdio_lock);
#else
mutex_lock(&phydev->mdio.bus->mdio_lock);
#endif
}
static inline void phy_unlock_mdio_bus(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->bus->mdio_lock);
#else
mutex_unlock(&phydev->mdio.bus->mdio_lock);
#endif
}
#endif
#if (KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE)
static inline int __phy_read(struct phy_device *phydev, u32 regnum)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct mii_bus *bus = phydev->bus;
int addr = phydev->addr;
return bus->read(bus, phydev->addr, regnum);
#else
struct mii_bus *bus = phydev->mdio.bus;
int addr = phydev->mdio.addr;
#endif
return bus->read(bus, addr, regnum);
}
static inline int __phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct mii_bus *bus = phydev->bus;
int addr = phydev->addr;
#else
struct mii_bus *bus = phydev->mdio.bus;
int addr = phydev->mdio.addr;
#endif
return bus->write(bus, addr, regnum, val);
}
#endif
static int ytphy_read_ext(struct phy_device *phydev, u32 regnum)
{
int ret;
phy_lock_mdio_bus(phydev);
ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
if (ret < 0)
goto err_handle;
ret = __phy_read(phydev, REG_DEBUG_DATA);
if (ret < 0)
goto err_handle;
err_handle:
phy_unlock_mdio_bus(phydev);
return ret;
}
static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val)
{
int ret;
phy_lock_mdio_bus(phydev);
ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
if (ret < 0)
goto err_handle;
ret = __phy_write(phydev, REG_DEBUG_DATA, val);
if (ret < 0)
goto err_handle;
err_handle:
phy_unlock_mdio_bus(phydev);
return ret;
}
__attribute__((unused)) static int ytphy_read_mmd(struct phy_device* phydev, u16 device, u16 reg)
{
int val;
phy_lock_mdio_bus(phydev);
__phy_write(phydev, REG_MII_MMD_CTRL, device);
__phy_write(phydev, REG_MII_MMD_DATA, reg);
__phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
val = __phy_read(phydev, REG_MII_MMD_DATA);
if (val < 0) {
dev_err(&phydev->mdio.dev, "error read mmd device(%u) reg (%u)\n", device, reg);
goto err_handle;
}
err_handle:
phy_unlock_mdio_bus(phydev);
return val;
}
__attribute__((unused)) static int ytphy_write_mmd(struct phy_device* phydev, u16 device, u16 reg, u16 value)
{
int ret = 0;
phy_lock_mdio_bus(phydev);
__phy_write(phydev, REG_MII_MMD_CTRL, device);
__phy_write(phydev, REG_MII_MMD_DATA, reg);
__phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
__phy_write(phydev, REG_MII_MMD_DATA, value);
phy_unlock_mdio_bus(phydev);
return ret;
}
static int ytphy_soft_reset(struct phy_device *phydev)
{
int ret = 0, val = 0;
val = phy_read(phydev, MII_BMCR);
if (val < 0)
return val;
ret = phy_write(phydev, MII_BMCR, val | BMCR_RESET);
if (ret < 0)
return ret;
return ret;
}
#if (YTPHY8531A_XTAL_INIT)
static int yt8531a_xtal_init(struct phy_device *phydev)
{
int ret = 0;
int val = 0;
static bool state = false;
#define YTPHY_WHILE_MAX_CNT 5
static uint32_t max_cnt = YTPHY_WHILE_MAX_CNT;
if (!state)
{
msleep(50);
do
{
ret = ytphy_write_ext(phydev, 0xa012, 0x88);
if (ret < 0)
return ret;
msleep(100);
val = ytphy_read_ext(phydev, 0xa012);
if (val < 0)
return val;
msleep(10);
}while((0x88 != val) && (max_cnt-- != 0));
ret = ytphy_write_ext(phydev, 0xa012, 0xd0);
if (ret < 0)
return ret;
state = !state;
}
return ret;
}
#endif
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8010_soft_reset(struct phy_device *phydev)
{
ytphy_soft_reset(phydev);
return 0;
}
static int yt8010AS_soft_reset(struct phy_device *phydev)
{
int ret = 0;
/* sgmii */
ytphy_write_ext(phydev, 0xe, 1);
ret = ytphy_soft_reset(phydev);
if (ret < 0) {
ytphy_write_ext(phydev, 0xe, 0);
return ret;
}
/* utp */
ytphy_write_ext(phydev, 0xe, 0);
ret = ytphy_soft_reset(phydev);
if (ret < 0)
return ret;
return 0;
}
#endif
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
static int yt8010_aneg_done(struct phy_device *phydev)
{
int val = 0;
val = phy_read(phydev, 0x1);
val = phy_read(phydev, 0x1);
return (val < 0) ? val : (val & BMSR_LSTATUS);
}
#endif
static int yt8010_config_init(struct phy_device *phydev)
{
int val;
phydev->autoneg = AUTONEG_DISABLE;
ytphy_write_ext(phydev, 0x1023, 0xfc00);
ytphy_write_ext(phydev, 0x101d, 0x12c0);
val = ytphy_read_ext(phydev, 0x1000);
val &= ~(1 << 7);
ytphy_write_ext(phydev, 0x1000, val);
ytphy_write_ext(phydev, 0x101d, 0x12c0);
ytphy_write_ext(phydev, 0x101e, 0x1900);
ytphy_write_ext(phydev, 0x101f, 0x1900);
ytphy_write_ext(phydev, 0x4083, 0x4327);
ytphy_write_ext(phydev, 0x4082, 0xc20);
ytphy_soft_reset(phydev);
return 0;
}
static int yt8010_config_aneg(struct phy_device *phydev)
{
phydev->speed = SPEED_100;
return 0;
}
static int yt8010_read_status(struct phy_device *phydev)
{
int ret = 0;
ret = genphy_update_link(phydev);
if (ret)
return ret;
/* for 8010, no definition mii reg 0x04, 0x11, here force 100/full */
phydev->speed = SPEED_100;
phydev->duplex = DUPLEX_FULL;
return 0;
}
static int yt8010AS_config_init(struct phy_device *phydev)
{
phydev->autoneg = AUTONEG_DISABLE;
ytphy_write_ext(phydev, 0x1009, 0x0);
return 0;
}
static int yt8011_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct device *dev = &phydev->dev;
#else
struct device *dev = &phydev->mdio.dev;
#endif
struct yt8xxx_priv *priv;
int chip_config;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
/* ext reg 0x9030 bit0
* 0 = chip works in RGMII mode; 1 = chip works in SGMII mode
*/
chip_config = ytphy_read_ext(phydev, 0x9030);
priv->chip_mode = chip_config & 0x1;
return 0;
}
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
#else
static int yt8011_soft_reset(struct phy_device *phydev)
{
struct yt8xxx_priv *priv = phydev->priv;
/* utp */
ytphy_write_ext(phydev, 0x9000, 0x0);
ytphy_soft_reset(phydev);
if (priv->chip_mode) /* sgmii */
{
ytphy_write_ext(phydev, 0x9000, 0x8000);
ytphy_soft_reset(phydev);
/* restore utp space */
ytphy_write_ext(phydev, 0x9000, 0x0);
}
return 0;
}
#endif
static int yt8011_config_aneg(struct phy_device *phydev)
{
phydev->speed = SPEED_1000;
return 0;
}
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
static int yt8011_aneg_done(struct phy_device *phydev)
{
int link_utp = 0;
/* UTP */
ytphy_write_ext(phydev, 0x9000, 0);
link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n", __func__, phydev->addr, link_utp);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n", __func__, phydev->mdio.addr, link_utp);
#endif
return !!(link_utp);
}
#endif
/* #define YT8011_RGMII_DVDDIO_3V3 */
/* #define YT8011_RGMII_DVDDIO_2V5 */
/* #define YT8011_RGMII_DVDDIO_1V8 */
static int yt8011_config_init(struct phy_device *phydev)
{
struct yt8xxx_priv *priv = phydev->priv;
phydev->autoneg = AUTONEG_DISABLE;
/* UTP */
ytphy_write_ext(phydev, 0x9000, 0x0);
ytphy_write_ext(phydev, 0x1008, 0x2119);
ytphy_write_ext(phydev, 0x1092, 0x712);
ytphy_write_ext(phydev, 0x90bc, 0x6661);
ytphy_write_ext(phydev, 0x90b9, 0x620b);
ytphy_write_ext(phydev, 0x2001, 0x6418);
ytphy_write_ext(phydev, 0x1019, 0x3712);
ytphy_write_ext(phydev, 0x101a, 0x3713);
ytphy_write_ext(phydev, 0x2015, 0x1012);
ytphy_write_ext(phydev, 0x2005, 0x810);
ytphy_write_ext(phydev, 0x2013, 0xff06);
ytphy_write_ext(phydev, 0x1000, 0x0028);
ytphy_write_ext(phydev, 0x1002, 0x3800);
ytphy_write_ext(phydev, 0x1090, 0x1012);
ytphy_write_ext(phydev, 0x1091, 0x1013);
ytphy_write_ext(phydev, 0x1100, 0x2020);
ytphy_write_mmd(phydev, 0x7, 0x1f42, 0x0016);
ytphy_write_ext(phydev, 0x1088, 0x002b);
ytphy_write_ext(phydev, 0x1088, 0x022b);
ytphy_write_ext(phydev, 0x1088, 0x020b);
ytphy_write_ext(phydev, 0x3008, 0x143);
ytphy_write_ext(phydev, 0x3009, 0x1918);
if (!(priv->chip_mode)) /* rgmii config */
{
#if defined (YT8011_RGMII_DVDDIO_3V3)
ytphy_write_ext(phydev, 0x9000, 0x8000);
ytphy_write_ext(phydev, 0x0062, 0x0000);
ytphy_write_ext(phydev, 0x9000, 0x0000);
ytphy_write_ext(phydev, 0x9031, 0xb200);
ytphy_write_ext(phydev, 0x903b, 0x0040);
ytphy_write_ext(phydev, 0x903e, 0x3b3b);
ytphy_write_ext(phydev, 0x903c, 0xf);
ytphy_write_ext(phydev, 0x903d, 0x1000);
ytphy_write_ext(phydev, 0x9038, 0x0000);
#elif defined (YT8011_RGMII_DVDDIO_2V5)
ytphy_write_ext(phydev, 0x9000, 0x8000);
ytphy_write_ext(phydev, 0x0062, 0x0000);
ytphy_write_ext(phydev, 0x9000, 0x0000);
ytphy_write_ext(phydev, 0x9031, 0xb200);
ytphy_write_ext(phydev, 0x9111, 0x5);
ytphy_write_ext(phydev, 0x9114, 0x3939);
ytphy_write_ext(phydev, 0x9112, 0xf);
ytphy_write_ext(phydev, 0x9110, 0x0);
ytphy_write_ext(phydev, 0x9113, 0x10);
ytphy_write_ext(phydev, 0x903d, 0x2);
#elif defined (YT8011_RGMII_DVDDIO_1V8)
ytphy_write_ext(phydev, 0x9000, 0x8000);
ytphy_write_ext(phydev, 0x0062, 0x0000);
ytphy_write_ext(phydev, 0x9000, 0x0000);
ytphy_write_ext(phydev, 0x9031, 0xb200);
ytphy_write_ext(phydev, 0x9116, 0x6);
ytphy_write_ext(phydev, 0x9119, 0x3939);
ytphy_write_ext(phydev, 0x9117, 0xf);
ytphy_write_ext(phydev, 0x9115, 0x0);
ytphy_write_ext(phydev, 0x9118, 0x20);
ytphy_write_ext(phydev, 0x903d, 0x3);
#endif
}
ytphy_soft_reset(phydev);
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif
return 0;
}
static int ytxxxx_automotive_adjust_status(struct phy_device *phydev, int val)
{
int speed_mode;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
int speed = -1;
#else
int speed = SPEED_UNKNOWN;
#endif
speed_mode = (val & YTXXXX_SPEED_MODE) >> YTXXXX_SPEED_MODE_BIT;
switch (speed_mode) {
case 1:
speed = SPEED_100;
break;
case 2:
speed = SPEED_1000;
break;
default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
speed = -1;
#else
speed = SPEED_UNKNOWN;
#endif
break;
}
phydev->speed = speed;
phydev->duplex = DUPLEX_FULL;
return 0;
}
static int yt8011_read_status(struct phy_device *phydev)
{
int ret;
int val;
int link;
int link_utp = 0;
/* UTP */
ret = ytphy_write_ext(phydev, 0x9000, 0x0);
if (ret < 0)
return ret;
val = phy_read(phydev, REG_PHY_SPEC_STATUS);
if (val < 0)
return val;
link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
if (link) {
link_utp = 1;
ytxxxx_automotive_adjust_status(phydev, val);
} else {
link_utp = 0;
}
if (link_utp) {
if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
__func__, phydev->addr, (unsigned int)val);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
__func__, phydev->mdio.addr, (unsigned int)val);
#endif
phydev->link = 1;
} else {
if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
phydev->link = 0;
}
if (link_utp)
ytphy_write_ext(phydev, 0x9000, 0x0);
return 0;
}
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_EXTREG_LED1, val);
return ret;
}
static int yt8512_config_init(struct phy_device *phydev)
{
int ret;
int val;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
ret = ytphy_config_init(phydev);
#else
ret = genphy_config_init(phydev);
#endif
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
speed = -1;
#else
speed = SPEED_UNKNOWN;
#endif
break;
}
phydev->speed = speed;
phydev->duplex = duplex;
return 0;
}
static int yt8522_read_status(struct phy_device *phydev)
{
int val;
int speed, speed_mode, duplex;
val = phy_read(phydev, REG_PHY_SPEC_STATUS);
if (val < 0)
return val;
if ((val & BIT(10)) >> YTXXXX_LINK_STATUS_BIT) { /* link up */
duplex = (val & BIT(13)) >> YTXXXX_DUPLEX_BIT;
speed_mode = (val & (BIT(15) | BIT(14))) >> YTXXXX_SPEED_MODE_BIT;
switch (speed_mode) {
case 0:
speed = SPEED_10;
break;
case 1:
speed = SPEED_100;
break;
case 2:
case 3:
default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
speed = -1;
#else
speed = SPEED_UNKNOWN;
#endif
break;
}
phydev->link = 1;
phydev->speed = speed;
phydev->duplex = duplex;
return 0;
}
phydev->link = 0;
return 0;
}
static int yt8522_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct device *dev = &phydev->dev;
#else
struct device *dev = &phydev->mdio.dev;
#endif
struct yt8xxx_priv *priv;
int chip_config;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
chip_config = ytphy_read_ext(phydev, YT8522_EXTENDED_COMBO_CTRL_1);
priv->chip_mode = ((chip_config & BIT(3)) >> 3);
return 0;
}
static int yt8522_config_init(struct phy_device *phydev)
{
int ret;
int val;
struct yt8xxx_priv *priv = phydev->priv;
if (!priv->chip_mode) { /* UTP */
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
ret = ytphy_config_init(phydev);
#else
ret = genphy_config_init(phydev);
#endif
if (ret < 0)
return ret;
val = ytphy_write_ext(phydev, YT8522_TX_CLK_DELAY, 0);
if (val < 0)
return val;
val = ytphy_write_ext(phydev, YT8522_ANAGLOG_IF_CTRL, 0xbf2a);
if (val < 0)
return val;
val = ytphy_write_ext(phydev, YT8522_DAC_CTRL, 0x297f);
if (val < 0)
return val;
val = ytphy_write_ext(phydev, YT8522_INTERPOLATOR_FILTER_1, 0x1FE);
if (val < 0)
return val;
val = ytphy_write_ext(phydev, YT8522_INTERPOLATOR_FILTER_2, 0x1FE);
if (val < 0)
return val;
/* 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 0;
}
static int yt8521_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct device *dev = &phydev->dev;
#else
struct device *dev = &phydev->mdio.dev;
#endif
struct yt8xxx_priv *priv;
int chip_config;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
chip_config = ytphy_read_ext(phydev, 0xa001);
priv->chip_mode = chip_config & 0x7;
switch (priv->chip_mode) {
case 1: //fiber<>rgmii
case 4:
case 5:
priv->polling_mode = YT_PHY_MODE_FIBER;
break;
case 2: //utp/fiber<>rgmii
case 6:
case 7:
priv->polling_mode = YT_PHY_MODE_POLL;
break;
case 3: //utp<>sgmii
case 0: //utp<>rgmii
default:
priv->polling_mode = YT_PHY_MODE_UTP;
break;
}
return 0;
}
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8521_soft_reset(struct phy_device *phydev)
{
int val;
struct yt8xxx_priv *priv = phydev->priv;
if (priv->polling_mode == YT_PHY_MODE_UTP) {
ytphy_write_ext(phydev, 0xa000, 0);
ytphy_soft_reset(phydev);
}
if (priv->polling_mode == YT_PHY_MODE_FIBER) {
ytphy_write_ext(phydev, 0xa000, 2);
ytphy_soft_reset(phydev);
ytphy_write_ext(phydev, 0xa000, 0);
}
if (priv->polling_mode == YT_PHY_MODE_POLL) {
val = ytphy_read_ext(phydev, 0xa001);
ytphy_write_ext(phydev, 0xa001, (val & ~0x8000));
ytphy_write_ext(phydev, 0xa000, 0);
ytphy_soft_reset(phydev);
}
return 0;
}
#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;
}
static 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);
return ret;
}
static int yt8511_config_out_125m(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[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);
#ifdef YT_8511_INIT_TO_MASTER
/* for customer, please enable it based on demand.
* configure to master
*/
/* master/slave config reg*/
val = bus->read(bus, phy_id, 0x9);
/* to be manual config and force to be master */
val |= (0x3<<11);
/* take effect until phy soft reset */
ret = bus->write(bus, phy_id, 0x9, val);
if (ret < 0)
return ret;
#endif
return ret;
}
static int yt8511_config_init(struct phy_device *phydev)
{
int ret;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
ret = ytphy_config_init(phydev);
#else
ret = genphy_config_init(phydev);
#endif
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif
return ret;
}
#endif /* GMAC_CLOCK_INPUT_NEEDED */
#if (YTPHY_WOL_FEATURE_ENABLE)
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_feature_enable_cfg(struct phy_device *phydev,
struct ytphy_wol_feature_cfg wol_cfg)
{
int ret = 0;
int val = 0;
val = ytphy_read_ext(phydev, YTPHY_WOL_FEATURE_REG_CFG);
if (val < 0)
return val;
if (wol_cfg.enable) {
val |= YTPHY_WOL_FEATURE_ENABLE_CFG;
if (wol_cfg.type == YTPHY_WOL_FEATURE_LEVEL_TRIGGER) {
val &= ~YTPHY_WOL_FEATURE_TYPE_CFG;
val &= ~YTPHY_WOL_FEATURE_INTR_SEL_CFG;
} else if (wol_cfg.type == YTPHY_WOL_FEATURE_PULSE_TRIGGER) {
val |= YTPHY_WOL_FEATURE_TYPE_CFG;
val |= YTPHY_WOL_FEATURE_INTR_SEL_CFG;
if (wol_cfg.width == YTPHY_WOL_FEATURE_84MS_PULSE_WIDTH) {
val &= ~YTPHY_WOL_FEATURE_WIDTH1_CFG;
val &= ~YTPHY_WOL_FEATURE_WIDTH2_CFG;
} else if (wol_cfg.width == YTPHY_WOL_FEATURE_168MS_PULSE_WIDTH) {
val |= YTPHY_WOL_FEATURE_WIDTH1_CFG;
val &= ~YTPHY_WOL_FEATURE_WIDTH2_CFG;
} else if (wol_cfg.width == YTPHY_WOL_FEATURE_336MS_PULSE_WIDTH) {
val &= ~YTPHY_WOL_FEATURE_WIDTH1_CFG;
val |= YTPHY_WOL_FEATURE_WIDTH2_CFG;
} else if (wol_cfg.width == YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH) {
val |= YTPHY_WOL_FEATURE_WIDTH1_CFG;
val |= YTPHY_WOL_FEATURE_WIDTH2_CFG;
}
}
} else {
val &= ~YTPHY_WOL_FEATURE_ENABLE_CFG;
val &= ~YTPHY_WOL_FEATURE_INTR_SEL_CFG;
}
ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_REG_CFG, val);
if (ret < 0)
return ret;
return 0;
}
static void ytphy_wol_feature_get(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_FEATURE_REG_CFG);
if (val < 0)
return;
if (val & YTPHY_WOL_FEATURE_ENABLE_CFG)
wol->wolopts |= WAKE_MAGIC;
//return;
}
static int ytphy_wol_feature_set(struct phy_device *phydev,
struct ethtool_wolinfo *wol)
{
int ret, curr_reg_space, val;
struct ytphy_wol_feature_cfg wol_cfg;
struct net_device *p_attached_dev = phydev->attached_dev;
memset(&wol_cfg, 0, sizeof(struct ytphy_wol_feature_cfg));
curr_reg_space = ytphy_read_ext(phydev, 0xa000);
if (curr_reg_space < 0)
return curr_reg_space;
/* 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 feature interrupt */
val = phy_read(phydev, YTPHY_UTP_INTR_REG);
val |= YTPHY_WOL_FEATURE_INTR;
ret = phy_write(phydev, YTPHY_UTP_INTR_REG, val);
if (ret < 0)
return ret;
/* Set the WOL feature config */
wol_cfg.enable = true;
wol_cfg.type = YTPHY_WOL_FEATURE_PULSE_TRIGGER;
wol_cfg.width = YTPHY_WOL_FEATURE_672MS_PULSE_WIDTH;
ret = ytphy_wol_feature_enable_cfg(phydev, wol_cfg);
if (ret < 0)
return ret;
/* Store the device address for the magic packet */
ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR2_4_MAGIC_PACKET,
((p_attached_dev->dev_addr[0] << 8) |
p_attached_dev->dev_addr[1]));
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR1_4_MAGIC_PACKET,
((p_attached_dev->dev_addr[2] << 8) |
p_attached_dev->dev_addr[3]));
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, YTPHY_WOL_FEATURE_MACADDR0_4_MAGIC_PACKET,
((p_attached_dev->dev_addr[4] << 8) |
p_attached_dev->dev_addr[5]));
if (ret < 0)
return ret;
} else {
wol_cfg.enable = false;
wol_cfg.type = YTPHY_WOL_FEATURE_TRIGGER_TYPE_MAX;
wol_cfg.width = YTPHY_WOL_FEATURE_PULSE_WIDTH_MAX;
ret = ytphy_wol_feature_enable_cfg(phydev, wol_cfg);
if (ret < 0)
return ret;
}
/* Recover to previous register space page */
ret = ytphy_switch_reg_space(phydev, curr_reg_space);
if (ret < 0)
return ret;
return 0;
}
#endif /*(YTPHY_WOL_FEATURE_ENABLE)*/
static int yt8521_config_init(struct phy_device *phydev)
{
int ret;
int val;
struct yt8xxx_priv *priv = phydev->priv;
#if (YTPHY_WOL_FEATURE_ENABLE)
struct ethtool_wolinfo wol;
/* set phy wol enable */
memset(&wol, 0x0, sizeof(struct ethtool_wolinfo));
wol.wolopts |= WAKE_MAGIC;
ytphy_wol_feature_set(phydev, &wol);
#endif
phydev->irq = PHY_POLL;
ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
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 */
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;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode = %d, polling mode = %d\n",
__func__, phydev->addr, priv->chip_mode, priv->polling_mode);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode = %d, polling mode = %d\n",
__func__, phydev->mdio.addr, priv->chip_mode, priv->polling_mode);
#endif
return ret;
}
/* for fiber mode, there is no 10M speed mode and
* this function is for this purpose.
*/
static int ytxxxx_adjust_status(struct phy_device *phydev, int val, int is_utp)
{
int speed_mode, duplex;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
int speed = -1;
#else
int speed = SPEED_UNKNOWN;
#endif
if (is_utp)
duplex = (val & YTXXXX_DUPLEX) >> YTXXXX_DUPLEX_BIT;
else
duplex = 1;
speed_mode = (val & YTXXXX_SPEED_MODE) >> YTXXXX_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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
speed = -1;
#else
speed = SPEED_UNKNOWN;
#endif
break;
}
phydev->speed = speed;
phydev->duplex = duplex;
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.
*/
static int yt8521_aneg_done(struct phy_device *phydev)
{
int link_fiber = 0, link_utp = 0;
/* reading Fiber */
ytphy_write_ext(phydev, 0xa000, 2);
link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
/* reading UTP */
ytphy_write_ext(phydev, 0xa000, 0);
if (!link_fiber)
link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_fiber: %d, link_utp: %d\n",
__func__, phydev->addr, link_fiber, link_utp);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_fiber: %d, link_utp: %d\n",
__func__, phydev->mdio.addr, link_fiber, link_utp);
#endif
return !!(link_fiber | link_utp);
}
static int yt8521_read_status(struct phy_device *phydev)
{
int ret;
int val;
int yt8521_fiber_latch_val;
int yt8521_fiber_curr_val;
int link;
int link_utp = 0, link_fiber = 0;
struct yt8xxx_priv *priv = phydev->priv;
if(priv->polling_mode != YT_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(YTXXXX_LINK_STATUS_BIT));
if (link) {
link_utp = 1;
ytxxxx_adjust_status(phydev, val, 1);
} else {
link_utp = 0;
}
}
if (priv->polling_mode != YT_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.
/* 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(YTXXXX_LINK_STATUS_BIT));
if (link && yt8521_fiber_latch_val != yt8521_fiber_curr_val) {
link = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
__func__, phydev->addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
__func__, phydev->mdio.addr, yt8521_fiber_latch_val, yt8521_fiber_curr_val);
#endif
}
if (link) {
link_fiber = 1;
ytxxxx_adjust_status(phydev, val, 0);
} else {
link_fiber = 0;
}
}
if (link_utp || link_fiber) {
if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n",
__func__, phydev->addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: %s, mii reg 0x11 = 0x%x\n",
__func__, phydev->mdio.addr, (link_utp && link_fiber) ? "UNKNOWN MEDIA" : (link_utp ? "UTP" : "Fiber"), (unsigned int)val);
#endif
phydev->link = 1;
} else {
if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
phydev->link = 0;
}
if (priv->polling_mode != YT_PHY_MODE_FIBER) { //utp or combo
if (link_fiber)
ytphy_write_ext(phydev, 0xa000, 2);
if (link_utp)
ytphy_write_ext(phydev, 0xa000, 0);
}
return 0;
}
static int yt8521_suspend(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
return 0;
}
static int yt8521_resume(struct phy_device *phydev)
{
int value, ret;
/* 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;
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_lock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
struct yt8xxx_priv *priv = phydev->priv;
if (priv->polling_mode != YT_PHY_MODE_FIBER) {
ytphy_write_ext(phydev, 0xa000, 0);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
}
if (priv->polling_mode != YT_PHY_MODE_UTP) {
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
return 0;
}
static int yt8531S_config_init(struct phy_device *phydev)
{
int ret = 0;
#if (YTPHY8531A_XTAL_INIT)
ret = yt8531a_xtal_init(phydev);
if (ret < 0)
return ret;
#endif
ret = yt8521_config_init(phydev);
return ret;
}
static int yt8531_config_init(struct phy_device *phydev)
{
int ret = 0;
#if (YTPHY8531A_XTAL_INIT)
ret = yt8531a_xtal_init(phydev);
if (ret < 0)
return ret;
#endif
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
ret = ytphy_config_init(phydev);
#else
ret = genphy_config_init(phydev);
#endif
if (ret < 0)
return ret;
return 0;
}
static int yt8614_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct device *dev = &phydev->dev;
#else
struct device *dev = &phydev->mdio.dev;
#endif
struct yt8xxx_priv *priv;
int chip_config;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
chip_config = ytphy_read_ext(phydev, 0xa007);
priv->chip_mode = chip_config & 0xf;
switch (priv->chip_mode) {
case 8: //4'b1000, Fiber x4 + Copper x4
case 12: //4'b1100, QSGMII x1 + Combo x4 mode;
case 13: //4'b1101, QSGMII x1 + Combo x4 mode;
priv->polling_mode = (YT_PHY_MODE_FIBER | YT_PHY_MODE_UTP);
break;
case 14: //4'b1110, QSGMII x1 + SGMII(MAC) x4 mode;
case 11: //4'b1011, QSGMII x1 + Fiber x4 mode;
priv->polling_mode = YT_PHY_MODE_FIBER;
break;
case 9: //4'b1001, Reserved.
case 10: //4'b1010, QSGMII x1 + Copper x4 mode
case 15: //4'b1111, SGMII(PHY) x4 + Copper x4 mode
default:
priv->polling_mode = YT_PHY_MODE_UTP;
break;
}
return 0;
}
static int yt8614Q_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
struct device *dev = &phydev->dev;
#else
struct device *dev = &phydev->mdio.dev;
#endif
struct yt8xxx_priv *priv;
int chip_config;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
chip_config = ytphy_read_ext(phydev, 0xa007);
priv->chip_mode = chip_config & 0xf;
switch (priv->chip_mode) {
case 0x1: //4'b0001, QSGMII to 1000BASE-X or 100BASE-FX x 4
default:
priv->polling_mode = YT_PHY_MODE_FIBER;
break;
}
return 0;
}
static int yt8614Q_config_aneg(struct phy_device *phydev)
{
//do nothing
return 0;
}
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8618_soft_reset(struct phy_device *phydev)
{
int ret;
ytphy_write_ext(phydev, 0xa000, 0);
ret = ytphy_soft_reset(phydev);
if (ret < 0)
return ret;
return 0;
}
static int yt8614_soft_reset(struct phy_device *phydev)
{
int ret;
/* qsgmii */
ytphy_write_ext(phydev, 0xa000, 2);
ret = ytphy_soft_reset(phydev);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
/* sgmii */
ytphy_write_ext(phydev, 0xa000, 3);
ret = ytphy_soft_reset(phydev);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
/* utp */
ytphy_write_ext(phydev, 0xa000, 0);
ret = ytphy_soft_reset(phydev);
if (ret < 0)
return ret;
return 0;
}
static int yt8614Q_soft_reset(struct phy_device *phydev)
{
int ret;
/* qsgmii */
ytphy_write_ext(phydev, 0xa000, 2);
ret = ytphy_soft_reset(phydev);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
/* sgmii */
ytphy_write_ext(phydev, 0xa000, 3);
ret = ytphy_soft_reset(phydev);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
return 0;
}
#endif
static int yt8618_config_init(struct phy_device *phydev)
{
int ret;
int val;
unsigned int retries = 12;
#if (YTPHY_861X_ABC_VER)
int port = 0;
#endif
phydev->irq = PHY_POLL;
#if (YTPHY_861X_ABC_VER)
port = yt8614_get_port_from_phydev(phydev);
#endif
ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
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 (YTPHY_861X_ABC_VER)
if ((port == 2) || (port == 5)) {
ret = ytphy_write_ext(phydev, 0x57, 0x2929);
if (ret < 0)
return ret;
}
#endif
val = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, val | BMCR_RESET);
do {
msleep(50);
ret = phy_read(phydev, MII_BMCR);
if (ret < 0)
return ret;
} while ((ret & BMCR_RESET) && --retries);
if (ret & BMCR_RESET)
return -ETIMEDOUT;
/* for QSGMII optimization */
ytphy_write_ext(phydev, 0xa000, 0x02);
ret = ytphy_write_ext(phydev, 0x3, 0x4F80);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
ret = ytphy_write_ext(phydev, 0xe, 0x4F80);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif
return ret;
}
#if (YTPHY_861X_ABC_VER)
static int yt8614_get_port_from_phydev(struct phy_device *phydev)
{
int tmp = ytphy_read_ext(phydev, 0xa0ff);
int phy_addr = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
phy_addr = (unsigned int)phydev->addr;
#else
phy_addr = (unsigned int)phydev->mdio.addr;
#endif
if ((phy_addr - tmp) < 0) {
ytphy_write_ext(phydev, 0xa0ff, phy_addr);
tmp = phy_addr;
}
return (phy_addr - tmp);
}
#endif
static int yt8614_config_init(struct phy_device *phydev)
{
int ret = 0;
int val;
unsigned int retries = 12;
#if (YTPHY_861X_ABC_VER)
int port = 0;
#endif
struct yt8xxx_priv *priv = phydev->priv;
phydev->irq = PHY_POLL;
#if (YTPHY_861X_ABC_VER)
port = yt8614_get_port_from_phydev(phydev);
#endif
ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
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 (YTPHY_861X_ABC_VER)
if (port == 2) {
ret = ytphy_write_ext(phydev, 0x57, 0x2929);
if (ret < 0)
return ret;
}
#endif
/* soft reset to take config effect */
val = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, val | BMCR_RESET);
do {
msleep(50);
ret = phy_read(phydev, MII_BMCR);
if (ret < 0)
return ret;
} while ((ret & BMCR_RESET) && --retries);
if (ret & BMCR_RESET)
return -ETIMEDOUT;
/* for QSGMII optimization */
ytphy_write_ext(phydev, 0xa000, 0x02);
ret = ytphy_write_ext(phydev, 0x3, 0x4F80);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
ret = ytphy_write_ext(phydev, 0xe, 0x4F80);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
/* for SGMII optimization */
ytphy_write_ext(phydev, 0xa000, 0x03);
ret = ytphy_write_ext(phydev, 0x3, 0x2420);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
ret = ytphy_write_ext(phydev, 0xe, 0x24a0);
if (ret < 0) {
ytphy_write_ext(phydev, 0xa000, 0);
return ret;
}
/* back up to utp*/
ytphy_write_ext(phydev, 0xa000, 0);
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->addr, priv->chip_mode, priv->polling_mode);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->mdio.addr, priv->chip_mode, priv->polling_mode);
#endif
return ret;
}
static int yt8614Q_config_init(struct phy_device *phydev)
{
struct yt8xxx_priv *priv = phydev->priv;
phydev->irq = PHY_POLL;
ytphy_write_ext(phydev, 0xa056, 0x7);
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->addr, priv->chip_mode, priv->polling_mode);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d, chip mode: %d, polling mode = %d\n", __func__, phydev->mdio.addr, priv->chip_mode, priv->polling_mode);
#endif
return 0;
}
static int yt8618_aneg_done(struct phy_device *phydev)
{
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
return genphy_aneg_done(phydev);
#else
return 1;
#endif
}
static int yt8614_aneg_done(struct phy_device *phydev)
{
int link_fiber = 0, link_utp = 0;
struct yt8xxx_priv *priv = phydev->priv;
if (priv->polling_mode & YT_PHY_MODE_FIBER) {
/* reading Fiber */
ytphy_write_ext(phydev, 0xa000, 3);
link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
}
if (priv->polling_mode & YT_PHY_MODE_UTP) {
/* reading UTP */
ytphy_write_ext(phydev, 0xa000, 0);
link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
}
return !!(link_fiber | link_utp);
}
static int yt8614Q_aneg_done(struct phy_device *phydev)
{
int link_fiber = 0;
struct yt8xxx_priv *priv = phydev->priv;
if (priv->polling_mode & YT_PHY_MODE_FIBER) {
/* reading Fiber */
ytphy_write_ext(phydev, 0xa000, 3);
link_fiber = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(YTXXXX_LINK_STATUS_BIT)));
}
return !!(link_fiber);
}
static int yt8614_read_status(struct phy_device *phydev)
{
int ret;
int val, yt8614_fiber_latch_val, yt8614_fiber_curr_val;
int link;
int link_utp = 0, link_fiber = 0;
struct yt8xxx_priv *priv = phydev->priv;
if (priv->polling_mode & YT_PHY_MODE_UTP) {
/* 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(YTXXXX_LINK_STATUS_BIT));
if (link) {
link_utp = 1;
/* here is same as 8521 and re-use the function; */
ytxxxx_adjust_status(phydev, val, 1);
} else {
link_utp = 0;
}
}
if (priv->polling_mode & YT_PHY_MODE_FIBER) {
/* 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;
/* 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(YTXXXX_LINK_STATUS_BIT));
if (link && yt8614_fiber_latch_val != yt8614_fiber_curr_val) {
link = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
__func__, phydev->addr, yt8614_fiber_latch_val, yt8614_fiber_curr_val);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
__func__, phydev->mdio.addr, yt8614_fiber_latch_val, yt8614_fiber_curr_val);
#endif
}
if (link) {
link_fiber = 1;
ytxxxx_adjust_status(phydev, val, 0);
} else {
link_fiber = 0;
}
}
if (link_utp || link_fiber) {
if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media %s\n",
__func__, phydev->addr, (link_utp && link_fiber) ? "both UTP and Fiber" : (link_utp ? "UTP" : "Fiber"));
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media %s\n",
__func__, phydev->mdio.addr, (link_utp && link_fiber) ? "both UTP and Fiber" : (link_utp ? "UTP" : "Fiber"));
#endif
phydev->link = 1;
} else {
if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
phydev->link = 0;
}
if (priv->polling_mode & YT_PHY_MODE_UTP) {
if (link_utp)
ytphy_write_ext(phydev, 0xa000, 0);
}
return 0;
}
static int yt8614Q_read_status(struct phy_device *phydev)
{
int ret;
int val, yt8614Q_fiber_latch_val, yt8614Q_fiber_curr_val;
int link;
int link_fiber = 0;
struct yt8xxx_priv *priv = phydev->priv;
if (priv->polling_mode & YT_PHY_MODE_FIBER) {
/* 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;
/* for fiber, from 1000m to 100m, there is not link down from 0x11,
* and check reg 1 to identify such case
*/
yt8614Q_fiber_latch_val = phy_read(phydev, MII_BMSR);
yt8614Q_fiber_curr_val = phy_read(phydev, MII_BMSR);
link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
if (link && yt8614Q_fiber_latch_val != yt8614Q_fiber_curr_val) {
link = 0;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
__func__, phydev->addr, yt8614Q_fiber_latch_val, yt8614Q_fiber_curr_val);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, fiber link down detect, latch = %04x, curr = %04x\n",
__func__, phydev->mdio.addr, yt8614Q_fiber_latch_val, yt8614Q_fiber_curr_val);
#endif
}
if (link) {
link_fiber = 1;
ytxxxx_adjust_status(phydev, val, 0);
} else {
link_fiber = 0;
}
}
if (link_fiber) {
if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media Fiber\n",
__func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media Fiber\n",
__func__, phydev->mdio.addr);
#endif
phydev->link = 1;
} else {
if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
phydev->link = 0;
}
return 0;
}
static int yt8618_read_status(struct phy_device *phydev)
{
int ret;
/* maybe for 8614 yt8521_fiber_latch_val, yt8521_fiber_curr_val; */
int val;
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(YTXXXX_LINK_STATUS_BIT));
if (link) {
link_utp = 1;
ytxxxx_adjust_status(phydev, val, 1);
} else {
link_utp = 0;
}
if (link_utp || link_fiber)
phydev->link = 1;
else
phydev->link = 0;
return 0;
}
static int yt8618_suspend(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
return 0;
}
static int yt8618_resume(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
return 0;
}
static int yt8614_suspend(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
return 0;
}
static int yt8614Q_suspend(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_lock(&phydev->lock);
#else
/* no need lock in 4.19 */
#endif
ytphy_write_ext(phydev, 0xa000, 3);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /*!(SYS_WAKEUP_BASED_ON_ETH_PKT)*/
return 0;
}
static int yt8614_resume(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
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 (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /* !(SYS_WAKEUP_BASED_ON_ETH_PKT) */
return 0;
}
static int yt8614Q_resume(struct phy_device *phydev)
{
#if !(SYS_WAKEUP_BASED_ON_ETH_PKT)
int value;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_lock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
ytphy_write_ext(phydev, 0xa000, 3);
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
mutex_unlock(&phydev->lock);
#else
/* no need lock/unlock in 4.19 */
#endif
#endif /* !(SYS_WAKEUP_BASED_ON_ETH_PKT) */
return 0;
}
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
static int yt8821_soft_reset(struct phy_device *phydev)
{
int ret, val;
val = ytphy_read_ext(phydev, 0xa001);
ytphy_write_ext(phydev, 0xa001, (val & ~0x8000));
ytphy_write_ext(phydev, 0xa000, 0);
ret = ytphy_soft_reset(phydev);
return ret;
}
#endif
static int yt8821_init(struct phy_device *phydev)
{
int ret = 0;
ret = ytphy_write_ext(phydev, 0xa000, 0x0);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x34e, 0x8080);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x4d2, 0x5200);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x4d3, 0x5200);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x372, 0x5a3c);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x374, 0x7c6c);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x336, 0xaa0a);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x340, 0x3022);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x36a, 0x8000);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x4b3, 0x7711);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x4b5, 0x2211);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x56, 0x20);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x56, 0x3f);
if (ret < 0)
return ret;
ret = ytphy_write_ext(phydev, 0x97, 0x380c);
if (ret < 0)
return ret;
/* soft reset */
ytphy_soft_reset(phydev);
return ret;
}
#define YT8821_CHIP_MODE_AUTO_BX2500_SGMII (1)
#define YT8821_CHIP_MODE_FORCE_BX2500 (0)
#define YT8821_CHIP_MODE_UTP_TO_FIBER_FORCE (0)
static int yt8821_config_init(struct phy_device *phydev)
{
int ret, val;
phydev->irq = PHY_POLL;
val = ytphy_read_ext(phydev, 0xa001);
#if (YT8821_CHIP_MODE_AUTO_BX2500_SGMII) //ext reg 0xa001 bit2:0 3'b000
val &= ~(BIT(0));
val &= ~(BIT(1));
val &= ~(BIT(2));
ret = ytphy_write_ext(phydev, 0xa001, val);
if (ret < 0)
return ret;
#elif (YT8821_CHIP_MODE_FORCE_BX2500) //ext reg 0xa001 bit2:0 3'b001
val |= BIT(0);
val &= ~(BIT(1));
val &= ~(BIT(2));
ret = ytphy_write_ext(phydev, 0xa001, val);
if (ret < 0)
return ret;
#elif (YT8821_CHIP_MODE_UTP_TO_FIBER_FORCE) //ext reg 0xa001 bit2:0 3'b101
val |= BIT(0);
val &= ~(BIT(1));
val |= BIT(2);
ret = ytphy_write_ext(phydev, 0xa001, val);
if (ret < 0)
return ret;
#endif
ret = yt8821_init(phydev);
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;
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n", __func__, phydev->mdio.addr);
#endif
return ret;
}
static int yt8821_aneg_done(struct phy_device *phydev)
{
int link_utp = 0;
/* reading UTP */
ytphy_write_ext(phydev, 0xa000, 0);
link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) & (BIT(10)));
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
__func__, phydev->addr, link_utp);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
__func__, phydev->mdio.addr, link_utp);
#endif
return !!(link_utp);
}
static int yt8821_adjust_status(struct phy_device *phydev, int val)
{
int speed_mode, duplex;
int speed_mode_bit15_14, speed_mode_bit9;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
int speed = -1;
#else
int speed = SPEED_UNKNOWN;
#endif
duplex = (val & YT8512_DUPLEX) >> YTXXXX_DUPLEX_BIT;
/* Bit9-Bit15-Bit14 speed mode 100---2.5G; 010---1000M; 001---100M; 000---10M */
speed_mode_bit15_14 = (val & YTXXXX_SPEED_MODE) >> YTXXXX_SPEED_MODE_BIT;
speed_mode_bit9 = (val & BIT(9)) >> 9;
speed_mode = (speed_mode_bit9 << 2) | speed_mode_bit15_14;
switch (speed_mode) {
case 0:
speed = SPEED_10;
break;
case 1:
speed = SPEED_100;
break;
case 2:
speed = SPEED_1000;
break;
case 4:
speed = SPEED_2500;
break;
default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
speed = -1;
#else
speed = SPEED_UNKNOWN;
#endif
break;
}
phydev->speed = speed;
phydev->duplex = duplex;
return 0;
}
static int yt8821_read_status(struct phy_device *phydev)
{
int ret;
int val;
int link;
int link_utp = 0;
/* 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(YTXXXX_LINK_STATUS_BIT));
if (link) {
link_utp = 1;
yt8821_adjust_status(phydev, val); /* speed(2500), duplex */
} else {
link_utp = 0;
}
if (link_utp) {
if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
__func__, phydev->addr, (unsigned int)val);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
__func__, phydev->mdio.addr, (unsigned int)val);
#endif
phydev->link = 1;
} else {
if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->addr);
#else
netdev_info(phydev->attached_dev, "%s, phy addr: %d, link down\n", __func__, phydev->mdio.addr);
#endif
phydev->link = 0;
}
if (link_utp)
ytphy_write_ext(phydev, 0xa000, 0);
return 0;
}
#if (KERNEL_VERSION(5, 0, 21) < LINUX_VERSION_CODE)
static int yt8821_get_features(struct phy_device *phydev)
{
linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported, 1);
return genphy_read_abilities(phydev);
}
#endif
static int yt8821_suspend(struct phy_device *phydev)
{
int value = 0;
int wol_enabled = 0;
#if (YTPHY_WOL_FEATURE_ENABLE)
value = phy_read(phydev, YTPHY_UTP_INTR_REG);
wol_enabled = value & YTPHY_WOL_FEATURE_INTR;
#endif
if (!wol_enabled)
{
value = phy_read(phydev, MII_BMCR);
phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);
}
return 0;
}
static int yt8821_resume(struct phy_device *phydev)
{
int value;
value = phy_read(phydev, MII_BMCR);
value &= ~BMCR_PDOWN;
value &= ~BMCR_ISOLATE;
phy_write(phydev, MII_BMCR, value);
return 0;
}
static struct phy_driver ytphy_drvs[] = {
{
.phy_id = PHY_ID_YT8010,
.name = "YT8010 100M Automotive Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_BASIC_FEATURES,
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8010_soft_reset,
#endif
.config_aneg = yt8010_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8010_aneg_done,
#endif
.config_init = yt8010_config_init,
.read_status = yt8010_read_status,
}, {
.phy_id = PHY_ID_YT8010AS,
.name = "YT8010AS 100M Automotive Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_BASIC_FEATURES,
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8010AS_soft_reset,
#endif
.config_aneg = yt8010_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8010_aneg_done,
#endif
.config_init = yt8010AS_config_init,
.read_status = yt8010_read_status,
}, {
.phy_id = PHY_ID_YT8011,
.name = "YT8011 Automotive Gigabit Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
.probe = yt8011_probe,
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8011_soft_reset,
#endif
.config_aneg = yt8011_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8011_aneg_done,
#endif
.config_init = yt8011_config_init,
.read_status = yt8011_read_status,
}, {
.phy_id = PHY_ID_YT8510,
.name = "YT8510 100/10Mb Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_BASIC_FEATURES,
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = ytphy_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
.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,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = ytphy_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if GMAC_CLOCK_INPUT_NEEDED
.config_init = yt8511_config_init,
#else
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE) || (KERNEL_VERSION(5, 3, 0) < LINUX_VERSION_CODE)
.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 100/10Mb Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_BASIC_FEATURES,
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = ytphy_soft_reset,
#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_YT8522,
.name = "YT8522 100/10Mb Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_BASIC_FEATURES,
.flags = PHY_POLL,
.probe = yt8522_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = ytphy_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
.config_init = yt8522_config_init,
.read_status = yt8522_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
.phy_id = PHY_ID_YT8521,
.name = "YT8521 Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
.probe = yt8521_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8521_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8521_aneg_done,
#endif
.config_init = yt8521_config_init,
.read_status = yt8521_read_status,
.suspend = yt8521_suspend,
.resume = yt8521_resume,
#if (YTPHY_WOL_FEATURE_ENABLE)
.get_wol = &ytphy_wol_feature_get,
.set_wol = &ytphy_wol_feature_set,
#endif
}, {
/* same as 8521 */
.phy_id = PHY_ID_YT8531S,
.name = "YT8531S Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
.probe = yt8521_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8521_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8521_aneg_done,
#endif
.config_init = yt8531S_config_init,
.read_status = yt8521_read_status,
.suspend = yt8521_suspend,
.resume = yt8521_resume,
#if (YTPHY_WOL_FEATURE_ENABLE)
.get_wol = &ytphy_wol_feature_get,
.set_wol = &ytphy_wol_feature_set,
#endif
}, {
/* same as 8511 */
.phy_id = PHY_ID_YT8531,
.name = "YT8531 Gigabit Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = ytphy_soft_reset,
#endif
.config_init = yt8531_config_init,
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
#if (YTPHY_WOL_FEATURE_ENABLE)
.get_wol = &ytphy_wol_feature_get,
.set_wol = &ytphy_wol_feature_set,
#endif
}, {
.phy_id = PHY_ID_YT8618,
.name = "YT8618 Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8618_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.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_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
.probe = yt8614_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8614_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8614_aneg_done,
#endif
.config_init = yt8614_config_init,
.read_status = yt8614_read_status,
.suspend = yt8614_suspend,
.resume = yt8614_resume,
},
{
.phy_id = PHY_ID_YT8614Q,
.name = "YT8614Q Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
.features = PHY_GBIT_FEATURES,
.flags = PHY_POLL,
.probe = yt8614Q_probe,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8614Q_soft_reset,
#endif
.config_aneg = yt8614Q_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8614Q_aneg_done,
#endif
.config_init = yt8614Q_config_init,
.read_status = yt8614Q_read_status,
.suspend = yt8614Q_suspend,
.resume = yt8614Q_resume,
},
{
.phy_id = PHY_ID_YT8821,
.name = "YT8821 2.5Gbps Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
#if (KERNEL_VERSION(5, 1, 0) > LINUX_VERSION_CODE)
.features = PHY_GBIT_FEATURES,
#endif
.flags = PHY_POLL,
#if (KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE)
#else
.soft_reset = yt8821_soft_reset,
#endif
.config_aneg = genphy_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
.aneg_done = yt8821_aneg_done,
#endif
#if (KERNEL_VERSION(5, 0, 21) < LINUX_VERSION_CODE)
.get_features = yt8821_get_features,
#endif
.config_init = yt8821_config_init,
#if (YTPHY_WOL_FEATURE_ENABLE)
.set_wol = &ytphy_wol_feature_set,
.get_wol = &ytphy_wol_feature_get,
#endif
.read_status = yt8821_read_status,
.suspend = yt8821_suspend,
.resume = yt8821_resume,
},
};
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
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)
{
return ytphy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}
static void __exit ytphy_exit(void)
{
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_YT8521, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8531S, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8531, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8618, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8614, MOTORCOMM_PHY_ID_MASK },
{ PHY_ID_YT8821, MOTORCOMM_PHY_ID_MASK },
{ }
};
MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);
2.2、使用config宏配置驱动
--- a/kernel/linux-4.9/arch/arm64/configs/sun50iw9p1smp_longan_defconfig
+++ b/kernel/linux-4.9/arch/arm64/configs/sun50iw9p1smp_longan_defconfig
@@ -1387,6 +1387,7 @@ CONFIG_ETHERNET=y
CONFIG_NET_VENDOR_ALLWINNER=y
# CONFIG_SUN4I_EMAC is not set
CONFIG_SUNXI_GMAC=y
+CONFIG_SUNXI_EXT_PHY=y
# CONFIG_ALTERA_TSE is not set
CONFIG_NET_VENDOR_AMAZON=y
CONFIG_NET_VENDOR_AMD=y
2.3、设备树
(1)kernel/linux-4.9/arch/arm64/boot/dts/sunxi/sun50iw9p1-pinctrl.dtsi
gmac0_pins_a: gmac@0 {
allwinner,pins = "PI0", "PI1", "PI2", "PI3",
"PI4", "PI5", "PI7",
"PI8", "PI9", "PI10", "PI11",
"PI12", "PI13", "PI14", "PI15";
allwinner,function = "gmac0";
allwinner,muxsel = <2>;
allwinner,drive = <3>;
allwinner,pull = <0>;
};
gmac0_pins_b: gmac@1 {
allwinner,pins = "PI0", "PI1", "PI2", "PI3",
"PI4", "PI5", "PI7",
"PI8", "PI9", "PI10", "PI11",
"PI12", "PI13", "PI14", "PI15";
allwinner,function = "io_disabled";
allwinner,muxsel = <7>;
allwinner,drive = <3>;
allwinner,pull = <0>;
};
/t507/configs/demo2.0/board.dts
gmac0:eth@05020000 {
compatible = "allwinner,sunxi-gmac";
reg = <0x0 0x05020000 0x0 0x10000>,
<0x0 0x03000030 0x0 0x4>;
interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "gmacirq";
clocks = <&clk_gmac0>, <&clk_ephy_25m>;
clock-names = "gmac", "ephy";
device_type = "gmac0";
pinctrl-0 = <&gmac0_pins_a>;
pinctrl-1 = <&gmac0_pins_b>;
pinctrl-names = "default", "sleep";
phy-mode = "rgmii";
tx-delay = <7>;
rx-delay = <0>;
use_ephy25m = <0>;
phy-rst;
gmac-power0;
gmac-power1;
gmac-power2;
status = "okay";
avcc-supply = <®_aldo3>;
//qca,disable-smarteee = <1>;
//qca,clk-out-frequency = <125000000>;
//qca,clk-out-strength = <0>;
//phy-rst = <&pio PI 6 1 1 1 1>;
phy_reset = <&pio PI 6 1 1 1 1>;
};
2.4、gmac驱动调试中遇到的问题
测试以太网时,第一次上电可以正常读取ID,识别到PHY

网口正常生成
但是无法获取IP,设置静态IP也无法ping通

确认PHY使能正常:

重新Up后,No PHY found!

加载失败则需要从以下三个方面进行分析:
(1)供电:PHY 一般无需单独的供电,GMAC 的 PIN BANK 在某些平台上需要单独供电,检查PIN 的供电是否正常
(2)时钟:主要检查 PHY 的 25M 时钟,如果是外部晶振,用示波器量晶振频率是否在 25Mhz 左 右,每款 PHY 对晶振的精确度要求不一致 PHY 的 datasheet 上会有相关说明;如果是内部 SOC25M 时钟供电,查看 SOC25M 对应的是哪个 PIN,用示波器量下 PIN 输出的时钟频率 是否在 25M 左右
(3)PHY-RST:确保对 PHY 在进行 MDIO 读写操作时需要 10ms 左右 rst 拉低以及 150ms 左右 rst 拉高操作
示波器测量后定位到无25兆时钟输出

使用外部晶振,硬件上焊接上电阻R204 R205,断开R207
使能25兆时钟后还是未找到PHY

reset:

配置情况:


为什么会识别不到phy,很奇怪???
对于此类问题,多半是引脚复用的问题。(由于是在脚本中配置了GPIO脚,导致排查了很久)
屏蔽此处GPIO的使用,该部分引脚是PHY的重要引脚

四、测试
网络设备

正常关闭和打开

联网测试:

iperf3测试:开发板于电脑网线连接,配置电脑端和设备端同一网段IP

Linux设备端

电脑端
