1998 lines
46 KiB
C
1998 lines
46 KiB
C
/*
|
|
* linux/drivers/net/ethernet/allwinner/sunxi_gmac.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/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/pinctrl/pinctrl.h>
|
|
#include <linux/crypto.h>
|
|
#include <crypto/algapi.h>
|
|
#include <crypto/hash.h>
|
|
#include <linux/err.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/io.h>
|
|
#include <linux/sunxi-sid.h>
|
|
#include <linux/sunxi-gpio.h>
|
|
#include "sunxi-gmac.h"
|
|
|
|
#define DMA_DESC_RX 256
|
|
#define DMA_DESC_TX 256
|
|
#define BUDGET (dma_desc_rx / 4)
|
|
#define TX_THRESH (dma_desc_tx / 4)
|
|
|
|
#define HASH_TABLE_SIZE 64
|
|
#define MAX_BUF_SZ (SZ_2K - 1)
|
|
|
|
#define POWER_CHAN_NUM 3
|
|
|
|
#undef PKT_DEBUG
|
|
#undef DESC_PRINT
|
|
|
|
#define circ_cnt(head, tail, size) (((head) > (tail)) ? \
|
|
((head) - (tail)) : \
|
|
((head) - (tail)) & ((size) - 1))
|
|
|
|
#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size))
|
|
|
|
#define circ_inc(n, s) (((n) + 1) % (s))
|
|
|
|
#define GETH_MAC_ADDRESS "00:00:00:00:00:00"
|
|
static char *mac_str = GETH_MAC_ADDRESS;
|
|
module_param(mac_str, charp, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)");
|
|
|
|
static int rxmode = 1;
|
|
module_param(rxmode, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(rxmode, "DMA threshold control value");
|
|
|
|
static int txmode = 1;
|
|
module_param(txmode, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(txmode, "DMA threshold control value");
|
|
|
|
static int pause = 0x400;
|
|
module_param(pause, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(pause, "Flow Control Pause Time");
|
|
|
|
#define TX_TIMEO 5000
|
|
static int watchdog = TX_TIMEO;
|
|
module_param(watchdog, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds");
|
|
|
|
static int dma_desc_rx = DMA_DESC_RX;
|
|
module_param(dma_desc_rx, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(watchdog, "The number of receive's descriptors");
|
|
|
|
static int dma_desc_tx = DMA_DESC_TX;
|
|
module_param(dma_desc_tx, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(watchdog, "The number of transmit's descriptors");
|
|
|
|
/* - 0: Flow Off
|
|
* - 1: Rx Flow
|
|
* - 2: Tx Flow
|
|
* - 3: Rx & Tx Flow
|
|
*/
|
|
static int flow_ctrl;
|
|
module_param(flow_ctrl, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]");
|
|
|
|
static unsigned long tx_delay;
|
|
module_param(tx_delay, ulong, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(tx_delay, "Adjust transmit clock delay, value: 0~7");
|
|
|
|
static unsigned long rx_delay;
|
|
module_param(rx_delay, ulong, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(rx_delay, "Adjust receive clock delay, value: 0~31");
|
|
|
|
/* whether using ephy_clk */
|
|
static int g_use_ephy_clk;
|
|
static int g_phy_addr;
|
|
|
|
struct geth_priv {
|
|
struct dma_desc *dma_tx;
|
|
struct sk_buff **tx_sk;
|
|
unsigned int tx_clean;
|
|
unsigned int tx_dirty;
|
|
dma_addr_t dma_tx_phy;
|
|
|
|
unsigned long buf_sz;
|
|
|
|
struct dma_desc *dma_rx;
|
|
struct sk_buff **rx_sk;
|
|
unsigned int rx_clean;
|
|
unsigned int rx_dirty;
|
|
dma_addr_t dma_rx_phy;
|
|
|
|
struct net_device *ndev;
|
|
struct device *dev;
|
|
struct napi_struct napi;
|
|
|
|
struct geth_extra_stats xstats;
|
|
|
|
struct mii_bus *mii;
|
|
int link;
|
|
int speed;
|
|
int duplex;
|
|
#define INT_PHY 0
|
|
#define EXT_PHY 1
|
|
int phy_ext;
|
|
int phy_interface;
|
|
|
|
void __iomem *base;
|
|
void __iomem *base_phy;
|
|
struct clk *geth_clk;
|
|
struct clk *ephy_clk;
|
|
struct pinctrl *pinctrl;
|
|
|
|
struct regulator *gmac_power[POWER_CHAN_NUM];
|
|
bool is_suspend;
|
|
int phyrst;
|
|
u8 rst_active_low;
|
|
/* definition spinlock */
|
|
spinlock_t lock;
|
|
spinlock_t tx_lock;
|
|
|
|
/* resume work */
|
|
struct work_struct eth_work;
|
|
};
|
|
|
|
static u64 geth_dma_mask = DMA_BIT_MASK(32);
|
|
|
|
void sunxi_udelay(int n)
|
|
{
|
|
udelay(n);
|
|
}
|
|
|
|
static int geth_stop(struct net_device *ndev);
|
|
static int geth_open(struct net_device *ndev);
|
|
static void geth_tx_complete(struct geth_priv *priv);
|
|
static void geth_rx_refill(struct net_device *ndev);
|
|
|
|
#ifdef CONFIG_GETH_ATTRS
|
|
static ssize_t adjust_bgs_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int value = 0;
|
|
u32 efuse_value;
|
|
struct net_device *ndev = to_net_dev(dev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
if (priv->phy_ext == INT_PHY) {
|
|
value = readl(priv->base_phy) >> 28;
|
|
if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
|
|
pr_err("get PHY efuse fail!\n");
|
|
else
|
|
#if defined(CONFIG_ARCH_SUN50IW2)
|
|
value = value - ((efuse_value >> 24) & 0x0F);
|
|
#else
|
|
pr_warn("miss config come from efuse!\n");
|
|
#endif
|
|
}
|
|
|
|
return sprintf(buf, "bgs: %d\n", value);
|
|
}
|
|
|
|
static ssize_t adjust_bgs_write(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int out = 0;
|
|
struct net_device *ndev = to_net_dev(dev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
u32 clk_value = readl(priv->base_phy);
|
|
u32 efuse_value;
|
|
|
|
out = simple_strtoul(buf, NULL, 10);
|
|
|
|
if (priv->phy_ext == INT_PHY) {
|
|
clk_value &= ~(0xF << 28);
|
|
if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
|
|
pr_err("get PHY efuse fail!\n");
|
|
else
|
|
#if defined(CONFIG_ARCH_SUN50IW2)
|
|
clk_value |= (((efuse_value >> 24) & 0x0F) + out) << 28;
|
|
#else
|
|
pr_warn("miss config come from efuse!\n");
|
|
#endif
|
|
}
|
|
|
|
writel(clk_value, priv->base_phy);
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct device_attribute adjust_reg[] = {
|
|
__ATTR(adjust_bgs, 0664, adjust_bgs_show, adjust_bgs_write),
|
|
};
|
|
|
|
static int geth_create_attrs(struct net_device *ndev)
|
|
{
|
|
int j, ret;
|
|
|
|
for (j = 0; j < ARRAY_SIZE(adjust_reg); j++) {
|
|
ret = device_create_file(&ndev->dev, &adjust_reg[j]);
|
|
if (ret)
|
|
goto sysfs_failed;
|
|
}
|
|
goto succeed;
|
|
|
|
sysfs_failed:
|
|
while (j--)
|
|
device_remove_file(&ndev->dev, &adjust_reg[j]);
|
|
succeed:
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
static void desc_print(struct dma_desc *desc, int size)
|
|
{
|
|
#ifdef DESC_PRINT
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
u32 *x = (u32 *)(desc + i);
|
|
|
|
pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n",
|
|
i, (unsigned long)(&desc[i]),
|
|
x[0], x[1], x[2], x[3]);
|
|
}
|
|
pr_info("\n");
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static ssize_t gphy_test_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
|
|
if (!dev) {
|
|
pr_err("Argment is invalid\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!ndev) {
|
|
pr_err("Net device is null\n");
|
|
return 0;
|
|
}
|
|
|
|
return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n"
|
|
"0 - Normal Mode\n"
|
|
"1 - Transmit Jitter Test\n"
|
|
"2 - Transmit Jitter Test(MASTER mode)\n"
|
|
"3 - Transmit Jitter Test(SLAVE mode)\n"
|
|
"4 - Transmit Distortion Test\n\n");
|
|
}
|
|
|
|
static ssize_t gphy_test_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
u16 value = 0;
|
|
int ret = 0;
|
|
u16 data = 0;
|
|
|
|
if (!dev) {
|
|
pr_err("Argument is invalid\n");
|
|
return count;
|
|
}
|
|
|
|
if (!ndev) {
|
|
pr_err("Net device is null\n");
|
|
return count;
|
|
}
|
|
|
|
data = sunxi_mdio_read(priv->base, g_phy_addr, MII_CTRL1000);
|
|
|
|
ret = kstrtou16(buf, 0, &value);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (value >= 0 && value <= 4) {
|
|
data &= ~(0x7 << 13);
|
|
data |= value << 13;
|
|
sunxi_mdio_write(priv->base, g_phy_addr, MII_CTRL1000, data);
|
|
pr_info("Set MII_CTRL1000(0x09) Reg: 0x%x\n", data);
|
|
} else {
|
|
pr_info("unknown value (%d)\n", value);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(gphy_test, 0664, gphy_test_show, gphy_test_store);
|
|
|
|
static int geth_power_on(struct geth_priv *priv)
|
|
{
|
|
int value;
|
|
int i;
|
|
|
|
value = readl(priv->base_phy);
|
|
if (priv->phy_ext == INT_PHY) {
|
|
value |= (1 << 15);
|
|
value &= ~(1 << 16);
|
|
value |= (3 << 17);
|
|
} else {
|
|
value &= ~(1 << 15);
|
|
|
|
for (i = 0; i < POWER_CHAN_NUM; i++) {
|
|
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
|
|
continue;
|
|
if (regulator_enable(priv->gmac_power[i]) != 0) {
|
|
pr_err("gmac-power%d enable error\n", i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
writel(value, priv->base_phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void geth_power_off(struct geth_priv *priv)
|
|
{
|
|
int value;
|
|
int i;
|
|
|
|
if (priv->phy_ext == INT_PHY) {
|
|
value = readl(priv->base_phy);
|
|
value |= (1 << 16);
|
|
writel(value, priv->base_phy);
|
|
} else {
|
|
for (i = 0; i < POWER_CHAN_NUM; i++) {
|
|
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
|
|
continue;
|
|
regulator_disable(priv->gmac_power[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* PHY interface operations */
|
|
static int geth_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
|
|
{
|
|
struct net_device *ndev = bus->priv;
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
return (int)sunxi_mdio_read(priv->base, phyaddr, phyreg);
|
|
}
|
|
|
|
static int geth_mdio_write(struct mii_bus *bus, int phyaddr,
|
|
int phyreg, u16 data)
|
|
{
|
|
struct net_device *ndev = bus->priv;
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
sunxi_mdio_write(priv->base, phyaddr, phyreg, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int geth_mdio_reset(struct mii_bus *bus)
|
|
{
|
|
struct net_device *ndev = bus->priv;
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
return sunxi_mdio_reset(priv->base);
|
|
}
|
|
|
|
static void geth_adjust_link(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct phy_device *phydev = ndev->phydev;
|
|
unsigned long flags;
|
|
int new_state = 0;
|
|
|
|
if (!phydev)
|
|
return;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (phydev->link) {
|
|
/* Now we make sure that we can be in full duplex mode.
|
|
* If not, we operate in half-duplex mode.
|
|
*/
|
|
if (phydev->duplex != priv->duplex) {
|
|
new_state = 1;
|
|
priv->duplex = phydev->duplex;
|
|
}
|
|
/* Flow Control operation */
|
|
if (phydev->pause)
|
|
sunxi_flow_ctrl(priv->base, phydev->duplex,
|
|
flow_ctrl, pause);
|
|
|
|
if (phydev->speed != priv->speed) {
|
|
new_state = 1;
|
|
priv->speed = phydev->speed;
|
|
}
|
|
|
|
if (priv->link == 0) {
|
|
new_state = 1;
|
|
priv->link = phydev->link;
|
|
}
|
|
|
|
if (new_state)
|
|
sunxi_set_link_mode(priv->base, priv->duplex, priv->speed);
|
|
|
|
#ifdef LOOPBACK_DEBUG
|
|
phydev->state = PHY_FORCING;
|
|
#endif
|
|
|
|
} else if (priv->link != phydev->link) {
|
|
new_state = 1;
|
|
priv->link = 0;
|
|
priv->speed = 0;
|
|
priv->duplex = -1;
|
|
}
|
|
|
|
if (new_state)
|
|
phy_print_status(phydev);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
|
|
static int geth_phy_init(struct net_device *ndev)
|
|
{
|
|
int value;
|
|
struct mii_bus *new_bus;
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct phy_device *phydev = ndev->phydev;
|
|
|
|
if (priv->is_suspend && phydev)
|
|
goto resume;
|
|
|
|
/* Fixup the phy interface type */
|
|
if (priv->phy_ext == INT_PHY) {
|
|
priv->phy_interface = PHY_INTERFACE_MODE_MII;
|
|
} else {
|
|
/* If config gpio to reset the phy device, we should reset it */
|
|
if (gpio_is_valid(priv->phyrst)) {
|
|
gpio_direction_output(priv->phyrst,
|
|
priv->rst_active_low);
|
|
msleep(50);
|
|
gpio_direction_output(priv->phyrst,
|
|
!priv->rst_active_low);
|
|
msleep(50);
|
|
}
|
|
}
|
|
|
|
new_bus = mdiobus_alloc();
|
|
if (!new_bus) {
|
|
netdev_err(ndev, "Failed to alloc new mdio bus\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
new_bus->name = dev_name(priv->dev);
|
|
new_bus->read = &geth_mdio_read;
|
|
new_bus->write = &geth_mdio_write;
|
|
new_bus->reset = &geth_mdio_reset;
|
|
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, 0);
|
|
|
|
new_bus->parent = priv->dev;
|
|
new_bus->priv = ndev;
|
|
|
|
if (mdiobus_register(new_bus)) {
|
|
pr_err("%s: Cannot register as MDIO bus\n", new_bus->name);
|
|
goto reg_fail;
|
|
}
|
|
|
|
priv->mii = new_bus;
|
|
|
|
{
|
|
int addr;
|
|
|
|
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
|
|
struct phy_device *phydev_tmp = mdiobus_get_phy(new_bus, addr);
|
|
|
|
if (phydev_tmp && (phydev_tmp->phy_id != 0x00)) {
|
|
phydev = phydev_tmp;
|
|
g_phy_addr = addr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!phydev) {
|
|
netdev_err(ndev, "No PHY found!\n");
|
|
goto err;
|
|
}
|
|
|
|
phy_write(phydev, MII_BMCR, BMCR_RESET);
|
|
while (BMCR_RESET & phy_read(phydev, MII_BMCR))
|
|
msleep(30);
|
|
|
|
value = phy_read(phydev, MII_BMCR);
|
|
phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN));
|
|
|
|
phydev->irq = PHY_POLL;
|
|
|
|
value = phy_connect_direct(ndev, phydev, &geth_adjust_link, priv->phy_interface);
|
|
if (value) {
|
|
netdev_err(ndev, "Could not attach to PHY\n");
|
|
goto err;
|
|
} else {
|
|
netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n",
|
|
ndev->name, phydev->interface, phydev->phy_id,
|
|
phydev->mdio.addr, "poll", dev_name(&phydev->mdio.dev));
|
|
}
|
|
|
|
phydev->supported &= PHY_GBIT_FEATURES;
|
|
phydev->advertising = phydev->supported;
|
|
|
|
resume:
|
|
if (priv->phy_ext == INT_PHY) {
|
|
/* EPHY Initial */
|
|
phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
|
|
phy_write(phydev, 0x12, 0x4824); /* Disable APS */
|
|
phy_write(phydev, 0x1f, 0x0200); /* switchto page 2 */
|
|
phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
|
|
phy_write(phydev, 0x1f, 0x0600); /* switchto page 6 */
|
|
phy_write(phydev, 0x14, 0x708F); /* PHYAFE TX optimization */
|
|
phy_write(phydev, 0x19, 0x0000);
|
|
phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */
|
|
phy_write(phydev, 0x15, 0x1530);
|
|
phy_write(phydev, 0x1f, 0x0800); /* switch to page 8 */
|
|
phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
|
|
phy_write(phydev, 0x1f, 0x0100); /* switchto page 1 */
|
|
/* reg 0x17 bit3,set 0 to disable iEEE */
|
|
phy_write(phydev, 0x17, phy_read(phydev, 0x17) & (~(1<<3)));
|
|
phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
|
|
}
|
|
if (priv->is_suspend)
|
|
phy_init_hw(phydev);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
mdiobus_unregister(new_bus);
|
|
reg_fail:
|
|
mdiobus_free(new_bus);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int geth_phy_release(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct phy_device *phydev = ndev->phydev;
|
|
int value = 0;
|
|
|
|
/* Stop and disconnect the PHY */
|
|
if (phydev)
|
|
phy_stop(phydev);
|
|
|
|
priv->link = PHY_DOWN;
|
|
priv->speed = 0;
|
|
priv->duplex = -1;
|
|
|
|
if (priv->is_suspend)
|
|
return 0;
|
|
|
|
if (phydev) {
|
|
value = phy_read(phydev, MII_BMCR);
|
|
phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));
|
|
phy_disconnect(phydev);
|
|
ndev->phydev = NULL;
|
|
}
|
|
|
|
if (priv->mii) {
|
|
mdiobus_unregister(priv->mii);
|
|
priv->mii->priv = NULL;
|
|
mdiobus_free(priv->mii);
|
|
priv->mii = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void geth_rx_refill(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct dma_desc *desc;
|
|
struct sk_buff *sk = NULL;
|
|
dma_addr_t paddr;
|
|
|
|
while (circ_space(priv->rx_clean, priv->rx_dirty, dma_desc_rx) > 0) {
|
|
int entry = priv->rx_clean;
|
|
|
|
/* Find the dirty's desc and clean it */
|
|
desc = priv->dma_rx + entry;
|
|
|
|
if (priv->rx_sk[entry] == NULL) {
|
|
sk = netdev_alloc_skb_ip_align(ndev, priv->buf_sz);
|
|
|
|
if (unlikely(sk == NULL))
|
|
break;
|
|
|
|
priv->rx_sk[entry] = sk;
|
|
paddr = dma_map_single(priv->dev, sk->data,
|
|
priv->buf_sz, DMA_FROM_DEVICE);
|
|
desc_buf_set(desc, paddr, priv->buf_sz);
|
|
}
|
|
|
|
/* sync memery */
|
|
wmb();
|
|
desc_set_own(desc);
|
|
priv->rx_clean = circ_inc(priv->rx_clean, dma_desc_rx);
|
|
}
|
|
}
|
|
|
|
/* geth_dma_desc_init - initialize the RX/TX descriptor list
|
|
* @ndev: net device structure
|
|
* Description: initialize the list for dma.
|
|
*/
|
|
static int geth_dma_desc_init(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
unsigned int buf_sz;
|
|
|
|
priv->rx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_rx,
|
|
GFP_KERNEL);
|
|
if (!priv->rx_sk)
|
|
return -ENOMEM;
|
|
|
|
priv->tx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_tx,
|
|
GFP_KERNEL);
|
|
if (!priv->tx_sk)
|
|
goto tx_sk_err;
|
|
|
|
/* Set the size of buffer depend on the MTU & max buf size */
|
|
buf_sz = MAX_BUF_SZ;
|
|
|
|
priv->dma_tx = dma_alloc_coherent(priv->dev,
|
|
dma_desc_tx *
|
|
sizeof(struct dma_desc),
|
|
&priv->dma_tx_phy,
|
|
GFP_KERNEL);
|
|
if (!priv->dma_tx)
|
|
goto dma_tx_err;
|
|
|
|
priv->dma_rx = dma_alloc_coherent(priv->dev,
|
|
dma_desc_rx *
|
|
sizeof(struct dma_desc),
|
|
&priv->dma_rx_phy,
|
|
GFP_KERNEL);
|
|
if (!priv->dma_rx)
|
|
goto dma_rx_err;
|
|
|
|
priv->buf_sz = buf_sz;
|
|
|
|
return 0;
|
|
|
|
dma_rx_err:
|
|
dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
|
|
priv->dma_tx, priv->dma_tx_phy);
|
|
dma_tx_err:
|
|
kfree(priv->tx_sk);
|
|
tx_sk_err:
|
|
kfree(priv->rx_sk);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void geth_free_rx_sk(struct geth_priv *priv)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < dma_desc_rx; i++) {
|
|
if (priv->rx_sk[i] != NULL) {
|
|
struct dma_desc *desc = priv->dma_rx + i;
|
|
|
|
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
|
|
desc_buf_get_len(desc),
|
|
DMA_FROM_DEVICE);
|
|
dev_kfree_skb_any(priv->rx_sk[i]);
|
|
priv->rx_sk[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void geth_free_tx_sk(struct geth_priv *priv)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < dma_desc_tx; i++) {
|
|
if (priv->tx_sk[i] != NULL) {
|
|
struct dma_desc *desc = priv->dma_tx + i;
|
|
|
|
if (desc_buf_get_addr(desc))
|
|
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
|
|
desc_buf_get_len(desc),
|
|
DMA_TO_DEVICE);
|
|
dev_kfree_skb_any(priv->tx_sk[i]);
|
|
priv->tx_sk[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void geth_free_dma_desc(struct geth_priv *priv)
|
|
{
|
|
/* Free the region of consistent memory previously allocated for the DMA */
|
|
dma_free_coherent(priv->dev, dma_desc_tx * sizeof(struct dma_desc),
|
|
priv->dma_tx, priv->dma_tx_phy);
|
|
dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
|
|
priv->dma_rx, priv->dma_rx_phy);
|
|
|
|
kfree(priv->rx_sk);
|
|
kfree(priv->tx_sk);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int geth_select_gpio_state(struct pinctrl *pctrl, char *name)
|
|
{
|
|
int ret = 0;
|
|
struct pinctrl_state *pctrl_state = NULL;
|
|
|
|
pctrl_state = pinctrl_lookup_state(pctrl, name);
|
|
if (IS_ERR(pctrl_state)) {
|
|
pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n",
|
|
name, pctrl_state);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = pinctrl_select_state(pctrl, pctrl_state);
|
|
if (ret < 0)
|
|
pr_err("gmac pinctrl_select_state(%s) failed! return %d\n",
|
|
name, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int geth_suspend(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
cancel_work_sync(&priv->eth_work);
|
|
|
|
if (!ndev || !netif_running(ndev))
|
|
return 0;
|
|
|
|
priv->is_suspend = true;
|
|
|
|
spin_lock(&priv->lock);
|
|
netif_device_detach(ndev);
|
|
spin_unlock(&priv->lock);
|
|
|
|
geth_stop(ndev);
|
|
|
|
if (priv->phy_ext == EXT_PHY)
|
|
geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void geth_resume_work(struct work_struct *work)
|
|
{
|
|
struct geth_priv *priv = container_of(work, struct geth_priv, eth_work);
|
|
struct net_device *ndev = priv->ndev;
|
|
int ret = 0;
|
|
|
|
if (!netif_running(ndev))
|
|
return;
|
|
|
|
if (priv->phy_ext == EXT_PHY)
|
|
geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT);
|
|
|
|
spin_lock(&priv->lock);
|
|
netif_device_attach(ndev);
|
|
spin_unlock(&priv->lock);
|
|
|
|
#if defined(CONFIG_SUNXI_EPHY)
|
|
if (!ephy_is_enable()) {
|
|
pr_info("[geth_resume] ephy is not enable, waiting...\n");
|
|
msleep(2000);
|
|
if (!ephy_is_enable()) {
|
|
netdev_err(ndev, "Wait for ephy resume timeout.\n");
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ret = geth_open(ndev);
|
|
if (!ret)
|
|
priv->is_suspend = false;
|
|
}
|
|
|
|
static void geth_resume(struct device *dev)
|
|
{
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
schedule_work(&priv->eth_work);
|
|
}
|
|
|
|
static int geth_freeze(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int geth_restore(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops geth_pm_ops = {
|
|
.complete = geth_resume,
|
|
.prepare = geth_suspend,
|
|
.suspend = NULL,
|
|
.resume = NULL,
|
|
.freeze = geth_freeze,
|
|
.restore = geth_restore,
|
|
};
|
|
#else
|
|
static const struct dev_pm_ops geth_pm_ops;
|
|
#endif /* CONFIG_PM */
|
|
|
|
#define sunxi_get_soc_chipid(x) {}
|
|
static void geth_chip_hwaddr(u8 *addr)
|
|
{
|
|
#define MD5_SIZE 16
|
|
#define CHIP_SIZE 16
|
|
|
|
struct crypto_ahash *tfm;
|
|
struct ahash_request *req;
|
|
struct scatterlist sg;
|
|
u8 result[MD5_SIZE];
|
|
u8 chipid[CHIP_SIZE];
|
|
int i = 0;
|
|
int ret = -1;
|
|
|
|
memset(chipid, 0, sizeof(chipid));
|
|
memset(result, 0, sizeof(result));
|
|
|
|
sunxi_get_soc_chipid((u8 *)chipid);
|
|
|
|
tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC);
|
|
if (IS_ERR(tfm)) {
|
|
pr_err("Failed to alloc md5\n");
|
|
return;
|
|
}
|
|
|
|
req = ahash_request_alloc(tfm, GFP_KERNEL);
|
|
if (!req)
|
|
goto out;
|
|
|
|
ahash_request_set_callback(req, 0, NULL, NULL);
|
|
|
|
ret = crypto_ahash_init(req);
|
|
if (ret) {
|
|
pr_err("crypto_ahash_init() failed\n");
|
|
goto out;
|
|
}
|
|
|
|
sg_init_one(&sg, chipid, sizeof(chipid) - 1);
|
|
ahash_request_set_crypt(req, &sg, result, sizeof(chipid) - 1);
|
|
ret = crypto_ahash_update(req);
|
|
if (ret) {
|
|
pr_err("crypto_ahash_update() failed for id\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = crypto_ahash_final(req);
|
|
if (ret) {
|
|
pr_err("crypto_ahash_final() failed for result\n");
|
|
goto out;
|
|
}
|
|
|
|
ahash_request_free(req);
|
|
|
|
/* Choose md5 result's [0][2][4][6][8][10] byte as mac address */
|
|
for (i = 0; i < ETH_ALEN; i++)
|
|
addr[i] = result[2 * i];
|
|
addr[0] &= 0xfe; /* clear multicast bit */
|
|
addr[0] |= 0x02; /* set local assignment bit (IEEE802) */
|
|
|
|
out:
|
|
crypto_free_ahash(tfm);
|
|
}
|
|
|
|
static void geth_check_addr(struct net_device *ndev, unsigned char *mac)
|
|
{
|
|
int i;
|
|
char *p = mac;
|
|
|
|
if (!is_valid_ether_addr(ndev->dev_addr)) {
|
|
for (i = 0; i < ETH_ALEN; i++, p++)
|
|
ndev->dev_addr[i] = simple_strtoul(p, &p, 16);
|
|
|
|
if (!is_valid_ether_addr(ndev->dev_addr))
|
|
geth_chip_hwaddr(ndev->dev_addr);
|
|
|
|
if (!is_valid_ether_addr(ndev->dev_addr)) {
|
|
random_ether_addr(ndev->dev_addr);
|
|
pr_warn("%s: Use random mac address\n", ndev->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void geth_clk_enable(struct geth_priv *priv)
|
|
{
|
|
int phy_interface = 0;
|
|
u32 clk_value;
|
|
u32 efuse_value;
|
|
|
|
if (clk_prepare_enable(priv->geth_clk))
|
|
pr_err("try to enable geth_clk failed!\n");
|
|
|
|
if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk)
|
|
&& !IS_ERR_OR_NULL(priv->ephy_clk)) {
|
|
if (clk_prepare_enable(priv->ephy_clk))
|
|
pr_err("try to enable ephy_clk failed!\n");
|
|
}
|
|
|
|
phy_interface = priv->phy_interface;
|
|
|
|
clk_value = readl(priv->base_phy);
|
|
if (phy_interface == PHY_INTERFACE_MODE_RGMII)
|
|
clk_value |= 0x00000004;
|
|
else
|
|
clk_value &= (~0x00000004);
|
|
|
|
clk_value &= (~0x00002003);
|
|
if (phy_interface == PHY_INTERFACE_MODE_RGMII
|
|
|| phy_interface == PHY_INTERFACE_MODE_GMII)
|
|
clk_value |= 0x00000002;
|
|
else if (phy_interface == PHY_INTERFACE_MODE_RMII)
|
|
clk_value |= 0x00002001;
|
|
|
|
if (priv->phy_ext == INT_PHY) {
|
|
if (0 != sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value))
|
|
pr_err("get PHY efuse fail!\n");
|
|
else
|
|
#if defined(CONFIG_ARCH_SUN50IW2)
|
|
clk_value |= (((efuse_value >> 24) & 0x0F) + 3) << 28;
|
|
#else
|
|
pr_warn("miss config come from efuse!\n");
|
|
#endif
|
|
}
|
|
|
|
/* Adjust Tx/Rx clock delay */
|
|
clk_value &= ~(0x07 << 10);
|
|
clk_value |= ((tx_delay & 0x07) << 10);
|
|
clk_value &= ~(0x1F << 5);
|
|
clk_value |= ((rx_delay & 0x1F) << 5);
|
|
|
|
writel(clk_value, priv->base_phy);
|
|
}
|
|
|
|
static void geth_clk_disable(struct geth_priv *priv)
|
|
{
|
|
if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk)
|
|
&& !IS_ERR_OR_NULL(priv->ephy_clk))
|
|
clk_disable_unprepare(priv->ephy_clk);
|
|
|
|
clk_disable_unprepare(priv->geth_clk);
|
|
}
|
|
|
|
static void geth_tx_err(struct geth_priv *priv)
|
|
{
|
|
netif_stop_queue(priv->ndev);
|
|
|
|
sunxi_stop_tx(priv->base);
|
|
|
|
geth_free_tx_sk(priv);
|
|
memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
|
|
desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
|
|
priv->tx_dirty = 0;
|
|
priv->tx_clean = 0;
|
|
sunxi_start_tx(priv->base, priv->dma_tx_phy);
|
|
|
|
priv->ndev->stats.tx_errors++;
|
|
netif_wake_queue(priv->ndev);
|
|
}
|
|
|
|
static inline void geth_schedule(struct geth_priv *priv)
|
|
{
|
|
if (likely(napi_schedule_prep(&priv->napi))) {
|
|
sunxi_int_disable(priv->base);
|
|
__napi_schedule(&priv->napi);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t geth_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct net_device *ndev = (struct net_device *)dev_id;
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
int status;
|
|
|
|
if (unlikely(!ndev)) {
|
|
pr_err("%s: invalid ndev pointer\n", __func__);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
status = sunxi_int_status(priv->base, (void *)(&priv->xstats));
|
|
|
|
if (likely(status == handle_tx_rx))
|
|
geth_schedule(priv);
|
|
else if (unlikely(status == tx_hard_error_bump_tc))
|
|
netdev_info(ndev, "Do nothing for bump tc\n");
|
|
else if (unlikely(status == tx_hard_error))
|
|
geth_tx_err(priv);
|
|
else
|
|
netdev_info(ndev, "Do nothing.....\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int geth_open(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
int ret = 0;
|
|
|
|
ret = geth_power_on(priv);
|
|
if (ret) {
|
|
netdev_err(ndev, "Power on is failed\n");
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
geth_clk_enable(priv);
|
|
|
|
netif_carrier_off(ndev);
|
|
|
|
ret = geth_phy_init(ndev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000);
|
|
if (ret) {
|
|
netdev_err(ndev, "Initialize hardware error\n");
|
|
goto desc_err;
|
|
}
|
|
|
|
sunxi_mac_init(priv->base, txmode, rxmode);
|
|
sunxi_set_umac(priv->base, ndev->dev_addr, 0);
|
|
|
|
if (!priv->is_suspend) {
|
|
ret = geth_dma_desc_init(ndev);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
goto desc_err;
|
|
}
|
|
}
|
|
|
|
memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
|
|
memset(priv->dma_rx, 0, dma_desc_rx * sizeof(struct dma_desc));
|
|
|
|
desc_init_chain(priv->dma_rx, (unsigned long)priv->dma_rx_phy, dma_desc_rx);
|
|
desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
|
|
|
|
priv->rx_clean = 0;
|
|
priv->rx_dirty = 0;
|
|
priv->tx_clean = 0;
|
|
priv->tx_dirty = 0;
|
|
geth_rx_refill(ndev);
|
|
|
|
/* Extra statistics */
|
|
memset(&priv->xstats, 0, sizeof(struct geth_extra_stats));
|
|
|
|
if (ndev->phydev)
|
|
phy_start(ndev->phydev);
|
|
|
|
sunxi_start_rx(priv->base, (unsigned long)((struct dma_desc *)
|
|
priv->dma_rx_phy + priv->rx_dirty));
|
|
sunxi_start_tx(priv->base, (unsigned long)((struct dma_desc *)
|
|
priv->dma_tx_phy + priv->tx_clean));
|
|
|
|
napi_enable(&priv->napi);
|
|
netif_start_queue(ndev);
|
|
|
|
/* Enable the Rx/Tx */
|
|
sunxi_mac_enable(priv->base);
|
|
|
|
return 0;
|
|
|
|
desc_err:
|
|
geth_phy_release(ndev);
|
|
err:
|
|
geth_clk_disable(priv);
|
|
if (priv->is_suspend)
|
|
napi_enable(&priv->napi);
|
|
|
|
geth_power_off(priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int geth_stop(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
netif_stop_queue(ndev);
|
|
napi_disable(&priv->napi);
|
|
|
|
netif_carrier_off(ndev);
|
|
|
|
/* Release PHY resources */
|
|
geth_phy_release(ndev);
|
|
|
|
/* Disable Rx/Tx */
|
|
sunxi_mac_disable(priv->base);
|
|
|
|
geth_clk_disable(priv);
|
|
geth_power_off(priv);
|
|
|
|
netif_tx_lock_bh(ndev);
|
|
/* Release the DMA TX/RX socket buffers */
|
|
geth_free_rx_sk(priv);
|
|
geth_free_tx_sk(priv);
|
|
netif_tx_unlock_bh(ndev);
|
|
|
|
/* Ensure that hareware have been stopped */
|
|
if (!priv->is_suspend)
|
|
geth_free_dma_desc(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void geth_tx_complete(struct geth_priv *priv)
|
|
{
|
|
unsigned int entry = 0;
|
|
struct sk_buff *skb = NULL;
|
|
struct dma_desc *desc = NULL;
|
|
int tx_stat;
|
|
|
|
spin_lock(&priv->tx_lock);
|
|
while (circ_cnt(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > 0) {
|
|
entry = priv->tx_clean;
|
|
desc = priv->dma_tx + entry;
|
|
|
|
/* Check if the descriptor is owned by the DMA. */
|
|
if (desc_get_own(desc))
|
|
break;
|
|
|
|
/* Verify tx error by looking at the last segment */
|
|
if (desc_get_tx_ls(desc)) {
|
|
tx_stat = desc_get_tx_status(desc, (void *)(&priv->xstats));
|
|
|
|
if (likely(!tx_stat))
|
|
priv->ndev->stats.tx_packets++;
|
|
else
|
|
priv->ndev->stats.tx_errors++;
|
|
}
|
|
|
|
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
|
|
desc_buf_get_len(desc), DMA_TO_DEVICE);
|
|
|
|
skb = priv->tx_sk[entry];
|
|
priv->tx_sk[entry] = NULL;
|
|
desc_init(desc);
|
|
|
|
/* Find next dirty desc */
|
|
priv->tx_clean = circ_inc(entry, dma_desc_tx);
|
|
|
|
if (unlikely(skb == NULL))
|
|
continue;
|
|
|
|
dev_kfree_skb(skb);
|
|
}
|
|
|
|
if (unlikely(netif_queue_stopped(priv->ndev)) &&
|
|
circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
|
|
TX_THRESH) {
|
|
netif_wake_queue(priv->ndev);
|
|
}
|
|
spin_unlock(&priv->tx_lock);
|
|
}
|
|
|
|
static netdev_tx_t geth_xmit(struct sk_buff *skb, struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
unsigned int entry;
|
|
struct dma_desc *desc, *first;
|
|
unsigned int len, tmp_len = 0;
|
|
int i, csum_insert;
|
|
int nfrags = skb_shinfo(skb)->nr_frags;
|
|
dma_addr_t paddr;
|
|
|
|
spin_lock(&priv->tx_lock);
|
|
if (unlikely(circ_space(priv->tx_dirty, priv->tx_clean,
|
|
dma_desc_tx) < (nfrags + 1))) {
|
|
if (!netif_queue_stopped(ndev)) {
|
|
netdev_err(ndev, "%s: BUG! Tx Ring full when queue awake\n", __func__);
|
|
netif_stop_queue(ndev);
|
|
}
|
|
spin_unlock(&priv->tx_lock);
|
|
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
|
|
csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL);
|
|
entry = priv->tx_dirty;
|
|
first = priv->dma_tx + entry;
|
|
desc = priv->dma_tx + entry;
|
|
|
|
len = skb_headlen(skb);
|
|
priv->tx_sk[entry] = skb;
|
|
|
|
#ifdef PKT_DEBUG
|
|
printk("======TX PKT DATA: ============\n");
|
|
/* dump the packet */
|
|
print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
|
|
16, 1, skb->data, 64, true);
|
|
#endif
|
|
|
|
/* Every desc max size is 2K */
|
|
while (len != 0) {
|
|
desc = priv->dma_tx + entry;
|
|
tmp_len = ((len > MAX_BUF_SZ) ? MAX_BUF_SZ : len);
|
|
|
|
paddr = dma_map_single(priv->dev, skb->data, tmp_len, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(priv->dev, paddr)) {
|
|
dev_kfree_skb(skb);
|
|
return -EIO;
|
|
}
|
|
desc_buf_set(desc, paddr, tmp_len);
|
|
/* Don't set the first's own bit, here */
|
|
if (first != desc) {
|
|
priv->tx_sk[entry] = NULL;
|
|
desc_set_own(desc);
|
|
}
|
|
|
|
entry = circ_inc(entry, dma_desc_tx);
|
|
len -= tmp_len;
|
|
}
|
|
|
|
for (i = 0; i < nfrags; i++) {
|
|
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
|
|
|
|
len = skb_frag_size(frag);
|
|
desc = priv->dma_tx + entry;
|
|
paddr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(priv->dev, paddr)) {
|
|
dev_kfree_skb(skb);
|
|
return -EIO;
|
|
}
|
|
|
|
desc_buf_set(desc, paddr, len);
|
|
desc_set_own(desc);
|
|
priv->tx_sk[entry] = NULL;
|
|
entry = circ_inc(entry, dma_desc_tx);
|
|
}
|
|
|
|
ndev->stats.tx_bytes += skb->len;
|
|
priv->tx_dirty = entry;
|
|
desc_tx_close(first, desc, csum_insert);
|
|
|
|
desc_set_own(first);
|
|
spin_unlock(&priv->tx_lock);
|
|
|
|
if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) <=
|
|
(MAX_SKB_FRAGS + 1)) {
|
|
netif_stop_queue(ndev);
|
|
if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
|
|
TX_THRESH)
|
|
netif_wake_queue(ndev);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printk("=======TX Descriptor DMA: 0x%08llx\n", priv->dma_tx_phy);
|
|
printk("Tx pointor: dirty: %d, clean: %d\n", priv->tx_dirty, priv->tx_clean);
|
|
desc_print(priv->dma_tx, dma_desc_tx);
|
|
#endif
|
|
sunxi_tx_poll(priv->base);
|
|
geth_tx_complete(priv);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static int geth_rx(struct geth_priv *priv, int limit)
|
|
{
|
|
unsigned int rxcount = 0;
|
|
unsigned int entry;
|
|
struct dma_desc *desc;
|
|
struct sk_buff *skb;
|
|
int status;
|
|
int frame_len;
|
|
|
|
while (rxcount < limit) {
|
|
entry = priv->rx_dirty;
|
|
desc = priv->dma_rx + entry;
|
|
|
|
if (desc_get_own(desc))
|
|
break;
|
|
|
|
rxcount++;
|
|
priv->rx_dirty = circ_inc(priv->rx_dirty, dma_desc_rx);
|
|
|
|
/* Get length & status from hardware */
|
|
frame_len = desc_rx_frame_len(desc);
|
|
status = desc_get_rx_status(desc, (void *)(&priv->xstats));
|
|
|
|
netdev_dbg(priv->ndev, "Rx frame size %d, status: %d\n",
|
|
frame_len, status);
|
|
|
|
skb = priv->rx_sk[entry];
|
|
if (unlikely(!skb)) {
|
|
netdev_err(priv->ndev, "Skb is null\n");
|
|
priv->ndev->stats.rx_dropped++;
|
|
break;
|
|
}
|
|
|
|
#ifdef PKT_DEBUG
|
|
printk("======RX PKT DATA: ============\n");
|
|
/* dump the packet */
|
|
print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
|
|
16, 1, skb->data, 64, true);
|
|
#endif
|
|
|
|
if (status == discard_frame) {
|
|
netdev_dbg(priv->ndev, "Get error pkt\n");
|
|
priv->ndev->stats.rx_errors++;
|
|
continue;
|
|
}
|
|
|
|
if (unlikely(status != llc_snap))
|
|
frame_len -= ETH_FCS_LEN;
|
|
|
|
priv->rx_sk[entry] = NULL;
|
|
|
|
skb_put(skb, frame_len);
|
|
dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
|
|
desc_buf_get_len(desc), DMA_FROM_DEVICE);
|
|
|
|
skb->protocol = eth_type_trans(skb, priv->ndev);
|
|
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
napi_gro_receive(&priv->napi, skb);
|
|
|
|
priv->ndev->stats.rx_packets++;
|
|
priv->ndev->stats.rx_bytes += frame_len;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (rxcount > 0) {
|
|
printk("======RX Descriptor DMA: 0x%08llx=\n", priv->dma_rx_phy);
|
|
printk("RX pointor: dirty: %d, clean: %d\n", priv->rx_dirty, priv->rx_clean);
|
|
desc_print(priv->dma_rx, dma_desc_rx);
|
|
}
|
|
#endif
|
|
geth_rx_refill(priv->ndev);
|
|
|
|
return rxcount;
|
|
}
|
|
|
|
static int geth_poll(struct napi_struct *napi, int budget)
|
|
{
|
|
struct geth_priv *priv = container_of(napi, struct geth_priv, napi);
|
|
int work_done = 0;
|
|
|
|
geth_tx_complete(priv);
|
|
work_done = geth_rx(priv, budget);
|
|
|
|
if (work_done < budget) {
|
|
napi_complete(napi);
|
|
sunxi_int_enable(priv->base);
|
|
}
|
|
|
|
return work_done;
|
|
}
|
|
|
|
static int geth_change_mtu(struct net_device *ndev, int new_mtu)
|
|
{
|
|
int max_mtu;
|
|
|
|
if (netif_running(ndev)) {
|
|
pr_err("%s: must be stopped to change its MTU\n", ndev->name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);
|
|
|
|
if ((new_mtu < 46) || (new_mtu > max_mtu)) {
|
|
pr_err("%s: invalid MTU, max MTU is: %d\n", ndev->name, max_mtu);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ndev->mtu = new_mtu;
|
|
netdev_update_features(ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static netdev_features_t geth_fix_features(struct net_device *ndev,
|
|
netdev_features_t features)
|
|
{
|
|
return features;
|
|
}
|
|
|
|
static void geth_set_rx_mode(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
unsigned int value = 0;
|
|
|
|
pr_debug("%s: # mcasts %d, # unicast %d\n",
|
|
__func__, netdev_mc_count(ndev), netdev_uc_count(ndev));
|
|
|
|
spin_lock(&priv->lock);
|
|
if (ndev->flags & IFF_PROMISC) {
|
|
value = GETH_FRAME_FILTER_PR;
|
|
} else if ((netdev_mc_count(ndev) > HASH_TABLE_SIZE) ||
|
|
(ndev->flags & IFF_ALLMULTI)) {
|
|
value = GETH_FRAME_FILTER_PM; /* pass all multi */
|
|
sunxi_hash_filter(priv->base, ~0UL, ~0UL);
|
|
} else if (!netdev_mc_empty(ndev)) {
|
|
u32 mc_filter[2];
|
|
struct netdev_hw_addr *ha;
|
|
|
|
/* Hash filter for multicast */
|
|
value = GETH_FRAME_FILTER_HMC;
|
|
|
|
memset(mc_filter, 0, sizeof(mc_filter));
|
|
netdev_for_each_mc_addr(ha, ndev) {
|
|
/* The upper 6 bits of the calculated CRC are used to
|
|
* index the contens of the hash table
|
|
*/
|
|
int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26;
|
|
/* The most significant bit determines the register to
|
|
* use (H/L) while the other 5 bits determine the bit
|
|
* within the register.
|
|
*/
|
|
mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
|
|
}
|
|
sunxi_hash_filter(priv->base, mc_filter[0], mc_filter[1]);
|
|
}
|
|
|
|
/* Handle multiple unicast addresses (perfect filtering)*/
|
|
if (netdev_uc_count(ndev) > 16) {
|
|
/* Switch to promiscuous mode is more than 8 addrs are required */
|
|
value |= GETH_FRAME_FILTER_PR;
|
|
} else {
|
|
int reg = 1;
|
|
struct netdev_hw_addr *ha;
|
|
|
|
netdev_for_each_uc_addr(ha, ndev) {
|
|
sunxi_set_umac(priv->base, ha->addr, reg);
|
|
reg++;
|
|
}
|
|
}
|
|
|
|
#ifdef FRAME_FILTER_DEBUG
|
|
/* Enable Receive all mode (to debug filtering_fail errors) */
|
|
value |= GETH_FRAME_FILTER_RA;
|
|
#endif
|
|
sunxi_set_filter(priv->base, value);
|
|
spin_unlock(&priv->lock);
|
|
}
|
|
|
|
static void geth_tx_timeout(struct net_device *ndev)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
geth_tx_err(priv);
|
|
}
|
|
|
|
static int geth_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
|
|
{
|
|
if (!netif_running(ndev))
|
|
return -EINVAL;
|
|
|
|
if (!ndev->phydev)
|
|
return -EINVAL;
|
|
|
|
return phy_mii_ioctl(ndev->phydev, rq, cmd);
|
|
}
|
|
|
|
/* Configuration changes (passed on by ifconfig) */
|
|
static int geth_config(struct net_device *ndev, struct ifmap *map)
|
|
{
|
|
if (ndev->flags & IFF_UP) /* can't act on a running interface */
|
|
return -EBUSY;
|
|
|
|
/* Don't allow changing the I/O address */
|
|
if (map->base_addr != ndev->base_addr) {
|
|
pr_warn("%s: can't change I/O address\n", ndev->name);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Don't allow changing the IRQ */
|
|
if (map->irq != ndev->irq) {
|
|
pr_warn("%s: can't change IRQ number %d\n", ndev->name, ndev->irq);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int geth_set_mac_address(struct net_device *ndev, void *p)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct sockaddr *addr = p;
|
|
|
|
if (!is_valid_ether_addr(addr->sa_data))
|
|
return -EADDRNOTAVAIL;
|
|
|
|
memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len);
|
|
sunxi_set_umac(priv->base, ndev->dev_addr, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int geth_set_features(struct net_device *ndev, netdev_features_t features)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
if (features & NETIF_F_LOOPBACK && netif_running(ndev))
|
|
sunxi_mac_loopback(priv->base, 1);
|
|
else
|
|
sunxi_mac_loopback(priv->base, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
/* Polling receive - used by NETCONSOLE and other diagnostic tools
|
|
* to allow network I/O with interrupts disabled.
|
|
*/
|
|
static void geth_poll_controller(struct net_device *dev)
|
|
{
|
|
disable_irq(dev->irq);
|
|
geth_interrupt(dev->irq, dev);
|
|
enable_irq(dev->irq);
|
|
}
|
|
#endif
|
|
|
|
static const struct net_device_ops geth_netdev_ops = {
|
|
.ndo_init = NULL,
|
|
.ndo_open = geth_open,
|
|
.ndo_start_xmit = geth_xmit,
|
|
.ndo_stop = geth_stop,
|
|
.ndo_change_mtu = geth_change_mtu,
|
|
.ndo_fix_features = geth_fix_features,
|
|
.ndo_set_rx_mode = geth_set_rx_mode,
|
|
.ndo_tx_timeout = geth_tx_timeout,
|
|
.ndo_do_ioctl = geth_ioctl,
|
|
.ndo_set_config = geth_config,
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
.ndo_poll_controller = geth_poll_controller,
|
|
#endif
|
|
.ndo_set_mac_address = geth_set_mac_address,
|
|
.ndo_set_features = geth_set_features,
|
|
};
|
|
|
|
static int geth_check_if_running(struct net_device *ndev)
|
|
{
|
|
if (!netif_running(ndev))
|
|
return -EBUSY;
|
|
return 0;
|
|
}
|
|
|
|
static int geth_get_sset_count(struct net_device *netdev, int sset)
|
|
{
|
|
int len;
|
|
|
|
switch (sset) {
|
|
case ETH_SS_STATS:
|
|
len = 0;
|
|
return len;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int geth_ethtool_getsettings(struct net_device *ndev,
|
|
struct ethtool_cmd *cmd)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct phy_device *phy = ndev->phydev;
|
|
int rc;
|
|
|
|
if (phy == NULL) {
|
|
netdev_err(ndev, "%s: %s: PHY is not registered\n",
|
|
__func__, ndev->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!netif_running(ndev)) {
|
|
pr_err("%s: interface is disabled: we cannot track "
|
|
"link speed / duplex setting\n", ndev->name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd->transceiver = XCVR_INTERNAL;
|
|
spin_lock_irq(&priv->lock);
|
|
rc = phy_ethtool_gset(phy, cmd);
|
|
spin_unlock_irq(&priv->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int geth_ethtool_setsettings(struct net_device *ndev,
|
|
struct ethtool_cmd *cmd)
|
|
{
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct phy_device *phy = ndev->phydev;
|
|
int rc;
|
|
|
|
spin_lock(&priv->lock);
|
|
rc = phy_ethtool_sset(phy, cmd);
|
|
spin_unlock(&priv->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void geth_ethtool_getdrvinfo(struct net_device *ndev,
|
|
struct ethtool_drvinfo *info)
|
|
{
|
|
strlcpy(info->driver, "sunxi_geth", sizeof(info->driver));
|
|
|
|
#define DRV_MODULE_VERSION "SUNXI Gbgit driver V1.1"
|
|
|
|
strcpy(info->version, DRV_MODULE_VERSION);
|
|
info->fw_version[0] = '\0';
|
|
}
|
|
|
|
static const struct ethtool_ops geth_ethtool_ops = {
|
|
.begin = geth_check_if_running,
|
|
.get_settings = geth_ethtool_getsettings,
|
|
.set_settings = geth_ethtool_setsettings,
|
|
.get_link = ethtool_op_get_link,
|
|
.get_pauseparam = NULL,
|
|
.set_pauseparam = NULL,
|
|
.get_ethtool_stats = NULL,
|
|
.get_strings = NULL,
|
|
.get_wol = NULL,
|
|
.set_wol = NULL,
|
|
.get_sset_count = geth_get_sset_count,
|
|
.get_drvinfo = geth_ethtool_getdrvinfo,
|
|
};
|
|
|
|
/* config hardware resource */
|
|
static int geth_hw_init(struct platform_device *pdev)
|
|
{
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret = 0;
|
|
struct resource *res;
|
|
u32 value;
|
|
struct gpio_config cfg;
|
|
const char *gmac_power;
|
|
char power[20];
|
|
int i;
|
|
|
|
#ifdef CONFIG_SUNXI_EXT_PHY
|
|
priv->phy_ext = EXT_PHY;
|
|
#else
|
|
priv->phy_ext = INT_PHY;
|
|
#endif
|
|
|
|
/* config memery resource */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (unlikely(!res)) {
|
|
pr_err("%s: ERROR: get gmac memory failed", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
priv->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (!priv->base) {
|
|
pr_err("%s: ERROR: gmac memory mapping failed", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
if (unlikely(!res)) {
|
|
pr_err("%s: ERROR: get phy memory failed", __func__);
|
|
ret = -ENODEV;
|
|
goto mem_err;
|
|
}
|
|
|
|
priv->base_phy = devm_ioremap_resource(&pdev->dev, res);
|
|
if (unlikely(!priv->base_phy)) {
|
|
pr_err("%s: ERROR: phy memory mapping failed", __func__);
|
|
ret = -ENOMEM;
|
|
goto mem_err;
|
|
}
|
|
|
|
/* config IRQ */
|
|
ndev->irq = platform_get_irq_byname(pdev, "gmacirq");
|
|
if (ndev->irq == -ENXIO) {
|
|
pr_err("%s: ERROR: MAC IRQ not found\n", __func__);
|
|
ret = -ENXIO;
|
|
goto irq_err;
|
|
}
|
|
|
|
ret = request_irq(ndev->irq, geth_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev);
|
|
if (unlikely(ret < 0)) {
|
|
pr_err("Could not request irq %d, error: %d\n", ndev->irq, ret);
|
|
goto irq_err;
|
|
}
|
|
|
|
/* config clock */
|
|
priv->geth_clk = of_clk_get_by_name(np, "gmac");
|
|
if (unlikely(!priv->geth_clk || IS_ERR(priv->geth_clk))) {
|
|
pr_err("Get gmac clock failed!\n");
|
|
ret = -EINVAL;
|
|
goto clk_err;
|
|
}
|
|
|
|
if (INT_PHY == priv->phy_ext) {
|
|
priv->ephy_clk = of_clk_get_by_name(np, "ephy");
|
|
if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
|
|
pr_err("Get ephy clock failed!\n");
|
|
ret = -EINVAL;
|
|
goto clk_err;
|
|
}
|
|
}
|
|
#if defined(CONFIG_ARCH_SUN8IW12)
|
|
else {
|
|
if (!of_property_read_u32(np, "use_ephy25m", &g_use_ephy_clk)
|
|
&& g_use_ephy_clk) {
|
|
priv->ephy_clk = of_clk_get_by_name(np, "ephy");
|
|
if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
|
|
pr_err("Get ephy clk failed!\n");
|
|
ret = -EINVAL;
|
|
goto clk_err;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* config power regulator */
|
|
if (EXT_PHY == priv->phy_ext) {
|
|
for (i = 0; i < POWER_CHAN_NUM; i++) {
|
|
snprintf(power, 15, "gmac-power%d", i);
|
|
ret = of_property_read_string(np, power, &gmac_power);
|
|
if (ret) {
|
|
priv->gmac_power[i] = NULL;
|
|
pr_info("gmac-power%d: NULL\n", i);
|
|
continue;
|
|
}
|
|
priv->gmac_power[i] = regulator_get(NULL, gmac_power);
|
|
if (IS_ERR(priv->gmac_power[i])) {
|
|
pr_err("gmac-power%d get error!\n", i);
|
|
ret = -EINVAL;
|
|
goto clk_err;
|
|
}
|
|
}
|
|
}
|
|
/* config other parameters */
|
|
priv->phy_interface = of_get_phy_mode(np);
|
|
if (priv->phy_interface != PHY_INTERFACE_MODE_MII &&
|
|
priv->phy_interface != PHY_INTERFACE_MODE_RGMII &&
|
|
priv->phy_interface != PHY_INTERFACE_MODE_RMII) {
|
|
pr_err("Not support phy type!\n");
|
|
priv->phy_interface = PHY_INTERFACE_MODE_MII;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "tx-delay", &value))
|
|
tx_delay = value;
|
|
|
|
if (!of_property_read_u32(np, "rx-delay", &value))
|
|
rx_delay = value;
|
|
|
|
/* config pinctrl */
|
|
if (EXT_PHY == priv->phy_ext) {
|
|
priv->phyrst = of_get_named_gpio_flags(np, "phy-rst", 0, (enum of_gpio_flags *)&cfg);
|
|
priv->rst_active_low = (cfg.data == OF_GPIO_ACTIVE_LOW) ? 1 : 0;
|
|
|
|
if (gpio_is_valid(priv->phyrst)) {
|
|
if (gpio_request(priv->phyrst, "phy-rst") < 0) {
|
|
pr_err("gmac gpio request fail!\n");
|
|
ret = -EINVAL;
|
|
goto pin_err;
|
|
}
|
|
}
|
|
|
|
priv->pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
|
|
if (IS_ERR_OR_NULL(priv->pinctrl)) {
|
|
pr_err("gmac pinctrl error!\n");
|
|
priv->pinctrl = NULL;
|
|
ret = -EINVAL;
|
|
goto pin_err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
pin_err:
|
|
if (EXT_PHY == priv->phy_ext) {
|
|
for (i = 0; i < POWER_CHAN_NUM; i++) {
|
|
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
|
|
continue;
|
|
regulator_put(priv->gmac_power[i]);
|
|
}
|
|
}
|
|
clk_err:
|
|
free_irq(ndev->irq, ndev);
|
|
irq_err:
|
|
devm_iounmap(&pdev->dev, priv->base_phy);
|
|
mem_err:
|
|
devm_iounmap(&pdev->dev, priv->base);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void geth_hw_release(struct platform_device *pdev)
|
|
{
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
int i;
|
|
|
|
devm_iounmap(&pdev->dev, (priv->base_phy));
|
|
devm_iounmap(&pdev->dev, priv->base);
|
|
free_irq(ndev->irq, ndev);
|
|
if (priv->geth_clk)
|
|
clk_put(priv->geth_clk);
|
|
|
|
if (EXT_PHY == priv->phy_ext) {
|
|
for (i = 0; i < POWER_CHAN_NUM; i++) {
|
|
if (IS_ERR_OR_NULL(priv->gmac_power[i]))
|
|
continue;
|
|
regulator_put(priv->gmac_power[i]);
|
|
}
|
|
|
|
if (!IS_ERR_OR_NULL(priv->pinctrl))
|
|
devm_pinctrl_put(priv->pinctrl);
|
|
|
|
if (gpio_is_valid(priv->phyrst))
|
|
gpio_free(priv->phyrst);
|
|
}
|
|
|
|
if (!IS_ERR_OR_NULL(priv->ephy_clk))
|
|
clk_put(priv->ephy_clk);
|
|
}
|
|
|
|
/**
|
|
* geth_probe
|
|
* @pdev: platform device pointer
|
|
* Description: the driver is initialized through platform_device.
|
|
*/
|
|
static int geth_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
struct net_device *ndev = NULL;
|
|
struct geth_priv *priv;
|
|
|
|
#ifdef CONFIG_OF
|
|
pdev->dev.dma_mask = &geth_dma_mask;
|
|
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
|
|
#endif
|
|
|
|
ndev = alloc_etherdev(sizeof(struct geth_priv));
|
|
if (!ndev) {
|
|
dev_err(&pdev->dev, "could not allocate device.\n");
|
|
return -ENOMEM;
|
|
}
|
|
SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
|
|
priv = netdev_priv(ndev);
|
|
platform_set_drvdata(pdev, ndev);
|
|
|
|
/* Must set private data to pdev, before call it */
|
|
ret = geth_hw_init(pdev);
|
|
if (0 != ret) {
|
|
pr_err("geth_hw_init fail!\n");
|
|
goto hw_err;
|
|
}
|
|
|
|
/* setup the netdevice, fill the field of netdevice */
|
|
ether_setup(ndev);
|
|
ndev->netdev_ops = &geth_netdev_ops;
|
|
netdev_set_default_ethtool_ops(ndev, &geth_ethtool_ops);
|
|
ndev->base_addr = (unsigned long)priv->base;
|
|
|
|
priv->ndev = ndev;
|
|
priv->dev = &pdev->dev;
|
|
|
|
/* TODO: support the VLAN frames */
|
|
ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM |
|
|
NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM;
|
|
|
|
ndev->features |= ndev->hw_features;
|
|
ndev->hw_features |= NETIF_F_LOOPBACK;
|
|
ndev->priv_flags |= IFF_UNICAST_FLT;
|
|
|
|
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
|
|
|
|
netif_napi_add(ndev, &priv->napi, geth_poll, BUDGET);
|
|
|
|
spin_lock_init(&priv->lock);
|
|
spin_lock_init(&priv->tx_lock);
|
|
|
|
/* The last val is mdc clock ratio */
|
|
sunxi_geth_register((void *)ndev->base_addr, HW_VERSION, 0x03);
|
|
|
|
ret = register_netdev(ndev);
|
|
if (ret) {
|
|
netif_napi_del(&priv->napi);
|
|
pr_err("Error: Register %s failed\n", ndev->name);
|
|
goto reg_err;
|
|
}
|
|
|
|
/* Before open the device, the mac address should be set */
|
|
geth_check_addr(ndev, mac_str);
|
|
|
|
#ifdef CONFIG_GETH_ATTRS
|
|
geth_create_attrs(ndev);
|
|
#endif
|
|
device_create_file(&pdev->dev, &dev_attr_gphy_test);
|
|
|
|
device_enable_async_suspend(&pdev->dev);
|
|
|
|
#ifdef CONFIG_PM
|
|
INIT_WORK(&priv->eth_work, geth_resume_work);
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
reg_err:
|
|
geth_hw_release(pdev);
|
|
hw_err:
|
|
platform_set_drvdata(pdev, NULL);
|
|
free_netdev(ndev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int geth_remove(struct platform_device *pdev)
|
|
{
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
|
struct geth_priv *priv = netdev_priv(ndev);
|
|
|
|
device_remove_file(&pdev->dev, &dev_attr_gphy_test);
|
|
|
|
netif_napi_del(&priv->napi);
|
|
unregister_netdev(ndev);
|
|
geth_hw_release(pdev);
|
|
platform_set_drvdata(pdev, NULL);
|
|
free_netdev(ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id geth_of_match[] = {
|
|
{.compatible = "allwinner,sunxi-gmac",},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, geth_of_match);
|
|
|
|
static struct platform_driver geth_driver = {
|
|
.probe = geth_probe,
|
|
.remove = geth_remove,
|
|
.driver = {
|
|
.name = "sunxi-gmac",
|
|
.owner = THIS_MODULE,
|
|
.pm = &geth_pm_ops,
|
|
.of_match_table = geth_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(geth_driver);
|
|
|
|
#ifndef MODULE
|
|
static int __init set_mac_addr(char *str)
|
|
{
|
|
char *p = str;
|
|
|
|
if (str && strlen(str))
|
|
memcpy(mac_str, p, 18);
|
|
|
|
return 0;
|
|
}
|
|
__setup("mac_addr=", set_mac_addr);
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("Allwinner Gigabit Ethernet driver");
|
|
MODULE_AUTHOR("fuzhaoke <fuzhaoke@allwinnertech.com>");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|