1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ALSA SoC driver for |
4 | * Asahi Kasei AK5386 Single-ended 24-Bit 192kHz delta-sigma ADC |
5 | * |
6 | * (c) 2013 Daniel Mack <zonque@gmail.com> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_gpio.h> |
13 | #include <linux/regulator/consumer.h> |
14 | #include <sound/soc.h> |
15 | #include <sound/pcm.h> |
16 | #include <sound/initval.h> |
17 | |
18 | static const char * const supply_names[] = { |
19 | "va" , "vd" |
20 | }; |
21 | |
22 | struct ak5386_priv { |
23 | int reset_gpio; |
24 | struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; |
25 | }; |
26 | |
27 | static const struct snd_soc_dapm_widget ak5386_dapm_widgets[] = { |
28 | SND_SOC_DAPM_INPUT("AINL" ), |
29 | SND_SOC_DAPM_INPUT("AINR" ), |
30 | }; |
31 | |
32 | static const struct snd_soc_dapm_route ak5386_dapm_routes[] = { |
33 | { "Capture" , NULL, "AINL" }, |
34 | { "Capture" , NULL, "AINR" }, |
35 | }; |
36 | |
37 | static int ak5386_soc_probe(struct snd_soc_component *component) |
38 | { |
39 | struct ak5386_priv *priv = snd_soc_component_get_drvdata(c: component); |
40 | return regulator_bulk_enable(ARRAY_SIZE(priv->supplies), consumers: priv->supplies); |
41 | } |
42 | |
43 | static void ak5386_soc_remove(struct snd_soc_component *component) |
44 | { |
45 | struct ak5386_priv *priv = snd_soc_component_get_drvdata(c: component); |
46 | regulator_bulk_disable(ARRAY_SIZE(priv->supplies), consumers: priv->supplies); |
47 | } |
48 | |
49 | #ifdef CONFIG_PM |
50 | static int ak5386_soc_suspend(struct snd_soc_component *component) |
51 | { |
52 | struct ak5386_priv *priv = snd_soc_component_get_drvdata(c: component); |
53 | regulator_bulk_disable(ARRAY_SIZE(priv->supplies), consumers: priv->supplies); |
54 | return 0; |
55 | } |
56 | |
57 | static int ak5386_soc_resume(struct snd_soc_component *component) |
58 | { |
59 | struct ak5386_priv *priv = snd_soc_component_get_drvdata(c: component); |
60 | return regulator_bulk_enable(ARRAY_SIZE(priv->supplies), consumers: priv->supplies); |
61 | } |
62 | #else |
63 | #define ak5386_soc_suspend NULL |
64 | #define ak5386_soc_resume NULL |
65 | #endif /* CONFIG_PM */ |
66 | |
67 | static const struct snd_soc_component_driver soc_component_ak5386 = { |
68 | .probe = ak5386_soc_probe, |
69 | .remove = ak5386_soc_remove, |
70 | .suspend = ak5386_soc_suspend, |
71 | .resume = ak5386_soc_resume, |
72 | .dapm_widgets = ak5386_dapm_widgets, |
73 | .num_dapm_widgets = ARRAY_SIZE(ak5386_dapm_widgets), |
74 | .dapm_routes = ak5386_dapm_routes, |
75 | .num_dapm_routes = ARRAY_SIZE(ak5386_dapm_routes), |
76 | .idle_bias_on = 1, |
77 | .use_pmdown_time = 1, |
78 | .endianness = 1, |
79 | }; |
80 | |
81 | static int ak5386_set_dai_fmt(struct snd_soc_dai *codec_dai, |
82 | unsigned int format) |
83 | { |
84 | struct snd_soc_component *component = codec_dai->component; |
85 | |
86 | format &= SND_SOC_DAIFMT_FORMAT_MASK; |
87 | if (format != SND_SOC_DAIFMT_LEFT_J && |
88 | format != SND_SOC_DAIFMT_I2S) { |
89 | dev_err(component->dev, "Invalid DAI format\n" ); |
90 | return -EINVAL; |
91 | } |
92 | |
93 | return 0; |
94 | } |
95 | |
96 | static int ak5386_hw_params(struct snd_pcm_substream *substream, |
97 | struct snd_pcm_hw_params *params, |
98 | struct snd_soc_dai *dai) |
99 | { |
100 | struct snd_soc_component *component = dai->component; |
101 | struct ak5386_priv *priv = snd_soc_component_get_drvdata(c: component); |
102 | |
103 | /* |
104 | * From the datasheet: |
105 | * |
106 | * All external clocks (MCLK, SCLK and LRCK) must be present unless |
107 | * PDN pin = āLā. If these clocks are not provided, the AK5386 may |
108 | * draw excess current due to its use of internal dynamically |
109 | * refreshed logic. If the external clocks are not present, place |
110 | * the AK5386 in power-down mode (PDN pin = āLā). |
111 | */ |
112 | |
113 | if (gpio_is_valid(number: priv->reset_gpio)) |
114 | gpio_set_value(gpio: priv->reset_gpio, value: 1); |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | static int ak5386_hw_free(struct snd_pcm_substream *substream, |
120 | struct snd_soc_dai *dai) |
121 | { |
122 | struct snd_soc_component *component = dai->component; |
123 | struct ak5386_priv *priv = snd_soc_component_get_drvdata(c: component); |
124 | |
125 | if (gpio_is_valid(number: priv->reset_gpio)) |
126 | gpio_set_value(gpio: priv->reset_gpio, value: 0); |
127 | |
128 | return 0; |
129 | } |
130 | |
131 | static const struct snd_soc_dai_ops ak5386_dai_ops = { |
132 | .set_fmt = ak5386_set_dai_fmt, |
133 | .hw_params = ak5386_hw_params, |
134 | .hw_free = ak5386_hw_free, |
135 | }; |
136 | |
137 | static struct snd_soc_dai_driver ak5386_dai = { |
138 | .name = "ak5386-hifi" , |
139 | .capture = { |
140 | .stream_name = "Capture" , |
141 | .channels_min = 1, |
142 | .channels_max = 2, |
143 | .rates = SNDRV_PCM_RATE_8000_192000, |
144 | .formats = SNDRV_PCM_FMTBIT_S8 | |
145 | SNDRV_PCM_FMTBIT_S16_LE | |
146 | SNDRV_PCM_FMTBIT_S24_LE | |
147 | SNDRV_PCM_FMTBIT_S24_3LE, |
148 | }, |
149 | .ops = &ak5386_dai_ops, |
150 | }; |
151 | |
152 | #ifdef CONFIG_OF |
153 | static const struct of_device_id ak5386_dt_ids[] = { |
154 | { .compatible = "asahi-kasei,ak5386" , }, |
155 | { } |
156 | }; |
157 | MODULE_DEVICE_TABLE(of, ak5386_dt_ids); |
158 | #endif |
159 | |
160 | static int ak5386_probe(struct platform_device *pdev) |
161 | { |
162 | struct device *dev = &pdev->dev; |
163 | struct ak5386_priv *priv; |
164 | int ret, i; |
165 | |
166 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
167 | if (!priv) |
168 | return -ENOMEM; |
169 | |
170 | dev_set_drvdata(dev, data: priv); |
171 | |
172 | for (i = 0; i < ARRAY_SIZE(supply_names); i++) |
173 | priv->supplies[i].supply = supply_names[i]; |
174 | |
175 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), |
176 | consumers: priv->supplies); |
177 | if (ret < 0) |
178 | return ret; |
179 | |
180 | priv->reset_gpio = of_get_named_gpio(np: dev->of_node, |
181 | list_name: "reset-gpio" , index: 0); |
182 | |
183 | if (gpio_is_valid(number: priv->reset_gpio)) |
184 | if (devm_gpio_request_one(dev, gpio: priv->reset_gpio, |
185 | GPIOF_OUT_INIT_LOW, |
186 | label: "AK5386 Reset" )) |
187 | priv->reset_gpio = -EINVAL; |
188 | |
189 | return devm_snd_soc_register_component(dev, component_driver: &soc_component_ak5386, |
190 | dai_drv: &ak5386_dai, num_dai: 1); |
191 | } |
192 | |
193 | static struct platform_driver ak5386_driver = { |
194 | .probe = ak5386_probe, |
195 | .driver = { |
196 | .name = "ak5386" , |
197 | .of_match_table = of_match_ptr(ak5386_dt_ids), |
198 | }, |
199 | }; |
200 | |
201 | module_platform_driver(ak5386_driver); |
202 | |
203 | MODULE_DESCRIPTION("ASoC driver for AK5386 ADC" ); |
204 | MODULE_AUTHOR("Daniel Mack <zonque@gmail.com>" ); |
205 | MODULE_LICENSE("GPL" ); |
206 | |