Changes in 4.9.249 spi: bcm2835aux: Fix use-after-free on unbind spi: bcm2835aux: Restore err assignment in bcm2835aux_spi_probe iwlwifi: pcie: limit memory read spin time arm64: dts: rockchip: Assign a fixed index to mmc devices on rk3399 boards. ARC: stack unwinding: don't assume non-current task is sleeping platform/x86: acer-wmi: add automatic keyboard background light toggle key as KEY_LIGHTS_TOGGLE Input: cm109 - do not stomp on control URB Input: i8042 - add Acer laptops to the i8042 reset list pinctrl: amd: remove debounce filter setting in IRQ type setting scsi: be2iscsi: Revert "Fix a theoretical leak in beiscsi_create_eqs()" spi: Prevent adding devices below an unregistering controller net/mlx4_en: Avoid scheduling restart task if it is already running tcp: fix cwnd-limited bug for TSO deferral where we send nothing net: stmmac: delete the eee_ctrl_timer after napi disabled net: stmmac: dwmac-meson8b: fix mask definition of the m250_sel mux net: bridge: vlan: fix error return code in __vlan_add() mac80211: mesh: fix mesh_pathtbl_init() error path USB: dummy-hcd: Fix uninitialized array use in init() USB: add RESET_RESUME quirk for Snapscan 1212 ALSA: usb-audio: Fix potential out-of-bounds shift ALSA: usb-audio: Fix control 'access overflow' errors from chmap xhci: Give USB2 ports time to enter U3 in bus suspend USB: sisusbvga: Make console support depend on BROKEN ALSA: pcm: oss: Fix potential out-of-bounds shift serial: 8250_omap: Avoid FIFO corruption caused by MDR1 access pinctrl: merrifield: Set default bias in case no particular value given pinctrl: baytrail: Avoid clearing debounce value when turning it off scsi: bnx2i: Requires MMU can: softing: softing_netdev_open(): fix error handling RDMA/cm: Fix an attempt to use non-valid pointer when cleaning timewait kernel/cpu: add arch override for clear_tasks_mm_cpumask() mm handling drm/tegra: sor: Disable clocks on error in tegra_sor_init() scsi: mpt3sas: Increase IOCInit request timeout to 30s dm table: Remove BUG_ON(in_interrupt()) soc/tegra: fuse: Fix index bug in get_process_id USB: serial: option: add interface-number sanity check to flag handling USB: gadget: f_acm: add support for SuperSpeed Plus USB: gadget: f_midi: setup SuperSpeed Plus descriptors USB: gadget: f_rndis: fix bitrate for SuperSpeed and above usb: gadget: f_fs: Re-use SS descriptors for SuperSpeedPlus usb: chipidea: ci_hdrc_imx: Pass DISABLE_DEVICE_STREAMING flag to imx6ul ARM: dts: exynos: fix roles of USB 3.0 ports on Odroid XU ARM: dts: exynos: fix USB 3.0 VBUS control and over-current pins on Exynos5410 ARM: dts: exynos: fix USB 3.0 pins supply being turned off on Odroid XU HID: i2c-hid: add Vero K147 to descriptor override serial_core: Check for port state when tty is in error state media: msi2500: assign SPI bus number dynamically md: fix a warning caused by a race between concurrent md_ioctl()s Bluetooth: Fix slab-out-of-bounds read in hci_le_direct_adv_report_evt() drm/gma500: fix double free of gma_connector RDMA/rxe: Compute PSN windows correctly ARM: p2v: fix handling of LPAE translation in BE mode crypto: talitos - Fix return type of current_desc_hdr() spi: img-spfi: fix reference leak in img_spfi_resume ASoC: pcm: DRAIN support reactivation arm64: dts: exynos: Correct psci compatible used on Exynos7 Bluetooth: Fix null pointer dereference in hci_event_packet() spi: spi-ti-qspi: fix reference leak in ti_qspi_setup spi: tegra20-slink: fix reference leak in slink ops of tegra20 spi: tegra20-sflash: fix reference leak in tegra_sflash_resume spi: tegra114: fix reference leak in tegra spi ops RDMa/mthca: Work around -Wenum-conversion warning MIPS: BCM47XX: fix kconfig dependency bug for BCM47XX_BCMA staging: greybus: codecs: Fix reference counter leak in error handling media: solo6x10: fix missing snd_card_free in error handling case drm/omap: dmm_tiler: fix return error code in omap_dmm_probe() Input: ads7846 - fix integer overflow on Rt calculation Input: ads7846 - fix unaligned access on 7845 powerpc/feature: Fix CPU_FTRS_ALWAYS by removing CPU_FTRS_GENERIC_32 crypto: omap-aes - Fix PM disable depth imbalance in omap_aes_probe soc: ti: knav_qmss: fix reference leak in knav_queue_probe soc: ti: Fix reference imbalance in knav_dma_probe drivers: soc: ti: knav_qmss_queue: Fix error return code in knav_queue_probe RDMA/cxgb4: Validate the number of CQEs memstick: fix a double-free bug in memstick_check ARM: dts: at91: sama5d4_xplained: add pincontrol for USB Host ARM: dts: at91: sama5d3_xplained: add pincontrol for USB Host orinoco: Move context allocation after processing the skb cw1200: fix missing destroy_workqueue() on error in cw1200_init_common media: siano: fix memory leak of debugfs members in smsdvb_hotplug mips: cdmm: fix use-after-free in mips_cdmm_bus_discover HSI: omap_ssi: Don't jump to free ID in ssi_add_controller() ARM: dts: at91: at91sam9rl: fix ADC triggers NFSv4.2: condition READDIR's mask for security label based on LSM state SUNRPC: xprt_load_transport() needs to support the netid "rdma6" lockd: don't use interval-based rebinding over TCP NFS: switch nfsiod to be an UNBOUND workqueue. vfio-pci: Use io_remap_pfn_range() for PCI IO memory media: saa7146: fix array overflow in vidioc_s_audio() clocksource/drivers/cadence_ttc: Fix memory leak in ttc_setup_clockevent() pinctrl: falcon: add missing put_device() call in pinctrl_falcon_probe() memstick: r592: Fix error return in r592_probe() ASoC: jz4740-i2s: add missed checks for clk_get() dm ioctl: fix error return code in target_message clocksource/drivers/arm_arch_timer: Correct fault programming of CNTKCTL_EL1.EVNTI cpufreq: highbank: Add missing MODULE_DEVICE_TABLE cpufreq: st: Add missing MODULE_DEVICE_TABLE cpufreq: loongson1: Add missing MODULE_ALIAS cpufreq: scpi: Add missing MODULE_ALIAS scsi: pm80xx: Fix error return in pm8001_pci_probe() seq_buf: Avoid type mismatch for seq_buf_init scsi: fnic: Fix error return code in fnic_probe() powerpc/pseries/hibernation: drop pseries_suspend_begin() from suspend ops usb: ehci-omap: Fix PM disable depth umbalance in ehci_hcd_omap_probe usb: oxu210hp-hcd: Fix memory leak in oxu_create speakup: fix uninitialized flush_lock nfsd: Fix message level for normal termination nfs_common: need lock during iterate through the list x86/kprobes: Restore BTF if the single-stepping is cancelled clk: tegra: Fix duplicated SE clock entry extcon: max77693: Fix modalias string ASoC: wm_adsp: remove "ctl" from list on error in wm_adsp_create_control() irqchip/alpine-msi: Fix freeing of interrupts on allocation error path um: chan_xterm: Fix fd leak nfc: s3fwrn5: Release the nfc firmware powerpc/ps3: use dma_mapping_error() checkpatch: fix unescaped left brace net: bcmgenet: Fix a resource leak in an error handling path in the probe functin net: allwinner: Fix some resources leak in the error handling path of the probe and in the remove function net: korina: fix return value watchdog: qcom: Avoid context switch in restart handler clk: ti: Fix memleak in ti_fapll_synth_setup perf record: Fix memory leak when using '--user-regs=?' to list registers qlcnic: Fix error code in probe clk: s2mps11: Fix a resource leak in error handling paths in the probe function cfg80211: initialize rekey_data Input: cros_ec_keyb - send 'scancodes' in addition to key events Input: goodix - add upside-down quirk for Teclast X98 Pro tablet media: gspca: Fix memory leak in probe media: sunxi-cir: ensure IR is handled when it is continuous media: netup_unidvb: Don't leak SPI master in probe error path Input: cyapa_gen6 - fix out-of-bounds stack access Revert "ACPI / resources: Use AE_CTRL_TERMINATE to terminate resources walks" ACPI: PNP: compare the string length in the matching_id() ALSA: pcm: oss: Fix a few more UBSAN fixes ALSA: usb-audio: Disable sample read check if firmware doesn't give back s390/dasd: prevent inconsistent LCU device data s390/dasd: fix list corruption of pavgroup group list s390/dasd: fix list corruption of lcu list staging: comedi: mf6x4: Fix AI end-of-conversion detection powerpc/perf: Exclude kernel samples while counting events in user space. USB: serial: mos7720: fix parallel-port state restore USB: serial: keyspan_pda: fix dropped unthrottle interrupts USB: serial: keyspan_pda: fix write deadlock USB: serial: keyspan_pda: fix stalled writes USB: serial: keyspan_pda: fix write-wakeup use-after-free USB: serial: keyspan_pda: fix tx-unthrottle use-after-free USB: serial: keyspan_pda: fix write unthrottling btrfs: quota: Set rescan progress to (u64)-1 if we hit last leaf btrfs: scrub: Don't use inode page cache in scrub_handle_errored_block() Btrfs: fix selftests failure due to uninitialized i_mode in test inodes btrfs: fix return value mixup in btrfs_get_extent ext4: fix a memory leak of ext4_free_data KVM: arm64: Introduce handling of AArch32 TTBCR2 traps powerpc/xmon: Change printk() to pr_cont() ceph: fix race in concurrent __ceph_remove_cap invocations jffs2: Fix GC exit abnormally jfs: Fix array index bounds check in dbAdjTree drm/dp_aux_dev: check aux_dev before use in drm_dp_aux_dev_get_by_minor() spi: spi-sh: Fix use-after-free on unbind spi: davinci: Fix use-after-free on unbind spi: pic32: Don't leak DMA channels in probe error path spi: rb4xx: Don't leak SPI master in probe error path spi: sc18is602: Don't leak SPI master in probe error path spi: st-ssc4: Fix unbalanced pm_runtime_disable() in probe error path soc: qcom: smp2p: Safely acquire spinlock without IRQs mtd: parser: cmdline: Fix parsing of part-names with colons iio: buffer: Fix demux update iio: adc: rockchip_saradc: fix missing clk_disable_unprepare() on error in rockchip_saradc_resume iio:pressure:mpl3115: Force alignment of buffer clk: mvebu: a3700: fix the XTAL MODE pin to MPP1_9 xen-blkback: set ring->xenblkd to NULL after kthread_stop() PCI: Fix pci_slot_release() NULL pointer dereference Linux 4.9.249 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com> Change-Id: I4829a32e2ea6e76eefea716f35f42ee02b75c265
971 lines
24 KiB
C
971 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
|
|
|
|
/* 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 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,
|
|
};
|
|
|
|
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 netdev_tx_t 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 NETDEV_TX_BUSY;
|
|
|
|
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;
|
|
|
|
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 */
|
|
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_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_iounmap;
|
|
}
|
|
|
|
db->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(db->clk)) {
|
|
ret = PTR_ERR(db->clk);
|
|
goto out_dispose_mapping;
|
|
}
|
|
|
|
ret = clk_prepare_enable(db->clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", ret);
|
|
goto out_dispose_mapping;
|
|
}
|
|
|
|
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_dispose_mapping:
|
|
irq_dispose_mapping(ndev->irq);
|
|
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);
|
|
irq_dispose_mapping(ndev->irq);
|
|
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");
|