| /* |
| * 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 = 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 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 */ |
| 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_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 */ |
| 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) { |
| free_irq(dev->irq, dev); |
| 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_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; |
| } |
| |
| 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-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"); |