From e1ed02f32d3d93e675c7d337878800b6c8277f57 Mon Sep 17 00:00:00 2001 From: Owen Huang Date: Thu, 8 Dec 2022 00:43:34 -0600 Subject: [PATCH 206/296] non-upstream: btsdio: add ifx bt shared sdio file 1. add module parameter: bt_over_sdio 2. copy latest bt_shared_sdio_ifx.c/.h to override files from dev branch (v5.15.58-hedorah-1-55500a1v1) commit 0f1a4c85e942 ("non-upstream: remove invalid register access to fix command error") Signed-off-by: Owen Huang --- .../broadcom/brcm80211/brcmfmac/Kconfig | 9 + .../broadcom/brcm80211/brcmfmac/Makefile | 2 + .../brcm80211/brcmfmac/bt_shared_sdio_ifx.c | 1029 +++++++++++++++++ .../brcm80211/brcmfmac/bt_shared_sdio_ifx.h | 54 + .../broadcom/brcm80211/brcmfmac/bus.h | 5 + .../broadcom/brcm80211/brcmfmac/common.c | 8 + .../broadcom/brcm80211/brcmfmac/common.h | 1 + .../broadcom/brcm80211/brcmfmac/sdio.c | 5 + .../broadcom/brcm80211/brcmfmac/sdio.h | 2 + 9 files changed, 1115 insertions(+) create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.c create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.h diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig index 48b5bccd5474..43e3cc8e87e9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig @@ -55,3 +55,12 @@ config BRCMFMAC_BT_SHARED_SDIO help This option enables the feautre of sharing the SDIO bus interface between Cypress BT and WiFi host drivers. + +config INFFMAC_BT_SHARED_SDIO + bool "FMAC shares SDIO bus to Bluetooth" + depends on BRCMFMAC + depends on BRCMFMAC_SDIO + default n + help + Selecting this to enables sharing the SDIO bus interface between + Infineon BT and WiFi host drivers. \ No newline at end of file diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile index bee103f4058f..a232a5b0ef1d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile @@ -57,6 +57,8 @@ brcmfmac-$(CONFIG_ACPI) += \ acpi.o brcmfmac-${CONFIG_BRCMFMAC_BT_SHARED_SDIO} += \ bt_shared_sdio.o +brcmfmac-${CONFIG_INFFMAC_BT_SHARED_SDIO} += \ + bt_shared_sdio_ifx.o ifeq ($(CONFIG_BRCMFMAC),m) obj-m += wcc/ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.c new file mode 100644 index 000000000000..62cac8e73467 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.c @@ -0,0 +1,1029 @@ +/* Infineon WLAN driver: BT shared SDIO implement + * + * copyright 2019, 2022 Cypress Semiconductor Corporation (an Infineon company) + * or an affiliate of Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related materials + * ("Software") is owned by Cypress Semiconductor Corporation or one of its + * affiliates ("Cypress") and is protected by and subject to + * worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license agreement + * accompanying the software package from which you obtained this Software ("EULA"). + * If no EULA applies, Cypress hereby grants you a personal, non-exclusive, + * non-transferable license to copy, modify, and compile the Software source code + * solely for use in connection with Cypress's integrated circuit products. + * Any reproduction, modification, translation, compilation, or representation + * of this Software except as specified above is prohibited without + * the expresswritten permission of Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Cypress reserves the right to make changes to the Software without notice. + * Cypress does not assume any liability arising out of the application or + * use of the Software or any product or circuit described in the Software. + * Cypress does not authorize its products for use in any products where a malfunction + * or failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). + * By including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing so + * agrees to indemnify Cypress against all liability. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "bus.h" +#include "chipcommon.h" +#include "core.h" +#include "sdio.h" +#include "soc.h" +#include "fwil.h" +#include "common.h" + +/* make sure BTS version is the same as bt drier */ +#define BTS_VER_MAJOR 2 +#define BTS_VER_MINOR 0 +#define BTS_VER_PATCH 0 +#define BTS_VERSION (BTS_VER_MAJOR << 24 | BTS_VER_MINOR << 16 | BTS_VER_PATCH << 8) + +/* make sure bt_shared_info is the same as bt drier */ +struct bt_shared_info { + /* bt info */ + void *bt_data; + void (*bt_int_fun)(void *data); + + /* wlan info */ + void *wlan_bus_if; + u32 enum_addr; +}; + +/* list wlan private data below */ +#define SDIOD_ADDR_BOUND 0x1000 +#define SDIOD_ADDR_BOUND_MASK 0xfff + +struct brcmf_bus *glob_bus_if; + +#define BTS_MAX_ERR_RECORD_CNT 128 + +enum bts_err_type { + ERR_REG_RB = 0, /* reg read 1 byte error */ + ERR_REG_WB = 1, /* reg write 1 byte error */ + ERR_REG_RL = 2, /* reg read 4 bytes error */ + ERR_REG_WL = 3, /* reg write 4 bytes error */ + ERR_BUF_RD = 4, /* receive buffer error */ + ERR_BUF_WT = 5, /* send buffer error */ + ERR_MEM_RW = 6, /* r/w memory error */ + ERR_MAX, +}; + +struct bts_err_reg { + u8 fn; + u32 addr; + u32 val; +}; + +struct bts_err_buf { + u32 nbytes; +}; + +struct bts_err_mem { + bool set; + u32 addr; + u32 size; +}; + +struct bts_cmd_entity { + struct list_head list; /* link into bt_if->err_list */ + enum bts_err_type type; + int err; + struct timespec64 time; + union { + struct bts_err_reg reg; + struct bts_err_buf buf; + struct bts_err_mem mem; + } u; +}; + +/** + * struct inf_bt_if - bt shared SDIO information. + * + * @ bt_data: bt internal structure data + * @ bt_sdio_int_cb: bt registered interrupt callback function + * @ bt_use_count: Counter that tracks whether BT is using the bus + */ +struct inf_bt_if { + void *bt_data; + void (*bt_sdio_int_cb)(void *data); + u32 use_count; /* Counter for tracking if BT is using the bus */ + bool set_bt_reset; /* set bt reset bit in wlan remove flow */ + + /* debug purpose */ + u32 cnt_attach; /* number of attach */ + u32 cnt_detach; /* number of detach */ + u32 cnt_total_err; /* number of error */ + spinlock_t err_list_lock; + struct list_head err_list; +}; + +bool inf_btsdio_inited(struct brcmf_bus *bus_if) +{ + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return false; + } + + if (!bus_if->bt_if) + return false; + + return true; +} + +// wait for next commit to recoed command +#if 0 +static void _btsdio_record_cmd(struct brcmf_sdio_cmd_info *cmd, + struct timespec64 ts_start) +{ + struct timespec64 ts_end, ts_delta; + + ktime_get_ts64(&ts_end); + ts_delta = timespec64_sub(ts_end, ts_start); + + cmd->num++; + cmd->time += timespec64_to_ns(&ts_delta); +} +#endif + +static char *inf_btsdio_err_char(enum bts_err_type type) +{ + switch (type) { + case ERR_REG_RB: + return "REG_RB"; + case ERR_REG_WB: + return "REG_WB"; + case ERR_REG_RL: + return "REG_RL"; + case ERR_REG_WL: + return "REG_WL"; + case ERR_BUF_RD: + return "BUF_RD"; + case ERR_BUF_WT: + return "BUF_WT"; + case ERR_MEM_RW: + return "MEM_RW"; + default: + return "unknown"; + } +} + +static void inf_btsdio_err_free_all(struct inf_bt_if *bt_if) +{ + struct bts_cmd_entity *cmd = NULL; + struct bts_cmd_entity *next = NULL; + + if (!bt_if) { + brcmf_err("bt_if is null\n"); + return; + } + + spin_lock(&bt_if->err_list_lock); + list_for_each_entry_safe(cmd, next, &bt_if->err_list, list) { + list_del(&cmd->list); + kfree(cmd); + } + spin_unlock(&bt_if->err_list_lock); +} + +static int inf_btsdio_cmd_alloc(struct inf_bt_if *bt_if, + struct bts_cmd_entity **cmd, enum bts_err_type type, int err) +{ + if (!bt_if || !cmd) { + brcmf_err("bt_if(%p) or cmd(%p) is null\n", bt_if, cmd); + return -EINVAL; + } + + if (++bt_if->cnt_total_err > BTS_MAX_ERR_RECORD_CNT) + return -EPERM; + + *cmd = kzalloc(sizeof(**cmd), GFP_KERNEL); + if (!*cmd) { + brcmf_err("alloc failed\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&(*cmd)->list); + (*cmd)->type = type; + (*cmd)->err = err; + ktime_get_ts64(&(*cmd)->time); + + return 0; +} + +static void inf_btsdio_err_enq(struct inf_bt_if *bt_if, struct bts_cmd_entity *cmd) +{ + if (!bt_if || !cmd) { + brcmf_err("bt_if(%p) or cmd(%p) is null\n", bt_if, cmd); + return; + } + + spin_lock(&bt_if->err_list_lock); + list_add_tail(&cmd->list, &bt_if->err_list); + spin_unlock(&bt_if->err_list_lock); +} + +static void inf_btsdio_err_reg_record(struct inf_bt_if *bt_if, + enum bts_err_type type, int err, u8 fn, u32 addr, u32 val) +{ + struct bts_cmd_entity *cmd = NULL; + struct bts_err_reg *reg = NULL; + + if (!bt_if) { + brcmf_err("bt_if is null\n"); + return; + } + + if (inf_btsdio_cmd_alloc(bt_if, &cmd, type, err)) + return; + + reg = &cmd->u.reg; + reg->fn = fn; + reg->addr = addr; + reg->val = val; + brcmf_err("[%5lld.%06ld] %8s err: %d\taddr: 0x%x\tval: 0x%x\n", + cmd->time.tv_sec, cmd->time.tv_nsec / NSEC_PER_USEC, + inf_btsdio_err_char(cmd->type), err, addr, val); + + inf_btsdio_err_enq(bt_if, cmd); +} + +static void inf_btsdio_err_buf_record(struct inf_bt_if *bt_if, + enum bts_err_type type, int err, u32 nbytes) +{ + struct bts_cmd_entity *cmd = NULL; + struct bts_err_buf *buf = NULL; + + if (!bt_if) { + brcmf_err("bt_if is null\n"); + return; + } + + if (inf_btsdio_cmd_alloc(bt_if, &cmd, type, err)) + return; + + buf = &cmd->u.buf; + buf->nbytes = nbytes; + brcmf_err("[%5lld.%06ld] %8s err: %d\tnbytes: %d\n", + cmd->time.tv_sec, cmd->time.tv_nsec / NSEC_PER_USEC, + inf_btsdio_err_char(cmd->type), err, nbytes); + + inf_btsdio_err_enq(bt_if, cmd); +} + +static void inf_btsdio_err_mem_record(struct inf_bt_if *bt_if, + int err, bool set, u32 addr, u32 size) +{ + struct bts_cmd_entity *cmd = NULL; + struct bts_err_mem *mem = NULL; + + if (!bt_if) { + brcmf_err("bt_if is null\n"); + return; + } + + if (inf_btsdio_cmd_alloc(bt_if, &cmd, ERR_MEM_RW, err)) + return; + + mem = &cmd->u.mem; + mem->set = set; + mem->addr = addr; + mem->size = size; + brcmf_err("[%5lld.%06ld] %8s err: %d\tset: %d\taddr: 0x%x\tsize: %d\n", + cmd->time.tv_sec, cmd->time.tv_nsec / NSEC_PER_USEC, + inf_btsdio_err_char(cmd->type), err, set, addr, size); + + inf_btsdio_err_enq(bt_if, cmd); +} + +static void inf_btsdio_err_dump(struct seq_file *seq, struct inf_bt_if *bt_if) +{ + struct bts_cmd_entity *cmd = NULL; + struct bts_err_reg *reg = NULL; + struct bts_err_buf *buf = NULL; + struct bts_err_mem *mem = NULL; + u8 idx = 0; + + if (!bt_if || !seq) { + brcmf_err("bt_if(%p) or seq(%p) is null\n", bt_if, seq); + return; + } + + if (bt_if->cnt_total_err > 0) + seq_printf(seq, "\ntotal error number: %d\n", bt_if->cnt_total_err); + + spin_lock(&bt_if->err_list_lock); + list_for_each_entry(cmd, &bt_if->err_list, list) { + seq_printf(seq, "%3d: [%5lld.%06ld] %8s err: %d\t", + ++idx, cmd->time.tv_sec, cmd->time.tv_nsec / NSEC_PER_USEC, + inf_btsdio_err_char(cmd->type), cmd->err); + switch (cmd->type) { + case ERR_REG_RB: + case ERR_REG_RL: + reg = &cmd->u.reg; + seq_printf(seq, "F%d addr: 0x%x", reg->fn, reg->addr); + break; + case ERR_REG_WB: + case ERR_REG_WL: + reg = &cmd->u.reg; + seq_printf(seq, "F%d addr: 0x%x\tval: 0x%x", + reg->fn, reg->addr, reg->val); + break; + case ERR_BUF_RD: + seq_puts(seq, "F3"); + break; + case ERR_BUF_WT: + buf = &cmd->u.buf; + seq_printf(seq, "F3 nbytes: %d", buf->nbytes); + break; + case ERR_MEM_RW: + mem = &cmd->u.mem; + seq_printf(seq, "F1 set: %d\taddr: %d\tsize: %d", + mem->set, mem->addr, mem->size); + break; + default: + break; + } + seq_puts(seq, "\n"); + } + spin_unlock(&bt_if->err_list_lock); +} + +static int inf_btsdio_debugfs_read(struct seq_file *seq, void *data) +{ + struct brcmf_bus *bus_if = NULL; + struct brcmf_sdio_dev *sdiodev = NULL; + struct inf_bt_if *bt_if = NULL; + struct mmc_host *host = NULL; + + if (!seq || !data) { + brcmf_err("seq(%p) or data(%p) is null\n", seq, data); + return 0; + } + bus_if = dev_get_drvdata(seq->private); + + if (!inf_btsdio_inited(bus_if)) { + seq_printf(seq, "Invalid bus_if (%p) or bt_if\n", bus_if); + return 0; + } + + sdiodev = bus_if->bus_priv.sdio; + bt_if = bus_if->bt_if; + + seq_printf(seq, + "chip: 0x%x\tversion (%d.%d.%d)\n" + "attach: %d\tdetach: %d\n" + "set_bt_reset: %d\n", + sdiodev->func1->device, BTS_VER_MAJOR, BTS_VER_MINOR, BTS_VER_PATCH, + bt_if->cnt_attach, bt_if->cnt_detach, + bt_if->set_bt_reset); + + if (bt_if->cnt_attach > bt_if->cnt_detach) + seq_printf(seq, "bt data: 0x%p\tbt cb: 0x%p\n", + bt_if->bt_data, bt_if->bt_sdio_int_cb); + + host = sdiodev->func2->card->host; + seq_printf(seq, "\nhost\n" + "%-5s: 0x%08x\t%-5s: 0x%08x\n" + "%-12s:%8d\t%-12s:%8d\t%-12s:%8d\t%-12s:%8d\n", + "caps", host->caps, "caps2", host->caps2, + "max blk cnt", host->max_blk_count, + "max req size", host->max_req_size, + "max seg", host->max_segs, + "max seg size", host->max_seg_size); + + seq_printf(seq, "\ndevice\n" + "%10s: %d\n" + "%-12s: %8d\t%-12s: %8d\t%-12s: %8d\n", + "sg_support", sdiodev->sg_support, + "max req size", sdiodev->max_request_size, + "max seg cnt", sdiodev->max_segment_count, + "max seq size", sdiodev->max_segment_size); + + seq_printf(seq, "\nblock size\n" + "%-3s:%4d\t%-3s:%4d\t%-3s:%4d\n", + "F1", sdiodev->func1->cur_blksize, + "F2", sdiodev->func2->cur_blksize, + "F3", sdiodev->func3->cur_blksize); + + inf_btsdio_err_dump(seq, bt_if); + + return 0; +} + +static void *inf_btsdio_get_func_entity(struct brcmf_sdio_dev *sdiodev, u8 fn) +{ + struct sdio_func *func = NULL; + + if (!sdiodev) { + brcmf_err("sdiodev is null\n"); + return NULL; + } + + if (fn == SDIO_FUNC_1) + func = sdiodev->func1; + else if (fn == SDIO_FUNC_2) + func = sdiodev->func2; + else if (fn == SDIO_FUNC_3) + func = sdiodev->func3; + + return func; +} + +void inf_btsdio_int_handler(struct brcmf_bus *bus_if) +{ + struct inf_bt_if *bt_if = NULL; + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return; + } + + if (!bus_if->bt_if) + return; + + bt_if = bus_if->bt_if; + + if (bt_if->bt_sdio_int_cb) + bt_if->bt_sdio_int_cb(bt_if->bt_data); +} + +bool inf_btsdio_is_active(struct brcmf_bus *bus_if) +{ + struct inf_bt_if *bt_if = NULL; + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return false; + } + + if (!bus_if->bt_if) + return false; + + bt_if = bus_if->bt_if; + + if (bt_if->use_count == 0) + return false; + + return true; +} + +bool inf_btsdio_set_bt_reset(struct brcmf_bus *bus_if) +{ + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return false; + } + + if (!bus_if->bt_if) + return false; + + return bus_if->bt_if->set_bt_reset; +} + +int inf_bus_attach(u32 ver, void *info) +{ + struct bt_shared_info *bts_info = NULL; + struct brcmf_sdio_dev *sdiodev = NULL; + struct inf_bt_if *bt_if = NULL; + + brcmf_dbg(INFO, "Enter\n"); + + if (!info) { + brcmf_err("info is null\n"); + return -EINVAL; + } + bts_info = (struct bt_shared_info *)info; + + if (!glob_bus_if) { + brcmf_err("btsdio is not initialized\n"); + return -EINVAL; + } + + if (!glob_bus_if->bt_if) { + brcmf_err("bt dev is not allocated\n"); + return -EINVAL; + } + + sdiodev = glob_bus_if->bus_priv.sdio; + + if (ver != BTS_VERSION) { + brcmf_err("version mismatch, bt 0x%x != wlan 0x%x\n", + ver, BTS_VERSION); + return -EINVAL; + } + + /* Get info from bt dev */ + bt_if = glob_bus_if->bt_if; + bt_if->bt_data = bts_info->bt_data; + bt_if->bt_sdio_int_cb = bts_info->bt_int_fun; + + /* Provide wlan info to bt dev */ + bts_info->wlan_bus_if = glob_bus_if; + bts_info->enum_addr = brcmf_sdio_get_enum_addr(sdiodev->bus); + + bt_if->cnt_attach++; + brcmf_dbg(INFO, "Done: device: 0x%x, enum addr: 0x%08x\n", + sdiodev->func1->device, bts_info->enum_addr); + return 0; +} +EXPORT_SYMBOL(inf_bus_attach); + +void inf_bus_detach(struct brcmf_bus *bus_if) +{ + struct inf_bt_if *bt_if = NULL; + + brcmf_dbg(INFO, "Enter\n"); + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return; + } + + if (!bus_if->bt_if) + return; + + bt_if = bus_if->bt_if; + + if (bt_if->bt_data) + bt_if->bt_data = NULL; + if (bt_if->bt_sdio_int_cb) + bt_if->bt_sdio_int_cb = NULL; + + bt_if->cnt_detach++; + brcmf_dbg(INFO, "Done\n"); +} +EXPORT_SYMBOL(inf_bus_detach); + +u8 inf_bus_reg_readb(struct brcmf_bus *bus_if, u8 fn, u32 addr, int *err) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct sdio_func *func = NULL; + struct timespec64 ts_start; + u8 val = 0; + + if (!bus_if || !err) { + brcmf_err("bus_if(%p) or err(%p) is null\n", bus_if, err); + *err = -EINVAL; + return 0; + } + + if (!bus_if->bt_if) { + *err = -EINVAL; + return 0; + } + + sdiodev = bus_if->bus_priv.sdio; + + func = inf_btsdio_get_func_entity(sdiodev, fn); + if (fn > SDIO_FUNC_3 || (fn != SDIO_FUNC_0 && !func)) { + brcmf_err("invalid function number = %d\n", fn); + *err = -EINVAL; + return 0; + } + + sdio_claim_host(sdiodev->func1); + ktime_get_ts64(&ts_start); + if (fn == SDIO_FUNC_0) + val = brcmf_sdiod_func0_rb(sdiodev, addr, err); + // wait for next commit for reading another function register + #if 0 + else + val = brcmf_sdiod_func_rb(func, addr, err); + #endif + // wait for next commit to recoed command + //_btsdio_record_cmd(&sdiodev->sdcnt.bt_reg_rb[fn], ts_start); + sdio_release_host(sdiodev->func1); + + brcmf_dbg(SDIO, "F%d addr: 0x%08x, val: 0x%02x, err: %d\n", fn, addr, val, *err); + + if (*err) + inf_btsdio_err_reg_record(bus_if->bt_if, ERR_REG_RB, *err, fn, addr, val); + + return val; +} +EXPORT_SYMBOL(inf_bus_reg_readb); + +void inf_bus_reg_writeb(struct brcmf_bus *bus_if, u8 fn, u32 addr, u8 val, int *err) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct sdio_func *func = NULL; + struct timespec64 ts_start; + + if (!bus_if || !err) { + brcmf_err("bus_if(%p) or err(%p) is null\n", bus_if, err); + *err = -EINVAL; + return; + } + + if (!bus_if->bt_if) { + *err = -EINVAL; + return; + } + + sdiodev = bus_if->bus_priv.sdio; + + func = inf_btsdio_get_func_entity(sdiodev, fn); + if (fn > SDIO_FUNC_3 || (fn != SDIO_FUNC_0 && !func)) { + brcmf_err("invalid function number = %d\n", fn); + *err = -EINVAL; + return; + } + + sdio_claim_host(sdiodev->func1); + ktime_get_ts64(&ts_start); + if (fn == SDIO_FUNC_0) + brcmf_sdiod_func0_wb(sdiodev, addr, val, err); + // wait for next commit for reading another function register + #if 0 + else + brcmf_sdiod_func_wb(func, addr, val, err); + #endif + // wait for next commit to recoed command + //_btsdio_record_cmd(&sdiodev->sdcnt.bt_reg_wb[fn], ts_start); + sdio_release_host(sdiodev->func1); + + brcmf_dbg(SDIO, "F%d addr: 0x%08x, val: 0x%02x, err: %d\n", fn, addr, val, *err); + + if (*err) + inf_btsdio_err_reg_record(bus_if->bt_if, ERR_REG_WB, *err, fn, addr, val); +} +EXPORT_SYMBOL(inf_bus_reg_writeb); + +u32 inf_bus_reg_readl(struct brcmf_bus *bus_if, u32 addr, int *err) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct timespec64 ts_start; + u32 val = 0; + + if (!bus_if || !err) { + brcmf_err("bus_if(%p) or err(%p) is null\n", bus_if, err); + *err = -EINVAL; + return 0; + } + + if (!bus_if->bt_if) { + *err = -EINVAL; + return 0; + } + + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + ktime_get_ts64(&ts_start); + val = brcmf_sdiod_readl(sdiodev, addr, err); + // wait for next commit to recoed command + //_btsdio_record_cmd(&sdiodev->sdcnt.bt_reg_rl, ts_start); + sdio_release_host(sdiodev->func1); + + brcmf_dbg(SDIO, "addr: 0x%08x, val: 0x%02x, err: %d\n", addr, val, *err); + + if (*err) + inf_btsdio_err_reg_record(bus_if->bt_if, ERR_REG_RL, *err, 1, addr, val); + + return val; +} +EXPORT_SYMBOL(inf_bus_reg_readl); + +void inf_bus_reg_writel(struct brcmf_bus *bus_if, u32 addr, u32 val, int *err) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct timespec64 ts_start; + + if (!bus_if || !err) { + brcmf_err("bus_if(%p) or err(%p) is null\n", bus_if, err); + *err = -EINVAL; + return; + } + + if (!bus_if->bt_if) { + *err = -EINVAL; + return; + } + + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + ktime_get_ts64(&ts_start); + brcmf_sdiod_writel(sdiodev, addr, val, err); + // wait for next commit to recoed command + //_btsdio_record_cmd(&sdiodev->sdcnt.bt_reg_wl, ts_start); + sdio_release_host(sdiodev->func1); + + brcmf_dbg(SDIO, "addr: 0x%08x, val: 0x%08x, err: %d\n", addr, val, *err); + + if (*err) + inf_btsdio_err_reg_record(bus_if->bt_if, ERR_REG_WL, *err, 1, addr, val); +} +EXPORT_SYMBOL(inf_bus_reg_writel); + +int inf_bus_recv_buf(struct brcmf_bus *bus_if, u8 *buf, u32 nbytes) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + int err = 0; + + if (!bus_if || !buf) { + brcmf_err("bus_if(%p) or buf(%p) is null\n", bus_if, buf); + return -EINVAL; + } + + if (!bus_if->bt_if) + return -EINVAL; + + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + // wait for next commit for Rx + //err = brcmf_sdiod_recv_buf(sdiodev, SDIO_FUNC_3, buf, nbytes); + sdio_release_host(sdiodev->func1); + + brcmf_dbg(DATA, "F3 receive nbytes: %d, err: %d\n", nbytes, err); + + if (err) + inf_btsdio_err_buf_record(bus_if->bt_if, ERR_BUF_RD, err, 0); + + return err; +} EXPORT_SYMBOL(inf_bus_recv_buf); + +int inf_bus_send_buf(struct brcmf_bus *bus_if, u8 *buf, u32 nbytes) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + int err = 0; + + if (!bus_if || !buf) { + brcmf_err("bus_if(%p) or buf(%p) is null\n", bus_if, buf); + return -EINVAL; + } + + if (!bus_if->bt_if) + return -EINVAL; + + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + // wait for next commit for Tx + //err = brcmf_sdiod_send_buf(sdiodev, SDIO_FUNC_3, buf, nbytes); + sdio_release_host(sdiodev->func1); + + brcmf_dbg(DATA, "F3 send nbytes: %d, err: %d\n", nbytes, err); + + if (err) + inf_btsdio_err_buf_record(bus_if->bt_if, ERR_BUF_WT, err, nbytes); + + return err; +} EXPORT_SYMBOL(inf_bus_send_buf); + +int inf_bus_membytes(struct brcmf_bus *bus_if, bool set, u32 address, u8 *data, u32 size) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + int err = 0; + u32 block1_offset = 0; + u32 block2_addr = 0; + u16 block1_size = 0; + u16 block2_size = 0; + u8 *block2_data = 0; + + if (!bus_if || !data) { + brcmf_err("bus_if(%p) or data(%p) is null\n", bus_if, data); + return -EINVAL; + } + + if (!bus_if->bt_if) + return -EINVAL; + + sdiodev = bus_if->bus_priv.sdio; + sdio_claim_host(sdiodev->func1); + do { + /* To avoid SDIO access crosses AXI 4k address boundaries crossing */ + if (((address & SDIOD_ADDR_BOUND_MASK) + size) > SDIOD_ADDR_BOUND) { + brcmf_dbg(SDIO, "data cross 4K boundary\n"); + /* The 1st 4k packet */ + block1_offset = address & SDIOD_ADDR_BOUND_MASK; + block1_size = (SDIOD_ADDR_BOUND - block1_offset); + + err = brcmf_sdiod_ramrw(sdiodev, set, address, + data, block1_size); + if (err) + break; + + /* The 2nd 4k packet */ + block2_addr = address + block1_size; + block2_size = size - block1_size; + block2_data = data + block1_size; + err = brcmf_sdiod_ramrw(sdiodev, set, block2_addr, + block2_data, block2_size); + } else { + err = brcmf_sdiod_ramrw(sdiodev, set, address, data, size); + } + } while (false); + sdio_release_host(sdiodev->func1); + + if (err) + inf_btsdio_err_mem_record(bus_if->bt_if, err, set, address, size); + + return err; +} +EXPORT_SYMBOL(inf_bus_membytes); + +int inf_bus_set_blocksz(struct brcmf_bus *bus_if, u16 blocksz) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + int err = 0; + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return -EINVAL; + } + + if (!bus_if->bt_if) + return -EINVAL; + + brcmf_dbg(INFO, "set F3 block size to %d\n", blocksz); + + sdiodev = bus_if->bus_priv.sdio; + sdio_claim_host(sdiodev->func1); + err = sdio_set_block_size(sdiodev->func3, blocksz); + sdio_release_host(sdiodev->func1); + if (err) + brcmf_err("set F3 block size failed, err: %d\n", err); + + return err; +} +EXPORT_SYMBOL(inf_bus_set_blocksz); + +/* Function to enable the Bus Clock + * This function is not callable from non-sleepable context + */ +int inf_bus_clk_enable(struct brcmf_bus *bus_if) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct inf_bt_if *bt_if = NULL; + int err = 0; + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return -EINVAL; + } + + if (!bus_if->bt_if) + return -EINVAL; + + bt_if = bus_if->bt_if; + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + bt_if->use_count++; + sdio_release_host(sdiodev->func1); + err = brcmf_sdio_sleep(sdiodev->bus, false); + + return err; +} +EXPORT_SYMBOL(inf_bus_clk_enable); + +/* Function to disable the Bus Clock + * This function is not callable from non-sleepable context + */ +int inf_bus_clk_disable(struct brcmf_bus *bus_if) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct inf_bt_if *bt_if = NULL; + int err = 0; + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return -EINVAL; + } + + if (!bus_if->bt_if) + return -EINVAL; + + bt_if = bus_if->bt_if; + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + if (bt_if->use_count != 0) + bt_if->use_count--; + sdio_release_host(sdiodev->func1); + err = brcmf_sdio_sleep(sdiodev->bus, true); + + return err; +} +EXPORT_SYMBOL(inf_bus_clk_disable); + +static bool inf_btsdio_is_over_sdio(struct brcmf_bus *bus_if) +{ + struct brcmf_pub *drvr = NULL; + struct brcmf_if *ifp = NULL; + struct brcmf_sdio_dev *sdiodev = NULL; + u32 bt_over_sdio_hw = 0; + int err = 0; + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return -EINVAL; + } + drvr = bus_if->drvr; + ifp = brcmf_get_ifp(drvr, 0); + sdiodev = bus_if->bus_priv.sdio; + + if (sdiodev->func1->device == SDIO_DEVICE_ID_CYPRESS_55500) { + err = brcmf_fil_iovar_int_get(ifp, "bt_over_sdio", &bt_over_sdio_hw); + if (err < 0) { + bt_over_sdio_hw = 0; + brcmf_err("failed to get bt_over_sdio\n"); + } + } + + brcmf_dbg(INFO, "SW: %d, HW: %d\n", + sdiodev->settings->bt_over_sdio, bt_over_sdio_hw); + + if (sdiodev->settings->bt_over_sdio & bt_over_sdio_hw) + return true; + else + return false; +} + +void inf_btsdio_init(struct brcmf_bus *bus_if) +{ + struct brcmf_sdio_dev *sdiodev = NULL; + struct inf_bt_if *bt_if = NULL; + + brcmf_dbg(INFO, "Enter\n"); + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return; + } + sdiodev = bus_if->bus_priv.sdio; + + if (!inf_btsdio_is_over_sdio(bus_if)) { + brcmf_err("bt over uart\n"); + return; + } + + /* Allocate bt dev */ + bt_if = kzalloc(sizeof(*bt_if), GFP_ATOMIC); + if (!bt_if) + return; + + glob_bus_if = bus_if; + bus_if->bt_if = bt_if; + + /* Initialize error list */ + INIT_LIST_HEAD(&bt_if->err_list); + spin_lock_init(&bt_if->err_list_lock); + + /* 43022: set bt reset by bt driver + * 55500: set bt reset by wl driver if hw enable bt over sdio + */ + if (sdiodev->func1->device == SDIO_DEVICE_ID_CYPRESS_55500) + bt_if->set_bt_reset = true; + + // wait for next commit for F3 interrupt + //brcmf_sdiod_intr_register_f3(sdiodev); + + brcmf_dbg(INFO, "init version (%d.%d.%d) done\n", + BTS_VER_MAJOR, BTS_VER_MINOR, BTS_VER_PATCH); +} + +void inf_btsdio_deinit(struct brcmf_bus *bus_if) +{ + brcmf_dbg(INFO, "Enter\n"); + + if (!bus_if) { + brcmf_err("bus_if is null\n"); + return; + } + + if (!bus_if->bt_if) + return; + + inf_bus_detach(bus_if); + + /* Free all error info */ + inf_btsdio_err_free_all(bus_if->bt_if); + + /* Free bt dev */ + kfree(bus_if->bt_if); + + bus_if->bt_if = NULL; + glob_bus_if = NULL; + + brcmf_dbg(INFO, "deinit done\n"); +} + +void inf_btsdio_debugfs_create(struct brcmf_pub *drvr) +{ + brcmf_debugfs_add_entry(drvr, "bts_info", inf_btsdio_debugfs_read); +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.h new file mode 100644 index 000000000000..921e82f518d5 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio_ifx.h @@ -0,0 +1,54 @@ +/* Infineon WLAN driver: BT shared SDIO implement + * + * copyright 2019, 2022 Cypress Semiconductor Corporation (an Infineon company) + * or an affiliate of Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related materials + * ("Software") is owned by Cypress Semiconductor Corporation or one of its + * affiliates ("Cypress") and is protected by and subject to + * worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license agreement + * accompanying the software package from which you obtained this Software ("EULA"). + * If no EULA applies, Cypress hereby grants you a personal, non-exclusive, + * non-transferable license to copy, modify, and compile the Software source code + * solely for use in connection with Cypress's integrated circuit products. + * Any reproduction, modification, translation, compilation, or representation + * of this Software except as specified above is prohibited without + * the expresswritten permission of Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Cypress reserves the right to make changes to the Software without notice. + * Cypress does not assume any liability arising out of the application or + * use of the Software or any product or circuit described in the Software. + * Cypress does not authorize its products for use in any products where a malfunction + * or failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). + * By including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing so + * agrees to indemnify Cypress against all liability. + */ + +#ifndef BT_SHARED_SDIO_IFX_H +#define BT_SHARED_SDIO_IFX_H + +#ifdef CONFIG_INFFMAC_BT_SHARED_SDIO + +void inf_btsdio_init(struct brcmf_bus *bus_if); +void inf_btsdio_deinit(struct brcmf_bus *bus_if); +void inf_btsdio_int_handler(struct brcmf_bus *bus_if); +bool inf_btsdio_is_active(struct brcmf_bus *bus_if); +bool inf_btsdio_set_bt_reset(struct brcmf_bus *bus_if); +bool inf_btsdio_inited(struct brcmf_bus *bus_if); +void inf_btsdio_debugfs_create(struct brcmf_pub *drvr); + +#else +#define inf_btsdio_init(bus_if) +#define inf_btsdio_deinit(bus_if) +#define inf_btsdio_is_active(bus_if) false +#define inf_btsdio_set_bt_reset(bus_if) false +#define inf_btsdio_inited(bus_if) false +#define inf_btsdio_debugfs_create(drvr) +#endif /* CONFIG_INFFMAC_BT_SHARED_SDIO */ + +#endif /* BT_SHARED_SDIO_IFX */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h index 104939f7dc99..1c336fde2d48 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h @@ -205,6 +205,11 @@ struct brcmf_bus { #ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO struct brcmf_bt_dev *bt_dev; #endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ +#ifdef CONFIG_INFFMAC_BT_SHARED_SDIO + struct inf_bt_if *bt_if; +#endif /* CONFIG_INFFMAC_BT_SHARED_SDIO */ + + }; /* diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c index cfdeeee1ead4..50c1599e52fc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c @@ -127,6 +127,10 @@ MODULE_PARM_DESC(offload_feat, "Offload feat bitmap: 0:arp 1:nd 2:mdns 3:icmp 4:tcp-keepalive " "5:dhcp-renewal 6:pno 7:keepalive 8:gtk 9:wowlpf (default: 0x1FF)"); +static int brcmf_bt_over_sdio; +module_param_named(bt_over_sdio, brcmf_bt_over_sdio, int, 0); +MODULE_PARM_DESC(bt_over_sdio, "Enable BT over SDIO"); + static struct brcmfmac_platform_data *brcmfmac_pdata; struct brcmf_mp_global_t brcmf_mp_global; @@ -748,6 +752,7 @@ int brcmf_debugfs_param_read(struct seq_file *s, void *data) seq_printf(s, "%-20s: %d\n", "offload_prof", brcmf_offload_prof); seq_printf(s, "%-20s: 0x%x\n", "offload_feat", brcmf_offload_feat); seq_printf(s, "%-20s: %d\n", "txglomsz", brcmf_sdiod_txglomsz); + seq_printf(s, "%-20s: %d\n", "bt_over_sdio", !!brcmf_bt_over_sdio); return 0; } @@ -809,6 +814,9 @@ struct brcmf_mp_device *brcmf_get_module_param(struct device *dev, settings->offload_feat = brcmf_offload_feat; brcmf_dbg(INFO, "offload_feat: 0x%x\n", settings->offload_feat); + settings->bt_over_sdio = !!brcmf_bt_over_sdio; + brcmf_dbg(INFO, "bt_over_sdio: %d\n", settings->bt_over_sdio); + if (bus_type == BRCMF_BUSTYPE_SDIO) { settings->bus.sdio.txglomsz = brcmf_sdiod_txglomsz; brcmf_dbg(INFO, "txglomsz: %d\n", settings->bus.sdio.txglomsz); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h index 01b5f903cf0b..7cdbb45935a8 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h @@ -66,6 +66,7 @@ struct brcmf_mp_device { bool sdio_rxf_in_kthread_enabled; unsigned int offload_prof; unsigned int offload_feat; + bool bt_over_sdio; struct brcmfmac_pd_cc *country_codes; const char *board_type; unsigned char mac[ETH_ALEN]; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index 697d98766c89..e84ae4e8c378 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -5544,3 +5544,8 @@ static int brcmf_ulp_event_notify(struct brcmf_if *ifp, return err; } + +u32 brcmf_sdio_get_enum_addr(struct brcmf_sdio *bus) +{ + return bus->sdio_core->base; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h index 64ebf80f132d..2692ce855d25 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h @@ -232,6 +232,7 @@ struct brcmf_ulp_event { struct brcmf_sdio_dev { struct sdio_func *func1; struct sdio_func *func2; + struct sdio_func *func3; u32 sbwad; /* Save backplane window address */ struct brcmf_core *cc_core; /* chipcommon core info struct */ struct brcmf_sdio *bus; @@ -436,6 +437,7 @@ void brcmf_sdio_wd_timer(struct brcmf_sdio *bus, bool active); void brcmf_sdio_wowl_config(struct device *dev, bool enabled); int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep); void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus); +u32 brcmf_sdio_get_enum_addr(struct brcmf_sdio *bus); /* SHM offsets */ #define M_DS1_CTRL_SDIO(ptr) ((ptr).ulp_shm_offset.m_ulp_ctrl_sdio) -- 2.25.1