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;
37 static int rescale_read_raw(struct iio_dev *indio_dev,
38 struct iio_chan_spec const *chan,
39 int *val, int *val2, long mask)
41 struct rescale *rescale = iio_priv(indio_dev);
46 case IIO_CHAN_INFO_RAW:
47 if (rescale->chan_processed)
49 * When only processed channels are supported, we
50 * read the processed data and scale it by 1/1
51 * augmented with whatever the rescaler has calculated.
53 return iio_read_channel_processed(rescale->source, val);
55 return iio_read_channel_raw(rescale->source, val);
57 case IIO_CHAN_INFO_SCALE:
58 if (rescale->chan_processed) {
60 * Processed channels are scaled 1-to-1
64 ret = IIO_VAL_FRACTIONAL;
66 ret = iio_read_channel_scale(rescale->source, val, val2);
69 case IIO_VAL_FRACTIONAL:
70 *val *= rescale->numerator;
71 *val2 *= rescale->denominator;
74 *val *= rescale->numerator;
75 if (rescale->denominator == 1)
77 *val2 = rescale->denominator;
78 return IIO_VAL_FRACTIONAL;
79 case IIO_VAL_FRACTIONAL_LOG2:
80 tmp = (s64)*val * 1000000000LL;
81 tmp = div_s64(tmp, rescale->denominator);
82 tmp *= rescale->numerator;
83 tmp = div_s64(tmp, 1000000000LL);
94 static int rescale_read_avail(struct iio_dev *indio_dev,
95 struct iio_chan_spec const *chan,
96 const int **vals, int *type, int *length,
99 struct rescale *rescale = iio_priv(indio_dev);
102 case IIO_CHAN_INFO_RAW:
104 return iio_read_avail_channel_raw(rescale->source,
111 static const struct iio_info rescale_info = {
112 .read_raw = rescale_read_raw,
113 .read_avail = rescale_read_avail,
116 static ssize_t rescale_read_ext_info(struct iio_dev *indio_dev,
118 struct iio_chan_spec const *chan,
121 struct rescale *rescale = iio_priv(indio_dev);
123 return iio_read_channel_ext_info(rescale->source,
124 rescale->ext_info[private].name,
128 static ssize_t rescale_write_ext_info(struct iio_dev *indio_dev,
130 struct iio_chan_spec const *chan,
131 const char *buf, size_t len)
133 struct rescale *rescale = iio_priv(indio_dev);
135 return iio_write_channel_ext_info(rescale->source,
136 rescale->ext_info[private].name,
140 static int rescale_configure_channel(struct device *dev,
141 struct rescale *rescale)
143 struct iio_chan_spec *chan = &rescale->chan;
144 struct iio_chan_spec const *schan = rescale->source->channel;
147 chan->output = schan->output;
148 chan->ext_info = rescale->ext_info;
149 chan->type = rescale->cfg->type;
151 if (iio_channel_has_info(schan, IIO_CHAN_INFO_RAW) ||
152 iio_channel_has_info(schan, IIO_CHAN_INFO_SCALE)) {
153 dev_info(dev, "using raw+scale source channel\n");
154 } else if (iio_channel_has_info(schan, IIO_CHAN_INFO_PROCESSED)) {
155 dev_info(dev, "using processed channel\n");
156 rescale->chan_processed = true;
158 dev_err(dev, "source channel is not supported\n");
162 chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
163 BIT(IIO_CHAN_INFO_SCALE);
166 * Using .read_avail() is fringe to begin with and makes no sense
167 * whatsoever for processed channels, so we make sure that this cannot
168 * be called on a processed channel.
170 if (iio_channel_has_available(schan, IIO_CHAN_INFO_RAW) &&
171 !rescale->chan_processed)
172 chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
177 static int rescale_current_sense_amplifier_props(struct device *dev,
178 struct rescale *rescale)
186 ret = device_property_read_u32(dev, "sense-resistor-micro-ohms",
189 dev_err(dev, "failed to read the sense resistance: %d\n", ret);
193 device_property_read_u32(dev, "sense-gain-mult", &gain_mult);
194 device_property_read_u32(dev, "sense-gain-div", &gain_div);
197 * Calculate the scaling factor, 1 / (gain * sense), or
198 * gain_div / (gain_mult * sense), while trying to keep the
199 * numerator/denominator from overflowing.
201 factor = gcd(sense, 1000000);
202 rescale->numerator = 1000000 / factor;
203 rescale->denominator = sense / factor;
205 factor = gcd(rescale->numerator, gain_mult);
206 rescale->numerator /= factor;
207 rescale->denominator *= gain_mult / factor;
209 factor = gcd(rescale->denominator, gain_div);
210 rescale->numerator *= gain_div / factor;
211 rescale->denominator /= factor;
216 static int rescale_current_sense_shunt_props(struct device *dev,
217 struct rescale *rescale)
223 ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms",
226 dev_err(dev, "failed to read the shunt resistance: %d\n", ret);
230 factor = gcd(shunt, 1000000);
231 rescale->numerator = 1000000 / factor;
232 rescale->denominator = shunt / factor;
237 static int rescale_voltage_divider_props(struct device *dev,
238 struct rescale *rescale)
243 ret = device_property_read_u32(dev, "output-ohms",
244 &rescale->denominator);
246 dev_err(dev, "failed to read output-ohms: %d\n", ret);
250 ret = device_property_read_u32(dev, "full-ohms",
251 &rescale->numerator);
253 dev_err(dev, "failed to read full-ohms: %d\n", ret);
257 factor = gcd(rescale->numerator, rescale->denominator);
258 rescale->numerator /= factor;
259 rescale->denominator /= factor;
264 enum rescale_variant {
265 CURRENT_SENSE_AMPLIFIER,
270 static const struct rescale_cfg rescale_cfg[] = {
271 [CURRENT_SENSE_AMPLIFIER] = {
273 .props = rescale_current_sense_amplifier_props,
275 [CURRENT_SENSE_SHUNT] = {
277 .props = rescale_current_sense_shunt_props,
279 [VOLTAGE_DIVIDER] = {
281 .props = rescale_voltage_divider_props,
285 static const struct of_device_id rescale_match[] = {
286 { .compatible = "current-sense-amplifier",
287 .data = &rescale_cfg[CURRENT_SENSE_AMPLIFIER], },
288 { .compatible = "current-sense-shunt",
289 .data = &rescale_cfg[CURRENT_SENSE_SHUNT], },
290 { .compatible = "voltage-divider",
291 .data = &rescale_cfg[VOLTAGE_DIVIDER], },
294 MODULE_DEVICE_TABLE(of, rescale_match);
296 static int rescale_probe(struct platform_device *pdev)
298 struct device *dev = &pdev->dev;
299 struct iio_dev *indio_dev;
300 struct iio_channel *source;
301 struct rescale *rescale;
307 source = devm_iio_channel_get(dev, NULL);
309 return dev_err_probe(dev, PTR_ERR(source),
310 "failed to get source channel\n");
312 sizeof_ext_info = iio_get_channel_ext_info_count(source);
313 if (sizeof_ext_info) {
314 sizeof_ext_info += 1; /* one extra entry for the sentinel */
315 sizeof_ext_info *= sizeof(*rescale->ext_info);
318 sizeof_priv = sizeof(*rescale) + sizeof_ext_info;
320 indio_dev = devm_iio_device_alloc(dev, sizeof_priv);
324 rescale = iio_priv(indio_dev);
326 rescale->cfg = of_device_get_match_data(dev);
327 rescale->numerator = 1;
328 rescale->denominator = 1;
330 ret = rescale->cfg->props(dev, rescale);
334 if (!rescale->numerator || !rescale->denominator) {
335 dev_err(dev, "invalid scaling factor.\n");
339 platform_set_drvdata(pdev, indio_dev);
341 rescale->source = source;
343 indio_dev->name = dev_name(dev);
344 indio_dev->info = &rescale_info;
345 indio_dev->modes = INDIO_DIRECT_MODE;
346 indio_dev->channels = &rescale->chan;
347 indio_dev->num_channels = 1;
348 if (sizeof_ext_info) {
349 rescale->ext_info = devm_kmemdup(dev,
350 source->channel->ext_info,
351 sizeof_ext_info, GFP_KERNEL);
352 if (!rescale->ext_info)
355 for (i = 0; rescale->ext_info[i].name; ++i) {
356 struct iio_chan_spec_ext_info *ext_info =
357 &rescale->ext_info[i];
359 if (source->channel->ext_info[i].read)
360 ext_info->read = rescale_read_ext_info;
361 if (source->channel->ext_info[i].write)
362 ext_info->write = rescale_write_ext_info;
363 ext_info->private = i;
367 ret = rescale_configure_channel(dev, rescale);
371 return devm_iio_device_register(dev, indio_dev);
374 static struct platform_driver rescale_driver = {
375 .probe = rescale_probe,
377 .name = "iio-rescale",
378 .of_match_table = rescale_match,
381 module_platform_driver(rescale_driver);
383 MODULE_DESCRIPTION("IIO rescale driver");
384 MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
385 MODULE_LICENSE("GPL v2");