hwmon: qpnp-adc-current: Fix potential race condition
There should be no synchronous Voltage and Current readings
initiated on IADC_USR when a single VADC_USR conversion is
in progress. If a synchronous Voltage and Current read
occures while a VADC_USR is in progress it results in an
invalid state for that single conversion on the VADC_USR.
There is a potential race condition where the above scenario
can occur that is due to an IADC conversion occuring before
the VADC_IADC USR sync conversion is complete and ends up
using the mode select as a VADC_IADC sync request.
Therefore use a single lock for both IADC and
synchronous VADC_IADC request.
Change-Id: Idb451b813bc5f08b7239d371fc0f8fa5e17429b1
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 adaff41..9839595 100644
--- a/drivers/hwmon/qpnp-adc-current.c
+++ b/drivers/hwmon/qpnp-adc-current.c
@@ -147,7 +147,6 @@
struct list_head list;
int64_t die_temp;
struct delayed_work iadc_work;
- struct mutex iadc_vadc_lock;
bool iadc_mode_sel;
struct qpnp_iadc_comp iadc_comp;
struct qpnp_vadc_chip *vadc_dev;
@@ -1012,12 +1011,10 @@
if (qpnp_iadc_is_valid(iadc) < 0)
return -EPROBE_DEFER;
- if (!iadc->iadc_mode_sel) {
- rc = qpnp_check_pmic_temp(iadc);
- if (rc) {
- pr_err("Error checking pmic therm temp\n");
- return rc;
- }
+ rc = qpnp_check_pmic_temp(iadc);
+ if (rc) {
+ pr_err("Error checking pmic therm temp\n");
+ return rc;
}
mutex_lock(&iadc->adc->adc_lock);
@@ -1121,24 +1118,21 @@
enum qpnp_iadc_channels i_channel, struct qpnp_iadc_result *i_result,
enum qpnp_vadc_channels v_channel, struct qpnp_vadc_result *v_result)
{
- int rc = 0;
+ int rc = 0, mode_sel = 0, num = 0, rsense_n_ohms = 0, sign = 0;
+ uint16_t raw_data;
+ int32_t rsense_u_ohms = 0;
+ int64_t result_current;
if (qpnp_iadc_is_valid(iadc) < 0)
return -EPROBE_DEFER;
- mutex_lock(&iadc->iadc_vadc_lock);
+ mutex_lock(&iadc->adc->adc_lock);
if (iadc->iadc_poll_eoc) {
pr_debug("acquiring iadc eoc wakelock\n");
pm_stay_awake(iadc->dev);
}
- rc = qpnp_check_pmic_temp(iadc);
- if (rc) {
- pr_err("PMIC die temp check failed\n");
- goto fail;
- }
-
iadc->iadc_mode_sel = true;
rc = qpnp_vadc_iadc_sync_request(iadc->vadc_dev, v_channel);
@@ -1147,11 +1141,43 @@
goto fail;
}
- rc = qpnp_iadc_read(iadc, i_channel, i_result);
- if (rc)
- pr_err("Configuring IADC failed\n");
- /* Intentional fall through to release VADC */
+ rc = qpnp_iadc_configure(iadc, i_channel, &raw_data, mode_sel);
+ if (rc < 0) {
+ pr_err("qpnp adc result read failed with %d\n", rc);
+ goto fail_release_vadc;
+ }
+ rc = qpnp_iadc_get_rsense(iadc, &rsense_n_ohms);
+ pr_debug("current raw:0%x and rsense:%d\n",
+ raw_data, rsense_n_ohms);
+ rsense_u_ohms = rsense_n_ohms/1000;
+ num = raw_data - iadc->adc->calib.offset_raw;
+ if (num < 0) {
+ sign = 1;
+ num = -num;
+ }
+
+ i_result->result_uv = (num * QPNP_ADC_GAIN_NV)/
+ (iadc->adc->calib.gain_raw - iadc->adc->calib.offset_raw);
+ result_current = i_result->result_uv;
+ result_current *= QPNP_IADC_NANO_VOLTS_FACTOR;
+ /* Intentional fall through. Process the result w/o comp */
+ do_div(result_current, rsense_u_ohms);
+
+ if (sign) {
+ i_result->result_uv = -i_result->result_uv;
+ result_current = -result_current;
+ }
+ result_current *= -1;
+ rc = qpnp_iadc_comp_result(iadc, &result_current);
+ if (rc < 0)
+ pr_err("Error during compensating the IADC\n");
+ rc = 0;
+ result_current *= -1;
+
+ i_result->result_ua = (int32_t) result_current;
+
+fail_release_vadc:
rc = qpnp_vadc_iadc_sync_complete_request(iadc->vadc_dev, v_channel,
v_result);
if (rc)
@@ -1163,7 +1189,7 @@
pr_debug("releasing iadc eoc wakelock\n");
pm_relax(iadc->dev);
}
- mutex_unlock(&iadc->iadc_vadc_lock);
+ mutex_unlock(&iadc->adc->adc_lock);
return rc;
}
@@ -1306,7 +1332,6 @@
goto fail;
}
- mutex_init(&iadc->iadc_vadc_lock);
INIT_WORK(&iadc->trigger_completion_work, qpnp_iadc_trigger_completion);
INIT_DELAYED_WORK(&iadc->iadc_work, qpnp_iadc_work);
rc = qpnp_iadc_comp_info(iadc);
@@ -1347,7 +1372,6 @@
int i = 0;
cancel_delayed_work(&iadc->iadc_work);
- mutex_destroy(&iadc->iadc_vadc_lock);
for_each_child_of_node(node, child) {
device_remove_file(&spmi->dev,
&iadc->sens_attr[i].dev_attr);