1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Sound card driver for Intel Broadwell Wildcat Point with Realtek 286
4 *
5 * Copyright (C) 2013, Intel Corporation. All rights reserved.
6 */
7
8#include <linux/module.h>
9#include <linux/platform_device.h>
10#include <sound/core.h>
11#include <sound/jack.h>
12#include <sound/pcm.h>
13#include <sound/pcm_params.h>
14#include <sound/soc.h>
15#include <sound/soc-acpi.h>
16#include "../../codecs/rt286.h"
17
18static struct snd_soc_jack card_headset;
19
20static struct snd_soc_jack_pin card_headset_pins[] = {
21	{
22		.pin = "Mic Jack",
23		.mask = SND_JACK_MICROPHONE,
24	},
25	{
26		.pin = "Headphone Jack",
27		.mask = SND_JACK_HEADPHONE,
28	},
29};
30
31static const struct snd_kcontrol_new card_controls[] = {
32	SOC_DAPM_PIN_SWITCH("Speaker"),
33	SOC_DAPM_PIN_SWITCH("Headphone Jack"),
34};
35
36static const struct snd_soc_dapm_widget card_widgets[] = {
37	SND_SOC_DAPM_HP("Headphone Jack", NULL),
38	SND_SOC_DAPM_SPK("Speaker", NULL),
39	SND_SOC_DAPM_MIC("Mic Jack", NULL),
40	SND_SOC_DAPM_MIC("DMIC1", NULL),
41	SND_SOC_DAPM_MIC("DMIC2", NULL),
42	SND_SOC_DAPM_LINE("Line Jack", NULL),
43};
44
45static const struct snd_soc_dapm_route card_routes[] = {
46	{"Speaker", NULL, "SPOR"},
47	{"Speaker", NULL, "SPOL"},
48
49	{"Headphone Jack", NULL, "HPO Pin"},
50
51	{"MIC1", NULL, "Mic Jack"},
52	{"LINE1", NULL, "Line Jack"},
53
54	{"DMIC1 Pin", NULL, "DMIC1"},
55	{"DMIC2 Pin", NULL, "DMIC2"},
56
57	/* CODEC BE connections */
58	{"SSP0 CODEC IN", NULL, "AIF1 Capture"},
59	{"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
60};
61
62static int codec_link_init(struct snd_soc_pcm_runtime *rtd)
63{
64	struct snd_soc_component *codec = snd_soc_rtd_to_codec(rtd, 0)->component;
65	int ret;
66
67	ret = snd_soc_card_jack_new_pins(rtd->card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0,
68					 &card_headset, card_headset_pins,
69					 ARRAY_SIZE(card_headset_pins));
70	if (ret)
71		return ret;
72
73	return snd_soc_component_set_jack(codec, &card_headset, NULL);
74}
75
76static void codec_link_exit(struct snd_soc_pcm_runtime *rtd)
77{
78	struct snd_soc_component *codec = snd_soc_rtd_to_codec(rtd, 0)->component;
79
80	snd_soc_component_set_jack(codec, NULL, NULL);
81}
82
83static int codec_link_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
84				      struct snd_pcm_hw_params *params)
85{
86	struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
87	struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
88
89	/* The ADSP will convert the FE rate to 48kHz, stereo. */
90	rate->min = rate->max = 48000;
91	channels->min = channels->max = 2;
92	/* Set SSP0 to 16 bit. */
93	params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
94
95	return 0;
96}
97
98static int codec_link_hw_params(struct snd_pcm_substream *substream,
99				struct snd_pcm_hw_params *params)
100{
101	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
102	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
103	int ret;
104
105	ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, SND_SOC_CLOCK_IN);
106	if (ret < 0) {
107		dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret);
108		return ret;
109	}
110
111	return ret;
112}
113
114static const struct snd_soc_ops codec_link_ops = {
115	.hw_params = codec_link_hw_params,
116};
117
118SND_SOC_DAILINK_DEF(system, DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
119SND_SOC_DAILINK_DEF(offload0, DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin")));
120SND_SOC_DAILINK_DEF(offload1, DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin")));
121SND_SOC_DAILINK_DEF(loopback, DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin")));
122
123SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY()));
124SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
125SND_SOC_DAILINK_DEF(codec, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1")));
126SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
127
128static struct snd_soc_dai_link card_dai_links[] = {
129	/* Front End DAI links */
130	{
131		.name = "System PCM",
132		.stream_name = "System Playback/Capture",
133		.nonatomic = 1,
134		.dynamic = 1,
135		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
136		.dpcm_playback = 1,
137		.dpcm_capture = 1,
138		SND_SOC_DAILINK_REG(system, dummy, platform),
139	},
140	{
141		.name = "Offload0",
142		.stream_name = "Offload0 Playback",
143		.nonatomic = 1,
144		.dynamic = 1,
145		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
146		.dpcm_playback = 1,
147		SND_SOC_DAILINK_REG(offload0, dummy, platform),
148	},
149	{
150		.name = "Offload1",
151		.stream_name = "Offload1 Playback",
152		.nonatomic = 1,
153		.dynamic = 1,
154		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
155		.dpcm_playback = 1,
156		SND_SOC_DAILINK_REG(offload1, dummy, platform),
157	},
158	{
159		.name = "Loopback PCM",
160		.stream_name = "Loopback",
161		.nonatomic = 1,
162		.dynamic = 1,
163		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
164		.dpcm_capture = 1,
165		SND_SOC_DAILINK_REG(loopback, dummy, platform),
166	},
167	/* Back End DAI links */
168	{
169		/* SSP0 - Codec */
170		.name = "Codec",
171		.id = 0,
172		.nonatomic = 1,
173		.no_pcm = 1,
174		.init = codec_link_init,
175		.exit = codec_link_exit,
176		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC,
177		.ignore_pmdown_time = 1,
178		.be_hw_params_fixup = codec_link_hw_params_fixup,
179		.ops = &codec_link_ops,
180		.dpcm_playback = 1,
181		.dpcm_capture = 1,
182		SND_SOC_DAILINK_REG(ssp0_port, codec, platform),
183	},
184};
185
186static int card_suspend_pre(struct snd_soc_card *card)
187{
188	struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, "rt286-aif1");
189
190	if (!codec_dai)
191		return 0;
192
193	return snd_soc_component_set_jack(codec_dai->component, NULL, NULL);
194}
195
196static int card_resume_post(struct snd_soc_card *card)
197{
198	struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, "rt286-aif1");
199
200	if (!codec_dai)
201		return 0;
202
203	return snd_soc_component_set_jack(codec_dai->component, &card_headset, NULL);
204}
205
206static struct snd_soc_card bdw_rt286_card = {
207	.owner = THIS_MODULE,
208	.suspend_pre = card_suspend_pre,
209	.resume_post = card_resume_post,
210	.dai_link = card_dai_links,
211	.num_links = ARRAY_SIZE(card_dai_links),
212	.controls = card_controls,
213	.num_controls = ARRAY_SIZE(card_controls),
214	.dapm_widgets = card_widgets,
215	.num_dapm_widgets = ARRAY_SIZE(card_widgets),
216	.dapm_routes = card_routes,
217	.num_dapm_routes = ARRAY_SIZE(card_routes),
218	.fully_routed = true,
219};
220
221/* Use space before codec name to simplify card ID, and simplify driver name. */
222#define SOF_CARD_NAME "bdw rt286" /* card name will be 'sof-bdw rt286' */
223#define SOF_DRIVER_NAME "SOF"
224
225#define CARD_NAME "broadwell-rt286"
226
227static int bdw_rt286_probe(struct platform_device *pdev)
228{
229	struct snd_soc_acpi_mach *mach;
230	struct device *dev = &pdev->dev;
231	int ret;
232
233	bdw_rt286_card.dev = dev;
234	mach = dev_get_platdata(dev);
235
236	ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt286_card, mach->mach_params.platform);
237	if (ret)
238		return ret;
239
240	if (snd_soc_acpi_sof_parent(dev)) {
241		bdw_rt286_card.name = SOF_CARD_NAME;
242		bdw_rt286_card.driver_name = SOF_DRIVER_NAME;
243	} else {
244		bdw_rt286_card.name = CARD_NAME;
245	}
246
247	return devm_snd_soc_register_card(dev, &bdw_rt286_card);
248}
249
250static struct platform_driver bdw_rt286_driver = {
251	.probe = bdw_rt286_probe,
252	.driver = {
253		.name = "bdw_rt286",
254		.pm = &snd_soc_pm_ops
255	},
256};
257
258module_platform_driver(bdw_rt286_driver)
259
260MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
261MODULE_DESCRIPTION("Sound card driver for Intel Broadwell Wildcat Point with Realtek 286");
262MODULE_LICENSE("GPL");
263MODULE_ALIAS("platform:bdw_rt286");
264