学无止境--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);
}