| /* |
| * Copyright (c) 2010 Broadcom Corporation |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| /* ****************** SDIO CARD Interface Functions **************************/ |
| |
| #include <fuchsia/hardware/gpio/cpp/banjo.h> |
| #include <fuchsia/hardware/sdio/cpp/banjo.h> |
| #include <inttypes.h> |
| #include <lib/ddk/device.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/ddk/trace/event.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zircon-internal/align.h> |
| #include <lib/zx/vmo.h> |
| #include <pthread.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <atomic> |
| #include <limits> |
| |
| #include <wifi/wifi-config.h> |
| |
| #ifndef _ALL_SOURCE |
| #define _ALL_SOURCE // Enables thrd_create_with_name in <threads.h>. |
| #endif |
| #include <threads.h> |
| |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/brcm_hw_ids.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/brcmu_utils.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/brcmu_wifi.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/bus.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/chip.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/chipcommon.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/common.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/debug.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/defs.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/device.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/linuxisms.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/macros.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/netbuf.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/sdio/sdio.h" |
| #include "src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/soc.h" |
| |
| #define SDIOH_API_ACCESS_RETRY_LIMIT 2 |
| |
| #define DMA_ALIGN_MASK 0x03 |
| |
| #define SDIO_FUNC1_BLOCKSIZE 64 |
| #define SDIO_FUNC2_BLOCKSIZE 512 |
| /* Maximum milliseconds to wait for F2 to come up */ |
| #define SDIO_WAIT_F2RDY 3000 |
| |
| #define BRCMF_DEFAULT_RXGLOM_SIZE 32 /* max rx frames in glom chain */ |
| |
| // The initial size of the DMA buffer |
| constexpr size_t kDmaInitialBufferSize = 4096; |
| // The maximum size the DMA buffer should be allowed to grow to |
| constexpr size_t kDmaMaxBufferSize = 131072; |
| // The minimum size a packet should be to enable DMA |
| constexpr size_t kDmaThresholdSize = 64; |
| |
| static void brcmf_sdiod_ib_irqhandler(struct brcmf_sdio_dev* sdiodev) { |
| BRCMF_DBG(INTR, "IB intr triggered"); |
| |
| brcmf_sdio_isr(sdiodev->bus); |
| } |
| |
| /* dummy handler for SDIO function 2 interrupt */ |
| static void brcmf_sdiod_dummy_irqhandler(struct brcmf_sdio_dev* sdiodev) {} |
| |
| zx_status_t brcmf_sdiod_configure_oob_interrupt(struct brcmf_sdio_dev* sdiodev, |
| wifi_config_t* config) { |
| zx_status_t ret = gpio_config_in(&sdiodev->gpios[WIFI_OOB_IRQ_GPIO_INDEX], GPIO_NO_PULL); |
| if (ret != ZX_OK) { |
| BRCMF_ERR("brcmf_sdiod_intr_register: gpio_config failed: %d", ret); |
| return ret; |
| } |
| |
| ret = gpio_get_interrupt(&sdiodev->gpios[WIFI_OOB_IRQ_GPIO_INDEX], config->oob_irq_mode, |
| &sdiodev->irq_handle); |
| if (ret != ZX_OK) { |
| BRCMF_ERR("brcmf_sdiod_intr_register: gpio_get_interrupt failed: %d", ret); |
| return ret; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t brcmf_sdiod_get_bootloader_macaddr(struct brcmf_sdio_dev* sdiodev, uint8_t* macaddr) { |
| // MAC address is only 6 bytes, but it is rounded up to 8 in the ZBI |
| uint8_t bootloader_macaddr[8]; |
| size_t actual_len; |
| zx_status_t ret = sdiodev->drvr->device->DeviceGetMetadata( |
| DEVICE_METADATA_MAC_ADDRESS, bootloader_macaddr, sizeof(bootloader_macaddr), &actual_len); |
| |
| if (ret != ZX_OK || actual_len < ETH_ALEN) { |
| return ret; |
| } |
| memcpy(macaddr, bootloader_macaddr, 6); |
| BRCMF_DBG(INFO, "got bootloader mac address: " MAC_FMT_STR, MAC_FMT_ARGS(macaddr)); |
| return ZX_OK; |
| } |
| |
| zx_status_t brcmf_sdiod_intr_register(struct brcmf_sdio_dev* sdiodev) { |
| struct brcmf_sdio_pd* pdata; |
| zx_status_t ret = ZX_OK; |
| uint8_t data; |
| uint32_t addr, gpiocontrol; |
| |
| pdata = sdiodev->settings->bus.sdio; |
| pdata->oob_irq_supported = false; |
| wifi_config_t config; |
| size_t actual; |
| |
| // Get Broadcom WiFi Metadata by calling the bus specific function |
| if (sdiodev && sdiodev->bus_if && sdiodev->bus_if->ops) { |
| ret = brcmf_bus_get_wifi_metadata(sdiodev->bus_if, &config, sizeof(wifi_config_t), &actual); |
| if ((ret != ZX_OK && ret != ZX_ERR_NOT_FOUND) || |
| (ret == ZX_OK && actual != sizeof(wifi_config_t))) { |
| BRCMF_ERR("brcmf_sdiod_intr_register: device_get_metadata failed"); |
| return ret; |
| } |
| } else { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // If there is metadata, OOB is supported. |
| if (ret == ZX_OK) { |
| BRCMF_DBG(SDIO, "Enter, register OOB IRQ"); |
| ret = brcmf_sdiod_configure_oob_interrupt(sdiodev, &config); |
| if (ret != ZX_OK) { |
| return ret; |
| } |
| pdata->oob_irq_supported = true; |
| int status = thrd_create_with_name(&sdiodev->isr_thread, &brcmf_sdio_oob_irqhandler, sdiodev, |
| "brcmf-sdio-isr"); |
| if (status != thrd_success) { |
| BRCMF_ERR("thrd_create_with_name failed: %d", status); |
| return ZX_ERR_INTERNAL; |
| } |
| sdiodev->oob_irq_requested = true; |
| ret = enable_irq_wake(sdiodev->irq_handle); |
| if (ret != ZX_OK) { |
| BRCMF_ERR("enable_irq_wake failed %d", ret); |
| return ret; |
| } |
| sdiodev->irq_wake = true; |
| |
| sdio_claim_host(sdiodev->func1); |
| |
| if (sdiodev->bus_if->chip == BRCM_CC_43362_CHIP_ID) { |
| /* assign GPIO to SDIO core */ |
| addr = CORE_CC_REG(SI_ENUM_BASE, gpiocontrol); |
| gpiocontrol = brcmf_sdiod_func1_rl(sdiodev, addr, &ret); |
| gpiocontrol |= 0x2; |
| brcmf_sdiod_func1_wl(sdiodev, addr, gpiocontrol, &ret); |
| |
| brcmf_sdiod_func1_wb(sdiodev, SBSDIO_GPIO_SELECT, 0xf, &ret); |
| brcmf_sdiod_func1_wb(sdiodev, SBSDIO_GPIO_OUT, 0, &ret); |
| brcmf_sdiod_func1_wb(sdiodev, SBSDIO_GPIO_EN, 0x2, &ret); |
| } |
| |
| /* must configure SDIO_CCCR_INT_ENABLE to enable irq */ |
| sdio_enable_fn_intr(&sdiodev->sdio_proto_fn1); |
| sdio_enable_fn_intr(&sdiodev->sdio_proto_fn2); |
| |
| /* redirect, configure and enable io for interrupt signal */ |
| data = SDIO_CCCR_BRCM_SEPINT_MASK | SDIO_CCCR_BRCM_SEPINT_OE; |
| if (config.oob_irq_mode == ZX_INTERRUPT_MODE_LEVEL_HIGH) { |
| data |= SDIO_CCCR_BRCM_SEPINT_ACT_HI; |
| } |
| brcmf_sdiod_vendor_control_wb(sdiodev, SDIO_CCCR_BRCM_SEPINT, data, &ret); |
| // TODO(cphoenix): This pause is probably unnecessary. |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(100))); |
| sdio_release_host(sdiodev->func1); |
| } else { |
| BRCMF_DBG(SDIO, "Entering"); |
| sdio_claim_host(sdiodev->func1); |
| sdio_enable_fn_intr(&sdiodev->sdio_proto_fn1); |
| (void)brcmf_sdiod_ib_irqhandler; // TODO(cphoenix): If we use these, plug them in later. |
| sdio_enable_fn_intr(&sdiodev->sdio_proto_fn2); |
| (void)brcmf_sdiod_dummy_irqhandler; |
| sdio_release_host(sdiodev->func1); |
| sdiodev->sd_irq_requested = true; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void brcmf_sdiod_intr_unregister(struct brcmf_sdio_dev* sdiodev) { |
| BRCMF_DBG(SDIO, "Entering oob=%d sd=%d", sdiodev->oob_irq_requested, sdiodev->sd_irq_requested); |
| |
| if (sdiodev->oob_irq_requested) { |
| sdio_claim_host(sdiodev->func1); |
| brcmf_sdiod_vendor_control_wb(sdiodev, SDIO_CCCR_BRCM_SEPINT, 0, NULL); |
| sdio_disable_fn_intr(&sdiodev->sdio_proto_fn1); |
| sdio_disable_fn_intr(&sdiodev->sdio_proto_fn2); |
| sdio_release_host(sdiodev->func1); |
| |
| sdiodev->oob_irq_requested = false; |
| if (sdiodev->irq_wake) { |
| disable_irq_wake(sdiodev->irq_handle); |
| sdiodev->irq_wake = false; |
| } |
| zx_handle_close(sdiodev->irq_handle); |
| int retval = 0; |
| int status = thrd_join(sdiodev->isr_thread, &retval); |
| if (status != thrd_success) { |
| BRCMF_ERR("thrd_join failed: %d", status); |
| } |
| sdiodev->oob_irq_requested = false; |
| } |
| |
| if (sdiodev->sd_irq_requested) { |
| sdio_claim_host(sdiodev->func1); |
| sdio_disable_fn_intr(&sdiodev->sdio_proto_fn2); |
| sdio_disable_fn_intr(&sdiodev->sdio_proto_fn1); |
| sdio_release_host(sdiodev->func1); |
| sdiodev->sd_irq_requested = false; |
| } |
| } |
| |
| void brcmf_sdiod_change_state(struct brcmf_sdio_dev* sdiodev, enum brcmf_sdiod_state state) { |
| if (sdiodev->state == BRCMF_SDIOD_NOMEDIUM || state == sdiodev->state) { |
| BRCMF_ERR("No medium or equal state: %d\n", sdiodev->state); |
| return; |
| } |
| |
| BRCMF_DBG(TRACE, "%d -> %d", sdiodev->state, state); |
| switch (sdiodev->state) { |
| case BRCMF_SDIOD_DATA: |
| /* any other state means bus interface is down */ |
| brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_DOWN); |
| break; |
| case BRCMF_SDIOD_DOWN: |
| /* transition from DOWN to DATA means bus interface is up */ |
| if (state == BRCMF_SDIOD_DATA) { |
| brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_UP); |
| } |
| break; |
| default: |
| break; |
| } |
| sdiodev->state = state; |
| } |
| |
| zx_status_t brcmf_sdiod_transfer(struct brcmf_sdio_dev* sdiodev, uint8_t func, uint32_t addr, |
| bool write, void* data, size_t size, bool fifo) { |
| sdio_rw_txn_t txn; |
| zx_status_t result; |
| // The size must be a multiple of 4 to use DMA, also don't use DMA for very |
| // small transfers as the overhead might not be worth it. |
| bool use_dma = ((size % 4) == 0) && size > kDmaThresholdSize; |
| |
| TRACE_DURATION("brcmfmac:isr", "sdiod_transfer", "func", TA_UINT32((uint32_t)func), "type", |
| TA_STRING(write ? "write" : "read"), "addr", TA_UINT32(addr), "size", |
| TA_UINT64((uint64_t)size)); |
| if (use_dma) { |
| if (size > sdiodev->dma_buffer_size) { |
| // Only resize the DMA buffer if it's not big enough. This saves a |
| // significant amount of time by not resizing the buffer for every |
| // transfer. Use a cached size value to avoid a syscall each time. |
| if (size > kDmaMaxBufferSize) { |
| BRCMF_ERR("Requested SDIO transfer VMO size %" PRIu64 " too large, max is %" PRIu64 "", |
| size, kDmaMaxBufferSize); |
| return ZX_ERR_NO_MEMORY; |
| } |
| result = sdiodev->dma_buffer.set_size(size); |
| if (result != ZX_OK) { |
| BRCMF_ERR("Error resizing SDIO transfer VMO: %s", zx_status_get_string(result)); |
| return result; |
| } |
| sdiodev->dma_buffer_size = size; |
| } |
| if (write) { |
| result = sdiodev->dma_buffer.write(data, 0, size); |
| if (result != ZX_OK) { |
| BRCMF_ERR("Error writing to SDIO transfer VMO: %s", zx_status_get_string(result)); |
| return result; |
| } |
| } |
| } |
| |
| txn.addr = addr; |
| txn.write = write; |
| txn.virt_buffer = reinterpret_cast<uint8_t*>(data); |
| if (size > std::numeric_limits<uint32_t>::max()) { |
| BRCMF_ERR("brcmf_sdiod_transfer failed: size invalid (overflow)"); |
| return ZX_ERR_INTERNAL; |
| } |
| txn.data_size = static_cast<uint32_t>(size); |
| txn.incr = !fifo; |
| txn.use_dma = use_dma; |
| txn.dma_vmo = use_dma ? sdiodev->dma_buffer.get() : ZX_HANDLE_INVALID; |
| txn.buf_offset = 0; |
| |
| if (func == SDIO_FN_1) { |
| result = sdio_do_rw_txn(&sdiodev->sdio_proto_fn1, &txn); |
| } else { |
| result = sdio_do_rw_txn(&sdiodev->sdio_proto_fn2, &txn); |
| } |
| |
| if (result != ZX_OK) { |
| if (result == ZX_ERR_TIMED_OUT) { |
| zx_status_t err = sdiodev->drvr->recovery_trigger->sdio_timeout_.Inc(); |
| if (err != ZX_OK) { |
| BRCMF_WARN("Failed to trigger, recovery likely in progress - status: %s", |
| zx_status_get_string(err)); |
| } |
| } |
| BRCMF_DBG(TEMP, "SDIO transaction failed: %s", zx_status_get_string(result)); |
| return result; |
| } |
| // Clear the TriggerCondition counter if SDIO transmission succeeded. |
| sdiodev->drvr->recovery_trigger->sdio_timeout_.Clear(); |
| |
| if (use_dma && !write) { |
| // This is a read operation, read the data from the VMO to the buffer |
| result = sdiodev->dma_buffer.read(data, 0, size); |
| if (result != ZX_OK) { |
| BRCMF_ERR("Error reading from SDIO transfer VMO: %s", zx_status_get_string(result)); |
| return result; |
| } |
| } |
| |
| return result; |
| } |
| |
| static uint8_t brcmf_sdiod_func_rb(struct brcmf_sdio_dev* sdiodev, uint8_t func, uint32_t addr, |
| zx_status_t* result_out) { |
| uint8_t data; |
| zx_status_t result; |
| result = brcmf_sdiod_transfer(sdiodev, func, addr, false, &data, sizeof(data), false); |
| if (result_out != NULL) { |
| *result_out = result; |
| } |
| return data; |
| } |
| |
| uint8_t brcmf_sdiod_vendor_control_rb(struct brcmf_sdio_dev* sdiodev, uint8_t addr, |
| zx_status_t* result_out) { |
| uint8_t data = 0; |
| zx_status_t result; |
| // Any function device can access the vendor control registers; fn2 could be used here instead. |
| result = sdio_do_vendor_control_rw_byte(&sdiodev->sdio_proto_fn1, false, addr, 0, &data); |
| if (result_out != NULL) { |
| *result_out = result; |
| } |
| return data; |
| } |
| |
| uint8_t brcmf_sdiod_func1_rb(struct brcmf_sdio_dev* sdiodev, uint32_t addr, |
| zx_status_t* result_out) { |
| return brcmf_sdiod_func_rb(sdiodev, SDIO_FN_1, addr, result_out); |
| } |
| |
| void brcmf_sdiod_vendor_control_wb(struct brcmf_sdio_dev* sdiodev, uint8_t addr, uint8_t data, |
| zx_status_t* result_out) { |
| zx_status_t result; |
| // Any function device can access the vendor control registers; fn2 could be used here instead. |
| result = sdio_do_vendor_control_rw_byte(&sdiodev->sdio_proto_fn1, true, addr, data, NULL); |
| if (result_out != NULL) { |
| *result_out = result; |
| } |
| } |
| |
| void brcmf_sdiod_func1_wb(struct brcmf_sdio_dev* sdiodev, uint32_t addr, uint8_t data, |
| zx_status_t* result_out) { |
| zx_status_t result; |
| result = brcmf_sdiod_transfer(sdiodev, SDIO_FN_1, addr, true, &data, sizeof(data), false); |
| if (result_out != NULL) { |
| *result_out = result; |
| } |
| } |
| |
| static zx_status_t brcmf_sdiod_set_backplane_window(struct brcmf_sdio_dev* sdiodev, uint32_t addr) { |
| uint32_t v; |
| uint32_t bar0 = addr & SBSDIO_SBWINDOW_MASK; |
| zx_status_t err = ZX_OK; |
| int i; |
| |
| if (bar0 == sdiodev->sbwad) { |
| return ZX_OK; |
| } |
| |
| v = bar0 >> 8; |
| |
| for (i = 0; i < 3 && err == ZX_OK; i++, v >>= 8) { |
| brcmf_sdiod_func1_wb(sdiodev, SBSDIO_FUNC1_SBADDRLOW + i, v & 0xff, &err); |
| } |
| |
| if (err == ZX_OK) { |
| sdiodev->sbwad = bar0; |
| } |
| |
| return err; |
| } |
| |
| uint32_t brcmf_sdiod_func1_rl(struct brcmf_sdio_dev* sdiodev, uint32_t addr, zx_status_t* ret) { |
| uint32_t data = 0; |
| zx_status_t retval; |
| |
| retval = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (retval == ZX_OK) { |
| SBSDIO_FORMAT_ADDR(addr); |
| retval = brcmf_sdiod_transfer(sdiodev, SDIO_FN_1, addr, false, &data, sizeof(data), false); |
| } |
| if (ret) { |
| *ret = retval; |
| } |
| |
| return data; |
| } |
| |
| void brcmf_sdiod_func1_wl(struct brcmf_sdio_dev* sdiodev, uint32_t addr, uint32_t data, |
| zx_status_t* ret) { |
| zx_status_t retval; |
| |
| retval = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (retval == ZX_OK) { |
| SBSDIO_FORMAT_ADDR(addr); |
| retval = brcmf_sdiod_transfer(sdiodev, SDIO_FN_1, addr, true, &data, sizeof(data), false); |
| } |
| if (ret) { |
| *ret = retval; |
| } |
| } |
| |
| static zx_status_t brcmf_sdiod_netbuf_read(struct brcmf_sdio_dev* sdiodev, uint8_t func, |
| uint32_t addr, uint8_t* data, size_t size) { |
| zx_status_t err; |
| TRACE_DURATION("brcmfmac:isr", "netbuf_read", "func", func, "len", size); |
| |
| SBSDIO_FORMAT_ADDR(addr); |
| /* Single netbuf use the standard mmc interface */ |
| if ((size & 3u) != 0) { |
| BRCMF_ERR("Unaligned SDIO read size %zu", size); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| switch (func) { |
| case SDIO_FN_1: |
| err = brcmf_sdiod_transfer(sdiodev, func, addr, false, data, size, false); |
| break; |
| case SDIO_FN_2: |
| err = brcmf_sdiod_transfer(sdiodev, func, addr, false, data, size, true); |
| break; |
| default: |
| /* bail out as things are really fishy here */ |
| WARN(1, "invalid sdio function number %d"); |
| err = ZX_ERR_IO_REFUSED; |
| }; |
| |
| if (err == ZX_ERR_IO_REFUSED) { |
| brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); |
| } |
| |
| return err; |
| } |
| |
| static zx_status_t brcmf_sdiod_netbuf_write(struct brcmf_sdio_dev* sdiodev, uint8_t func, |
| uint32_t addr, uint8_t* data, size_t size) { |
| zx_status_t err; |
| |
| TRACE_DURATION("brcmfmac:isr", "sdiod_netbuf_write", "func", func, "len", size); |
| |
| SBSDIO_FORMAT_ADDR(addr); |
| /* Single netbuf use the standard mmc interface */ |
| if ((size & 3u) != 0) { |
| BRCMF_ERR("Unaligned SDIO write size %zu", size); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| err = brcmf_sdiod_transfer(sdiodev, func, addr, true, data, size, false); |
| |
| if (err == ZX_ERR_IO_REFUSED) { |
| brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); |
| } |
| |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_recv_buf(struct brcmf_sdio_dev* sdiodev, uint8_t* buf, uint nbytes) { |
| uint32_t addr = sdiodev->cc_core->base; |
| zx_status_t err = ZX_OK; |
| unsigned int req_sz; |
| |
| err = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (err != ZX_OK) { |
| return err; |
| } |
| |
| SBSDIO_FORMAT_ADDR(addr); |
| |
| req_sz = ZX_ROUNDUP(nbytes, SDIOD_SIZE_ALIGNMENT); |
| |
| err = brcmf_sdiod_transfer(sdiodev, SDIO_FN_2, addr, false, buf, req_sz, true); |
| |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_recv_pkt(struct brcmf_sdio_dev* sdiodev, struct brcmf_netbuf* pkt) { |
| uint32_t addr = sdiodev->cc_core->base; |
| zx_status_t err = ZX_OK; |
| |
| TRACE_DURATION("brcmfmac:isr", "recv_pkt"); |
| |
| err = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (err != ZX_OK) { |
| goto done; |
| } |
| |
| err = brcmf_sdiod_netbuf_read(sdiodev, SDIO_FN_2, addr, pkt->data, pkt->len); |
| |
| done: |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_recv_chain(struct brcmf_sdio_dev* sdiodev, struct brcmf_netbuf_list* pktq, |
| uint totlen) { |
| struct brcmf_netbuf* glom_netbuf = NULL; |
| struct brcmf_netbuf* netbuf; |
| uint32_t addr = sdiodev->cc_core->base; |
| zx_status_t err = ZX_OK; |
| uint32_t list_len = brcmf_netbuf_list_length(pktq); |
| |
| TRACE_DURATION("brcmfmac:isr", "sdiod_recv_chain", "list_len", TA_UINT32(list_len)); |
| |
| BRCMF_DBG(SDIO, "addr = 0x%x, size = %d", addr, list_len); |
| |
| err = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (err != ZX_OK) { |
| goto done; |
| } |
| |
| if (list_len == 1) { |
| netbuf = brcmf_netbuf_list_peek_head(pktq); |
| err = brcmf_sdiod_netbuf_read(sdiodev, SDIO_FN_2, addr, netbuf->data, netbuf->len); |
| } else { |
| glom_netbuf = brcmu_pkt_buf_get_netbuf(totlen); |
| if (!glom_netbuf) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| err = brcmf_sdiod_netbuf_read(sdiodev, SDIO_FN_2, addr, glom_netbuf->data, glom_netbuf->len); |
| if (err != ZX_OK) { |
| goto done; |
| } |
| |
| brcmf_netbuf_list_for_every(pktq, netbuf) { |
| memcpy(netbuf->data, glom_netbuf->data, netbuf->len); |
| brcmf_netbuf_shrink_head(glom_netbuf, netbuf->len); |
| } |
| } |
| |
| done: |
| brcmu_pkt_buf_free_netbuf(glom_netbuf); |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_send_buf(struct brcmf_sdio_dev* sdiodev, uint8_t* buf, uint nbytes) { |
| uint32_t addr = sdiodev->cc_core->base; |
| zx_status_t err; |
| uint32_t req_sz; |
| |
| TRACE_DURATION("brcmfmac:isr", "sdiod_send_buf"); |
| |
| err = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (err != ZX_OK) { |
| return err; |
| } |
| |
| SBSDIO_FORMAT_ADDR(addr); |
| |
| req_sz = ZX_ROUNDUP(nbytes, SDIOD_SIZE_ALIGNMENT); |
| |
| if (err == ZX_OK) { |
| err = brcmf_sdiod_transfer(sdiodev, SDIO_FN_2, addr, true, buf, req_sz, false); |
| } |
| |
| if (err == ZX_ERR_IO_REFUSED) { |
| brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); |
| } |
| |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_send_pkt(struct brcmf_sdio_dev* sdiodev, struct brcmf_netbuf_list* pktq) { |
| struct brcmf_netbuf* netbuf; |
| uint32_t addr = sdiodev->cc_core->base; |
| zx_status_t err; |
| |
| BRCMF_DBG(SDIO, "addr = 0x%x, size = %d", addr, brcmf_netbuf_list_length(pktq)); |
| |
| TRACE_DURATION("brcmfmac:isr", "sdiod_send_pkt"); |
| |
| err = brcmf_sdiod_set_backplane_window(sdiodev, addr); |
| if (err != ZX_OK) { |
| return err; |
| } |
| |
| brcmf_netbuf_list_for_every(pktq, netbuf) { |
| // We use allocated_size minus head space here to take place of alignment of data size in |
| // netbuf. |
| err = brcmf_sdiod_netbuf_write(sdiodev, SDIO_FN_2, addr, netbuf->data, |
| netbuf->allocated_size - brcmf_netbuf_head_space(netbuf)); |
| if (err != ZX_OK) { |
| break; |
| } |
| } |
| |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_ramrw(struct brcmf_sdio_dev* sdiodev, bool write, uint32_t address, |
| void* data, size_t data_size) { |
| zx_status_t err = ZX_OK; |
| // SBSDIO_SB_OFT_ADDR_LIMIT is the max transfer limit a single chunk. |
| uint32_t transfer_address = address & SBSDIO_SB_OFT_ADDR_MASK; |
| size_t transfer_size; |
| |
| /* Determine initial transfer parameters */ |
| sdio_claim_host(sdiodev->func1); |
| // Handling the situation when transfer_address + transfer_size > SBSDIO_SB_OFT_ADDR_LIMIT by |
| // doing address alignment. |
| if ((transfer_address + data_size) & SBSDIO_SBWINDOW_MASK) { |
| transfer_size = (SBSDIO_SB_OFT_ADDR_LIMIT - transfer_address); |
| } else { |
| transfer_size = data_size; |
| } |
| |
| /* Do the transfer(s) */ |
| while (data_size) { |
| /* Set the backplane window to include the start address */ |
| err = brcmf_sdiod_set_backplane_window(sdiodev, address); |
| |
| if (err != ZX_OK) { |
| break; |
| } |
| |
| if (write) { |
| err = brcmf_sdiod_netbuf_write(sdiodev, SDIO_FN_1, transfer_address, (uint8_t*)data, |
| transfer_size); |
| } else { |
| err = brcmf_sdiod_netbuf_read(sdiodev, SDIO_FN_1, transfer_address, (uint8_t*)data, |
| transfer_size); |
| } |
| |
| if (err != ZX_OK) { |
| BRCMF_ERR("membytes transfer failed"); |
| break; |
| } |
| |
| /* Adjust for next transfer (if any) */ |
| data_size -= transfer_size; |
| if (data) |
| data = static_cast<char*>(data) + transfer_size; |
| address += transfer_size; |
| transfer_address += transfer_size; |
| transfer_size = std::min<size_t>(SBSDIO_SB_OFT_ADDR_LIMIT, data_size); |
| } |
| |
| sdio_release_host(sdiodev->func1); |
| |
| return err; |
| } |
| |
| zx_status_t brcmf_sdiod_abort(struct brcmf_sdio_dev* sdiodev, uint32_t func) { |
| BRCMF_DBG(SDIO, "Enter"); |
| |
| /* Issue abort cmd52 command through F0 */ |
| if (func == SDIO_FN_1) { |
| sdio_io_abort(&sdiodev->sdio_proto_fn1); |
| } else { |
| sdio_io_abort(&sdiodev->sdio_proto_fn2); |
| } |
| |
| BRCMF_DBG(SDIO, "Exit"); |
| return ZX_OK; |
| } |
| |
| static zx_status_t brcmf_sdiod_remove(struct brcmf_sdio_dev* sdiodev) { |
| sdiodev->state = BRCMF_SDIOD_DOWN; |
| if (sdiodev->bus) { |
| brcmf_sdio_remove(sdiodev->bus); |
| sdiodev->bus = NULL; |
| } |
| |
| /* Disable Function 2 */ |
| sdio_claim_host(sdiodev->func2); |
| sdio_disable_fn(&sdiodev->sdio_proto_fn2); |
| sdio_release_host(sdiodev->func2); |
| |
| /* Disable Function 1 */ |
| sdio_claim_host(sdiodev->func1); |
| sdio_disable_fn(&sdiodev->sdio_proto_fn1); |
| sdio_release_host(sdiodev->func1); |
| |
| sdiodev->sbwad = 0; |
| |
| // TODO(cphoenix): Power management stuff |
| // pm_runtime_allow(sdiodev->func1->card->host->parent); |
| return ZX_OK; |
| } |
| |
| // TODO(cphoenix): Power management stuff |
| #ifdef POWER_MANAGEMENT |
| static void brcmf_sdiod_host_fixup(struct mmc_host* host) { |
| /* runtime-pm powers off the device */ |
| pm_runtime_forbid(host->parent); |
| /* avoid removal detection upon resume */ |
| host->caps |= MMC_CAP_NONREMOVABLE; // Defined outside this driver's codebase |
| } |
| #endif // POWER_MANAGEMENT |
| |
| static zx_status_t brcmf_sdiod_probe(struct brcmf_sdio_dev* sdiodev) { |
| zx_status_t ret = ZX_OK; |
| |
| ret = sdio_update_block_size(&sdiodev->sdio_proto_fn1, SDIO_FUNC1_BLOCKSIZE, false); |
| if (ret != ZX_OK) { |
| BRCMF_ERR("Failed to set F1 blocksize"); |
| goto out; |
| } |
| ret = sdio_update_block_size(&sdiodev->sdio_proto_fn2, SDIO_FUNC2_BLOCKSIZE, false); |
| if (ret != ZX_OK) { |
| BRCMF_ERR("Failed to set F2 blocksize"); |
| goto out; |
| } |
| |
| /* increase F2 timeout */ |
| // TODO(cphoenix): SDIO doesn't use timeout yet |
| // sdiodev->func2->enable_timeout = SDIO_WAIT_F2RDY; |
| |
| /* Enable Function 1 */ |
| ret = sdio_enable_fn(&sdiodev->sdio_proto_fn1); |
| if (ret != ZX_OK) { |
| BRCMF_ERR("Failed to enable F1: err=%d", ret); |
| goto out; |
| } |
| |
| /* try to attach to the target device */ |
| sdiodev->bus = brcmf_sdio_probe(sdiodev); |
| if (!sdiodev->bus) { |
| ret = ZX_ERR_IO_NOT_PRESENT; |
| goto out; |
| } |
| // brcmf_sdiod_host_fixup(sdiodev->func2->card->host); |
| out: |
| if (ret != ZX_OK) { |
| brcmf_sdiod_remove(sdiodev); |
| } |
| |
| return ret; |
| } |
| |
| #ifdef TODO_ADD_SDIO_IDS // Put some of these into binding.c |
| #define BRCMF_SDIO_DEVICE(dev_id) \ |
| { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, dev_id) } |
| |
| /* devices we support, null terminated */ |
| static const struct sdio_device_id brcmf_sdmmc_ids[] = { |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43143), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43241), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4329), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4330), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4334), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43340), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43341), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43362), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4335_4339), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4339), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4354), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4356), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_4373), |
| BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4359), |
| {/* end: all zeroes */}}; |
| #endif // TODO_ADD_SDIO_IDS |
| |
| zx_status_t brcmf_sdio_register(brcmf_pub* drvr, std::unique_ptr<brcmf_bus>* out_bus) { |
| zx_status_t err; |
| |
| std::unique_ptr<struct brcmf_bus> bus_if; |
| struct sdio_func* func1 = NULL; |
| struct sdio_func* func2 = NULL; |
| struct brcmf_sdio_dev* sdiodev = NULL; |
| |
| BRCMF_DBG(SDIO, "Enter"); |
| // One for SDIO, one or two GPIOs. |
| sdio_protocol_t sdio_proto_fn1; |
| sdio_protocol_t sdio_proto_fn2; |
| gpio_protocol_t gpio_protos[GPIO_COUNT]; |
| bool has_debug_gpio = false; |
| |
| ddk::SdioProtocolClient sdio_fn1(drvr->device->parent(), "sdio-function-1"); |
| if (!sdio_fn1.is_valid()) { |
| BRCMF_ERR("sdio function 1 fragment not found"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| sdio_fn1.GetProto(&sdio_proto_fn1); |
| |
| ddk::SdioProtocolClient sdio_fn2(drvr->device->parent(), "sdio-function-2"); |
| if (!sdio_fn2.is_valid()) { |
| BRCMF_ERR("sdio function 2 fragment not found"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| sdio_fn2.GetProto(&sdio_proto_fn2); |
| |
| ddk::GpioProtocolClient gpio(drvr->device->parent(), "gpio-oob"); |
| if (!gpio.is_valid()) { |
| BRCMF_ERR("ZX_PROTOCOL_GPIO not found"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| gpio.GetProto(&gpio_protos[WIFI_OOB_IRQ_GPIO_INDEX]); |
| |
| // Debug GPIO is optional |
| gpio = ddk::GpioProtocolClient(drvr->device->parent(), "gpio-debug"); |
| if (gpio.is_valid()) { |
| has_debug_gpio = true; |
| gpio.GetProto(&gpio_protos[DEBUG_GPIO_INDEX]); |
| } |
| |
| sdio_hw_info_t devinfo; |
| sdio_get_dev_hw_info(&sdio_proto_fn1, &devinfo); |
| if (devinfo.dev_hw_info.num_funcs < 3) { |
| BRCMF_ERR("Not enough SDIO funcs (need 3, have %d)", devinfo.dev_hw_info.num_funcs); |
| return ZX_ERR_IO; |
| } |
| |
| pthread_mutexattr_t mutex_attr; |
| if (pthread_mutexattr_init(&mutex_attr)) { |
| return ZX_ERR_INTERNAL; |
| } |
| if (pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE)) { |
| err = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } |
| |
| BRCMF_DBG(SDIO, "sdio vendor ID: 0x%04x", devinfo.funcs_hw_info[SDIO_FN_1].manufacturer_id); |
| BRCMF_DBG(SDIO, "sdio device ID: 0x%04x", devinfo.funcs_hw_info[SDIO_FN_1].product_id); |
| |
| // TODO(cphoenix): Reexamine this when SDIO is more mature - do we need to support "quirks" in |
| // Fuchsia? (MMC_QUIRK_LENIENT_FN0 is defined outside this driver.) |
| /* Set MMC_QUIRK_LENIENT_FN0 for this card */ |
| // func->card->quirks |= MMC_QUIRK_LENIENT_FN0; |
| |
| bus_if = std::make_unique<brcmf_bus>(); |
| func1 = static_cast<decltype(func1)>(calloc(1, sizeof(struct sdio_func))); |
| if (!func1) { |
| err = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } else { |
| if (pthread_mutex_init(&func1->lock, &mutex_attr)) { |
| free(func1); |
| func1 = NULL; |
| err = ZX_ERR_INTERNAL; |
| goto fail; |
| } |
| } |
| func2 = static_cast<decltype(func2)>(calloc(1, sizeof(struct sdio_func))); |
| if (!func2) { |
| err = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } else { |
| if (pthread_mutex_init(&func2->lock, &mutex_attr)) { |
| free(func2); |
| func2 = NULL; |
| err = ZX_ERR_INTERNAL; |
| goto fail; |
| } |
| } |
| sdiodev = new brcmf_sdio_dev{}; |
| if (!sdiodev) { |
| err = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } |
| memcpy(&sdiodev->sdio_proto_fn1, &sdio_proto_fn1, sizeof(sdiodev->sdio_proto_fn1)); |
| memcpy(&sdiodev->sdio_proto_fn2, &sdio_proto_fn2, sizeof(sdiodev->sdio_proto_fn2)); |
| memcpy(&sdiodev->gpios[WIFI_OOB_IRQ_GPIO_INDEX], &gpio_protos[WIFI_OOB_IRQ_GPIO_INDEX], |
| sizeof(gpio_protos[WIFI_OOB_IRQ_GPIO_INDEX])); |
| if (has_debug_gpio) { |
| memcpy(&sdiodev->gpios[DEBUG_GPIO_INDEX], &gpio_protos[DEBUG_GPIO_INDEX], |
| sizeof(gpio_protos[DEBUG_GPIO_INDEX])); |
| sdiodev->has_debug_gpio = true; |
| } |
| sdiodev->bus_if = bus_if.get(); |
| sdiodev->func1 = func1; |
| sdiodev->func2 = func2; |
| sdiodev->ctl_done_timeout = ZX_MSEC(CTL_DONE_TIMEOUT_MSEC); |
| sdiodev->drvr = drvr; |
| bus_if->bus_priv.sdio = sdiodev; |
| |
| sdiodev->manufacturer_id = devinfo.funcs_hw_info[SDIO_FN_1].manufacturer_id; |
| sdiodev->product_id = devinfo.funcs_hw_info[SDIO_FN_1].product_id; |
| |
| err = zx::vmo::create(kDmaInitialBufferSize, ZX_VMO_RESIZABLE, &sdiodev->dma_buffer); |
| if (err != ZX_OK) { |
| BRCMF_ERR("Error creating DMA buffer: %s", zx_status_get_string(err)); |
| goto fail; |
| } |
| sdiodev->dma_buffer_size = kDmaInitialBufferSize; |
| |
| brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_DOWN); |
| |
| BRCMF_DBG(SDIO, "F2 found, calling brcmf_sdiod_probe..."); |
| err = brcmf_sdiod_probe(sdiodev); |
| if (err != ZX_OK) { |
| BRCMF_ERR("F2 error, probe failed %d...", err); |
| goto fail; |
| } |
| |
| pthread_mutexattr_destroy(&mutex_attr); |
| BRCMF_DBG(SDIO, "F2 init completed..."); |
| |
| *out_bus = std::move(bus_if); |
| return ZX_OK; |
| |
| fail: |
| delete sdiodev; |
| if (func2) { |
| pthread_mutex_destroy(&func2->lock); |
| free(func2); |
| } |
| if (func1) { |
| pthread_mutex_destroy(&func1->lock); |
| free(func1); |
| } |
| pthread_mutexattr_destroy(&mutex_attr); |
| return err; |
| } |
| |
| static void brcmf_ops_sdio_remove(struct brcmf_sdio_dev* sdiodev) { |
| BRCMF_DBG(SDIO, "Enter"); |
| if (sdiodev == NULL) { |
| return; |
| } |
| BRCMF_DBG(SDIO, "sdio vendor ID: 0x%04x", sdiodev->manufacturer_id); |
| BRCMF_DBG(SDIO, "sdio device ID: 0x%04x", sdiodev->product_id); |
| |
| /* start by unregistering irqs */ |
| brcmf_sdiod_intr_unregister(sdiodev); |
| |
| brcmf_sdiod_remove(sdiodev); |
| |
| if (sdiodev->func1) { |
| pthread_mutex_destroy(&sdiodev->func1->lock); |
| free(sdiodev->func1); |
| } |
| if (sdiodev->func2) { |
| pthread_mutex_destroy(&sdiodev->func2->lock); |
| free(sdiodev->func2); |
| } |
| |
| BRCMF_DBG(SDIO, "Exit"); |
| } |
| |
| void brcmf_sdio_exit(brcmf_bus* bus) { |
| BRCMF_DBG(SDIO, "Enter"); |
| |
| brcmf_ops_sdio_remove(bus->bus_priv.sdio); |
| delete bus->bus_priv.sdio; |
| bus->bus_priv.sdio = nullptr; |
| } |