/**************************************************************************** * drivers/net/lan91c111.c * * Copyright (C) 2018 Pinecone Inc. All rights reserved. * Author: Xiang Xiao * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lan91c111.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Work queue support is required. */ #if !defined(CONFIG_SCHED_WORKQUEUE) # error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE) #else /* Use the selected work queue */ # if defined(CONFIG_LAN91C111_HPWORK) # define LAN91C111_WORK HPWORK # elif defined(CONFIG_LAN91C111_LPWORK) # define LAN91C111_WORK LPWORK # else # error Neither CONFIG_LAN91C111_HPWORK nor CONFIG_LAN91C111_LPWORK defined # endif #endif #ifdef CONFIG_NET_DUMPPACKET # define lan91c111_dumppacket lib_dumpbuffer #else # define lan91c111_dumppacket(m, b, l) #endif /* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per second */ #define LAN91C111_WDDELAY (1*CLK_TCK) /* MII busy delay = 1 microsecond */ #define LAN91C111_MIIDELAY 1 /**************************************************************************** * Private Types ****************************************************************************/ /* lan91c111_driver_s encapsulates all state information for a single * hardware interface */ struct lan91c111_driver_s { uintptr_t base; /* Base address */ int irq; /* IRQ number */ uint16_t bank; /* Current bank */ WDOG_ID txpoll; /* TX poll timer */ struct work_s irqwork; /* For deferring interrupt work to the work queue */ struct work_s pollwork; /* For deferring poll work to the work queue */ uint8_t pktbuf[MAX_NETDEV_PKTSIZE + 4]; /* +4 due to getregs32/putregs32 */ /* This holds the information visible to the NuttX network */ struct net_driver_s dev; /* Interface understood by the network */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Common TX logic */ static int lan91c111_transmit(FAR struct net_driver_s *dev); static int lan91c111_txpoll(FAR struct net_driver_s *dev); /* Interrupt handling */ static void lan91c111_reply(FAR struct net_driver_s *dev); static void lan91c111_receive(FAR struct net_driver_s *dev); static void lan91c111_txdone(FAR struct net_driver_s *dev); static void lan91c111_interrupt_work(FAR void *arg); static int lan91c111_interrupt(int irq, FAR void *context, FAR void *arg); /* Watchdog timer expirations */ static void lan91c111_poll_work(FAR void *arg); static void lan91c111_poll_expiry(int argc, wdparm_t arg, ...); /* NuttX callback functions */ static int lan91c111_ifup(FAR struct net_driver_s *dev); static int lan91c111_ifdown(FAR struct net_driver_s *dev); static void lan91c111_txavail_work(FAR void *arg); static int lan91c111_txavail(FAR struct net_driver_s *dev); #if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6) static int lan91c111_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac); #ifdef CONFIG_NET_IGMP static int lan91c111_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac); #endif #ifdef CONFIG_NET_ICMPv6 static void lan91c111_ipv6multicast(FAR struct net_driver_s *dev); #endif #endif #ifdef CONFIG_NETDEV_IOCTL static int lan91c111_ioctl(FAR struct net_driver_s *dev, int cmd, unsigned long arg); #endif /**************************************************************************** * Private Functions ****************************************************************************/ /* MAC register access, assume lock hold by caller */ static uint16_t updatebank(FAR struct lan91c111_driver_s *priv, uint16_t offset) { uint16_t bank = offset >> 8; if (bank != priv->bank) { *(FAR volatile uint16_t *)(priv->base + BSR_REG) = bank; priv->bank = bank; } return offset & 0xff; } static uint8_t getreg8(FAR struct lan91c111_driver_s *priv, uint16_t offset) { offset = updatebank(priv, offset); return *(FAR volatile uint8_t *)(priv->base + offset); } static uint16_t getreg16(FAR struct lan91c111_driver_s *priv, uint16_t offset) { offset = updatebank(priv, offset); return *(FAR volatile uint16_t *)(priv->base + offset); } static void putreg8(FAR struct lan91c111_driver_s *priv, uint16_t offset, uint8_t value) { offset = updatebank(priv, offset); *(FAR volatile uint8_t *)(priv->base + offset) = value; } static void putreg16(FAR struct lan91c111_driver_s *priv, uint16_t offset, uint16_t value) { offset = updatebank(priv, offset); *(FAR volatile uint16_t *)(priv->base + offset) = value; } static void modifyreg16(FAR struct lan91c111_driver_s *priv, uint16_t offset, uint16_t clearbits, uint16_t setbits) { uint16_t value; offset = updatebank(priv, offset); value = *(FAR volatile uint16_t *)(priv->base + offset); value &= ~clearbits; value |= setbits; *(FAR volatile uint16_t *)(priv->base + offset) = value; } static void getregs32(FAR struct lan91c111_driver_s *priv, uint16_t offset, void *value_, size_t length) { FAR uint32_t *value = value_; size_t i; offset = updatebank(priv, offset); for (i = 0; i < length; i += sizeof(*value)) { *value++ = *(FAR volatile uint32_t *)(priv->base + offset); } } static void putregs32(FAR struct lan91c111_driver_s *priv, uint16_t offset, const void *value_, size_t length) { FAR const uint32_t *value = value_; size_t i; offset = updatebank(priv, offset); for (i = 0; i < length; i += sizeof(*value)) { *(FAR volatile uint32_t *)(priv->base + offset) = *value++; } } static void copyfrom16(FAR struct lan91c111_driver_s *priv, uint16_t offset, FAR void *value_, size_t length) { FAR uint16_t *value = value_; size_t i; offset = updatebank(priv, offset); for (i = 0; i < length; i += sizeof(*value)) { *value++ = *(FAR volatile uint16_t *)(priv->base + offset); offset += sizeof(*value); } } static void copyto16(FAR struct lan91c111_driver_s *priv, uint16_t offset, FAR const void *value_, size_t length) { FAR const uint16_t *value = value_; size_t i; offset = updatebank(priv, offset); for (i = 0; i < length; i += sizeof(*value)) { *(FAR volatile uint16_t *)(priv->base + offset) = *value++; offset += sizeof(*value); } } /* PHY register access, assume lock hold by caller */ static void outmii(FAR struct lan91c111_driver_s *priv, uint32_t value, size_t bits) { uint32_t mask; uint16_t mii; mii = getreg16(priv, MII_REG); mii &= ~MII_MCLK; mii |= MII_MDOE; for (mask = 1 << (bits - 1); mask; mask >>= 1) { if (value & mask) { mii |= MII_MDO; } else { mii &= ~MII_MDO; } putreg16(priv, MII_REG, mii); up_udelay(LAN91C111_MIIDELAY); putreg16(priv, MII_REG, mii | MII_MCLK); up_udelay(LAN91C111_MIIDELAY); } } static uint32_t inmii(FAR struct lan91c111_driver_s *priv, size_t bits) { uint32_t value = 0; uint32_t mask; uint16_t mii; mii = getreg16(priv, MII_REG); mii &= ~(MII_MCLK | MII_MDOE); for (mask = 1 << (bits - 1); mask; mask >>= 1) { putreg16(priv, MII_REG, mii); up_udelay(LAN91C111_MIIDELAY); if (getreg16(priv, MII_REG) & MII_MDI) { value |= mask; } putreg16(priv, MII_REG, mii | MII_MCLK); up_udelay(LAN91C111_MIIDELAY); } return value; } static uint16_t getphy(FAR struct lan91c111_driver_s *priv, uint8_t offset) { uint16_t value; /* Idle - 32 ones */ outmii(priv, 0xffffffff, 32); /* Start(01) + read(10) + addr(00000) + offset(5bits) */ outmii(priv, 6 << 10 | 0 << 5 | offset, 14); /* Turnaround(2bits) + value(16bits) */ value = inmii(priv, 18); /* Cut the high 2bits */ /* Return to idle state */ modifyreg16(priv, MII_REG, MII_MCLK | MII_MDOE | MII_MDO, 0); return value; } static void putphy(FAR struct lan91c111_driver_s *priv, uint8_t offset, uint16_t value) { /* Idle - 32 ones */ outmii(priv, 0xffffffff, 32); /* Start(01) + write(01) + addr(00000) + offset(5bits) + turnaround(10) + * value(16bits) */ outmii(priv, 5 << 28 | 0 << 23 | offset << 18 | 2 << 16 | value, 32); /* Return to idle state */ modifyreg16(priv, MII_REG, MII_MCLK | MII_MDOE | MII_MDO, 0); } /* Small utility function, assume lock hold by caller */ static void lan91c111_command_mmu(FAR struct lan91c111_driver_s *priv, uint16_t cmd) { putreg16(priv, MMU_CMD_REG, cmd); while (getreg16(priv, MMU_CMD_REG) & MC_BUSY) { /* Wait until the current command finish */ } } /**************************************************************************** * Name: lan91c111_transmit * * Description: * Start hardware transmission. Called either from the txdone interrupt * handling or from watchdog based polling. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * The network is locked. * ****************************************************************************/ static int lan91c111_transmit(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; uint32_t pages; uint8_t packet; /* Verify that the hardware is ready to send another packet. If we get * here, then we are committed to sending a packet; Higher level logic * must have assured that there is no transmission in progress. */ /* The MMU wants the number of pages to be the number of 256 bytes * 'pages', minus 1 (since a packet can't ever have 0 pages :)) * * Packet size for allocating is data length +6 (for additional status * words, length and ctl) * * If odd size then last byte is included in ctl word. */ pages = ((dev->d_len & ~1) + 6) >> 8; while (1) { /* Release the received packet if no free memory */ if (!(getreg16(priv, MIR_REG) & MIR_FREE_MASK) && !(getreg8(priv, RXFIFO_REG) & RXFIFO_REMPTY)) { lan91c111_command_mmu(priv, MC_RELEASE); NETDEV_RXERRORS(dev); } /* Now, try to allocate the memory */ lan91c111_command_mmu(priv, MC_ALLOC | pages); while (1) /* Then wait the response */ { if (getreg8(priv, INT_REG) & IM_ALLOC_INT) { /* Acknowledge the interrupt */ putreg8(priv, INT_REG, IM_ALLOC_INT); break; } } /* Check the result */ packet = getreg8(priv, AR_REG); if (!(packet & AR_FAILED)) { break; /* Got the packet */ } } /* Increment statistics */ lan91c111_dumppacket("transmit", dev->d_buf, dev->d_len); NETDEV_TXPACKETS(dev); /* Send the packet: address=dev->d_buf, length=dev->d_len */ /* Point to the beginning of the packet */ putreg8(priv, PN_REG, packet); putreg16(priv, PTR_REG, PTR_AUTOINC); /* Send the status(set to zeros) and the packet * length(+6 for status, length and ctl byte) */ putreg16(priv, DATA_REG, 0); putreg16(priv, DATA_REG, dev->d_len + 6); /* Append ctl byte */ dev->d_buf[dev->d_len] = TC_ODD; dev->d_buf[dev->d_len + 1] = 0x00; /* Copy and enqueue the buffer */ putregs32(priv, DATA_REG, dev->d_buf, dev->d_len + 2); lan91c111_command_mmu(priv, MC_ENQUEUE); /* Assume the transmission no error, otherwise * revert the increment in lan91c111_txdone */ NETDEV_TXDONE(dev); return OK; } /**************************************************************************** * Name: lan91c111_txpoll * * Description: * The transmitter is available, check if the network has any outgoing * packets ready to send. This is a callback from devif_poll(). * devif_poll() may be called: * * 1. When the preceding TX packet send is complete, * 2. When the preceding TX packet send fail * 3. During normal TX polling * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * OK on success; a negated errno on failure * * Assumptions: * The network is locked. * ****************************************************************************/ static int lan91c111_txpoll(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; /* If the polling resulted in data that should be sent out on the network, * the field d_len is set to a value > 0. */ if (dev->d_len > 0) { /* Look up the destination MAC address and add it to the Ethernet * header. */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 if (IFF_IS_IPv4(dev->d_flags)) #endif { arp_out(dev); } #endif /* CONFIG_NET_IPv4 */ #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else #endif { neighbor_out(dev); } #endif /* CONFIG_NET_IPv6 */ if (!devif_loopback(dev)) { /* Send the packet */ lan91c111_transmit(dev); /* Check if there is room in the device to hold another packet. If not, * return a non-zero value to terminate the poll. */ return !(getreg16(priv, MIR_REG) & MIR_FREE_MASK); } } /* If zero is returned, the polling will continue until all connections have * been examined. */ return 0; } /**************************************************************************** * Name: lan91c111_reply * * Description: * After a packet has been received and dispatched to the network, it * may return with an outgoing packet. This function checks for * that case and performs the transmission if necessary. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static void lan91c111_reply(FAR struct net_driver_s *dev) { /* If the packet dispatch resulted in data that should be sent out on the * network, the field d_len will set to a value > 0. */ if (dev->d_len > 0) { /* Update the Ethernet header with the correct MAC address */ #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 /* Check for an outgoing IPv4 packet */ if (IFF_IS_IPv4(dev->d_flags)) #endif { arp_out(dev); } #endif #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 /* Otherwise, it must be an outgoing IPv6 packet */ else #endif { neighbor_out(dev); } #endif /* And send the packet */ lan91c111_transmit(dev); } } /**************************************************************************** * Name: lan91c111_receive * * Description: * An interrupt was received indicating the availability of a new RX packet * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static void lan91c111_receive(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; FAR struct eth_hdr_s *eth = (FAR struct eth_hdr_s *)dev->d_buf; uint16_t status, length; /* If the RX FIFO is empty then nothing to do */ if (getreg8(priv, RXFIFO_REG) & RXFIFO_REMPTY) { return; } /* Read from start of packet */ putreg16(priv, PTR_REG, PTR_RCV | PTR_AUTOINC | PTR_READ); /* Check for errors and update statistics */ status = getreg16(priv, DATA_REG); if (status & RS_ERRORS) { lan91c111_command_mmu(priv, MC_RELEASE); NETDEV_RXERRORS(dev); return; } /* Check if the packet is a valid size for the network buffer * configuration. */ length = getreg16(priv, DATA_REG); length &= 0x07ff; /* Mask off top bits */ /* Remove the header and tail space */ length -= status & RS_ODDFRAME ? 5 : 6; if (length < ETH_HDRLEN || length > MAX_NETDEV_PKTSIZE) { lan91c111_command_mmu(priv, MC_RELEASE); NETDEV_RXERRORS(dev); return; } /* Copy the data from the hardware to dev->d_buf. Set * amount of data in dev->d_len */ getregs32(priv, DATA_REG, dev->d_buf, length); dev->d_len = length; lan91c111_command_mmu(priv, MC_RELEASE); lan91c111_dumppacket("receive", dev->d_buf, dev->d_len); NETDEV_RXPACKETS(dev); #ifdef CONFIG_NET_PKT /* When packet sockets are enabled, feed the frame into the packet tap */ pkt_input(dev); #endif /* We only accept IP packets of the configured type and ARP packets */ #ifdef CONFIG_NET_IPv4 if (eth->type == HTONS(ETHTYPE_IP)) { ninfo("IPv4 frame\n"); NETDEV_RXIPV4(dev); /* Handle ARP on input, then dispatch IPv4 packet to the network * layer. */ arp_ipin(dev); ipv4_input(dev); /* Check for a reply to the IPv4 packet */ lan91c111_reply(dev); } else #endif #ifdef CONFIG_NET_IPv6 if (eth->type == HTONS(ETHTYPE_IP6)) { ninfo("Iv6 frame\n"); NETDEV_RXIPV6(dev); /* Dispatch IPv6 packet to the network layer */ ipv6_input(dev); /* Check for a reply to the IPv6 packet */ lan91c111_reply(dev); } else #endif #ifdef CONFIG_NET_ARP if (eth->type == htons(ETHTYPE_ARP)) { ninfo("ARP frame\n"); NETDEV_RXARP(dev); /* Dispatch ARP packet to the network layer */ arp_arpin(dev); /* If the above function invocation resulted in data that should be * sent out on the network, the field d_len will set to a value > 0. */ if (dev->d_len > 0) { lan91c111_transmit(dev); } } else #endif { NETDEV_RXDROPPED(dev); } } /**************************************************************************** * Name: lan91c111_txdone * * Description: * An interrupt was received indicating that the last TX packet(s) is done * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static void lan91c111_txdone(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; uint16_t status; uint8_t packet; /* If the TX FIFO is empty then nothing to do */ packet = getreg8(priv, TXFIFO_REG); if (packet & TXFIFO_TEMPTY) { return; } /* Read the status word and free this packet */ putreg8(priv, PN_REG, packet); putreg16(priv, PTR_REG, PTR_AUTOINC | PTR_READ); status = getreg16(priv, DATA_REG); lan91c111_command_mmu(priv, MC_FREEPKT); /* Check for errors and update statistics */ if (status & ES_ERRORS) { /* Re-enable transmit */ modifyreg16(priv, TCR_REG, 0, TCR_ENABLE); #ifdef CONFIG_NETDEV_STATISTICS /* Revert the increment in lan91c111_transmit */ dev->d_statistics.tx_done--; #endif NETDEV_TXERRORS(dev); } else { DEBUGASSERT(0); } } /**************************************************************************** * Name: lan91c111_phy_notify * * Description: * An interrupt was received indicating that the phy has status change * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static void lan91c111_phy_notify(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; do { if (getphy(priv, MII_MSR) & MII_MSR_LINKSTATUS) { if (getphy(priv, MII_LPA) & MII_LPA_FULL) { modifyreg16(priv, TCR_REG, 0, TCR_SWFDUP); } else { modifyreg16(priv, TCR_REG, TCR_SWFDUP, 0); } netdev_carrier_on(dev); } else { netdev_carrier_off(dev); } } while (getphy(priv, PHY_INT_REG) & PHY_INT_INT); } /**************************************************************************** * Name: lan91c111_interrupt_work * * Description: * Perform interrupt related work from the worker thread * * Parameters: * arg - The argument passed when work_queue() was called. * * Returned Value: * OK on success * * Assumptions: * Runs on a worker thread. * ****************************************************************************/ static void lan91c111_interrupt_work(FAR void *arg) { FAR struct net_driver_s *dev = arg; FAR struct lan91c111_driver_s *priv = dev->d_private; uint8_t status; /* Lock the network and serialize driver operations if necessary. * NOTE: Serialization is only required in the case where the driver work * is performed on an LP worker thread and where more than one LP worker * thread has been configured. */ net_lock(); /* Process pending Ethernet interrupts */ while (1) { /* Get interrupt status bits */ status = getreg8(priv, INT_REG); status &= getreg8(priv, IM_REG); if (!status) { break; } /* Handle interrupts according to status bit settings */ /* Check if we received an incoming packet, if so, call lan91c111_receive() */ if (status & IM_RCV_INT) { lan91c111_receive(dev); } if (status & IM_RX_OVRN_INT) { NETDEV_RXERRORS(dev); } /* Check if a packet transmission just completed. If so, call lan91c111_txdone. */ if (status & IM_TX_INT) { lan91c111_txdone(dev); } /* Check if we have the phy interrupt, if so, call lan91c111_phy_notify() */ if (status & IM_MDINT) { lan91c111_phy_notify(dev); } /* Clear interrupt status bits */ putreg8(priv, INT_REG, status); /* In any event, poll the network for new TX data */ if (getreg16(priv, MIR_REG) & MIR_FREE_MASK) { devif_poll(dev, lan91c111_txpoll); } } net_unlock(); /* Re-enable Ethernet interrupts */ up_enable_irq(priv->irq); } /**************************************************************************** * Name: lan91c111_interrupt * * Description: * Hardware interrupt handler * * Parameters: * irq - Number of the IRQ that generated the interrupt * context - Interrupt register state save info (architecture-specific) * * Returned Value: * OK on success * * Assumptions: * Runs in the context of a the Ethernet interrupt handler. Local * interrupts are disabled by the interrupt logic. * ****************************************************************************/ static int lan91c111_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct net_driver_s *dev = arg; FAR struct lan91c111_driver_s *priv = dev->d_private; /* Disable further Ethernet interrupts. */ up_disable_irq(priv->irq); /* Schedule to perform the interrupt processing on the worker thread. */ work_queue(LAN91C111_WORK, &priv->irqwork, lan91c111_interrupt_work, dev, 0); return OK; } /**************************************************************************** * Name: lan91c111_poll_work * * Description: * Perform periodic polling from the worker thread * * Parameters: * arg - The argument passed when work_queue() as called. * * Returned Value: * OK on success * * Assumptions: * Run on a work queue thread. * ****************************************************************************/ static void lan91c111_poll_work(FAR void *arg) { FAR struct net_driver_s *dev = arg; FAR struct lan91c111_driver_s *priv = dev->d_private; /* Lock the network and serialize driver operations if necessary. * NOTE: Serialization is only required in the case where the driver work * is performed on an LP worker thread and where more than one LP worker * thread has been configured. */ net_lock(); /* Perform the poll */ /* Check if there is room in the send another TX packet. We cannot perform * the TX poll if he are unable to accept another packet for transmission. */ if (getreg16(priv, MIR_REG) & MIR_FREE_MASK) { /* If so, update TCP timing states and poll the network for new XMIT data. * Hmmm.. might be bug here. Does this mean if there is a transmit in * progress, we will missing TCP time state updates? */ devif_timer(dev, lan91c111_txpoll); } /* Setup the watchdog poll timer again */ wd_start(priv->txpoll, LAN91C111_WDDELAY, lan91c111_poll_expiry, 1, (wdparm_t)dev); net_unlock(); } /**************************************************************************** * Name: lan91c111_poll_expiry * * Description: * Periodic timer handler. Called from the timer interrupt handler. * * Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * Runs in the context of a the timer interrupt handler. Local * interrupts are disabled by the interrupt logic. * ****************************************************************************/ static void lan91c111_poll_expiry(int argc, wdparm_t arg, ...) { FAR struct net_driver_s *dev = (FAR struct net_driver_s *)arg; FAR struct lan91c111_driver_s *priv = dev->d_private; /* Schedule to perform the interrupt processing on the worker thread. */ work_queue(LAN91C111_WORK, &priv->pollwork, lan91c111_poll_work, dev, 0); } /**************************************************************************** * Name: lan91c111_ifup * * Description: * NuttX Callback: Bring up the Ethernet interface when an IP address is * provided * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static int lan91c111_ifup(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; #ifdef CONFIG_NET_IPv4 ninfo("Bringing up: %d.%d.%d.%d\n", dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24); #endif #ifdef CONFIG_NET_IPv6 ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2], dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5], dev->d_ipv6addr[6], dev->d_ipv6addr[7]); #endif net_lock(); /* Initialize PHYs, the Ethernet interface, and setup up Ethernet interrupts */ putreg16(priv, CONFIG_REG, CONFIG_DEFAULT); putreg16(priv, CTL_REG, CTL_DEFAULT); putreg16(priv, TCR_REG, TCR_DEFAULT); putreg16(priv, RCR_REG, RCR_DEFAULT); putreg16(priv, RPC_REG, RPC_DEFAULT); putreg8(priv, IM_REG, IM_MDINT | IM_RX_OVRN_INT | IM_RCV_INT | IM_TX_INT); putphy(priv, PHY_MASK_REG, /* Interrupts listed here are disabled */ PHY_INT_LOSSSYNC | PHY_INT_CWRD | PHY_INT_SSD | PHY_INT_ESD | PHY_INT_RPOL | PHY_INT_JAB | PHY_INT_SPDDET | PHY_INT_DPLXDET); putphy(priv, MII_MCR, MII_MCR_ANENABLE | MII_MCR_ANRESTART); lan91c111_phy_notify(dev); /* Check the initial phy state */ /* Instantiate the MAC address from dev->d_mac.ether.ether_addr_octet */ copyto16(priv, ADDR0_REG, &dev->d_mac.ether, sizeof(dev->d_mac.ether)); #ifdef CONFIG_NET_ICMPv6 /* Set up IPv6 multicast address filtering */ lan91c111_ipv6multicast(dev); #endif /* Set and activate a timer process */ wd_start(priv->txpoll, LAN91C111_WDDELAY, lan91c111_poll_expiry, 1, (wdparm_t)dev); net_unlock(); /* Enable the Ethernet interrupt */ up_enable_irq(priv->irq); return OK; } /**************************************************************************** * Name: lan91c111_ifdown * * Description: * NuttX Callback: Stop the interface. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static int lan91c111_ifdown(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; irqstate_t flags; /* Disable the Ethernet interrupt */ flags = enter_critical_section(); up_disable_irq(priv->irq); /* Cancel the TX poll timer and work */ wd_cancel(priv->txpoll); work_cancel(LAN91C111_WORK, &priv->irqwork); work_cancel(LAN91C111_WORK, &priv->pollwork); /* Put the EMAC in its reset, non-operational state. This should be * a known configuration that will guarantee the lan91c111_ifup() always * successfully brings the interface back up. */ putreg8(priv, IM_REG, 0); putreg16(priv, RCR_REG, RCR_CLEAR); putreg16(priv, TCR_REG, TCR_CLEAR); putreg16(priv, CTL_REG, CTL_CLEAR); putreg16(priv, CONFIG_REG, CONFIG_CLEAR); leave_critical_section(flags); return OK; } /**************************************************************************** * Name: lan91c111_txavail_work * * Description: * Perform an out-of-cycle poll on the worker thread. * * Parameters: * arg - Reference to the NuttX driver state structure (cast to void*) * * Returned Value: * None * * Assumptions: * Runs on a work queue thread. * ****************************************************************************/ static void lan91c111_txavail_work(FAR void *arg) { FAR struct net_driver_s *dev = arg; FAR struct lan91c111_driver_s *priv = dev->d_private; /* Lock the network and serialize driver operations if necessary. * NOTE: Serialization is only required in the case where the driver work * is performed on an LP worker thread and where more than one LP worker * thread has been configured. */ net_lock(); /* Ignore the notification if the interface is not yet up */ if (IFF_IS_UP(dev->d_flags)) { /* Check if there is room in the hardware to hold another outgoing * packet. */ if (getreg16(priv, MIR_REG) & MIR_FREE_MASK) { /* If so, then poll the network for new XMIT data */ devif_poll(dev, lan91c111_txpoll); } } net_unlock(); } /**************************************************************************** * Name: lan91c111_txavail * * Description: * Driver callback invoked when new TX data is available. This is a * stimulus perform an out-of-cycle poll and, thereby, reduce the TX * latency. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static int lan91c111_txavail(FAR struct net_driver_s *dev) { FAR struct lan91c111_driver_s *priv = dev->d_private; /* Is our single work structure available? It may not be if there are * pending interrupt actions and we will have to ignore the Tx * availability action. */ if (work_available(&priv->pollwork)) { /* Schedule to serialize the poll on the worker thread. */ work_queue(LAN91C111_WORK, &priv->pollwork, lan91c111_txavail_work, dev, 0); } return OK; } /**************************************************************************** * Name: lan91c111_addmac * * Description: * NuttX Callback: Add the specified MAC address to the hardware multicast * address filtering * * IEEE (CRC32) from http://www.microchip.com/wwwproducts/en/LAN91C111 * * Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be added * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ #if defined(CONFIG_NET_IGMP) || defined(CONFIG_NET_ICMPv6) static uint32_t lan91c111_crc32(FAR const uint8_t *src, size_t len) { uint32_t crc = 0xffffffff; uint8_t carry, temp; size_t i, j; for (i = 0; i < len; i++) { temp = *src++; for (j = 0; j < 8; j++) { carry = (crc & 0x80000000 ? 1 : 0) ^ (temp & 0x01); crc <<= 1; if (carry) { crc = (crc ^ 0x04c11db6) | carry; } temp >>= 1; } } return crc; } static int lan91c111_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac) { FAR struct lan91c111_driver_s *priv = dev->d_private; uint16_t off, bit; uint32_t hash; /* Calculate Ethernet CRC32 for MAC */ hash = lan91c111_crc32(mac, ETHER_ADDR_LEN); /* The multicast table is a register array of 4 16-bit registers * and the hash value is defined as the six most significant bits * of the CRC of the destination addresses. The two msb's determine * the register to be used (MCAST1-MCAST4), while the other four * determine the bit within the register. If the appropriate bit in * the table is set, the packet is received. */ off = (hash >> 29) & 0x06; bit = (hash >> 26) & 0x0f; /* Add the MAC address to the hardware multicast routing table */ net_lock(); modifyreg16(priv, MCAST_REG1 + off, 0, 1 << bit); net_unlock(); return OK; } #endif /**************************************************************************** * Name: lan91c111_rmmac * * Description: * NuttX Callback: Remove the specified MAC address from the hardware * multicast address filtering * * Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be removed * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ #ifdef CONFIG_NET_IGMP static int lan91c111_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac) { FAR struct lan91c111_driver_s *priv = dev->d_private; uint16_t off, bit; uint32_t hash; /* Calculate Ethernet CRC32 for MAC */ hash = lan91c111_crc32(mac, ETHER_ADDR_LEN); /* TODO: since the six most significant bits of the CRC from two * different destination addresses may have the same value. We * need a reference count here to avoid clear the bit prematurely. */ off = (hash >> 29) & 0x06; bit = (hash >> 26) & 0x0f; /* Remove the MAC address from the hardware multicast routing table */ net_lock(); modifyreg16(priv, MCAST_REG1 + off, 1 << bit, 0); net_unlock(); return OK; } #endif /**************************************************************************** * Name: lan91c111_ipv6multicast * * Description: * Configure the IPv6 multicast MAC address. * * Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ #ifdef CONFIG_NET_ICMPv6 static void lan91c111_ipv6multicast(FAR struct net_driver_s *dev) { uint16_t tmp16; uint8_t mac[6]; /* For ICMPv6, we need to add the IPv6 multicast address * * For IPv6 multicast addresses, the Ethernet MAC is derived by * the four low-order octets OR'ed with the MAC 33:33:00:00:00:00, * so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map * to the Ethernet MAC address 33:33:00:01:00:03. * * NOTES: This appears correct for the ICMPv6 Router Solicitation * Message, but the ICMPv6 Neighbor Solicitation message seems to * use 33:33:ff:01:00:03. */ mac[0] = 0x33; mac[1] = 0x33; tmp16 = dev->d_ipv6addr[6]; mac[2] = 0xff; mac[3] = tmp16 >> 8; tmp16 = dev->d_ipv6addr[7]; mac[4] = tmp16 & 0xff; mac[5] = tmp16 >> 8; ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); lan91c111_addmac(dev, mac); #ifdef CONFIG_NET_ICMPv6_AUTOCONF /* Add the IPv6 all link-local nodes Ethernet address. This is the * address that we expect to receive ICMPv6 Router Advertisement * packets. */ lan91c111_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet); #endif /* CONFIG_NET_ICMPv6_AUTOCONF */ #ifdef CONFIG_NET_ICMPv6_ROUTER /* Add the IPv6 all link-local routers Ethernet address. This is the * address that we expect to receive ICMPv6 Router Solicitation * packets. */ lan91c111_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet); #endif /* CONFIG_NET_ICMPv6_ROUTER */ } #endif /* CONFIG_NET_ICMPv6 */ /**************************************************************************** * Name: lan91c111_ioctl * * Description: * Handle network IOCTL commands directed to this device. * * Parameters: * dev - Reference to the NuttX driver state structure * cmd - The IOCTL command * arg - The argument for the IOCTL command * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * The network is locked. * ****************************************************************************/ #ifdef CONFIG_NETDEV_IOCTL static int lan91c111_ioctl(FAR struct net_driver_s *dev, int cmd, unsigned long arg) { FAR struct lan91c111_driver_s *priv = dev->d_private; struct mii_ioctl_data_s *req = (void *)arg; int ret = OK; net_lock(); /* Decode and dispatch the driver-specific IOCTL command */ switch (cmd) { case SIOCGMIIPHY: /* Get MII PHY address */ req->phy_id = 0; break; case SIOCGMIIREG: /* Get register from MII PHY */ req->val_out = getphy(priv, req->reg_num); break; case SIOCSMIIREG: /* Set register in MII PHY */ putphy(priv, req->reg_num, req->val_in); break; default: nerr("ERROR: Unrecognized IOCTL command: %d\n", command); ret = -ENOTTY; /* Special return value for this case */ } net_unlock(); return ret; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: lan91c111_initialize * * Description: * Initialize the Ethernet controller and driver * * Parameters: * base - The controller base address * irq - The controller irq number * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * Called early in initialization before multi-tasking is initiated. * ****************************************************************************/ int lan91c111_initialize(uintptr_t base, int irq) { FAR struct lan91c111_driver_s *priv; FAR struct net_driver_s *dev; uint16_t macrev; uint32_t phyid; int ret; /* Allocate the interface structure */ priv = kmm_zalloc(sizeof(*priv)); if (priv == NULL) { return -ENOMEM; } dev = &priv->dev; priv->base = base; priv->irq = irq; /* Check if a Ethernet chip is recognized at its I/O base */ macrev = getreg16(priv, REV_REG); phyid = getphy(priv, MII_PHYID1) << 16; phyid |= getphy(priv, MII_PHYID2); ninfo("base: %08x irq: %d rev: %04x phy: %08x\n", base, irq, macrev, phyid); if ((macrev >> 4 & 0x0f) != CHIP_91111FD || phyid != PHY_LAN83C183) { nerr("ERROR: Unsupport LAN91C111's MAC/PHY\n"); ret = -ENODEV; goto err; } /* Attach the IRQ to the driver */ ret = irq_attach(irq, lan91c111_interrupt, dev); if (ret < 0) { /* We could not attach the ISR to the interrupt */ goto err; } /* Initialize the driver structure */ dev->d_buf = priv->pktbuf; /* Single packet buffer */ dev->d_ifup = lan91c111_ifup; /* I/F up (new IP address) callback */ dev->d_ifdown = lan91c111_ifdown; /* I/F down callback */ dev->d_txavail = lan91c111_txavail; /* New TX data callback */ #ifdef CONFIG_NET_IGMP dev->d_addmac = lan91c111_addmac; /* Add multicast MAC address */ dev->d_rmmac = lan91c111_rmmac; /* Remove multicast MAC address */ #endif #ifdef CONFIG_NETDEV_IOCTL dev->d_ioctl = lan91c111_ioctl; /* Handle network IOCTL commands */ #endif dev->d_private = priv; /* Used to recover private state from dev */ /* Create a watchdog for timing polling for transmissions */ priv->txpoll = wd_create(); /* Create periodic poll timer */ DEBUGASSERT(priv->txpoll != NULL); /* Put the interface in the down state. This usually amounts to resetting * the device and/or calling lan91c111_ifdown(). */ putreg16(priv, RCR_REG, RCR_SOFTRST); up_udelay(10); /* Pause to make the chip happy */ putreg16(priv, RCR_REG, RCR_CLEAR); putreg16(priv, CONFIG_REG, CONFIG_CLEAR); lan91c111_command_mmu(priv, MC_RESET); putphy(priv, MII_MCR, MII_MCR_RESET); while (getphy(priv, MII_MCR) & MII_MCR_RESET) { /* Loop, reset don't finish yet */ } /* Read the MAC address from the hardware into dev->d_mac.ether.ether_addr_octet */ copyfrom16(priv, ADDR0_REG, &dev->d_mac.ether, sizeof(dev->d_mac.ether)); /* Register the device with the OS so that socket IOCTLs can be performed */ netdev_register(dev, NET_LL_ETHERNET); return OK; err: kmm_free(priv); return ret; }