Merge "asoc: codecs: bt-swr: Implement driver for BT Soundwire"
This commit is contained in:
commit
3a34edef4e
@ -234,6 +234,10 @@ ifdef CONFIG_SND_SWR_HAPTICS
|
||||
SWR_HAP_OBJS += swr-haptics.o
|
||||
endif
|
||||
|
||||
ifdef CONFIG_LPASS_BT_SWR
|
||||
LPASS_BT_SWR_OBJS += lpass-bt-swr.o
|
||||
endif
|
||||
|
||||
LINUX_INC += -Iinclude/linux
|
||||
|
||||
INCS += $(COMMON_INC) \
|
||||
@ -279,6 +283,7 @@ ifeq ($(KERNEL_BUILD), 1)
|
||||
obj-y += wsa884x/
|
||||
obj-y += wsa883x/
|
||||
obj-y += rouleur/
|
||||
obj-y += ./
|
||||
endif
|
||||
# Module information used by KBuild framework
|
||||
obj-$(CONFIG_WCD9XXX_CODEC_CORE) += wcd_core_dlkm.o
|
||||
@ -318,5 +323,8 @@ hdmi_dlkm-y := $(HDMICODEC_OBJS)
|
||||
obj-$(CONFIG_SND_SWR_HAPTICS) += swr_haptics_dlkm.o
|
||||
swr_haptics_dlkm-y := $(SWR_HAP_OBJS)
|
||||
|
||||
obj-$(CONFIG_LPASS_BT_SWR) += lpass_bt_swr_dlkm.o
|
||||
lpass_bt_swr_dlkm-y := $(LPASS_BT_SWR_OBJS)
|
||||
|
||||
# inject some build related information
|
||||
DEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\"
|
||||
|
672
asoc/codecs/lpass-bt-swr.c
Normal file
672
asoc/codecs/lpass-bt-swr.c
Normal file
@ -0,0 +1,672 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <soc/swr-common.h>
|
||||
#include <asoc/msm-cdc-pinctrl.h>
|
||||
#include <dsp/digital-cdc-rsc-mgr.h>
|
||||
#include <soc/swr-wcd.h>
|
||||
#include <soc/snd_event.h>
|
||||
|
||||
#define DRV_NAME "lpass-bt-swr"
|
||||
|
||||
/* pm runtime auto suspend timer in msecs */
|
||||
#define LPASS_BT_SWR_AUTO_SUSPEND_DELAY 100 /* delay in msec */
|
||||
|
||||
#define LPASS_BT_SWR_STRING_LEN 80
|
||||
|
||||
#define LPASS_BT_SWR_CHILD_DEVICES_MAX 1
|
||||
|
||||
/* Hold instance to soundwire platform device */
|
||||
struct lpass_bt_swr_ctrl_data {
|
||||
struct platform_device *lpass_bt_swr_pdev;
|
||||
};
|
||||
|
||||
struct lpass_bt_swr_ctrl_platform_data {
|
||||
void *handle; /* holds parent private data */
|
||||
int (*read)(void *handle, int reg);
|
||||
int (*write)(void *handle, int reg, int val);
|
||||
int (*bulk_write)(void *handle, u32 *reg, u32 *val, size_t len);
|
||||
int (*clk)(void *handle, bool enable);
|
||||
int (*core_vote)(void *handle, bool enable);
|
||||
int (*handle_irq)(void *handle,
|
||||
irqreturn_t (*swrm_irq_handler)(int irq,
|
||||
void *data),
|
||||
void *swrm_handle,
|
||||
int action);
|
||||
};
|
||||
|
||||
struct lpass_bt_swr_priv {
|
||||
struct device *dev;
|
||||
struct mutex vote_lock;
|
||||
struct mutex swr_clk_lock;
|
||||
struct mutex ssr_lock;
|
||||
bool dev_up;
|
||||
bool initial_boot;
|
||||
|
||||
struct clk *lpass_core_hw_vote;
|
||||
struct clk *lpass_audio_hw_vote;
|
||||
int core_hw_vote_count;
|
||||
int core_audio_vote_count;
|
||||
int swr_clk_users;
|
||||
struct clk *clk_handle;
|
||||
struct clk *clk_handle_2x;
|
||||
|
||||
struct lpass_bt_swr_ctrl_data *swr_ctrl_data;
|
||||
struct lpass_bt_swr_ctrl_platform_data swr_plat_data;
|
||||
struct work_struct lpass_bt_swr_add_child_devices_work;
|
||||
struct platform_device *pdev_child_devices
|
||||
[LPASS_BT_SWR_CHILD_DEVICES_MAX];
|
||||
int child_count;
|
||||
|
||||
struct device_node *bt_swr_gpio_p;
|
||||
|
||||
/* Entry for version info */
|
||||
struct snd_info_entry *entry;
|
||||
struct snd_info_entry *version_entry;
|
||||
|
||||
struct blocking_notifier_head notifier;
|
||||
struct device *clk_dev;
|
||||
};
|
||||
|
||||
static struct lpass_bt_swr_priv *lpass_bt_priv;
|
||||
|
||||
static void lpass_bt_swr_add_child_devices(struct work_struct *work)
|
||||
{
|
||||
struct lpass_bt_swr_priv *priv;
|
||||
struct platform_device *pdev;
|
||||
struct device_node *node;
|
||||
struct lpass_bt_swr_ctrl_data *swr_ctrl_data = NULL, *temp;
|
||||
int ret;
|
||||
u16 count = 0, ctrl_num = 0;
|
||||
struct lpass_bt_swr_ctrl_platform_data *platdata;
|
||||
char plat_dev_name[LPASS_BT_SWR_STRING_LEN];
|
||||
|
||||
priv = container_of(work, struct lpass_bt_swr_priv,
|
||||
lpass_bt_swr_add_child_devices_work);
|
||||
if (!priv) {
|
||||
pr_err("%s: Memory for priv does not exist\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
if (!priv->dev || !priv->dev->of_node) {
|
||||
dev_err(priv->dev,
|
||||
"%s: DT node for priv does not exist\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
platdata = &priv->swr_plat_data;
|
||||
priv->child_count = 0;
|
||||
|
||||
for_each_available_child_of_node(priv->dev->of_node, node) {
|
||||
if (strnstr(node->name, "bt_swr_mstr",
|
||||
strlen("bt_swr_mstr")) != NULL)
|
||||
strscpy(plat_dev_name, "bt_swr_mstr",
|
||||
(LPASS_BT_SWR_STRING_LEN - 1));
|
||||
else
|
||||
continue;
|
||||
|
||||
pdev = platform_device_alloc(plat_dev_name, -1);
|
||||
if (!pdev) {
|
||||
dev_err(priv->dev, "%s: pdev memory alloc failed\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
return;
|
||||
}
|
||||
pdev->dev.parent = priv->dev;
|
||||
pdev->dev.of_node = node;
|
||||
|
||||
ret = platform_device_add_data(pdev, platdata,
|
||||
sizeof(*platdata));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"%s: cannot add plat data ctrl:%d\n",
|
||||
__func__, ctrl_num);
|
||||
goto fail_pdev_add;
|
||||
}
|
||||
|
||||
temp = krealloc(swr_ctrl_data,
|
||||
(ctrl_num + 1) * sizeof(
|
||||
struct lpass_bt_swr_ctrl_data),
|
||||
GFP_KERNEL);
|
||||
if (!temp) {
|
||||
dev_err(&pdev->dev, "out of memory\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail_pdev_add;
|
||||
}
|
||||
swr_ctrl_data = temp;
|
||||
swr_ctrl_data[ctrl_num].lpass_bt_swr_pdev = pdev;
|
||||
ctrl_num++;
|
||||
|
||||
dev_dbg(&pdev->dev, "%s: Adding soundwire ctrl device(s)\n",
|
||||
__func__);
|
||||
priv->swr_ctrl_data = swr_ctrl_data;
|
||||
|
||||
ret = platform_device_add(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"%s: Cannot add platform device\n",
|
||||
__func__);
|
||||
goto fail_pdev_add;
|
||||
}
|
||||
|
||||
if (priv->child_count < LPASS_BT_SWR_CHILD_DEVICES_MAX)
|
||||
priv->pdev_child_devices[
|
||||
priv->child_count++] = pdev;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
fail_pdev_add:
|
||||
for (count = 0; count < priv->child_count; count++)
|
||||
platform_device_put(priv->pdev_child_devices[count]);
|
||||
}
|
||||
|
||||
|
||||
bool lpass_bt_swr_check_core_votes(struct lpass_bt_swr_priv *priv)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
mutex_lock(&priv->vote_lock);
|
||||
if (!priv->dev_up ||
|
||||
(priv->lpass_core_hw_vote && !priv->core_hw_vote_count) ||
|
||||
(priv->lpass_audio_hw_vote && !priv->core_audio_vote_count))
|
||||
ret = false;
|
||||
mutex_unlock(&priv->vote_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_bt_swr_core_vote(void *handle, bool enable)
|
||||
{
|
||||
int rc = 0;
|
||||
struct lpass_bt_swr_priv *priv = (struct lpass_bt_swr_priv *) handle;
|
||||
|
||||
if (priv == NULL) {
|
||||
pr_err_ratelimited("%s: priv data is NULL\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!priv->dev_up && enable) {
|
||||
pr_err("%s: adsp is not up\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
pm_runtime_get_sync(priv->dev);
|
||||
if (lpass_bt_swr_check_core_votes(priv))
|
||||
rc = 0;
|
||||
else
|
||||
rc = -ENOTSYNC;
|
||||
} else {
|
||||
pm_runtime_put_autosuspend(priv->dev);
|
||||
pm_runtime_mark_last_busy(priv->dev);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lpass_bt_swr_mclk_enable(
|
||||
struct lpass_bt_swr_priv *priv,
|
||||
bool mclk_enable)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
dev_dbg(priv->dev, "%s: mclk_enable = %u\n",
|
||||
__func__, mclk_enable);
|
||||
|
||||
ret = lpass_bt_swr_core_vote(priv, true);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: request core vote failed\n",
|
||||
__func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (mclk_enable) {
|
||||
ret = clk_prepare_enable(priv->clk_handle);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: bt_swr_clk enable failed\n", __func__);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (priv->clk_handle_2x) {
|
||||
ret = clk_prepare_enable(priv->clk_handle_2x);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: bt_swr_2x_clk enable failed\n", __func__);
|
||||
clk_disable_unprepare(priv->clk_handle);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clk_disable_unprepare(priv->clk_handle);
|
||||
if (priv->clk_handle_2x)
|
||||
clk_disable_unprepare(priv->clk_handle_2x);
|
||||
}
|
||||
|
||||
error:
|
||||
lpass_bt_swr_core_vote(priv, false);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpass_bt_swrm_clock(void *handle, bool enable)
|
||||
{
|
||||
struct lpass_bt_swr_priv *priv = (struct lpass_bt_swr_priv *) handle;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&priv->swr_clk_lock);
|
||||
|
||||
dev_dbg(priv->dev, "%s: swrm clock %s\n",
|
||||
__func__, (enable ? "enable" : "disable"));
|
||||
if (enable) {
|
||||
pm_runtime_get_sync(priv->dev);
|
||||
if (priv->swr_clk_users == 0) {
|
||||
ret = msm_cdc_pinctrl_select_active_state(
|
||||
priv->bt_swr_gpio_p);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: bt swr pinctrl enable failed\n",
|
||||
__func__);
|
||||
pm_runtime_mark_last_busy(priv->dev);
|
||||
pm_runtime_put_autosuspend(priv->dev);
|
||||
goto exit;
|
||||
}
|
||||
ret = lpass_bt_swr_mclk_enable(priv, true);
|
||||
if (ret < 0) {
|
||||
msm_cdc_pinctrl_select_sleep_state(
|
||||
priv->bt_swr_gpio_p);
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: lpass bt swr request clock enable failed\n",
|
||||
__func__);
|
||||
pm_runtime_mark_last_busy(priv->dev);
|
||||
pm_runtime_put_autosuspend(priv->dev);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
priv->swr_clk_users++;
|
||||
pm_runtime_mark_last_busy(priv->dev);
|
||||
pm_runtime_put_autosuspend(priv->dev);
|
||||
} else {
|
||||
if (priv->swr_clk_users <= 0) {
|
||||
dev_err_ratelimited(priv->dev, "%s: clock already disabled\n",
|
||||
__func__);
|
||||
priv->swr_clk_users = 0;
|
||||
goto exit;
|
||||
}
|
||||
priv->swr_clk_users--;
|
||||
if (priv->swr_clk_users == 0) {
|
||||
lpass_bt_swr_mclk_enable(priv, false);
|
||||
ret = msm_cdc_pinctrl_select_sleep_state(
|
||||
priv->bt_swr_gpio_p);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: bt swr pinctrl disable failed\n",
|
||||
__func__);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
dev_dbg(priv->dev, "%s: swrm clock users %d\n",
|
||||
__func__, priv->swr_clk_users);
|
||||
exit:
|
||||
mutex_unlock(&priv->swr_clk_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lpass_bt_swr_ssr_disable(struct device *dev, void *data)
|
||||
{
|
||||
struct lpass_bt_swr_priv *priv = data;
|
||||
|
||||
if (!priv->dev_up) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: already disabled\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&priv->ssr_lock);
|
||||
priv->dev_up = false;
|
||||
mutex_unlock(&priv->ssr_lock);
|
||||
|
||||
swrm_wcd_notify(priv->swr_ctrl_data->lpass_bt_swr_pdev,
|
||||
SWR_DEVICE_SSR_DOWN, NULL);
|
||||
|
||||
}
|
||||
|
||||
static int lpass_bt_swr_ssr_enable(struct device *dev, void *data)
|
||||
{
|
||||
struct lpass_bt_swr_priv *priv = data;
|
||||
int ret;
|
||||
|
||||
if (priv->initial_boot) {
|
||||
priv->initial_boot = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&priv->ssr_lock);
|
||||
priv->dev_up = true;
|
||||
mutex_unlock(&priv->ssr_lock);
|
||||
|
||||
mutex_lock(&priv->swr_clk_lock);
|
||||
|
||||
dev_dbg(priv->dev, "%s: swrm clock users %d\n",
|
||||
__func__, priv->swr_clk_users);
|
||||
|
||||
lpass_bt_swr_mclk_enable(priv, false);
|
||||
ret = msm_cdc_pinctrl_select_sleep_state(
|
||||
priv->bt_swr_gpio_p);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: bt swr pinctrl disable failed\n",
|
||||
__func__);
|
||||
}
|
||||
|
||||
if (priv->swr_clk_users > 0) {
|
||||
lpass_bt_swr_mclk_enable(priv, true);
|
||||
ret = msm_cdc_pinctrl_select_active_state(
|
||||
priv->bt_swr_gpio_p);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(priv->dev,
|
||||
"%s: bt swr pinctrl enable failed\n",
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&priv->swr_clk_lock);
|
||||
|
||||
swrm_wcd_notify(priv->swr_ctrl_data->lpass_bt_swr_pdev,
|
||||
SWR_DEVICE_SSR_UP, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_event_ops lpass_bt_swr_ssr_ops = {
|
||||
.enable = lpass_bt_swr_ssr_enable,
|
||||
.disable = lpass_bt_swr_ssr_disable,
|
||||
};
|
||||
|
||||
static int lpass_bt_swr_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct lpass_bt_swr_priv *priv;
|
||||
int ret;
|
||||
struct clk *lpass_core_hw_vote = NULL;
|
||||
struct clk *lpass_audio_hw_vote = NULL;
|
||||
struct clk *bt_swr_clk = NULL;
|
||||
struct clk *bt_swr_2x_clk = NULL;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(struct lpass_bt_swr_priv),
|
||||
GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&priv->notifier);
|
||||
priv->dev = &pdev->dev;
|
||||
priv->dev_up = true;
|
||||
priv->core_hw_vote_count = 0;
|
||||
priv->core_audio_vote_count = 0;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, priv);
|
||||
mutex_init(&priv->vote_lock);
|
||||
mutex_init(&priv->swr_clk_lock);
|
||||
mutex_init(&priv->ssr_lock);
|
||||
|
||||
priv->bt_swr_gpio_p = of_parse_phandle(pdev->dev.of_node,
|
||||
"qcom,bt-swr-gpios", 0);
|
||||
if (!priv->bt_swr_gpio_p) {
|
||||
dev_err(&pdev->dev, "%s: swr_gpios handle not provided!\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (msm_cdc_pinctrl_get_state(priv->bt_swr_gpio_p) < 0) {
|
||||
dev_info(&pdev->dev, "%s: failed to get swr pin state\n",
|
||||
__func__);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
/* Register LPASS core hw vote */
|
||||
lpass_core_hw_vote = devm_clk_get(&pdev->dev, "lpass_core_hw_vote");
|
||||
if (IS_ERR(lpass_core_hw_vote)) {
|
||||
ret = PTR_ERR(lpass_core_hw_vote);
|
||||
dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n",
|
||||
__func__, "lpass_core_hw_vote", ret);
|
||||
lpass_core_hw_vote = NULL;
|
||||
ret = 0;
|
||||
}
|
||||
priv->lpass_core_hw_vote = lpass_core_hw_vote;
|
||||
|
||||
/* Register LPASS audio hw vote */
|
||||
lpass_audio_hw_vote = devm_clk_get(&pdev->dev, "lpass_audio_hw_vote");
|
||||
if (IS_ERR(lpass_audio_hw_vote)) {
|
||||
ret = PTR_ERR(lpass_audio_hw_vote);
|
||||
dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n",
|
||||
__func__, "lpass_audio_hw_vote", ret);
|
||||
lpass_audio_hw_vote = NULL;
|
||||
ret = 0;
|
||||
}
|
||||
priv->lpass_audio_hw_vote = lpass_audio_hw_vote;
|
||||
|
||||
/* Register bt swr clk vote */
|
||||
bt_swr_clk = devm_clk_get(&pdev->dev, "bt_swr_mclk_clk");
|
||||
if (IS_ERR(bt_swr_clk)) {
|
||||
ret = PTR_ERR(bt_swr_clk);
|
||||
dev_err(&pdev->dev, "%s: clk get %s failed %d\n",
|
||||
__func__, "bt_swr_clk", ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
priv->clk_handle = bt_swr_clk;
|
||||
|
||||
/* Register bt swr 2x clk vote */
|
||||
bt_swr_2x_clk = devm_clk_get(&pdev->dev, "bt_swr_mclk_clk_2x");
|
||||
if (IS_ERR(bt_swr_2x_clk)) {
|
||||
ret = PTR_ERR(bt_swr_2x_clk);
|
||||
dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n",
|
||||
__func__, "bt_swr_2x_clk", ret);
|
||||
bt_swr_2x_clk = NULL;
|
||||
ret = 0;
|
||||
}
|
||||
priv->clk_handle_2x = bt_swr_2x_clk;
|
||||
|
||||
/* Add soundwire child devices. */
|
||||
INIT_WORK(&priv->lpass_bt_swr_add_child_devices_work,
|
||||
lpass_bt_swr_add_child_devices);
|
||||
|
||||
priv->swr_plat_data.handle = (void *)priv;
|
||||
priv->swr_plat_data.read = NULL;
|
||||
priv->swr_plat_data.write = NULL;
|
||||
priv->swr_plat_data.bulk_write = NULL;
|
||||
priv->swr_plat_data.clk = lpass_bt_swrm_clock;
|
||||
priv->swr_plat_data.core_vote = lpass_bt_swr_core_vote;
|
||||
priv->swr_plat_data.handle_irq = NULL;
|
||||
|
||||
lpass_bt_priv = priv;
|
||||
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, LPASS_BT_SWR_AUTO_SUSPEND_DELAY);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
pm_suspend_ignore_children(&pdev->dev, true);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
/* call scheduler to add child devices. */
|
||||
schedule_work(&priv->lpass_bt_swr_add_child_devices_work);
|
||||
|
||||
priv->initial_boot = true;
|
||||
ret = snd_event_client_register(priv->dev, &lpass_bt_swr_ssr_ops, priv);
|
||||
if (!ret) {
|
||||
snd_event_notify(priv->dev, SND_EVENT_UP);
|
||||
dev_err(&pdev->dev, "%s: Registered SSR ops\n", __func__);
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"%s: Registration with SND event FWK failed ret = %d\n",
|
||||
__func__, ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpass_bt_swr_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lpass_bt_swr_priv *priv = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
if (!priv)
|
||||
return -EINVAL;
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
of_platform_depopulate(&pdev->dev);
|
||||
mutex_destroy(&priv->vote_lock);
|
||||
mutex_destroy(&priv->swr_clk_lock);
|
||||
mutex_destroy(&priv->ssr_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int lpass_bt_swr_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct lpass_bt_swr_priv *priv = platform_get_drvdata(pdev);
|
||||
int ret = 0;
|
||||
|
||||
dev_dbg(dev, "%s, enter\n", __func__);
|
||||
mutex_lock(&priv->vote_lock);
|
||||
if (priv->lpass_core_hw_vote == NULL) {
|
||||
dev_dbg(dev, "%s: Invalid lpass core hw node\n", __func__);
|
||||
goto audio_vote;
|
||||
}
|
||||
|
||||
if (priv->core_hw_vote_count == 0) {
|
||||
ret = digital_cdc_rsc_mgr_hw_vote_enable(priv->lpass_core_hw_vote, dev);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(dev, "%s:lpass core hw enable failed\n",
|
||||
__func__);
|
||||
goto audio_vote;
|
||||
}
|
||||
}
|
||||
priv->core_hw_vote_count++;
|
||||
|
||||
audio_vote:
|
||||
if (priv->lpass_audio_hw_vote == NULL) {
|
||||
dev_dbg(dev, "%s: Invalid lpass audio hw node\n", __func__);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (priv->core_audio_vote_count == 0) {
|
||||
ret = digital_cdc_rsc_mgr_hw_vote_enable(priv->lpass_audio_hw_vote, dev);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(dev, "%s:lpass audio hw enable failed\n",
|
||||
__func__);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
priv->core_audio_vote_count++;
|
||||
|
||||
done:
|
||||
mutex_unlock(&priv->vote_lock);
|
||||
dev_dbg(dev, "%s, leave, hw_vote %d, audio_vote %d\n", __func__,
|
||||
priv->core_hw_vote_count, priv->core_audio_vote_count);
|
||||
pm_runtime_set_autosuspend_delay(priv->dev, LPASS_BT_SWR_AUTO_SUSPEND_DELAY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lpass_bt_swr_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct lpass_bt_swr_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
dev_dbg(dev, "%s, enter\n", __func__);
|
||||
mutex_lock(&priv->vote_lock);
|
||||
if (priv->lpass_core_hw_vote != NULL) {
|
||||
if (--priv->core_hw_vote_count == 0)
|
||||
digital_cdc_rsc_mgr_hw_vote_disable(
|
||||
priv->lpass_core_hw_vote, dev);
|
||||
if (priv->core_hw_vote_count < 0)
|
||||
priv->core_hw_vote_count = 0;
|
||||
} else {
|
||||
dev_dbg(dev, "%s: Invalid lpass core hw node\n",
|
||||
__func__);
|
||||
}
|
||||
|
||||
if (priv->lpass_audio_hw_vote != NULL) {
|
||||
if (--priv->core_audio_vote_count == 0)
|
||||
digital_cdc_rsc_mgr_hw_vote_disable(
|
||||
priv->lpass_audio_hw_vote, dev);
|
||||
if (priv->core_audio_vote_count < 0)
|
||||
priv->core_audio_vote_count = 0;
|
||||
} else {
|
||||
dev_dbg(dev, "%s: Invalid lpass audio hw node\n",
|
||||
__func__);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->vote_lock);
|
||||
dev_dbg(dev, "%s, leave, hw_vote %d, audio_vote %d\n", __func__,
|
||||
priv->core_hw_vote_count, priv->core_audio_vote_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static const struct of_device_id lpass_bt_swr_dt_match[] = {
|
||||
{.compatible = "qcom,lpass-bt-swr"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lpass_bt_swr_dt_match);
|
||||
|
||||
static const struct dev_pm_ops lpass_bt_swr_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(
|
||||
pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume
|
||||
)
|
||||
SET_RUNTIME_PM_OPS(
|
||||
lpass_bt_swr_runtime_suspend,
|
||||
lpass_bt_swr_runtime_resume,
|
||||
NULL
|
||||
)
|
||||
};
|
||||
|
||||
static struct platform_driver lpass_bt_swr_drv = {
|
||||
.driver = {
|
||||
.name = "lpass-bt-swr",
|
||||
.pm = &lpass_bt_swr_pm_ops,
|
||||
.of_match_table = lpass_bt_swr_dt_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = lpass_bt_swr_probe,
|
||||
.remove = lpass_bt_swr_remove,
|
||||
};
|
||||
|
||||
static int lpass_bt_swr_drv_init(void)
|
||||
{
|
||||
return platform_driver_register(&lpass_bt_swr_drv);
|
||||
}
|
||||
|
||||
static void lpass_bt_swr_drv_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&lpass_bt_swr_drv);
|
||||
}
|
||||
|
||||
static int __init lpass_bt_swr_init(void)
|
||||
{
|
||||
lpass_bt_swr_drv_init();
|
||||
return 0;
|
||||
}
|
||||
module_init(lpass_bt_swr_init);
|
||||
|
||||
static void __exit lpass_bt_swr_exit(void)
|
||||
{
|
||||
lpass_bt_swr_drv_exit();
|
||||
}
|
||||
module_exit(lpass_bt_swr_exit);
|
||||
|
||||
MODULE_SOFTDEP("pre: bt_fm_swr");
|
||||
MODULE_DESCRIPTION("LPASS BT SWR driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user