/**************************************************************************** * drivers/wireless/ieee80211/bcm43xxx/bcmf_sdio.c * * 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 #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" /**************************************************************************** * 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_LOWPOWER_TIMEOUT_TICK SEC2TICK(2) /* Chip-common 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)) /**************************************************************************** * Public Data ****************************************************************************/ /* Supported chip configurations */ #ifdef CONFIG_IEEE80211_BROADCOM_BCM4301X extern const struct bcmf_chip_data bcmf_4301x_config_data; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43362 extern const struct bcmf_chip_data bcmf_43362_config_data; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43438 extern const struct bcmf_chip_data bcmf_43438_config_data; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43455 extern const struct bcmf_chip_data bcmf_43455_config_data; #endif /**************************************************************************** * 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 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 ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ int bcmf_oob_irq(FAR void *arg) { FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)arg; int semcount; if (sbus->ready) { /* Signal bmcf thread */ sbus->irq_pending = true; nxsem_get_value(&sbus->thread_signal, &semcount); if (semcount < 1) { nxsem_post(&sbus->thread_signal); } } return OK; } int bcmf_sdio_kso_enable(FAR struct bcmf_sdio_dev_s *sbus, bool enable) { uint8_t value; int loops; int ret; if (!sbus->ready) { return -EPERM; } if (sbus->kso_enable == enable) { return OK; } if (enable) { loops = 200; while (--loops > 0) { ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_SLEEPCSR, SBSDIO_FUNC1_SLEEPCSR_KSO_MASK | SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK); if (ret != OK) { wlerr("HT Avail request failed %d\n", ret); return ret; } nxsig_usleep(100 * 1000); ret = bcmf_read_reg(sbus, 1, SBSDIO_FUNC1_SLEEPCSR, &value); if (ret != OK) { return ret; } if ((value & (SBSDIO_FUNC1_SLEEPCSR_KSO_MASK | SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK)) != 0) { break; } } if (loops <= 0) { return -ETIMEDOUT; } } else { ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_SLEEPCSR, 0); } if (ret == OK) { sbus->kso_enable = enable; } return ret; } int bcmf_sdio_bus_sleep(FAR struct bcmf_sdio_dev_s *sbus, bool sleep) { uint8_t value; int loops; int ret; if (!sbus->ready) { return -EPERM; } 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 */ nxsig_usleep(100 * 1000); 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; } int bcmf_sdio_bus_lowpower(FAR struct bcmf_sdio_dev_s *sbus, bool enable) { return sbus->support_sr ? bcmf_sdio_kso_enable(sbus, !enable) : bcmf_sdio_bus_sleep(sbus, enable); } /**************************************************************************** * Name: bcmf_probe ****************************************************************************/ int bcmf_probe(FAR struct bcmf_sdio_dev_s *sbus) { int ret; #ifdef CONFIG_IEEE80211_BROADCOM_SDIO_EHS_MODE uint8_t value; #endif /* 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; } #ifdef CONFIG_IEEE80211_BROADCOM_SDIO_EHS_MODE /* Default device clock speed is up to 25 MHz. * We could set EHS bit to operate at a clock rate up to 50 MHz. */ ret = bcmf_read_reg(sbus, 0, SDIO_CCCR_HIGHSPEED, &value); if (ret != OK) { goto exit_error; } if (value & SDIO_CCCR_HIGHSPEED_SHS) { /* If the chip confirms its High-Speed capability, * enable the High-Speed mode. */ ret = bcmf_write_reg(sbus, 0, SDIO_CCCR_HIGHSPEED, SDIO_CCCR_HIGHSPEED_EHS); if (ret != OK) { goto exit_error; } } else { wlwarn("High-Speed mode is not supported by the chip!\n"); } #endif SDIO_CLOCK(sbus->sdio_dev, CLOCK_SD_TRANSFER_4BIT); nxsig_usleep(BCMF_CLOCK_SETUP_DELAY_MS * 1000); /* 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) { nxsig_usleep(10 * 1000); 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) { /* Power device */ bcmf_board_power(sbus->minor, true); /* Attach and prepare SDIO interrupts */ SDIO_ATTACH(sbus->sdio_dev); /* Set ID mode clocking (<400KHz) */ SDIO_CLOCK(sbus->sdio_dev, CLOCK_IDMODE); /* Reset device */ bcmf_board_reset(sbus->minor, true); nxsig_usleep(BCMF_DEVICE_RESET_DELAY_MS * 1000); bcmf_board_reset(sbus->minor, false); /* Wait for device to start */ nxsig_usleep(BCMF_DEVICE_START_DELAY_MS * 1000); 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_sdio_kso_enable(sbus, true); sbus->support_sr = true; } 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); } } /**************************************************************************** * Name: bcmf_bus_sdio_initialize ****************************************************************************/ static int bcmf_bus_sdio_initialize(FAR struct bcmf_dev_s *priv, int minor, FAR struct sdio_dev_s *dev) { FAR struct bcmf_sdio_dev_s *sbus; FAR char *argv[2]; char arg1[32]; int ret; /* Allocate sdio bus structure */ sbus = 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 */ nxmutex_init(&sbus->queue_lock); list_initialize(&sbus->tx_queue); list_initialize(&sbus->rx_queue); /* Setup free buffer list */ bcmf_initialize_interface_frames(); /* Init thread semaphore */ nxsem_init(&sbus->thread_signal, 0, 0); /* Configure hardware */ bcmf_board_initialize(sbus->minor); /* Register sdio bus */ priv->bus = &sbus->bus; /* Spawn bcmf daemon thread */ snprintf(arg1, sizeof(arg1), "%p", priv); argv[0] = arg1; argv[1] = NULL; ret = kthread_create(BCMF_THREAD_NAME, CONFIG_IEEE80211_BROADCOM_SCHED_PRIORITY, BCMF_THREAD_STACK_SIZE, bcmf_sdio_thread, argv); if (ret <= 0) { wlerr("Cannot spawn bcmf thread\n"); ret = -EBADE; goto exit_free_bus; } sbus->thread_id = (pid_t)ret; return OK; exit_free_bus: kmm_free(sbus); priv->bus = NULL; return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: bcmf_sdio_initialize * * Description: * Initialize the drive with a SDIO connection. ****************************************************************************/ int bcmf_sdio_initialize(int minor, FAR struct sdio_dev_s *dev) { int ret; FAR struct bcmf_dev_s *priv; wlinfo("minor: %d\n", minor); priv = bcmf_allocate_device(); if (!priv) { return -ENOMEM; } /* Init sdio bus */ ret = bcmf_bus_sdio_initialize(priv, minor, dev); if (ret != OK) { ret = -EIO; goto exit_free_device; } /* Bus initialized, register network driver */ return bcmf_driver_initialize(priv); exit_free_device: bcmf_free_device(priv); return ret; } /**************************************************************************** * 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; if (!sbus->ready) { return -EPERM; } /* 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_active ****************************************************************************/ int bcmf_bus_sdio_active(FAR struct bcmf_dev_s *priv, bool active) { FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; int ret = OK; if (!active) { goto exit_uninit_hw; } /* Initialize device hardware */ ret = bcmf_hwinitialize(sbus); if (ret != OK) { return ret; } sbus->ready = active; /* 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; } nxsig_usleep(100 * 1000); ret = bcmf_bus_setup_interrupts(sbus); if (ret != OK) { goto exit_uninit_hw; } ret = bcmf_sdio_sr_init(sbus); if (ret == OK) { return ret; } exit_uninit_hw: sbus->ready = false; bcmf_hwuninitialize(sbus); 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_BCM4301X case SDIO_DEVICE_ID_BROADCOM_43012: case SDIO_DEVICE_ID_BROADCOM_43013: wlinfo("bcm%d chip detected\n", chipid); sbus->chip = (struct bcmf_chip_data *)&bcmf_4301x_config_data; break; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43362 case SDIO_DEVICE_ID_BROADCOM_43362: wlinfo("bcm43362 chip detected\n"); sbus->chip = (struct bcmf_chip_data *)&bcmf_43362_config_data; break; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43438 case SDIO_DEVICE_ID_BROADCOM_43430: wlinfo("bcm43438 chip detected\n"); sbus->chip = (struct bcmf_chip_data *)&bcmf_43438_config_data; break; #endif #ifdef CONFIG_IEEE80211_BROADCOM_BCM43455 case SDIO_DEVICE_ID_BROADCOM_43455: wlinfo("bcm43455 chip detected\n"); sbus->chip = (struct bcmf_chip_data *)&bcmf_43455_config_data; break; #endif default: wlerr("chip 0x%x is not supported\n", chipid); return -ENODEV; } return OK; } int bcmf_sdio_thread(int argc, char **argv) { FAR struct bcmf_dev_s *priv = (FAR struct bcmf_dev_s *) ((uintptr_t)strtoul(argv[1], NULL, 16)); FAR struct bcmf_sdio_dev_s *sbus = (FAR struct bcmf_sdio_dev_s *)priv->bus; uint32_t timeout = BCMF_LOWPOWER_TIMEOUT_TICK; int ret; wlinfo(" Enter\n"); /* FIXME wait for the chip to be ready to receive commands */ nxsig_usleep(50 * 1000); while (true) { /* Check if RX/TX frames are available */ if ((sbus->intstatus & I_HMB_FRAME_IND) == 0 && list_is_empty(&sbus->tx_queue) && !sbus->irq_pending) { /* Wait for event (device interrupt or user request) */ if (timeout == UINT_MAX) { ret = nxsem_wait_uninterruptible(&sbus->thread_signal); } else { ret = nxsem_tickwait_uninterruptible(&sbus->thread_signal, timeout); } if (ret == -ETIMEDOUT) { /* Turn off clock request. */ timeout = UINT_MAX; #ifdef CONFIG_IEEE80211_BROADCOM_LOWPOWER bcmf_sdio_bus_lowpower(sbus, true); #endif continue; } else if (ret < 0) { wlerr("Error while waiting for semaphore\n"); break; } } timeout = BCMF_LOWPOWER_TIMEOUT_TICK; /* Wake up device */ bcmf_sdio_bus_lowpower(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); } wlinfo("Exit\n"); return 0; }