mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-23 17:22:01 +07:00
934d0048d0
The EMAC EMAC_RX_IO_DATA_REG data register is dual-purpose: On one hand it is used to move actual packet data off the wire. This will be in wire-format and accepted as such by higher layers such as IP. Therefore it is correctly read as-is (i.e. raw) using readsl. On the other hand it provides metadata about incoming transfers to the driver such as length and checksum validation status. This data is little-endian, always and it is interpreted by the driver. Therefore it needs to be swapped to CPU endianness to make sense to the driver. This is already done for the "receive header" but not rxhdr. Read rxhdr using readl in order for sun4i-emac to work correctly when running a big-endian kernel. Signed-off-by: Michael Weiser <michael.weiser@gmx.de> Cc: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: netdev@vger.kernel.org Signed-off-by: David S. Miller <davem@davemloft.net>
988 lines
24 KiB
C
988 lines
24 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/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 <linux/soc/sunxi/sunxi_sram.h>
|
|
|
|
#include "sun4i-emac.h"
|
|
|
|
#define DRV_NAME "sun4i-emac"
|
|
#define DRV_VERSION "1.02"
|
|
|
|
#define EMAC_MAX_FRAME_LEN 0x0600
|
|
|
|
#define EMAC_DEFAULT_MSG_ENABLE 0x0000
|
|
static int debug = -1; /* defaults above */;
|
|
module_param(debug, int, 0);
|
|
MODULE_PARM_DESC(debug, "debug message flags");
|
|
|
|
/* 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 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 = dev->phydev;
|
|
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);
|
|
struct phy_device *phydev;
|
|
|
|
/* to-do: PHY interrupts are currently not supported */
|
|
|
|
/* attach the mac to the phy */
|
|
phydev = of_phy_connect(db->ndev, db->phy_node,
|
|
&emac_handle_link_change, 0,
|
|
db->phy_interface);
|
|
if (!phydev) {
|
|
netdev_err(db->ndev, "could not find the PHY\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* mask with MAC supported features */
|
|
phydev->supported &= PHY_BASIC_FEATURES;
|
|
phydev->advertising = phydev->supported;
|
|
|
|
db->link = 0;
|
|
db->speed = 0;
|
|
db->duplex = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void emac_mdio_remove(struct net_device *dev)
|
|
{
|
|
phy_disconnect(dev->phydev);
|
|
}
|
|
|
|
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 phy_device *phydev = dev->phydev;
|
|
|
|
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 u32 emac_get_msglevel(struct net_device *dev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
|
|
return db->msg_enable;
|
|
}
|
|
|
|
static void emac_set_msglevel(struct net_device *dev, u32 value)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(dev);
|
|
|
|
db->msg_enable = value;
|
|
}
|
|
|
|
static const struct ethtool_ops emac_ethtool_ops = {
|
|
.get_drvinfo = emac_get_drvinfo,
|
|
.get_link = ethtool_op_get_link,
|
|
.get_link_ksettings = phy_ethtool_get_link_ksettings,
|
|
.set_link_ksettings = phy_ethtool_set_link_ksettings,
|
|
.get_msglevel = emac_get_msglevel,
|
|
.set_msglevel = emac_set_msglevel,
|
|
};
|
|
|
|
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 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 void emac_set_rx_mode(struct net_device *ndev)
|
|
{
|
|
struct emac_board_info *db = netdev_priv(ndev);
|
|
unsigned int reg_val;
|
|
|
|
/* set up RX */
|
|
reg_val = readl(db->membase + EMAC_RX_CTL_REG);
|
|
|
|
if (ndev->flags & IFF_PROMISC)
|
|
reg_val |= EMAC_RX_CTL_PASS_ALL_EN;
|
|
else
|
|
reg_val &= ~EMAC_RX_CTL_PASS_ALL_EN;
|
|
|
|
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);
|
|
}
|
|
|
|
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 */
|
|
netif_trans_update(dev);
|
|
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 */
|
|
netif_trans_update(dev);
|
|
} 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 */
|
|
netif_trans_update(dev);
|
|
}
|
|
|
|
if ((db->tx_fifo_stat & 3) == 3) {
|
|
/* Second packet */
|
|
netif_stop_queue(dev);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&db->lock, flags);
|
|
|
|
/* free this SKB */
|
|
dev_consume_skb_any(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;
|
|
|
|
rxhdr = readl(db->membase + EMAC_RX_IO_DATA_REG);
|
|
|
|
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 */
|
|
if (good_packet) {
|
|
skb = netdev_alloc_skb(dev, rxlen + 4);
|
|
if (!skb)
|
|
continue;
|
|
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) {
|
|
free_irq(dev->irq, dev);
|
|
netdev_err(dev, "cannot probe MDIO bus\n");
|
|
return ret;
|
|
}
|
|
|
|
phy_start(dev->phydev);
|
|
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 interrupt 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(ndev->phydev);
|
|
|
|
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_set_rx_mode = emac_set_rx_mode,
|
|
.ndo_do_ioctl = emac_ioctl,
|
|
.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;
|
|
db->msg_enable = netif_msg_init(debug, EMAC_DEFAULT_MSG_ENABLE);
|
|
|
|
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_iounmap;
|
|
}
|
|
|
|
db->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(db->clk)) {
|
|
ret = PTR_ERR(db->clk);
|
|
goto out_iounmap;
|
|
}
|
|
|
|
ret = clk_prepare_enable(db->clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", ret);
|
|
goto out_iounmap;
|
|
}
|
|
|
|
ret = sunxi_sram_claim(&pdev->dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Error couldn't map SRAM to device\n");
|
|
goto out_clk_disable_unprepare;
|
|
}
|
|
|
|
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_release_sram;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
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_release_sram;
|
|
}
|
|
|
|
dev_info(&pdev->dev, "%s: at %p, IRQ %d MAC: %pM\n",
|
|
ndev->name, db->membase, ndev->irq, ndev->dev_addr);
|
|
|
|
return 0;
|
|
|
|
out_release_sram:
|
|
sunxi_sram_release(&pdev->dev);
|
|
out_clk_disable_unprepare:
|
|
clk_disable_unprepare(db->clk);
|
|
out_iounmap:
|
|
iounmap(db->membase);
|
|
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);
|
|
struct emac_board_info *db = netdev_priv(ndev);
|
|
|
|
unregister_netdev(ndev);
|
|
sunxi_sram_release(&pdev->dev);
|
|
clk_disable_unprepare(db->clk);
|
|
iounmap(db->membase);
|
|
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-a10-emac",},
|
|
|
|
/* Deprecated */
|
|
{.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");
|