/* * sun6i_gmac.c: Allwinnertech Gigabit Ethernet u-boot driver * Copyright © 2006-2012, AllWinner Technology. Co., Ltd. * All Rights Resvered * Author: shuge * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, mail to service@allwinnertech.com. */ #include #include #include #include #include #include #include #include #include #include #include "sun6i_gmac.h" dma_desc_t dma_desc_tx[1]; dma_desc_t dma_desc_rx[1]; static char rx_packet[2048]; #if GET_SYSCLK_SELF static u32 get_pll1_clk(void) { u32 pll1_cfg; u32 n, k, m; pll1_cfg = readl(CCMU_BASE + PLL1_CFG); n = (0x1f & (pll1_cfg >> 8)) + 1; k = (0x03 & (pll1_cfg >> 4)) + 1; k = (0x03 & (pll1_cfg >> 0)) + 1; return (24000000 * n * k) / m; } static u32 get_pll6_clk(void) { u32 pll6_cfg; u32 n, k; pll6_cfg = readl(CCMU_BASE + PLL6_CFG); n = (0x1f & (pll6_cfg >> 8)) + 1; k = (0x03 & (pll6_cfg >> 4)) + 1; return (24000000 * n * k) >> 1; } static u32 get_axi_clk(void) { u32 axi_cfg, clk_hz, axi_div; axi_cfg = readl(CCMU_BASE + AXI_CFG); axi_div = axi_cfg & AXI_CLK_DIV; switch(axi_cfg & AXI_SRC_SEL){ case AXI_SRC_LOSC: clk_hz = 32000; break; case AXI_SRC_OSC24M: clk_hz = 24000000; break; default: clk_hz = get_pll1_clk(); break; } axi_div < 0x03 ? (clk_hz/(axi_div +1)) : (clk_hz >> 2); return clk_hz; } static u32 clk_ahb1_get(void) { u32 ahb_cfg, clk_hz, ahb_div, ahb_pre; ahb_cfg = readl(CCMU_BASE + AHB1_CFG); ahb_pre = (ahb_cfg & AHB1_PER_DIV) >> 6; ahb_div = (ahb_cfg & AHB1_CLK_DIV) >> 4; switch(ahb_cfg & AHB1_SRC_SEL){ case SRC_LOSC: clk_hz = 32000; break; case SRC_OSC24M: clk_hz = 24000000; break; case SRC_AXI: clk_hz = get_axi_clk(); break; case SRC_PLL6: clk_hz = get_pll6_clk() / (ahb_pre + 1); break; default: break; } return (clk_hz >>= ahb_div); } #endif static void random_ether_addr(u8 *addr) { int i; unsigned long long rand; for(i=0; i<6; i++){ rand = get_timer(0) * 0xfedf4fd; rand = rand * 0xd263f967 + 0xea6f22ad8235; addr[i] = (uchar)(rand % 0x100); } addr[0] &= 0xfe; addr[0] |= 0x02; } static u16 gmac_phy_read(struct eth_device *dev, int phy_adr, int reg) { int reg_val; reg_val = readl(dev->iobase + GMAC_GMII_ADDR); reg_val &= ~(MII_PHY_MASK | MII_WRITE); reg_val |= (((phy_adr << 11) | (reg <<6)) | MII_BUSY); while(readl(dev->iobase + GMAC_GMII_ADDR) & MII_BUSY); writel(reg_val, dev->iobase + GMAC_GMII_ADDR); while(readl(dev->iobase + GMAC_GMII_ADDR) & MII_BUSY); return (u16)readl(dev->iobase + GMAC_GMII_DATA); } static void gmac_phy_write(struct eth_device *dev, u8 phy_adr, u8 reg, u16 data) { int reg_val; reg_val = readl(dev->iobase + GMAC_GMII_ADDR); reg_val &= ~(MII_PHY_MASK); reg_val |= (((phy_adr << 11) | (reg <<6)) | MII_WRITE | MII_BUSY); /* Wait MII operation is complete */ while(readl(dev->iobase + GMAC_GMII_ADDR) & MII_BUSY); writel(data, dev->iobase + GMAC_GMII_DATA); writel(reg_val, dev->iobase + GMAC_GMII_ADDR); /* Until operation is complete and exiting */ while(readl(dev->iobase + GMAC_GMII_ADDR) & MII_BUSY); } #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) int gmac_miiphy_read (const char *devname, u8 phy_adr, u8 reg, u16 *value) { struct eth_device *dev; dev = eth_get_dev_by_name(devname); *value = gmac_phy_read(dev, phy_adr, reg); return 0; } int gmac_miiphy_write(const char *devname, u8 phy_adr, u8 reg, u16 data) { struct eth_device *dev; dev = eth_get_dev_by_name(devname); gmac_phy_write(dev, phy_adr, reg, data); return 0; } #endif static int gmac_xmit(struct eth_device *dev, volatile void *packet, int length) { u32 reg_val, xmit_stat; dma_desc_t *tx_p = dma_desc_tx; int tmo; /* Wait the Prev packet compled and timeout flush it */ tmo = get_timer(0) + 5 * CONFIG_SYS_HZ; while(tx_p->desc0.rx.own){ if(get_timer(0) > tmo) break; } /* configure transmit dma descriptor */ tx_p->desc0.all = TX_SINGLE_DESC0; tx_p->desc1.all = TX_SINGLE_DESC1; tx_p->desc1.tx.cic = cic_full; tx_p->desc1.tx.buf1_size = (MAX_BUF_LEN & length); tx_p->desc2 = (void *)packet; /* flush Transmit FIFO */ reg_val = readl(GDMA_BASE + GDMA_OP_MODE); reg_val |= OP_MODE_FTF; writel(reg_val, GDMA_BASE + GDMA_OP_MODE); /* Start Transmit */ xmit_stat = readl(GDMA_BASE + GDMA_STATUS) & STATUS_TPS; if(xmit_stat & STATUS_TBUS) writel(STATUS_TBUS, GDMA_BASE + GDMA_STATUS); xmit_stat &= STATUS_TPS; if(xmit_stat == TPS_SUSP){ writel(0x01, GDMA_BASE + GDMA_XMT_POLL); } else if(xmit_stat == TPS_STOP) { reg_val = readl(GDMA_BASE + GDMA_OP_MODE); reg_val |= OP_MODE_ST; writel(reg_val, GDMA_BASE + GDMA_OP_MODE); } return 0; } static int check_rx_coe(int ipc_err, int type, int payload_err) { int ret = good_frame; u32 status = (type << 2 | ipc_err << 1 | payload_err) & 0x7; /* bits 5 7 0 | Frame status * ---------------------------------------------------------- * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects) * 1 0 0 | IPv4/6 No CSUM errorS. * 1 0 1 | IPv4/6 CSUM PAYLOAD error * 1 1 0 | IPv4/6 CSUM IP HR error * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS * 0 0 1 | IPv4/6 unsupported IP PAYLOAD * 0 1 1 | COE bypassed.. no IPv4/6 frame * 0 1 0 | Reserved. */ switch(status){ case 0x00: ret = llc_snap; break; case 0x04: ret = good_frame; break; case 0x05: ret = csum_none; break; case 0x06: ret = csum_none; break; case 0x07: ret = csum_none; break; case 0x01: ret = discard_frame; break; case 0x03: ret = discard_frame; break; } return ret; } static int rx_status(dma_desc_t *p) { int ret = good_frame; if (p->desc0.rx.err_sum) ret = discard_frame; /* After a payload csum error, the ES bit is set. * It doesn't match with the information reported into the databook. * At any rate, we need to understand if the CSUM hw computation is ok * and report this info to the upper layers. */ ret = check_rx_coe(p->desc0.rx.ipch_err, p->desc0.rx.frm_type, p->desc0.rx.chsum_err); return ret; } static int gmac_recv(struct eth_device *dev) { u32 len, reg_val, recv_stat; dma_desc_t *rx_p = (void *)dma_desc_rx; if(rx_p->desc0.rx.own) return 0; recv_stat = rx_status(rx_p); if(recv_stat != discard_frame){ if(recv_stat != llc_snap) len = (rx_p->desc0.rx.frm_len - 4); else len = rx_p->desc0.rx.frm_len; NetReceive((volatile uchar *)(rx_p->desc2), len); } rx_p->desc0.all = RX_SINGLE_DESC0; rx_p->desc1.all = RX_SINGLE_DESC1; rx_p->desc1.rx.buf1_size = (MAX_BUF_LEN & 0xFFF); rx_p->desc2 = (void *)rx_packet; recv_stat = readl(GDMA_BASE + GDMA_STATUS); if(recv_stat & STATUS_RBUS) writel(STATUS_RBUS, GDMA_BASE + GDMA_STATUS); recv_stat &= STATUS_RPS; if(recv_stat == RPS_STOP){ reg_val = readl(GDMA_BASE + GDMA_OP_MODE); reg_val |= OP_MODE_SR; writel(reg_val, GDMA_BASE + GDMA_OP_MODE); }else if(recv_stat == RPS_SUSP){ writel(0x01, GDMA_BASE + GDMA_RCV_POLL); } return 0; } static int gmac_sys_init(void) { u32 reg_val; /* configure PIOA for gmac */ writel(0x22222222, PA_CFG0); writel(0x22222222, PA_CFG1); writel(0x22222222, PA_CFG2); /* enalbe clk for gmac */ reg_val = readl(CCMU_BASE + AHB1_GATING); reg_val |= GMAC_AHB_BIT; writel(reg_val, CCMU_BASE + AHB1_GATING); return 0; } static int mii_phy_init(struct eth_device *dev) { int reg_val; /* set clk for mii */ reg_val = readl(GMAC_BASE + GMAC_GMII_ADDR); reg_val |= MII_CLK; writel(reg_val, GMAC_BASE + GMAC_GMII_ADDR); /* Reset phy chip */ if(gmac_phy_read(dev, PHY_ADDR, MII_BMCR) & BMCR_PDOWN){ gmac_phy_write(dev, PHY_ADDR, MII_BMCR, ~BMCR_PDOWN); while(gmac_phy_read(dev, PHY_ADDR, MII_BMCR) & BMCR_PDOWN); printf("PHY Power on OK!!!\n"); } reg_val = gmac_phy_read(dev, PHY_ADDR, MII_BMCR); gmac_phy_write(dev, PHY_ADDR, MII_BMCR, reg_val | BMCR_RESET); while(gmac_phy_read(dev, PHY_ADDR, MII_BMCR) & BMCR_RESET); while(!(gmac_phy_read(dev, PHY_ADDR, MII_BMSR) & BMSR_ANEGCOMPLETE)); if(gmac_phy_read(dev, PHY_ADDR, MII_BMCR) & BMCR_FULLDPLX){ reg_val = readl(GMAC_BASE + GMAC_CONTROL); reg_val |= GMAC_CTL_DM; writel(reg_val, GMAC_BASE + GMAC_CONTROL); } if(gmac_phy_read(dev, PHY_ADDR, MII_BMCR) & BMCR_SPEED100){ reg_val = readl(GMAC_BASE + GMAC_CONTROL); reg_val |= GMAC_CTL_FES; writel(reg_val, GMAC_BASE + GMAC_CONTROL); } /* just for debug loopback */ /* reg_val |= BMCR_LOOPBACK; gmac_phy_write(dev, PHY_ADDR, MII_BMCR, reg_val); reg_val = gmac_phy_read(dev, PHY_ADDR, MII_BMCR); printf("REG0: %08x\n", reg_val); */ reg_val = gmac_phy_read(dev, PHY_ADDR, MII_BMCR); printf("%s Speed: %dMbps, Mode: %s, Loopback: %s\n", dev->name, reg_val&BMCR_SPEED100 ? 100 : 10, reg_val&BMCR_FULLDPLX ? "Full duplex" : "Half duplex", reg_val&BMCR_LOOPBACK ? "LOOP" : "NO"); return 0; } static int gmac_init(struct eth_device *dev, bd_t *bis) { u32 reg_val; /* Reset gmac and gdma */ writel(SOFT_RESET, GDMA_BASE + GDMA_BUS_MODE); while(readl(GDMA_BASE + GDMA_BUS_MODE) & SOFT_RESET); /* init phy */ mii_phy_init(dev); /* GMAC core init */ reg_val = readl(GMAC_BASE + GMAC_CONTROL); reg_val |= GMAC_DEF_CONF; reg_val &= (~GMAC_CTL_LM); writel(reg_val, GMAC_BASE + GMAC_CONTROL); /* GMAC frame filter */ reg_val = readl(GMAC_BASE + GMAC_FRAME_FILTER); reg_val |= 0x80000000; writel(reg_val, GMAC_BASE + GMAC_FRAME_FILTER); /* GDMA init */ reg_val = readl(GDMA_BASE + GDMA_BUS_MODE); reg_val &= (~BUS_ADDR_ALIGN); reg_val &= (~BUS_MODE_4PBL); reg_val &= (~BUS_MODE_USP); /* set rx_user_separate_pbl */ reg_val |= (1<<17) & BUS_MODE_RXPBL; /* set Programmbale Burst Length */ reg_val |= ((1<<8) & BUS_MODE_PBL); reg_val &= (~BUS_MODE_FIXBUST); reg_val |= ((0<<2) & BUS_MODE_DSL); reg_val |= ((0<<14) & BUS_MODE_RTPR); reg_val |= BUS_MODE_DA; writel(reg_val, GDMA_BASE + GDMA_BUS_MODE); reg_val = readl(GDMA_BASE + GDMA_OP_MODE); reg_val |= GDMA_OP_CONF; reg_val &= (~OP_MODE_OSF); writel(reg_val, GDMA_BASE + GDMA_OP_MODE); /* Disable all interrupt of dma */ writel(0x00000000, GDMA_BASE + GDMA_INTR_ENA); memset((void *)dma_desc_tx, 0, sizeof(dma_desc_t)); memset((void *)dma_desc_rx, 0, sizeof(dma_desc_t)); writel((u32)dma_desc_tx, GDMA_BASE + GDMA_XMT_LIST); writel((u32)dma_desc_rx, GDMA_BASE + GDMA_RCV_LIST); return 0; } static void gmac_halt(struct eth_device *dev) { int reg_val; reg_val = readl(dev->iobase + GMAC_CONTROL); reg_val &= (~GMAC_CTL_TE & ~GMAC_CTL_RE); writel(reg_val, dev->iobase + GMAC_CONTROL); } int gmac_write_hwaddr(struct eth_device *dev) { u32 mac_hi; u32 mac_lo; mac_hi = (dev->enetaddr[3] << 24) | (dev->enetaddr[2] << 16) | (dev->enetaddr[1] << 8) | (dev->enetaddr[0]); writel(mac_hi, dev->iobase + GMAC_ADDR_HI(0)); mac_lo = (dev->enetaddr[5] << 8) | (dev->enetaddr[4]); writel(mac_lo, dev->iobase + GMAC_ADDR_LO(0)); return 0; } int gmac_initialize(bd_t *bis) { struct eth_device *dev; dev = (struct eth_device *)malloc(sizeof *dev); if(!dev) return -ENOMEM; memset(dev, 0, (size_t)sizeof(*dev)); strcpy(dev->name, "eth0"); /*set random hwaddr for mac */ random_ether_addr(dev->enetaddr); dev->iobase = GMAC_BASE; dev->init = gmac_init; dev->halt = gmac_halt; dev->send = gmac_xmit; dev->recv = gmac_recv; dev->write_hwaddr = gmac_write_hwaddr; gmac_sys_init(); //gmac_init(dev, bis); //mii_phy_init(dev); //gmac_write_hwaddr(dev); eth_register(dev); #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) miiphy_register(dev->name, gmac_miiphy_read, gmac_miiphy_write); #endif return 0; }