| /* Mediatek STAR MAC network driver. |
| * |
| * Copyright (c) 2016-2017 Mediatek Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/platform_device.h> |
| #include <linux/gpio.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_platform.h> |
| #include <linux/of_address.h> |
| #include <linux/mii.h> |
| #include <linux/ethtool.h> |
| #include <linux/of_net.h> |
| |
| #include "star.h" |
| #include "star_procfs.h" |
| #include "mtk_spm_sleep.h" |
| |
| #define STAR_DRV_NAME "star-eth" |
| #define STAR_DRV_VERSION "version-1.0" |
| #define ETH_WOL_NAME "WOL" |
| |
| int star_dbg_level = STAR_ERR; |
| |
| static void star_finish_xmit(struct net_device *dev); |
| |
| static struct sk_buff *get_skb(struct net_device *ndev) |
| { |
| unsigned char *tail; |
| u32 offset; |
| struct sk_buff *skb; |
| |
| skb = dev_alloc_skb(ndev->mtu + ETH_EXTRA_PKT_LEN); |
| if (!skb) |
| return NULL; |
| |
| /* Shift to 16 byte alignment */ |
| /* while call dev_alloc_skb(), the pointer of |
| * skb->tail & skb->data is the same |
| */ |
| tail = skb_tail_pointer(skb); |
| if (((uintptr_t)tail) & (ETH_SKB_ALIGNMENT - 1)) { |
| offset = ((uintptr_t)tail) & (ETH_SKB_ALIGNMENT - 1); |
| skb_reserve(skb, ETH_SKB_ALIGNMENT - offset); |
| } |
| |
| /* Reserve 2 bytes for zero copy */ |
| /* Reserving 2 bytes makes the skb->data points to |
| * a 16-byte aligned address after eth_type_trans is called. |
| * Since eth_type_trans will extracts the pointer (ETH_LEN) |
| * 14 bytes. With this 2 bytes reserved, the skb->data |
| * can be 16-byte aligned before passing to upper layer. |
| */ |
| skb_reserve(skb, 2); |
| |
| return skb; |
| } |
| |
| /* pre-allocate Rx buffer */ |
| static int alloc_rx_skbs(star_dev *star_dev) |
| { |
| int retval; |
| star_private *star_prv = star_dev->star_prv; |
| |
| do { |
| u32 dmaBuf; |
| struct sk_buff *skb = get_skb(star_prv->dev); |
| |
| if (!skb) { |
| STAR_MSG(STAR_ERR, "Error! No momory for rx sk_buff\n"); |
| return -ENOMEM; |
| } |
| |
| /* Note: |
| * We pass to dma addr with skb->tail-2 (4N aligned), |
| * Because Star Ethernet buffer must 16 byte align |
| * But the RX_OFFSET_2B_DIS has to be set to 0, making |
| * DMA to write tail (4N+2) addr. |
| */ |
| dmaBuf = dma_map_single(star_dev->dev, |
| skb_tail_pointer(skb) - 2, |
| skb_tailroom(skb), |
| DMA_FROM_DEVICE); |
| if (dma_mapping_error(star_dev->dev, dmaBuf)) { |
| STAR_MSG(STAR_ERR, "dma_mapping_error error\n"); |
| return -ENOMEM; |
| } |
| |
| retval = star_dma_rx_set(star_dev, dmaBuf, |
| skb_tailroom(skb), (uintptr_t)skb); |
| STAR_MSG(STAR_VERB, "rx descriptor idx(%d) for skb(%p)\n", |
| retval, skb); |
| if (retval < 0) { |
| dma_unmap_single(star_dev->dev, dmaBuf, |
| skb_tailroom(skb), DMA_FROM_DEVICE); |
| dev_kfree_skb(skb); |
| } |
| } while (retval >= 0); |
| |
| return 0; |
| } |
| |
| /* Free Tx descriptor and skbs not xmited */ |
| static void free_tx_skbs(star_dev *star_dev) |
| { |
| int retval; |
| uintptr_t extBuf; |
| u32 ctrl_len, len, dmaBuf; |
| |
| do { |
| retval = star_dma_tx_get(star_dev, |
| (u32 *)&dmaBuf, &ctrl_len, &extBuf); |
| if (retval >= 0 && extBuf != 0) { |
| len = star_dma_tx_length(ctrl_len); |
| dma_unmap_single(star_dev->dev, dmaBuf, |
| len, DMA_TO_DEVICE); |
| STAR_MSG(STAR_DBG, |
| "get tx desc index(%d) for skb(0x%lx)\n", |
| retval, extBuf); |
| dev_kfree_skb((struct sk_buff *)extBuf); |
| } |
| } while (retval >= 0); |
| } |
| |
| static void free_rx_skbs(star_dev *star_dev) |
| { |
| int retval; |
| uintptr_t extBuf; |
| u32 dmaBuf; |
| |
| /* Free Rx descriptor */ |
| do { |
| retval = star_dma_rx_get(star_dev, |
| (u32 *)&dmaBuf, NULL, &extBuf); |
| if (retval >= 0 && extBuf != 0) { |
| dma_unmap_single(star_dev->dev, dmaBuf, |
| skb_tailroom((struct sk_buff *) |
| extBuf), |
| DMA_FROM_DEVICE); |
| STAR_MSG(STAR_DBG, |
| "get tx desc index(%d) for skb(0x%lx)\n", |
| retval, extBuf); |
| dev_kfree_skb((struct sk_buff *)extBuf); |
| } |
| } while (retval >= 0); |
| } |
| |
| static int receive_one_packet(star_dev *star_dev, bool napi) |
| { |
| int retval; |
| uintptr_t extBuf; |
| u32 ctrl_len, len, dmaBuf; |
| struct sk_buff *curr_skb, *new_skb; |
| star_private *star_prv = star_dev->star_prv; |
| struct net_device *ndev = star_prv->dev; |
| |
| retval = star_dma_rx_get(star_dev, &dmaBuf, &ctrl_len, &extBuf); |
| /*no any skb to receive*/ |
| if (retval < 0) |
| return retval; |
| |
| curr_skb = (struct sk_buff *)extBuf; |
| dma_unmap_single(star_dev->dev, dmaBuf, |
| skb_tailroom(curr_skb), DMA_FROM_DEVICE); |
| STAR_MSG(STAR_VERB, "%s(%s):rx des %d for skb(0x%lx)/length(%d)\n", |
| __func__, ndev->name, retval, extBuf, |
| star_dma_rx_length(ctrl_len)); |
| |
| if (star_dma_rx_valid(ctrl_len)) { |
| len = star_dma_rx_length(ctrl_len); |
| new_skb = get_skb(ndev); |
| if (new_skb) { |
| skb_put(curr_skb, len); |
| curr_skb->ip_summed = CHECKSUM_NONE; |
| curr_skb->protocol = eth_type_trans(curr_skb, ndev); |
| curr_skb->dev = ndev; |
| |
| /* send the packet up protocol stack */ |
| (napi ? netif_receive_skb : netif_rx)(curr_skb); |
| /* set the time of the last receive */ |
| star_dev->stats.rx_packets++; |
| star_dev->stats.rx_bytes += len; |
| } else { |
| star_dev->stats.rx_dropped++; |
| new_skb = curr_skb; |
| } |
| } else { |
| /* Error packet */ |
| new_skb = curr_skb; |
| star_dev->stats.rx_errors++; |
| star_dev->stats.rx_crc_errors += star_dma_rx_crc_err(ctrl_len); |
| } |
| |
| dmaBuf = dma_map_single(star_dev->dev, |
| skb_tail_pointer(new_skb) - 2, |
| skb_tailroom(new_skb), |
| DMA_FROM_DEVICE); |
| star_dma_rx_set(star_dev, dmaBuf, |
| skb_tailroom(new_skb), (uintptr_t)new_skb); |
| |
| return retval; |
| } |
| |
| static int star_poll(struct napi_struct *napi, int budget) |
| { |
| int retval, npackets; |
| star_private *star_prv = container_of(napi, star_private, napi); |
| star_dev *star_dev = &star_prv->star_dev; |
| |
| for (npackets = 0; npackets < budget; npackets++) { |
| retval = receive_one_packet(star_dev, true); |
| if (retval < 0) |
| break; |
| } |
| |
| star_dma_rx_resume(star_dev); |
| |
| if (npackets < budget) { |
| local_irq_disable(); |
| napi_complete(napi); |
| star_intr_rx_enable(star_dev); |
| local_irq_enable(); |
| } |
| |
| return npackets; |
| } |
| |
| /* star tx use tasklet */ |
| static void star_dsr(unsigned long data) |
| { |
| star_private *star_prv; |
| star_dev *star_dev; |
| struct net_device *ndev = (struct net_device *)data; |
| |
| STAR_MSG(STAR_VERB, "%s(%s)\n", __func__, ndev->name); |
| |
| star_prv = netdev_priv(ndev); |
| star_dev = &star_prv->star_dev; |
| |
| if (star_prv->tsk_tx) { |
| star_prv->tsk_tx = false; |
| star_finish_xmit(ndev); |
| } |
| } |
| |
| static irqreturn_t star_isr(int irq, void *dev_id) |
| { |
| u32 intrStatus; |
| u32 intr_clr_msk = 0xffffffff; |
| star_private *star_prv; |
| star_dev *star_dev; |
| struct net_device *dev = (struct net_device *)dev_id; |
| |
| intr_clr_msk &= ~STAR_INT_STA_RXC; |
| |
| if (!dev) { |
| STAR_MSG(STAR_ERR, "star_isr - unknown device\n"); |
| return IRQ_NONE; |
| } |
| |
| STAR_MSG(STAR_VERB, "star_isr(%s)\n", dev->name); |
| |
| star_prv = netdev_priv(dev); |
| star_dev = &star_prv->star_dev; |
| |
| star_intr_disable(star_dev); |
| intrStatus = star_intr_status(star_dev); |
| star_intr_clear(star_dev, intrStatus & intr_clr_msk); |
| |
| do { |
| STAR_MSG(STAR_VERB, |
| "star_isr:interrupt status(0x%08x)\n", intrStatus); |
| if (intrStatus & STAR_INT_STA_RXC) { |
| STAR_MSG(STAR_VERB, "rx complete\n"); |
| /* Disable rx interrupts */ |
| star_intr_rx_disable(star_dev); |
| /* Clear rx interrupt */ |
| star_intr_clear(star_dev, STAR_INT_STA_RXC); |
| napi_schedule(&star_prv->napi); |
| } |
| |
| if (intrStatus & STAR_INT_STA_RXQF) |
| STAR_MSG(STAR_VERB, "rx queue full\n"); |
| |
| if (intrStatus & STAR_INT_STA_RXFIFOFULL) |
| STAR_MSG(STAR_WARN, "rx fifo full\n"); |
| |
| if (intrStatus & STAR_INT_STA_TXC) { |
| STAR_MSG(STAR_VERB, " tx complete\n"); |
| star_prv->tsk_tx = true; |
| } |
| |
| if (intrStatus & STAR_INT_STA_TXQE) |
| STAR_MSG(STAR_VERB, "tx queue empty\n"); |
| |
| if (intrStatus & STAR_INT_STA_RX_PCODE) |
| STAR_MSG(STAR_DBG, "Rx PCODE\n"); |
| |
| if (intrStatus & STAR_INT_STA_MAGICPKT) |
| STAR_MSG(STAR_WARN, "magic packet received\n"); |
| |
| if (intrStatus & STAR_INT_STA_MIBCNTHALF) { |
| STAR_MSG(STAR_VERB, " mib counter reach 2G\n"); |
| star_mib_init(star_dev); |
| } |
| |
| if (intrStatus & STAR_INT_STA_PORTCHANGE) { |
| STAR_MSG(STAR_DBG, "port status change\n"); |
| star_link_status_change(star_dev); |
| } |
| |
| /* read interrupt requests came during interrupt handling */ |
| intrStatus = star_intr_status(star_dev); |
| star_intr_clear(star_dev, intrStatus & intr_clr_msk); |
| } while ((intrStatus & intr_clr_msk) != 0); |
| |
| star_intr_enable(star_dev); |
| if (star_prv->tsk_tx) |
| tasklet_schedule(&star_prv->dsr); |
| |
| STAR_MSG(STAR_VERB, "star_isr return\n"); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #ifdef CONFIG_STAR_USE_RMII_MODE |
| static irqreturn_t star_eint_isr(int irq, void *dev_id) |
| { |
| STAR_MSG(STAR_DBG, "enter star_eint_isr\n"); |
| |
| return IRQ_HANDLED; |
| } |
| #endif |
| |
| #ifdef CONFIG_NET_POLL_CONTROLLER |
| static void star_netpoll(struct net_device *dev) |
| { |
| star_private *tp = netdev_priv(dev); |
| star_dev *pdev = tp->mii.dev; |
| |
| disable_irq(pdev->irq); |
| star_isr(pdev->irq, dev); |
| enable_irq(pdev->irq); |
| } |
| #endif |
| |
| static int star_mac_enable(struct net_device *ndev) |
| { |
| int intrStatus; |
| star_private *star_prv = netdev_priv(ndev); |
| star_dev *star_dev = &star_prv->star_dev; |
| |
| STAR_MSG(STAR_DBG, "%s(%s)\n", __func__, ndev->name); |
| |
| /* Start RX FIFO receive */ |
| star_nic_pdset(star_dev, false); |
| |
| star_intr_disable(star_dev); |
| star_dma_tx_stop(star_dev); |
| star_dma_rx_stop(star_dev); |
| |
| netif_carrier_off(ndev); |
| |
| star_mac_init(star_dev, ndev->dev_addr); |
| |
| star_dma_init(star_dev, |
| star_prv->desc_vir_addr, star_prv->desc_dma_addr); |
| |
| /*Enable PHY auto-polling*/ |
| star_phyctrl_init(star_dev, 1, star_prv->phy_addr); |
| |
| if (alloc_rx_skbs(star_dev)) { |
| STAR_MSG(STAR_ERR, "rx bufs init fail\n"); |
| return -ENOMEM; |
| } |
| |
| STAR_MSG(STAR_DBG, "request interrupt vector=%d\n", ndev->irq); |
| if (request_irq(ndev->irq, star_isr, IRQF_TRIGGER_FALLING, |
| ndev->name, ndev) != 0) { |
| STAR_MSG(STAR_ERR, "interrupt %d request fail\n", ndev->irq); |
| return -ENODEV; |
| } |
| |
| #ifdef CONFIG_STAR_USE_RMII_MODE |
| STAR_MSG(STAR_DBG, "request eint_irq vector=%d\n", star_prv->eint_irq); |
| if (request_irq(star_prv->eint_irq, star_eint_isr, |
| IRQ_TYPE_EDGE_FALLING, ndev->name, ndev) != 0) { |
| STAR_MSG(STAR_ERR, |
| "eint_irq %d request fail\n", star_prv->eint_irq); |
| return -ENODEV; |
| } |
| #endif |
| |
| napi_enable(&star_prv->napi); |
| |
| intrStatus = star_intr_status(star_dev); |
| star_intr_clear(star_dev, intrStatus); |
| star_intr_enable(star_dev); |
| |
| star_dev->phy_ops->init(star_dev); |
| |
| dma_tx_start_and_reset_tx_desc(star_dev); |
| dma_rx_start_and_reset_rx_desc(star_dev); |
| |
| star_link_status_change(star_dev); |
| netif_start_queue(ndev); |
| |
| return 0; |
| } |
| |
| static void star_mac_disable(struct net_device *ndev) |
| { |
| int intrStatus; |
| star_private *star_prv = netdev_priv(ndev); |
| star_dev *star_dev = &star_prv->star_dev; |
| |
| STAR_MSG(STAR_DBG, "%s(%s)\n", __func__, ndev->name); |
| |
| netif_stop_queue(ndev); |
| |
| napi_disable(&star_prv->napi); |
| |
| star_intr_disable(star_dev); |
| star_dma_tx_stop(star_dev); |
| star_dma_rx_stop(star_dev); |
| intrStatus = star_intr_status(star_dev); |
| star_intr_clear(star_dev, intrStatus); |
| |
| free_irq(ndev->irq, ndev); |
| #ifdef CONFIG_STAR_USE_RMII_MODE |
| free_irq(star_prv->eint_irq, ndev); |
| #endif |
| |
| /* Free Tx descriptor */ |
| free_tx_skbs(star_dev); |
| |
| /* Free Rx descriptor */ |
| free_rx_skbs(star_dev); |
| } |
| |
| static int star_open(struct net_device *ndev) |
| { |
| int ret; |
| star_private *star_prv = netdev_priv(ndev); |
| |
| STAR_MSG(STAR_DBG, "star_open(%s)\n", ndev->name); |
| |
| if (star_prv->opened) { |
| STAR_MSG(STAR_DBG, "%s(%s) is already open\n", |
| __func__, ndev->name); |
| return 0; |
| } |
| |
| ret = star_mac_enable(ndev); |
| if (ret) { |
| STAR_MSG(STAR_DBG, "star_mac_enable(%s) fail\n", ndev->name); |
| return ret; |
| } |
| |
| star_prv->opened = true; |
| |
| return 0; |
| } |
| |
| static int star_stop(struct net_device *ndev) |
| { |
| star_private *star_prv = netdev_priv(ndev); |
| |
| STAR_MSG(STAR_DBG, "enter %s\n", __func__); |
| if (!star_prv->opened) { |
| STAR_MSG(STAR_DBG, "%s(%s) is already close\n", __func__, |
| ndev->name); |
| return 0; |
| } |
| |
| star_mac_disable(ndev); |
| star_prv->opened = false; |
| |
| return 0; |
| } |
| |
| static int star_start_xmit(struct sk_buff *skb, struct net_device *ndev) |
| { |
| u32 dmaBuf; |
| unsigned long flags; |
| star_private *star_prv; |
| star_dev *star_dev; |
| |
| star_prv = netdev_priv(ndev); |
| star_dev = &star_prv->star_dev; |
| |
| /* If frame size > Max frame size, drop this packet */ |
| if (skb->len > ETH_MAX_FRAME_SIZE) { |
| STAR_MSG(STAR_WARN, "%s:Tx frame len is oversized(%d bytes)\n", |
| ndev->name, skb->len); |
| dev_kfree_skb(skb); |
| star_dev->stats.tx_dropped++; |
| return NETDEV_TX_OK; |
| } |
| |
| dmaBuf = dma_map_single(star_dev->dev, |
| skb->data, skb_headlen(skb), DMA_TO_DEVICE); |
| if (unlikely(dma_mapping_error(star_dev->dev, dmaBuf))) { |
| STAR_MSG(STAR_ERR, "%s,dma_mapping_error error\n", __func__); |
| return -ENOMEM; |
| } |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| star_dma_tx_set(star_dev, dmaBuf, skb->len, (uintptr_t)skb); |
| /* Tx descriptor ring full */ |
| if (star_dev->tx_num == star_dev->tx_ring_size) |
| netif_stop_queue(ndev); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| star_dma_tx_resume(star_dev); |
| |
| return NETDEV_TX_OK; |
| } |
| |
| static void star_finish_xmit(struct net_device *ndev) |
| { |
| int retval, wake = 0; |
| star_private *star_prv; |
| star_dev *star_dev; |
| |
| star_prv = netdev_priv(ndev); |
| star_dev = &star_prv->star_dev; |
| |
| do { |
| uintptr_t extBuf; |
| u32 ctrl_len; |
| u32 len; |
| u32 dmaBuf; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| retval = star_dma_tx_get(star_dev, (u32 *)&dmaBuf, |
| &ctrl_len, &extBuf); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| |
| if (retval >= 0 && extBuf != 0) { |
| len = star_dma_tx_length(ctrl_len); |
| dma_unmap_single(star_dev->dev, |
| dmaBuf, len, DMA_TO_DEVICE); |
| STAR_MSG(STAR_VERB, |
| "%s get tx desc(%d) for skb(0x%lx), len(%08x)\n", |
| __func__, retval, extBuf, len); |
| |
| dev_kfree_skb_irq((struct sk_buff *)extBuf); |
| |
| star_dev->stats.tx_bytes += len; |
| star_dev->stats.tx_packets++; |
| wake = 1; |
| } |
| } while (retval >= 0); |
| |
| if (wake) |
| netif_wake_queue(ndev); |
| } |
| |
| static struct net_device_stats *star_get_stats(struct net_device *ndev) |
| { |
| star_private *star_prv; |
| star_dev *star_dev; |
| |
| STAR_MSG(STAR_VERB, "enter %s\n", __func__); |
| |
| star_prv = netdev_priv(ndev); |
| star_dev = &star_prv->star_dev; |
| |
| return &star_dev->stats; |
| } |
| |
| #define STAR_HTABLE_SIZE 512 |
| #define STAR_HTABLE_SIZE_LIMIT (STAR_HTABLE_SIZE >> 1) |
| |
| static void star_set_multicast_list(struct net_device *ndev) |
| { |
| unsigned long flags; |
| star_private *star_prv; |
| star_dev *star_dev; |
| |
| STAR_MSG(STAR_VERB, "enter %s\n", __func__); |
| |
| star_prv = netdev_priv(ndev); |
| star_dev = &star_prv->star_dev; |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| |
| if (ndev->flags & IFF_PROMISC) { |
| STAR_MSG(STAR_WARN, "%s: Promiscuous mode enabled.\n", |
| ndev->name); |
| star_arl_promisc_enable(star_dev); |
| } else if ((netdev_mc_count(ndev) > STAR_HTABLE_SIZE_LIMIT) || |
| (ndev->flags & IFF_ALLMULTI)) { |
| u32 hashIdx; |
| |
| for (hashIdx = 0; hashIdx < STAR_HTABLE_SIZE; hashIdx++) |
| star_set_hashbit(star_dev, hashIdx, 1); |
| } else { |
| struct netdev_hw_addr *ha; |
| |
| netdev_for_each_mc_addr(ha, ndev) { |
| u32 hashAddr; |
| |
| hashAddr = (u32)(((ha->addr[0] & 0x1) << 8) |
| + (u32)(ha->addr[5])); |
| star_set_hashbit(star_dev, hashAddr, 1); |
| } |
| } |
| |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| } |
| |
| static int star_ioctl(struct net_device *dev, struct ifreq *req, int cmd) |
| { |
| star_private *star_prv = netdev_priv(dev); |
| unsigned long flags; |
| int rc = 0; |
| |
| if (!netif_running(dev)) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| rc = generic_mii_ioctl(&star_prv->mii, if_mii(req), cmd, NULL); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| |
| return rc; |
| } |
| |
| static void star_tx_timeout(struct net_device *ndev) |
| { |
| bool state; |
| int ret; |
| |
| STAR_MSG(STAR_ERR, "%s tx timeout \n", __func__); |
| STAR_MSG(STAR_DBG, "request interrupt vector=%d\n", ndev->irq); |
| ret = irq_get_irqchip_state(ndev->irq, IRQCHIP_STATE_MASKED, &state); |
| STAR_MSG(STAR_DBG, "irq mask status(ret=%d)=0x%x\n", ret, state); |
| ret = irq_get_irqchip_state(ndev->irq, IRQCHIP_STATE_PENDING, &state); |
| STAR_MSG(STAR_DBG, "irq pending status(ret=%d)=0x%x\n", ret, state); |
| ret = irq_get_irqchip_state(ndev->irq, IRQCHIP_STATE_ACTIVE, &state); |
| STAR_MSG(STAR_DBG, "irq active status(ret=%d)=0x%x\n", ret, state); |
| } |
| |
| static int mdcmdio_read(struct net_device *dev, int phy_id, int location) |
| { |
| star_private *star_prv; |
| star_dev *star_dev; |
| |
| star_prv = netdev_priv(dev); |
| star_dev = &star_prv->star_dev; |
| |
| return star_mdc_mdio_read(star_dev, phy_id, location); |
| } |
| |
| static void mdcmdio_write(struct net_device *dev, int phy_id, |
| int location, int val) |
| { |
| star_private *star_prv; |
| star_dev *star_dev; |
| |
| star_prv = netdev_priv(dev); |
| star_dev = &star_prv->star_dev; |
| star_mdc_mdio_write(star_dev, phy_id, location, val); |
| } |
| |
| const struct net_device_ops star_netdev_ops = { |
| .ndo_open = star_open, |
| .ndo_stop = star_stop, |
| .ndo_start_xmit = star_start_xmit, |
| .ndo_get_stats = star_get_stats, |
| .ndo_set_rx_mode = star_set_multicast_list, |
| .ndo_do_ioctl = star_ioctl, |
| .ndo_tx_timeout = star_tx_timeout, |
| #ifdef CONFIG_NET_POLL_CONTROLLER |
| .ndo_poll_controller = star_netpoll, |
| #endif |
| .ndo_change_mtu = eth_change_mtu, |
| .ndo_set_mac_address = eth_mac_addr, |
| .ndo_validate_addr = eth_validate_addr, |
| }; |
| |
| static int starmac_get_link_ksettings(struct net_device *ndev, |
| struct ethtool_link_ksettings *settings) |
| { |
| unsigned long flags; |
| star_private *star_prv = netdev_priv(ndev); |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| mii_ethtool_get_link_ksettings(&star_prv->mii, settings); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| |
| return 0; |
| } |
| |
| static int starmac_set_link_ksettings(struct net_device *ndev, |
| const struct ethtool_link_ksettings *settings) |
| { |
| int ret; |
| unsigned long flags; |
| star_private *star_prv = netdev_priv(ndev); |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| ret = mii_ethtool_set_link_ksettings(&star_prv->mii, settings); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| |
| return ret; |
| } |
| |
| static int starmac_nway_reset(struct net_device *ndev) |
| { |
| int ret; |
| unsigned long flags; |
| star_private *star_prv = netdev_priv(ndev); |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| ret = mii_nway_restart(&star_prv->mii); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| |
| return ret; |
| } |
| |
| static u32 starmac_get_link(struct net_device *ndev) |
| { |
| u32 ret; |
| unsigned long flags; |
| star_private *star_prv = netdev_priv(ndev); |
| |
| spin_lock_irqsave(&star_prv->lock, flags); |
| ret = mii_link_ok(&star_prv->mii); |
| spin_unlock_irqrestore(&star_prv->lock, flags); |
| STAR_MSG(STAR_DBG, "ETHTOOL_TEST is called\n"); |
| |
| return ret; |
| } |
| |
| static int starmac_check_if_running(struct net_device *dev) |
| { |
| if (!netif_running(dev)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static void starmac_get_drvinfo(struct net_device *dev, |
| struct ethtool_drvinfo *info) |
| { |
| strlcpy(info->driver, STAR_DRV_NAME, sizeof(info->driver)); |
| strlcpy(info->version, STAR_DRV_VERSION, sizeof(info->version)); |
| } |
| |
| static struct ethtool_ops starmac_ethtool_ops = { |
| .begin = starmac_check_if_running, |
| .get_drvinfo = starmac_get_drvinfo, |
| .get_link_ksettings = starmac_get_link_ksettings, |
| .set_link_ksettings = starmac_set_link_ksettings, |
| .nway_reset = starmac_nway_reset, |
| .get_link = starmac_get_link, |
| }; |
| |
| int star_get_wol_flag(star_private *star_prv) |
| { |
| return star_prv->support_wol; |
| } |
| |
| void star_set_wol_flag(star_private *star_prv, bool flag) |
| { |
| star_prv->support_wol = flag; |
| } |
| |
| static int star_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct net_device *netdev = platform_get_drvdata(pdev); |
| star_private *star_prv = netdev_priv(netdev); |
| star_dev *star_dev = &star_prv->star_dev; |
| |
| STAR_MSG(STAR_DBG, "entered %s, line(%d)\n", __func__, __LINE__); |
| |
| if (star_prv->opened) { |
| if (WOL_NONE == star_prv->wol) { |
| STAR_MSG(STAR_DBG, "Not support wol.\n"); |
| star_mac_disable(netdev); |
| clk_disable_unprepare(star_prv->core_clk); |
| clk_disable_unprepare(star_prv->reg_clk); |
| clk_disable_unprepare(star_prv->trans_clk); |
| regulator_disable(star_prv->phy_regulator); |
| } else if (MAC_WOL == star_prv->wol) { |
| STAR_MSG(STAR_DBG, "support mac wol.\n"); |
| // spm_set_sleep_26m_req(1); |
| star_config_wol(star_dev, true); |
| } else if (PHY_WOL == star_prv->wol) { |
| STAR_MSG(STAR_DBG, "support phy wol.\n"); |
| star_mac_disable(netdev); |
| if (star_dev->phy_ops->wol_enable) |
| star_dev->phy_ops->wol_enable(netdev); |
| enable_irq_wake(star_prv->eint_irq); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int star_resume(struct platform_device *pdev) |
| { |
| struct net_device *netdev = platform_get_drvdata(pdev); |
| star_private *star_prv = netdev_priv(netdev); |
| star_dev *star_dev = &star_prv->star_dev; |
| int ret; |
| |
| STAR_MSG(STAR_DBG, "entered %s(%s)\n", __func__, netdev->name); |
| |
| if (star_prv->opened) { |
| if (WOL_NONE == star_prv->wol) { |
| STAR_MSG(STAR_DBG, "Not support wol.\n"); |
| ret = regulator_enable(star_prv->phy_regulator); |
| if (ret != 0) |
| STAR_MSG(STAR_ERR, "failed to regulator_enable(%d)\n", ret); |
| |
| ret = clk_prepare_enable(star_prv->core_clk); |
| if (ret < 0) |
| STAR_MSG(STAR_ERR, "failed to enable core-clk (%d)\n", ret); |
| |
| ret = clk_prepare_enable(star_prv->reg_clk); |
| if (ret < 0) |
| STAR_MSG(STAR_ERR, "failed to enable reg-clk (%d)\n", ret); |
| |
| ret = clk_prepare_enable(star_prv->trans_clk); |
| if (ret < 0) |
| STAR_MSG(STAR_ERR, "failed to enable trans-clk (%d)\n", ret); |
| |
| star_hw_init(star_dev); |
| star_mac_enable(netdev); |
| } else if (MAC_WOL == star_prv->wol) { |
| STAR_MSG(STAR_DBG, "support mac wol.\n"); |
| star_config_wol(star_dev, false); |
| // spm_set_sleep_26m_req(0); |
| } else if (PHY_WOL == star_prv->wol) { |
| STAR_MSG(STAR_DBG, "support phy wol.\n"); |
| if (star_dev->phy_ops->wol_disable) |
| star_dev->phy_ops->wol_disable(netdev); |
| disable_irq_wake(star_prv->eint_irq); |
| star_hw_init(star_dev); |
| star_mac_enable(netdev); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int star_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| star_private *star_prv; |
| star_dev *star_dev; |
| struct net_device *netdev; |
| struct device_node *np; |
| const char *mac_addr; |
| |
| star_set_dbg_level(STAR_DBG); |
| STAR_MSG(STAR_DBG, "%s entered\n", __func__); |
| |
| netdev = alloc_etherdev(sizeof(star_private)); |
| if (!netdev) |
| return -ENOMEM; |
| |
| pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); |
| pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; |
| |
| SET_NETDEV_DEV(netdev, &pdev->dev); |
| |
| star_prv = netdev_priv(netdev); |
| memset(star_prv, 0, sizeof(star_private)); |
| star_prv->dev = netdev; |
| /* defalt close eth */ |
| star_prv->opened = false; |
| |
| #ifdef ETH_SUPPORT_WOL |
| STAR_MSG(STAR_DBG, "%s() support WOL\n", __func__); |
| star_prv->support_wol = true; |
| #endif |
| star_dev = &star_prv->star_dev; |
| star_dev->dev = &pdev->dev; |
| |
| np = of_find_compatible_node(NULL, NULL, "mediatek,mt8516-ethernet"); |
| if (!np) { |
| STAR_MSG(STAR_ERR, "%s, fail to find node\n", __func__); |
| ret = -EINVAL; |
| goto err_free_netdev; |
| } |
| |
| star_prv->core_clk = devm_clk_get(&pdev->dev, "core"); |
| if (IS_ERR(star_prv->core_clk)) { |
| ret = PTR_ERR(star_prv->core_clk); |
| STAR_MSG(STAR_ERR, "failed to get core-clk: %d\n", ret); |
| goto err_free_netdev; |
| } |
| ret = clk_prepare_enable(star_prv->core_clk); |
| if (ret < 0) { |
| STAR_MSG(STAR_ERR, "failed to enable core-clk (%d)\n", ret); |
| goto err_free_netdev; |
| } |
| |
| star_prv->reg_clk = devm_clk_get(&pdev->dev, "reg"); |
| if (IS_ERR(star_prv->reg_clk)) { |
| ret = PTR_ERR(star_prv->reg_clk); |
| STAR_MSG(STAR_ERR, "failed to get reg-clk: %d\n", ret); |
| goto err_free_netdev; |
| } |
| ret = clk_prepare_enable(star_prv->reg_clk); |
| if (ret < 0) { |
| STAR_MSG(STAR_ERR, "failed to enable reg-clk (%d)\n", ret); |
| goto err_free_netdev; |
| } |
| |
| star_prv->trans_clk = devm_clk_get(&pdev->dev, "trans"); |
| if (IS_ERR(star_prv->trans_clk)) { |
| ret = PTR_ERR(star_prv->trans_clk); |
| STAR_MSG(STAR_ERR, "failed to get trans-clk: %d\n", ret); |
| goto err_free_netdev; |
| } |
| ret = clk_prepare_enable(star_prv->trans_clk); |
| if (ret < 0) { |
| STAR_MSG(STAR_ERR, "failed to enable trans-clk (%d)\n", ret); |
| goto err_free_netdev; |
| } |
| |
| star_prv->phy_regulator = devm_regulator_get(&pdev->dev, "eth-regulator"); |
| ret = regulator_set_voltage(star_prv->phy_regulator, 3300000, 3300000); |
| if (ret != 0) { |
| STAR_MSG(STAR_ERR, "failed to regulator_set_voltage(%d)\n", ret); |
| return ret; |
| } |
| ret = regulator_enable(star_prv->phy_regulator); |
| if (ret != 0) { |
| STAR_MSG(STAR_ERR, "failed to regulator_enable(%d)\n", ret); |
| return ret; |
| } |
| |
| star_dev->base = of_iomap(np, 0); |
| if (!star_dev->base) { |
| STAR_MSG(STAR_ERR, "fail to ioremap eth!\n"); |
| ret = -ENOMEM; |
| goto err_free_netdev; |
| } |
| |
| star_dev->pericfg_base = of_iomap(np, 1); |
| if (!star_dev->pericfg_base) { |
| STAR_MSG(STAR_ERR, "fail to ioremap pericfg_base!\n"); |
| ret = -ENOMEM; |
| goto err_free_netdev; |
| } |
| |
| STAR_MSG(STAR_DBG, "BASE: mac(0x%p), clk(0x%p)\n", |
| star_dev->base, star_dev->pericfg_base); |
| |
| #ifdef CONFIG_STAR_USE_RMII_MODE |
| star_switch_to_rmii_mode(star_dev); |
| #endif |
| |
| tasklet_init(&star_prv->dsr, star_dsr, (unsigned long)netdev); |
| |
| /* Init system locks */ |
| spin_lock_init(&star_prv->lock); |
| |
| star_prv->desc_vir_addr = |
| (uintptr_t)dma_alloc_coherent(star_dev->dev, |
| TX_DESC_TOTAL_SIZE + |
| RX_DESC_TOTAL_SIZE, |
| &star_prv->desc_dma_addr, |
| GFP_KERNEL | GFP_DMA); |
| if (!star_prv->desc_vir_addr) { |
| STAR_MSG(STAR_ERR, "fail to dma_alloc_coherent!!\n"); |
| ret = -ENOMEM; |
| goto alloc_desc_fail; |
| } |
| |
| star_dev->star_prv = star_prv; |
| |
| STAR_MSG(STAR_DBG, "Ethernet disable powerdown!\n"); |
| star_nic_pdset(star_dev, false); |
| |
| star_hw_init(star_dev); |
| |
| /* Get PHY ID */ |
| star_prv->phy_addr = star_detect_phyid(star_dev); |
| if (star_prv->phy_addr == 32) { |
| STAR_MSG(STAR_ERR, "can't detect phy_addr,default to %d\n", |
| star_prv->phy_addr); |
| ret = -ENODEV; |
| goto phy_detect_fail; |
| } else { |
| STAR_MSG(STAR_WARN, "PHY addr = 0x%04x\n", star_prv->phy_addr); |
| } |
| |
| star_prv->mii.phy_id = star_prv->phy_addr; |
| star_prv->mii.dev = netdev; |
| star_prv->mii.mdio_read = mdcmdio_read; |
| star_prv->mii.mdio_write = mdcmdio_write; |
| star_prv->mii.phy_id_mask = 0x1f; |
| star_prv->mii.reg_num_mask = 0x1f; |
| |
| /* Set MAC address */ |
| mac_addr = of_get_mac_address(np); |
| if (!IS_ERR_OR_NULL(mac_addr)) |
| ether_addr_copy(netdev->dev_addr, mac_addr); |
| |
| STAR_MSG(STAR_DBG, "default netdev->dev_addr(%pM).\n", netdev->dev_addr); |
| /* If the mac address is invalid, use random mac address */ |
| if (!is_valid_ether_addr(netdev->dev_addr)) { |
| random_ether_addr(netdev->dev_addr); |
| STAR_MSG(STAR_WARN, "generated random MAC address %pM\n", |
| netdev->dev_addr); |
| netdev->addr_assign_type = NET_ADDR_RANDOM; |
| } |
| |
| netdev->irq = platform_get_irq(pdev, 0); |
| if (netdev->irq < 0) { |
| STAR_MSG(STAR_ERR, "no IRQ resource found\n"); |
| goto phy_detect_fail; |
| } |
| STAR_MSG(STAR_DBG, "eth irq (%d)\n", netdev->irq); |
| |
| #ifdef CONFIG_STAR_USE_RMII_MODE |
| star_prv->eint_pin = of_get_named_gpio(np, "eth-gpios", 0); |
| if (star_prv->eint_pin < 0) |
| STAR_MSG(STAR_DBG, "not find eth-gpio\n"); |
| star_prv->eint_irq = gpio_to_irq(star_prv->eint_pin); |
| #endif |
| star_prv->wol = WOL_NONE; |
| star_prv->wol_flag = false; |
| |
| netdev->base_addr = (unsigned long)star_dev->base; |
| netdev->netdev_ops = &star_netdev_ops; |
| |
| STAR_MSG(STAR_DBG, "EthTool installed\n"); |
| netdev->ethtool_ops = &starmac_ethtool_ops; |
| |
| netif_napi_add(netdev, &star_prv->napi, star_poll, STAR_NAPI_WEIGHT); |
| |
| ret = register_netdev(netdev); |
| if (ret) |
| goto phy_detect_fail; |
| |
| platform_set_drvdata(pdev, netdev); |
| |
| ret = star_init_procfs(); |
| if (ret) |
| STAR_MSG(STAR_WARN, "star_init_procfs fail\n"); |
| |
| STAR_MSG(STAR_DBG, "star_probe success.\n"); |
| |
| return 0; |
| |
| phy_detect_fail: |
| dma_free_coherent(star_dev->dev, |
| TX_DESC_TOTAL_SIZE + RX_DESC_TOTAL_SIZE, |
| (void *)star_prv->desc_vir_addr, |
| star_prv->desc_dma_addr); |
| |
| alloc_desc_fail: |
| free_netdev(netdev); |
| err_free_netdev: |
| unregister_netdev(netdev); |
| STAR_MSG(STAR_ERR, "Star MAC init fail\n"); |
| return ret; |
| } |
| |
| static int star_remove(struct platform_device *pdev) |
| { |
| struct net_device *netdev = platform_get_drvdata(pdev); |
| star_private *star_prv = netdev_priv(netdev); |
| star_dev *star_dev = &star_prv->star_dev; |
| |
| star_exit_procfs(); |
| |
| unregister_netdev(netdev); |
| |
| dma_free_coherent(star_dev->dev, |
| TX_DESC_TOTAL_SIZE + RX_DESC_TOTAL_SIZE, |
| (void *)star_prv->desc_vir_addr, |
| star_prv->desc_dma_addr); |
| |
| free_netdev(netdev); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id star_of_match[] = { |
| { .compatible = "mediatek,mt8516-ethernet", }, |
| {}, |
| }; |
| |
| static struct platform_device *star_pdev; |
| |
| static struct platform_driver star_pdrv = { |
| .driver = { |
| .name = STAR_DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = star_of_match, |
| }, |
| .probe = star_probe, |
| .suspend = star_suspend, |
| .resume = star_resume, |
| .remove = star_remove, |
| }; |
| |
| static int __init star_init(void) |
| { |
| int err; |
| |
| STAR_MSG(STAR_DBG, "enter %s\n", __func__); |
| |
| err = platform_driver_register(&star_pdrv); |
| if (err) |
| return err; |
| |
| STAR_MSG(STAR_DBG, "%s success.\n", __func__); |
| return 0; |
| } |
| |
| static void __exit star_exit(void) |
| { |
| platform_device_unregister(star_pdev); |
| platform_driver_unregister(&star_pdrv); |
| STAR_MSG(STAR_DBG, "%s ...\n", __func__); |
| } |
| |
| module_init(star_init); |
| module_exit(star_exit); |
| |
| MODULE_AUTHOR("Leilk Liu <leilk.liu@mediatek.com>"); |
| MODULE_DESCRIPTION("Mediatek STAR Network Driver"); |
| MODULE_LICENSE("GPL"); |
| |