1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * dmic.c -- SoC audio for Generic Digital MICs |
4 | * |
5 | * Author: Liam Girdwood <lrg@slimlogic.co.uk> |
6 | */ |
7 | |
8 | #include <linux/delay.h> |
9 | #include <linux/gpio.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/module.h> |
14 | #include <sound/core.h> |
15 | #include <sound/pcm.h> |
16 | #include <sound/soc.h> |
17 | #include <sound/soc-dapm.h> |
18 | |
19 | #define MAX_MODESWITCH_DELAY 70 |
20 | static int modeswitch_delay; |
21 | module_param(modeswitch_delay, uint, 0644); |
22 | |
23 | static int wakeup_delay; |
24 | module_param(wakeup_delay, uint, 0644); |
25 | |
26 | struct dmic { |
27 | struct gpio_desc *gpio_en; |
28 | int wakeup_delay; |
29 | /* Delay after DMIC mode switch */ |
30 | int modeswitch_delay; |
31 | }; |
32 | |
33 | static int dmic_daiops_trigger(struct snd_pcm_substream *substream, |
34 | int cmd, struct snd_soc_dai *dai) |
35 | { |
36 | struct snd_soc_component *component = dai->component; |
37 | struct dmic *dmic = snd_soc_component_get_drvdata(c: component); |
38 | |
39 | switch (cmd) { |
40 | case SNDRV_PCM_TRIGGER_STOP: |
41 | if (dmic->modeswitch_delay) |
42 | mdelay(dmic->modeswitch_delay); |
43 | |
44 | break; |
45 | } |
46 | |
47 | return 0; |
48 | } |
49 | |
50 | static const struct snd_soc_dai_ops dmic_dai_ops = { |
51 | .trigger = dmic_daiops_trigger, |
52 | }; |
53 | |
54 | static int dmic_aif_event(struct snd_soc_dapm_widget *w, |
55 | struct snd_kcontrol *kcontrol, int event) { |
56 | struct snd_soc_component *component = snd_soc_dapm_to_component(dapm: w->dapm); |
57 | struct dmic *dmic = snd_soc_component_get_drvdata(c: component); |
58 | |
59 | switch (event) { |
60 | case SND_SOC_DAPM_POST_PMU: |
61 | if (dmic->gpio_en) |
62 | gpiod_set_value_cansleep(desc: dmic->gpio_en, value: 1); |
63 | |
64 | if (dmic->wakeup_delay) |
65 | msleep(msecs: dmic->wakeup_delay); |
66 | break; |
67 | case SND_SOC_DAPM_POST_PMD: |
68 | if (dmic->gpio_en) |
69 | gpiod_set_value_cansleep(desc: dmic->gpio_en, value: 0); |
70 | break; |
71 | } |
72 | |
73 | return 0; |
74 | } |
75 | |
76 | static struct snd_soc_dai_driver dmic_dai = { |
77 | .name = "dmic-hifi" , |
78 | .capture = { |
79 | .stream_name = "Capture" , |
80 | .channels_min = 1, |
81 | .channels_max = 8, |
82 | .rates = SNDRV_PCM_RATE_CONTINUOUS, |
83 | .formats = SNDRV_PCM_FMTBIT_S32_LE |
84 | | SNDRV_PCM_FMTBIT_S24_LE |
85 | | SNDRV_PCM_FMTBIT_S16_LE |
86 | | SNDRV_PCM_FMTBIT_DSD_U8 |
87 | | SNDRV_PCM_FMTBIT_DSD_U16_LE |
88 | | SNDRV_PCM_FMTBIT_DSD_U32_LE, |
89 | }, |
90 | .ops = &dmic_dai_ops, |
91 | }; |
92 | |
93 | static int dmic_component_probe(struct snd_soc_component *component) |
94 | { |
95 | struct dmic *dmic; |
96 | |
97 | dmic = devm_kzalloc(dev: component->dev, size: sizeof(*dmic), GFP_KERNEL); |
98 | if (!dmic) |
99 | return -ENOMEM; |
100 | |
101 | dmic->gpio_en = devm_gpiod_get_optional(dev: component->dev, |
102 | con_id: "dmicen" , flags: GPIOD_OUT_LOW); |
103 | if (IS_ERR(ptr: dmic->gpio_en)) |
104 | return PTR_ERR(ptr: dmic->gpio_en); |
105 | |
106 | device_property_read_u32(dev: component->dev, propname: "wakeup-delay-ms" , |
107 | val: &dmic->wakeup_delay); |
108 | device_property_read_u32(dev: component->dev, propname: "modeswitch-delay-ms" , |
109 | val: &dmic->modeswitch_delay); |
110 | if (wakeup_delay) |
111 | dmic->wakeup_delay = wakeup_delay; |
112 | if (modeswitch_delay) |
113 | dmic->modeswitch_delay = modeswitch_delay; |
114 | |
115 | if (dmic->modeswitch_delay > MAX_MODESWITCH_DELAY) |
116 | dmic->modeswitch_delay = MAX_MODESWITCH_DELAY; |
117 | |
118 | snd_soc_component_set_drvdata(c: component, data: dmic); |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static const struct snd_soc_dapm_widget dmic_dapm_widgets[] = { |
124 | SND_SOC_DAPM_AIF_OUT_E("DMIC AIF" , "Capture" , 0, |
125 | SND_SOC_NOPM, 0, 0, dmic_aif_event, |
126 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), |
127 | SND_SOC_DAPM_INPUT("DMic" ), |
128 | }; |
129 | |
130 | static const struct snd_soc_dapm_route intercon[] = { |
131 | {"DMIC AIF" , NULL, "DMic" }, |
132 | }; |
133 | |
134 | static const struct snd_soc_component_driver soc_dmic = { |
135 | .probe = dmic_component_probe, |
136 | .dapm_widgets = dmic_dapm_widgets, |
137 | .num_dapm_widgets = ARRAY_SIZE(dmic_dapm_widgets), |
138 | .dapm_routes = intercon, |
139 | .num_dapm_routes = ARRAY_SIZE(intercon), |
140 | .idle_bias_on = 1, |
141 | .use_pmdown_time = 1, |
142 | .endianness = 1, |
143 | }; |
144 | |
145 | static int dmic_dev_probe(struct platform_device *pdev) |
146 | { |
147 | int err; |
148 | u32 chans; |
149 | struct snd_soc_dai_driver *dai_drv = &dmic_dai; |
150 | |
151 | if (pdev->dev.of_node) { |
152 | err = of_property_read_u32(np: pdev->dev.of_node, propname: "num-channels" , out_value: &chans); |
153 | if (err && (err != -EINVAL)) |
154 | return err; |
155 | |
156 | if (!err) { |
157 | if (chans < 1 || chans > 8) |
158 | return -EINVAL; |
159 | |
160 | dai_drv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dai_drv), GFP_KERNEL); |
161 | if (!dai_drv) |
162 | return -ENOMEM; |
163 | |
164 | memcpy(dai_drv, &dmic_dai, sizeof(*dai_drv)); |
165 | dai_drv->capture.channels_max = chans; |
166 | } |
167 | } |
168 | |
169 | return devm_snd_soc_register_component(dev: &pdev->dev, |
170 | component_driver: &soc_dmic, dai_drv, num_dai: 1); |
171 | } |
172 | |
173 | MODULE_ALIAS("platform:dmic-codec" ); |
174 | |
175 | static const struct of_device_id dmic_dev_match[] = { |
176 | {.compatible = "dmic-codec" }, |
177 | {} |
178 | }; |
179 | MODULE_DEVICE_TABLE(of, dmic_dev_match); |
180 | |
181 | static struct platform_driver dmic_driver = { |
182 | .driver = { |
183 | .name = "dmic-codec" , |
184 | .of_match_table = dmic_dev_match, |
185 | }, |
186 | .probe = dmic_dev_probe, |
187 | }; |
188 | |
189 | module_platform_driver(dmic_driver); |
190 | |
191 | MODULE_DESCRIPTION("Generic DMIC driver" ); |
192 | MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>" ); |
193 | MODULE_LICENSE("GPL" ); |
194 | |