RTEMS network driver port-MDIO communication
First look at the connection between bbb board processor and network card :
To be clear, bbb board am3359 processor is comes with MAC.
The figure can be seen, the processor and network card connection is GMII way, using MDIO and network card control register to communicate.
First understand how uboot achieve mdio communication.
View uboot source code, first check the network card registration and initialization methods:
Uboot After starting.s starts the function that was first called on the hardware initialization is the eth_initialize function:
Source: /net/eth.c
Function called eth_common_init function, the function is as follows:
- static void eth_common_init(void)
- {
- bootstage_mark(BOOTSTAGE_ID_NET_ETH_START);
- #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
- miiphy_init();
- #endif
- #ifdef CONFIG_PHYLIB
- phy_init();
- #endif
- /*
- * If board-specific initialization exists, call it.
- * If not, call a CPU-specific one
- */
- if (board_eth_init != __def_eth_init) {
- if (board_eth_init(gd->bd) < 0)
- printf("Board Net Initialization Failed\n");
- } else if (cpu_eth_init != __def_eth_init) {
- if (cpu_eth_init(gd->bd) < 0)
- printf("CPU Net Initialization Failed\n");
- } else {
- #ifndef CONFIG_DM_ETH
- printf("Net Initialization Skipped\n");
- #endif
- }
- }
Where the miiphy_init function is a registered mii device that lets it join the linked list.
Then call phy_init function, the function of the network card device for the registration and initialization:
- int phy_init(void)
- {
- #ifdef CONFIG_PHY_AQUANTIA
- phy_aquantia_init();
- #endif
- #ifdef CONFIG_PHY_ATHEROS
- phy_atheros_init();
- #endif
- #ifdef CONFIG_PHY_BROADCOM
- phy_broadcom_init();
- #endif
- #ifdef CONFIG_PHY_CORTINA
- phy_cortina_init();
- #endif
- #ifdef CONFIG_PHY_DAVICOM
- phy_davicom_init();
- #endif
- #ifdef CONFIG_PHY_ET1011C
- phy_et1011c_init();
- #endif
- #ifdef CONFIG_PHY_LXT
- phy_lxt_init();
- #endif
- #ifdef CONFIG_PHY_MARVELL
- phy_marvell_init();
- #endif
- #ifdef CONFIG_PHY_MICREL
- phy_micrel_init();
- #endif
- #ifdef CONFIG_PHY_NATSEMI
- phy_natsemi_init();
- #endif
- #ifdef CONFIG_PHY_REALTEK
- phy_realtek_init();
- #endif
- #ifdef CONFIG_PHY_SMSC
- phy_smsc_init();
- #endif
- #ifdef CONFIG_PHY_TERANETICS
- phy_teranetics_init();
- #endif
- #ifdef CONFIG_PHY_VITESSE
- phy_vitesse_init();
- #endif
- return 0;
- }
the network card lan8710 is smsc company, select phy_smsc_init function to compile:
The function is implemented as follows:
/drivers/net/phy/smsc.c
- #include <miiphy.h>
- /* This code does not check the partner abilities. */
- static int smsc_parse_status(struct phy_device *phydev)
- {
- int mii_reg;
- mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
- if (mii_reg & (BMSR_100FULL | BMSR_100HALF))
- phydev->speed = SPEED_100;
- else
- phydev->speed = SPEED_10;
- if (mii_reg & (BMSR_10FULL | BMSR_100FULL))
- phydev->duplex = DUPLEX_FULL;
- else
- phydev->duplex = DUPLEX_HALF;
- return 0;
- }
- static int smsc_startup(struct phy_device *phydev)
- {
- genphy_update_link(phydev);
- smsc_parse_status(phydev);
- return 0;
- }
- static struct phy_driver lan8700_driver = {
- .name = "SMSC LAN8700",
- .uid = 0x0007c0c0,
- .mask = 0xffff0,
- .features = PHY_BASIC_FEATURES,
- .config = &genphy_config_aneg,
- .startup = &smsc_startup,
- .shutdown = &genphy_shutdown,
- };
- static struct phy_driver lan911x_driver = {
- .name = "SMSC LAN911x Internal PHY",
- .uid = 0x0007c0d0,
- .mask = 0xffff0,
- .features = PHY_BASIC_FEATURES,
- .config = &genphy_config_aneg,
- .startup = &smsc_startup,
- .shutdown = &genphy_shutdown,
- };
- static struct phy_driver lan8710_driver = {
- .name = "SMSC LAN8710/LAN8720",
- .uid = 0x0007c0f0,
- .mask = 0xffff0,
- .features = PHY_BASIC_FEATURES,
- .config = &genphy_config_aneg,
- .startup = &genphy_startup,
- .shutdown = &genphy_shutdown,
- };
- int phy_smsc_init(void)
- {
- phy_register(&lan8710_driver);
- phy_register(&lan911x_driver);
- phy_register(&lan8700_driver);
- return 0;
- }
You can see for the lan8710a network card phy_register, that is, the network card device to join the linked list.
Then go back to the eth_common_init function and execute the board_eth_init function down:
The function is defined in the board.c file, including the bbb board specific configuration process, very important
The concrete realization is as follows:
- int board_eth_init(bd_t *bis)
- {
- int rv, n = 0;
- uint8_t mac_addr[6];
- uint32_t mac_hi, mac_lo;
- __maybe_unused struct am335x_baseboard_id header;
- /* try reading mac address from efuse */
- mac_lo = readl(&cdev->macid0l);
- mac_hi = readl(&cdev->macid0h);
- mac_addr[0] = mac_hi & 0xFF;
- mac_addr[1] = (mac_hi & 0xFF00) >> 8;
- mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
- mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
- mac_addr[4] = mac_lo & 0xFF;
- mac_addr[5] = (mac_lo & 0xFF00) >> 8;
- #if (defined(CONFIG_DRIVER_TI_CPSW) && !defined(CONFIG_SPL_BUILD)) || \
- (defined(CONFIG_SPL_ETH_SUPPORT) && defined(CONFIG_SPL_BUILD))
- if (!getenv("ethaddr")) {
- printf("<ethaddr> not set. Validating first E-fuse MAC\n");
- if (is_valid_ethaddr(mac_addr))
- eth_setenv_enetaddr("ethaddr", mac_addr);
- }
- #ifdef CONFIG_DRIVER_TI_CPSW
- mac_lo = readl(&cdev->macid1l);
- mac_hi = readl(&cdev->macid1h);
- mac_addr[0] = mac_hi & 0xFF;
- mac_addr[1] = (mac_hi & 0xFF00) >> 8;
- mac_addr[2] = (mac_hi & 0xFF0000) >> 16;
- mac_addr[3] = (mac_hi & 0xFF000000) >> 24;
- mac_addr[4] = mac_lo & 0xFF;
- mac_addr[5] = (mac_lo & 0xFF00) >> 8;
- if (!getenv("eth1addr")) {
- if (is_valid_ethaddr(mac_addr))
- eth_setenv_enetaddr("eth1addr", mac_addr);
- }
- if (read_eeprom(&header) < 0)
- puts("Could not get board ID.\n");
- if (board_is_bone(&header) || board_is_bone_lt(&header) ||
- board_is_idk(&header)) {
- writel(MII_MODE_ENABLE, &cdev->miisel);
- cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =
- PHY_INTERFACE_MODE_MII;
- } else {
- writel((RGMII_MODE_ENABLE | RGMII_INT_DELAY), &cdev->miisel);
- cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =
- PHY_INTERFACE_MODE_RGMII;
- }
- rv = cpsw_register(&cpsw_data);
- if (rv < 0)
- printf("Error %d registering CPSW switch\n", rv);
- else
- n += rv;
- #endif
- /*
- *
- * CPSW RGMII Internal Delay Mode is not supported in all PVT
- * operating points. So we must set the TX clock delay feature
- * in the AR8051 PHY. Since we only support a single ethernet
- * device in U-Boot, we only do this for the first instance.
- */
- #define AR8051_PHY_DEBUG_ADDR_REG 0x1d
- #define AR8051_PHY_DEBUG_DATA_REG 0x1e
- #define AR8051_DEBUG_RGMII_CLK_DLY_REG 0x5
- #define AR8051_RGMII_TX_CLK_DLY 0x100
- if (board_is_evm_sk(&header) || board_is_gp_evm(&header)) {
- const char *devname;
- devname = miiphy_get_current_dev();
- miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_ADDR_REG,
- AR8051_DEBUG_RGMII_CLK_DLY_REG);
- miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_DATA_REG,
- AR8051_RGMII_TX_CLK_DLY);
- }
- #endif
- #if defined(CONFIG_USB_ETHER) && \
- (!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_USBETH_SUPPORT))
- if (is_valid_ethaddr(mac_addr))
- eth_setenv_enetaddr("usbnet_devaddr", mac_addr);
- rv = usb_eth_initialize(bis);
- if (rv < 0)
- printf("Error %d registering USB_ETHER\n", rv);
- else
- n += rv;
- #endif
- return n;
- }
Analyze the function:
First read the mac address, and then set it as an environment variable, easy to view in the command line.
(MII_MODE_ENABLE, & cdev-> miisel), call this system io function, the MII_MODE_ENABLE macro definition of the corresponding value written to & cdev-> miisel this register, and the register address is CTRL_DEVICE_BASE + miisel , That is, CTRL_DEVICE_BASE the basic address plus miisel offset, CTRL_DEVICE_BASE the macro value is 0x4A101000, defined in the arch / arm / include / asm / arch-am33xx / hardware_am33xx.h file.
After enabling, board_eth_init continues to call the cpsw_register function. In this function, it will communicate with the network card mdio to view the network card ID in the network card register.
The specific function is in the /drivers/net/phy/phy.c file:
- int __weak get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id)
- {
- int phy_reg;
- /* Grab the bits from PHYIR1, and put them
- * in the upper half */
- phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);
- if (phy_reg < 0)
- return -EIO;
- *phy_id = (phy_reg & 0xffff) << 16;
- /* Grab the bits from PHYIR2, and put them in the lower half */
- phy_reg = bus->read(bus, addr, devad, MII_PHYSID2);
- if (phy_reg < 0)
- return -EIO;
- *phy_id |= (phy_reg & 0xffff);
- return 0;
- }
The registers of the NIC are IDID and PHYSID2, so they need to read these two registers.
The specific instructions for implementing the read register are: bus-> read (bus, addr, devad, MII_PHYSID1)
This bus read function is actually CPSW_mdio_read function, as follows:
/drivers/netcpsw.c
- static int cpsw_mdio_read(struct mii_dev *bus, int phy_id,
- int dev_addr, int phy_reg)
- {
- int data;
- u32 reg;
- if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
- return -EINVAL;
- wait_for_user_access();
- reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |
- (phy_id << 16));
- __raw_writel(reg, &mdio_regs->user[0].access);
- reg = wait_for_user_access();
- data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;
- return data;
- }
In this function, the wait_for_user_access function is called first:
- static inline u32 wait_for_user_access(void)
- {
- u32 reg = 0;
- int timeout = MDIO_TIMEOUT;
- while (timeout-- &&
- ((reg = __raw_readl(&mdio_regs->user[0].access)) & USERACCESS_GO))
- udelay(10);
- if (timeout == -1) {
- printf("wait_for_user_access Timeout\n");
- return -ETIMEDOUT;
- }
- return reg;
- }
The function of the function is to constantly read a card register value, to determine whether access, if you can return to non-0 value, or return a negative number.
And then return to the above function, call the __raw_writel function to write the value of the register, the value reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) | (phy_id << 16)); said to communicate and read the register , So that the network card to be prepared.
Register address is & mdio_regs-> user [0] .access, after writing, continue to call reg = wait_for_user_access (); read the value of the register.
And then return the reg value to judge: data = (reg & USERACCESS_ACK)? (Reg & USERACCESS_DATA): -1;
If reg & USERACCESS_ACK is positive, that is, the network card register to make a response, that data is right, then reg & USERACCESS_DATA, its assignment to the data. Otherwise the data assignment is -1, indicating that no data is being read.
Read the register and return to the get_phy_id function, phy_reg = bus-> read (bus, addr, devad, MII_PHYSID1);
This time the value of phy_reg is the value of the register being read
Then read the next register: phy_reg = bus-> read (bus, addr, devad, MII_PHYSID2);
Because the network card id a register fit, divided into two registers. After the two registers are read, a simple operation can be a complete network card ID.
评论
发表评论