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

Commit 41dfbf02 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "leds: qti-flash: Add maximum available flash current prediction"

parents c6aacf32 19b532c6
Loading
Loading
Loading
Loading
+305 −11
Original line number Diff line number Diff line
@@ -15,13 +15,21 @@
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/soc/qcom/battery_charger.h>

#include "leds.h"

#define FLASH_LED_REVISION1		0x00

#define FLASH_LED_PERIPH_SUBTYPE		0x05

#define FLASH_LED_STATUS1		0x06

#define FLASH_LED_STATUS2		0x07
#define  FLASH_LED_OTST1_STATUS		BIT(5)
#define  FLASH_LED_OTST2_STATUS		BIT(4)
#define  FLASH_LED_VPH_PWR_LOW		BIT(0)

#define FLASH_INT_RT_STS		0x10
@@ -59,6 +67,17 @@
#define  FLASH_LED_ENABLE(id)			BIT(id)
#define  FLASH_LED_DISABLE		0

#define FLASH_LED_MITIGATION_SEL		0x63
#define  FLASH_LED_PREEMPTIVE_LMH_MASK	GENMASK(1, 0)
#define  FLASH_LED_LMH_MITIGATION_SW		0x2

#define FLASH_LED_THERMAL_OTST2_CFG1		0x78
#define FLASH_LED_THERMAL_OTST1_CFG1		0x7A
#define  FLASH_LED_THERMAL_THRSH_MASK		GENMASK(2, 0)
#define  FLASH_LED_V1_OTST1_THRSH_MIN		0x13
#define  FLASH_LED_V2_OTST1_THRSH_MIN		0x10
#define  FLASH_LED_OTST2_THRSH_MIN		0x30

#define MAX_IRES_LEVELS		2
#define IRES_12P5_MAX_CURR_MA	1500
#define IRES_5P0_MAX_CURR_MA		640
@@ -67,6 +86,13 @@
#define IRES_5P0_UA		5000
#define IRES_DEFAULT_UA		IRES_12P5_UA
#define MAX_FLASH_CURRENT_MA		2000
#define IBATT_OCP_THRESH_DEFAULT_UA		4500000
#define FLASH_THERMAL_LEVELS		2
#define OTST1_IDX		0
#define OTST2_IDX		1
#define OTST1_CURR_LIM_MA		200
#define OTST2_CURR_LIM_MA		500
#define VLED_MAX_DEFAULT_UV		3500000

enum flash_led_type {
	FLASH_LED_TYPE_UNKNOWN,
@@ -74,12 +100,19 @@ enum flash_led_type {
	FLASH_LED_TYPE_TORCH,
};

enum flash_led_revision {
	FLASH_LED_REVISION_2P0 = 1,
};

enum flash_led_subtype {
	FLASH_LED_4_CHAN = 0x7,
};

enum strobe_type {
	SW_STROBE = 0,
	HW_STROBE,
};

/* Configurations for each individual flash or torch device */
struct flash_node_data {
	struct qti_flash_led		*led;
	struct led_classdev_flash		fdev;
@@ -112,6 +145,7 @@ struct flash_switch_data {
 * @regmap		: Pointer for regmap structure
 * @fnode		: Pointer for array of child LED devices
 * @snode		: Pointer for array of child switch devices
 * @batt_psy		: Pointer for battery power supply
 * @lock		: Spinlock to be used for critical section
 * @num_fnodes		: Number of flash/torch nodes defined in device tree
 * @num_snodes		: Number of switch nodes defined in device tree
@@ -119,15 +153,21 @@ struct flash_switch_data {
 * @all_ramp_up_done_irq		: IRQ number for all ramp up interrupt
 * @all_ramp_down_done_irq		: IRQ number for all ramp down interrupt
 * @led_fault_irq		: IRQ number for LED fault interrupt
 * @max_current		: Maximum current available for flash
 * @thermal_derate_current	: Thermal derating current limits
 * @base		: Base address of the flash LED module
 * @revision		: Revision of the flash LED module
 * @subtype		: Peripheral subtype of the flash LED module
 * @max_channels	: Maximum number of channels supported by flash module
 * @ref_count		: Reference count used to enable/disable flash LED
 * @trigger_lmh		: Flag to enable lmh mitigation
 */
struct qti_flash_led {
	struct platform_device		*pdev;
	struct regmap		*regmap;
	struct flash_node_data		*fnode;
	struct flash_switch_data		*snode;
	struct power_supply		*batt_psy;
	spinlock_t		lock;
	u32			num_fnodes;
	u32			num_snodes;
@@ -136,9 +176,22 @@ struct qti_flash_led {
	int			all_ramp_down_done_irq;
	int			led_fault_irq;
	int			max_current;
	int			thermal_derate_current[FLASH_THERMAL_LEVELS];
	u16			base;
	u8		revision;
	u8		subtype;
	u8		max_channels;
	u8		ref_count;
	bool		trigger_lmh;
};

struct flash_current_headroom {
	u16 current_ma;
	u16 headroom_mv;
};

static const struct flash_current_headroom pm8350c_map[4] = {
	{750, 200}, {1000, 250}, {1250, 300}, {1500, 400},
};

static const u32 flash_led_max_ires_values[MAX_IRES_LEVELS] = {
@@ -586,15 +639,214 @@ static struct led_classdev *trigger_to_lcdev(struct led_trigger *trig)
	return NULL;
}

static ssize_t qti_flash_led_max_current_show(struct device *dev,
		struct device_attribute *attr, char *buf)
#define UCONV			1000000LL
#define MCONV			1000LL
#define VIN_FLASH_MIN_UV	3300000LL
#define BOB_EFFICIENCY	900LL
#define VFLASH_DIP_MARGIN_UV	50000
#define VOLTAGE_HDRM_DEFAULT_MV		400
#define VDIP_THRESH_DEFAULT_UV		2800000LL

static int qti_flash_led_get_voltage_headroom(
					struct qti_flash_led *led)
{
	struct flash_switch_data *snode;
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	static const struct flash_current_headroom *hdrm_map;
	int i, j, voltage_hdrm_mv = 0, voltage_hdrm_max = 0;
	u32 map_size = 0;

	snode = container_of(led_cdev, struct flash_switch_data, cdev);
	if (led->subtype == FLASH_LED_4_CHAN) {
		hdrm_map = pm8350c_map;
		map_size = ARRAY_SIZE(pm8350c_map);
	}

	return scnprintf(buf, PAGE_SIZE, "%d\n", snode->led->max_current);
	for (i = 0; i < led->num_fnodes; i++) {
		if (!led->fnode[i].configured)
			continue;

		voltage_hdrm_mv = VOLTAGE_HDRM_DEFAULT_MV;

		for (j = 0; j < map_size; j++) {
			if (led->fnode[i].current_ma <= hdrm_map[j].current_ma)
				voltage_hdrm_mv = hdrm_map[j].headroom_mv;
		}

		voltage_hdrm_max = max(voltage_hdrm_max, voltage_hdrm_mv);
	}

	if (!voltage_hdrm_max)
		voltage_hdrm_max = VOLTAGE_HDRM_DEFAULT_MV;

	return voltage_hdrm_max;
}

static int qti_flash_led_calc_max_avail_current(
			struct qti_flash_led *led,
			int *max_current_ma)
{
	int rc;
	int rbatt_uohm, ocv_uv, ibatt_now_ua, voltage_hdrm_mv;
	int64_t ibatt_safe_ua, i_flash_ua, i_avail_ua, vflash_vdip,
		vph_flash_uv, vin_flash_uv, p_flash_fw;
	union power_supply_propval prop = {};

	rc = qti_battery_charger_get_prop("battery", BATTERY_RESISTANCE,
						&rbatt_uohm);
	if (rc < 0) {
		pr_err("Failed to get battery resistance, rc=%d\n",
				rc);
		return rc;
	}

	if (!rbatt_uohm) {
		*max_current_ma = MAX_FLASH_CURRENT_MA;
		return 0;
	}

	if (!led->batt_psy)
		led->batt_psy = power_supply_get_by_name("battery");

	if (!led->batt_psy) {
		pr_err("Failed to get battery power supply, rc=%d\n", rc);
		return -EINVAL;
	}

	rc = power_supply_get_property(led->batt_psy,
		POWER_SUPPLY_PROP_VOLTAGE_OCV, &prop);
	if (rc < 0) {
		pr_err("Failed to get battery OCV, rc=%d\n", rc);
		return rc;
	}
	ocv_uv = prop.intval;

	rc = power_supply_get_property(led->batt_psy,
		POWER_SUPPLY_PROP_CURRENT_NOW, &prop);
	if (rc < 0) {
		pr_err("Failed to get battery current, rc=%d\n", rc);
		return rc;
	}

	/* Battery power supply returns -ve value for discharging */
	ibatt_now_ua = -(prop.intval);

	voltage_hdrm_mv = qti_flash_led_get_voltage_headroom(led);
	vflash_vdip = VDIP_THRESH_DEFAULT_UV;

	if (!led->trigger_lmh) {
		rc = qti_flash_led_masked_write(led, FLASH_LED_MITIGATION_SEL,
			FLASH_LED_PREEMPTIVE_LMH_MASK,
			FLASH_LED_LMH_MITIGATION_SW);
		if (rc < 0) {
			pr_err("Failed to enable LMH mitigation, rc=%d\n", rc);
			return rc;
		}

		/* Wait for lmh mitigation to take effect */
		udelay(100);

		led->trigger_lmh = true;
		return qti_flash_led_calc_max_avail_current(led,
					max_current_ma);
	}

	ibatt_safe_ua = DIV_ROUND_CLOSEST((ocv_uv -
				(vflash_vdip + VFLASH_DIP_MARGIN_UV)) * UCONV,
				rbatt_uohm);

	if (ibatt_safe_ua < IBATT_OCP_THRESH_DEFAULT_UA) {
		i_flash_ua = ibatt_safe_ua - ibatt_now_ua;
		vph_flash_uv = vflash_vdip + VFLASH_DIP_MARGIN_UV;
	} else {
		i_flash_ua = IBATT_OCP_THRESH_DEFAULT_UA - ibatt_now_ua;
		vph_flash_uv = ocv_uv - DIV_ROUND_CLOSEST((int64_t)rbatt_uohm
				* IBATT_OCP_THRESH_DEFAULT_UA, UCONV);
	}

	vin_flash_uv = max(VLED_MAX_DEFAULT_UV +
				(voltage_hdrm_mv * MCONV), VIN_FLASH_MIN_UV);

	p_flash_fw = BOB_EFFICIENCY * vph_flash_uv * i_flash_ua;
	i_avail_ua = DIV_ROUND_CLOSEST(p_flash_fw, (vin_flash_uv * MCONV));

	*max_current_ma = min(MAX_FLASH_CURRENT_MA,
				(int)(DIV_ROUND_CLOSEST(i_avail_ua, MCONV)));

	pr_debug("rbatt_uohm=%d ocv_uv=%d ibatt_now_ua=%d i_avail_ua=%lld\n",
			rbatt_uohm, ocv_uv, ibatt_now_ua, i_avail_ua);

	return 0;
}

static int qti_flash_led_calc_thermal_current(
			struct qti_flash_led *led,
			int *thermal_current_limit)
{
	int rc;
	u8 otst_status, thrsh_min = 0;

	if (led->subtype == FLASH_LED_4_CHAN) {
		thrsh_min = FLASH_LED_V1_OTST1_THRSH_MIN;

		if (led->revision == FLASH_LED_REVISION_2P0)
			thrsh_min = FLASH_LED_V2_OTST1_THRSH_MIN;
	}

	rc = qti_flash_led_masked_write(led,
			FLASH_LED_THERMAL_OTST1_CFG1,
			FLASH_LED_THERMAL_THRSH_MASK,
			thrsh_min);
	if (rc < 0)
		return rc;

	rc = qti_flash_led_masked_write(led,
			FLASH_LED_THERMAL_OTST2_CFG1,
			FLASH_LED_THERMAL_THRSH_MASK,
			FLASH_LED_OTST2_THRSH_MIN);
	if (rc < 0)
		return rc;

	/* Check THERMAL OTST status */
	rc = qti_flash_led_read(led, FLASH_LED_STATUS2, &otst_status, 1);
	if (rc < 0)
		return rc;

	if (otst_status & FLASH_LED_OTST1_STATUS)
		*thermal_current_limit = led->thermal_derate_current[OTST1_IDX];
	else if (otst_status & FLASH_LED_OTST2_STATUS)
		*thermal_current_limit = led->thermal_derate_current[OTST2_IDX];

	pr_debug("thermal_current_limit=%d\n", *thermal_current_limit);

	return 0;
}

static int qti_flash_led_get_max_avail_current(
				struct qti_flash_led *led, int *max_current_ma)
{
	int thermal_current_limit = 0, rc;

	led->trigger_lmh = false;
	rc = qti_flash_led_calc_max_avail_current(led, max_current_ma);
	if (rc < 0) {
		pr_err("Failed to calculate max avail current, rc=%d\n", rc);
		return rc;
	}

	rc = qti_flash_led_calc_thermal_current(led,
			&thermal_current_limit);
	if (rc < 0) {
		pr_err("Failed to calculate thermal current limit, rc=%d\n",
			rc);
		return rc;
	}

	if (thermal_current_limit)
		*max_current_ma = min(*max_current_ma, thermal_current_limit);

	led->max_current = *max_current_ma;

	pr_debug("max_current_ma=%d\n", *max_current_ma);

	return 0;
}

int qti_flash_led_prepare(struct led_trigger *trig, int options,
@@ -602,6 +854,7 @@ int qti_flash_led_prepare(struct led_trigger *trig, int options,
{
	struct led_classdev *led_cdev;
	struct flash_switch_data *snode;
	int rc = 0;

	if (!trig) {
		pr_err("Invalid led_trigger\n");
@@ -617,13 +870,36 @@ int qti_flash_led_prepare(struct led_trigger *trig, int options,
	snode = container_of(led_cdev, struct flash_switch_data, cdev);

	if (options & QUERY_MAX_AVAIL_CURRENT) {
		*max_current = snode->led->max_current;
		rc = qti_flash_led_get_max_avail_current(snode->led,
					max_current);
		if (rc < 0) {
			pr_err("Failed to query max avail current, rc=%d\n",
				rc);
			return rc;
		}
	}

	return 0;
}
EXPORT_SYMBOL(qti_flash_led_prepare);

static ssize_t qti_flash_led_max_current_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct flash_switch_data *snode;
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	int rc;

	snode = container_of(led_cdev, struct flash_switch_data, cdev);
	rc = qti_flash_led_get_max_avail_current(snode->led,
				&snode->led->max_current);
	if (rc < 0) {
		pr_err("Failed to get max current, rc=%d\n", rc);
		return -EINVAL;
	}
EXPORT_SYMBOL(qti_flash_led_prepare);

	return scnprintf(buf, PAGE_SIZE, "%d\n", snode->led->max_current);
}

static struct device_attribute qti_flash_led_attrs[] = {
	__ATTR(max_current, 0664, qti_flash_led_max_current_show, NULL),
@@ -728,6 +1004,18 @@ static int qti_flash_led_setup(struct qti_flash_led *led)
	int rc = 0, i, addr_offset;
	u8 val, mask;

	rc = qti_flash_led_read(led, FLASH_LED_REVISION1, &val, 1);
	if (rc < 0)
		return rc;

	led->revision = val;

	rc = qti_flash_led_read(led, FLASH_LED_PERIPH_SUBTYPE, &val, 1);
	if (rc < 0)
		return rc;

	led->subtype = val;

	for (i = 0; i < led->num_fnodes; i++) {
		addr_offset = led->fnode[i].id;
		rc = qti_flash_led_masked_write(led,
@@ -749,6 +1037,9 @@ static int qti_flash_led_setup(struct qti_flash_led *led)

	led->max_current = MAX_FLASH_CURRENT_MA;

	led->thermal_derate_current[OTST1_IDX] = OTST1_CURR_LIM_MA;
	led->thermal_derate_current[OTST2_IDX] = OTST2_CURR_LIM_MA;

	return rc;
}

@@ -1231,6 +1522,9 @@ static int qti_flash_led_remove(struct platform_device *pdev)
	struct qti_flash_led *led = dev_get_drvdata(&pdev->dev);
	int i, j;

	if (led->batt_psy)
		power_supply_put(led->batt_psy);

	for (i = 0; (i < led->num_snodes); i++) {
		for (j = 0; j < ARRAY_SIZE(qti_flash_led_attrs); j++)
			sysfs_remove_file(&led->snode[i].cdev.dev->kobj,