Browse Source

Merge 44216114e5199a6bc02c002de6fb3e11f8aeba37 on remote branch

Change-Id: Ib86366cb43035354ec09c9a4a3e2d67496fec0c1
Linux Build Service Account 1 year ago
parent
commit
f6a4b792d4

+ 8 - 0
asoc/codecs/Kbuild

@@ -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 - 0
asoc/codecs/lpass-bt-swr.c

@@ -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");

+ 50 - 9
asoc/codecs/wcd9378/wcd9378.c

@@ -1062,11 +1062,11 @@ static int wcd9378_sys_usage_bit_get(
 		case ADC3:
 			if ((snd_soc_component_read(component,
 					WCD9378_TX_NEW_TX_CH34_MUX) &
-					WCD9378_TX_NEW_TX_CH34_MUX_CH3_SEL_SHIFT) == 0x01) {
+					WCD9378_TX_NEW_TX_CH34_MUX_CH3_SEL_MASK) == 0x01) {
 				*sys_usage_bit = TX2_AMIC1_EN;
 			} else if ((snd_soc_component_read(component,
 					WCD9378_TX_NEW_TX_CH34_MUX) &
-					WCD9378_TX_NEW_TX_CH34_MUX_CH3_SEL_SHIFT) == 0x03) {
+					WCD9378_TX_NEW_TX_CH34_MUX_CH3_SEL_MASK) == 0x03) {
 				*sys_usage_bit = TX2_AMIC4_EN;
 			} else {
 				dev_err(component->dev, "%s: unsupport usecase, pls check\n",
@@ -1503,11 +1503,6 @@ static int wcd9378_codec_hphl_dac_event(struct snd_soc_dapm_widget *w,
 				WCD9378_CDC_COMP_CTL_0_HPHL_COMP_EN_MASK, 0x02);
 			wcd9378_rx_connect_port(component, COMP_L, true);
 		}
-
-		if (wcd9378->update_wcd_event)
-			wcd9378->update_wcd_event(wcd9378->handle,
-						SLV_BOLERO_EVT_RX_MUTE,
-						(WCD_RX1 << 0x10));
 		break;
 	case SND_SOC_DAPM_POST_PMD:
 		/*OCP FSM DISABLE*/
@@ -1524,7 +1519,7 @@ static int wcd9378_codec_hphl_dac_event(struct snd_soc_dapm_widget *w,
 		if (wcd9378->comp1_enable) {
 			snd_soc_component_update_bits(component, WCD9378_CDC_COMP_CTL_0,
 				WCD9378_CDC_COMP_CTL_0_HPHL_COMP_EN_MASK, 0x00);
-			wcd9378_rx_connect_port(component, COMP_R, false);
+			wcd9378_rx_connect_port(component, COMP_L, false);
 		}
 		break;
 	default:
@@ -1622,6 +1617,8 @@ static int wcd9378_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w,
 			wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX1 << 0x10));
+		wcd_enable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_HPHL_PDM_WD_INT);
 
 		act_ps = snd_soc_component_read(component, WCD9378_PDE47_ACT_PS);
 		if (act_ps)
@@ -1636,6 +1633,8 @@ static int wcd9378_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w,
 			wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX1 << 0x10 | 0x1));
+		wcd_disable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_HPHL_PDM_WD_INT);
 
 		if (wcd9378->update_wcd_event && wcd9378->comp1_enable)
 			wcd9378->update_wcd_event(wcd9378->handle,
@@ -1676,6 +1675,8 @@ static int wcd9378_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w,
 			wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX2 << 0x10));
+		wcd_enable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_HPHR_PDM_WD_INT);
 
 		act_ps = snd_soc_component_read(component, WCD9378_PDE47_ACT_PS);
 		if (act_ps)
@@ -1690,6 +1691,8 @@ static int wcd9378_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w,
 			wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX2 << 0x10 | 0x1));
+		wcd_disable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_HPHR_PDM_WD_INT);
 
 		if (wcd9378->update_wcd_event && wcd9378->comp2_enable)
 			wcd9378->update_wcd_event(wcd9378->handle,
@@ -1740,12 +1743,15 @@ static int wcd9378_codec_enable_aux_pa(struct snd_soc_dapm_widget *w,
 				wcd9378->update_wcd_event(wcd9378->handle,
 							SLV_BOLERO_EVT_RX_MUTE,
 							(WCD_RX2 << 0x10));
-
+			wcd_enable_irq(&wcd9378->irq_info,
+						WCD9378_IRQ_HPHR_PDM_WD_INT);
 		} else {
 			if (wcd9378->update_wcd_event)
 				wcd9378->update_wcd_event(wcd9378->handle,
 							SLV_BOLERO_EVT_RX_MUTE,
 							(WCD_RX3 << 0x10));
+			wcd_enable_irq(&wcd9378->irq_info,
+						WCD9378_IRQ_AUX_PDM_WD_INT);
 		}
 
 		act_ps = snd_soc_component_read(component, WCD9378_PDE23_ACT_PS);
@@ -1762,11 +1768,15 @@ static int wcd9378_codec_enable_aux_pa(struct snd_soc_dapm_widget *w,
 				wcd9378->update_wcd_event(wcd9378->handle,
 							SLV_BOLERO_EVT_RX_MUTE,
 							(WCD_RX2 << 0x10 | 0x1));
+			wcd_disable_irq(&wcd9378->irq_info,
+						WCD9378_IRQ_HPHR_PDM_WD_INT);
 		} else {
 			if (wcd9378->update_wcd_event)
 				wcd9378->update_wcd_event(wcd9378->handle,
 							SLV_BOLERO_EVT_RX_MUTE,
 							(WCD_RX3 << 0x10 | 0x1));
+			wcd_disable_irq(&wcd9378->irq_info,
+						WCD9378_IRQ_AUX_PDM_WD_INT);
 		}
 		break;
 	};
@@ -1804,11 +1814,16 @@ static int wcd9378_codec_enable_ear_pa(struct snd_soc_dapm_widget *w,
 				wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX1 << 0x10));
+			wcd_enable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_HPHL_PDM_WD_INT);
+
 		} else {
 			if (wcd9378->update_wcd_event)
 				wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX3 << 0x10));
+			wcd_enable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_AUX_PDM_WD_INT);
 		}
 
 		act_ps = snd_soc_component_read(component, WCD9378_PDE23_ACT_PS);
@@ -1826,11 +1841,15 @@ static int wcd9378_codec_enable_ear_pa(struct snd_soc_dapm_widget *w,
 				wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX1 << 0x10 | 0x1));
+			wcd_disable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_HPHL_PDM_WD_INT);
 		} else {
 			if (wcd9378->update_wcd_event)
 				wcd9378->update_wcd_event(wcd9378->handle,
 						SLV_BOLERO_EVT_RX_MUTE,
 						(WCD_RX3 << 0x10 | 0x1));
+			wcd_disable_irq(&wcd9378->irq_info,
+					WCD9378_IRQ_AUX_PDM_WD_INT);
 		}
 		break;
 	};
@@ -4300,6 +4319,13 @@ static struct snd_soc_dai_driver wcd9378_dai[] = {
 	},
 };
 
+static irqreturn_t wcd9378_wd_handle_irq(int irq, void *data)
+{
+	pr_err_ratelimited("%s: Watchdog interrupt for irq =%d triggered\n",
+			   __func__, irq);
+	return IRQ_HANDLED;
+}
+
 static int wcd9378_bind(struct device *dev)
 {
 	int ret = 0;
@@ -4369,6 +4395,18 @@ static int wcd9378_bind(struct device *dev)
 			__func__);
 	wcd9378->tx_swr_dev->slave_irq = wcd9378->virq;
 
+	/* Request for watchdog interrupt */
+	wcd_request_irq(&wcd9378->irq_info, WCD9378_IRQ_HPHR_PDM_WD_INT,
+			"HPHR PDM WD INT", wcd9378_wd_handle_irq, NULL);
+	wcd_request_irq(&wcd9378->irq_info, WCD9378_IRQ_HPHL_PDM_WD_INT,
+			"HPHL PDM WD INT", wcd9378_wd_handle_irq, NULL);
+	wcd_request_irq(&wcd9378->irq_info, WCD9378_IRQ_AUX_PDM_WD_INT,
+			"AUX PDM WD INT", wcd9378_wd_handle_irq, NULL);
+	/* Disable watchdog interrupt for HPH and AUX */
+	wcd_disable_irq(&wcd9378->irq_info, WCD9378_IRQ_HPHR_PDM_WD_INT);
+	wcd_disable_irq(&wcd9378->irq_info, WCD9378_IRQ_HPHL_PDM_WD_INT);
+	wcd_disable_irq(&wcd9378->irq_info, WCD9378_IRQ_AUX_PDM_WD_INT);
+
 	ret = snd_soc_register_component(dev, &soc_codec_dev_wcd9378,
 					wcd9378_dai, ARRAY_SIZE(wcd9378_dai));
 	if (ret) {
@@ -4390,6 +4428,9 @@ static void wcd9378_unbind(struct device *dev)
 {
 	struct wcd9378_priv *wcd9378 = dev_get_drvdata(dev);
 
+	wcd_free_irq(&wcd9378->irq_info, WCD9378_IRQ_HPHR_PDM_WD_INT, NULL);
+	wcd_free_irq(&wcd9378->irq_info, WCD9378_IRQ_HPHL_PDM_WD_INT, NULL);
+	wcd_free_irq(&wcd9378->irq_info, WCD9378_IRQ_AUX_PDM_WD_INT, NULL);
 	wcd_irq_exit(&wcd9378->irq_info, wcd9378->virq);
 	snd_soc_unregister_component(dev);
 	component_unbind_all(dev, wcd9378);

+ 2 - 0
include/soc/soundwire.h

@@ -29,6 +29,7 @@ enum {
 #define SWR_CLK_RATE_4P8MHZ      4800000
 #define SWR_CLK_RATE_9P6MHZ      9600000
 #define SWR_CLK_RATE_11P2896MHZ  11289600
+#define SWR_CLK_RATE_12P288MHZ   12288000
 
 extern struct bus_type soundwire_type;
 struct swr_device;
@@ -132,6 +133,7 @@ struct swr_port_info {
 	u8 req_ch;
 	u8 num_ch;
 	u32 ch_rate;
+	u32 req_ch_rate;
 };
 
 struct swr_port_params {

+ 2 - 0
include/soc/swr-wcd.h

@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
  * Copyright (c) 2015, 2017-2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #ifndef _LINUX_SWR_WCD_H
@@ -34,6 +35,7 @@ struct swr_mstr_port {
 #define MCLK_FREQ		9600000
 #define MCLK_FREQ_LP		600000
 #define MCLK_FREQ_NATIVE	11289600
+#define MCLK_FREQ_12288		12288000
 
 #if (IS_ENABLED(CONFIG_SOUNDWIRE_WCD_CTRL) || \
 	IS_ENABLED(CONFIG_SOUNDWIRE_MSTR_CTRL))

+ 230 - 30
soc/swr-mstr-ctrl.c

@@ -88,6 +88,17 @@
 #define SWRM_REG_GAP_START 0x2C54
 #define SWRM_REG_GAP_END 0x4000
 
+#define SAMPLING_RATE_44P1KHZ   44100
+#define SAMPLING_RATE_88P2KHZ   88200
+#define SAMPLING_RATE_176P4KHZ  176400
+#define SAMPLING_RATE_352P8KHZ  352800
+
+#define SAMPLING_RATE_48KHZ   48000
+#define SAMPLING_RATE_96KHZ   96000
+#define SAMPLING_RATE_192KHZ  192000
+#define SAMPLING_RATE_384KHZ  384000
+
+
 /* pm runtime auto suspend timer in msecs */
 static int auto_suspend_timer = 500;
 module_param(auto_suspend_timer, int, 0664);
@@ -103,7 +114,9 @@ enum {
 enum {
 	MASTER_ID_WSA = 1,
 	MASTER_ID_RX,
-	MASTER_ID_TX
+	MASTER_ID_TX,
+	MASTER_ID_WSA2,
+	MASTER_ID_BT = 5
 };
 
 enum {
@@ -752,7 +765,7 @@ static int swrm_get_port_config(struct swr_mstr_ctrl *swrm)
 	struct port_params *params;
 	u32 usecase = 0;
 
-	if (swrm->master_id == MASTER_ID_TX)
+	if (swrm->master_id == MASTER_ID_TX || swrm->master_id == MASTER_ID_BT)
 		return 0;
 	/* TODO - Send usecase information to avoid checking for master_id */
 	if (swrm->mport_cfg[SWRM_DSD_PARAMS_PORT].port_en &&
@@ -774,11 +787,44 @@ static int swrm_get_port_config(struct swr_mstr_ctrl *swrm)
 	return 0;
 }
 
+static bool swrm_is_fractional_sample_rate(u32 sample_rate)
+{
+	switch (sample_rate) {
+	case SAMPLING_RATE_44P1KHZ:
+	case SAMPLING_RATE_88P2KHZ:
+	case SAMPLING_RATE_176P4KHZ:
+	case SAMPLING_RATE_352P8KHZ:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool swrm_is_flow_ctrl_needed(struct swrm_mports *mport, u32 bus_clk)
+{
+	struct swr_port_info *port_req = NULL;
+
+	list_for_each_entry(port_req, &mport->port_req_list, list) {
+
+		if (swrm_is_fractional_sample_rate(port_req->req_ch_rate) &&
+				(bus_clk % port_req->req_ch_rate)) {
+			pr_debug("%s: flow control needed on Master port ID %d\n",
+					 __func__, port_req->master_port_id);
+			return true;
+		}
+	}
+	return false;
+}
+
 static int swrm_pcm_port_config(struct swr_mstr_ctrl *swrm, u8 port_num,
-				u8 stream_type, bool dir, bool enable)
+				struct swrm_mports *mport, bool enable)
 {
 	u16 reg_addr = 0;
 	u32 reg_val = 0;
+	u8 stream_type = mport->stream_type;
+	bool dir = mport->dir;
+	u32 flow_mode = (dir) ? SWRM_DP_PORT_CONTROL__FLOW_MODE_PULL :
+			SWRM_DP_PORT_CONTROL__FLOW_MODE_PUSH;
 
 	if (!port_num || port_num > SWR_MSTR_PORT_LEN) {
 		dev_err_ratelimited(swrm->dev, "%s: invalid port: %d\n",
@@ -824,6 +870,37 @@ static int swrm_pcm_port_config(struct swr_mstr_ctrl *swrm, u8 port_num,
 				swr_master_write(swrm, SWRM_COMP_FEATURE_CFG, reg_val);
 		}
 	}
+	dev_dbg(swrm->dev, "%s : pcm port %s, reg_val = %d, for addr %x\n",
+			__func__, enable ? "Enabled" : "disabled", reg_val, reg_addr);
+
+	if (swrm_is_flow_ctrl_needed(mport, swrm->bus_clk) && enable) {
+		/*Flow control pull/push mode. */
+		reg_addr = SWRM_DP_PORT_CONTROL(port_num);
+		reg_val = swr_master_read(swrm, reg_addr);
+		reg_val |= flow_mode;
+		swr_master_write(swrm, reg_addr, reg_val);
+
+		/*SELF GEN SUBRATE ENABLE*/
+		reg_addr = ((dir) ? SWRM_DIN_DP_PCM_PORT_CTRL(port_num) :
+			SWRM_DOUT_DP_PCM_PORT_CTRL(port_num));
+		reg_val = swr_master_read(swrm, reg_addr);
+		reg_val |= SWRM_DOUT_DP_PCM_PORT_CTRL__SELF_GEN_SUB_RATE_EN;
+		swr_master_write(swrm, reg_addr, reg_val);
+
+		/*M VALID SAMPLE*/
+		reg_addr = SWRM_DP_FLOW_CTRL_M_VALID_SAMPLE(port_num);
+		swr_master_write(swrm, reg_addr, 147);
+		/*N REPEAT PERIOD*/
+		reg_addr = SWRM_DP_FLOW_CTRL_N_REPEAT_PERIOD(port_num);
+		swr_master_write(swrm, reg_addr, 160);
+	}
+
+	if (!enable) {
+		/* Reset flow control configuration registers to defaults. */
+		swr_master_write(swrm, SWRM_DP_PORT_CONTROL(port_num), 0x0);
+		swr_master_write(swrm, SWRM_DP_FLOW_CTRL_M_VALID_SAMPLE(port_num), 0x1);
+		swr_master_write(swrm, SWRM_DP_FLOW_CTRL_N_REPEAT_PERIOD(port_num), 0x1);
+	}
 	return 0;
 }
 
@@ -917,6 +994,7 @@ static void swrm_wait_for_fifo_avail(struct swr_mstr_ctrl *swrm, int swrm_rd_wr)
 			dev_err_ratelimited(swrm->dev,
 					"%s err write overflow\n", __func__);
 	}
+
 }
 
 static int swrm_cmd_fifo_rd_cmd(struct swr_mstr_ctrl *swrm, int *cmd_data,
@@ -1180,6 +1258,12 @@ static void swrm_switch_frame_shape(struct swr_mstr_ctrl *swrm, int mclk_freq)
 		n_row = SWR_ROW_64;
 		row = SWRM_ROW_64;
 		frame_sync = SWRM_FRAME_SYNC_SEL_NATIVE;
+	} else if (mclk_freq == MCLK_FREQ_12288) {
+		n_col = SWR_MIN_COL;
+		col = SWRM_COL_02;
+		n_row = SWR_ROW_64;
+		row = SWRM_ROW_64;
+		frame_sync = SWRM_FRAME_SYNC_SEL;
 	} else {
 		n_col = SWR_MIN_COL;
 		col = SWRM_COL_02;
@@ -1256,6 +1340,8 @@ int swrm_get_clk_div_rate(int mclk_freq, int bus_clk_freq)
 			bus_clk_freq = SWR_CLK_RATE_9P6MHZ;
 	} else if (mclk_freq == SWR_CLK_RATE_11P2896MHZ)
 		bus_clk_freq = SWR_CLK_RATE_11P2896MHZ;
+	else if (mclk_freq == SWR_CLK_RATE_12P288MHZ)
+		bus_clk_freq = SWR_CLK_RATE_12P288MHZ;
 
 	return bus_clk_freq;
 }
@@ -1333,8 +1419,7 @@ static void swrm_disable_ports(struct swr_master *master,
 			__func__, i,
 			(SWRM_DP_PORT_CTRL_BANK((i + 1), bank)), value);
 		if (!mport->req_ch)
-			swrm_pcm_port_config(swrm, (i + 1),
-				mport->stream_type, mport->dir, false);
+			swrm_pcm_port_config(swrm, (i + 1), mport, false);
 	}
 }
 
@@ -1417,6 +1502,22 @@ static int swrm_get_uc(int bus_clk)
 	return SWR_UC0;
 }
 
+static int swrm_adjust_sample_rate(u32 sample_rate)
+{
+	switch (sample_rate) {
+	case SAMPLING_RATE_44P1KHZ:
+		return SAMPLING_RATE_48KHZ;
+	case SAMPLING_RATE_88P2KHZ:
+		return SAMPLING_RATE_96KHZ;
+	case SAMPLING_RATE_176P4KHZ:
+		return SAMPLING_RATE_192KHZ;
+	case SAMPLING_RATE_352P8KHZ:
+		return SAMPLING_RATE_384KHZ;
+	default:
+		return sample_rate;
+	}
+}
+
 static void swrm_get_device_frame_shape(struct swr_mstr_ctrl *swrm,
 					struct swrm_mports *mport,
 					struct swr_port_info *port_req)
@@ -1441,6 +1542,20 @@ static void swrm_get_device_frame_shape(struct swr_mstr_ctrl *swrm,
 		port_req->blk_pack_mode = 0xFF;
 		port_req->blk_grp_count = 0xFF;
 		port_req->lane_ctrl = swrm->pp[uc][port_id_offset].lane_ctrl;
+	} else if (swrm->master_id == MASTER_ID_BT) {
+		port_req->sinterval =
+				((swrm->bus_clk * 2) / port_req->ch_rate) - 1;
+		if (mport->dir == 0)
+			port_req->offset1 = 0;
+		else
+			port_req->offset1 = 0x14;
+		port_req->offset2 = 0x00;
+		port_req->hstart = 1;
+		port_req->hstop = 0xF;
+		port_req->word_length = 0xF;
+		port_req->blk_pack_mode = 0xFF;
+		port_req->blk_grp_count = 0xFF;
+		port_req->lane_ctrl = 0;
 	} else {
 		/* copy master port config to slave */
 		port_req->sinterval = mport->sinterval;
@@ -1463,6 +1578,7 @@ static void swrm_get_device_frame_shape(struct swr_mstr_ctrl *swrm,
 			return;
 		port_req->offset1 = swrm->pp[uc][port_id_offset].offset1;
 	}
+
 }
 
 static void swrm_copy_data_port_config(struct swr_master *master, u8 bank)
@@ -1495,8 +1611,8 @@ static void swrm_copy_data_port_config(struct swr_master *master, u8 bank)
 		if (!mport->port_en)
 			continue;
 
-		swrm_pcm_port_config(swrm, (i + 1),
-				mport->stream_type, mport->dir, true);
+		swrm_pcm_port_config(swrm, (i + 1), mport, true);
+
 		j = 0;
 		lane_ctrl  = 0;
 		sinterval = 0xFFFF;
@@ -1537,14 +1653,13 @@ static void swrm_copy_data_port_config(struct swr_master *master, u8 bank)
 								bank));
 
 			/* Only wite MSB if SI > 0xFF */
-			if (port_req->sinterval > 0xFF) {
-				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
-				val[len++] = SWR_REG_VAL_PACK(
-						(port_req->sinterval >> 8) & 0xFF,
-						port_req->dev_num, get_cmd_id(swrm),
-						SWRS_DP_SAMPLE_CONTROL_2_BANK(slv_id,
-									bank));
-			}
+			reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+			val[len++] = SWR_REG_VAL_PACK(
+					(port_req->sinterval >> 8) & 0xFF,
+					port_req->dev_num, get_cmd_id(swrm),
+					SWRS_DP_SAMPLE_CONTROL_2_BANK(slv_id,
+								bank));
+
 			if (port_req->offset1 != SWR_INVALID_PARAM) {
 				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
 				val[len++] = SWR_REG_VAL_PACK(port_req->offset1,
@@ -1604,12 +1719,61 @@ static void swrm_copy_data_port_config(struct swr_master *master, u8 bank)
 						SWRS_DP_LANE_CONTROL_BANK(
 								slv_id, bank));
 			}
+			if (port_req->req_ch_rate != port_req->ch_rate) {
+				dev_dbg(swrm->dev, "requested sample rate is fractional");
+				if (mport->dir == 0) {
+					reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+					val[len++] =
+						SWR_REG_VAL_PACK(1,
+							port_req->dev_num, get_cmd_id(swrm),
+							SWRS_DP_PORT_CONTROL(
+								slv_id));
+				} else if (mport->dir == 1) {
+					reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+					val[len++] =
+						SWR_REG_VAL_PACK(2,
+							port_req->dev_num, get_cmd_id(swrm),
+							SWRS_DP_PORT_CONTROL(
+								slv_id));
+				}
+
+				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+				val[len++] = SWR_REG_VAL_PACK(4,
+						port_req->dev_num, get_cmd_id(swrm),
+						SWRS_DPn_FEATURE_EN(port_req->slave_port_id));
+				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+				val[len++] = SWR_REG_VAL_PACK(1,
+							port_req->dev_num, get_cmd_id(swrm),
+							SWRS_DPn_FLOW_CTRL_N_REPEAT_PERIOD(
+								port_req->slave_port_id));
+				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+				val[len++] = SWR_REG_VAL_PACK(1,
+							port_req->dev_num, get_cmd_id(swrm),
+							SWRS_DPn_FLOW_CTRL_M_VALID_SAMPLE(
+								port_req->slave_port_id));
+			} else {
+				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+				val[len++] = SWR_REG_VAL_PACK(0, port_req->dev_num,
+						get_cmd_id(swrm), SWRS_DP_PORT_CONTROL(slv_id));
+
+				reg[len] = SWRM_CMD_FIFO_WR_CMD(swrm->ee_val);
+				val[len++] = SWR_REG_VAL_PACK(0, port_req->dev_num,
+						get_cmd_id(swrm),
+						SWRS_DPn_FEATURE_EN(port_req->slave_port_id));
+			}
+
 			port_req->ch_en = port_req->req_ch;
 			dev_offset[port_req->dev_num] = port_req->offset1;
 		}
 		if (swrm->master_id == MASTER_ID_TX) {
 			mport->sinterval = sinterval;
 			mport->lane_ctrl = lane_ctrl;
+		} else if (swrm->master_id == MASTER_ID_BT) {
+			mport->sinterval = sinterval;
+			mport->lane_ctrl = lane_ctrl;
+			mport->word_length = 0xF;
+			mport->hstart = 1;
+			mport->hstop = 0xF;
 		}
 		value = ((mport->req_ch)
 				<< SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
@@ -1789,6 +1953,11 @@ static int swrm_slvdev_datapath_control(struct swr_master *master, bool enable)
 		n_row = SWR_ROW_64;
 		row = SWRM_ROW_64;
 		frame_sync = SWRM_FRAME_SYNC_SEL_NATIVE;
+	} else if (swrm->mclk_freq == MCLK_FREQ_12288) {
+		dev_dbg(swrm->dev, "setting 64 x %d frameshape\n", col);
+		n_row = SWR_ROW_64;
+		row = SWRM_ROW_64;
+		frame_sync = SWRM_FRAME_SYNC_SEL;
 	} else {
 		dev_dbg(swrm->dev, "setting 50 x %d frameshape\n", col);
 		n_row = SWR_ROW_50;
@@ -1895,6 +2064,9 @@ static int swrm_connect_port(struct swr_master *master,
 			port_req->slave_port_id = portinfo->port_id[i];
 			port_req->num_ch = portinfo->num_ch[i];
 			port_req->ch_rate = portinfo->ch_rate[i];
+			port_req->req_ch_rate = portinfo->ch_rate[i];
+			if (swrm_is_fractional_sample_rate(port_req->ch_rate))
+				port_req->ch_rate = swrm_adjust_sample_rate(port_req->ch_rate);
 			port_req->ch_en = 0;
 			port_req->master_port_id = mstr_port_id;
 			list_add(&port_req->list, &mport->port_req_list);
@@ -1902,10 +2074,10 @@ static int swrm_connect_port(struct swr_master *master,
 		port_req->req_ch |= portinfo->ch_en[i];
 
 		dev_dbg(&master->dev,
-			"%s: mstr port %d, slv port %d ch_rate %d num_ch %d\n",
+			"%s: mstr port %d, slv port %d ch_rate %d num_ch %d req_ch_rate %d\n",
 			__func__, port_req->master_port_id,
 			port_req->slave_port_id, port_req->ch_rate,
-			port_req->num_ch);
+			port_req->num_ch, port_req->req_ch_rate);
 		/* Put the port req on master port */
 		mport = &(swrm->mport_cfg[mstr_port_id]);
 		mport->port_en = true;
@@ -2589,6 +2761,7 @@ static int swrm_master_init(struct swr_mstr_ctrl *swrm)
 	u32 val;
 	u8 row_ctrl = SWR_ROW_50;
 	u8 col_ctrl = SWR_MIN_COL;
+	u8 num_rows = SWRM_ROW_50;
 	u8 ssp_period = 1;
 	u8 retry_cmd_num = 3;
 	u32 reg[SWRM_MAX_INIT_REG];
@@ -2612,7 +2785,13 @@ static int swrm_master_init(struct swr_mstr_ctrl *swrm)
 				__func__, temp);
 		}
 	}
-	ssp_period = swrm_get_ssp_period(swrm, SWRM_ROW_50,
+
+	if (swrm->master_id == MASTER_ID_BT) {
+		row_ctrl = SWR_ROW_64;
+		num_rows = SWRM_ROW_64;
+	}
+
+	ssp_period = swrm_get_ssp_period(swrm, num_rows,
 					SWRM_COL_02, SWRM_FRAME_SYNC_SEL);
 	dev_dbg(swrm->dev, "%s: ssp_period: %d\n", __func__, ssp_period);
 
@@ -2644,10 +2823,12 @@ static int swrm_master_init(struct swr_mstr_ctrl *swrm)
 		reg[len] = SWRM_LINK_MANAGER_EE;
 		value[len++] = swrm->ee_val;
 	}
-#ifdef CONFIG_SWRM_VER_2P0
-	reg[len] = SWRM_CLK_CTRL(swrm->ee_val);
-	value[len++] = 0x01;
-#endif
+
+	if (swrm->master_id == MASTER_ID_BT) {
+		/* Enable self_gen_frame_sync. */
+		reg[len] = SWRM_SELF_GENERATE_FRAME_SYNC;
+		value[len++] = 0x01;
+	}
 
 #ifdef CONFIG_SWRM_VER_1P7
 	reg[len] = SWRM_MCP_BUS_CTRL;
@@ -2669,10 +2850,14 @@ static int swrm_master_init(struct swr_mstr_ctrl *swrm)
 	reg[len] = SWRM_INTERRUPT_EN(swrm->ee_val);
 	value[len++] = swrm->intr_mask;
 
-
 	reg[len] = SWRM_COMP_CFG;
 	value[len++] = 0x03;
 
+#ifdef CONFIG_SWRM_VER_2P0
+	reg[len] = SWRM_CLK_CTRL(swrm->ee_val);
+	value[len++] = 0x01;
+#endif
+
 	swr_master_bulk_write(swrm, reg, value, len);
 
 	if (!swrm_check_link_status(swrm, 0x1)) {
@@ -2908,7 +3093,11 @@ static int swrm_probe(struct platform_device *pdev)
 			goto err_pdata_fail;
 		}
 		swrm->port_mapping[port_num][ch_iter].port_type = port_type;
-		swrm->port_mapping[port_num][ch_iter++].ch_mask = ch_mask;
+
+		if (swrm->master_id == MASTER_ID_BT)
+			swrm->port_mapping[port_num][ch_iter++].ch_mask = 1;
+		else
+			swrm->port_mapping[port_num][ch_iter++].ch_mask = ch_mask;
 		old_port_num = port_num;
 	}
 	devm_kfree(&pdev->dev, temp);
@@ -2942,6 +3131,10 @@ static int swrm_probe(struct platform_device *pdev)
 	swrm->swr_irq_wakeup_capable = 0;
 	swrm->mclk_freq = MCLK_FREQ;
 	swrm->bus_clk = MCLK_FREQ;
+	if (swrm->master_id == MASTER_ID_BT) {
+		swrm->mclk_freq = MCLK_FREQ_12288;
+		swrm->bus_clk = MCLK_FREQ_12288;
+	}
 	swrm->dev_up = true;
 	swrm->state = SWR_MSTR_UP;
 	swrm->ipc_wakeup = false;
@@ -2970,9 +3163,12 @@ static int swrm_probe(struct platform_device *pdev)
 	for (i = 0 ; i < SWR_MSTR_PORT_LEN; i++) {
 		INIT_LIST_HEAD(&swrm->mport_cfg[i].port_req_list);
 
-		if (swrm->master_id == MASTER_ID_TX) {
+		if (swrm->master_id == MASTER_ID_TX || swrm->master_id == MASTER_ID_BT) {
 			swrm->mport_cfg[i].sinterval = 0xFFFF;
-			swrm->mport_cfg[i].offset1 = 0x00;
+			if (swrm->master_id == MASTER_ID_BT && i > 3)
+				swrm->mport_cfg[i].offset1 = 0x14;
+			else
+				swrm->mport_cfg[i].offset1 = 0x00;
 			swrm->mport_cfg[i].offset2 = 0x00;
 			swrm->mport_cfg[i].hstart = 0xFF;
 			swrm->mport_cfg[i].hstop = 0xFF;
@@ -2980,8 +3176,12 @@ static int swrm_probe(struct platform_device *pdev)
 			swrm->mport_cfg[i].blk_grp_count = 0xFF;
 			swrm->mport_cfg[i].word_length = 0xFF;
 			swrm->mport_cfg[i].lane_ctrl = 0x00;
-			swrm->mport_cfg[i].dir = 0x00;
-			swrm->mport_cfg[i].stream_type = 0x00;
+			if (swrm->master_id == MASTER_ID_BT && i > 3)
+				swrm->mport_cfg[i].dir = 0x01;
+			else
+				swrm->mport_cfg[i].dir = 0x00;
+			swrm->mport_cfg[i].stream_type =
+				(swrm->master_id == MASTER_ID_TX) ? 0x00 : 0x01;
 		}
 	}
 	if (of_property_read_u32(pdev->dev.of_node,
@@ -3456,7 +3656,7 @@ static int swrm_runtime_suspend(struct device *dev)
 			/* Mask bus clash interrupt */
 			swrm->intr_mask &= ~((u32)0x08);
 			swr_master_write(swrm, SWRM_INTERRUPT_EN(swrm->ee_val),
-					 swrm->intr_mask);
+					swrm->intr_mask);
 			mutex_unlock(&swrm->reslock);
 			/* clock stop sequence */
 			swrm_cmd_fifo_wr_cmd(swrm, 0x2, 0xF, 0xF,
@@ -4137,7 +4337,7 @@ static struct platform_driver swr_mstr_driver = {
 	.probe = swrm_probe,
 	.remove = swrm_remove,
 	.driver = {
-		.name = SWR_WCD_NAME,
+		.name = SWR_NAME,
 		.owner = THIS_MODULE,
 		.pm = &swrm_dev_pm_ops,
 		.of_match_table = swrm_dt_match,

+ 1 - 1
soc/swr-mstr-ctrl.h

@@ -31,7 +31,7 @@
 #define SWR_MAX_COL		7 /* Cols = 16 */
 #define SWR_MIN_COL		0 /* Cols = 2 */
 
-#define SWR_WCD_NAME	"swr-wcd"
+#define SWR_NAME	"swr-mgr"
 
 #define SWR_MSTR_PORT_LEN	13 /* Number of master ports */
 

+ 7 - 0
soc/swr-mstr-registers.h

@@ -126,6 +126,13 @@
 #define SWRM_DIN_DP_FEATURES_EN(n)        (SWRM_BASE+0x104C+0x100*n)
 #define SWRM_DIN_DP_PCM_PORT_CTRL(n)      (SWRM_BASE+0x1054+0x100*n)
 
+#define SWRM_DP_FLOW_CTRL_M_VALID_SAMPLE(n)	(SWRM_BASE+0x1080+0x100*n)
+#define SWRM_DP_FLOW_CTRL_N_REPEAT_PERIOD(n)    (SWRM_BASE+0x1084+0x100*n)
+
+#define SWRM_DP_PORT_CONTROL__FLOW_MODE_PUSH (0x8)
+#define SWRM_DP_PORT_CONTROL__FLOW_MODE_PULL (0x10)
+#define SWRM_DOUT_DP_PCM_PORT_CTRL__SELF_GEN_SUB_RATE_EN (0x4)
+
 #ifdef CONFIG_SWRM_VER_2P0
 #define SWRM_MAX_REGISTER SWRM_TO_CPU_SW_MESSAGE_READ(2, 2)
 #else

+ 4 - 0
soc/swr-slave-registers.h

@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
  * Copyright (c) 2015, 2018-2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #ifndef _SWR_SLAVE_REGISTERS_H
@@ -68,5 +69,8 @@
 #define SWRS_TEST_BUS_STATUS_LOW        (SWRS_BASE+0x200A)
 #define SWRS_TEST_BUS_STATUS_HIGH       (SWRS_BASE+0x200B)
 #define SWRS_LOOPBACK_CTL               (SWRS_BASE+0x2009)
+#define SWRS_DPn_FEATURE_EN(n)		(SWRS_BASE+0x000001D4+0x100*n)
+#define SWRS_DPn_FLOW_CTRL_N_REPEAT_PERIOD(n)		(SWRS_BASE+0x000001CC+0x100*n)
+#define SWRS_DPn_FLOW_CTRL_M_VALID_SAMPLE(n)		(SWRS_BASE+0x000001C4+0x100*n)
 
 #endif /* _SWR_SLAVE_REGISTERS_H */