Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c1f30594 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "regulator: cpr-regulator: Update CPR de-aging algorithm"

parents 6b1b0f37 28f22979
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -682,7 +682,8 @@ Optional properties:
				specified in the array should be bypassed for the de-aging procedure. The number of
				elements should be less than or equal to 32. The values of the array elements should
				be greater than or equal to 0 and less than or equal to 31.
				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
				This property is required for power-domains with bypass mux present in HW.
				This property can be required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-aging-ro-scaling-factor:	The aging ring oscillator (RO) scaling factor with units of QUOT/V.
				This value is used for calculating a voltage margin from RO measurements.
				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+257 −64
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#include <linux/pm_opp.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/sort.h>
#include <linux/uaccess.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
@@ -163,6 +164,7 @@
/* RBCPR Aging Resgister */
#define REG_RBCPR_HTOL_AGE		0x160
#define RBCPR_HTOL_AGE_PAGE		BIT(1)
#define RBCPR_AGE_DATA_STATUS		BIT(2)

/* RBCPR Clock Control Register */
#define RBCPR_CLK_SEL_MASK	BIT(0)
@@ -189,6 +191,21 @@
#define FLAGS_SET_MIN_VOLTAGE		BIT(1)
#define FLAGS_UPLIFT_QUOT_VOLT		BIT(2)

/*
 * The number of individual aging measurements to perform which are then
 * averaged together in order to determine the final aging adjustment value.
 */
#define CPR_AGING_MEASUREMENT_ITERATIONS	16

/*
 * Aging measurements for the aged and unaged ring oscillators take place a few
 * microseconds apart.  If the vdd-supply voltage fluctuates between the two
 * measurements, then the difference between them will be incorrect.  The
 * difference could end up too high or too low.  This constant defines the
 * number of lowest and highest measurements to ignore when averaging.
 */
#define CPR_AGING_MEASUREMENT_FILTER	3

#define CPR_REGULATOR_DRIVER_NAME	"qcom,cpr-regulator"

/**
@@ -1082,25 +1099,57 @@ _exit:
	return IRQ_HANDLED;
}

/**
 * cmp_int() - int comparison function to be passed into the sort() function
 *		which leads to ascending sorting
 * @a:			First int value
 * @b:			Second int value
 *
 * Return: >0 if a > b, 0 if a == b, <0 if a < b
 */
static int cmp_int(const void *a, const void *b)
{
	return *(int *)a - *(int *)b;
}

static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
			struct cpr_aging_sensor_info *aging_sensor_info)
{
	int retries, rc = 0, sel_fast = 0, i;
	int age_quot_min, age_quot_max;
	u32 val;
	int quot_min, quot_max, is_aging_measurement, aging_measurement_count;
	int quot_min_scaled, quot_max_scaled, quot_delta_scaled_sum;
	int retries, rc = 0, sel_fast = 0, i, quot_delta_scaled;
	u32 val, gcnt_ref, gcnt;
	int *quot_delta_results, filtered_count;


	quot_delta_results = kcalloc(CPR_AGING_MEASUREMENT_ITERATIONS,
			sizeof(*quot_delta_results), GFP_ATOMIC);
	if (!quot_delta_results)
		return -ENOMEM;

	/* Clear the target quotient value and gate count of all ROs */
	for (i = 0; i < CPR_NUM_RING_OSC; i++)
		cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0);

	/* Program GCNT0/1 for getting aging data */
	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), cpr_vreg->gcnt);
	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), cpr_vreg->gcnt);
	gcnt_ref = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000;
	gcnt = gcnt_ref * 3 / 2;
	val = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) <<
			RBCPR_GCNT_TARGET_GCNT_SHIFT;
	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), val);
	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), val);

	val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(0));
	cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET0 = 0x%08x\n", val);

	val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(1));
	cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET1 = 0x%08x\n", val);

	/* Program TIMER_INTERVAL to zero */
	cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, 0);

	/* Bypass sensors in collapsible domain */
	if (cpr_vreg->aging_info->aging_sensor_bypass)
		cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0,
			(cpr_vreg->aging_info->aging_sensor_bypass &
		RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id)));
@@ -1128,14 +1177,26 @@ static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
		goto _exit;
	}

	/* Set age page mode and send cont nack */
	/* Set age page mode */
	cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, RBCPR_HTOL_AGE_PAGE);

	aging_measurement_count = 0;
	quot_delta_scaled_sum = 0;

	for (i = 0; i < CPR_AGING_MEASUREMENT_ITERATIONS; i++) {
		/* Send cont nack */
		cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);

	/* Make sure cpr starts next measurement with toggling busy bit */
		/*
		 * Make sure cpr starts next measurement with
		 * toggling busy bit
		 */
		mb();

	/* Wait for controller to finish measurement and time-out after 5ms */
		/*
		 * Wait for controller to finish measurement
		 * and time-out after 5ms
		 */
		retries = 50;
		while (retries-- && cpr_ctl_is_busy(cpr_vreg))
			udelay(100);
@@ -1146,6 +1207,10 @@ static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
			goto _exit;
		}

		/* Check for PAGE_IS_AGE flag in status register */
		val = cpr_read(cpr_vreg, REG_RBCPR_HTOL_AGE);
		is_aging_measurement = val & RBCPR_AGE_DATA_STATUS;

		val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_1);
		sel_fast = RBCPR_RESULT_1_SEL_FAST(val);
		cpr_debug(cpr_vreg, "RBCPR_RESULT_1 = 0x%08x\n", val);
@@ -1154,22 +1219,60 @@ static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
		cpr_debug(cpr_vreg, "RBCPR_DEBUG1 = 0x%08x\n", val);

		if (sel_fast == 1) {
		age_quot_min = RBCPR_DEBUG1_QUOT_FAST(val);
		age_quot_max = RBCPR_DEBUG1_QUOT_SLOW(val);
			quot_min = RBCPR_DEBUG1_QUOT_FAST(val);
			quot_max = RBCPR_DEBUG1_QUOT_SLOW(val);
		} else {
		age_quot_min = RBCPR_DEBUG1_QUOT_SLOW(val);
		age_quot_max = RBCPR_DEBUG1_QUOT_FAST(val);
			quot_min = RBCPR_DEBUG1_QUOT_SLOW(val);
			quot_max = RBCPR_DEBUG1_QUOT_FAST(val);
		}

	cpr_debug(cpr_vreg, "Age sensor[%d]: quot_min = %d, quot_max = %d\n",
			aging_sensor_info->sensor_id,
			age_quot_min, age_quot_max);
		/*
		 * Scale the quotients so that they are equivalent to the fused
		 * values.  This accounts for the difference in measurement
		 * interval times.
		 */

		quot_min_scaled = quot_min * (gcnt_ref + 1) / (gcnt + 1);
		quot_max_scaled = quot_max * (gcnt_ref + 1) / (gcnt + 1);

		quot_delta_scaled = 0;
		if (is_aging_measurement) {
			quot_delta_scaled = quot_min_scaled - quot_max_scaled;
			quot_delta_results[aging_measurement_count++] =
					quot_delta_scaled;
		}

	aging_sensor_info->current_quot_diff = age_quot_min - age_quot_max;
		cpr_debug(cpr_vreg,
			"Age sensor[%d]: current aging quot diff = %d\n",
			"Age sensor[%d]: measurement[%d]: page_is_age=%u quot_min = %d, quot_max = %d quot_min_scaled = %d, quot_max_scaled = %d quot_delta_scaled = %d\n",
			aging_sensor_info->sensor_id, i, is_aging_measurement,
			quot_min, quot_max, quot_min_scaled, quot_max_scaled,
			quot_delta_scaled);
	}

	filtered_count
		= aging_measurement_count - CPR_AGING_MEASUREMENT_FILTER * 2;
	if (filtered_count > 0) {
		sort(quot_delta_results, aging_measurement_count,
			sizeof(*quot_delta_results), cmp_int, NULL);

		quot_delta_scaled_sum = 0;
		for (i = 0; i < filtered_count; i++)
			quot_delta_scaled_sum
				+= quot_delta_results[i
					+ CPR_AGING_MEASUREMENT_FILTER];

		aging_sensor_info->current_quot_diff
			= quot_delta_scaled_sum / filtered_count;
		cpr_debug(cpr_vreg,
			"Age sensor[%d]: average aging quotient delta = %d (count = %d)\n",
			aging_sensor_info->sensor_id,
			aging_sensor_info->current_quot_diff);
			aging_sensor_info->current_quot_diff, filtered_count);
	} else {
		cpr_err(cpr_vreg, "%d aging measurements completed after %d iterations\n",
			aging_measurement_count,
			CPR_AGING_MEASUREMENT_ITERATIONS);
		rc = -EBUSY;
	}

_exit:
	/* Clear age page bit */
@@ -1179,6 +1282,7 @@ _exit:
	cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0x0);

	/* Clear the sensor bypass */
	if (cpr_vreg->aging_info->aging_sensor_bypass)
		cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0, 0x0);

	/* Unmask all sensors */
@@ -1353,6 +1457,14 @@ static int cpr_calculate_de_aging_margin(struct cpr_regulator *cpr_vreg)
		return rc;
	}

	/* Force PWM mode */
	rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_NORMAL);
	if (rc) {
		cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n",
			REGULATOR_MODE_NORMAL, rc);
		return rc;
	}

	get_online_cpus();
	cpumask_and(&tmp_mask, &cpr_vreg->cpu_mask, cpu_online_mask);
	if (!cpumask_empty(&tmp_mask)) {
@@ -1369,6 +1481,14 @@ static int cpr_calculate_de_aging_margin(struct cpr_regulator *cpr_vreg)

	put_online_cpus();

	/* Set to initial mode */
	rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_IDLE);
	if (rc) {
		cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n",
			REGULATOR_MODE_IDLE, rc);
		return rc;
	}

	/* Clear interrupts */
	cpr_irq_clr(cpr_vreg);

@@ -3554,11 +3674,11 @@ static int cpr_aging_init(struct platform_device *pdev,
		return rc;
	}

	prop = of_find_property(of_node, "qcom,cpr-non-collapsible-sensors",
				&len);
	if (of_find_property(of_node, "qcom,cpr-non-collapsible-sensors",
				&len)) {
		len = len / sizeof(u32);
	if ((!prop) || len < 0 || len > 32) {
		cpr_err(cpr_vreg, "qcom,cpr-non-collapsible-sensors is missing or has an incorrect size\n");
		if (len <= 0 || len > 32) {
			cpr_err(cpr_vreg, "qcom,cpr-non-collapsible-sensors has an incorrect size\n");
			return -EINVAL;
		}

@@ -3581,10 +3701,15 @@ static int cpr_aging_init(struct platform_device *pdev,
			non_collapsible_sensor_mask |= BIT(sensor);
		}

	/* Bypass the sensors in collapsible domain for de-aging measurements */
	aging_info->aging_sensor_bypass = ~(non_collapsible_sensor_mask);
	cpr_info(cpr_vreg, "sensor bypass mask for aging = 0x%08x\n",
		/*
		 * Bypass the sensors in collapsible domain for
		 * de-aging measurements
		 */
		aging_info->aging_sensor_bypass =
						~(non_collapsible_sensor_mask);
		cpr_debug(cpr_vreg, "sensor bypass mask for aging = 0x%08x\n",
			aging_info->aging_sensor_bypass);
	}

	prop = of_find_property(pdev->dev.of_node, "qcom,cpr-aging-derate",
			NULL);
@@ -5664,6 +5789,64 @@ static const struct file_operations cpr_debug_info_fops = {
	.read = cpr_debug_info_read,
};

static int cpr_aging_debug_info_open(struct inode *inode, struct file *file)
{
	file->private_data = inode->i_private;

	return 0;
}

static ssize_t cpr_aging_debug_info_read(struct file *file, char __user *buff,
				size_t count, loff_t *ppos)
{
	struct cpr_regulator *cpr_vreg = file->private_data;
	struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
	char *debugfs_buf;
	ssize_t len, ret = 0;
	int i;

	debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (!debugfs_buf)
		return -ENOMEM;

	mutex_lock(&cpr_vreg->cpr_mutex);

	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
			"aging_adj_volt = [");
	ret += len;

	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
		len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
				" %d", aging_info->voltage_adjust[i]);
		ret += len;
	}

	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
			" ]uV\n");
	ret += len;

	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
			"aging_measurement_done = %s\n",
			aging_info->cpr_aging_done ? "true" : "false");
	ret += len;

	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
			"aging_measurement_error = %s\n",
			aging_info->cpr_aging_error ? "true" : "false");
	ret += len;

	mutex_unlock(&cpr_vreg->cpr_mutex);

	ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret);
	kfree(debugfs_buf);
	return ret;
}

static const struct file_operations cpr_aging_debug_info_fops = {
	.open = cpr_aging_debug_info_open,
	.read = cpr_aging_debug_info_read,
};

static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg)
{
	struct dentry *temp;
@@ -5714,6 +5897,16 @@ static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg)
		cpr_err(cpr_vreg, "cpr_max_ceiling node creation failed\n");
		return;
	}

	if (cpr_vreg->aging_info) {
		temp = debugfs_create_file("aging_debug_info", S_IRUGO,
					cpr_vreg->debugfs, cpr_vreg,
					&cpr_aging_debug_info_fops);
		if (IS_ERR_OR_NULL(temp)) {
			cpr_err(cpr_vreg, "aging_debug_info node creation failed\n");
			return;
		}
	}
}

static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg)