1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Machine driver for AMD Stoney platform using ES8336 Codec |
4 | * |
5 | * Copyright 2022 Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <sound/core.h> |
9 | #include <sound/soc.h> |
10 | #include <sound/pcm.h> |
11 | #include <sound/pcm_params.h> |
12 | #include <sound/soc-dapm.h> |
13 | #include <sound/jack.h> |
14 | #include <linux/gpio.h> |
15 | #include <linux/device.h> |
16 | #include <linux/dmi.h> |
17 | #include <linux/gpio/consumer.h> |
18 | #include <linux/gpio/machine.h> |
19 | #include <linux/i2c.h> |
20 | #include <linux/input.h> |
21 | #include <linux/module.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/acpi.h> |
24 | |
25 | #include "acp.h" |
26 | |
27 | #define DUAL_CHANNEL 2 |
28 | #define DRV_NAME "acp2x_mach" |
29 | #define ST_JADEITE 1 |
30 | #define ES8336_PLL_FREQ (48000 * 256) |
31 | |
32 | static unsigned long acp2x_machine_id; |
33 | static struct snd_soc_jack st_jack; |
34 | static struct device *codec_dev; |
35 | static struct gpio_desc *gpio_pa; |
36 | |
37 | static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, |
38 | struct snd_kcontrol *kcontrol, int event) |
39 | { |
40 | if (SND_SOC_DAPM_EVENT_ON(event)) |
41 | gpiod_set_value_cansleep(desc: gpio_pa, value: true); |
42 | else |
43 | gpiod_set_value_cansleep(desc: gpio_pa, value: false); |
44 | |
45 | return 0; |
46 | } |
47 | |
48 | static struct snd_soc_jack_pin st_es8316_jack_pins[] = { |
49 | { |
50 | .pin = "Headphone" , |
51 | .mask = SND_JACK_HEADPHONE, |
52 | }, |
53 | { |
54 | .pin = "Headset Mic" , |
55 | .mask = SND_JACK_MICROPHONE, |
56 | }, |
57 | }; |
58 | |
59 | static int st_es8336_init(struct snd_soc_pcm_runtime *rtd) |
60 | { |
61 | int ret; |
62 | struct snd_soc_card *card; |
63 | struct snd_soc_component *codec; |
64 | |
65 | codec = snd_soc_rtd_to_codec(rtd, 0)->component; |
66 | card = rtd->card; |
67 | |
68 | ret = snd_soc_card_jack_new_pins(card, id: "Headset" , type: SND_JACK_HEADSET | SND_JACK_BTN_0, |
69 | jack: &st_jack, pins: st_es8316_jack_pins, |
70 | ARRAY_SIZE(st_es8316_jack_pins)); |
71 | if (ret) { |
72 | dev_err(card->dev, "HP jack creation failed %d\n" , ret); |
73 | return ret; |
74 | } |
75 | snd_jack_set_key(jack: st_jack.jack, type: SND_JACK_BTN_0, KEY_PLAYPAUSE); |
76 | ret = snd_soc_component_set_jack(component: codec, jack: &st_jack, NULL); |
77 | if (ret) { |
78 | dev_err(rtd->dev, "Headset Jack call-back failed: %d\n" , ret); |
79 | return ret; |
80 | } |
81 | return 0; |
82 | } |
83 | |
84 | static const unsigned int st_channels[] = { |
85 | DUAL_CHANNEL, |
86 | }; |
87 | |
88 | static const unsigned int st_rates[] = { |
89 | 48000, |
90 | }; |
91 | |
92 | static const struct snd_pcm_hw_constraint_list st_constraints_rates = { |
93 | .count = ARRAY_SIZE(st_rates), |
94 | .list = st_rates, |
95 | .mask = 0, |
96 | }; |
97 | |
98 | static const struct snd_pcm_hw_constraint_list st_constraints_channels = { |
99 | .count = ARRAY_SIZE(st_channels), |
100 | .list = st_channels, |
101 | .mask = 0, |
102 | }; |
103 | |
104 | static int st_es8336_codec_startup(struct snd_pcm_substream *substream) |
105 | { |
106 | struct snd_pcm_runtime *runtime; |
107 | struct snd_soc_pcm_runtime *rtd; |
108 | struct snd_soc_card *card; |
109 | struct acp_platform_info *machine; |
110 | struct snd_soc_dai *codec_dai; |
111 | int ret; |
112 | |
113 | runtime = substream->runtime; |
114 | rtd = snd_soc_substream_to_rtd(substream); |
115 | card = rtd->card; |
116 | machine = snd_soc_card_get_drvdata(card); |
117 | codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
118 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, ES8336_PLL_FREQ, SND_SOC_CLOCK_IN); |
119 | if (ret < 0) { |
120 | dev_err(rtd->dev, "can't set codec sysclk: %d\n" , ret); |
121 | return ret; |
122 | } |
123 | runtime->hw.channels_max = DUAL_CHANNEL; |
124 | snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_CHANNELS, |
125 | l: &st_constraints_channels); |
126 | snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE, |
127 | l: &st_constraints_rates); |
128 | |
129 | machine->play_i2s_instance = I2S_MICSP_INSTANCE; |
130 | machine->cap_i2s_instance = I2S_MICSP_INSTANCE; |
131 | machine->capture_channel = CAP_CHANNEL0; |
132 | return 0; |
133 | } |
134 | |
135 | static const struct snd_soc_ops st_es8336_ops = { |
136 | .startup = st_es8336_codec_startup, |
137 | }; |
138 | |
139 | SND_SOC_DAILINK_DEF(designware1, |
140 | DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.2.auto" ))); |
141 | SND_SOC_DAILINK_DEF(codec, |
142 | DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00" , "ES8316 HiFi" ))); |
143 | SND_SOC_DAILINK_DEF(platform, |
144 | DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_audio_dma.1.auto" ))); |
145 | |
146 | static struct snd_soc_dai_link st_dai_es8336[] = { |
147 | { |
148 | .name = "amdes8336" , |
149 | .stream_name = "ES8336 HiFi Play" , |
150 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
151 | | SND_SOC_DAIFMT_CBP_CFP, |
152 | .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, |
153 | .dpcm_capture = 1, |
154 | .dpcm_playback = 1, |
155 | .init = st_es8336_init, |
156 | .ops = &st_es8336_ops, |
157 | SND_SOC_DAILINK_REG(designware1, codec, platform), |
158 | }, |
159 | }; |
160 | |
161 | static const struct snd_soc_dapm_widget st_widgets[] = { |
162 | SND_SOC_DAPM_SPK("Speaker" , NULL), |
163 | SND_SOC_DAPM_HP("Headphone" , NULL), |
164 | SND_SOC_DAPM_MIC("Headset Mic" , NULL), |
165 | SND_SOC_DAPM_MIC("Internal Mic" , NULL), |
166 | |
167 | SND_SOC_DAPM_SUPPLY("Speaker Power" , SND_SOC_NOPM, 0, 0, |
168 | sof_es8316_speaker_power_event, |
169 | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), |
170 | }; |
171 | |
172 | static const struct snd_soc_dapm_route st_audio_route[] = { |
173 | {"Speaker" , NULL, "HPOL" }, |
174 | {"Speaker" , NULL, "HPOR" }, |
175 | {"Headphone" , NULL, "HPOL" }, |
176 | {"Headphone" , NULL, "HPOR" }, |
177 | {"MIC1" , NULL, "Headset Mic" }, |
178 | {"MIC2" , NULL, "Internal Mic" }, |
179 | {"Speaker" , NULL, "Speaker Power" }, |
180 | }; |
181 | |
182 | static const struct snd_kcontrol_new st_mc_controls[] = { |
183 | SOC_DAPM_PIN_SWITCH("Speaker" ), |
184 | SOC_DAPM_PIN_SWITCH("Headphone" ), |
185 | SOC_DAPM_PIN_SWITCH("Headset Mic" ), |
186 | SOC_DAPM_PIN_SWITCH("Internal Mic" ), |
187 | }; |
188 | |
189 | static const struct acpi_gpio_params pa_enable_gpio = { 0, 0, false }; |
190 | static const struct acpi_gpio_mapping acpi_es8336_gpios[] = { |
191 | { "pa-enable-gpios" , &pa_enable_gpio, 1 }, |
192 | { } |
193 | }; |
194 | |
195 | static int st_es8336_late_probe(struct snd_soc_card *card) |
196 | { |
197 | struct acpi_device *adev; |
198 | int ret; |
199 | |
200 | adev = acpi_dev_get_first_match_dev(hid: "ESSX8336" , NULL, hrv: -1); |
201 | if (!adev) |
202 | return -ENODEV; |
203 | |
204 | codec_dev = acpi_get_first_physical_node(adev); |
205 | acpi_dev_put(adev); |
206 | if (!codec_dev) |
207 | dev_err(card->dev, "can not find codec dev\n" ); |
208 | |
209 | ret = devm_acpi_dev_add_driver_gpios(dev: codec_dev, gpios: acpi_es8336_gpios); |
210 | if (ret) |
211 | dev_warn(card->dev, "Failed to add driver gpios\n" ); |
212 | |
213 | gpio_pa = gpiod_get_optional(dev: codec_dev, con_id: "pa-enable" , flags: GPIOD_OUT_LOW); |
214 | if (IS_ERR(ptr: gpio_pa)) { |
215 | ret = dev_err_probe(dev: card->dev, err: PTR_ERR(ptr: gpio_pa), |
216 | fmt: "could not get pa-enable GPIO\n" ); |
217 | put_device(dev: codec_dev); |
218 | return ret; |
219 | } |
220 | return 0; |
221 | } |
222 | |
223 | static struct snd_soc_card st_card = { |
224 | .name = "acpes8336" , |
225 | .owner = THIS_MODULE, |
226 | .dai_link = st_dai_es8336, |
227 | .num_links = ARRAY_SIZE(st_dai_es8336), |
228 | .dapm_widgets = st_widgets, |
229 | .num_dapm_widgets = ARRAY_SIZE(st_widgets), |
230 | .dapm_routes = st_audio_route, |
231 | .num_dapm_routes = ARRAY_SIZE(st_audio_route), |
232 | .controls = st_mc_controls, |
233 | .num_controls = ARRAY_SIZE(st_mc_controls), |
234 | .late_probe = st_es8336_late_probe, |
235 | }; |
236 | |
237 | static int st_es8336_quirk_cb(const struct dmi_system_id *id) |
238 | { |
239 | acp2x_machine_id = ST_JADEITE; |
240 | return 1; |
241 | } |
242 | |
243 | static const struct dmi_system_id st_es8336_quirk_table[] = { |
244 | { |
245 | .callback = st_es8336_quirk_cb, |
246 | .matches = { |
247 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMD" ), |
248 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jadeite" ), |
249 | }, |
250 | }, |
251 | { |
252 | .callback = st_es8336_quirk_cb, |
253 | .matches = { |
254 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "IP3 Technology CO.,Ltd." ), |
255 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ASN1D" ), |
256 | }, |
257 | }, |
258 | { |
259 | .callback = st_es8336_quirk_cb, |
260 | .matches = { |
261 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Standard" ), |
262 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ASN10" ), |
263 | }, |
264 | }, |
265 | {} |
266 | }; |
267 | |
268 | static int st_es8336_probe(struct platform_device *pdev) |
269 | { |
270 | int ret; |
271 | struct snd_soc_card *card; |
272 | struct acp_platform_info *machine; |
273 | |
274 | machine = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct acp_platform_info), GFP_KERNEL); |
275 | if (!machine) |
276 | return -ENOMEM; |
277 | |
278 | dmi_check_system(list: st_es8336_quirk_table); |
279 | switch (acp2x_machine_id) { |
280 | case ST_JADEITE: |
281 | card = &st_card; |
282 | st_card.dev = &pdev->dev; |
283 | break; |
284 | default: |
285 | return -ENODEV; |
286 | } |
287 | |
288 | platform_set_drvdata(pdev, data: card); |
289 | snd_soc_card_set_drvdata(card, data: machine); |
290 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card: &st_card); |
291 | if (ret) { |
292 | return dev_err_probe(dev: &pdev->dev, err: ret, |
293 | fmt: "devm_snd_soc_register_card(%s) failed\n" , |
294 | card->name); |
295 | } |
296 | return 0; |
297 | } |
298 | |
299 | #ifdef CONFIG_ACPI |
300 | static const struct acpi_device_id st_audio_acpi_match[] = { |
301 | {"AMDI8336" , 0}, |
302 | {}, |
303 | }; |
304 | MODULE_DEVICE_TABLE(acpi, st_audio_acpi_match); |
305 | #endif |
306 | |
307 | static struct platform_driver st_mach_driver = { |
308 | .driver = { |
309 | .name = "st-es8316" , |
310 | .acpi_match_table = ACPI_PTR(st_audio_acpi_match), |
311 | .pm = &snd_soc_pm_ops, |
312 | }, |
313 | .probe = st_es8336_probe, |
314 | }; |
315 | |
316 | module_platform_driver(st_mach_driver); |
317 | |
318 | MODULE_AUTHOR("Vijendar.Mukunda@amd.com" ); |
319 | MODULE_DESCRIPTION("st-es8316 audio support" ); |
320 | MODULE_LICENSE("GPL v2" ); |
321 | |