hwmon: qpnp-adc-current: Add periodic calibration
Support periodic IADC peripheral calibration to perform
calibration as a function of time and temperature.
Calibration is performed every 5 minutes and for
every 5degC temperature change in the die temperature.
As part of the periodic calibration update the USR trim
registers with fresh offset values. The clients can read
the offset trim values through their own trim registers.
Support reading internal RSENSE trim register and
use it for current calculation.
Also update the gain and offset api to support clients
who require the updated periodic calibrated values.
Change-Id: I2b3f0383b76417069333fb95c0c7fc1ac06eafd5
Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
diff --git a/drivers/hwmon/qpnp-adc-current.c b/drivers/hwmon/qpnp-adc-current.c
index aa375d7..0a85b93 100644
--- a/drivers/hwmon/qpnp-adc-current.c
+++ b/drivers/hwmon/qpnp-adc-current.c
@@ -111,6 +111,13 @@
#define QPNP_DATA1 0x61
#define QPNP_CONV_TIMEOUT_ERR 2
+#define QPNP_IADC_SEC_ACCESS 0xD0
+#define QPNP_IADC_SEC_ACCESS_DATA 0xA5
+#define QPNP_IADC_MSB_OFFSET 0xF2
+#define QPNP_IADC_LSB_OFFSET 0xF3
+#define QPNP_IADC_NOMINAL_RSENSE 0xF4
+#define QPNP_IADC_ATE_GAIN_CALIB_OFFSET 0xF5
+
#define QPNP_IADC_ADC_CH_SEL_CTL 0x48
#define QPNP_IADC_ADC_CHX_SEL_SHIFT 3
@@ -127,15 +134,27 @@
#define QPNP_ADC_CONV_TIME_MIN 8000
#define QPNP_ADC_CONV_TIME_MAX 8200
-#define QPNP_ADC_GAIN_CALCULATION_UV 17857
-#define QPNP_IADC_RSENSE_MILLI_FACTOR 1000
+#define QPNP_ADC_GAIN_NV 17857
+#define QPNP_OFFSET_CALIBRATION_SHORT_CADC_LEADS_IDEAL 0
+#define QPNP_IADC_INTERNAL_RSENSE_N_OHMS_FACTOR 10000000
+#define QPNP_IADC_NANO_VOLTS_FACTOR 1000000000
+#define QPNP_IADC_CALIB_SECONDS 300000
+#define QPNP_IADC_RSENSE_LSB_N_OHMS_PER_BIT 15625
+#define QPNP_IADC_DIE_TEMP_CALIB_OFFSET 5000
+
+#define QPNP_RAW_CODE_16_BIT_MSB_MASK 0xff00
+#define QPNP_RAW_CODE_16_BIT_LSB_MASK 0xff
+#define QPNP_BIT_SHIFT_8 8
+#define QPNP_RSENSE_MSB_SIGN_CHECK 0x80
struct qpnp_iadc_drv {
- struct qpnp_adc_drv *adc;
- int32_t rsense;
- struct device *iadc_hwmon;
- bool iadc_init_calib;
- bool iadc_initialized;
+ struct qpnp_adc_drv *adc;
+ int32_t rsense;
+ struct device *iadc_hwmon;
+ bool iadc_init_calib;
+ bool iadc_initialized;
+ int64_t die_temp_calib_offset;
+ struct delayed_work iadc_work;
struct sensor_device_attribute sens_attr[0];
};
@@ -253,9 +272,10 @@
return 0;
}
-static int32_t qpnp_iadc_read_conversion_result(int32_t *data)
+static int32_t qpnp_iadc_read_conversion_result(uint16_t *data)
{
uint8_t rslt_lsb, rslt_msb;
+ uint16_t rslt;
int32_t rc;
rc = qpnp_iadc_read_reg(QPNP_IADC_DATA0, &rslt_lsb);
@@ -270,16 +290,18 @@
return rc;
}
- *data = (rslt_msb << 8) | rslt_lsb;
+ rslt = (rslt_msb << 8) | rslt_lsb;
+ *data = rslt;
rc = qpnp_iadc_enable(false);
if (rc)
return rc;
+
return 0;
}
static int32_t qpnp_iadc_configure(enum qpnp_iadc_channels channel,
- int32_t *result)
+ uint16_t *raw_code)
{
struct qpnp_iadc_drv *iadc = qpnp_iadc;
u8 qpnp_iadc_mode_reg = 0, qpnp_iadc_ch_sel_reg = 0;
@@ -346,7 +368,7 @@
wait_for_completion(&iadc->adc->adc_rslt_completion);
- rc = qpnp_iadc_read_conversion_result(result);
+ rc = qpnp_iadc_read_conversion_result(raw_code);
if (rc) {
pr_err("qpnp adc read adc failed with %d\n", rc);
return rc;
@@ -355,32 +377,109 @@
return 0;
}
-static int32_t qpnp_iadc_init_calib(void)
+static int32_t qpnp_convert_raw_offset_voltage(void)
{
struct qpnp_iadc_drv *iadc = qpnp_iadc;
- int32_t rc = 0, result;
+ uint32_t num = 0;
- rc = qpnp_iadc_configure(GAIN_CALIBRATION_25MV, &result);
+ num = iadc->adc->calib.offset_raw - iadc->adc->calib.offset_raw;
+
+ iadc->adc->calib.offset_uv = (num * QPNP_ADC_GAIN_NV)/
+ (iadc->adc->calib.gain_raw - iadc->adc->calib.offset_raw);
+
+ num = iadc->adc->calib.gain_raw - iadc->adc->calib.offset_raw;
+
+ iadc->adc->calib.gain_uv = (num * QPNP_ADC_GAIN_NV)/
+ (iadc->adc->calib.gain_raw - iadc->adc->calib.offset_raw);
+
+ return 0;
+}
+
+static int32_t qpnp_iadc_calibrate_for_trim(void)
+{
+ struct qpnp_iadc_drv *iadc = qpnp_iadc;
+ uint8_t rslt_lsb, rslt_msb;
+ int32_t rc = 0;
+ uint16_t raw_data;
+
+ rc = qpnp_iadc_configure(GAIN_CALIBRATION_17P857MV, &raw_data);
if (rc < 0) {
pr_err("qpnp adc result read failed with %d\n", rc);
goto fail;
}
- iadc->adc->calib.gain = result;
+ iadc->adc->calib.gain_raw = raw_data;
rc = qpnp_iadc_configure(OFFSET_CALIBRATION_SHORT_CADC_LEADS,
- &result);
+ &raw_data);
if (rc < 0) {
pr_err("qpnp adc result read failed with %d\n", rc);
goto fail;
}
- iadc->adc->calib.offset = result;
+ iadc->adc->calib.offset_raw = raw_data;
+ if (rc < 0) {
+ pr_err("qpnp adc offset/gain calculation failed\n");
+ goto fail;
+ }
+ rc = qpnp_convert_raw_offset_voltage();
+
+ rslt_msb = (raw_data & QPNP_RAW_CODE_16_BIT_MSB_MASK) >>
+ QPNP_BIT_SHIFT_8;
+ rslt_lsb = raw_data & QPNP_RAW_CODE_16_BIT_LSB_MASK;
+
+ rc = qpnp_iadc_write_reg(QPNP_IADC_SEC_ACCESS,
+ QPNP_IADC_SEC_ACCESS_DATA);
+ if (rc < 0) {
+ pr_err("qpnp iadc configure error for sec access\n");
+ goto fail;
+ }
+
+ rc = qpnp_iadc_write_reg(QPNP_IADC_MSB_OFFSET,
+ rslt_msb);
+ if (rc < 0) {
+ pr_err("qpnp iadc configure error for MSB write\n");
+ goto fail;
+ }
+
+ rc = qpnp_iadc_write_reg(QPNP_IADC_SEC_ACCESS,
+ QPNP_IADC_SEC_ACCESS_DATA);
+ if (rc < 0) {
+ pr_err("qpnp iadc configure error for sec access\n");
+ goto fail;
+ }
+
+ rc = qpnp_iadc_write_reg(QPNP_IADC_LSB_OFFSET,
+ rslt_lsb);
+ if (rc < 0) {
+ pr_err("qpnp iadc configure error for LSB write\n");
+ goto fail;
+ }
fail:
return rc;
}
+static void qpnp_iadc_work(struct work_struct *work)
+{
+ struct qpnp_iadc_drv *iadc = qpnp_iadc;
+ int rc = 0;
+
+ mutex_lock(&iadc->adc->adc_lock);
+
+ rc = qpnp_iadc_calibrate_for_trim();
+ if (rc)
+ pr_err("periodic IADC calibration failed\n");
+
+ mutex_unlock(&iadc->adc->adc_lock);
+
+ schedule_delayed_work(&iadc->iadc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (QPNP_IADC_CALIB_SECONDS)));
+
+ return;
+}
+
static int32_t qpnp_iadc_version_check(void)
{
uint8_t revision;
@@ -411,40 +510,102 @@
}
EXPORT_SYMBOL(qpnp_iadc_is_ready);
-int32_t qpnp_iadc_read(enum qpnp_iadc_channels channel,
- int32_t *result)
+int32_t qpnp_iadc_get_rsense(int32_t *rsense)
+{
+ uint8_t rslt_rsense;
+ int32_t rc, sign_bit = 0;
+
+ rc = qpnp_iadc_read_reg(QPNP_IADC_NOMINAL_RSENSE, &rslt_rsense);
+ if (rc < 0) {
+ pr_err("qpnp adc rsense read failed with %d\n", rc);
+ return rc;
+ }
+
+ if (rslt_rsense & QPNP_RSENSE_MSB_SIGN_CHECK)
+ sign_bit = 1;
+
+ rslt_rsense &= ~QPNP_RSENSE_MSB_SIGN_CHECK;
+
+ if (sign_bit)
+ *rsense = QPNP_IADC_INTERNAL_RSENSE_N_OHMS_FACTOR -
+ (rslt_rsense * QPNP_IADC_RSENSE_LSB_N_OHMS_PER_BIT);
+ else
+ *rsense = QPNP_IADC_INTERNAL_RSENSE_N_OHMS_FACTOR +
+ (rslt_rsense * QPNP_IADC_RSENSE_LSB_N_OHMS_PER_BIT);
+
+ return rc;
+}
+
+int32_t qpnp_check_pmic_temp(void)
{
struct qpnp_iadc_drv *iadc = qpnp_iadc;
- int32_t vsense_mv = 0, rc;
+ struct qpnp_vadc_result result_pmic_therm;
+ int rc;
+
+ rc = qpnp_vadc_read(DIE_TEMP, &result_pmic_therm);
+ if (rc < 0)
+ return rc;
+
+ if (((uint64_t) (result_pmic_therm.physical -
+ iadc->die_temp_calib_offset))
+ > QPNP_IADC_DIE_TEMP_CALIB_OFFSET) {
+ mutex_lock(&iadc->adc->adc_lock);
+
+ rc = qpnp_iadc_calibrate_for_trim();
+ if (rc)
+ pr_err("periodic IADC calibration failed\n");
+
+ mutex_unlock(&iadc->adc->adc_lock);
+ }
+
+ return 0;
+}
+
+int32_t qpnp_iadc_read(enum qpnp_iadc_channels channel,
+ struct qpnp_iadc_result *result)
+{
+ struct qpnp_iadc_drv *iadc = qpnp_iadc;
+ int32_t rc, rsense_n_ohms, sign = 0, num;
+ int64_t result_current;
+ uint16_t raw_data;
if (!iadc || !iadc->iadc_initialized)
return -EPROBE_DEFER;
- mutex_lock(&iadc->adc->adc_lock);
-
- if (!iadc->iadc_init_calib) {
- rc = qpnp_iadc_version_check();
- if (rc)
- goto fail;
- rc = qpnp_iadc_init_calib();
- if (rc) {
- pr_err("Calibration failed\n");
- goto fail;
- } else
- iadc->iadc_init_calib = true;
+ rc = qpnp_check_pmic_temp();
+ if (rc) {
+ pr_err("Error checking pmic therm temp\n");
+ return rc;
}
- rc = qpnp_iadc_configure(channel, result);
+ mutex_lock(&iadc->adc->adc_lock);
+
+ rc = qpnp_iadc_configure(channel, &raw_data);
if (rc < 0) {
pr_err("qpnp adc result read failed with %d\n", rc);
goto fail;
}
- *result = ((vsense_mv - iadc->adc->calib.offset) *
- QPNP_ADC_GAIN_CALCULATION_UV)/
- (iadc->adc->calib.gain - iadc->adc->calib.offset);
+ rc = qpnp_iadc_get_rsense(&rsense_n_ohms);
- *result = (*result / (qpnp_iadc->rsense));
+ num = raw_data - iadc->adc->calib.offset_raw;
+ if (num < 0) {
+ sign = 1;
+ num = -num;
+ }
+
+ result->result_uv = (num * QPNP_ADC_GAIN_NV)/
+ (iadc->adc->calib.gain_raw - iadc->adc->calib.offset_raw);
+ result_current = result->result_uv;
+ result_current *= QPNP_IADC_NANO_VOLTS_FACTOR;
+ do_div(result_current, rsense_n_ohms);
+
+ if (sign) {
+ result->result_uv = -result->result_uv;
+ result_current = -result_current;
+ }
+
+ result->result_ua = (int32_t) result_current;
fail:
mutex_unlock(&iadc->adc->adc_lock);
@@ -452,24 +613,39 @@
}
EXPORT_SYMBOL(qpnp_iadc_read);
-int32_t qpnp_iadc_get_gain(int32_t *result)
+int32_t qpnp_iadc_get_gain_and_offset(struct qpnp_iadc_calib *result)
{
- return qpnp_iadc_read(GAIN_CALIBRATION_25MV, result);
-}
-EXPORT_SYMBOL(qpnp_iadc_get_gain);
+ struct qpnp_iadc_drv *iadc = qpnp_iadc;
+ int rc;
-int32_t qpnp_iadc_get_offset(enum qpnp_iadc_channels channel,
- int32_t *result)
-{
- return qpnp_iadc_read(channel, result);
+ if (!iadc || !iadc->iadc_initialized)
+ return -EPROBE_DEFER;
+
+ rc = qpnp_check_pmic_temp();
+ if (rc) {
+ pr_err("Error checking pmic therm temp\n");
+ return rc;
+ }
+
+ mutex_lock(&iadc->adc->adc_lock);
+ result->gain_raw = iadc->adc->calib.gain_raw;
+ result->ideal_gain_nv = QPNP_ADC_GAIN_NV;
+ result->gain_uv = iadc->adc->calib.gain_uv;
+ result->offset_raw = iadc->adc->calib.offset_raw;
+ result->ideal_offset_uv =
+ QPNP_OFFSET_CALIBRATION_SHORT_CADC_LEADS_IDEAL;
+ result->offset_uv = iadc->adc->calib.offset_uv;
+ mutex_unlock(&iadc->adc->adc_lock);
+
+ return 0;
}
-EXPORT_SYMBOL(qpnp_iadc_get_offset);
+EXPORT_SYMBOL(qpnp_iadc_get_gain_and_offset);
static ssize_t qpnp_iadc_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- int32_t result;
+ struct qpnp_iadc_result result;
int rc = -1;
rc = qpnp_iadc_read(attr->index, &result);
@@ -478,7 +654,7 @@
return 0;
return snprintf(buf, QPNP_ADC_HWMON_NAME_LENGTH,
- "Result:%d\n", result);
+ "Result:%d\n", result.result_ua);
}
static struct sensor_device_attribute qpnp_adc_attr =
@@ -592,10 +768,28 @@
rc = qpnp_iadc_configure_interrupt();
if (rc) {
- dev_err(&spmi->dev, "failed to configure interrupt");
+ dev_err(&spmi->dev, "failed to configure interrupt\n");
return rc;
}
+ rc = qpnp_iadc_version_check();
+ if (rc) {
+ dev_err(&spmi->dev, "IADC version not supported\n");
+ return rc;
+ }
+
+ rc = qpnp_iadc_calibrate_for_trim();
+ if (rc) {
+ dev_err(&spmi->dev, "failed to calibrate for USR trim\n");
+ return rc;
+ }
+ iadc->iadc_init_calib = true;
+ INIT_DELAYED_WORK(&iadc->iadc_work, qpnp_iadc_work);
+ schedule_delayed_work(&iadc->iadc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (QPNP_IADC_CALIB_SECONDS)));
+ iadc->iadc_initialized = true;
+
return 0;
}
diff --git a/include/linux/qpnp/qpnp-adc.h b/include/linux/qpnp/qpnp-adc.h
index f74db80..e5516ab 100644
--- a/include/linux/qpnp/qpnp-adc.h
+++ b/include/linux/qpnp/qpnp-adc.h
@@ -129,7 +129,7 @@
INTERNAL_RSENSE = 0,
EXTERNAL_RSENSE,
ALT_LEAD_PAIR,
- GAIN_CALIBRATION_25MV,
+ GAIN_CALIBRATION_17P857MV,
OFFSET_CALIBRATION_SHORT_CADC_LEADS,
OFFSET_CALIBRATION_CSP_CSN,
OFFSET_CALIBRATION_CSP2_CSN2,
@@ -590,13 +590,31 @@
* @channel - Channel for which the historical offset and gain is
* calculated. Available channels are internal rsense,
* external rsense and alternate lead pairs.
- * @offset - Offset value for the channel.
- * @gain - Gain of the channel.
+ * @offset_raw - raw Offset value for the channel.
+ * @gain_raw - raw Gain of the channel.
+ * @ideal_offset_uv - ideal offset value for the channel.
+ * @ideal_gain_nv - ideal gain for the channel.
+ * @offset_uv - converted value of offset in uV.
+ * @gain_uv - converted value of gain in uV.
*/
struct qpnp_iadc_calib {
enum qpnp_iadc_channels channel;
- int32_t offset;
- int32_t gain;
+ uint16_t offset_raw;
+ uint16_t gain_raw;
+ uint32_t ideal_offset_uv;
+ uint32_t ideal_gain_nv;
+ uint32_t offset_uv;
+ uint32_t gain_uv;
+};
+
+/**
+ * struct qpnp_iadc_result - IADC read result structure.
+ * @oresult_uv - Result of ADC in uV.
+ * @result_ua - Result of ADC in uA.
+ */
+struct qpnp_iadc_result {
+ int32_t result_uv;
+ int32_t result_ua;
};
/**
@@ -854,7 +872,7 @@
const struct qpnp_vadc_chan_properties *chan_prop,
struct qpnp_vadc_result *chan_rslt);
{ return -ENXIO; }
-static inline int32_t qpnp_vadc_is_read(void)
+static inline int32_t qpnp_vadc_is_ready(void)
{ return -ENXIO; }
#endif
@@ -867,23 +885,18 @@
* @result: Current across rsens in mV.
*/
int32_t qpnp_iadc_read(enum qpnp_iadc_channels channel,
- int32_t *result);
+ struct qpnp_iadc_result *result);
/**
- * qpnp_iadc_get_gain() - Performs gain calibration over 25mV reference
- * across CCADC.
- * @result: Gain result across 25mV reference.
+ * qpnp_iadc_get_gain_and_offset() - Performs gain calibration
+ * over 17.8571mV and offset over selected
+ * channel. Channel can be internal rsense,
+ * external rsense and alternate lead pair.
+ * @result: result structure where the gain and offset is stored of
+ * type qpnp_iadc_calib.
*/
-int32_t qpnp_iadc_get_gain(int32_t *result);
+int32_t qpnp_iadc_get_gain_and_offset(struct qpnp_iadc_calib *result);
/**
- * qpnp_iadc_get_offset() - Performs offset calibration over selected
- * channel. Channel can be internal rsense,
- * external rsense and alternate lead pair.
- * @result: Gain result across 25mV reference.
- */
-int32_t qpnp_iadc_get_offset(enum qpnp_iadc_channels channel,
- int32_t *result);
-/**
* qpnp_iadc_is_ready() - Clients can use this API to check if the
* device is ready to use.
* @result: 0 on success and -EPROBE_DEFER when probe for the device
@@ -892,14 +905,12 @@
int32_t qpnp_iadc_is_ready(void);
#else
static inline int32_t qpnp_iadc_read(enum qpnp_iadc_channels channel,
- int *result)
+ struct qpnp_iadc_result *result)
{ return -ENXIO; }
-static inline int32_t qpnp_iadc_get_gain(int32_t *result)
+static inline int32_t qpnp_iadc_get_gain_and_offset(struct qpnp_iadc_calib
+ *result)
{ return -ENXIO; }
-static inline int32_t qpnp_iadc_get_offset(enum qpnp_iadc_channels channel,
- int32_t *result)
-{ return -ENXIO; }
-static inline int32_t qpnp_iadc_is_read(void)
+static inline int32_t qpnp_iadc_is_ready(void)
{ return -ENXIO; }
#endif