1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * ADMFM2000 Dual Microwave Down Converter |
4 | * |
5 | * Copyright 2024 Analog Devices Inc. |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/err.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/iio/iio.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/property.h> |
17 | |
18 | #define ADMFM2000_MIXER_MODE 0 |
19 | #define ADMFM2000_DIRECT_IF_MODE 1 |
20 | #define ADMFM2000_DSA_GPIOS 5 |
21 | #define ADMFM2000_MODE_GPIOS 2 |
22 | #define ADMFM2000_MAX_GAIN 0 |
23 | #define ADMFM2000_MIN_GAIN -31000 |
24 | #define ADMFM2000_DEFAULT_GAIN -0x20 |
25 | |
26 | struct admfm2000_state { |
27 | struct mutex lock; /* protect sensor state */ |
28 | struct gpio_desc *sw1_ch[2]; |
29 | struct gpio_desc *sw2_ch[2]; |
30 | struct gpio_desc *dsa1_gpios[5]; |
31 | struct gpio_desc *dsa2_gpios[5]; |
32 | u32 gain[2]; |
33 | }; |
34 | |
35 | static int admfm2000_mode(struct iio_dev *indio_dev, u32 chan, u32 mode) |
36 | { |
37 | struct admfm2000_state *st = iio_priv(indio_dev); |
38 | int i; |
39 | |
40 | switch (mode) { |
41 | case ADMFM2000_MIXER_MODE: |
42 | for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { |
43 | gpiod_set_value_cansleep(desc: st->sw1_ch[i], value: (chan == 0) ? 1 : 0); |
44 | gpiod_set_value_cansleep(desc: st->sw2_ch[i], value: (chan == 0) ? 0 : 1); |
45 | } |
46 | return 0; |
47 | case ADMFM2000_DIRECT_IF_MODE: |
48 | for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { |
49 | gpiod_set_value_cansleep(desc: st->sw1_ch[i], value: (chan == 0) ? 0 : 1); |
50 | gpiod_set_value_cansleep(desc: st->sw2_ch[i], value: (chan == 0) ? 1 : 0); |
51 | } |
52 | return 0; |
53 | default: |
54 | return -EINVAL; |
55 | } |
56 | } |
57 | |
58 | static int admfm2000_attenuation(struct iio_dev *indio_dev, u32 chan, u32 value) |
59 | { |
60 | struct admfm2000_state *st = iio_priv(indio_dev); |
61 | int i; |
62 | |
63 | switch (chan) { |
64 | case 0: |
65 | for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) |
66 | gpiod_set_value_cansleep(desc: st->dsa1_gpios[i], value: value & (1 << i)); |
67 | return 0; |
68 | case 1: |
69 | for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) |
70 | gpiod_set_value_cansleep(desc: st->dsa2_gpios[i], value: value & (1 << i)); |
71 | return 0; |
72 | default: |
73 | return -EINVAL; |
74 | } |
75 | } |
76 | |
77 | static int admfm2000_read_raw(struct iio_dev *indio_dev, |
78 | struct iio_chan_spec const *chan, int *val, |
79 | int *val2, long mask) |
80 | { |
81 | struct admfm2000_state *st = iio_priv(indio_dev); |
82 | int gain; |
83 | |
84 | switch (mask) { |
85 | case IIO_CHAN_INFO_HARDWAREGAIN: |
86 | mutex_lock(&st->lock); |
87 | gain = ~(st->gain[chan->channel]) * -1000; |
88 | *val = gain / 1000; |
89 | *val2 = (gain % 1000) * 1000; |
90 | mutex_unlock(lock: &st->lock); |
91 | |
92 | return IIO_VAL_INT_PLUS_MICRO_DB; |
93 | default: |
94 | return -EINVAL; |
95 | } |
96 | } |
97 | |
98 | static int admfm2000_write_raw(struct iio_dev *indio_dev, |
99 | struct iio_chan_spec const *chan, int val, |
100 | int val2, long mask) |
101 | { |
102 | struct admfm2000_state *st = iio_priv(indio_dev); |
103 | int gain, ret; |
104 | |
105 | if (val < 0) |
106 | gain = (val * 1000) - (val2 / 1000); |
107 | else |
108 | gain = (val * 1000) + (val2 / 1000); |
109 | |
110 | if (gain > ADMFM2000_MAX_GAIN || gain < ADMFM2000_MIN_GAIN) |
111 | return -EINVAL; |
112 | |
113 | switch (mask) { |
114 | case IIO_CHAN_INFO_HARDWAREGAIN: |
115 | mutex_lock(&st->lock); |
116 | st->gain[chan->channel] = ~((abs(gain) / 1000) & 0x1F); |
117 | |
118 | ret = admfm2000_attenuation(indio_dev, chan: chan->channel, |
119 | value: st->gain[chan->channel]); |
120 | mutex_unlock(lock: &st->lock); |
121 | return ret; |
122 | default: |
123 | return -EINVAL; |
124 | } |
125 | } |
126 | |
127 | static int admfm2000_write_raw_get_fmt(struct iio_dev *indio_dev, |
128 | struct iio_chan_spec const *chan, |
129 | long mask) |
130 | { |
131 | switch (mask) { |
132 | case IIO_CHAN_INFO_HARDWAREGAIN: |
133 | return IIO_VAL_INT_PLUS_MICRO_DB; |
134 | default: |
135 | return -EINVAL; |
136 | } |
137 | } |
138 | |
139 | static const struct iio_info admfm2000_info = { |
140 | .read_raw = &admfm2000_read_raw, |
141 | .write_raw = &admfm2000_write_raw, |
142 | .write_raw_get_fmt = &admfm2000_write_raw_get_fmt, |
143 | }; |
144 | |
145 | #define ADMFM2000_CHAN(_channel) { \ |
146 | .type = IIO_VOLTAGE, \ |
147 | .output = 1, \ |
148 | .indexed = 1, \ |
149 | .channel = _channel, \ |
150 | .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ |
151 | } |
152 | |
153 | static const struct iio_chan_spec admfm2000_channels[] = { |
154 | ADMFM2000_CHAN(0), |
155 | ADMFM2000_CHAN(1), |
156 | }; |
157 | |
158 | static int admfm2000_channel_config(struct admfm2000_state *st, |
159 | struct iio_dev *indio_dev) |
160 | { |
161 | struct platform_device *pdev = to_platform_device(indio_dev->dev.parent); |
162 | struct device *dev = &pdev->dev; |
163 | struct fwnode_handle *child; |
164 | struct gpio_desc **dsa; |
165 | struct gpio_desc **sw; |
166 | int ret, i; |
167 | bool mode; |
168 | u32 reg; |
169 | |
170 | device_for_each_child_node(dev, child) { |
171 | ret = fwnode_property_read_u32(fwnode: child, propname: "reg" , val: ®); |
172 | if (ret) { |
173 | fwnode_handle_put(fwnode: child); |
174 | return dev_err_probe(dev, err: ret, |
175 | fmt: "Failed to get reg property\n" ); |
176 | } |
177 | |
178 | if (reg >= indio_dev->num_channels) { |
179 | fwnode_handle_put(fwnode: child); |
180 | return dev_err_probe(dev, err: -EINVAL, fmt: "reg bigger than: %d\n" , |
181 | indio_dev->num_channels); |
182 | } |
183 | |
184 | if (fwnode_property_present(fwnode: child, propname: "adi,mixer-mode" )) |
185 | mode = ADMFM2000_MIXER_MODE; |
186 | else |
187 | mode = ADMFM2000_DIRECT_IF_MODE; |
188 | |
189 | switch (reg) { |
190 | case 0: |
191 | sw = st->sw1_ch; |
192 | dsa = st->dsa1_gpios; |
193 | break; |
194 | case 1: |
195 | sw = st->sw2_ch; |
196 | dsa = st->dsa2_gpios; |
197 | break; |
198 | default: |
199 | fwnode_handle_put(fwnode: child); |
200 | return -EINVAL; |
201 | } |
202 | |
203 | for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { |
204 | sw[i] = devm_fwnode_gpiod_get_index(dev, child, con_id: "switch" , |
205 | index: i, flags: GPIOD_OUT_LOW, NULL); |
206 | if (IS_ERR(ptr: sw[i])) { |
207 | fwnode_handle_put(fwnode: child); |
208 | return dev_err_probe(dev, err: PTR_ERR(ptr: sw[i]), |
209 | fmt: "Failed to get gpios\n" ); |
210 | } |
211 | } |
212 | |
213 | for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) { |
214 | dsa[i] = devm_fwnode_gpiod_get_index(dev, child, |
215 | con_id: "attenuation" , index: i, |
216 | flags: GPIOD_OUT_LOW, NULL); |
217 | if (IS_ERR(ptr: dsa[i])) { |
218 | fwnode_handle_put(fwnode: child); |
219 | return dev_err_probe(dev, err: PTR_ERR(ptr: dsa[i]), |
220 | fmt: "Failed to get gpios\n" ); |
221 | } |
222 | } |
223 | |
224 | ret = admfm2000_mode(indio_dev, chan: reg, mode); |
225 | if (ret) { |
226 | fwnode_handle_put(fwnode: child); |
227 | return ret; |
228 | } |
229 | } |
230 | |
231 | return 0; |
232 | } |
233 | |
234 | static int admfm2000_probe(struct platform_device *pdev) |
235 | { |
236 | struct device *dev = &pdev->dev; |
237 | struct admfm2000_state *st; |
238 | struct iio_dev *indio_dev; |
239 | int ret; |
240 | |
241 | indio_dev = devm_iio_device_alloc(parent: dev, sizeof_priv: sizeof(*st)); |
242 | if (!indio_dev) |
243 | return -ENOMEM; |
244 | |
245 | st = iio_priv(indio_dev); |
246 | |
247 | indio_dev->name = "admfm2000" ; |
248 | indio_dev->num_channels = ARRAY_SIZE(admfm2000_channels); |
249 | indio_dev->channels = admfm2000_channels; |
250 | indio_dev->info = &admfm2000_info; |
251 | indio_dev->modes = INDIO_DIRECT_MODE; |
252 | |
253 | st->gain[0] = ADMFM2000_DEFAULT_GAIN; |
254 | st->gain[1] = ADMFM2000_DEFAULT_GAIN; |
255 | |
256 | mutex_init(&st->lock); |
257 | |
258 | ret = admfm2000_channel_config(st, indio_dev); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | return devm_iio_device_register(dev, indio_dev); |
263 | } |
264 | |
265 | static const struct of_device_id admfm2000_of_match[] = { |
266 | { .compatible = "adi,admfm2000" }, |
267 | { } |
268 | }; |
269 | MODULE_DEVICE_TABLE(of, admfm2000_of_match); |
270 | |
271 | static struct platform_driver admfm2000_driver = { |
272 | .driver = { |
273 | .name = "admfm2000" , |
274 | .of_match_table = admfm2000_of_match, |
275 | }, |
276 | .probe = admfm2000_probe, |
277 | }; |
278 | module_platform_driver(admfm2000_driver); |
279 | |
280 | MODULE_AUTHOR("Kim Seer Paller <kimseer.paller@analog.com>" ); |
281 | MODULE_DESCRIPTION("ADMFM2000 Dual Microwave Down Converter" ); |
282 | MODULE_LICENSE("GPL" ); |
283 | |