blob: 36e813d9c73f1c25afe114466ec716a440cf7069 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* IIO driver for PAC1921 High-Side Power/Current Monitor
*
* Copyright (C) 2024 Matteo Martelli <matteomartelli3@gmail.com>
*/
#include <linux/unaligned.h>
#include <linux/bitfield.h>
#include <linux/i2c.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/regmap.h>
#include <linux/units.h>
/* pac1921 registers */
#define PAC1921_REG_GAIN_CFG 0x00
#define PAC1921_REG_INT_CFG 0x01
#define PAC1921_REG_CONTROL 0x02
#define PAC1921_REG_VBUS 0x10
#define PAC1921_REG_VSENSE 0x12
#define PAC1921_REG_OVERFLOW_STS 0x1C
#define PAC1921_REG_VPOWER 0x1D
/* pac1921 gain configuration bits */
#define PAC1921_GAIN_DI_GAIN_MASK GENMASK(5, 3)
#define PAC1921_GAIN_DV_GAIN_MASK GENMASK(2, 0)
/* pac1921 integration configuration bits */
#define PAC1921_INT_CFG_SMPL_MASK GENMASK(7, 4)
#define PAC1921_INT_CFG_VSFEN BIT(3)
#define PAC1921_INT_CFG_VBFEN BIT(2)
#define PAC1921_INT_CFG_RIOV BIT(1)
#define PAC1921_INT_CFG_INTEN BIT(0)
/* pac1921 control bits */
#define PAC1921_CONTROL_MXSL_MASK GENMASK(7, 6)
enum pac1921_mxsl {
PAC1921_MXSL_VPOWER_PIN = 0,
PAC1921_MXSL_VSENSE_FREE_RUN = 1,
PAC1921_MXSL_VBUS_FREE_RUN = 2,
PAC1921_MXSL_VPOWER_FREE_RUN = 3,
};
#define PAC1921_CONTROL_SLEEP BIT(2)
/* pac1921 result registers mask and resolution */
#define PAC1921_RES_MASK GENMASK(15, 6)
#define PAC1921_RES_RESOLUTION 1023
/* pac1921 overflow status bits */
#define PAC1921_OVERFLOW_VSOV BIT(2)
#define PAC1921_OVERFLOW_VBOV BIT(1)
#define PAC1921_OVERFLOW_VPOV BIT(0)
/* pac1921 constants */
#define PAC1921_MAX_VSENSE_MV 100
#define PAC1921_MAX_VBUS_V 32
/* Time to first communication after power up (tINT_T) */
#define PAC1921_POWERUP_TIME_MS 20
/* Time from Sleep State to Start of Integration Period (tSLEEP_TO_INT) */
#define PAC1921_SLEEP_TO_INT_TIME_US 86
/* pac1921 defaults */
#define PAC1921_DEFAULT_DV_GAIN 0 /* 2^(value): 1x gain (HW default) */
#define PAC1921_DEFAULT_DI_GAIN 0 /* 2^(value): 1x gain (HW default) */
#define PAC1921_DEFAULT_NUM_SAMPLES 0 /* 2^(value): 1 sample (HW default) */
/*
* Pre-computed scale factors for BUS voltage
* format: IIO_VAL_INT_PLUS_NANO
* unit: mV
*
* Vbus scale (mV) = max_vbus (mV) / dv_gain / resolution
*/
static const int pac1921_vbus_scales[][2] = {
{ 31, 280547409 }, /* dv_gain x1 */
{ 15, 640273704 }, /* dv_gain x2 */
{ 7, 820136852 }, /* dv_gain x4 */
{ 3, 910068426 }, /* dv_gain x8 */
{ 1, 955034213 }, /* dv_gain x16 */
{ 0, 977517106 }, /* dv_gain x32 */
};
/*
* Pre-computed scales for SENSE voltage
* format: IIO_VAL_INT_PLUS_NANO
* unit: mV
*
* Vsense scale (mV) = max_vsense (mV) / di_gain / resolution
*/
static const int pac1921_vsense_scales[][2] = {
{ 0, 97751710 }, /* di_gain x1 */
{ 0, 48875855 }, /* di_gain x2 */
{ 0, 24437927 }, /* di_gain x4 */
{ 0, 12218963 }, /* di_gain x8 */
{ 0, 6109481 }, /* di_gain x16 */
{ 0, 3054740 }, /* di_gain x32 */
{ 0, 1527370 }, /* di_gain x64 */
{ 0, 763685 }, /* di_gain x128 */
};
/*
* Numbers of samples used to integrate measurements at the end of an
* integration period.
*
* Changing the number of samples affects the integration period: higher the
* number of samples, longer the integration period.
*
* These correspond to the oversampling ratios available exposed to userspace.
*/
static const int pac1921_int_num_samples[] = {
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048
};
/*
* The integration period depends on the configuration of number of integration
* samples, measurement resolution and post filters. The following array
* contains integration periods, in microsecs unit, based on table 4-5 from
* datasheet considering power integration mode, 14-Bit resolution and post
* filters on. Each index corresponds to a specific number of samples from 1
* to 2048.
*/
static const unsigned int pac1921_int_periods_usecs[] = {
2720, /* 1 sample */
4050, /* 2 samples */
6790, /* 4 samples */
12200, /* 8 samples */
23000, /* 16 samples */
46000, /* 32 samples */
92000, /* 64 samples */
184000, /* 128 samples */
368000, /* 256 samples */
736000, /* 512 samples */
1471000, /* 1024 samples */
2941000 /* 2048 samples */
};
/* pac1921 regmap configuration */
static const struct regmap_range pac1921_regmap_wr_ranges[] = {
regmap_reg_range(PAC1921_REG_GAIN_CFG, PAC1921_REG_CONTROL),
};
static const struct regmap_access_table pac1921_regmap_wr_table = {
.yes_ranges = pac1921_regmap_wr_ranges,
.n_yes_ranges = ARRAY_SIZE(pac1921_regmap_wr_ranges),
};
static const struct regmap_range pac1921_regmap_rd_ranges[] = {
regmap_reg_range(PAC1921_REG_GAIN_CFG, PAC1921_REG_CONTROL),
regmap_reg_range(PAC1921_REG_VBUS, PAC1921_REG_VPOWER + 1),
};
static const struct regmap_access_table pac1921_regmap_rd_table = {
.yes_ranges = pac1921_regmap_rd_ranges,
.n_yes_ranges = ARRAY_SIZE(pac1921_regmap_rd_ranges),
};
static const struct regmap_config pac1921_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.rd_table = &pac1921_regmap_rd_table,
.wr_table = &pac1921_regmap_wr_table,
};
enum pac1921_channels {
PAC1921_CHAN_VBUS = 0,
PAC1921_CHAN_VSENSE = 1,
PAC1921_CHAN_CURRENT = 2,
PAC1921_CHAN_POWER = 3,
};
#define PAC1921_NUM_MEAS_CHANS 4
struct pac1921_priv {
struct i2c_client *client;
struct regmap *regmap;
struct regulator *vdd;
struct iio_info iio_info;
/*
* Synchronize access to private members, and ensure atomicity of
* consecutive regmap operations.
*/
struct mutex lock;
u32 rshunt_uohm; /* uOhm */
u8 dv_gain;
u8 di_gain;
u8 n_samples;
u8 prev_ovf_flags;
u8 ovf_enabled_events;
bool first_integr_started;
bool first_integr_done;
unsigned long integr_started_time_jiffies;
unsigned int integr_period_usecs;
int current_scales[ARRAY_SIZE(pac1921_vsense_scales)][2];
struct {
u16 chan[PAC1921_NUM_MEAS_CHANS];
s64 timestamp __aligned(8);
} scan;
};
/*
* Check if first integration after configuration update has completed.
*
* Must be called with lock held.
*/
static bool pac1921_data_ready(struct pac1921_priv *priv)
{
if (!priv->first_integr_started)
return false;
if (!priv->first_integr_done) {
unsigned long t_ready;
/*
* Data valid after the device entered into integration state,
* considering worst case where the device was in sleep state,
* and completed the first integration period.
*/
t_ready = priv->integr_started_time_jiffies +
usecs_to_jiffies(PAC1921_SLEEP_TO_INT_TIME_US) +
usecs_to_jiffies(priv->integr_period_usecs);
if (time_before(jiffies, t_ready))
return false;
priv->first_integr_done = true;
}
return true;
}
static inline void pac1921_calc_scale(int dividend, int divisor, int *val,
int *val2)
{
s64 tmp;
tmp = div_s64(dividend * (s64)NANO, divisor);
*val = (int)div_s64_rem(tmp, NANO, val2);
}
/*
* Fill the table of scale factors for current
* format: IIO_VAL_INT_PLUS_NANO
* unit: mA
*
* Vsense LSB (nV) = max_vsense (nV) * di_gain / resolution
* Current scale (mA) = Vsense LSB (nV) / shunt (uOhm)
*
* Must be called with held lock when updating after first initialization.
*/
static void pac1921_calc_current_scales(struct pac1921_priv *priv)
{
for (unsigned int i = 0; i < ARRAY_SIZE(priv->current_scales); i++) {
int max = (PAC1921_MAX_VSENSE_MV * MICRO) >> i;
int vsense_lsb = DIV_ROUND_CLOSEST(max, PAC1921_RES_RESOLUTION);
pac1921_calc_scale(vsense_lsb, (int)priv->rshunt_uohm,
&priv->current_scales[i][0],
&priv->current_scales[i][1]);
}
}
/*
* Check if overflow occurred and if so, push the corresponding events.
*
* Must be called with lock held.
*/
static int pac1921_check_push_overflow(struct iio_dev *indio_dev, s64 timestamp)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
unsigned int flags;
int ret;
ret = regmap_read(priv->regmap, PAC1921_REG_OVERFLOW_STS, &flags);
if (ret)
return ret;
if (flags & PAC1921_OVERFLOW_VBOV &&
!(priv->prev_ovf_flags & PAC1921_OVERFLOW_VBOV) &&
priv->ovf_enabled_events & PAC1921_OVERFLOW_VBOV) {
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(
IIO_VOLTAGE, PAC1921_CHAN_VBUS,
IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
timestamp);
}
if (flags & PAC1921_OVERFLOW_VSOV &&
!(priv->prev_ovf_flags & PAC1921_OVERFLOW_VSOV) &&
priv->ovf_enabled_events & PAC1921_OVERFLOW_VSOV) {
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(
IIO_VOLTAGE, PAC1921_CHAN_VSENSE,
IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
timestamp);
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(
IIO_CURRENT, PAC1921_CHAN_CURRENT,
IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
timestamp);
}
if (flags & PAC1921_OVERFLOW_VPOV &&
!(priv->prev_ovf_flags & PAC1921_OVERFLOW_VPOV) &&
priv->ovf_enabled_events & PAC1921_OVERFLOW_VPOV) {
iio_push_event(indio_dev,
IIO_UNMOD_EVENT_CODE(
IIO_POWER, PAC1921_CHAN_POWER,
IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
timestamp);
}
priv->prev_ovf_flags = (u8)flags;
return 0;
}
/*
* Read the value from a result register
*
* Result registers contain the most recent averaged values of Vbus, Vsense and
* Vpower. Each value is 10 bits wide and spread across two consecutive 8 bit
* registers, with 6 bit LSB zero padding.
*/
static int pac1921_read_res(struct pac1921_priv *priv, unsigned long reg,
u16 *val)
{
int ret = regmap_bulk_read(priv->regmap, (unsigned int)reg, val,
sizeof(*val));
if (ret)
return ret;
*val = FIELD_GET(PAC1921_RES_MASK, get_unaligned_be16(val));
return 0;
}
static int pac1921_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
guard(mutex)(&priv->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW: {
s64 ts;
u16 res_val;
int ret;
if (!pac1921_data_ready(priv))
return -EBUSY;
ts = iio_get_time_ns(indio_dev);
ret = pac1921_check_push_overflow(indio_dev, ts);
if (ret)
return ret;
ret = pac1921_read_res(priv, chan->address, &res_val);
if (ret)
return ret;
*val = (int)res_val;
return IIO_VAL_INT;
}
case IIO_CHAN_INFO_SCALE:
switch (chan->channel) {
case PAC1921_CHAN_VBUS:
*val = pac1921_vbus_scales[priv->dv_gain][0];
*val2 = pac1921_vbus_scales[priv->dv_gain][1];
return IIO_VAL_INT_PLUS_NANO;
case PAC1921_CHAN_VSENSE:
*val = pac1921_vsense_scales[priv->di_gain][0];
*val2 = pac1921_vsense_scales[priv->di_gain][1];
return IIO_VAL_INT_PLUS_NANO;
case PAC1921_CHAN_CURRENT:
*val = priv->current_scales[priv->di_gain][0];
*val2 = priv->current_scales[priv->di_gain][1];
return IIO_VAL_INT_PLUS_NANO;
case PAC1921_CHAN_POWER: {
/*
* Power scale factor in mW:
* Current scale (mA) * max_vbus (V) / dv_gain
*/
/* Get current scale based on di_gain */
int *curr_scale = priv->current_scales[priv->di_gain];
/* Convert current_scale from INT_PLUS_NANO to INT */
s64 tmp = curr_scale[0] * (s64)NANO + curr_scale[1];
/* Multiply by max_vbus (V) / dv_gain */
tmp *= PAC1921_MAX_VBUS_V >> (int)priv->dv_gain;
/* Convert back to INT_PLUS_NANO */
*val = (int)div_s64_rem(tmp, NANO, val2);
return IIO_VAL_INT_PLUS_NANO;
}
default:
return -EINVAL;
}
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
*val = pac1921_int_num_samples[priv->n_samples];
return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
/*
* The sampling frequency (Hz) is read-only and corresponds to
* how often the device provides integrated measurements into
* the result registers, thus it's 1/integration_period.
* The integration period depends on the number of integration
* samples, measurement resolution and post filters.
*
* 1/(integr_period_usecs/MICRO) = MICRO/integr_period_usecs
*/
*val = MICRO;
*val2 = (int)priv->integr_period_usecs;
return IIO_VAL_FRACTIONAL;
default:
return -EINVAL;
}
}
static int pac1921_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
switch (mask) {
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
*type = IIO_VAL_INT;
*vals = pac1921_int_num_samples;
*length = ARRAY_SIZE(pac1921_int_num_samples);
return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
}
/*
* Perform configuration update sequence: set the device into read state, then
* write the config register and set the device back into integration state.
* Also reset integration start time and mark first integration to be yet
* completed.
*
* Must be called with lock held.
*/
static int pac1921_update_cfg_reg(struct pac1921_priv *priv, unsigned int reg,
unsigned int mask, unsigned int val)
{
/* Enter READ state before configuration */
int ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
PAC1921_INT_CFG_INTEN, 0);
if (ret)
return ret;
/* Update configuration value */
ret = regmap_update_bits(priv->regmap, reg, mask, val);
if (ret)
return ret;
/* Re-enable integration */
ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
PAC1921_INT_CFG_INTEN, PAC1921_INT_CFG_INTEN);
if (ret)
return ret;
/*
* Reset integration started time and mark this integration period as
* the first one so that new measurements will be considered as valid
* only at the end of this integration period.
*/
priv->integr_started_time_jiffies = jiffies;
priv->first_integr_done = false;
return 0;
}
/*
* Retrieve the index of the given scale (represented by scale_val and
* scale_val2) from scales_tbl. The returned index (if found) is the log2 of
* the gain corresponding to the given scale.
*
* Must be called with lock held if the scales_tbl can change runtime (e.g. for
* the current scales table)
*/
static int pac1921_lookup_scale(const int (*const scales_tbl)[2], size_t size,
int scale_val, int scale_val2)
{
for (unsigned int i = 0; i < size; i++)
if (scales_tbl[i][0] == scale_val &&
scales_tbl[i][1] == scale_val2)
return (int)i;
return -EINVAL;
}
/*
* Configure device with the given gain (only if changed)
*
* Must be called with lock held.
*/
static int pac1921_update_gain(struct pac1921_priv *priv, u8 *priv_val, u8 gain,
unsigned int mask)
{
unsigned int reg_val;
int ret;
if (*priv_val == gain)
return 0;
reg_val = (gain << __ffs(mask)) & mask;
ret = pac1921_update_cfg_reg(priv, PAC1921_REG_GAIN_CFG, mask, reg_val);
if (ret)
return ret;
*priv_val = gain;
return 0;
}
/*
* Given a scale factor represented by scale_val and scale_val2 with format
* IIO_VAL_INT_PLUS_NANO, find the corresponding gain value and write it to the
* device.
*
* Must be called with lock held.
*/
static int pac1921_update_gain_from_scale(struct pac1921_priv *priv,
struct iio_chan_spec const *chan,
int scale_val, int scale_val2)
{
int ret;
switch (chan->channel) {
case PAC1921_CHAN_VBUS:
ret = pac1921_lookup_scale(pac1921_vbus_scales,
ARRAY_SIZE(pac1921_vbus_scales),
scale_val, scale_val2);
if (ret < 0)
return ret;
return pac1921_update_gain(priv, &priv->dv_gain, (u8)ret,
PAC1921_GAIN_DV_GAIN_MASK);
case PAC1921_CHAN_VSENSE:
ret = pac1921_lookup_scale(pac1921_vsense_scales,
ARRAY_SIZE(pac1921_vsense_scales),
scale_val, scale_val2);
if (ret < 0)
return ret;
return pac1921_update_gain(priv, &priv->di_gain, (u8)ret,
PAC1921_GAIN_DI_GAIN_MASK);
case PAC1921_CHAN_CURRENT:
ret = pac1921_lookup_scale(priv->current_scales,
ARRAY_SIZE(priv->current_scales),
scale_val, scale_val2);
if (ret < 0)
return ret;
return pac1921_update_gain(priv, &priv->di_gain, (u8)ret,
PAC1921_GAIN_DI_GAIN_MASK);
default:
return -EINVAL;
}
}
/*
* Retrieve the index of the given number of samples from the constant table.
* The returned index (if found) is the log2 of the given num_samples.
*/
static int pac1921_lookup_int_num_samples(int num_samples)
{
for (unsigned int i = 0; i < ARRAY_SIZE(pac1921_int_num_samples); i++)
if (pac1921_int_num_samples[i] == num_samples)
return (int)i;
return -EINVAL;
}
/*
* Update the device with the given number of integration samples.
*
* Must be called with lock held.
*/
static int pac1921_update_int_num_samples(struct pac1921_priv *priv,
int num_samples)
{
unsigned int reg_val;
u8 n_samples;
int ret;
ret = pac1921_lookup_int_num_samples(num_samples);
if (ret < 0)
return ret;
n_samples = (u8)ret;
if (priv->n_samples == n_samples)
return 0;
reg_val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, n_samples);
ret = pac1921_update_cfg_reg(priv, PAC1921_REG_INT_CFG,
PAC1921_INT_CFG_SMPL_MASK, reg_val);
if (ret)
return ret;
priv->n_samples = n_samples;
priv->integr_period_usecs = pac1921_int_periods_usecs[priv->n_samples];
return 0;
}
static int pac1921_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
long info)
{
switch (info) {
case IIO_CHAN_INFO_SCALE:
return IIO_VAL_INT_PLUS_NANO;
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int pac1921_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val,
int val2, long mask)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
guard(mutex)(&priv->lock);
switch (mask) {
case IIO_CHAN_INFO_SCALE:
return pac1921_update_gain_from_scale(priv, chan, val, val2);
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
return pac1921_update_int_num_samples(priv, val);
default:
return -EINVAL;
}
}
static int pac1921_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, char *label)
{
switch (chan->channel) {
case PAC1921_CHAN_VBUS:
return sprintf(label, "vbus\n");
case PAC1921_CHAN_VSENSE:
return sprintf(label, "vsense\n");
case PAC1921_CHAN_CURRENT:
return sprintf(label, "current\n");
case PAC1921_CHAN_POWER:
return sprintf(label, "power\n");
default:
return -EINVAL;
}
}
static int pac1921_read_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
guard(mutex)(&priv->lock);
switch (chan->channel) {
case PAC1921_CHAN_VBUS:
return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VBOV);
case PAC1921_CHAN_VSENSE:
case PAC1921_CHAN_CURRENT:
return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VSOV);
case PAC1921_CHAN_POWER:
return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VPOV);
default:
return -EINVAL;
}
}
static int pac1921_write_event_config(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir, int state)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
u8 ovf_bit;
guard(mutex)(&priv->lock);
switch (chan->channel) {
case PAC1921_CHAN_VBUS:
ovf_bit = PAC1921_OVERFLOW_VBOV;
break;
case PAC1921_CHAN_VSENSE:
case PAC1921_CHAN_CURRENT:
ovf_bit = PAC1921_OVERFLOW_VSOV;
break;
case PAC1921_CHAN_POWER:
ovf_bit = PAC1921_OVERFLOW_VPOV;
break;
default:
return -EINVAL;
}
if (state)
priv->ovf_enabled_events |= ovf_bit;
else
priv->ovf_enabled_events &= ~ovf_bit;
return 0;
}
static int pac1921_read_event_value(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
enum iio_event_info info, int *val,
int *val2)
{
switch (info) {
case IIO_EV_INFO_VALUE:
*val = PAC1921_RES_RESOLUTION;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static const struct iio_info pac1921_iio = {
.read_raw = pac1921_read_raw,
.read_avail = pac1921_read_avail,
.write_raw = pac1921_write_raw,
.write_raw_get_fmt = pac1921_write_raw_get_fmt,
.read_label = pac1921_read_label,
.read_event_config = pac1921_read_event_config,
.write_event_config = pac1921_write_event_config,
.read_event_value = pac1921_read_event_value,
};
static ssize_t pac1921_read_shunt_resistor(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
char *buf)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
int vals[2];
if (chan->channel != PAC1921_CHAN_CURRENT)
return -EINVAL;
guard(mutex)(&priv->lock);
vals[0] = (int)priv->rshunt_uohm;
vals[1] = MICRO;
return iio_format_value(buf, IIO_VAL_FRACTIONAL, 1, vals);
}
static ssize_t pac1921_write_shunt_resistor(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
u64 rshunt_uohm;
int val, val_fract;
int ret;
if (chan->channel != PAC1921_CHAN_CURRENT)
return -EINVAL;
ret = iio_str_to_fixpoint(buf, 100000, &val, &val_fract);
if (ret)
return ret;
rshunt_uohm = (u32)val * MICRO + (u32)val_fract;
if (rshunt_uohm == 0 || rshunt_uohm > INT_MAX)
return -EINVAL;
guard(mutex)(&priv->lock);
priv->rshunt_uohm = (u32)rshunt_uohm;
pac1921_calc_current_scales(priv);
return len;
}
/*
* Emit on sysfs the list of available scales contained in scales_tbl
*
* TODO:: this function can be replaced with iio_format_avail_list() if the
* latter will ever be exported.
*
* Must be called with lock held if the scales_tbl can change runtime (e.g. for
* the current scales table)
*/
static ssize_t pac1921_format_scale_avail(const int (*const scales_tbl)[2],
size_t size, char *buf)
{
ssize_t len = 0;
for (unsigned int i = 0; i < size; i++) {
if (i != 0) {
len += sysfs_emit_at(buf, len, " ");
if (len >= PAGE_SIZE)
return -EFBIG;
}
len += sysfs_emit_at(buf, len, "%d.%09d", scales_tbl[i][0],
scales_tbl[i][1]);
if (len >= PAGE_SIZE)
return -EFBIG;
}
len += sysfs_emit_at(buf, len, "\n");
return len;
}
/*
* Read available scales for a specific channel
*
* NOTE: using extended info insted of iio.read_avail() because access to
* current scales must be locked as they depend on shunt resistor which may
* change runtime. Caller of iio.read_avail() would access the table unlocked
* instead.
*/
static ssize_t pac1921_read_scale_avail(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
char *buf)
{
struct pac1921_priv *priv = iio_priv(indio_dev);
const int (*scales_tbl)[2];
size_t size;
switch (chan->channel) {
case PAC1921_CHAN_VBUS:
scales_tbl = pac1921_vbus_scales;
size = ARRAY_SIZE(pac1921_vbus_scales);
return pac1921_format_scale_avail(scales_tbl, size, buf);
case PAC1921_CHAN_VSENSE:
scales_tbl = pac1921_vsense_scales;
size = ARRAY_SIZE(pac1921_vsense_scales);
return pac1921_format_scale_avail(scales_tbl, size, buf);
case PAC1921_CHAN_CURRENT: {
guard(mutex)(&priv->lock);
scales_tbl = priv->current_scales;
size = ARRAY_SIZE(priv->current_scales);
return pac1921_format_scale_avail(scales_tbl, size, buf);
}
default:
return -EINVAL;
}
}
#define PAC1921_EXT_INFO_SCALE_AVAIL { \
.name = "scale_available", \
.read = pac1921_read_scale_avail, \
.shared = IIO_SEPARATE, \
}
static const struct iio_chan_spec_ext_info pac1921_ext_info_voltage[] = {
PAC1921_EXT_INFO_SCALE_AVAIL,
{}
};
static const struct iio_chan_spec_ext_info pac1921_ext_info_current[] = {
PAC1921_EXT_INFO_SCALE_AVAIL,
{
.name = "shunt_resistor",
.read = pac1921_read_shunt_resistor,
.write = pac1921_write_shunt_resistor,
.shared = IIO_SEPARATE,
},
{}
};
static const struct iio_event_spec pac1921_overflow_event[] = {
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
},
};
static const struct iio_chan_spec pac1921_channels[] = {
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
.channel = PAC1921_CHAN_VBUS,
.address = PAC1921_REG_VBUS,
.scan_index = PAC1921_CHAN_VBUS,
.scan_type = {
.sign = 'u',
.realbits = 10,
.storagebits = 16,
.endianness = IIO_CPU
},
.indexed = 1,
.event_spec = pac1921_overflow_event,
.num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
.ext_info = pac1921_ext_info_voltage,
},
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
.channel = PAC1921_CHAN_VSENSE,
.address = PAC1921_REG_VSENSE,
.scan_index = PAC1921_CHAN_VSENSE,
.scan_type = {
.sign = 'u',
.realbits = 10,
.storagebits = 16,
.endianness = IIO_CPU
},
.indexed = 1,
.event_spec = pac1921_overflow_event,
.num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
.ext_info = pac1921_ext_info_voltage,
},
{
.type = IIO_CURRENT,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
.channel = PAC1921_CHAN_CURRENT,
.address = PAC1921_REG_VSENSE,
.scan_index = PAC1921_CHAN_CURRENT,
.scan_type = {
.sign = 'u',
.realbits = 10,
.storagebits = 16,
.endianness = IIO_CPU
},
.event_spec = pac1921_overflow_event,
.num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
.ext_info = pac1921_ext_info_current,
},
{
.type = IIO_POWER,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_all =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
BIT(IIO_CHAN_INFO_SAMP_FREQ),
.info_mask_shared_by_all_available =
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
.channel = PAC1921_CHAN_POWER,
.address = PAC1921_REG_VPOWER,
.scan_index = PAC1921_CHAN_POWER,
.scan_type = {
.sign = 'u',
.realbits = 10,
.storagebits = 16,
.endianness = IIO_CPU
},
.event_spec = pac1921_overflow_event,
.num_event_specs = ARRAY_SIZE(pac1921_overflow_event),
},
IIO_CHAN_SOFT_TIMESTAMP(PAC1921_NUM_MEAS_CHANS),
};
static irqreturn_t pac1921_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *idev = pf->indio_dev;
struct pac1921_priv *priv = iio_priv(idev);
int ret;
int bit;
int ch = 0;
guard(mutex)(&priv->lock);
if (!pac1921_data_ready(priv))
goto done;
ret = pac1921_check_push_overflow(idev, pf->timestamp);
if (ret)
goto done;
iio_for_each_active_channel(idev, bit) {
u16 val;
ret = pac1921_read_res(priv, idev->channels[ch].address, &val);
if (ret)
goto done;
priv->scan.chan[ch++] = val;
}
iio_push_to_buffers_with_timestamp(idev, &priv->scan, pf->timestamp);
done:
iio_trigger_notify_done(idev->trig);
return IRQ_HANDLED;
}
/*
* Initialize device by writing initial configuration and putting it into
* integration state.
*
* Must be called with lock held when called after first initialization
* (e.g. from pm resume)
*/
static int pac1921_init(struct pac1921_priv *priv)
{
unsigned int val;
int ret;
/* Enter READ state before configuration */
ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
PAC1921_INT_CFG_INTEN, 0);
if (ret)
return ret;
/* Configure gains, use 14-bits measurement resolution (HW default) */
val = FIELD_PREP(PAC1921_GAIN_DI_GAIN_MASK, priv->di_gain) |
FIELD_PREP(PAC1921_GAIN_DV_GAIN_MASK, priv->dv_gain);
ret = regmap_write(priv->regmap, PAC1921_REG_GAIN_CFG, val);
if (ret)
return ret;
/*
* Configure integration:
* - num of integration samples
* - filters enabled (HW default)
* - set READ/INT pin override (RIOV) to control operation mode via
* register instead of pin
*/
val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, priv->n_samples) |
PAC1921_INT_CFG_VSFEN | PAC1921_INT_CFG_VBFEN |
PAC1921_INT_CFG_RIOV;
ret = regmap_write(priv->regmap, PAC1921_REG_INT_CFG, val);
if (ret)
return ret;
/*
* Init control register:
* - VPower free run integration mode
* - OUT pin full scale range: 3V (HW detault)
* - no timeout, no sleep, no sleep override, no recalc (HW defaults)
*/
val = FIELD_PREP(PAC1921_CONTROL_MXSL_MASK,
PAC1921_MXSL_VPOWER_FREE_RUN);
ret = regmap_write(priv->regmap, PAC1921_REG_CONTROL, val);
if (ret)
return ret;
/* Enable integration */
ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
PAC1921_INT_CFG_INTEN, PAC1921_INT_CFG_INTEN);
if (ret)
return ret;
priv->first_integr_started = true;
priv->integr_started_time_jiffies = jiffies;
priv->integr_period_usecs = pac1921_int_periods_usecs[priv->n_samples];
return 0;
}
static int pac1921_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct pac1921_priv *priv = iio_priv(indio_dev);
int ret;
guard(mutex)(&priv->lock);
priv->first_integr_started = false;
priv->first_integr_done = false;
ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG,
PAC1921_INT_CFG_INTEN, 0);
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, PAC1921_REG_CONTROL,
PAC1921_CONTROL_SLEEP, PAC1921_CONTROL_SLEEP);
if (ret)
return ret;
return regulator_disable(priv->vdd);
}
static int pac1921_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct pac1921_priv *priv = iio_priv(indio_dev);
int ret;
guard(mutex)(&priv->lock);
ret = regulator_enable(priv->vdd);
if (ret)
return ret;
msleep(PAC1921_POWERUP_TIME_MS);
return pac1921_init(priv);
}
static DEFINE_SIMPLE_DEV_PM_OPS(pac1921_pm_ops, pac1921_suspend,
pac1921_resume);
static void pac1921_regulator_disable(void *data)
{
struct regulator *regulator = data;
regulator_disable(regulator);
}
static int pac1921_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct pac1921_priv *priv;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
if (!indio_dev)
return -ENOMEM;
priv = iio_priv(indio_dev);
priv->client = client;
i2c_set_clientdata(client, indio_dev);
priv->regmap = devm_regmap_init_i2c(client, &pac1921_regmap_config);
if (IS_ERR(priv->regmap))
return dev_err_probe(dev, (int)PTR_ERR(priv->regmap),
"Cannot initialize register map\n");
devm_mutex_init(dev, &priv->lock);
priv->dv_gain = PAC1921_DEFAULT_DV_GAIN;
priv->di_gain = PAC1921_DEFAULT_DI_GAIN;
priv->n_samples = PAC1921_DEFAULT_NUM_SAMPLES;
ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms",
&priv->rshunt_uohm);
if (ret)
return dev_err_probe(dev, ret,
"Cannot read shunt resistor property\n");
if (priv->rshunt_uohm == 0 || priv->rshunt_uohm > INT_MAX)
return dev_err_probe(dev, -EINVAL,
"Invalid shunt resistor: %u\n",
priv->rshunt_uohm);
pac1921_calc_current_scales(priv);
priv->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(priv->vdd))
return dev_err_probe(dev, (int)PTR_ERR(priv->vdd),
"Cannot get vdd regulator\n");
ret = regulator_enable(priv->vdd);
if (ret)
return dev_err_probe(dev, ret, "Cannot enable vdd regulator\n");
ret = devm_add_action_or_reset(dev, pac1921_regulator_disable,
priv->vdd);
if (ret)
return dev_err_probe(dev, ret,
"Cannot add action for vdd regulator disposal\n");
msleep(PAC1921_POWERUP_TIME_MS);
ret = pac1921_init(priv);
if (ret)
return dev_err_probe(dev, ret, "Cannot initialize device\n");
priv->iio_info = pac1921_iio;
indio_dev->name = "pac1921";
indio_dev->info = &priv->iio_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = pac1921_channels;
indio_dev->num_channels = ARRAY_SIZE(pac1921_channels);
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
&iio_pollfunc_store_time,
&pac1921_trigger_handler, NULL);
if (ret)
return dev_err_probe(dev, ret,
"Cannot setup IIO triggered buffer\n");
ret = devm_iio_device_register(dev, indio_dev);
if (ret)
return dev_err_probe(dev, ret, "Cannot register IIO device\n");
return 0;
}
static const struct i2c_device_id pac1921_id[] = {
{ .name = "pac1921", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pac1921_id);
static const struct of_device_id pac1921_of_match[] = {
{ .compatible = "microchip,pac1921" },
{ }
};
MODULE_DEVICE_TABLE(of, pac1921_of_match);
static struct i2c_driver pac1921_driver = {
.driver = {
.name = "pac1921",
.pm = pm_sleep_ptr(&pac1921_pm_ops),
.of_match_table = pac1921_of_match,
},
.probe = pac1921_probe,
.id_table = pac1921_id,
};
module_i2c_driver(pac1921_driver);
MODULE_AUTHOR("Matteo Martelli <matteomartelli3@gmail.com>");
MODULE_DESCRIPTION("IIO driver for PAC1921 High-Side Power/Current Monitor");
MODULE_LICENSE("GPL");