diff options
Diffstat (limited to 'drivers/hwmon/ina3221.c')
| -rw-r--r-- | drivers/hwmon/ina3221.c | 176 | 
1 files changed, 133 insertions, 43 deletions
| diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index 3626b87a5fd2..e0637fed9585 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -22,6 +22,7 @@  #include <linux/of.h>  #include <linux/pm_runtime.h>  #include <linux/regmap.h> +#include <linux/util_macros.h>  #define INA3221_DRIVER_NAME		"ina3221" @@ -51,6 +52,9 @@  #define INA3221_CONFIG_VBUS_CT_SHIFT	6  #define INA3221_CONFIG_VBUS_CT_MASK	GENMASK(8, 6)  #define INA3221_CONFIG_VBUS_CT(x)	(((x) & GENMASK(8, 6)) >> 6) +#define INA3221_CONFIG_AVG_SHIFT	9 +#define INA3221_CONFIG_AVG_MASK		GENMASK(11, 9) +#define INA3221_CONFIG_AVG(x)		(((x) & GENMASK(11, 9)) >> 9)  #define INA3221_CONFIG_CHs_EN_MASK	GENMASK(14, 12)  #define INA3221_CONFIG_CHx_EN(x)	BIT(14 - (x)) @@ -135,17 +139,42 @@ static const u16 ina3221_conv_time[] = {  	140, 204, 332, 588, 1100, 2116, 4156, 8244,  }; -static inline int ina3221_wait_for_data(struct ina3221_data *ina) +/* Lookup table for number of samples using in averaging mode */ +static const int ina3221_avg_samples[] = { +	1, 4, 16, 64, 128, 256, 512, 1024, +}; + +/* Converting update_interval in msec to conversion time in usec */ +static inline u32 ina3221_interval_ms_to_conv_time(u16 config, int interval) +{ +	u32 channels = hweight16(config & INA3221_CONFIG_CHs_EN_MASK); +	u32 samples_idx = INA3221_CONFIG_AVG(config); +	u32 samples = ina3221_avg_samples[samples_idx]; + +	/* Bisect the result to Bus and Shunt conversion times */ +	return DIV_ROUND_CLOSEST(interval * 1000 / 2, channels * samples); +} + +/* Converting CONFIG register value to update_interval in usec */ +static inline u32 ina3221_reg_to_interval_us(u16 config)  { -	u32 channels = hweight16(ina->reg_config & INA3221_CONFIG_CHs_EN_MASK); -	u32 vbus_ct_idx = INA3221_CONFIG_VBUS_CT(ina->reg_config); -	u32 vsh_ct_idx = INA3221_CONFIG_VSH_CT(ina->reg_config); +	u32 channels = hweight16(config & INA3221_CONFIG_CHs_EN_MASK); +	u32 vbus_ct_idx = INA3221_CONFIG_VBUS_CT(config); +	u32 vsh_ct_idx = INA3221_CONFIG_VSH_CT(config); +	u32 samples_idx = INA3221_CONFIG_AVG(config); +	u32 samples = ina3221_avg_samples[samples_idx];  	u32 vbus_ct = ina3221_conv_time[vbus_ct_idx];  	u32 vsh_ct = ina3221_conv_time[vsh_ct_idx]; -	u32 wait, cvrf;  	/* Calculate total conversion time */ -	wait = channels * (vbus_ct + vsh_ct); +	return channels * (vbus_ct + vsh_ct) * samples; +} + +static inline int ina3221_wait_for_data(struct ina3221_data *ina) +{ +	u32 wait, cvrf; + +	wait = ina3221_reg_to_interval_us(ina->reg_config);  	/* Polling the CVRF bit to make sure read data is ready */  	return regmap_field_read_poll_timeout(ina->fields[F_CVRF], @@ -176,6 +205,26 @@ static const u8 ina3221_in_reg[] = {  	INA3221_SHUNT3,  }; +static int ina3221_read_chip(struct device *dev, u32 attr, long *val) +{ +	struct ina3221_data *ina = dev_get_drvdata(dev); +	int regval; + +	switch (attr) { +	case hwmon_chip_samples: +		regval = INA3221_CONFIG_AVG(ina->reg_config); +		*val = ina3221_avg_samples[regval]; +		return 0; +	case hwmon_chip_update_interval: +		/* Return in msec */ +		*val = ina3221_reg_to_interval_us(ina->reg_config); +		*val = DIV_ROUND_CLOSEST(*val, 1000); +		return 0; +	default: +		return -EOPNOTSUPP; +	} +} +  static int ina3221_read_in(struct device *dev, u32 attr, int channel, long *val)  {  	const bool is_shunt = channel > INA3221_CHANNEL3; @@ -279,6 +328,48 @@ static int ina3221_read_curr(struct device *dev, u32 attr,  	}  } +static int ina3221_write_chip(struct device *dev, u32 attr, long val) +{ +	struct ina3221_data *ina = dev_get_drvdata(dev); +	int ret, idx; +	u32 tmp; + +	switch (attr) { +	case hwmon_chip_samples: +		idx = find_closest(val, ina3221_avg_samples, +				   ARRAY_SIZE(ina3221_avg_samples)); + +		tmp = (ina->reg_config & ~INA3221_CONFIG_AVG_MASK) | +		      (idx << INA3221_CONFIG_AVG_SHIFT); +		ret = regmap_write(ina->regmap, INA3221_CONFIG, tmp); +		if (ret) +			return ret; + +		/* Update reg_config accordingly */ +		ina->reg_config = tmp; +		return 0; +	case hwmon_chip_update_interval: +		tmp = ina3221_interval_ms_to_conv_time(ina->reg_config, val); +		idx = find_closest(tmp, ina3221_conv_time, +				   ARRAY_SIZE(ina3221_conv_time)); + +		/* Update Bus and Shunt voltage conversion times */ +		tmp = INA3221_CONFIG_VBUS_CT_MASK | INA3221_CONFIG_VSH_CT_MASK; +		tmp = (ina->reg_config & ~tmp) | +		      (idx << INA3221_CONFIG_VBUS_CT_SHIFT) | +		      (idx << INA3221_CONFIG_VSH_CT_SHIFT); +		ret = regmap_write(ina->regmap, INA3221_CONFIG, tmp); +		if (ret) +			return ret; + +		/* Update reg_config accordingly */ +		ina->reg_config = tmp; +		return 0; +	default: +		return -EOPNOTSUPP; +	} +} +  static int ina3221_write_curr(struct device *dev, u32 attr,  			      int channel, long val)  { @@ -309,6 +400,7 @@ static int ina3221_write_enable(struct device *dev, int channel, bool enable)  	struct ina3221_data *ina = dev_get_drvdata(dev);  	u16 config, mask = INA3221_CONFIG_CHx_EN(channel);  	u16 config_old = ina->reg_config & mask; +	u32 tmp;  	int ret;  	config = enable ? mask : 0; @@ -327,14 +419,13 @@ static int ina3221_write_enable(struct device *dev, int channel, bool enable)  	}  	/* Enable or disable the channel */ -	ret = regmap_update_bits(ina->regmap, INA3221_CONFIG, mask, config); +	tmp = (ina->reg_config & ~mask) | (config & mask); +	ret = regmap_write(ina->regmap, INA3221_CONFIG, tmp);  	if (ret)  		goto fail;  	/* Cache the latest config register value */ -	ret = regmap_read(ina->regmap, INA3221_CONFIG, &ina->reg_config); -	if (ret) -		goto fail; +	ina->reg_config = tmp;  	/* For disabling routine, decrease refcount or suspend() at last */  	if (!enable) @@ -361,6 +452,9 @@ static int ina3221_read(struct device *dev, enum hwmon_sensor_types type,  	mutex_lock(&ina->lock);  	switch (type) { +	case hwmon_chip: +		ret = ina3221_read_chip(dev, attr, val); +		break;  	case hwmon_in:  		/* 0-align channel ID */  		ret = ina3221_read_in(dev, attr, channel - 1, val); @@ -387,6 +481,9 @@ static int ina3221_write(struct device *dev, enum hwmon_sensor_types type,  	mutex_lock(&ina->lock);  	switch (type) { +	case hwmon_chip: +		ret = ina3221_write_chip(dev, attr, val); +		break;  	case hwmon_in:  		/* 0-align channel ID */  		ret = ina3221_write_enable(dev, channel - 1, val); @@ -423,6 +520,14 @@ static umode_t ina3221_is_visible(const void *drvdata,  	const struct ina3221_input *input = NULL;  	switch (type) { +	case hwmon_chip: +		switch (attr) { +		case hwmon_chip_samples: +		case hwmon_chip_update_interval: +			return 0644; +		default: +			return 0; +		}  	case hwmon_in:  		/* Ignore in0_ */  		if (channel == 0) @@ -458,44 +563,29 @@ static umode_t ina3221_is_visible(const void *drvdata,  	}  } -static const u32 ina3221_in_config[] = { -	/* 0: dummy, skipped in is_visible */ -	HWMON_I_INPUT, -	/* 1-3: input voltage Channels */ -	HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, -	HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, -	HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, -	/* 4-6: shunt voltage Channels */ -	HWMON_I_INPUT, -	HWMON_I_INPUT, -	HWMON_I_INPUT, -	0 -}; - -static const struct hwmon_channel_info ina3221_in = { -	.type = hwmon_in, -	.config = ina3221_in_config, -}; -  #define INA3221_HWMON_CURR_CONFIG (HWMON_C_INPUT | \  				   HWMON_C_CRIT | HWMON_C_CRIT_ALARM | \  				   HWMON_C_MAX | HWMON_C_MAX_ALARM) -static const u32 ina3221_curr_config[] = { -	INA3221_HWMON_CURR_CONFIG, -	INA3221_HWMON_CURR_CONFIG, -	INA3221_HWMON_CURR_CONFIG, -	0 -}; - -static const struct hwmon_channel_info ina3221_curr = { -	.type = hwmon_curr, -	.config = ina3221_curr_config, -}; -  static const struct hwmon_channel_info *ina3221_info[] = { -	&ina3221_in, -	&ina3221_curr, +	HWMON_CHANNEL_INFO(chip, +			   HWMON_C_SAMPLES, +			   HWMON_C_UPDATE_INTERVAL), +	HWMON_CHANNEL_INFO(in, +			   /* 0: dummy, skipped in is_visible */ +			   HWMON_I_INPUT, +			   /* 1-3: input voltage Channels */ +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, +			   HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL, +			   /* 4-6: shunt voltage Channels */ +			   HWMON_I_INPUT, +			   HWMON_I_INPUT, +			   HWMON_I_INPUT), +	HWMON_CHANNEL_INFO(curr, +			   INA3221_HWMON_CURR_CONFIG, +			   INA3221_HWMON_CURR_CONFIG, +			   INA3221_HWMON_CURR_CONFIG),  	NULL  }; |