// SPDX-License-Identifier: GPL-2.0-only
/*
* INA3221 Triple Current/Voltage Monitor
*
* Copyright (C) 2016 Texas Instruments Incorporated - https://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*/
#include <linux/debugfs.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/util_macros.h>
#define INA3221_DRIVER_NAME "ina3221"
#define INA3221_CONFIG 0x00
#define INA3221_SHUNT1 0x01
#define INA3221_BUS1 0x02
#define INA3221_SHUNT2 0x03
#define INA3221_BUS2 0x04
#define INA3221_SHUNT3 0x05
#define INA3221_BUS3 0x06
#define INA3221_CRIT1 0x07
#define INA3221_WARN1 0x08
#define INA3221_CRIT2 0x09
#define INA3221_WARN2 0x0a
#define INA3221_CRIT3 0x0b
#define INA3221_WARN3 0x0c
#define INA3221_SHUNT_SUM 0x0d
#define INA3221_CRIT_SUM 0x0e
#define INA3221_MASK_ENABLE 0x0f
#define INA3221_CONFIG_MODE_MASK GENMASK(2, 0)
#define INA3221_CONFIG_MODE_POWERDOWN 0
#define INA3221_CONFIG_MODE_SHUNT BIT(0)
#define INA3221_CONFIG_MODE_BUS BIT(1)
#define INA3221_CONFIG_MODE_CONTINUOUS BIT(2)
#define INA3221_CONFIG_VSH_CT_SHIFT 3
#define INA3221_CONFIG_VSH_CT_MASK GENMASK(5, 3)
#define INA3221_CONFIG_VSH_CT(x) (((x) & GENMASK(5, 3)) >> 3)
#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))
#define INA3221_MASK_ENABLE_SCC_MASK GENMASK(14, 12)
#define INA3221_CONFIG_DEFAULT 0x7127
#define INA3221_RSHUNT_DEFAULT 10000
enum ina3221_fields {
/* Configuration */
F_RST,
/* Status Flags */
F_CVRF,
/* Warning Flags */
F_WF3, F_WF2, F_WF1,
/* Alert Flags: SF is the summation-alert flag */
F_SF, F_CF3, F_CF2, F_CF1,
/* sentinel */
F_MAX_FIELDS
};
static const struct reg_field ina3221_reg_fields[] = {
[F_RST] = REG_FIELD(INA3221_CONFIG, 15, 15),
[F_CVRF] = REG_FIELD(INA3221_MASK_ENABLE, 0, 0),
[F_WF3] = REG_FIELD(INA3221_MASK_ENABLE, 3, 3),
[F_WF2] = REG_FIELD(INA3221_MASK_ENABLE, 4, 4),
[F_WF1] = REG_FIELD(INA3221_MASK_ENABLE, 5, 5),
[F_SF] = REG_FIELD(INA3221_MASK_ENABLE, 6, 6),
[F_CF3] = REG_FIELD(INA3221_MASK_ENABLE, 7, 7),
[F_CF2] = REG_FIELD(INA3221_MASK_ENABLE, 8, 8),
[F_CF1] = REG_FIELD(INA3221_MASK_ENABLE, 9, 9),
};
enum ina3221_channels {
INA3221_CHANNEL1,
INA3221_CHANNEL2,
INA3221_CHANNEL3,
INA3221_NUM_CHANNELS
};
/**
* struct ina3221_input - channel input source specific information
* @label: label of channel input source
* @shunt_resistor: shunt resistor value of channel input source
* @disconnected: connection status of channel input source
* @summation_disable: channel summation status of input source
*/
struct ina3221_input {
const char *label;
int shunt_resistor;
bool disconnected;
bool summation_disable;
};
/**
* struct ina3221_data - device specific information
* @pm_dev: Device pointer for pm runtime
* @regmap: Register map of the device
* @fields: Register fields of the device
* @inputs: Array of channel input source specific structures
* @lock: mutex lock to serialize sysfs attribute accesses
* @debugfs: Pointer to debugfs entry for device
* @reg_config: Register value of INA3221_CONFIG
* @summation_shunt_resistor: equivalent shunt resistor value for summation
* @summation_channel_control: Value written to SCC field in INA3221_MASK_ENABLE
* @single_shot: running in single-shot operating mode
*/
struct ina3221_data {
struct device *pm_dev;
struct regmap *regmap;
struct regmap_field *fields[F_MAX_FIELDS];
struct ina3221_input inputs[INA3221_NUM_CHANNELS];
struct mutex lock;
struct dentry *debugfs;
u32 reg_config;
int summation_shunt_resistor;
u32 summation_channel_control;
bool single_shot;
};
static inline bool ina3221_is_enabled(struct ina3221_data *ina, int channel)
{
/* Summation channel checks shunt resistor values */
if (channel > INA3221_CHANNEL3)
return ina->summation_shunt_resistor != 0;
return pm_runtime_active(ina->pm_dev) &&
(ina->reg_config & INA3221_CONFIG_CHx_EN(channel));
}
/*
* Helper function to return the resistor value for current summation.
*
* There is a condition to calculate current summation -- all the shunt
* resistor values should be the same, so as to simply fit the formula:
* current summation = shunt voltage summation / shunt resistor
*
* Returns the equivalent shunt resistor value on success or 0 on failure
*/
static inline int ina3221_summation_shunt_resistor(struct ina3221_data *ina)
{
struct ina3221_input *input = ina->inputs;
int i, shunt_resistor = 0;
for (i = 0; i < INA3221_NUM_CHANNELS; i++) {
if (input[i].disconnected || !input[i].shunt_resistor ||
input[i].summation_disable)
continue;
if (!shunt_resistor) {
/* Found the reference shunt resistor value */
shunt_resistor = input[i].shunt_resistor;
} else {
/* No summation if resistor values are different */
if (shunt_