SmartAudio/lichee/linux-4.9/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c

688 lines
15 KiB
C

/*
* linux/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
*
* Copyright © 2016-2018, fuzhaoke
* Author: fuzhaoke <fuzhaoke@allwinnertech.com>
*
* This file is provided under a dual BSD/GPL license. When using or
* redistributing this file, you may do so under either license.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/ctype.h>
#include <linux/printk.h>
#include <linux/io.h>
#include "sunxi-gmac.h"
/******************************************************************************
* sun8iw6 operations
*****************************************************************************/
#define GETH_BASIC_CTL0 0x00
#define GETH_BASIC_CTL1 0x04
#define GETH_INT_STA 0x08
#define GETH_INT_EN 0x0C
#define GETH_TX_CTL0 0x10
#define GETH_TX_CTL1 0x14
#define GETH_TX_FLOW_CTL 0x1C
#define GETH_TX_DESC_LIST 0x20
#define GETH_RX_CTL0 0x24
#define GETH_RX_CTL1 0x28
#define GETH_RX_DESC_LIST 0x34
#define GETH_RX_FRM_FLT 0x38
#define GETH_RX_HASH0 0x40
#define GETH_RX_HASH1 0x44
#define GETH_MDIO_ADDR 0x48
#define GETH_MDIO_DATA 0x4C
#define GETH_ADDR_HI(reg) (0x50 + ((reg) << 3))
#define GETH_ADDR_LO(reg) (0x54 + ((reg) << 3))
#define GETH_TX_DMA_STA 0xB0
#define GETH_TX_CUR_DESC 0xB4
#define GETH_TX_CUR_BUF 0xB8
#define GETH_RX_DMA_STA 0xC0
#define GETH_RX_CUR_DESC 0xC4
#define GETH_RX_CUR_BUF 0xC8
#define GETH_RGMII_STA 0xD0
#define RGMII_IRQ 0x00000001
#define CTL0_LM 0x02
#define CTL0_DM 0x01
#define CTL0_SPEED 0x04
#define BURST_LEN 0x3F000000
#define RX_TX_PRI 0x02
#define SOFT_RST 0x01
#define TX_FLUSH 0x01
#define TX_MD 0x02
#define TX_NEXT_FRM 0x04
#define TX_TH 0x0700
#define RX_FLUSH 0x01
#define RX_MD 0x02
#define RX_RUNT_FRM 0x04
#define RX_TH 0x0030
#define TX_INT 0x00001
#define TX_STOP_INT 0x00002
#define TX_UA_INT 0x00004
#define TX_TOUT_INT 0x00008
#define TX_UNF_INT 0x00010
#define TX_EARLY_INT 0x00020
#define RX_INT 0x00100
#define RX_UA_INT 0x00200
#define RX_STOP_INT 0x00400
#define RX_TOUT_INT 0x00800
#define RX_OVF_INT 0x01000
#define RX_EARLY_INT 0x02000
#define LINK_STA_INT 0x10000
#define DISCARD_FRAME -1
#define GOOD_FRAME 0
#define CSUM_NONE 2
#define LLC_SNAP 4
#define SF_DMA_MODE 1
/* Flow Control defines */
#define FLOW_OFF 0
#define FLOW_RX 1
#define FLOW_TX 2
#define FLOW_AUTO (FLOW_TX | FLOW_RX)
#define HASH_TABLE_SIZE 64
#define PAUSE_TIME 0x200
#define GMAC_MAX_UNICAST_ADDRESSES 8
/* PHY address */
#define PHY_ADDR 0x01
#define PHY_DM 0x0010
#define PHY_AUTO_NEG 0x0020
#define PHY_POWERDOWN 0x0080
#define PHY_NEG_EN 0x1000
#define MII_BUSY 0x00000001
#define MII_WRITE 0x00000002
#define MII_PHY_MASK 0x0000FFC0
#define MII_CR_MASK 0x0000001C
#define MII_CLK 0x00000008
/* bits 4 3 2 | AHB1 Clock | MDC Clock
* -------------------------------------------------------
* 0 0 0 | 60 ~ 100 MHz | div-42
* 0 0 1 | 100 ~ 150 MHz | div-62
* 0 1 0 | 20 ~ 35 MHz | div-16
* 0 1 1 | 35 ~ 60 MHz | div-26
* 1 0 0 | 150 ~ 250 MHz | div-102
* 1 0 1 | 250 ~ 300 MHz | div-124
* 1 1 x | Reserved |
*/
enum csum_insertion {
cic_dis = 0, /* Checksum Insertion Control */
cic_ip = 1, /* Only IP header */
cic_no_pse = 2, /* IP header but not pseudoheader */
cic_full = 3, /* IP header and pseudoheader */
};
struct gethdev {
void *iobase;
unsigned int ver;
unsigned int mdc_div;
};
static struct gethdev hwdev;
/***************************************************************************
* External interface
**************************************************************************/
/* Set a ring desc buffer */
void desc_init_chain(struct dma_desc *desc, unsigned long addr, unsigned int size)
{
/* In chained mode the desc3 points to the next element in the ring.
* The latest element has to point to the head.
*/
int i;
struct dma_desc *p = desc;
unsigned long dma_phy = addr;
for (i = 0; i < (size - 1); i++) {
dma_phy += sizeof(struct dma_desc);
p->desc3 = (unsigned int)dma_phy;
/* Chain mode */
p->desc1.all |= (1 << 24);
p++;
}
p->desc1.all |= (1 << 24);
p->desc3 = (unsigned int)addr;
}
int sunxi_mdio_read(void *iobase, int phyaddr, int phyreg)
{
unsigned int value = 0;
/* Mask the MDC_DIV_RATIO */
value |= ((hwdev.mdc_div & 0x07) << 20);
value |= (((phyaddr << 12) & (0x0001F000)) |
((phyreg << 4) & (0x000007F0)) |
MII_BUSY);
while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
;
writel(value, iobase + GETH_MDIO_ADDR);
while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
;
return (int)readl(iobase + GETH_MDIO_DATA);
}
int sunxi_mdio_write(void *iobase, int phyaddr, int phyreg, unsigned short data)
{
unsigned int value;
value = ((0x07 << 20) & readl(iobase + GETH_MDIO_ADDR)) |
(hwdev.mdc_div << 20);
value |= (((phyaddr << 12) & (0x0001F000)) |
((phyreg << 4) & (0x000007F0))) |
MII_WRITE | MII_BUSY;
/* Wait until any existing MII operation is complete */
while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
;
/* Set the MII address register to write */
writel(data, iobase + GETH_MDIO_DATA);
writel(value, iobase + GETH_MDIO_ADDR);
/* Wait until any existing MII operation is complete */
while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
;
return 0;
}
int sunxi_mdio_reset(void *iobase)
{
writel((4 << 2), iobase + GETH_MDIO_ADDR);
return 0;
}
void sunxi_set_link_mode(void *iobase, int duplex, int speed)
{
unsigned int ctrl = readl(iobase + GETH_BASIC_CTL0);
if (!duplex)
ctrl &= ~CTL0_DM;
else
ctrl |= CTL0_DM;
switch (speed) {
case 1000:
ctrl &= ~0x0C;
break;
case 100:
case 10:
default:
ctrl |= 0x08;
if (speed == 100)
ctrl |= 0x04;
else
ctrl &= ~0x04;
break;
}
writel(ctrl, iobase + GETH_BASIC_CTL0);
}
void sunxi_mac_loopback(void *iobase, int enable)
{
int reg;
reg = readl(iobase + GETH_BASIC_CTL0);
if (enable)
reg |= 0x02;
else
reg &= ~0x02;
writel(reg, iobase + GETH_BASIC_CTL0);
}
void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause)
{
unsigned int flow = 0;
if (fc & FLOW_RX) {
flow = readl(iobase + GETH_RX_CTL0);
flow |= 0x10000;
writel(flow, iobase + GETH_RX_CTL0);
}
if (fc & FLOW_TX) {
flow = readl(iobase + GETH_TX_FLOW_CTL);
flow |= 0x00001;
writel(flow, iobase + GETH_TX_FLOW_CTL);
}
if (duplex) {
flow = readl(iobase + GETH_TX_FLOW_CTL);
flow |= (pause << 4);
writel(flow, iobase + GETH_TX_FLOW_CTL);
}
}
int sunxi_int_status(void *iobase, struct geth_extra_stats *x)
{
int ret = 0;
/* read the status register (CSR5) */
unsigned int intr_status;
intr_status = readl(iobase + GETH_RGMII_STA);
if (intr_status & RGMII_IRQ)
readl(iobase + GETH_RGMII_STA);
intr_status = readl(iobase + GETH_INT_STA);
/* ABNORMAL interrupts */
if (intr_status & TX_UNF_INT) {
ret = tx_hard_error_bump_tc;
x->tx_undeflow_irq++;
}
if (intr_status & TX_TOUT_INT) {
x->tx_jabber_irq++;
}
if (intr_status & RX_OVF_INT) {
x->rx_overflow_irq++;
}
if (intr_status & RX_UA_INT) {
x->rx_buf_unav_irq++;
}
if (intr_status & RX_STOP_INT) {
x->rx_process_stopped_irq++;
}
if (intr_status & RX_TOUT_INT) {
x->rx_watchdog_irq++;
}
if (intr_status & TX_EARLY_INT) {
x->tx_early_irq++;
}
if (intr_status & TX_STOP_INT) {
x->tx_process_stopped_irq++;
ret = tx_hard_error;
}
/* TX/RX NORMAL interrupts */
if (intr_status & (TX_INT | RX_INT | RX_EARLY_INT | TX_UA_INT)) {
x->normal_irq_n++;
if (intr_status & (TX_INT | RX_INT))
ret = handle_tx_rx;
}
/* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */
writel(intr_status & 0x3FFF, iobase + GETH_INT_STA);
return ret;
}
void sunxi_start_rx(void *iobase, unsigned long rxbase)
{
unsigned int value;
/* Write the base address of Rx descriptor lists into registers */
writel(rxbase, iobase + GETH_RX_DESC_LIST);
value = readl(iobase + GETH_RX_CTL1);
value |= 0x40000000;
writel(value, iobase + GETH_RX_CTL1);
}
void sunxi_stop_rx(void *iobase)
{
unsigned int value;
value = readl(iobase + GETH_RX_CTL1);
value &= ~0x40000000;
writel(value, iobase + GETH_RX_CTL1);
}
void sunxi_start_tx(void *iobase, unsigned long txbase)
{
unsigned int value;
/* Write the base address of Tx descriptor lists into registers */
writel(txbase, iobase + GETH_TX_DESC_LIST);
value = readl(iobase + GETH_TX_CTL1);
value |= 0x40000000;
writel(value, iobase + GETH_TX_CTL1);
}
void sunxi_stop_tx(void *iobase)
{
unsigned int value = readl(iobase + GETH_TX_CTL1);
value &= ~0x40000000;
writel(value, iobase + GETH_TX_CTL1);
}
static int sunxi_dma_init(void *iobase)
{
unsigned int value;
/* Burst should be 8 */
value = (8 << 24);
#ifdef CONFIG_GMAC_DA
value |= RX_TX_PRI; /* Rx has priority over tx */
#endif
writel(value, iobase + GETH_BASIC_CTL1);
/* Mask interrupts by writing to CSR7 */
writel(RX_INT | TX_INT | TX_UNF_INT, iobase + GETH_INT_EN);
return 0;
}
int sunxi_mac_init(void *iobase, int txmode, int rxmode)
{
unsigned int value;
sunxi_dma_init(iobase);
/* Initialize the core component */
value = readl(iobase + GETH_TX_CTL0);
value |= (1 << 30); /* Jabber Disable */
writel(value, iobase + GETH_TX_CTL0);
value = readl(iobase + GETH_RX_CTL0);
value |= (1 << 27); /* Enable CRC & IPv4 Header Checksum */
value |= (1 << 28); /* Automatic Pad/CRC Stripping */
value |= (1 << 29); /* Jumbo Frame Enable */
writel(value, iobase + GETH_RX_CTL0);
writel((hwdev.mdc_div << 20), iobase + GETH_MDIO_ADDR); /* MDC_DIV_RATIO */
/* Set the Rx&Tx mode */
value = readl(iobase + GETH_TX_CTL1);
if (txmode == SF_DMA_MODE) {
/* Transmit COE type 2 cannot be done in cut-through mode. */
value |= TX_MD;
/* Operating on second frame increase the performance
* especially when transmit store-and-forward is used.
*/
value |= TX_NEXT_FRM;
} else {
value &= ~TX_MD;
value &= ~TX_TH;
/* Set the transmit threshold */
if (txmode <= 64)
value |= 0x00000000;
else if (txmode <= 128)
value |= 0x00000100;
else if (txmode <= 192)
value |= 0x00000200;
else
value |= 0x00000300;
}
writel(value, iobase + GETH_TX_CTL1);
value = readl(iobase + GETH_RX_CTL1);
if (rxmode == SF_DMA_MODE) {
value |= RX_MD;
} else {
value &= ~RX_MD;
value &= ~RX_TH;
if (rxmode <= 32)
value |= 0x10;
else if (rxmode <= 64)
value |= 0x00;
else if (rxmode <= 96)
value |= 0x20;
else
value |= 0x30;
}
writel(value, iobase + GETH_RX_CTL1);
return 0;
}
void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high)
{
writel(high, iobase + GETH_RX_HASH0);
writel(low, iobase + GETH_RX_HASH1);
}
void sunxi_set_filter(void *iobase, unsigned long flags)
{
int tmp_flags;
tmp_flags = readl(iobase + GETH_RX_FRM_FLT);
tmp_flags |= ((flags >> 31) |
((flags >> 9) & 0x00000002) |
((flags << 1) & 0x00000010) |
((flags >> 3) & 0x00000060) |
((flags << 7) & 0x00000300) |
((flags << 6) & 0x00003000) |
((flags << 12) & 0x00030000) |
(flags << 31));
writel(tmp_flags, iobase + GETH_RX_FRM_FLT);
}
void sunxi_set_umac(void *iobase, unsigned char *addr, int index)
{
unsigned long data;
data = (addr[5] << 8) | addr[4];
writel(data, iobase + GETH_ADDR_HI(index));
data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
writel(data, iobase + GETH_ADDR_LO(index));
}
void sunxi_mac_enable(void *iobase)
{
unsigned long value;
value = readl(iobase + GETH_TX_CTL0);
value |= (1 << 31);
writel(value, iobase + GETH_TX_CTL0);
value = readl(iobase + GETH_RX_CTL0);
value |= (1 << 31);
writel(value, iobase + GETH_RX_CTL0);
}
void sunxi_mac_disable(void *iobase)
{
unsigned long value;
value = readl(iobase + GETH_TX_CTL0);
value &= ~(1 << 31);
writel(value, iobase + GETH_TX_CTL0);
value = readl(iobase + GETH_RX_CTL0);
value &= ~(1 << 31);
writel(value, iobase + GETH_RX_CTL0);
}
void sunxi_tx_poll(void *iobase)
{
unsigned int value;
value = readl(iobase + GETH_TX_CTL1);
writel(value | 0x80000000, iobase + GETH_TX_CTL1);
}
void sunxi_rx_poll(void *iobase)
{
unsigned int value;
value = readl(iobase + GETH_RX_CTL1);
writel(value | 0x80000000, iobase + GETH_RX_CTL1);
}
void sunxi_int_enable(void *iobase)
{
writel(RX_INT | TX_INT | TX_UNF_INT, iobase + GETH_INT_EN);
}
void sunxi_int_disable(void *iobase)
{
writel(0, iobase + GETH_INT_EN);
}
void desc_buf_set(struct dma_desc *desc, unsigned long paddr, int size)
{
desc->desc1.all &= (~((1 << 11) - 1));
desc->desc1.all |= (size & ((1 << 11) - 1));
desc->desc2 = paddr;
}
void desc_set_own(struct dma_desc *desc)
{
desc->desc0.all |= 0x80000000;
}
void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert)
{
struct dma_desc *desc = first;
first->desc1.tx.first_sg = 1;
end->desc1.tx.last_seg = 1;
end->desc1.tx.interrupt = 1;
if (csum_insert)
do {
desc->desc1.tx.cic = 3;
desc++;
} while (desc <= end);
}
void desc_init(struct dma_desc *desc)
{
desc->desc1.all = 0;
desc->desc2 = 0;
desc->desc1.all |= (1 << 24);
}
int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x)
{
int ret = 0;
if (desc->desc0.tx.under_err) {
x->tx_underflow++;
ret = -1;
}
if (desc->desc0.tx.no_carr) {
x->tx_carrier++;
ret = -1;
}
if (desc->desc0.tx.loss_carr) {
x->tx_losscarrier++;
ret = -1;
}
#if 0
if ((desc->desc0.tx.ex_deferral) ||
(desc->desc0.tx.ex_coll) ||
(desc->desc0.tx.late_coll))
stats->collisions += desc->desc0.tx.coll_cnt;
#endif
if (desc->desc0.tx.deferred)
x->tx_deferred++;
return ret;
}
int desc_buf_get_len(struct dma_desc *desc)
{
return (desc->desc1.all & ((1 << 11) - 1));
}
int desc_buf_get_addr(struct dma_desc *desc)
{
return desc->desc2;
}
int desc_rx_frame_len(struct dma_desc *desc)
{
return desc->desc0.rx.frm_len;
}
int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x)
{
int ret = good_frame;
if (desc->desc0.rx.last_desc == 0) {
return discard_frame;
}
if (desc->desc0.rx.err_sum) {
if (desc->desc0.rx.desc_err)
x->rx_desc++;
if (desc->desc0.rx.sou_filter)
x->sa_filter_fail++;
if (desc->desc0.rx.over_err)
x->overflow_error++;
if (desc->desc0.rx.ipch_err)
x->ipc_csum_error++;
if (desc->desc0.rx.late_coll)
x->rx_collision++;
if (desc->desc0.rx.crc_err)
x->rx_crc++;
ret = discard_frame;
}
if (desc->desc0.rx.len_err) {
ret = discard_frame;
}
if (desc->desc0.rx.mii_err) {
ret = discard_frame;
}
return ret;
}
int desc_get_own(struct dma_desc *desc)
{
return desc->desc0.all & 0x80000000;
}
int desc_get_tx_ls(struct dma_desc *desc)
{
return desc->desc1.tx.last_seg;
}
int sunxi_geth_register(void *iobase, int version, unsigned int div)
{
hwdev.ver = version;
hwdev.iobase = iobase;
hwdev.mdc_div = div;
return 0;
}
int sunxi_mac_reset(void *iobase, void (*delay)(int), int n)
{
unsigned int value;
/* DMA SW reset */
value = readl(iobase + GETH_BASIC_CTL1);
value |= SOFT_RST;
writel(value, iobase + GETH_BASIC_CTL1);
delay(n);
return !!(readl(iobase + GETH_BASIC_CTL1) & SOFT_RST);
}