linux内核中的以太网phy芯片的驱动介绍:以lan8720和ip101作为对比

60 篇文章 11 订阅
31 篇文章 1 订阅

背景:

由于公司开发的新项目中设备需要联网,使用了MZ391的4G模块,并通过rmii接口和phy芯片ip101gr链接。

主控平台rv1108通过rmii接口和phy芯片lan8720相连接。

ip101gr和lan8720通过类似于以太网的双绞线:tx+,tx-,rx+,rx-,相连接。之所以mac和phy要分开,是因为mac属于数字电路部分,主要处理的信号是属于数字信号,将上层ip层等数据通过rmii接口发送给phy,或者将phy发送过来的数据在发送给上层等。phy属于模拟电路部分,主要处理魔力电路信号,就是将从rmii接收到的数据转换成可以通过网线传输的差分模拟信号,或者是将网线上接收到的差分模拟信号转换为可以通过rmii发送的数字信号。

硬件链接:https://blog.csdn.net/u010299133/article/details/87716700


驱动介绍(主要对比以下两个文件):

dts文件加上mac的配置

lan8720a的驱动代码:drivers/net/phy/smsc.c

ip101gr的驱动代码:drivers/net/phy/icplus.c

在内核中需要sudo make menuconfig中选择好相应的驱动,在编译的时候能够编译进入内核。

当然如果想要phy芯片能够正常工作,还必须在menuconfig中选择好mac驱动,由于以太网传输数据量大,所以在芯片的mac和我们这里介绍的phy芯片之间都需要使用到dma:

 

驱动的注册和注销:

在内核加载完成后,会执行相应的驱动的插入函数中的phy驱动的注册。

 

其中比较重要的参数:phy_driver结构体

phy_id:the result of reading the UID register of this phy type,and them with the phy_id_mask。this driver only work for phys with id which match this field

name:the friendly name of the phy type

phy_id_mask:the defines the important bits of the phy_id

features:a list of feature(speed,duplex,etc) supported by the phy

flag:A bit field defining certain other feature this phy support

struct phy_driver {
	u32 phy_id;
	char *name;
	unsigned int phy_id_mask;
	u32 features;
	u32 flags;

	/*
	 * Called to initialize the PHY,
	 * including after a reset
	 */
	int (*config_init)(struct phy_device *phydev);

	/*
	 * Called during discovery.  Used to set
	 * up device-specific structures, if any
	 */
	int (*probe)(struct phy_device *phydev);

	/* PHY Power Management */
	int (*suspend)(struct phy_device *phydev);
	int (*resume)(struct phy_device *phydev);

	/*
	 * Configures the advertisement and resets
	 * autonegotiation if phydev->autoneg is on,
	 * forces the speed to the current settings in phydev
	 * if phydev->autoneg is off
	 */
	int (*config_aneg)(struct phy_device *phydev);

	/* Determines the negotiated speed and duplex */
	int (*read_status)(struct phy_device *phydev);

	/* Clears any pending interrupts */
	int (*ack_interrupt)(struct phy_device *phydev);

	/* Enables or disables interrupts */
	int (*config_intr)(struct phy_device *phydev);

	/*
	 * Checks if the PHY generated an interrupt.
	 * For multi-PHY devices with shared PHY interrupt pin
	 */
	int (*did_interrupt)(struct phy_device *phydev);

	/* Clears up any memory if needed */
	void (*remove)(struct phy_device *phydev);

	/* Returns true if this is a suitable driver for the given
	 * phydev.  If NULL, matching is based on phy_id and
	 * phy_id_mask.
	 */
	int (*match_phy_device)(struct phy_device *phydev);

	/* Handles ethtool queries for hardware time stamping. */
	int (*ts_info)(struct phy_device *phydev, struct ethtool_ts_info *ti);

	/* Handles SIOCSHWTSTAMP ioctl for hardware time stamping. */
	int  (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);

	/*
	 * Requests a Rx timestamp for 'skb'. If the skb is accepted,
	 * the phy driver promises to deliver it using netif_rx() as
	 * soon as a timestamp becomes available. One of the
	 * PTP_CLASS_ values is passed in 'type'. The function must
	 * return true if the skb is accepted for delivery.
	 */
	bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);

	/*
	 * Requests a Tx timestamp for 'skb'. The phy driver promises
	 * to deliver it using skb_complete_tx_timestamp() as soon as a
	 * timestamp becomes available. One of the PTP_CLASS_ values
	 * is passed in 'type'.
	 */
	void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);

	/* Some devices (e.g. qnap TS-119P II) require PHY register changes to
	 * enable Wake on LAN, so set_wol is provided to be called in the
	 * ethernet driver's set_wol function. */
	int (*set_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);

	/* See set_wol, but for checking whether Wake on LAN is enabled. */
	void (*get_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);

	struct device_driver driver;
};

.config_aneg    = genphy_config_aneg, 

/**
 * genphy_config_aneg - restart auto-negotiation or write BMCR
 * @phydev: target phy_device struct
 *
 * Description: If auto-negotiation is enabled, we configure the
 *   advertising, and then restart auto-negotiation.  If it is not
 *   enabled, then we write the BMCR.
 */
int genphy_config_aneg(struct phy_device *phydev)
{
	int result;

	if (AUTONEG_ENABLE != phydev->autoneg)
		return genphy_setup_forced(phydev);

	result = genphy_config_advert(phydev);

	if (result < 0) /* error */
		return result;

	if (result == 0) {
		/* Advertisement hasn't changed, but maybe aneg was never on to
		 * begin with?  Or maybe phy was isolated? */
		int ctl = phy_read(phydev, MII_BMCR);

		if (ctl < 0)
			return ctl;

		if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE))
			result = 1; /* do restart aneg */
	}

	/* Only restart aneg if we are advertising something different
	 * than we were before.	 */
	if (result > 0)
		result = genphy_restart_aneg(phydev);

	return result;
}
EXPORT_SYMBOL(genphy_config_aneg);

 .read_status    = lan87xx_read_status,

static int lan87xx_read_status(struct phy_device *phydev)
{
	int err = genphy_read_status(phydev);

	if (!phydev->link) {
		/* Disable EDPD to wake up PHY */
		int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
		if (rc < 0)
			return rc;

		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
			       rc & ~MII_LAN83C185_EDPWRDOWN);
		if (rc < 0)
			return rc;

		/* Sleep 64 ms to allow ~5 link test pulses to be sent */
		msleep(64);

		/* Re-enable EDPD */
		rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
		if (rc < 0)
			return rc;

		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
			       rc | MII_LAN83C185_EDPWRDOWN);
		if (rc < 0)
			return rc;
	}

	return err;
}

 .config_init    = smsc_phy_config_init,

static int smsc_phy_config_init(struct phy_device *phydev)
{
	int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
	if (rc < 0)
		return rc;

	/* If the SMSC PHY is in power down mode, then set it
	 * in all capable mode before using it.
	 */
	if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
		int timeout = 50000;

		/* set "all capable" mode and reset the phy */
		rc |= MII_LAN83C185_MODE_ALL;
		phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
		phy_write(phydev, MII_BMCR, BMCR_RESET);

		/* wait end of reset (max 500 ms) */
		do {
			udelay(10);
			if (timeout-- == 0)
				return -1;
			rc = phy_read(phydev, MII_BMCR);
		} while (rc & BMCR_RESET);
	}

	rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
	if (rc < 0)
		return rc;

	/* Enable energy detect mode for this SMSC Transceivers */
	rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
		       rc | MII_LAN83C185_EDPWRDOWN);
	if (rc < 0)
		return rc;

	return smsc_phy_ack_interrupt (phydev);
}

.ack_interrupt    = smsc_phy_ack_interrupt,

static int smsc_phy_ack_interrupt(struct phy_device *phydev)
{
	int rc = phy_read (phydev, MII_LAN83C185_ISF);

	return rc < 0 ? rc : 0;
}

 .config_intr    = smsc_phy_config_intr,

static int smsc_phy_config_intr(struct phy_device *phydev)
{
	int rc = phy_write (phydev, MII_LAN83C185_IM,
			((PHY_INTERRUPT_ENABLED == phydev->interrupts)
			? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
			: 0));

	return rc < 0 ? rc : 0;
}

 .suspend    = genphy_suspend,

int genphy_suspend(struct phy_device *phydev)
{
	int value;

	mutex_lock(&phydev->lock);

	value = phy_read(phydev, MII_BMCR);
	phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));

	mutex_unlock(&phydev->lock);

	return 0;
}
EXPORT_SYMBOL(genphy_suspend);

 .resume        = genphy_resume,

int genphy_resume(struct phy_device *phydev)
{
	int value;

	mutex_lock(&phydev->lock);

	value = phy_read(phydev, MII_BMCR);
	phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN));

	mutex_unlock(&phydev->lock);

	return 0;
}
EXPORT_SYMBOL(genphy_resume);

其中phy_write和phy_read分别如下:其中mdiobus_read和mdiobus_write位于drivers/net/phy/mdio_bus.c文件中,就是实现对phy芯片上的mdio和mdc的操作。

/**
 * phy_read - Convenience function for reading a given PHY register
 * @phydev: the phy_device struct
 * @regnum: register number to read
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
static inline int phy_read(struct phy_device *phydev, u32 regnum)
{
	return mdiobus_read(phydev->bus, phydev->addr, regnum);
}

/**
 * phy_write - Convenience function for writing a given PHY register
 * @phydev: the phy_device struct
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{
	return mdiobus_write(phydev->bus, phydev->addr, regnum, val);
}

 

phy_device结构体:

/* phy_device: An instance of a PHY
 *
 * drv: Pointer to the driver for this PHY instance
 * bus: Pointer to the bus this PHY is on
 * dev: driver model device structure for this PHY
 * phy_id: UID for this device found during discovery
 * c45_ids: 802.3-c45 Device Identifers if is_c45.
 * is_c45:  Set to true if this phy uses clause 45 addressing.
 * state: state of the PHY for management purposes
 * dev_flags: Device-specific flags used by the PHY driver.
 * addr: Bus address of PHY
 * link_timeout: The number of timer firings to wait before the
 * giving up on the current attempt at acquiring a link
 * irq: IRQ number of the PHY's interrupt (-1 if none)
 * phy_timer: The timer for handling the state machine
 * phy_queue: A work_queue for the interrupt
 * attached_dev: The attached enet driver's device instance ptr
 * adjust_link: Callback for the enet controller to respond to
 * changes in the link state.
 * adjust_state: Callback for the enet driver to respond to
 * changes in the state machine.
 *
 * speed, duplex, pause, supported, advertising, and
 * autoneg are used like in mii_if_info
 *
 * interrupts currently only supports enabled or disabled,
 * but could be changed in the future to support enabling
 * and disabling specific interrupts
 *
 * Contains some infrastructure for polling and interrupt
 * handling, as well as handling shifts in PHY hardware state
 */

struct phy_device {
	/* Information about the PHY type */
	/* And management functions */
	struct phy_driver *drv;

	struct mii_bus *bus;

	struct device dev;

	u32 phy_id;

	struct phy_c45_device_ids c45_ids;
	bool is_c45;

	enum phy_state state;

	u32 dev_flags;

	phy_interface_t interface;

	/* Bus address of the PHY (0-31) */
	int addr;

	/*
	 * forced speed & duplex (no autoneg)
	 * partner speed & duplex & pause (autoneg)
	 */
	int speed;
	int duplex;
	int pause;
	int asym_pause;

	/* The most recently read link state */
	int link;

	/* Enabled Interrupts */
	u32 interrupts;

	/* Union of PHY and Attached devices' supported modes */
	/* See mii.h for more info */
	u32 supported;
	u32 advertising;

	int autoneg;

	int link_timeout;

	/*
	 * Interrupt number for this PHY
	 * -1 means no interrupt
	 */
	int irq;

	/* private data pointer */
	/* For use by PHYs to maintain extra state */
	void *priv;

	/* Interrupt and Polling infrastructure */
	struct work_struct phy_queue;
	struct delayed_work state_queue;
	atomic_t irq_disable;

	struct mutex lock;

	struct net_device *attached_dev;

	void (*adjust_link)(struct net_device *dev);

	void (*adjust_state)(struct net_device *dev);
};

 

参考文献:

https://blog.csdn.net/jk198310/article/details/12909341

https://blog.csdn.net/helloyizhou/article/details/72675533

  • 0
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LAN8720是一款常用的以太网PHY芯片,它可以通过RMII或MII接口与Linux系统连接。下面是一个可能的LAN8720 Linux驱动程序的示例代码: ``` #include <linux/module.h> #include <linux/kernel.h> #include <linux/i2c.h> #include <linux/netdevice.h> #include <linux/ethtool.h> #include <linux/of.h> #include <linux/of_device.h> /* LAN8720 PHY registers */ #define LAN8720_REG_BCR 0x00 /* Basic Control Register */ #define LAN8720_REG_BSR 0x01 /* Basic Status Register */ #define LAN8720_REG_PHYID1 0x02 /* PHY Identifier 1 */ #define LAN8720_REG_PHYID2 0x03 /* PHY Identifier 2 */ #define LAN8720_REG_ANAR 0x04 /* Auto-Negotiation Advertisement Register */ #define LAN8720_REG_ANLPAR 0x05 /* Auto-Negotiation Link Partner Ability Register */ #define LAN8720_REG_ANER 0x06 /* Auto-Negotiation Expansion Register */ #define LAN8720_REG_DSCR 0x10 /* PCS/TX Descriptor Register */ #define LAN8720_REG_DSCSR 0x11 /* PCS/TX Descriptor and Status Register */ #define LAN8720_REG_PHYCR 0x19 /* PHY Control Register */ #define LAN8720_PHY_RESET_DELAY_MS 100 struct lan8720_priv { struct device *dev; struct net_device *netdev; void __iomem *regs; }; static int lan8720_mdio_read(struct mii_bus *bus, int phy_addr, int reg_addr) { struct lan8720_priv *priv = bus->priv; void __iomem *regs = priv->regs; int val; writel(0x80000000 | (phy_addr << 23) | (reg_addr << 18), regs + LAN8720_REG_DSCR); while (!(readl(regs + LAN8720_REG_DSCSR) & 0x80000000)); val = readl(regs + LAN8720_REG_DSCR) & 0xffff; return val; } static int lan8720_mdio_write(struct mii_bus *bus, int phy_addr, int reg_addr, u16 val) { struct lan8720_priv *priv = bus->priv; void __iomem *regs = priv->regs; writel(0xc0000000 | (phy_addr << 23) | (reg_addr << 18) | val, regs + LAN8720_REG_DSCR); while (!(readl(regs + LAN8720_REG_DSCSR) & 0x80000000)); return 0; } static int lan8720_phy_reset(struct lan8720_priv *priv) { u16 val; /* Reset PHY */ lan8720_mdio_write(priv->netdev->mdio_bus, priv->netdev->phydev->addr, LAN8720_REG_BCR, 0x8000); msleep(LAN8720_PHY_RESET_DELAY_MS); /* Wait for PHY to come out of reset */ val = lan8720_mdio_read(priv->netdev->mdio_bus, priv->netdev->phydev->addr, LAN8720_REG_BCR); if (val & 0x8000) return -EBUSY; return 0; } static int lan8720_phy_init(struct lan8720_priv *priv) { u16 val; /* Reset PHY */ if (lan8720_phy_reset(priv)) return -EBUSY; /* Enable auto-negotiation */ lan8720_mdio_write(priv->netdev->mdio_bus, priv->netdev->phydev->addr, LAN8720_REG_BCR, 0x1000); /* Wait for auto-negotiation to complete */ do { val = lan8720_mdio_read(priv->netdev->mdio_bus, priv->netdev->phydev->addr, LAN8720_REG_BSR); } while (!(val & 0x0020)); /* Enable RX/TX */ lan8720_mdio_write(priv->netdev->mdio_bus, priv->netdev->phydev->addr, LAN8720_REG_BCR, 0x2000); return 0; } static int lan8720_probe(struct platform_device *pdev) { struct lan8720_priv *priv; struct net_device *netdev; struct device_node *np = pdev->dev.of_node; struct resource *res; int ret; /* Allocate private data */ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* Allocate netdev */ netdev = alloc_etherdev(sizeof(*priv)); if (!netdev) return -ENOMEM; /* Set netdev MAC address */ of_property_read_u8_array(np, "local-mac-address", netdev->dev_addr, ETH_ALEN); /* Initialize netdev */ netdev->netdev_ops = &lan8720_netdev_ops; netdev->ethtool_ops = &lan8720_ethtool_ops; netdev->needs_free_netdev = true; netif_napi_add(netdev, &priv->napi, lan8720_rx_napi, NAPI_POLL_WEIGHT); /* Allocate MDIO bus */ netdev->phydev = mdiobus_alloc(); if (!netdev->phydev) { free_netdev(netdev); return -ENOMEM; } /* Set MDIO bus parameters */ netdev->phydev->bus = priv->netdev->mdio_bus; netdev->phydev->mdio_read = lan8720_mdio_read; netdev->phydev->mdio_write = lan8720_mdio_write; netdev->phydev->priv = priv; /* Initialize PHY */ ret = lan8720_phy_init(priv); if (ret) { mdiobus_free(netdev->phydev->bus); free_netdev(netdev); return ret; } /* Set netdev device and add it to network interface */ priv->dev = &pdev->dev; priv->netdev = netdev; platform_set_drvdata(pdev, priv); ret = register_netdev(netdev); if (ret) { mdiobus_free(netdev->phydev->bus); free_netdev(netdev); return ret; } return 0; } static int lan8720_remove(struct platform_device *pdev) { struct lan8720_priv *priv = platform_get_drvdata(pdev); unregister_netdev(priv->netdev); mdiobus_free(priv->netdev->phydev->bus); free_netdev(priv->netdev); return 0; } static const struct of_device_id lan8720_of_match[] = { { .compatible = "smsc,lan8720" }, { }, }; MODULE_DEVICE_TABLE(of, lan8720_of_match); static struct platform_driver lan8720_driver = { .probe = lan8720_probe, .remove = lan8720_remove, .driver = { .name = "lan8720", .of_match_table = lan8720_of_match, }, }; module_platform_driver(lan8720_driver); MODULE_AUTHOR("Your Name Here"); MODULE_DESCRIPTION("LAN8720 driver"); MODULE_LICENSE("GPL"); ``` 需要注意的是,以上示例代码只是一个框架,实际的驱动程序需要根据硬件的具体特性进行实现。另外,该驱动程序还包含了一个NAPI驱动程序,以便在高速网络环境下进行数据接收。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值