学无止境--U-boot下的ETH PHY驱动探索

备注:学习记录所用,若有高手不吝赐教,万分感谢!

一、概括

环境:

cpu:fsl91030m

phy:yt8512(motorcomm厂)

完整的phy驱动需要eth phy驱动、mdio驱动、mii驱动(一般ic原厂自带),并且需要将其嵌入到eth驱动中。

 

二、外部phy驱动

1、驱动位置

drivers/net/phy中添加motorcomm.c文件

2、驱动实现:

static int yt8512_config(struct phy_device *phydev)
{
	int ctl = 0;
	
	ctl = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
	if (ctl < 0)
		return ctl;
	ctl &= ~(BMCR_SPEED10 | BMCR_SPEED1000 | BMCR_ANENABLE);
	ctl |= BMCR_FULLDPLX | BMCR_SPEED100;
		
	/* First clear the PHY */
	//phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, ctl | BMCR_RESET);

	return genphy_config_aneg(phydev);
}

static int yt8512_parse_status(struct phy_device *phydev)
{
	int mii_reg;
	int speed;

	mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, PHY_REG_SPEC_STATUS);

	if (mii_reg & YT8512_DUPLEX_STATUS)
		phydev->duplex = DUPLEX_FULL;
	else
		phydev->duplex = DUPLEX_HALF;

	speed = mii_reg & YT8512_SPEED_MASK;
	switch (speed) {
	case YT8512_SPEED_1000:
		phydev->speed = SPEED_1000;
		break;
	case YT8512_SPEED_100:
		phydev->speed = SPEED_100;
		break;
	case YT8512_SPEED_10:
		phydev->speed = SPEED_10;
		break;
	default:
		phydev->speed = SPEED_100;
		break;
	}

	return 0;
}

static int yt8512_startup(struct phy_device *phydev)
{
	unsigned phy_ctl;
	int ret;

	ret = genphy_update_link(phydev);
	if (ret)
		return ret;

	return yt8512_parse_status(phydev);
}


static struct phy_driver yt8512_driver = {
	.name = "motocomm,yt8512b",
	.uid = PHY_ID_YT8512B,
	.mask = PHY_ID_MASK,
	.features = PHY_BASIC_FEATURES,
	.config = &yt8512_config,
	.startup = &yt8512_startup,
	.shutdown = &genphy_shutdown,
};

 /*该函数在driver/net/phy/phy.c/phy_init()中调用*/
int phy_motorcomm_init(void)
{
	phy_register(&yt8512_driver);
	
	return 0;
}

3、设备树

mdio: mdio@62000000{
    compatible = "nuclei,ux600fd_mdio";
    reg = <0x0 0x62000000 0x0 0x100>;
    #address-cells = <0x1>;
    #size-cells = <0x0>;
    status = "okay";
        yt8512b@0 {
        compatible = "motocomm,yt8512b";
        reg = <0>;
        };
};

 

三、MDIO驱动

  1、驱动位置

    drivers/net、中添加xy1000_mdio.c文件

  2、驱动实现:非DM方式

#define NUCLEI_UX600FD_MII_DEBUG

#define FSLRAL_MDIO_INNER 			0
#define FSLRAL_MDIO_EXTRAL 			1

/****************************************reg define****************************************/
#define LOCAL_BUS_BASE						(0x60000000)
#define MDIO_INITIATOR_REG_BASE				(LOCAL_BUS_BASE + 0x02000000)

#define MDIO_INITIATOR_REG_MDIO_FRM_FIELDr   (MDIO_INITIATOR_REG_BASE + 0x00)
#define MDIO_INITIATOR_REG_MDIO_FRM_CTRLr    (MDIO_INITIATOR_REG_BASE + 0x04)
#define MDIO_INITIATOR_REG_MDIO_CMDm         (MDIO_INITIATOR_REG_BASE + 0x08)
#define MDIO_INITIATOR_REG_MDIO_MASTER_CTRLr (MDIO_INITIATOR_REG_BASE + 0x0C)

#define PAD_CONFIG_REG_PAD_SDAr              (0x640090e8)
#define PAD_CONFIG_REG_PAD_SCLr              (0x640090e4)
#define TOP_CFG_REG_INVISIBLE_REGr           (0x60000040)

#define BITS(start, end)             ((0xFFFFFFFFUL << (start)) & (0xFFFFFFFFUL >> (31U - (uint32_t)(end)))) 

#ifdef CONFIG_DM_MDIO
struct mdio_nuclei_ux600fd_plat {
	void *base;
};

static int mdio_nuclei_ux600fd_read(struct udevice *dev, int addr, int devad, int reg)
{
	struct mdio_nuclei_ux600fd_plat *plat = dev_get_platdata(dev);

	return 0;
}

static int mdio_nuclei_ux600fd_write(struct udevice *dev, int addr, int devad, int reg,
			      u16 val)
{
	struct mdio_nuclei_ux600fd_plat *plat = dev_get_platdata(dev);

	return 0;
}

static int mdio_nuclei_ux600fd_reset(struct udevice *dev)
{
	struct mdio_nuclei_ux600fd_plat *plat = dev_get_platdata(dev);

	fslral_mdio_mode_cfg(FSLRAL_MDIO_INNER);
	
	return 0;
}

static const struct mdio_ops mdio_nuclei_ux600fd_ops = {
	.read = mdio_nuclei_ux600fd_read,
	.write = mdio_nuclei_ux600fd_write,
	.reset = mdio_nuclei_ux600fd_reset,
};

static int mdio_nuclei_ux600fd_probe(struct udevice *dev)
{
	struct mdio_nuclei_ux600fd_plat *plat = dev_get_platdata(dev);
	fdt_addr_t addr;

	addr = devfdt_get_addr(dev);
	if (addr == FDT_ADDR_T_NONE)
		return -EINVAL;

	plat->base = (void *)addr;

	fslral_mdio_mode_cfg(FSLRAL_MDIO_EXTRAL);

	return 0;
}

static int mdio_nuclei_ux600fd_ofdata_to_platdata(struct udevice *dev)
{
	struct mdio_nuclei_ux600fd_plat *plat = dev_get_platdata(dev);
	fdt_addr_t addr;

	addr = devfdt_get_addr(dev);
	if (addr == FDT_ADDR_T_NONE)
		return -EINVAL;

	plat->base = (void *)addr;
	return 0;
}

static const struct udevice_id mdio_nuclei_ux600fd_ids[] = {
	{ .compatible = "nuclei,ux600fdmdio" },
	{ }
};

U_BOOT_DRIVER(ux600fd_mdio) = {
	.name		= "ux600fd_mdio",
	.id		= UCLASS_MDIO,
	.of_match	= mdio_nuclei_ux600fd_ids,
	.ofdata_to_platdata = of_match_ptr(mdio_nuclei_ux600fd_ofdata_to_platdata),
	.probe		= mdio_nuclei_ux600fd_probe,
	.ops		= &mdio_nuclei_ux600fd_ops,
	.priv_auto_alloc_size = sizeof(struct mdio_nuclei_ux600fd_priv),
};
#else /*非DM方式*/

/**/
int fslral_mdio_mode_cfg(struct xy1000_mii_mng __iomem *phyregs, int unit, uint8_t mode)
{
	/*该平台mido可以控制内部phy与外部hpy
      此处切换
    */	
	
	return 0;
}

int fslral_mdio_master_read(struct xy1000_mii_mng __iomem *phyregs, int phy_ad, int reg_ad, int *val)
{
    /*按照平台的操作流程控制寄存器,实现从外部phy读取数据*/
	return rv;
}

int fslral_mdio_master_write(struct xy1000_mii_mng __iomem *phyregs, int phy_ad, int reg_ad, int value)
{
	/*按照平台的操作流程控制寄存器,实现向外部phy写入数据*/
	return 0;
}

int fslral_mdio_master_read_extern(struct mii_dev *bus, int addr, int devaddr, int reg)
{
	int val = 0;
	struct xy1000_mii_mng __iomem *phyregs =
		(struct xy1000_mii_mng __iomem *)bus->priv;
	
	fslral_mdio_mode_cfg(phyregs, 0, FSLRAL_MDIO_EXTRAL);
	fslral_mdio_master_read(phyregs, addr, reg, &val);
	fslral_mdio_mode_cfg(phyregs, 0, FSLRAL_MDIO_INNER);
	
	return val;
}


int fslral_mdio_master_write_extern(struct mii_dev *bus, int addr, int devaddr,  int reg, u16 value)
{
	int rv = 0;
	struct xy1000_mii_mng __iomem *phyregs =
		(struct xy1000_mii_mng __iomem *)bus->priv;
	
	fslral_mdio_mode_cfg(phyregs, 0, FSLRAL_MDIO_EXTRAL);
	rv = fslral_mdio_master_write(phyregs, addr, reg, value);
	fslral_mdio_mode_cfg(phyregs, 0, FSLRAL_MDIO_INNER);
	return rv;
}

int fslral_mdio_master_reset_extern(struct mii_dev *bus)
{
	int rv = 0;
	struct xy1000_mii_mng __iomem *phyregs =
		(struct xy1000_mii_mng __iomem *)bus->priv;

	return rv;
};


int mdio_nuclei_ux600fd_init(bd_t *bis, struct xy1000_mdio_info *mdio_info)
{
	struct mii_dev *bus;
	uint32_t regvalue = 0;
	
	bus = mdio_alloc();
	if (!bus) {
		printf("Failed to allocate NUCLEI_UX600FD-MDIO bus\n");
		return -ENOMEM;
	}

	bus->read = fslral_mdio_master_read_extern;
	bus->write = fslral_mdio_master_write_extern;
	bus->reset = fslral_mdio_master_reset_extern;
	strncpy(bus->name, mdio_info->name, sizeof(bus->name));
	bus->priv = (void *)mdio_info->regs;

	return mdio_register(bus);
}
#endif

 

四、嵌入ETH驱动

  1、mdio注册:在eth初始化中调用mdio_nuclei_ux600fd_init()

  2、在网卡驱动初始化中配置phy

    2.1、结构体初始化

priv->regs = UX600FD_TOP_CFG_REGS_BASE; /* for rgmii cfg */
priv->miiregs_sgmii = XY1000_MDIO_REGS_BASE;
priv->phyname = XY1000_PHY_NAME;
priv->phyaddr = XY1000_PHY_ADDR;
priv->mii_devname = XY1000_MII_NAME;
priv->ethdev = dev;

    2.2、ETH板级初始化流程

/*
* Discover which PHY is attached to the device, and configure it
* properly. If the PHY is not recognized, then return 0
* (failure). Otherwise, return 1
*/

static int fslral_init_phy(struct mac_eth_priv *priv)
{
    struct phy_device *phydev = NULL;
    struct mii_dev *bus;
    u32 rgmii_csr;

    bus = miiphy_get_dev_by_name(priv->mii_devname);

    priv->interface = PHY_INTERFACE_MODE_MII;
    phydev = phy_connect(bus, priv->phyaddr, priv->ethdev, priv->interface);

    if (!phydev)
    {
        priv->phydev = NULL;
        return0;
    }

    phydev->supported &= PHY_BASIC_FEATURES;
    //phydev->advertising = phydev->supported;
    priv->phydev = phydev;

    return phy_config(phydev); /*实际调用了yt8512_config()*/
}

//网卡驱动初始化
int fslral_eth_init(bd_t *bis)
{
	struct eth_device *dev;
	struct mac_eth_priv *priv;
	uint32_t tmp;

//	printf("switch_en 0x%x\n",*(volatile uint32_t *)(0x60000000 + INVISIBLE_CFG));
	//适配新的socket板子,不然网口不通,manage_mode  写0,再复位,解复位,added by pfliu
	tmp = *(volatile uint32_t *)(0x60000000 + 0x4);
	tmp &= (~(1<<14));
	*(volatile uint32_t *)(0x60000000 + 0x4) = tmp;
	ndelay(1000);

	*(volatile uint32_t *)(0x60000000 + 0x28) = 0;
	ndelay(1000);
	*(volatile uint32_t *)(0x60000000 + 0x28) = 0x3;
/**/	 

	//判断switch_en
	if ((*(volatile uint32_t *)(0x60000000 + INVISIBLE_CFG) & 0x01) != 0x01)
	{
		printf("error: switch_en is off\n");
		//set_words((volatile uint32_t *)(0x60000000 + INVISIBLE_CFG), 1, 0, 0, 0x01);
	}
	else
	{	
		set_words((volatile uint32_t *)(0x67800000 + DMA_AXI_WR_CFG), 1, 0, 7, 0x0F);
		set_words((volatile uint32_t *)(0x67800000 + DMA_AXI_RD_CFG), 1, 0, 7, 0x0F);

		//分配网卡设备空间
		dev = (struct eth_device *)malloc(sizeof(*dev));
		if (dev == NULL)
		{
			printf("malloc dev failed\n");
			return 0;
		}
		memset(dev, 0, sizeof(*dev));
		
		//分配私有数据空间
		priv = (struct mac_eth_priv *)malloc(sizeof(*priv));
		if (priv == NULL)
		{
			printf("malloc priv failed\n");
			return 0;
		}
		memcpy(dev->enetaddr, host_addr, 6);
		
		//时钟与复位
		
		//网卡设备IO地址
		priv->io_addr = (void __iomem *)0x67800000;
		sprintf(dev->name, "xy1000_eth");
		
		#ifdef CONFIG_UX600FD_MDIO
		/*added by xiahui, for external phy, 20230718*/
		priv->regs = UX600FD_TOP_CFG_REGS_BASE; /* for rgmii cfg */
		priv->miiregs_sgmii = XY1000_MDIO_REGS_BASE;
		priv->phyname = XY1000_PHY_NAME;
		priv->phyaddr = XY1000_PHY_ADDR;
		priv->mii_devname = XY1000_MII_NAME;
		priv->ethdev = dev;
		#endif		
		//网卡操作集
		dev->priv = priv;
		dev->init = fh_init;
		dev->halt = fh_halt;
		dev->send = fh_send;
		dev->recv = fh_recv;
		
		//分配dma空间
		//有cache,不能完全同步数据
		//dma_addr = (unsigned char *)kmalloc(DMA_PKT_MAX_SIZE * 2, 0);
	 	//dma_addr = (unsigned char *)malloc(DMA_PKT_MAX_SIZE * 2);
		//使用无cache地址:0x40000000 ~ 0x40008000
		dma_addr = ioremap(0x40000000, DMA_PKT_MAX_SIZE * 2);
		if (dma_addr == NULL)
			printf("dma_alloc fail\n");
		
		dma_rx_buf = dma_addr;
		dma_tx_buf = dma_addr + DMA_PKT_MAX_SIZE;
		memset(dma_rx_buf, 0x00, DMA_PKT_MAX_SIZE);
		memset(dma_tx_buf, 0x00, DMA_PKT_MAX_SIZE);
		
		eth_register(dev);
	
		#ifdef CONFIG_UX600FD_MDIO
		fslral_init_phy(priv);
		#endif
	
	}
}

int fh_net_initialize(bd_t *bis)
{
	#ifdef CONFIG_UX600FD_MDIO
	struct xy1000_mdio_info info;

	info.regs = XY1000_MDIO_REGS_BASE;
	info.name = XY1000_MII_NAME;
	
	mdio_nuclei_ux600fd_init(bis, &info);
	#endif
	
	fslral_eth_init(bis);

	return 0;
}

  3、初始化网卡(eth_device的init 实现)中添加以下代码初始化phy和rgmii

/* Start up the PHY */
    if (priv->phydev)
    {
        ret = phy_startup(priv->phydev); /*实际调用了yt8512_startup()*/
        if (ret) {
        printf("Could not initialize PHY %s\n",
        priv->phydev->dev->name);

        return 0;
    }
    fslral_adjust_link(priv);
}

    初始化rgmii函数:

/*
* Configure rgmii on negotiated speed and duplex
* reported by PHY handling code
*/
static void fslral_adjust_link(struct mac_eth_priv *priv)
{
    void __iomem *regs = priv->regs;
    struct phy_device *phydev = priv->phydev;
    u32 duplex, speed, mode, xmii_state;

    if (!phydev->link) {
        printf("%s: No link.\n", phydev->dev->name);
        return;
    }

    if (phydev->duplex)
        duplex = UX600FD_RGMII_FULL_DUPLEX;

    switch (phydev->speed) {
        case1000:
            mode = UX600FD_INTERFACE_MODE_RGMII;
            break;
        case100:
            mode = UX600FD_INTERFACE_MODE_MII_MAC;
            speed = UX600FD_RGMII_SPEED_100;
            xmii_state = 2;
            break;
        case10:
            mode = UX600FD_INTERFACE_MODE_MII_MAC;
            speed = UX600FD_RGMII_SPEED_10;
            xmii_state = 2;
            break;
        default:
            printf("%s: Speed was bad\n", phydev->dev->name);
        break;
    }

    set_words(regs + UX600FD_RGMII_CSR_REGS_OFFSET, 1, UX600FD_RGMII0_MODE_STRT_BIT, \
              UX600FD_RGMII0_MODE_END_BIT, mode);
    set_words(regs + UX600FD_RGMII_CSR_REGS_OFFSET, 1, UX600FD_RGMII0_SPEED_STRT_BIT, \
              UX600FD_RGMII0_SPEED_END_BIT, speed );
    set_words(regs + UX600FD_RGMII_CSR_REGS_OFFSET, 1, 4, 5, xmii_state);
    set_words(regs + UX600FD_RGMII_DUPLEX_REGS_OFFSET, 1, UX600FD_RGMII0_DUPLEX_STRT_BIT, \
              UX600FD_RGMII0_DUPLEX_END_BIT, duplex);

    printf("Speed: %d, %s duplex%s\n", phydev->speed,
    (phydev->duplex) ? "full" : "half",
    (phydev->port == PORT_FIBRE) ? ", fiber mode" : "");
}

  4、关闭网卡(eth_device的halt实现)时添加以下代码关闭phy

/* Shut down the PHY, as needed */
if (priv->phydev)
    phy_shutdown(priv->phydev); /*实际调用了genphy_shutdown()*/

  5、

五、板级ETH初始化

eth_initialize()中的board_eth_init()函数需要自己实现

extern int fh_net_initialize(bd_t *bis);
int board_eth_init(bd_t *bis)
{
    int ret;
    int gpio_rst_phy = 9;

    ret = gpio_request(gpio_rst_phy, "phy_reset");

    if (ret && ret != -EBUSY) {
        printf("gpio: requesting pin %d failed\n", gpio_rst_phy);
        return -1;
    }

    gpio_direction_output(gpio_rst_phy, 0);
    mdelay(200);
    gpio_direction_output(gpio_rst_phy, 1);

    return fh_net_initialize(bis);
}

 

posted @ 2024-01-16 17:05  xMofang  阅读(805)  评论(0编辑  收藏  举报