// SPDX-License-Identifier: GPL-2.0+ /* * IIO driver for PAC1921 High-Side Power/Current Monitor * * Copyright (C) 2024 Matteo Martelli */ #include #include #include #include #include #include #include #include #include /* 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 "); MODULE_DESCRIPTION("IIO driver for PAC1921 High-Side Power/Current Monitor"); MODULE_LICENSE("GPL");