/* * sunxi_emac.c -- Allwinner A10 ethernet driver * * (C) Copyright 2012, Stefan Roese * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include /* EMAC register */ struct emac_regs { u32 ctl; /* 0x00 */ u32 tx_mode; /* 0x04 */ u32 tx_flow; /* 0x08 */ u32 tx_ctl0; /* 0x0c */ u32 tx_ctl1; /* 0x10 */ u32 tx_ins; /* 0x14 */ u32 tx_pl0; /* 0x18 */ u32 tx_pl1; /* 0x1c */ u32 tx_sta; /* 0x20 */ u32 tx_io_data; /* 0x24 */ u32 tx_io_data1;/* 0x28 */ u32 tx_tsvl0; /* 0x2c */ u32 tx_tsvh0; /* 0x30 */ u32 tx_tsvl1; /* 0x34 */ u32 tx_tsvh1; /* 0x38 */ u32 rx_ctl; /* 0x3c */ u32 rx_hash0; /* 0x40 */ u32 rx_hash1; /* 0x44 */ u32 rx_sta; /* 0x48 */ u32 rx_io_data; /* 0x4c */ u32 rx_fbc; /* 0x50 */ u32 int_ctl; /* 0x54 */ u32 int_sta; /* 0x58 */ u32 mac_ctl0; /* 0x5c */ u32 mac_ctl1; /* 0x60 */ u32 mac_ipgt; /* 0x64 */ u32 mac_ipgr; /* 0x68 */ u32 mac_clrt; /* 0x6c */ u32 mac_maxf; /* 0x70 */ u32 mac_supp; /* 0x74 */ u32 mac_test; /* 0x78 */ u32 mac_mcfg; /* 0x7c */ u32 mac_mcmd; /* 0x80 */ u32 mac_madr; /* 0x84 */ u32 mac_mwtd; /* 0x88 */ u32 mac_mrdd; /* 0x8c */ u32 mac_mind; /* 0x90 */ u32 mac_ssrr; /* 0x94 */ u32 mac_a0; /* 0x98 */ u32 mac_a1; /* 0x9c */ }; /* SRAMC register */ struct sunxi_sramc_regs { u32 ctrl0; u32 ctrl1; }; /* 0: Disable 1: Aborted frame enable(default) */ #define EMAC_TX_AB_M (0x1 << 0) /* 0: CPU 1: DMA(default) */ #define EMAC_TX_TM (0x1 << 1) #define EMAC_TX_SETUP (0) /* 0: DRQ asserted 1: DRQ automatically(default) */ #define EMAC_RX_DRQ_MODE (0x1 << 1) /* 0: CPU 1: DMA(default) */ #define EMAC_RX_TM (0x1 << 2) /* 0: Normal(default) 1: Pass all Frames */ #define EMAC_RX_PA (0x1 << 4) /* 0: Normal(default) 1: Pass Control Frames */ #define EMAC_RX_PCF (0x1 << 5) /* 0: Normal(default) 1: Pass Frames with CRC Error */ #define EMAC_RX_PCRCE (0x1 << 6) /* 0: Normal(default) 1: Pass Frames with Length Error */ #define EMAC_RX_PLE (0x1 << 7) /* 0: Normal 1: Pass Frames length out of range(default) */ #define EMAC_RX_POR (0x1 << 8) /* 0: Not accept 1: Accept unicast Packets(default) */ #define EMAC_RX_UCAD (0x1 << 16) /* 0: Normal(default) 1: DA Filtering */ #define EMAC_RX_DAF (0x1 << 17) /* 0: Not accept 1: Accept multicast Packets(default) */ #define EMAC_RX_MCO (0x1 << 20) /* 0: Disable(default) 1: Enable Hash filter */ #define EMAC_RX_MHF (0x1 << 21) /* 0: Not accept 1: Accept Broadcast Packets(default) */ #define EMAC_RX_BCO (0x1 << 22) /* 0: Disable(default) 1: Enable SA Filtering */ #define EMAC_RX_SAF (0x1 << 24) /* 0: Normal(default) 1: Inverse Filtering */ #define EMAC_RX_SAIF (0x1 << 25) #define EMAC_RX_SETUP (EMAC_RX_POR | EMAC_RX_UCAD | EMAC_RX_DAF | \ EMAC_RX_MCO | EMAC_RX_BCO) /* 0: Disable 1: Enable Receive Flow Control(default) */ #define EMAC_MAC_CTL0_RFC (0x1 << 2) /* 0: Disable 1: Enable Transmit Flow Control(default) */ #define EMAC_MAC_CTL0_TFC (0x1 << 3) #define EMAC_MAC_CTL0_SETUP (EMAC_MAC_CTL0_RFC | EMAC_MAC_CTL0_TFC) /* 0: Disable 1: Enable MAC Frame Length Checking(default) */ #define EMAC_MAC_CTL1_FLC (0x1 << 1) /* 0: Disable(default) 1: Enable Huge Frame */ #define EMAC_MAC_CTL1_HF (0x1 << 2) /* 0: Disable(default) 1: Enable MAC Delayed CRC */ #define EMAC_MAC_CTL1_DCRC (0x1 << 3) /* 0: Disable 1: Enable MAC CRC(default) */ #define EMAC_MAC_CTL1_CRC (0x1 << 4) /* 0: Disable 1: Enable MAC PAD Short frames(default) */ #define EMAC_MAC_CTL1_PC (0x1 << 5) /* 0: Disable(default) 1: Enable MAC PAD Short frames and append CRC */ #define EMAC_MAC_CTL1_VC (0x1 << 6) /* 0: Disable(default) 1: Enable MAC auto detect Short frames */ #define EMAC_MAC_CTL1_ADP (0x1 << 7) /* 0: Disable(default) 1: Enable */ #define EMAC_MAC_CTL1_PRE (0x1 << 8) /* 0: Disable(default) 1: Enable */ #define EMAC_MAC_CTL1_LPE (0x1 << 9) /* 0: Disable(default) 1: Enable no back off */ #define EMAC_MAC_CTL1_NB (0x1 << 12) /* 0: Disable(default) 1: Enable */ #define EMAC_MAC_CTL1_BNB (0x1 << 13) /* 0: Disable(default) 1: Enable */ #define EMAC_MAC_CTL1_ED (0x1 << 14) #define EMAC_MAC_CTL1_SETUP (EMAC_MAC_CTL1_FLC | EMAC_MAC_CTL1_CRC | \ EMAC_MAC_CTL1_PC) #define EMAC_MAC_IPGT 0x15 #define EMAC_MAC_NBTB_IPG1 0xc #define EMAC_MAC_NBTB_IPG2 0x12 #define EMAC_MAC_CW 0x37 #define EMAC_MAC_RM 0xf #define EMAC_MAC_MFL 0x0600 /* Receive status */ #define EMAC_CRCERR (0x1 << 4) #define EMAC_LENERR (0x3 << 5) #define DMA_CPU_TRRESHOLD 2000 struct emac_eth_dev { u32 speed; u32 duplex; u32 phy_configured; int link_printed; }; struct emac_rxhdr { s16 rx_len; u16 rx_status; }; static void emac_inblk_32bit(void *reg, void *data, int count) { int cnt = (count + 3) >> 2; if (cnt) { u32 *buf = data; do { u32 x = readl(reg); *buf++ = x; } while (--cnt); } } static void emac_outblk_32bit(void *reg, void *data, int count) { int cnt = (count + 3) >> 2; if (cnt) { const u32 *buf = data; do { writel(*buf++, reg); } while (--cnt); } } /* Read a word from phyxcer */ static int emac_phy_read(const char *devname, unsigned char addr, unsigned char reg, unsigned short *value) { struct eth_device *dev = eth_get_dev_by_name(devname); struct emac_regs *regs = (struct emac_regs *)dev->iobase; /* issue the phy address and reg */ writel(addr << 8 | reg, ®s->mac_madr); /* pull up the phy io line */ writel(0x1, ®s->mac_mcmd); /* Wait read complete */ mdelay(1); /* push down the phy io line */ writel(0x0, ®s->mac_mcmd); /* and write data */ *value = readl(®s->mac_mrdd); return 0; } /* Write a word to phyxcer */ static int emac_phy_write(const char *devname, unsigned char addr, unsigned char reg, unsigned short value) { struct eth_device *dev = eth_get_dev_by_name(devname); struct emac_regs *regs = (struct emac_regs *)dev->iobase; /* issue the phy address and reg */ writel(addr << 8 | reg, ®s->mac_madr); /* pull up the phy io line */ writel(0x1, ®s->mac_mcmd); /* Wait write complete */ mdelay(1); /* push down the phy io line */ writel(0x0, ®s->mac_mcmd); /* and write data */ writel(value, ®s->mac_mwtd); return 0; } static void emac_setup(struct eth_device *dev) { struct emac_regs *regs = (struct emac_regs *)dev->iobase; u32 reg_val; u16 phy_val; u32 duplex_flag; /* Set up TX */ writel(EMAC_TX_SETUP, ®s->tx_mode); /* Set up RX */ writel(EMAC_RX_SETUP, ®s->rx_ctl); /* Set MAC */ /* Set MAC CTL0 */ writel(EMAC_MAC_CTL0_SETUP, ®s->mac_ctl0); /* Set MAC CTL1 */ emac_phy_read(dev->name, 1, 0, &phy_val); debug("PHY SETUP, reg 0 value: %x\n", phy_val); duplex_flag = !!(phy_val & (1 << 8)); reg_val = 0; if (duplex_flag) reg_val = (0x1 << 0); writel(EMAC_MAC_CTL1_SETUP | reg_val, ®s->mac_ctl1); /* Set up IPGT */ writel(EMAC_MAC_IPGT, ®s->mac_ipgt); /* Set up IPGR */ writel(EMAC_MAC_NBTB_IPG2 | (EMAC_MAC_NBTB_IPG1 << 8), ®s->mac_ipgr); /* Set up Collison window */ writel(EMAC_MAC_RM | (EMAC_MAC_CW << 8), ®s->mac_clrt); /* Set up Max Frame Length */ writel(EMAC_MAC_MFL, ®s->mac_maxf); } static void emac_reset(struct eth_device *dev) { struct emac_regs *regs = (struct emac_regs *)dev->iobase; debug("resetting device\n"); /* RESET device */ writel(0, ®s->ctl); udelay(200); writel(1, ®s->ctl); udelay(200); } static int sunxi_emac_eth_init(struct eth_device *dev, bd_t *bd) { struct emac_regs *regs = (struct emac_regs *)dev->iobase; struct emac_eth_dev *priv = dev->priv; u16 phy_reg; /* Init EMAC */ /* Flush RX FIFO */ setbits_le32(®s->rx_ctl, 0x8); udelay(1); /* Init MAC */ /* Soft reset MAC */ clrbits_le32(®s->mac_ctl0, 0x1 << 15); /* Clear RX counter */ writel(0x0, ®s->rx_fbc); udelay(1); /* Set up EMAC */ emac_setup(dev); writel(dev->enetaddr[0] << 16 | dev->enetaddr[1] << 8 | dev->enetaddr[2], ®s->mac_a1); writel(dev->enetaddr[3] << 16 | dev->enetaddr[4] << 8 | dev->enetaddr[5], ®s->mac_a0); mdelay(1); emac_reset(dev); /* PHY POWER UP */ emac_phy_read(dev->name, 1, 0, &phy_reg); emac_phy_write(dev->name, 1, 0, phy_reg & (~(0x1 << 11))); mdelay(1); emac_phy_read(dev->name, 1, 0, &phy_reg); priv->speed = miiphy_speed(dev->name, 0); priv->duplex = miiphy_duplex(dev->name, 0); /* Print link status only once */ if (!priv->link_printed) { printf("ENET Speed is %d Mbps - %s duplex connection\n", priv->speed, (priv->duplex == HALF) ? "HALF" : "FULL"); priv->link_printed = 1; } /* Set EMAC SPEED depend on PHY */ clrsetbits_le32(®s->mac_supp, 1 << 8, ((phy_reg & (0x1 << 13)) >> 13) << 8); /* Set duplex depend on phy */ clrsetbits_le32(®s->mac_ctl1, 1 << 0, ((phy_reg & (0x1 << 8)) >> 8) << 0); /* Enable RX/TX */ setbits_le32(®s->ctl, 0x7); return 0; } static void sunxi_emac_eth_halt(struct eth_device *dev) { /* Nothing to do here */ } static int sunxi_emac_eth_recv(struct eth_device *dev) { struct emac_regs *regs = (struct emac_regs *)dev->iobase; struct emac_rxhdr rxhdr; u32 rxcount; u32 reg_val; int rx_len; int rx_status; int good_packet; /* Check packet ready or not */ /* Race warning: The first packet might arrive with * the interrupts disabled, but the second will fix */ rxcount = readl(®s->rx_fbc); if (!rxcount) { /* Had one stuck? */ rxcount = readl(®s->rx_fbc); if (!rxcount) return 0; } reg_val = readl(®s->rx_io_data); if (reg_val != 0x0143414d) { /* Disable RX */ clrbits_le32(®s->ctl, 0x1 << 2); /* Flush RX FIFO */ setbits_le32(®s->rx_ctl, 0x1 << 3); while (readl(®s->rx_ctl) & (0x1 << 3)) ; /* Enable RX */ setbits_le32(®s->ctl, 0x1 << 2); return 0; } /* A packet ready now * Get status/length */ good_packet = 1; emac_inblk_32bit(®s->rx_io_data, &rxhdr, sizeof(rxhdr)); rx_len = rxhdr.rx_len; rx_status = rxhdr.rx_status; /* Packet Status check */ if (rx_len < 0x40) { good_packet = 0; debug("RX: Bad Packet (runt)\n"); } /* rx_status is identical to RSR register. */ if (0 & rx_status & (EMAC_CRCERR | EMAC_LENERR)) { good_packet = 0; if (rx_status & EMAC_CRCERR) printf("crc error\n"); if (rx_status & EMAC_LENERR) printf("length error\n"); } /* Move data from EMAC */ if (good_packet) { if (rx_len > DMA_CPU_TRRESHOLD) { printf("Received packet is too big (len=%d)\n", rx_len); } else { emac_inblk_32bit((void *)®s->rx_io_data, NetRxPackets[0], rx_len); /* Pass to upper layer */ NetReceive(NetRxPackets[0], rx_len); return rx_len; } } return 0; } static int sunxi_emac_eth_send(struct eth_device *dev, void *packet, int len) { struct emac_regs *regs = (struct emac_regs *)dev->iobase; /* Select channel 0 */ writel(0, ®s->tx_ins); /* Write packet */ emac_outblk_32bit((void *)®s->tx_io_data, packet, len); /* Set TX len */ writel(len, ®s->tx_pl0); /* Start translate from fifo to phy */ setbits_le32(®s->tx_ctl0, 1); return 0; } int sunxi_emac_initialize(void) { struct sunxi_ccm_reg *const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; struct sunxi_sramc_regs *sram = (struct sunxi_sramc_regs *)SUNXI_SRAMC_BASE; struct emac_regs *regs = (struct emac_regs *)SUNXI_EMAC_BASE; struct eth_device *dev; struct emac_eth_dev *priv; int pin; dev = malloc(sizeof(*dev)); if (dev == NULL) return -ENOMEM; priv = (struct emac_eth_dev *)malloc(sizeof(struct emac_eth_dev)); if (!priv) { free(dev); return -ENOMEM; } memset(dev, 0, sizeof(*dev)); memset(priv, 0, sizeof(struct emac_eth_dev)); /* Map SRAM to EMAC */ setbits_le32(&sram->ctrl1, 0x5 << 2); /* Configure pin mux settings for MII Ethernet */ for (pin = SUNXI_GPA(0); pin <= SUNXI_GPA(17); pin++) sunxi_gpio_set_cfgpin(pin, SUNXI_GPA0_EMAC); /* Set up clock gating */ setbits_le32(&ccm->ahb_gate0, 0x1 << AHB_GATE_OFFSET_EMAC); /* Set MII clock */ clrsetbits_le32(®s->mac_mcfg, 0xf << 2, 0xd << 2); dev->iobase = (int)regs; dev->priv = priv; dev->init = sunxi_emac_eth_init; dev->halt = sunxi_emac_eth_halt; dev->send = sunxi_emac_eth_send; dev->recv = sunxi_emac_eth_recv; strcpy(dev->name, "emac"); eth_register(dev); miiphy_register(dev->name, emac_phy_read, emac_phy_write); return 0; }