blob: 47e4d4e9ca280aadc90a5b5b808f6e59f4600c91 [file] [log] [blame]
/* Mediatek STAR MAC network driver.
*
* Copyright (c) 2016-2017 Mediatek Corporation
*
* 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.
*/
#include "star.h"
#ifdef CONFIG_ARM64
uintptr_t tx_skb_reserve[TX_DESC_NUM];
uintptr_t rx_skb_reserve[RX_DESC_NUM];
#endif
u16 star_mdc_mdio_read(star_dev *dev, u32 phy_addr, u32 phy_reg)
{
u16 data;
u32 phy_ctl;
void __iomem *base = dev->base;
/* Clear previous read/write OK status (write 1 clear) */
star_set_reg(STAR_PHY_CTRL0(base), STAR_PHY_CTRL0_RWOK);
phy_ctl = (phy_addr & STAR_PHY_CTRL0_PA_MASK)
<< STAR_PHY_CTRL0_PA_OFFSET |
(phy_reg & STAR_PHY_CTRL0_PREG_MASK)
<< STAR_PHY_CTRL0_PREG_OFFSET |
STAR_PHY_CTRL0_RDCMD;
star_mb();
star_set_reg(STAR_PHY_CTRL0(base), phy_ctl);
star_mb();
STAR_POLLING_TIMEOUT(star_is_set_bit(STAR_PHY_CTRL0(base),
STAR_PHY_CTRL0_RWOK));
star_mb();
data = (u16)star_get_bit_mask(STAR_PHY_CTRL0(base),
STAR_PHY_CTRL0_RWDATA_MASK,
STAR_PHY_CTRL0_RWDATA_OFFSET);
return data;
}
void star_mdc_mdio_write(star_dev *dev, u32 phy_addr, u32 phy_reg, u16 value)
{
u32 phy_ctl;
void __iomem *base = dev->base;
/* Clear previous read/write OK status (write 1 clear) */
star_set_reg(STAR_PHY_CTRL0(base), STAR_PHY_CTRL0_RWOK);
phy_ctl = ((value & STAR_PHY_CTRL0_RWDATA_MASK)
<< STAR_PHY_CTRL0_RWDATA_OFFSET) |
((phy_addr & STAR_PHY_CTRL0_PA_MASK)
<< STAR_PHY_CTRL0_PA_OFFSET) |
((phy_reg & STAR_PHY_CTRL0_PREG_MASK)
<< STAR_PHY_CTRL0_PREG_OFFSET) |
STAR_PHY_CTRL0_WTCMD;
star_mb();
star_set_reg(STAR_PHY_CTRL0(base), phy_ctl);
star_mb();
STAR_POLLING_TIMEOUT(star_is_set_bit(STAR_PHY_CTRL0(base),
STAR_PHY_CTRL0_RWOK));
}
static void desc_tx_init(tx_desc *tx_desc, u32 is_eor)
{
tx_desc->buffer = 0;
tx_desc->ctrl_len = TX_COWN | (is_eor ? TX_EOR : 0);
tx_desc->vtag = 0;
tx_desc->reserve = 0;
}
static void desc_rx_init(rx_desc *rx_desc, u32 is_eor)
{
rx_desc->buffer = 0;
rx_desc->ctrl_len = RX_COWN | (is_eor ? RX_EOR : 0);
rx_desc->vtag = 0;
rx_desc->reserve = 0;
}
static void desc_tx_take(tx_desc *tx_desc)
{
if (desc_tx_dma(tx_desc))
tx_desc->ctrl_len |= TX_COWN;
}
static void desc_rx_take(rx_desc *rx_desc)
{
if (desc_rx_dma(rx_desc))
rx_desc->ctrl_len |= RX_COWN;
}
int star_dma_init(star_dev *dev, uintptr_t desc_viraddr,
dma_addr_t desc_dmaaddrdr)
{
int i;
void __iomem *base = dev->base;
STAR_MSG(STAR_VERB, "%s virAddr=0x%lx\n", __func__, desc_viraddr);
dev->tx_ring_size = TX_DESC_NUM;
dev->rx_ring_size = RX_DESC_NUM;
dev->tx_desc = (tx_desc *)desc_viraddr;
dev->rx_desc = (rx_desc *)dev->tx_desc + dev->tx_ring_size;
for (i = 0; i < dev->tx_ring_size; i++)
desc_tx_init(dev->tx_desc + i, i == dev->tx_ring_size - 1);
for (i = 0; i < dev->rx_ring_size; i++)
desc_rx_init(dev->rx_desc + i, i == dev->rx_ring_size - 1);
dev->tx_head = 0;
dev->tx_tail = 0;
dev->rx_head = 0;
dev->rx_tail = 0;
dev->tx_num = 0;
dev->rx_num = 0;
/* Set Tx/Rx descriptor address */
star_set_reg(STAR_TX_BASE_ADDR(base), (u32)desc_dmaaddrdr);
star_set_reg(STAR_TX_DPTR(base), (u32)desc_dmaaddrdr);
star_set_reg(STAR_RX_BASE_ADDR(base),
(u32)desc_dmaaddrdr + sizeof(tx_desc) * dev->tx_ring_size);
star_set_reg(STAR_RX_DPTR(base),
(u32)desc_dmaaddrdr + sizeof(tx_desc) * dev->tx_ring_size);
star_intr_disable(dev);
return 0;
}
int star_dma_tx_set(star_dev *dev, u32 buffer, u32 length, uintptr_t ext_buf)
{
int is_tx_last;
int desc_idx = dev->tx_head;
tx_desc *tx_desc = dev->tx_desc + desc_idx;
u32 len = (((length < 60) ? 60 : length) & TX_LEN_MASK)
<< TX_LEN_OFFSET;
/* Error checking */
if (dev->tx_num == dev->tx_ring_size)
goto err;
/* descriptor is not empty - cannot set */
if (!desc_tx_empty(tx_desc))
goto err;
tx_desc->buffer = buffer;
tx_desc->ctrl_len |= len | TX_FS | TX_LS | TX_INT;
#ifdef CONFIG_ARM64
tx_skb_reserve[desc_idx] = ext_buf;
#else
tx_desc->reserve = ext_buf;
#endif
/* star memory barrier */
wmb();
/* Set HW own */
tx_desc->ctrl_len &= ~TX_COWN;
dev->tx_num++;
is_tx_last = desc_tx_last(tx_desc);
dev->tx_head = is_tx_last ? 0 : desc_idx + 1;
return desc_idx;
err:
return -1;
}
int star_dma_tx_get(star_dev *dev, u32 *buffer,
u32 *ctrl_len, uintptr_t *ext_buf)
{
int is_tx_last;
int desc_idx = dev->tx_tail;
tx_desc *tx_desc = dev->tx_desc + desc_idx;
if (dev->tx_num == 0)
goto err;
if (desc_tx_dma(tx_desc))
goto err;
if (desc_tx_empty(tx_desc))
goto err;
if (buffer != 0)
*buffer = tx_desc->buffer;
if (ctrl_len != 0)
*ctrl_len = tx_desc->ctrl_len;
#ifdef CONFIG_ARM64
if (ext_buf != 0)
*ext_buf = tx_skb_reserve[desc_idx];
#else
if (ext_buf != 0)
*ext_buf = tx_desc->reserve;
#endif
/* add star memory barrier */
rmb();
desc_tx_init(tx_desc, desc_tx_last(tx_desc));
dev->tx_num--;
is_tx_last = desc_tx_last(tx_desc);
dev->tx_tail = is_tx_last ? 0 : desc_idx + 1;
return desc_idx;
err:
return -1;
}
int star_dma_rx_set(star_dev *dev, u32 buffer, u32 length, uintptr_t ext_buf)
{
int desc_idx = dev->rx_head;
rx_desc *rx_desc = dev->rx_desc + desc_idx;
int is_rx_last;
/* Error checking */
if (dev->rx_num == dev->rx_ring_size)
goto err;
/* descriptor is not empty - cannot set */
if (!desc_rx_empty(rx_desc))
goto err;
rx_desc->buffer = buffer;
rx_desc->ctrl_len |= ((length & RX_LEN_MASK) << RX_LEN_OFFSET);
#ifdef CONFIG_ARM64
rx_skb_reserve[desc_idx] = ext_buf;
#else
rx_desc->reserve = ext_buf;
#endif
/* star memory barrier */
wmb();
/* Set HW own */
rx_desc->ctrl_len &= ~RX_COWN;
dev->rx_num++;
is_rx_last = desc_rx_last(rx_desc);
dev->rx_head = is_rx_last ? 0 : desc_idx + 1;
return desc_idx;
err:
return -1;
}
int star_dma_rx_get(star_dev *dev, u32 *buffer,
u32 *ctrl_len, uintptr_t *ext_buf)
{
int is_rx_last;
int desc_idx = dev->rx_tail;
rx_desc *rx_desc = dev->rx_desc + desc_idx;
/* Error checking */
/* No buffer can be got */
if (dev->rx_num == 0)
goto err;
/* descriptor is owned by DMA - cannot get */
if (desc_rx_dma(rx_desc))
goto err;
/* descriptor is empty - cannot get */
if (desc_rx_empty(rx_desc))
goto err;
if (buffer != 0)
*buffer = rx_desc->buffer;
if (ctrl_len != 0)
*ctrl_len = rx_desc->ctrl_len;
#ifdef CONFIG_ARM64
if (ext_buf != 0)
*ext_buf = rx_skb_reserve[desc_idx];
#else
if (ext_buf != 0)
*ext_buf = rx_desc->reserve;
#endif
/* star memory barrier */
rmb();
desc_rx_init(rx_desc, desc_rx_last(rx_desc));
dev->rx_num--;
is_rx_last = desc_rx_last(rx_desc);
dev->rx_tail = is_rx_last ? 0 : desc_idx + 1;
return desc_idx;
err:
return -1;
}
void star_dma_tx_stop(star_dev *dev)
{
int i;
star_dma_tx_disable(dev);
for (i = 0; i < dev->tx_ring_size; i++)
desc_tx_take(dev->tx_desc + i);
}
void star_dma_rx_stop(star_dev *dev)
{
int i;
star_dma_rx_disable(dev);
for (i = 0; i < dev->rx_ring_size; i++)
desc_rx_take(dev->rx_desc + i);
}
int star_mac_init(star_dev *dev, u8 mac_addr[6])
{
void __iomem *base = dev->base;
STAR_MSG(STAR_VERB, "MAC Initialization\n");
/* Set Mac Address */
star_set_reg(star_my_mac_h(base),
mac_addr[0] << 8 | mac_addr[1] << 0);
star_set_reg(star_my_mac_l(base),
mac_addr[2] << 24 | mac_addr[3] << 16 |
mac_addr[4] << 8 | mac_addr[5] << 0);
/* Set Mac Configuration */
star_set_reg(STAR_MAC_CFG(base),
STAR_MAC_CFG_CRCSTRIP |
STAR_MAC_CFG_MAXLEN_1522 |
/* 12 byte IPG */
(0x1f & STAR_MAC_CFG_IPG_MASK) << STAR_MAC_CFG_IPG_OFFSET);
/* Init Flow Control register */
star_set_reg(STAR_FC_CFG(base),
STAR_FC_CFG_SEND_PAUSE_TH_DEF |
STAR_FC_CFG_UCPAUSEDIS |
STAR_FC_CFG_BPEN);
/* Init SEND_PAUSE_RLS */
star_set_reg(star_extend_cfg(base), STAR_EXTEND_CFG_SEND_PAUSE_RLS_DEF);
/* Init MIB counter (reset to 0) */
star_mib_init(dev);
/* Enable Hash Table BIST */
star_set_bit(star_hash_ctrl(base), STAR_HASH_CTRL_HASHEN);
/* Reset Hash Table (All reset to 0) */
star_reset_hash_table(dev);
star_clear_bit(STAR_ARL_CFG(base), STAR_ARL_CFG_MISCMODE);
star_clear_bit(STAR_ARL_CFG(base), STAR_ARL_CFG_HASHALG_CRCDA);
/*Recv VLAN tag in RX packet */
star_clear_bit(STAR_MAC_CFG(base), STAR_MAC_CFG_VLANSTRIP);
return 0;
}
static void star_mib_reset(star_dev *dev)
{
void __iomem *base = dev->base;
star_get_reg(STAR_MIB_RXOKPKT(base));
star_get_reg(STAR_MIB_RXOKBYTE(base));
star_get_reg(STAR_MIB_RXRUNT(base));
star_get_reg(STAR_MIB_RXOVERSIZE(base));
star_get_reg(STAR_MIB_RXNOBUFDROP(base));
star_get_reg(STAR_MIB_RXCRCERR(base));
star_get_reg(STAR_MIB_RXARLDROP(base));
star_get_reg(STAR_MIB_RXVLANDROP(base));
star_get_reg(STAR_MIB_RXCKSERR(base));
star_get_reg(STAR_MIB_RXPAUSE(base));
star_get_reg(STAR_MIB_TXOKPKT(base));
star_get_reg(STAR_MIB_TXOKBYTE(base));
star_get_reg(STAR_MIB_TXPAUSECOL(base));
}
int star_mib_init(star_dev *dev)
{
star_mib_reset(dev);
return 0;
}
int star_phyctrl_init(star_dev *dev, u32 enable, u32 phy_addr)
{
u32 data;
void __iomem *base = dev->base;
data = STAR_PHY_CTRL1_FORCETXFC |
STAR_PHY_CTRL1_FORCERXFC |
STAR_PHY_CTRL1_FORCEFULL |
STAR_PHY_CTRL1_FORCESPD_100M |
STAR_PHY_CTRL1_ANEN;
STAR_MSG(STAR_VERB, "PHY Control Initialization\n");
/* Enable/Disable PHY auto-polling */
if (enable)
star_set_reg(STAR_PHY_CTRL1(base),
data | STAR_PHY_CTRL1_APEN |
(phy_addr &
STAR_PHY_CTRL1_phy_addr_MASK)
<< STAR_PHY_CTRL1_phy_addr_OFFSET);
else
star_set_reg(STAR_PHY_CTRL1(base), data | STAR_PHY_CTRL1_APDIS);
return 0;
}
void star_set_hashbit(star_dev *dev, u32 addr, u32 value)
{
u32 data;
void __iomem *base = dev->base;
STAR_POLLING_TIMEOUT(star_is_set_bit(star_hash_ctrl(base),
STAR_HASH_CTRL_HTBISTDONE));
STAR_POLLING_TIMEOUT(star_is_set_bit(star_hash_ctrl(base),
STAR_HASH_CTRL_HTBISTOK));
STAR_POLLING_TIMEOUT(!star_is_set_bit(star_hash_ctrl(base),
STAR_HASH_CTRL_START));
data = (STAR_HASH_CTRL_HASHEN |
STAR_HASH_CTRL_ACCESSWT | STAR_HASH_CTRL_START |
(value ? STAR_HASH_CTRL_HBITDATA : 0) |
(addr & STAR_HASH_CTRL_HBITADDR_MASK)
<< STAR_HASH_CTRL_HBITADDR_OFFSET);
star_set_reg(star_hash_ctrl(base), data);
STAR_POLLING_TIMEOUT(!star_is_set_bit(star_hash_ctrl(base),
STAR_HASH_CTRL_START));
}
int star_hw_init(star_dev *dev)
{
star_set_reg(ETHSYS_CONFIG(dev->base),
SWC_MII_MODE | EXT_MDC_MODE | MII_PAD_OE);
star_set_reg(MAC_CLOCK_CONFIG(dev->base),
(star_get_reg(MAC_CLOCK_CONFIG(dev->base)) &
(~(0xff << 0))) | MDC_CLK_DIV_10);
return 0;
}
void star_link_status_change(star_dev *dev)
{
u32 val, speed;
val = star_get_reg(STAR_PHY_CTRL1(dev->base));
if (dev->link_up != ((val & STAR_PHY_CTRL1_STA_LINK) ? 1UL : 0UL)) {
dev->link_up = (val & STAR_PHY_CTRL1_STA_LINK) ? 1UL : 0UL;
STAR_MSG(STAR_WARN, "Link status: %s\n",
dev->link_up ? "Up" : "Down");
if (dev->link_up) {
speed = ((val >> STAR_PHY_CTRL1_STA_SPD_OFFSET) &
STAR_PHY_CTRL1_STA_SPD_MASK);
STAR_MSG(STAR_WARN, "%s Duplex - %s Mbps mode\n",
(val & STAR_PHY_CTRL1_STA_FULL) ?
"Full" : "Half",
!speed ? "10" : (speed == 1 ? "100" :
(speed == 2 ? "1000" : "unknown")));
STAR_MSG(STAR_WARN,
"TX flow control:%s, RX flow control:%s\n",
(val & STAR_PHY_CTRL1_STA_TXFC) ? "On" : "Off",
(val & STAR_PHY_CTRL1_STA_RXFC) ? "On" : "Off");
} else {
netif_carrier_off(((star_private *)dev->star_prv)->dev);
}
}
if (dev->link_up)
netif_carrier_on(((star_private *)dev->star_prv)->dev);
}
void star_nic_pdset(star_dev *dev, bool flag)
{
#define MAX_NICPDRDY_RETRY 10000
u32 data, retry = 0;
data = star_get_reg(STAR_MAC_CFG(dev->base));
if (flag) {
data |= STAR_MAC_CFG_NICPD;
star_set_reg(STAR_MAC_CFG(dev->base), data);
/* wait until NIC_PD_READY and clear it */
do {
data = star_get_reg(STAR_MAC_CFG(dev->base));
if (data & STAR_MAC_CFG_NICPDRDY) {
/* clear NIC_PD_READY */
data |= STAR_MAC_CFG_NICPDRDY;
star_set_reg(STAR_MAC_CFG(dev->base), data);
break;
}
} while (retry++ < MAX_NICPDRDY_RETRY);
if (retry >= MAX_NICPDRDY_RETRY)
STAR_MSG(STAR_ERR, "timeout MAX_NICPDRDY_RETRY(%d)\n",
MAX_NICPDRDY_RETRY);
} else {
data &= ~STAR_MAC_CFG_NICPD;
star_set_reg(STAR_MAC_CFG(dev->base), data);
}
}
void star_config_wol(star_dev *star_dev, bool enable)
{
STAR_MSG(STAR_DBG, "[%s]%s wol\n", __func__,
enable ? "enable" : "disable");
if (enable) {
star_set_reg(star_int_sta(star_dev->base),
star_get_reg(star_int_sta(star_dev->base)));
star_set_bit(STAR_MAC_CFG(star_dev->base), STAR_MAC_CFG_WOLEN);
star_mb();
star_clear_bit(star_int_mask(star_dev->base),
STAR_INT_STA_MAGICPKT);
} else {
star_clear_bit(STAR_MAC_CFG(star_dev->base),
STAR_MAC_CFG_WOLEN);
star_mb();
star_set_bit(star_int_mask(star_dev->base),
STAR_INT_STA_MAGICPKT);
}
}
void star_switch_to_rmii_mode(star_dev *star_dev)
{
u32 reg_val;
reg_val = star_get_reg(star_dev->pericfg_base + 0x14);
reg_val &= ~(0xf << 0);
/* select RMII mode */
reg_val |= (0x1 << 0);
star_set_reg(star_dev->pericfg_base + 0x14, reg_val);
#ifdef STAR_USE_TX_CLOCK
reg_val = star_get_reg(star_dev->pericfg_base + 0x18);
reg_val &= ~(0x1 << 0);
/* select tx clock */
reg_val |= (0x1 << 0);
star_set_reg(star_dev->pericfg_base + 0x18, reg_val);
#endif
}