forked from projectceladon/linux-intel-lts2021
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ASoC: codecs: wcd938x-sdw: add SoundWire driver
This patch adds support to SoundWire devices on WCD9380/WCD9385 Codec Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> Link: https://lore.kernel.org/r/20210609090943.7896-6-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown <broonie@kernel.org>
- Loading branch information
1 parent
e02c65f
commit 1657252
Showing
3 changed files
with
446 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,315 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
// Copyright (c) 2021, Linaro Limited | ||
|
||
#include <linux/module.h> | ||
#include <linux/slab.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/device.h> | ||
#include <linux/kernel.h> | ||
#include <linux/component.h> | ||
#include <sound/soc.h> | ||
#include <linux/pm_runtime.h> | ||
#include <linux/irqdomain.h> | ||
#include <linux/of.h> | ||
#include <linux/soundwire/sdw.h> | ||
#include <linux/soundwire/sdw_type.h> | ||
#include <linux/soundwire/sdw_registers.h> | ||
#include <linux/regmap.h> | ||
#include <sound/soc.h> | ||
#include <sound/soc-dapm.h> | ||
#include "wcd938x.h" | ||
|
||
#define SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(m) (0xE0 + 0x10 * (m)) | ||
|
||
static struct wcd938x_sdw_ch_info wcd938x_sdw_rx_ch_info[] = { | ||
WCD_SDW_CH(WCD938X_HPH_L, WCD938X_HPH_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_HPH_R, WCD938X_HPH_PORT, BIT(1)), | ||
WCD_SDW_CH(WCD938X_CLSH, WCD938X_CLSH_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_COMP_L, WCD938X_COMP_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_COMP_R, WCD938X_COMP_PORT, BIT(1)), | ||
WCD_SDW_CH(WCD938X_LO, WCD938X_LO_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_DSD_L, WCD938X_DSD_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_DSD_R, WCD938X_DSD_PORT, BIT(1)), | ||
}; | ||
|
||
static struct wcd938x_sdw_ch_info wcd938x_sdw_tx_ch_info[] = { | ||
WCD_SDW_CH(WCD938X_ADC1, WCD938X_ADC_1_2_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_ADC2, WCD938X_ADC_1_2_PORT, BIT(1)), | ||
WCD_SDW_CH(WCD938X_ADC3, WCD938X_ADC_3_4_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_ADC4, WCD938X_ADC_3_4_PORT, BIT(1)), | ||
WCD_SDW_CH(WCD938X_DMIC0, WCD938X_DMIC_0_3_MBHC_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_DMIC1, WCD938X_DMIC_0_3_MBHC_PORT, BIT(1)), | ||
WCD_SDW_CH(WCD938X_MBHC, WCD938X_DMIC_0_3_MBHC_PORT, BIT(2)), | ||
WCD_SDW_CH(WCD938X_DMIC2, WCD938X_DMIC_0_3_MBHC_PORT, BIT(2)), | ||
WCD_SDW_CH(WCD938X_DMIC3, WCD938X_DMIC_0_3_MBHC_PORT, BIT(3)), | ||
WCD_SDW_CH(WCD938X_DMIC4, WCD938X_DMIC_4_7_PORT, BIT(0)), | ||
WCD_SDW_CH(WCD938X_DMIC5, WCD938X_DMIC_4_7_PORT, BIT(1)), | ||
WCD_SDW_CH(WCD938X_DMIC6, WCD938X_DMIC_4_7_PORT, BIT(2)), | ||
WCD_SDW_CH(WCD938X_DMIC7, WCD938X_DMIC_4_7_PORT, BIT(3)), | ||
}; | ||
|
||
static struct sdw_dpn_prop wcd938x_dpn_prop[WCD938X_MAX_SWR_PORTS] = { | ||
{ | ||
.num = 1, | ||
.type = SDW_DPN_SIMPLE, | ||
.min_ch = 1, | ||
.max_ch = 8, | ||
.simple_ch_prep_sm = true, | ||
}, { | ||
.num = 2, | ||
.type = SDW_DPN_SIMPLE, | ||
.min_ch = 1, | ||
.max_ch = 4, | ||
.simple_ch_prep_sm = true, | ||
}, { | ||
.num = 3, | ||
.type = SDW_DPN_SIMPLE, | ||
.min_ch = 1, | ||
.max_ch = 4, | ||
.simple_ch_prep_sm = true, | ||
}, { | ||
.num = 4, | ||
.type = SDW_DPN_SIMPLE, | ||
.min_ch = 1, | ||
.max_ch = 4, | ||
.simple_ch_prep_sm = true, | ||
}, { | ||
.num = 5, | ||
.type = SDW_DPN_SIMPLE, | ||
.min_ch = 1, | ||
.max_ch = 4, | ||
.simple_ch_prep_sm = true, | ||
} | ||
}; | ||
|
||
struct device *wcd938x_sdw_device_get(struct device_node *np) | ||
{ | ||
return bus_find_device_by_of_node(&sdw_bus_type, np); | ||
|
||
} | ||
EXPORT_SYMBOL_GPL(wcd938x_sdw_device_get); | ||
|
||
int wcd938x_swr_get_current_bank(struct sdw_slave *sdev) | ||
{ | ||
int bank; | ||
|
||
bank = sdw_read(sdev, SDW_SCP_CTRL); | ||
|
||
return ((bank & 0x40) ? 1 : 0); | ||
} | ||
EXPORT_SYMBOL_GPL(wcd938x_swr_get_current_bank); | ||
|
||
int wcd938x_sdw_hw_params(struct wcd938x_sdw_priv *wcd, | ||
struct snd_pcm_substream *substream, | ||
struct snd_pcm_hw_params *params, | ||
struct snd_soc_dai *dai) | ||
{ | ||
struct sdw_port_config port_config[WCD938X_MAX_SWR_PORTS]; | ||
unsigned long ch_mask; | ||
int i, j; | ||
|
||
wcd->sconfig.ch_count = 1; | ||
wcd->active_ports = 0; | ||
for (i = 0; i < WCD938X_MAX_SWR_PORTS; i++) { | ||
ch_mask = wcd->port_config[i].ch_mask; | ||
|
||
if (!ch_mask) | ||
continue; | ||
|
||
for_each_set_bit(j, &ch_mask, 4) | ||
wcd->sconfig.ch_count++; | ||
|
||
port_config[wcd->active_ports] = wcd->port_config[i]; | ||
wcd->active_ports++; | ||
} | ||
|
||
wcd->sconfig.bps = 1; | ||
wcd->sconfig.frame_rate = params_rate(params); | ||
if (wcd->is_tx) | ||
wcd->sconfig.direction = SDW_DATA_DIR_TX; | ||
else | ||
wcd->sconfig.direction = SDW_DATA_DIR_RX; | ||
|
||
wcd->sconfig.type = SDW_STREAM_PCM; | ||
|
||
return sdw_stream_add_slave(wcd->sdev, &wcd->sconfig, | ||
&port_config[0], wcd->active_ports, | ||
wcd->sruntime); | ||
} | ||
EXPORT_SYMBOL_GPL(wcd938x_sdw_hw_params); | ||
|
||
int wcd938x_sdw_free(struct wcd938x_sdw_priv *wcd, | ||
struct snd_pcm_substream *substream, | ||
struct snd_soc_dai *dai) | ||
{ | ||
sdw_stream_remove_slave(wcd->sdev, wcd->sruntime); | ||
|
||
return 0; | ||
} | ||
EXPORT_SYMBOL_GPL(wcd938x_sdw_free); | ||
|
||
int wcd938x_sdw_set_sdw_stream(struct wcd938x_sdw_priv *wcd, | ||
struct snd_soc_dai *dai, | ||
void *stream, int direction) | ||
{ | ||
wcd->sruntime = stream; | ||
|
||
return 0; | ||
} | ||
EXPORT_SYMBOL_GPL(wcd938x_sdw_set_sdw_stream); | ||
|
||
static int wcd9380_update_status(struct sdw_slave *slave, | ||
enum sdw_slave_status status) | ||
{ | ||
return 0; | ||
} | ||
|
||
static int wcd9380_bus_config(struct sdw_slave *slave, | ||
struct sdw_bus_params *params) | ||
{ | ||
sdw_write(slave, SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(params->next_bank), 0x01); | ||
|
||
return 0; | ||
} | ||
|
||
static int wcd9380_interrupt_callback(struct sdw_slave *slave, | ||
struct sdw_slave_intr_status *status) | ||
{ | ||
struct wcd938x_sdw_priv *wcd = dev_get_drvdata(&slave->dev); | ||
|
||
return wcd938x_handle_sdw_irq(wcd); | ||
} | ||
|
||
static struct sdw_slave_ops wcd9380_slave_ops = { | ||
.update_status = wcd9380_update_status, | ||
.interrupt_callback = wcd9380_interrupt_callback, | ||
.bus_config = wcd9380_bus_config, | ||
}; | ||
|
||
static int wcd938x_sdw_component_bind(struct device *dev, | ||
struct device *master, void *data) | ||
{ | ||
return 0; | ||
} | ||
|
||
static void wcd938x_sdw_component_unbind(struct device *dev, | ||
struct device *master, void *data) | ||
{ | ||
} | ||
|
||
static const struct component_ops wcd938x_sdw_component_ops = { | ||
.bind = wcd938x_sdw_component_bind, | ||
.unbind = wcd938x_sdw_component_unbind, | ||
}; | ||
|
||
static int wcd9380_probe(struct sdw_slave *pdev, | ||
const struct sdw_device_id *id) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
struct wcd938x_sdw_priv *wcd; | ||
int ret; | ||
|
||
wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL); | ||
if (!wcd) | ||
return -ENOMEM; | ||
|
||
/** | ||
* Port map index starts with 0, however the data port for this codec | ||
* are from index 1 | ||
*/ | ||
if (of_property_read_bool(dev->of_node, "qcom,tx-port-mapping")) { | ||
wcd->is_tx = true; | ||
ret = of_property_read_u32_array(dev->of_node, "qcom,tx-port-mapping", | ||
&pdev->m_port_map[1], | ||
WCD938X_MAX_TX_SWR_PORTS); | ||
} else { | ||
ret = of_property_read_u32_array(dev->of_node, "qcom,rx-port-mapping", | ||
&pdev->m_port_map[1], | ||
WCD938X_MAX_SWR_PORTS); | ||
} | ||
|
||
if (ret < 0) | ||
dev_info(dev, "Static Port mapping not specified\n"); | ||
|
||
wcd->sdev = pdev; | ||
dev_set_drvdata(dev, wcd); | ||
|
||
pdev->prop.scp_int1_mask = SDW_SCP_INT1_IMPL_DEF | | ||
SDW_SCP_INT1_BUS_CLASH | | ||
SDW_SCP_INT1_PARITY; | ||
pdev->prop.lane_control_support = true; | ||
if (wcd->is_tx) { | ||
struct regmap *rm; | ||
|
||
pdev->prop.source_ports = GENMASK(WCD938X_MAX_SWR_PORTS, 0); | ||
pdev->prop.src_dpn_prop = wcd938x_dpn_prop; | ||
wcd->ch_info = &wcd938x_sdw_tx_ch_info[0]; | ||
pdev->prop.wake_capable = true; | ||
|
||
rm = devm_regmap_init_sdw(pdev, &wcd938x_regmap_config); | ||
if (IS_ERR(rm)) | ||
return PTR_ERR(rm); | ||
} else { | ||
pdev->prop.sink_ports = GENMASK(WCD938X_MAX_SWR_PORTS, 0); | ||
pdev->prop.sink_dpn_prop = wcd938x_dpn_prop; | ||
wcd->ch_info = &wcd938x_sdw_rx_ch_info[0]; | ||
} | ||
|
||
pm_runtime_set_autosuspend_delay(dev, 3000); | ||
pm_runtime_use_autosuspend(dev); | ||
pm_runtime_mark_last_busy(dev); | ||
pm_runtime_set_active(dev); | ||
pm_runtime_enable(dev); | ||
|
||
return component_add(dev, &wcd938x_sdw_component_ops); | ||
} | ||
|
||
static const struct sdw_device_id wcd9380_slave_id[] = { | ||
SDW_SLAVE_ENTRY(0x0217, 0x10d, 0), | ||
{}, | ||
}; | ||
MODULE_DEVICE_TABLE(sdw, wcd9380_slave_id); | ||
|
||
static int __maybe_unused wcd938x_sdw_runtime_suspend(struct device *dev) | ||
{ | ||
struct regmap *regmap = dev_get_regmap(dev, NULL); | ||
|
||
if (regmap) { | ||
regcache_cache_only(regmap, true); | ||
regcache_mark_dirty(regmap); | ||
} | ||
return 0; | ||
} | ||
|
||
static int __maybe_unused wcd938x_sdw_runtime_resume(struct device *dev) | ||
{ | ||
struct regmap *regmap = dev_get_regmap(dev, NULL); | ||
|
||
if (regmap) { | ||
regcache_cache_only(regmap, false); | ||
regcache_sync(regmap); | ||
} | ||
|
||
pm_runtime_mark_last_busy(dev); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct dev_pm_ops wcd938x_sdw_pm_ops = { | ||
SET_RUNTIME_PM_OPS(wcd938x_sdw_runtime_suspend, wcd938x_sdw_runtime_resume, NULL) | ||
}; | ||
|
||
|
||
static struct sdw_driver wcd9380_codec_driver = { | ||
.probe = wcd9380_probe, | ||
.ops = &wcd9380_slave_ops, | ||
.id_table = wcd9380_slave_id, | ||
.driver = { | ||
.name = "wcd9380-codec", | ||
.pm = &wcd938x_sdw_pm_ops, | ||
} | ||
}; | ||
module_sdw_driver(wcd9380_codec_driver); | ||
|
||
MODULE_DESCRIPTION("WCD938X SDW codec driver"); | ||
MODULE_LICENSE("GPL"); |
Oops, something went wrong.