Merge tag 'v5.15.57' into rpi-5.15.y
[platform/kernel/linux-rpi.git] / sound / soc / bcm / googlevoicehat-codec.c
1 /*
2  * Driver for the Google voiceHAT audio codec for Raspberry Pi.
3  *
4  * Author:      Peter Malkin <petermalkin@google.com>
5  *              Copyright 2016
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  */
16
17 #include <linux/device.h>
18 #include <linux/err.h>
19 #include <linux/gpio.h>
20 #include <linux/gpio/consumer.h>
21 #include <linux/init.h>
22 #include <linux/kernel.h>
23 #include <linux/mod_devicetable.h>
24 #include <linux/module.h>
25 #include <linux/of.h>
26 #include <linux/platform_device.h>
27 #include <linux/version.h>
28 #include <sound/pcm.h>
29 #include <sound/soc.h>
30 #include <sound/soc-dai.h>
31 #include <sound/soc-dapm.h>
32
33 #define ICS43432_RATE_MIN_HZ    7190  /* from data sheet */
34 #define ICS43432_RATE_MAX_HZ    52800 /* from data sheet */
35 /* Delay in enabling SDMODE after clock settles to remove pop */
36 #define SDMODE_DELAY_MS         5
37
38 struct voicehat_priv {
39         struct delayed_work enable_sdmode_work;
40         struct gpio_desc *sdmode_gpio;
41         unsigned long sdmode_delay_jiffies;
42 };
43
44 static void voicehat_enable_sdmode_work(struct work_struct *work)
45 {
46         struct voicehat_priv *voicehat = container_of(work,
47                                                       struct voicehat_priv,
48                                                       enable_sdmode_work.work);
49         gpiod_set_value(voicehat->sdmode_gpio, 1);
50 }
51
52 static int voicehat_component_probe(struct snd_soc_component *component)
53 {
54         struct voicehat_priv *voicehat =
55                                 snd_soc_component_get_drvdata(component);
56
57         voicehat->sdmode_gpio = devm_gpiod_get(component->dev, "sdmode",
58                                                GPIOD_OUT_LOW);
59         if (IS_ERR(voicehat->sdmode_gpio)) {
60                 dev_err(component->dev, "Unable to allocate GPIO pin\n");
61                 return PTR_ERR(voicehat->sdmode_gpio);
62         }
63
64         INIT_DELAYED_WORK(&voicehat->enable_sdmode_work,
65                           voicehat_enable_sdmode_work);
66         return 0;
67 }
68
69 static void voicehat_component_remove(struct snd_soc_component *component)
70 {
71         struct voicehat_priv *voicehat =
72                                 snd_soc_component_get_drvdata(component);
73
74         cancel_delayed_work_sync(&voicehat->enable_sdmode_work);
75 }
76
77 static const struct snd_soc_dapm_widget voicehat_dapm_widgets[] = {
78         SND_SOC_DAPM_OUTPUT("Speaker"),
79 };
80
81 static const struct snd_soc_dapm_route voicehat_dapm_routes[] = {
82         {"Speaker", NULL, "HiFi Playback"},
83 };
84
85 static const struct snd_soc_component_driver voicehat_component_driver = {
86         .probe = voicehat_component_probe,
87         .remove = voicehat_component_remove,
88         .dapm_widgets = voicehat_dapm_widgets,
89         .num_dapm_widgets = ARRAY_SIZE(voicehat_dapm_widgets),
90         .dapm_routes = voicehat_dapm_routes,
91         .num_dapm_routes = ARRAY_SIZE(voicehat_dapm_routes),
92 };
93
94 static int voicehat_daiops_trigger(struct snd_pcm_substream *substream, int cmd,
95                                    struct snd_soc_dai *dai)
96 {
97         struct snd_soc_component *component = dai->component;
98         struct voicehat_priv *voicehat =
99                                 snd_soc_component_get_drvdata(component);
100
101         if (voicehat->sdmode_delay_jiffies == 0)
102                 return 0;
103
104         dev_dbg(dai->dev, "CMD             %d", cmd);
105         dev_dbg(dai->dev, "Playback Active %d", dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]);
106         dev_dbg(dai->dev, "Capture Active  %d", dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]);
107
108         switch (cmd) {
109         case SNDRV_PCM_TRIGGER_START:
110         case SNDRV_PCM_TRIGGER_RESUME:
111         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
112                 if (dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]) {
113                         dev_info(dai->dev, "Enabling audio amp...\n");
114                         queue_delayed_work(
115                                 system_power_efficient_wq,
116                                 &voicehat->enable_sdmode_work,
117                                 voicehat->sdmode_delay_jiffies);
118                 }
119                 break;
120         case SNDRV_PCM_TRIGGER_STOP:
121         case SNDRV_PCM_TRIGGER_SUSPEND:
122         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
123                 if (dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]) {
124                         cancel_delayed_work(&voicehat->enable_sdmode_work);
125                         dev_info(dai->dev, "Disabling audio amp...\n");
126                         gpiod_set_value(voicehat->sdmode_gpio, 0);
127                 }
128                 break;
129         }
130         return 0;
131 }
132
133 static const struct snd_soc_dai_ops voicehat_dai_ops = {
134         .trigger = voicehat_daiops_trigger,
135 };
136
137 static struct snd_soc_dai_driver voicehat_dai = {
138         .name = "voicehat-hifi",
139         .capture = {
140                 .stream_name = "HiFi Capture",
141                 .channels_min = 2,
142                 .channels_max = 2,
143                 .rates = SNDRV_PCM_RATE_48000,
144                 .formats = SNDRV_PCM_FMTBIT_S32_LE
145         },
146         .playback = {
147                 .stream_name = "HiFi Playback",
148                 .channels_min = 2,
149                 .channels_max = 2,
150                 .rates = SNDRV_PCM_RATE_48000,
151                 .formats = SNDRV_PCM_FMTBIT_S32_LE
152         },
153         .ops = &voicehat_dai_ops,
154         .symmetric_rate = 1
155 };
156
157 #ifdef CONFIG_OF
158 static const struct of_device_id voicehat_ids[] = {
159                 { .compatible = "google,voicehat", }, {}
160         };
161         MODULE_DEVICE_TABLE(of, voicehat_ids);
162 #endif
163
164 static int voicehat_platform_probe(struct platform_device *pdev)
165 {
166         struct voicehat_priv *voicehat;
167         unsigned int sdmode_delay;
168         int ret;
169
170         voicehat = devm_kzalloc(&pdev->dev, sizeof(*voicehat), GFP_KERNEL);
171         if (!voicehat)
172                 return -ENOMEM;
173
174         ret = device_property_read_u32(&pdev->dev, "voicehat_sdmode_delay",
175                                        &sdmode_delay);
176
177         if (ret) {
178                 sdmode_delay = SDMODE_DELAY_MS;
179                 dev_info(&pdev->dev,
180                          "property 'voicehat_sdmode_delay' not found default 5 mS");
181         } else {
182                 dev_info(&pdev->dev, "property 'voicehat_sdmode_delay' found delay= %d mS",
183                          sdmode_delay);
184         }
185         voicehat->sdmode_delay_jiffies = msecs_to_jiffies(sdmode_delay);
186
187         dev_set_drvdata(&pdev->dev, voicehat);
188
189         return snd_soc_register_component(&pdev->dev,
190                                           &voicehat_component_driver,
191                                           &voicehat_dai,
192                                           1);
193 }
194
195 static int voicehat_platform_remove(struct platform_device *pdev)
196 {
197         snd_soc_unregister_component(&pdev->dev);
198         return 0;
199 }
200
201 static struct platform_driver voicehat_driver = {
202         .driver = {
203                 .name = "voicehat-codec",
204                 .of_match_table = of_match_ptr(voicehat_ids),
205         },
206         .probe = voicehat_platform_probe,
207         .remove = voicehat_platform_remove,
208 };
209
210 module_platform_driver(voicehat_driver);
211
212 MODULE_DESCRIPTION("Google voiceHAT Codec driver");
213 MODULE_AUTHOR("Peter Malkin <petermalkin@google.com>");
214 MODULE_LICENSE("GPL v2");