OSDN Git Service

bluetooth: Add support for clocks
authorSungjun Park <sjpark@codeaurora.org>
Wed, 15 Jun 2016 21:40:56 +0000 (14:40 -0700)
committerKyle Yan <kyan@codeaurora.org>
Tue, 5 Jul 2016 22:28:53 +0000 (15:28 -0700)
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 <sjpark@codeaurora.org>
drivers/bluetooth/bluetooth-power.c
include/linux/bluetooth-power.h

index 16378a1..1317dda 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/bluetooth-power.h>
 #include <linux/slab.h>
 #include <linux/regulator/consumer.h>
+#include <linux/clk.h>
 #include <net/cnss.h>
 #include "btfm_slim.h"
 #include <linux/fs.h>
@@ -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;
index d2dfc14..7be94d2 100644 (file)
@@ -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);
 };