SmartAudio/lichee/brandy/u-boot-2011.09/drivers/net/sun6i_gmac.c

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;
}