1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * All Sensors DLH series low voltage digital pressure sensors |
4 | * |
5 | * Copyright (c) 2019 AVL DiTEST GmbH |
6 | * Tomislav Denis <tomislav.denis@avl.com> |
7 | * |
8 | * Datasheet: https://www.allsensors.com/cad/DS-0355_Rev_B.PDF |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/iio/iio.h> |
15 | #include <linux/iio/buffer.h> |
16 | #include <linux/iio/trigger_consumer.h> |
17 | #include <linux/iio/triggered_buffer.h> |
18 | #include <asm/unaligned.h> |
19 | |
20 | /* Commands */ |
21 | #define DLH_START_SINGLE 0xAA |
22 | |
23 | /* Status bits */ |
24 | #define DLH_STATUS_OK 0x40 |
25 | |
26 | /* DLH data format */ |
27 | #define DLH_NUM_READ_BYTES 7 |
28 | #define DLH_NUM_DATA_BYTES 3 |
29 | #define DLH_NUM_PR_BITS 24 |
30 | #define DLH_NUM_TEMP_BITS 24 |
31 | |
32 | /* DLH timings */ |
33 | #define DLH_SINGLE_DUT_MS 5 |
34 | |
35 | enum dhl_ids { |
36 | dlhl60d, |
37 | dlhl60g, |
38 | }; |
39 | |
40 | struct dlh_info { |
41 | u8 osdig; /* digital offset factor */ |
42 | unsigned int fss; /* full scale span (inch H2O) */ |
43 | }; |
44 | |
45 | struct dlh_state { |
46 | struct i2c_client *client; |
47 | struct dlh_info info; |
48 | bool use_interrupt; |
49 | struct completion completion; |
50 | u8 rx_buf[DLH_NUM_READ_BYTES]; |
51 | }; |
52 | |
53 | static struct dlh_info dlh_info_tbl[] = { |
54 | [dlhl60d] = { |
55 | .osdig = 2, |
56 | .fss = 120, |
57 | }, |
58 | [dlhl60g] = { |
59 | .osdig = 10, |
60 | .fss = 60, |
61 | }, |
62 | }; |
63 | |
64 | |
65 | static int dlh_cmd_start_single(struct dlh_state *st) |
66 | { |
67 | int ret; |
68 | |
69 | ret = i2c_smbus_write_byte(client: st->client, DLH_START_SINGLE); |
70 | if (ret) |
71 | dev_err(&st->client->dev, |
72 | "%s: I2C write byte failed\n" , __func__); |
73 | |
74 | return ret; |
75 | } |
76 | |
77 | static int dlh_cmd_read_data(struct dlh_state *st) |
78 | { |
79 | int ret; |
80 | |
81 | ret = i2c_master_recv(client: st->client, buf: st->rx_buf, DLH_NUM_READ_BYTES); |
82 | if (ret < 0) { |
83 | dev_err(&st->client->dev, |
84 | "%s: I2C read block failed\n" , __func__); |
85 | return ret; |
86 | } |
87 | |
88 | if (st->rx_buf[0] != DLH_STATUS_OK) { |
89 | dev_err(&st->client->dev, |
90 | "%s: invalid status 0x%02x\n" , __func__, st->rx_buf[0]); |
91 | return -EBUSY; |
92 | } |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | static int dlh_start_capture_and_read(struct dlh_state *st) |
98 | { |
99 | int ret; |
100 | |
101 | if (st->use_interrupt) |
102 | reinit_completion(x: &st->completion); |
103 | |
104 | ret = dlh_cmd_start_single(st); |
105 | if (ret) |
106 | return ret; |
107 | |
108 | if (st->use_interrupt) { |
109 | ret = wait_for_completion_timeout(x: &st->completion, |
110 | timeout: msecs_to_jiffies(DLH_SINGLE_DUT_MS)); |
111 | if (!ret) { |
112 | dev_err(&st->client->dev, |
113 | "%s: conversion timed out\n" , __func__); |
114 | return -ETIMEDOUT; |
115 | } |
116 | } else { |
117 | mdelay(DLH_SINGLE_DUT_MS); |
118 | } |
119 | |
120 | return dlh_cmd_read_data(st); |
121 | } |
122 | |
123 | static int dlh_read_direct(struct dlh_state *st, |
124 | unsigned int *pressure, unsigned int *temperature) |
125 | { |
126 | int ret; |
127 | |
128 | ret = dlh_start_capture_and_read(st); |
129 | if (ret) |
130 | return ret; |
131 | |
132 | *pressure = get_unaligned_be24(p: &st->rx_buf[1]); |
133 | *temperature = get_unaligned_be24(p: &st->rx_buf[4]); |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int dlh_read_raw(struct iio_dev *indio_dev, |
139 | struct iio_chan_spec const *channel, int *value, |
140 | int *value2, long mask) |
141 | { |
142 | struct dlh_state *st = iio_priv(indio_dev); |
143 | unsigned int pressure, temperature; |
144 | int ret; |
145 | s64 tmp; |
146 | s32 rem; |
147 | |
148 | switch (mask) { |
149 | case IIO_CHAN_INFO_RAW: |
150 | ret = iio_device_claim_direct_mode(indio_dev); |
151 | if (ret) |
152 | return ret; |
153 | |
154 | ret = dlh_read_direct(st, pressure: &pressure, temperature: &temperature); |
155 | iio_device_release_direct_mode(indio_dev); |
156 | if (ret) |
157 | return ret; |
158 | |
159 | switch (channel->type) { |
160 | case IIO_PRESSURE: |
161 | *value = pressure; |
162 | return IIO_VAL_INT; |
163 | |
164 | case IIO_TEMP: |
165 | *value = temperature; |
166 | return IIO_VAL_INT; |
167 | |
168 | default: |
169 | return -EINVAL; |
170 | } |
171 | case IIO_CHAN_INFO_SCALE: |
172 | switch (channel->type) { |
173 | case IIO_PRESSURE: |
174 | tmp = div_s64(dividend: 125LL * st->info.fss * 24909 * 100, |
175 | divisor: 1 << DLH_NUM_PR_BITS); |
176 | tmp = div_s64_rem(dividend: tmp, divisor: 1000000000LL, remainder: &rem); |
177 | *value = tmp; |
178 | *value2 = rem; |
179 | return IIO_VAL_INT_PLUS_NANO; |
180 | |
181 | case IIO_TEMP: |
182 | *value = 125 * 1000; |
183 | *value2 = DLH_NUM_TEMP_BITS; |
184 | return IIO_VAL_FRACTIONAL_LOG2; |
185 | |
186 | default: |
187 | return -EINVAL; |
188 | } |
189 | case IIO_CHAN_INFO_OFFSET: |
190 | switch (channel->type) { |
191 | case IIO_PRESSURE: |
192 | *value = -125 * st->info.fss * 24909; |
193 | *value2 = 100 * st->info.osdig * 100000; |
194 | return IIO_VAL_FRACTIONAL; |
195 | |
196 | case IIO_TEMP: |
197 | *value = -40 * 1000; |
198 | return IIO_VAL_INT; |
199 | |
200 | default: |
201 | return -EINVAL; |
202 | } |
203 | } |
204 | |
205 | return -EINVAL; |
206 | } |
207 | |
208 | static const struct iio_info dlh_info = { |
209 | .read_raw = dlh_read_raw, |
210 | }; |
211 | |
212 | static const struct iio_chan_spec dlh_channels[] = { |
213 | { |
214 | .type = IIO_PRESSURE, |
215 | .indexed = 1, |
216 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
217 | .info_mask_shared_by_type = |
218 | BIT(IIO_CHAN_INFO_SCALE) | |
219 | BIT(IIO_CHAN_INFO_OFFSET), |
220 | .scan_index = 0, |
221 | .scan_type = { |
222 | .sign = 'u', |
223 | .realbits = DLH_NUM_PR_BITS, |
224 | .storagebits = 32, |
225 | .shift = 8, |
226 | .endianness = IIO_BE, |
227 | }, |
228 | }, { |
229 | .type = IIO_TEMP, |
230 | .indexed = 1, |
231 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
232 | .info_mask_shared_by_type = |
233 | BIT(IIO_CHAN_INFO_SCALE) | |
234 | BIT(IIO_CHAN_INFO_OFFSET), |
235 | .scan_index = 1, |
236 | .scan_type = { |
237 | .sign = 'u', |
238 | .realbits = DLH_NUM_TEMP_BITS, |
239 | .storagebits = 32, |
240 | .shift = 8, |
241 | .endianness = IIO_BE, |
242 | }, |
243 | } |
244 | }; |
245 | |
246 | static irqreturn_t dlh_trigger_handler(int irq, void *private) |
247 | { |
248 | struct iio_poll_func *pf = private; |
249 | struct iio_dev *indio_dev = pf->indio_dev; |
250 | struct dlh_state *st = iio_priv(indio_dev); |
251 | int ret; |
252 | unsigned int chn, i = 0; |
253 | __be32 tmp_buf[2] = { }; |
254 | |
255 | ret = dlh_start_capture_and_read(st); |
256 | if (ret) |
257 | goto out; |
258 | |
259 | for_each_set_bit(chn, indio_dev->active_scan_mask, |
260 | indio_dev->masklength) { |
261 | memcpy(&tmp_buf[i++], |
262 | &st->rx_buf[1] + chn * DLH_NUM_DATA_BYTES, |
263 | DLH_NUM_DATA_BYTES); |
264 | } |
265 | |
266 | iio_push_to_buffers(indio_dev, data: tmp_buf); |
267 | |
268 | out: |
269 | iio_trigger_notify_done(trig: indio_dev->trig); |
270 | |
271 | return IRQ_HANDLED; |
272 | } |
273 | |
274 | static irqreturn_t dlh_interrupt(int irq, void *private) |
275 | { |
276 | struct iio_dev *indio_dev = private; |
277 | struct dlh_state *st = iio_priv(indio_dev); |
278 | |
279 | complete(&st->completion); |
280 | |
281 | return IRQ_HANDLED; |
282 | }; |
283 | |
284 | static int dlh_probe(struct i2c_client *client) |
285 | { |
286 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
287 | struct dlh_state *st; |
288 | struct iio_dev *indio_dev; |
289 | int ret; |
290 | |
291 | if (!i2c_check_functionality(adap: client->adapter, |
292 | I2C_FUNC_I2C | I2C_FUNC_SMBUS_WRITE_BYTE)) { |
293 | dev_err(&client->dev, |
294 | "adapter doesn't support required i2c functionality\n" ); |
295 | return -EOPNOTSUPP; |
296 | } |
297 | |
298 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*st)); |
299 | if (!indio_dev) { |
300 | dev_err(&client->dev, "failed to allocate iio device\n" ); |
301 | return -ENOMEM; |
302 | } |
303 | |
304 | i2c_set_clientdata(client, data: indio_dev); |
305 | |
306 | st = iio_priv(indio_dev); |
307 | st->info = dlh_info_tbl[id->driver_data]; |
308 | st->client = client; |
309 | st->use_interrupt = false; |
310 | |
311 | indio_dev->name = id->name; |
312 | indio_dev->info = &dlh_info; |
313 | indio_dev->modes = INDIO_DIRECT_MODE; |
314 | indio_dev->channels = dlh_channels; |
315 | indio_dev->num_channels = ARRAY_SIZE(dlh_channels); |
316 | |
317 | if (client->irq > 0) { |
318 | ret = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, |
319 | handler: dlh_interrupt, NULL, |
320 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, |
321 | devname: id->name, dev_id: indio_dev); |
322 | if (ret) { |
323 | dev_err(&client->dev, "failed to allocate threaded irq" ); |
324 | return ret; |
325 | } |
326 | |
327 | st->use_interrupt = true; |
328 | init_completion(x: &st->completion); |
329 | } |
330 | |
331 | ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, |
332 | NULL, &dlh_trigger_handler, NULL); |
333 | if (ret) { |
334 | dev_err(&client->dev, "failed to setup iio buffer\n" ); |
335 | return ret; |
336 | } |
337 | |
338 | ret = devm_iio_device_register(&client->dev, indio_dev); |
339 | if (ret) |
340 | dev_err(&client->dev, "failed to register iio device\n" ); |
341 | |
342 | return ret; |
343 | } |
344 | |
345 | static const struct of_device_id dlh_of_match[] = { |
346 | { .compatible = "asc,dlhl60d" }, |
347 | { .compatible = "asc,dlhl60g" }, |
348 | {} |
349 | }; |
350 | MODULE_DEVICE_TABLE(of, dlh_of_match); |
351 | |
352 | static const struct i2c_device_id dlh_id[] = { |
353 | { "dlhl60d" , dlhl60d }, |
354 | { "dlhl60g" , dlhl60g }, |
355 | {} |
356 | }; |
357 | MODULE_DEVICE_TABLE(i2c, dlh_id); |
358 | |
359 | static struct i2c_driver dlh_driver = { |
360 | .driver = { |
361 | .name = "dlhl60d" , |
362 | .of_match_table = dlh_of_match, |
363 | }, |
364 | .probe = dlh_probe, |
365 | .id_table = dlh_id, |
366 | }; |
367 | module_i2c_driver(dlh_driver); |
368 | |
369 | MODULE_AUTHOR("Tomislav Denis <tomislav.denis@avl.com>" ); |
370 | MODULE_DESCRIPTION("Driver for All Sensors DLH series pressure sensors" ); |
371 | MODULE_LICENSE("GPL v2" ); |
372 | |