1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * ADL8113 Low Noise Amplifier with integrated bypass switches
4 *
5 * Copyright 2025 Analog Devices Inc.
6 */
7
8 #include <linux/array_size.h>
9 #include <linux/bitmap.h>
10 #include <linux/device/driver.h>
11 #include <linux/dev_printk.h>
12 #include <linux/err.h>
13 #include <linux/gpio/consumer.h>
14 #include <linux/iio/iio.h>
15 #include <linux/mod_devicetable.h>
16 #include <linux/module.h>
17 #include <linux/platform_device.h>
18 #include <linux/property.h>
19 #include <linux/regulator/consumer.h>
20 #include <linux/types.h>
21
22 enum adl8113_signal_path {
23 ADL8113_INTERNAL_AMP,
24 ADL8113_INTERNAL_BYPASS,
25 ADL8113_EXTERNAL_A,
26 ADL8113_EXTERNAL_B,
27 };
28
29 struct adl8113_gain_config {
30 enum adl8113_signal_path path;
31 int gain_db;
32 };
33
34 struct adl8113_state {
35 struct gpio_descs *gpios;
36 struct adl8113_gain_config *gain_configs;
37 unsigned int num_gain_configs;
38 enum adl8113_signal_path current_path;
39 };
40
41 static const char * const adl8113_supply_names[] = {
42 "vdd1",
43 "vss2",
44 "vdd2",
45 };
46
adl8113_set_path(struct adl8113_state * st,enum adl8113_signal_path path)47 static int adl8113_set_path(struct adl8113_state *st,
48 enum adl8113_signal_path path)
49 {
50 DECLARE_BITMAP(values, 2);
51 int ret;
52
53 /*
54 * Determine GPIO values based on signal path.
55 * Va: bit 0, Vb: bit 1.
56 */
57 switch (path) {
58 case ADL8113_INTERNAL_AMP:
59 bitmap_write(values, 0x00, 0, 2);
60 break;
61 case ADL8113_INTERNAL_BYPASS:
62 bitmap_write(values, 0x03, 0, 2);
63 break;
64 case ADL8113_EXTERNAL_A:
65 bitmap_write(values, 0x02, 0, 2);
66 break;
67 case ADL8113_EXTERNAL_B:
68 bitmap_write(values, 0x01, 0, 2);
69 break;
70 default:
71 return -EINVAL;
72 }
73
74 ret = gpiod_set_array_value_cansleep(st->gpios->ndescs, st->gpios->desc,
75 st->gpios->info, values);
76 if (ret)
77 return ret;
78
79 st->current_path = path;
80 return 0;
81 }
82
adl8113_find_gain_config(struct adl8113_state * st,int gain_db)83 static int adl8113_find_gain_config(struct adl8113_state *st, int gain_db)
84 {
85 unsigned int i;
86
87 for (i = 0; i < st->num_gain_configs; i++) {
88 if (st->gain_configs[i].gain_db == gain_db)
89 return i;
90 }
91 return -EINVAL;
92 }
93
94 static const struct iio_chan_spec adl8113_channels[] = {
95 {
96 .type = IIO_VOLTAGE,
97 .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN),
98 },
99 };
100
adl8113_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)101 static int adl8113_read_raw(struct iio_dev *indio_dev,
102 struct iio_chan_spec const *chan,
103 int *val, int *val2, long mask)
104 {
105 struct adl8113_state *st = iio_priv(indio_dev);
106 unsigned int i;
107
108 switch (mask) {
109 case IIO_CHAN_INFO_HARDWAREGAIN:
110 /* Find current gain configuration */
111 for (i = 0; i < st->num_gain_configs; i++) {
112 if (st->gain_configs[i].path == st->current_path) {
113 *val = st->gain_configs[i].gain_db;
114 *val2 = 0;
115 return IIO_VAL_INT_PLUS_MICRO_DB;
116 }
117 }
118 return -EINVAL;
119 default:
120 return -EINVAL;
121 }
122 }
123
adl8113_write_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int val,int val2,long mask)124 static int adl8113_write_raw(struct iio_dev *indio_dev,
125 struct iio_chan_spec const *chan,
126 int val, int val2, long mask)
127 {
128 struct adl8113_state *st = iio_priv(indio_dev);
129 int config_idx;
130
131 switch (mask) {
132 case IIO_CHAN_INFO_HARDWAREGAIN:
133 if (val2 != 0)
134 return -EINVAL;
135
136 config_idx = adl8113_find_gain_config(st, val);
137 if (config_idx < 0)
138 return config_idx;
139
140 return adl8113_set_path(st, st->gain_configs[config_idx].path);
141 default:
142 return -EINVAL;
143 }
144 }
145
146 static const struct iio_info adl8113_info = {
147 .read_raw = adl8113_read_raw,
148 .write_raw = adl8113_write_raw,
149 };
150
adl8113_init_gain_configs(struct device * dev,struct adl8113_state * st)151 static int adl8113_init_gain_configs(struct device *dev, struct adl8113_state *st)
152 {
153 int external_a_gain, external_b_gain;
154 unsigned int i;
155
156 /*
157 * Allocate for all 4 possible paths:
158 * - Internal amp and bypass (always present)
159 * - External bypass A and B (optional if configured)
160 */
161 st->gain_configs = devm_kcalloc(dev, 4, sizeof(*st->gain_configs),
162 GFP_KERNEL);
163 if (!st->gain_configs)
164 return -ENOMEM;
165
166 /* Start filling the gain configurations with data */
167 i = 0;
168
169 /* Always include internal amplifier (14dB) */
170 st->gain_configs[i++] = (struct adl8113_gain_config) {
171 .path = ADL8113_INTERNAL_AMP,
172 .gain_db = 14,
173 };
174
175 /* Always include internal bypass (-2dB insertion loss) */
176 st->gain_configs[i++] = (struct adl8113_gain_config) {
177 .path = ADL8113_INTERNAL_BYPASS,
178 .gain_db = -2,
179 };
180
181 /* Add external bypass A if configured */
182 if (!device_property_read_u32(dev, "adi,external-bypass-a-gain-db",
183 &external_a_gain)) {
184 st->gain_configs[i++] = (struct adl8113_gain_config) {
185 .path = ADL8113_EXTERNAL_A,
186 .gain_db = external_a_gain,
187 };
188 }
189
190 /* Add external bypass B if configured */
191 if (!device_property_read_u32(dev, "adi,external-bypass-b-gain-db",
192 &external_b_gain)) {
193 st->gain_configs[i++] = (struct adl8113_gain_config) {
194 .path = ADL8113_EXTERNAL_B,
195 .gain_db = external_b_gain,
196 };
197 }
198
199 st->num_gain_configs = i;
200
201 return 0;
202 }
203
adl8113_probe(struct platform_device * pdev)204 static int adl8113_probe(struct platform_device *pdev)
205 {
206 struct device *dev = &pdev->dev;
207 struct adl8113_state *st;
208 struct iio_dev *indio_dev;
209 int ret;
210
211 indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
212 if (!indio_dev)
213 return -ENOMEM;
214
215 st = iio_priv(indio_dev);
216
217 st->gpios = devm_gpiod_get_array(dev, "ctrl", GPIOD_OUT_LOW);
218 if (IS_ERR(st->gpios))
219 return dev_err_probe(dev, PTR_ERR(st->gpios),
220 "failed to get control GPIOs\n");
221
222 if (st->gpios->ndescs != 2)
223 return dev_err_probe(dev, -EINVAL,
224 "expected 2 control GPIOs, got %u\n",
225 st->gpios->ndescs);
226
227 ret = devm_regulator_bulk_get_enable(dev,
228 ARRAY_SIZE(adl8113_supply_names),
229 adl8113_supply_names);
230 if (ret)
231 return dev_err_probe(dev, ret,
232 "failed to get and enable supplies\n");
233
234 /* Initialize gain configurations from devicetree */
235 ret = adl8113_init_gain_configs(dev, st);
236 if (ret)
237 return ret;
238
239 /* Initialize to internal amplifier path (14dB) */
240 ret = adl8113_set_path(st, ADL8113_INTERNAL_AMP);
241 if (ret)
242 return ret;
243
244 indio_dev->info = &adl8113_info;
245 indio_dev->name = "adl8113";
246 indio_dev->channels = adl8113_channels;
247 indio_dev->num_channels = ARRAY_SIZE(adl8113_channels);
248
249 return devm_iio_device_register(dev, indio_dev);
250 }
251
252 static const struct of_device_id adl8113_of_match[] = {
253 { .compatible = "adi,adl8113" },
254 { }
255 };
256 MODULE_DEVICE_TABLE(of, adl8113_of_match);
257
258 static struct platform_driver adl8113_driver = {
259 .driver = {
260 .name = "adl8113",
261 .of_match_table = adl8113_of_match,
262 },
263 .probe = adl8113_probe,
264 };
265 module_platform_driver(adl8113_driver);
266
267 MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
268 MODULE_DESCRIPTION("Analog Devices ADL8113 Low Noise Amplifier");
269 MODULE_LICENSE("GPL");
270