Skip to content

Commit 1657252

Browse files
Srinivas-Kandagatlabroonie
authored andcommitted
ASoC: codecs: wcd938x-sdw: add SoundWire driver
This patch adds support to SoundWire devices on WCD9380/WCD9385 Codec Signed-off-by: Srinivas Kandagatla <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mark Brown <[email protected]>
1 parent e02c65f commit 1657252

File tree

3 files changed

+446
-0
lines changed

3 files changed

+446
-0
lines changed

sound/soc/codecs/wcd938x-sdw.c

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// Copyright (c) 2021, Linaro Limited
3+
4+
#include <linux/module.h>
5+
#include <linux/slab.h>
6+
#include <linux/platform_device.h>
7+
#include <linux/device.h>
8+
#include <linux/kernel.h>
9+
#include <linux/component.h>
10+
#include <sound/soc.h>
11+
#include <linux/pm_runtime.h>
12+
#include <linux/irqdomain.h>
13+
#include <linux/of.h>
14+
#include <linux/soundwire/sdw.h>
15+
#include <linux/soundwire/sdw_type.h>
16+
#include <linux/soundwire/sdw_registers.h>
17+
#include <linux/regmap.h>
18+
#include <sound/soc.h>
19+
#include <sound/soc-dapm.h>
20+
#include "wcd938x.h"
21+
22+
#define SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(m) (0xE0 + 0x10 * (m))
23+
24+
static struct wcd938x_sdw_ch_info wcd938x_sdw_rx_ch_info[] = {
25+
WCD_SDW_CH(WCD938X_HPH_L, WCD938X_HPH_PORT, BIT(0)),
26+
WCD_SDW_CH(WCD938X_HPH_R, WCD938X_HPH_PORT, BIT(1)),
27+
WCD_SDW_CH(WCD938X_CLSH, WCD938X_CLSH_PORT, BIT(0)),
28+
WCD_SDW_CH(WCD938X_COMP_L, WCD938X_COMP_PORT, BIT(0)),
29+
WCD_SDW_CH(WCD938X_COMP_R, WCD938X_COMP_PORT, BIT(1)),
30+
WCD_SDW_CH(WCD938X_LO, WCD938X_LO_PORT, BIT(0)),
31+
WCD_SDW_CH(WCD938X_DSD_L, WCD938X_DSD_PORT, BIT(0)),
32+
WCD_SDW_CH(WCD938X_DSD_R, WCD938X_DSD_PORT, BIT(1)),
33+
};
34+
35+
static struct wcd938x_sdw_ch_info wcd938x_sdw_tx_ch_info[] = {
36+
WCD_SDW_CH(WCD938X_ADC1, WCD938X_ADC_1_2_PORT, BIT(0)),
37+
WCD_SDW_CH(WCD938X_ADC2, WCD938X_ADC_1_2_PORT, BIT(1)),
38+
WCD_SDW_CH(WCD938X_ADC3, WCD938X_ADC_3_4_PORT, BIT(0)),
39+
WCD_SDW_CH(WCD938X_ADC4, WCD938X_ADC_3_4_PORT, BIT(1)),
40+
WCD_SDW_CH(WCD938X_DMIC0, WCD938X_DMIC_0_3_MBHC_PORT, BIT(0)),
41+
WCD_SDW_CH(WCD938X_DMIC1, WCD938X_DMIC_0_3_MBHC_PORT, BIT(1)),
42+
WCD_SDW_CH(WCD938X_MBHC, WCD938X_DMIC_0_3_MBHC_PORT, BIT(2)),
43+
WCD_SDW_CH(WCD938X_DMIC2, WCD938X_DMIC_0_3_MBHC_PORT, BIT(2)),
44+
WCD_SDW_CH(WCD938X_DMIC3, WCD938X_DMIC_0_3_MBHC_PORT, BIT(3)),
45+
WCD_SDW_CH(WCD938X_DMIC4, WCD938X_DMIC_4_7_PORT, BIT(0)),
46+
WCD_SDW_CH(WCD938X_DMIC5, WCD938X_DMIC_4_7_PORT, BIT(1)),
47+
WCD_SDW_CH(WCD938X_DMIC6, WCD938X_DMIC_4_7_PORT, BIT(2)),
48+
WCD_SDW_CH(WCD938X_DMIC7, WCD938X_DMIC_4_7_PORT, BIT(3)),
49+
};
50+
51+
static struct sdw_dpn_prop wcd938x_dpn_prop[WCD938X_MAX_SWR_PORTS] = {
52+
{
53+
.num = 1,
54+
.type = SDW_DPN_SIMPLE,
55+
.min_ch = 1,
56+
.max_ch = 8,
57+
.simple_ch_prep_sm = true,
58+
}, {
59+
.num = 2,
60+
.type = SDW_DPN_SIMPLE,
61+
.min_ch = 1,
62+
.max_ch = 4,
63+
.simple_ch_prep_sm = true,
64+
}, {
65+
.num = 3,
66+
.type = SDW_DPN_SIMPLE,
67+
.min_ch = 1,
68+
.max_ch = 4,
69+
.simple_ch_prep_sm = true,
70+
}, {
71+
.num = 4,
72+
.type = SDW_DPN_SIMPLE,
73+
.min_ch = 1,
74+
.max_ch = 4,
75+
.simple_ch_prep_sm = true,
76+
}, {
77+
.num = 5,
78+
.type = SDW_DPN_SIMPLE,
79+
.min_ch = 1,
80+
.max_ch = 4,
81+
.simple_ch_prep_sm = true,
82+
}
83+
};
84+
85+
struct device *wcd938x_sdw_device_get(struct device_node *np)
86+
{
87+
return bus_find_device_by_of_node(&sdw_bus_type, np);
88+
89+
}
90+
EXPORT_SYMBOL_GPL(wcd938x_sdw_device_get);
91+
92+
int wcd938x_swr_get_current_bank(struct sdw_slave *sdev)
93+
{
94+
int bank;
95+
96+
bank = sdw_read(sdev, SDW_SCP_CTRL);
97+
98+
return ((bank & 0x40) ? 1 : 0);
99+
}
100+
EXPORT_SYMBOL_GPL(wcd938x_swr_get_current_bank);
101+
102+
int wcd938x_sdw_hw_params(struct wcd938x_sdw_priv *wcd,
103+
struct snd_pcm_substream *substream,
104+
struct snd_pcm_hw_params *params,
105+
struct snd_soc_dai *dai)
106+
{
107+
struct sdw_port_config port_config[WCD938X_MAX_SWR_PORTS];
108+
unsigned long ch_mask;
109+
int i, j;
110+
111+
wcd->sconfig.ch_count = 1;
112+
wcd->active_ports = 0;
113+
for (i = 0; i < WCD938X_MAX_SWR_PORTS; i++) {
114+
ch_mask = wcd->port_config[i].ch_mask;
115+
116+
if (!ch_mask)
117+
continue;
118+
119+
for_each_set_bit(j, &ch_mask, 4)
120+
wcd->sconfig.ch_count++;
121+
122+
port_config[wcd->active_ports] = wcd->port_config[i];
123+
wcd->active_ports++;
124+
}
125+
126+
wcd->sconfig.bps = 1;
127+
wcd->sconfig.frame_rate = params_rate(params);
128+
if (wcd->is_tx)
129+
wcd->sconfig.direction = SDW_DATA_DIR_TX;
130+
else
131+
wcd->sconfig.direction = SDW_DATA_DIR_RX;
132+
133+
wcd->sconfig.type = SDW_STREAM_PCM;
134+
135+
return sdw_stream_add_slave(wcd->sdev, &wcd->sconfig,
136+
&port_config[0], wcd->active_ports,
137+
wcd->sruntime);
138+
}
139+
EXPORT_SYMBOL_GPL(wcd938x_sdw_hw_params);
140+
141+
int wcd938x_sdw_free(struct wcd938x_sdw_priv *wcd,
142+
struct snd_pcm_substream *substream,
143+
struct snd_soc_dai *dai)
144+
{
145+
sdw_stream_remove_slave(wcd->sdev, wcd->sruntime);
146+
147+
return 0;
148+
}
149+
EXPORT_SYMBOL_GPL(wcd938x_sdw_free);
150+
151+
int wcd938x_sdw_set_sdw_stream(struct wcd938x_sdw_priv *wcd,
152+
struct snd_soc_dai *dai,
153+
void *stream, int direction)
154+
{
155+
wcd->sruntime = stream;
156+
157+
return 0;
158+
}
159+
EXPORT_SYMBOL_GPL(wcd938x_sdw_set_sdw_stream);
160+
161+
static int wcd9380_update_status(struct sdw_slave *slave,
162+
enum sdw_slave_status status)
163+
{
164+
return 0;
165+
}
166+
167+
static int wcd9380_bus_config(struct sdw_slave *slave,
168+
struct sdw_bus_params *params)
169+
{
170+
sdw_write(slave, SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(params->next_bank), 0x01);
171+
172+
return 0;
173+
}
174+
175+
static int wcd9380_interrupt_callback(struct sdw_slave *slave,
176+
struct sdw_slave_intr_status *status)
177+
{
178+
struct wcd938x_sdw_priv *wcd = dev_get_drvdata(&slave->dev);
179+
180+
return wcd938x_handle_sdw_irq(wcd);
181+
}
182+
183+
static struct sdw_slave_ops wcd9380_slave_ops = {
184+
.update_status = wcd9380_update_status,
185+
.interrupt_callback = wcd9380_interrupt_callback,
186+
.bus_config = wcd9380_bus_config,
187+
};
188+
189+
static int wcd938x_sdw_component_bind(struct device *dev,
190+
struct device *master, void *data)
191+
{
192+
return 0;
193+
}
194+
195+
static void wcd938x_sdw_component_unbind(struct device *dev,
196+
struct device *master, void *data)
197+
{
198+
}
199+
200+
static const struct component_ops wcd938x_sdw_component_ops = {
201+
.bind = wcd938x_sdw_component_bind,
202+
.unbind = wcd938x_sdw_component_unbind,
203+
};
204+
205+
static int wcd9380_probe(struct sdw_slave *pdev,
206+
const struct sdw_device_id *id)
207+
{
208+
struct device *dev = &pdev->dev;
209+
struct wcd938x_sdw_priv *wcd;
210+
int ret;
211+
212+
wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL);
213+
if (!wcd)
214+
return -ENOMEM;
215+
216+
/**
217+
* Port map index starts with 0, however the data port for this codec
218+
* are from index 1
219+
*/
220+
if (of_property_read_bool(dev->of_node, "qcom,tx-port-mapping")) {
221+
wcd->is_tx = true;
222+
ret = of_property_read_u32_array(dev->of_node, "qcom,tx-port-mapping",
223+
&pdev->m_port_map[1],
224+
WCD938X_MAX_TX_SWR_PORTS);
225+
} else {
226+
ret = of_property_read_u32_array(dev->of_node, "qcom,rx-port-mapping",
227+
&pdev->m_port_map[1],
228+
WCD938X_MAX_SWR_PORTS);
229+
}
230+
231+
if (ret < 0)
232+
dev_info(dev, "Static Port mapping not specified\n");
233+
234+
wcd->sdev = pdev;
235+
dev_set_drvdata(dev, wcd);
236+
237+
pdev->prop.scp_int1_mask = SDW_SCP_INT1_IMPL_DEF |
238+
SDW_SCP_INT1_BUS_CLASH |
239+
SDW_SCP_INT1_PARITY;
240+
pdev->prop.lane_control_support = true;
241+
if (wcd->is_tx) {
242+
struct regmap *rm;
243+
244+
pdev->prop.source_ports = GENMASK(WCD938X_MAX_SWR_PORTS, 0);
245+
pdev->prop.src_dpn_prop = wcd938x_dpn_prop;
246+
wcd->ch_info = &wcd938x_sdw_tx_ch_info[0];
247+
pdev->prop.wake_capable = true;
248+
249+
rm = devm_regmap_init_sdw(pdev, &wcd938x_regmap_config);
250+
if (IS_ERR(rm))
251+
return PTR_ERR(rm);
252+
} else {
253+
pdev->prop.sink_ports = GENMASK(WCD938X_MAX_SWR_PORTS, 0);
254+
pdev->prop.sink_dpn_prop = wcd938x_dpn_prop;
255+
wcd->ch_info = &wcd938x_sdw_rx_ch_info[0];
256+
}
257+
258+
pm_runtime_set_autosuspend_delay(dev, 3000);
259+
pm_runtime_use_autosuspend(dev);
260+
pm_runtime_mark_last_busy(dev);
261+
pm_runtime_set_active(dev);
262+
pm_runtime_enable(dev);
263+
264+
return component_add(dev, &wcd938x_sdw_component_ops);
265+
}
266+
267+
static const struct sdw_device_id wcd9380_slave_id[] = {
268+
SDW_SLAVE_ENTRY(0x0217, 0x10d, 0),
269+
{},
270+
};
271+
MODULE_DEVICE_TABLE(sdw, wcd9380_slave_id);
272+
273+
static int __maybe_unused wcd938x_sdw_runtime_suspend(struct device *dev)
274+
{
275+
struct regmap *regmap = dev_get_regmap(dev, NULL);
276+
277+
if (regmap) {
278+
regcache_cache_only(regmap, true);
279+
regcache_mark_dirty(regmap);
280+
}
281+
return 0;
282+
}
283+
284+
static int __maybe_unused wcd938x_sdw_runtime_resume(struct device *dev)
285+
{
286+
struct regmap *regmap = dev_get_regmap(dev, NULL);
287+
288+
if (regmap) {
289+
regcache_cache_only(regmap, false);
290+
regcache_sync(regmap);
291+
}
292+
293+
pm_runtime_mark_last_busy(dev);
294+
295+
return 0;
296+
}
297+
298+
static const struct dev_pm_ops wcd938x_sdw_pm_ops = {
299+
SET_RUNTIME_PM_OPS(wcd938x_sdw_runtime_suspend, wcd938x_sdw_runtime_resume, NULL)
300+
};
301+
302+
303+
static struct sdw_driver wcd9380_codec_driver = {
304+
.probe = wcd9380_probe,
305+
.ops = &wcd9380_slave_ops,
306+
.id_table = wcd9380_slave_id,
307+
.driver = {
308+
.name = "wcd9380-codec",
309+
.pm = &wcd938x_sdw_pm_ops,
310+
}
311+
};
312+
module_sdw_driver(wcd9380_codec_driver);
313+
314+
MODULE_DESCRIPTION("WCD938X SDW codec driver");
315+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)