Files
android_kernel_samsung_sm86…/asoc/codecs/swr-dmic.c
Sudheer Papothi 34e6984323 ASoC: swr-dmics: Enable soundwire DMICs on lahaina
Soundwire DMICs provide the same quality as regular DMICs but
the interface is soundwire. This reduces the number of pins
on the MSM. Enable soundwire DMICs on lahaina.

Change-Id: I4245369fdcb29cf96f315ebc1d2e9a83a0fbc14b
Signed-off-by: Sudheer Papothi <spapothi@codeaurora.org>
2020-02-18 14:01:42 -08:00

658 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/printk.h>
#include <linux/bitops.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/debugfs.h>
#include <soc/soundwire.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include <asoc/msm-cdc-pinctrl.h>
#include <asoc/msm-cdc-supply.h>
#include <dt-bindings/sound/audio-codec-port-types.h>
#include "wcd938x/wcd938x.h"
#include "swr-dmic.h"
static int swr_master_channel_map[] = {
ZERO,
SWRM_TX1_CH1,
SWRM_TX1_CH2,
SWRM_TX1_CH3,
SWRM_TX1_CH4,
SWRM_TX2_CH1,
SWRM_TX2_CH2,
SWRM_TX2_CH3,
SWRM_TX2_CH4,
SWRM_TX3_CH1,
SWRM_TX3_CH2,
SWRM_TX3_CH3,
SWRM_TX3_CH4,
SWRM_PCM_IN,
};
/*
* Private data Structure for swr-dmic. All parameters related to
* external mic codec needs to be defined here.
*/
struct swr_dmic_priv {
struct device *dev;
struct swr_device *swr_slave;
struct snd_soc_component *component;
struct snd_soc_component_driver *driver;
struct snd_soc_component *supply_component;
u32 micb_num;
struct device_node *wcd_handle;
bool is_wcd_supply;
int is_en_supply;
int port_type;
u8 tx_master_port_map[SWR_DMIC_MAX_PORTS];
};
const char *codec_name_list[] = {
"swr-dmic-01",
"swr-dmic-02",
"swr-dmic-03",
"swr-dmic-04",
};
const char *dai_name_list[] = {
"swr_dmic_tx0",
"swr_dmic_tx1",
"swr_dmic_tx2",
"swr_dmic_tx3",
};
const char *aif_name_list[] = {
"SWR_DMIC_AIF0 Playback",
"SWR_DMIC_AIF1 Playback",
"SWR_DMIC_AIF2 Playback",
"SWR_DMIC_AIF3 Playback",
};
static int swr_dmic_reset(struct swr_device *pdev);
static int swr_dmic_up(struct swr_device *pdev);
static int swr_dmic_down(struct swr_device *pdev);
static inline int swr_dmic_tx_get_slave_port_type_idx(const char *wname,
unsigned int *port_idx)
{
u8 port_type;
if (strnstr(wname, "HIFI", strlen(wname)))
port_type = SWR_DMIC_HIFI_PORT;
else if (strnstr(wname, "LP", strlen(wname)))
port_type = SWR_DMIC_LP_PORT;
else
return -EINVAL;
*port_idx = port_type;
return 0;
}
static inline int swr_dmic_get_master_port_val(int port)
{
int i;
for (i = 0; i < ARRAY_SIZE(swr_master_channel_map); i++)
if (port == swr_master_channel_map[i])
return i;
return 0;
}
static int swr_dmic_tx_master_port_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component =
snd_soc_kcontrol_component(kcontrol);
struct swr_dmic_priv *swr_dmic = snd_soc_component_get_drvdata(component);
int ret = 0;
int slave_port_idx;
ret = swr_dmic_tx_get_slave_port_type_idx(kcontrol->id.name,
&slave_port_idx);
if (ret) {
dev_dbg(component->dev, "%s: invalid port string\n", __func__);
return ret;
}
swr_dmic->port_type = slave_port_idx;
ucontrol->value.integer.value[0] =
swr_dmic_get_master_port_val(
swr_dmic->tx_master_port_map[slave_port_idx]);
dev_dbg(component->dev, "%s: ucontrol->value.integer.value[0] = %ld\n",
__func__, ucontrol->value.integer.value[0]);
return 0;
}
static int swr_dmic_tx_master_port_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component =
snd_soc_kcontrol_component(kcontrol);
struct swr_dmic_priv *swr_dmic = snd_soc_component_get_drvdata(component);
int ret = 0;
int slave_port_idx;
ret = swr_dmic_tx_get_slave_port_type_idx(kcontrol->id.name,
&slave_port_idx);
if (ret) {
dev_dbg(component->dev, "%s: invalid port string\n", __func__);
return ret;
}
swr_dmic->port_type = slave_port_idx;
swr_dmic->tx_master_port_map[slave_port_idx] =
swr_master_channel_map[ucontrol->value.enumerated.item[0]];
dev_dbg(component->dev, "%s: slv port id: %d, master_port_type: %d\n",
__func__, slave_port_idx,
swr_dmic->tx_master_port_map[slave_port_idx]);
return 0;
}
static int dmic_swr_ctrl(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
int ret = 0;
struct snd_soc_component *component =
snd_soc_dapm_to_component(w->dapm);
struct swr_dmic_priv *swr_dmic =
snd_soc_component_get_drvdata(component);
u8 num_ch = 1;
u8 ch_mask = 0x01; // only DpnChannelEN1 register is available
u32 ch_rate = SWR_CLK_RATE_4P8MHZ;
u8 num_port = 1;
u8 port_type = 0;
u8 port_id = swr_dmic->port_type;
/*
* Port 1 is high quality / 2.4 or 3.072 Mbps
* Port 2 is listen low power / 0.6 or 0.768 Mbps
*/
if(swr_dmic->port_type)
ch_rate = SWR_CLK_RATE_2P4MHZ;
else
ch_rate = SWR_CLK_RATE_4P8MHZ;
port_type = swr_dmic->tx_master_port_map[port_id];
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = swr_connect_port(swr_dmic->swr_slave, &port_id,
num_port, &ch_mask, &ch_rate,
&num_ch, &port_type);
break;
case SND_SOC_DAPM_POST_PMU:
ret = swr_slvdev_datapath_control(swr_dmic->swr_slave,
swr_dmic->swr_slave->dev_num, true);
break;
case SND_SOC_DAPM_PRE_PMD:
ret = swr_slvdev_datapath_control(swr_dmic->swr_slave,
swr_dmic->swr_slave->dev_num, false);
break;
case SND_SOC_DAPM_POST_PMD:
ret = swr_disconnect_port(swr_dmic->swr_slave,
&port_id, num_port, &ch_mask, &port_type);
break;
};
return ret;
}
static int swr_dmic_enable_supply(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int event)
{
struct snd_soc_component *component =
snd_soc_dapm_to_component(w->dapm);
struct swr_dmic_priv *swr_dmic =
snd_soc_component_get_drvdata(component);
int ret = 0;
dev_dbg(component->dev, "%s wname: %s event: %d\n", __func__,
w->name, event);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = swr_dmic_up(swr_dmic->swr_slave);
break;
case SND_SOC_DAPM_POST_PMU:
ret = swr_dmic_reset(swr_dmic->swr_slave);
break;
case SND_SOC_DAPM_POST_PMD:
ret = swr_dmic_down(swr_dmic->swr_slave);
break;
}
if (ret)
dev_dbg(component->dev, "%s wname: %s event: %d ret : %d\n",
__func__, w->name, event, ret);
return ret;
}
static const char * const tx_master_port_text[] = {
"ZERO", "SWRM_TX1_CH1", "SWRM_TX1_CH2", "SWRM_TX1_CH3", "SWRM_TX1_CH4",
"SWRM_TX2_CH1", "SWRM_TX2_CH2", "SWRM_TX2_CH3", "SWRM_TX2_CH4",
"SWRM_TX3_CH1", "SWRM_TX3_CH2", "SWRM_TX3_CH3", "SWRM_TX3_CH4",
"SWRM_PCM_IN",
};
static const struct soc_enum tx_master_port_enum =
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tx_master_port_text),
tx_master_port_text);
static const struct snd_kcontrol_new swr_dmic_snd_controls[] = {
SOC_ENUM_EXT("HIFI PortMap", tx_master_port_enum,
swr_dmic_tx_master_port_get, swr_dmic_tx_master_port_put),
SOC_ENUM_EXT("LP PortMap", tx_master_port_enum,
swr_dmic_tx_master_port_get, swr_dmic_tx_master_port_put),
};
static const struct snd_kcontrol_new dmic_switch[] = {
SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0)
};
static const struct snd_soc_dapm_widget swr_dmic_dapm_widgets[] = {
SND_SOC_DAPM_MIXER_E("SWR_DMIC_MIXER", SND_SOC_NOPM, 0, 0,
dmic_switch, ARRAY_SIZE(dmic_switch), dmic_swr_ctrl,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_INPUT("SWR_DMIC"),
SND_SOC_DAPM_SUPPLY_S("SMIC_SUPPLY", 1, SND_SOC_NOPM, 0, 0,
swr_dmic_enable_supply,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_OUTPUT("SWR_DMIC_OUTPUT"),
};
static const struct snd_soc_dapm_route swr_dmic_audio_map[] = {
{"SWR_DMIC", NULL, "SMIC_SUPPLY"},
{"SWR_DMIC_MIXER", "Switch", "SWR_DMIC"},
{"SWR_DMIC_OUTPUT", NULL, "SWR_DMIC_MIXER"},
};
static int swr_dmic_codec_probe(struct snd_soc_component *component)
{
struct swr_dmic_priv *swr_dmic =
snd_soc_component_get_drvdata(component);
if (!swr_dmic)
return -EINVAL;
swr_dmic->component = component;
return 0;
}
static void swr_dmic_codec_remove(struct snd_soc_component *component)
{
struct swr_dmic_priv *swr_dmic =
snd_soc_component_get_drvdata(component);
swr_dmic->component = NULL;
return;
}
static const struct snd_soc_component_driver soc_codec_dev_swr_dmic = {
.name = NULL,
.probe = swr_dmic_codec_probe,
.remove = swr_dmic_codec_remove,
.controls = swr_dmic_snd_controls,
.num_controls = ARRAY_SIZE(swr_dmic_snd_controls),
.dapm_widgets = swr_dmic_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(swr_dmic_dapm_widgets),
.dapm_routes = swr_dmic_audio_map,
.num_dapm_routes = ARRAY_SIZE(swr_dmic_audio_map),
};
static int enable_wcd_codec_supply(struct swr_dmic_priv *swr_dmic, bool enable)
{
int rc = 0;
int micb_num = swr_dmic->micb_num;
struct snd_soc_component *component = swr_dmic->supply_component;
if (!component) {
pr_err("%s: component is NULL\n", __func__);
return -EINVAL;
}
if (enable)
rc = wcd938x_codec_force_enable_micbias_v2(component,
SND_SOC_DAPM_PRE_PMU, micb_num);
else
rc = wcd938x_codec_force_enable_micbias_v2(component,
SND_SOC_DAPM_POST_PMD, micb_num);
return rc;
}
static int swr_dmic_parse_supply(struct device_node *np,
struct swr_dmic_priv *swr_dmic)
{
struct platform_device *pdev = NULL;
if (!np || !swr_dmic)
return -EINVAL;
pdev = of_find_device_by_node(np);
if (!pdev)
return -EINVAL;
swr_dmic->supply_component = snd_soc_lookup_component(&pdev->dev, NULL);
return 0;
}
static int swr_dmic_probe(struct swr_device *pdev)
{
int ret = 0;
int i = 0;
u8 swr_devnum = 0;
int dev_index = -1;
char* prefix_name = NULL;
struct swr_dmic_priv *swr_dmic = NULL;
const char *swr_dmic_name_prefix_of = NULL;
const char *swr_dmic_codec_name_of = NULL;
struct snd_soc_component *component = NULL;
swr_dmic = devm_kzalloc(&pdev->dev, sizeof(struct swr_dmic_priv),
GFP_KERNEL);
if (!swr_dmic)
return -ENOMEM;
ret = of_property_read_u32(pdev->dev.of_node, "qcom,swr-dmic-supply",
&swr_dmic->micb_num);
if (ret) {
dev_dbg(&pdev->dev, "%s: Looking up %s property in node %s failed\n",
__func__, "qcom,swr-dmic-supply",
pdev->dev.of_node->full_name);
goto err;
}
swr_dmic->wcd_handle = of_parse_phandle(pdev->dev.of_node,
"qcom,wcd-handle", 0);
if (!swr_dmic->wcd_handle) {
dev_dbg(&pdev->dev, "%s: no wcd handle listed\n",
__func__);
swr_dmic->is_wcd_supply = false;
} else {
swr_dmic_parse_supply(swr_dmic->wcd_handle, swr_dmic);
swr_dmic->is_wcd_supply = true;
}
if (swr_dmic->is_wcd_supply) {
ret = enable_wcd_codec_supply(swr_dmic, true);
if (ret) {
ret = -EPROBE_DEFER;
goto dev_err;
}
++swr_dmic->is_en_supply;
}
swr_set_dev_data(pdev, swr_dmic);
swr_dmic->swr_slave = pdev;
ret = of_property_read_string(pdev->dev.of_node, "qcom,swr-dmic-prefix",
&swr_dmic_name_prefix_of);
if (ret) {
dev_dbg(&pdev->dev, "%s: Looking up %s property in node %s failed\n",
__func__, "qcom,swr-dmic-prefix",
pdev->dev.of_node->full_name);
goto dev_err;
}
ret = of_property_read_string(pdev->dev.of_node, "qcom,codec-name",
&swr_dmic_codec_name_of);
if (ret) {
dev_dbg(&pdev->dev, "%s: Looking up %s property in node %s failed\n",
__func__, "qcom,codec-name",
pdev->dev.of_node->full_name);
goto dev_err;
}
/*
* Add 5msec delay to provide sufficient time for
* soundwire auto enumeration of slave devices as
* as per HW requirement.
*/
usleep_range(5000, 5010);
ret = swr_get_logical_dev_num(pdev, pdev->addr, &swr_devnum);
if (ret) {
dev_dbg(&pdev->dev,
"%s get devnum %d for dev addr %lx failed\n",
__func__, swr_devnum, pdev->addr);
ret = -EPROBE_DEFER;
goto dev_err;
}
pdev->dev_num = swr_devnum;
swr_dmic->driver = devm_kzalloc(&pdev->dev,
sizeof(struct snd_soc_component_driver), GFP_KERNEL);
if (!swr_dmic->driver) {
ret = -ENOMEM;
goto dev_err;
}
memcpy(swr_dmic->driver, &soc_codec_dev_swr_dmic,
sizeof(struct snd_soc_component_driver));
for (i = 0; i < ARRAY_SIZE(codec_name_list); i++) {
if (!strcmp(swr_dmic_codec_name_of, codec_name_list[i])) {
dev_index = i;
break;
}
}
if (dev_index < 0) {
ret = -EINVAL;
goto dev_err;
}
swr_dmic->driver->name = dai_name_list[dev_index];
ret = snd_soc_register_component(&pdev->dev, swr_dmic->driver,
NULL, 0);
if (ret) {
dev_err(&pdev->dev, "%s: Codec registration failed\n",
__func__);
goto dev_err;
}
component = snd_soc_lookup_component(&pdev->dev,
swr_dmic->driver->name);
swr_dmic->component = component;
prefix_name = devm_kzalloc(&pdev->dev,
strlen(swr_dmic_name_prefix_of),
GFP_KERNEL);
if (!prefix_name) {
ret = -ENOMEM;
goto dev_err;
}
strlcpy(prefix_name, swr_dmic_name_prefix_of,
strlen(swr_dmic_name_prefix_of));
component->name_prefix = prefix_name;
return 0;
dev_err:
if (swr_dmic->is_en_supply == 1) {
enable_wcd_codec_supply(swr_dmic, false);
--swr_dmic->is_en_supply;
}
swr_dmic->is_wcd_supply = false;
swr_dmic->wcd_handle = NULL;
swr_remove_device(pdev);
err:
return ret;
}
static int swr_dmic_remove(struct swr_device *pdev)
{
struct swr_dmic_priv *swr_dmic;
swr_dmic = swr_get_dev_data(pdev);
if (!swr_dmic) {
dev_err(&pdev->dev, "%s: swr_dmic is NULL\n", __func__);
return -EINVAL;
}
snd_soc_unregister_component(&pdev->dev);
swr_set_dev_data(pdev, NULL);
return 0;
}
static int swr_dmic_up(struct swr_device *pdev)
{
int ret = 0;
struct swr_dmic_priv *swr_dmic;
swr_dmic = swr_get_dev_data(pdev);
if (!swr_dmic) {
dev_err(&pdev->dev, "%s: swr_dmic is NULL\n", __func__);
return -EINVAL;
}
++swr_dmic->is_en_supply;
if (swr_dmic->is_en_supply == 1)
ret = enable_wcd_codec_supply(swr_dmic, true);
return ret;
}
static int swr_dmic_down(struct swr_device *pdev)
{
struct swr_dmic_priv *swr_dmic;
int ret = 0;
swr_dmic = swr_get_dev_data(pdev);
if (!swr_dmic) {
dev_err(&pdev->dev, "%s: swr_dmic is NULL\n", __func__);
return -EINVAL;
}
--swr_dmic->is_en_supply;
if (swr_dmic->is_en_supply < 0) {
dev_warn(&pdev->dev, "%s: mismatch in supply count %d\n",
__func__, swr_dmic->is_en_supply);
swr_dmic->is_en_supply = 0;
goto done;
}
if (!swr_dmic->is_en_supply)
enable_wcd_codec_supply(swr_dmic, false);
done:
return ret;
}
static int swr_dmic_reset(struct swr_device *pdev)
{
struct swr_dmic_priv *swr_dmic;
u8 retry = 5;
u8 devnum = 0;
swr_dmic = swr_get_dev_data(pdev);
if (!swr_dmic) {
dev_err(&pdev->dev, "%s: swr_dmic is NULL\n", __func__);
return -EINVAL;
}
while (swr_get_logical_dev_num(pdev, pdev->addr, &devnum) && retry--) {
/* Retry after 1 msec delay */
usleep_range(1000, 1100);
}
pdev->dev_num = devnum;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int swr_dmic_suspend(struct device *dev)
{
dev_dbg(dev, "%s: system suspend\n", __func__);
return 0;
}
static int swr_dmic_resume(struct device *dev)
{
struct swr_dmic_priv *swr_dmic = swr_get_dev_data(to_swr_device(dev));
if (!swr_dmic) {
dev_err(dev, "%s: swr_dmic private data is NULL\n", __func__);
return -EINVAL;
}
dev_dbg(dev, "%s: system resume\n", __func__);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops swr_dmic_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(swr_dmic_suspend, swr_dmic_resume)
};
static const struct swr_device_id swr_dmic_id[] = {
{"swr-dmic", 0},
{}
};
static const struct of_device_id swr_dmic_dt_match[] = {
{
.compatible = "qcom,swr-dmic",
},
{}
};
static struct swr_driver swr_dmic_driver = {
.driver = {
.name = "swr-dmic",
.owner = THIS_MODULE,
.pm = &swr_dmic_pm_ops,
.of_match_table = swr_dmic_dt_match,
},
.probe = swr_dmic_probe,
.remove = swr_dmic_remove,
.id_table = swr_dmic_id,
.device_up = swr_dmic_up,
.device_down = swr_dmic_down,
.reset_device = swr_dmic_reset,
};
static int __init swr_dmic_init(void)
{
return swr_driver_register(&swr_dmic_driver);
}
static void __exit swr_dmic_exit(void)
{
swr_driver_unregister(&swr_dmic_driver);
}
module_init(swr_dmic_init);
module_exit(swr_dmic_exit);
MODULE_DESCRIPTION("SWR DMIC driver");
MODULE_LICENSE("GPL v2");