e9c56f8d2f
The sun4i-emac driver uses devm_request_irq at .ndo_open time, but relies on the managed device mechanism to actually free it. This causes an issue whenever someone wants to restart the interface, the interrupt still being held, and not yet released. Fall back to using the regular request_irq at .ndo_open time, and introduce a free_irq during .ndo_stop. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: stable@vger.kernel.org # 3.11+ Signed-off-by: David S. Miller <davem@davemloft.net>
956 lines
23 KiB
C
956 lines
23 KiB
C
/*
|
|
* Allwinner EMAC Fast Ethernet driver for Linux.
|
|
*
|
|
* Copyright 2012-2013 Stefan Roese <sr@denx.de>
|
|
* Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
|
|
*
|
|
* Based on the Linux driver provided by Allwinner:
|
|
* Copyright (C) 1997 Sten Wang
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/phy.h>
|
|
|
|
#include "sun4i-emac.h"
|
|
|
|
#define DRV_NAME "sun4i-emac"
|
|
#define DRV_VERSION "1.02"
|
|
|
|
#define EMAC_MAX_FRAME_LEN 0x0600
|
|
|
|
/* Transmit timeout, default 5 seconds. */
|
|
static int watchdog = 5000;
|
|
module_param(watchdog, int, 0400);
|
|
MODULE_PARM_DESC(watchdog, "transmit timeout in milliseconds");
|
|
|
|
/* EMAC register address locking.
|
|
*
|
|
* The EMAC uses an address register to control where data written
|
|
* to the data register goes. This means that the address register
|
|
* must be preserved over interrupts or similar calls.
|
|
*
|
|
* During interrupt and other critical calls, a spinlock is used to
|
|
* protect the system, but the calls themselves save the address
|
|
* in the address register in case they are interrupting another
|
|
* access to the device.
|
|
*
|
|
* For general accesses a lock is provided so that calls which are
|
|
* allowed to sleep are serialised so that the address register does
|
|
* not need to be saved. This lock also serves to serialise access
|
|
* to the EEPROM and PHY access registers which are shared between
|
|
* these two devices.
|
|
*/
|
|
|
|
/* The driver supports the original EMACE, and now the two newer
|
|
* devices, EMACA and EMACB.
|
|
*/
|
|
|
|
struct emac_board_info {
|
|
struct clk *clk;
|
|
struct device *dev;
|
|
struct platform_device *pdev;
|
|
spinlock_t lock;
|
|
void __iomem *membase;
|
|
u32 msg_enable;
|
|
struct net_device *ndev;
|
|
struct sk_buff *skb_last;
|
|
u16 tx_fifo_stat;
|
|
|
|
int emacrx_completed_flag;
|
|
|
|
struct phy_device *phy_dev;
|
|
struct device_node *phy_node;
|
|
unsigned int link;
|
|
unsigned int speed;
|
|
unsigned int duplex;
|
|
|
|
phy_interface_t phy_interface;
|
|
};
|
|
|
|
static void emac_update_speed(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
unsigned int reg_val;
|
|
|
|
/* set EMAC SPEED, depend on PHY */
|
|
reg_val = readl(db->membase + EMAC_MAC_SUPP_REG);
|
|
reg_val &= ~(0x1 << 8);
|
|
if (db->speed == SPEED_100)
|
|
reg_val |= 1 << 8;
|
|
writel(reg_val, db->membase + EMAC_MAC_SUPP_REG);
|
|
}
|
|
|
|
static void emac_update_duplex(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
unsigned int reg_val;
|
|
|
|
/* set duplex depend on phy */
|
|
reg_val = readl(db->membase + EMAC_MAC_CTL1_REG);
|
|
reg_val &= ~EMAC_MAC_CTL1_DUPLEX_EN;
|
|
if (db->duplex)
|
|
reg_val |= EMAC_MAC_CTL1_DUPLEX_EN;
|
|
writel(reg_val, db->membase + EMAC_MAC_CTL1_REG);
|
|
}
|
|
|
|
static void emac_handle_link_change(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
struct phy_device *phydev = db->phy_dev;
|
|
unsigned long flags;
|
|
int status_change = 0;
|
|
|
|
if (phydev->link) {
|
|
if (db->speed != phydev->speed) {
|
|
spin_lock_irqsave(&db->lock, flags);
|
|
db->speed = phydev->speed;
|
|
emac_update_speed(dev);
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
status_change = 1;
|
|
}
|
|
|
|
if (db->duplex != phydev->duplex) {
|
|
spin_lock_irqsave(&db->lock, flags);
|
|
db->duplex = phydev->duplex;
|
|
emac_update_duplex(dev);
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
status_change = 1;
|
|
}
|
|
}
|
|
|
|
if (phydev->link != db->link) {
|
|
if (!phydev->link) {
|
|
db->speed = 0;
|
|
db->duplex = -1;
|
|
}
|
|
db->link = phydev->link;
|
|
|
|
status_change = 1;
|
|
}
|
|
|
|
if (status_change)
|
|
phy_print_status(phydev);
|
|
}
|
|
|
|
static int emac_mdio_probe(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
|
|
/* to-do: PHY interrupts are currently not supported */
|
|
|
|
/* attach the mac to the phy */
|
|
db->phy_dev = of_phy_connect(db->ndev, db->phy_node,
|
|
&emac_handle_link_change, 0,
|
|
db->phy_interface);
|
|
if (!db->phy_dev) {
|
|
netdev_err(db->ndev, "could not find the PHY\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* mask with MAC supported features */
|
|
db->phy_dev->supported &= PHY_BASIC_FEATURES;
|
|
db->phy_dev->advertising = db->phy_dev->supported;
|
|
|
|
db->link = 0;
|
|
db->speed = 0;
|
|
db->duplex = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void emac_mdio_remove(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
|
|
phy_disconnect(db->phy_dev);
|
|
db->phy_dev = NULL;
|
|
}
|
|
|
|
static void emac_reset(struct emac_board_info *db)
|
|
{
|
|
dev_dbg(db->dev, "resetting device\n");
|
|
|
|
/* RESET device */
|
|
writel(0, db->membase + EMAC_CTL_REG);
|
|
udelay(200);
|
|
writel(EMAC_CTL_RESET, db->membase + EMAC_CTL_REG);
|
|
udelay(200);
|
|
}
|
|
|
|
static void emac_outblk_32bit(void __iomem *reg, void *data, int count)
|
|
{
|
|
writesl(reg, data, round_up(count, 4) / 4);
|
|
}
|
|
|
|
static void emac_inblk_32bit(void __iomem *reg, void *data, int count)
|
|
{
|
|
readsl(reg, data, round_up(count, 4) / 4);
|
|
}
|
|
|
|
static int emac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
|
|
{
|
|
struct emac_board_info *dm = netdev_priv(dev);
|
|
struct phy_device *phydev = dm->phy_dev;
|
|
|
|
if (!netif_running(dev))
|
|
return -EINVAL;
|
|
|
|
if (!phydev)
|
|
return -ENODEV;
|
|
|
|
return phy_mii_ioctl(phydev, rq, cmd);
|
|
}
|
|
|
|
/* ethtool ops */
|
|
static void emac_get_drvinfo(struct net_device *dev,
|
|
struct ethtool_drvinfo *info)
|
|
{
|
|
strlcpy(info->driver, DRV_NAME, sizeof(DRV_NAME));
|
|
strlcpy(info->version, DRV_VERSION, sizeof(DRV_VERSION));
|
|
strlcpy(info->bus_info, dev_name(&dev->dev), sizeof(info->bus_info));
|
|
}
|
|
|
|
static int emac_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
struct emac_board_info *dm = netdev_priv(dev);
|
|
struct phy_device *phydev = dm->phy_dev;
|
|
|
|
if (!phydev)
|
|
return -ENODEV;
|
|
|
|
return phy_ethtool_gset(phydev, cmd);
|
|
}
|
|
|
|
static int emac_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
struct emac_board_info *dm = netdev_priv(dev);
|
|
struct phy_device *phydev = dm->phy_dev;
|
|
|
|
if (!phydev)
|
|
return -ENODEV;
|
|
|
|
return phy_ethtool_sset(phydev, cmd);
|
|
}
|
|
|
|
static const struct ethtool_ops emac_ethtool_ops = {
|
|
.get_drvinfo = emac_get_drvinfo,
|
|
.get_settings = emac_get_settings,
|
|
.set_settings = emac_set_settings,
|
|
.get_link = ethtool_op_get_link,
|
|
};
|
|
|
|
static unsigned int emac_setup(struct net_device *ndev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(ndev);
|
|
unsigned int reg_val;
|
|
|
|
/* set up TX */
|
|
reg_val = readl(db->membase + EMAC_TX_MODE_REG);
|
|
|
|
writel(reg_val | EMAC_TX_MODE_ABORTED_FRAME_EN,
|
|
db->membase + EMAC_TX_MODE_REG);
|
|
|
|
/* set up RX */
|
|
reg_val = readl(db->membase + EMAC_RX_CTL_REG);
|
|
|
|
writel(reg_val | EMAC_RX_CTL_PASS_LEN_OOR_EN |
|
|
EMAC_RX_CTL_ACCEPT_UNICAST_EN | EMAC_RX_CTL_DA_FILTER_EN |
|
|
EMAC_RX_CTL_ACCEPT_MULTICAST_EN |
|
|
EMAC_RX_CTL_ACCEPT_BROADCAST_EN,
|
|
db->membase + EMAC_RX_CTL_REG);
|
|
|
|
/* set MAC */
|
|
/* set MAC CTL0 */
|
|
reg_val = readl(db->membase + EMAC_MAC_CTL0_REG);
|
|
writel(reg_val | EMAC_MAC_CTL0_RX_FLOW_CTL_EN |
|
|
EMAC_MAC_CTL0_TX_FLOW_CTL_EN,
|
|
db->membase + EMAC_MAC_CTL0_REG);
|
|
|
|
/* set MAC CTL1 */
|
|
reg_val = readl(db->membase + EMAC_MAC_CTL1_REG);
|
|
reg_val |= EMAC_MAC_CTL1_LEN_CHECK_EN;
|
|
reg_val |= EMAC_MAC_CTL1_CRC_EN;
|
|
reg_val |= EMAC_MAC_CTL1_PAD_EN;
|
|
writel(reg_val, db->membase + EMAC_MAC_CTL1_REG);
|
|
|
|
/* set up IPGT */
|
|
writel(EMAC_MAC_IPGT_FULL_DUPLEX, db->membase + EMAC_MAC_IPGT_REG);
|
|
|
|
/* set up IPGR */
|
|
writel((EMAC_MAC_IPGR_IPG1 << 8) | EMAC_MAC_IPGR_IPG2,
|
|
db->membase + EMAC_MAC_IPGR_REG);
|
|
|
|
/* set up Collison window */
|
|
writel((EMAC_MAC_CLRT_COLLISION_WINDOW << 8) | EMAC_MAC_CLRT_RM,
|
|
db->membase + EMAC_MAC_CLRT_REG);
|
|
|
|
/* set up Max Frame Length */
|
|
writel(EMAC_MAX_FRAME_LEN,
|
|
db->membase + EMAC_MAC_MAXF_REG);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int emac_powerup(struct net_device *ndev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(ndev);
|
|
unsigned int reg_val;
|
|
|
|
/* initial EMAC */
|
|
/* flush RX FIFO */
|
|
reg_val = readl(db->membase + EMAC_RX_CTL_REG);
|
|
reg_val |= 0x8;
|
|
writel(reg_val, db->membase + EMAC_RX_CTL_REG);
|
|
udelay(1);
|
|
|
|
/* initial MAC */
|
|
/* soft reset MAC */
|
|
reg_val = readl(db->membase + EMAC_MAC_CTL0_REG);
|
|
reg_val &= ~EMAC_MAC_CTL0_SOFT_RESET;
|
|
writel(reg_val, db->membase + EMAC_MAC_CTL0_REG);
|
|
|
|
/* set MII clock */
|
|
reg_val = readl(db->membase + EMAC_MAC_MCFG_REG);
|
|
reg_val &= (~(0xf << 2));
|
|
reg_val |= (0xD << 2);
|
|
writel(reg_val, db->membase + EMAC_MAC_MCFG_REG);
|
|
|
|
/* clear RX counter */
|
|
writel(0x0, db->membase + EMAC_RX_FBC_REG);
|
|
|
|
/* disable all interrupt and clear interrupt status */
|
|
writel(0, db->membase + EMAC_INT_CTL_REG);
|
|
reg_val = readl(db->membase + EMAC_INT_STA_REG);
|
|
writel(reg_val, db->membase + EMAC_INT_STA_REG);
|
|
|
|
udelay(1);
|
|
|
|
/* set up EMAC */
|
|
emac_setup(ndev);
|
|
|
|
/* set mac_address to chip */
|
|
writel(ndev->dev_addr[0] << 16 | ndev->dev_addr[1] << 8 | ndev->
|
|
dev_addr[2], db->membase + EMAC_MAC_A1_REG);
|
|
writel(ndev->dev_addr[3] << 16 | ndev->dev_addr[4] << 8 | ndev->
|
|
dev_addr[5], db->membase + EMAC_MAC_A0_REG);
|
|
|
|
mdelay(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emac_set_mac_address(struct net_device *dev, void *p)
|
|
{
|
|
struct sockaddr *addr = p;
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
|
|
if (netif_running(dev))
|
|
return -EBUSY;
|
|
|
|
memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
|
|
|
|
writel(dev->dev_addr[0] << 16 | dev->dev_addr[1] << 8 | dev->
|
|
dev_addr[2], db->membase + EMAC_MAC_A1_REG);
|
|
writel(dev->dev_addr[3] << 16 | dev->dev_addr[4] << 8 | dev->
|
|
dev_addr[5], db->membase + EMAC_MAC_A0_REG);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Initialize emac board */
|
|
static void emac_init_device(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
unsigned long flags;
|
|
unsigned int reg_val;
|
|
|
|
spin_lock_irqsave(&db->lock, flags);
|
|
|
|
emac_update_speed(dev);
|
|
emac_update_duplex(dev);
|
|
|
|
/* enable RX/TX */
|
|
reg_val = readl(db->membase + EMAC_CTL_REG);
|
|
writel(reg_val | EMAC_CTL_RESET | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN,
|
|
db->membase + EMAC_CTL_REG);
|
|
|
|
/* enable RX/TX0/RX Hlevel interrup */
|
|
reg_val = readl(db->membase + EMAC_INT_CTL_REG);
|
|
reg_val |= (0xf << 0) | (0x01 << 8);
|
|
writel(reg_val, db->membase + EMAC_INT_CTL_REG);
|
|
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
}
|
|
|
|
/* Our watchdog timed out. Called by the networking layer */
|
|
static void emac_timeout(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
unsigned long flags;
|
|
|
|
if (netif_msg_timer(db))
|
|
dev_err(db->dev, "tx time out.\n");
|
|
|
|
/* Save previous register address */
|
|
spin_lock_irqsave(&db->lock, flags);
|
|
|
|
netif_stop_queue(dev);
|
|
emac_reset(db);
|
|
emac_init_device(dev);
|
|
/* We can accept TX packets again */
|
|
dev->trans_start = jiffies;
|
|
netif_wake_queue(dev);
|
|
|
|
/* Restore previous register address */
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
}
|
|
|
|
/* Hardware start transmission.
|
|
* Send a packet to media from the upper layer.
|
|
*/
|
|
static int emac_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
unsigned long channel;
|
|
unsigned long flags;
|
|
|
|
channel = db->tx_fifo_stat & 3;
|
|
if (channel == 3)
|
|
return 1;
|
|
|
|
channel = (channel == 1 ? 1 : 0);
|
|
|
|
spin_lock_irqsave(&db->lock, flags);
|
|
|
|
writel(channel, db->membase + EMAC_TX_INS_REG);
|
|
|
|
emac_outblk_32bit(db->membase + EMAC_TX_IO_DATA_REG,
|
|
skb->data, skb->len);
|
|
dev->stats.tx_bytes += skb->len;
|
|
|
|
db->tx_fifo_stat |= 1 << channel;
|
|
/* TX control: First packet immediately send, second packet queue */
|
|
if (channel == 0) {
|
|
/* set TX len */
|
|
writel(skb->len, db->membase + EMAC_TX_PL0_REG);
|
|
/* start translate from fifo to phy */
|
|
writel(readl(db->membase + EMAC_TX_CTL0_REG) | 1,
|
|
db->membase + EMAC_TX_CTL0_REG);
|
|
|
|
/* save the time stamp */
|
|
dev->trans_start = jiffies;
|
|
} else if (channel == 1) {
|
|
/* set TX len */
|
|
writel(skb->len, db->membase + EMAC_TX_PL1_REG);
|
|
/* start translate from fifo to phy */
|
|
writel(readl(db->membase + EMAC_TX_CTL1_REG) | 1,
|
|
db->membase + EMAC_TX_CTL1_REG);
|
|
|
|
/* save the time stamp */
|
|
dev->trans_start = jiffies;
|
|
}
|
|
|
|
if ((db->tx_fifo_stat & 3) == 3) {
|
|
/* Second packet */
|
|
netif_stop_queue(dev);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
|
|
/* free this SKB */
|
|
dev_kfree_skb(skb);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
/* EMAC interrupt handler
|
|
* receive the packet to upper layer, free the transmitted packet
|
|
*/
|
|
static void emac_tx_done(struct net_device *dev, struct emac_board_info *db,
|
|
unsigned int tx_status)
|
|
{
|
|
/* One packet sent complete */
|
|
db->tx_fifo_stat &= ~(tx_status & 3);
|
|
if (3 == (tx_status & 3))
|
|
dev->stats.tx_packets += 2;
|
|
else
|
|
dev->stats.tx_packets++;
|
|
|
|
if (netif_msg_tx_done(db))
|
|
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
|
|
|
|
netif_wake_queue(dev);
|
|
}
|
|
|
|
/* Received a packet and pass to upper layer
|
|
*/
|
|
static void emac_rx(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
struct sk_buff *skb;
|
|
u8 *rdptr;
|
|
bool good_packet;
|
|
static int rxlen_last;
|
|
unsigned int reg_val;
|
|
u32 rxhdr, rxstatus, rxcount, rxlen;
|
|
|
|
/* Check packet ready or not */
|
|
while (1) {
|
|
/* race warning: the first packet might arrive with
|
|
* the interrupts disabled, but the second will fix
|
|
* it
|
|
*/
|
|
rxcount = readl(db->membase + EMAC_RX_FBC_REG);
|
|
|
|
if (netif_msg_rx_status(db))
|
|
dev_dbg(db->dev, "RXCount: %x\n", rxcount);
|
|
|
|
if ((db->skb_last != NULL) && (rxlen_last > 0)) {
|
|
dev->stats.rx_bytes += rxlen_last;
|
|
|
|
/* Pass to upper layer */
|
|
db->skb_last->protocol = eth_type_trans(db->skb_last,
|
|
dev);
|
|
netif_rx(db->skb_last);
|
|
dev->stats.rx_packets++;
|
|
db->skb_last = NULL;
|
|
rxlen_last = 0;
|
|
|
|
reg_val = readl(db->membase + EMAC_RX_CTL_REG);
|
|
reg_val &= ~EMAC_RX_CTL_DMA_EN;
|
|
writel(reg_val, db->membase + EMAC_RX_CTL_REG);
|
|
}
|
|
|
|
if (!rxcount) {
|
|
db->emacrx_completed_flag = 1;
|
|
reg_val = readl(db->membase + EMAC_INT_CTL_REG);
|
|
reg_val |= (0xf << 0) | (0x01 << 8);
|
|
writel(reg_val, db->membase + EMAC_INT_CTL_REG);
|
|
|
|
/* had one stuck? */
|
|
rxcount = readl(db->membase + EMAC_RX_FBC_REG);
|
|
if (!rxcount)
|
|
return;
|
|
}
|
|
|
|
reg_val = readl(db->membase + EMAC_RX_IO_DATA_REG);
|
|
if (netif_msg_rx_status(db))
|
|
dev_dbg(db->dev, "receive header: %x\n", reg_val);
|
|
if (reg_val != EMAC_UNDOCUMENTED_MAGIC) {
|
|
/* disable RX */
|
|
reg_val = readl(db->membase + EMAC_CTL_REG);
|
|
writel(reg_val & ~EMAC_CTL_RX_EN,
|
|
db->membase + EMAC_CTL_REG);
|
|
|
|
/* Flush RX FIFO */
|
|
reg_val = readl(db->membase + EMAC_RX_CTL_REG);
|
|
writel(reg_val | (1 << 3),
|
|
db->membase + EMAC_RX_CTL_REG);
|
|
|
|
do {
|
|
reg_val = readl(db->membase + EMAC_RX_CTL_REG);
|
|
} while (reg_val & (1 << 3));
|
|
|
|
/* enable RX */
|
|
reg_val = readl(db->membase + EMAC_CTL_REG);
|
|
writel(reg_val | EMAC_CTL_RX_EN,
|
|
db->membase + EMAC_CTL_REG);
|
|
reg_val = readl(db->membase + EMAC_INT_CTL_REG);
|
|
reg_val |= (0xf << 0) | (0x01 << 8);
|
|
writel(reg_val, db->membase + EMAC_INT_CTL_REG);
|
|
|
|
db->emacrx_completed_flag = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
/* A packet ready now & Get status/length */
|
|
good_packet = true;
|
|
|
|
emac_inblk_32bit(db->membase + EMAC_RX_IO_DATA_REG,
|
|
&rxhdr, sizeof(rxhdr));
|
|
|
|
if (netif_msg_rx_status(db))
|
|
dev_dbg(db->dev, "rxhdr: %x\n", *((int *)(&rxhdr)));
|
|
|
|
rxlen = EMAC_RX_IO_DATA_LEN(rxhdr);
|
|
rxstatus = EMAC_RX_IO_DATA_STATUS(rxhdr);
|
|
|
|
if (netif_msg_rx_status(db))
|
|
dev_dbg(db->dev, "RX: status %02x, length %04x\n",
|
|
rxstatus, rxlen);
|
|
|
|
/* Packet Status check */
|
|
if (rxlen < 0x40) {
|
|
good_packet = false;
|
|
if (netif_msg_rx_err(db))
|
|
dev_dbg(db->dev, "RX: Bad Packet (runt)\n");
|
|
}
|
|
|
|
if (unlikely(!(rxstatus & EMAC_RX_IO_DATA_STATUS_OK))) {
|
|
good_packet = false;
|
|
|
|
if (rxstatus & EMAC_RX_IO_DATA_STATUS_CRC_ERR) {
|
|
if (netif_msg_rx_err(db))
|
|
dev_dbg(db->dev, "crc error\n");
|
|
dev->stats.rx_crc_errors++;
|
|
}
|
|
|
|
if (rxstatus & EMAC_RX_IO_DATA_STATUS_LEN_ERR) {
|
|
if (netif_msg_rx_err(db))
|
|
dev_dbg(db->dev, "length error\n");
|
|
dev->stats.rx_length_errors++;
|
|
}
|
|
}
|
|
|
|
/* Move data from EMAC */
|
|
skb = dev_alloc_skb(rxlen + 4);
|
|
if (good_packet && skb) {
|
|
skb_reserve(skb, 2);
|
|
rdptr = (u8 *) skb_put(skb, rxlen - 4);
|
|
|
|
/* Read received packet from RX SRAM */
|
|
if (netif_msg_rx_status(db))
|
|
dev_dbg(db->dev, "RxLen %x\n", rxlen);
|
|
|
|
emac_inblk_32bit(db->membase + EMAC_RX_IO_DATA_REG,
|
|
rdptr, rxlen);
|
|
dev->stats.rx_bytes += rxlen;
|
|
|
|
/* Pass to upper layer */
|
|
skb->protocol = eth_type_trans(skb, dev);
|
|
netif_rx(skb);
|
|
dev->stats.rx_packets++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static irqreturn_t emac_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct net_device *dev = dev_id;
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
int int_status;
|
|
unsigned long flags;
|
|
unsigned int reg_val;
|
|
|
|
/* A real interrupt coming */
|
|
|
|
/* holders of db->lock must always block IRQs */
|
|
spin_lock_irqsave(&db->lock, flags);
|
|
|
|
/* Disable all interrupts */
|
|
writel(0, db->membase + EMAC_INT_CTL_REG);
|
|
|
|
/* Got EMAC interrupt status */
|
|
/* Got ISR */
|
|
int_status = readl(db->membase + EMAC_INT_STA_REG);
|
|
/* Clear ISR status */
|
|
writel(int_status, db->membase + EMAC_INT_STA_REG);
|
|
|
|
if (netif_msg_intr(db))
|
|
dev_dbg(db->dev, "emac interrupt %02x\n", int_status);
|
|
|
|
/* Received the coming packet */
|
|
if ((int_status & 0x100) && (db->emacrx_completed_flag == 1)) {
|
|
/* carrier lost */
|
|
db->emacrx_completed_flag = 0;
|
|
emac_rx(dev);
|
|
}
|
|
|
|
/* Transmit Interrupt check */
|
|
if (int_status & (0x01 | 0x02))
|
|
emac_tx_done(dev, db, int_status);
|
|
|
|
if (int_status & (0x04 | 0x08))
|
|
netdev_info(dev, " ab : %x\n", int_status);
|
|
|
|
/* Re-enable interrupt mask */
|
|
if (db->emacrx_completed_flag == 1) {
|
|
reg_val = readl(db->membase + EMAC_INT_CTL_REG);
|
|
reg_val |= (0xf << 0) | (0x01 << 8);
|
|
writel(reg_val, db->membase + EMAC_INT_CTL_REG);
|
|
}
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
/*
|
|
* Used by netconsole
|
|
*/
|
|
static void emac_poll_controller(struct net_device *dev)
|
|
{
|
|
disable_irq(dev->irq);
|
|
emac_interrupt(dev->irq, dev);
|
|
enable_irq(dev->irq);
|
|
}
|
|
#endif
|
|
|
|
/* Open the interface.
|
|
* The interface is opened whenever "ifconfig" actives it.
|
|
*/
|
|
static int emac_open(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
int ret;
|
|
|
|
if (netif_msg_ifup(db))
|
|
dev_dbg(db->dev, "enabling %s\n", dev->name);
|
|
|
|
if (request_irq(dev->irq, &emac_interrupt, 0, dev->name, dev))
|
|
return -EAGAIN;
|
|
|
|
/* Initialize EMAC board */
|
|
emac_reset(db);
|
|
emac_init_device(dev);
|
|
|
|
ret = emac_mdio_probe(dev);
|
|
if (ret < 0) {
|
|
netdev_err(dev, "cannot probe MDIO bus\n");
|
|
return ret;
|
|
}
|
|
|
|
phy_start(db->phy_dev);
|
|
netif_start_queue(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void emac_shutdown(struct net_device *dev)
|
|
{
|
|
unsigned int reg_val;
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
|
|
/* Disable all interrupt */
|
|
writel(0, db->membase + EMAC_INT_CTL_REG);
|
|
|
|
/* clear interupt status */
|
|
reg_val = readl(db->membase + EMAC_INT_STA_REG);
|
|
writel(reg_val, db->membase + EMAC_INT_STA_REG);
|
|
|
|
/* Disable RX/TX */
|
|
reg_val = readl(db->membase + EMAC_CTL_REG);
|
|
reg_val &= ~(EMAC_CTL_TX_EN | EMAC_CTL_RX_EN | EMAC_CTL_RESET);
|
|
writel(reg_val, db->membase + EMAC_CTL_REG);
|
|
}
|
|
|
|
/* Stop the interface.
|
|
* The interface is stopped when it is brought.
|
|
*/
|
|
static int emac_stop(struct net_device *ndev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(ndev);
|
|
|
|
if (netif_msg_ifdown(db))
|
|
dev_dbg(db->dev, "shutting down %s\n", ndev->name);
|
|
|
|
netif_stop_queue(ndev);
|
|
netif_carrier_off(ndev);
|
|
|
|
phy_stop(db->phy_dev);
|
|
|
|
emac_mdio_remove(ndev);
|
|
|
|
emac_shutdown(ndev);
|
|
|
|
free_irq(ndev->irq, ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct net_device_ops emac_netdev_ops = {
|
|
.ndo_open = emac_open,
|
|
.ndo_stop = emac_stop,
|
|
.ndo_start_xmit = emac_start_xmit,
|
|
.ndo_tx_timeout = emac_timeout,
|
|
.ndo_do_ioctl = emac_ioctl,
|
|
.ndo_change_mtu = eth_change_mtu,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
.ndo_set_mac_address = emac_set_mac_address,
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
.ndo_poll_controller = emac_poll_controller,
|
|
#endif
|
|
};
|
|
|
|
/* Search EMAC board, allocate space and register it
|
|
*/
|
|
static int emac_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct emac_board_info *db;
|
|
struct net_device *ndev;
|
|
int ret = 0;
|
|
const char *mac_addr;
|
|
|
|
ndev = alloc_etherdev(sizeof(struct emac_board_info));
|
|
if (!ndev) {
|
|
dev_err(&pdev->dev, "could not allocate device.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
|
|
db = netdev_priv(ndev);
|
|
memset(db, 0, sizeof(*db));
|
|
|
|
db->dev = &pdev->dev;
|
|
db->ndev = ndev;
|
|
db->pdev = pdev;
|
|
|
|
spin_lock_init(&db->lock);
|
|
|
|
db->membase = of_iomap(np, 0);
|
|
if (!db->membase) {
|
|
dev_err(&pdev->dev, "failed to remap registers\n");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* fill in parameters for net-dev structure */
|
|
ndev->base_addr = (unsigned long)db->membase;
|
|
ndev->irq = irq_of_parse_and_map(np, 0);
|
|
if (ndev->irq == -ENXIO) {
|
|
netdev_err(ndev, "No irq resource\n");
|
|
ret = ndev->irq;
|
|
goto out;
|
|
}
|
|
|
|
db->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(db->clk))
|
|
goto out;
|
|
|
|
clk_prepare_enable(db->clk);
|
|
|
|
db->phy_node = of_parse_phandle(np, "phy", 0);
|
|
if (!db->phy_node) {
|
|
dev_err(&pdev->dev, "no associated PHY\n");
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
/* Read MAC-address from DT */
|
|
mac_addr = of_get_mac_address(np);
|
|
if (mac_addr)
|
|
memcpy(ndev->dev_addr, mac_addr, ETH_ALEN);
|
|
|
|
/* Check if the MAC address is valid, if not get a random one */
|
|
if (!is_valid_ether_addr(ndev->dev_addr)) {
|
|
eth_hw_addr_random(ndev);
|
|
dev_warn(&pdev->dev, "using random MAC address %pM\n",
|
|
ndev->dev_addr);
|
|
}
|
|
|
|
db->emacrx_completed_flag = 1;
|
|
emac_powerup(ndev);
|
|
emac_reset(db);
|
|
|
|
ether_setup(ndev);
|
|
|
|
ndev->netdev_ops = &emac_netdev_ops;
|
|
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
|
|
ndev->ethtool_ops = &emac_ethtool_ops;
|
|
|
|
platform_set_drvdata(pdev, ndev);
|
|
|
|
/* Carrier starts down, phylib will bring it up */
|
|
netif_carrier_off(ndev);
|
|
|
|
ret = register_netdev(ndev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Registering netdev failed!\n");
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
dev_info(&pdev->dev, "%s: at %p, IRQ %d MAC: %pM\n",
|
|
ndev->name, db->membase, ndev->irq, ndev->dev_addr);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
dev_err(db->dev, "not found (%d).\n", ret);
|
|
|
|
free_netdev(ndev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int emac_remove(struct platform_device *pdev)
|
|
{
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
|
|
|
unregister_netdev(ndev);
|
|
free_netdev(ndev);
|
|
|
|
dev_dbg(&pdev->dev, "released and freed device\n");
|
|
return 0;
|
|
}
|
|
|
|
static int emac_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
struct net_device *ndev = platform_get_drvdata(dev);
|
|
|
|
netif_carrier_off(ndev);
|
|
netif_device_detach(ndev);
|
|
emac_shutdown(ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emac_resume(struct platform_device *dev)
|
|
{
|
|
struct net_device *ndev = platform_get_drvdata(dev);
|
|
struct emac_board_info *db = netdev_priv(ndev);
|
|
|
|
emac_reset(db);
|
|
emac_init_device(ndev);
|
|
netif_device_attach(ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id emac_of_match[] = {
|
|
{.compatible = "allwinner,sun4i-emac",},
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, emac_of_match);
|
|
|
|
static struct platform_driver emac_driver = {
|
|
.driver = {
|
|
.name = "sun4i-emac",
|
|
.of_match_table = emac_of_match,
|
|
},
|
|
.probe = emac_probe,
|
|
.remove = emac_remove,
|
|
.suspend = emac_suspend,
|
|
.resume = emac_resume,
|
|
};
|
|
|
|
module_platform_driver(emac_driver);
|
|
|
|
MODULE_AUTHOR("Stefan Roese <sr@denx.de>");
|
|
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
|
|
MODULE_DESCRIPTION("Allwinner A10 emac network driver");
|
|
MODULE_LICENSE("GPL");
|