/**************************************************************************** * drivers/wireless/ieee80211/bcmf_sdio.c * * Copyright (C) 2017-2018 Gregory Nutt. All rights reserved. * Author: Simon Piriou * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcmf_sdio.h" #include "bcmf_core.h" #include "bcmf_sdpcm.h" #include "bcmf_utils.h" #include "bcmf_sdio_core.h" #include "bcmf_sdio_regs.h" /* Supported chip configurations */ #ifdef CONFIG_IEEE80211_BROADCOM_BCM43362 extern const struct bcmf_sdio_chip bcmf_43362_config_sdio; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43438 extern const struct bcmf_sdio_chip bcmf_43438_config_sdio; #endif /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define BCMF_DEVICE_RESET_DELAY_MS 10 #define BCMF_DEVICE_START_DELAY_MS 10 #define BCMF_CLOCK_SETUP_DELAY_MS 500 #define BCMF_THREAD_NAME "bcmf" #define BCMF_THREAD_STACK_SIZE 2048 #define BCMF_WAITDOG_TIMEOUT_TICK (5*CLOCKS_PER_SEC) /* Chipcommon registers */ #define CHIPCOMMON_GPIO_CONTROL ((uint32_t)(0x18000000 + 0x6C) ) #define CHIPCOMMON_SR_CONTROL0 ((uint32_t)(0x18000000 + 0x504) ) #define CHIPCOMMON_SR_CONTROL1 ((uint32_t)(0x18000000 + 0x508) ) /**************************************************************************** * Private Types ****************************************************************************/ /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int bcmf_probe(FAR struct bcmf_sdio_dev_s *sbus); static int bcmf_hwinitialize(FAR struct bcmf_sdio_dev_s *sbus); static void bcmf_hwuninitialize(FAR struct bcmf_sdio_dev_s *sbus); static int bcmf_chipinitialize(FAR struct bcmf_sdio_dev_s *sbus); static int bcmf_oob_irq(FAR void *arg); static int bcmf_sdio_bus_sleep(FAR struct bcmf_sdio_dev_s *sbus, bool sleep); static void bcmf_sdio_waitdog_timeout(int argc, wdparm_t arg1, ...); static int bcmf_sdio_thread(int argc, char **argv); static int bcmf_sdio_find_block_size(unsigned int size); static int bcmf_sdio_sr_init(FAR struct bcmf_sdio_dev_s *sbus); static bool brcm_chip_sr_capable(FAR struct bcmf_sdio_dev_s *sbus); /**************************************************************************** * Private Data ****************************************************************************/ /* FIXME remove */ FAR struct bcmf_dev_s *g_sdio_priv; /* Buffer pool for SDIO bus interface * This pool is shared between all driver devices */ static struct bcmf_sdio_frame g_pktframes[BCMF_PKT_POOL_SIZE]; /* TODO free_queue should be static */ /**************************************************************************** * Private Functions ****************************************************************************/ int bcmf_oob_irq(FAR void *arg) { FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)arg; if (sbus->ready) { /* Signal bmcf thread */ sbus->irq_pending = true; nxsem_post(&sbus->thread_signal); } return OK; } int bcmf_sdio_bus_sleep(FAR struct bcmf_sdio_dev_s *sbus, bool sleep) { int ret; int loops; uint8_t value; if (sbus->sleeping == sleep) { return OK; } if (sleep) { sbus->sleeping = true; return bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, 0); } else { loops = 200; while (--loops > 0) { /* Request HT Avail */ ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, SBSDIO_HT_AVAIL_REQ | SBSDIO_FORCE_HT); if (ret != OK) { wlerr("HT Avail request failed %d\n", ret); return ret; } /* Wait for High Throughput clock */ up_mdelay(100); ret = bcmf_read_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, &value); if (ret != OK) { return ret; } if (value & SBSDIO_HT_AVAIL) { /* High Throughput clock is ready */ break; } } if (loops <= 0) { wlerr("HT clock not ready\n"); return -ETIMEDOUT; } sbus->sleeping = false; } return OK; } /**************************************************************************** * Name: bcmf_probe ****************************************************************************/ int bcmf_probe(FAR struct bcmf_sdio_dev_s *sbus) { int ret; /* Probe sdio card compatible device */ ret = sdio_probe(sbus->sdio_dev); if (ret != OK) { goto exit_error; } /* Set FN0 / FN1 / FN2 default block size */ ret = sdio_set_blocksize(sbus->sdio_dev, 0, 64); if (ret != OK) { goto exit_error; } ret = sdio_set_blocksize(sbus->sdio_dev, 1, 64); if (ret != OK) { goto exit_error; } ret = sdio_set_blocksize(sbus->sdio_dev, 2, 64); if (ret != OK) { goto exit_error; } /* Enable device interrupts for FN0, FN1 and FN2 */ ret = bcmf_write_reg(sbus, 0, SDIO_CCCR_INTEN, (1 << 0) | (1 << 1) | (1 << 2)); if (ret != OK) { goto exit_error; } /* Default device clock speed is up to 25 Mhz * We could set EHS bit to operate at a clock rate up to 50 Mhz. */ SDIO_CLOCK(sbus->sdio_dev, CLOCK_SD_TRANSFER_4BIT); up_mdelay(BCMF_CLOCK_SETUP_DELAY_MS); /* Enable bus FN1 */ ret = sdio_enable_function(sbus->sdio_dev, 1); if (ret != OK) { goto exit_error; } return OK; exit_error: wlerr("ERROR: failed to probe device %d\n", sbus->minor); return ret; } /**************************************************************************** * Name: bcmf_businitialize ****************************************************************************/ int bcmf_businitialize(FAR struct bcmf_sdio_dev_s *sbus) { int ret; int loops; uint8_t value; /* Send Active Low-Power clock request */ ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ | SBSDIO_FORCE_ALP); if (ret != OK) { return ret; } loops = 10; while (--loops > 0) { up_mdelay(10); ret = bcmf_read_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, &value); if (ret != OK) { return ret; } if (value & SBSDIO_ALP_AVAIL) { /* Active Low-Power clock is ready */ break; } } if (loops <= 0) { wlerr("failed to enable ALP\n"); return -ETIMEDOUT; } /* Clear Active Low-Power clock request */ ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, 0); if (ret != OK) { return ret; } /* Disable pull-ups on SDIO cmd, d0-2 lines */ ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_SDIOPULLUP, 0); if (ret != OK) { return ret; } /* Do chip specific initialization */ ret = bcmf_chipinitialize(sbus); if (ret != OK) { return ret; } /* Upload firmware */ ret = bcmf_core_upload_firmware(sbus); if (ret != OK) { return ret; } /* Enable FN2 (frame transfers) */ ret = sdio_enable_function(sbus->sdio_dev, 2); if (ret != OK) { return ret; } return OK; } int bcmf_bus_setup_interrupts(FAR struct bcmf_sdio_dev_s *sbus) { int ret; /* Configure gpio interrupt pin */ bcmf_board_setup_oob_irq(sbus->minor, bcmf_oob_irq, (void *)sbus); /* Enable function 2 interrupt */ ret = sdio_enable_interrupt(sbus->sdio_dev, 0); if (ret != OK) { return ret; } ret = sdio_enable_interrupt(sbus->sdio_dev, 2); if (ret != OK) { return ret; } #ifndef CONFIG_BCMFMAC_NO_OOB /* Redirect, configure and enable io for out-of-band interrupt signal */ ret = bcmf_write_reg(sbus, 0, SDIO_CCCR_BRCM_SEPINT, SDIO_SEPINT_MASK | SDIO_SEPINT_OE | SDIO_SEPINT_ACT_HI); if (ret != OK) { return ret; } #endif /* Wake up chip to be sure function 2 is running */ ret = bcmf_sdio_bus_sleep(sbus, false); if (ret != OK) { return ret; } /* FN2 successfully enabled, set core and enable interrupts */ bcmf_write_sbregw(sbus, CORE_BUS_REG(sbus->chip->core_base[SDIOD_CORE_ID], hostintmask), I_HMB_SW_MASK); bcmf_write_sbregb(sbus, CORE_BUS_REG(sbus->chip->core_base[SDIOD_CORE_ID], funcintmask), 2); /* Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped. */ bcmf_write_reg(sbus, 1, SBSDIO_WATERMARK, 8); return OK; } /**************************************************************************** * Name: bcmf_hwinitialize ****************************************************************************/ int bcmf_hwinitialize(FAR struct bcmf_sdio_dev_s *sbus) { /* Attach and prepare SDIO interrupts */ SDIO_ATTACH(sbus->sdio_dev); /* Set ID mode clocking (<400KHz) */ SDIO_CLOCK(sbus->sdio_dev, CLOCK_IDMODE); /* Configure hardware */ bcmf_board_initialize(sbus->minor); /* Reset and power device */ bcmf_board_reset(sbus->minor, true); bcmf_board_power(sbus->minor, true); up_mdelay(BCMF_DEVICE_RESET_DELAY_MS); bcmf_board_reset(sbus->minor, false); /* Wait for device to start */ up_mdelay(BCMF_DEVICE_START_DELAY_MS); return OK; } /**************************************************************************** * Name: bcmf_hwuninitialize ****************************************************************************/ void bcmf_hwuninitialize(FAR struct bcmf_sdio_dev_s *sbus) { /* Shutdown device */ bcmf_board_power(sbus->minor, false); bcmf_board_reset(sbus->minor, true); } int bcmf_sdio_find_block_size(unsigned int size) { int ret = 0; int size_copy = size; while (size_copy) { size_copy >>= 1; ret++; } if (size & (size-1)) { return 1 << ret; } return 1 << (ret - 1); } /* Init save-restore if the firmware support it: */ static int bcmf_sdio_sr_init(FAR struct bcmf_sdio_dev_s *sbus) { uint8_t data; if (brcm_chip_sr_capable(sbus)) { /* Configure WakeupCtrl register to set HtAvail request bit in * chipClockCSR register after the sdiod core is powered on. */ bcmf_read_reg(sbus, 1, SBSDIO_FUNC1_WAKEUPCTRL, &data); data |= SBSDIO_FUNC1_WCTRL_HTWAIT_MASK; bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_WAKEUPCTRL, data); /* Set brcmCardCapability to noCmdDecode mode. * It makes sdiod_aos to wakeup host for any activity of cmd line, * even though module won't decode cmd or respond */ bcmf_write_reg(sbus, 0, SDIO_CCCR_BRCM_CARDCAP, SDIO_CCCR_BRCM_CARDCAP_CMD_NODEC); bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_CHIPCLKCSR, SBSDIO_FORCE_HT); /* Enable KeepSdioOn (KSO) bit for normal operation */ bcmf_read_reg(sbus, 1, SBSDIO_FUNC1_SLEEPCSR, &data); if ((data & SBSDIO_FUNC1_SLEEPCSR_KSO_MASK) == 0) { data |= SBSDIO_FUNC1_SLEEPCSR_KSO_MASK; bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_SLEEPCSR, data); } } return OK; } /* Check if the firmware supports save restore feature. * TODO: Add more chip specific logic, and move it to a new bcmf_chip.c file. */ static bool brcm_chip_sr_capable(FAR struct bcmf_sdio_dev_s *sbus) { uint32_t srctrl = 0; int ret; /* Check if fw initialized sr engine */ ret = bcmf_read_sbregw(sbus, CHIPCOMMON_SR_CONTROL1, &srctrl); if (ret != OK) { return false; } else { return (srctrl != 0); } } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: bcmf_transfer_bytes ****************************************************************************/ int bcmf_transfer_bytes(FAR struct bcmf_sdio_dev_s *sbus, bool write, uint8_t function, uint32_t address, uint8_t *buf, unsigned int len) { unsigned int blocklen; unsigned int nblocks; /* Use rw_io_direct method if len is 1 */ if (len == 0) { return -EINVAL; } if (len == 1) { if (write) { return sdio_io_rw_direct(sbus->sdio_dev, write, function, address, *buf, NULL); } return sdio_io_rw_direct(sbus->sdio_dev, write, function, address, 0, buf); } /* Find best block settings for transfer */ if (len >= 64) { /* Use block mode */ blocklen = 64; nblocks = (len+63) / 64; } else { /* Use byte mode */ blocklen = bcmf_sdio_find_block_size(len); nblocks = 0; } return sdio_io_rw_extended(sbus->sdio_dev, write, function, address, true, buf, blocklen, nblocks); } /**************************************************************************** * Name: bcmf_read_reg ****************************************************************************/ int bcmf_read_reg(FAR struct bcmf_sdio_dev_s *sbus, uint8_t function, uint32_t address, uint8_t *reg) { *reg = 0; return bcmf_transfer_bytes(sbus, false, function, address, reg, 1); } /**************************************************************************** * Name: bcmf_write_reg ****************************************************************************/ int bcmf_write_reg(FAR struct bcmf_sdio_dev_s *sbus, uint8_t function, uint32_t address, uint8_t reg) { return bcmf_transfer_bytes(sbus, true, function, address, ®, 1); } /**************************************************************************** * Name: bcmf_bus_sdio_initialize ****************************************************************************/ int bcmf_bus_sdio_initialize(FAR struct bcmf_dev_s *priv, int minor, FAR struct sdio_dev_s *dev) { int ret; FAR struct bcmf_sdio_dev_s *sbus; /* Allocate sdio bus structure */ sbus = (FAR struct bcmf_sdio_dev_s *)kmm_malloc(sizeof(*sbus)); if (!sbus) { return -ENOMEM; } /* Initialize sdio bus device structure */ memset(sbus, 0, sizeof(*sbus)); sbus->sdio_dev = dev; sbus->minor = minor; sbus->ready = false; sbus->sleeping = true; sbus->bus.txframe = bcmf_sdpcm_queue_frame; sbus->bus.rxframe = bcmf_sdpcm_get_rx_frame; sbus->bus.allocate_frame = bcmf_sdpcm_alloc_frame; sbus->bus.free_frame = bcmf_sdpcm_free_frame; sbus->bus.stop = NULL; /* TODO */ /* Init transmit frames queue */ if ((ret = nxsem_init(&sbus->queue_mutex, 0, 1)) != OK) { goto exit_free_bus; } sq_init(&sbus->tx_queue); sq_init(&sbus->rx_queue); sq_init(&sbus->free_queue); /* Setup free buffer list */ /* FIXME this should be static to driver */ for (ret = 0; ret < BCMF_PKT_POOL_SIZE; ret++) { bcmf_dqueue_push(&sbus->free_queue, &g_pktframes[ret].list_entry); } /* Init thread semaphore */ if ((ret = nxsem_init(&sbus->thread_signal, 0, 0)) != OK) { goto exit_free_bus; } if ((ret = nxsem_setprotocol(&sbus->thread_signal, SEM_PRIO_NONE)) != OK) { goto exit_free_bus; } /* Init thread waitdog */ sbus->waitdog = wd_create(); if (!sbus->waitdog) { ret = -ENOMEM; goto exit_free_bus; } /* Initialize device hardware */ ret = bcmf_hwinitialize(sbus); if (ret != OK) { goto exit_free_waitdog; } /* Probe device */ ret = bcmf_probe(sbus); if (ret != OK) { goto exit_uninit_hw; } /* Initialize device bus */ ret = bcmf_businitialize(sbus); if (ret != OK) { goto exit_uninit_hw; } up_mdelay(100); sbus->ready = true; ret = bcmf_bus_setup_interrupts(sbus); if (ret != OK) { goto exit_uninit_hw; } ret = bcmf_sdio_sr_init(sbus); if (ret != OK) { goto exit_uninit_hw; } /* FIXME global variable for now */ g_sdio_priv = priv; /* Register sdio bus */ priv->bus = &sbus->bus; /* Start the waitdog timer */ (void)wd_start(sbus->waitdog, BCMF_WAITDOG_TIMEOUT_TICK, bcmf_sdio_waitdog_timeout, (wdparm_t)priv); /* Spawn bcmf daemon thread */ ret = kthread_create(BCMF_THREAD_NAME, SCHED_PRIORITY_MAX, BCMF_THREAD_STACK_SIZE, bcmf_sdio_thread, (FAR char * const *)NULL); if (ret <= 0) { wlerr("Cannot spawn bcmf thread\n"); ret = -EBADE; goto exit_uninit_hw; } sbus->thread_id = ret; /* SDIO bus is up and running */ return OK; exit_uninit_hw: bcmf_hwuninitialize(sbus); exit_free_waitdog: wd_delete(sbus->waitdog); exit_free_bus: kmm_free(sbus); priv->bus = NULL; return ret; } int bcmf_chipinitialize(FAR struct bcmf_sdio_dev_s *sbus) { uint32_t value = 0; int chipid; int ret; ret = bcmf_read_sbregw(sbus, SI_ENUM_BASE, &value); if (ret != OK) { return ret; } chipid = value & 0xffff; sbus->cur_chip_id = chipid; switch (chipid) { #ifdef CONFIG_IEEE80211_BROADCOM_BCM43362 case SDIO_DEVICE_ID_BROADCOM_43362: wlinfo("bcm43362 chip detected\n"); sbus->chip = (struct bcmf_sdio_chip *)&bcmf_43362_config_sdio; break; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43438 case SDIO_DEVICE_ID_BROADCOM_43430: wlinfo("bcm43438 chip detected\n"); sbus->chip = (struct bcmf_sdio_chip *)&bcmf_43438_config_sdio; break; #endif default: wlerr("chip 0x%x is not supported\n", chipid); return -ENODEV; } return OK; } void bcmf_sdio_waitdog_timeout(int argc, wdparm_t arg1, ...) { FAR struct bcmf_dev_s *priv = (FAR struct bcmf_dev_s *)arg1; FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; /* Notify bcmf thread */ wlinfo("Notify bcmf thread\n"); nxsem_post(&sbus->thread_signal); } int bcmf_sdio_thread(int argc, char **argv) { FAR struct bcmf_dev_s *priv = g_sdio_priv; FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; int ret; wlinfo(" Enter\n"); /* FIXME wait for the chip to be ready to receive commands */ up_mdelay(50); while (sbus->ready) { /* Wait for event (device interrupt, user request or waitdog timer) */ ret = nxsem_wait(&sbus->thread_signal); if (ret < 0) { wlerr("Error while waiting for semaphore\n"); break; } /* Restart the waitdog timer */ (void)wd_start(sbus->waitdog, BCMF_WAITDOG_TIMEOUT_TICK, bcmf_sdio_waitdog_timeout, (wdparm_t)priv); /* Wake up device */ bcmf_sdio_bus_sleep(sbus, false); if (sbus->irq_pending) { /* Woken up by interrupt, read device status */ sbus->irq_pending = false; bcmf_read_sbregw(sbus, CORE_BUS_REG(sbus->chip->core_base[SDIOD_CORE_ID], intstatus), &sbus->intstatus); /* Clear interrupts */ bcmf_write_sbregw(sbus, CORE_BUS_REG(sbus->chip->core_base[SDIOD_CORE_ID], intstatus), sbus->intstatus); } /* On frame indication, read available frames */ if (sbus->intstatus & I_HMB_FRAME_IND) { do { ret = bcmf_sdpcm_readframe(priv); } while (ret == OK); if (ret == -ENODATA) { /* All frames processed */ sbus->intstatus &= ~I_HMB_FRAME_IND; } } /* Send all queued frames */ do { ret = bcmf_sdpcm_sendframe(priv); } while (ret == OK); /* Check if RX frames are available */ if (sbus->intstatus & I_HMB_FRAME_IND) { /* Try again */ wlinfo("Try read again\n"); continue; } /* Re-configure the board GPIO interrupt pin */ bcmf_board_setup_oob_irq(sbus->minor, bcmf_oob_irq, (void *)sbus); /* If we're done for now, turn off clock request. */ #if 0 /* TODO add wakelock */ bcmf_sdio_bus_sleep(sbus, true); #endif } wlinfo("Exit\n"); return 0; } struct bcmf_sdio_frame *bcmf_sdio_allocate_frame(FAR struct bcmf_dev_s *priv, bool block, bool tx) { FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; struct bcmf_sdio_frame *sframe; dq_entry_t *entry = NULL; while (1) { if (nxsem_wait(&sbus->queue_mutex) < 0) { DEBUGPANIC(); } #if 0 if (!tx || sbus->tx_queue_count < BCMF_PKT_POOL_SIZE-1) #endif { if ((entry = bcmf_dqueue_pop_tail(&sbus->free_queue)) != NULL) { if (tx) { sbus->tx_queue_count += 1; } nxsem_post(&sbus->queue_mutex); break; } } nxsem_post(&sbus->queue_mutex); if (block) { /* TODO use signaling semaphore */ wlinfo("alloc failed %d\n", tx); up_mdelay(100); continue; } wlinfo("No avail buffer\n"); return NULL; } sframe = container_of(entry, struct bcmf_sdio_frame, list_entry); sframe->header.len = HEADER_SIZE + MAX_NETDEV_PKTSIZE; sframe->header.base = sframe->data; sframe->header.data = sframe->data; sframe->tx = tx; return sframe; } void bcmf_sdio_free_frame(FAR struct bcmf_dev_s *priv, struct bcmf_sdio_frame *sframe) { FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; if (nxsem_wait(&sbus->queue_mutex) < 0) { DEBUGPANIC(); } bcmf_dqueue_push(&sbus->free_queue, &sframe->list_entry); if (sframe->tx) { sbus->tx_queue_count -= 1; } nxsem_post(&sbus->queue_mutex); }