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
);
279 if (IS_ERR(source
)) {
280 if (PTR_ERR(source
) != -EPROBE_DEFER
)
281 dev_err(dev
, "failed to get source channel\n");
282 return PTR_ERR(source
);
285 sizeof_ext_info
= iio_get_channel_ext_info_count(source
);
286 if (sizeof_ext_info
) {
287 sizeof_ext_info
+= 1; /* one extra entry for the sentinel */
288 sizeof_ext_info
*= sizeof(*rescale
->ext_info
);
291 sizeof_priv
= sizeof(*rescale
) + sizeof_ext_info
;
293 indio_dev
= devm_iio_device_alloc(dev
, sizeof_priv
);
297 rescale
= iio_priv(indio_dev
);
299 rescale
->cfg
= of_device_get_match_data(dev
);
300 rescale
->numerator
= 1;
301 rescale
->denominator
= 1;
303 ret
= rescale
->cfg
->props(dev
, rescale
);
307 if (!rescale
->numerator
|| !rescale
->denominator
) {
308 dev_err(dev
, "invalid scaling factor.\n");
312 platform_set_drvdata(pdev
, indio_dev
);
314 rescale
->source
= source
;
316 indio_dev
->name
= dev_name(dev
);
317 indio_dev
->dev
.parent
= dev
;
318 indio_dev
->info
= &rescale_info
;
319 indio_dev
->modes
= INDIO_DIRECT_MODE
;
320 indio_dev
->channels
= &rescale
->chan
;
321 indio_dev
->num_channels
= 1;
322 if (sizeof_ext_info
) {
323 rescale
->ext_info
= devm_kmemdup(dev
,
324 source
->channel
->ext_info
,
325 sizeof_ext_info
, GFP_KERNEL
);
326 if (!rescale
->ext_info
)
329 for (i
= 0; rescale
->ext_info
[i
].name
; ++i
) {
330 struct iio_chan_spec_ext_info
*ext_info
=
331 &rescale
->ext_info
[i
];
333 if (source
->channel
->ext_info
[i
].read
)
334 ext_info
->read
= rescale_read_ext_info
;
335 if (source
->channel
->ext_info
[i
].write
)
336 ext_info
->write
= rescale_write_ext_info
;
337 ext_info
->private = i
;
341 ret
= rescale_configure_channel(dev
, rescale
);
345 return devm_iio_device_register(dev
, indio_dev
);
348 static struct platform_driver rescale_driver
= {
349 .probe
= rescale_probe
,
351 .name
= "iio-rescale",
352 .of_match_table
= rescale_match
,
355 module_platform_driver(rescale_driver
);
357 MODULE_DESCRIPTION("IIO rescale driver");
358 MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
359 MODULE_LICENSE("GPL v2");