summaryrefslogtreecommitdiffstats
path: root/drivers/bluetooth/btmrvl_sdio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/bluetooth/btmrvl_sdio.c')
-rw-r--r--drivers/bluetooth/btmrvl_sdio.c304
1 files changed, 302 insertions, 2 deletions
diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c
index 550bce089fa..0057c0b7a77 100644
--- a/drivers/bluetooth/btmrvl_sdio.c
+++ b/drivers/bluetooth/btmrvl_sdio.c
@@ -24,6 +24,7 @@
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/sdio_func.h>
#include <linux/module.h>
+#include <linux/devcoredump.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
@@ -33,6 +34,24 @@
#define VERSION "1.0"
+static struct memory_type_mapping mem_type_mapping_tbl[] = {
+ {"ITCM", NULL, 0, 0xF0},
+ {"DTCM", NULL, 0, 0xF1},
+ {"SQRAM", NULL, 0, 0xF2},
+ {"APU", NULL, 0, 0xF3},
+ {"CIU", NULL, 0, 0xF4},
+ {"ICU", NULL, 0, 0xF5},
+ {"MAC", NULL, 0, 0xF6},
+ {"EXT7", NULL, 0, 0xF7},
+ {"EXT8", NULL, 0, 0xF8},
+ {"EXT9", NULL, 0, 0xF9},
+ {"EXT10", NULL, 0, 0xFA},
+ {"EXT11", NULL, 0, 0xFB},
+ {"EXT12", NULL, 0, 0xFC},
+ {"EXT13", NULL, 0, 0xFD},
+ {"EXTLAST", NULL, 0, 0xFE},
+};
+
/* The btmrvl_sdio_remove() callback function is called
* when user removes this module from kernel space or ejects
* the card from the slot. The driver handles these 2 cases
@@ -122,6 +141,9 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_8897 = {
.int_read_to_clear = true,
.host_int_rsr = 0x01,
.card_misc_cfg = 0xcc,
+ .fw_dump_ctrl = 0xe2,
+ .fw_dump_start = 0xe3,
+ .fw_dump_end = 0xea,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = {
@@ -130,6 +152,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = {
.reg = &btmrvl_reg_8688,
.support_pscan_win_report = false,
.sd_blksz_fw_dl = 64,
+ .supports_fw_dump = false,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = {
@@ -138,6 +161,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = {
.reg = &btmrvl_reg_87xx,
.support_pscan_win_report = false,
.sd_blksz_fw_dl = 256,
+ .supports_fw_dump = false,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = {
@@ -146,6 +170,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = {
.reg = &btmrvl_reg_87xx,
.support_pscan_win_report = false,
.sd_blksz_fw_dl = 256,
+ .supports_fw_dump = false,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8887 = {
@@ -154,6 +179,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8887 = {
.reg = &btmrvl_reg_8887,
.support_pscan_win_report = true,
.sd_blksz_fw_dl = 256,
+ .supports_fw_dump = false,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8897 = {
@@ -162,6 +188,7 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8897 = {
.reg = &btmrvl_reg_8897,
.support_pscan_win_report = true,
.sd_blksz_fw_dl = 256,
+ .supports_fw_dump = true,
};
static const struct sdio_device_id btmrvl_sdio_ids[] = {
@@ -764,8 +791,8 @@ static void btmrvl_sdio_interrupt(struct sdio_func *func)
card = sdio_get_drvdata(func);
if (!card || !card->priv) {
- BT_ERR("sbi_interrupt(%p) card or priv is "
- "NULL, card=%p\n", func, card);
+ BT_ERR("sbi_interrupt(%p) card or priv is NULL, card=%p",
+ func, card);
return;
}
@@ -1080,6 +1107,277 @@ static int btmrvl_sdio_wakeup_fw(struct btmrvl_private *priv)
return ret;
}
+static void btmrvl_sdio_dump_regs(struct btmrvl_private *priv)
+{
+ struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
+ int ret = 0;
+ unsigned int reg, reg_start, reg_end;
+ char buf[256], *ptr;
+ u8 loop, func, data;
+ int MAX_LOOP = 2;
+
+ btmrvl_sdio_wakeup_fw(priv);
+ sdio_claim_host(card->func);
+
+ for (loop = 0; loop < MAX_LOOP; loop++) {
+ memset(buf, 0, sizeof(buf));
+ ptr = buf;
+
+ if (loop == 0) {
+ /* Read the registers of SDIO function0 */
+ func = loop;
+ reg_start = 0;
+ reg_end = 9;
+ } else {
+ func = 2;
+ reg_start = 0;
+ reg_end = 0x09;
+ }
+
+ ptr += sprintf(ptr, "SDIO Func%d (%#x-%#x): ",
+ func, reg_start, reg_end);
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ if (func == 0)
+ data = sdio_f0_readb(card->func, reg, &ret);
+ else
+ data = sdio_readb(card->func, reg, &ret);
+
+ if (!ret) {
+ ptr += sprintf(ptr, "%02x ", data);
+ } else {
+ ptr += sprintf(ptr, "ERR");
+ break;
+ }
+ }
+
+ BT_INFO("%s", buf);
+ }
+
+ sdio_release_host(card->func);
+}
+
+/* This function read/write firmware */
+static enum
+rdwr_status btmrvl_sdio_rdwr_firmware(struct btmrvl_private *priv,
+ u8 doneflag)
+{
+ struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
+ int ret, tries;
+ u8 ctrl_data = 0;
+
+ sdio_writeb(card->func, FW_DUMP_HOST_READY, card->reg->fw_dump_ctrl,
+ &ret);
+
+ if (ret) {
+ BT_ERR("SDIO write err");
+ return RDWR_STATUS_FAILURE;
+ }
+
+ for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
+ ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl,
+ &ret);
+
+ if (ret) {
+ BT_ERR("SDIO read err");
+ return RDWR_STATUS_FAILURE;
+ }
+
+ if (ctrl_data == FW_DUMP_DONE)
+ break;
+ if (doneflag && ctrl_data == doneflag)
+ return RDWR_STATUS_DONE;
+ if (ctrl_data != FW_DUMP_HOST_READY) {
+ BT_INFO("The ctrl reg was changed, re-try again!");
+ sdio_writeb(card->func, FW_DUMP_HOST_READY,
+ card->reg->fw_dump_ctrl, &ret);
+ if (ret) {
+ BT_ERR("SDIO write err");
+ return RDWR_STATUS_FAILURE;
+ }
+ }
+ usleep_range(100, 200);
+ }
+
+ if (ctrl_data == FW_DUMP_HOST_READY) {
+ BT_ERR("Fail to pull ctrl_data");
+ return RDWR_STATUS_FAILURE;
+ }
+
+ return RDWR_STATUS_SUCCESS;
+}
+
+/* This function dump sdio register and memory data */
+static void btmrvl_sdio_dump_firmware(struct btmrvl_private *priv)
+{
+ struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
+ int ret = 0;
+ unsigned int reg, reg_start, reg_end;
+ enum rdwr_status stat;
+ u8 *dbg_ptr, *end_ptr, *fw_dump_data, *fw_dump_ptr;
+ u8 dump_num, idx, i, read_reg, doneflag = 0;
+ u32 memory_size, fw_dump_len = 0;
+
+ /* dump sdio register first */
+ btmrvl_sdio_dump_regs(priv);
+
+ if (!card->supports_fw_dump) {
+ BT_ERR("Firmware dump not supported for this card!");
+ return;
+ }
+
+ for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) {
+ struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx];
+
+ if (entry->mem_ptr) {
+ vfree(entry->mem_ptr);
+ entry->mem_ptr = NULL;
+ }
+ entry->mem_size = 0;
+ }
+
+ btmrvl_sdio_wakeup_fw(priv);
+ sdio_claim_host(card->func);
+
+ BT_INFO("== btmrvl firmware dump start ==");
+
+ stat = btmrvl_sdio_rdwr_firmware(priv, doneflag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ reg = card->reg->fw_dump_start;
+ /* Read the number of the memories which will dump */
+ dump_num = sdio_readb(card->func, reg, &ret);
+
+ if (ret) {
+ BT_ERR("SDIO read memory length err");
+ goto done;
+ }
+
+ /* Read the length of every memory which will dump */
+ for (idx = 0; idx < dump_num; idx++) {
+ struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx];
+
+ stat = btmrvl_sdio_rdwr_firmware(priv, doneflag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ memory_size = 0;
+ reg = card->reg->fw_dump_start;
+ for (i = 0; i < 4; i++) {
+ read_reg = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ BT_ERR("SDIO read err");
+ goto done;
+ }
+ memory_size |= (read_reg << i*8);
+ reg++;
+ }
+
+ if (memory_size == 0) {
+ BT_INFO("Firmware dump finished!");
+ break;
+ }
+
+ BT_INFO("%s_SIZE=0x%x", entry->mem_name, memory_size);
+ entry->mem_ptr = vzalloc(memory_size + 1);
+ entry->mem_size = memory_size;
+ if (!entry->mem_ptr) {
+ BT_ERR("Vzalloc %s failed", entry->mem_name);
+ goto done;
+ }
+
+ fw_dump_len += (strlen("========Start dump ") +
+ strlen(entry->mem_name) +
+ strlen("========\n") +
+ (memory_size + 1) +
+ strlen("\n========End dump========\n"));
+
+ dbg_ptr = entry->mem_ptr;
+ end_ptr = dbg_ptr + memory_size;
+
+ doneflag = entry->done_flag;
+ BT_INFO("Start %s output, please wait...",
+ entry->mem_name);
+
+ do {
+ stat = btmrvl_sdio_rdwr_firmware(priv, doneflag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ reg_start = card->reg->fw_dump_start;
+ reg_end = card->reg->fw_dump_end;
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ *dbg_ptr = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ BT_ERR("SDIO read err");
+ goto done;
+ }
+ if (dbg_ptr < end_ptr)
+ dbg_ptr++;
+ else
+ BT_ERR("Allocated buffer not enough");
+ }
+
+ if (stat != RDWR_STATUS_DONE) {
+ continue;
+ } else {
+ BT_INFO("%s done: size=0x%tx",
+ entry->mem_name,
+ dbg_ptr - entry->mem_ptr);
+ break;
+ }
+ } while (1);
+ }
+
+ BT_INFO("== btmrvl firmware dump end ==");
+
+done:
+ sdio_release_host(card->func);
+
+ if (fw_dump_len == 0)
+ return;
+
+ fw_dump_data = vzalloc(fw_dump_len+1);
+ if (!fw_dump_data) {
+ BT_ERR("Vzalloc fw_dump_data fail!");
+ return;
+ }
+ fw_dump_ptr = fw_dump_data;
+
+ /* Dump all the memory data into single file, a userspace script will
+ be used to split all the memory data to multiple files*/
+ BT_INFO("== btmrvl firmware dump to /sys/class/devcoredump start");
+ for (idx = 0; idx < dump_num; idx++) {
+ struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx];
+
+ if (entry->mem_ptr) {
+ strcpy(fw_dump_ptr, "========Start dump ");
+ fw_dump_ptr += strlen("========Start dump ");
+
+ strcpy(fw_dump_ptr, entry->mem_name);
+ fw_dump_ptr += strlen(entry->mem_name);
+
+ strcpy(fw_dump_ptr, "========\n");
+ fw_dump_ptr += strlen("========\n");
+
+ memcpy(fw_dump_ptr, entry->mem_ptr, entry->mem_size);
+ fw_dump_ptr += entry->mem_size;
+
+ strcpy(fw_dump_ptr, "\n========End dump========\n");
+ fw_dump_ptr += strlen("\n========End dump========\n");
+
+ vfree(mem_type_mapping_tbl[idx].mem_ptr);
+ mem_type_mapping_tbl[idx].mem_ptr = NULL;
+ }
+ }
+
+ /* fw_dump_data will be free in device coredump release function
+ after 5 min*/
+ dev_coredumpv(&priv->btmrvl_dev.hcidev->dev, fw_dump_data,
+ fw_dump_len, GFP_KERNEL);
+ BT_INFO("== btmrvl firmware dump to /sys/class/devcoredump end");
+}
+
static int btmrvl_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
@@ -1103,6 +1401,7 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
card->reg = data->reg;
card->sd_blksz_fw_dl = data->sd_blksz_fw_dl;
card->support_pscan_win_report = data->support_pscan_win_report;
+ card->supports_fw_dump = data->supports_fw_dump;
}
if (btmrvl_sdio_register_dev(card) < 0) {
@@ -1134,6 +1433,7 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
priv->hw_host_to_card = btmrvl_sdio_host_to_card;
priv->hw_wakeup_firmware = btmrvl_sdio_wakeup_fw;
priv->hw_process_int_status = btmrvl_sdio_process_int_status;
+ priv->firmware_dump = btmrvl_sdio_dump_firmware;
if (btmrvl_register_hdev(priv)) {
BT_ERR("Register hdev failed!");