michallenc commented on code in PR #16936:
URL: https://github.com/apache/nuttx/pull/16936#discussion_r2309359730


##########
drivers/net/oa_tc6/oa_tc6.c:
##########
@@ -0,0 +1,2176 @@
+/****************************************************************************
+ * drivers/net/oa_tc6/oa_tc6.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 <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/spi/spi.h>
+#include <sys/endian.h>
+
+#include <nuttx/wqueue.h>
+#include <nuttx/mutex.h>
+
+#include <nuttx/net/ioctl.h>
+
+#include <nuttx/net/netdev_lowerhalf.h>
+
+#include <nuttx/net/oa_tc6.h>
+
+#ifdef CONFIG_NET_OA_TC6_NCV7410
+#include "oa_tc6_ncv7410.h"
+#endif
+
+#ifdef CONFIG_NET_OA_TC6_LAN865X
+#include "oa_tc6_lan865x.h"
+#endif
+
+#include "oa_tc6.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define OA_TC6_WORK LPWORK
+
+#define OA_TC6_N_TRIES 5
+
+#define OA_TC6_RECOVERY_WORK_INTERVAL_MS 1000
+
+/* Maximum frame size = (MTU + LL heaader size) + FCS size */
+
+#define OA_TC6_MAX_FRAME_SIZE(p) (p->dev.netdev.d_pktsize + 4)
+
+/* Packet Memory ************************************************************/
+
+/* Maximum number of allocated TX and RX netpackets */
+
+#define OA_TC6_TX_QUOTA 1
+#define OA_TC6_RX_QUOTA 2
+
+#if CONFIG_IOB_NBUFFERS < (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)
+#  error "CONFIG_IOB_NBUFFERS must be > (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)"
+#endif
+
+#ifndef CONFIG_SCHED_LPWORK
+#  error "CONFIG_SCHED_LPWORK is needed by the OA-TC6 driver"
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Bit calculations */
+
+static int oa_tc6_get_parity(uint32_t word);
+
+/* SPI transfers */
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer);
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer);
+
+/* Interrupt handling */
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg);
+static void oa_tc6_interrupt_work(FAR void *arg);
+
+/* SPI recovery */
+
+static void oa_tc6_recovery_work(FAR void *arg);
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv);
+
+/* Data Transaction Protocol logic */
+
+static void oa_tc6_io_work(FAR void *arg);
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf);
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf);
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv);
+
+/* SPI utility functions */
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_deselect_spi(FAR struct oa_tc6_driver_s *priv);
+
+/* OA-TC6 reset and configuration */
+
+static int oa_tc6_read_reg_raw(FAR struct spi_dev_s *spi,
+                               FAR struct oa_tc6_config_s *config,
+                               oa_tc6_regid_t regid, FAR uint32_t *word);
+static int oa_tc6_get_phyid(FAR struct spi_dev_s *spi,
+                            FAR struct oa_tc6_config_s *config,
+                            FAR uint32_t *phyid);
+static int oa_tc6_init_by_id(FAR struct spi_dev_s *spi,
+                             FAR struct oa_tc6_config_s *config,
+                             uint32_t phyid);
+
+static int oa_tc6_reset(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_config(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_enable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_disable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_update_mac_filter(FAR struct oa_tc6_driver_s *priv);
+
+/* Driver buffer manipulation */
+
+static void oa_tc6_reset_driver_buffers(FAR struct oa_tc6_driver_s *priv);
+
+/* MDIO */
+
+static int oa_tc6_miireg_get_regid(FAR struct mii_ioctl_data_s *req,
+                                   FAR oa_tc6_regid_t *regid);
+static int oa_tc6_get_mmd_base(uint8_t mmd,
+                               FAR uint8_t *mms, FAR uint16_t *addr);
+
+/* NuttX callback functions */
+
+static int oa_tc6_ifup(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_ifdown(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt);
+static FAR netpkt_t *oa_tc6_receive(FAR struct netdev_lowerhalf_s *dev);
+#ifdef CONFIG_NET_MCASTGROUP
+static int oa_tc6_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac);
+static int oa_tc6_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int oa_tc6_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
+                        unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct netdev_ops_s g_oa_tc6_ops =
+{
+  .ifup     = oa_tc6_ifup,
+  .ifdown   = oa_tc6_ifdown,
+  .transmit = oa_tc6_transmit,
+  .receive  = oa_tc6_receive,
+#ifdef CONFIG_NET_MCASTGROUP
+  .addmac   = oa_tc6_addmac,
+  .rmmac    = oa_tc6_rmmac,
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  .ioctl    = oa_tc6_ioctl
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: oa_tc6_get_parity
+ *
+ * Description:
+ *   Obtain parity of a 32-bit word.
+ *
+ * Input Parameters:
+ *   word - 32-bit word, subject to the parity calculation
+ *
+ * Returned Value:
+ *   If the parity of the word is even, zero is returned.
+ *   Otherwise one is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_get_parity(uint32_t word)
+{
+  /* www-graphics.stanford.edu/~seander/bithacks.html */
+
+  word ^= word >> 1;
+  word ^= word >> 2;
+  word = (word & 0x11111111u) * 0x11111111u;
+  return (word >> 28) & 1;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_poll_footer
+ *
+ * Description:
+ *   Poll a data transaction chunk footer.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - pointer to a 32-bit footer destination variable
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer)
+{
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+  uint32_t header;
+
+  header =   (1 << OA_TC6_DNC_POS)   /* Data Not Control */
+           | (1 << OA_TC6_NORX_POS); /* No Read */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, footer))
+    {
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exchange_chunk
+ *
+ * Description:
+ *   Send a data chunk to MAC-PHY and simultaneously receive chunk.
+ *
+ *   Computing header parity, checking footer parity, converting to proper
+ *   endianness and setting DNC flag is done by this function.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   txbuf  - buffer with transmit chunk data
+ *   rxbuf  - buffer to save the received chunk to
+ *   header - header controlling the transaction
+ *   footer - pointer to a 32-bit value for the footer
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer)
+{
+  header |= (1 << OA_TC6_DNC_POS);
+  header |= (!oa_tc6_get_parity(header) << OA_TC6_P_POS);
+  header = htobe32(header);
+
+  ((uint32_t *)txbuf)[0] = header;
+
+  oa_tc6_select_spi(priv);
+  SPI_EXCHANGE(priv->spi, txbuf, rxbuf, OA_TC6_CHUNK_SIZE(priv));
+  oa_tc6_deselect_spi(priv);
+
+  *footer = *((uint32_t *)(&rxbuf[priv->config->chunk_payload_size]));
+
+  *footer = be32toh(*footer);
+  if (!oa_tc6_get_parity(*footer) ||
+      oa_tc6_header_bad(*footer)  ||
+      !oa_tc6_mac_phy_sync(*footer))
+    {
+      if (!oa_tc6_get_parity(*footer))
+        {
+          nerr("Error: Wrong parity in the footer\n");
+        }
+
+      if (oa_tc6_header_bad(*footer))
+        {
+          nerr("Error: HDRB set in the footer\n");
+        }
+
+      if (!oa_tc6_mac_phy_sync(*footer))
+        {
+          nerr("Error: MAC-PHY lost configuration, "
+               "SYNC cleared in the footer\n");
+        }
+
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt
+ *
+ * Description:
+ *   Schedule interrupt work when the interrupt signal from MAC-PHY is
+ *   received.
+ *
+ * Input Parameters:
+ *   irq     - not used
+ *   context - not used
+ *   arg     - oa_tc6_driver_s priv structure to be passed to the interrupt
+ *             worker
+ *
+ * Returned Value:
+ *   OK is always returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+
+  ninfo("Info: OA-TC6 interrupt!\n");
+
+  /* Schedule interrupt work */
+
+  work_queue(OA_TC6_WORK, &priv->interrupt_work,
+             oa_tc6_interrupt_work, priv, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt_work
+ *
+ * Description:
+ *   Identify the interrupt source and perform necessary work.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_interrupt_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t footer;
+  int tries = OA_TC6_N_TRIES;
+
+  nxmutex_lock(&priv->lock);

Review Comment:
   I would check the return value of `nxmutex_lock` call for robustness sake



##########
drivers/net/oa_tc6/oa_tc6.c:
##########
@@ -0,0 +1,2176 @@
+/****************************************************************************
+ * drivers/net/oa_tc6/oa_tc6.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 <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/spi/spi.h>
+#include <sys/endian.h>
+
+#include <nuttx/wqueue.h>
+#include <nuttx/mutex.h>
+
+#include <nuttx/net/ioctl.h>
+
+#include <nuttx/net/netdev_lowerhalf.h>
+
+#include <nuttx/net/oa_tc6.h>
+
+#ifdef CONFIG_NET_OA_TC6_NCV7410
+#include "oa_tc6_ncv7410.h"
+#endif
+
+#ifdef CONFIG_NET_OA_TC6_LAN865X
+#include "oa_tc6_lan865x.h"
+#endif
+
+#include "oa_tc6.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define OA_TC6_WORK LPWORK
+
+#define OA_TC6_N_TRIES 5
+
+#define OA_TC6_RECOVERY_WORK_INTERVAL_MS 1000
+
+/* Maximum frame size = (MTU + LL heaader size) + FCS size */
+
+#define OA_TC6_MAX_FRAME_SIZE(p) (p->dev.netdev.d_pktsize + 4)
+
+/* Packet Memory ************************************************************/
+
+/* Maximum number of allocated TX and RX netpackets */
+
+#define OA_TC6_TX_QUOTA 1
+#define OA_TC6_RX_QUOTA 2
+
+#if CONFIG_IOB_NBUFFERS < (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)
+#  error "CONFIG_IOB_NBUFFERS must be > (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)"
+#endif
+
+#ifndef CONFIG_SCHED_LPWORK
+#  error "CONFIG_SCHED_LPWORK is needed by the OA-TC6 driver"
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Bit calculations */
+
+static int oa_tc6_get_parity(uint32_t word);
+
+/* SPI transfers */
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer);
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer);
+
+/* Interrupt handling */
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg);
+static void oa_tc6_interrupt_work(FAR void *arg);
+
+/* SPI recovery */
+
+static void oa_tc6_recovery_work(FAR void *arg);
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv);
+
+/* Data Transaction Protocol logic */
+
+static void oa_tc6_io_work(FAR void *arg);
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf);
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf);
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv);
+
+/* SPI utility functions */
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_deselect_spi(FAR struct oa_tc6_driver_s *priv);
+
+/* OA-TC6 reset and configuration */
+
+static int oa_tc6_read_reg_raw(FAR struct spi_dev_s *spi,
+                               FAR struct oa_tc6_config_s *config,
+                               oa_tc6_regid_t regid, FAR uint32_t *word);
+static int oa_tc6_get_phyid(FAR struct spi_dev_s *spi,
+                            FAR struct oa_tc6_config_s *config,
+                            FAR uint32_t *phyid);
+static int oa_tc6_init_by_id(FAR struct spi_dev_s *spi,
+                             FAR struct oa_tc6_config_s *config,
+                             uint32_t phyid);
+
+static int oa_tc6_reset(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_config(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_enable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_disable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_update_mac_filter(FAR struct oa_tc6_driver_s *priv);
+
+/* Driver buffer manipulation */
+
+static void oa_tc6_reset_driver_buffers(FAR struct oa_tc6_driver_s *priv);
+
+/* MDIO */
+
+static int oa_tc6_miireg_get_regid(FAR struct mii_ioctl_data_s *req,
+                                   FAR oa_tc6_regid_t *regid);
+static int oa_tc6_get_mmd_base(uint8_t mmd,
+                               FAR uint8_t *mms, FAR uint16_t *addr);
+
+/* NuttX callback functions */
+
+static int oa_tc6_ifup(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_ifdown(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt);
+static FAR netpkt_t *oa_tc6_receive(FAR struct netdev_lowerhalf_s *dev);
+#ifdef CONFIG_NET_MCASTGROUP
+static int oa_tc6_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac);
+static int oa_tc6_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int oa_tc6_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
+                        unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct netdev_ops_s g_oa_tc6_ops =
+{
+  .ifup     = oa_tc6_ifup,
+  .ifdown   = oa_tc6_ifdown,
+  .transmit = oa_tc6_transmit,
+  .receive  = oa_tc6_receive,
+#ifdef CONFIG_NET_MCASTGROUP
+  .addmac   = oa_tc6_addmac,
+  .rmmac    = oa_tc6_rmmac,
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  .ioctl    = oa_tc6_ioctl
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: oa_tc6_get_parity
+ *
+ * Description:
+ *   Obtain parity of a 32-bit word.
+ *
+ * Input Parameters:
+ *   word - 32-bit word, subject to the parity calculation
+ *
+ * Returned Value:
+ *   If the parity of the word is even, zero is returned.
+ *   Otherwise one is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_get_parity(uint32_t word)
+{
+  /* www-graphics.stanford.edu/~seander/bithacks.html */
+
+  word ^= word >> 1;
+  word ^= word >> 2;
+  word = (word & 0x11111111u) * 0x11111111u;
+  return (word >> 28) & 1;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_poll_footer
+ *
+ * Description:
+ *   Poll a data transaction chunk footer.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - pointer to a 32-bit footer destination variable
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer)
+{
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+  uint32_t header;
+
+  header =   (1 << OA_TC6_DNC_POS)   /* Data Not Control */
+           | (1 << OA_TC6_NORX_POS); /* No Read */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, footer))
+    {
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exchange_chunk
+ *
+ * Description:
+ *   Send a data chunk to MAC-PHY and simultaneously receive chunk.
+ *
+ *   Computing header parity, checking footer parity, converting to proper
+ *   endianness and setting DNC flag is done by this function.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   txbuf  - buffer with transmit chunk data
+ *   rxbuf  - buffer to save the received chunk to
+ *   header - header controlling the transaction
+ *   footer - pointer to a 32-bit value for the footer
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer)
+{
+  header |= (1 << OA_TC6_DNC_POS);
+  header |= (!oa_tc6_get_parity(header) << OA_TC6_P_POS);
+  header = htobe32(header);
+
+  ((uint32_t *)txbuf)[0] = header;
+
+  oa_tc6_select_spi(priv);
+  SPI_EXCHANGE(priv->spi, txbuf, rxbuf, OA_TC6_CHUNK_SIZE(priv));
+  oa_tc6_deselect_spi(priv);
+
+  *footer = *((uint32_t *)(&rxbuf[priv->config->chunk_payload_size]));
+
+  *footer = be32toh(*footer);
+  if (!oa_tc6_get_parity(*footer) ||
+      oa_tc6_header_bad(*footer)  ||
+      !oa_tc6_mac_phy_sync(*footer))
+    {
+      if (!oa_tc6_get_parity(*footer))
+        {
+          nerr("Error: Wrong parity in the footer\n");
+        }
+
+      if (oa_tc6_header_bad(*footer))
+        {
+          nerr("Error: HDRB set in the footer\n");
+        }
+
+      if (!oa_tc6_mac_phy_sync(*footer))
+        {
+          nerr("Error: MAC-PHY lost configuration, "
+               "SYNC cleared in the footer\n");
+        }
+
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt
+ *
+ * Description:
+ *   Schedule interrupt work when the interrupt signal from MAC-PHY is
+ *   received.
+ *
+ * Input Parameters:
+ *   irq     - not used
+ *   context - not used
+ *   arg     - oa_tc6_driver_s priv structure to be passed to the interrupt
+ *             worker
+ *
+ * Returned Value:
+ *   OK is always returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+
+  ninfo("Info: OA-TC6 interrupt!\n");
+
+  /* Schedule interrupt work */
+
+  work_queue(OA_TC6_WORK, &priv->interrupt_work,
+             oa_tc6_interrupt_work, priv, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt_work
+ *
+ * Description:
+ *   Identify the interrupt source and perform necessary work.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_interrupt_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t footer;
+  int tries = OA_TC6_N_TRIES;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP)
+    {
+      ninfo("Info: Interrupt work invoked when the interface is not up\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  ninfo("Info: OA-TC6 interrupt worker invoked!\n");
+
+  do
+    {
+      if (!oa_tc6_poll_footer(priv, &footer))
+        {
+          break;
+        }
+
+      nerr("Error: Polling footer unsuccessful\n");
+    }
+  while (--tries);
+
+  if (!tries)
+    {
+      nerr("Error: Failed to poll footer in %d tries, carrier down\n",
+           OA_TC6_N_TRIES);
+
+      oa_tc6_enter_recovery(priv);
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_ext_status(footer))
+    {
+      /* Device-specific driver may implement special functionality on EXST */
+
+      priv->ops->action(priv, OA_TC6_ACTION_EXST);
+    }
+
+  /* Update MAC-PHY buffer status */
+
+  priv->txc = oa_tc6_tx_credits(footer);
+  priv->rca = oa_tc6_rx_available(footer);
+
+  if ((priv->tx_pkt && priv->txc) || priv->rca)
+    {
+      /* Schedule IO work */
+
+      ninfo("Info: Scheduled io_work from interrupt_work\n");
+      work_queue(OA_TC6_WORK, &priv->io_work, oa_tc6_io_work, priv, 0);
+    }
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_recovery_work
+ *
+ * Description:
+ *   Every OA_TC6_RECOVERY_WORK_INTERVAL_MS milliseconds check whether the
+ *   SPI is already available.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_recovery_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t regval;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP_RECOVERY)
+    {
+      ninfo("Info: Trying to recover when not in recovery\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_read_reg(priv, OA_TC6_CONFIG0_REGID, &regval))
+    {
+      nwarn("Warning: MAC-PHY still not available\n");
+
+      work_queue(OA_TC6_WORK, &priv->recovery_work,
+                 oa_tc6_recovery_work, priv,
+                 MSEC2TICK(OA_TC6_RECOVERY_WORK_INTERVAL_MS));
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  ninfo("Info: Reading register successful during recovery\n");
+
+  if (!oa_tc6_get_field(regval, CONFIG0_SYNC))
+    {
+      /* MAC-PHY has lost config. Shut down */
+
+      nwarn("Warning: MAC-PHY has lost config (SYNC = '0'), "
+            "shutting down\n");
+
+      priv->ifstate = OA_TC6_IFSTATE_UP_UNKNOWN;
+
+      /* Unlock before shutting down, otherwise would result in deadlock */
+
+      nxmutex_unlock(&priv->lock);
+
+      net_lock();
+      netdev_ifdown(&priv->dev.netdev);
+      net_unlock();
+
+      return;
+    }
+
+  ninfo("Info: MAC-PHY SPI contact successful, exiting recovery\n");
+
+  oa_tc6_exit_recovery(priv);
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_enter_recovery
+ *
+ * Description:
+ *   Disable the interface after the contact over the SPI is lost.
+ *   Enter recovery mode in waiting for contact over SPI.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv)
+{
+  /* Disable interrupt on the board level */
+
+  priv->config->enable(priv->config, false);
+
+  work_cancel(OA_TC6_WORK, &priv->interrupt_work);
+  work_cancel(OA_TC6_WORK, &priv->io_work);
+
+  oa_tc6_reset_driver_buffers(priv);
+
+  priv->ifstate = OA_TC6_IFSTATE_UP_RECOVERY;
+
+  net_lock();
+  netdev_lower_carrier_off(&priv->dev);
+  net_unlock();
+
+  work_queue(OA_TC6_WORK, &priv->recovery_work,
+             oa_tc6_recovery_work, priv,
+             MSEC2TICK(OA_TC6_RECOVERY_WORK_INTERVAL_MS));
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exit_recovery
+ *
+ * Description:
+ *   Re-enable the interface after the contact over the SPI is recovered.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv)
+{
+  priv->ifstate = OA_TC6_IFSTATE_UP;
+
+  net_lock();
+  netdev_lower_carrier_on(&priv->dev);
+  net_unlock();
+
+  work_queue(OA_TC6_WORK, &priv->interrupt_work,
+             oa_tc6_interrupt_work, priv, 0);
+
+  /* Enable interrupt on the board level */
+
+  priv->config->enable(priv->config, true);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_io_work
+ *
+ * Description:
+ *   Exchange data chunk with the MAC-PHY.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_io_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+
+  uint32_t header;
+  uint32_t footer;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP)
+    {
+      nerr("Error: Trying to work when the interface is not up\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  header = oa_tc6_prep_chunk_exchange(priv, txbuf);
+
+  /* Perform the SPI exchange */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, &footer))
+    {
+      nerr("Error: Chunk exchange failed\n");
+
+      /* Reset buffers, effectively dropping frames */
+
+      oa_tc6_reset_driver_buffers(priv);
+
+      /* Plan the interrupt work to try and find out what's going on */
+
+      work_queue(OA_TC6_WORK, &priv->interrupt_work,
+                 oa_tc6_interrupt_work, priv, 0);
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_ext_status(footer))
+    {
+      priv->ops->action(priv, OA_TC6_ACTION_EXST);
+    }
+
+  oa_tc6_try_finish_tx_packet(priv);
+
+  oa_tc6_handle_rx_chunk(priv, footer, rxbuf);
+
+  /* Schedule further work if needed */
+
+  if ((priv->tx_pkt && priv->txc) || priv->rca)
+    {
+      work_queue(OA_TC6_WORK, &priv->io_work, oa_tc6_io_work, priv, 0);
+    }
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_prep_chunk_exchange
+ *
+ * Description:
+ *   Determine whether there is data to transmit or receive.
+ *   Set the appropriate header bitfields and fill the txbuf accordingly.
+ *
+ * Input Parameters:
+ *   priv  - pointer to the driver-specific state structure
+ *   txbuf - pointer to the transmit chunk buffer
+ *
+ * Returned Value:
+ *   Returns the prepared chunk header.
+ *
+ ****************************************************************************/
+
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf)
+{
+  uint32_t header = 0;
+  int txlen;
+
+  if (priv->tx_pkt && priv->txc)
+    {
+      header |= (1 << OA_TC6_DV_POS);  /* Data Valid */
+
+      if (priv->tx_pkt_idx == 0)
+        {
+          header |=   (1 << OA_TC6_SV_POS)   /* Start Valid           */
+                    | (0 << OA_TC6_SWO_POS); /* Start Word Offset = 0 */
+        }
+
+      txlen = priv->tx_pkt_len - priv->tx_pkt_idx;
+
+      if (txlen <= priv->config->chunk_payload_size)
+        {
+          header |=   (1 << OA_TC6_EV_POS)             /* End Valid       */
+                    | ((txlen - 1) << OA_TC6_EBO_POS); /* End Byte Offset */
+        }
+      else
+        {
+          txlen = priv->config->chunk_payload_size;
+        }
+
+      /* Copy data from network to txbuf, leave 4 bytes for header */
+
+      netpkt_copyout(&priv->dev, &txbuf[4], priv->tx_pkt,
+                     txlen, priv->tx_pkt_idx);
+      priv->tx_pkt_idx += txlen;
+    }
+
+  if (oa_tc6_can_rx(priv) == false)
+    {
+      header |= (1 << OA_TC6_NORX_POS);  /* No RX */
+    }
+
+  return header;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_can_rx
+ *
+ * Description:
+ *   Determine whether rx data is available and whether it can be received.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   If it is possible to receive an rx chunk, true is returned,
+ *   otherwise false is returned.
+ *
+ ****************************************************************************/
+
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv)
+{
+  if (!priv->rca)
+    {
+      return false;
+    }
+
+  if (priv->rx_pkt_ready)
+    {
+      return false;
+    }
+
+  if (priv->rx_pkt)
+    {
+      return true;
+    }
+
+  /* No RX packet, try to alloc */
+
+  priv->rx_pkt = netpkt_alloc(&priv->dev, NETPKT_RX);
+  if (priv->rx_pkt)
+    {
+      priv->rx_pkt_idx = 0;
+      return true;
+    }
+
+  ninfo("Info: Failed to alloc rx netpkt\n");
+
+  /* There is no buffer for RX data */
+
+  return false;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_try_finish_tx_packet
+ *
+ * Description:
+ *   Check whether the entire packet has been transmitted.
+ *   If so, free the tx netpkt and notify the upperhalf.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  if (priv->tx_pkt && (priv->tx_pkt_idx == priv->tx_pkt_len))
+    {
+      oa_tc6_release_tx_packet(priv);
+      netdev_lower_txdone(&priv->dev);
+    }
+}
+
+/****************************************************************************
+ * Name: oa_tc6_handle_rx_chunk
+ *
+ * Description:
+ *   Parse the received footer, update buffer status and handle data
+ *   in the rxbuf.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - the received footer
+ *   rxbuf  - pointer to the received data buffer
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf)
+{
+  int rxlen;
+  int newlen;
+
+  /* Update buffer status */
+
+  priv->txc = oa_tc6_tx_credits(footer);
+  priv->rca = oa_tc6_rx_available(footer);
+
+  /* Check rx_pkt && !rx_pkt_ready,
+   * oa_tc6_data_valid flag might have been set due to an SPI error
+   */
+
+  if (oa_tc6_data_valid(footer) && priv->rx_pkt && !priv->rx_pkt_ready)
+    {
+      if (oa_tc6_start_valid(footer))
+        {
+          /* When the end chunk is lost, this will save the upcoming frame */
+
+          priv->rx_pkt_idx = 0;
+        }
+      else if (priv->rx_pkt_idx == 0)
+        {
+          /* Skip to the start of the next frame */
+
+          return;
+        }
+
+      if (oa_tc6_end_valid(footer))
+        {
+          if (oa_tc6_frame_drop(footer))
+            {
+              nwarn("Warning: Dropping frame (FD)\n");
+              oa_tc6_release_rx_packet(priv);
+              return;
+            }
+
+          if (priv->rx_pkt_idx > OA_TC6_MAX_FRAME_SIZE(priv))
+            {
+              nwarn("Warning: Dropping frame (too long)\n");
+              oa_tc6_release_rx_packet(priv);
+              return;
+            }
+
+          rxlen = oa_tc6_end_byte_offset(footer) + 1;
+        }
+      else
+        {
+          rxlen = priv->config->chunk_payload_size;
+        }
+
+      newlen = priv->rx_pkt_idx + rxlen;
+
+      if (newlen > OA_TC6_MAX_FRAME_SIZE(priv))
+        {
+          nwarn("Dropping chunk of a packet that is too long");
+
+          /* Set index so that a subsequent chunk with
+           * smaller payload won't pass
+           */
+
+          priv->rx_pkt_idx = OA_TC6_MAX_FRAME_SIZE(priv) + 1;
+          return;
+        }
+
+      netpkt_copyin(&priv->dev, priv->rx_pkt, rxbuf,
+                    rxlen, priv->rx_pkt_idx);
+      priv->rx_pkt_idx = newlen;
+
+      if (oa_tc6_end_valid(footer))
+        {
+          oa_tc6_finalize_rx_packet(priv);
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: oa_tc6_finalize_rx_packet
+ *
+ * Description:
+ *   Strip down last 4 bytes (FCS) from the rx packet, mark it ready
+ *   and notify upper.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  netpkt_setdatalen(&priv->dev, priv->rx_pkt,
+                    netpkt_getdatalen(&priv->dev, priv->rx_pkt) - 4);
+  priv->rx_pkt_ready = true;
+  netdev_lower_rxready(&priv->dev);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_release_tx_packet
+ *
+ * Description:
+ *   Release the tx packet.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  netpkt_free(&priv->dev, priv->tx_pkt, NETPKT_TX);
+  priv->tx_pkt = NULL;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_release_rx_packet
+ *
+ * Description:
+ *   Release the rx packet.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  netpkt_free(&priv->dev, priv->rx_pkt, NETPKT_RX);
+  priv->rx_pkt = NULL;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_(select/deselect)_spi
+ *
+ * Description:
+ *   Helper functions to setup SPI hardware.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv)
+{
+  SPI_LOCK(priv->spi, true);
+
+  SPI_SETMODE(priv->spi, OA_TC6_SPI_MODE);
+  SPI_SETBITS(priv->spi, OA_TC6_SPI_NBITS);
+  SPI_HWFEATURES(priv->spi, 0);  /* disable HW features */
+  SPI_SETFREQUENCY(priv->spi, priv->config->frequency);
+#ifdef CONFIG_SPI_DELAY_CONTROL
+  SPI_SETDELAY(priv->spi, 0, 0, 0, 0);

Review Comment:
   Delay values should be configurable, likely from Kconfig. You can check 
`lcd/st7789.c` driver. But this is a nitpick, can be added in a later patch if 
you want.



##########
drivers/net/oa_tc6/oa_tc6.c:
##########
@@ -0,0 +1,2176 @@
+/****************************************************************************
+ * drivers/net/oa_tc6/oa_tc6.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 <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/spi/spi.h>
+#include <sys/endian.h>
+
+#include <nuttx/wqueue.h>
+#include <nuttx/mutex.h>
+
+#include <nuttx/net/ioctl.h>
+
+#include <nuttx/net/netdev_lowerhalf.h>
+
+#include <nuttx/net/oa_tc6.h>
+
+#ifdef CONFIG_NET_OA_TC6_NCV7410
+#include "oa_tc6_ncv7410.h"
+#endif
+
+#ifdef CONFIG_NET_OA_TC6_LAN865X
+#include "oa_tc6_lan865x.h"
+#endif
+
+#include "oa_tc6.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define OA_TC6_WORK LPWORK
+
+#define OA_TC6_N_TRIES 5
+
+#define OA_TC6_RECOVERY_WORK_INTERVAL_MS 1000
+
+/* Maximum frame size = (MTU + LL heaader size) + FCS size */
+
+#define OA_TC6_MAX_FRAME_SIZE(p) (p->dev.netdev.d_pktsize + 4)
+
+/* Packet Memory ************************************************************/
+
+/* Maximum number of allocated TX and RX netpackets */
+
+#define OA_TC6_TX_QUOTA 1
+#define OA_TC6_RX_QUOTA 2
+
+#if CONFIG_IOB_NBUFFERS < (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)
+#  error "CONFIG_IOB_NBUFFERS must be > (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)"
+#endif
+
+#ifndef CONFIG_SCHED_LPWORK
+#  error "CONFIG_SCHED_LPWORK is needed by the OA-TC6 driver"
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Bit calculations */
+
+static int oa_tc6_get_parity(uint32_t word);
+
+/* SPI transfers */
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer);
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer);
+
+/* Interrupt handling */
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg);
+static void oa_tc6_interrupt_work(FAR void *arg);
+
+/* SPI recovery */
+
+static void oa_tc6_recovery_work(FAR void *arg);
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv);
+
+/* Data Transaction Protocol logic */
+
+static void oa_tc6_io_work(FAR void *arg);
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf);
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf);
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv);
+
+/* SPI utility functions */
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_deselect_spi(FAR struct oa_tc6_driver_s *priv);
+
+/* OA-TC6 reset and configuration */
+
+static int oa_tc6_read_reg_raw(FAR struct spi_dev_s *spi,
+                               FAR struct oa_tc6_config_s *config,
+                               oa_tc6_regid_t regid, FAR uint32_t *word);
+static int oa_tc6_get_phyid(FAR struct spi_dev_s *spi,
+                            FAR struct oa_tc6_config_s *config,
+                            FAR uint32_t *phyid);
+static int oa_tc6_init_by_id(FAR struct spi_dev_s *spi,
+                             FAR struct oa_tc6_config_s *config,
+                             uint32_t phyid);
+
+static int oa_tc6_reset(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_config(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_enable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_disable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_update_mac_filter(FAR struct oa_tc6_driver_s *priv);
+
+/* Driver buffer manipulation */
+
+static void oa_tc6_reset_driver_buffers(FAR struct oa_tc6_driver_s *priv);
+
+/* MDIO */
+
+static int oa_tc6_miireg_get_regid(FAR struct mii_ioctl_data_s *req,
+                                   FAR oa_tc6_regid_t *regid);
+static int oa_tc6_get_mmd_base(uint8_t mmd,
+                               FAR uint8_t *mms, FAR uint16_t *addr);
+
+/* NuttX callback functions */
+
+static int oa_tc6_ifup(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_ifdown(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt);
+static FAR netpkt_t *oa_tc6_receive(FAR struct netdev_lowerhalf_s *dev);
+#ifdef CONFIG_NET_MCASTGROUP
+static int oa_tc6_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac);
+static int oa_tc6_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int oa_tc6_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
+                        unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct netdev_ops_s g_oa_tc6_ops =
+{
+  .ifup     = oa_tc6_ifup,
+  .ifdown   = oa_tc6_ifdown,
+  .transmit = oa_tc6_transmit,
+  .receive  = oa_tc6_receive,
+#ifdef CONFIG_NET_MCASTGROUP
+  .addmac   = oa_tc6_addmac,
+  .rmmac    = oa_tc6_rmmac,
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  .ioctl    = oa_tc6_ioctl
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: oa_tc6_get_parity
+ *
+ * Description:
+ *   Obtain parity of a 32-bit word.
+ *
+ * Input Parameters:
+ *   word - 32-bit word, subject to the parity calculation
+ *
+ * Returned Value:
+ *   If the parity of the word is even, zero is returned.
+ *   Otherwise one is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_get_parity(uint32_t word)
+{
+  /* www-graphics.stanford.edu/~seander/bithacks.html */
+
+  word ^= word >> 1;
+  word ^= word >> 2;
+  word = (word & 0x11111111u) * 0x11111111u;
+  return (word >> 28) & 1;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_poll_footer
+ *
+ * Description:
+ *   Poll a data transaction chunk footer.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - pointer to a 32-bit footer destination variable
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer)
+{
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+  uint32_t header;
+
+  header =   (1 << OA_TC6_DNC_POS)   /* Data Not Control */
+           | (1 << OA_TC6_NORX_POS); /* No Read */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, footer))
+    {
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exchange_chunk
+ *
+ * Description:
+ *   Send a data chunk to MAC-PHY and simultaneously receive chunk.
+ *
+ *   Computing header parity, checking footer parity, converting to proper
+ *   endianness and setting DNC flag is done by this function.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   txbuf  - buffer with transmit chunk data
+ *   rxbuf  - buffer to save the received chunk to
+ *   header - header controlling the transaction
+ *   footer - pointer to a 32-bit value for the footer
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer)
+{
+  header |= (1 << OA_TC6_DNC_POS);
+  header |= (!oa_tc6_get_parity(header) << OA_TC6_P_POS);
+  header = htobe32(header);
+
+  ((uint32_t *)txbuf)[0] = header;
+
+  oa_tc6_select_spi(priv);
+  SPI_EXCHANGE(priv->spi, txbuf, rxbuf, OA_TC6_CHUNK_SIZE(priv));
+  oa_tc6_deselect_spi(priv);
+
+  *footer = *((uint32_t *)(&rxbuf[priv->config->chunk_payload_size]));
+
+  *footer = be32toh(*footer);
+  if (!oa_tc6_get_parity(*footer) ||

Review Comment:
   I would consider storing the result in a temporary variable instead of 
calling these functions twice.



##########
drivers/net/oa_tc6/oa_tc6.c:
##########
@@ -0,0 +1,2176 @@
+/****************************************************************************
+ * drivers/net/oa_tc6/oa_tc6.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 <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/spi/spi.h>
+#include <sys/endian.h>
+
+#include <nuttx/wqueue.h>
+#include <nuttx/mutex.h>
+
+#include <nuttx/net/ioctl.h>
+
+#include <nuttx/net/netdev_lowerhalf.h>
+
+#include <nuttx/net/oa_tc6.h>
+
+#ifdef CONFIG_NET_OA_TC6_NCV7410
+#include "oa_tc6_ncv7410.h"
+#endif
+
+#ifdef CONFIG_NET_OA_TC6_LAN865X
+#include "oa_tc6_lan865x.h"
+#endif
+
+#include "oa_tc6.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define OA_TC6_WORK LPWORK
+
+#define OA_TC6_N_TRIES 5
+
+#define OA_TC6_RECOVERY_WORK_INTERVAL_MS 1000
+
+/* Maximum frame size = (MTU + LL heaader size) + FCS size */
+
+#define OA_TC6_MAX_FRAME_SIZE(p) (p->dev.netdev.d_pktsize + 4)
+
+/* Packet Memory ************************************************************/
+
+/* Maximum number of allocated TX and RX netpackets */
+
+#define OA_TC6_TX_QUOTA 1
+#define OA_TC6_RX_QUOTA 2
+
+#if CONFIG_IOB_NBUFFERS < (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)
+#  error "CONFIG_IOB_NBUFFERS must be > (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)"
+#endif
+
+#ifndef CONFIG_SCHED_LPWORK
+#  error "CONFIG_SCHED_LPWORK is needed by the OA-TC6 driver"
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Bit calculations */
+
+static int oa_tc6_get_parity(uint32_t word);
+
+/* SPI transfers */
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer);
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer);
+
+/* Interrupt handling */
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg);
+static void oa_tc6_interrupt_work(FAR void *arg);
+
+/* SPI recovery */
+
+static void oa_tc6_recovery_work(FAR void *arg);
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv);
+
+/* Data Transaction Protocol logic */
+
+static void oa_tc6_io_work(FAR void *arg);
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf);
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf);
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv);
+
+/* SPI utility functions */
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_deselect_spi(FAR struct oa_tc6_driver_s *priv);
+
+/* OA-TC6 reset and configuration */
+
+static int oa_tc6_read_reg_raw(FAR struct spi_dev_s *spi,
+                               FAR struct oa_tc6_config_s *config,
+                               oa_tc6_regid_t regid, FAR uint32_t *word);
+static int oa_tc6_get_phyid(FAR struct spi_dev_s *spi,
+                            FAR struct oa_tc6_config_s *config,
+                            FAR uint32_t *phyid);
+static int oa_tc6_init_by_id(FAR struct spi_dev_s *spi,
+                             FAR struct oa_tc6_config_s *config,
+                             uint32_t phyid);
+
+static int oa_tc6_reset(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_config(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_enable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_disable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_update_mac_filter(FAR struct oa_tc6_driver_s *priv);
+
+/* Driver buffer manipulation */
+
+static void oa_tc6_reset_driver_buffers(FAR struct oa_tc6_driver_s *priv);
+
+/* MDIO */
+
+static int oa_tc6_miireg_get_regid(FAR struct mii_ioctl_data_s *req,
+                                   FAR oa_tc6_regid_t *regid);
+static int oa_tc6_get_mmd_base(uint8_t mmd,
+                               FAR uint8_t *mms, FAR uint16_t *addr);
+
+/* NuttX callback functions */
+
+static int oa_tc6_ifup(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_ifdown(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt);
+static FAR netpkt_t *oa_tc6_receive(FAR struct netdev_lowerhalf_s *dev);
+#ifdef CONFIG_NET_MCASTGROUP
+static int oa_tc6_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac);
+static int oa_tc6_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int oa_tc6_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
+                        unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct netdev_ops_s g_oa_tc6_ops =
+{
+  .ifup     = oa_tc6_ifup,
+  .ifdown   = oa_tc6_ifdown,
+  .transmit = oa_tc6_transmit,
+  .receive  = oa_tc6_receive,
+#ifdef CONFIG_NET_MCASTGROUP
+  .addmac   = oa_tc6_addmac,
+  .rmmac    = oa_tc6_rmmac,
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  .ioctl    = oa_tc6_ioctl
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: oa_tc6_get_parity
+ *
+ * Description:
+ *   Obtain parity of a 32-bit word.
+ *
+ * Input Parameters:
+ *   word - 32-bit word, subject to the parity calculation
+ *
+ * Returned Value:
+ *   If the parity of the word is even, zero is returned.
+ *   Otherwise one is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_get_parity(uint32_t word)
+{
+  /* www-graphics.stanford.edu/~seander/bithacks.html */
+
+  word ^= word >> 1;
+  word ^= word >> 2;
+  word = (word & 0x11111111u) * 0x11111111u;
+  return (word >> 28) & 1;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_poll_footer
+ *
+ * Description:
+ *   Poll a data transaction chunk footer.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - pointer to a 32-bit footer destination variable
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer)
+{
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+  uint32_t header;
+
+  header =   (1 << OA_TC6_DNC_POS)   /* Data Not Control */
+           | (1 << OA_TC6_NORX_POS); /* No Read */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, footer))
+    {
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exchange_chunk
+ *
+ * Description:
+ *   Send a data chunk to MAC-PHY and simultaneously receive chunk.
+ *
+ *   Computing header parity, checking footer parity, converting to proper
+ *   endianness and setting DNC flag is done by this function.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   txbuf  - buffer with transmit chunk data
+ *   rxbuf  - buffer to save the received chunk to
+ *   header - header controlling the transaction
+ *   footer - pointer to a 32-bit value for the footer
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer)
+{
+  header |= (1 << OA_TC6_DNC_POS);
+  header |= (!oa_tc6_get_parity(header) << OA_TC6_P_POS);
+  header = htobe32(header);
+
+  ((uint32_t *)txbuf)[0] = header;
+
+  oa_tc6_select_spi(priv);
+  SPI_EXCHANGE(priv->spi, txbuf, rxbuf, OA_TC6_CHUNK_SIZE(priv));
+  oa_tc6_deselect_spi(priv);
+
+  *footer = *((uint32_t *)(&rxbuf[priv->config->chunk_payload_size]));
+
+  *footer = be32toh(*footer);
+  if (!oa_tc6_get_parity(*footer) ||
+      oa_tc6_header_bad(*footer)  ||
+      !oa_tc6_mac_phy_sync(*footer))
+    {
+      if (!oa_tc6_get_parity(*footer))
+        {
+          nerr("Error: Wrong parity in the footer\n");
+        }
+
+      if (oa_tc6_header_bad(*footer))
+        {
+          nerr("Error: HDRB set in the footer\n");
+        }
+
+      if (!oa_tc6_mac_phy_sync(*footer))
+        {
+          nerr("Error: MAC-PHY lost configuration, "
+               "SYNC cleared in the footer\n");
+        }
+
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt
+ *
+ * Description:
+ *   Schedule interrupt work when the interrupt signal from MAC-PHY is
+ *   received.
+ *
+ * Input Parameters:
+ *   irq     - not used
+ *   context - not used
+ *   arg     - oa_tc6_driver_s priv structure to be passed to the interrupt
+ *             worker
+ *
+ * Returned Value:
+ *   OK is always returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+
+  ninfo("Info: OA-TC6 interrupt!\n");
+
+  /* Schedule interrupt work */
+
+  work_queue(OA_TC6_WORK, &priv->interrupt_work,
+             oa_tc6_interrupt_work, priv, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt_work
+ *
+ * Description:
+ *   Identify the interrupt source and perform necessary work.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_interrupt_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t footer;
+  int tries = OA_TC6_N_TRIES;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP)
+    {
+      ninfo("Info: Interrupt work invoked when the interface is not up\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  ninfo("Info: OA-TC6 interrupt worker invoked!\n");
+
+  do
+    {
+      if (!oa_tc6_poll_footer(priv, &footer))
+        {
+          break;
+        }
+
+      nerr("Error: Polling footer unsuccessful\n");
+    }
+  while (--tries);
+
+  if (!tries)
+    {
+      nerr("Error: Failed to poll footer in %d tries, carrier down\n",
+           OA_TC6_N_TRIES);
+
+      oa_tc6_enter_recovery(priv);
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_ext_status(footer))
+    {
+      /* Device-specific driver may implement special functionality on EXST */
+
+      priv->ops->action(priv, OA_TC6_ACTION_EXST);
+    }
+
+  /* Update MAC-PHY buffer status */
+
+  priv->txc = oa_tc6_tx_credits(footer);
+  priv->rca = oa_tc6_rx_available(footer);
+
+  if ((priv->tx_pkt && priv->txc) || priv->rca)
+    {
+      /* Schedule IO work */
+
+      ninfo("Info: Scheduled io_work from interrupt_work\n");
+      work_queue(OA_TC6_WORK, &priv->io_work, oa_tc6_io_work, priv, 0);
+    }
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_recovery_work
+ *
+ * Description:
+ *   Every OA_TC6_RECOVERY_WORK_INTERVAL_MS milliseconds check whether the
+ *   SPI is already available.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_recovery_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t regval;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP_RECOVERY)
+    {
+      ninfo("Info: Trying to recover when not in recovery\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_read_reg(priv, OA_TC6_CONFIG0_REGID, &regval))
+    {
+      nwarn("Warning: MAC-PHY still not available\n");
+
+      work_queue(OA_TC6_WORK, &priv->recovery_work,
+                 oa_tc6_recovery_work, priv,
+                 MSEC2TICK(OA_TC6_RECOVERY_WORK_INTERVAL_MS));
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  ninfo("Info: Reading register successful during recovery\n");
+
+  if (!oa_tc6_get_field(regval, CONFIG0_SYNC))
+    {
+      /* MAC-PHY has lost config. Shut down */
+
+      nwarn("Warning: MAC-PHY has lost config (SYNC = '0'), "
+            "shutting down\n");
+
+      priv->ifstate = OA_TC6_IFSTATE_UP_UNKNOWN;
+
+      /* Unlock before shutting down, otherwise would result in deadlock */
+
+      nxmutex_unlock(&priv->lock);
+
+      net_lock();
+      netdev_ifdown(&priv->dev.netdev);
+      net_unlock();
+
+      return;
+    }
+
+  ninfo("Info: MAC-PHY SPI contact successful, exiting recovery\n");
+
+  oa_tc6_exit_recovery(priv);
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_enter_recovery
+ *
+ * Description:
+ *   Disable the interface after the contact over the SPI is lost.
+ *   Enter recovery mode in waiting for contact over SPI.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv)
+{
+  /* Disable interrupt on the board level */
+
+  priv->config->enable(priv->config, false);
+
+  work_cancel(OA_TC6_WORK, &priv->interrupt_work);
+  work_cancel(OA_TC6_WORK, &priv->io_work);
+
+  oa_tc6_reset_driver_buffers(priv);
+
+  priv->ifstate = OA_TC6_IFSTATE_UP_RECOVERY;
+
+  net_lock();
+  netdev_lower_carrier_off(&priv->dev);
+  net_unlock();
+
+  work_queue(OA_TC6_WORK, &priv->recovery_work,
+             oa_tc6_recovery_work, priv,
+             MSEC2TICK(OA_TC6_RECOVERY_WORK_INTERVAL_MS));
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exit_recovery
+ *
+ * Description:
+ *   Re-enable the interface after the contact over the SPI is recovered.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv)
+{
+  priv->ifstate = OA_TC6_IFSTATE_UP;
+
+  net_lock();
+  netdev_lower_carrier_on(&priv->dev);
+  net_unlock();
+
+  work_queue(OA_TC6_WORK, &priv->interrupt_work,
+             oa_tc6_interrupt_work, priv, 0);
+
+  /* Enable interrupt on the board level */
+
+  priv->config->enable(priv->config, true);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_io_work
+ *
+ * Description:
+ *   Exchange data chunk with the MAC-PHY.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_io_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+
+  uint32_t header;
+  uint32_t footer;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP)
+    {
+      nerr("Error: Trying to work when the interface is not up\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  header = oa_tc6_prep_chunk_exchange(priv, txbuf);
+
+  /* Perform the SPI exchange */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, &footer))
+    {
+      nerr("Error: Chunk exchange failed\n");
+
+      /* Reset buffers, effectively dropping frames */
+
+      oa_tc6_reset_driver_buffers(priv);
+
+      /* Plan the interrupt work to try and find out what's going on */
+
+      work_queue(OA_TC6_WORK, &priv->interrupt_work,
+                 oa_tc6_interrupt_work, priv, 0);
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_ext_status(footer))
+    {
+      priv->ops->action(priv, OA_TC6_ACTION_EXST);
+    }
+
+  oa_tc6_try_finish_tx_packet(priv);
+
+  oa_tc6_handle_rx_chunk(priv, footer, rxbuf);
+
+  /* Schedule further work if needed */
+
+  if ((priv->tx_pkt && priv->txc) || priv->rca)
+    {
+      work_queue(OA_TC6_WORK, &priv->io_work, oa_tc6_io_work, priv, 0);
+    }
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_prep_chunk_exchange
+ *
+ * Description:
+ *   Determine whether there is data to transmit or receive.
+ *   Set the appropriate header bitfields and fill the txbuf accordingly.
+ *
+ * Input Parameters:
+ *   priv  - pointer to the driver-specific state structure
+ *   txbuf - pointer to the transmit chunk buffer
+ *
+ * Returned Value:
+ *   Returns the prepared chunk header.
+ *
+ ****************************************************************************/
+
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf)
+{
+  uint32_t header = 0;
+  int txlen;
+
+  if (priv->tx_pkt && priv->txc)
+    {
+      header |= (1 << OA_TC6_DV_POS);  /* Data Valid */
+
+      if (priv->tx_pkt_idx == 0)
+        {
+          header |=   (1 << OA_TC6_SV_POS)   /* Start Valid           */
+                    | (0 << OA_TC6_SWO_POS); /* Start Word Offset = 0 */
+        }
+
+      txlen = priv->tx_pkt_len - priv->tx_pkt_idx;
+
+      if (txlen <= priv->config->chunk_payload_size)
+        {
+          header |=   (1 << OA_TC6_EV_POS)             /* End Valid       */
+                    | ((txlen - 1) << OA_TC6_EBO_POS); /* End Byte Offset */
+        }
+      else
+        {
+          txlen = priv->config->chunk_payload_size;
+        }
+
+      /* Copy data from network to txbuf, leave 4 bytes for header */
+
+      netpkt_copyout(&priv->dev, &txbuf[4], priv->tx_pkt,
+                     txlen, priv->tx_pkt_idx);
+      priv->tx_pkt_idx += txlen;
+    }
+
+  if (oa_tc6_can_rx(priv) == false)
+    {
+      header |= (1 << OA_TC6_NORX_POS);  /* No RX */
+    }
+
+  return header;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_can_rx
+ *
+ * Description:
+ *   Determine whether rx data is available and whether it can be received.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   If it is possible to receive an rx chunk, true is returned,
+ *   otherwise false is returned.
+ *
+ ****************************************************************************/
+
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv)
+{
+  if (!priv->rca)
+    {
+      return false;
+    }
+
+  if (priv->rx_pkt_ready)
+    {
+      return false;
+    }
+
+  if (priv->rx_pkt)
+    {
+      return true;
+    }
+
+  /* No RX packet, try to alloc */
+
+  priv->rx_pkt = netpkt_alloc(&priv->dev, NETPKT_RX);
+  if (priv->rx_pkt)
+    {
+      priv->rx_pkt_idx = 0;
+      return true;
+    }
+
+  ninfo("Info: Failed to alloc rx netpkt\n");
+
+  /* There is no buffer for RX data */
+
+  return false;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_try_finish_tx_packet
+ *
+ * Description:
+ *   Check whether the entire packet has been transmitted.
+ *   If so, free the tx netpkt and notify the upperhalf.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  if (priv->tx_pkt && (priv->tx_pkt_idx == priv->tx_pkt_len))
+    {
+      oa_tc6_release_tx_packet(priv);
+      netdev_lower_txdone(&priv->dev);
+    }
+}
+
+/****************************************************************************
+ * Name: oa_tc6_handle_rx_chunk
+ *
+ * Description:
+ *   Parse the received footer, update buffer status and handle data
+ *   in the rxbuf.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - the received footer
+ *   rxbuf  - pointer to the received data buffer
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf)
+{
+  int rxlen;
+  int newlen;
+
+  /* Update buffer status */
+
+  priv->txc = oa_tc6_tx_credits(footer);
+  priv->rca = oa_tc6_rx_available(footer);
+
+  /* Check rx_pkt && !rx_pkt_ready,
+   * oa_tc6_data_valid flag might have been set due to an SPI error
+   */
+
+  if (oa_tc6_data_valid(footer) && priv->rx_pkt && !priv->rx_pkt_ready)
+    {
+      if (oa_tc6_start_valid(footer))
+        {
+          /* When the end chunk is lost, this will save the upcoming frame */
+
+          priv->rx_pkt_idx = 0;
+        }
+      else if (priv->rx_pkt_idx == 0)
+        {
+          /* Skip to the start of the next frame */
+
+          return;
+        }
+
+      if (oa_tc6_end_valid(footer))
+        {
+          if (oa_tc6_frame_drop(footer))
+            {
+              nwarn("Warning: Dropping frame (FD)\n");
+              oa_tc6_release_rx_packet(priv);
+              return;
+            }
+
+          if (priv->rx_pkt_idx > OA_TC6_MAX_FRAME_SIZE(priv))
+            {
+              nwarn("Warning: Dropping frame (too long)\n");
+              oa_tc6_release_rx_packet(priv);
+              return;
+            }
+
+          rxlen = oa_tc6_end_byte_offset(footer) + 1;
+        }
+      else
+        {
+          rxlen = priv->config->chunk_payload_size;
+        }
+
+      newlen = priv->rx_pkt_idx + rxlen;
+
+      if (newlen > OA_TC6_MAX_FRAME_SIZE(priv))
+        {
+          nwarn("Dropping chunk of a packet that is too long");
+
+          /* Set index so that a subsequent chunk with
+           * smaller payload won't pass
+           */
+
+          priv->rx_pkt_idx = OA_TC6_MAX_FRAME_SIZE(priv) + 1;
+          return;
+        }
+
+      netpkt_copyin(&priv->dev, priv->rx_pkt, rxbuf,
+                    rxlen, priv->rx_pkt_idx);
+      priv->rx_pkt_idx = newlen;
+
+      if (oa_tc6_end_valid(footer))
+        {
+          oa_tc6_finalize_rx_packet(priv);
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: oa_tc6_finalize_rx_packet
+ *
+ * Description:
+ *   Strip down last 4 bytes (FCS) from the rx packet, mark it ready
+ *   and notify upper.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  netpkt_setdatalen(&priv->dev, priv->rx_pkt,
+                    netpkt_getdatalen(&priv->dev, priv->rx_pkt) - 4);
+  priv->rx_pkt_ready = true;
+  netdev_lower_rxready(&priv->dev);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_release_tx_packet
+ *
+ * Description:
+ *   Release the tx packet.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  netpkt_free(&priv->dev, priv->tx_pkt, NETPKT_TX);
+  priv->tx_pkt = NULL;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_release_rx_packet
+ *
+ * Description:
+ *   Release the rx packet.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv)
+{
+  netpkt_free(&priv->dev, priv->rx_pkt, NETPKT_RX);
+  priv->rx_pkt = NULL;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_(select/deselect)_spi
+ *
+ * Description:
+ *   Helper functions to setup SPI hardware.
+ *
+ * Input Parameters:
+ *   priv - pointer to the driver-specific state structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv)
+{
+  SPI_LOCK(priv->spi, true);
+
+  SPI_SETMODE(priv->spi, OA_TC6_SPI_MODE);
+  SPI_SETBITS(priv->spi, OA_TC6_SPI_NBITS);
+  SPI_HWFEATURES(priv->spi, 0);  /* disable HW features */
+  SPI_SETFREQUENCY(priv->spi, priv->config->frequency);
+#ifdef CONFIG_SPI_DELAY_CONTROL
+  SPI_SETDELAY(priv->spi, 0, 0, 0, 0);
+#endif
+
+  SPI_SELECT(priv->spi, priv->config->id, true);
+}
+
+static void oa_tc6_deselect_spi(FAR struct oa_tc6_driver_s *priv)
+{
+  SPI_SELECT(priv->spi, priv->config->id, false);
+
+  SPI_LOCK(priv->spi, false);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_read_reg_raw
+ *
+ * Description:
+ *   Read a MAC-PHY register without the need for the whole oa_tc6_driver_s
+ *   structure.
+ *
+ * Input Parameters:
+ *   spi    - pointer to the spi device instance
+ *   config - pointer to the MAC-PHY configuration structure
+ *
+ * Returned Value:
+ *   On successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_read_reg_raw(FAR struct spi_dev_s *spi,
+                               FAR struct oa_tc6_config_s *config,
+                               oa_tc6_regid_t regid, FAR uint32_t *word)
+{
+  uint32_t txdata[3];
+  uint32_t rxdata[3];
+  uint8_t  mms  = OA_TC6_REGID_GET_MMS(regid);
+  uint16_t addr = OA_TC6_REGID_GET_ADDR(regid);
+  int parity;
+  uint32_t header;
+
+  /* Prepare header */
+
+  header =   (mms  << OA_TC6_MMS_POS)
+           | (addr << OA_TC6_ADDR_POS);
+  parity = oa_tc6_get_parity(header);
+  header |= parity ? 0 : OA_TC6_P_MASK;  /* Make header odd parity */
+
+  /* Convert to big endian */
+
+  header = htobe32(header);
+
+  /* Prepare exchange */
+
+  txdata[0] = header;
+
+  SPI_LOCK(spi, true);

Review Comment:
   Call `oa_tc6_select_spi`



##########
drivers/net/oa_tc6/oa_tc6.c:
##########
@@ -0,0 +1,2176 @@
+/****************************************************************************
+ * drivers/net/oa_tc6/oa_tc6.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 <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/spi/spi.h>
+#include <sys/endian.h>
+
+#include <nuttx/wqueue.h>
+#include <nuttx/mutex.h>
+
+#include <nuttx/net/ioctl.h>
+
+#include <nuttx/net/netdev_lowerhalf.h>
+
+#include <nuttx/net/oa_tc6.h>
+
+#ifdef CONFIG_NET_OA_TC6_NCV7410
+#include "oa_tc6_ncv7410.h"
+#endif
+
+#ifdef CONFIG_NET_OA_TC6_LAN865X
+#include "oa_tc6_lan865x.h"
+#endif
+
+#include "oa_tc6.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define OA_TC6_WORK LPWORK
+
+#define OA_TC6_N_TRIES 5
+
+#define OA_TC6_RECOVERY_WORK_INTERVAL_MS 1000
+
+/* Maximum frame size = (MTU + LL heaader size) + FCS size */
+
+#define OA_TC6_MAX_FRAME_SIZE(p) (p->dev.netdev.d_pktsize + 4)
+
+/* Packet Memory ************************************************************/
+
+/* Maximum number of allocated TX and RX netpackets */
+
+#define OA_TC6_TX_QUOTA 1
+#define OA_TC6_RX_QUOTA 2
+
+#if CONFIG_IOB_NBUFFERS < (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)
+#  error "CONFIG_IOB_NBUFFERS must be > (OA_TC6_TX_QUOTA + OA_TC6_RX_QUOTA)"
+#endif
+
+#ifndef CONFIG_SCHED_LPWORK
+#  error "CONFIG_SCHED_LPWORK is needed by the OA-TC6 driver"
+#endif
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Bit calculations */
+
+static int oa_tc6_get_parity(uint32_t word);
+
+/* SPI transfers */
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer);
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer);
+
+/* Interrupt handling */
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg);
+static void oa_tc6_interrupt_work(FAR void *arg);
+
+/* SPI recovery */
+
+static void oa_tc6_recovery_work(FAR void *arg);
+static void oa_tc6_enter_recovery(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_exit_recovery(FAR struct oa_tc6_driver_s *priv);
+
+/* Data Transaction Protocol logic */
+
+static void oa_tc6_io_work(FAR void *arg);
+static uint32_t oa_tc6_prep_chunk_exchange(FAR struct oa_tc6_driver_s *priv,
+                                           FAR uint8_t *txbuf);
+static bool oa_tc6_can_rx(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_try_finish_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_handle_rx_chunk(FAR struct oa_tc6_driver_s *priv,
+                                   uint32_t footer, FAR uint8_t *rxbuf);
+static void oa_tc6_finalize_rx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_tx_packet(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_release_rx_packet(FAR struct oa_tc6_driver_s *priv);
+
+/* SPI utility functions */
+
+static void oa_tc6_select_spi(FAR struct oa_tc6_driver_s *priv);
+static void oa_tc6_deselect_spi(FAR struct oa_tc6_driver_s *priv);
+
+/* OA-TC6 reset and configuration */
+
+static int oa_tc6_read_reg_raw(FAR struct spi_dev_s *spi,
+                               FAR struct oa_tc6_config_s *config,
+                               oa_tc6_regid_t regid, FAR uint32_t *word);
+static int oa_tc6_get_phyid(FAR struct spi_dev_s *spi,
+                            FAR struct oa_tc6_config_s *config,
+                            FAR uint32_t *phyid);
+static int oa_tc6_init_by_id(FAR struct spi_dev_s *spi,
+                             FAR struct oa_tc6_config_s *config,
+                             uint32_t phyid);
+
+static int oa_tc6_reset(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_config(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_enable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_disable(FAR struct oa_tc6_driver_s *priv);
+static int oa_tc6_update_mac_filter(FAR struct oa_tc6_driver_s *priv);
+
+/* Driver buffer manipulation */
+
+static void oa_tc6_reset_driver_buffers(FAR struct oa_tc6_driver_s *priv);
+
+/* MDIO */
+
+static int oa_tc6_miireg_get_regid(FAR struct mii_ioctl_data_s *req,
+                                   FAR oa_tc6_regid_t *regid);
+static int oa_tc6_get_mmd_base(uint8_t mmd,
+                               FAR uint8_t *mms, FAR uint16_t *addr);
+
+/* NuttX callback functions */
+
+static int oa_tc6_ifup(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_ifdown(FAR struct netdev_lowerhalf_s *dev);
+static int oa_tc6_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt);
+static FAR netpkt_t *oa_tc6_receive(FAR struct netdev_lowerhalf_s *dev);
+#ifdef CONFIG_NET_MCASTGROUP
+static int oa_tc6_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac);
+static int oa_tc6_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac);
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+static int oa_tc6_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
+                        unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct netdev_ops_s g_oa_tc6_ops =
+{
+  .ifup     = oa_tc6_ifup,
+  .ifdown   = oa_tc6_ifdown,
+  .transmit = oa_tc6_transmit,
+  .receive  = oa_tc6_receive,
+#ifdef CONFIG_NET_MCASTGROUP
+  .addmac   = oa_tc6_addmac,
+  .rmmac    = oa_tc6_rmmac,
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  .ioctl    = oa_tc6_ioctl
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: oa_tc6_get_parity
+ *
+ * Description:
+ *   Obtain parity of a 32-bit word.
+ *
+ * Input Parameters:
+ *   word - 32-bit word, subject to the parity calculation
+ *
+ * Returned Value:
+ *   If the parity of the word is even, zero is returned.
+ *   Otherwise one is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_get_parity(uint32_t word)
+{
+  /* www-graphics.stanford.edu/~seander/bithacks.html */
+
+  word ^= word >> 1;
+  word ^= word >> 2;
+  word = (word & 0x11111111u) * 0x11111111u;
+  return (word >> 28) & 1;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_poll_footer
+ *
+ * Description:
+ *   Poll a data transaction chunk footer.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   footer - pointer to a 32-bit footer destination variable
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_poll_footer(FAR struct oa_tc6_driver_s *priv,
+                              FAR uint32_t *footer)
+{
+  uint8_t *txbuf = priv->txbuf;
+  uint8_t *rxbuf = priv->rxbuf;
+  uint32_t header;
+
+  header =   (1 << OA_TC6_DNC_POS)   /* Data Not Control */
+           | (1 << OA_TC6_NORX_POS); /* No Read */
+
+  if (oa_tc6_exchange_chunk(priv, txbuf, rxbuf, header, footer))
+    {
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_exchange_chunk
+ *
+ * Description:
+ *   Send a data chunk to MAC-PHY and simultaneously receive chunk.
+ *
+ *   Computing header parity, checking footer parity, converting to proper
+ *   endianness and setting DNC flag is done by this function.
+ *
+ * Input Parameters:
+ *   priv   - pointer to the driver-specific state structure
+ *   txbuf  - buffer with transmit chunk data
+ *   rxbuf  - buffer to save the received chunk to
+ *   header - header controlling the transaction
+ *   footer - pointer to a 32-bit value for the footer
+ *
+ * Returned Value:
+ *   On a successful transaction OK is returned, otherwise ERROR is returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_exchange_chunk(FAR struct oa_tc6_driver_s *priv,
+                                 FAR uint8_t *txbuf, FAR uint8_t *rxbuf,
+                                 uint32_t header, FAR uint32_t *footer)
+{
+  header |= (1 << OA_TC6_DNC_POS);
+  header |= (!oa_tc6_get_parity(header) << OA_TC6_P_POS);
+  header = htobe32(header);
+
+  ((uint32_t *)txbuf)[0] = header;
+
+  oa_tc6_select_spi(priv);
+  SPI_EXCHANGE(priv->spi, txbuf, rxbuf, OA_TC6_CHUNK_SIZE(priv));
+  oa_tc6_deselect_spi(priv);
+
+  *footer = *((uint32_t *)(&rxbuf[priv->config->chunk_payload_size]));
+
+  *footer = be32toh(*footer);
+  if (!oa_tc6_get_parity(*footer) ||
+      oa_tc6_header_bad(*footer)  ||
+      !oa_tc6_mac_phy_sync(*footer))
+    {
+      if (!oa_tc6_get_parity(*footer))
+        {
+          nerr("Error: Wrong parity in the footer\n");
+        }
+
+      if (oa_tc6_header_bad(*footer))
+        {
+          nerr("Error: HDRB set in the footer\n");
+        }
+
+      if (!oa_tc6_mac_phy_sync(*footer))
+        {
+          nerr("Error: MAC-PHY lost configuration, "
+               "SYNC cleared in the footer\n");
+        }
+
+      return ERROR;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt
+ *
+ * Description:
+ *   Schedule interrupt work when the interrupt signal from MAC-PHY is
+ *   received.
+ *
+ * Input Parameters:
+ *   irq     - not used
+ *   context - not used
+ *   arg     - oa_tc6_driver_s priv structure to be passed to the interrupt
+ *             worker
+ *
+ * Returned Value:
+ *   OK is always returned.
+ *
+ ****************************************************************************/
+
+static int oa_tc6_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+
+  ninfo("Info: OA-TC6 interrupt!\n");
+
+  /* Schedule interrupt work */
+
+  work_queue(OA_TC6_WORK, &priv->interrupt_work,
+             oa_tc6_interrupt_work, priv, 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: oa_tc6_interrupt_work
+ *
+ * Description:
+ *   Identify the interrupt source and perform necessary work.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_interrupt_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t footer;
+  int tries = OA_TC6_N_TRIES;
+
+  nxmutex_lock(&priv->lock);
+
+  if (priv->ifstate != OA_TC6_IFSTATE_UP)
+    {
+      ninfo("Info: Interrupt work invoked when the interface is not up\n");
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  ninfo("Info: OA-TC6 interrupt worker invoked!\n");
+
+  do
+    {
+      if (!oa_tc6_poll_footer(priv, &footer))
+        {
+          break;
+        }
+
+      nerr("Error: Polling footer unsuccessful\n");
+    }
+  while (--tries);
+
+  if (!tries)
+    {
+      nerr("Error: Failed to poll footer in %d tries, carrier down\n",
+           OA_TC6_N_TRIES);
+
+      oa_tc6_enter_recovery(priv);
+
+      nxmutex_unlock(&priv->lock);
+      return;
+    }
+
+  if (oa_tc6_ext_status(footer))
+    {
+      /* Device-specific driver may implement special functionality on EXST */
+
+      priv->ops->action(priv, OA_TC6_ACTION_EXST);
+    }
+
+  /* Update MAC-PHY buffer status */
+
+  priv->txc = oa_tc6_tx_credits(footer);
+  priv->rca = oa_tc6_rx_available(footer);
+
+  if ((priv->tx_pkt && priv->txc) || priv->rca)
+    {
+      /* Schedule IO work */
+
+      ninfo("Info: Scheduled io_work from interrupt_work\n");
+      work_queue(OA_TC6_WORK, &priv->io_work, oa_tc6_io_work, priv, 0);
+    }
+
+  nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: oa_tc6_recovery_work
+ *
+ * Description:
+ *   Every OA_TC6_RECOVERY_WORK_INTERVAL_MS milliseconds check whether the
+ *   SPI is already available.
+ *
+ * Input Parameters:
+ *   arg - pointer to driver private data
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void oa_tc6_recovery_work(FAR void *arg)
+{
+  FAR struct oa_tc6_driver_s *priv = (FAR struct oa_tc6_driver_s *)arg;
+  uint32_t regval;
+
+  nxmutex_lock(&priv->lock);

Review Comment:
   Same here and in other occurrences.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@nuttx.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to