Commit | Line | Data |
---|---|---|
8e82fe2a | 1 | // SPDX-License-Identifier: GPL-2.0 |
fa8d9151 NA |
2 | /* |
3 | * MAX9759 Amplifier Driver | |
4 | * | |
5 | * Copyright (c) 2017 BayLibre, SAS. | |
6 | * Author: Neil Armstrong <narmstrong@baylibre.com> | |
7 | */ | |
8 | ||
9 | #include <linux/gpio/consumer.h> | |
10 | #include <linux/module.h> | |
11 | #include <sound/soc.h> | |
12 | #include <sound/soc-dapm.h> | |
13 | #include <sound/tlv.h> | |
14 | ||
15 | #define DRV_NAME "max9759" | |
16 | ||
17 | struct max9759 { | |
18 | struct gpio_desc *gpiod_shutdown; | |
19 | struct gpio_desc *gpiod_mute; | |
20 | struct gpio_descs *gpiod_gain; | |
21 | bool is_mute; | |
22 | unsigned int gain; | |
23 | }; | |
24 | ||
25 | static int pga_event(struct snd_soc_dapm_widget *w, | |
26 | struct snd_kcontrol *control, int event) | |
27 | { | |
28 | struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); | |
29 | struct max9759 *priv = snd_soc_component_get_drvdata(c); | |
30 | ||
31 | if (SND_SOC_DAPM_EVENT_ON(event)) | |
32 | gpiod_set_value_cansleep(priv->gpiod_shutdown, 0); | |
33 | else | |
34 | gpiod_set_value_cansleep(priv->gpiod_shutdown, 1); | |
35 | ||
36 | return 0; | |
37 | } | |
38 | ||
39 | /* From 6dB to 24dB in steps of 6dB */ | |
40 | static const DECLARE_TLV_DB_SCALE(speaker_gain_tlv, 600, 600, 0); | |
41 | ||
42 | static int speaker_gain_control_get(struct snd_kcontrol *kcontrol, | |
43 | struct snd_ctl_elem_value *ucontrol) | |
44 | { | |
45 | struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); | |
46 | struct max9759 *priv = snd_soc_component_get_drvdata(c); | |
47 | ||
48 | ucontrol->value.integer.value[0] = priv->gain; | |
49 | ||
50 | return 0; | |
51 | } | |
52 | ||
53 | static const bool speaker_gain_table[4][2] = { | |
54 | /* G1, G2 */ | |
55 | {true, true}, /* +6dB */ | |
56 | {false, true}, /* +12dB */ | |
57 | {true, false}, /* +18dB */ | |
58 | {false, false}, /* +24dB */ | |
59 | }; | |
60 | ||
61 | static int speaker_gain_control_put(struct snd_kcontrol *kcontrol, | |
62 | struct snd_ctl_elem_value *ucontrol) | |
63 | { | |
64 | struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); | |
65 | struct max9759 *priv = snd_soc_component_get_drvdata(c); | |
66 | ||
67 | if (ucontrol->value.integer.value[0] > 3) | |
68 | return -EINVAL; | |
69 | ||
70 | priv->gain = ucontrol->value.integer.value[0]; | |
71 | ||
72 | /* G1 */ | |
73 | gpiod_set_value_cansleep(priv->gpiod_gain->desc[0], | |
74 | speaker_gain_table[priv->gain][0]); | |
75 | /* G2 */ | |
76 | gpiod_set_value_cansleep(priv->gpiod_gain->desc[1], | |
77 | speaker_gain_table[priv->gain][1]); | |
78 | ||
79 | return 1; | |
80 | } | |
81 | ||
82 | static int speaker_mute_get(struct snd_kcontrol *kcontrol, | |
83 | struct snd_ctl_elem_value *ucontrol) | |
84 | { | |
85 | struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); | |
86 | struct max9759 *priv = snd_soc_component_get_drvdata(c); | |
87 | ||
88 | ucontrol->value.integer.value[0] = !priv->is_mute; | |
89 | ||
90 | return 0; | |
91 | } | |
92 | ||
93 | static int speaker_mute_put(struct snd_kcontrol *kcontrol, | |
94 | struct snd_ctl_elem_value *ucontrol) | |
95 | { | |
96 | struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); | |
97 | struct max9759 *priv = snd_soc_component_get_drvdata(c); | |
98 | ||
99 | priv->is_mute = !ucontrol->value.integer.value[0]; | |
100 | ||
101 | gpiod_set_value_cansleep(priv->gpiod_mute, priv->is_mute); | |
102 | ||
103 | return 1; | |
104 | } | |
105 | ||
106 | static const struct snd_kcontrol_new max9759_dapm_controls[] = { | |
107 | SOC_SINGLE_EXT_TLV("Speaker Gain Volume", 0, 0, 3, 0, | |
108 | speaker_gain_control_get, speaker_gain_control_put, | |
109 | speaker_gain_tlv), | |
110 | SOC_SINGLE_BOOL_EXT("Playback Switch", 0, | |
111 | speaker_mute_get, speaker_mute_put), | |
112 | }; | |
113 | ||
114 | static const struct snd_soc_dapm_widget max9759_dapm_widgets[] = { | |
115 | SND_SOC_DAPM_INPUT("INL"), | |
116 | SND_SOC_DAPM_INPUT("INR"), | |
117 | SND_SOC_DAPM_PGA_E("PGA", SND_SOC_NOPM, 0, 0, NULL, 0, pga_event, | |
118 | (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)), | |
119 | SND_SOC_DAPM_OUTPUT("OUTL"), | |
120 | SND_SOC_DAPM_OUTPUT("OUTR"), | |
121 | }; | |
122 | ||
123 | static const struct snd_soc_dapm_route max9759_dapm_routes[] = { | |
124 | { "PGA", NULL, "INL" }, | |
125 | { "PGA", NULL, "INR" }, | |
126 | { "OUTL", NULL, "PGA" }, | |
127 | { "OUTR", NULL, "PGA" }, | |
128 | }; | |
129 | ||
130 | static const struct snd_soc_component_driver max9759_component_driver = { | |
131 | .controls = max9759_dapm_controls, | |
132 | .num_controls = ARRAY_SIZE(max9759_dapm_controls), | |
133 | .dapm_widgets = max9759_dapm_widgets, | |
134 | .num_dapm_widgets = ARRAY_SIZE(max9759_dapm_widgets), | |
135 | .dapm_routes = max9759_dapm_routes, | |
136 | .num_dapm_routes = ARRAY_SIZE(max9759_dapm_routes), | |
137 | }; | |
138 | ||
139 | static int max9759_probe(struct platform_device *pdev) | |
140 | { | |
141 | struct device *dev = &pdev->dev; | |
142 | struct max9759 *priv; | |
fa8d9151 NA |
143 | |
144 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
145 | if (!priv) | |
146 | return -ENOMEM; | |
147 | ||
148 | platform_set_drvdata(pdev, priv); | |
149 | ||
150 | priv->gpiod_shutdown = devm_gpiod_get(dev, "shutdown", GPIOD_OUT_HIGH); | |
6df96c8f KM |
151 | if (IS_ERR(priv->gpiod_shutdown)) |
152 | return dev_err_probe(dev, PTR_ERR(priv->gpiod_shutdown), | |
153 | "Failed to get 'shutdown' gpio"); | |
fa8d9151 NA |
154 | |
155 | priv->gpiod_mute = devm_gpiod_get(dev, "mute", GPIOD_OUT_HIGH); | |
6df96c8f KM |
156 | if (IS_ERR(priv->gpiod_mute)) |
157 | return dev_err_probe(dev, PTR_ERR(priv->gpiod_mute), | |
158 | "Failed to get 'mute' gpio"); | |
fa8d9151 NA |
159 | priv->is_mute = true; |
160 | ||
161 | priv->gpiod_gain = devm_gpiod_get_array(dev, "gain", GPIOD_OUT_HIGH); | |
6df96c8f KM |
162 | if (IS_ERR(priv->gpiod_gain)) |
163 | return dev_err_probe(dev, PTR_ERR(priv->gpiod_gain), | |
164 | "Failed to get 'gain' gpios"); | |
fa8d9151 NA |
165 | priv->gain = 0; |
166 | ||
167 | if (priv->gpiod_gain->ndescs != 2) { | |
168 | dev_err(dev, "Invalid 'gain' gpios count: %d", | |
169 | priv->gpiod_gain->ndescs); | |
170 | return -EINVAL; | |
171 | } | |
172 | ||
173 | return devm_snd_soc_register_component(dev, &max9759_component_driver, | |
174 | NULL, 0); | |
175 | } | |
176 | ||
177 | #ifdef CONFIG_OF | |
178 | static const struct of_device_id max9759_ids[] = { | |
179 | { .compatible = "maxim,max9759", }, | |
180 | { } | |
181 | }; | |
182 | MODULE_DEVICE_TABLE(of, max9759_ids); | |
183 | #endif | |
184 | ||
185 | static struct platform_driver max9759_driver = { | |
186 | .driver = { | |
187 | .name = DRV_NAME, | |
188 | .of_match_table = of_match_ptr(max9759_ids), | |
189 | }, | |
190 | .probe = max9759_probe, | |
191 | }; | |
192 | ||
193 | module_platform_driver(max9759_driver); | |
194 | ||
195 | MODULE_DESCRIPTION("ASoC MAX9759 amplifier driver"); | |
196 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); | |
197 | MODULE_LICENSE("GPL"); |