summaryrefslogtreecommitdiff
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.c740
1 files changed, 648 insertions, 92 deletions
diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c
index 75c262694632..93932a0d8625 100644
--- a/drivers/bluetooth/btmrvl_sdio.c
+++ b/drivers/bluetooth/btmrvl_sdio.c
@@ -1,29 +1,18 @@
-/**
+// SPDX-License-Identifier: GPL-2.0-only
+/*
* Marvell BT-over-SDIO driver: SDIO interface related functions.
*
* Copyright (C) 2009, Marvell International Ltd.
- *
- * This software file (the "File") is distributed by Marvell International
- * Ltd. under the terms of the GNU General Public License Version 2, June 1991
- * (the "License"). You may use, redistribute and/or modify this File in
- * accordance with the terms and conditions of the License, a copy of which
- * is available by writing to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
- * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
- *
- *
- * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
- * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
- * this warranty disclaimer.
**/
#include <linux/firmware.h>
#include <linux/slab.h>
+#include <linux/suspend.h>
#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 +22,93 @@
#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},
+};
+
+static const struct of_device_id btmrvl_sdio_of_match_table[] __maybe_unused = {
+ { .compatible = "marvell,sd8897-bt" },
+ { .compatible = "marvell,sd8997-bt" },
+ { }
+};
+
+static irqreturn_t btmrvl_wake_irq_bt(int irq, void *priv)
+{
+ struct btmrvl_sdio_card *card = priv;
+ struct device *dev = &card->func->dev;
+ struct btmrvl_plt_wake_cfg *cfg = card->plt_wake_cfg;
+
+ dev_info(dev, "wake by bt\n");
+ cfg->wake_by_bt = true;
+ disable_irq_nosync(irq);
+
+ pm_wakeup_event(dev, 0);
+ pm_system_wakeup();
+
+ return IRQ_HANDLED;
+}
+
+/* This function parses device tree node using mmc subnode devicetree API.
+ * The device node is saved in card->plt_of_node.
+ * If the device tree node exists and includes interrupts attributes, this
+ * function will request platform specific wakeup interrupt.
+ */
+static int btmrvl_sdio_probe_of(struct device *dev,
+ struct btmrvl_sdio_card *card)
+{
+ struct btmrvl_plt_wake_cfg *cfg;
+ int ret;
+
+ if (!dev->of_node ||
+ !of_match_node(btmrvl_sdio_of_match_table, dev->of_node)) {
+ dev_info(dev, "sdio device tree data not available\n");
+ return -1;
+ }
+
+ card->plt_of_node = dev->of_node;
+
+ card->plt_wake_cfg = devm_kzalloc(dev, sizeof(*card->plt_wake_cfg),
+ GFP_KERNEL);
+ cfg = card->plt_wake_cfg;
+ if (cfg && card->plt_of_node) {
+ cfg->irq_bt = irq_of_parse_and_map(card->plt_of_node, 0);
+ if (!cfg->irq_bt) {
+ dev_err(dev, "fail to parse irq_bt from device tree\n");
+ cfg->irq_bt = -1;
+ } else {
+ ret = devm_request_irq(dev, cfg->irq_bt,
+ btmrvl_wake_irq_bt,
+ IRQF_NO_AUTOEN, "bt_wake", card);
+ if (ret) {
+ dev_err(dev,
+ "Failed to request irq_bt %d (%d)\n",
+ cfg->irq_bt, ret);
+ }
+
+ /* Configure wakeup (enabled by default) */
+ ret = devm_device_init_wakeup(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init wakeup\n");
+ }
+ }
+
+ return 0;
+}
+
/* 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
@@ -64,6 +140,7 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_8688 = {
.io_port_0 = 0x00,
.io_port_1 = 0x01,
.io_port_2 = 0x02,
+ .int_read_to_clear = false,
};
static const struct btmrvl_sdio_card_reg btmrvl_reg_87xx = {
.cfg = 0x00,
@@ -80,9 +157,30 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_87xx = {
.io_port_0 = 0x78,
.io_port_1 = 0x79,
.io_port_2 = 0x7a,
+ .int_read_to_clear = false,
+};
+
+static const struct btmrvl_sdio_card_reg btmrvl_reg_8887 = {
+ .cfg = 0x00,
+ .host_int_mask = 0x08,
+ .host_intstatus = 0x0C,
+ .card_status = 0x5C,
+ .sq_read_base_addr_a0 = 0x6C,
+ .sq_read_base_addr_a1 = 0x6D,
+ .card_revision = 0xC8,
+ .card_fw_status0 = 0x88,
+ .card_fw_status1 = 0x89,
+ .card_rx_len = 0x8A,
+ .card_rx_unit = 0x8B,
+ .io_port_0 = 0xE4,
+ .io_port_1 = 0xE5,
+ .io_port_2 = 0xE6,
+ .int_read_to_clear = true,
+ .host_int_rsr = 0x04,
+ .card_misc_cfg = 0xD8,
};
-static const struct btmrvl_sdio_card_reg btmrvl_reg_88xx = {
+static const struct btmrvl_sdio_card_reg btmrvl_reg_8897 = {
.cfg = 0x00,
.host_int_mask = 0x02,
.host_intstatus = 0x03,
@@ -97,52 +195,137 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_88xx = {
.io_port_0 = 0xd8,
.io_port_1 = 0xd9,
.io_port_2 = 0xda,
+ .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_card_reg btmrvl_reg_89xx = {
+ .cfg = 0x00,
+ .host_int_mask = 0x08,
+ .host_intstatus = 0x0c,
+ .card_status = 0x5c,
+ .sq_read_base_addr_a0 = 0xf8,
+ .sq_read_base_addr_a1 = 0xf9,
+ .card_revision = 0xc8,
+ .card_fw_status0 = 0xe8,
+ .card_fw_status1 = 0xe9,
+ .card_rx_len = 0xea,
+ .card_rx_unit = 0xeb,
+ .io_port_0 = 0xe4,
+ .io_port_1 = 0xe5,
+ .io_port_2 = 0xe6,
+ .int_read_to_clear = true,
+ .host_int_rsr = 0x04,
+ .card_misc_cfg = 0xd8,
+ .fw_dump_ctrl = 0xf0,
+ .fw_dump_start = 0xf1,
+ .fw_dump_end = 0xf8,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = {
.helper = "mrvl/sd8688_helper.bin",
.firmware = "mrvl/sd8688.bin",
.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 = {
.helper = NULL,
.firmware = "mrvl/sd8787_uapsta.bin",
.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 = {
.helper = NULL,
.firmware = "mrvl/sd8797_uapsta.bin",
.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 = {
+ .helper = NULL,
+ .firmware = "mrvl/sd8887_uapsta.bin",
+ .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 = {
.helper = NULL,
.firmware = "mrvl/sd8897_uapsta.bin",
- .reg = &btmrvl_reg_88xx,
+ .reg = &btmrvl_reg_8897,
+ .support_pscan_win_report = true,
+ .sd_blksz_fw_dl = 256,
+ .supports_fw_dump = true,
+};
+
+static const struct btmrvl_sdio_device btmrvl_sdio_sd8977 = {
+ .helper = NULL,
+ .firmware = "mrvl/sdsd8977_combo_v2.bin",
+ .reg = &btmrvl_reg_89xx,
+ .support_pscan_win_report = true,
+ .sd_blksz_fw_dl = 256,
+ .supports_fw_dump = true,
+};
+
+static const struct btmrvl_sdio_device btmrvl_sdio_sd8987 = {
+ .helper = NULL,
+ .firmware = "mrvl/sd8987_uapsta.bin",
+ .reg = &btmrvl_reg_89xx,
+ .support_pscan_win_report = true,
.sd_blksz_fw_dl = 256,
+ .supports_fw_dump = true,
+};
+
+static const struct btmrvl_sdio_device btmrvl_sdio_sd8997 = {
+ .helper = NULL,
+ .firmware = "mrvl/sdsd8997_combo_v4.bin",
+ .reg = &btmrvl_reg_89xx,
+ .support_pscan_win_report = true,
+ .sd_blksz_fw_dl = 256,
+ .supports_fw_dump = true,
};
static const struct sdio_device_id btmrvl_sdio_ids[] = {
/* Marvell SD8688 Bluetooth device */
- { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x9105),
- .driver_data = (unsigned long) &btmrvl_sdio_sd8688 },
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8688_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8688 },
/* Marvell SD8787 Bluetooth device */
- { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911A),
- .driver_data = (unsigned long) &btmrvl_sdio_sd8787 },
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8787_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8787 },
/* Marvell SD8787 Bluetooth AMP device */
- { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911B),
- .driver_data = (unsigned long) &btmrvl_sdio_sd8787 },
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8787_BT_AMP),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8787 },
/* Marvell SD8797 Bluetooth device */
- { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x912A),
- .driver_data = (unsigned long) &btmrvl_sdio_sd8797 },
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8797_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8797 },
+ /* Marvell SD8887 Bluetooth device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8887_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8887 },
/* Marvell SD8897 Bluetooth device */
- { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x912E),
- .driver_data = (unsigned long) &btmrvl_sdio_sd8897 },
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8897_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8897 },
+ /* Marvell SD8977 Bluetooth device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8977_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8977 },
+ /* Marvell SD8987 Bluetooth device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8987_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8987 },
+ /* Marvell SD8997 Bluetooth device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8997_BT),
+ .driver_data = (unsigned long)&btmrvl_sdio_sd8997 },
{ } /* Terminating entry */
};
@@ -269,7 +452,7 @@ static int btmrvl_sdio_verify_fw_download(struct btmrvl_sdio_card *card,
if (firmwarestat == FIRMWARE_READY)
return 0;
- msleep(10);
+ msleep(100);
}
return -ETIMEDOUT;
@@ -486,7 +669,7 @@ static int btmrvl_sdio_download_fw_w_helper(struct btmrvl_sdio_card *card)
if (firmwarelen - offset < txlen)
txlen = firmwarelen - offset;
- tx_blocks = (txlen + blksz_dl - 1) / blksz_dl;
+ tx_blocks = DIV_ROUND_UP(txlen, blksz_dl);
memcpy(fwbuf, &firmware[offset], txlen);
}
@@ -506,7 +689,7 @@ static int btmrvl_sdio_download_fw_w_helper(struct btmrvl_sdio_card *card)
offset += txlen;
} while (true);
- BT_DBG("FW download over, size %d bytes", offset);
+ BT_INFO("FW download over, size %d bytes", offset);
ret = 0;
@@ -522,7 +705,7 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
int ret, num_blocks, blksz;
struct sk_buff *skb = NULL;
u32 type;
- u8 *payload = NULL;
+ u8 *payload;
struct hci_dev *hdev = priv->btmrvl_dev.hcidev;
struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
@@ -551,8 +734,8 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
}
/* Allocate buffer */
- skb = bt_skb_alloc(num_blocks * blksz + BTSDIO_DMA_ALIGN, GFP_ATOMIC);
- if (skb == NULL) {
+ skb = bt_skb_alloc(num_blocks * blksz + BTSDIO_DMA_ALIGN, GFP_KERNEL);
+ if (!skb) {
BT_ERR("No free skb");
ret = -ENOMEM;
goto exit;
@@ -596,29 +779,27 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
case HCI_ACLDATA_PKT:
case HCI_SCODATA_PKT:
case HCI_EVENT_PKT:
- bt_cb(skb)->pkt_type = type;
- skb->dev = (void *)hdev;
+ hci_skb_pkt_type(skb) = type;
skb_put(skb, buf_len);
skb_pull(skb, SDIO_HEADER_LEN);
if (type == HCI_EVENT_PKT) {
if (btmrvl_check_evtpkt(priv, skb))
- hci_recv_frame(skb);
+ hci_recv_frame(hdev, skb);
} else {
- hci_recv_frame(skb);
+ hci_recv_frame(hdev, skb);
}
hdev->stat.byte_rx += buf_len;
break;
case MRVL_VENDOR_PKT:
- bt_cb(skb)->pkt_type = HCI_VENDOR_PKT;
- skb->dev = (void *)hdev;
+ hci_skb_pkt_type(skb) = HCI_VENDOR_PKT;
skb_put(skb, buf_len);
skb_pull(skb, SDIO_HEADER_LEN);
if (btmrvl_process_event(priv, skb))
- hci_recv_frame(skb);
+ hci_recv_frame(hdev, skb);
hdev->stat.byte_rx += buf_len;
break;
@@ -669,46 +850,81 @@ static int btmrvl_sdio_process_int_status(struct btmrvl_private *priv)
return 0;
}
-static void btmrvl_sdio_interrupt(struct sdio_func *func)
+static int btmrvl_sdio_read_to_clear(struct btmrvl_sdio_card *card, u8 *ireg)
{
- struct btmrvl_private *priv;
- struct btmrvl_sdio_card *card;
- ulong flags;
- u8 ireg = 0;
+ struct btmrvl_adapter *adapter = card->priv->adapter;
int ret;
- card = sdio_get_drvdata(func);
- if (!card || !card->priv) {
- BT_ERR("sbi_interrupt(%p) card or priv is "
- "NULL, card=%p\n", func, card);
- return;
+ ret = sdio_readsb(card->func, adapter->hw_regs, 0, SDIO_BLOCK_SIZE);
+ if (ret) {
+ BT_ERR("sdio_readsb: read int hw_regs failed: %d", ret);
+ return ret;
}
- priv = card->priv;
+ *ireg = adapter->hw_regs[card->reg->host_intstatus];
+ BT_DBG("hw_regs[%#x]=%#x", card->reg->host_intstatus, *ireg);
+
+ return 0;
+}
+
+static int btmrvl_sdio_write_to_clear(struct btmrvl_sdio_card *card, u8 *ireg)
+{
+ int ret;
- ireg = sdio_readb(card->func, card->reg->host_intstatus, &ret);
+ *ireg = sdio_readb(card->func, card->reg->host_intstatus, &ret);
if (ret) {
- BT_ERR("sdio_readb: read int status register failed");
- return;
+ BT_ERR("sdio_readb: read int status failed: %d", ret);
+ return ret;
}
- if (ireg != 0) {
+ if (*ireg) {
/*
* DN_LD_HOST_INT_STATUS and/or UP_LD_HOST_INT_STATUS
* Clear the interrupt status register and re-enable the
* interrupt.
*/
- BT_DBG("ireg = 0x%x", ireg);
+ BT_DBG("int_status = 0x%x", *ireg);
- sdio_writeb(card->func, ~(ireg) & (DN_LD_HOST_INT_STATUS |
- UP_LD_HOST_INT_STATUS),
- card->reg->host_intstatus, &ret);
+ sdio_writeb(card->func, ~(*ireg) & (DN_LD_HOST_INT_STATUS |
+ UP_LD_HOST_INT_STATUS),
+ card->reg->host_intstatus, &ret);
if (ret) {
- BT_ERR("sdio_writeb: clear int status register failed");
- return;
+ BT_ERR("sdio_writeb: clear int status failed: %d", ret);
+ return ret;
}
}
+ return 0;
+}
+
+static void btmrvl_sdio_interrupt(struct sdio_func *func)
+{
+ struct btmrvl_private *priv;
+ struct btmrvl_sdio_card *card;
+ ulong flags;
+ u8 ireg = 0;
+ int ret;
+
+ card = sdio_get_drvdata(func);
+ if (!card || !card->priv) {
+ BT_ERR("sbi_interrupt(%p) card or priv is NULL, card=%p",
+ func, card);
+ return;
+ }
+
+ priv = card->priv;
+
+ if (priv->surprise_removed)
+ return;
+
+ if (card->reg->int_read_to_clear)
+ ret = btmrvl_sdio_read_to_clear(card, &ireg);
+ else
+ ret = btmrvl_sdio_write_to_clear(card, &ireg);
+
+ if (ret)
+ return;
+
spin_lock_irqsave(&priv->driver_lock, flags);
sdio_ireg |= ireg;
spin_unlock_irqrestore(&priv->driver_lock, flags);
@@ -720,7 +936,7 @@ static int btmrvl_sdio_register_dev(struct btmrvl_sdio_card *card)
{
struct sdio_func *func;
u8 reg;
- int ret = 0;
+ int ret;
if (!card || !card->func) {
BT_ERR("Error: card or function is NULL!");
@@ -779,6 +995,30 @@ static int btmrvl_sdio_register_dev(struct btmrvl_sdio_card *card)
BT_DBG("SDIO FUNC%d IO port: 0x%x", func->num, card->ioport);
+ if (card->reg->int_read_to_clear) {
+ reg = sdio_readb(func, card->reg->host_int_rsr, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+ sdio_writeb(func, reg | 0x3f, card->reg->host_int_rsr, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+
+ reg = sdio_readb(func, card->reg->card_misc_cfg, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+ sdio_writeb(func, reg | 0x10, card->reg->card_misc_cfg, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+ }
+
sdio_set_drvdata(func, card);
sdio_release_host(func);
@@ -850,7 +1090,6 @@ static int btmrvl_sdio_host_to_card(struct btmrvl_private *priv,
{
struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
int ret = 0;
- int buf_block_len;
int blksz;
int i = 0;
u8 *buf = NULL;
@@ -862,9 +1101,13 @@ static int btmrvl_sdio_host_to_card(struct btmrvl_private *priv,
return -EINVAL;
}
+ blksz = DIV_ROUND_UP(nb, SDIO_BLOCK_SIZE) * SDIO_BLOCK_SIZE;
+
buf = payload;
- if ((unsigned long) payload & (BTSDIO_DMA_ALIGN - 1)) {
- tmpbufsz = ALIGN_SZ(nb, BTSDIO_DMA_ALIGN);
+ if ((unsigned long) payload & (BTSDIO_DMA_ALIGN - 1) ||
+ nb < blksz) {
+ tmpbufsz = ALIGN_SZ(blksz, BTSDIO_DMA_ALIGN) +
+ BTSDIO_DMA_ALIGN;
tmpbuf = kzalloc(tmpbufsz, GFP_KERNEL);
if (!tmpbuf)
return -ENOMEM;
@@ -872,15 +1115,12 @@ static int btmrvl_sdio_host_to_card(struct btmrvl_private *priv,
memcpy(buf, payload, nb);
}
- blksz = SDIO_BLOCK_SIZE;
- buf_block_len = (nb + blksz - 1) / blksz;
-
sdio_claim_host(card->func);
do {
/* Transfer data to card */
ret = sdio_writesb(card->func, card->ioport, buf,
- buf_block_len * blksz);
+ blksz);
if (ret < 0) {
i++;
BT_ERR("i=%d writesb failed: %d", i, ret);
@@ -947,17 +1187,18 @@ static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card)
}
}
- sdio_release_host(card->func);
-
/*
* winner or not, with this test the FW synchronizes when the
* module can continue its initialization
*/
if (btmrvl_sdio_verify_fw_download(card, pollnum)) {
BT_ERR("FW failed to be active in time!");
- return -ETIMEDOUT;
+ ret = -ETIMEDOUT;
+ goto done;
}
+ sdio_release_host(card->func);
+
return 0;
done:
@@ -986,6 +1227,286 @@ 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_coredump(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct btmrvl_sdio_card *card;
+ struct btmrvl_private *priv;
+ 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 = 0, idx, i, read_reg, doneflag = 0;
+ u32 memory_size, fw_dump_len = 0;
+ int size = 0;
+
+ card = sdio_get_drvdata(func);
+ priv = card->priv;
+
+ /* 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!");
+ sdio_writeb(card->func, FW_DUMP_READ_DONE,
+ card->reg->fw_dump_ctrl, &ret);
+ if (ret) {
+ BT_ERR("SDIO Write MEMDUMP_FINISH ERR");
+ goto done;
+ }
+ 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) {
+ 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) {
+ size += scnprintf(fw_dump_ptr + size,
+ fw_dump_len + 1 - size,
+ "========Start dump %s========\n",
+ entry->mem_name);
+
+ memcpy(fw_dump_ptr + size, entry->mem_ptr,
+ entry->mem_size);
+ size += entry->mem_size;
+
+ size += scnprintf(fw_dump_ptr + size,
+ fw_dump_len + 1 - size,
+ "\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(&card->func->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)
{
@@ -1008,6 +1529,8 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
card->firmware = data->firmware;
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) {
@@ -1026,6 +1549,9 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
btmrvl_sdio_enable_host_int(card);
+ /* Device tree node parsing and platform specific configuration*/
+ btmrvl_sdio_probe_of(&func->dev, card);
+
priv = btmrvl_add_card(card);
if (!priv) {
BT_ERR("Initializing card failed!");
@@ -1046,12 +1572,6 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
goto disable_host_int;
}
- priv->btmrvl_dev.psmode = 1;
- btmrvl_enable_ps(priv);
-
- priv->btmrvl_dev.gpio_gap = 0xffff;
- btmrvl_send_hscfg_cmd(priv);
-
return 0;
disable_host_int:
@@ -1076,7 +1596,9 @@ static void btmrvl_sdio_remove(struct sdio_func *func)
MODULE_SHUTDOWN_REQ);
btmrvl_sdio_disable_host_int(card);
}
- BT_DBG("unregester dev");
+
+ BT_DBG("unregister dev");
+ card->priv->surprise_removed = true;
btmrvl_sdio_unregister_dev(card);
btmrvl_remove_card(card->priv);
}
@@ -1110,29 +1632,47 @@ static int btmrvl_sdio_suspend(struct device *dev)
return 0;
}
+ /* Enable platform specific wakeup interrupt */
+ if (card->plt_wake_cfg && card->plt_wake_cfg->irq_bt >= 0 &&
+ device_may_wakeup(dev)) {
+ card->plt_wake_cfg->wake_by_bt = false;
+ enable_irq(card->plt_wake_cfg->irq_bt);
+ enable_irq_wake(card->plt_wake_cfg->irq_bt);
+ }
+
priv = card->priv;
+ priv->adapter->is_suspending = true;
+ hcidev = priv->btmrvl_dev.hcidev;
+ BT_DBG("%s: SDIO suspend", hcidev->name);
+ hci_suspend_dev(hcidev);
if (priv->adapter->hs_state != HS_ACTIVATED) {
if (btmrvl_enable_hs(priv)) {
- BT_ERR("HS not actived, suspend failed!");
+ BT_ERR("HS not activated, suspend failed!");
+ /* Disable platform specific wakeup interrupt */
+ if (card->plt_wake_cfg &&
+ card->plt_wake_cfg->irq_bt >= 0 &&
+ device_may_wakeup(dev)) {
+ disable_irq_wake(card->plt_wake_cfg->irq_bt);
+ disable_irq(card->plt_wake_cfg->irq_bt);
+ }
+
+ priv->adapter->is_suspending = false;
return -EBUSY;
}
}
- hcidev = priv->btmrvl_dev.hcidev;
- BT_DBG("%s: SDIO suspend", hcidev->name);
- hci_suspend_dev(hcidev);
- skb_queue_purge(&priv->adapter->tx_queue);
+ priv->adapter->is_suspending = false;
priv->adapter->is_suspended = true;
/* We will keep the power when hs enabled successfully */
if (priv->adapter->hs_state == HS_ACTIVATED) {
BT_DBG("suspend with MMC_PM_KEEP_POWER");
return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
- } else {
- BT_DBG("suspend without MMC_PM_KEEP_POWER");
- return 0;
}
+
+ BT_DBG("suspend without MMC_PM_KEEP_POWER");
+ return 0;
}
static int btmrvl_sdio_resume(struct device *dev)
@@ -1163,13 +1703,25 @@ static int btmrvl_sdio_resume(struct device *dev)
return 0;
}
- priv->adapter->is_suspended = false;
- hcidev = priv->btmrvl_dev.hcidev;
- BT_DBG("%s: SDIO resume", hcidev->name);
- hci_resume_dev(hcidev);
priv->hw_wakeup_firmware(priv);
priv->adapter->hs_state = HS_DEACTIVATED;
+ hcidev = priv->btmrvl_dev.hcidev;
BT_DBG("%s: HS DEACTIVATED in resume!", hcidev->name);
+ priv->adapter->is_suspended = false;
+ BT_DBG("%s: SDIO resume", hcidev->name);
+ hci_resume_dev(hcidev);
+
+ /* Disable platform specific wakeup interrupt */
+ if (card->plt_wake_cfg && card->plt_wake_cfg->irq_bt >= 0 &&
+ device_may_wakeup(dev)) {
+ disable_irq_wake(card->plt_wake_cfg->irq_bt);
+ disable_irq(card->plt_wake_cfg->irq_bt);
+ if (card->plt_wake_cfg->wake_by_bt)
+ /* Undo our disable, since interrupt handler already
+ * did this.
+ */
+ enable_irq(card->plt_wake_cfg->irq_bt);
+ }
return 0;
}
@@ -1185,7 +1737,7 @@ static struct sdio_driver bt_mrvl_sdio = {
.probe = btmrvl_sdio_probe,
.remove = btmrvl_sdio_remove,
.drv = {
- .owner = THIS_MODULE,
+ .coredump = btmrvl_sdio_coredump,
.pm = &btmrvl_sdio_pm_ops,
}
};
@@ -1222,4 +1774,8 @@ MODULE_FIRMWARE("mrvl/sd8688_helper.bin");
MODULE_FIRMWARE("mrvl/sd8688.bin");
MODULE_FIRMWARE("mrvl/sd8787_uapsta.bin");
MODULE_FIRMWARE("mrvl/sd8797_uapsta.bin");
+MODULE_FIRMWARE("mrvl/sd8887_uapsta.bin");
MODULE_FIRMWARE("mrvl/sd8897_uapsta.bin");
+MODULE_FIRMWARE("mrvl/sdsd8977_combo_v2.bin");
+MODULE_FIRMWARE("mrvl/sd8987_uapsta.bin");
+MODULE_FIRMWARE("mrvl/sdsd8997_combo_v4.bin");