1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * The LTC2309 is an 8-Channel, 12-Bit SAR ADC with an I2C Interface. |
4 | * |
5 | * Datasheet: |
6 | * https://www.analog.com/media/en/technical-documentation/data-sheets/2309fd.pdf |
7 | * |
8 | * Copyright (c) 2023, Liam Beguin <liambeguin@gmail.com> |
9 | */ |
10 | #include <linux/bitfield.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/iio/iio.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/regulator/consumer.h> |
17 | |
18 | #define LTC2309_ADC_RESOLUTION 12 |
19 | |
20 | #define LTC2309_DIN_CH_MASK GENMASK(7, 4) |
21 | #define LTC2309_DIN_SDN BIT(7) |
22 | #define LTC2309_DIN_OSN BIT(6) |
23 | #define LTC2309_DIN_S1 BIT(5) |
24 | #define LTC2309_DIN_S0 BIT(4) |
25 | #define LTC2309_DIN_UNI BIT(3) |
26 | #define LTC2309_DIN_SLEEP BIT(2) |
27 | |
28 | /** |
29 | * struct ltc2309 - internal device data structure |
30 | * @dev: Device reference |
31 | * @client: I2C reference |
32 | * @vref: External reference source |
33 | * @lock: Lock to serialize data access |
34 | * @vref_mv: Internal voltage reference |
35 | */ |
36 | struct ltc2309 { |
37 | struct device *dev; |
38 | struct i2c_client *client; |
39 | struct regulator *vref; |
40 | struct mutex lock; /* serialize data access */ |
41 | int vref_mv; |
42 | }; |
43 | |
44 | /* Order matches expected channel address, See datasheet Table 1. */ |
45 | enum ltc2309_channels { |
46 | LTC2309_CH0_CH1 = 0, |
47 | LTC2309_CH2_CH3, |
48 | LTC2309_CH4_CH5, |
49 | LTC2309_CH6_CH7, |
50 | LTC2309_CH1_CH0, |
51 | LTC2309_CH3_CH2, |
52 | LTC2309_CH5_CH4, |
53 | LTC2309_CH7_CH6, |
54 | LTC2309_CH0, |
55 | LTC2309_CH2, |
56 | LTC2309_CH4, |
57 | LTC2309_CH6, |
58 | LTC2309_CH1, |
59 | LTC2309_CH3, |
60 | LTC2309_CH5, |
61 | LTC2309_CH7, |
62 | }; |
63 | |
64 | #define LTC2309_CHAN(_chan, _addr) { \ |
65 | .type = IIO_VOLTAGE, \ |
66 | .indexed = 1, \ |
67 | .address = _addr, \ |
68 | .channel = _chan, \ |
69 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
70 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
71 | } |
72 | |
73 | #define LTC2309_DIFF_CHAN(_chan, _chan2, _addr) { \ |
74 | .type = IIO_VOLTAGE, \ |
75 | .differential = 1, \ |
76 | .indexed = 1, \ |
77 | .address = _addr, \ |
78 | .channel = _chan, \ |
79 | .channel2 = _chan2, \ |
80 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
81 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
82 | } |
83 | |
84 | static const struct iio_chan_spec ltc2309_channels[] = { |
85 | LTC2309_CHAN(0, LTC2309_CH0), |
86 | LTC2309_CHAN(1, LTC2309_CH1), |
87 | LTC2309_CHAN(2, LTC2309_CH2), |
88 | LTC2309_CHAN(3, LTC2309_CH3), |
89 | LTC2309_CHAN(4, LTC2309_CH4), |
90 | LTC2309_CHAN(5, LTC2309_CH5), |
91 | LTC2309_CHAN(6, LTC2309_CH6), |
92 | LTC2309_CHAN(7, LTC2309_CH7), |
93 | LTC2309_DIFF_CHAN(0, 1, LTC2309_CH0_CH1), |
94 | LTC2309_DIFF_CHAN(2, 3, LTC2309_CH2_CH3), |
95 | LTC2309_DIFF_CHAN(4, 5, LTC2309_CH4_CH5), |
96 | LTC2309_DIFF_CHAN(6, 7, LTC2309_CH6_CH7), |
97 | LTC2309_DIFF_CHAN(1, 0, LTC2309_CH1_CH0), |
98 | LTC2309_DIFF_CHAN(3, 2, LTC2309_CH3_CH2), |
99 | LTC2309_DIFF_CHAN(5, 4, LTC2309_CH5_CH4), |
100 | LTC2309_DIFF_CHAN(7, 6, LTC2309_CH7_CH6), |
101 | }; |
102 | |
103 | static int ltc2309_read_raw_channel(struct ltc2309 *ltc2309, |
104 | unsigned long address, int *val) |
105 | { |
106 | int ret; |
107 | u16 buf; |
108 | u8 din; |
109 | |
110 | din = FIELD_PREP(LTC2309_DIN_CH_MASK, address & 0x0f) | |
111 | FIELD_PREP(LTC2309_DIN_UNI, 1) | |
112 | FIELD_PREP(LTC2309_DIN_SLEEP, 0); |
113 | |
114 | ret = i2c_smbus_write_byte(client: ltc2309->client, value: din); |
115 | if (ret < 0) { |
116 | dev_err(ltc2309->dev, "i2c command failed: %pe\n" , |
117 | ERR_PTR(ret)); |
118 | return ret; |
119 | } |
120 | |
121 | ret = i2c_master_recv(client: ltc2309->client, buf: (char *)&buf, count: 2); |
122 | if (ret < 0) { |
123 | dev_err(ltc2309->dev, "i2c read failed: %pe\n" , ERR_PTR(ret)); |
124 | return ret; |
125 | } |
126 | |
127 | *val = be16_to_cpu(buf) >> 4; |
128 | |
129 | return ret; |
130 | } |
131 | |
132 | static int ltc2309_read_raw(struct iio_dev *indio_dev, |
133 | struct iio_chan_spec const *chan, int *val, |
134 | int *val2, long mask) |
135 | { |
136 | struct ltc2309 *ltc2309 = iio_priv(indio_dev); |
137 | int ret; |
138 | |
139 | switch (mask) { |
140 | case IIO_CHAN_INFO_RAW: |
141 | mutex_lock(<c2309->lock); |
142 | ret = ltc2309_read_raw_channel(ltc2309, address: chan->address, val); |
143 | mutex_unlock(lock: <c2309->lock); |
144 | if (ret < 0) |
145 | return -EINVAL; |
146 | return IIO_VAL_INT; |
147 | case IIO_CHAN_INFO_SCALE: |
148 | *val = ltc2309->vref_mv; |
149 | *val2 = LTC2309_ADC_RESOLUTION; |
150 | return IIO_VAL_FRACTIONAL_LOG2; |
151 | default: |
152 | return -EINVAL; |
153 | } |
154 | } |
155 | |
156 | static const struct iio_info ltc2309_info = { |
157 | .read_raw = ltc2309_read_raw, |
158 | }; |
159 | |
160 | static void ltc2309_regulator_disable(void *regulator) |
161 | { |
162 | regulator_disable(regulator); |
163 | } |
164 | |
165 | static int ltc2309_probe(struct i2c_client *client) |
166 | { |
167 | struct iio_dev *indio_dev; |
168 | struct ltc2309 *ltc2309; |
169 | int ret; |
170 | |
171 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*ltc2309)); |
172 | if (!indio_dev) |
173 | return -ENOMEM; |
174 | |
175 | ltc2309 = iio_priv(indio_dev); |
176 | ltc2309->dev = &indio_dev->dev; |
177 | ltc2309->client = client; |
178 | ltc2309->vref_mv = 4096; /* Default to the internal ref */ |
179 | |
180 | indio_dev->name = "ltc2309" ; |
181 | indio_dev->modes = INDIO_DIRECT_MODE; |
182 | indio_dev->channels = ltc2309_channels; |
183 | indio_dev->num_channels = ARRAY_SIZE(ltc2309_channels); |
184 | indio_dev->info = <c2309_info; |
185 | |
186 | ltc2309->vref = devm_regulator_get_optional(dev: &client->dev, id: "vref" ); |
187 | if (IS_ERR(ptr: ltc2309->vref)) { |
188 | ret = PTR_ERR(ptr: ltc2309->vref); |
189 | if (ret == -ENODEV) |
190 | ltc2309->vref = NULL; |
191 | else |
192 | return ret; |
193 | } |
194 | |
195 | if (ltc2309->vref) { |
196 | ret = regulator_enable(regulator: ltc2309->vref); |
197 | if (ret) |
198 | return dev_err_probe(dev: ltc2309->dev, err: ret, |
199 | fmt: "failed to enable vref\n" ); |
200 | |
201 | ret = devm_add_action_or_reset(ltc2309->dev, |
202 | ltc2309_regulator_disable, |
203 | ltc2309->vref); |
204 | if (ret) { |
205 | return dev_err_probe(dev: ltc2309->dev, err: ret, |
206 | fmt: "failed to add regulator_disable action: %d\n" , |
207 | ret); |
208 | } |
209 | |
210 | ret = regulator_get_voltage(regulator: ltc2309->vref); |
211 | if (ret < 0) |
212 | return ret; |
213 | |
214 | ltc2309->vref_mv = ret / 1000; |
215 | } |
216 | |
217 | mutex_init(<c2309->lock); |
218 | |
219 | return devm_iio_device_register(&client->dev, indio_dev); |
220 | } |
221 | |
222 | static const struct of_device_id ltc2309_of_match[] = { |
223 | { .compatible = "lltc,ltc2309" }, |
224 | { } |
225 | }; |
226 | MODULE_DEVICE_TABLE(of, ltc2309_of_match); |
227 | |
228 | static const struct i2c_device_id ltc2309_id[] = { |
229 | { "ltc2309" }, |
230 | { } |
231 | }; |
232 | MODULE_DEVICE_TABLE(i2c, ltc2309_id); |
233 | |
234 | static struct i2c_driver ltc2309_driver = { |
235 | .driver = { |
236 | .name = "ltc2309" , |
237 | .of_match_table = ltc2309_of_match, |
238 | }, |
239 | .probe = ltc2309_probe, |
240 | .id_table = ltc2309_id, |
241 | }; |
242 | module_i2c_driver(ltc2309_driver); |
243 | |
244 | MODULE_AUTHOR("Liam Beguin <liambeguin@gmail.com>" ); |
245 | MODULE_DESCRIPTION("Linear Technology LTC2309 ADC" ); |
246 | MODULE_LICENSE("GPL v2" ); |
247 | |