diff options
Diffstat (limited to 'drivers/bluetooth')
| -rw-r--r-- | drivers/bluetooth/Kconfig | 28 | ||||
| -rw-r--r-- | drivers/bluetooth/Makefile | 5 | ||||
| -rw-r--r-- | drivers/bluetooth/ath3k.c | 18 | ||||
| -rw-r--r-- | drivers/bluetooth/bluetooth-power.c | 773 | ||||
| -rw-r--r-- | drivers/bluetooth/btfm_slim.c | 565 | ||||
| -rw-r--r-- | drivers/bluetooth/btfm_slim.h | 165 | ||||
| -rw-r--r-- | drivers/bluetooth/btfm_slim_codec.c | 434 | ||||
| -rw-r--r-- | drivers/bluetooth/btfm_slim_wcn3990.c | 176 | ||||
| -rw-r--r-- | drivers/bluetooth/btfm_slim_wcn3990.h | 141 | ||||
| -rw-r--r-- | drivers/bluetooth/btqca.c | 58 | ||||
| -rw-r--r-- | drivers/bluetooth/hci_h4.c | 8 | ||||
| -rw-r--r-- | drivers/bluetooth/hci_ldisc.c | 47 | ||||
| -rw-r--r-- | drivers/bluetooth/hci_qca.c | 19 | ||||
| -rw-r--r-- | drivers/bluetooth/hci_uart.h | 1 |
14 files changed, 2411 insertions, 27 deletions
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index ec6af1595062..57b60eeb11c6 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -320,4 +320,32 @@ config BT_WILINK Say Y here to compile support for Texas Instrument's WiLink7 driver into the kernel or say M to compile it as module (btwilink). +config MSM_BT_POWER + tristate "MSM Bluetooth Power Control" + depends on ARCH_QCOM && RFKILL + default m + help + Provides a parameter to switch on/off power from PMIC + to Bluetooth device. + +config BTFM_SLIM + tristate "MSM Bluetooth/FM Slimbus Driver" + depends on 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 + tristate "MSM Bluetooth/FM WCN3990 Device" + 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 diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 07c9cf381e5a..1151d0d17bf6 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -23,6 +23,11 @@ obj-$(CONFIG_BT_WILINK) += btwilink.o obj-$(CONFIG_BT_BCM) += btbcm.o obj-$(CONFIG_BT_RTL) += btrtl.o obj-$(CONFIG_BT_QCA) += btqca.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 diff --git a/drivers/bluetooth/ath3k.c b/drivers/bluetooth/ath3k.c index 5df8e1234505..071c94457d34 100644 --- a/drivers/bluetooth/ath3k.c +++ b/drivers/bluetooth/ath3k.c @@ -209,13 +209,27 @@ static int ath3k_load_firmware(struct usb_device *udev, { u8 *send_buf; int err, pipe, len, size, sent = 0; - int count = firmware->size; + int count; BT_DBG("udev %p", udev); + if (!firmware || !firmware->data || firmware->size <= 0) { + err = -EINVAL; + BT_ERR("Not a valid FW file"); + return err; + } + + count = firmware->size; + + if (count < FW_HDR_SIZE) { + err = -EINVAL; + BT_ERR("ath3k loading invalid size of file"); + return err; + } + pipe = usb_sndctrlpipe(udev, 0); - send_buf = kmalloc(BULK_SIZE, GFP_KERNEL); + send_buf = kzalloc(BULK_SIZE, GFP_KERNEL); if (!send_buf) { BT_ERR("Can't allocate memory chunk for firmware"); return -ENOMEM; diff --git a/drivers/bluetooth/bluetooth-power.c b/drivers/bluetooth/bluetooth-power.c new file mode 100644 index 000000000000..99c18e3d66d7 --- /dev/null +++ b/drivers/bluetooth/bluetooth-power.c @@ -0,0 +1,773 @@ +/* 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> +#include <net/cnss.h> +#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, S_IRUGO, 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) { + dev_err(dev, "No memory for vreg: %s\n", 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); diff --git a/drivers/bluetooth/btfm_slim.c b/drivers/bluetooth/btfm_slim.c new file mode 100644 index 000000000000..0a61186167ba --- /dev/null +++ b/drivers/bluetooth/btfm_slim.c @@ -0,0 +1,565 @@ +/* Copyright (c) 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. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/debugfs.h> +#include <linux/ratelimit.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <btfm_slim.h> +#include <btfm_slim_wcn3990.h> +#include <linux/bluetooth-power.h> + +int btfm_slim_write(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *src, uint8_t pgd) +{ + int ret, i; + struct slim_ele_access msg; + int slim_write_tries = SLIM_SLAVE_RW_MAX_TRIES; + + BTFMSLIM_DBG("Write to %s", pgd?"PGD":"IFD"); + msg.start_offset = SLIM_SLAVE_REG_OFFSET + reg; + msg.num_bytes = bytes; + msg.comp = NULL; + + for ( ; slim_write_tries != 0; slim_write_tries--) { + mutex_lock(&btfmslim->xfer_lock); + ret = slim_change_val_element(pgd ? btfmslim->slim_pgd : + &btfmslim->slim_ifd, &msg, src, bytes); + mutex_unlock(&btfmslim->xfer_lock); + if (ret == 0) + break; + usleep_range(5000, 5100); + } + + if (ret) { + BTFMSLIM_ERR("failed (%d)", ret); + return ret; + } + + for (i = 0; i < bytes; i++) + BTFMSLIM_DBG("Write 0x%02x to reg 0x%x", ((uint8_t *)src)[i], + reg + i); + return 0; +} + +int btfm_slim_write_pgd(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *src) +{ + return btfm_slim_write(btfmslim, reg, bytes, src, PGD); +} + +int btfm_slim_write_inf(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *src) +{ + return btfm_slim_write(btfmslim, reg, bytes, src, IFD); +} + +int btfm_slim_read(struct btfmslim *btfmslim, unsigned short reg, + int bytes, void *dest, uint8_t pgd) +{ + int ret, i; + struct slim_ele_access msg; + int slim_read_tries = SLIM_SLAVE_RW_MAX_TRIES; + + BTFMSLIM_DBG("Read from %s", pgd?"PGD":"IFD"); + msg.start_offset = SLIM_SLAVE_REG_OFFSET + reg; + msg.num_bytes = bytes; + msg.comp = NULL; + + for ( ; slim_read_tries != 0; slim_read_tries--) { + mutex_lock(&btfmslim->xfer_lock); + ret = slim_request_val_element(pgd ? btfmslim->slim_pgd : + &btfmslim->slim_ifd, &msg, dest, bytes); + mutex_unlock(&btfmslim->xfer_lock); + if (ret == 0) + break; + usleep_range(5000, 5100); + } + + if (ret) + BTFMSLIM_ERR("failed (%d)", ret); + + for (i = 0; i < bytes; i++) + BTFMSLIM_DBG("Read 0x%02x from reg 0x%x", ((uint8_t *)dest)[i], + reg + i); + + return 0; +} + +int btfm_slim_read_pgd(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *dest) +{ + return btfm_slim_read(btfmslim, reg, bytes, dest, PGD); +} + +int btfm_slim_read_inf(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *dest) +{ + return btfm_slim_read(btfmslim, reg, bytes, dest, IFD); +} + +int btfm_slim_enable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch, + uint8_t rxport, uint32_t rates, uint8_t grp, uint8_t nchan) +{ + int ret, i; + struct slim_ch prop; + struct btfmslim_ch *chan = ch; + uint16_t ch_h[2]; + + if (!btfmslim || !ch) + return -EINVAL; + + BTFMSLIM_DBG("port: %d ch: %d", ch->port, ch->ch); + + /* Define the channel with below parameters */ + prop.prot = SLIM_AUTO_ISO; + prop.baser = SLIM_RATE_4000HZ; + prop.dataf = (rates == 48000) ? SLIM_CH_DATAF_NOT_DEFINED + : SLIM_CH_DATAF_LPCM_AUDIO; + prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE; + prop.ratem = (rates/4000); + prop.sampleszbits = 16; + + ch_h[0] = ch->ch_hdl; + ch_h[1] = (grp) ? (ch+1)->ch_hdl : 0; + + ret = slim_define_ch(btfmslim->slim_pgd, &prop, ch_h, nchan, grp, + &ch->grph); + if (ret < 0) { + BTFMSLIM_ERR("slim_define_ch failed ret[%d]", ret); + goto error; + } + + for (i = 0; i < nchan; i++, ch++) { + /* Enable port through registration setting */ + if (btfmslim->vendor_port_en) { + ret = btfmslim->vendor_port_en(btfmslim, ch->port, + rxport, 1); + if (ret < 0) { + BTFMSLIM_ERR("vendor_port_en failed ret[%d]", + ret); + goto error; + } + } + + if (rxport) { + BTFMSLIM_INFO("slim_connect_sink(port: %d, ch: %d)", + ch->port, ch->ch); + /* Connect Port with channel given by Machine driver*/ + ret = slim_connect_sink(btfmslim->slim_pgd, + &ch->port_hdl, 1, ch->ch_hdl); + if (ret < 0) { + BTFMSLIM_ERR("slim_connect_sink failed ret[%d]", + ret); + goto remove_channel; + } + } else { + BTFMSLIM_INFO("slim_connect_src(port: %d, ch: %d)", + ch->port, ch->ch); + /* Connect Port with channel given by Machine driver*/ + ret = slim_connect_src(btfmslim->slim_pgd, ch->port_hdl, + ch->ch_hdl); + if (ret < 0) { + BTFMSLIM_ERR("slim_connect_src failed ret[%d]", + ret); + goto remove_channel; + } + } + } + + /* Activate the channel immediately */ + BTFMSLIM_INFO( + "port: %d, ch: %d, grp: %d, ch->grph: 0x%x, ch_hdl: 0x%x", + chan->port, chan->ch, grp, chan->grph, chan->ch_hdl); + + ret = slim_control_ch(btfmslim->slim_pgd, (grp ? chan->grph : + chan->ch_hdl), SLIM_CH_ACTIVATE, true); + if (ret < 0) { + BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret); + goto remove_channel; + } + +error: + return ret; + +remove_channel: + /* Remove the channel immediately*/ + ret = slim_control_ch(btfmslim->slim_pgd, (grp ? ch->grph : ch->ch_hdl), + SLIM_CH_REMOVE, true); + if (ret < 0) + BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret); + + return ret; +} + +int btfm_slim_disable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch, + uint8_t rxport, uint8_t grp, uint8_t nchan) +{ + int ret, i; + + if (!btfmslim || !ch) + return -EINVAL; + + BTFMSLIM_INFO("port:%d, grp: %d, ch->grph:0x%x, ch->ch_hdl:0x%x ", + ch->port, grp, ch->grph, ch->ch_hdl); + + /* Remove the channel immediately*/ + ret = slim_control_ch(btfmslim->slim_pgd, (grp ? ch->grph : ch->ch_hdl), + SLIM_CH_REMOVE, true); + if (ret < 0) { + BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret); + ret = slim_disconnect_ports(btfmslim->slim_pgd, + &ch->port_hdl, 1); + if (ret < 0) { + BTFMSLIM_ERR("slim_disconnect_ports failed ret[%d]", + ret); + goto error; + } + } + /* Disable port through registration setting */ + for (i = 0; i < nchan; i++, ch++) { + if (btfmslim->vendor_port_en) { + ret = btfmslim->vendor_port_en(btfmslim, ch->port, + rxport, 0); + if (ret < 0) { + BTFMSLIM_ERR("vendor_port_en failed ret[%d]", + ret); + break; + } + } + } + +error: + return ret; +} + +static int btfm_slim_get_logical_addr(struct slim_device *slim) +{ + int ret = 0; + const unsigned long timeout = jiffies + + msecs_to_jiffies(SLIM_SLAVE_PRESENT_TIMEOUT); + + do { + ret = slim_get_logical_addr(slim, slim->e_addr, + ARRAY_SIZE(slim->e_addr), &slim->laddr); + if (!ret) { + BTFMSLIM_DBG("Assigned l-addr: 0x%x", slim->laddr); + break; + } + /* Give SLIMBUS time to report present and be ready. */ + usleep_range(1000, 1100); + BTFMSLIM_DBG("retyring get logical addr"); + } while (time_before(jiffies, timeout)); + + return ret; +} + +static int btfm_slim_alloc_port(struct btfmslim *btfmslim) +{ + int ret = -EINVAL, i; + struct btfmslim_ch *rx_chs; + struct btfmslim_ch *tx_chs; + + if (!btfmslim) + return ret; + + rx_chs = btfmslim->rx_chs; + tx_chs = btfmslim->tx_chs; + + if (!rx_chs || !tx_chs) + return ret; + + BTFMSLIM_DBG("Rx: id\tname\tport\thdl\tch\tch_hdl"); + for (i = 0 ; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && + (i < BTFM_SLIM_NUM_CODEC_DAIS); i++, rx_chs++) { + + /* Get Rx port handler from slimbus driver based + * on port number + */ + ret = slim_get_slaveport(btfmslim->slim_pgd->laddr, + rx_chs->port, &rx_chs->port_hdl, SLIM_SINK); + if (ret < 0) { + BTFMSLIM_ERR("slave port failure port#%d - ret[%d]", + rx_chs->port, SLIM_SINK); + return ret; + } + BTFMSLIM_DBG(" %d\t%s\t%d\t%x\t%d\t%x", rx_chs->id, + rx_chs->name, rx_chs->port, rx_chs->port_hdl, + rx_chs->ch, rx_chs->ch_hdl); + } + + BTFMSLIM_DBG("Tx: id\tname\tport\thdl\tch\tch_hdl"); + for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && + (i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) { + + /* Get Tx port handler from slimbus driver based + * on port number + */ + ret = slim_get_slaveport(btfmslim->slim_pgd->laddr, + tx_chs->port, &tx_chs->port_hdl, SLIM_SRC); + if (ret < 0) { + BTFMSLIM_ERR("slave port failure port#%d - ret[%d]", + tx_chs->port, SLIM_SRC); + return ret; + } + BTFMSLIM_DBG(" %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id, + tx_chs->name, tx_chs->port, tx_chs->port_hdl, + tx_chs->ch, tx_chs->ch_hdl); + } + return ret; +} + +int btfm_slim_hw_init(struct btfmslim *btfmslim) +{ + int ret; + + BTFMSLIM_DBG(""); + if (!btfmslim) + return -EINVAL; + + if (btfmslim->enabled) { + BTFMSLIM_DBG("Already enabled"); + return 0; + } + mutex_lock(&btfmslim->io_lock); + + /* Assign Logical Address for PGD (Ported Generic Device) + * enumeration address + */ + ret = btfm_slim_get_logical_addr(btfmslim->slim_pgd); + if (ret) { + BTFMSLIM_ERR("failed to get slimbus %s logical address: %d", + btfmslim->slim_pgd->name, ret); + goto error; + } + + /* Assign Logical Address for Ported Generic Device + * enumeration address + */ + ret = btfm_slim_get_logical_addr(&btfmslim->slim_ifd); + if (ret) { + BTFMSLIM_ERR("failed to get slimbus %s logical address: %d", + btfmslim->slim_ifd.name, ret); + goto error; + } + + /* Allocate ports with logical address to get port handler from + * slimbus driver + */ + ret = btfm_slim_alloc_port(btfmslim); + if (ret) + goto error; + + /* Start vendor specific initialization and get port information */ + if (btfmslim->vendor_init) + ret = btfmslim->vendor_init(btfmslim); + + /* Only when all registers read/write successfully, it set to + * enabled status + */ + btfmslim->enabled = 1; +error: + mutex_unlock(&btfmslim->io_lock); + return ret; +} + + +int btfm_slim_hw_deinit(struct btfmslim *btfmslim) +{ + int ret = 0; + + if (!btfmslim) + return -EINVAL; + + if (!btfmslim->enabled) { + BTFMSLIM_DBG("Already disabled"); + return 0; + } + mutex_lock(&btfmslim->io_lock); + btfmslim->enabled = 0; + mutex_unlock(&btfmslim->io_lock); + return ret; +} + +static int btfm_slim_get_dt_info(struct btfmslim *btfmslim) +{ + int ret = 0; + struct slim_device *slim = btfmslim->slim_pgd; + struct slim_device *slim_ifd = &btfmslim->slim_ifd; + struct property *prop; + + if (!slim || !slim_ifd) + return -EINVAL; + + if (slim->dev.of_node) { + BTFMSLIM_DBG("Platform data from device tree (%s)", + slim->name); + ret = of_property_read_string(slim->dev.of_node, + "qcom,btfm-slim-ifd", &slim_ifd->name); + if (ret) { + BTFMSLIM_ERR("Looking up %s property in node %s failed", + "qcom,btfm-slim-ifd", + slim->dev.of_node->full_name); + return -ENODEV; + } + BTFMSLIM_DBG("qcom,btfm-slim-ifd (%s)", slim_ifd->name); + + prop = of_find_property(slim->dev.of_node, + "qcom,btfm-slim-ifd-elemental-addr", NULL); + if (!prop) { + BTFMSLIM_ERR("Looking up %s property in node %s failed", + "qcom,btfm-slim-ifd-elemental-addr", + slim->dev.of_node->full_name); + return -ENODEV; + } else if (prop->length != 6) { + BTFMSLIM_ERR( + "invalid codec slim ifd addr. addr length= %d", + prop->length); + return -ENODEV; + } + memcpy(slim_ifd->e_addr, prop->value, 6); + BTFMSLIM_DBG( + "PGD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x", + slim->e_addr[0], slim->e_addr[1], slim->e_addr[2], + slim->e_addr[3], slim->e_addr[4], slim->e_addr[5]); + BTFMSLIM_DBG( + "IFD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x", + slim_ifd->e_addr[0], slim_ifd->e_addr[1], + slim_ifd->e_addr[2], slim_ifd->e_addr[3], + slim_ifd->e_addr[4], slim_ifd->e_addr[5]); + } else { + BTFMSLIM_ERR("Platform data is not valid"); + } + + return ret; +} + +static int btfm_slim_probe(struct slim_device *slim) +{ + int ret = 0; + struct btfmslim *btfm_slim; + + BTFMSLIM_DBG(""); + if (!slim->ctrl) + return -EINVAL; + + /* Allocation btfmslim data pointer */ + btfm_slim = kzalloc(sizeof(struct btfmslim), GFP_KERNEL); + if (btfm_slim == NULL) { + BTFMSLIM_ERR("error, allocation failed"); + return -ENOMEM; + } + /* BTFM Slimbus driver control data configuration */ + btfm_slim->slim_pgd = slim; + + /* Assign vendor specific function */ + btfm_slim->rx_chs = SLIM_SLAVE_RXPORT; + btfm_slim->tx_chs = SLIM_SLAVE_TXPORT; + btfm_slim->vendor_init = SLIM_SLAVE_INIT; + btfm_slim->vendor_port_en = SLIM_SLAVE_PORT_EN; + + /* Created Mutex for slimbus data transfer */ + mutex_init(&btfm_slim->io_lock); + mutex_init(&btfm_slim->xfer_lock); + + /* Get Device tree node for Interface Device enumeration address */ + ret = btfm_slim_get_dt_info(btfm_slim); + if (ret) + goto dealloc; + + /* Add Interface Device for slimbus driver */ + ret = slim_add_device(btfm_slim->slim_pgd->ctrl, &btfm_slim->slim_ifd); + if (ret) { + BTFMSLIM_ERR("error, adding SLIMBUS device failed"); + goto dealloc; + } + + /* Platform driver data allocation */ + slim->dev.platform_data = btfm_slim; + + /* Driver specific data allocation */ + btfm_slim->dev = &slim->dev; + ret = btfm_slim_register_codec(&slim->dev); + ret = bt_register_slimdev(&slim->dev); + return ret; + +dealloc: + mutex_destroy(&btfm_slim->io_lock); + mutex_destroy(&btfm_slim->xfer_lock); + kfree(btfm_slim); + return ret; +} +static int btfm_slim_remove(struct slim_device *slim) +{ + struct btfmslim *btfm_slim = slim->dev.platform_data; + + BTFMSLIM_DBG(""); + mutex_destroy(&btfm_slim->io_lock); + mutex_destroy(&btfm_slim->xfer_lock); + snd_soc_unregister_codec(&slim->dev); + + BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_ifd"); + slim_remove_device(&btfm_slim->slim_ifd); + + BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_pgd"); + slim_remove_device(slim); + + kfree(btfm_slim); + return 0; +} + +static const struct slim_device_id btfm_slim_id[] = { + {SLIM_SLAVE_COMPATIBLE_STR, 0}, + {} +}; + +static struct slim_driver btfm_slim_driver = { + .driver = { + .name = "btfmslim-driver", + .owner = THIS_MODULE, + }, + .probe = btfm_slim_probe, + .remove = btfm_slim_remove, + .id_table = btfm_slim_id +}; + +static int __init btfm_slim_init(void) +{ + int ret; + + BTFMSLIM_DBG(""); + ret = slim_driver_register(&btfm_slim_driver); + if (ret) + BTFMSLIM_ERR("Failed to register slimbus driver: %d", ret); + return ret; +} + +static void __exit btfm_slim_exit(void) +{ + BTFMSLIM_DBG(""); + slim_driver_unregister(&btfm_slim_driver); +} + +module_init(btfm_slim_init); +module_exit(btfm_slim_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BTFM Slimbus Slave driver"); diff --git a/drivers/bluetooth/btfm_slim.h b/drivers/bluetooth/btfm_slim.h new file mode 100644 index 000000000000..c7b2b45eb19d --- /dev/null +++ b/drivers/bluetooth/btfm_slim.h @@ -0,0 +1,165 @@ +/* Copyright (c) 2016-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. + */ +#ifndef BTFM_SLIM_H +#define BTFM_SLIM_H +#include <linux/slimbus/slimbus.h> + +#define BTFMSLIM_DBG(fmt, arg...) pr_debug("%s: " fmt "\n", __func__, ## arg) +#define BTFMSLIM_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg) +#define BTFMSLIM_ERR(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg) + +/* Vendor specific defines + * This should redefines in slimbus slave specific header +*/ +#define SLIM_SLAVE_COMPATIBLE_STR "btfmslim_slave" +#define SLIM_SLAVE_REG_OFFSET 0x0000 +#define SLIM_SLAVE_RXPORT NULL +#define SLIM_SLAVE_TXPORT NULL +#define SLIM_SLAVE_INIT NULL +#define SLIM_SLAVE_PORT_EN NULL + +/* Misc defines */ +#define SLIM_SLAVE_RW_MAX_TRIES 3 +#define SLIM_SLAVE_PRESENT_TIMEOUT 100 + +#define PGD 1 +#define IFD 0 + + +/* Codec driver defines */ +enum { + BTFM_FM_SLIM_TX = 0, + BTFM_BT_SCO_SLIM_TX, + BTFM_BT_SCO_A2DP_SLIM_RX, + BTFM_BT_SPLIT_A2DP_SLIM_RX, + BTFM_SLIM_NUM_CODEC_DAIS +}; + +/* Slimbus Port defines - This should be redefined in specific device file */ +#define BTFM_SLIM_PGD_PORT_LAST 0xFF + +struct btfmslim_ch { + int id; + char *name; + uint32_t port_hdl; /* slimbus port handler */ + uint16_t port; /* slimbus port number */ + + uint8_t ch; /* slimbus channel number */ + uint16_t ch_hdl; /* slimbus channel handler */ + uint16_t grph; /* slimbus group channel handler */ +}; + +struct btfmslim { + struct device *dev; + struct slim_device *slim_pgd; + struct slim_device slim_ifd; + struct mutex io_lock; + struct mutex xfer_lock; + uint8_t enabled; + + uint32_t num_rx_port; + uint32_t num_tx_port; + uint32_t sample_rate; + + struct btfmslim_ch *rx_chs; + struct btfmslim_ch *tx_chs; + + int (*vendor_init)(struct btfmslim *btfmslim); + int (*vendor_port_en)(struct btfmslim *btfmslim, uint8_t port_num, + uint8_t rxport, uint8_t enable); +}; + +/** + * btfm_slim_hw_init: Initialize slimbus slave device + * Returns: + * 0: Success + * else: Fail + */ +int btfm_slim_hw_init(struct btfmslim *btfmslim); + +/** + * btfm_slim_hw_deinit: Deinitialize slimbus slave device + * Returns: + * 0: Success + * else: Fail + */ +int btfm_slim_hw_deinit(struct btfmslim *btfmslim); + +/** + * btfm_slim_write: write value to pgd or ifd device + * @btfmslim: slimbus slave device data pointer. + * @reg: slimbus slave register address + * @bytes: length of data + * @src: data pointer to write + * @pgd: selection for device: either PGD or IFD + * Returns: + * -EINVAL + * -ETIMEDOUT + * -ENOMEM + */ +int btfm_slim_write(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *src, uint8_t pgd); + + + +/** + * btfm_slim_read: read value from pgd or ifd device + * @btfmslim: slimbus slave device data pointer. + * @reg: slimbus slave register address + * @bytes: length of data + * @dest: data pointer to read + * @pgd: selection for device: either PGD or IFD + * Returns: + * -EINVAL + * -ETIMEDOUT + * -ENOMEM + */ +int btfm_slim_read(struct btfmslim *btfmslim, + uint16_t reg, int bytes, void *dest, uint8_t pgd); + + +/** + * btfm_slim_enable_ch: enable channel for slimbus slave port + * @btfmslim: slimbus slave device data pointer. + * @ch: slimbus slave channel pointer + * @rxport: rxport or txport + * Returns: + * -EINVAL + * -ETIMEDOUT + * -ENOMEM + */ +int btfm_slim_enable_ch(struct btfmslim *btfmslim, + struct btfmslim_ch *ch, uint8_t rxport, uint32_t rates, + uint8_t grp, uint8_t nchan); + +/** + * btfm_slim_disable_ch: disable channel for slimbus slave port + * @btfmslim: slimbus slave device data pointer. + * @ch: slimbus slave channel pointer + * @rxport: rxport or txport + * Returns: + * -EINVAL + * -ETIMEDOUT + * -ENOMEM + */ +int btfm_slim_disable_ch(struct btfmslim *btfmslim, + struct btfmslim_ch *ch, uint8_t rxport, uint8_t grp, uint8_t nchan); + +/** + * btfm_slim_register_codec: Register codec driver in slimbus device node + * @dev: device node + * Returns: + * -ENOMEM + * 0 +*/ +int btfm_slim_register_codec(struct device *dev); +#endif /* BTFM_SLIM_H */ diff --git a/drivers/bluetooth/btfm_slim_codec.c b/drivers/bluetooth/btfm_slim_codec.c new file mode 100644 index 000000000000..035e8d9fb5fd --- /dev/null +++ b/drivers/bluetooth/btfm_slim_codec.c @@ -0,0 +1,434 @@ +/* Copyright (c) 2016-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. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/debugfs.h> +#include <linux/slimbus/slimbus.h> +#include <linux/ratelimit.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <btfm_slim.h> + +static int bt_soc_enable_status; + + +static int btfm_slim_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + return 0; +} + +static unsigned int btfm_slim_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + return 0; +} + +static int bt_soc_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = bt_soc_enable_status; + return 1; +} + +static int bt_soc_status_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 1; +} + +static const struct snd_kcontrol_new status_controls[] = { + SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0, + bt_soc_status_get, + bt_soc_status_put) + +}; + + +static int btfm_slim_codec_probe(struct snd_soc_codec *codec) +{ + snd_soc_add_codec_controls(codec, status_controls, + ARRAY_SIZE(status_controls)); + return 0; +} + +static int btfm_slim_codec_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static int btfm_slim_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct btfmslim *btfmslim = dai->dev->platform_data; + + BTFMSLIM_DBG("substream = %s stream = %d dai->name = %s", + substream->name, substream->stream, dai->name); + ret = btfm_slim_hw_init(btfmslim); + return ret; +} + +static void btfm_slim_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int i; + struct btfmslim *btfmslim = dai->dev->platform_data; + struct btfmslim_ch *ch; + uint8_t rxport, grp = false, nchan = 1; + + BTFMSLIM_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name, + dai->id, dai->rate); + + switch (dai->id) { + case BTFM_FM_SLIM_TX: + grp = true; nchan = 2; + ch = btfmslim->tx_chs; + rxport = 0; + break; + case BTFM_BT_SCO_SLIM_TX: + ch = btfmslim->tx_chs; + rxport = 0; + break; + case BTFM_BT_SCO_A2DP_SLIM_RX: + case BTFM_BT_SPLIT_A2DP_SLIM_RX: + ch = btfmslim->rx_chs; + rxport = 1; + break; + case BTFM_SLIM_NUM_CODEC_DAIS: + default: + BTFMSLIM_ERR("dai->id is invalid:%d", dai->id); + return; + } + + /* Search for dai->id matched port handler */ + for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && + (ch->id != BTFM_SLIM_NUM_CODEC_DAIS) && + (ch->id != dai->id); ch++, i++) + ; + + if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) || + (ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) { + BTFMSLIM_ERR("ch is invalid!!"); + return; + } + + btfm_slim_disable_ch(btfmslim, ch, rxport, grp, nchan); + btfm_slim_hw_deinit(btfmslim); +} + +static int btfm_slim_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + BTFMSLIM_DBG("dai->name = %s DAI-ID %x rate %d num_ch %d", + dai->name, dai->id, params_rate(params), + params_channels(params)); + + return 0; +} + +int btfm_slim_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int i, ret = -EINVAL; + struct btfmslim *btfmslim = dai->dev->platform_data; + struct btfmslim_ch *ch; + uint8_t rxport, grp = false, nchan = 1; + bt_soc_enable_status = 0; + + BTFMSLIM_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name, + dai->id, dai->rate); + + /* save sample rate */ + btfmslim->sample_rate = dai->rate; + + switch (dai->id) { + case BTFM_FM_SLIM_TX: + grp = true; nchan = 2; + ch = btfmslim->tx_chs; + rxport = 0; + break; + case BTFM_BT_SCO_SLIM_TX: + ch = btfmslim->tx_chs; + rxport = 0; + break; + case BTFM_BT_SCO_A2DP_SLIM_RX: + case BTFM_BT_SPLIT_A2DP_SLIM_RX: + ch = btfmslim->rx_chs; + rxport = 1; + break; + case BTFM_SLIM_NUM_CODEC_DAIS: + default: + BTFMSLIM_ERR("dai->id is invalid:%d", dai->id); + return ret; + } + + /* Search for dai->id matched port handler */ + for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && + (ch->id != BTFM_SLIM_NUM_CODEC_DAIS) && + (ch->id != dai->id); ch++, i++) + ; + + if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) || + (ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) { + BTFMSLIM_ERR("ch is invalid!!"); + return ret; + } + + ret = btfm_slim_enable_ch(btfmslim, ch, rxport, dai->rate, grp, nchan); + + /* save the enable channel status */ + if (ret == 0) + bt_soc_enable_status = 1; + return ret; +} + +/* This function will be called once during boot up */ +static int btfm_slim_dai_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + int ret = -EINVAL, i; + struct btfmslim *btfmslim = dai->dev->platform_data; + struct btfmslim_ch *rx_chs; + struct btfmslim_ch *tx_chs; + + BTFMSLIM_DBG(""); + + if (!btfmslim) + return ret; + + rx_chs = btfmslim->rx_chs; + tx_chs = btfmslim->tx_chs; + + if (!rx_chs || !tx_chs) + return ret; + + BTFMSLIM_DBG("Rx: id\tname\tport\thdl\tch\tch_hdl"); + for (i = 0; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < rx_num); + i++, rx_chs++) { + /* Set Rx Channel number from machine driver and + * get channel handler from slimbus driver + */ + rx_chs->ch = *(uint8_t *)(rx_slot + i); + ret = slim_query_ch(btfmslim->slim_pgd, rx_chs->ch, + &rx_chs->ch_hdl); + if (ret < 0) { + BTFMSLIM_ERR("slim_query_ch failure ch#%d - ret[%d]", + rx_chs->ch, ret); + goto error; + } + BTFMSLIM_DBG(" %d\t%s\t%d\t%x\t%d\t%x", rx_chs->id, + rx_chs->name, rx_chs->port, rx_chs->port_hdl, + rx_chs->ch, rx_chs->ch_hdl); + } + + BTFMSLIM_DBG("Tx: id\tname\tport\thdl\tch\tch_hdl"); + for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num); + i++, tx_chs++) { + /* Set Tx Channel number from machine driver and + * get channel handler from slimbus driver + */ + tx_chs->ch = *(uint8_t *)(tx_slot + i); + ret = slim_query_ch(btfmslim->slim_pgd, tx_chs->ch, + &tx_chs->ch_hdl); + if (ret < 0) { + BTFMSLIM_ERR("slim_query_ch failure ch#%d - ret[%d]", + tx_chs->ch, ret); + goto error; + } + BTFMSLIM_DBG(" %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id, + tx_chs->name, tx_chs->port, tx_chs->port_hdl, + tx_chs->ch, tx_chs->ch_hdl); + } + +error: + return ret; +} + +static int btfm_slim_dai_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) +{ + int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1; + struct btfmslim *btfmslim = dai->dev->platform_data; + struct btfmslim_ch *ch = NULL; + + if (!btfmslim) + return ret; + + switch (dai->id) { + case BTFM_FM_SLIM_TX: + num = 2; + case BTFM_BT_SCO_SLIM_TX: + if (!tx_slot || !tx_num) { + BTFMSLIM_ERR("Invalid tx_slot %p or tx_num %p", + tx_slot, tx_num); + return -EINVAL; + } + ch = btfmslim->tx_chs; + if (!ch) + return -EINVAL; + slot = tx_slot; + *rx_slot = 0; + *tx_num = num; + *rx_num = 0; + break; + case BTFM_BT_SCO_A2DP_SLIM_RX: + case BTFM_BT_SPLIT_A2DP_SLIM_RX: + if (!rx_slot || !rx_num) { + BTFMSLIM_ERR("Invalid rx_slot %p or rx_num %p", + rx_slot, rx_num); + return -EINVAL; + } + ch = btfmslim->rx_chs; + if (!ch) + return -EINVAL; + slot = rx_slot; + *tx_slot = 0; + *tx_num = 0; + *rx_num = num; + break; + default: + BTFMSLIM_ERR("Unsupported DAI %d", dai->id); + return -EINVAL; + } + + do { + for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != + BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != dai->id); + ch++, i++) + ; + + if (ch->id == BTFM_SLIM_NUM_CODEC_DAIS || + i == BTFM_SLIM_NUM_CODEC_DAIS) { + BTFMSLIM_ERR( + "No channel has been allocated for dai (%d)", + dai->id); + return -EINVAL; + } + + *(slot + j) = ch->ch; + BTFMSLIM_DBG("id:%d, port:%d, ch:%d, slot: %d", ch->id, + ch->port, ch->ch, *(slot + j)); + + /* In case it has mulitiple channels */ + if (++j < num) + ch++; + } while (j < num); + + return 0; +} + +static struct snd_soc_dai_ops btfmslim_dai_ops = { + .startup = btfm_slim_dai_startup, + .shutdown = btfm_slim_dai_shutdown, + .hw_params = btfm_slim_dai_hw_params, + .prepare = btfm_slim_dai_prepare, + .set_channel_map = btfm_slim_dai_set_channel_map, + .get_channel_map = btfm_slim_dai_get_channel_map, +}; + +static struct snd_soc_dai_driver btfmslim_dai[] = { + { /* FM Audio data multiple channel : FM -> qdsp */ + .name = "btfm_fm_slim_tx", + .id = BTFM_FM_SLIM_TX, + .capture = { + .stream_name = "FM TX Capture", + .rates = SNDRV_PCM_RATE_48000, /* 48 KHz */ + .formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */ + .rate_max = 48000, + .rate_min = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &btfmslim_dai_ops, + }, + { /* Bluetooth SCO voice uplink: bt -> modem */ + .name = "btfm_bt_sco_slim_tx", + .id = BTFM_BT_SCO_SLIM_TX, + .capture = { + .stream_name = "SCO TX Capture", + /* 8 KHz or 16 KHz */ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */ + .rate_max = 16000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &btfmslim_dai_ops, + }, + { /* Bluetooth SCO voice downlink: modem -> bt or A2DP Playback */ + .name = "btfm_bt_sco_a2dp_slim_rx", + .id = BTFM_BT_SCO_A2DP_SLIM_RX, + .playback = { + .stream_name = "SCO A2DP RX Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 + | SNDRV_PCM_RATE_48000, /* 8 or 16 or 48 Khz*/ + .formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */ + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &btfmslim_dai_ops, + }, + { /* Bluetooth Split A2DP data: qdsp -> bt */ + .name = "btfm_bt_split_a2dp_slim_rx", + .id = BTFM_BT_SPLIT_A2DP_SLIM_RX, + .playback = { + .stream_name = "SPLIT A2DP Playback", + .rates = SNDRV_PCM_RATE_48000, /* 48 KHz */ + .formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */ + .rate_max = 48000, + .rate_min = 48000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &btfmslim_dai_ops, + }, +}; + +static struct snd_soc_codec_driver btfmslim_codec = { + .probe = btfm_slim_codec_probe, + .remove = btfm_slim_codec_remove, + .read = btfm_slim_codec_read, + .write = btfm_slim_codec_write, +}; + +int btfm_slim_register_codec(struct device *dev) +{ + int ret = 0; + + BTFMSLIM_DBG(""); + /* Register Codec driver */ + ret = snd_soc_register_codec(dev, &btfmslim_codec, + btfmslim_dai, ARRAY_SIZE(btfmslim_dai)); + + if (ret) + BTFMSLIM_ERR("failed to register codec (%d)", ret); + + return ret; +} + +MODULE_DESCRIPTION("BTFM Slimbus Codec driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/bluetooth/btfm_slim_wcn3990.c b/drivers/bluetooth/btfm_slim_wcn3990.c new file mode 100644 index 000000000000..7abd5598c47b --- /dev/null +++ b/drivers/bluetooth/btfm_slim_wcn3990.c @@ -0,0 +1,176 @@ +/* Copyright (c) 2016-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. + */ +#include <linux/slimbus/slimbus.h> +#include <btfm_slim.h> +#include <btfm_slim_wcn3990.h> + +/* WCN3990 Port assignment */ +struct btfmslim_ch wcn3990_rxport[] = { + {.id = BTFM_BT_SCO_A2DP_SLIM_RX, .name = "SCO_A2P_Rx", + .port = CHRK_SB_PGD_PORT_RX_SCO}, + {.id = BTFM_BT_SPLIT_A2DP_SLIM_RX, .name = "A2P_Rx", + .port = CHRK_SB_PGD_PORT_RX_A2P}, + {.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "", + .port = BTFM_SLIM_PGD_PORT_LAST}, +}; + +struct btfmslim_ch wcn3990_txport[] = { + {.id = BTFM_FM_SLIM_TX, .name = "FM_Tx1", + .port = CHRK_SB_PGD_PORT_TX1_FM}, + {.id = BTFM_FM_SLIM_TX, .name = "FM_Tx2", + .port = CHRK_SB_PGD_PORT_TX2_FM}, + {.id = BTFM_BT_SCO_SLIM_TX, .name = "SCO_Tx", + .port = CHRK_SB_PGD_PORT_TX_SCO}, + {.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "", + .port = BTFM_SLIM_PGD_PORT_LAST}, +}; + +/* Function description */ +int btfm_slim_chrk_hw_init(struct btfmslim *btfmslim) +{ + int ret = 0; + uint8_t reg_val; + uint16_t reg; + + BTFMSLIM_DBG(""); + + if (!btfmslim) + return -EINVAL; + + /* Get SB_SLAVE_HW_REV_MSB value*/ + reg = CHRK_SB_SLAVE_HW_REV_MSB; + ret = btfm_slim_read(btfmslim, reg, 1, ®_val, IFD); + if (ret) { + BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg); + goto error; + } + BTFMSLIM_DBG("Major Rev: 0x%x, Minor Rev: 0x%x", + (reg_val & 0xF0) >> 4, (reg_val & 0x0F)); + + /* Get SB_SLAVE_HW_REV_LSB value*/ + reg = CHRK_SB_SLAVE_HW_REV_LSB; + ret = btfm_slim_read(btfmslim, reg, 1, ®_val, IFD); + if (ret) { + BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg); + goto error; + } + BTFMSLIM_DBG("Step Rev: 0x%x", reg_val); + +error: + return ret; +} + +static inline int is_fm_port(uint8_t port_num) +{ + if (port_num == CHRK_SB_PGD_PORT_TX1_FM || + port_num == CHRK_SB_PGD_PORT_TX2_FM) + return 1; + else + return 0; +} + +int btfm_slim_chrk_enable_port(struct btfmslim *btfmslim, uint8_t port_num, + uint8_t rxport, uint8_t enable) +{ + int ret = 0; + uint8_t reg_val = 0, en; + uint8_t port_bit = 0; + uint16_t reg; + + BTFMSLIM_DBG("port(%d) enable(%d)", port_num, enable); + + if (rxport) { + if (enable) { + /* For SCO Rx, A2DP Rx */ + reg_val = 0x1; + port_bit = port_num - 0x10; + reg = CHRK_SB_PGD_RX_PORTn_MULTI_CHNL_0(port_bit); + BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)", + reg_val, reg); + ret = btfm_slim_write(btfmslim, reg, 1, ®_val, IFD); + if (ret) { + BTFMSLIM_ERR("failed to write (%d) reg 0x%x", + ret, reg); + goto error; + } + } + /* Port enable */ + reg = CHRK_SB_PGD_PORT_RX_CFGN(port_num - 0x10); + goto enable_disable_rxport; + } + if (!enable) + goto enable_disable_txport; + + /* txport */ + /* Multiple Channel Setting */ + if (is_fm_port(port_num)) { + reg_val = (0x1 << CHRK_SB_PGD_PORT_TX1_FM) | + (0x1 << CHRK_SB_PGD_PORT_TX2_FM); + reg = CHRK_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num); + ret = btfm_slim_write(btfmslim, reg, 1, ®_val, IFD); + if (ret) { + BTFMSLIM_ERR("failed to write (%d) reg 0x%x", + ret, reg); + goto error; + } + } else if (port_num == CHRK_SB_PGD_PORT_TX_SCO) { + /* SCO Tx */ + reg_val = 0x1 << CHRK_SB_PGD_PORT_TX_SCO; + reg = CHRK_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num); + BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)", + reg_val, reg); + ret = btfm_slim_write(btfmslim, reg, 1, ®_val, IFD); + if (ret) { + BTFMSLIM_ERR("failed to write (%d) reg 0x%x", + ret, reg); + goto error; + } + } + + /* Enable Tx port hw auto recovery for underrun or overrun error */ + reg_val = (CHRK_ENABLE_OVERRUN_AUTO_RECOVERY | + CHRK_ENABLE_UNDERRUN_AUTO_RECOVERY); + reg = CHRK_SB_PGD_PORT_TX_OR_UR_CFGN(port_num); + ret = btfm_slim_write(btfmslim, reg, 1, ®_val, IFD); + if (ret) { + BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg); + goto error; + } + +enable_disable_txport: + /* Port enable */ + reg = CHRK_SB_PGD_PORT_TX_CFGN(port_num); + +enable_disable_rxport: + if (enable) + en = CHRK_SB_PGD_PORT_ENABLE; + else + en = CHRK_SB_PGD_PORT_DISABLE; + + if (is_fm_port(port_num)) + reg_val = en | CHRK_SB_PGD_PORT_WM_L8; + else if (port_num == CHRK_SB_PGD_PORT_TX_SCO) + reg_val = enable ? en | CHRK_SB_PGD_PORT_WM_L1 : en; + else + reg_val = enable ? en | CHRK_SB_PGD_PORT_WM_LB : en; + + if (enable && port_num == CHRK_SB_PGD_PORT_TX_SCO) + BTFMSLIM_INFO("programming SCO Tx with reg_val %d to reg 0x%x", + reg_val, reg); + + ret = btfm_slim_write(btfmslim, reg, 1, ®_val, IFD); + if (ret) + BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg); + +error: + return ret; +} diff --git a/drivers/bluetooth/btfm_slim_wcn3990.h b/drivers/bluetooth/btfm_slim_wcn3990.h new file mode 100644 index 000000000000..b637ac581201 --- /dev/null +++ b/drivers/bluetooth/btfm_slim_wcn3990.h @@ -0,0 +1,141 @@ +/* Copyright (c) 2016-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. + */ +#ifndef BTFM_SLIM_WCN3990_H +#define BTFM_SLIM_WCN3990_H +#ifdef CONFIG_BTFM_SLIM_WCN3990 +#include <btfm_slim.h> + +/* Registers Address */ +#define CHRK_SB_COMP_TEST 0x00000000 +#define CHRK_SB_SLAVE_HW_REV_MSB 0x00000001 +#define CHRK_SB_SLAVE_HW_REV_LSB 0x00000002 +#define CHRK_SB_DEBUG_FEATURES 0x00000005 +#define CHRK_SB_INTF_INT_EN 0x00000010 +#define CHRK_SB_INTF_INT_STATUS 0x00000011 +#define CHRK_SB_INTF_INT_CLR 0x00000012 +#define CHRK_SB_FRM_CFG 0x00000013 +#define CHRK_SB_FRM_STATUS 0x00000014 +#define CHRK_SB_FRM_INT_EN 0x00000015 +#define CHRK_SB_FRM_INT_STATUS 0x00000016 +#define CHRK_SB_FRM_INT_CLR 0x00000017 +#define CHRK_SB_FRM_WAKEUP 0x00000018 +#define CHRK_SB_FRM_CLKCTL_DONE 0x00000019 +#define CHRK_SB_FRM_IE_STATUS 0x0000001A +#define CHRK_SB_FRM_VE_STATUS 0x0000001B +#define CHRK_SB_PGD_TX_CFG_STATUS 0x00000020 +#define CHRK_SB_PGD_RX_CFG_STATUS 0x00000021 +#define CHRK_SB_PGD_DEV_INT_EN 0x00000022 +#define CHRK_SB_PGD_DEV_INT_STATUS 0x00000023 +#define CHRK_SB_PGD_DEV_INT_CLR 0x00000024 +#define CHRK_SB_PGD_PORT_INT_EN_RX_0 0x00000030 +#define CHRK_SB_PGD_PORT_INT_EN_RX_1 0x00000031 +#define CHRK_SB_PGD_PORT_INT_EN_TX_0 0x00000032 +#define CHRK_SB_PGD_PORT_INT_EN_TX_1 0x00000033 +#define CHRK_SB_PGD_PORT_INT_STATUS_RX_0 0x00000034 +#define CHRK_SB_PGD_PORT_INT_STATUS_RX_1 0x00000035 +#define CHRK_SB_PGD_PORT_INT_STATUS_TX_0 0x00000036 +#define CHRK_SB_PGD_PORT_INT_STATUS_TX_1 0x00000037 +#define CHRK_SB_PGD_PORT_INT_CLR_RX_0 0x00000038 +#define CHRK_SB_PGD_PORT_INT_CLR_RX_1 0x00000039 +#define CHRK_SB_PGD_PORT_INT_CLR_TX_0 0x0000003A +#define CHRK_SB_PGD_PORT_INT_CLR_TX_1 0x0000003B +#define CHRK_SB_PGD_PORT_RX_CFGN(n) (0x00000040 + n) +#define CHRK_SB_PGD_PORT_TX_CFGN(n) (0x00000050 + n) +#define CHRK_SB_PGD_PORT_INT_RX_SOURCEN(n) (0x00000060 + n) +#define CHRK_SB_PGD_PORT_INT_TX_SOURCEN(n) (0x00000070 + n) +#define CHRK_SB_PGD_PORT_RX_STATUSN(n) (0x00000080 + n) +#define CHRK_SB_PGD_PORT_TX_STATUSN(n) (0x00000090 + n) +#define CHRK_SB_PGD_TX_PORTn_MULTI_CHNL_0(n) (0x00000100 + 0x4*n) +#define CHRK_SB_PGD_TX_PORTn_MULTI_CHNL_1(n) (0x00000101 + 0x4*n) +#define CHRK_SB_PGD_RX_PORTn_MULTI_CHNL_0(n) (0x00000180 + 0x4*n) +#define CHRK_SB_PGD_RX_PORTn_MULTI_CHNL_1(n) (0x00000181 + 0x4*n) +#define CHRK_SB_PGD_PORT_TX_OR_UR_CFGN(n) (0x000001F0 + n) + +/* Register Bit Setting */ +#define CHRK_ENABLE_OVERRUN_AUTO_RECOVERY (0x1 << 1) +#define CHRK_ENABLE_UNDERRUN_AUTO_RECOVERY (0x1 << 0) +#define CHRK_SB_PGD_PORT_ENABLE (0x1 << 0) +#define CHRK_SB_PGD_PORT_DISABLE (0x0 << 0) +#define CHRK_SB_PGD_PORT_WM_L1 (0x1 << 1) +#define CHRK_SB_PGD_PORT_WM_L2 (0x2 << 1) +#define CHRK_SB_PGD_PORT_WM_L3 (0x3 << 1) +#define CHRK_SB_PGD_PORT_WM_L8 (0x8 << 1) +#define CHRK_SB_PGD_PORT_WM_LB (0xB << 1) + +#define CHRK_SB_PGD_PORT_RX_NUM 16 +#define CHRK_SB_PGD_PORT_TX_NUM 16 + +/* PGD Port Map */ +#define CHRK_SB_PGD_PORT_TX_SCO 0 +#define CHRK_SB_PGD_PORT_TX1_FM 1 +#define CHRK_SB_PGD_PORT_TX2_FM 2 +#define CHRK_SB_PGD_PORT_RX_SCO 16 +#define CHRK_SB_PGD_PORT_RX_A2P 17 + + +/* Function Prototype */ + +/* + * btfm_slim_chrk_hw_init: Initialize wcn3990 specific slimbus slave device + * @btfmslim: slimbus slave device data pointer. + * Returns: + * 0: Success + * else: Fail + */ +int btfm_slim_chrk_hw_init(struct btfmslim *btfmslim); + +/* + * btfm_slim_chrk_enable_rxport: Enable wcn3990 Rx port by given port number + * @btfmslim: slimbus slave device data pointer. + * @portNum: slimbus slave port number to enable + * @rxport: rxport or txport + * @enable: enable port or disable port + * Returns: + * 0: Success + * else: Fail + */ +int btfm_slim_chrk_enable_port(struct btfmslim *btfmslim, uint8_t portNum, + uint8_t rxport, uint8_t enable); + +/* Specific defines for wcn3990 slimbus device */ +#define WCN3990_SLIM_REG_OFFSET 0x0800 + +#ifdef SLIM_SLAVE_REG_OFFSET +#undef SLIM_SLAVE_REG_OFFSET +#define SLIM_SLAVE_REG_OFFSET WCN3990_SLIM_REG_OFFSET +#endif + +/* Assign vendor specific function */ +extern struct btfmslim_ch wcn3990_txport[]; +extern struct btfmslim_ch wcn3990_rxport[]; + +#ifdef SLIM_SLAVE_RXPORT +#undef SLIM_SLAVE_RXPORT +#define SLIM_SLAVE_RXPORT (&wcn3990_rxport[0]) +#endif + +#ifdef SLIM_SLAVE_TXPORT +#undef SLIM_SLAVE_TXPORT +#define SLIM_SLAVE_TXPORT (&wcn3990_txport[0]) +#endif + +#ifdef SLIM_SLAVE_INIT +#undef SLIM_SLAVE_INIT +#define SLIM_SLAVE_INIT btfm_slim_chrk_hw_init +#endif + +#ifdef SLIM_SLAVE_PORT_EN +#undef SLIM_SLAVE_PORT_EN +#define SLIM_SLAVE_PORT_EN btfm_slim_chrk_enable_port +#endif +#endif /* CONFIG_BTFM_WCN3990 */ +#endif /* BTFM_SLIM_WCN3990_H */ diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 593fc2a5be0f..adcc8f5d864f 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -27,6 +27,9 @@ #define VERSION "0.1" +#define MAX_PATCH_FILE_SIZE (100*1024) +#define MAX_NVM_FILE_SIZE (10*1024) + static int rome_patch_ver_req(struct hci_dev *hdev, u32 *rome_version) { struct sk_buff *skb; @@ -285,27 +288,59 @@ static int rome_download_firmware(struct hci_dev *hdev, struct rome_config *config) { const struct firmware *fw; + u32 type_len, length; + struct tlv_type_hdr *tlv; int ret; - BT_INFO("%s: ROME Downloading %s", hdev->name, config->fwname); - + BT_INFO("%s: ROME Downloading file: %s", hdev->name, config->fwname); ret = request_firmware(&fw, config->fwname, &hdev->dev); - if (ret) { - BT_ERR("%s: Failed to request file: %s (%d)", hdev->name, - config->fwname, ret); + + if (ret || !fw || !fw->data || fw->size <= 0) { + BT_ERR("Failed to request file: err = (%d)", ret); + ret = ret ? ret : -EINVAL; return ret; } + if (config->type == TLV_TYPE_PATCH && + (fw->size > MAX_PATCH_FILE_SIZE)) { + ret = -EINVAL; + BT_ERR("TLV_PATCH dload: wrong patch file sizes"); + goto exit; + } else if (config->type == TLV_TYPE_NVM && + (fw->size > MAX_NVM_FILE_SIZE)) { + ret = -EINVAL; + BT_ERR("TLV_NVM dload: wrong NVM file sizes"); + goto exit; + } else { + ret = -EINVAL; + BT_ERR("TLV_NVM dload: wrong config type selected"); + goto exit; + } - rome_tlv_check_data(config, fw); + if (fw->size < sizeof(struct tlv_type_hdr)) { + ret = -EINVAL; + BT_ERR("Firware size smaller to fit minimum value"); + goto exit; + } + tlv = (struct tlv_type_hdr *)fw->data; + type_len = le32_to_cpu(tlv->type_len); + length = (type_len >> 8) & 0x00ffffff; + + if (fw->size - 4 != length) { + ret = -EINVAL; + BT_ERR("Requested size not matching size in header"); + goto exit; + } + + rome_tlv_check_data(config, fw); ret = rome_tlv_download_request(hdev, fw); + if (ret) { - BT_ERR("%s: Failed to download file: %s (%d)", hdev->name, - config->fwname, ret); + BT_ERR("Failed to download FW: error = (%d)", ret); } +exit: release_firmware(fw); - return ret; } @@ -316,8 +351,9 @@ int qca_set_bdaddr_rome(struct hci_dev *hdev, const bdaddr_t *bdaddr) int err; cmd[0] = EDL_NVM_ACCESS_SET_REQ_CMD; - cmd[1] = 0x02; /* TAG ID */ - cmd[2] = sizeof(bdaddr_t); /* size */ + /* Set the TAG ID of 0x02 for NVM set and size of tag */ + cmd[1] = 0x02; + cmd[2] = sizeof(bdaddr_t); memcpy(cmd + 3, bdaddr, sizeof(bdaddr_t)); skb = __hci_cmd_sync_ev(hdev, EDL_NVM_ACCESS_OPCODE, sizeof(cmd), cmd, HCI_VENDOR_PKT, HCI_INIT_TIMEOUT); diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c index a6fce48da0fb..820a82415bc1 100644 --- a/drivers/bluetooth/hci_h4.c +++ b/drivers/bluetooth/hci_h4.c @@ -57,7 +57,7 @@ static int h4_open(struct hci_uart *hu) { struct h4_struct *h4; - BT_DBG("hu %p", hu); + BT_DBG("hu %pK", hu); h4 = kzalloc(sizeof(*h4), GFP_KERNEL); if (!h4) @@ -74,7 +74,7 @@ static int h4_flush(struct hci_uart *hu) { struct h4_struct *h4 = hu->priv; - BT_DBG("hu %p", hu); + BT_DBG("hu %pK", hu); skb_queue_purge(&h4->txq); @@ -88,7 +88,7 @@ static int h4_close(struct hci_uart *hu) hu->priv = NULL; - BT_DBG("hu %p", hu); + BT_DBG("hu %pK", hu); skb_queue_purge(&h4->txq); @@ -105,7 +105,7 @@ static int h4_enqueue(struct hci_uart *hu, struct sk_buff *skb) { struct h4_struct *h4 = hu->priv; - BT_DBG("hu %p skb %p", hu, skb); + BT_DBG("hu %pK skb %pK", hu, skb); /* Prepend skb with frame type */ memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 63809e7bbc02..116255ad5661 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -53,6 +53,16 @@ static const struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; +static inline void hci_uart_proto_lock(struct hci_uart *hu) +{ + mutex_lock(&hu->proto_lock); +} + +static inline void hci_uart_proto_unlock(struct hci_uart *hu) +{ + mutex_unlock(&hu->proto_lock); +} + int hci_uart_register_proto(const struct hci_uart_proto *p) { if (p->id >= HCI_UART_MAX_PROTO) @@ -113,8 +123,15 @@ static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) { struct sk_buff *skb = hu->tx_skb; - if (!skb) + if (!skb) { + hci_uart_proto_lock(hu); + if (!hu->proto) { + hci_uart_proto_unlock(hu); + return NULL; + } skb = hu->proto->dequeue(hu); + hci_uart_proto_unlock(hu); + } else hu->tx_skb = NULL; @@ -142,6 +159,8 @@ static void hci_uart_write_work(struct work_struct *work) struct hci_dev *hdev = hu->hdev; struct sk_buff *skb; + BT_DBG("hu %pK hdev %pK tty %pK", hu, hdev, tty); + /* REVISIT: should we cope with bad skbs or ->write() returning * and error value ? */ @@ -205,7 +224,7 @@ int hci_uart_init_ready(struct hci_uart *hu) /* Initialize device */ static int hci_uart_open(struct hci_dev *hdev) { - BT_DBG("%s %p", hdev->name, hdev); + BT_DBG("%s %pK", hdev->name, hdev); /* Nothing to do for UART driver */ return 0; @@ -217,7 +236,7 @@ static int hci_uart_flush(struct hci_dev *hdev) struct hci_uart *hu = hci_get_drvdata(hdev); struct tty_struct *tty = hu->tty; - BT_DBG("hdev %p tty %p", hdev, tty); + BT_DBG("hdev %pK tty %pK", hdev, tty); if (hu->tx_skb) { kfree_skb(hu->tx_skb); hu->tx_skb = NULL; @@ -236,7 +255,7 @@ static int hci_uart_flush(struct hci_dev *hdev) /* Close device */ static int hci_uart_close(struct hci_dev *hdev) { - BT_DBG("hdev %p", hdev); + BT_DBG("hdev %pK", hdev); hci_uart_flush(hdev); hdev->flush = NULL; @@ -450,7 +469,7 @@ static int hci_uart_tty_open(struct tty_struct *tty) { struct hci_uart *hu; - BT_DBG("tty %p", tty); + BT_DBG("tty %pK", tty); /* Error if the tty has no write op instead of leaving an exploitable hole */ @@ -470,6 +489,9 @@ static int hci_uart_tty_open(struct tty_struct *tty) INIT_WORK(&hu->init_ready, hci_uart_init_work); INIT_WORK(&hu->write_work, hci_uart_write_work); + spin_lock_init(&hu->rx_lock); + mutex_init(&hu->proto_lock); + /* Flush any pending characters in the driver and line discipline. */ /* FIXME: why is this needed. Note don't use ldisc_ref here as the @@ -492,7 +514,7 @@ static void hci_uart_tty_close(struct tty_struct *tty) struct hci_uart *hu = tty->disc_data; struct hci_dev *hdev; - BT_DBG("tty %p", tty); + BT_DBG("tty %pK", tty); /* Detach from the tty */ tty->disc_data = NULL; @@ -501,8 +523,11 @@ static void hci_uart_tty_close(struct tty_struct *tty) return; hdev = hu->hdev; - if (hdev) + if (hdev) { hci_uart_close(hdev); + if (test_bit(HCI_UART_REGISTERED, &hu->flags)) + hci_unregister_dev(hdev); + } cancel_work_sync(&hu->write_work); @@ -512,10 +537,18 @@ static void hci_uart_tty_close(struct tty_struct *tty) hci_unregister_dev(hdev); hci_free_dev(hdev); } + hci_uart_proto_lock(hu); hu->proto->close(hu); + hu->proto = NULL; + hci_uart_proto_unlock(hu); } clear_bit(HCI_UART_PROTO_SET, &hu->flags); + cancel_work_sync(&hu->write_work); + + if (hdev) + hci_free_dev(hdev); + mutex_destroy(&hu->proto_lock); kfree(hu); } diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index ecfb9ed2cff6..e24de668d3b0 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -231,11 +231,11 @@ static void qca_wq_awake_device(struct work_struct *work) BT_DBG("hu %p wq awake device", hu); + spin_lock(&qca->hci_ibs_lock); + /* Vote for serial clock */ serial_clock_vote(HCI_IBS_TX_VOTE_CLOCK_ON, hu); - spin_lock(&qca->hci_ibs_lock); - /* Send wake indication to device */ if (send_hci_ibs_cmd(HCI_IBS_WAKE_IND, hu) < 0) BT_ERR("Failed to send WAKE to device"); @@ -260,9 +260,10 @@ static void qca_wq_awake_rx(struct work_struct *work) BT_DBG("hu %p wq awake rx", hu); + spin_lock(&qca->hci_ibs_lock); + serial_clock_vote(HCI_IBS_RX_VOTE_CLOCK_ON, hu); - spin_lock(&qca->hci_ibs_lock); qca->rx_ibs_state = HCI_IBS_RX_AWAKE; /* Always acknowledge device wake up, @@ -287,7 +288,11 @@ static void qca_wq_serial_rx_clock_vote_off(struct work_struct *work) BT_DBG("hu %p rx clock vote off", hu); + spin_lock(&qca->hci_ibs_lock); + serial_clock_vote(HCI_IBS_RX_VOTE_CLOCK_OFF, hu); + + spin_unlock(&qca->hci_ibs_lock); } static void qca_wq_serial_tx_clock_vote_off(struct work_struct *work) @@ -298,6 +303,8 @@ static void qca_wq_serial_tx_clock_vote_off(struct work_struct *work) BT_DBG("hu %p tx clock vote off", hu); + spin_lock(&qca->hci_ibs_lock); + /* Run HCI tx handling unlocked */ hci_uart_tx_wakeup(hu); @@ -305,6 +312,8 @@ static void qca_wq_serial_tx_clock_vote_off(struct work_struct *work) * It is up to the tty driver to pend the clocks off until tx done. */ serial_clock_vote(HCI_IBS_TX_VOTE_CLOCK_OFF, hu); + + spin_unlock(&qca->hci_ibs_lock); } static void hci_ibs_tx_idle_timeout(unsigned long arg) @@ -520,8 +529,12 @@ static int qca_close(struct hci_uart *hu) BT_DBG("hu %p qca close", hu); + spin_lock(&qca->hci_ibs_lock); + serial_clock_vote(HCI_IBS_VOTE_STATS_UPDATE, hu); + spin_unlock(&qca->hci_ibs_lock); + skb_queue_purge(&qca->tx_wait_q); skb_queue_purge(&qca->txq); del_timer(&qca->tx_idle_timer); diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h index 82d6a3886868..c08b27c327d6 100644 --- a/drivers/bluetooth/hci_uart.h +++ b/drivers/bluetooth/hci_uart.h @@ -82,6 +82,7 @@ struct hci_uart { struct work_struct write_work; const struct hci_uart_proto *proto; + struct mutex proto_lock; void *priv; struct sk_buff *tx_skb; |
