From d17e102e1d043393388d4a2cbe56280717623882 Mon Sep 17 00:00:00 2001 From: Sungjun Park Date: Wed, 15 Jun 2016 14:40:56 -0700 Subject: [PATCH] bluetooth: Add support for clocks On some platforms, clock needs to be controlled by Bluetooth driver. Add support for that by obtaining it through a device tree property and use it for voting when required. Change-Id: I42a16f6dc8d5c11963713ae489ca6d8c9bf927e5 Signed-off-by: Sungjun Park --- drivers/bluetooth/bluetooth-power.c | 129 ++++++++++++++++++++++++++++++++++-- include/linux/bluetooth-power.h | 11 +++ 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/drivers/bluetooth/bluetooth-power.c b/drivers/bluetooth/bluetooth-power.c index 16378a195843..1317ddaa3c23 100644 --- a/drivers/bluetooth/bluetooth-power.c +++ b/drivers/bluetooth/bluetooth-power.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "btfm_slim.h" #include @@ -169,6 +170,47 @@ static int bt_configure_vreg(struct bt_power_vreg_data *vreg) return rc; } +static int bt_clk_enable(struct bt_power_clk_data *clk) +{ + int rc = 0; + + BT_PWR_DBG("%s", clk->name); + + /* Get the clock handle for vreg */ + if (!clk->clk || clk->is_enabled) { + BT_PWR_ERR("error - node: %p, clk->is_enabled:%d", + clk->clk, clk->is_enabled); + return -EINVAL; + } + + rc = clk_prepare_enable(clk->clk); + if (rc) { + BT_PWR_ERR("failed to enable %s, rc(%d)\n", clk->name, rc); + return rc; + } + + clk->is_enabled = true; + return rc; +} + +static int bt_clk_disable(struct bt_power_clk_data *clk) +{ + int rc = 0; + + BT_PWR_DBG("%s", clk->name); + + /* Get the clock handle for vreg */ + if (!clk->clk || !clk->is_enabled) { + BT_PWR_ERR("error - node: %p, clk->is_enabled:%d", + clk->clk, clk->is_enabled); + return -EINVAL; + } + clk_disable_unprepare(clk->clk); + + clk->is_enabled = false; + return rc; +} + static int bt_configure_gpios(int on) { int rc = 0; @@ -252,6 +294,16 @@ static int bluetooth_power(int on) goto chip_pwd_fail; } } + /* Parse dt_info and check if a target requires clock voting. + * Enable BT clock when BT is on and disable it when BT is off + */ + if (bt_power_pdata->bt_chip_clk) { + rc = bt_clk_enable(bt_power_pdata->bt_chip_clk); + if (rc < 0) { + BT_PWR_ERR("bt_power gpio config failed"); + goto clk_fail; + } + } if (bt_power_pdata->bt_gpio_sys_rst > 0) { rc = bt_configure_gpios(on); if (rc < 0) { @@ -260,21 +312,31 @@ static int bluetooth_power(int on) } } } else { - bt_configure_gpios(on); + if (bt_power_pdata->bt_gpio_sys_rst > 0) + bt_configure_gpios(on); gpio_fail: if (bt_power_pdata->bt_gpio_sys_rst > 0) gpio_free(bt_power_pdata->bt_gpio_sys_rst); - bt_vreg_disable(bt_power_pdata->bt_chip_pwd); + if (bt_power_pdata->bt_chip_clk) + bt_clk_disable(bt_power_pdata->bt_chip_clk); +clk_fail: + if (bt_power_pdata->bt_chip_pwd) + bt_vreg_disable(bt_power_pdata->bt_chip_pwd); chip_pwd_fail: - bt_vreg_disable(bt_power_pdata->bt_vdd_ldo); + if (bt_power_pdata->bt_vdd_ldo) + bt_vreg_disable(bt_power_pdata->bt_vdd_ldo); vdd_ldo_fail: - bt_vreg_disable(bt_power_pdata->bt_vdd_pa); + if (bt_power_pdata->bt_vdd_pa) + bt_vreg_disable(bt_power_pdata->bt_vdd_pa); vdd_pa_fail: - bt_vreg_disable(bt_power_pdata->bt_vdd_core); + if (bt_power_pdata->bt_vdd_core) + bt_vreg_disable(bt_power_pdata->bt_vdd_core); vdd_core_fail: - bt_vreg_disable(bt_power_pdata->bt_vdd_xtal); + if (bt_power_pdata->bt_vdd_xtal) + bt_vreg_disable(bt_power_pdata->bt_vdd_xtal); vdd_xtal_fail: - bt_vreg_disable(bt_power_pdata->bt_vdd_io); + if (bt_power_pdata->bt_vdd_io) + bt_vreg_disable(bt_power_pdata->bt_vdd_io); } out: return rc; @@ -386,6 +448,7 @@ static int bt_dt_parse_vreg_info(struct device *dev, BT_PWR_DBG("vreg dev tree parse for %s", vreg_name); + *vreg_data = NULL; snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name); if (of_parse_phandle(np, prop_name, 0)) { vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); @@ -431,6 +494,53 @@ err: return ret; } +static int bt_dt_parse_clk_info(struct device *dev, + struct bt_power_clk_data **clk_data) +{ + int ret = -EINVAL; + struct bt_power_clk_data *clk = NULL; + struct device_node *np = dev->of_node; + + BT_PWR_DBG(""); + + *clk_data = NULL; + if (of_parse_phandle(np, "clocks", 0)) { + clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL); + if (!clk) { + BT_PWR_ERR("No memory for clocks"); + ret = -ENOMEM; + goto err; + } + + /* Allocated 20 bytes size buffer for clock name string */ + clk->name = devm_kzalloc(dev, 20, GFP_KERNEL); + + /* Parse clock name from node */ + ret = of_property_read_string_index(np, "clock-names", 0, + &(clk->name)); + if (ret < 0) { + BT_PWR_ERR("reading \"clock-names\" failed"); + return ret; + } + + clk->clk = devm_clk_get(dev, clk->name); + if (IS_ERR(clk->clk)) { + ret = PTR_ERR(clk->clk); + BT_PWR_ERR("failed to get %s, ret (%d)", + clk->name, ret); + clk->clk = NULL; + return ret; + } + + *clk_data = clk; + } else { + BT_PWR_ERR("clocks is not provided in device tree"); + } + +err: + return ret; +} + static int bt_power_populate_dt_pinfo(struct platform_device *pdev) { int rc; @@ -482,6 +592,11 @@ static int bt_power_populate_dt_pinfo(struct platform_device *pdev) "qca,bt-chip-pwd"); if (rc < 0) BT_PWR_ERR("bt-chip-pwd not provided in device tree"); + + rc = bt_dt_parse_clk_info(&pdev->dev, + &bt_power_pdata->bt_chip_clk); + if (rc < 0) + BT_PWR_ERR("clock not provided in device tree"); } bt_power_pdata->bt_power_setup = bluetooth_power; diff --git a/include/linux/bluetooth-power.h b/include/linux/bluetooth-power.h index d2dfc1477c9d..7be94d298b88 100644 --- a/include/linux/bluetooth-power.h +++ b/include/linux/bluetooth-power.h @@ -44,6 +44,15 @@ struct bt_power_vreg_data { bool is_enabled; }; +struct bt_power_clk_data { + /* clock regulator handle */ + struct clk *clk; + /* clock name */ + const char *name; + /* is this clock enabled? */ + bool is_enabled; +}; + /* * Platform data for the bluetooth power driver. */ @@ -67,6 +76,8 @@ struct bluetooth_power_platform_data { * shares a common gpio to bring chip out of reset. */ struct bt_power_vreg_data *bt_chip_pwd; + /* bluetooth reference clock */ + struct bt_power_clk_data *bt_chip_clk; /* Optional: Bluetooth power setup function */ int (*bt_power_setup)(int); }; -- 2.11.0