1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Sound card driver for Intel Haswell Lynx Point with Realtek 5640
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/pcm.h>
12#include <sound/pcm_params.h>
13#include <sound/soc.h>
14#include <sound/soc-acpi.h>
15#include "../../codecs/rt5640.h"
16
17static const struct snd_soc_dapm_widget card_widgets[] = {
18	SND_SOC_DAPM_HP("Headphones", NULL),
19	SND_SOC_DAPM_MIC("Mic", NULL),
20};
21
22static const struct snd_soc_dapm_route card_routes[] = {
23	{"Headphones", NULL, "HPOR"},
24	{"Headphones", NULL, "HPOL"},
25	{"IN2P", NULL, "Mic"},
26
27	/* CODEC BE connections */
28	{"SSP0 CODEC IN", NULL, "AIF1 Capture"},
29	{"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
30};
31
32static int codec_link_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
33				      struct snd_pcm_hw_params *params)
34{
35	struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
36	struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
37
38	/* The ADSP will convert the FE rate to 48k, stereo. */
39	rate->min = rate->max = 48000;
40	channels->min = channels->max = 2;
41	/* Set SSP0 to 16 bit. */
42	params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
43
44	return 0;
45}
46
47static int codec_link_hw_params(struct snd_pcm_substream *substream,
48				struct snd_pcm_hw_params *params)
49{
50	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
51	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
52	int ret;
53
54	ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, SND_SOC_CLOCK_IN);
55	if (ret < 0) {
56		dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret);
57		return ret;
58	}
59
60	/* Set correct codec filter for DAI format and clock config. */
61	snd_soc_component_update_bits(codec_dai->component, 0x83, 0xffff, 0x8000);
62
63	return ret;
64}
65
66static const struct snd_soc_ops codec_link_ops = {
67	.hw_params = codec_link_hw_params,
68};
69
70SND_SOC_DAILINK_DEF(system, DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
71SND_SOC_DAILINK_DEF(offload0, DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin")));
72SND_SOC_DAILINK_DEF(offload1, DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin")));
73SND_SOC_DAILINK_DEF(loopback, DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin")));
74
75SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY()));
76SND_SOC_DAILINK_DEF(codec, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT33CA:00", "rt5640-aif1")));
77SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
78SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
79
80static struct snd_soc_dai_link card_dai_links[] = {
81	/* Front End DAI links */
82	{
83		.name = "System",
84		.stream_name = "System Playback/Capture",
85		.nonatomic = 1,
86		.dynamic = 1,
87		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
88		.dpcm_playback = 1,
89		.dpcm_capture = 1,
90		SND_SOC_DAILINK_REG(system, dummy, platform),
91	},
92	{
93		.name = "Offload0",
94		.stream_name = "Offload0 Playback",
95		.nonatomic = 1,
96		.dynamic = 1,
97		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
98		.dpcm_playback = 1,
99		SND_SOC_DAILINK_REG(offload0, dummy, platform),
100	},
101	{
102		.name = "Offload1",
103		.stream_name = "Offload1 Playback",
104		.nonatomic = 1,
105		.dynamic = 1,
106		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
107		.dpcm_playback = 1,
108		SND_SOC_DAILINK_REG(offload1, dummy, platform),
109	},
110	{
111		.name = "Loopback",
112		.stream_name = "Loopback",
113		.nonatomic = 1,
114		.dynamic = 1,
115		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
116		.dpcm_capture = 1,
117		SND_SOC_DAILINK_REG(loopback, dummy, platform),
118	},
119	/* Back End DAI links */
120	{
121		/* SSP0 - Codec */
122		.name = "Codec",
123		.id = 0,
124		.nonatomic = 1,
125		.no_pcm = 1,
126		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC,
127		.ignore_pmdown_time = 1,
128		.be_hw_params_fixup = codec_link_hw_params_fixup,
129		.ops = &codec_link_ops,
130		.dpcm_playback = 1,
131		.dpcm_capture = 1,
132		SND_SOC_DAILINK_REG(ssp0_port, codec, platform),
133	},
134};
135
136static struct snd_soc_card hsw_rt5640_card = {
137	.name = "haswell-rt5640",
138	.owner = THIS_MODULE,
139	.dai_link = card_dai_links,
140	.num_links = ARRAY_SIZE(card_dai_links),
141	.dapm_widgets = card_widgets,
142	.num_dapm_widgets = ARRAY_SIZE(card_widgets),
143	.dapm_routes = card_routes,
144	.num_dapm_routes = ARRAY_SIZE(card_routes),
145	.fully_routed = true,
146};
147
148static int hsw_rt5640_probe(struct platform_device *pdev)
149{
150	struct snd_soc_acpi_mach *mach;
151	struct device *dev = &pdev->dev;
152	int ret;
153
154	hsw_rt5640_card.dev = dev;
155	mach = dev_get_platdata(dev);
156
157	ret = snd_soc_fixup_dai_links_platform_name(&hsw_rt5640_card, mach->mach_params.platform);
158	if (ret)
159		return ret;
160
161	return devm_snd_soc_register_card(dev, &hsw_rt5640_card);
162}
163
164static struct platform_driver hsw_rt5640_driver = {
165	.probe = hsw_rt5640_probe,
166	.driver = {
167		.name = "hsw_rt5640",
168		.pm = &snd_soc_pm_ops,
169	},
170};
171
172module_platform_driver(hsw_rt5640_driver)
173
174MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
175MODULE_DESCRIPTION("Sound card driver for Intel Haswell Lynx Point with Realtek 5640");
176MODULE_LICENSE("GPL");
177MODULE_ALIAS("platform:hsw_rt5640");
178