501 lines
12 KiB
C
Executable File
501 lines
12 KiB
C
Executable File
/*
|
|
* sun6i_gmac.c: Allwinnertech Gigabit Ethernet u-boot driver
|
|
* Copyright © 2006-2012, AllWinner Technology. Co., Ltd.
|
|
* All Rights Resvered
|
|
* Author: shuge <shuge@allwinnertech.com>
|
|
*
|
|
* 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 <linux/types.h>
|
|
#include <common.h>
|
|
#include <command.h>
|
|
#include <asm/io.h>
|
|
#include <net.h>
|
|
#include <malloc.h>
|
|
#include <miiphy.h>
|
|
#include <linux/mii.h>
|
|
#include <netdev.h>
|
|
#include <errno.h>
|
|
#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;
|
|
}
|
|
|