| /**************************************************************************** |
| * drivers/net/lan91c111.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <assert.h> |
| #include <nuttx/debug.h> |
| #include <errno.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/wqueue.h> |
| |
| #include <nuttx/net/ip.h> |
| #include <nuttx/net/netdev.h> |
| #include <nuttx/net/lan91c111.h> |
| |
| #ifdef CONFIG_NET_PKT |
| # include <nuttx/net/pkt.h> |
| #endif |
| |
| #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) |
| #endif |
| |
| /* The low priority work queue is preferred. If it is not enabled, LPWORK |
| * will be the same as HPWORK. |
| * |
| * NOTE: However, the network should NEVER run on the high priority work |
| * queue! That queue is intended only to service short back end interrupt |
| * processing that never suspends. Suspending the high priority work queue |
| * may bring the system to its knees! |
| */ |
| |
| #define LAN91C111_WORK LPWORK |
| |
| #ifdef CONFIG_NET_DUMPPACKET |
| # define lan91c111_dumppacket lib_dumpbuffer |
| #else |
| # define lan91c111_dumppacket(m, b, l) |
| #endif |
| |
| /* 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 */ |
| struct work_s irqwork; /* For deferring interrupt work to the work queue */ |
| struct work_s pollwork; /* For deferring poll work to the work queue */ |
| spinlock_t lock; /* Spinlock to protect the driver state */ |
| uint16_t bank; /* Current bank */ |
| uint16_t pktbuf[(MAX_NETDEV_PKTSIZE + 4 + 1) / 2]; /* +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); |
| |
| /* 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_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) |
| static int lan91c111_addmac(FAR struct net_driver_s *dev, |
| FAR const uint8_t *mac); |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int lan91c111_rmmac(FAR struct net_driver_s *dev, |
| FAR const uint8_t *mac); |
| #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; |
| |
| /* 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); |
| } |
| |
| /**************************************************************************** |
| * 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) |
| { |
| /* 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; |
| uint16_t 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 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); |
| |
| /* Receive an IPv4 packet from the network device */ |
| |
| 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("IPv6 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_input(dev); |
| |
| /* Check for a reply to the ARP packet */ |
| |
| lan91c111_reply(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 |
| { |
| DEBUGPANIC(); |
| } |
| } |
| |
| /**************************************************************************** |
| * 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. |
| */ |
| |
| netdev_lock(dev); |
| |
| /* 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); |
| } |
| } |
| |
| netdev_unlock(dev); |
| |
| /* 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_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: %u.%u.%u.%u\n", |
| ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr), |
| ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr)); |
| #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 |
| |
| netdev_lock(dev); |
| |
| /* Initialize PHYs, 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)); |
| |
| netdev_unlock(dev); |
| |
| /* 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 = spin_lock_irqsave(&priv->lock); |
| up_disable_irq(priv->irq); |
| |
| 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); |
| |
| spin_unlock_irqrestore(&priv->lock, 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. |
| */ |
| |
| netdev_lock(dev); |
| |
| /* 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); |
| } |
| } |
| |
| netdev_unlock(dev); |
| } |
| |
| /**************************************************************************** |
| * 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_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) |
| static uint32_t lan91c111_crc32(FAR const uint8_t *src, size_t len) |
| { |
| uint32_t crc = 0xffffffff; |
| uint8_t carry; |
| uint8_t temp; |
| size_t i; |
| size_t 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; |
| uint16_t 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 */ |
| |
| netdev_lock(dev); |
| modifyreg16(priv, MCAST_REG1 + off, 0, 1 << bit); |
| netdev_unlock(dev); |
| |
| 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_MCASTGROUP |
| 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; |
| uint16_t 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 */ |
| |
| netdev_lock(dev); |
| modifyreg16(priv, MCAST_REG1 + off, 1 << bit, 0); |
| netdev_unlock(dev); |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * 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; |
| FAR struct mii_ioctl_data_s *req = (FAR void *)arg; |
| int ret = OK; |
| |
| netdev_lock(dev); |
| |
| /* 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 */ |
| } |
| |
| netdev_unlock(dev); |
| 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: Unsupported 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; |
| } |
| |
| spin_lock_init(&priv->lock); |
| |
| /* Initialize the driver structure */ |
| |
| dev->d_buf = (FAR uint8_t *)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_MCASTGROUP |
| 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 */ |
| |
| /* 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 MAC address from the hardware into dev->d_mac.ether */ |
| |
| 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; |
| } |