| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| /** |
| * @brief Microchip LAN9730 USB to 10/100 Ethernet adaptor |
| * @see https://www.microchip.com/wwwproducts/en/LAN9730 |
| */ |
| #include <autoconf.h> |
| #include <usbdrivers/gen_config.h> |
| #include <lwip/gen_config.h> |
| #ifdef CONFIG_LIB_LWIP |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <errno.h> |
| #include <usb/drivers/lan9730.h> |
| |
| #include <netif/etharp.h> |
| #include <lwip/stats.h> |
| |
| #include "../services.h" |
| |
| #define COL_RX "\e[42;30m" |
| #define COL_TX "\e[43;30m" |
| #define COL_DEF "\e[0;0m" |
| |
| //#define ETH_TRAFFIC_DEBUG |
| #define ETH_ENABLE_IRQS |
| |
| #define INT_MACRTO BIT(19) |
| #define INT_RXFIFO BIT(18) |
| #define INT_TXSTOP BIT(17) |
| #define INT_RXSTOP BIT(16) |
| #define INT_PHY BIT(15) |
| #define INT_TXE BIT(14) |
| #define INT_TDFU BIT(13) |
| #define INT_TDF0 BIT(12) |
| #define INT_RXDF BIT(11) |
| #define INT_GPIO(x) (((x) & MASK(10)) << 0) |
| |
| /* SCSR (System Control and Status Registers) */ |
| #define REG_ID_REV 0x000 |
| #define REG_INT_STS 0x008 |
| #define REG_RX_CFG 0x00C |
| #define REG_TX_CFG 0x010 |
| #define REG_HW_CFG 0x014 |
| #define REG_RX_FIFO_INF 0x018 |
| #define REG_TX_FIFO_INF 0x01C |
| #define REG_PMT_CTL 0x020 |
| #define REG_LED_GPIO_CFG 0x024 |
| #define REG_GPIO_CFG 0x028 |
| #define REG_AFC_CFG 0x02C |
| #define REG_E2P_CMD 0x030 |
| #define REG_E2P_DATA 0x034 |
| #define REG_BURST_CAP 0x038 |
| #define REG_DP_SEL 0x040 |
| #define REG_DP_CMD 0x044 |
| #define REG_DP_ADDR 0x048 |
| #define REG_DP_DATA0 0x04C |
| #define REG_DP_DATA1 0x050 |
| #define REG_GPIO_WAKE 0x064 |
| #define REG_INT_EP_CTL 0x068 |
| #define REG_BULK_IN_DLY 0x06C |
| #define REG_DBG_RX_FIFO_LVL 0x070 |
| #define REG_DBG_RX_FIFO_PTR 0x074 |
| #define REG_DBG_TX_FIFO_LVL 0x078 |
| #define REG_DBG_TX_FIFO_PTR 0x07C |
| #define REG_HS_ATTR 0x0A0 |
| #define REG_FS_ATTR 0x0A4 |
| #define REG_STRNG_ATTR0 0x0A8 |
| #define REG_STRNG_ATTR1 0x0AC |
| #define REG_FLAG_ATTR 0x0B0 |
| /* MCSR (MAC Control and Status Registers */ |
| #define REG_MAC_CR 0x100 |
| #define REG_ADDRH 0x104 |
| #define REG_ADDRL 0x108 |
| #define REG_HASHH 0x10C |
| #define REG_HASHL 0x110 |
| #define REG_MII_ACCESS 0x114 |
| #define REG_MII_DATA 0x118 |
| #define REG_FLOW 0x11C |
| #define REG_VLAN1 0x120 |
| #define REG_VLAN2 0x124 |
| #define REG_WUFF 0x128 |
| #define REG_WUCSR 0x12C |
| #define REG_COE_CR 0x130 |
| |
| /**** PHY registers ****/ |
| /* IEEE Defined Registers */ |
| #define PHYREG_BASIC_CONTROL 0 |
| #define PHYREG_BASIC_STATUS 1 |
| #define PHYREG_PHY_ID1 2 |
| #define PHYREG_PHY_ID2 3 |
| #define PHYREG_AUTONEG_ADVERTISEMENT 4 |
| #define PHYREG_AUTONEG_LINK_PARTNER_ABILITY 5 |
| #define PHYREG_AUTONEG_EXPANSION 6 |
| #define PHYREG_AUTONEG_NEXT_PAGE 7 |
| #define PHYREG_AUTONEG_LINK_PARTNER_NEXT_PAGE_ABILITY 8 |
| #define PHYREG_1000BASET_CONTROL 9 |
| #define PHYREG_1000BASET_STATUS 10 |
| #define PHYREG_EXTENDED_CONTROL 11 |
| #define PHYREG_EXTENDED_DATA_WRITE 12 |
| #define PHYREG_EXTENDED_DATA_READ 13 |
| #define PHYREG_EXTENDED_STATUS 15 |
| |
| /*** KSZ9021 ***/ |
| #if 1 |
| /* Vendor Specific Registers */ |
| #define PHYREG_REMOTE_LOOPBACK_LED_MODE 17 |
| #define PHYREG_LINKMD_CABLE_DIAGNOSTIC 18 |
| #define PHYREG_DIGITAL_PMA_PCS_STATUS 19 |
| #define PHYREG_RXER_COUNTER 21 |
| #define PHYREG_INTERRUPT_CONTROL_STATUS 27 |
| #define PHYREG_DIGITAL_DEBUG_CONTROL1 28 |
| #define PHYREG_PHY_CONTROL 31 |
| |
| /* Extended Registers */ |
| #define PHYREG_COMMON_CONTROL 256 |
| #define PHYREG_STRAP_STATUS 257 |
| #define PHYREG_OPERATION_MODE_STRAP_OVERRIDE 258 |
| #define PHYREG_OPERATION_MODE_STRAP_STATUS 259 |
| #define PHYREG_RGMII_CLOCK_AND_CONTROL_PAD_SKEW 260 |
| #define PHYREG_RGMII_RX_DATA_PAD_SKEW 261 |
| #define PHYREG_ANALOG_TEST_REGISTER 263 |
| #endif |
| |
| /* Basic control */ |
| #define PHYBC_SOFT_RESET BIT(15) |
| #define PHYBC_LOOPBACK BIT(14) |
| #define PHYBC_SPEED_100 BIT(13) |
| #define PHYBC_AUTONEG_EN BIT(12) |
| #define PHYBC_POWER_DOWN BIT(11) |
| #define PHYBC_AUTONEG_RESTART BIT( 9) |
| #define PHYBC_FULLDUPLEX BIT( 8) |
| #define PHYBC_COLLISION_TST BIT( 7) |
| /* Status */ |
| #define PHYBS_100BASE_T4 BIT(15) |
| #define PHYBS_100BASE_TX_FULL BIT(14) |
| #define PHYBS_100BASE_TX_HALF BIT(13) |
| #define PHYBS_10BASE_TX_FULL BIT(12) |
| #define PHYBS_10BASE_TX_HALF BIT(11) |
| #define PHYBS_AUTONEG_DONE BIT( 5) |
| #define PHYBS_REMOTE_FAULT BIT( 4) |
| #define PHYBS_AUTONEG_AVAIL BIT( 3) |
| #define PHYBS_LINKUP BIT( 2) |
| #define PHYBS_JABBER BIT( 1) |
| #define PHYBS_EXTCAP BIT( 0) |
| /* Auto neg AD */ |
| #define PHYAN_AD_REMOTE_FAULT BIT(13) |
| #define PHYAN_AD_PAUSE_NONE (0x0 * BIT(10)) |
| #define PHYAN_AD_PAUSE_SYM (0x1 * BIT(10)) |
| #define PHYAN_AD_PAUSE_ASYM (0x2 * BIT(10)) |
| #define PHYAN_AD_PAUSE_BOTH (0x3 * BIT(10)) |
| #define PHYAN_AD_PAUSE_MASK PHYAN_AD_PAUSE_BOTH |
| #define PHYAN_AD_100BASE_FULL BIT(8) |
| #define PHYAN_AD_100BASE_HALF BIT(7) |
| #define PHYAN_AD_10BASE_FULL BIT(6) |
| #define PHYAN_AD_10BASE_HALF BIT(5) |
| /* Link partner ability */ |
| #define PHYAN_ADLP_NEXT_PAGE BIT(15) |
| #define PHYAN_ADLP_ACK BIT(14) |
| #define PHYAN_ADLP_REMOTE_FAULT BIT(13) |
| #define PHYAN_ADLP_PAUSE_NONE (0x0 * BIT(10)) |
| #define PHYAN_ADLP_PAUSE_SYM (0x1 * BIT(10)) |
| #define PHYAN_ADLP_PAUSE_ASYM (0x2 * BIT(10)) |
| #define PHYAN_ADLP_PAUSE_BOTH (0x3 * BIT(10)) |
| #define PHYAN_ADLP_PAUSE_MASK PHYAN_AD_PAUSE_BOTH |
| #define PHYAN_ADLP_100BASE_T4 BIT(9) |
| #define PHYAN_ADLP_100BASE_FULL BIT(8) |
| #define PHYAN_ADLP_100BASE_HALF BIT(7) |
| #define PHYAN_ADLP_10BASE_FULL BIT(6) |
| #define PHYAN_ADLP_10BASE_HALF BIT(5) |
| |
| /***********************/ |
| |
| #define MACCR_FULL_DUPLEX BIT(20) |
| #define MACCR_TXEN BIT( 3) |
| #define MACCR_RXEN BIT( 2) |
| |
| #define FEAT_REMOTE_WAKEUP 0x00 |
| #define FEAT_EPSTALL 0x02 |
| #define STAT_REMOTE_WAKEUP BIT(1) |
| #define STAT_SELF_POWERED BIT(0) |
| #define STAT_BULK_STALL BIT(0) |
| |
| /* Bitfields for RX usb packet header */ |
| #define RXSTAT_FILTERING_FAIL BIT(30) |
| #define RXSTAT_FRAME_LENGTH(x) ((x) << 16) |
| #define RXSTAT_FRAME_LENGTH_MASK FRAME_LENGTH(0x3fff) |
| #define RXSTAT_GET_FRAME_LENGTH(x) (((x) & RXSTAT_FRAME_LENGTH_MASK) >> 16) |
| #define RXSTAT_ERROR_STATUS BIT(15) |
| #define RXSTAT_BROADCAST_FRAME BIT(13) |
| #define RXSTAT_LENGTH_ERROR BIT(12) |
| #define RXSTAT_RUNT_FRAME BIT(11) |
| #define RXSTAT_MULTICAST_FRAME BIT(10) |
| #define RXSTAT_FRAME_TOO_LONG BIT( 7) |
| #define RXSTAT_COLLISION_SEEN BIT( 6) |
| #define RXSTAT_FRAME_TYPE BIT( 5) |
| #define RXSTAT_RECEIVE_WDG_TO BIT( 4) |
| #define RXSTAT_MII_ERROR BIT( 3) |
| #define RXSTAT_DRIBBLING_BIT BIT( 2) |
| #define RXSTAT_CRC_ERROR BIT( 1) |
| |
| /* Bitfields for TX and RX usb packet header */ |
| #define TXCMDA_DATA_START_OFFSET(x) ((x) << 16) |
| #define TXCMDA_DATA_START_OFFSET_MASK DATA_START_OFFSET(0x3) |
| #define TXCMDA_FIRST_SEGMENT BIT(13) |
| #define TXCMDA_LAST_SEGMENT BIT(12) |
| #define TXCMDA_BUFFER_SIZE(x) ((x) << 0) |
| #define TXCMDA_BUFFER_SIZE_MASK BUFFER_SIZE(0x7ff) |
| |
| #define TXCMDB_CHECKSUM_ENABLE BIT(14) |
| #define TXCMDB_ADD_CRC_DISABLE BIT(13) |
| #define TXCMDB_DISABLE_ETH_PADDING BIT(12) |
| #define TXCMDB_FRAME_LENGTH(x) ((x) << 0) |
| #define TXCMDB_FRAME_LENGTH_MASK TXSTAT_FRAME_LENGTH(0x7ff) |
| |
| #define RXSTS_FILTERING_FAIL BIT(30) |
| #define RXSTS_FRAME_LENGTH(x) (((x) >> 16) & 0x3fff) |
| #define RXSTS_ERROR_STATUS BIT(15) |
| #define RXSTS_BROADCAST_FRAME BIT(13) |
| #define RXSTS_LENGTH_ERROR BIT(12) |
| #define RXSTS_RUNT_FRAME BIT(11) |
| #define RXSTS_MULTICAST_FRAME BIT(10) |
| #define RXSTS_FRAME_TOO_LONG BIT( 7) |
| #define RXSTS_COLLISION_SEEN BIT( 6) |
| #define RXSTS_FRAME_TYPE BIT( 5) |
| #define RXSTS_RX_WDT_TO BIT( 4) |
| #define RXSTS_MII_ERROR BIT( 3) |
| #define RXSTS_DRIBBLING_BIT BIT( 2) |
| #define RXSTS_CRC_ERROR BIT( 1) |
| |
| /* Bitfields for interrupt USB packet */ |
| #define ETHINT_MACRTO BIT(19) |
| #define ETHINT_RX_FULL BIT(18) |
| #define ETHINT_TXSTOP BIT(17) |
| #define ETHINT_RXSTOP BIT(16) |
| #define ETHINT_PHY BIT(15) |
| #define ETHINT_TX_EMPTY BIT(14) |
| #define ETHINT_TDFU BIT(13) |
| #define ETHINT_TDFO BIT(12) |
| #define ETHINT_RXDF BIT(11) |
| #define ETHINT_GPIO(x) (((x) >> 0) & 0x7ff) |
| |
| /* Bitfields for TX/RX configuration */ |
| #define ETHTXCFG_TXON BIT(2) |
| #define ETHTXCFG_TXSTOP BIT(1) |
| #define ETHTXCFG_TXFLUSH BIT(0) |
| #define ETHRXCFG_RXFLUSH BIT(0) |
| |
| struct usb_eth { |
| struct usb_dev *udev; |
| /// Endpoints |
| struct endpoint *ep_in; |
| struct endpoint *ep_out; |
| struct endpoint *ep_int; |
| /// IRQs |
| struct xact reg_read_xact[2]; |
| struct xact reg_write_xact[2]; |
| struct xact rx_xact; |
| #if defined(ETH_ENABLE_IRQS) |
| uint32_t *intbm; |
| struct xact int_xact; |
| #endif |
| }; |
| |
| static inline struct usb_eth *netif_get_eth_driver(struct netif *netif) |
| { |
| return (struct usb_eth *)netif->state; |
| } |
| |
| static inline struct usbreq __clear_ep_feature_req(uint16_t ep, uint16_t feature) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_OTHER), |
| .bRequest = CLR_FEATURE, |
| .wValue = feature, |
| .wIndex = ep, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __clear_remote_wakeup(uint16_t ep) |
| { |
| return __clear_ep_feature_req(ep, FEAT_REMOTE_WAKEUP); |
| } |
| |
| static inline struct usbreq __clear_epstall(uint16_t ep) |
| { |
| return __clear_ep_feature_req(ep, FEAT_EPSTALL); |
| } |
| |
| /* Get the status of an endpoint, or the device if endpoint 0 */ |
| static inline struct usbreq __get_status_req(uint16_t ep) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_STD | USB_RCPT_DEVICE), |
| .bRequest = GET_STATUS, |
| .wValue = 0, |
| .wIndex = ep, |
| .wLength = 2 |
| }; |
| if (ep != 0) { |
| /* Endpoint request type */ |
| r.bmRequestType |= USB_RCPT_ENDPOINT; |
| } |
| if (ep & 0x1) { |
| /* Direction */ |
| r.wIndex |= BIT(7); |
| } |
| return r; |
| } |
| |
| static inline struct usbreq __reg_write_req(uint16_t addr) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_VEN | USB_RCPT_DEVICE), |
| .bRequest = 0xA0, |
| .wValue = 0, |
| .wIndex = addr, |
| .wLength = 4 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __reg_read_req(uint16_t addr) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_VEN | USB_RCPT_DEVICE), |
| .bRequest = 0xA1, |
| .wValue = 0, |
| .wIndex = addr, |
| .wLength = 4 |
| }; |
| return r; |
| } |
| |
| static int write_register(struct usb_eth *eth, int addr, uint32_t v) |
| { |
| struct usbreq *r; |
| uint32_t *d; |
| int err; |
| |
| r = xact_get_vaddr(ð->reg_write_xact[0]); |
| d = xact_get_vaddr(ð->reg_write_xact[1]); |
| *r = __reg_write_req(addr); |
| *d = v; |
| |
| /* FIXME: Find out why the delay is important? */ |
| ps_mdelay(100); |
| err = usbdev_schedule_xact(eth->udev, eth->udev->ep_ctrl, |
| eth->reg_write_xact, 2, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| return err; |
| } |
| |
| static int read_register(struct usb_eth *eth, int addr, uint32_t *v) |
| { |
| struct usbreq *r; |
| uint32_t *d; |
| int err; |
| |
| r = xact_get_vaddr(ð->reg_read_xact[0]); |
| d = xact_get_vaddr(ð->reg_read_xact[1]); |
| *r = __reg_read_req(addr); |
| |
| /* FIXME: Find out why the delay is important? */ |
| ps_mdelay(100); |
| err = usbdev_schedule_xact(eth->udev, eth->udev->ep_ctrl, |
| eth->reg_read_xact, 2, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| *v = *d; |
| return err; |
| } |
| |
| static int read_phy_register(struct usb_eth *eth, int addr, uint32_t *v) |
| { |
| uint32_t data; |
| int err; |
| /* Issue the request */ |
| data = BIT(11) | (addr << 6) | BIT(0); |
| err = write_register(eth, REG_MII_ACCESS, data);; |
| if (err) { |
| ZF_LOGF("Write PHY error\n"); |
| } |
| /* Wait for completion */ |
| do { |
| err = read_register(eth, REG_MII_ACCESS, &data); |
| if (err) { |
| ZF_LOGF("Read PHY error\n"); |
| } |
| } while (data & BIT(0)); |
| |
| /* Read the data */ |
| err = read_register(eth, REG_MII_DATA, v); |
| if (err) { |
| ZF_LOGF("Read PHY error\n"); |
| } |
| return err; |
| } |
| |
| static int write_phy_register(struct usb_eth *eth, int addr, uint32_t v) |
| { |
| uint32_t data; |
| int err; |
| /* Setup the data */ |
| err = write_register(eth, REG_MII_DATA, v); |
| if (err) { |
| ZF_LOGF("Write PHY error\n"); |
| } |
| /* Issue the request */ |
| data = BIT(11) | (addr << 6) | BIT(1) | BIT(0); |
| err = write_register(eth, REG_MII_ACCESS, data);; |
| if (err) { |
| ZF_LOGF("Write PHY error\n"); |
| } |
| /* Wait for completion */ |
| do { |
| err = read_register(eth, REG_MII_ACCESS, &data); |
| if (err) { |
| ZF_LOGF("Read PHY error\n"); |
| } |
| } while (data & BIT(0)); |
| return 0; |
| } |
| |
| #define DUMPREG(eth, r) \ |
| do { \ |
| uint32_t v; \ |
| read_register(eth, r, &v); \ |
| printf("(0x%03x)%20s: 0x%08x\n", r, #r, v); \ |
| }while(0) |
| |
| #define DUMPPHYREG(eth, r) \ |
| do { \ |
| uint32_t v; \ |
| read_phy_register(eth, r, &v); \ |
| printf("(0x%03x)%20s: 0x%08x\n", r, #r, v); \ |
| }while(0) |
| |
| UNUSED static void dump_pbuf(struct pbuf *p) |
| { |
| struct pbuf *q; |
| int i, j; |
| printf("##PBUF##"); |
| for (q = p, i = 0; q != NULL; q = q->next) { |
| char *payload = (char *)q->payload; |
| for (j = 0; j < q->len; j++, i++) { |
| if ((i % 32) == 0) { |
| printf("\n0x%03x: ", i); |
| } |
| printf("%02x", payload[j]); |
| } |
| } |
| printf(COL_DEF "\n"); |
| } |
| |
| UNUSED static void dump_rxptr(struct usb_eth *eth) |
| { |
| DUMPREG(eth, REG_RX_FIFO_INF); |
| DUMPREG(eth, REG_TX_FIFO_INF); |
| DUMPREG(eth, REG_TX_CFG); |
| } |
| |
| UNUSED static void dump_registers(struct usb_eth *eth) |
| { |
| printf("### SCSR ###\n"); |
| DUMPREG(eth, REG_ID_REV); |
| DUMPREG(eth, REG_INT_STS); |
| DUMPREG(eth, REG_RX_CFG); |
| DUMPREG(eth, REG_TX_CFG); |
| DUMPREG(eth, REG_HW_CFG); |
| DUMPREG(eth, REG_RX_FIFO_INF); |
| DUMPREG(eth, REG_TX_FIFO_INF); |
| DUMPREG(eth, REG_PMT_CTL); |
| DUMPREG(eth, REG_LED_GPIO_CFG); |
| DUMPREG(eth, REG_GPIO_CFG); |
| DUMPREG(eth, REG_AFC_CFG); |
| DUMPREG(eth, REG_E2P_CMD); |
| DUMPREG(eth, REG_E2P_DATA); |
| DUMPREG(eth, REG_BURST_CAP); |
| DUMPREG(eth, REG_DP_SEL); |
| DUMPREG(eth, REG_DP_CMD); |
| DUMPREG(eth, REG_DP_ADDR); |
| DUMPREG(eth, REG_DP_DATA0); |
| DUMPREG(eth, REG_DP_DATA1); |
| DUMPREG(eth, REG_GPIO_WAKE); |
| DUMPREG(eth, REG_INT_EP_CTL); |
| DUMPREG(eth, REG_BULK_IN_DLY); |
| DUMPREG(eth, REG_DBG_RX_FIFO_LVL); |
| DUMPREG(eth, REG_DBG_RX_FIFO_PTR); |
| DUMPREG(eth, REG_DBG_TX_FIFO_PTR); |
| DUMPREG(eth, REG_DBG_TX_FIFO_PTR); |
| DUMPREG(eth, REG_HS_ATTR); |
| DUMPREG(eth, REG_FS_ATTR); |
| DUMPREG(eth, REG_STRNG_ATTR0); |
| DUMPREG(eth, REG_STRNG_ATTR1); |
| DUMPREG(eth, REG_FLAG_ATTR); |
| /* MCSR (MAC Control and Status Registers */ |
| printf("### MCSR ###\n"); |
| DUMPREG(eth, REG_MAC_CR); |
| DUMPREG(eth, REG_ADDRH); |
| DUMPREG(eth, REG_ADDRL); |
| DUMPREG(eth, REG_HASHH); |
| DUMPREG(eth, REG_HASHL); |
| DUMPREG(eth, REG_MII_ACCESS); |
| DUMPREG(eth, REG_MII_DATA); |
| DUMPREG(eth, REG_FLOW); |
| DUMPREG(eth, REG_VLAN1); |
| DUMPREG(eth, REG_VLAN2); |
| DUMPREG(eth, REG_WUFF); |
| DUMPREG(eth, REG_WUCSR); |
| DUMPREG(eth, REG_COE_CR); |
| |
| printf("### PHY ###\n"); |
| DUMPPHYREG(eth, PHYREG_BASIC_CONTROL); |
| DUMPPHYREG(eth, PHYREG_BASIC_STATUS); |
| DUMPPHYREG(eth, PHYREG_PHY_ID1); |
| DUMPPHYREG(eth, PHYREG_PHY_ID2); |
| DUMPPHYREG(eth, PHYREG_AUTONEG_ADVERTISEMENT); |
| DUMPPHYREG(eth, PHYREG_AUTONEG_LINK_PARTNER_ABILITY); |
| DUMPPHYREG(eth, PHYREG_AUTONEG_EXPANSION); |
| DUMPPHYREG(eth, PHYREG_AUTONEG_NEXT_PAGE); |
| DUMPPHYREG(eth, PHYREG_AUTONEG_LINK_PARTNER_NEXT_PAGE_ABILITY); |
| DUMPPHYREG(eth, PHYREG_1000BASET_CONTROL); |
| DUMPPHYREG(eth, PHYREG_1000BASET_STATUS); |
| DUMPPHYREG(eth, PHYREG_EXTENDED_CONTROL); |
| DUMPPHYREG(eth, PHYREG_EXTENDED_DATA_WRITE); |
| DUMPPHYREG(eth, PHYREG_EXTENDED_DATA_READ); |
| DUMPPHYREG(eth, PHYREG_EXTENDED_STATUS); |
| printf("##############################\n"); |
| } |
| |
| static int mac_init(struct usb_eth *eth) |
| { |
| uint64_t mac = 0xe0ae2a751000; |
| uint32_t v; |
| /*** MAC ***/ |
| /* Set MAC address */ |
| v = (mac >> 32) & 0xffff; |
| write_register(eth, REG_ADDRH, v); |
| v = (mac >> 0) & 0xffffffff; |
| write_register(eth, REG_ADDRL, v); |
| /* Enable TX/RX full duplex */ |
| v = MACCR_FULL_DUPLEX | MACCR_TXEN | MACCR_RXEN; |
| write_register(eth, REG_MAC_CR, v); |
| |
| /*** PHY ***/ |
| /* Soft reset */ |
| v = PHYBC_SOFT_RESET; |
| write_phy_register(eth, PHYREG_BASIC_CONTROL, 0); |
| do { |
| read_phy_register(eth, PHYREG_BASIC_CONTROL, &v); |
| } while (v & PHYBC_SOFT_RESET); |
| /* Advertisement */ |
| read_phy_register(eth, PHYREG_AUTONEG_ADVERTISEMENT, &v); |
| v |= PHYAN_AD_100BASE_FULL | PHYAN_AD_100BASE_HALF |
| | PHYAN_AD_10BASE_FULL | PHYAN_AD_10BASE_HALF; |
| write_phy_register(eth, PHYREG_AUTONEG_ADVERTISEMENT, v); |
| /* Start auto negotiate */ |
| v = PHYBC_AUTONEG_EN | PHYBC_AUTONEG_RESTART; |
| write_phy_register(eth, PHYREG_BASIC_CONTROL, v); |
| do { |
| read_phy_register(eth, PHYREG_BASIC_STATUS, &v); |
| } while (!(v & PHYBS_AUTONEG_DONE)); |
| /* Report the link speed */ |
| printf("USB-Eth: Link "); |
| if (v & PHYBS_LINKUP) { |
| int speed; |
| char *duplex; |
| printf("up, "); |
| /* Speed */ |
| if (v & (PHYBS_100BASE_TX_FULL | PHYBS_100BASE_TX_HALF)) { |
| speed = 100; |
| } else if (v & (PHYBS_10BASE_TX_FULL | PHYBS_10BASE_TX_HALF)) { |
| speed = 10; |
| } else { |
| speed = -1; |
| } |
| /* Duplex */ |
| if (v & (PHYBS_100BASE_TX_FULL | PHYBS_10BASE_TX_FULL)) { |
| duplex = "full"; |
| } else if (v & (PHYBS_100BASE_TX_HALF | PHYBS_10BASE_TX_HALF)) { |
| duplex = "half"; |
| } else { |
| duplex = "???"; |
| } |
| /* Report */ |
| if (v & PHYBS_100BASE_T4) { |
| printf("100Mbps T4\n"); |
| } else { |
| printf("%dMbps, %s duplex\n", speed, duplex); |
| } |
| |
| } else { |
| printf("down\n"); |
| } |
| |
| /* Clear IRQs */ |
| v = 0x0; |
| write_register(eth, REG_INT_STS, v); |
| /* Enable IRQs */ |
| v = ETHINT_MACRTO | ETHINT_RX_FULL | ETHINT_TXSTOP | ETHINT_RXSTOP |
| | ETHINT_PHY | ETHINT_TX_EMPTY | ETHINT_TDFU | ETHINT_TDFO |
| | ETHINT_RXDF; |
| write_register(eth, REG_INT_EP_CTL, v); |
| /* TX on */ |
| v = ETHTXCFG_TXON; |
| write_register(eth, REG_TX_CFG, v); |
| return 0; |
| } |
| |
| static int do_lan9730_input(struct netif *netif, struct xact *xact) |
| { |
| struct eth_hdr *ethhdr; |
| struct pbuf *p, *q; |
| uint32_t *hdr; |
| char *payload; |
| uint32_t sts; |
| int len; |
| hdr = (uint32_t *) xact_get_vaddr(xact); |
| payload = (char *)&hdr[1]; |
| sts = *hdr; |
| if (sts & RXSTS_ERROR_STATUS) { |
| printf("error frame status 0x%x\n", sts); |
| return -1; |
| } |
| len = RXSTS_FRAME_LENGTH(sts); |
| if (len == 0) { |
| return 0; |
| } |
| |
| /* Construct the packet */ |
| p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); |
| if (!p) { |
| ZF_LOGF("LWIP out of memory\n"); |
| } |
| for (q = p; q != NULL; q = q->next) { |
| memcpy(q->payload, payload, q->len); |
| payload += q->len; |
| } |
| |
| #ifdef ETH_TRAFFIC_DEBUG |
| printf("\n" COL_RX "RX packet (%d bytes)\n", p->tot_len); |
| payload = (char *)&hdr[1]; |
| dump_pbuf(p); |
| printf(COL_DEF); |
| #endif |
| |
| ethhdr = p->payload; |
| |
| switch (htons(ethhdr->type)) { |
| /* IP or ARP packet? */ |
| case ETHTYPE_IP: |
| case ETHTYPE_ARP: |
| #if PPPOE_SUPPORT |
| /* PPPoE packet? */ |
| case ETHTYPE_PPPOEDISC: |
| case ETHTYPE_PPPOE: |
| #endif /* PPPOE_SUPPORT */ |
| /* full packet send to tcpip_thread to process */ |
| if (netif->input(p, netif) != ERR_OK) { |
| LWIP_DEBUGF(NETIF_DEBUG, |
| ("ethernetif_input: IP input error\n")); |
| pbuf_free(p); |
| p = NULL; |
| } |
| break; |
| |
| default: |
| pbuf_free(p); |
| p = NULL; |
| break; |
| } |
| return 0; |
| } |
| |
| static int eth_process_status(struct netif *netif, uint32_t status) |
| { |
| struct usb_eth *eth; |
| eth = netif_get_eth_driver(netif); |
| int err = 0; |
| if (status & INT_RXFIFO) { |
| err = lan9730_input(netif); |
| status &= ~INT_RXFIFO; |
| } |
| if (status) { |
| ZF_LOGD("Unknown USB-ETH IRQ status: 0x%x\n", status); |
| } |
| return err; |
| } |
| |
| #if defined(ETH_ENABLE_IRQS) |
| static int eth_irq_handler(void *token, enum usb_xact_status stat, int bytes_remaining) |
| { |
| struct netif *netif = (struct netif *)token; |
| struct usb_eth *eth; |
| int err; |
| int len; |
| |
| if (!token) { |
| ZF_LOGF("Invalid token\n"); |
| } |
| eth = netif_get_eth_driver(netif); |
| len = eth->int_xact.len - bytes_remaining; |
| |
| /* Check the status */ |
| if (stat != XACTSTAT_SUCCESS) { |
| ZF_LOGD("Received unsuccessful IRQ\n"); |
| return 1; |
| } |
| if (len != 4) { |
| ZF_LOGD("Unexpected number of bytes for INT packet (%d)\n", len); |
| return 1; |
| } |
| |
| ZF_LOGD("Handling IRQ\n"); |
| err = eth_process_status(netif, *eth->intbm); |
| if (err) { |
| ZF_LOGD("Spurious IRQ\n"); |
| } |
| |
| usbdev_schedule_xact(eth->udev, eth->ep_int, ð->int_xact, 1, |
| ð_irq_handler, netif); |
| |
| return 1; |
| } |
| #endif |
| |
| static err_t tx_packet(struct usb_eth *eth, struct pbuf *p) |
| { |
| struct xact xact[2]; |
| struct pbuf *q; |
| uint32_t *hdr; |
| char *payload; |
| int err; |
| |
| #ifdef ETH_TRAFFIC_DEBUG |
| printf("\n" COL_TX "TX packet (%d bytes)\n", p->tot_len); |
| dump_pbuf(p); |
| printf(COL_DEF); |
| #endif |
| /* Allocate the buffers */ |
| xact[0].type = PID_OUT; |
| xact[0].len = p->tot_len + sizeof(*hdr) * 2; |
| /* TODO: This forces a data toggle. It is needed because we do not store |
| * the DATAx that the packet should be sent to. */ |
| xact[1].type = PID_OUT; |
| xact[1].len = 0; |
| err = usb_alloc_xact(eth->udev->dman, xact, 2); |
| if (err) { |
| return ENOMEM; |
| } |
| hdr = (uint32_t *) xact_get_vaddr(&xact[0]); |
| payload = (char *)&hdr[2]; |
| /* Construct the packet */ |
| hdr[0] = TXCMDA_DATA_START_OFFSET(0) | TXCMDA_BUFFER_SIZE(p->tot_len); |
| hdr[0] |= TXCMDA_FIRST_SEGMENT | TXCMDA_LAST_SEGMENT; |
| hdr[1] = TXCMDB_FRAME_LENGTH(p->tot_len); |
| hdr[1] |= TXCMDB_ADD_CRC_DISABLE; |
| for (q = p; q != NULL; q = q->next) { |
| memcpy(payload, q->payload, q->len); |
| payload += q->len; |
| } |
| /* send it */ |
| err = usbdev_schedule_xact(eth->udev, eth->ep_out, xact, 2, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| return err; |
| } |
| |
| int lan9730_input(struct netif *netif) |
| { |
| struct usb_eth *eth; |
| struct xact xact; |
| int err; |
| |
| eth = netif_get_eth_driver(netif); |
| |
| /* Allocate a buffer */ |
| xact.type = PID_IN; |
| xact.len = eth->ep_in->max_pkt; |
| err = usb_alloc_xact(eth->udev->dman, &xact, 1); |
| if (err) { |
| return -1; |
| } |
| /* Read in a frame */ |
| err = usbdev_schedule_xact(eth->udev, eth->ep_in, &xact, 1, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| if (err <= 0) { |
| /* Nothing there. Return */ |
| err = 0; |
| } else { |
| /* Forward the usb packet */ |
| err = do_lan9730_input(netif, &xact); |
| } |
| /* Clean up */ |
| usb_destroy_xact(eth->udev->dman, &xact, 1); |
| return err; |
| } |
| |
| err_t lan9730_linkoutput(struct netif *netif, struct pbuf *p) |
| { |
| struct usb_eth *usb_eth = netif_get_eth_driver(netif); |
| |
| err_t ret = 0; |
| |
| #if ETH_PAD_SIZE |
| pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ |
| #endif |
| |
| ret = tx_packet(usb_eth, p); |
| pbuf_ref(p); |
| |
| #if ETH_PAD_SIZE |
| pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ |
| #endif |
| |
| LINK_STATS_INC(link.xmit); |
| |
| return ret; |
| } |
| |
| err_t lan9730_init(struct netif *netif) |
| { |
| struct usb_eth *usb_eth; |
| if (netif->state == NULL) { |
| return ERR_ARG; |
| } |
| usb_eth = netif_get_eth_driver(netif); |
| (void)usb_eth; |
| |
| netif->output = etharp_output; |
| netif->linkoutput = lan9730_linkoutput; |
| |
| NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, |
| 100000000 /* TODO Stop hard-coding Mbps */); |
| |
| netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | |
| NETIF_FLAG_LINK_UP; |
| netif->hwaddr_len = 6; |
| netif->mtu = 1500; |
| netif->hwaddr[0] = 0x00; |
| netif->hwaddr[1] = 0x10; |
| netif->hwaddr[2] = 0x75; |
| netif->hwaddr[3] = 0x2a; |
| netif->hwaddr[4] = 0xae; |
| netif->hwaddr[5] = 0xe0; |
| return ERR_OK; |
| } |
| |
| struct netif *lan9730_driver_bind(usb_dev_t *udev) |
| { |
| struct netif *netif; |
| struct usb_eth *eth; |
| struct usbreq *req; |
| struct xact xact; |
| int err; |
| |
| if (!is_eth(udev)) { |
| return NULL; |
| } |
| /* Allocate memory */ |
| eth = (struct usb_eth *)usb_malloc(sizeof(*eth)); |
| if (eth == NULL) { |
| return NULL; |
| } |
| netif = (struct netif *)usb_malloc(sizeof(*netif)); |
| if (netif == NULL) { |
| usb_free(eth); |
| return NULL; |
| } |
| memset(eth, 0, sizeof(*eth)); |
| memset(netif, 0, sizeof(*netif)); |
| |
| eth->udev = udev; |
| udev->dev_data = (struct udev_priv *)eth; |
| |
| err = usbdev_parse_config(udev, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Invalid descriptors\n"); |
| } |
| |
| /* Activate configuration */ |
| ZF_LOGD("Configure ETH\n"); |
| xact.type = PID_SETUP; |
| xact.len = sizeof(*req); |
| err = usb_alloc_xact(eth->udev->dman, &xact, 1); |
| if (err) { |
| ZF_LOGE("Out of DMA memory\n");; |
| return NULL; |
| } |
| req = xact_get_vaddr(&xact); |
| *req = __set_configuration_req(1); |
| err = usbdev_schedule_xact(udev, udev->ep_ctrl, &xact, 1, NULL, NULL); |
| if (err < 0) { |
| ZF_LOGE("Transaction error\n"); |
| return NULL; |
| } |
| usb_destroy_xact(udev->dman, &xact, 1); |
| |
| /* Find endpoints */ |
| for (int i = 0; udev->ep[i] != NULL; i++) { |
| if (udev->ep[i]->type == EP_BULK) { |
| if (udev->ep[i]->dir == EP_DIR_OUT) { |
| eth->ep_out = udev->ep[i]; |
| } else { |
| eth->ep_in = udev->ep[i]; |
| } |
| } else if (udev->ep[i]->type == EP_INTERRUPT) { |
| eth->ep_int = udev->ep[i]; |
| } else { |
| continue; |
| } |
| } |
| |
| /* Cache a transactions buffer for register access */ |
| eth->reg_read_xact[0].type = PID_SETUP; |
| eth->reg_read_xact[0].len = sizeof(struct usbreq); |
| eth->reg_read_xact[1].type = PID_IN; |
| eth->reg_read_xact[1].len = sizeof(uint32_t); |
| eth->reg_write_xact[0].type = PID_SETUP; |
| eth->reg_write_xact[0].len = sizeof(struct usbreq); |
| eth->reg_write_xact[1].type = PID_OUT; |
| eth->reg_write_xact[1].len = sizeof(uint32_t); |
| err = usb_alloc_xact(eth->udev->dman, eth->reg_read_xact, 2); |
| if (err) { |
| return NULL; |
| } |
| err = usb_alloc_xact(eth->udev->dman, eth->reg_write_xact, 2); |
| if (err) { |
| return NULL; |
| } |
| |
| mac_init(eth); |
| |
| #if defined(ETH_ENABLE_IRQS) |
| eth->int_xact.type = PID_IN; |
| eth->int_xact.len = eth->ep_int->max_pkt; |
| err = usb_alloc_xact(udev->dman, ð->int_xact, 1); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| eth->intbm = xact_get_vaddr(ð->int_xact); |
| memset(eth->intbm, 0, eth->int_xact.len); |
| ZF_LOGD("Registering for INT (%d ms)\n", eth->ep_int->interval); |
| usbdev_schedule_xact(udev, eth->ep_int, ð->int_xact, 1, |
| ð_irq_handler, netif); |
| #else |
| eth->intbm = NULL; |
| eth->int_xact.vaddr = NULL; |
| eth->int_xact.len = 0; |
| (void)eth_irq_handler; |
| #endif |
| netif_add(netif, NULL, NULL, NULL, eth, lan9730_init, ethernet_input); |
| |
| return netif; |
| } |
| |
| int lan9730_poll_status(struct netif *netif) |
| { |
| struct usb_eth *eth; |
| struct xact xact[1]; |
| uint32_t *status; |
| int err; |
| |
| if (netif->state == NULL) { |
| return ERR_ARG; |
| } |
| eth = netif_get_eth_driver(netif); |
| |
| /* Allocate the buffers */ |
| xact[0].type = PID_IN; |
| xact[0].len = 4; |
| err = usb_alloc_xact(eth->udev->dman, xact, 1); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| status = (uint32_t *) xact_get_vaddr(&xact[0]); |
| |
| err = usbdev_schedule_xact(eth->udev, eth->ep_int, xact, 1, NULL, NULL); |
| if (!err) { |
| err = eth_process_status(netif, *status); |
| } |
| usb_destroy_xact(eth->udev->dman, xact, 1); |
| |
| return err; |
| } |
| |
| #endif /* CONFIG_LIB_LWIP */ |