/*
* 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 "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