1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * mt7986-wm8960.c -- MT7986-WM8960 ALSA SoC machine driver |
4 | * |
5 | * Copyright (c) 2023 MediaTek Inc. |
6 | * Authors: Vic Wu <vic.wu@mediatek.com> |
7 | * Maso Huang <maso.huang@mediatek.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <sound/soc.h> |
12 | |
13 | #include "mt7986-afe-common.h" |
14 | |
15 | static const struct snd_soc_dapm_widget mt7986_wm8960_widgets[] = { |
16 | SND_SOC_DAPM_HP("Headphone" , NULL), |
17 | SND_SOC_DAPM_MIC("AMIC" , NULL), |
18 | }; |
19 | |
20 | static const struct snd_kcontrol_new mt7986_wm8960_controls[] = { |
21 | SOC_DAPM_PIN_SWITCH("Headphone" ), |
22 | SOC_DAPM_PIN_SWITCH("AMIC" ), |
23 | }; |
24 | |
25 | SND_SOC_DAILINK_DEFS(playback, |
26 | DAILINK_COMP_ARRAY(COMP_CPU("DL1" )), |
27 | DAILINK_COMP_ARRAY(COMP_DUMMY()), |
28 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
29 | |
30 | SND_SOC_DAILINK_DEFS(capture, |
31 | DAILINK_COMP_ARRAY(COMP_CPU("UL1" )), |
32 | DAILINK_COMP_ARRAY(COMP_DUMMY()), |
33 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
34 | |
35 | SND_SOC_DAILINK_DEFS(codec, |
36 | DAILINK_COMP_ARRAY(COMP_CPU("ETDM" )), |
37 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi" )), |
38 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
39 | |
40 | static struct snd_soc_dai_link mt7986_wm8960_dai_links[] = { |
41 | /* FE */ |
42 | { |
43 | .name = "wm8960-playback" , |
44 | .stream_name = "wm8960-playback" , |
45 | .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
46 | SND_SOC_DPCM_TRIGGER_POST}, |
47 | .dynamic = 1, |
48 | .dpcm_playback = 1, |
49 | SND_SOC_DAILINK_REG(playback), |
50 | }, |
51 | { |
52 | .name = "wm8960-capture" , |
53 | .stream_name = "wm8960-capture" , |
54 | .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
55 | SND_SOC_DPCM_TRIGGER_POST}, |
56 | .dynamic = 1, |
57 | .dpcm_capture = 1, |
58 | SND_SOC_DAILINK_REG(capture), |
59 | }, |
60 | /* BE */ |
61 | { |
62 | .name = "wm8960-codec" , |
63 | .no_pcm = 1, |
64 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
65 | SND_SOC_DAIFMT_NB_NF | |
66 | SND_SOC_DAIFMT_CBS_CFS | |
67 | SND_SOC_DAIFMT_GATED, |
68 | .dpcm_playback = 1, |
69 | .dpcm_capture = 1, |
70 | SND_SOC_DAILINK_REG(codec), |
71 | }, |
72 | }; |
73 | |
74 | static struct snd_soc_card mt7986_wm8960_card = { |
75 | .name = "mt7986-wm8960" , |
76 | .owner = THIS_MODULE, |
77 | .dai_link = mt7986_wm8960_dai_links, |
78 | .num_links = ARRAY_SIZE(mt7986_wm8960_dai_links), |
79 | .controls = mt7986_wm8960_controls, |
80 | .num_controls = ARRAY_SIZE(mt7986_wm8960_controls), |
81 | .dapm_widgets = mt7986_wm8960_widgets, |
82 | .num_dapm_widgets = ARRAY_SIZE(mt7986_wm8960_widgets), |
83 | }; |
84 | |
85 | static int mt7986_wm8960_machine_probe(struct platform_device *pdev) |
86 | { |
87 | struct snd_soc_card *card = &mt7986_wm8960_card; |
88 | struct snd_soc_dai_link *dai_link; |
89 | struct device_node *platform, *codec; |
90 | struct device_node *platform_dai_node, *codec_dai_node; |
91 | int ret, i; |
92 | |
93 | card->dev = &pdev->dev; |
94 | |
95 | platform = of_get_child_by_name(node: pdev->dev.of_node, name: "platform" ); |
96 | |
97 | if (platform) { |
98 | platform_dai_node = of_parse_phandle(np: platform, phandle_name: "sound-dai" , index: 0); |
99 | of_node_put(node: platform); |
100 | |
101 | if (!platform_dai_node) { |
102 | dev_err(&pdev->dev, "Failed to parse platform/sound-dai property\n" ); |
103 | return -EINVAL; |
104 | } |
105 | } else { |
106 | dev_err(&pdev->dev, "Property 'platform' missing or invalid\n" ); |
107 | return -EINVAL; |
108 | } |
109 | |
110 | for_each_card_prelinks(card, i, dai_link) { |
111 | if (dai_link->platforms->name) |
112 | continue; |
113 | dai_link->platforms->of_node = platform_dai_node; |
114 | } |
115 | |
116 | codec = of_get_child_by_name(node: pdev->dev.of_node, name: "codec" ); |
117 | |
118 | if (codec) { |
119 | codec_dai_node = of_parse_phandle(np: codec, phandle_name: "sound-dai" , index: 0); |
120 | of_node_put(node: codec); |
121 | |
122 | if (!codec_dai_node) { |
123 | of_node_put(node: platform_dai_node); |
124 | dev_err(&pdev->dev, "Failed to parse codec/sound-dai property\n" ); |
125 | return -EINVAL; |
126 | } |
127 | } else { |
128 | of_node_put(node: platform_dai_node); |
129 | dev_err(&pdev->dev, "Property 'codec' missing or invalid\n" ); |
130 | return -EINVAL; |
131 | } |
132 | |
133 | for_each_card_prelinks(card, i, dai_link) { |
134 | if (dai_link->codecs->name) |
135 | continue; |
136 | dai_link->codecs->of_node = codec_dai_node; |
137 | } |
138 | |
139 | ret = snd_soc_of_parse_audio_routing(card, propname: "audio-routing" ); |
140 | if (ret) { |
141 | dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n" , ret); |
142 | goto err_of_node_put; |
143 | } |
144 | |
145 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
146 | if (ret) { |
147 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "%s snd_soc_register_card fail\n" , __func__); |
148 | goto err_of_node_put; |
149 | } |
150 | |
151 | err_of_node_put: |
152 | of_node_put(node: platform_dai_node); |
153 | of_node_put(node: codec_dai_node); |
154 | return ret; |
155 | } |
156 | |
157 | static const struct of_device_id mt7986_wm8960_machine_dt_match[] = { |
158 | {.compatible = "mediatek,mt7986-wm8960-sound" }, |
159 | { /* sentinel */ } |
160 | }; |
161 | MODULE_DEVICE_TABLE(of, mt7986_wm8960_machine_dt_match); |
162 | |
163 | static struct platform_driver mt7986_wm8960_machine = { |
164 | .driver = { |
165 | .name = "mt7986-wm8960" , |
166 | .of_match_table = mt7986_wm8960_machine_dt_match, |
167 | }, |
168 | .probe = mt7986_wm8960_machine_probe, |
169 | }; |
170 | |
171 | module_platform_driver(mt7986_wm8960_machine); |
172 | |
173 | /* Module information */ |
174 | MODULE_DESCRIPTION("MT7986 WM8960 ALSA SoC machine driver" ); |
175 | MODULE_AUTHOR("Vic Wu <vic.wu@mediatek.com>" ); |
176 | MODULE_LICENSE("GPL" ); |
177 | MODULE_ALIAS("mt7986 wm8960 soc card" ); |
178 | |