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
  1. int eth_initialize(void)  
  2. {  
  3.     int num_devices = 0;  
  4.     struct udevice *dev;  
  5.   
  6.     eth_common_init();  
  7.   
  8.     /* 
  9.      * Devices need to write the hwaddr even if not started so that Linux 
  10.      * will have access to the hwaddr that u-boot stored for the device. 
  11.      * This is accomplished by attempting to probe each device and calling 
  12.      * their write_hwaddr() operation. 
  13.      */  
  14.     uclass_first_device(UCLASS_ETH, &dev);  
  15.     if (!dev) {  
  16.         printf("No ethernet found.\n");  
  17.         bootstage_error(BOOTSTAGE_ID_NET_ETH_START);  
  18.     } else {  
  19.         char *ethprime = getenv("ethprime");  
  20.         struct udevice *prime_dev = NULL;  
  21.   
  22.         if (ethprime)  
  23.             prime_dev = eth_get_dev_by_name(ethprime);  
  24.         if (prime_dev) {  
  25.             eth_set_dev(prime_dev);  
  26.             eth_current_changed();  
  27.         } else {  
  28.             eth_set_dev(NULL);  
  29.         }  
  30.   
  31.         bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT);  
  32.         do {  
  33.             if (num_devices)  
  34.                 printf(", ");  
  35.   
  36.             printf("eth%d: %s", dev->seq, dev->name);  
  37.   
  38.             if (ethprime && dev == prime_dev)  
  39.                 printf(" [PRIME]");  
  40.   
  41.             eth_write_hwaddr(dev);  
  42.   
  43.             uclass_next_device(&dev);  
  44.             num_devices++;  
  45.         } while (dev);  
  46.   
  47.         putc('\n');  
  48.     }  
  49.   
  50.     return num_devices;  
  51. }  

Function called eth_common_init function, the function is as follows:
  1. static void eth_common_init(void)  
  2. {  
  3.     bootstage_mark(BOOTSTAGE_ID_NET_ETH_START);  
  4. #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)  
  5.     miiphy_init();  
  6. #endif  
  7.   
  8. #ifdef CONFIG_PHYLIB  
  9.     phy_init();  
  10. #endif  
  11.   
  12.     /* 
  13.      * If board-specific initialization exists, call it. 
  14.      * If not, call a CPU-specific one 
  15.      */  
  16.     if (board_eth_init != __def_eth_init) {  
  17.         if (board_eth_init(gd->bd) < 0)  
  18.             printf("Board Net Initialization Failed\n");  
  19.     } else if (cpu_eth_init != __def_eth_init) {  
  20.         if (cpu_eth_init(gd->bd) < 0)  
  21.             printf("CPU Net Initialization Failed\n");  
  22.     } else {  
  23. #ifndef CONFIG_DM_ETH  
  24.         printf("Net Initialization Skipped\n");  
  25. #endif  
  26.     }  
  27. }  

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:

  1. int phy_init(void)  
  2. {  
  3. #ifdef CONFIG_PHY_AQUANTIA  
  4.     phy_aquantia_init();  
  5. #endif  
  6. #ifdef CONFIG_PHY_ATHEROS  
  7.     phy_atheros_init();  
  8. #endif  
  9. #ifdef CONFIG_PHY_BROADCOM  
  10.     phy_broadcom_init();  
  11. #endif  
  12. #ifdef CONFIG_PHY_CORTINA  
  13.     phy_cortina_init();  
  14. #endif  
  15. #ifdef CONFIG_PHY_DAVICOM  
  16.     phy_davicom_init();  
  17. #endif  
  18. #ifdef CONFIG_PHY_ET1011C  
  19.     phy_et1011c_init();  
  20. #endif  
  21. #ifdef CONFIG_PHY_LXT  
  22.     phy_lxt_init();  
  23. #endif  
  24. #ifdef CONFIG_PHY_MARVELL  
  25.     phy_marvell_init();  
  26. #endif  
  27. #ifdef CONFIG_PHY_MICREL  
  28.     phy_micrel_init();  
  29. #endif  
  30. #ifdef CONFIG_PHY_NATSEMI  
  31.     phy_natsemi_init();  
  32. #endif  
  33. #ifdef CONFIG_PHY_REALTEK  
  34.     phy_realtek_init();  
  35. #endif  
  36. #ifdef CONFIG_PHY_SMSC  
  37.     phy_smsc_init();  
  38. #endif  
  39. #ifdef CONFIG_PHY_TERANETICS  
  40.     phy_teranetics_init();  
  41. #endif  
  42. #ifdef CONFIG_PHY_VITESSE  
  43.     phy_vitesse_init();  
  44. #endif  
  45.   
  46.     return 0;  
  47. }  

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

  1. #include <miiphy.h>  
  2.   
  3. /* This code does not check the partner abilities. */  
  4. static int smsc_parse_status(struct phy_device *phydev)  
  5. {  
  6.     int mii_reg;  
  7.   
  8.     mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);  
  9.   
  10.     if (mii_reg & (BMSR_100FULL | BMSR_100HALF))  
  11.         phydev->speed = SPEED_100;  
  12.     else  
  13.         phydev->speed = SPEED_10;  
  14.   
  15.     if (mii_reg & (BMSR_10FULL | BMSR_100FULL))  
  16.         phydev->duplex = DUPLEX_FULL;  
  17.     else  
  18.         phydev->duplex = DUPLEX_HALF;  
  19.   
  20.     return 0;  
  21. }  
  22.   
  23. static int smsc_startup(struct phy_device *phydev)  
  24. {  
  25.     genphy_update_link(phydev);  
  26.     smsc_parse_status(phydev);  
  27.     return 0;  
  28. }  
  29.   
  30. static struct phy_driver lan8700_driver = {  
  31.     .name = "SMSC LAN8700",  
  32.     .uid = 0x0007c0c0,  
  33.     .mask = 0xffff0,  
  34.     .features = PHY_BASIC_FEATURES,  
  35.     .config = &genphy_config_aneg,  
  36.     .startup = &smsc_startup,  
  37.     .shutdown = &genphy_shutdown,  
  38. };  
  39.   
  40. static struct phy_driver lan911x_driver = {  
  41.     .name = "SMSC LAN911x Internal PHY",  
  42.     .uid = 0x0007c0d0,  
  43.     .mask = 0xffff0,  
  44.     .features = PHY_BASIC_FEATURES,  
  45.     .config = &genphy_config_aneg,  
  46.     .startup = &smsc_startup,  
  47.     .shutdown = &genphy_shutdown,  
  48. };  
  49.   
  50. static struct phy_driver lan8710_driver = {  
  51.     .name = "SMSC LAN8710/LAN8720",  
  52.     .uid = 0x0007c0f0,  
  53.     .mask = 0xffff0,  
  54.     .features = PHY_BASIC_FEATURES,  
  55.     .config = &genphy_config_aneg,  
  56.     .startup = &genphy_startup,  
  57.     .shutdown = &genphy_shutdown,  
  58. };  
  59.   
  60. int phy_smsc_init(void)  
  61. {  
  62.     phy_register(&lan8710_driver);  
  63.     phy_register(&lan911x_driver);  
  64.     phy_register(&lan8700_driver);  
  65.   
  66.     return 0;  
  67. }  

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:

  1. int board_eth_init(bd_t *bis)  
  2. {  
  3.     int rv, n = 0;  
  4.     uint8_t mac_addr[6];  
  5.     uint32_t mac_hi, mac_lo;  
  6.     __maybe_unused struct am335x_baseboard_id header;  
  7.   
  8.     /* try reading mac address from efuse */  
  9.     mac_lo = readl(&cdev->macid0l);  
  10.     mac_hi = readl(&cdev->macid0h);  
  11.     mac_addr[0] = mac_hi & 0xFF;  
  12.     mac_addr[1] = (mac_hi & 0xFF00) >> 8;  
  13.     mac_addr[2] = (mac_hi & 0xFF0000) >> 16;  
  14.     mac_addr[3] = (mac_hi & 0xFF000000) >> 24;  
  15.     mac_addr[4] = mac_lo & 0xFF;  
  16.     mac_addr[5] = (mac_lo & 0xFF00) >> 8;  
  17.   
  18. #if (defined(CONFIG_DRIVER_TI_CPSW) && !defined(CONFIG_SPL_BUILD)) || \  
  19.     (defined(CONFIG_SPL_ETH_SUPPORT) && defined(CONFIG_SPL_BUILD))  
  20.     if (!getenv("ethaddr")) {  
  21.         printf("<ethaddr> not set. Validating first E-fuse MAC\n");  
  22.   
  23.         if (is_valid_ethaddr(mac_addr))  
  24.             eth_setenv_enetaddr("ethaddr", mac_addr);  
  25.     }  
  26.   
  27. #ifdef CONFIG_DRIVER_TI_CPSW  
  28.   
  29.     mac_lo = readl(&cdev->macid1l);  
  30.     mac_hi = readl(&cdev->macid1h);  
  31.     mac_addr[0] = mac_hi & 0xFF;  
  32.     mac_addr[1] = (mac_hi & 0xFF00) >> 8;  
  33.     mac_addr[2] = (mac_hi & 0xFF0000) >> 16;  
  34.     mac_addr[3] = (mac_hi & 0xFF000000) >> 24;  
  35.     mac_addr[4] = mac_lo & 0xFF;  
  36.     mac_addr[5] = (mac_lo & 0xFF00) >> 8;  
  37.   
  38.     if (!getenv("eth1addr")) {  
  39.         if (is_valid_ethaddr(mac_addr))  
  40.             eth_setenv_enetaddr("eth1addr", mac_addr);  
  41.     }  
  42.   
  43.     if (read_eeprom(&header) < 0)  
  44.         puts("Could not get board ID.\n");  
  45.   
  46.     if (board_is_bone(&header) || board_is_bone_lt(&header) ||  
  47.         board_is_idk(&header)) {  
  48.         writel(MII_MODE_ENABLE, &cdev->miisel);  
  49.         cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =  
  50.                 PHY_INTERFACE_MODE_MII;  
  51.     } else {  
  52.         writel((RGMII_MODE_ENABLE | RGMII_INT_DELAY), &cdev->miisel);  
  53.         cpsw_slaves[0].phy_if = cpsw_slaves[1].phy_if =  
  54.                 PHY_INTERFACE_MODE_RGMII;  
  55.     }  
  56.   
  57.     rv = cpsw_register(&cpsw_data);  
  58.     if (rv < 0)  
  59.         printf("Error %d registering CPSW switch\n", rv);  
  60.     else  
  61.         n += rv;  
  62. #endif  
  63.   
  64.     /* 
  65.      * 
  66.      * CPSW RGMII Internal Delay Mode is not supported in all PVT 
  67.      * operating points.  So we must set the TX clock delay feature 
  68.      * in the AR8051 PHY.  Since we only support a single ethernet 
  69.      * device in U-Boot, we only do this for the first instance. 
  70.      */  
  71. #define AR8051_PHY_DEBUG_ADDR_REG   0x1d  
  72. #define AR8051_PHY_DEBUG_DATA_REG   0x1e  
  73. #define AR8051_DEBUG_RGMII_CLK_DLY_REG  0x5  
  74. #define AR8051_RGMII_TX_CLK_DLY     0x100  
  75.   
  76.     if (board_is_evm_sk(&header) || board_is_gp_evm(&header)) {  
  77.         const char *devname;  
  78.         devname = miiphy_get_current_dev();  
  79.   
  80.         miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_ADDR_REG,  
  81.                 AR8051_DEBUG_RGMII_CLK_DLY_REG);  
  82.         miiphy_write(devname, 0x0, AR8051_PHY_DEBUG_DATA_REG,  
  83.                 AR8051_RGMII_TX_CLK_DLY);  
  84.     }  
  85. #endif  
  86. #if defined(CONFIG_USB_ETHER) && \  
  87.     (!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_USBETH_SUPPORT))  
  88.     if (is_valid_ethaddr(mac_addr))  
  89.         eth_setenv_enetaddr("usbnet_devaddr", mac_addr);  
  90.   
  91.     rv = usb_eth_initialize(bis);  
  92.     if (rv < 0)  
  93.         printf("Error %d registering USB_ETHER\n", rv);  
  94.     else  
  95.         n += rv;  
  96. #endif  
  97.     return n;  
  98. }  

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:

  1. int __weak get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id)  
  2. {  
  3.     int phy_reg;  
  4.   
  5.     /* Grab the bits from PHYIR1, and put them 
  6.      * in the upper half */  
  7.     phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);  
  8.   
  9.     if (phy_reg < 0)  
  10.         return -EIO;  
  11.   
  12.     *phy_id = (phy_reg & 0xffff) << 16;  
  13.   
  14.     /* Grab the bits from PHYIR2, and put them in the lower half */  
  15.     phy_reg = bus->read(bus, addr, devad, MII_PHYSID2);  
  16.   
  17.     if (phy_reg < 0)  
  18.         return -EIO;  
  19.   
  20.     *phy_id |= (phy_reg & 0xffff);  
  21.   
  22.     return 0;  
  23. }  

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

  1. static int cpsw_mdio_read(struct mii_dev *bus, int phy_id,  
  2.                 int dev_addr, int phy_reg)  
  3. {  
  4.     int data;  
  5.     u32 reg;  
  6.   
  7.     if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)  
  8.         return -EINVAL;  
  9.   
  10.     wait_for_user_access();  
  11.     reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |  
  12.            (phy_id << 16));  
  13.     __raw_writel(reg, &mdio_regs->user[0].access);  
  14.     reg = wait_for_user_access();  
  15.   
  16.     data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;  
  17.     return data;  
  18. }  

In this function, the wait_for_user_access function is called first:

  1. static inline u32 wait_for_user_access(void)  
  2. {  
  3.     u32 reg = 0;  
  4.     int timeout = MDIO_TIMEOUT;  
  5.   
  6.     while (timeout-- &&  
  7.     ((reg = __raw_readl(&mdio_regs->user[0].access)) & USERACCESS_GO))  
  8.         udelay(10);  
  9.   
  10.     if (timeout == -1) {  
  11.         printf("wait_for_user_access Timeout\n");  
  12.         return -ETIMEDOUT;  
  13.     }  
  14.     return reg;  
  15. }  

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.

评论

此博客中的热门博文

RTEMS Network Transplantation - rtems system initialization process analysis

RTEMS-libbsd generates drive device firmware

Add wpa_supplicant_fork command on RTEMS-libbsd