nuttx/drivers/wireless/ieee80211/bcmf_core.c
Dave Marples 970295d0fe i.MXRT USDHC: This change completes SDIO support for IMXRT, and also adds support for WiFi using the AP6212A module based on Simon Piriou's rather excellent work. The patch should also address DavidS's concern about width setting for USDHC1 & 2.
Testing of the WiFi is minimal so far but functionality is proven. I'm specifically not happy that the driver doesn't recover elegantly from a DMA data checksum failure, but that is an issue that can be dealt with in due course ... I'm trying to get the rest of the interfaces fleshed out and the hardware proven so it can go for pre-production build. I _think_ there's only Bluetooth and USB-device left to implement now.
2019-07-28 16:20:33 -06:00

596 lines
16 KiB
C

/****************************************************************************
* drivers/wireless/ieee80211/bcmf_core.c
*
* Copyright (C) 2017-2018 Gregory Nutt. All rights reserved.
* Author: Simon Piriou <spiriou31@gmail.com>
*
* 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 <nuttx/config.h>
#include <nuttx/compiler.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include "bcmf_core.h"
#include "bcmf_sdio.h"
#include "bcmf_sdio_regs.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Agent registers (common for every core) */
#define BCMA_IOCTL 0x0408 /* IO control */
#define BCMA_IOST 0x0500 /* IO status */
#define BCMA_RESET_CTL 0x0800 /* Reset control */
#define BCMA_RESET_ST 0x0804
#define BCMA_IOCTL_CLK 0x0001
#define BCMA_IOCTL_FGC 0x0002
#define BCMA_IOCTL_CORE_BITS 0x3FFC
#define BCMA_IOCTL_PME_EN 0x4000
#define BCMA_IOCTL_BIST_EN 0x8000
#define BCMA_IOST_CORE_BITS 0x0FFF
#define BCMA_IOST_DMA64 0x1000
#define BCMA_IOST_GATED_CLK 0x2000
#define BCMA_IOST_BIST_ERROR 0x4000
#define BCMA_IOST_BIST_DONE 0x8000
#define BCMA_RESET_CTL_RESET 0x0001
/* SOCSRAM core registers */
#define SOCSRAM_BANKX_INDEX ((uint32_t) (0x18004000 + 0x10) )
#define SOCSRAM_BANKX_PDA ((uint32_t) (0x18004000 + 0x44) )
/* Transfer size properties */
#define BCMF_UPLOAD_TRANSFER_SIZE (64 * 256)
/* Define this to validate uploaded materials */
/* #define DBG_VALIDATE_UPLOAD */
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef DBG_VALIDATE_UPLOAD
static uint8_t compare_buffer[BCMF_UPLOAD_TRANSFER_SIZE];
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int bcmf_core_set_backplane_window(FAR struct bcmf_sdio_dev_s *sbus,
uint32_t address);
static int bcmf_upload_binary(FAR struct bcmf_sdio_dev_s *sbusv,
uint32_t address, uint8_t *buf,
unsigned int len);
static int bcmf_upload_nvram(FAR struct bcmf_sdio_dev_s *sbus);
/****************************************************************************
* Private Functions
****************************************************************************/
int bcmf_core_set_backplane_window(FAR struct bcmf_sdio_dev_s *sbus,
uint32_t address)
{
int ret;
int i;
address &= ~SBSDIO_SB_OFT_ADDR_MASK;
for (i = 1; i < 4; i++)
{
uint8_t addr_part = (address >> (8*i)) & 0xff;
uint8_t cur_addr_part = (sbus->backplane_current_addr >> (8*i)) & 0xff;
if (addr_part != cur_addr_part)
{
/* Update current backplane base address */
ret = bcmf_write_reg(sbus, 1, SBSDIO_FUNC1_SBADDRLOW + i - 1,
addr_part);
if (ret != OK)
{
return ret;
}
sbus->backplane_current_addr &= ~(0xff << (8*i));
sbus->backplane_current_addr |= addr_part << (8*i);
}
}
return OK;
}
int bcmf_upload_binary(FAR struct bcmf_sdio_dev_s *sbus, uint32_t address,
uint8_t *buf, unsigned int len)
{
unsigned int size;
#ifdef DBG_VALIDATE_UPLOAD
uint32_t validate_address = address;
uint8_t *validate_buffer = buf;
unsigned int validate_len = len;
#endif
while (len > 0)
{
/* Set the backplane window to include the start address */
int ret = bcmf_core_set_backplane_window(sbus, address);
if (ret != OK)
{
wlerr("Backplane setting failed at %08x\n", address);
return ret;
}
if (len > BCMF_UPLOAD_TRANSFER_SIZE)
{
size = BCMF_UPLOAD_TRANSFER_SIZE;
}
else
{
size = len;
}
/* Transfer firmware data */
ret = bcmf_transfer_bytes(sbus, true, 1,
address & SBSDIO_SB_OFT_ADDR_MASK, buf, size);
if (ret != OK)
{
wlerr("transfer failed %d %x %d\n", ret, address, size);
return ret;
}
len -= size;
address += size;
buf += size;
}
#ifdef DBG_VALIDATE_UPLOAD
wlwarn("Validating....\n");
while (validate_len > 0)
{
/* Set the backplane window to include the start address */
int ret = bcmf_core_set_backplane_window(sbus, validate_address);
if (ret != OK)
{
wlerr("Backplane setting failed at %08x\n", validate_address);
return ret;
}
if (validate_len > BCMF_UPLOAD_TRANSFER_SIZE)
{
size = BCMF_UPLOAD_TRANSFER_SIZE;
}
else
{
size = validate_len;
}
/* Transfer firmware data */
ret = bcmf_transfer_bytes(sbus, false, 1,
validate_address & SBSDIO_SB_OFT_ADDR_MASK,
compare_buffer, size);
if (ret != OK)
{
wlerr("validate transfer failed %d %x %d\n", ret, validate_address,
size);
return ret;
}
if (memcmp(validate_buffer, compare_buffer, size))
{
wlerr("Match failed at address base %08x\n", validate_address);
return -EILSEQ;
}
validate_len -= size;
validate_address += size;
validate_buffer += size;
}
wlwarn("Validation passed\n");
#endif
return OK;
}
#ifdef CONFIG_IEEE80211_BROADCOM_FWFILES
int bcmf_upload_file(FAR struct bcmf_sdio_dev_s *sbus, uint32_t address,
FAR const char *path)
{
struct file finfo;
FAR uint8_t *buf;
size_t total_read;
ssize_t nread;
int ret;
/* Open the file in the detached state */
ret = file_open(&finfo, path, O_RDONLY | O_BINARY);
if (ret < 0)
{
wlerr("ERROR: Failed to open the FILE MTD file %s: %d\n", path, ret);
return ret;
}
/* Allocate an I/O buffer */
buf = (FAR uint8_t *)kmm_malloc(BCMF_UPLOAD_TRANSFER_SIZE);
if (buf == NULL)
{
wlerr("ERROR: Failed allocate an I/O buffer\n");
ret = -ENOMEM;
goto errout_with_file;
}
/* Loop until the firmware has been loaded */
do
{
/* Set the backplane window to include the start address */
nread = file_read(&finfo, buf, BCMF_UPLOAD_TRANSFER_SIZE);
if (nread < 0)
{
ret = (int)nread;
wlerr("ERROR: Failed to read file: %d\n", ret);
goto errout_with_buf;
}
if (nread == 0)
{
break;
}
wlinfo("Read %ld bytes\n", (long)nread);
ret = bcmf_core_set_backplane_window(sbus, address);
if (ret < 0)
{
wlerr("ERROR: bcmf_core_set_backplane_window() failed: %d\n", ret);
goto errout_with_buf;
}
total_read = nread;
/* Transfer firmware data */
ret = bcmf_transfer_bytes(sbus, true, 1,
address & SBSDIO_SB_OFT_ADDR_MASK, buf,
total_read);
if (ret < 0)
{
wlerr("ERROR: Transfer failed address=%lx total_read=%lu: %d\n",
(unsigned long)address, (unsigned long)total_read, ret);
goto errout_with_buf;
}
address += total_read;
}
while (nread == BCMF_UPLOAD_TRANSFER_SIZE);
file_close(&finfo);
kmm_free(buf);
wlinfo("Upload complete\n");
return OK;
errout_with_buf:
kmm_free(buf);
errout_with_file:
file_close(&finfo);
return ret;
}
#endif
int bcmf_upload_nvram(FAR struct bcmf_sdio_dev_s *sbus)
{
int ret;
uint32_t nvram_sz;
uint32_t token;
/* Round up the size of the image */
nvram_sz = (*sbus->chip->nvram_image_size + 63) & (-64);
wlinfo("nvram size is %d %d bytes\n", nvram_sz,
*sbus->chip->nvram_image_size);
/* Write image */
ret = bcmf_upload_binary(sbus, sbus->chip->ram_size - 4 - nvram_sz,
sbus->chip->nvram_image,
*sbus->chip->nvram_image_size);
if (ret != OK)
{
return ret;
}
/* Generate length token */
token = nvram_sz / 4;
token = (~token << 16) | (token & 0x0000ffff);
/* Write the length token to the last word */
ret = bcmf_write_sbreg(sbus, sbus->chip->ram_size - 4,
(FAR uint8_t *)&token, 4);
if (ret != OK)
{
return ret;
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: bcmf_read_sbreg
****************************************************************************/
int bcmf_read_sbreg(FAR struct bcmf_sdio_dev_s *sbus, uint32_t address,
FAR uint8_t *reg, unsigned int len)
{
int ret = bcmf_core_set_backplane_window(sbus, address);
if (ret != OK)
{
return ret;
}
address &= SBSDIO_SB_OFT_ADDR_MASK;
/* Map to 32-bit access if len == 4 */
if (len == 4)
{
address |= SBSDIO_SB_ACCESS_2_4B_FLAG;
}
return bcmf_transfer_bytes(sbus, false, 1, address, reg, len);
}
/****************************************************************************
* Name: bcmf_write_sbreg
****************************************************************************/
int bcmf_write_sbreg(FAR struct bcmf_sdio_dev_s *sbus, uint32_t address,
FAR uint8_t *reg, unsigned int len)
{
int ret = bcmf_core_set_backplane_window(sbus, address);
if (ret != OK)
{
return ret;
}
address &= SBSDIO_SB_OFT_ADDR_MASK;
/* Map to 32-bit access if len == 4 */
if (len == 4)
{
address |= SBSDIO_SB_ACCESS_2_4B_FLAG;
}
return bcmf_transfer_bytes(sbus, true, 1, address, reg, len);
}
/****************************************************************************
* Name: bcmf_core_upload_firmware
****************************************************************************/
int bcmf_core_upload_firmware(FAR struct bcmf_sdio_dev_s *sbus)
{
int ret;
wlinfo("upload firmware\n");
/* Disable ARMCM3 core and reset SOCRAM core to set device in firmware
* upload mode
*/
bcmf_core_disable(sbus, WLAN_ARMCM3_CORE_ID);
bcmf_core_reset(sbus, SOCSRAM_CORE_ID);
/* Do chip specific initialization */
if (sbus->cur_chip_id == SDIO_DEVICE_ID_BROADCOM_43430)
{
/* Disable remap for SRAM_3. Only for 4343x */
bcmf_write_sbregw(sbus, SOCSRAM_BANKX_INDEX, 0x3);
bcmf_write_sbregw(sbus, SOCSRAM_BANKX_PDA, 0);
}
up_mdelay(50);
/* Flash chip firmware */
#ifdef CONFIG_IEEE80211_BROADCOM_FWFILES
ret = bcmf_upload_file(sbus, 0, CONFIG_IEEE80211_BROADCOM_FWFILENAME);
#else
wlinfo("firmware size is %d bytes\n", *sbus->chip->firmware_image_size);
ret = bcmf_upload_binary(sbus, 0, sbus->chip->firmware_image,
*sbus->chip->firmware_image_size);
#endif
if (ret < 0)
{
wlerr("ERROR: Failed to upload firmware\n");
return ret;
}
/* Flash NVRAM configuration file */
wlinfo("upload nvram configuration\n");
ret = bcmf_upload_nvram(sbus);
if (ret < 0)
{
wlerr("ERROR: Failed to upload NVRAM\n");
return ret;
}
/* Firmware upload done, restart ARMCM3 core */
up_mdelay(10);
bcmf_core_reset(sbus, WLAN_ARMCM3_CORE_ID);
/* Check ARMCM3 core is running */
up_mdelay(10);
if (!bcmf_core_isup(sbus, WLAN_ARMCM3_CORE_ID))
{
wlerr("Cannot start ARMCM3 core\n");
return -ETIMEDOUT;
}
return OK;
}
bool bcmf_core_isup(FAR struct bcmf_sdio_dev_s *sbus, unsigned int core)
{
uint32_t value = 0;
if (core >= MAX_CORE_ID)
{
wlerr("Invalid core id %d\n", core);
return false;
}
uint32_t base = sbus->chip->core_base[core];
bcmf_read_sbregw(sbus, base + BCMA_IOCTL, &value);
if ((value & (BCMA_IOCTL_FGC | BCMA_IOCTL_CLK)) != BCMA_IOCTL_CLK)
{
return false;
}
bcmf_read_sbregw(sbus, base + BCMA_RESET_CTL, &value);
return (value & BCMA_RESET_CTL_RESET) == 0;
}
void bcmf_core_disable(FAR struct bcmf_sdio_dev_s *sbus, unsigned int core)
{
uint8_t value;
if (core >= MAX_CORE_ID)
{
wlerr("Invalid core id %d\n", core);
return;
}
uint32_t base = sbus->chip->core_base[core];
/* Check if core is already in reset state */
bcmf_read_sbregb(sbus, base + BCMA_RESET_CTL, &value);
if ((value & BCMA_RESET_CTL_RESET) != 0)
{
/* Core already disabled */
return;
}
/* Ensure no backplane operation is pending */
up_mdelay(10);
/* Set core in reset state */
bcmf_write_sbregb(sbus, base + BCMA_RESET_CTL, BCMA_RESET_CTL_RESET);
up_udelay(1);
/* Write 0 to the IO control and read it back */
bcmf_write_sbregb(sbus, base + BCMA_IOCTL, 0);
bcmf_read_sbregb(sbus, base + BCMA_IOCTL, &value);
up_udelay(10);
}
void bcmf_core_reset(FAR struct bcmf_sdio_dev_s *sbus, unsigned int core)
{
uint32_t value;
if (core >= MAX_CORE_ID)
{
wlerr("Invalid core id %d\n", core);
return;
}
uint32_t base = sbus->chip->core_base[core];
/* Put core in reset state */
bcmf_core_disable(sbus, core);
/* Run initialization sequence */
bcmf_write_sbregb(sbus, base + BCMA_IOCTL, BCMA_IOCTL_FGC | BCMA_IOCTL_CLK);
bcmf_read_sbregw(sbus, base + BCMA_IOCTL, &value);
bcmf_write_sbregb(sbus, base + BCMA_RESET_CTL, 0);
bcmf_read_sbregw(sbus, base + BCMA_RESET_CTL, &value);
up_udelay(1);
bcmf_write_sbregb(sbus, base + BCMA_IOCTL, BCMA_IOCTL_CLK);
bcmf_read_sbregw(sbus, base + BCMA_IOCTL, &value);
up_udelay(1);
}