Loading Documentation/devicetree/bindings/bluetooth/bluetooth_power.txt 0 → 100644 +61 −0 Original line number Diff line number Diff line * Bluetooth Controller Bluetooth controller communicates with the Bluetooth Host using HCI Transport layer. HCI Transport layer can be based on UART or USB serial communication protocol. Required properties: - compatible: Should be set to one of the following: qca,ar3002 qca,qca6174 qca,wcn3990 - qca,bt-reset-gpio: GPIO pin to bring BT Controller out of reset Optional properties: - qca,bt-vdd-pa-supply: Bluetooth VDD PA regulator handle - qca,bt-vdd-io-supply: Bluetooth VDD IO regulator handle - qca,bt-vdd-ldo-supply: Bluetooth VDD LDO regulator handle. Kept under optional parameters as some of the chipsets doesn't require ldo or it may use from same vddio. - qca,bt-vdd-xtal-supply: Bluetooth VDD XTAL regulator handle - qca,bt-vdd-core-supply: Bluetooth VDD CORE regulator handle - qca,bt-chip-pwd-supply: Chip power down gpio is required when bluetooth module and other modules like wifi co-exist in a singe chip and shares a common gpio to bring chip out of reset. - qca,bt-vdd-pa-voltage-level: specifies VDD PA voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-io-voltage-level: specifies VDD IO voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-ldo-voltage-level: specifies VDD LDO voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-xtal-voltage-level: specifies VDD XTAL voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-core-voltage-level: specifies VDD CORE voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-io-current-level: specifies VDD IO current level in microamps - qca,bt-vdd-xtal-current-level: specifies VDD XTAL current level in microamps - qca,bt-vdd-core-current-level: specifies VDD CORE current level in microamps. - qca,bt-vdd-ldo-current-level: specifies VDD LDO current level in microamps. - qca,bt-vdd-pa-current-level: specifies VDD PA current level in microamps. - qca,bt-chip-pwd-current-level: specifies Chip Power current level in microamps. Example: bt-ar3002 { compatible = "qca,ar3002"; qca,bt-reset-gpio = <&pm8941_gpios 34 0>; qca,bt-vdd-io-supply = <&pm8941_s3>; qca,bt-vdd-pa-supply = <&pm8941_l19>; qca,bt-vdd-xtal-supply = <&pm8994_l30>; qca,bt-vdd-core-supply = <&pm8994_s3>; qca,bt-chip-pwd-supply = <&ath_chip_pwd_l>; qca,bt-vdd-io-voltage-level = <1800000 1800000>; qca,bt-vdd-pa-voltage-level = <2900000 2900000>; qca,bt-vdd-xtal-voltage-level = <1800000 1800000>; qca,bt-vdd-core-voltage-level = <1300000 1300000>; qca,bt-vdd-io-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-xtal-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-core-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-ldo-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-pa-current-level = <1>; /* LPM/PFM */ }; Documentation/devicetree/bindings/bluetooth/btfm_slim.txt 0 → 100644 +20 −0 Original line number Diff line number Diff line * BTFM Slimbus Slave Driver BTFM Slimbus Slave driver configure and initialize slimbus slave device. Bluetooth SCO and FM Audio data is transferred over slimbus interface. Required properties: - compatible: Should be set to one of the following: btfmslim_slave - qcom,btfm-slim-ifd: BTFM slimbus slave device entry name Optional properties: - qcom,btfm-slim-ifd-elemental-addr: BTFM slimbus slave device enumeration address Example: btfmslim_codec: wcn3990 { compatible = "qcom,btfmslim_slave"; elemental-addr = [00 01 20 02 17 02]; qcom,btfm-slim-ifd = "btfmslim_slave_ifd"; qcom,btfm-slim-ifd-elemental-addr = [00 00 20 02 17 02]; }; drivers/bluetooth/Kconfig +31 −0 Original line number Diff line number Diff line Loading @@ -377,4 +377,35 @@ config BT_QCOMSMD Say Y here to compile support for HCI over Qualcomm SMD into the kernel or say M to compile as a module. config MSM_BT_POWER bool "MSM Bluetooth Power Control" depends on ARCH_QCOM && RFKILL help MSM Bluetooth Power control driver. This provides a parameter to switch on/off power from PMIC to Bluetooth device. This will control LDOs/Clock/GPIOs to control Bluetooth Chipset based on power on/off sequence. config BTFM_SLIM bool "MSM Bluetooth/FM Slimbus Driver" select SLIMBUS default MSM_BT_POWER help This enables BT/FM slimbus driver to get multiple audio channel. This will make use of slimbus platform driver and slimbus codec driver to communicate with slimbus machine driver and LPSS which is Slimbus master. Slimbus slave initialization and configuration will be done through this driver. config BTFM_SLIM_WCN3990 bool "MSM Bluetooth/FM WCN3990 Device" default BTFM_SLIM depends on BTFM_SLIM help This enables specific driver handle for WCN3990 device. It is designed to adapt any future BT/FM device to implement a specific chip initialization process and control. endmenu drivers/bluetooth/Makefile +6 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,12 @@ obj-$(CONFIG_BT_QCA) += btqca.o obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o obj-$(CONFIG_MSM_BT_POWER) += bluetooth-power.o obj-$(CONFIG_BTFM_SLIM) += btfm_slim.o obj-$(CONFIG_BTFM_SLIM) += btfm_slim_codec.o obj-$(CONFIG_BTFM_SLIM_WCN3990) += btfm_slim_wcn3990.o btmrvl-y := btmrvl_main.o btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o Loading drivers/bluetooth/bluetooth-power.c 0 → 100644 +777 −0 Original line number Diff line number Diff line /* Copyright (c) 2009-2010, 2013-2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* * Bluetooth Power Switch Module * controls power to external Bluetooth device * with interface to power management device */ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/rfkill.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/delay.h> #include <linux/bluetooth-power.h> #include <linux/slab.h> #include <linux/regulator/consumer.h> #include <linux/clk.h> #if defined(CONFIG_CNSS) #include <net/cnss.h> #endif #include "btfm_slim.h" #include <linux/fs.h> #define BT_PWR_DBG(fmt, arg...) pr_debug("%s: " fmt "\n", __func__, ## arg) #define BT_PWR_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg) #define BT_PWR_ERR(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg) static const struct of_device_id bt_power_match_table[] = { { .compatible = "qca,ar3002" }, { .compatible = "qca,qca6174" }, { .compatible = "qca,wcn3990" }, {} }; static struct bluetooth_power_platform_data *bt_power_pdata; static struct platform_device *btpdev; static bool previous; static int pwr_state; struct class *bt_class; static int bt_major; static int bt_vreg_init(struct bt_power_vreg_data *vreg) { int rc = 0; struct device *dev = &btpdev->dev; BT_PWR_DBG("vreg_get for : %s", vreg->name); /* Get the regulator handle */ vreg->reg = regulator_get(dev, vreg->name); if (IS_ERR(vreg->reg)) { rc = PTR_ERR(vreg->reg); pr_err("%s: regulator_get(%s) failed. rc=%d\n", __func__, vreg->name, rc); goto out; } if ((regulator_count_voltages(vreg->reg) > 0) && (vreg->low_vol_level) && (vreg->high_vol_level)) vreg->set_voltage_sup = 1; out: return rc; } static int bt_vreg_enable(struct bt_power_vreg_data *vreg) { int rc = 0; BT_PWR_DBG("vreg_en for : %s", vreg->name); if (!vreg->is_enabled) { if (vreg->set_voltage_sup) { rc = regulator_set_voltage(vreg->reg, vreg->low_vol_level, vreg->high_vol_level); if (rc < 0) { BT_PWR_ERR("vreg_set_vol(%s) failed rc=%d\n", vreg->name, rc); goto out; } } if (vreg->load_uA >= 0) { rc = regulator_set_load(vreg->reg, vreg->load_uA); if (rc < 0) { BT_PWR_ERR("vreg_set_mode(%s) failed rc=%d\n", vreg->name, rc); goto out; } } rc = regulator_enable(vreg->reg); if (rc < 0) { BT_PWR_ERR("regulator_enable(%s) failed. rc=%d\n", vreg->name, rc); goto out; } vreg->is_enabled = true; } out: return rc; } static int bt_vreg_disable(struct bt_power_vreg_data *vreg) { int rc = 0; if (!vreg) return rc; BT_PWR_DBG("vreg_disable for : %s", vreg->name); if (vreg->is_enabled) { rc = regulator_disable(vreg->reg); if (rc < 0) { BT_PWR_ERR("regulator_disable(%s) failed. rc=%d\n", vreg->name, rc); goto out; } vreg->is_enabled = false; if (vreg->set_voltage_sup) { /* Set the min voltage to 0 */ rc = regulator_set_voltage(vreg->reg, 0, vreg->high_vol_level); if (rc < 0) { BT_PWR_ERR("vreg_set_vol(%s) failed rc=%d\n", vreg->name, rc); goto out; } } if (vreg->load_uA >= 0) { rc = regulator_set_load(vreg->reg, 0); if (rc < 0) { BT_PWR_ERR("vreg_set_mode(%s) failed rc=%d\n", vreg->name, rc); } } } out: return rc; } static int bt_configure_vreg(struct bt_power_vreg_data *vreg) { int rc = 0; BT_PWR_DBG("config %s", vreg->name); /* Get the regulator handle for vreg */ if (!(vreg->reg)) { rc = bt_vreg_init(vreg); if (rc < 0) return rc; } rc = bt_vreg_enable(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; int bt_reset_gpio = bt_power_pdata->bt_gpio_sys_rst; BT_PWR_DBG("bt_gpio= %d on: %d", bt_reset_gpio, on); if (on) { rc = gpio_request(bt_reset_gpio, "bt_sys_rst_n"); if (rc) { BT_PWR_ERR("unable to request gpio %d (%d)\n", bt_reset_gpio, rc); return rc; } rc = gpio_direction_output(bt_reset_gpio, 0); if (rc) { BT_PWR_ERR("Unable to set direction\n"); return rc; } msleep(50); rc = gpio_direction_output(bt_reset_gpio, 1); if (rc) { BT_PWR_ERR("Unable to set direction\n"); return rc; } msleep(50); } else { gpio_set_value(bt_reset_gpio, 0); msleep(100); } return rc; } static int bluetooth_power(int on) { int rc = 0; BT_PWR_DBG("on: %d", on); if (on) { if (bt_power_pdata->bt_vdd_io) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_io); if (rc < 0) { BT_PWR_ERR("bt_power vddio config failed"); goto out; } } if (bt_power_pdata->bt_vdd_xtal) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_xtal); if (rc < 0) { BT_PWR_ERR("bt_power vddxtal config failed"); goto vdd_xtal_fail; } } if (bt_power_pdata->bt_vdd_core) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_core); if (rc < 0) { BT_PWR_ERR("bt_power vddcore config failed"); goto vdd_core_fail; } } if (bt_power_pdata->bt_vdd_pa) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_pa); if (rc < 0) { BT_PWR_ERR("bt_power vddpa config failed"); goto vdd_pa_fail; } } if (bt_power_pdata->bt_vdd_ldo) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_ldo); if (rc < 0) { BT_PWR_ERR("bt_power vddldo config failed"); goto vdd_ldo_fail; } } if (bt_power_pdata->bt_chip_pwd) { rc = bt_configure_vreg(bt_power_pdata->bt_chip_pwd); if (rc < 0) { BT_PWR_ERR("bt_power chippwd config failed"); 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) { BT_PWR_ERR("bt_power gpio config failed"); goto gpio_fail; } } } else { 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); 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: if (bt_power_pdata->bt_vdd_ldo) bt_vreg_disable(bt_power_pdata->bt_vdd_ldo); vdd_ldo_fail: if (bt_power_pdata->bt_vdd_pa) bt_vreg_disable(bt_power_pdata->bt_vdd_pa); vdd_pa_fail: if (bt_power_pdata->bt_vdd_core) bt_vreg_disable(bt_power_pdata->bt_vdd_core); vdd_core_fail: if (bt_power_pdata->bt_vdd_xtal) bt_vreg_disable(bt_power_pdata->bt_vdd_xtal); vdd_xtal_fail: if (bt_power_pdata->bt_vdd_io) bt_vreg_disable(bt_power_pdata->bt_vdd_io); } out: return rc; } static int bluetooth_toggle_radio(void *data, bool blocked) { int ret = 0; int (*power_control)(int enable); power_control = ((struct bluetooth_power_platform_data *)data)->bt_power_setup; if (previous != blocked) ret = (*power_control)(!blocked); if (!ret) previous = blocked; return ret; } static const struct rfkill_ops bluetooth_power_rfkill_ops = { .set_block = bluetooth_toggle_radio, }; #if defined(CONFIG_CNSS) && defined(CONFIG_CLD_LL_CORE) static ssize_t enable_extldo(struct device *dev, struct device_attribute *attr, char *buf) { int ret; bool enable = false; struct cnss_platform_cap cap; ret = cnss_get_platform_cap(&cap); if (ret) { BT_PWR_ERR("Platform capability info from CNSS not available!"); enable = false; } else if (!ret && (cap.cap_flag & CNSS_HAS_EXTERNAL_SWREG)) { enable = true; } return snprintf(buf, 6, "%s", (enable ? "true" : "false")); } #else static ssize_t enable_extldo(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, 6, "%s", "false"); } #endif static DEVICE_ATTR(extldo, 0444, enable_extldo, NULL); static int bluetooth_power_rfkill_probe(struct platform_device *pdev) { struct rfkill *rfkill; int ret; rfkill = rfkill_alloc("bt_power", &pdev->dev, RFKILL_TYPE_BLUETOOTH, &bluetooth_power_rfkill_ops, pdev->dev.platform_data); if (!rfkill) { dev_err(&pdev->dev, "rfkill allocate failed\n"); return -ENOMEM; } /* add file into rfkill0 to handle LDO27 */ ret = device_create_file(&pdev->dev, &dev_attr_extldo); if (ret < 0) BT_PWR_ERR("device create file error!"); /* force Bluetooth off during init to allow for user control */ rfkill_init_sw_state(rfkill, 1); previous = 1; ret = rfkill_register(rfkill); if (ret) { dev_err(&pdev->dev, "rfkill register failed=%d\n", ret); rfkill_destroy(rfkill); return ret; } platform_set_drvdata(pdev, rfkill); return 0; } static void bluetooth_power_rfkill_remove(struct platform_device *pdev) { struct rfkill *rfkill; dev_dbg(&pdev->dev, "%s\n", __func__); rfkill = platform_get_drvdata(pdev); if (rfkill) rfkill_unregister(rfkill); rfkill_destroy(rfkill); platform_set_drvdata(pdev, NULL); } #define MAX_PROP_SIZE 32 static int bt_dt_parse_vreg_info(struct device *dev, struct bt_power_vreg_data **vreg_data, const char *vreg_name) { int len, ret = 0; const __be32 *prop; char prop_name[MAX_PROP_SIZE]; struct bt_power_vreg_data *vreg; struct device_node *np = dev->of_node; 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); if (!vreg) { BT_PWR_ERR("No memory for vreg: %s", vreg_name); ret = -ENOMEM; goto err; } vreg->name = vreg_name; /* Parse voltage-level from each node */ snprintf(prop_name, MAX_PROP_SIZE, "%s-voltage-level", vreg_name); prop = of_get_property(np, prop_name, &len); if (!prop || (len != (2 * sizeof(__be32)))) { dev_warn(dev, "%s %s property\n", prop ? "invalid format" : "no", prop_name); } else { vreg->low_vol_level = be32_to_cpup(&prop[0]); vreg->high_vol_level = be32_to_cpup(&prop[1]); } /* Parse current-level from each node */ snprintf(prop_name, MAX_PROP_SIZE, "%s-current-level", vreg_name); ret = of_property_read_u32(np, prop_name, &vreg->load_uA); if (ret < 0) { BT_PWR_DBG("%s property is not valid\n", prop_name); vreg->load_uA = -1; ret = 0; } *vreg_data = vreg; BT_PWR_DBG("%s: vol=[%d %d]uV, current=[%d]uA\n", vreg->name, vreg->low_vol_level, vreg->high_vol_level, vreg->load_uA); } else BT_PWR_INFO("%s: is not provided in device tree", vreg_name); 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; BT_PWR_DBG(""); if (!bt_power_pdata) return -ENOMEM; if (pdev->dev.of_node) { bt_power_pdata->bt_gpio_sys_rst = of_get_named_gpio(pdev->dev.of_node, "qca,bt-reset-gpio", 0); if (bt_power_pdata->bt_gpio_sys_rst < 0) BT_PWR_ERR("bt-reset-gpio not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_core, "qca,bt-vdd-core"); if (rc < 0) BT_PWR_ERR("bt-vdd-core not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_io, "qca,bt-vdd-io"); if (rc < 0) BT_PWR_ERR("bt-vdd-io not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_xtal, "qca,bt-vdd-xtal"); if (rc < 0) BT_PWR_ERR("bt-vdd-xtal not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_pa, "qca,bt-vdd-pa"); if (rc < 0) BT_PWR_ERR("bt-vdd-pa not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_ldo, "qca,bt-vdd-ldo"); if (rc < 0) BT_PWR_ERR("bt-vdd-ldo not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_chip_pwd, "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; return 0; } static int bt_power_probe(struct platform_device *pdev) { int ret = 0; dev_dbg(&pdev->dev, "%s\n", __func__); bt_power_pdata = kzalloc(sizeof(struct bluetooth_power_platform_data), GFP_KERNEL); if (!bt_power_pdata) { BT_PWR_ERR("Failed to allocate memory"); return -ENOMEM; } if (pdev->dev.of_node) { ret = bt_power_populate_dt_pinfo(pdev); if (ret < 0) { BT_PWR_ERR("Failed to populate device tree info"); goto free_pdata; } pdev->dev.platform_data = bt_power_pdata; } else if (pdev->dev.platform_data) { /* Optional data set to default if not provided */ if (!((struct bluetooth_power_platform_data *) (pdev->dev.platform_data))->bt_power_setup) ((struct bluetooth_power_platform_data *) (pdev->dev.platform_data))->bt_power_setup = bluetooth_power; memcpy(bt_power_pdata, pdev->dev.platform_data, sizeof(struct bluetooth_power_platform_data)); pwr_state = 0; } else { BT_PWR_ERR("Failed to get platform data"); goto free_pdata; } if (bluetooth_power_rfkill_probe(pdev) < 0) goto free_pdata; btpdev = pdev; return 0; free_pdata: kfree(bt_power_pdata); return ret; } static int bt_power_remove(struct platform_device *pdev) { dev_dbg(&pdev->dev, "%s\n", __func__); bluetooth_power_rfkill_remove(pdev); if (bt_power_pdata->bt_chip_pwd->reg) regulator_put(bt_power_pdata->bt_chip_pwd->reg); kfree(bt_power_pdata); return 0; } int bt_register_slimdev(struct device *dev) { BT_PWR_DBG(""); if (!bt_power_pdata || (dev == NULL)) { BT_PWR_ERR("Failed to allocate memory"); return -EINVAL; } bt_power_pdata->slim_dev = dev; return 0; } static long bt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0, pwr_cntrl = 0; switch (cmd) { case BT_CMD_SLIM_TEST: if (!bt_power_pdata->slim_dev) { BT_PWR_ERR("slim_dev is null\n"); return -EINVAL; } ret = btfm_slim_hw_init( bt_power_pdata->slim_dev->platform_data ); break; case BT_CMD_PWR_CTRL: pwr_cntrl = (int)arg; BT_PWR_ERR("BT_CMD_PWR_CTRL pwr_cntrl:%d", pwr_cntrl); if (pwr_state != pwr_cntrl) { ret = bluetooth_power(pwr_cntrl); if (!ret) pwr_state = pwr_cntrl; } else { BT_PWR_ERR("BT chip state is already :%d no change d\n" , pwr_state); ret = 0; } break; default: return -EINVAL; } return ret; } static struct platform_driver bt_power_driver = { .probe = bt_power_probe, .remove = bt_power_remove, .driver = { .name = "bt_power", .owner = THIS_MODULE, .of_match_table = bt_power_match_table, }, }; static const struct file_operations bt_dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl = bt_ioctl, .compat_ioctl = bt_ioctl, }; static int __init bluetooth_power_init(void) { int ret; ret = platform_driver_register(&bt_power_driver); bt_major = register_chrdev(0, "bt", &bt_dev_fops); if (bt_major < 0) { BTFMSLIM_ERR("failed to allocate char dev\n"); goto chrdev_unreg; } bt_class = class_create(THIS_MODULE, "bt-dev"); if (IS_ERR(bt_class)) { BTFMSLIM_ERR("coudn't create class"); goto chrdev_unreg; } if (device_create(bt_class, NULL, MKDEV(bt_major, 0), NULL, "btpower") == NULL) { BTFMSLIM_ERR("failed to allocate char dev\n"); goto chrdev_unreg; } return 0; chrdev_unreg: unregister_chrdev(bt_major, "bt"); class_destroy(bt_class); return ret; } static void __exit bluetooth_power_exit(void) { platform_driver_unregister(&bt_power_driver); } MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MSM Bluetooth power control driver"); module_init(bluetooth_power_init); module_exit(bluetooth_power_exit); Loading
Documentation/devicetree/bindings/bluetooth/bluetooth_power.txt 0 → 100644 +61 −0 Original line number Diff line number Diff line * Bluetooth Controller Bluetooth controller communicates with the Bluetooth Host using HCI Transport layer. HCI Transport layer can be based on UART or USB serial communication protocol. Required properties: - compatible: Should be set to one of the following: qca,ar3002 qca,qca6174 qca,wcn3990 - qca,bt-reset-gpio: GPIO pin to bring BT Controller out of reset Optional properties: - qca,bt-vdd-pa-supply: Bluetooth VDD PA regulator handle - qca,bt-vdd-io-supply: Bluetooth VDD IO regulator handle - qca,bt-vdd-ldo-supply: Bluetooth VDD LDO regulator handle. Kept under optional parameters as some of the chipsets doesn't require ldo or it may use from same vddio. - qca,bt-vdd-xtal-supply: Bluetooth VDD XTAL regulator handle - qca,bt-vdd-core-supply: Bluetooth VDD CORE regulator handle - qca,bt-chip-pwd-supply: Chip power down gpio is required when bluetooth module and other modules like wifi co-exist in a singe chip and shares a common gpio to bring chip out of reset. - qca,bt-vdd-pa-voltage-level: specifies VDD PA voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-io-voltage-level: specifies VDD IO voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-ldo-voltage-level: specifies VDD LDO voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-xtal-voltage-level: specifies VDD XTAL voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-core-voltage-level: specifies VDD CORE voltage levels for supply. Should be specified in pairs (min, max), units uV - qca,bt-vdd-io-current-level: specifies VDD IO current level in microamps - qca,bt-vdd-xtal-current-level: specifies VDD XTAL current level in microamps - qca,bt-vdd-core-current-level: specifies VDD CORE current level in microamps. - qca,bt-vdd-ldo-current-level: specifies VDD LDO current level in microamps. - qca,bt-vdd-pa-current-level: specifies VDD PA current level in microamps. - qca,bt-chip-pwd-current-level: specifies Chip Power current level in microamps. Example: bt-ar3002 { compatible = "qca,ar3002"; qca,bt-reset-gpio = <&pm8941_gpios 34 0>; qca,bt-vdd-io-supply = <&pm8941_s3>; qca,bt-vdd-pa-supply = <&pm8941_l19>; qca,bt-vdd-xtal-supply = <&pm8994_l30>; qca,bt-vdd-core-supply = <&pm8994_s3>; qca,bt-chip-pwd-supply = <&ath_chip_pwd_l>; qca,bt-vdd-io-voltage-level = <1800000 1800000>; qca,bt-vdd-pa-voltage-level = <2900000 2900000>; qca,bt-vdd-xtal-voltage-level = <1800000 1800000>; qca,bt-vdd-core-voltage-level = <1300000 1300000>; qca,bt-vdd-io-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-xtal-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-core-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-ldo-current-level = <1>; /* LPM/PFM */ qca,bt-vdd-pa-current-level = <1>; /* LPM/PFM */ };
Documentation/devicetree/bindings/bluetooth/btfm_slim.txt 0 → 100644 +20 −0 Original line number Diff line number Diff line * BTFM Slimbus Slave Driver BTFM Slimbus Slave driver configure and initialize slimbus slave device. Bluetooth SCO and FM Audio data is transferred over slimbus interface. Required properties: - compatible: Should be set to one of the following: btfmslim_slave - qcom,btfm-slim-ifd: BTFM slimbus slave device entry name Optional properties: - qcom,btfm-slim-ifd-elemental-addr: BTFM slimbus slave device enumeration address Example: btfmslim_codec: wcn3990 { compatible = "qcom,btfmslim_slave"; elemental-addr = [00 01 20 02 17 02]; qcom,btfm-slim-ifd = "btfmslim_slave_ifd"; qcom,btfm-slim-ifd-elemental-addr = [00 00 20 02 17 02]; };
drivers/bluetooth/Kconfig +31 −0 Original line number Diff line number Diff line Loading @@ -377,4 +377,35 @@ config BT_QCOMSMD Say Y here to compile support for HCI over Qualcomm SMD into the kernel or say M to compile as a module. config MSM_BT_POWER bool "MSM Bluetooth Power Control" depends on ARCH_QCOM && RFKILL help MSM Bluetooth Power control driver. This provides a parameter to switch on/off power from PMIC to Bluetooth device. This will control LDOs/Clock/GPIOs to control Bluetooth Chipset based on power on/off sequence. config BTFM_SLIM bool "MSM Bluetooth/FM Slimbus Driver" select SLIMBUS default MSM_BT_POWER help This enables BT/FM slimbus driver to get multiple audio channel. This will make use of slimbus platform driver and slimbus codec driver to communicate with slimbus machine driver and LPSS which is Slimbus master. Slimbus slave initialization and configuration will be done through this driver. config BTFM_SLIM_WCN3990 bool "MSM Bluetooth/FM WCN3990 Device" default BTFM_SLIM depends on BTFM_SLIM help This enables specific driver handle for WCN3990 device. It is designed to adapt any future BT/FM device to implement a specific chip initialization process and control. endmenu
drivers/bluetooth/Makefile +6 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,12 @@ obj-$(CONFIG_BT_QCA) += btqca.o obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o obj-$(CONFIG_MSM_BT_POWER) += bluetooth-power.o obj-$(CONFIG_BTFM_SLIM) += btfm_slim.o obj-$(CONFIG_BTFM_SLIM) += btfm_slim_codec.o obj-$(CONFIG_BTFM_SLIM_WCN3990) += btfm_slim_wcn3990.o btmrvl-y := btmrvl_main.o btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o Loading
drivers/bluetooth/bluetooth-power.c 0 → 100644 +777 −0 Original line number Diff line number Diff line /* Copyright (c) 2009-2010, 2013-2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* * Bluetooth Power Switch Module * controls power to external Bluetooth device * with interface to power management device */ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/rfkill.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/delay.h> #include <linux/bluetooth-power.h> #include <linux/slab.h> #include <linux/regulator/consumer.h> #include <linux/clk.h> #if defined(CONFIG_CNSS) #include <net/cnss.h> #endif #include "btfm_slim.h" #include <linux/fs.h> #define BT_PWR_DBG(fmt, arg...) pr_debug("%s: " fmt "\n", __func__, ## arg) #define BT_PWR_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg) #define BT_PWR_ERR(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg) static const struct of_device_id bt_power_match_table[] = { { .compatible = "qca,ar3002" }, { .compatible = "qca,qca6174" }, { .compatible = "qca,wcn3990" }, {} }; static struct bluetooth_power_platform_data *bt_power_pdata; static struct platform_device *btpdev; static bool previous; static int pwr_state; struct class *bt_class; static int bt_major; static int bt_vreg_init(struct bt_power_vreg_data *vreg) { int rc = 0; struct device *dev = &btpdev->dev; BT_PWR_DBG("vreg_get for : %s", vreg->name); /* Get the regulator handle */ vreg->reg = regulator_get(dev, vreg->name); if (IS_ERR(vreg->reg)) { rc = PTR_ERR(vreg->reg); pr_err("%s: regulator_get(%s) failed. rc=%d\n", __func__, vreg->name, rc); goto out; } if ((regulator_count_voltages(vreg->reg) > 0) && (vreg->low_vol_level) && (vreg->high_vol_level)) vreg->set_voltage_sup = 1; out: return rc; } static int bt_vreg_enable(struct bt_power_vreg_data *vreg) { int rc = 0; BT_PWR_DBG("vreg_en for : %s", vreg->name); if (!vreg->is_enabled) { if (vreg->set_voltage_sup) { rc = regulator_set_voltage(vreg->reg, vreg->low_vol_level, vreg->high_vol_level); if (rc < 0) { BT_PWR_ERR("vreg_set_vol(%s) failed rc=%d\n", vreg->name, rc); goto out; } } if (vreg->load_uA >= 0) { rc = regulator_set_load(vreg->reg, vreg->load_uA); if (rc < 0) { BT_PWR_ERR("vreg_set_mode(%s) failed rc=%d\n", vreg->name, rc); goto out; } } rc = regulator_enable(vreg->reg); if (rc < 0) { BT_PWR_ERR("regulator_enable(%s) failed. rc=%d\n", vreg->name, rc); goto out; } vreg->is_enabled = true; } out: return rc; } static int bt_vreg_disable(struct bt_power_vreg_data *vreg) { int rc = 0; if (!vreg) return rc; BT_PWR_DBG("vreg_disable for : %s", vreg->name); if (vreg->is_enabled) { rc = regulator_disable(vreg->reg); if (rc < 0) { BT_PWR_ERR("regulator_disable(%s) failed. rc=%d\n", vreg->name, rc); goto out; } vreg->is_enabled = false; if (vreg->set_voltage_sup) { /* Set the min voltage to 0 */ rc = regulator_set_voltage(vreg->reg, 0, vreg->high_vol_level); if (rc < 0) { BT_PWR_ERR("vreg_set_vol(%s) failed rc=%d\n", vreg->name, rc); goto out; } } if (vreg->load_uA >= 0) { rc = regulator_set_load(vreg->reg, 0); if (rc < 0) { BT_PWR_ERR("vreg_set_mode(%s) failed rc=%d\n", vreg->name, rc); } } } out: return rc; } static int bt_configure_vreg(struct bt_power_vreg_data *vreg) { int rc = 0; BT_PWR_DBG("config %s", vreg->name); /* Get the regulator handle for vreg */ if (!(vreg->reg)) { rc = bt_vreg_init(vreg); if (rc < 0) return rc; } rc = bt_vreg_enable(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; int bt_reset_gpio = bt_power_pdata->bt_gpio_sys_rst; BT_PWR_DBG("bt_gpio= %d on: %d", bt_reset_gpio, on); if (on) { rc = gpio_request(bt_reset_gpio, "bt_sys_rst_n"); if (rc) { BT_PWR_ERR("unable to request gpio %d (%d)\n", bt_reset_gpio, rc); return rc; } rc = gpio_direction_output(bt_reset_gpio, 0); if (rc) { BT_PWR_ERR("Unable to set direction\n"); return rc; } msleep(50); rc = gpio_direction_output(bt_reset_gpio, 1); if (rc) { BT_PWR_ERR("Unable to set direction\n"); return rc; } msleep(50); } else { gpio_set_value(bt_reset_gpio, 0); msleep(100); } return rc; } static int bluetooth_power(int on) { int rc = 0; BT_PWR_DBG("on: %d", on); if (on) { if (bt_power_pdata->bt_vdd_io) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_io); if (rc < 0) { BT_PWR_ERR("bt_power vddio config failed"); goto out; } } if (bt_power_pdata->bt_vdd_xtal) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_xtal); if (rc < 0) { BT_PWR_ERR("bt_power vddxtal config failed"); goto vdd_xtal_fail; } } if (bt_power_pdata->bt_vdd_core) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_core); if (rc < 0) { BT_PWR_ERR("bt_power vddcore config failed"); goto vdd_core_fail; } } if (bt_power_pdata->bt_vdd_pa) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_pa); if (rc < 0) { BT_PWR_ERR("bt_power vddpa config failed"); goto vdd_pa_fail; } } if (bt_power_pdata->bt_vdd_ldo) { rc = bt_configure_vreg(bt_power_pdata->bt_vdd_ldo); if (rc < 0) { BT_PWR_ERR("bt_power vddldo config failed"); goto vdd_ldo_fail; } } if (bt_power_pdata->bt_chip_pwd) { rc = bt_configure_vreg(bt_power_pdata->bt_chip_pwd); if (rc < 0) { BT_PWR_ERR("bt_power chippwd config failed"); 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) { BT_PWR_ERR("bt_power gpio config failed"); goto gpio_fail; } } } else { 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); 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: if (bt_power_pdata->bt_vdd_ldo) bt_vreg_disable(bt_power_pdata->bt_vdd_ldo); vdd_ldo_fail: if (bt_power_pdata->bt_vdd_pa) bt_vreg_disable(bt_power_pdata->bt_vdd_pa); vdd_pa_fail: if (bt_power_pdata->bt_vdd_core) bt_vreg_disable(bt_power_pdata->bt_vdd_core); vdd_core_fail: if (bt_power_pdata->bt_vdd_xtal) bt_vreg_disable(bt_power_pdata->bt_vdd_xtal); vdd_xtal_fail: if (bt_power_pdata->bt_vdd_io) bt_vreg_disable(bt_power_pdata->bt_vdd_io); } out: return rc; } static int bluetooth_toggle_radio(void *data, bool blocked) { int ret = 0; int (*power_control)(int enable); power_control = ((struct bluetooth_power_platform_data *)data)->bt_power_setup; if (previous != blocked) ret = (*power_control)(!blocked); if (!ret) previous = blocked; return ret; } static const struct rfkill_ops bluetooth_power_rfkill_ops = { .set_block = bluetooth_toggle_radio, }; #if defined(CONFIG_CNSS) && defined(CONFIG_CLD_LL_CORE) static ssize_t enable_extldo(struct device *dev, struct device_attribute *attr, char *buf) { int ret; bool enable = false; struct cnss_platform_cap cap; ret = cnss_get_platform_cap(&cap); if (ret) { BT_PWR_ERR("Platform capability info from CNSS not available!"); enable = false; } else if (!ret && (cap.cap_flag & CNSS_HAS_EXTERNAL_SWREG)) { enable = true; } return snprintf(buf, 6, "%s", (enable ? "true" : "false")); } #else static ssize_t enable_extldo(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, 6, "%s", "false"); } #endif static DEVICE_ATTR(extldo, 0444, enable_extldo, NULL); static int bluetooth_power_rfkill_probe(struct platform_device *pdev) { struct rfkill *rfkill; int ret; rfkill = rfkill_alloc("bt_power", &pdev->dev, RFKILL_TYPE_BLUETOOTH, &bluetooth_power_rfkill_ops, pdev->dev.platform_data); if (!rfkill) { dev_err(&pdev->dev, "rfkill allocate failed\n"); return -ENOMEM; } /* add file into rfkill0 to handle LDO27 */ ret = device_create_file(&pdev->dev, &dev_attr_extldo); if (ret < 0) BT_PWR_ERR("device create file error!"); /* force Bluetooth off during init to allow for user control */ rfkill_init_sw_state(rfkill, 1); previous = 1; ret = rfkill_register(rfkill); if (ret) { dev_err(&pdev->dev, "rfkill register failed=%d\n", ret); rfkill_destroy(rfkill); return ret; } platform_set_drvdata(pdev, rfkill); return 0; } static void bluetooth_power_rfkill_remove(struct platform_device *pdev) { struct rfkill *rfkill; dev_dbg(&pdev->dev, "%s\n", __func__); rfkill = platform_get_drvdata(pdev); if (rfkill) rfkill_unregister(rfkill); rfkill_destroy(rfkill); platform_set_drvdata(pdev, NULL); } #define MAX_PROP_SIZE 32 static int bt_dt_parse_vreg_info(struct device *dev, struct bt_power_vreg_data **vreg_data, const char *vreg_name) { int len, ret = 0; const __be32 *prop; char prop_name[MAX_PROP_SIZE]; struct bt_power_vreg_data *vreg; struct device_node *np = dev->of_node; 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); if (!vreg) { BT_PWR_ERR("No memory for vreg: %s", vreg_name); ret = -ENOMEM; goto err; } vreg->name = vreg_name; /* Parse voltage-level from each node */ snprintf(prop_name, MAX_PROP_SIZE, "%s-voltage-level", vreg_name); prop = of_get_property(np, prop_name, &len); if (!prop || (len != (2 * sizeof(__be32)))) { dev_warn(dev, "%s %s property\n", prop ? "invalid format" : "no", prop_name); } else { vreg->low_vol_level = be32_to_cpup(&prop[0]); vreg->high_vol_level = be32_to_cpup(&prop[1]); } /* Parse current-level from each node */ snprintf(prop_name, MAX_PROP_SIZE, "%s-current-level", vreg_name); ret = of_property_read_u32(np, prop_name, &vreg->load_uA); if (ret < 0) { BT_PWR_DBG("%s property is not valid\n", prop_name); vreg->load_uA = -1; ret = 0; } *vreg_data = vreg; BT_PWR_DBG("%s: vol=[%d %d]uV, current=[%d]uA\n", vreg->name, vreg->low_vol_level, vreg->high_vol_level, vreg->load_uA); } else BT_PWR_INFO("%s: is not provided in device tree", vreg_name); 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; BT_PWR_DBG(""); if (!bt_power_pdata) return -ENOMEM; if (pdev->dev.of_node) { bt_power_pdata->bt_gpio_sys_rst = of_get_named_gpio(pdev->dev.of_node, "qca,bt-reset-gpio", 0); if (bt_power_pdata->bt_gpio_sys_rst < 0) BT_PWR_ERR("bt-reset-gpio not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_core, "qca,bt-vdd-core"); if (rc < 0) BT_PWR_ERR("bt-vdd-core not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_io, "qca,bt-vdd-io"); if (rc < 0) BT_PWR_ERR("bt-vdd-io not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_xtal, "qca,bt-vdd-xtal"); if (rc < 0) BT_PWR_ERR("bt-vdd-xtal not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_pa, "qca,bt-vdd-pa"); if (rc < 0) BT_PWR_ERR("bt-vdd-pa not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_vdd_ldo, "qca,bt-vdd-ldo"); if (rc < 0) BT_PWR_ERR("bt-vdd-ldo not provided in device tree"); rc = bt_dt_parse_vreg_info(&pdev->dev, &bt_power_pdata->bt_chip_pwd, "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; return 0; } static int bt_power_probe(struct platform_device *pdev) { int ret = 0; dev_dbg(&pdev->dev, "%s\n", __func__); bt_power_pdata = kzalloc(sizeof(struct bluetooth_power_platform_data), GFP_KERNEL); if (!bt_power_pdata) { BT_PWR_ERR("Failed to allocate memory"); return -ENOMEM; } if (pdev->dev.of_node) { ret = bt_power_populate_dt_pinfo(pdev); if (ret < 0) { BT_PWR_ERR("Failed to populate device tree info"); goto free_pdata; } pdev->dev.platform_data = bt_power_pdata; } else if (pdev->dev.platform_data) { /* Optional data set to default if not provided */ if (!((struct bluetooth_power_platform_data *) (pdev->dev.platform_data))->bt_power_setup) ((struct bluetooth_power_platform_data *) (pdev->dev.platform_data))->bt_power_setup = bluetooth_power; memcpy(bt_power_pdata, pdev->dev.platform_data, sizeof(struct bluetooth_power_platform_data)); pwr_state = 0; } else { BT_PWR_ERR("Failed to get platform data"); goto free_pdata; } if (bluetooth_power_rfkill_probe(pdev) < 0) goto free_pdata; btpdev = pdev; return 0; free_pdata: kfree(bt_power_pdata); return ret; } static int bt_power_remove(struct platform_device *pdev) { dev_dbg(&pdev->dev, "%s\n", __func__); bluetooth_power_rfkill_remove(pdev); if (bt_power_pdata->bt_chip_pwd->reg) regulator_put(bt_power_pdata->bt_chip_pwd->reg); kfree(bt_power_pdata); return 0; } int bt_register_slimdev(struct device *dev) { BT_PWR_DBG(""); if (!bt_power_pdata || (dev == NULL)) { BT_PWR_ERR("Failed to allocate memory"); return -EINVAL; } bt_power_pdata->slim_dev = dev; return 0; } static long bt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0, pwr_cntrl = 0; switch (cmd) { case BT_CMD_SLIM_TEST: if (!bt_power_pdata->slim_dev) { BT_PWR_ERR("slim_dev is null\n"); return -EINVAL; } ret = btfm_slim_hw_init( bt_power_pdata->slim_dev->platform_data ); break; case BT_CMD_PWR_CTRL: pwr_cntrl = (int)arg; BT_PWR_ERR("BT_CMD_PWR_CTRL pwr_cntrl:%d", pwr_cntrl); if (pwr_state != pwr_cntrl) { ret = bluetooth_power(pwr_cntrl); if (!ret) pwr_state = pwr_cntrl; } else { BT_PWR_ERR("BT chip state is already :%d no change d\n" , pwr_state); ret = 0; } break; default: return -EINVAL; } return ret; } static struct platform_driver bt_power_driver = { .probe = bt_power_probe, .remove = bt_power_remove, .driver = { .name = "bt_power", .owner = THIS_MODULE, .of_match_table = bt_power_match_table, }, }; static const struct file_operations bt_dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl = bt_ioctl, .compat_ioctl = bt_ioctl, }; static int __init bluetooth_power_init(void) { int ret; ret = platform_driver_register(&bt_power_driver); bt_major = register_chrdev(0, "bt", &bt_dev_fops); if (bt_major < 0) { BTFMSLIM_ERR("failed to allocate char dev\n"); goto chrdev_unreg; } bt_class = class_create(THIS_MODULE, "bt-dev"); if (IS_ERR(bt_class)) { BTFMSLIM_ERR("coudn't create class"); goto chrdev_unreg; } if (device_create(bt_class, NULL, MKDEV(bt_major, 0), NULL, "btpower") == NULL) { BTFMSLIM_ERR("failed to allocate char dev\n"); goto chrdev_unreg; } return 0; chrdev_unreg: unregister_chrdev(bt_major, "bt"); class_destroy(bt_class); return ret; } static void __exit bluetooth_power_exit(void) { platform_driver_unregister(&bt_power_driver); } MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MSM Bluetooth power control driver"); module_init(bluetooth_power_init); module_exit(bluetooth_power_exit);