1 // SPDX-License-Identifier: GPL-2.0
5 * Copyright (C) 2018 Axentia Technologies AB
7 * Author: Peter Rosin <peda@axentia.se>
10 #include <linux/err.h>
11 #include <linux/gcd.h>
12 #include <linux/iio/consumer.h>
13 #include <linux/iio/iio.h>
14 #include <linux/module.h>
16 #include <linux/of_device.h>
17 #include <linux/platform_device.h>
18 #include <linux/property.h>
23 enum iio_chan_type type
;
24 int (*props
)(struct device
*dev
, struct rescale
*rescale
);
28 const struct rescale_cfg
*cfg
;
29 struct iio_channel
*source
;
30 struct iio_chan_spec chan
;
31 struct iio_chan_spec_ext_info
*ext_info
;
36 static int rescale_read_raw(struct iio_dev
*indio_dev
,
37 struct iio_chan_spec
const *chan
,
38 int *val
, int *val2
, long mask
)
40 struct rescale
*rescale
= iio_priv(indio_dev
);
41 unsigned long long tmp
;
45 case IIO_CHAN_INFO_RAW
:
46 return iio_read_channel_raw(rescale
->source
, val
);
48 case IIO_CHAN_INFO_SCALE
:
49 ret
= iio_read_channel_scale(rescale
->source
, val
, val2
);
51 case IIO_VAL_FRACTIONAL
:
52 *val
*= rescale
->numerator
;
53 *val2
*= rescale
->denominator
;
56 *val
*= rescale
->numerator
;
57 if (rescale
->denominator
== 1)
59 *val2
= rescale
->denominator
;
60 return IIO_VAL_FRACTIONAL
;
61 case IIO_VAL_FRACTIONAL_LOG2
:
62 tmp
= *val
* 1000000000LL;
63 do_div(tmp
, rescale
->denominator
);
64 tmp
*= rescale
->numerator
;
65 do_div(tmp
, 1000000000LL);
76 static int rescale_read_avail(struct iio_dev
*indio_dev
,
77 struct iio_chan_spec
const *chan
,
78 const int **vals
, int *type
, int *length
,
81 struct rescale
*rescale
= iio_priv(indio_dev
);
84 case IIO_CHAN_INFO_RAW
:
86 return iio_read_avail_channel_raw(rescale
->source
,
93 static const struct iio_info rescale_info
= {
94 .read_raw
= rescale_read_raw
,
95 .read_avail
= rescale_read_avail
,
98 static ssize_t
rescale_read_ext_info(struct iio_dev
*indio_dev
,
100 struct iio_chan_spec
const *chan
,
103 struct rescale
*rescale
= iio_priv(indio_dev
);
105 return iio_read_channel_ext_info(rescale
->source
,
106 rescale
->ext_info
[private].name
,
110 static ssize_t
rescale_write_ext_info(struct iio_dev
*indio_dev
,
112 struct iio_chan_spec
const *chan
,
113 const char *buf
, size_t len
)
115 struct rescale
*rescale
= iio_priv(indio_dev
);
117 return iio_write_channel_ext_info(rescale
->source
,
118 rescale
->ext_info
[private].name
,
122 static int rescale_configure_channel(struct device
*dev
,
123 struct rescale
*rescale
)
125 struct iio_chan_spec
*chan
= &rescale
->chan
;
126 struct iio_chan_spec
const *schan
= rescale
->source
->channel
;
129 chan
->output
= schan
->output
;
130 chan
->ext_info
= rescale
->ext_info
;
131 chan
->type
= rescale
->cfg
->type
;
133 if (!iio_channel_has_info(schan
, IIO_CHAN_INFO_RAW
) ||
134 !iio_channel_has_info(schan
, IIO_CHAN_INFO_SCALE
)) {
135 dev_err(dev
, "source channel does not support raw/scale\n");
139 chan
->info_mask_separate
= BIT(IIO_CHAN_INFO_RAW
) |
140 BIT(IIO_CHAN_INFO_SCALE
);
142 if (iio_channel_has_available(schan
, IIO_CHAN_INFO_RAW
))
143 chan
->info_mask_separate_available
|= BIT(IIO_CHAN_INFO_RAW
);
148 static int rescale_current_sense_amplifier_props(struct device
*dev
,
149 struct rescale
*rescale
)
157 ret
= device_property_read_u32(dev
, "sense-resistor-micro-ohms",
160 dev_err(dev
, "failed to read the sense resistance: %d\n", ret
);
164 device_property_read_u32(dev
, "sense-gain-mult", &gain_mult
);
165 device_property_read_u32(dev
, "sense-gain-div", &gain_div
);
168 * Calculate the scaling factor, 1 / (gain * sense), or
169 * gain_div / (gain_mult * sense), while trying to keep the
170 * numerator/denominator from overflowing.
172 factor
= gcd(sense
, 1000000);
173 rescale
->numerator
= 1000000 / factor
;
174 rescale
->denominator
= sense
/ factor
;
176 factor
= gcd(rescale
->numerator
, gain_mult
);
177 rescale
->numerator
/= factor
;
178 rescale
->denominator
*= gain_mult
/ factor
;
180 factor
= gcd(rescale
->denominator
, gain_div
);
181 rescale
->numerator
*= gain_div
/ factor
;
182 rescale
->denominator
/= factor
;
187 static int rescale_current_sense_shunt_props(struct device
*dev
,
188 struct rescale
*rescale
)
194 ret
= device_property_read_u32(dev
, "shunt-resistor-micro-ohms",
197 dev_err(dev
, "failed to read the shunt resistance: %d\n", ret
);
201 factor
= gcd(shunt
, 1000000);
202 rescale
->numerator
= 1000000 / factor
;
203 rescale
->denominator
= shunt
/ factor
;
208 static int rescale_voltage_divider_props(struct device
*dev
,
209 struct rescale
*rescale
)
214 ret
= device_property_read_u32(dev
, "output-ohms",
215 &rescale
->denominator
);
217 dev_err(dev
, "failed to read output-ohms: %d\n", ret
);
221 ret
= device_property_read_u32(dev
, "full-ohms",
222 &rescale
->numerator
);
224 dev_err(dev
, "failed to read full-ohms: %d\n", ret
);
228 factor
= gcd(rescale
->numerator
, rescale
->denominator
);
229 rescale
->numerator
/= factor
;
230 rescale
->denominator
/= factor
;
235 enum rescale_variant
{
236 CURRENT_SENSE_AMPLIFIER
,
241 static const struct rescale_cfg rescale_cfg
[] = {
242 [CURRENT_SENSE_AMPLIFIER
] = {
244 .props
= rescale_current_sense_amplifier_props
,
246 [CURRENT_SENSE_SHUNT
] = {
248 .props
= rescale_current_sense_shunt_props
,
250 [VOLTAGE_DIVIDER
] = {
252 .props
= rescale_voltage_divider_props
,
256 static const struct of_device_id rescale_match
[] = {
257 { .compatible
= "current-sense-amplifier",
258 .data
= &rescale_cfg
[CURRENT_SENSE_AMPLIFIER
], },
259 { .compatible
= "current-sense-shunt",
260 .data
= &rescale_cfg
[CURRENT_SENSE_SHUNT
], },
261 { .compatible
= "voltage-divider",
262 .data
= &rescale_cfg
[VOLTAGE_DIVIDER
], },
265 MODULE_DEVICE_TABLE(of
, rescale_match
);
267 static int rescale_probe(struct platform_device
*pdev
)
269 struct device
*dev
= &pdev
->dev
;
270 struct iio_dev
*indio_dev
;
271 struct iio_channel
*source
;
272 struct rescale
*rescale
;
278 source
= devm_iio_channel_get(dev
, NULL
);
280 return dev_err_probe(dev
, PTR_ERR(source
),
281 "failed to get source channel\n");
283 sizeof_ext_info
= iio_get_channel_ext_info_count(source
);
284 if (sizeof_ext_info
) {
285 sizeof_ext_info
+= 1; /* one extra entry for the sentinel */
286 sizeof_ext_info
*= sizeof(*rescale
->ext_info
);
289 sizeof_priv
= sizeof(*rescale
) + sizeof_ext_info
;
291 indio_dev
= devm_iio_device_alloc(dev
, sizeof_priv
);
295 rescale
= iio_priv(indio_dev
);
297 rescale
->cfg
= of_device_get_match_data(dev
);
298 rescale
->numerator
= 1;
299 rescale
->denominator
= 1;
301 ret
= rescale
->cfg
->props(dev
, rescale
);
305 if (!rescale
->numerator
|| !rescale
->denominator
) {
306 dev_err(dev
, "invalid scaling factor.\n");
310 platform_set_drvdata(pdev
, indio_dev
);
312 rescale
->source
= source
;
314 indio_dev
->name
= dev_name(dev
);
315 indio_dev
->info
= &rescale_info
;
316 indio_dev
->modes
= INDIO_DIRECT_MODE
;
317 indio_dev
->channels
= &rescale
->chan
;
318 indio_dev
->num_channels
= 1;
319 if (sizeof_ext_info
) {
320 rescale
->ext_info
= devm_kmemdup(dev
,
321 source
->channel
->ext_info
,
322 sizeof_ext_info
, GFP_KERNEL
);
323 if (!rescale
->ext_info
)
326 for (i
= 0; rescale
->ext_info
[i
].name
; ++i
) {
327 struct iio_chan_spec_ext_info
*ext_info
=
328 &rescale
->ext_info
[i
];
330 if (source
->channel
->ext_info
[i
].read
)
331 ext_info
->read
= rescale_read_ext_info
;
332 if (source
->channel
->ext_info
[i
].write
)
333 ext_info
->write
= rescale_write_ext_info
;
334 ext_info
->private = i
;
338 ret
= rescale_configure_channel(dev
, rescale
);
342 return devm_iio_device_register(dev
, indio_dev
);
345 static struct platform_driver rescale_driver
= {
346 .probe
= rescale_probe
,
348 .name
= "iio-rescale",
349 .of_match_table
= rescale_match
,
352 module_platform_driver(rescale_driver
);
354 MODULE_DESCRIPTION("IIO rescale driver");
355 MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
356 MODULE_LICENSE("GPL v2");