Merge tag 'v5.15.57' into rpi-5.15.y
[platform/kernel/linux-rpi.git] / sound / soc / bcm / audioinjector-isolated-soundcard.c
1 /*
2  * ASoC Driver for AudioInjector.net isolated soundcard
3  *
4  *  Created on: 20-February-2020
5  *      Author: flatmax@flatmax.org
6  *              based on audioinjector-octo-soundcard.c
7  *
8  * Copyright (C) 2020 Flatmax Pty. Ltd.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * version 2 as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  */
19
20 #include <linux/module.h>
21 #include <linux/types.h>
22 #include <linux/gpio/consumer.h>
23
24 #include <sound/core.h>
25 #include <sound/soc.h>
26 #include <sound/pcm_params.h>
27 #include <sound/control.h>
28
29 static struct gpio_desc *mute_gpio;
30
31 static const unsigned int audioinjector_isolated_rates[] = {
32         192000, 96000, 48000, 32000, 24000, 16000, 8000
33 };
34
35 static struct snd_pcm_hw_constraint_list audioinjector_isolated_constraints = {
36         .list = audioinjector_isolated_rates,
37         .count = ARRAY_SIZE(audioinjector_isolated_rates),
38 };
39
40 static int audioinjector_isolated_dai_init(struct snd_soc_pcm_runtime *rtd)
41 {
42         int ret=snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
43         if (ret)
44                 return ret;
45
46         return snd_soc_dai_set_bclk_ratio(asoc_rtd_to_cpu(rtd, 0), 64);
47 }
48
49 static int audioinjector_isolated_startup(struct snd_pcm_substream *substream)
50 {
51         snd_pcm_hw_constraint_list(substream->runtime, 0,
52                                 SNDRV_PCM_HW_PARAM_RATE, &audioinjector_isolated_constraints);
53
54         return 0;
55 }
56
57 static int audioinjector_isolated_trigger(struct snd_pcm_substream *substream,
58                                                                 int cmd){
59
60         switch (cmd) {
61         case SNDRV_PCM_TRIGGER_STOP:
62         case SNDRV_PCM_TRIGGER_SUSPEND:
63         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
64                 gpiod_set_value(mute_gpio, 0);
65                 break;
66         case SNDRV_PCM_TRIGGER_START:
67         case SNDRV_PCM_TRIGGER_RESUME:
68         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
69                 gpiod_set_value(mute_gpio, 1);
70                 break;
71         default:
72                 return -EINVAL;
73         }
74         return 0;
75 }
76
77 static struct snd_soc_ops audioinjector_isolated_ops = {
78         .startup        = audioinjector_isolated_startup,
79         .trigger = audioinjector_isolated_trigger,
80 };
81
82 SND_SOC_DAILINK_DEFS(audioinjector_isolated,
83         DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
84         DAILINK_COMP_ARRAY(COMP_CODEC("cs4271.1-0010", "cs4271-hifi")),
85         DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
86
87 static struct snd_soc_dai_link audioinjector_isolated_dai[] = {
88         {
89                 .name = "AudioInjector ISO",
90                 .stream_name = "AI-HIFI",
91                 .ops = &audioinjector_isolated_ops,
92                 .init = audioinjector_isolated_dai_init,
93                 .symmetric_rate = 1,
94                 .symmetric_channels = 1,
95                 .dai_fmt = SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
96                 SND_SOC_DAILINK_REG(audioinjector_isolated),
97         }
98 };
99
100 static const struct snd_soc_dapm_widget audioinjector_isolated_widgets[] = {
101         SND_SOC_DAPM_OUTPUT("OUTPUTS"),
102         SND_SOC_DAPM_INPUT("INPUTS"),
103 };
104
105 static const struct snd_soc_dapm_route audioinjector_isolated_route[] = {
106         /* Balanced outputs */
107         {"OUTPUTS", NULL, "AOUTA+"},
108         {"OUTPUTS", NULL, "AOUTA-"},
109         {"OUTPUTS", NULL, "AOUTB+"},
110         {"OUTPUTS", NULL, "AOUTB-"},
111
112         /* Balanced inputs */
113         {"AINA", NULL, "INPUTS"},
114         {"AINB", NULL, "INPUTS"},
115 };
116
117 static struct snd_soc_card snd_soc_audioinjector_isolated = {
118         .name = "audioinjector-isolated-soundcard",
119         .dai_link = audioinjector_isolated_dai,
120         .num_links = ARRAY_SIZE(audioinjector_isolated_dai),
121
122         .dapm_widgets = audioinjector_isolated_widgets,
123         .num_dapm_widgets = ARRAY_SIZE(audioinjector_isolated_widgets),
124         .dapm_routes = audioinjector_isolated_route,
125         .num_dapm_routes = ARRAY_SIZE(audioinjector_isolated_route),
126 };
127
128 static int audioinjector_isolated_probe(struct platform_device *pdev)
129 {
130         struct snd_soc_card *card = &snd_soc_audioinjector_isolated;
131         int ret;
132
133         card->dev = &pdev->dev;
134
135         if (pdev->dev.of_node) {
136                 struct snd_soc_dai_link *dai = &audioinjector_isolated_dai[0];
137                 struct device_node *i2s_node =
138                                         of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
139
140                 if (i2s_node) {
141                         dai->cpus->dai_name = NULL;
142                         dai->cpus->of_node = i2s_node;
143                         dai->platforms->name = NULL;
144                         dai->platforms->of_node = i2s_node;
145                 } else {
146                                 dev_err(&pdev->dev,
147                                 "i2s-controller missing or invalid in DT\n");
148                                 return -EINVAL;
149                 }
150
151                 mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
152                 if (IS_ERR(mute_gpio)){
153                         dev_err(&pdev->dev, "mute gpio not found in dt overlay\n");
154                         return PTR_ERR(mute_gpio);
155                 }
156         }
157
158         ret = devm_snd_soc_register_card(&pdev->dev, card);
159         if (ret && ret != -EPROBE_DEFER)
160                 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
161         return ret;
162 }
163
164 static const struct of_device_id audioinjector_isolated_of_match[] = {
165         { .compatible = "ai,audioinjector-isolated-soundcard", },
166         {},
167 };
168 MODULE_DEVICE_TABLE(of, audioinjector_isolated_of_match);
169
170 static struct platform_driver audioinjector_isolated_driver = {
171         .driver = {
172                 .name                   = "audioinjector-isolated",
173                 .owner                  = THIS_MODULE,
174                 .of_match_table = audioinjector_isolated_of_match,
175         },
176         .probe  = audioinjector_isolated_probe,
177 };
178
179 module_platform_driver(audioinjector_isolated_driver);
180 MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
181 MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard");
182 MODULE_LICENSE("GPL v2");
183 MODULE_ALIAS("platform:audioinjector-isolated-soundcard");