1609 lines
38 KiB
C
1609 lines
38 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2020 Fuzhou Rockchip Electronics Co., Ltd
|
|
*/
|
|
|
|
#include <linux/ip.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/prefetch.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/udp.h>
|
|
#include <linux/skbuff.h>
|
|
#include <net/pkt_cls.h>
|
|
#include <net/tcp.h>
|
|
#include <net/udp.h>
|
|
#include <linux/soc/rockchip/rk_vendor_storage.h>
|
|
#include "stmmac.h"
|
|
#include "dwmac1000.h"
|
|
#include "dwmac_dma.h"
|
|
#include "dwmac-rk-tool.h"
|
|
|
|
enum {
|
|
LOOPBACK_TYPE_GMAC = 1,
|
|
LOOPBACK_TYPE_PHY
|
|
};
|
|
|
|
enum {
|
|
LOOPBACK_SPEED10 = 10,
|
|
LOOPBACK_SPEED100 = 100,
|
|
LOOPBACK_SPEED1000 = 1000
|
|
};
|
|
|
|
struct dwmac_rk_packet_attrs {
|
|
unsigned char src[6];
|
|
unsigned char dst[6];
|
|
u32 ip_src;
|
|
u32 ip_dst;
|
|
int tcp;
|
|
int sport;
|
|
int dport;
|
|
int size;
|
|
};
|
|
|
|
struct dwmac_rk_hdr {
|
|
__be32 version;
|
|
__be64 magic;
|
|
u32 id;
|
|
int tx;
|
|
int rx;
|
|
} __packed;
|
|
|
|
struct dwmac_rk_lb_priv {
|
|
/* desc && buffer */
|
|
struct dma_desc *dma_tx;
|
|
dma_addr_t dma_tx_phy;
|
|
struct sk_buff *tx_skbuff;
|
|
dma_addr_t tx_skbuff_dma;
|
|
unsigned int tx_skbuff_dma_len;
|
|
|
|
struct dma_desc *dma_rx ____cacheline_aligned_in_smp;
|
|
dma_addr_t dma_rx_phy;
|
|
struct sk_buff *rx_skbuff;
|
|
dma_addr_t rx_skbuff_dma;
|
|
u32 rx_tail_addr;
|
|
u32 tx_tail_addr;
|
|
|
|
/* rx buffer size */
|
|
unsigned int dma_buf_sz;
|
|
unsigned int buf_sz;
|
|
|
|
int type;
|
|
int speed;
|
|
struct dwmac_rk_packet_attrs *packet;
|
|
|
|
unsigned int actual_size;
|
|
int scan;
|
|
int sysfs;
|
|
u32 id;
|
|
int tx;
|
|
int rx;
|
|
int final_tx;
|
|
int final_rx;
|
|
int max_delay;
|
|
};
|
|
|
|
#define DMA_CONTROL_OSP BIT(4)
|
|
#define DMA_CHAN_BASE_ADDR 0x00001100
|
|
#define DMA_CHAN_BASE_OFFSET 0x80
|
|
#define DMA_CHANX_BASE_ADDR(x) (DMA_CHAN_BASE_ADDR + \
|
|
((x) * DMA_CHAN_BASE_OFFSET))
|
|
#define DMA_CHAN_TX_CONTROL(x) (DMA_CHANX_BASE_ADDR(x) + 0x4)
|
|
#define DMA_CHAN_STATUS(x) (DMA_CHANX_BASE_ADDR(x) + 0x60)
|
|
#define DMA_CHAN_STATUS_ERI BIT(11)
|
|
#define DMA_CHAN_STATUS_ETI BIT(10)
|
|
|
|
#define STMMAC_ALIGN(x) __ALIGN_KERNEL(x, SMP_CACHE_BYTES)
|
|
#define MAX_DELAYLINE 0x7f
|
|
#define RK3588_MAX_DELAYLINE 0xc7
|
|
#define SCAN_STEP 0x5
|
|
#define SCAN_VALID_RANGE 0xA
|
|
|
|
#define DWMAC_RK_TEST_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \
|
|
sizeof(struct dwmac_rk_hdr))
|
|
#define DWMAC_RK_TEST_PKT_MAGIC 0xdeadcafecafedeadULL
|
|
#define DWMAC_RK_TEST_PKT_MAX_SIZE 1500
|
|
|
|
static __maybe_unused struct dwmac_rk_packet_attrs dwmac_rk_udp_attr = {
|
|
.dst = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
.tcp = 0,
|
|
.size = 1024,
|
|
};
|
|
|
|
static __maybe_unused struct dwmac_rk_packet_attrs dwmac_rk_tcp_attr = {
|
|
.dst = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
.tcp = 1,
|
|
.size = 1024,
|
|
};
|
|
|
|
static int dwmac_rk_enable_mac_loopback(struct stmmac_priv *priv, int speed,
|
|
int addr, bool phy)
|
|
{
|
|
u32 ctrl;
|
|
int phy_val;
|
|
|
|
ctrl = readl(priv->ioaddr + GMAC_CONTROL);
|
|
ctrl &= ~priv->hw->link.speed_mask;
|
|
ctrl |= GMAC_CONTROL_LM;
|
|
|
|
if (phy)
|
|
phy_val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
|
|
switch (speed) {
|
|
case LOOPBACK_SPEED1000:
|
|
ctrl |= priv->hw->link.speed1000;
|
|
if (phy) {
|
|
phy_val &= ~BMCR_SPEED100;
|
|
phy_val |= BMCR_SPEED1000;
|
|
}
|
|
break;
|
|
case LOOPBACK_SPEED100:
|
|
ctrl |= priv->hw->link.speed100;
|
|
if (phy) {
|
|
phy_val &= ~BMCR_SPEED1000;
|
|
phy_val |= BMCR_SPEED100;
|
|
}
|
|
break;
|
|
case LOOPBACK_SPEED10:
|
|
ctrl |= priv->hw->link.speed10;
|
|
if (phy) {
|
|
phy_val &= ~BMCR_SPEED1000;
|
|
phy_val &= ~BMCR_SPEED100;
|
|
}
|
|
break;
|
|
default:
|
|
return -EPERM;
|
|
}
|
|
|
|
ctrl |= priv->hw->link.duplex;
|
|
writel(ctrl, priv->ioaddr + GMAC_CONTROL);
|
|
|
|
if (phy) {
|
|
phy_val &= ~BMCR_PDOWN;
|
|
phy_val &= ~BMCR_ANENABLE;
|
|
phy_val &= ~BMCR_PDOWN;
|
|
phy_val |= BMCR_FULLDPLX;
|
|
mdiobus_write(priv->mii, addr, MII_BMCR, phy_val);
|
|
phy_val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
}
|
|
|
|
if (likely(priv->plat->fix_mac_speed))
|
|
priv->plat->fix_mac_speed(priv->plat->bsp_priv, speed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwmac_rk_disable_mac_loopback(struct stmmac_priv *priv, int addr)
|
|
{
|
|
u32 ctrl;
|
|
int phy_val;
|
|
|
|
ctrl = readl(priv->ioaddr + GMAC_CONTROL);
|
|
ctrl &= ~GMAC_CONTROL_LM;
|
|
writel(ctrl, priv->ioaddr + GMAC_CONTROL);
|
|
|
|
phy_val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
phy_val |= BMCR_ANENABLE;
|
|
|
|
mdiobus_write(priv->mii, addr, MII_BMCR, phy_val);
|
|
phy_val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwmac_rk_set_mac_loopback(struct stmmac_priv *priv,
|
|
int speed, bool enable,
|
|
int addr, bool phy)
|
|
{
|
|
if (enable)
|
|
return dwmac_rk_enable_mac_loopback(priv, speed, addr, phy);
|
|
else
|
|
return dwmac_rk_disable_mac_loopback(priv, addr);
|
|
}
|
|
|
|
static int dwmac_rk_enable_phy_loopback(struct stmmac_priv *priv, int speed,
|
|
int addr, bool phy)
|
|
{
|
|
u32 ctrl;
|
|
int val;
|
|
|
|
ctrl = readl(priv->ioaddr + MAC_CTRL_REG);
|
|
ctrl &= ~priv->hw->link.speed_mask;
|
|
|
|
if (phy)
|
|
val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
|
|
switch (speed) {
|
|
case LOOPBACK_SPEED1000:
|
|
ctrl |= priv->hw->link.speed1000;
|
|
if (phy) {
|
|
val &= ~BMCR_SPEED100;
|
|
val |= BMCR_SPEED1000;
|
|
}
|
|
break;
|
|
case LOOPBACK_SPEED100:
|
|
ctrl |= priv->hw->link.speed100;
|
|
if (phy) {
|
|
val &= ~BMCR_SPEED1000;
|
|
val |= BMCR_SPEED100;
|
|
}
|
|
break;
|
|
case LOOPBACK_SPEED10:
|
|
ctrl |= priv->hw->link.speed10;
|
|
if (phy) {
|
|
val &= ~BMCR_SPEED1000;
|
|
val &= ~BMCR_SPEED100;
|
|
}
|
|
break;
|
|
default:
|
|
return -EPERM;
|
|
}
|
|
|
|
ctrl |= priv->hw->link.duplex;
|
|
writel(ctrl, priv->ioaddr + MAC_CTRL_REG);
|
|
|
|
if (phy) {
|
|
val |= BMCR_FULLDPLX;
|
|
val &= ~BMCR_PDOWN;
|
|
val &= ~BMCR_ANENABLE;
|
|
val |= BMCR_LOOPBACK;
|
|
mdiobus_write(priv->mii, addr, MII_BMCR, val);
|
|
val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
}
|
|
|
|
if (likely(priv->plat->fix_mac_speed))
|
|
priv->plat->fix_mac_speed(priv->plat->bsp_priv, speed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwmac_rk_disable_phy_loopback(struct stmmac_priv *priv, int addr)
|
|
{
|
|
int val;
|
|
|
|
val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
val |= BMCR_ANENABLE;
|
|
val &= ~BMCR_LOOPBACK;
|
|
|
|
mdiobus_write(priv->mii, addr, MII_BMCR, val);
|
|
val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwmac_rk_set_phy_loopback(struct stmmac_priv *priv,
|
|
int speed, bool enable,
|
|
int addr, bool phy)
|
|
{
|
|
if (enable)
|
|
return dwmac_rk_enable_phy_loopback(priv, speed,
|
|
addr, phy);
|
|
else
|
|
return dwmac_rk_disable_phy_loopback(priv, addr);
|
|
}
|
|
|
|
static int dwmac_rk_set_loopback(struct stmmac_priv *priv,
|
|
int type, int speed, bool enable,
|
|
int addr, bool phy)
|
|
{
|
|
int ret;
|
|
|
|
switch (type) {
|
|
case LOOPBACK_TYPE_PHY:
|
|
ret = dwmac_rk_set_phy_loopback(priv, speed, enable, addr, phy);
|
|
break;
|
|
case LOOPBACK_TYPE_GMAC:
|
|
ret = dwmac_rk_set_mac_loopback(priv, speed, enable, addr, phy);
|
|
break;
|
|
default:
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
|
|
usleep_range(100000, 200000);
|
|
return ret;
|
|
}
|
|
|
|
static inline void dwmac_rk_ether_addr_copy(u8 *dst, const u8 *src)
|
|
{
|
|
u16 *a = (u16 *)dst;
|
|
const u16 *b = (const u16 *)src;
|
|
|
|
a[0] = b[0];
|
|
a[1] = b[1];
|
|
a[2] = b[2];
|
|
}
|
|
|
|
static void dwmac_rk_udp4_hwcsum(struct sk_buff *skb, __be32 src, __be32 dst)
|
|
{
|
|
struct udphdr *uh = udp_hdr(skb);
|
|
int offset = skb_transport_offset(skb);
|
|
int len = skb->len - offset;
|
|
|
|
skb->csum_start = skb_transport_header(skb) - skb->head;
|
|
skb->csum_offset = offsetof(struct udphdr, check);
|
|
uh->check = ~csum_tcpudp_magic(src, dst, len,
|
|
IPPROTO_UDP, 0);
|
|
}
|
|
|
|
static struct sk_buff *dwmac_rk_get_skb(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct udphdr *uhdr = NULL;
|
|
struct tcphdr *thdr = NULL;
|
|
struct dwmac_rk_hdr *shdr;
|
|
struct ethhdr *ehdr;
|
|
struct iphdr *ihdr;
|
|
struct dwmac_rk_packet_attrs *attr;
|
|
int iplen, size, nfrags;
|
|
|
|
attr = lb_priv->packet;
|
|
size = attr->size + DWMAC_RK_TEST_PKT_SIZE;
|
|
if (attr->tcp)
|
|
size += sizeof(struct tcphdr);
|
|
else
|
|
size += sizeof(struct udphdr);
|
|
|
|
if (size >= DWMAC_RK_TEST_PKT_MAX_SIZE)
|
|
return NULL;
|
|
|
|
lb_priv->actual_size = size;
|
|
|
|
skb = netdev_alloc_skb_ip_align(priv->dev, size);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
skb_linearize(skb);
|
|
nfrags = skb_shinfo(skb)->nr_frags;
|
|
if (nfrags > 0) {
|
|
pr_err("%s: TX nfrags is not zero\n", __func__);
|
|
dev_kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
|
|
ehdr = (struct ethhdr *)skb_push(skb, ETH_HLEN);
|
|
skb_reset_mac_header(skb);
|
|
|
|
skb_set_network_header(skb, skb->len);
|
|
ihdr = (struct iphdr *)skb_put(skb, sizeof(*ihdr));
|
|
|
|
skb_set_transport_header(skb, skb->len);
|
|
if (attr->tcp)
|
|
thdr = (struct tcphdr *)skb_put(skb, sizeof(*thdr));
|
|
else
|
|
uhdr = (struct udphdr *)skb_put(skb, sizeof(*uhdr));
|
|
|
|
eth_zero_addr(ehdr->h_source);
|
|
eth_zero_addr(ehdr->h_dest);
|
|
|
|
dwmac_rk_ether_addr_copy(ehdr->h_source, priv->dev->dev_addr);
|
|
dwmac_rk_ether_addr_copy(ehdr->h_dest, attr->dst);
|
|
|
|
ehdr->h_proto = htons(ETH_P_IP);
|
|
|
|
if (attr->tcp) {
|
|
if (!thdr) {
|
|
dev_kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
|
|
thdr->source = htons(attr->sport);
|
|
thdr->dest = htons(attr->dport);
|
|
thdr->doff = sizeof(struct tcphdr) / 4;
|
|
thdr->check = 0;
|
|
} else {
|
|
if (!uhdr) {
|
|
dev_kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
|
|
uhdr->source = htons(attr->sport);
|
|
uhdr->dest = htons(attr->dport);
|
|
uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size);
|
|
uhdr->check = 0;
|
|
}
|
|
|
|
ihdr->ihl = 5;
|
|
ihdr->ttl = 32;
|
|
ihdr->version = 4;
|
|
if (attr->tcp)
|
|
ihdr->protocol = IPPROTO_TCP;
|
|
else
|
|
ihdr->protocol = IPPROTO_UDP;
|
|
|
|
iplen = sizeof(*ihdr) + sizeof(*shdr) + attr->size;
|
|
if (attr->tcp)
|
|
iplen += sizeof(*thdr);
|
|
else
|
|
iplen += sizeof(*uhdr);
|
|
|
|
ihdr->tot_len = htons(iplen);
|
|
ihdr->frag_off = 0;
|
|
ihdr->saddr = htonl(attr->ip_src);
|
|
ihdr->daddr = htonl(attr->ip_dst);
|
|
ihdr->tos = 0;
|
|
ihdr->id = 0;
|
|
ip_send_check(ihdr);
|
|
|
|
shdr = (struct dwmac_rk_hdr *)skb_put(skb, sizeof(*shdr));
|
|
shdr->version = 0;
|
|
shdr->magic = cpu_to_be64(DWMAC_RK_TEST_PKT_MAGIC);
|
|
shdr->id = lb_priv->id;
|
|
shdr->tx = lb_priv->tx;
|
|
shdr->rx = lb_priv->rx;
|
|
|
|
if (attr->size) {
|
|
skb_put(skb, attr->size);
|
|
get_random_bytes((u8 *)shdr + sizeof(*shdr), attr->size);
|
|
}
|
|
|
|
skb->csum = 0;
|
|
skb->ip_summed = CHECKSUM_PARTIAL;
|
|
if (attr->tcp) {
|
|
if (!thdr) {
|
|
dev_kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
|
|
thdr->check = ~tcp_v4_check(skb->len, ihdr->saddr,
|
|
ihdr->daddr, 0);
|
|
skb->csum_start = skb_transport_header(skb) - skb->head;
|
|
skb->csum_offset = offsetof(struct tcphdr, check);
|
|
} else {
|
|
dwmac_rk_udp4_hwcsum(skb, ihdr->saddr, ihdr->daddr);
|
|
}
|
|
|
|
skb->protocol = htons(ETH_P_IP);
|
|
skb->pkt_type = PACKET_HOST;
|
|
|
|
return skb;
|
|
}
|
|
|
|
static int dwmac_rk_loopback_validate(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct dwmac_rk_hdr *shdr;
|
|
struct ethhdr *ehdr;
|
|
struct udphdr *uhdr;
|
|
struct tcphdr *thdr;
|
|
struct iphdr *ihdr;
|
|
int ret = -EAGAIN;
|
|
|
|
if (skb->len >= DWMAC_RK_TEST_PKT_MAX_SIZE)
|
|
goto out;
|
|
|
|
if (lb_priv->actual_size != skb->len)
|
|
goto out;
|
|
|
|
ehdr = (struct ethhdr *)(skb->data);
|
|
if (!ether_addr_equal(ehdr->h_dest, lb_priv->packet->dst))
|
|
goto out;
|
|
|
|
if (!ether_addr_equal(ehdr->h_source, priv->dev->dev_addr))
|
|
goto out;
|
|
|
|
ihdr = (struct iphdr *)(skb->data + ETH_HLEN);
|
|
|
|
if (lb_priv->packet->tcp) {
|
|
if (ihdr->protocol != IPPROTO_TCP)
|
|
goto out;
|
|
|
|
thdr = (struct tcphdr *)((u8 *)ihdr + 4 * ihdr->ihl);
|
|
if (thdr->dest != htons(lb_priv->packet->dport))
|
|
goto out;
|
|
|
|
shdr = (struct dwmac_rk_hdr *)((u8 *)thdr + sizeof(*thdr));
|
|
} else {
|
|
if (ihdr->protocol != IPPROTO_UDP)
|
|
goto out;
|
|
|
|
uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl);
|
|
if (uhdr->dest != htons(lb_priv->packet->dport))
|
|
goto out;
|
|
|
|
shdr = (struct dwmac_rk_hdr *)((u8 *)uhdr + sizeof(*uhdr));
|
|
}
|
|
|
|
if (shdr->magic != cpu_to_be64(DWMAC_RK_TEST_PKT_MAGIC))
|
|
goto out;
|
|
|
|
if (lb_priv->id != shdr->id)
|
|
goto out;
|
|
|
|
if (lb_priv->tx != shdr->tx || lb_priv->rx != shdr->rx)
|
|
goto out;
|
|
|
|
ret = 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static inline int dwmac_rk_rx_fill(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct dma_desc *p;
|
|
struct sk_buff *skb;
|
|
|
|
p = lb_priv->dma_rx;
|
|
if (likely(!lb_priv->rx_skbuff)) {
|
|
skb = netdev_alloc_skb_ip_align(priv->dev, lb_priv->buf_sz);
|
|
if (unlikely(!skb))
|
|
return -ENOMEM;
|
|
|
|
if (skb_linearize(skb)) {
|
|
pr_err("%s: Rx skb linearize failed\n", __func__);
|
|
lb_priv->rx_skbuff = NULL;
|
|
dev_kfree_skb(skb);
|
|
return -EPERM;
|
|
}
|
|
|
|
lb_priv->rx_skbuff = skb;
|
|
lb_priv->rx_skbuff_dma =
|
|
dma_map_single(priv->device, skb->data, lb_priv->dma_buf_sz,
|
|
DMA_FROM_DEVICE);
|
|
if (dma_mapping_error(priv->device,
|
|
lb_priv->rx_skbuff_dma)) {
|
|
pr_err("%s: Rx dma map failed\n", __func__);
|
|
lb_priv->rx_skbuff = NULL;
|
|
dev_kfree_skb(skb);
|
|
return -EFAULT;
|
|
}
|
|
|
|
stmmac_set_desc_addr(priv, p, lb_priv->rx_skbuff_dma);
|
|
/* Fill DES3 in case of RING mode */
|
|
if (lb_priv->dma_buf_sz == BUF_SIZE_16KiB)
|
|
p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB);
|
|
}
|
|
|
|
wmb();
|
|
stmmac_set_rx_owner(priv, p, priv->use_riwt);
|
|
wmb();
|
|
|
|
stmmac_set_rx_tail_ptr(priv, priv->ioaddr, lb_priv->rx_tail_addr, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwmac_rk_rx_clean(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
if (likely(lb_priv->rx_skbuff_dma)) {
|
|
dma_unmap_single(priv->device,
|
|
lb_priv->rx_skbuff_dma,
|
|
lb_priv->dma_buf_sz, DMA_FROM_DEVICE);
|
|
lb_priv->rx_skbuff_dma = 0;
|
|
}
|
|
|
|
if (likely(lb_priv->rx_skbuff)) {
|
|
dev_consume_skb_any(lb_priv->rx_skbuff);
|
|
lb_priv->rx_skbuff = NULL;
|
|
}
|
|
}
|
|
|
|
static int dwmac_rk_rx_validate(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct dma_desc *p;
|
|
struct sk_buff *skb;
|
|
int coe = priv->hw->rx_csum;
|
|
unsigned int frame_len;
|
|
|
|
p = lb_priv->dma_rx;
|
|
skb = lb_priv->rx_skbuff;
|
|
if (unlikely(!skb)) {
|
|
pr_err("%s: Inconsistent Rx descriptor chain\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
frame_len = priv->hw->desc->get_rx_frame_len(p, coe);
|
|
/* check if frame_len fits the preallocated memory */
|
|
if (frame_len > lb_priv->dma_buf_sz) {
|
|
pr_err("%s: frame_len long: %d\n", __func__, frame_len);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
frame_len -= ETH_FCS_LEN;
|
|
prefetch(skb->data - NET_IP_ALIGN);
|
|
skb_put(skb, frame_len);
|
|
dma_unmap_single(priv->device,
|
|
lb_priv->rx_skbuff_dma,
|
|
lb_priv->dma_buf_sz,
|
|
DMA_FROM_DEVICE);
|
|
|
|
return dwmac_rk_loopback_validate(priv, lb_priv, skb);
|
|
}
|
|
|
|
static int dwmac_rk_get_desc_status(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct dma_desc *txp, *rxp;
|
|
int tx_status, rx_status;
|
|
|
|
txp = lb_priv->dma_tx;
|
|
tx_status = priv->hw->desc->tx_status(&priv->dev->stats,
|
|
&priv->xstats, txp,
|
|
priv->ioaddr);
|
|
/* Check if the descriptor is owned by the DMA */
|
|
if (unlikely(tx_status & tx_dma_own))
|
|
return -EBUSY;
|
|
|
|
rxp = lb_priv->dma_rx;
|
|
/* read the status of the incoming frame */
|
|
rx_status = priv->hw->desc->rx_status(&priv->dev->stats,
|
|
&priv->xstats, rxp);
|
|
if (unlikely(rx_status & dma_own))
|
|
return -EBUSY;
|
|
|
|
usleep_range(100, 150);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwmac_rk_tx_clean(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct sk_buff *skb = lb_priv->tx_skbuff;
|
|
struct dma_desc *p;
|
|
|
|
p = lb_priv->dma_tx;
|
|
|
|
if (likely(lb_priv->tx_skbuff_dma)) {
|
|
dma_unmap_single(priv->device,
|
|
lb_priv->tx_skbuff_dma,
|
|
lb_priv->tx_skbuff_dma_len,
|
|
DMA_TO_DEVICE);
|
|
lb_priv->tx_skbuff_dma = 0;
|
|
}
|
|
|
|
if (likely(skb)) {
|
|
dev_consume_skb_any(skb);
|
|
lb_priv->tx_skbuff = NULL;
|
|
}
|
|
|
|
priv->hw->desc->release_tx_desc(p, priv->mode);
|
|
}
|
|
|
|
static int dwmac_rk_xmit(struct sk_buff *skb, struct net_device *dev,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
unsigned int nopaged_len = skb_headlen(skb);
|
|
int csum_insertion = 0;
|
|
struct dma_desc *desc;
|
|
unsigned int des;
|
|
|
|
priv->hw->mac->reset_eee_mode(priv->hw);
|
|
|
|
csum_insertion = (skb->ip_summed == CHECKSUM_PARTIAL);
|
|
|
|
desc = lb_priv->dma_tx;
|
|
lb_priv->tx_skbuff = skb;
|
|
|
|
des = dma_map_single(priv->device, skb->data,
|
|
nopaged_len, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(priv->device, des))
|
|
goto dma_map_err;
|
|
lb_priv->tx_skbuff_dma = des;
|
|
|
|
stmmac_set_desc_addr(priv, desc, des);
|
|
lb_priv->tx_skbuff_dma_len = nopaged_len;
|
|
|
|
/* Prepare the first descriptor setting the OWN bit too */
|
|
stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len,
|
|
csum_insertion, priv->mode, 1, 1,
|
|
skb->len);
|
|
stmmac_enable_dma_transmission(priv, priv->ioaddr);
|
|
|
|
lb_priv->tx_tail_addr = lb_priv->dma_tx_phy + sizeof(*desc);
|
|
stmmac_set_tx_tail_ptr(priv, priv->ioaddr, lb_priv->tx_tail_addr, 0);
|
|
|
|
return 0;
|
|
|
|
dma_map_err:
|
|
pr_err("%s: Tx dma map failed\n", __func__);
|
|
dev_kfree_skb(skb);
|
|
return -EFAULT;
|
|
}
|
|
|
|
static int __dwmac_rk_loopback_run(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
u32 rx_channels_count = min_t(u32, priv->plat->rx_queues_to_use, 1);
|
|
u32 tx_channels_count = min_t(u32, priv->plat->tx_queues_to_use, 1);
|
|
struct sk_buff *tx_skb;
|
|
u32 chan = 0;
|
|
int ret = -EIO, delay;
|
|
u32 status;
|
|
bool finish = false;
|
|
|
|
if (lb_priv->speed == LOOPBACK_SPEED1000)
|
|
delay = 10;
|
|
else if (lb_priv->speed == LOOPBACK_SPEED100)
|
|
delay = 20;
|
|
else if (lb_priv->speed == LOOPBACK_SPEED10)
|
|
delay = 50;
|
|
else
|
|
return -EPERM;
|
|
|
|
if (dwmac_rk_rx_fill(priv, lb_priv))
|
|
return -ENOMEM;
|
|
|
|
/* Enable the MAC Rx/Tx */
|
|
stmmac_mac_set(priv, priv->ioaddr, true);
|
|
|
|
for (chan = 0; chan < rx_channels_count; chan++)
|
|
stmmac_start_rx(priv, priv->ioaddr, chan);
|
|
for (chan = 0; chan < tx_channels_count; chan++)
|
|
stmmac_start_tx(priv, priv->ioaddr, chan);
|
|
|
|
tx_skb = dwmac_rk_get_skb(priv, lb_priv);
|
|
if (!tx_skb) {
|
|
ret = -ENOMEM;
|
|
goto stop;
|
|
}
|
|
|
|
if (dwmac_rk_xmit(tx_skb, priv->dev, lb_priv)) {
|
|
ret = -EFAULT;
|
|
goto stop;
|
|
}
|
|
|
|
do {
|
|
usleep_range(100, 150);
|
|
delay--;
|
|
if (priv->plat->has_gmac4) {
|
|
status = readl(priv->ioaddr + DMA_CHAN_STATUS(0));
|
|
finish = (status & DMA_CHAN_STATUS_ERI) && (status & DMA_CHAN_STATUS_ETI);
|
|
} else {
|
|
status = readl(priv->ioaddr + DMA_STATUS);
|
|
finish = (status & DMA_STATUS_ERI) && (status & DMA_STATUS_ETI);
|
|
}
|
|
|
|
if (finish) {
|
|
if (!dwmac_rk_get_desc_status(priv, lb_priv)) {
|
|
ret = dwmac_rk_rx_validate(priv, lb_priv);
|
|
break;
|
|
}
|
|
}
|
|
} while (delay <= 0);
|
|
writel((status & 0x1ffff), priv->ioaddr + DMA_STATUS);
|
|
|
|
stop:
|
|
for (chan = 0; chan < rx_channels_count; chan++)
|
|
stmmac_stop_rx(priv, priv->ioaddr, chan);
|
|
for (chan = 0; chan < tx_channels_count; chan++)
|
|
stmmac_stop_tx(priv, priv->ioaddr, chan);
|
|
|
|
stmmac_mac_set(priv, priv->ioaddr, false);
|
|
/* wait for state machine is disabled */
|
|
usleep_range(100, 150);
|
|
|
|
dwmac_rk_tx_clean(priv, lb_priv);
|
|
dwmac_rk_rx_clean(priv, lb_priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dwmac_rk_loopback_with_identify(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv,
|
|
int tx, int rx)
|
|
{
|
|
lb_priv->id++;
|
|
lb_priv->tx = tx;
|
|
lb_priv->rx = rx;
|
|
|
|
lb_priv->packet = &dwmac_rk_tcp_attr;
|
|
dwmac_rk_set_rgmii_delayline(priv, tx, rx);
|
|
|
|
return __dwmac_rk_loopback_run(priv, lb_priv);
|
|
}
|
|
|
|
static inline bool dwmac_rk_delayline_is_txvalid(struct dwmac_rk_lb_priv *lb_priv,
|
|
int tx)
|
|
{
|
|
if (tx > 0 && tx < lb_priv->max_delay)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static inline bool dwmac_rk_delayline_is_valid(struct dwmac_rk_lb_priv *lb_priv,
|
|
int tx, int rx)
|
|
{
|
|
if ((tx > 0 && tx < lb_priv->max_delay) &&
|
|
(rx > 0 && rx < lb_priv->max_delay))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static int dwmac_rk_delayline_scan_cross(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
int tx_left, tx_right, rx_up, rx_down;
|
|
int i, j, tx_index, rx_index;
|
|
int tx_mid = 0, rx_mid = 0;
|
|
|
|
/* initiation */
|
|
tx_index = SCAN_STEP;
|
|
rx_index = SCAN_STEP;
|
|
|
|
re_scan:
|
|
/* start from rx based on the experience */
|
|
for (i = rx_index; i <= (lb_priv->max_delay - SCAN_STEP); i += SCAN_STEP) {
|
|
tx_left = 0;
|
|
tx_right = 0;
|
|
tx_mid = 0;
|
|
|
|
for (j = tx_index; j <= (lb_priv->max_delay - SCAN_STEP);
|
|
j += SCAN_STEP) {
|
|
if (!dwmac_rk_loopback_with_identify(priv,
|
|
lb_priv, j, i)) {
|
|
if (!tx_left)
|
|
tx_left = j;
|
|
tx_right = j;
|
|
}
|
|
}
|
|
|
|
/* look for tx_mid */
|
|
if ((tx_right - tx_left) > SCAN_VALID_RANGE) {
|
|
tx_mid = (tx_right + tx_left) / 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Worst case: reach the end */
|
|
if (i >= (lb_priv->max_delay - SCAN_STEP))
|
|
goto end;
|
|
|
|
rx_up = 0;
|
|
rx_down = 0;
|
|
|
|
/* look for rx_mid base on the tx_mid */
|
|
for (i = SCAN_STEP; i <= (lb_priv->max_delay - SCAN_STEP);
|
|
i += SCAN_STEP) {
|
|
if (!dwmac_rk_loopback_with_identify(priv, lb_priv,
|
|
tx_mid, i)) {
|
|
if (!rx_up)
|
|
rx_up = i;
|
|
rx_down = i;
|
|
}
|
|
}
|
|
|
|
if ((rx_down - rx_up) > SCAN_VALID_RANGE) {
|
|
/* Now get the rx_mid */
|
|
rx_mid = (rx_up + rx_down) / 2;
|
|
} else {
|
|
rx_index += SCAN_STEP;
|
|
rx_mid = 0;
|
|
goto re_scan;
|
|
}
|
|
|
|
if (dwmac_rk_delayline_is_valid(lb_priv, tx_mid, rx_mid)) {
|
|
lb_priv->final_tx = tx_mid;
|
|
lb_priv->final_rx = rx_mid;
|
|
|
|
pr_info("Find available tx_delay = 0x%02x, rx_delay = 0x%02x\n",
|
|
lb_priv->final_tx, lb_priv->final_rx);
|
|
|
|
return 0;
|
|
}
|
|
end:
|
|
pr_err("Can't find available delayline\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
static int dwmac_rk_delayline_scan(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
int phy_iface = dwmac_rk_get_phy_interface(priv);
|
|
int tx, rx, tx_sum, rx_sum, count;
|
|
int tx_mid, rx_mid;
|
|
int ret = -ENXIO;
|
|
|
|
tx_sum = 0;
|
|
rx_sum = 0;
|
|
count = 0;
|
|
|
|
for (rx = 0x0; rx <= lb_priv->max_delay; rx++) {
|
|
if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID)
|
|
rx = -1;
|
|
printk(KERN_CONT "RX(%03d):", rx);
|
|
for (tx = 0x0; tx <= lb_priv->max_delay; tx++) {
|
|
if (!dwmac_rk_loopback_with_identify(priv,
|
|
lb_priv, tx, rx)) {
|
|
tx_sum += tx;
|
|
rx_sum += rx;
|
|
count++;
|
|
printk(KERN_CONT "O");
|
|
} else {
|
|
printk(KERN_CONT " ");
|
|
}
|
|
}
|
|
printk(KERN_CONT "\n");
|
|
|
|
if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID)
|
|
break;
|
|
}
|
|
|
|
if (tx_sum && rx_sum && count) {
|
|
tx_mid = tx_sum / count;
|
|
rx_mid = rx_sum / count;
|
|
|
|
if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID) {
|
|
if (dwmac_rk_delayline_is_txvalid(lb_priv, tx_mid)) {
|
|
lb_priv->final_tx = tx_mid;
|
|
lb_priv->final_rx = -1;
|
|
ret = 0;
|
|
}
|
|
} else {
|
|
if (dwmac_rk_delayline_is_valid(lb_priv, tx_mid, rx_mid)) {
|
|
lb_priv->final_tx = tx_mid;
|
|
lb_priv->final_rx = rx_mid;
|
|
ret = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret) {
|
|
pr_err("\nCan't find suitable delayline\n");
|
|
} else {
|
|
if (phy_iface == PHY_INTERFACE_MODE_RGMII_RXID)
|
|
pr_info("Find available tx_delay = 0x%02x, rx_delay = disable\n",
|
|
lb_priv->final_tx);
|
|
else
|
|
pr_info("\nFind suitable tx_delay = 0x%02x, rx_delay = 0x%02x\n",
|
|
lb_priv->final_tx, lb_priv->final_rx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dwmac_rk_loopback_delayline_scan(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
if (lb_priv->sysfs)
|
|
return dwmac_rk_delayline_scan(priv, lb_priv);
|
|
else
|
|
return dwmac_rk_delayline_scan_cross(priv, lb_priv);
|
|
}
|
|
|
|
static void dwmac_rk_dma_free_rx_skbufs(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
if (lb_priv->rx_skbuff) {
|
|
dma_unmap_single(priv->device, lb_priv->rx_skbuff_dma,
|
|
lb_priv->dma_buf_sz, DMA_FROM_DEVICE);
|
|
dev_kfree_skb_any(lb_priv->rx_skbuff);
|
|
}
|
|
lb_priv->rx_skbuff = NULL;
|
|
}
|
|
|
|
static void dwmac_rk_dma_free_tx_skbufs(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
if (lb_priv->tx_skbuff_dma) {
|
|
dma_unmap_single(priv->device,
|
|
lb_priv->tx_skbuff_dma,
|
|
lb_priv->tx_skbuff_dma_len,
|
|
DMA_TO_DEVICE);
|
|
}
|
|
|
|
if (lb_priv->tx_skbuff) {
|
|
dev_kfree_skb_any(lb_priv->tx_skbuff);
|
|
lb_priv->tx_skbuff = NULL;
|
|
lb_priv->tx_skbuff_dma = 0;
|
|
}
|
|
}
|
|
|
|
static int dwmac_rk_init_dma_desc_rings(struct net_device *dev, gfp_t flags,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
struct dma_desc *p;
|
|
|
|
p = lb_priv->dma_tx;
|
|
p->des2 = 0;
|
|
lb_priv->tx_skbuff_dma = 0;
|
|
lb_priv->tx_skbuff_dma_len = 0;
|
|
lb_priv->tx_skbuff = NULL;
|
|
|
|
lb_priv->rx_skbuff = NULL;
|
|
stmmac_init_rx_desc(priv, lb_priv->dma_rx,
|
|
priv->use_riwt, priv->mode,
|
|
true, lb_priv->dma_buf_sz);
|
|
|
|
stmmac_init_tx_desc(priv, lb_priv->dma_tx,
|
|
priv->mode,
|
|
true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwmac_rk_alloc_dma_desc_resources(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
int ret = -ENOMEM;
|
|
|
|
/* desc dma map */
|
|
lb_priv->dma_rx = dma_alloc_coherent(priv->device,
|
|
sizeof(struct dma_desc),
|
|
&lb_priv->dma_rx_phy,
|
|
GFP_KERNEL);
|
|
if (!lb_priv->dma_rx)
|
|
return ret;
|
|
|
|
lb_priv->dma_tx = dma_alloc_coherent(priv->device,
|
|
sizeof(struct dma_desc),
|
|
&lb_priv->dma_tx_phy,
|
|
GFP_KERNEL);
|
|
if (!lb_priv->dma_tx) {
|
|
dma_free_coherent(priv->device,
|
|
sizeof(struct dma_desc),
|
|
lb_priv->dma_rx, lb_priv->dma_rx_phy);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwmac_rk_free_dma_desc_resources(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
/* Release the DMA TX/RX socket buffers */
|
|
dwmac_rk_dma_free_rx_skbufs(priv, lb_priv);
|
|
dwmac_rk_dma_free_tx_skbufs(priv, lb_priv);
|
|
|
|
dma_free_coherent(priv->device, sizeof(struct dma_desc),
|
|
lb_priv->dma_tx, lb_priv->dma_tx_phy);
|
|
dma_free_coherent(priv->device, sizeof(struct dma_desc),
|
|
lb_priv->dma_rx, lb_priv->dma_rx_phy);
|
|
}
|
|
|
|
static int dwmac_rk_init_dma_engine(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
u32 rx_channels_count = min_t(u32, priv->plat->rx_queues_to_use, 1);
|
|
u32 tx_channels_count = min_t(u32, priv->plat->tx_queues_to_use, 1);
|
|
u32 dma_csr_ch = max(rx_channels_count, tx_channels_count);
|
|
u32 chan = 0;
|
|
int ret = 0;
|
|
|
|
ret = stmmac_reset(priv, priv->ioaddr);
|
|
if (ret) {
|
|
dev_err(priv->device, "Failed to reset the dma\n");
|
|
return ret;
|
|
}
|
|
|
|
/* DMA Configuration */
|
|
stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg, 0);
|
|
|
|
if (priv->plat->axi)
|
|
stmmac_axi(priv, priv->ioaddr, priv->plat->axi);
|
|
|
|
for (chan = 0; chan < dma_csr_ch; chan++)
|
|
stmmac_init_chan(priv, priv->ioaddr, priv->plat->dma_cfg, 0);
|
|
|
|
/* DMA RX Channel Configuration */
|
|
for (chan = 0; chan < rx_channels_count; chan++) {
|
|
stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
|
|
lb_priv->dma_rx_phy, 0);
|
|
|
|
lb_priv->rx_tail_addr = lb_priv->dma_rx_phy +
|
|
(1 * sizeof(struct dma_desc));
|
|
stmmac_set_rx_tail_ptr(priv, priv->ioaddr,
|
|
lb_priv->rx_tail_addr, 0);
|
|
}
|
|
|
|
/* DMA TX Channel Configuration */
|
|
for (chan = 0; chan < tx_channels_count; chan++) {
|
|
stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg,
|
|
lb_priv->dma_tx_phy, chan);
|
|
|
|
lb_priv->tx_tail_addr = lb_priv->dma_tx_phy;
|
|
stmmac_set_tx_tail_ptr(priv, priv->ioaddr,
|
|
lb_priv->tx_tail_addr, chan);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void dwmac_rk_dma_operation_mode(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
u32 rx_channels_count = min_t(u32, priv->plat->rx_queues_to_use, 1);
|
|
u32 tx_channels_count = min_t(u32, priv->plat->tx_queues_to_use, 1);
|
|
int rxfifosz = priv->plat->rx_fifo_size;
|
|
int txfifosz = priv->plat->tx_fifo_size;
|
|
u32 txmode = SF_DMA_MODE;
|
|
u32 rxmode = SF_DMA_MODE;
|
|
u32 chan = 0;
|
|
u8 qmode = 0;
|
|
|
|
if (rxfifosz == 0)
|
|
rxfifosz = priv->dma_cap.rx_fifo_size;
|
|
if (txfifosz == 0)
|
|
txfifosz = priv->dma_cap.tx_fifo_size;
|
|
|
|
/* Adjust for real per queue fifo size */
|
|
rxfifosz /= rx_channels_count;
|
|
txfifosz /= tx_channels_count;
|
|
|
|
/* configure all channels */
|
|
for (chan = 0; chan < rx_channels_count; chan++) {
|
|
qmode = priv->plat->rx_queues_cfg[chan].mode_to_use;
|
|
|
|
stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan,
|
|
rxfifosz, qmode);
|
|
stmmac_set_dma_bfsize(priv, priv->ioaddr, lb_priv->dma_buf_sz,
|
|
chan);
|
|
}
|
|
|
|
for (chan = 0; chan < tx_channels_count; chan++) {
|
|
qmode = priv->plat->tx_queues_cfg[chan].mode_to_use;
|
|
|
|
stmmac_dma_tx_mode(priv, priv->ioaddr, txmode, chan,
|
|
txfifosz, qmode);
|
|
}
|
|
}
|
|
|
|
static void dwmac_rk_rx_queue_dma_chan_map(struct stmmac_priv *priv)
|
|
{
|
|
u32 rx_queues_count = min_t(u32, priv->plat->rx_queues_to_use, 1);
|
|
u32 queue;
|
|
u32 chan;
|
|
|
|
for (queue = 0; queue < rx_queues_count; queue++) {
|
|
chan = priv->plat->rx_queues_cfg[queue].chan;
|
|
stmmac_map_mtl_to_dma(priv, priv->hw, queue, chan);
|
|
}
|
|
}
|
|
|
|
static void dwmac_rk_mac_enable_rx_queues(struct stmmac_priv *priv)
|
|
{
|
|
u32 rx_queues_count = min_t(u32, priv->plat->rx_queues_to_use, 1);
|
|
int queue;
|
|
u8 mode;
|
|
|
|
for (queue = 0; queue < rx_queues_count; queue++) {
|
|
mode = priv->plat->rx_queues_cfg[queue].mode_to_use;
|
|
stmmac_rx_queue_enable(priv, priv->hw, mode, queue);
|
|
}
|
|
}
|
|
|
|
static void dwmac_rk_mtl_configuration(struct stmmac_priv *priv)
|
|
{
|
|
/* Map RX MTL to DMA channels */
|
|
dwmac_rk_rx_queue_dma_chan_map(priv);
|
|
|
|
/* Enable MAC RX Queues */
|
|
dwmac_rk_mac_enable_rx_queues(priv);
|
|
}
|
|
|
|
static void dwmac_rk_mmc_setup(struct stmmac_priv *priv)
|
|
{
|
|
unsigned int mode = MMC_CNTRL_RESET_ON_READ | MMC_CNTRL_COUNTER_RESET |
|
|
MMC_CNTRL_PRESET | MMC_CNTRL_FULL_HALF_PRESET;
|
|
|
|
stmmac_mmc_intr_all_mask(priv, priv->mmcaddr);
|
|
|
|
if (priv->dma_cap.rmon) {
|
|
stmmac_mmc_ctrl(priv, priv->mmcaddr, mode);
|
|
memset(&priv->mmc, 0, sizeof(struct stmmac_counters));
|
|
} else {
|
|
netdev_info(priv->dev, "No MAC Management Counters available\n");
|
|
}
|
|
}
|
|
|
|
static int dwmac_rk_init(struct net_device *dev,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
int ret;
|
|
u32 mode;
|
|
|
|
lb_priv->dma_buf_sz = 1536; /* mtu 1500 size */
|
|
|
|
if (priv->plat->has_gmac4)
|
|
lb_priv->buf_sz = priv->dma_cap.rx_fifo_size; /* rx fifo size */
|
|
else
|
|
lb_priv->buf_sz = 4096; /* rx fifo size */
|
|
|
|
ret = dwmac_rk_alloc_dma_desc_resources(priv, lb_priv);
|
|
if (ret < 0) {
|
|
pr_err("%s: DMA descriptors allocation failed\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = dwmac_rk_init_dma_desc_rings(dev, GFP_KERNEL, lb_priv);
|
|
if (ret < 0) {
|
|
pr_err("%s: DMA descriptors initialization failed\n", __func__);
|
|
goto init_error;
|
|
}
|
|
|
|
/* DMA initialization and SW reset */
|
|
ret = dwmac_rk_init_dma_engine(priv, lb_priv);
|
|
if (ret < 0) {
|
|
pr_err("%s: DMA engine initialization failed\n", __func__);
|
|
goto init_error;
|
|
}
|
|
|
|
/* Copy the MAC addr into the HW */
|
|
priv->hw->mac->set_umac_addr(priv->hw, dev->dev_addr, 0);
|
|
|
|
/* Initialize the MAC Core */
|
|
stmmac_core_init(priv, priv->hw, dev);
|
|
|
|
dwmac_rk_mtl_configuration(priv);
|
|
|
|
dwmac_rk_mmc_setup(priv);
|
|
|
|
ret = priv->hw->mac->rx_ipc(priv->hw);
|
|
if (!ret) {
|
|
pr_warn(" RX IPC Checksum Offload disabled\n");
|
|
priv->plat->rx_coe = STMMAC_RX_COE_NONE;
|
|
priv->hw->rx_csum = 0;
|
|
}
|
|
|
|
/* Set the HW DMA mode and the COE */
|
|
dwmac_rk_dma_operation_mode(priv, lb_priv);
|
|
|
|
if (priv->plat->has_gmac4) {
|
|
mode = readl(priv->ioaddr + DMA_CHAN_TX_CONTROL(0));
|
|
/* Disable OSP to get best performance */
|
|
mode &= ~DMA_CONTROL_OSP;
|
|
writel(mode, priv->ioaddr + DMA_CHAN_TX_CONTROL(0));
|
|
} else {
|
|
/* Disable OSF */
|
|
mode = readl(priv->ioaddr + DMA_CONTROL);
|
|
writel((mode & ~DMA_CONTROL_OSF), priv->ioaddr + DMA_CONTROL);
|
|
}
|
|
|
|
stmmac_enable_dma_irq(priv, priv->ioaddr, 0, 1, 1);
|
|
|
|
if (priv->hw->pcs)
|
|
stmmac_pcs_ctrl_ane(priv, priv->hw, 1, priv->hw->ps, 0);
|
|
|
|
return 0;
|
|
init_error:
|
|
dwmac_rk_free_dma_desc_resources(priv, lb_priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void dwmac_rk_release(struct net_device *dev,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
stmmac_disable_dma_irq(priv, priv->ioaddr, 0, 0, 0);
|
|
|
|
/* Release and free the Rx/Tx resources */
|
|
dwmac_rk_free_dma_desc_resources(priv, lb_priv);
|
|
}
|
|
|
|
static int dwmac_rk_get_max_delayline(struct stmmac_priv *priv)
|
|
{
|
|
if (of_device_is_compatible(priv->device->of_node,
|
|
"rockchip,rk3588-gmac"))
|
|
return RK3588_MAX_DELAYLINE;
|
|
else
|
|
return MAX_DELAYLINE;
|
|
}
|
|
|
|
static int dwmac_rk_phy_poll_reset(struct stmmac_priv *priv, int addr)
|
|
{
|
|
/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
|
|
unsigned int val, retries = 12;
|
|
int ret;
|
|
|
|
val = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
mdiobus_write(priv->mii, addr, MII_BMCR, val | BMCR_RESET);
|
|
|
|
do {
|
|
msleep(50);
|
|
ret = mdiobus_read(priv->mii, addr, MII_BMCR);
|
|
if (ret < 0)
|
|
return ret;
|
|
} while (ret & BMCR_RESET && --retries);
|
|
if (ret & BMCR_RESET)
|
|
return -ETIMEDOUT;
|
|
|
|
msleep(1);
|
|
return 0;
|
|
}
|
|
|
|
static int dwmac_rk_loopback_run(struct stmmac_priv *priv,
|
|
struct dwmac_rk_lb_priv *lb_priv)
|
|
{
|
|
struct net_device *ndev = priv->dev;
|
|
int phy_iface = dwmac_rk_get_phy_interface(priv);
|
|
int ndev_up, phy_addr;
|
|
int ret = -EINVAL;
|
|
|
|
if (!ndev || !priv->mii)
|
|
return -EINVAL;
|
|
|
|
phy_addr = priv->dev->phydev->mdio.addr;
|
|
lb_priv->max_delay = dwmac_rk_get_max_delayline(priv);
|
|
|
|
rtnl_lock();
|
|
/* check the netdevice up or not */
|
|
ndev_up = ndev->flags & IFF_UP;
|
|
|
|
if (ndev_up) {
|
|
if (!netif_running(ndev) || !ndev->phydev) {
|
|
rtnl_unlock();
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check if the negotiation status */
|
|
if (ndev->phydev->state != PHY_NOLINK &&
|
|
ndev->phydev->state != PHY_RUNNING) {
|
|
rtnl_unlock();
|
|
pr_warn("Try again later, after negotiation done\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
ndev->netdev_ops->ndo_stop(ndev);
|
|
|
|
if (priv->plat->stmmac_rst)
|
|
reset_control_assert(priv->plat->stmmac_rst);
|
|
dwmac_rk_phy_poll_reset(priv, phy_addr);
|
|
if (priv->plat->stmmac_rst)
|
|
reset_control_deassert(priv->plat->stmmac_rst);
|
|
}
|
|
/* wait for phy and controller ready */
|
|
usleep_range(100000, 200000);
|
|
|
|
dwmac_rk_set_loopback(priv, lb_priv->type, lb_priv->speed,
|
|
true, phy_addr, true);
|
|
|
|
ret = dwmac_rk_init(ndev, lb_priv);
|
|
if (ret)
|
|
goto exit_init;
|
|
|
|
dwmac_rk_set_loopback(priv, lb_priv->type, lb_priv->speed,
|
|
true, phy_addr, false);
|
|
|
|
if (lb_priv->scan) {
|
|
/* scan only support for rgmii mode */
|
|
if (phy_iface != PHY_INTERFACE_MODE_RGMII &&
|
|
phy_iface != PHY_INTERFACE_MODE_RGMII_ID &&
|
|
phy_iface != PHY_INTERFACE_MODE_RGMII_RXID &&
|
|
phy_iface != PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
ret = dwmac_rk_loopback_delayline_scan(priv, lb_priv);
|
|
} else {
|
|
lb_priv->id++;
|
|
lb_priv->tx = 0;
|
|
lb_priv->rx = 0;
|
|
|
|
lb_priv->packet = &dwmac_rk_tcp_attr;
|
|
ret = __dwmac_rk_loopback_run(priv, lb_priv);
|
|
}
|
|
|
|
out:
|
|
dwmac_rk_release(ndev, lb_priv);
|
|
dwmac_rk_set_loopback(priv, lb_priv->type, lb_priv->speed,
|
|
false, phy_addr, false);
|
|
exit_init:
|
|
if (ndev_up)
|
|
ndev->netdev_ops->ndo_open(ndev);
|
|
|
|
rtnl_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t rgmii_delayline_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
int tx, rx;
|
|
|
|
dwmac_rk_get_rgmii_delayline(priv, &tx, &rx);
|
|
|
|
return sprintf(buf, "tx delayline: 0x%x, rx delayline: 0x%x\n",
|
|
tx, rx);
|
|
}
|
|
|
|
static ssize_t rgmii_delayline_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
int tx = 0, rx = 0;
|
|
char tmp[32];
|
|
size_t buf_size = min(count, (sizeof(tmp) - 1));
|
|
char *data;
|
|
|
|
memset(tmp, 0, sizeof(tmp));
|
|
strncpy(tmp, buf, buf_size);
|
|
|
|
data = tmp;
|
|
data = strstr(data, " ");
|
|
if (!data)
|
|
goto out;
|
|
*data = 0;
|
|
data++;
|
|
|
|
if (kstrtoint(tmp, 0, &tx) || tx > dwmac_rk_get_max_delayline(priv))
|
|
goto out;
|
|
|
|
if (kstrtoint(data, 0, &rx) || rx > dwmac_rk_get_max_delayline(priv))
|
|
goto out;
|
|
|
|
dwmac_rk_set_rgmii_delayline(priv, tx, rx);
|
|
pr_info("Set rgmii delayline tx: 0x%x, rx: 0x%x\n", tx, rx);
|
|
|
|
return count;
|
|
out:
|
|
pr_err("wrong delayline value input, range is <0x0, 0x7f>\n");
|
|
pr_err("usage: <tx_delayline> <rx_delayline>\n");
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_RW(rgmii_delayline);
|
|
|
|
static ssize_t mac_lb_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct dwmac_rk_lb_priv *lb_priv;
|
|
int ret, speed;
|
|
|
|
lb_priv = kzalloc(sizeof(*lb_priv), GFP_KERNEL);
|
|
if (!lb_priv)
|
|
return -ENOMEM;
|
|
|
|
ret = kstrtoint(buf, 0, &speed);
|
|
if (ret) {
|
|
kfree(lb_priv);
|
|
return count;
|
|
}
|
|
pr_info("MAC loopback speed set to %d\n", speed);
|
|
|
|
lb_priv->sysfs = 1;
|
|
lb_priv->type = LOOPBACK_TYPE_GMAC;
|
|
lb_priv->speed = speed;
|
|
lb_priv->scan = 0;
|
|
|
|
ret = dwmac_rk_loopback_run(priv, lb_priv);
|
|
kfree(lb_priv);
|
|
|
|
if (!ret)
|
|
pr_info("MAC loopback: PASS\n");
|
|
else
|
|
pr_info("MAC loopback: FAIL\n");
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(mac_lb);
|
|
|
|
static ssize_t phy_lb_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct dwmac_rk_lb_priv *lb_priv;
|
|
int ret, speed;
|
|
|
|
lb_priv = kzalloc(sizeof(*lb_priv), GFP_KERNEL);
|
|
if (!lb_priv)
|
|
return -ENOMEM;
|
|
|
|
ret = kstrtoint(buf, 0, &speed);
|
|
if (ret) {
|
|
kfree(lb_priv);
|
|
return count;
|
|
}
|
|
pr_info("PHY loopback speed set to %d\n", speed);
|
|
|
|
lb_priv->sysfs = 1;
|
|
lb_priv->type = LOOPBACK_TYPE_PHY;
|
|
lb_priv->speed = speed;
|
|
lb_priv->scan = 0;
|
|
|
|
ret = dwmac_rk_loopback_run(priv, lb_priv);
|
|
if (!ret)
|
|
pr_info("PHY loopback: PASS\n");
|
|
else
|
|
pr_info("PHY loopback: FAIL\n");
|
|
|
|
kfree(lb_priv);
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(phy_lb);
|
|
|
|
static ssize_t phy_lb_scan_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct stmmac_priv *priv = netdev_priv(ndev);
|
|
struct dwmac_rk_lb_priv *lb_priv;
|
|
int ret, speed;
|
|
|
|
lb_priv = kzalloc(sizeof(*lb_priv), GFP_KERNEL);
|
|
if (!lb_priv)
|
|
return -ENOMEM;
|
|
|
|
ret = kstrtoint(buf, 0, &speed);
|
|
if (ret) {
|
|
kfree(lb_priv);
|
|
return count;
|
|
}
|
|
pr_info("Delayline scan speed set to %d\n", speed);
|
|
|
|
lb_priv->sysfs = 1;
|
|
lb_priv->type = LOOPBACK_TYPE_PHY;
|
|
lb_priv->speed = speed;
|
|
lb_priv->scan = 1;
|
|
|
|
dwmac_rk_loopback_run(priv, lb_priv);
|
|
|
|
kfree(lb_priv);
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(phy_lb_scan);
|
|
|
|
int dwmac_rk_create_loopback_sysfs(struct device *device)
|
|
{
|
|
int ret;
|
|
|
|
ret = device_create_file(device, &dev_attr_rgmii_delayline);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = device_create_file(device, &dev_attr_mac_lb);
|
|
if (ret)
|
|
goto remove_rgmii_delayline;
|
|
|
|
ret = device_create_file(device, &dev_attr_phy_lb);
|
|
if (ret)
|
|
goto remove_mac_lb;
|
|
|
|
ret = device_create_file(device, &dev_attr_phy_lb_scan);
|
|
if (ret)
|
|
goto remove_phy_lb;
|
|
|
|
return 0;
|
|
|
|
remove_rgmii_delayline:
|
|
device_remove_file(device, &dev_attr_rgmii_delayline);
|
|
|
|
remove_mac_lb:
|
|
device_remove_file(device, &dev_attr_mac_lb);
|
|
|
|
remove_phy_lb:
|
|
device_remove_file(device, &dev_attr_phy_lb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int dwmac_rk_remove_loopback_sysfs(struct device *device)
|
|
{
|
|
device_remove_file(device, &dev_attr_rgmii_delayline);
|
|
device_remove_file(device, &dev_attr_mac_lb);
|
|
device_remove_file(device, &dev_attr_phy_lb);
|
|
device_remove_file(device, &dev_attr_phy_lb_scan);
|
|
|
|
return 0;
|
|
}
|