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

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

Merge "leds: leds-qpnp-flash: add PMIC die temp current derate feature"

parents 27b950fc 17f24f00
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -54,6 +54,23 @@ Optional properties:
				  when battery voltage is low
- qcom,otst2-moduled-enabled	: Boolean type. This enables driver to enable MASK to support
				OTST2 connection.
- qcom,follow-otst2-rb-disabled	: Boolean type. This allows driver to reset/deset module.
				By default, driver resets module. This entry allows driver to
				bypass reset module sequence.
- qcom,die-current-derate-enabled: Boolean type. This enables driver to get maximum flash LED
                                   current, based on PMIC die temperature threshold to
				   avoid significant current derate from hardware. This property
				   is not needed if PMIC is older than PMI8994v2.0.
- qcom,die-temp-vadc		: VADC channel source for flash LED. This property is not
				  needed if PMIC is older than PMI8994v2.0.
- qcom,die-temp-threshold       : Integer type array for PMIC die temperature threshold.
				  Array should have at least one value. Values should be in
				  celcius. This property is not needed if PMIC is older than
				  PMI8994v2.0.
- qcom,die-temp-derate-current	: Integer type arrray for PMIC die temperature derate
				  current. Array should have at least one value. Values
				  should be in mA. This property is not needed if PMIC is older
				  than PMI8994v2.0.

Required properties inside child node. Chile node contains settings for each individual LED.
Each LED hardware needs a node for itself and a switch node to control brightness.
@@ -103,6 +120,10 @@ Example:
		qcom,vph-pwr-droop-debounce-time = <10>;
		qcom,headroom-sense-ch0-enabled;
		qcom,headroom-sense-ch1-enabled;
		qcom,die-current-derate-enabled;
		qcom,die-temp-vadc = <&pmi8994_vadc>;
		qcom,die-temp-threshold = <85 80 75 70 65>;
		qcom,die-temp-derate-current = <400 800 1200 1600 2000>;

		pm8226_flash0: qcom,flash_0 {
			label = "flash";
+214 −36
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>
#include <linux/qpnp/qpnp-adc.h>
#include "leds.h"
#include <linux/debugfs.h>
#include <linux/uaccess.h>
@@ -186,6 +187,10 @@ struct flash_node_data {
 * Flash LED configuration read from device tree
 */
struct flash_led_platform_data {
	unsigned int			temp_threshold_num;
	unsigned int			temp_derate_curr_num;
	unsigned int			*die_temp_derate_curr_ma;
	unsigned int			*die_temp_threshold_degc;
	u16				ramp_up_step;
	u16				ramp_dn_step;
	u16				vph_pwr_droop_threshold;
@@ -205,6 +210,7 @@ struct flash_led_platform_data {
	bool				power_detect_en;
	bool				mask3_en;
	bool				follow_rb_disable;
	bool				die_current_derate_en;
};

struct qpnp_flash_led_buffer {
@@ -226,6 +232,7 @@ struct qpnp_flash_led {
	struct flash_node_data		*flash_node;
	struct power_supply		*battery_psy;
	struct workqueue_struct		*ordered_workq;
	struct qpnp_vadc_chip		*vadc_dev;
	struct mutex			flash_led_lock;
	struct qpnp_flash_led_buffer	*log;
	struct dentry			*dbgfs_root;
@@ -523,19 +530,58 @@ qpnp_led_masked_write(struct spmi_device *spmi_dev, u16 addr, u8 mask, u8 val)
	return rc;
}

static int qpnp_flash_led_get_allowed_die_temp_curr(struct qpnp_flash_led *led,
							int64_t die_temp_degc)
{
	int die_temp_curr_ma;

	if (die_temp_degc >= led->pdata->die_temp_threshold_degc[0])
		die_temp_curr_ma =  0;
	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[1])
		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[0];
	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[2])
		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[1];
	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[3])
		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[2];
	else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[4])
		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[3];
	else
		die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[4];

	return die_temp_curr_ma;
}

static int64_t qpnp_flash_led_get_die_temp(struct qpnp_flash_led *led)
{
	struct qpnp_vadc_result die_temp_result;
	int rc;

	rc = qpnp_vadc_read(led->vadc_dev, SPARE2, &die_temp_result);
	if (rc) {
		pr_err("failed to read the die temp\n");
		return -EINVAL;
	}

	return die_temp_result.physical;
}

static int
qpnp_flash_led_get_max_avail_current(struct flash_node_data *flash_node,
					struct qpnp_flash_led *led)
{
	union power_supply_propval prop;
	int max_curr_avail_ma;
	int64_t chg_temp_milidegc, die_temp_degc;
	int max_curr_avail_ma = 2000;
	int allowed_die_temp_curr_ma = 2000;
	int rc;

	if (led->pdata->power_detect_en) {
		if (!led->battery_psy) {
			dev_err(&led->spmi_dev->dev,
				"Failed to query power supply\n");
			return -EINVAL;
		}

		/*
		* When charging is enabled, enforce this new
		* enabelment sequence to reduce fuel gauge
@@ -545,7 +591,6 @@ qpnp_flash_led_get_max_avail_current(struct flash_node_data *flash_node,
			rc = qpnp_led_masked_write(led->spmi_dev,
				FLASH_MODULE_ENABLE_CTRL(led->base),
				FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);

			if (rc) {
				dev_err(&led->spmi_dev->dev,
				"Module enable reg write failed\n");
@@ -565,10 +610,57 @@ qpnp_flash_led_get_max_avail_current(struct flash_node_data *flash_node,
		}

		max_curr_avail_ma = (prop.intval / FLASH_LED_UA_PER_MA);
	}

	/* When thermal mitigation is available, this logic
	*  will execute, to derate current based on PMIC die
	*  temperature.
	*/
	if (led->pdata->die_current_derate_en) {
		chg_temp_milidegc = qpnp_flash_led_get_die_temp(led);
		if (chg_temp_milidegc < 0)
			return -EINVAL;

		die_temp_degc = div_s64(chg_temp_milidegc, 1000);
		allowed_die_temp_curr_ma =
			qpnp_flash_led_get_allowed_die_temp_curr(led,
								die_temp_degc);
		if (allowed_die_temp_curr_ma < 0)
			return -EINVAL;
	}

	max_curr_avail_ma = (max_curr_avail_ma >= allowed_die_temp_curr_ma)
				? allowed_die_temp_curr_ma : max_curr_avail_ma;

	return max_curr_avail_ma;
}

static ssize_t qpnp_flash_led_die_temp_store(struct device *dev,
					struct device_attribute *attr,
					const char *buf, size_t count)
{
	struct qpnp_flash_led *led;
	struct flash_node_data *flash_node;
	unsigned long val;
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	ssize_t ret;

	ret = kstrtoul(buf, 10, &val);
	if (ret)
		return ret;

	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
	led = dev_get_drvdata(&flash_node->spmi_dev->dev);

	/*'0' for disable die_temp feature; non-zero to enable feature*/
	if (val == 0)
		led->pdata->die_current_derate_en = false;
	else
		led->pdata->die_current_derate_en = true;

	return count;
}

static ssize_t qpnp_led_strobe_type_store(struct device *dev,
			struct device_attribute *attr,
			const char *buf, size_t count)
@@ -658,18 +750,23 @@ static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
	struct qpnp_flash_led *led;
	struct flash_node_data *flash_node;
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	int max_curr_avail_ma;
	int max_curr_avail_ma = 0;

	flash_node = container_of(led_cdev, struct flash_node_data, cdev);
	led = dev_get_drvdata(&flash_node->spmi_dev->dev);

	if (led->pdata->power_detect_en) {
	if (led->flash_node[0].flash_on)
		max_curr_avail_ma += led->flash_node[0].max_current;
	if (led->flash_node[1].flash_on)
		max_curr_avail_ma += led->flash_node[1].max_current;

	if (led->pdata->power_detect_en ||
			led->pdata->die_current_derate_en) {
		max_curr_avail_ma =
			qpnp_flash_led_get_max_avail_current(flash_node, led);

		if (max_curr_avail_ma < 0)
			return -EINVAL;
		else
			max_curr_avail_ma = (int)flash_node->max_current;
	}

	return snprintf(buf, PAGE_SIZE, "%u\n", max_curr_avail_ma);
@@ -688,6 +785,9 @@ static struct device_attribute qpnp_flash_led_attrs[] = {
	__ATTR(max_allowed_current, (S_IRUGO | S_IWUSR | S_IWGRP),
				qpnp_flash_led_max_current_show,
				NULL),
	__ATTR(enable_die_temp_current_derate, (S_IRUGO | S_IWUSR | S_IWGRP),
				NULL,
				qpnp_flash_led_die_temp_store),
};

static int qpnp_flash_led_get_thermal_derate_rate(const char *rate)
@@ -1170,7 +1270,8 @@ static void qpnp_flash_led_work(struct work_struct *work)
			goto exit_flash_led_work;
		}

		if (led->pdata->power_detect_en) {
		if (led->pdata->power_detect_en ||
					led->pdata->die_current_derate_en) {
			if (led->battery_psy) {
				led->battery_psy->get_property(led->battery_psy,
					POWER_SUPPLY_PROP_STATUS,
@@ -1189,6 +1290,7 @@ static void qpnp_flash_led_work(struct work_struct *work)
					|| psy_prop.intval ==
					POWER_SUPPLY_STATUS_NOT_CHARGING)
					led->charging_enabled = false;
			}
			max_curr_avail_ma =
				qpnp_flash_led_get_max_avail_current
							(flash_node, led);
@@ -1198,7 +1300,6 @@ static void qpnp_flash_led_work(struct work_struct *work)
				goto exit_flash_led_work;
			}
		}
		}

		if (led->flash_node[led->num_leds - 1].id ==
							FLASH_LED_SWITCH) {
@@ -1966,6 +2067,83 @@ static int qpnp_flash_led_parse_common_dt(
	led->pdata->follow_rb_disable = of_property_read_bool(node,
						"qcom,follow-otst2-rb-disabled");

	led->pdata->die_current_derate_en = of_property_read_bool(node,
					"qcom,die-current-derate-enabled");

	if (led->pdata->die_current_derate_en) {
		led->vadc_dev = qpnp_get_vadc(&led->spmi_dev->dev,
							"die-temp");
		if (IS_ERR(led->vadc_dev)) {
			pr_err("VADC channel property Missing\n");
			return -EINVAL;
		}

		if (of_find_property(node, "qcom,die-temp-threshold",
				&led->pdata->temp_threshold_num)) {

			if (led->pdata->temp_threshold_num > 0) {
				led->pdata->die_temp_threshold_degc =
				devm_kzalloc(&led->spmi_dev->dev,
						led->pdata->temp_threshold_num,
						GFP_KERNEL);

				if (led->pdata->die_temp_threshold_degc
								== NULL) {
					dev_err(&led->spmi_dev->dev,
					"failed to allocate die temp array\n");
					return -ENOMEM;
				}
				led->pdata->temp_threshold_num /=
							sizeof(unsigned int);

				rc = of_property_read_u32_array(node,
						"qcom,die-temp-threshold",
				led->pdata->die_temp_threshold_degc,
						led->pdata->temp_threshold_num);
				if (rc) {
					dev_err(&led->spmi_dev->dev,
					"couldn't read temp threshold rc=%d\n",
								rc);
					return rc;
				}
			}
		}

		if (of_find_property(node, "qcom,die-temp-derate-current",
					&led->pdata->temp_derate_curr_num)) {
			if (led->pdata->temp_derate_curr_num > 0) {
				led->pdata->die_temp_derate_curr_ma =
					devm_kzalloc(&led->spmi_dev->dev,
					led->pdata->temp_derate_curr_num,
					GFP_KERNEL);
				if (led->pdata->die_temp_derate_curr_ma
								== NULL) {
					dev_err(&led->spmi_dev->dev,
						"failed to allocate die derate current array\n");
					return -ENOMEM;
				}
				led->pdata->temp_derate_curr_num /=
						sizeof(unsigned int);

				rc = of_property_read_u32_array(node,
						"qcom,die-temp-derate-current",
				led->pdata->die_temp_derate_curr_ma,
				led->pdata->temp_derate_curr_num);
				if (rc) {
					dev_err(&led->spmi_dev->dev,
					"couldn't read temp limits rc =%d\n",
								rc);
					return rc;
				}
			}
		}
		if (led->pdata->temp_threshold_num !=
					led->pdata->temp_derate_curr_num) {
			pr_err("Both array size are not same\n");
			return -EINVAL;
		}
	}

	led->pinctrl = devm_pinctrl_get(&led->spmi_dev->dev);
	if (IS_ERR_OR_NULL(led->pinctrl)) {
		dev_err(&led->spmi_dev->dev,