summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/iio/adc/ti-ads112c14.c152
1 files changed, 149 insertions, 3 deletions
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index bc0fd6839b72..3bf27c30fd00 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -9,12 +9,14 @@
*/
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/math64.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -128,6 +130,15 @@
#define ADS112C14_INT_REF0_mV 1250
#define ADS112C14_INT_REF1_mV 2500
+/*
+ * Available gains as tenths (e.g. value 5 == 0.5 gain). Indexes correspond to
+ * ADS112C14_GAIN_CFG_GAIN values.
+ */
+static const u32 ads112c14_pga_gains_x10[] = {
+ 5, 10, 20, 40, 50, 80, 100, 160, /* 0 - 7 */
+ 200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */
+};
+
struct ads112c14_chip_info {
const char *name;
u8 device_id;
@@ -189,12 +200,17 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
.address = 1,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
| BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
},
};
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
+ /* Synchronizes access to register value fields. */
+ struct mutex lock;
+ u8 sys_mon_chan_short_gain_val;
+ int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
};
static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
@@ -250,15 +266,21 @@ static const struct regmap_config ads112c14_regmap_config = {
static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
+ u32 gain_val;
int ret;
- /* TODO: GAIN is useful for shorted PGA inputs. */
- /* All SYS_MON channels use GAIN of 1 to keep it simple. */
+ /*
+ * All SYS_MON channels use GAIN of 1 to keep it simple. Other than
+ * the internal short channel, where it is useful in practice.
+ */
+ gain_val = chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT ?
+ data->sys_mon_chan_short_gain_val : 1;
+
ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
ADS112C14_GAIN_CFG_SYS_MON |
ADS112C14_GAIN_CFG_GAIN,
FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
- FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
+ FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, gain_val));
if (ret)
return ret;
@@ -301,6 +323,8 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
u32 reg_val;
int ret;
+ guard(mutex)(&data->lock);
+
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
/* Not implemented yet. */
return -EINVAL;
@@ -336,6 +360,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
int *val, int *val2, long mask)
{
struct ads112c14_data *data = iio_priv(indio_dev);
+ const int *scale_avail;
u32 vref_uV, fsr_bits;
/* Selecting V_REF source is not implemented yet. */
@@ -385,6 +410,19 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_FRACTIONAL_LOG2;
}
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ u8 idx;
+
+ guard(mutex)(&data->lock);
+
+ idx = data->sys_mon_chan_short_gain_val;
+ scale_avail = data->sys_mon_chan_short_scale_available[idx];
+ *val = scale_avail[0];
+ *val2 = scale_avail[1];
+
+ return IIO_VAL_DECIMAL64_PICO;
+ }
+
*val = vref_uV / (MICRO / MILLI);
/*
@@ -417,6 +455,75 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
}
}
+static int ads112c14_read_avail(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, const int **vals,
+ int *type, int *length, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ guard(mutex)(&data->lock);
+
+ *vals = (const int *)data->sys_mon_chan_short_scale_available;
+ *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
+ *type = IIO_VAL_DECIMAL64_PICO;
+ return IIO_AVAIL_LIST;
+ }
+
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct ads112c14_data *data = iio_priv(indio_dev);
+ const int (*scale_avail)[2];
+ u8 *gain_val;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE: {
+ guard(mutex)(&data->lock);
+
+ if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+ scale_avail = data->sys_mon_chan_short_scale_available;
+ gain_val = &data->sys_mon_chan_short_gain_val;
+ } else {
+ return -EINVAL;
+ }
+
+ for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+ if (iio_val_s64_compose(val, val2) ==
+ iio_val_s64_compose(scale_avail[i][0], scale_avail[i][1])) {
+ *gain_val = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static int ads112c14_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, char *label)
{
@@ -448,9 +555,39 @@ static int ads112c14_read_label(struct iio_dev *indio_dev,
static const struct iio_info ads112c14_info = {
.read_raw = ads112c14_read_raw,
+ .read_avail = ads112c14_read_avail,
+ .write_raw = ads112c14_write_raw,
+ .write_raw_get_fmt = ads112c14_write_raw_get_fmt,
.read_label = ads112c14_read_label,
};
+static void ads112c14_populate_scale_available(int (*scale_avail)[2],
+ u32 full_scale, u32 fsr_bits)
+{
+ for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+ u64 gain_x10 = ads112c14_pga_gains_x10[i];
+ s64 scale;
+
+ scale = div64_u64((u64)PICO * 10U * full_scale,
+ gain_x10 * BIT(fsr_bits));
+
+ iio_val_s64_decompose(scale, &scale_avail[i][0],
+ &scale_avail[i][1]);
+ }
+}
+
+static void ads112c14_populate_tables(struct ads112c14_data *data)
+{
+ u32 full_scale, fsr_bits;
+
+ /* For now, assuming all sys_mon channels are using 2.5V reference. */
+ full_scale = ADS112C14_INT_REF1_mV;
+ fsr_bits = data->chip_info->resolution_bits - 1;
+
+ ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available,
+ full_scale, fsr_bits);
+}
+
static int ads112c14_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -471,6 +608,10 @@ static int ads112c14_probe(struct i2c_client *client)
data = iio_priv(indio_dev);
data->chip_info = info;
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
ret = devm_regulator_get_enable(dev, "dvdd");
if (ret)
return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
@@ -506,6 +647,9 @@ static int ads112c14_probe(struct i2c_client *client)
if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
return dev_err_probe(dev, -EIO, "reset failed\n");
+ /* Default gain after reset is 1. */
+ data->sys_mon_chan_short_gain_val = 1;
+
/*
* Clear reset bit to prepare for next probe. And clear AVDD fault since
* that happens on every reset.
@@ -532,6 +676,8 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
+ ads112c14_populate_tables(data);
+
indio_dev->name = info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ads112c14_sys_mon_channels;