btmrvl: add platform specific wakeup interrupt support
On some arm-based platforms, we need to configure platform specific parameters by device tree node and also define our node as a child node of parent SDIO host controller. This patch parses these parameters from device tree. It includes calibration data download to firmware, wakeup pin configured to firmware, and soc specific wake up gpio, which will be set as wakeup interrupt pin. Signed-off-by: Xinming Hu <huxm@marvell.com> Signed-off-by: Amitkumar Karwar <akarwar@marvell.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
parent
0065d1c5ac
commit
bb7f4f0bce
4 changed files with 116 additions and 15 deletions
|
@ -23,6 +23,17 @@
|
|||
#include <linux/bitops.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_irq.h>
|
||||
|
||||
#define BTM_HEADER_LEN 4
|
||||
#define BTM_UPLD_SIZE 2312
|
||||
|
|
|
@ -510,34 +510,39 @@ static int btmrvl_download_cal_data(struct btmrvl_private *priv,
|
|||
static int btmrvl_check_device_tree(struct btmrvl_private *priv)
|
||||
{
|
||||
struct device_node *dt_node;
|
||||
struct btmrvl_sdio_card *card = priv->btmrvl_dev.card;
|
||||
u8 cal_data[BT_CAL_HDR_LEN + BT_CAL_DATA_SIZE];
|
||||
int ret;
|
||||
u32 val;
|
||||
int ret = 0;
|
||||
u16 gpio, gap;
|
||||
|
||||
for_each_compatible_node(dt_node, NULL, "btmrvl,cfgdata") {
|
||||
ret = of_property_read_u32(dt_node, "btmrvl,gpio-gap", &val);
|
||||
if (!ret)
|
||||
priv->btmrvl_dev.gpio_gap = val;
|
||||
if (card->plt_of_node) {
|
||||
dt_node = card->plt_of_node;
|
||||
ret = of_property_read_u16(dt_node, "marvell,wakeup-pin",
|
||||
&gpio);
|
||||
if (ret)
|
||||
gpio = (priv->btmrvl_dev.gpio_gap & 0xff00) >> 8;
|
||||
|
||||
ret = of_property_read_u8_array(dt_node, "btmrvl,cal-data",
|
||||
ret = of_property_read_u16(dt_node, "marvell,wakeup-gap-ms",
|
||||
&gap);
|
||||
if (ret)
|
||||
gap = (u8)(priv->btmrvl_dev.gpio_gap & 0x00ff);
|
||||
|
||||
priv->btmrvl_dev.gpio_gap = (gpio << 8) + gap;
|
||||
|
||||
ret = of_property_read_u8_array(dt_node, "marvell,cal-data",
|
||||
cal_data + BT_CAL_HDR_LEN,
|
||||
BT_CAL_DATA_SIZE);
|
||||
if (ret) {
|
||||
of_node_put(dt_node);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
BT_DBG("Use cal data from device tree");
|
||||
ret = btmrvl_download_cal_data(priv, cal_data,
|
||||
BT_CAL_DATA_SIZE);
|
||||
if (ret) {
|
||||
if (ret)
|
||||
BT_ERR("Fail to download calibrate data");
|
||||
of_node_put(dt_node);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int btmrvl_setup(struct hci_dev *hdev)
|
||||
|
|
|
@ -52,6 +52,68 @@ static struct memory_type_mapping mem_type_mapping_tbl[] = {
|
|||
{"EXTLAST", NULL, 0, 0xFE},
|
||||
};
|
||||
|
||||
static const struct of_device_id btmrvl_sdio_of_match_table[] = {
|
||||
{ .compatible = "marvell,sd8897-bt" },
|
||||
{ .compatible = "marvell,sd8997-bt" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static irqreturn_t btmrvl_wake_irq_bt(int irq, void *priv)
|
||||
{
|
||||
struct btmrvl_plt_wake_cfg *cfg = priv;
|
||||
|
||||
if (cfg->irq_bt >= 0) {
|
||||
pr_info("%s: wake by bt", __func__);
|
||||
cfg->wake_by_bt = true;
|
||||
disable_irq_nosync(irq);
|
||||
}
|
||||
|
||||
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)) {
|
||||
pr_err("sdio platform data not available");
|
||||
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");
|
||||
} else {
|
||||
ret = devm_request_irq(dev, cfg->irq_bt,
|
||||
btmrvl_wake_irq_bt,
|
||||
IRQF_TRIGGER_LOW,
|
||||
"bt_wake", cfg);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to request irq_bt %d (%d)\n",
|
||||
cfg->irq_bt, ret);
|
||||
}
|
||||
disable_irq(cfg->irq_bt);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1464,6 +1526,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!");
|
||||
|
@ -1544,6 +1609,13 @@ 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) {
|
||||
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;
|
||||
|
@ -1606,6 +1678,13 @@ static int btmrvl_sdio_resume(struct device *dev)
|
|||
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) {
|
||||
disable_irq_wake(card->plt_wake_cfg->irq_bt);
|
||||
if (!card->plt_wake_cfg->wake_by_bt)
|
||||
disable_irq(card->plt_wake_cfg->irq_bt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,10 @@
|
|||
|
||||
#define FIRMWARE_READY 0xfedc
|
||||
|
||||
struct btmrvl_plt_wake_cfg {
|
||||
int irq_bt;
|
||||
bool wake_by_bt;
|
||||
};
|
||||
|
||||
struct btmrvl_sdio_card_reg {
|
||||
u8 cfg;
|
||||
|
@ -97,6 +101,8 @@ struct btmrvl_sdio_card {
|
|||
u16 sd_blksz_fw_dl;
|
||||
u8 rx_unit;
|
||||
struct btmrvl_private *priv;
|
||||
struct device_node *plt_of_node;
|
||||
struct btmrvl_plt_wake_cfg *plt_wake_cfg;
|
||||
};
|
||||
|
||||
struct btmrvl_sdio_device {
|
||||
|
|
Loading…
Reference in a new issue