Merge "bluetooth: Add snapshot of bluetooth components"

This commit is contained in:
Linux Build Service Account 2018-01-03 16:44:10 -08:00 committed by Gerrit - the friendly Code Review server
commit 3e58ae46b7
11 changed files with 2530 additions and 0 deletions

View File

@ -0,0 +1,61 @@
* 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 */
};

View File

@ -0,0 +1,20 @@
* 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];
};

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,777 @@
/* 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);

View File

@ -0,0 +1,598 @@
/* 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/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 = ((rates == 44100) || (rates == 88200)) ?
SLIM_PUSH : SLIM_AUTO_ISO;
prop.baser = ((rates == 44100) || (rates == 88200)) ?
SLIM_RATE_11025HZ : SLIM_RATE_4000HZ;
prop.dataf = ((rates == 48000) || (rates == 44100) ||
(rates == 88200) || (rates == 96000)) ?
SLIM_CH_DATAF_NOT_DEFINED : SLIM_CH_DATAF_LPCM_AUDIO;
prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE;
prop.ratem = ((rates == 44100) || (rates == 88200)) ?
(rates/11025) : (rates/4000);
prop.sampleszbits = 16;
ch_h[0] = ch->ch_hdl;
ch_h[1] = (grp) ? (ch+1)->ch_hdl : 0;
BTFMSLIM_INFO("channel define - prot:%d, dataf:%d, auxf:%d",
prop.prot, prop.dataf, prop.auxf);
BTFMSLIM_INFO("channel define - rates:%d, baser:%d, ratem:%d",
rates, prop.baser, prop.ratem);
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);
/* For 44.1/88.2 Khz A2DP Rx, disconnect the port first */
if (rxport &&
(btfmslim->sample_rate == 44100 ||
btfmslim->sample_rate == 88200)) {
BTFMSLIM_DBG("disconnecting the ports, removing the channel");
ret = slim_disconnect_ports(btfmslim->slim_pgd,
&ch->port_hdl, 1);
if (ret < 0) {
BTFMSLIM_ERR("slim_disconnect_ports failed ret[%d]",
ret);
}
}
/* 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);
if (btfmslim->sample_rate != 44100 &&
btfmslim->sample_rate != 88200) {
ret = slim_disconnect_ports(btfmslim->slim_pgd,
&ch->port_hdl, 1);
if (ret < 0) {
BTFMSLIM_ERR("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);
if (ret) {
BTFMSLIM_ERR("error, registering slimbus codec failed");
goto free;
}
ret = bt_register_slimdev(&slim->dev);
if (ret < 0) {
btfm_slim_unregister_codec(&slim->dev);
goto free;
}
return ret;
free:
slim_remove_device(&btfm_slim->slim_ifd);
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);
kfree(btfm_slim);
BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_pgd");
slim_remove_device(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");

View File

@ -0,0 +1,173 @@
/* 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);
/**
* btfm_slim_unregister_codec: Unregister codec driver in slimbus device node
* @dev: device node
* Returns:
* VOID
*/
void btfm_slim_unregister_codec(struct device *dev);
#endif /* BTFM_SLIM_H */

View File

@ -0,0 +1,447 @@
/* 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;
}
static 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 {
if (!ch)
return -EINVAL;
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;
}
if (!slot)
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",
/* 8/16/44.1/48/88.2/96 Khz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 96000,
.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;
}
void btfm_slim_unregister_codec(struct device *dev)
{
BTFMSLIM_DBG("");
/* Unregister Codec driver */
snd_soc_unregister_codec(dev);
}
MODULE_DESCRIPTION("BTFM Slimbus Codec driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,187 @@
/* 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, &reg_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, &reg_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 rxport_num = 0;
uint16_t reg;
BTFMSLIM_DBG("port(%d) enable(%d)", port_num, enable);
if (rxport) {
BTFMSLIM_DBG("sample rate is %d", btfmslim->sample_rate);
if (enable &&
btfmslim->sample_rate != 44100 &&
btfmslim->sample_rate != 88200) {
BTFMSLIM_DBG("setting multichannel bit");
/* For SCO Rx, A2DP Rx other than 44.1 and 88.2Khz */
if (port_num < 24) {
rxport_num = port_num - 16;
reg_val = 0x01 << rxport_num;
reg = CHRK_SB_PGD_RX_PORTn_MULTI_CHNL_0(
rxport_num);
} else {
rxport_num = port_num - 24;
reg_val = 0x01 << rxport_num;
reg = CHRK_SB_PGD_RX_PORTn_MULTI_CHNL_1(
rxport_num);
}
BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, 1, &reg_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, &reg_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, &reg_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, &reg_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, &reg_val, IFD);
if (ret)
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
error:
return ret;
}

View File

@ -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 */

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 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.
*
*/
#ifndef __LINUX_BLUETOOTH_POWER_H
#define __LINUX_BLUETOOTH_POWER_H
/*
* voltage regulator information required for configuring the
* bluetooth chipset
*/
struct bt_power_vreg_data {
/* voltage regulator handle */
struct regulator *reg;
/* regulator name */
const char *name;
/* voltage levels to be set */
unsigned int low_vol_level;
unsigned int high_vol_level;
/* current level to be set */
unsigned int load_uA;
/*
* is set voltage supported for this regulator?
* false => set voltage is not supported
* true => set voltage is supported
*
* Some regulators (like gpio-regulators, LVS (low voltage swtiches)
* PMIC regulators) dont have the capability to call
* regulator_set_voltage or regulator_set_optimum_mode
* Use this variable to indicate if its a such regulator or not
*/
bool set_voltage_sup;
/* is this regulator enabled? */
bool is_enabled;
};
struct bt_power_clk_data {
/* clock regulator handle */
struct clk *clk;
/* clock name */
const char *name;
/* is this clock enabled? */
bool is_enabled;
};
/*
* Platform data for the bluetooth power driver.
*/
struct bluetooth_power_platform_data {
/* Bluetooth reset gpio */
int bt_gpio_sys_rst;
struct device *slim_dev;
/* VDDIO voltage regulator */
struct bt_power_vreg_data *bt_vdd_io;
/* VDD_PA voltage regulator */
struct bt_power_vreg_data *bt_vdd_pa;
/* VDD_LDOIN voltage regulator */
struct bt_power_vreg_data *bt_vdd_ldo;
/* VDD_XTAL voltage regulator */
struct bt_power_vreg_data *bt_vdd_xtal;
/* VDD_CORE voltage regulator */
struct bt_power_vreg_data *bt_vdd_core;
/* Optional: chip power down gpio-regulator
* chip power down data is required when bluetooth module
* and other modules like wifi co-exist in a single chip and
* shares a common gpio to bring chip out of reset.
*/
struct bt_power_vreg_data *bt_chip_pwd;
/* bluetooth reference clock */
struct bt_power_clk_data *bt_chip_clk;
/* Optional: Bluetooth power setup function */
int (*bt_power_setup)(int);
};
int bt_register_slimdev(struct device *dev);
#define BT_CMD_SLIM_TEST 0xbfac
#define BT_CMD_PWR_CTRL 0xbfad
#endif /* __LINUX_BLUETOOTH_POWER_H */