1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ASoC driver for PROTO AudioCODEC (with a WM8731) |
4 | * |
5 | * Author: Florian Meier, <koalo@koalo.de> |
6 | * Copyright 2013 |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | |
12 | #include <sound/core.h> |
13 | #include <sound/pcm.h> |
14 | #include <sound/soc.h> |
15 | #include <sound/jack.h> |
16 | |
17 | #include "../codecs/wm8731.h" |
18 | |
19 | #define XTAL_RATE 12288000 /* This is fixed on this board */ |
20 | |
21 | static int snd_proto_init(struct snd_soc_pcm_runtime *rtd) |
22 | { |
23 | struct snd_soc_card *card = rtd->card; |
24 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
25 | |
26 | /* Set proto sysclk */ |
27 | int ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8731_SYSCLK_XTAL, |
28 | XTAL_RATE, SND_SOC_CLOCK_IN); |
29 | if (ret < 0) { |
30 | dev_err(card->dev, "Failed to set WM8731 SYSCLK: %d\n" , |
31 | ret); |
32 | return ret; |
33 | } |
34 | |
35 | return 0; |
36 | } |
37 | |
38 | static const struct snd_soc_dapm_widget snd_proto_widget[] = { |
39 | SND_SOC_DAPM_MIC("Microphone Jack" , NULL), |
40 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
41 | }; |
42 | |
43 | static const struct snd_soc_dapm_route snd_proto_route[] = { |
44 | /* speaker connected to LHPOUT/RHPOUT */ |
45 | {"Headphone Jack" , NULL, "LHPOUT" }, |
46 | {"Headphone Jack" , NULL, "RHPOUT" }, |
47 | |
48 | /* mic is connected to Mic Jack, with WM8731 Mic Bias */ |
49 | {"MICIN" , NULL, "Mic Bias" }, |
50 | {"Mic Bias" , NULL, "Microphone Jack" }, |
51 | }; |
52 | |
53 | /* audio machine driver */ |
54 | static struct snd_soc_card snd_proto = { |
55 | .name = "snd_mikroe_proto" , |
56 | .owner = THIS_MODULE, |
57 | .dapm_widgets = snd_proto_widget, |
58 | .num_dapm_widgets = ARRAY_SIZE(snd_proto_widget), |
59 | .dapm_routes = snd_proto_route, |
60 | .num_dapm_routes = ARRAY_SIZE(snd_proto_route), |
61 | }; |
62 | |
63 | static int snd_proto_probe(struct platform_device *pdev) |
64 | { |
65 | struct snd_soc_dai_link *dai; |
66 | struct snd_soc_dai_link_component *comp; |
67 | struct device_node *np = pdev->dev.of_node; |
68 | struct device_node *codec_np, *cpu_np; |
69 | struct device_node *bitclkmaster = NULL; |
70 | struct device_node *framemaster = NULL; |
71 | unsigned int dai_fmt; |
72 | int ret = 0; |
73 | |
74 | if (!np) { |
75 | dev_err(&pdev->dev, "No device node supplied\n" ); |
76 | return -EINVAL; |
77 | } |
78 | |
79 | snd_proto.dev = &pdev->dev; |
80 | ret = snd_soc_of_parse_card_name(card: &snd_proto, propname: "model" ); |
81 | if (ret) |
82 | return ret; |
83 | |
84 | dai = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dai), GFP_KERNEL); |
85 | if (!dai) |
86 | return -ENOMEM; |
87 | |
88 | /* for cpus/codecs/platforms */ |
89 | comp = devm_kzalloc(dev: &pdev->dev, size: 3 * sizeof(*comp), GFP_KERNEL); |
90 | if (!comp) |
91 | return -ENOMEM; |
92 | |
93 | snd_proto.dai_link = dai; |
94 | snd_proto.num_links = 1; |
95 | |
96 | dai->cpus = &comp[0]; |
97 | dai->num_cpus = 1; |
98 | dai->codecs = &comp[1]; |
99 | dai->num_codecs = 1; |
100 | dai->platforms = &comp[2]; |
101 | dai->num_platforms = 1; |
102 | |
103 | dai->name = "WM8731" ; |
104 | dai->stream_name = "WM8731 HiFi" ; |
105 | dai->codecs->dai_name = "wm8731-hifi" ; |
106 | dai->init = &snd_proto_init; |
107 | |
108 | codec_np = of_parse_phandle(np, phandle_name: "audio-codec" , index: 0); |
109 | if (!codec_np) { |
110 | dev_err(&pdev->dev, "audio-codec node missing\n" ); |
111 | return -EINVAL; |
112 | } |
113 | dai->codecs->of_node = codec_np; |
114 | |
115 | cpu_np = of_parse_phandle(np, phandle_name: "i2s-controller" , index: 0); |
116 | if (!cpu_np) { |
117 | dev_err(&pdev->dev, "i2s-controller missing\n" ); |
118 | ret = -EINVAL; |
119 | goto put_codec_node; |
120 | } |
121 | dai->cpus->of_node = cpu_np; |
122 | dai->platforms->of_node = cpu_np; |
123 | |
124 | dai_fmt = snd_soc_daifmt_parse_format(np, NULL); |
125 | snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL, |
126 | bitclkmaster: &bitclkmaster, framemaster: &framemaster); |
127 | if (bitclkmaster != framemaster) { |
128 | dev_err(&pdev->dev, "Must be the same bitclock and frame master\n" ); |
129 | ret = -EINVAL; |
130 | goto put_cpu_node; |
131 | } |
132 | if (bitclkmaster) { |
133 | if (codec_np == bitclkmaster) |
134 | dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; |
135 | else |
136 | dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; |
137 | } else { |
138 | dai_fmt |= snd_soc_daifmt_parse_clock_provider_as_flag(np, NULL); |
139 | } |
140 | |
141 | |
142 | dai->dai_fmt = dai_fmt; |
143 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card: &snd_proto); |
144 | if (ret) |
145 | dev_err_probe(dev: &pdev->dev, err: ret, |
146 | fmt: "snd_soc_register_card() failed\n" ); |
147 | |
148 | |
149 | put_cpu_node: |
150 | of_node_put(node: bitclkmaster); |
151 | of_node_put(node: framemaster); |
152 | of_node_put(node: cpu_np); |
153 | put_codec_node: |
154 | of_node_put(node: codec_np); |
155 | return ret; |
156 | } |
157 | |
158 | static const struct of_device_id snd_proto_of_match[] = { |
159 | { .compatible = "mikroe,mikroe-proto" , }, |
160 | {}, |
161 | }; |
162 | MODULE_DEVICE_TABLE(of, snd_proto_of_match); |
163 | |
164 | static struct platform_driver snd_proto_driver = { |
165 | .driver = { |
166 | .name = "snd-mikroe-proto" , |
167 | .of_match_table = snd_proto_of_match, |
168 | }, |
169 | .probe = snd_proto_probe, |
170 | }; |
171 | |
172 | module_platform_driver(snd_proto_driver); |
173 | |
174 | MODULE_AUTHOR("Florian Meier" ); |
175 | MODULE_DESCRIPTION("ASoC Driver for PROTO board (WM8731)" ); |
176 | MODULE_LICENSE("GPL" ); |
177 | |