1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * PCM1754 DAC ASoC codec driver
4 *
5 * Copyright (c) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
6 * Copyright (c) 2025 Stefan Kerkmann <s.kerkmann@pengutronix.de>
7 */
8
9 #include <linux/gpio/consumer.h>
10 #include <linux/module.h>
11 #include <linux/regulator/consumer.h>
12
13 #include <sound/pcm_params.h>
14 #include <sound/soc.h>
15
16 struct pcm1754_priv {
17 unsigned int format;
18 struct gpio_desc *gpiod_mute;
19 struct gpio_desc *gpiod_format;
20 };
21
pcm1754_set_dai_fmt(struct snd_soc_dai * codec_dai,unsigned int format)22 static int pcm1754_set_dai_fmt(struct snd_soc_dai *codec_dai,
23 unsigned int format)
24 {
25 struct snd_soc_component *component = codec_dai->component;
26 struct pcm1754_priv *priv = snd_soc_component_get_drvdata(component);
27
28 priv->format = format;
29
30 return 0;
31 }
32
pcm1754_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params,struct snd_soc_dai * codec_dai)33 static int pcm1754_hw_params(struct snd_pcm_substream *substream,
34 struct snd_pcm_hw_params *params,
35 struct snd_soc_dai *codec_dai)
36 {
37 struct snd_soc_component *component = codec_dai->component;
38 struct pcm1754_priv *priv = snd_soc_component_get_drvdata(component);
39 int format;
40
41 switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {
42 case SND_SOC_DAIFMT_RIGHT_J:
43 switch (params_width(params)) {
44 case 16:
45 format = 1;
46 break;
47 default:
48 return -EINVAL;
49 }
50 break;
51 case SND_SOC_DAIFMT_I2S:
52 switch (params_width(params)) {
53 case 16:
54 fallthrough;
55 case 24:
56 format = 0;
57 break;
58 default:
59 return -EINVAL;
60 }
61 break;
62 default:
63 dev_err(component->dev, "Invalid DAI format\n");
64 return -EINVAL;
65 }
66
67 gpiod_set_value_cansleep(priv->gpiod_format, format);
68
69 return 0;
70 }
71
pcm1754_mute_stream(struct snd_soc_dai * dai,int mute,int stream)72 static int pcm1754_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
73 {
74 struct pcm1754_priv *priv = snd_soc_component_get_drvdata(dai->component);
75
76 gpiod_set_value_cansleep(priv->gpiod_mute, mute);
77
78 return 0;
79 }
80
81 static const struct snd_soc_dai_ops pcm1754_dai_ops = {
82 .set_fmt = pcm1754_set_dai_fmt,
83 .hw_params = pcm1754_hw_params,
84 .mute_stream = pcm1754_mute_stream,
85 };
86
87 static const struct snd_soc_dai_driver pcm1754_dai = {
88 .name = "pcm1754",
89 .playback = {
90 .stream_name = "Playback",
91 .channels_min = 2,
92 .channels_max = 2,
93 .rates = SNDRV_PCM_RATE_CONTINUOUS,
94 .rate_min = 5000,
95 .rate_max = 200000,
96 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE
97 },
98 .ops = &pcm1754_dai_ops,
99 };
100
101 static const struct snd_soc_dapm_widget pcm1754_dapm_widgets[] = {
102 SND_SOC_DAPM_REGULATOR_SUPPLY("VCC", 0, 0),
103
104 SND_SOC_DAPM_DAC("DAC1", "Channel 1 Playback", SND_SOC_NOPM, 0, 0),
105 SND_SOC_DAPM_DAC("DAC2", "Channel 2 Playback", SND_SOC_NOPM, 0, 0),
106
107 SND_SOC_DAPM_OUTPUT("VOUTL"),
108 SND_SOC_DAPM_OUTPUT("VOUTR"),
109 };
110
111 static const struct snd_soc_dapm_route pcm1754_dapm_routes[] = {
112 { "DAC1", NULL, "Playback" },
113 { "DAC2", NULL, "Playback" },
114
115 { "DAC1", NULL, "VCC" },
116 { "DAC2", NULL, "VCC" },
117
118 { "VOUTL", NULL, "DAC1" },
119 { "VOUTR", NULL, "DAC2" },
120 };
121
122 static const struct snd_soc_component_driver soc_component_dev_pcm1754 = {
123 .dapm_widgets = pcm1754_dapm_widgets,
124 .num_dapm_widgets = ARRAY_SIZE(pcm1754_dapm_widgets),
125 .dapm_routes = pcm1754_dapm_routes,
126 .num_dapm_routes = ARRAY_SIZE(pcm1754_dapm_routes),
127 };
128
pcm1754_probe(struct platform_device * pdev)129 static int pcm1754_probe(struct platform_device *pdev)
130 {
131 struct pcm1754_priv *priv;
132 struct device *dev = &pdev->dev;
133 struct snd_soc_dai_driver *dai_drv;
134 int ret;
135
136 dai_drv = devm_kmemdup(dev, &pcm1754_dai, sizeof(*dai_drv), GFP_KERNEL);
137 if (!dai_drv)
138 return -ENOMEM;
139
140 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
141 if (!priv)
142 return -ENOMEM;
143
144 priv->gpiod_mute = devm_gpiod_get_optional(dev, "mute", GPIOD_OUT_HIGH);
145 if (IS_ERR(priv->gpiod_mute))
146 return dev_err_probe(dev, PTR_ERR(priv->gpiod_mute),
147 "failed to get mute gpio");
148
149 priv->gpiod_format = devm_gpiod_get_optional(dev, "format", GPIOD_OUT_LOW);
150 if (IS_ERR(priv->gpiod_format))
151 return dev_err_probe(dev, PTR_ERR(priv->gpiod_format),
152 "failed to get format gpio");
153
154 dev_set_drvdata(dev, priv);
155
156 ret = devm_snd_soc_register_component(
157 &pdev->dev, &soc_component_dev_pcm1754, dai_drv, 1);
158 if (ret)
159 return dev_err_probe(dev, ret, "failed to register");
160
161 return 0;
162 }
163
164 #ifdef CONFIG_OF
165 static const struct of_device_id pcm1754_of_match[] = {
166 { .compatible = "ti,pcm1754" },
167 { }
168 };
169 MODULE_DEVICE_TABLE(of, pcm1754_of_match);
170 #endif
171
172 static struct platform_driver pcm1754_codec_driver = {
173 .driver = {
174 .name = "pcm1754-codec",
175 .of_match_table = of_match_ptr(pcm1754_of_match),
176 },
177 .probe = pcm1754_probe,
178 };
179
180 module_platform_driver(pcm1754_codec_driver);
181
182 MODULE_DESCRIPTION("ASoC PCM1754 driver");
183 MODULE_AUTHOR("Alvin Šipraga <alsi@bang-olufsen.dk>");
184 MODULE_AUTHOR("Stefan Kerkmann <s.kerkmann@pengutronix.de>");
185 MODULE_LICENSE("GPL");
186