linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver
@ 2017-11-16 12:18 Kiran Gunda
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
                   ` (3 more replies)
  0 siblings, 4 replies; 28+ messages in thread
From: Kiran Gunda @ 2017-11-16 12:18 UTC (permalink / raw)
  To: bjorn.andersson, linux-arm-msm
  Cc: linux-kernel, linux-arm-msm-owner, Kiran Gunda

WLED driver provides the interface to the display driver to adjust the
brightness of the display backlight. This driver exposes two APIs to set
and get the brightness of the display backlight through the backlight
framework. This driver has the support to handle the OVP
(over voltage protection) and the SC (short circuit protection)
interrupts. It also has the auto calibration algorithm support to
configure the right strings if the user specified string configuration
is in-correct.

Kiran Gunda (4):
  qcom: spmi-wled: Add support for qcom wled driver
  qcom: spmi-wled: Add support for short circuit handling
  qcom: spmi-wled: Add support for OVP interrupt handling
  qcom: spmi-wled: Add auto-calibration logic support

 .../bindings/leds/backlight/qcom-spmi-wled.txt     | 118 +++
 drivers/video/backlight/Kconfig                    |   9 +
 drivers/video/backlight/Makefile                   |   1 +
 drivers/video/backlight/qcom-spmi-wled.c           | 999 +++++++++++++++++++++
 4 files changed, 1127 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
 create mode 100644 drivers/video/backlight/qcom-spmi-wled.c

-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-16 12:18 [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver Kiran Gunda
@ 2017-11-16 12:18 ` Kiran Gunda
  2017-11-16 16:55   ` Bjorn Andersson
                     ` (3 more replies)
  2017-11-16 12:18 ` [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Kiran Gunda
                   ` (2 subsequent siblings)
  3 siblings, 4 replies; 28+ messages in thread
From: Kiran Gunda @ 2017-11-16 12:18 UTC (permalink / raw)
  To: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Rob Herring, Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds,
	devicetree, linux-kernel, linux-fbdev
  Cc: linux-arm-msm-owner, Kiran Gunda

WLED driver provides the interface to the display driver to
adjust the brightness of the display backlight.

Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
---
 .../bindings/leds/backlight/qcom-spmi-wled.txt     |  90 ++++
 drivers/video/backlight/Kconfig                    |   9 +
 drivers/video/backlight/Makefile                   |   1 +
 drivers/video/backlight/qcom-spmi-wled.c           | 504 +++++++++++++++++++++
 4 files changed, 604 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
 create mode 100644 drivers/video/backlight/qcom-spmi-wled.c

diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
new file mode 100644
index 0000000..f1ea25b
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
@@ -0,0 +1,90 @@
+Binding for Qualcomm WLED driver
+
+WLED (White Light Emitting Diode) driver is used for controlling display
+backlight that is part of PMIC on Qualcomm Technologies reference platforms.
+The PMIC is connected to the host processor via SPMI bus.
+
+- compatible
+	Usage:      required
+	Value type: <string>
+	Definition: should be "qcom,pm8998-spmi-wled".
+
+- reg
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition:  Base address and size of the WLED modules.
+
+- reg-names
+	Usage:      required
+	Value type: <string>
+	Definition:  Names associated with base addresses. should be
+		     "qcom-wled-ctrl-base", "qcom-wled-sink-base".
+
+- label
+	Usage:      required
+	Value type: <string>
+	Definition: The name of the backlight device.
+
+- default-brightness
+	Usage:      optional
+	Value type: <u32>
+	Definition: brightness value on boot, value from: 0-4095
+		    default: 2048
+
+- qcom,fs-current-limit
+	Usage:      optional
+	Value type: <u32>
+	Definition: per-string full scale current limit in uA. value from
+		    0 to 30000 with 5000 uA resolution. default: 25000 uA
+
+- qcom,current-boost-limit
+	Usage:      optional
+	Value type: <u32>
+	Definition: ILIM threshold in mA. values are 105, 280, 450, 620, 970,
+		    1150, 1300, 1500. default: 970 mA
+
+- qcom,switching-freq
+	Usage:      optional
+	Value type: <u32>
+	Definition: Switching frequency in KHz. values are
+		    600, 640, 685, 738, 800, 872, 960, 1066, 1200, 1371,
+		    1600, 1920, 2400, 3200, 4800, 9600.
+		    default: 800 KHz
+
+- qcom,ovp
+	Usage:      optional
+	Value type: <u32>
+	Definition: Over-voltage protection limit in mV. values are 31100,
+		    29600, 19600, 18100.
+	            default: 29600 mV
+
+- qcom,string-cfg
+	Usage:      optional
+	Value type: <u32>
+	Definition: Bit mask of the wled strings. Bit 0 to 3 indicates strings
+		    0 to 3 respectively. Wled module has four strings of leds
+		    numbered from 0 to 3. Each string of leds are operated
+		    individually. Specify the strings using the bit mask. Any
+		    combination of led strings can be used.
+		    default value is 15 (b1111).
+
+- qcom,en-cabc
+	Usage:      optional
+	Value type: <bool>
+	Definition: Specify if cabc (content adaptive backlight control) is
+		    needed.
+
+Example:
+
+qcom-wled@d800 {
+	compatible = "qcom,pm8998-spmi-wled";
+	reg = <0xd800 0xd900>;
+	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
+	label = "backlight";
+
+	qcom,fs-current-limit = <25000>;
+	qcom,current-boost-limit = <970>;
+	qcom,switching-freq = <800>;
+	qcom,ovp = <29600>;
+	qcom,string-cfg = <15>;
+};
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 4e1d2ad..19ea799 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -306,6 +306,15 @@ config BACKLIGHT_PM8941_WLED
 	  If you have the Qualcomm PM8941, say Y to enable a driver for the
 	  WLED block.
 
+config BACKLIGHT_QCOM_SPMI_WLED
+	tristate "Qualcomm WLED Driver"
+	select REGMAP
+	help
+	  If you have the Qualcomm WLED used for backlight control, say Y to
+	  enable a driver for the  WLED block. This driver provides the
+	  interface to the display driver to adjust the brightness of the
+	  display backlight. This supports PMI8998 currently.
+
 config BACKLIGHT_SAHARA
 	tristate "Tabletkiosk Sahara Touch-iT Backlight Driver"
 	depends on X86
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 5e28f01..f6627e5 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_BACKLIGHT_PANDORA)		+= pandora_bl.o
 obj-$(CONFIG_BACKLIGHT_PCF50633)	+= pcf50633-backlight.o
 obj-$(CONFIG_BACKLIGHT_PM8941_WLED)	+= pm8941-wled.o
 obj-$(CONFIG_BACKLIGHT_PWM)		+= pwm_bl.o
+obj-$(CONFIG_BACKLIGHT_QCOM_SPMI_WLED)	+= qcom-spmi-wled.o
 obj-$(CONFIG_BACKLIGHT_SAHARA)		+= kb3886_bl.o
 obj-$(CONFIG_BACKLIGHT_SKY81452)	+= sky81452-backlight.o
 obj-$(CONFIG_BACKLIGHT_TOSA)		+= tosa_bl.o
diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
new file mode 100644
index 0000000..14c3adc
--- /dev/null
+++ b/drivers/video/backlight/qcom-spmi-wled.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/backlight.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+
+/* General definitions */
+#define QCOM_WLED_DEFAULT_BRIGHTNESS		2048
+#define  QCOM_WLED_MAX_BRIGHTNESS		4095
+
+/* WLED control registers */
+#define QCOM_WLED_CTRL_MOD_ENABLE		0x46
+#define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
+#define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
+
+#define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
+#define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
+
+#define QCOM_WLED_CTRL_OVP			0x4d
+#define  QCOM_WLED_CTRL_OVP_MASK		GENMASK(1, 0)
+
+#define QCOM_WLED_CTRL_ILIM			0x4e
+#define  QCOM_WLED_CTRL_ILIM_MASK		GENMASK(2, 0)
+
+/* WLED sink registers */
+#define QCOM_WLED_SINK_CURR_SINK_EN		0x46
+#define  QCOM_WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
+#define  QCOM_WLED_SINK_CURR_SINK_SHFT		0x04
+
+#define QCOM_WLED_SINK_SYNC			0x47
+#define  QCOM_WLED_SINK_SYNC_MASK		GENMASK(3, 0)
+#define  QCOM_WLED_SINK_SYNC_LED1		BIT(0)
+#define  QCOM_WLED_SINK_SYNC_LED2		BIT(1)
+#define  QCOM_WLED_SINK_SYNC_LED3		BIT(2)
+#define  QCOM_WLED_SINK_SYNC_LED4		BIT(3)
+#define  QCOM_WLED_SINK_SYNC_CLEAR		0x00
+
+#define QCOM_WLED_SINK_MOD_EN_REG(n)		(0x50 + (n * 0x10))
+#define  QCOM_WLED_SINK_REG_STR_MOD_MASK	BIT(7)
+#define  QCOM_WLED_SINK_REG_STR_MOD_EN		BIT(7)
+
+#define QCOM_WLED_SINK_SYNC_DLY_REG(n)		(0x51 + (n * 0x10))
+#define QCOM_WLED_SINK_FS_CURR_REG(n)		(0x52 + (n * 0x10))
+#define  QCOM_WLED_SINK_FS_MASK			GENMASK(3, 0)
+
+#define QCOM_WLED_SINK_CABC_REG(n)		(0x56 + (n * 0x10))
+#define  QCOM_WLED_SINK_CABC_MASK		BIT(7)
+#define  QCOM_WLED_SINK_CABC_EN			BIT(7)
+
+#define QCOM_WLED_SINK_BRIGHT_LSB_REG(n)	(0x57 + (n * 0x10))
+#define QCOM_WLED_SINK_BRIGHT_MSB_REG(n)	(0x58 + (n * 0x10))
+
+struct qcom_wled_config {
+	u32 i_boost_limit;
+	u32 ovp;
+	u32 switch_freq;
+	u32 fs_current;
+	u32 string_cfg;
+	bool en_cabc;
+};
+
+struct qcom_wled {
+	const char *name;
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	u16 sink_addr;
+	u16 ctrl_addr;
+	u32 brightness;
+	bool prev_state;
+
+	struct qcom_wled_config cfg;
+};
+
+static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
+{
+	int rc;
+
+	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
+			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
+	return rc;
+}
+
+static int qcom_wled_get_brightness(struct backlight_device *bl)
+{
+	struct qcom_wled *wled = bl_get_data(bl);
+
+	return wled->brightness;
+}
+
+static int qcom_wled_sync_toggle(struct qcom_wled *wled)
+{
+	int rc;
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->sink_addr + QCOM_WLED_SINK_SYNC,
+			QCOM_WLED_SINK_SYNC_MASK, QCOM_WLED_SINK_SYNC_MASK);
+	if (rc < 0)
+		return rc;
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->sink_addr + QCOM_WLED_SINK_SYNC,
+			QCOM_WLED_SINK_SYNC_MASK, QCOM_WLED_SINK_SYNC_CLEAR);
+
+	return rc;
+}
+
+static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 brightness)
+{
+	int rc, i;
+	u16 low_limit = QCOM_WLED_MAX_BRIGHTNESS * 4 / 1000;
+	u8 string_cfg = wled->cfg.string_cfg;
+	u8 v[2];
+
+	/* WLED's lower limit of operation is 0.4% */
+	if (brightness > 0 && brightness < low_limit)
+		brightness = low_limit;
+
+	v[0] = brightness & 0xff;
+	v[1] = (brightness >> 8) & 0xf;
+
+	for (i = 0; (string_cfg >> i) != 0; i++) {
+		if (string_cfg & BIT(i)) {
+			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
+					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int qcom_wled_update_status(struct backlight_device *bl)
+{
+	struct qcom_wled *wled = bl_get_data(bl);
+	u16 brightness = bl->props.brightness;
+	int rc;
+
+	if (bl->props.power != FB_BLANK_UNBLANK ||
+	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
+	    bl->props.state & BL_CORE_FBBLANK)
+		brightness = 0;
+
+	if (brightness) {
+		rc = qcom_wled_set_brightness(wled, brightness);
+		if (rc < 0) {
+			pr_err("wled failed to set brightness rc:%d\n", rc);
+			return rc;
+		}
+
+		if (!!brightness != wled->prev_state) {
+			rc = qcom_wled_module_enable(wled, !!brightness);
+			if (rc < 0) {
+				pr_err("wled enable failed rc:%d\n", rc);
+				return rc;
+			}
+		}
+	} else {
+		rc = qcom_wled_module_enable(wled, brightness);
+		if (rc < 0) {
+			pr_err("wled disable failed rc:%d\n", rc);
+			return rc;
+		}
+	}
+
+	wled->prev_state = !!brightness;
+
+	rc = qcom_wled_sync_toggle(wled);
+	if (rc < 0) {
+		pr_err("wled sync failed rc:%d\n", rc);
+		return rc;
+	}
+
+	wled->brightness = brightness;
+
+	return rc;
+}
+
+static int qcom_wled_setup(struct qcom_wled *wled)
+{
+	int rc, temp, i;
+	u8 sink_en = 0;
+	u8 string_cfg = wled->cfg.string_cfg;
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
+			QCOM_WLED_CTRL_OVP_MASK, wled->cfg.ovp);
+	if (rc < 0)
+		return rc;
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_ILIM,
+			QCOM_WLED_CTRL_ILIM_MASK, wled->cfg.i_boost_limit);
+	if (rc < 0)
+		return rc;
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_SWITCH_FREQ,
+			QCOM_WLED_CTRL_SWITCH_FREQ_MASK, wled->cfg.switch_freq);
+	if (rc < 0)
+		return rc;
+
+	for (i = 0; (string_cfg >> i) != 0; i++) {
+		if (string_cfg & BIT(i)) {
+			u16 addr = wled->sink_addr +
+					QCOM_WLED_SINK_MOD_EN_REG(i);
+
+			rc = regmap_update_bits(wled->regmap, addr,
+					QCOM_WLED_SINK_REG_STR_MOD_MASK,
+					QCOM_WLED_SINK_REG_STR_MOD_EN);
+			if (rc < 0)
+				return rc;
+
+			addr = wled->sink_addr +
+					QCOM_WLED_SINK_FS_CURR_REG(i);
+			rc = regmap_update_bits(wled->regmap, addr,
+					QCOM_WLED_SINK_FS_MASK,
+					wled->cfg.fs_current);
+			if (rc < 0)
+				return rc;
+
+			addr = wled->sink_addr +
+					QCOM_WLED_SINK_CABC_REG(i);
+			rc = regmap_update_bits(wled->regmap, addr,
+					QCOM_WLED_SINK_CABC_MASK,
+					wled->cfg.en_cabc ?
+					QCOM_WLED_SINK_CABC_EN : 0);
+			if (rc)
+				return rc;
+
+			temp = i + QCOM_WLED_SINK_CURR_SINK_SHFT;
+			sink_en |= 1 << temp;
+		}
+	}
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
+			QCOM_WLED_SINK_CURR_SINK_MASK, sink_en);
+	if (rc < 0)
+		return rc;
+
+	rc = qcom_wled_sync_toggle(wled);
+	if (rc < 0) {
+		pr_err("Failed to toggle sync reg rc:%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static const struct qcom_wled_config wled_config_defaults = {
+	.i_boost_limit = 4,
+	.fs_current = 10,
+	.ovp = 1,
+	.switch_freq = 11,
+	.string_cfg = 0xf,
+	.en_cabc = 0,
+};
+
+struct qcom_wled_var_cfg {
+	const u32 *values;
+	u32 (*fn)(u32);
+	int size;
+};
+
+static const u32 wled_i_boost_limit_values[] = {
+	105, 280, 450, 620, 970, 1150, 1300, 1500,
+};
+
+static const struct qcom_wled_var_cfg wled_i_boost_limit_cfg = {
+	.values = wled_i_boost_limit_values,
+	.size = ARRAY_SIZE(wled_i_boost_limit_values),
+};
+
+static const u32 wled_fs_current_values[] = {
+	0, 2500, 5000, 7500, 10000, 12500, 15000, 17500, 20000,
+	22500, 25000, 27500, 30000,
+};
+
+static const struct qcom_wled_var_cfg wled_fs_current_cfg = {
+	.values = wled_fs_current_values,
+	.size = ARRAY_SIZE(wled_fs_current_values),
+};
+
+static const u32 wled_ovp_values[] = {
+	31100, 29600, 19600, 18100,
+};
+
+static const struct qcom_wled_var_cfg wled_ovp_cfg = {
+	.values = wled_ovp_values,
+	.size = ARRAY_SIZE(wled_ovp_values),
+};
+
+static u32 qcom_wled_switch_freq_values_fn(u32 idx)
+{
+	return 19200 / (2 * (1 + idx));
+}
+
+static const struct qcom_wled_var_cfg wled_switch_freq_cfg = {
+	.fn = qcom_wled_switch_freq_values_fn,
+	.size = 16,
+};
+
+static const struct qcom_wled_var_cfg wled_string_cfg = {
+	.size = 16,
+};
+
+static u32 qcom_wled_values(const struct qcom_wled_var_cfg *cfg, u32 idx)
+{
+	if (idx >= cfg->size)
+		return UINT_MAX;
+	if (cfg->fn)
+		return cfg->fn(idx);
+	if (cfg->values)
+		return cfg->values[idx];
+	return idx;
+}
+
+static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
+{
+	struct qcom_wled_config *cfg = &wled->cfg;
+	const __be32 *prop_addr;
+	u32 val, c;
+	int rc, i, j;
+
+	const struct {
+		const char *name;
+		u32 *val_ptr;
+		const struct qcom_wled_var_cfg *cfg;
+	} u32_opts[] = {
+		{
+			"qcom,current-boost-limit",
+			&cfg->i_boost_limit,
+			.cfg = &wled_i_boost_limit_cfg,
+		},
+		{
+			"qcom,fs-current-limit",
+			&cfg->fs_current,
+			.cfg = &wled_fs_current_cfg,
+		},
+		{
+			"qcom,ovp",
+			&cfg->ovp,
+			.cfg = &wled_ovp_cfg,
+		},
+		{
+			"qcom,switching-freq",
+			&cfg->switch_freq,
+			.cfg = &wled_switch_freq_cfg,
+		},
+		{
+			"qcom,string-cfg",
+			&cfg->string_cfg,
+			.cfg = &wled_string_cfg,
+		},
+	};
+
+	const struct {
+		const char *name;
+		bool *val_ptr;
+	} bool_opts[] = {
+		{ "qcom,en-cabc", &cfg->en_cabc, },
+	};
+
+	prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
+	if (!prop_addr) {
+		pr_err("invalid IO resources\n");
+		return -EINVAL;
+	}
+	wled->ctrl_addr = be32_to_cpu(*prop_addr);
+
+	prop_addr = of_get_address(dev->of_node, 1, NULL, NULL);
+	if (!prop_addr) {
+		pr_err("invalid IO resources\n");
+		return -EINVAL;
+	}
+	wled->sink_addr = be32_to_cpu(*prop_addr);
+	rc = of_property_read_string(dev->of_node, "label", &wled->name);
+	if (rc < 0)
+		wled->name = dev->of_node->name;
+
+	*cfg = wled_config_defaults;
+	for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
+		rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
+		if (rc == -EINVAL) {
+			continue;
+		} else if (rc < 0) {
+			pr_err("error reading '%s'\n", u32_opts[i].name);
+			return rc;
+		}
+
+		c = UINT_MAX;
+		for (j = 0; c != val; j++) {
+			c = qcom_wled_values(u32_opts[i].cfg, j);
+			if (c == UINT_MAX) {
+				pr_err("invalid value for '%s'\n",
+					u32_opts[i].name);
+				return -EINVAL;
+			}
+
+			if (c == val)
+				break;
+		}
+
+		pr_debug("'%s' = %u\n", u32_opts[i].name, c);
+		*u32_opts[i].val_ptr = j;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
+		if (of_property_read_bool(dev->of_node, bool_opts[i].name))
+			*bool_opts[i].val_ptr = true;
+	}
+
+	return 0;
+}
+
+static const struct backlight_ops qcom_wled_ops = {
+	.update_status = qcom_wled_update_status,
+	.get_brightness = qcom_wled_get_brightness,
+};
+
+static int qcom_wled_probe(struct platform_device *pdev)
+{
+	struct backlight_properties props;
+	struct backlight_device *bl;
+	struct qcom_wled *wled;
+	struct regmap *regmap;
+	u32 val;
+	int rc;
+
+	regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!regmap) {
+		pr_err("Unable to get regmap\n");
+		return -EINVAL;
+	}
+
+	wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
+	if (!wled)
+		return -ENOMEM;
+
+	wled->regmap = regmap;
+	wled->pdev = pdev;
+
+	rc = qcom_wled_configure(wled, &pdev->dev);
+	if (rc < 0) {
+		pr_err("wled configure failed rc:%d\n", rc);
+		return rc;
+	}
+
+	rc = qcom_wled_setup(wled);
+	if (rc < 0) {
+		pr_err("wled setup failed rc:%d\n", rc);
+		return rc;
+	}
+
+	val = QCOM_WLED_DEFAULT_BRIGHTNESS;
+	of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
+	wled->brightness = val;
+
+	platform_set_drvdata(pdev, wled);
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_RAW;
+	props.brightness = val;
+	props.max_brightness = QCOM_WLED_MAX_BRIGHTNESS;
+	bl = devm_backlight_device_register(&pdev->dev, pdev->name,
+					    &pdev->dev, wled,
+					    &qcom_wled_ops, &props);
+	return PTR_ERR_OR_ZERO(bl);
+}
+
+static const struct of_device_id qcom_wled_match_table[] = {
+	{ .compatible = "qcom,pm8998-spmi-wled",},
+	{ },
+};
+
+static struct platform_driver qcom_wled_driver = {
+	.probe = qcom_wled_probe,
+	.driver	= {
+		.name = "qcom-spmi-wled",
+		.of_match_table	= qcom_wled_match_table,
+	},
+};
+
+module_platform_driver(qcom_wled_driver);
+
+MODULE_DESCRIPTION("Qualcomm SPMI PMIC WLED driver");
+MODULE_LICENSE("GPL v2");
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling
  2017-11-16 12:18 [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver Kiran Gunda
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
@ 2017-11-16 12:18 ` Kiran Gunda
  2017-11-17 20:30   ` Rob Herring
  2017-12-05  4:35   ` Bjorn Andersson
  2017-11-16 12:18 ` [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling Kiran Gunda
  2017-11-16 12:18 ` [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support Kiran Gunda
  3 siblings, 2 replies; 28+ messages in thread
From: Kiran Gunda @ 2017-11-16 12:18 UTC (permalink / raw)
  To: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Rob Herring, Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds,
	devicetree, linux-kernel, linux-fbdev
  Cc: linux-arm-msm-owner, Kiran Gunda

Handle the short circuit(SC) interrupt and check if the SC interrupt
is valid. Re-enable the module to check if it goes away. Disable the
module altogether if the SC event persists.

Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
---
 .../bindings/leds/backlight/qcom-spmi-wled.txt     |  22 ++++
 drivers/video/backlight/qcom-spmi-wled.c           | 126 ++++++++++++++++++++-
 2 files changed, 142 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
index f1ea25b..768608c 100644
--- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
+++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
@@ -74,6 +74,26 @@ The PMIC is connected to the host processor via SPMI bus.
 	Definition: Specify if cabc (content adaptive backlight control) is
 		    needed.
 
+- qcom,ext-pfet-sc-pro-en
+	Usage:      optional
+	Value type: <bool>
+	Definition: Specify if external PFET control for short circuit
+		    protection is needed.
+
+- interrupts
+	Usage:      optional
+	Value type: <prop encoded array>
+	Definition: Interrupts associated with WLED. Interrupts can be
+		    specified as per the encoding listed under
+		    Documentation/devicetree/bindings/spmi/
+		    qcom,spmi-pmic-arb.txt.
+
+- interrupt-names
+	Usage:      optional
+	Value type: <string>
+	Definition: Interrupt names associated with the interrupts.
+		    Must be "sc-irq".
+
 Example:
 
 qcom-wled@d800 {
@@ -82,6 +102,8 @@ qcom-wled@d800 {
 	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
 	label = "backlight";
 
+	interrupts = <0x3 0xd8 0x2 IRQ_TYPE_EDGE_RISING>;
+	interrupt-names = "sc-irq";
 	qcom,fs-current-limit = <25000>;
 	qcom,current-boost-limit = <970>;
 	qcom,switching-freq = <800>;
diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
index 14c3adc..7dbaaa7 100644
--- a/drivers/video/backlight/qcom-spmi-wled.c
+++ b/drivers/video/backlight/qcom-spmi-wled.c
@@ -11,6 +11,9 @@
  * GNU General Public License for more details.
  */
 
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/ktime.h>
 #include <linux/kernel.h>
 #include <linux/backlight.h>
 #include <linux/module.h>
@@ -23,7 +26,13 @@
 #define QCOM_WLED_DEFAULT_BRIGHTNESS		2048
 #define  QCOM_WLED_MAX_BRIGHTNESS		4095
 
+#define QCOM_WLED_SC_DLY_MS			20
+#define QCOM_WLED_SC_CNT_MAX			5
+#define QCOM_WLED_SC_RESET_CNT_DLY_US		1000000
+
 /* WLED control registers */
+#define QCOM_WLED_CTRL_FAULT_STATUS		0x08
+
 #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
 #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
 #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
@@ -37,6 +46,15 @@
 #define QCOM_WLED_CTRL_ILIM			0x4e
 #define  QCOM_WLED_CTRL_ILIM_MASK		GENMASK(2, 0)
 
+#define QCOM_WLED_CTRL_SHORT_PROTECT		0x5e
+#define  QCOM_WLED_CTRL_SHORT_EN_MASK		BIT(7)
+
+#define QCOM_WLED_CTRL_SEC_ACCESS		0xd0
+#define  QCOM_WLED_CTRL_SEC_UNLOCK		0xa5
+
+#define QCOM_WLED_CTRL_TEST1			0xe2
+#define  QCOM_WLED_EXT_FET_DTEST2		0x09
+
 /* WLED sink registers */
 #define QCOM_WLED_SINK_CURR_SINK_EN		0x46
 #define  QCOM_WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
@@ -71,19 +89,23 @@ struct qcom_wled_config {
 	u32 switch_freq;
 	u32 fs_current;
 	u32 string_cfg;
+	int sc_irq;
 	bool en_cabc;
+	bool ext_pfet_sc_pro_en;
 };
 
 struct qcom_wled {
 	const char *name;
 	struct platform_device *pdev;
 	struct regmap *regmap;
+	struct mutex lock;
+	struct qcom_wled_config cfg;
+	ktime_t last_sc_event_time;
 	u16 sink_addr;
 	u16 ctrl_addr;
 	u32 brightness;
+	u32 sc_count;
 	bool prev_state;
-
-	struct qcom_wled_config cfg;
 };
 
 static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
@@ -157,25 +179,26 @@ static int qcom_wled_update_status(struct backlight_device *bl)
 	    bl->props.state & BL_CORE_FBBLANK)
 		brightness = 0;
 
+	mutex_lock(&wled->lock);
 	if (brightness) {
 		rc = qcom_wled_set_brightness(wled, brightness);
 		if (rc < 0) {
 			pr_err("wled failed to set brightness rc:%d\n", rc);
-			return rc;
+			goto unlock_mutex;
 		}
 
 		if (!!brightness != wled->prev_state) {
 			rc = qcom_wled_module_enable(wled, !!brightness);
 			if (rc < 0) {
 				pr_err("wled enable failed rc:%d\n", rc);
-				return rc;
+				goto unlock_mutex;
 			}
 		}
 	} else {
 		rc = qcom_wled_module_enable(wled, brightness);
 		if (rc < 0) {
 			pr_err("wled disable failed rc:%d\n", rc);
-			return rc;
+			goto unlock_mutex;
 		}
 	}
 
@@ -184,19 +207,69 @@ static int qcom_wled_update_status(struct backlight_device *bl)
 	rc = qcom_wled_sync_toggle(wled);
 	if (rc < 0) {
 		pr_err("wled sync failed rc:%d\n", rc);
-		return rc;
+		goto unlock_mutex;
 	}
 
 	wled->brightness = brightness;
 
+unlock_mutex:
+	mutex_unlock(&wled->lock);
 	return rc;
 }
 
+static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
+{
+	struct qcom_wled *wled = _wled;
+	int rc;
+	u32 val;
+	s64 elapsed_time;
+
+	rc = regmap_read(wled->regmap,
+		wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS, &val);
+	if (rc < 0) {
+		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	wled->sc_count++;
+	pr_err("WLED short circuit detected %d times fault_status=%x\n",
+		wled->sc_count, val);
+	mutex_lock(&wled->lock);
+	rc = qcom_wled_module_enable(wled, false);
+	if (rc < 0) {
+		pr_err("wled disable failed rc:%d\n", rc);
+		goto unlock_mutex;
+	}
+
+	elapsed_time = ktime_us_delta(ktime_get(),
+				wled->last_sc_event_time);
+	if (elapsed_time > QCOM_WLED_SC_RESET_CNT_DLY_US) {
+		wled->sc_count = 0;
+	} else if (wled->sc_count > QCOM_WLED_SC_CNT_MAX) {
+		pr_err("SC trigged %d times, disabling WLED forever!\n",
+			wled->sc_count);
+		goto unlock_mutex;
+	}
+
+	wled->last_sc_event_time = ktime_get();
+
+	msleep(QCOM_WLED_SC_DLY_MS);
+	rc = qcom_wled_module_enable(wled, true);
+	if (rc < 0)
+		pr_err("wled enable failed rc:%d\n", rc);
+
+unlock_mutex:
+	mutex_unlock(&wled->lock);
+
+	return IRQ_HANDLED;
+}
+
 static int qcom_wled_setup(struct qcom_wled *wled)
 {
 	int rc, temp, i;
 	u8 sink_en = 0;
 	u8 string_cfg = wled->cfg.string_cfg;
+	int sc_irq = wled->cfg.sc_irq;
 
 	rc = regmap_update_bits(wled->regmap,
 			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
@@ -261,6 +334,39 @@ static int qcom_wled_setup(struct qcom_wled *wled)
 		return rc;
 	}
 
+	if (sc_irq >= 0) {
+		rc = devm_request_threaded_irq(&wled->pdev->dev, sc_irq,
+				NULL, qcom_wled_sc_irq_handler, IRQF_ONESHOT,
+				"qcom_wled_sc_irq", wled);
+		if (rc < 0) {
+			pr_err("Unable to request sc(%d) IRQ(err:%d)\n",
+				sc_irq, rc);
+			return rc;
+		}
+
+		rc = regmap_update_bits(wled->regmap,
+				wled->ctrl_addr + QCOM_WLED_CTRL_SHORT_PROTECT,
+				QCOM_WLED_CTRL_SHORT_EN_MASK,
+				QCOM_WLED_CTRL_SHORT_EN_MASK);
+		if (rc < 0)
+			return rc;
+
+		if (wled->cfg.ext_pfet_sc_pro_en) {
+			/* unlock the secure access regisetr */
+			rc = regmap_write(wled->regmap, wled->ctrl_addr +
+					QCOM_WLED_CTRL_SEC_ACCESS,
+					QCOM_WLED_CTRL_SEC_UNLOCK);
+			if (rc < 0)
+				return rc;
+
+			rc = regmap_write(wled->regmap,
+					wled->ctrl_addr + QCOM_WLED_CTRL_TEST1,
+					QCOM_WLED_EXT_FET_DTEST2);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
 	return 0;
 }
 
@@ -271,6 +377,7 @@ static int qcom_wled_setup(struct qcom_wled *wled)
 	.switch_freq = 11,
 	.string_cfg = 0xf,
 	.en_cabc = 0,
+	.ext_pfet_sc_pro_en = 1,
 };
 
 struct qcom_wled_var_cfg {
@@ -376,6 +483,7 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
 		bool *val_ptr;
 	} bool_opts[] = {
 		{ "qcom,en-cabc", &cfg->en_cabc, },
+		{ "qcom,ext-pfet-sc-pro", &cfg->ext_pfet_sc_pro_en, },
 	};
 
 	prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
@@ -427,6 +535,10 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
 			*bool_opts[i].val_ptr = true;
 	}
 
+	wled->cfg.sc_irq = platform_get_irq_byname(wled->pdev, "sc-irq");
+	if (wled->cfg.sc_irq < 0)
+		dev_dbg(&wled->pdev->dev, "sc irq is not used\n");
+
 	return 0;
 }
 
@@ -469,6 +581,8 @@ static int qcom_wled_probe(struct platform_device *pdev)
 		return rc;
 	}
 
+	mutex_init(&wled->lock);
+
 	val = QCOM_WLED_DEFAULT_BRIGHTNESS;
 	of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
 	wled->brightness = val;
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling
  2017-11-16 12:18 [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver Kiran Gunda
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
  2017-11-16 12:18 ` [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Kiran Gunda
@ 2017-11-16 12:18 ` Kiran Gunda
  2017-12-05  4:45   ` Bjorn Andersson
  2017-11-16 12:18 ` [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support Kiran Gunda
  3 siblings, 1 reply; 28+ messages in thread
From: Kiran Gunda @ 2017-11-16 12:18 UTC (permalink / raw)
  To: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Rob Herring, Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds,
	devicetree, linux-kernel, linux-fbdev
  Cc: linux-arm-msm-owner, Kiran Gunda

WLED peripheral has over voltage protection(OVP) circuitry and the OVP
fault is notified through an interrupt. Though this fault condition rising
is due to an incorrect hardware configuration is mitigated in the hardware,
it still needs to be detected and handled. Add support for it.

When WLED module is enabled, keep OVP fault interrupt disabled for 10 ms to
account for soft start delay.

Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
---
 .../bindings/leds/backlight/qcom-spmi-wled.txt     |  7 +-
 drivers/video/backlight/qcom-spmi-wled.c           | 83 ++++++++++++++++++++++
 2 files changed, 87 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
index 768608c..d39ee93 100644
--- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
+++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
@@ -92,7 +92,7 @@ The PMIC is connected to the host processor via SPMI bus.
 	Usage:      optional
 	Value type: <string>
 	Definition: Interrupt names associated with the interrupts.
-		    Must be "sc-irq".
+		    Currently supported interrupts are "sc-irq" and "ovp-irq".
 
 Example:
 
@@ -102,8 +102,9 @@ qcom-wled@d800 {
 	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
 	label = "backlight";
 
-	interrupts = <0x3 0xd8 0x2 IRQ_TYPE_EDGE_RISING>;
-	interrupt-names = "sc-irq";
+	interrupts = <0x3 0xd8 0x2 IRQ_TYPE_EDGE_RISING>,
+			<0x3 0xd8 0x1 IRQ_TYPE_EDGE_RISING>;
+	interrupt-names = "sc-irq", "ovp-irq";
 	qcom,fs-current-limit = <25000>;
 	qcom,current-boost-limit = <970>;
 	qcom,switching-freq = <800>;
diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
index 7dbaaa7..8b2a77a 100644
--- a/drivers/video/backlight/qcom-spmi-wled.c
+++ b/drivers/video/backlight/qcom-spmi-wled.c
@@ -29,9 +29,15 @@
 #define QCOM_WLED_SC_DLY_MS			20
 #define QCOM_WLED_SC_CNT_MAX			5
 #define QCOM_WLED_SC_RESET_CNT_DLY_US		1000000
+#define QCOM_WLED_SOFT_START_DLY_US		10000
 
 /* WLED control registers */
 #define QCOM_WLED_CTRL_FAULT_STATUS		0x08
+#define  QCOM_WLED_CTRL_ILIM_FAULT_BIT		BIT(0)
+#define  QCOM_WLED_CTRL_OVP_FAULT_BIT		BIT(1)
+#define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
+
+#define QCOM_WLED_CTRL_INT_RT_STS		0x10
 
 #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
 #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
@@ -90,6 +96,7 @@ struct qcom_wled_config {
 	u32 fs_current;
 	u32 string_cfg;
 	int sc_irq;
+	int ovp_irq;
 	bool en_cabc;
 	bool ext_pfet_sc_pro_en;
 };
@@ -106,6 +113,7 @@ struct qcom_wled {
 	u32 brightness;
 	u32 sc_count;
 	bool prev_state;
+	bool ovp_irq_disabled;
 };
 
 static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
@@ -115,6 +123,28 @@ static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
 	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
 			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
 			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
+	if (rc < 0)
+		return rc;
+	/*
+	 * Wait for at least 10ms before enabling OVP fault interrupt after
+	 * enabling the module so that soft start is completed. Keep the OVP
+	 * interrupt disabled when the module is disabled.
+	 */
+	if (val) {
+		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
+				QCOM_WLED_SOFT_START_DLY_US + 1000);
+
+		if (wled->cfg.ovp_irq > 0 && wled->ovp_irq_disabled) {
+			enable_irq(wled->cfg.ovp_irq);
+			wled->ovp_irq_disabled = false;
+		}
+	} else {
+		if (wled->cfg.ovp_irq > 0 && !wled->ovp_irq_disabled) {
+			disable_irq(wled->cfg.ovp_irq);
+			wled->ovp_irq_disabled = true;
+		}
+	}
+
 	return rc;
 }
 
@@ -264,12 +294,42 @@ static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
 	return IRQ_HANDLED;
 }
 
+static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
+{
+	struct qcom_wled *wled = _wled;
+	int rc;
+	u32 int_sts, fault_sts;
+
+	rc = regmap_read(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
+	if (rc < 0) {
+		pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	rc = regmap_read(wled->regmap, wled->ctrl_addr +
+			QCOM_WLED_CTRL_FAULT_STATUS, &fault_sts);
+	if (rc < 0) {
+		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	if (fault_sts &
+		(QCOM_WLED_CTRL_OVP_FAULT_BIT | QCOM_WLED_CTRL_ILIM_FAULT_BIT))
+		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
+			int_sts, fault_sts);
+
+	return IRQ_HANDLED;
+}
+
 static int qcom_wled_setup(struct qcom_wled *wled)
 {
 	int rc, temp, i;
 	u8 sink_en = 0;
+	u32 val;
 	u8 string_cfg = wled->cfg.string_cfg;
 	int sc_irq = wled->cfg.sc_irq;
+	int ovp_irq = wled->cfg.ovp_irq;
 
 	rc = regmap_update_bits(wled->regmap,
 			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
@@ -367,6 +427,25 @@ static int qcom_wled_setup(struct qcom_wled *wled)
 		}
 	}
 
+	if (ovp_irq >= 0) {
+		rc = devm_request_threaded_irq(&wled->pdev->dev, ovp_irq,
+				NULL, qcom_wled_ovp_irq_handler, IRQF_ONESHOT,
+				"qcom_wled_ovp_irq", wled);
+		if (rc < 0) {
+			dev_err(&wled->pdev->dev, "Unable to request ovp(%d) IRQ(err:%d)\n",
+				ovp_irq, rc);
+			return rc;
+		}
+
+		rc = regmap_read(wled->regmap, wled->ctrl_addr +
+				QCOM_WLED_CTRL_MOD_ENABLE, &val);
+		/* disable the OVP irq only if the module is not enabled */
+		if (!rc && !(val & QCOM_WLED_CTRL_MOD_EN_MASK)) {
+			disable_irq(ovp_irq);
+			wled->ovp_irq_disabled = true;
+		}
+	}
+
 	return 0;
 }
 
@@ -539,6 +618,10 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
 	if (wled->cfg.sc_irq < 0)
 		dev_dbg(&wled->pdev->dev, "sc irq is not used\n");
 
+	wled->cfg.ovp_irq = platform_get_irq_byname(wled->pdev, "ovp-irq");
+	if (wled->cfg.ovp_irq < 0)
+		dev_dbg(&wled->pdev->dev, "ovp irq is not used\n");
+
 	return 0;
 }
 
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2017-11-16 12:18 [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver Kiran Gunda
                   ` (2 preceding siblings ...)
  2017-11-16 12:18 ` [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling Kiran Gunda
@ 2017-11-16 12:18 ` Kiran Gunda
  2017-12-05  5:40   ` Bjorn Andersson
  3 siblings, 1 reply; 28+ messages in thread
From: Kiran Gunda @ 2017-11-16 12:18 UTC (permalink / raw)
  To: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Rob Herring, Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds,
	devicetree, linux-kernel, linux-fbdev
  Cc: linux-arm-msm-owner, Kiran Gunda

The auto-calibration algorithm checks if the current WLED sink
configuration is valid. It tries enabling every sink and checks
if the OVP fault is observed. Based on this information it
detects and enables the valid sink configuration. Auto calibration
will be triggered when the OVP fault interrupts are seen frequently
thereby it tries to fix the sink configuration.

Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
---
 .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
 drivers/video/backlight/qcom-spmi-wled.c           | 304 ++++++++++++++++++++-
 2 files changed, 306 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
index d39ee93..f06c0cd 100644
--- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
+++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
@@ -94,6 +94,11 @@ The PMIC is connected to the host processor via SPMI bus.
 	Definition: Interrupt names associated with the interrupts.
 		    Currently supported interrupts are "sc-irq" and "ovp-irq".
 
+- qcom,auto-calibration
+	Usage:      optional
+	Value type: <bool>
+	Definition: Enables auto-calibration of the WLED sink configuration.
+
 Example:
 
 qcom-wled@d800 {
diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
index 8b2a77a..aee5c56 100644
--- a/drivers/video/backlight/qcom-spmi-wled.c
+++ b/drivers/video/backlight/qcom-spmi-wled.c
@@ -38,11 +38,14 @@
 #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
 
 #define QCOM_WLED_CTRL_INT_RT_STS		0x10
+#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
 
 #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
 #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
 #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
 
+#define QCOM_WLED_CTRL_FDBK_OP			0x48
+
 #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
 #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
 
@@ -99,6 +102,7 @@ struct qcom_wled_config {
 	int ovp_irq;
 	bool en_cabc;
 	bool ext_pfet_sc_pro_en;
+	bool auto_calib_enabled;
 };
 
 struct qcom_wled {
@@ -108,18 +112,25 @@ struct qcom_wled {
 	struct mutex lock;
 	struct qcom_wled_config cfg;
 	ktime_t last_sc_event_time;
+	ktime_t start_ovp_fault_time;
 	u16 sink_addr;
 	u16 ctrl_addr;
+	u16 auto_calibration_ovp_count;
 	u32 brightness;
 	u32 sc_count;
 	bool prev_state;
 	bool ovp_irq_disabled;
+	bool auto_calib_done;
+	bool force_mod_disable;
 };
 
 static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
 {
 	int rc;
 
+	if (wled->force_mod_disable)
+		return 0;
+
 	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
 			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
 			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
@@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 brightness)
 	v[1] = (brightness >> 8) & 0xf;
 
 	for (i = 0; (string_cfg >> i) != 0; i++) {
-		if (string_cfg & BIT(i)) {
 			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
 					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
 			if (rc < 0)
 				return rc;
-		}
 	}
 
 	return 0;
@@ -294,6 +303,262 @@ static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
 	return IRQ_HANDLED;
 }
 
+#define AUTO_CALIB_BRIGHTNESS		200
+static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
+{
+	int rc = 0, i;
+	u32 sink_config = 0, int_sts;
+	u8 reg = 0, sink_test = 0, sink_valid = 0;
+	u8 string_cfg = wled->cfg.string_cfg;
+
+	/* read configured sink configuration */
+	rc = regmap_read(wled->regmap, wled->sink_addr +
+			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
+	if (rc < 0) {
+		pr_err("Failed to read SINK configuration rc=%d\n", rc);
+		goto failed_calib;
+	}
+
+	/* disable the module before starting calibration */
+	rc = regmap_update_bits(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
+			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
+	if (rc < 0) {
+		pr_err("Failed to disable WLED module rc=%d\n",	rc);
+		goto failed_calib;
+	}
+
+	/* set low brightness across all sinks */
+	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
+	if (rc < 0) {
+		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
+		goto failed_calib;
+	}
+
+	if (wled->cfg.en_cabc) {
+		for (i = 0; (string_cfg >> i) != 0; i++) {
+			reg = 0;
+			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
+					QCOM_WLED_SINK_CABC_REG(i),
+					QCOM_WLED_SINK_CABC_MASK, reg);
+			if (rc < 0)
+				goto failed_calib;
+		}
+	}
+
+	/* disable all sinks */
+	rc = regmap_write(wled->regmap,
+			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
+	if (rc < 0) {
+		pr_err("Failed to disable all sinks rc=%d\n", rc);
+		goto failed_calib;
+	}
+
+	/* iterate through the strings one by one */
+	for (i = 0; (string_cfg >> i) != 0; i++) {
+		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
+
+		/* Enable feedback control */
+		rc = regmap_write(wled->regmap, wled->ctrl_addr +
+				QCOM_WLED_CTRL_FDBK_OP, i + 1);
+		if (rc < 0) {
+			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
+				i + 1, rc);
+			goto failed_calib;
+		}
+
+		/* enable the sink */
+		rc = regmap_write(wled->regmap, wled->sink_addr +
+				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
+		if (rc < 0) {
+			pr_err("Failed to configure SINK %d rc=%d\n",
+						i + 1, rc);
+			goto failed_calib;
+		}
+
+		/* Enable the module */
+		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+				QCOM_WLED_CTRL_MOD_ENABLE,
+				QCOM_WLED_CTRL_MOD_EN_MASK,
+				QCOM_WLED_CTRL_MOD_EN_MASK);
+		if (rc < 0) {
+			pr_err("Failed to enable WLED module rc=%d\n", rc);
+			goto failed_calib;
+		}
+
+		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
+				QCOM_WLED_SOFT_START_DLY_US + 1000);
+
+		rc = regmap_read(wled->regmap, wled->ctrl_addr +
+				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
+		if (rc < 0) {
+			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
+			goto failed_calib;
+		}
+
+		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
+			pr_debug("WLED OVP fault detected with SINK %d\n",
+						i + 1);
+		else
+			sink_valid |= sink_test;
+
+		/* Disable the module */
+		rc = regmap_update_bits(wled->regmap,
+				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
+				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
+		if (rc < 0) {
+			pr_err("Failed to disable WLED module rc=%d\n", rc);
+			goto failed_calib;
+		}
+	}
+
+	if (sink_valid == sink_config) {
+		pr_debug("WLED auto-calibration complete, default sink-config=%x OK!\n",
+						sink_config);
+	} else {
+		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
+						sink_config, sink_valid);
+		sink_config = sink_valid;
+	}
+
+	if (!sink_config) {
+		pr_warn("No valid WLED sinks found\n");
+		wled->force_mod_disable = true;
+		goto failed_calib;
+	}
+
+	/* write the new sink configuration */
+	rc = regmap_write(wled->regmap,
+			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
+			sink_config);
+	if (rc < 0) {
+		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
+		goto failed_calib;
+	}
+
+	/* MODULATOR_EN setting for valid sinks */
+	for (i = 0; (string_cfg >> i) != 0; i++) {
+		if (wled->cfg.en_cabc) {
+			reg = QCOM_WLED_SINK_CABC_EN;
+			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
+					QCOM_WLED_SINK_CABC_REG(i),
+					QCOM_WLED_SINK_CABC_MASK, reg);
+			if (rc < 0)
+				goto failed_calib;
+		}
+
+		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
+			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
+		else
+			reg = 0x0; /* disable modulator_en for unused sink */
+
+		rc = regmap_write(wled->regmap, wled->sink_addr +
+				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
+		if (rc < 0) {
+			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
+			goto failed_calib;
+		}
+	}
+
+	/* restore the feedback setting */
+	rc = regmap_write(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
+	if (rc < 0) {
+		pr_err("Failed to restore feedback setting rc=%d\n", rc);
+		goto failed_calib;
+	}
+
+	/* restore  brightness */
+	rc = qcom_wled_set_brightness(wled, wled->brightness);
+	if (rc < 0) {
+		pr_err("Failed to set brightness after calibration rc=%d\n",
+			rc);
+		goto failed_calib;
+	}
+
+	rc = regmap_update_bits(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
+			QCOM_WLED_CTRL_MOD_EN_MASK,
+			QCOM_WLED_CTRL_MOD_EN_MASK);
+	if (rc < 0) {
+		pr_err("Failed to enable WLED module rc=%d\n", rc);
+		goto failed_calib;
+	}
+
+	/* delay for WLED soft-start */
+	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
+			QCOM_WLED_SOFT_START_DLY_US + 1000);
+
+failed_calib:
+	return rc;
+}
+
+#define WLED_AUTO_CAL_OVP_COUNT		5
+#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
+static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
+{
+	s64 elapsed_time_us;
+
+	/*
+	 * Check if the OVP fault was an occasional one
+	 * or if its firing continuously, the latter qualifies
+	 * for an auto-calibration check.
+	 */
+	if (!wled->auto_calibration_ovp_count) {
+		wled->start_ovp_fault_time = ktime_get();
+		wled->auto_calibration_ovp_count++;
+	} else {
+		elapsed_time_us = ktime_us_delta(ktime_get(),
+				wled->start_ovp_fault_time);
+		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
+			wled->auto_calibration_ovp_count = 0;
+		else
+			wled->auto_calibration_ovp_count++;
+
+		if (wled->auto_calibration_ovp_count >=
+				WLED_AUTO_CAL_OVP_COUNT) {
+			wled->auto_calibration_ovp_count = 0;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
+{
+	int rc;
+	u32 fault_status = 0, rt_status = 0;
+
+	if (!wled->cfg.auto_calib_enabled)
+		return 0;
+
+	rc = regmap_read(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
+			&rt_status);
+	if (rc < 0)
+		pr_err("Failed to read RT status rc=%d\n", rc);
+
+	rc = regmap_read(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
+			&fault_status);
+	if (rc < 0)
+		pr_err("Failed to read fault status rc=%d\n", rc);
+
+	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
+			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
+		mutex_lock(&wled->lock);
+		rc = qcom_wled_auto_calibrate(wled);
+		if (rc < 0)
+			pr_err("Failed auto-calibration rc=%d\n", rc);
+		else
+			wled->auto_calib_done = true;
+		mutex_unlock(&wled->lock);
+	}
+
+	return rc;
+}
+
 static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
 {
 	struct qcom_wled *wled = _wled;
@@ -319,6 +584,33 @@ static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
 		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
 			int_sts, fault_sts);
 
+	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
+		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
+			if (qcom_wled_auto_cal_required(wled)) {
+				mutex_lock(&wled->lock);
+				if (wled->cfg.ovp_irq > 0 &&
+						!wled->ovp_irq_disabled) {
+					disable_irq_nosync(wled->cfg.ovp_irq);
+					wled->ovp_irq_disabled = true;
+				}
+
+				rc = qcom_wled_auto_calibrate(wled);
+				if (rc < 0)
+					pr_err("Failed auto-calibration rc=%d\n",
+						rc);
+				else
+					wled->auto_calib_done = true;
+
+				if (wled->cfg.ovp_irq > 0 &&
+						wled->ovp_irq_disabled) {
+					enable_irq(wled->cfg.ovp_irq);
+					wled->ovp_irq_disabled = false;
+				}
+				mutex_unlock(&wled->lock);
+			}
+		}
+	}
+
 	return IRQ_HANDLED;
 }
 
@@ -394,6 +686,10 @@ static int qcom_wled_setup(struct qcom_wled *wled)
 		return rc;
 	}
 
+	rc = qcom_wled_auto_calibrate_at_init(wled);
+	if (rc < 0)
+		pr_err("Failed to auto-calibrate at init rc=%d\n", rc);
+
 	if (sc_irq >= 0) {
 		rc = devm_request_threaded_irq(&wled->pdev->dev, sc_irq,
 				NULL, qcom_wled_sc_irq_handler, IRQF_ONESHOT,
@@ -432,7 +728,7 @@ static int qcom_wled_setup(struct qcom_wled *wled)
 				NULL, qcom_wled_ovp_irq_handler, IRQF_ONESHOT,
 				"qcom_wled_ovp_irq", wled);
 		if (rc < 0) {
-			dev_err(&wled->pdev->dev, "Unable to request ovp(%d) IRQ(err:%d)\n",
+			pr_err("Unable to request ovp(%d) IRQ(err:%d)\n",
 				ovp_irq, rc);
 			return rc;
 		}
@@ -457,6 +753,7 @@ static int qcom_wled_setup(struct qcom_wled *wled)
 	.string_cfg = 0xf,
 	.en_cabc = 0,
 	.ext_pfet_sc_pro_en = 1,
+	.auto_calib_enabled = 1,
 };
 
 struct qcom_wled_var_cfg {
@@ -563,6 +860,7 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
 	} bool_opts[] = {
 		{ "qcom,en-cabc", &cfg->en_cabc, },
 		{ "qcom,ext-pfet-sc-pro", &cfg->ext_pfet_sc_pro_en, },
+		{ "qcom,auto-calibration", &cfg->auto_calib_enabled, },
 	};
 
 	prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
@ 2017-11-16 16:55   ` Bjorn Andersson
  2017-11-17  6:36     ` kgunda
  2017-11-17 20:28   ` Rob Herring
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2017-11-16 16:55 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:

> WLED driver provides the interface to the display driver to
> adjust the brightness of the display backlight.
> 

Hi Kiran,

This driver has a lot in common with the already upstream pm8941-wled.c,
because it's just a new revision of the same block.

Please extend the existing driver rather than providing a new one
(and yes, renaming the file is okay).

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-16 16:55   ` Bjorn Andersson
@ 2017-11-17  6:36     ` kgunda
  2017-11-17  6:56       ` Bjorn Andersson
  0 siblings, 1 reply; 28+ messages in thread
From: kgunda @ 2017-11-17  6:36 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-11-16 22:25, Bjorn Andersson wrote:
> On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> 
>> WLED driver provides the interface to the display driver to
>> adjust the brightness of the display backlight.
>> 
> 
> Hi Kiran,
> 
> This driver has a lot in common with the already upstream 
> pm8941-wled.c,
> because it's just a new revision of the same block.
> 
> Please extend the existing driver rather than providing a new one
> (and yes, renaming the file is okay).
> 
> Regards,
> Bjorn

Hi Bjorn,

Yes this driver design is similar to pm8941, however the WLED HW block 
has undergone quite a few changes in
analog and digital from PM8941 to PM8998. Few of them include splitting 
one module into wled-ctrl
and wled-sink peripherals, changes in the register offsets and the bit 
interpretation. Hence we
concluded that it was better to have a new driver to support this new 
gen WELD module and decouple
it from the pm8941. Also, going forward this driver will support AMOLED 
AVDD rail (not supported by pm8941)
touching a few more registers/configuration and newer PMICs. So spinning 
off a new driver would make it
cleaner and easier to extend further.

Thanks,
Kiran

> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-17  6:36     ` kgunda
@ 2017-11-17  6:56       ` Bjorn Andersson
  2017-11-17  8:33         ` Lee Jones
  2017-11-17  9:52         ` kgunda
  0 siblings, 2 replies; 28+ messages in thread
From: Bjorn Andersson @ 2017-11-17  6:56 UTC (permalink / raw)
  To: kgunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 16 Nov 22:36 PST 2017, kgunda@codeaurora.org wrote:

> On 2017-11-16 22:25, Bjorn Andersson wrote:
> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> > 
> > > WLED driver provides the interface to the display driver to
> > > adjust the brightness of the display backlight.
> > > 
> > 
> > Hi Kiran,
> > 
> > This driver has a lot in common with the already upstream pm8941-wled.c,
> > because it's just a new revision of the same block.
> > 
> > Please extend the existing driver rather than providing a new one
> > (and yes, renaming the file is okay).
> > 
> > Regards,
> > Bjorn
> 
> Hi Bjorn,
> 
> Yes this driver design is similar to pm8941, however the WLED HW block
> has undergone quite a few changes in analog and digital from PM8941 to
> PM8998.

I can see that, looking at the documentation.

> Few of them include splitting one module into wled-ctrl and wled-sink
> peripherals, changes in the register offsets and the bit
> interpretation.

This is typical and something we need to handle in all these drivers, to
avoid having one driver per platform.

> Hence we concluded that it was better to have a new driver to support
> this new gen WELD module and decouple it from the pm8941.

Okay, I can see how it's easier to not have to case about anything but
pmi8998 in this driver, but where do you add the support for other WLED
versions? What about PMI8994? Will there not be similar differences
(registers that has moved around) in the future?

> Also, going forward this driver will support AMOLED AVDD rail (not
> supported by pm8941) touching a few more registers/configuration and
> newer PMICs.

Is this a feature that was introduced in PMI8998? Will this support not
be dependent on the pmic version?

> So spinning off a new driver would make it cleaner and easier to
> extend further.
> 

It's for sure easier at this point in time, but your argumentation
implies that PMI8998+1 should go into it's own driver as well.

I suspect that if you're going to reuse this driver for future PMIC
versions you will have to deal with register layout differences and new
feature set, and as such I'm not convinced that a new driver is needed.


Can you give any concrete examples of where it is not possible or
undesirable to maintain the pm8941 support in the same driver?

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-17  6:56       ` Bjorn Andersson
@ 2017-11-17  8:33         ` Lee Jones
  2017-11-17 11:01           ` kgunda
  2017-11-17  9:52         ` kgunda
  1 sibling, 1 reply; 28+ messages in thread
From: Lee Jones @ 2017-11-17  8:33 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: kgunda, linux-arm-msm, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu, 16 Nov 2017, Bjorn Andersson wrote:

> On Thu 16 Nov 22:36 PST 2017, kgunda@codeaurora.org wrote:
> 
> > On 2017-11-16 22:25, Bjorn Andersson wrote:
> > > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> > > 
> > > > WLED driver provides the interface to the display driver to
> > > > adjust the brightness of the display backlight.
> > > > 
> > > 
> > > Hi Kiran,
> > > 
> > > This driver has a lot in common with the already upstream pm8941-wled.c,
> > > because it's just a new revision of the same block.
> > > 
> > > Please extend the existing driver rather than providing a new one
> > > (and yes, renaming the file is okay).
> > > 
> > > Regards,
> > > Bjorn
> > 
> > Hi Bjorn,
> > 
> > Yes this driver design is similar to pm8941, however the WLED HW block
> > has undergone quite a few changes in analog and digital from PM8941 to
> > PM8998.
> 
> I can see that, looking at the documentation.
> 
> > Few of them include splitting one module into wled-ctrl and wled-sink
> > peripherals, changes in the register offsets and the bit
> > interpretation.
> 
> This is typical and something we need to handle in all these drivers, to
> avoid having one driver per platform.
> 
> > Hence we concluded that it was better to have a new driver to support
> > this new gen WELD module and decouple it from the pm8941.
> 
> Okay, I can see how it's easier to not have to case about anything but
> pmi8998 in this driver, but where do you add the support for other WLED
> versions? What about PMI8994? Will there not be similar differences
> (registers that has moved around) in the future?
> 
> > Also, going forward this driver will support AMOLED AVDD rail (not
> > supported by pm8941) touching a few more registers/configuration and
> > newer PMICs.
> 
> Is this a feature that was introduced in PMI8998? Will this support not
> be dependent on the pmic version?
> 
> > So spinning off a new driver would make it cleaner and easier to
> > extend further.
> > 
> 
> It's for sure easier at this point in time, but your argumentation
> implies that PMI8998+1 should go into it's own driver as well.
> 
> I suspect that if you're going to reuse this driver for future PMIC
> versions you will have to deal with register layout differences and new
> feature set, and as such I'm not convinced that a new driver is needed.
> 
> Can you give any concrete examples of where it is not possible or
> undesirable to maintain the pm8941 support in the same driver?

I agree with Bjorn.  If you can support multiple devices in a single
driver with a couple of simple ddata struct differences and a slightly
different regmap, you should.

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-17  6:56       ` Bjorn Andersson
  2017-11-17  8:33         ` Lee Jones
@ 2017-11-17  9:52         ` kgunda
  1 sibling, 0 replies; 28+ messages in thread
From: kgunda @ 2017-11-17  9:52 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-11-17 12:26, Bjorn Andersson wrote:
> On Thu 16 Nov 22:36 PST 2017, kgunda@codeaurora.org wrote:
> 
>> On 2017-11-16 22:25, Bjorn Andersson wrote:
>> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
>> >
>> > > WLED driver provides the interface to the display driver to
>> > > adjust the brightness of the display backlight.
>> > >
>> >
>> > Hi Kiran,
>> >
>> > This driver has a lot in common with the already upstream pm8941-wled.c,
>> > because it's just a new revision of the same block.
>> >
>> > Please extend the existing driver rather than providing a new one
>> > (and yes, renaming the file is okay).
>> >
>> > Regards,
>> > Bjorn
>> 
>> Hi Bjorn,
>> 
>> Yes this driver design is similar to pm8941, however the WLED HW block
>> has undergone quite a few changes in analog and digital from PM8941 to
>> PM8998.
> 
> I can see that, looking at the documentation.
> 
>> Few of them include splitting one module into wled-ctrl and wled-sink
>> peripherals, changes in the register offsets and the bit
>> interpretation.
> 
> This is typical and something we need to handle in all these drivers, 
> to
> avoid having one driver per platform.
> 
>> Hence we concluded that it was better to have a new driver to support
>> this new gen WELD module and decouple it from the pm8941.
> 
> Okay, I can see how it's easier to not have to case about anything but
> pmi8998 in this driver, but where do you add the support for other WLED
> versions? What about PMI8994? Will there not be similar differences
> (registers that has moved around) in the future?
> 
>> Also, going forward this driver will support AMOLED AVDD rail (not
>> supported by pm8941) touching a few more registers/configuration and
>> newer PMICs.
> 
> Is this a feature that was introduced in PMI8998? Will this support not
> be dependent on the pmic version?
> 
>> So spinning off a new driver would make it cleaner and easier to
>> extend further.
>> 
> 
> It's for sure easier at this point in time, but your argumentation
> implies that PMI8998+1 should go into it's own driver as well.
> 
> I suspect that if you're going to reuse this driver for future PMIC
> versions you will have to deal with register layout differences and new
> feature set, and as such I'm not convinced that a new driver is needed.
> 
> 
> Can you give any concrete examples of where it is not possible or
> undesirable to maintain the pm8941 support in the same driver?
> 
> Regards,
> Bjorn

Hi Bjorn,

Thanks for the inputs! Following are the reasons to go for the new 
driver
and this driver can support 5 PMICs.

1.Majority of  register, offsets and config values don’t match  up  
between
  PMI8998 and PM8941
2.Feature such as – SC protection handling in SW cannot be done for 8941 
as
there is no SC event/irq, AMOELD AVDD cannot supported by PM8941
3.Feature such as – string auto-calibration even if common will have to 
use
different offsets/registers in the same SW logic
4.PMI8998, PMI8994, PMI8950 and PM660 all of them have this same WLED 
module
(and register map) with very minor changes unlike 8941.

Thanks,
Kiran


> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-17  8:33         ` Lee Jones
@ 2017-11-17 11:01           ` kgunda
  0 siblings, 0 replies; 28+ messages in thread
From: kgunda @ 2017-11-17 11:01 UTC (permalink / raw)
  To: Lee Jones
  Cc: Bjorn Andersson, linux-arm-msm, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-11-17 14:03, Lee Jones wrote:
> On Thu, 16 Nov 2017, Bjorn Andersson wrote:
> 
>> On Thu 16 Nov 22:36 PST 2017, kgunda@codeaurora.org wrote:
>> 
>> > On 2017-11-16 22:25, Bjorn Andersson wrote:
>> > > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
>> > >
>> > > > WLED driver provides the interface to the display driver to
>> > > > adjust the brightness of the display backlight.
>> > > >
>> > >
>> > > Hi Kiran,
>> > >
>> > > This driver has a lot in common with the already upstream pm8941-wled.c,
>> > > because it's just a new revision of the same block.
>> > >
>> > > Please extend the existing driver rather than providing a new one
>> > > (and yes, renaming the file is okay).
>> > >
>> > > Regards,
>> > > Bjorn
>> >
>> > Hi Bjorn,
>> >
>> > Yes this driver design is similar to pm8941, however the WLED HW block
>> > has undergone quite a few changes in analog and digital from PM8941 to
>> > PM8998.
>> 
>> I can see that, looking at the documentation.
>> 
>> > Few of them include splitting one module into wled-ctrl and wled-sink
>> > peripherals, changes in the register offsets and the bit
>> > interpretation.
>> 
>> This is typical and something we need to handle in all these drivers, 
>> to
>> avoid having one driver per platform.
>> 
>> > Hence we concluded that it was better to have a new driver to support
>> > this new gen WELD module and decouple it from the pm8941.
>> 
>> Okay, I can see how it's easier to not have to case about anything but
>> pmi8998 in this driver, but where do you add the support for other 
>> WLED
>> versions? What about PMI8994? Will there not be similar differences
>> (registers that has moved around) in the future?
>> 
>> > Also, going forward this driver will support AMOLED AVDD rail (not
>> > supported by pm8941) touching a few more registers/configuration and
>> > newer PMICs.
>> 
>> Is this a feature that was introduced in PMI8998? Will this support 
>> not
>> be dependent on the pmic version?
>> 
>> > So spinning off a new driver would make it cleaner and easier to
>> > extend further.
>> >
>> 
>> It's for sure easier at this point in time, but your argumentation
>> implies that PMI8998+1 should go into it's own driver as well.
>> 
>> I suspect that if you're going to reuse this driver for future PMIC
>> versions you will have to deal with register layout differences and 
>> new
>> feature set, and as such I'm not convinced that a new driver is 
>> needed.
>> 
>> Can you give any concrete examples of where it is not possible or
>> undesirable to maintain the pm8941 support in the same driver?
> 
> I agree with Bjorn.  If you can support multiple devices in a single
> driver with a couple of simple ddata struct differences and a slightly
> different regmap, you should.

Hi Lee,
Thanks for the feedback! The regmap difference is not confined to couple 
of registers.
Except the one register all the registers have some difference in offset 
or bitmap or
config values as compared to the pm8941. Below is the table for the 
reference. The below
table covers only the registers those exist in the pm8941 driver, if we 
keep adding the
other features the changes may be huge. Apart from this I have mentioned 
other feature
differences between pm8941 and pm8998 as well in response to Bjorn. 
Please refer that
as well.

Register	             Compatibility between 8998 Vs 8941
========                     
===================================================
WLED_MODULE_ENABLE	       Register address offset is same and config 
values match
WLED1_ILED_SYNC_BIT	       Register address offset and config values not 
matching.
WLED1_SWITCHING_FREQUENCY      Register address offset and config values 
are matching.
                                But there is an extra override bit in 
pm8998.
WLED1_WLED_OVP	               Register address offset same. But config 
values are not matching.
WLED1_WLED_ILIM	               Register address offset is same. But 
config values are not matching
WLED1_EN_CURRENT_SINK	       Both register address offset and config 
values are not matching.
WLED1_LED1_MODULATOR_EN	       Both register address offset and config 
values are not matching.
WLED1_LED1_FULL_SCALE_CURRENT  Both register address offset and config 
values are not matching.
WLED1_LED1_MODULATOR_SRC_SE    Register address offset not matching, but 
the config values are matching
WLED1_LED1_CABC_EN	       Register address offset not matching, but the 
config values are not matching.

Thanks,
Kiran

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
  2017-11-16 16:55   ` Bjorn Andersson
@ 2017-11-17 20:28   ` Rob Herring
  2017-12-05  2:01   ` Bjorn Andersson
  2017-12-15 20:30   ` Pavel Machek
  3 siblings, 0 replies; 28+ messages in thread
From: Rob Herring @ 2017-11-17 20:28 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu, Nov 16, 2017 at 05:48:34PM +0530, Kiran Gunda wrote:
> WLED driver provides the interface to the display driver to
> adjust the brightness of the display backlight.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  90 ++++

Please split bindings to separate patch.

>  drivers/video/backlight/Kconfig                    |   9 +
>  drivers/video/backlight/Makefile                   |   1 +
>  drivers/video/backlight/qcom-spmi-wled.c           | 504 +++++++++++++++++++++
>  4 files changed, 604 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>  create mode 100644 drivers/video/backlight/qcom-spmi-wled.c

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling
  2017-11-16 12:18 ` [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Kiran Gunda
@ 2017-11-17 20:30   ` Rob Herring
  2017-11-20 11:42     ` kgunda
  2017-12-05  4:35   ` Bjorn Andersson
  1 sibling, 1 reply; 28+ messages in thread
From: Rob Herring @ 2017-11-17 20:30 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu, Nov 16, 2017 at 05:48:35PM +0530, Kiran Gunda wrote:
> Handle the short circuit(SC) interrupt and check if the SC interrupt
> is valid. Re-enable the module to check if it goes away. Disable the
> module altogether if the SC event persists.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  22 ++++

... and also put all the binding in one patch. I want to review the full 
view of the h/w, not piecemeal.

>  drivers/video/backlight/qcom-spmi-wled.c           | 126 ++++++++++++++++++++-
>  2 files changed, 142 insertions(+), 6 deletions(-)

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling
  2017-11-17 20:30   ` Rob Herring
@ 2017-11-20 11:42     ` kgunda
  0 siblings, 0 replies; 28+ messages in thread
From: kgunda @ 2017-11-20 11:42 UTC (permalink / raw)
  To: Rob Herring
  Cc: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Pavel Machek,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-11-18 02:00, Rob Herring wrote:
> On Thu, Nov 16, 2017 at 05:48:35PM +0530, Kiran Gunda wrote:
>> Handle the short circuit(SC) interrupt and check if the SC interrupt
>> is valid. Re-enable the module to check if it goes away. Disable the
>> module altogether if the SC event persists.
>> 
>> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> ---
>>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  22 ++++
> 
> ... and also put all the binding in one patch. I want to review the 
> full
> view of the h/w, not piecemeal.
> 
Sure. Will send the next series with the changes suggested.
>>  drivers/video/backlight/qcom-spmi-wled.c           | 126 
>> ++++++++++++++++++++-
>>  2 files changed, 142 insertions(+), 6 deletions(-)

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
  2017-11-16 16:55   ` Bjorn Andersson
  2017-11-17 20:28   ` Rob Herring
@ 2017-12-05  2:01   ` Bjorn Andersson
  2017-12-11  9:11     ` kgunda
  2017-12-15 20:30   ` Pavel Machek
  3 siblings, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2017-12-05  2:01 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:

> WLED driver provides the interface to the display driver to
> adjust the brightness of the display backlight.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  90 ++++
>  drivers/video/backlight/Kconfig                    |   9 +
>  drivers/video/backlight/Makefile                   |   1 +
>  drivers/video/backlight/qcom-spmi-wled.c           | 504 +++++++++++++++++++++
>  4 files changed, 604 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>  create mode 100644 drivers/video/backlight/qcom-spmi-wled.c
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> new file mode 100644
> index 0000000..f1ea25b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> @@ -0,0 +1,90 @@
> +Binding for Qualcomm WLED driver
> +

This binding document quite well describe the pm8941 as well, so please
improve the existing binding (changing to this style is preferable).

> +WLED (White Light Emitting Diode) driver is used for controlling display
> +backlight that is part of PMIC on Qualcomm Technologies reference platforms.
> +The PMIC is connected to the host processor via SPMI bus.
> +
> +- compatible
> +	Usage:      required
> +	Value type: <string>
> +	Definition: should be "qcom,pm8998-spmi-wled".

There's no WLED in the pm8998, so please make this pmi8998. This pmic is
SPMI only, so there's no need to keep "spmi" in the compatible.

> +
> +- reg
> +	Usage:      required
> +	Value type: <prop-encoded-array>
> +	Definition:  Base address and size of the WLED modules.
> +
> +- reg-names
> +	Usage:      required
> +	Value type: <string>
> +	Definition:  Names associated with base addresses. should be
> +		     "qcom-wled-ctrl-base", "qcom-wled-sink-base".
> +
> +- label
> +	Usage:      required
> +	Value type: <string>
> +	Definition: The name of the backlight device.
> +
> +- default-brightness
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: brightness value on boot, value from: 0-4095
> +		    default: 2048
> +
> +- qcom,fs-current-limit
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: per-string full scale current limit in uA. value from
> +		    0 to 30000 with 5000 uA resolution. default: 25000 uA

"in steps of 5mA"

> +
> +- qcom,current-boost-limit
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: ILIM threshold in mA. values are 105, 280, 450, 620, 970,
> +		    1150, 1300, 1500. default: 970 mA
> +
> +- qcom,switching-freq
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: Switching frequency in KHz. values are
> +		    600, 640, 685, 738, 800, 872, 960, 1066, 1200, 1371,
> +		    1600, 1920, 2400, 3200, 4800, 9600.
> +		    default: 800 KHz
> +
> +- qcom,ovp
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: Over-voltage protection limit in mV. values are 31100,
> +		    29600, 19600, 18100.
> +	            default: 29600 mV
> +
> +- qcom,string-cfg
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: Bit mask of the wled strings. Bit 0 to 3 indicates strings
> +		    0 to 3 respectively. Wled module has four strings of leds
> +		    numbered from 0 to 3. Each string of leds are operated
> +		    individually. Specify the strings using the bit mask. Any
> +		    combination of led strings can be used.
> +		    default value is 15 (b1111).

Please try to avoid expressing things as bitmasks in DT.

The only difference from 8941 here is that there's one additional
string, so please start off by expressing this as the existing binding.

If you really need this flexibility you can follow up with an addition
of a property like this, but name it something like
"qcom,enabled-strings" and make this support available for pm8941 as
well.

> +
> +- qcom,en-cabc

No need for the "en", the presence of a bool property means that it's
enabled.

> +	Usage:      optional
> +	Value type: <bool>
> +	Definition: Specify if cabc (content adaptive backlight control) is
> +		    needed.

I presume cabc isn't ever "needed", just make the description "Enable
content adaptive backlight control".

> +
> +Example:
> +
> +qcom-wled@d800 {
> +	compatible = "qcom,pm8998-spmi-wled";
> +	reg = <0xd800 0xd900>;
> +	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
> +	label = "backlight";
> +
> +	qcom,fs-current-limit = <25000>;
> +	qcom,current-boost-limit = <970>;
> +	qcom,switching-freq = <800>;
> +	qcom,ovp = <29600>;
> +	qcom,string-cfg = <15>;
> +};
[..]
> diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c

After reviewing your arguments and comparing the drivers I still think
it's beneficial to support both these hardware revisions in the same
driver.

The majority of the register differences relates to the current sink
being split out, but this can easily be handled by a few well places
accessor functions - which depends on this being the case or not.

The addition of OVP handling would benefit 8941 as well.

The short circuit handling in your patches are isolated and not taking
this code path on 8941 should not pose any problems.

[..]
> +/* General definitions */
> +#define QCOM_WLED_DEFAULT_BRIGHTNESS		2048
> +#define  QCOM_WLED_MAX_BRIGHTNESS		4095
> +
> +/* WLED control registers */
> +#define QCOM_WLED_CTRL_MOD_ENABLE		0x46
> +#define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
> +#define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
> +
> +#define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
> +#define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
> +
> +#define QCOM_WLED_CTRL_OVP			0x4d
> +#define  QCOM_WLED_CTRL_OVP_MASK		GENMASK(1, 0)
> +
> +#define QCOM_WLED_CTRL_ILIM			0x4e
> +#define  QCOM_WLED_CTRL_ILIM_MASK		GENMASK(2, 0)
> +
> +/* WLED sink registers */
> +#define QCOM_WLED_SINK_CURR_SINK_EN		0x46
> +#define  QCOM_WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
> +#define  QCOM_WLED_SINK_CURR_SINK_SHFT		0x04

Shifts are typically not given as hex...

> +
> +#define QCOM_WLED_SINK_SYNC			0x47
> +#define  QCOM_WLED_SINK_SYNC_MASK		GENMASK(3, 0)
> +#define  QCOM_WLED_SINK_SYNC_LED1		BIT(0)
> +#define  QCOM_WLED_SINK_SYNC_LED2		BIT(1)
> +#define  QCOM_WLED_SINK_SYNC_LED3		BIT(2)
> +#define  QCOM_WLED_SINK_SYNC_LED4		BIT(3)
> +#define  QCOM_WLED_SINK_SYNC_CLEAR		0x00
> +
> +#define QCOM_WLED_SINK_MOD_EN_REG(n)		(0x50 + (n * 0x10))
> +#define  QCOM_WLED_SINK_REG_STR_MOD_MASK	BIT(7)
> +#define  QCOM_WLED_SINK_REG_STR_MOD_EN		BIT(7)
> +
> +#define QCOM_WLED_SINK_SYNC_DLY_REG(n)		(0x51 + (n * 0x10))
> +#define QCOM_WLED_SINK_FS_CURR_REG(n)		(0x52 + (n * 0x10))
> +#define  QCOM_WLED_SINK_FS_MASK			GENMASK(3, 0)
> +
> +#define QCOM_WLED_SINK_CABC_REG(n)		(0x56 + (n * 0x10))
> +#define  QCOM_WLED_SINK_CABC_MASK		BIT(7)
> +#define  QCOM_WLED_SINK_CABC_EN			BIT(7)
> +
> +#define QCOM_WLED_SINK_BRIGHT_LSB_REG(n)	(0x57 + (n * 0x10))
> +#define QCOM_WLED_SINK_BRIGHT_MSB_REG(n)	(0x58 + (n * 0x10))
> +
> +struct qcom_wled_config {
> +	u32 i_boost_limit;
> +	u32 ovp;
> +	u32 switch_freq;
> +	u32 fs_current;
> +	u32 string_cfg;
> +	bool en_cabc;
> +};
> +
> +struct qcom_wled {
> +	const char *name;
> +	struct platform_device *pdev;

Lug around the struct device * instead of the platform_device, and use
this for dev_* prints throughout the code.

> +	struct regmap *regmap;
> +	u16 sink_addr;
> +	u16 ctrl_addr;
> +	u32 brightness;
> +	bool prev_state;

You can derive prev_state from wled->brightness in
qcom_wled_update_status().

> +
> +	struct qcom_wled_config cfg;
> +};
> +
> +static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
> +{
> +	int rc;
> +
> +	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> +			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
> +			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);

This shift obfuscate the fact that val is only 0 or 1, make val a bool
and make the macro for the enabled state be BIT(7).

> +	return rc;
> +}
> +
> +static int qcom_wled_get_brightness(struct backlight_device *bl)
> +{
> +	struct qcom_wled *wled = bl_get_data(bl);
> +
> +	return wled->brightness;
> +}
> +
> +static int qcom_wled_sync_toggle(struct qcom_wled *wled)
> +{
> +	int rc;
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->sink_addr + QCOM_WLED_SINK_SYNC,
> +			QCOM_WLED_SINK_SYNC_MASK, QCOM_WLED_SINK_SYNC_MASK);
> +	if (rc < 0)
> +		return rc;
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->sink_addr + QCOM_WLED_SINK_SYNC,
> +			QCOM_WLED_SINK_SYNC_MASK, QCOM_WLED_SINK_SYNC_CLEAR);
> +
> +	return rc;
> +}
> +
> +static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 brightness)
> +{
> +	int rc, i;
> +	u16 low_limit = QCOM_WLED_MAX_BRIGHTNESS * 4 / 1000;
> +	u8 string_cfg = wled->cfg.string_cfg;
> +	u8 v[2];
> +
> +	/* WLED's lower limit of operation is 0.4% */
> +	if (brightness > 0 && brightness < low_limit)
> +		brightness = low_limit;

What happens between 0 and 0.4%? Is this policy or is this related to
some hardware issue?

Also, this function will not be called with brightness = 0, so you don't
need to check that case.

> +
> +	v[0] = brightness & 0xff;
> +	v[1] = (brightness >> 8) & 0xf;
> +
> +	for (i = 0; (string_cfg >> i) != 0; i++) {

The condition looks optimal... Just loop from 0 to 3 and it will be
easier to read without any measurable losses.

> +		if (string_cfg & BIT(i)) {

Flip this condition around and use "continue" to reduce the indentation
level of the rest of the block.

> +			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
> +					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
> +			if (rc < 0)
> +				return rc;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int qcom_wled_update_status(struct backlight_device *bl)
> +{
> +	struct qcom_wled *wled = bl_get_data(bl);
> +	u16 brightness = bl->props.brightness;
> +	int rc;
> +
> +	if (bl->props.power != FB_BLANK_UNBLANK ||
> +	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
> +	    bl->props.state & BL_CORE_FBBLANK)
> +		brightness = 0;
> +
> +	if (brightness) {
> +		rc = qcom_wled_set_brightness(wled, brightness);
> +		if (rc < 0) {
> +			pr_err("wled failed to set brightness rc:%d\n", rc);

Use dev_err() and dev_dbg() throughout the driver.

> +			return rc;
> +		}
> +
> +		if (!!brightness != wled->prev_state) {
> +			rc = qcom_wled_module_enable(wled, !!brightness);
> +			if (rc < 0) {
> +				pr_err("wled enable failed rc:%d\n", rc);
> +				return rc;
> +			}
> +		}

This block is exactly the same as the else statement, there's no need to
repeat yourself.

> +	} else {
> +		rc = qcom_wled_module_enable(wled, brightness);
> +		if (rc < 0) {
> +			pr_err("wled disable failed rc:%d\n", rc);
> +			return rc;
> +		}
> +	}
> +
> +	wled->prev_state = !!brightness;
> +
> +	rc = qcom_wled_sync_toggle(wled);
> +	if (rc < 0) {
> +		pr_err("wled sync failed rc:%d\n", rc);
> +		return rc;
> +	}
> +
> +	wled->brightness = brightness;
> +
> +	return rc;
> +}
> +
> +static int qcom_wled_setup(struct qcom_wled *wled)
> +{
> +	int rc, temp, i;
> +	u8 sink_en = 0;
> +	u8 string_cfg = wled->cfg.string_cfg;
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
> +			QCOM_WLED_CTRL_OVP_MASK, wled->cfg.ovp);
> +	if (rc < 0)
> +		return rc;
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_ILIM,
> +			QCOM_WLED_CTRL_ILIM_MASK, wled->cfg.i_boost_limit);
> +	if (rc < 0)
> +		return rc;
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_SWITCH_FREQ,
> +			QCOM_WLED_CTRL_SWITCH_FREQ_MASK, wled->cfg.switch_freq);
> +	if (rc < 0)
> +		return rc;
> +
> +	for (i = 0; (string_cfg >> i) != 0; i++) {
> +		if (string_cfg & BIT(i)) {

Same as above.

> +			u16 addr = wled->sink_addr +
> +					QCOM_WLED_SINK_MOD_EN_REG(i);
> +
> +			rc = regmap_update_bits(wled->regmap, addr,
> +					QCOM_WLED_SINK_REG_STR_MOD_MASK,
> +					QCOM_WLED_SINK_REG_STR_MOD_EN);
> +			if (rc < 0)
> +				return rc;
> +
> +			addr = wled->sink_addr +
> +					QCOM_WLED_SINK_FS_CURR_REG(i);
> +			rc = regmap_update_bits(wled->regmap, addr,
> +					QCOM_WLED_SINK_FS_MASK,
> +					wled->cfg.fs_current);
> +			if (rc < 0)
> +				return rc;
> +
> +			addr = wled->sink_addr +
> +					QCOM_WLED_SINK_CABC_REG(i);
> +			rc = regmap_update_bits(wled->regmap, addr,
> +					QCOM_WLED_SINK_CABC_MASK,
> +					wled->cfg.en_cabc ?
> +					QCOM_WLED_SINK_CABC_EN : 0);
> +			if (rc)
> +				return rc;
> +
> +			temp = i + QCOM_WLED_SINK_CURR_SINK_SHFT;
> +			sink_en |= 1 << temp;

I'm failing to see the reason for the "temp" variable here. Please do:

  sink_en |= BIT(i + QCOM_WLED_SINK_CURR_SINK_SHFT)

> +		}
> +	}
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
> +			QCOM_WLED_SINK_CURR_SINK_MASK, sink_en);
> +	if (rc < 0)
> +		return rc;
> +
> +	rc = qcom_wled_sync_toggle(wled);
> +	if (rc < 0) {
> +		pr_err("Failed to toggle sync reg rc:%d\n", rc);
> +		return rc;
> +	}
> +
> +	return 0;
> +}
> +
[..]
> +static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
> +{
> +	struct qcom_wled_config *cfg = &wled->cfg;
> +	const __be32 *prop_addr;
> +	u32 val, c;
> +	int rc, i, j;
> +
> +	const struct {
> +		const char *name;
> +		u32 *val_ptr;
> +		const struct qcom_wled_var_cfg *cfg;
> +	} u32_opts[] = {

I suggest that you tie this list of options to the compatible (through
of_device_id->data) and pass it as a parameter to this function. That
way you can handle variation in properties and their values between
different compatibles.

[..]
> +	*cfg = wled_config_defaults;
> +	for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
> +		rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);

of_property_read_u32() returns -ENODATA when there's no associated data,
you can probably use this to implement support for the boolean types in
the same list of opts.

[..]
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
> +		if (of_property_read_bool(dev->of_node, bool_opts[i].name))
> +			*bool_opts[i].val_ptr = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct backlight_ops qcom_wled_ops = {
> +	.update_status = qcom_wled_update_status,
> +	.get_brightness = qcom_wled_get_brightness,
> +};
> +
> +static int qcom_wled_probe(struct platform_device *pdev)
> +{
> +	struct backlight_properties props;
> +	struct backlight_device *bl;
> +	struct qcom_wled *wled;
> +	struct regmap *regmap;
> +	u32 val;
> +	int rc;
> +
> +	regmap = dev_get_regmap(pdev->dev.parent, NULL);
> +	if (!regmap) {
> +		pr_err("Unable to get regmap\n");
> +		return -EINVAL;
> +	}
> +
> +	wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
> +	if (!wled)
> +		return -ENOMEM;
> +
> +	wled->regmap = regmap;
> +	wled->pdev = pdev;
> +
> +	rc = qcom_wled_configure(wled, &pdev->dev);
> +	if (rc < 0) {
> +		pr_err("wled configure failed rc:%d\n", rc);

qcom_wled_configure() already printed an error message for you, no need
to repeat this.

> +		return rc;
> +	}
> +

Please also run checkpatch.pl with the --strict option and fix the
indentation issues reported.

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling
  2017-11-16 12:18 ` [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Kiran Gunda
  2017-11-17 20:30   ` Rob Herring
@ 2017-12-05  4:35   ` Bjorn Andersson
  2017-12-11  9:28     ` kgunda
  1 sibling, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2017-12-05  4:35 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:

> Handle the short circuit(SC) interrupt and check if the SC interrupt
> is valid. Re-enable the module to check if it goes away. Disable the
> module altogether if the SC event persists.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  22 ++++
>  drivers/video/backlight/qcom-spmi-wled.c           | 126 ++++++++++++++++++++-
>  2 files changed, 142 insertions(+), 6 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> index f1ea25b..768608c 100644
> --- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> @@ -74,6 +74,26 @@ The PMIC is connected to the host processor via SPMI bus.
>  	Definition: Specify if cabc (content adaptive backlight control) is
>  		    needed.
>  
> +- qcom,ext-pfet-sc-pro-en

Please use readable names, rather than a bunch of abbreviations.

> +	Usage:      optional
> +	Value type: <bool>
> +	Definition: Specify if external PFET control for short circuit
> +		    protection is needed.

What does this mean? At least change the wording to "...protection is
used".

> +
> +- interrupts
> +	Usage:      optional
> +	Value type: <prop encoded array>
> +	Definition: Interrupts associated with WLED. Interrupts can be
> +		    specified as per the encoding listed under
> +		    Documentation/devicetree/bindings/spmi/
> +		    qcom,spmi-pmic-arb.txt.
> +
> +- interrupt-names
> +	Usage:      optional
> +	Value type: <string>
> +	Definition: Interrupt names associated with the interrupts.
> +		    Must be "sc-irq".

This is obviously an irq, so no need to include that in the name. I
would also prefer if you use the name "short" to make this easier to
read.

> +
>  Example:
>  
>  qcom-wled@d800 {
> @@ -82,6 +102,8 @@ qcom-wled@d800 {
>  	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
>  	label = "backlight";
>  
> +	interrupts = <0x3 0xd8 0x2 IRQ_TYPE_EDGE_RISING>;

We tend to write these on the form <decimal, hex, decimal, enum>, please
follow this.

> +	interrupt-names = "sc-irq";
>  	qcom,fs-current-limit = <25000>;
>  	qcom,current-boost-limit = <970>;
>  	qcom,switching-freq = <800>;
> diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
> index 14c3adc..7dbaaa7 100644
> --- a/drivers/video/backlight/qcom-spmi-wled.c
> +++ b/drivers/video/backlight/qcom-spmi-wled.c
> @@ -11,6 +11,9 @@
>   * GNU General Public License for more details.
>   */
>  
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/ktime.h>
>  #include <linux/kernel.h>
>  #include <linux/backlight.h>
>  #include <linux/module.h>
> @@ -23,7 +26,13 @@
>  #define QCOM_WLED_DEFAULT_BRIGHTNESS		2048
>  #define  QCOM_WLED_MAX_BRIGHTNESS		4095
>  
> +#define QCOM_WLED_SC_DLY_MS			20
> +#define QCOM_WLED_SC_CNT_MAX			5
> +#define QCOM_WLED_SC_RESET_CNT_DLY_US		1000000

With times of this ballpark you can just use jiffies, with this just
being HZ.

> +
>  /* WLED control registers */
> +#define QCOM_WLED_CTRL_FAULT_STATUS		0x08
> +
>  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
> @@ -37,6 +46,15 @@
>  #define QCOM_WLED_CTRL_ILIM			0x4e
>  #define  QCOM_WLED_CTRL_ILIM_MASK		GENMASK(2, 0)
>  
> +#define QCOM_WLED_CTRL_SHORT_PROTECT		0x5e
> +#define  QCOM_WLED_CTRL_SHORT_EN_MASK		BIT(7)
> +
> +#define QCOM_WLED_CTRL_SEC_ACCESS		0xd0
> +#define  QCOM_WLED_CTRL_SEC_UNLOCK		0xa5
> +
> +#define QCOM_WLED_CTRL_TEST1			0xe2
> +#define  QCOM_WLED_EXT_FET_DTEST2		0x09
> +
>  /* WLED sink registers */
>  #define QCOM_WLED_SINK_CURR_SINK_EN		0x46
>  #define  QCOM_WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
> @@ -71,19 +89,23 @@ struct qcom_wled_config {
>  	u32 switch_freq;
>  	u32 fs_current;
>  	u32 string_cfg;
> +	int sc_irq;

Keep data parsed directly from DT in the config and move this to
qcom_wled.

>  	bool en_cabc;
> +	bool ext_pfet_sc_pro_en;

This name is long and hard to parse. "external_pfet" would be much
easier to read.

>  };
>  
>  struct qcom_wled {
>  	const char *name;
>  	struct platform_device *pdev;
>  	struct regmap *regmap;
> +	struct mutex lock;
> +	struct qcom_wled_config cfg;
> +	ktime_t last_sc_event_time;
>  	u16 sink_addr;
>  	u16 ctrl_addr;
>  	u32 brightness;
> +	u32 sc_count;
>  	bool prev_state;
> -
> -	struct qcom_wled_config cfg;

Moving this seems unnecessary.

>  };
>  
>  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
> @@ -157,25 +179,26 @@ static int qcom_wled_update_status(struct backlight_device *bl)
>  	    bl->props.state & BL_CORE_FBBLANK)
>  		brightness = 0;
>  
> +	mutex_lock(&wled->lock);

Is this lock necessary?

> +static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
> +{
> +	struct qcom_wled *wled = _wled;
> +	int rc;
> +	u32 val;
> +	s64 elapsed_time;
> +
> +	rc = regmap_read(wled->regmap,
> +		wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS, &val);
> +	if (rc < 0) {
> +		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
> +		return IRQ_HANDLED;
> +	}
> +
> +	wled->sc_count++;
> +	pr_err("WLED short circuit detected %d times fault_status=%x\n",
> +		wled->sc_count, val);

Who will read this and is it worth the extra read of FAULT_STATUS just
to produce this print?

> +	mutex_lock(&wled->lock);
> +	rc = qcom_wled_module_enable(wled, false);
> +	if (rc < 0) {
> +		pr_err("wled disable failed rc:%d\n", rc);
> +		goto unlock_mutex;
> +	}
> +
> +	elapsed_time = ktime_us_delta(ktime_get(),
> +				wled->last_sc_event_time);
> +	if (elapsed_time > QCOM_WLED_SC_RESET_CNT_DLY_US) {
> +		wled->sc_count = 0;
> +	} else if (wled->sc_count > QCOM_WLED_SC_CNT_MAX) {

This isn't really "else elapsed_time was more than DLY_US". Split this
into:

if (elapsed_time > xyz)
	wled->sc_count = 0;

if (wled->sc_count > QCOM_WLED_SC_CNT_MAX)
	...

> +		pr_err("SC trigged %d times, disabling WLED forever!\n",

"forever" as in "until someone turns it on again"?

> +			wled->sc_count);
> +		goto unlock_mutex;
> +	}
> +
> +	wled->last_sc_event_time = ktime_get();
> +
> +	msleep(QCOM_WLED_SC_DLY_MS);
> +	rc = qcom_wled_module_enable(wled, true);
> +	if (rc < 0)
> +		pr_err("wled enable failed rc:%d\n", rc);
> +
> +unlock_mutex:
> +	mutex_unlock(&wled->lock);
> +
> +	return IRQ_HANDLED;
> +}
> +
>  static int qcom_wled_setup(struct qcom_wled *wled)
>  {
>  	int rc, temp, i;
>  	u8 sink_en = 0;
>  	u8 string_cfg = wled->cfg.string_cfg;
> +	int sc_irq = wled->cfg.sc_irq;
>  
>  	rc = regmap_update_bits(wled->regmap,
>  			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
> @@ -261,6 +334,39 @@ static int qcom_wled_setup(struct qcom_wled *wled)
>  		return rc;
>  	}
>  
> +	if (sc_irq >= 0) {

I think things will be cleaner if you let qcom_wled_setup() configure
the hardware based on the wled->cfg (as is done to this point) and then
deal with the interrupts in a separate code path from the probe
function.

> +		rc = devm_request_threaded_irq(&wled->pdev->dev, sc_irq,
> +				NULL, qcom_wled_sc_irq_handler, IRQF_ONESHOT,
> +				"qcom_wled_sc_irq", wled);
> +		if (rc < 0) {
> +			pr_err("Unable to request sc(%d) IRQ(err:%d)\n",
> +				sc_irq, rc);

sc_irq is just a number without meaning, no need to print it.

> +			return rc;
> +		}
> +
> +		rc = regmap_update_bits(wled->regmap,
> +				wled->ctrl_addr + QCOM_WLED_CTRL_SHORT_PROTECT,
> +				QCOM_WLED_CTRL_SHORT_EN_MASK,
> +				QCOM_WLED_CTRL_SHORT_EN_MASK);
> +		if (rc < 0)
> +			return rc;
> +
> +		if (wled->cfg.ext_pfet_sc_pro_en) {
> +			/* unlock the secure access regisetr */

Spelling of register, and this operation does "Unlock the secure
register access" it doesn't unlock the secure access register.

> +			rc = regmap_write(wled->regmap, wled->ctrl_addr +
> +					QCOM_WLED_CTRL_SEC_ACCESS,
> +					QCOM_WLED_CTRL_SEC_UNLOCK);
> +			if (rc < 0)
> +				return rc;
> +
> +			rc = regmap_write(wled->regmap,
> +					wled->ctrl_addr + QCOM_WLED_CTRL_TEST1,
> +					QCOM_WLED_EXT_FET_DTEST2);

What is the relationship between DTEST2 and the external FET?

> +			if (rc < 0)
> +				return rc;
> +		}
> +	}
> +
>  	return 0;
>  }
>  
> @@ -271,6 +377,7 @@ static int qcom_wled_setup(struct qcom_wled *wled)
>  	.switch_freq = 11,
>  	.string_cfg = 0xf,
>  	.en_cabc = 0,
> +	.ext_pfet_sc_pro_en = 1,
>  };
>  
>  struct qcom_wled_var_cfg {
> @@ -376,6 +483,7 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
>  		bool *val_ptr;
>  	} bool_opts[] = {
>  		{ "qcom,en-cabc", &cfg->en_cabc, },
> +		{ "qcom,ext-pfet-sc-pro", &cfg->ext_pfet_sc_pro_en, },
>  	};
>  
>  	prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
> @@ -427,6 +535,10 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
>  			*bool_opts[i].val_ptr = true;
>  	}
>  
> +	wled->cfg.sc_irq = platform_get_irq_byname(wled->pdev, "sc-irq");
> +	if (wled->cfg.sc_irq < 0)
> +		dev_dbg(&wled->pdev->dev, "sc irq is not used\n");
> +

Move this to qcom_wled_probe() or into its own code path, together with
the rest of the sc_irq initialization.

And as you're not enabling or disabling it you can store it in a local
variable.

>  	return 0;
>  }
>  

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling
  2017-11-16 12:18 ` [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling Kiran Gunda
@ 2017-12-05  4:45   ` Bjorn Andersson
  2017-12-11  9:31     ` kgunda
  0 siblings, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2017-12-05  4:45 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:

> WLED peripheral has over voltage protection(OVP) circuitry and the OVP
> fault is notified through an interrupt. Though this fault condition rising
> is due to an incorrect hardware configuration is mitigated in the hardware,
> it still needs to be detected and handled. Add support for it.
> 
> When WLED module is enabled, keep OVP fault interrupt disabled for 10 ms to
> account for soft start delay.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  7 +-
>  drivers/video/backlight/qcom-spmi-wled.c           | 83 ++++++++++++++++++++++
>  2 files changed, 87 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> index 768608c..d39ee93 100644
> --- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> @@ -92,7 +92,7 @@ The PMIC is connected to the host processor via SPMI bus.
>  	Usage:      optional
>  	Value type: <string>
>  	Definition: Interrupt names associated with the interrupts.
> -		    Must be "sc-irq".
> +		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>  

As before, we know this is an IRQ, so omit the -irq from the name.

[..]
> diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
[..]
> @@ -115,6 +123,28 @@ static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> +	if (rc < 0)
> +		return rc;
> +	/*
> +	 * Wait for at least 10ms before enabling OVP fault interrupt after
> +	 * enabling the module so that soft start is completed. Keep the OVP
> +	 * interrupt disabled when the module is disabled.
> +	 */
> +	if (val) {
> +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> +				QCOM_WLED_SOFT_START_DLY_US + 1000);

This is sleeping in the brightness/enable code path, can you
schedule_delayed_work() instead to not block this code path
unnecessarily?

> +
> +		if (wled->cfg.ovp_irq > 0 && wled->ovp_irq_disabled) {
> +			enable_irq(wled->cfg.ovp_irq);
> +			wled->ovp_irq_disabled = false;
> +		}
> +	} else {
> +		if (wled->cfg.ovp_irq > 0 && !wled->ovp_irq_disabled) {
> +			disable_irq(wled->cfg.ovp_irq);
> +			wled->ovp_irq_disabled = true;
> +		}
> +	}
> +
>  	return rc;
>  }
>  
> @@ -264,12 +294,42 @@ static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
>  	return IRQ_HANDLED;
>  }
>  
> +static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
> +{
> +	struct qcom_wled *wled = _wled;
> +	int rc;
> +	u32 int_sts, fault_sts;
> +
> +	rc = regmap_read(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> +	if (rc < 0) {
> +		pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> +		return IRQ_HANDLED;
> +	}
> +
> +	rc = regmap_read(wled->regmap, wled->ctrl_addr +
> +			QCOM_WLED_CTRL_FAULT_STATUS, &fault_sts);
> +	if (rc < 0) {
> +		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (fault_sts &
> +		(QCOM_WLED_CTRL_OVP_FAULT_BIT | QCOM_WLED_CTRL_ILIM_FAULT_BIT))
> +		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
> +			int_sts, fault_sts);

All this function does is print things to the log. When is this
information consumed and by whom? dev_dbg() instead?

> +
> +	return IRQ_HANDLED;
> +}
> +
>  static int qcom_wled_setup(struct qcom_wled *wled)
>  {
>  	int rc, temp, i;
>  	u8 sink_en = 0;
> +	u32 val;
>  	u8 string_cfg = wled->cfg.string_cfg;
>  	int sc_irq = wled->cfg.sc_irq;
> +	int ovp_irq = wled->cfg.ovp_irq;
>  
>  	rc = regmap_update_bits(wled->regmap,
>  			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
> @@ -367,6 +427,25 @@ static int qcom_wled_setup(struct qcom_wled *wled)
>  		}
>  	}
>  
> +	if (ovp_irq >= 0) {

As with the previous patch.

[..]
> @@ -539,6 +618,10 @@ static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
[..]
> +	wled->cfg.ovp_irq = platform_get_irq_byname(wled->pdev, "ovp-irq");
> +	if (wled->cfg.ovp_irq < 0)
> +		dev_dbg(&wled->pdev->dev, "ovp irq is not used\n");
> +

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2017-11-16 12:18 ` [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support Kiran Gunda
@ 2017-12-05  5:40   ` Bjorn Andersson
  2018-04-19 10:45     ` kgunda
  0 siblings, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2017-12-05  5:40 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:

> The auto-calibration algorithm checks if the current WLED sink
> configuration is valid. It tries enabling every sink and checks
> if the OVP fault is observed. Based on this information it
> detects and enables the valid sink configuration. Auto calibration
> will be triggered when the OVP fault interrupts are seen frequently
> thereby it tries to fix the sink configuration.
> 

So it's not auto "calibration" it's auto "detection" of strings?

When is this feature needed?

> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>  drivers/video/backlight/qcom-spmi-wled.c           | 304 ++++++++++++++++++++-
>  2 files changed, 306 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> index d39ee93..f06c0cd 100644
> --- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via SPMI bus.
>  	Definition: Interrupt names associated with the interrupts.
>  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>  
> +- qcom,auto-calibration

qcom,auto-string-detect?

> +	Usage:      optional
> +	Value type: <bool>
> +	Definition: Enables auto-calibration of the WLED sink configuration.
> +
>  Example:
>  
>  qcom-wled@d800 {
> diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
> index 8b2a77a..aee5c56 100644
> --- a/drivers/video/backlight/qcom-spmi-wled.c
> +++ b/drivers/video/backlight/qcom-spmi-wled.c
> @@ -38,11 +38,14 @@
>  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>  
>  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
> +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)

The use of BIT() makes this a mask and not a bit number, so if you just
drop that you can afford to spell out the "FAULT" like the data sheet
does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?

>  
>  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>  
> +#define QCOM_WLED_CTRL_FDBK_OP			0x48

This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
unreadable?

> +
>  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>  
> @@ -99,6 +102,7 @@ struct qcom_wled_config {
>  	int ovp_irq;
>  	bool en_cabc;
>  	bool ext_pfet_sc_pro_en;
> +	bool auto_calib_enabled;
>  };
>  
>  struct qcom_wled {
> @@ -108,18 +112,25 @@ struct qcom_wled {
>  	struct mutex lock;
>  	struct qcom_wled_config cfg;
>  	ktime_t last_sc_event_time;
> +	ktime_t start_ovp_fault_time;
>  	u16 sink_addr;
>  	u16 ctrl_addr;
> +	u16 auto_calibration_ovp_count;
>  	u32 brightness;
>  	u32 sc_count;
>  	bool prev_state;
>  	bool ovp_irq_disabled;
> +	bool auto_calib_done;
> +	bool force_mod_disable;
>  };
>  
>  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>  {
>  	int rc;
>  
> +	if (wled->force_mod_disable)
> +		return 0;
> +
>  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 brightness)
>  	v[1] = (brightness >> 8) & 0xf;
>  
>  	for (i = 0; (string_cfg >> i) != 0; i++) {
> -		if (string_cfg & BIT(i)) {

Why was this check here in the first place, if it's now fine to
configure the brightness of all strings?

Also, a single-string config of 0b0001 will only set brightness on the
first string, while 0b1000 will set brightness on all strings.

>  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>  			if (rc < 0)
>  				return rc;
> -		}
>  	}
>  
>  	return 0;
> @@ -294,6 +303,262 @@ static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
>  	return IRQ_HANDLED;
>  }
>  
> +#define AUTO_CALIB_BRIGHTNESS		200
> +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
> +{
> +	int rc = 0, i;
> +	u32 sink_config = 0, int_sts;
> +	u8 reg = 0, sink_test = 0, sink_valid = 0;
> +	u8 string_cfg = wled->cfg.string_cfg;
> +
> +	/* read configured sink configuration */
> +	rc = regmap_read(wled->regmap, wled->sink_addr +
> +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
> +	if (rc < 0) {
> +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
> +		goto failed_calib;
> +	}
> +
> +	/* disable the module before starting calibration */
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> +	if (rc < 0) {
> +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
> +		goto failed_calib;
> +	}

Any error handling beyond this point seems to leave the backlight off
(indefinitely?), this does seem like potentially bad user experience...

In particular I wonder about the case when this would happen at some
random time, minutes, hours, days, months after the device was booted.

> +
> +	/* set low brightness across all sinks */
> +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
> +	if (rc < 0) {
> +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
> +		goto failed_calib;
> +	}
> +
> +	if (wled->cfg.en_cabc) {
> +		for (i = 0; (string_cfg >> i) != 0; i++) {
> +			reg = 0;
> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> +					QCOM_WLED_SINK_CABC_REG(i),
> +					QCOM_WLED_SINK_CABC_MASK, reg);

Just replace "reg" with 0.

> +			if (rc < 0)
> +				goto failed_calib;
> +		}
> +	}
> +
> +	/* disable all sinks */
> +	rc = regmap_write(wled->regmap,
> +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
> +	if (rc < 0) {
> +		pr_err("Failed to disable all sinks rc=%d\n", rc);
> +		goto failed_calib;
> +	}
> +
> +	/* iterate through the strings one by one */
> +	for (i = 0; (string_cfg >> i) != 0; i++) {
> +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);

BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);

> +
> +		/* Enable feedback control */
> +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
> +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
> +		if (rc < 0) {
> +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
> +				i + 1, rc);
> +			goto failed_calib;
> +		}
> +
> +		/* enable the sink */
> +		rc = regmap_write(wled->regmap, wled->sink_addr +
> +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
> +		if (rc < 0) {
> +			pr_err("Failed to configure SINK %d rc=%d\n",
> +						i + 1, rc);
> +			goto failed_calib;
> +		}
> +
> +		/* Enable the module */
> +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> +				QCOM_WLED_CTRL_MOD_ENABLE,
> +				QCOM_WLED_CTRL_MOD_EN_MASK,
> +				QCOM_WLED_CTRL_MOD_EN_MASK);

I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
makes me wonder why it's done differently in qcom_wled_module_enable().

> +		if (rc < 0) {
> +			pr_err("Failed to enable WLED module rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +
> +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> +
> +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
> +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> +		if (rc < 0) {
> +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +
> +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
> +			pr_debug("WLED OVP fault detected with SINK %d\n",
> +						i + 1);
> +		else
> +			sink_valid |= sink_test;
> +
> +		/* Disable the module */
> +		rc = regmap_update_bits(wled->regmap,
> +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> +		if (rc < 0) {
> +			pr_err("Failed to disable WLED module rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +	}
> +
> +	if (sink_valid == sink_config) {
> +		pr_debug("WLED auto-calibration complete, default sink-config=%x OK!\n",
> +						sink_config);
> +	} else {
> +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
> +						sink_config, sink_valid);
> +		sink_config = sink_valid;
> +	}
> +
> +	if (!sink_config) {
> +		pr_warn("No valid WLED sinks found\n");
> +		wled->force_mod_disable = true;
> +		goto failed_calib;
> +	}
> +
> +	/* write the new sink configuration */
> +	rc = regmap_write(wled->regmap,
> +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
> +			sink_config);
> +	if (rc < 0) {
> +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
> +		goto failed_calib;
> +	}
> +
> +	/* MODULATOR_EN setting for valid sinks */

"Enable valid sinks"

> +	for (i = 0; (string_cfg >> i) != 0; i++) {
> +		if (wled->cfg.en_cabc) {
> +			reg = QCOM_WLED_SINK_CABC_EN;

"reg" is a bad name of a variable holding the "value" to be written to a
register.

> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> +					QCOM_WLED_SINK_CABC_REG(i),
> +					QCOM_WLED_SINK_CABC_MASK, reg);

Again, just inline the value in the function call.

> +			if (rc < 0)
> +				goto failed_calib;
> +		}
> +
> +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))

BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)

> +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
> +		else
> +			reg = 0x0; /* disable modulator_en for unused sink */
> +
> +		rc = regmap_write(wled->regmap, wled->sink_addr +
> +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
> +		if (rc < 0) {
> +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +	}
> +
> +	/* restore the feedback setting */
> +	rc = regmap_write(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
> +	if (rc < 0) {
> +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
> +		goto failed_calib;
> +	}
> +
> +	/* restore  brightness */
> +	rc = qcom_wled_set_brightness(wled, wled->brightness);
> +	if (rc < 0) {
> +		pr_err("Failed to set brightness after calibration rc=%d\n",
> +			rc);
> +		goto failed_calib;
> +	}
> +
> +	rc = regmap_update_bits(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> +			QCOM_WLED_CTRL_MOD_EN_MASK,
> +			QCOM_WLED_CTRL_MOD_EN_MASK);
> +	if (rc < 0) {
> +		pr_err("Failed to enable WLED module rc=%d\n", rc);
> +		goto failed_calib;
> +	}
> +
> +	/* delay for WLED soft-start */

What comes after this that you want to delay?

This delay is used to make the OVP IRQ not fire immediately, but as
we've now successfully executed the string auto detection run we're
never going to do anything in the OVP handler.

> +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> +			QCOM_WLED_SOFT_START_DLY_US + 1000);
> +
> +failed_calib:
> +	return rc;
> +}
> +
> +#define WLED_AUTO_CAL_OVP_COUNT		5
> +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
> +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
> +{
> +	s64 elapsed_time_us;
> +
> +	/*
> +	 * Check if the OVP fault was an occasional one
> +	 * or if its firing continuously, the latter qualifies
> +	 * for an auto-calibration check.
> +	 */
> +	if (!wled->auto_calibration_ovp_count) {
> +		wled->start_ovp_fault_time = ktime_get();
> +		wled->auto_calibration_ovp_count++;
> +	} else {
> +		elapsed_time_us = ktime_us_delta(ktime_get(),
> +				wled->start_ovp_fault_time);
> +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
> +			wled->auto_calibration_ovp_count = 0;
> +		else
> +			wled->auto_calibration_ovp_count++;
> +
> +		if (wled->auto_calibration_ovp_count >=
> +				WLED_AUTO_CAL_OVP_COUNT) {
> +			wled->auto_calibration_ovp_count = 0;
> +			return true;
> +		}
> +	}
> +
> +	return false;
> +}
> +
> +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)

I presume this function is expected to detect if there is a invalid
configuration at boot and try to figure out which strings are actually
wired.

> +{
> +	int rc;
> +	u32 fault_status = 0, rt_status = 0;
> +
> +	if (!wled->cfg.auto_calib_enabled)
> +		return 0;
> +
> +	rc = regmap_read(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
> +			&rt_status);
> +	if (rc < 0)
> +		pr_err("Failed to read RT status rc=%d\n", rc);
> +
> +	rc = regmap_read(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
> +			&fault_status);
> +	if (rc < 0)
> +		pr_err("Failed to read fault status rc=%d\n", rc);
> +
> +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
> +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {

You should be able to drop the extra () around these.

> +		mutex_lock(&wled->lock);
> +		rc = qcom_wled_auto_calibrate(wled);
> +		if (rc < 0)
> +			pr_err("Failed auto-calibration rc=%d\n", rc);

qcom_wled_auto_calibrate() did already print, no need to repeat this.

> +		else
> +			wled->auto_calib_done = true;
> +		mutex_unlock(&wled->lock);
> +	}
> +
> +	return rc;
> +}
> +
>  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>  {
>  	struct qcom_wled *wled = _wled;
> @@ -319,6 +584,33 @@ static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>  			int_sts, fault_sts);
>  
> +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
> +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
> +			if (qcom_wled_auto_cal_required(wled)) {

So this will be invoked only once, iff we didn't boot with a faulty
configuration in which case the qcom_wled_auto_calibrate_at_init() has
already done this step and set auto_calib_done.


Which also would mean that all logic in this handler, beyond the
printouts, are only ever going to be executed zero or one times.

Why don't you just do auto-detection during probe (iff the flag is set
in DT) and you can remove all this extra logic?

> +				mutex_lock(&wled->lock);
> +				if (wled->cfg.ovp_irq > 0 &&
> +						!wled->ovp_irq_disabled) {
> +					disable_irq_nosync(wled->cfg.ovp_irq);
> +					wled->ovp_irq_disabled = true;
> +				}
> +
> +				rc = qcom_wled_auto_calibrate(wled);
> +				if (rc < 0)
> +					pr_err("Failed auto-calibration rc=%d\n",
> +						rc);

qcom_wled_auto_calibrate() did already print.

> +				else
> +					wled->auto_calib_done = true;
> +
> +				if (wled->cfg.ovp_irq > 0 &&
> +						wled->ovp_irq_disabled) {
> +					enable_irq(wled->cfg.ovp_irq);
> +					wled->ovp_irq_disabled = false;
> +				}
> +				mutex_unlock(&wled->lock);
> +			}
> +		}
> +	}
> +

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-12-05  2:01   ` Bjorn Andersson
@ 2017-12-11  9:11     ` kgunda
  0 siblings, 0 replies; 28+ messages in thread
From: kgunda @ 2017-12-11  9:11 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-12-05 07:31, Bjorn Andersson wrote:
> On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> 
>> WLED driver provides the interface to the display driver to
>> adjust the brightness of the display backlight.
>> 
>> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> ---
>>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  90 ++++
>>  drivers/video/backlight/Kconfig                    |   9 +
>>  drivers/video/backlight/Makefile                   |   1 +
>>  drivers/video/backlight/qcom-spmi-wled.c           | 504 
>> +++++++++++++++++++++
>>  4 files changed, 604 insertions(+)
>>  create mode 100644 
>> Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>  create mode 100644 drivers/video/backlight/qcom-spmi-wled.c
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> new file mode 100644
>> index 0000000..f1ea25b
>> --- /dev/null
>> +++ 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> @@ -0,0 +1,90 @@
>> +Binding for Qualcomm WLED driver
>> +
> 
> This binding document quite well describe the pm8941 as well, so please
> improve the existing binding (changing to this style is preferable).
> 
Sure. Will do it in the next series, where I will re-use pm8941 driver 
for
PMI8998 as well.
>> +WLED (White Light Emitting Diode) driver is used for controlling 
>> display
>> +backlight that is part of PMIC on Qualcomm Technologies reference 
>> platforms.
>> +The PMIC is connected to the host processor via SPMI bus.
>> +
>> +- compatible
>> +	Usage:      required
>> +	Value type: <string>
>> +	Definition: should be "qcom,pm8998-spmi-wled".
> 
> There's no WLED in the pm8998, so please make this pmi8998. This pmic 
> is
> SPMI only, so there's no need to keep "spmi" in the compatible.
> 
Sure. Will change it.
>> +
>> +- reg
>> +	Usage:      required
>> +	Value type: <prop-encoded-array>
>> +	Definition:  Base address and size of the WLED modules.
>> +
>> +- reg-names
>> +	Usage:      required
>> +	Value type: <string>
>> +	Definition:  Names associated with base addresses. should be
>> +		     "qcom-wled-ctrl-base", "qcom-wled-sink-base".
>> +
>> +- label
>> +	Usage:      required
>> +	Value type: <string>
>> +	Definition: The name of the backlight device.
>> +
>> +- default-brightness
>> +	Usage:      optional
>> +	Value type: <u32>
>> +	Definition: brightness value on boot, value from: 0-4095
>> +		    default: 2048
>> +
>> +- qcom,fs-current-limit
>> +	Usage:      optional
>> +	Value type: <u32>
>> +	Definition: per-string full scale current limit in uA. value from
>> +		    0 to 30000 with 5000 uA resolution. default: 25000 uA
> 
> "in steps of 5mA"
> 
Will address it in next series.
>> +
>> +- qcom,current-boost-limit
>> +	Usage:      optional
>> +	Value type: <u32>
>> +	Definition: ILIM threshold in mA. values are 105, 280, 450, 620, 
>> 970,
>> +		    1150, 1300, 1500. default: 970 mA
>> +
>> +- qcom,switching-freq
>> +	Usage:      optional
>> +	Value type: <u32>
>> +	Definition: Switching frequency in KHz. values are
>> +		    600, 640, 685, 738, 800, 872, 960, 1066, 1200, 1371,
>> +		    1600, 1920, 2400, 3200, 4800, 9600.
>> +		    default: 800 KHz
>> +
>> +- qcom,ovp
>> +	Usage:      optional
>> +	Value type: <u32>
>> +	Definition: Over-voltage protection limit in mV. values are 31100,
>> +		    29600, 19600, 18100.
>> +	            default: 29600 mV
>> +
>> +- qcom,string-cfg
>> +	Usage:      optional
>> +	Value type: <u32>
>> +	Definition: Bit mask of the wled strings. Bit 0 to 3 indicates 
>> strings
>> +		    0 to 3 respectively. Wled module has four strings of leds
>> +		    numbered from 0 to 3. Each string of leds are operated
>> +		    individually. Specify the strings using the bit mask. Any
>> +		    combination of led strings can be used.
>> +		    default value is 15 (b1111).
> 
> Please try to avoid expressing things as bitmasks in DT.
> 
> The only difference from 8941 here is that there's one additional
> string, so please start off by expressing this as the existing binding.
> 
> If you really need this flexibility you can follow up with an addition
> of a property like this, but name it something like
> "qcom,enabled-strings" and make this support available for pm8941 as
> well.
> 
Sure. Will address it.
>> +
>> +- qcom,en-cabc
> 
> No need for the "en", the presence of a bool property means that it's
> enabled.
> 
Will address it in next series.
>> +	Usage:      optional
>> +	Value type: <bool>
>> +	Definition: Specify if cabc (content adaptive backlight control) is
>> +		    needed.
> 
> I presume cabc isn't ever "needed", just make the description "Enable
> content adaptive backlight control".
> 
Will address it in next series.
>> +
>> +Example:
>> +
>> +qcom-wled@d800 {
>> +	compatible = "qcom,pm8998-spmi-wled";
>> +	reg = <0xd800 0xd900>;
>> +	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
>> +	label = "backlight";
>> +
>> +	qcom,fs-current-limit = <25000>;
>> +	qcom,current-boost-limit = <970>;
>> +	qcom,switching-freq = <800>;
>> +	qcom,ovp = <29600>;
>> +	qcom,string-cfg = <15>;
>> +};
> [..]
>> diff --git a/drivers/video/backlight/qcom-spmi-wled.c 
>> b/drivers/video/backlight/qcom-spmi-wled.c
> 
> After reviewing your arguments and comparing the drivers I still think
> it's beneficial to support both these hardware revisions in the same
> driver.
> 
> The majority of the register differences relates to the current sink
> being split out, but this can easily be handled by a few well places
> accessor functions - which depends on this being the case or not.
> 
> The addition of OVP handling would benefit 8941 as well.
> 
> The short circuit handling in your patches are isolated and not taking
> this code path on 8941 should not pose any problems.
> 
> [..]
Ok. I will reuse the pm8941-wled.c driver for pmi8998.
>> +/* General definitions */
>> +#define QCOM_WLED_DEFAULT_BRIGHTNESS		2048
>> +#define  QCOM_WLED_MAX_BRIGHTNESS		4095
>> +
>> +/* WLED control registers */
>> +#define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>> +#define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>> +#define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> +
>> +#define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>> +#define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> +
>> +#define QCOM_WLED_CTRL_OVP			0x4d
>> +#define  QCOM_WLED_CTRL_OVP_MASK		GENMASK(1, 0)
>> +
>> +#define QCOM_WLED_CTRL_ILIM			0x4e
>> +#define  QCOM_WLED_CTRL_ILIM_MASK		GENMASK(2, 0)
>> +
>> +/* WLED sink registers */
>> +#define QCOM_WLED_SINK_CURR_SINK_EN		0x46
>> +#define  QCOM_WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
>> +#define  QCOM_WLED_SINK_CURR_SINK_SHFT		0x04
> 
> Shifts are typically not given as hex...
> 
Will address it in next series.
>> +
>> +#define QCOM_WLED_SINK_SYNC			0x47
>> +#define  QCOM_WLED_SINK_SYNC_MASK		GENMASK(3, 0)
>> +#define  QCOM_WLED_SINK_SYNC_LED1		BIT(0)
>> +#define  QCOM_WLED_SINK_SYNC_LED2		BIT(1)
>> +#define  QCOM_WLED_SINK_SYNC_LED3		BIT(2)
>> +#define  QCOM_WLED_SINK_SYNC_LED4		BIT(3)
>> +#define  QCOM_WLED_SINK_SYNC_CLEAR		0x00
>> +
>> +#define QCOM_WLED_SINK_MOD_EN_REG(n)		(0x50 + (n * 0x10))
>> +#define  QCOM_WLED_SINK_REG_STR_MOD_MASK	BIT(7)
>> +#define  QCOM_WLED_SINK_REG_STR_MOD_EN		BIT(7)
>> +
>> +#define QCOM_WLED_SINK_SYNC_DLY_REG(n)		(0x51 + (n * 0x10))
>> +#define QCOM_WLED_SINK_FS_CURR_REG(n)		(0x52 + (n * 0x10))
>> +#define  QCOM_WLED_SINK_FS_MASK			GENMASK(3, 0)
>> +
>> +#define QCOM_WLED_SINK_CABC_REG(n)		(0x56 + (n * 0x10))
>> +#define  QCOM_WLED_SINK_CABC_MASK		BIT(7)
>> +#define  QCOM_WLED_SINK_CABC_EN			BIT(7)
>> +
>> +#define QCOM_WLED_SINK_BRIGHT_LSB_REG(n)	(0x57 + (n * 0x10))
>> +#define QCOM_WLED_SINK_BRIGHT_MSB_REG(n)	(0x58 + (n * 0x10))
>> +
>> +struct qcom_wled_config {
>> +	u32 i_boost_limit;
>> +	u32 ovp;
>> +	u32 switch_freq;
>> +	u32 fs_current;
>> +	u32 string_cfg;
>> +	bool en_cabc;
>> +};
>> +
>> +struct qcom_wled {
>> +	const char *name;
>> +	struct platform_device *pdev;
> 
> Lug around the struct device * instead of the platform_device, and use
> this for dev_* prints throughout the code.
> 
Will address it in next series.
>> +	struct regmap *regmap;
>> +	u16 sink_addr;
>> +	u16 ctrl_addr;
>> +	u32 brightness;
>> +	bool prev_state;
> 
> You can derive prev_state from wled->brightness in
> qcom_wled_update_status().
> 
Will remove it in next series.
>> +
>> +	struct qcom_wled_config cfg;
>> +};
>> +
>> +static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>> +{
>> +	int rc;
>> +
>> +	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> +			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>> +			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> 
> This shift obfuscate the fact that val is only 0 or 1, make val a bool
> and make the macro for the enabled state be BIT(7).
> 
Will address it in next series.
>> +	return rc;
>> +}
>> +
>> +static int qcom_wled_get_brightness(struct backlight_device *bl)
>> +{
>> +	struct qcom_wled *wled = bl_get_data(bl);
>> +
>> +	return wled->brightness;
>> +}
>> +
>> +static int qcom_wled_sync_toggle(struct qcom_wled *wled)
>> +{
>> +	int rc;
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->sink_addr + QCOM_WLED_SINK_SYNC,
>> +			QCOM_WLED_SINK_SYNC_MASK, QCOM_WLED_SINK_SYNC_MASK);
>> +	if (rc < 0)
>> +		return rc;
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->sink_addr + QCOM_WLED_SINK_SYNC,
>> +			QCOM_WLED_SINK_SYNC_MASK, QCOM_WLED_SINK_SYNC_CLEAR);
>> +
>> +	return rc;
>> +}
>> +
>> +static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 
>> brightness)
>> +{
>> +	int rc, i;
>> +	u16 low_limit = QCOM_WLED_MAX_BRIGHTNESS * 4 / 1000;
>> +	u8 string_cfg = wled->cfg.string_cfg;
>> +	u8 v[2];
>> +
>> +	/* WLED's lower limit of operation is 0.4% */
>> +	if (brightness > 0 && brightness < low_limit)
>> +		brightness = low_limit;
> 
> What happens between 0 and 0.4%? Is this policy or is this related to
> some hardware issue?
> 
This is related to a HW bug and if the brightness goes below 0.4% when
the module is enabled, we see the continuous OVP interrupts.
> Also, this function will not be called with brightness = 0, so you 
> don't
> need to check that case.
> 
We are disabling the module when the brightness is '0'. We update the 
brightness
and enable the module for the next update request. So it is not needed 
to call this
function for '0' brightness.
>> +
>> +	v[0] = brightness & 0xff;
>> +	v[1] = (brightness >> 8) & 0xf;
>> +
>> +	for (i = 0; (string_cfg >> i) != 0; i++) {
> 
> The condition looks optimal... Just loop from 0 to 3 and it will be
> easier to read without any measurable losses.
> 
sure. will address it in next series.
>> +		if (string_cfg & BIT(i)) {
> 
> Flip this condition around and use "continue" to reduce the indentation
> level of the rest of the block.
> 
sure. will address it in next series.
>> +			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>> +					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>> +			if (rc < 0)
>> +				return rc;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int qcom_wled_update_status(struct backlight_device *bl)
>> +{
>> +	struct qcom_wled *wled = bl_get_data(bl);
>> +	u16 brightness = bl->props.brightness;
>> +	int rc;
>> +
>> +	if (bl->props.power != FB_BLANK_UNBLANK ||
>> +	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
>> +	    bl->props.state & BL_CORE_FBBLANK)
>> +		brightness = 0;
>> +
>> +	if (brightness) {
>> +		rc = qcom_wled_set_brightness(wled, brightness);
>> +		if (rc < 0) {
>> +			pr_err("wled failed to set brightness rc:%d\n", rc);
> 
> Use dev_err() and dev_dbg() throughout the driver.
> 
sure. will address it in next series.
>> +			return rc;
>> +		}
>> +
>> +		if (!!brightness != wled->prev_state) {
>> +			rc = qcom_wled_module_enable(wled, !!brightness);
>> +			if (rc < 0) {
>> +				pr_err("wled enable failed rc:%d\n", rc);
>> +				return rc;
>> +			}
>> +		}
> 
> This block is exactly the same as the else statement, there's no need 
> to
> repeat yourself.
> 
This else is for the "if (brightness) {" not for the just above it.
>> +	} else {
>> +		rc = qcom_wled_module_enable(wled, brightness);
>> +		if (rc < 0) {
>> +			pr_err("wled disable failed rc:%d\n", rc);
>> +			return rc;
>> +		}
>> +	}
>> +
>> +	wled->prev_state = !!brightness;
>> +
>> +	rc = qcom_wled_sync_toggle(wled);
>> +	if (rc < 0) {
>> +		pr_err("wled sync failed rc:%d\n", rc);
>> +		return rc;
>> +	}
>> +
>> +	wled->brightness = brightness;
>> +
>> +	return rc;
>> +}
>> +
>> +static int qcom_wled_setup(struct qcom_wled *wled)
>> +{
>> +	int rc, temp, i;
>> +	u8 sink_en = 0;
>> +	u8 string_cfg = wled->cfg.string_cfg;
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
>> +			QCOM_WLED_CTRL_OVP_MASK, wled->cfg.ovp);
>> +	if (rc < 0)
>> +		return rc;
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_ILIM,
>> +			QCOM_WLED_CTRL_ILIM_MASK, wled->cfg.i_boost_limit);
>> +	if (rc < 0)
>> +		return rc;
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_SWITCH_FREQ,
>> +			QCOM_WLED_CTRL_SWITCH_FREQ_MASK, wled->cfg.switch_freq);
>> +	if (rc < 0)
>> +		return rc;
>> +
>> +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> +		if (string_cfg & BIT(i)) {
> 
> Same as above.
> 
Ok. Will address it in next series.
>> +			u16 addr = wled->sink_addr +
>> +					QCOM_WLED_SINK_MOD_EN_REG(i);
>> +
>> +			rc = regmap_update_bits(wled->regmap, addr,
>> +					QCOM_WLED_SINK_REG_STR_MOD_MASK,
>> +					QCOM_WLED_SINK_REG_STR_MOD_EN);
>> +			if (rc < 0)
>> +				return rc;
>> +
>> +			addr = wled->sink_addr +
>> +					QCOM_WLED_SINK_FS_CURR_REG(i);
>> +			rc = regmap_update_bits(wled->regmap, addr,
>> +					QCOM_WLED_SINK_FS_MASK,
>> +					wled->cfg.fs_current);
>> +			if (rc < 0)
>> +				return rc;
>> +
>> +			addr = wled->sink_addr +
>> +					QCOM_WLED_SINK_CABC_REG(i);
>> +			rc = regmap_update_bits(wled->regmap, addr,
>> +					QCOM_WLED_SINK_CABC_MASK,
>> +					wled->cfg.en_cabc ?
>> +					QCOM_WLED_SINK_CABC_EN : 0);
>> +			if (rc)
>> +				return rc;
>> +
>> +			temp = i + QCOM_WLED_SINK_CURR_SINK_SHFT;
>> +			sink_en |= 1 << temp;
> 
> I'm failing to see the reason for the "temp" variable here. Please do:
> 
>   sink_en |= BIT(i + QCOM_WLED_SINK_CURR_SINK_SHFT)
> 
Ok. Will address it in next series.
>> +		}
>> +	}
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
>> +			QCOM_WLED_SINK_CURR_SINK_MASK, sink_en);
>> +	if (rc < 0)
>> +		return rc;
>> +
>> +	rc = qcom_wled_sync_toggle(wled);
>> +	if (rc < 0) {
>> +		pr_err("Failed to toggle sync reg rc:%d\n", rc);
>> +		return rc;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
> [..]
>> +static int qcom_wled_configure(struct qcom_wled *wled, struct device 
>> *dev)
>> +{
>> +	struct qcom_wled_config *cfg = &wled->cfg;
>> +	const __be32 *prop_addr;
>> +	u32 val, c;
>> +	int rc, i, j;
>> +
>> +	const struct {
>> +		const char *name;
>> +		u32 *val_ptr;
>> +		const struct qcom_wled_var_cfg *cfg;
>> +	} u32_opts[] = {
> 
> I suggest that you tie this list of options to the compatible (through
> of_device_id->data) and pass it as a parameter to this function. That
> way you can handle variation in properties and their values between
> different compatibles.
> 
Ok. Will address it in next series.
> [..]
>> +	*cfg = wled_config_defaults;
>> +	for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
>> +		rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
> 
> of_property_read_u32() returns -ENODATA when there's no associated 
> data,
> you can probably use this to implement support for the boolean types in
> the same list of opts.
> 
> [..]
>> +	}
>> +
>> +	for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
>> +		if (of_property_read_bool(dev->of_node, bool_opts[i].name))
>> +			*bool_opts[i].val_ptr = true;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct backlight_ops qcom_wled_ops = {
>> +	.update_status = qcom_wled_update_status,
>> +	.get_brightness = qcom_wled_get_brightness,
>> +};
>> +
>> +static int qcom_wled_probe(struct platform_device *pdev)
>> +{
>> +	struct backlight_properties props;
>> +	struct backlight_device *bl;
>> +	struct qcom_wled *wled;
>> +	struct regmap *regmap;
>> +	u32 val;
>> +	int rc;
>> +
>> +	regmap = dev_get_regmap(pdev->dev.parent, NULL);
>> +	if (!regmap) {
>> +		pr_err("Unable to get regmap\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
>> +	if (!wled)
>> +		return -ENOMEM;
>> +
>> +	wled->regmap = regmap;
>> +	wled->pdev = pdev;
>> +
>> +	rc = qcom_wled_configure(wled, &pdev->dev);
>> +	if (rc < 0) {
>> +		pr_err("wled configure failed rc:%d\n", rc);
> 
> qcom_wled_configure() already printed an error message for you, no need
> to repeat this.
> 
Will address it in next series.
>> +		return rc;
>> +	}
>> +
> 
> Please also run checkpatch.pl with the --strict option and fix the
> indentation issues reported.
> 
Sure.
> Regards,
> Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling
  2017-12-05  4:35   ` Bjorn Andersson
@ 2017-12-11  9:28     ` kgunda
  0 siblings, 0 replies; 28+ messages in thread
From: kgunda @ 2017-12-11  9:28 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-12-05 10:05, Bjorn Andersson wrote:
> On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> 
>> Handle the short circuit(SC) interrupt and check if the SC interrupt
>> is valid. Re-enable the module to check if it goes away. Disable the
>> module altogether if the SC event persists.
>> 
>> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> ---
>>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  22 ++++
>>  drivers/video/backlight/qcom-spmi-wled.c           | 126 
>> ++++++++++++++++++++-
>>  2 files changed, 142 insertions(+), 6 deletions(-)
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> index f1ea25b..768608c 100644
>> --- 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> +++ 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> @@ -74,6 +74,26 @@ The PMIC is connected to the host processor via 
>> SPMI bus.
>>  	Definition: Specify if cabc (content adaptive backlight control) is
>>  		    needed.
>> 
>> +- qcom,ext-pfet-sc-pro-en
> 
> Please use readable names, rather than a bunch of abbreviations.
> 
Ok. Will address it in next series.
>> +	Usage:      optional
>> +	Value type: <bool>
>> +	Definition: Specify if external PFET control for short circuit
>> +		    protection is needed.
> 
> What does this mean? At least change the wording to "...protection is
> used".
> 
Ok. Will address it in next series.
>> +
>> +- interrupts
>> +	Usage:      optional
>> +	Value type: <prop encoded array>
>> +	Definition: Interrupts associated with WLED. Interrupts can be
>> +		    specified as per the encoding listed under
>> +		    Documentation/devicetree/bindings/spmi/
>> +		    qcom,spmi-pmic-arb.txt.
>> +
>> +- interrupt-names
>> +	Usage:      optional
>> +	Value type: <string>
>> +	Definition: Interrupt names associated with the interrupts.
>> +		    Must be "sc-irq".
> 
> This is obviously an irq, so no need to include that in the name. I
> would also prefer if you use the name "short" to make this easier to
> read.
> 
Ok. Will address it in next series.
>> +
>>  Example:
>> 
>>  qcom-wled@d800 {
>> @@ -82,6 +102,8 @@ qcom-wled@d800 {
>>  	reg-names = "qcom-wled-ctrl-base", "qcom-wled-sink-base";
>>  	label = "backlight";
>> 
>> +	interrupts = <0x3 0xd8 0x2 IRQ_TYPE_EDGE_RISING>;
> 
> We tend to write these on the form <decimal, hex, decimal, enum>, 
> please
> follow this.
> 
Ok. Will address it in next series.
>> +	interrupt-names = "sc-irq";
>>  	qcom,fs-current-limit = <25000>;
>>  	qcom,current-boost-limit = <970>;
>>  	qcom,switching-freq = <800>;
>> diff --git a/drivers/video/backlight/qcom-spmi-wled.c 
>> b/drivers/video/backlight/qcom-spmi-wled.c
>> index 14c3adc..7dbaaa7 100644
>> --- a/drivers/video/backlight/qcom-spmi-wled.c
>> +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> @@ -11,6 +11,9 @@
>>   * GNU General Public License for more details.
>>   */
>> 
>> +#include <linux/delay.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/ktime.h>
>>  #include <linux/kernel.h>
>>  #include <linux/backlight.h>
>>  #include <linux/module.h>
>> @@ -23,7 +26,13 @@
>>  #define QCOM_WLED_DEFAULT_BRIGHTNESS		2048
>>  #define  QCOM_WLED_MAX_BRIGHTNESS		4095
>> 
>> +#define QCOM_WLED_SC_DLY_MS			20
>> +#define QCOM_WLED_SC_CNT_MAX			5
>> +#define QCOM_WLED_SC_RESET_CNT_DLY_US		1000000
> 
> With times of this ballpark you can just use jiffies, with this just
> being HZ.
> 
Ok. Will address it in next series.
>> +
>>  /* WLED control registers */
>> +#define QCOM_WLED_CTRL_FAULT_STATUS		0x08
>> +
>>  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>>  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>>  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> @@ -37,6 +46,15 @@
>>  #define QCOM_WLED_CTRL_ILIM			0x4e
>>  #define  QCOM_WLED_CTRL_ILIM_MASK		GENMASK(2, 0)
>> 
>> +#define QCOM_WLED_CTRL_SHORT_PROTECT		0x5e
>> +#define  QCOM_WLED_CTRL_SHORT_EN_MASK		BIT(7)
>> +
>> +#define QCOM_WLED_CTRL_SEC_ACCESS		0xd0
>> +#define  QCOM_WLED_CTRL_SEC_UNLOCK		0xa5
>> +
>> +#define QCOM_WLED_CTRL_TEST1			0xe2
>> +#define  QCOM_WLED_EXT_FET_DTEST2		0x09
>> +
>>  /* WLED sink registers */
>>  #define QCOM_WLED_SINK_CURR_SINK_EN		0x46
>>  #define  QCOM_WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
>> @@ -71,19 +89,23 @@ struct qcom_wled_config {
>>  	u32 switch_freq;
>>  	u32 fs_current;
>>  	u32 string_cfg;
>> +	int sc_irq;
> 
> Keep data parsed directly from DT in the config and move this to
> qcom_wled.
> 
Ok. Will address it in next series.
>>  	bool en_cabc;
>> +	bool ext_pfet_sc_pro_en;
> 
> This name is long and hard to parse. "external_pfet" would be much
> easier to read.
> 
Ok. Will address it in next series.
>>  };
>> 
>>  struct qcom_wled {
>>  	const char *name;
>>  	struct platform_device *pdev;
>>  	struct regmap *regmap;
>> +	struct mutex lock;
>> +	struct qcom_wled_config cfg;
>> +	ktime_t last_sc_event_time;
>>  	u16 sink_addr;
>>  	u16 ctrl_addr;
>>  	u32 brightness;
>> +	u32 sc_count;
>>  	bool prev_state;
>> -
>> -	struct qcom_wled_config cfg;
> 
> Moving this seems unnecessary.
> 
Ok. Will address it in next series.
>>  };
>> 
>>  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>> @@ -157,25 +179,26 @@ static int qcom_wled_update_status(struct 
>> backlight_device *bl)
>>  	    bl->props.state & BL_CORE_FBBLANK)
>>  		brightness = 0;
>> 
>> +	mutex_lock(&wled->lock);
> 
> Is this lock necessary?
> 
Yes. It avoid the race between the upate_status and sc_irq as the module 
is enabled
at one place and disabled at other place respectively.
>> +static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
>> +{
>> +	struct qcom_wled *wled = _wled;
>> +	int rc;
>> +	u32 val;
>> +	s64 elapsed_time;
>> +
>> +	rc = regmap_read(wled->regmap,
>> +		wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS, &val);
>> +	if (rc < 0) {
>> +		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
>> +		return IRQ_HANDLED;
>> +	}
>> +
>> +	wled->sc_count++;
>> +	pr_err("WLED short circuit detected %d times fault_status=%x\n",
>> +		wled->sc_count, val);
> 
> Who will read this and is it worth the extra read of FAULT_STATUS just
> to produce this print?
> 
As this SC irq gets triggered in very rare conditions, i think it is 
okay
to have a print for the information purpose.
>> +	mutex_lock(&wled->lock);
>> +	rc = qcom_wled_module_enable(wled, false);
>> +	if (rc < 0) {
>> +		pr_err("wled disable failed rc:%d\n", rc);
>> +		goto unlock_mutex;
>> +	}
>> +
>> +	elapsed_time = ktime_us_delta(ktime_get(),
>> +				wled->last_sc_event_time);
>> +	if (elapsed_time > QCOM_WLED_SC_RESET_CNT_DLY_US) {
>> +		wled->sc_count = 0;
>> +	} else if (wled->sc_count > QCOM_WLED_SC_CNT_MAX) {
> 
> This isn't really "else elapsed_time was more than DLY_US". Split this
> into:
> 
> if (elapsed_time > xyz)
> 	wled->sc_count = 0;
> 
> if (wled->sc_count > QCOM_WLED_SC_CNT_MAX)
> 	...
> 
Ok. sure.
>> +		pr_err("SC trigged %d times, disabling WLED forever!\n",
> 
> "forever" as in "until someone turns it on again"?
> 
Yes. It is turned on for the next reboot or some one forcefully enables 
it form the
sysfs.

>> +			wled->sc_count);
>> +		goto unlock_mutex;
>> +	}
>> +
>> +	wled->last_sc_event_time = ktime_get();
>> +
>> +	msleep(QCOM_WLED_SC_DLY_MS);
>> +	rc = qcom_wled_module_enable(wled, true);
>> +	if (rc < 0)
>> +		pr_err("wled enable failed rc:%d\n", rc);
>> +
>> +unlock_mutex:
>> +	mutex_unlock(&wled->lock);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>>  static int qcom_wled_setup(struct qcom_wled *wled)
>>  {
>>  	int rc, temp, i;
>>  	u8 sink_en = 0;
>>  	u8 string_cfg = wled->cfg.string_cfg;
>> +	int sc_irq = wled->cfg.sc_irq;
>> 
>>  	rc = regmap_update_bits(wled->regmap,
>>  			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
>> @@ -261,6 +334,39 @@ static int qcom_wled_setup(struct qcom_wled 
>> *wled)
>>  		return rc;
>>  	}
>> 
>> +	if (sc_irq >= 0) {
> 
> I think things will be cleaner if you let qcom_wled_setup() configure
> the hardware based on the wled->cfg (as is done to this point) and then
> deal with the interrupts in a separate code path from the probe
> function.
> 
Ok. sure.
>> +		rc = devm_request_threaded_irq(&wled->pdev->dev, sc_irq,
>> +				NULL, qcom_wled_sc_irq_handler, IRQF_ONESHOT,
>> +				"qcom_wled_sc_irq", wled);
>> +		if (rc < 0) {
>> +			pr_err("Unable to request sc(%d) IRQ(err:%d)\n",
>> +				sc_irq, rc);
> 
> sc_irq is just a number without meaning, no need to print it.
> 
Sure. Will remove it.
>> +			return rc;
>> +		}
>> +
>> +		rc = regmap_update_bits(wled->regmap,
>> +				wled->ctrl_addr + QCOM_WLED_CTRL_SHORT_PROTECT,
>> +				QCOM_WLED_CTRL_SHORT_EN_MASK,
>> +				QCOM_WLED_CTRL_SHORT_EN_MASK);
>> +		if (rc < 0)
>> +			return rc;
>> +
>> +		if (wled->cfg.ext_pfet_sc_pro_en) {
>> +			/* unlock the secure access regisetr */
> 
> Spelling of register, and this operation does "Unlock the secure
> register access" it doesn't unlock the secure access register.
> 
Sure. Will correct it.
>> +			rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> +					QCOM_WLED_CTRL_SEC_ACCESS,
>> +					QCOM_WLED_CTRL_SEC_UNLOCK);
>> +			if (rc < 0)
>> +				return rc;
>> +
>> +			rc = regmap_write(wled->regmap,
>> +					wled->ctrl_addr + QCOM_WLED_CTRL_TEST1,
>> +					QCOM_WLED_EXT_FET_DTEST2);
> 
> What is the relationship between DTEST2 and the external FET?
> External FET is controlled through the DTEST2 register. External FET is 
> not part of the
WLED IP so it is controlled from the DTEST pins.
>> +			if (rc < 0)
>> +				return rc;
>> +		}
>> +	}
>> +
>>  	return 0;
>>  }
>> 
>> @@ -271,6 +377,7 @@ static int qcom_wled_setup(struct qcom_wled *wled)
>>  	.switch_freq = 11,
>>  	.string_cfg = 0xf,
>>  	.en_cabc = 0,
>> +	.ext_pfet_sc_pro_en = 1,
>>  };
>> 
>>  struct qcom_wled_var_cfg {
>> @@ -376,6 +483,7 @@ static int qcom_wled_configure(struct qcom_wled 
>> *wled, struct device *dev)
>>  		bool *val_ptr;
>>  	} bool_opts[] = {
>>  		{ "qcom,en-cabc", &cfg->en_cabc, },
>> +		{ "qcom,ext-pfet-sc-pro", &cfg->ext_pfet_sc_pro_en, },
>>  	};
>> 
>>  	prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
>> @@ -427,6 +535,10 @@ static int qcom_wled_configure(struct qcom_wled 
>> *wled, struct device *dev)
>>  			*bool_opts[i].val_ptr = true;
>>  	}
>> 
>> +	wled->cfg.sc_irq = platform_get_irq_byname(wled->pdev, "sc-irq");
>> +	if (wled->cfg.sc_irq < 0)
>> +		dev_dbg(&wled->pdev->dev, "sc irq is not used\n");
>> +
> 
> Move this to qcom_wled_probe() or into its own code path, together with
> the rest of the sc_irq initialization.
> 
> And as you're not enabling or disabling it you can store it in a local
> variable.
> 
Ok. Sure.
>>  	return 0;
>>  }
>> 
> 
> Regards,
> Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling
  2017-12-05  4:45   ` Bjorn Andersson
@ 2017-12-11  9:31     ` kgunda
  0 siblings, 0 replies; 28+ messages in thread
From: kgunda @ 2017-12-11  9:31 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2017-12-05 10:15, Bjorn Andersson wrote:
> On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> 
>> WLED peripheral has over voltage protection(OVP) circuitry and the OVP
>> fault is notified through an interrupt. Though this fault condition 
>> rising
>> is due to an incorrect hardware configuration is mitigated in the 
>> hardware,
>> it still needs to be detected and handled. Add support for it.
>> 
>> When WLED module is enabled, keep OVP fault interrupt disabled for 10 
>> ms to
>> account for soft start delay.
>> 
>> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> ---
>>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  7 +-
>>  drivers/video/backlight/qcom-spmi-wled.c           | 83 
>> ++++++++++++++++++++++
>>  2 files changed, 87 insertions(+), 3 deletions(-)
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> index 768608c..d39ee93 100644
>> --- 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> +++ 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> @@ -92,7 +92,7 @@ The PMIC is connected to the host processor via SPMI 
>> bus.
>>  	Usage:      optional
>>  	Value type: <string>
>>  	Definition: Interrupt names associated with the interrupts.
>> -		    Must be "sc-irq".
>> +		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> 
> 
> As before, we know this is an IRQ, so omit the -irq from the name.
> 
> [..]
Sure. Will change it in the next series.
>> diff --git a/drivers/video/backlight/qcom-spmi-wled.c 
>> b/drivers/video/backlight/qcom-spmi-wled.c
> [..]
>> @@ -115,6 +123,28 @@ static int qcom_wled_module_enable(struct 
>> qcom_wled *wled, int val)
>>  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>>  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>>  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> +	if (rc < 0)
>> +		return rc;
>> +	/*
>> +	 * Wait for at least 10ms before enabling OVP fault interrupt after
>> +	 * enabling the module so that soft start is completed. Keep the OVP
>> +	 * interrupt disabled when the module is disabled.
>> +	 */
>> +	if (val) {
>> +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> 
> This is sleeping in the brightness/enable code path, can you
> schedule_delayed_work() instead to not block this code path
> unnecessarily?
> 
Sure. Will change it in the next series.
>> +
>> +		if (wled->cfg.ovp_irq > 0 && wled->ovp_irq_disabled) {
>> +			enable_irq(wled->cfg.ovp_irq);
>> +			wled->ovp_irq_disabled = false;
>> +		}
>> +	} else {
>> +		if (wled->cfg.ovp_irq > 0 && !wled->ovp_irq_disabled) {
>> +			disable_irq(wled->cfg.ovp_irq);
>> +			wled->ovp_irq_disabled = true;
>> +		}
>> +	}
>> +
>>  	return rc;
>>  }
>> 
>> @@ -264,12 +294,42 @@ static irqreturn_t qcom_wled_sc_irq_handler(int 
>> irq, void *_wled)
>>  	return IRQ_HANDLED;
>>  }
>> 
>> +static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> +{
>> +	struct qcom_wled *wled = _wled;
>> +	int rc;
>> +	u32 int_sts, fault_sts;
>> +
>> +	rc = regmap_read(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> +	if (rc < 0) {
>> +		pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> +		return IRQ_HANDLED;
>> +	}
>> +
>> +	rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> +			QCOM_WLED_CTRL_FAULT_STATUS, &fault_sts);
>> +	if (rc < 0) {
>> +		pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
>> +		return IRQ_HANDLED;
>> +	}
>> +
>> +	if (fault_sts &
>> +		(QCOM_WLED_CTRL_OVP_FAULT_BIT | QCOM_WLED_CTRL_ILIM_FAULT_BIT))
>> +		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>> +			int_sts, fault_sts);
> 
> All this function does is print things to the log. When is this
> information consumed and by whom? dev_dbg() instead?
> 
Sure. Will change it in the next series.
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>>  static int qcom_wled_setup(struct qcom_wled *wled)
>>  {
>>  	int rc, temp, i;
>>  	u8 sink_en = 0;
>> +	u32 val;
>>  	u8 string_cfg = wled->cfg.string_cfg;
>>  	int sc_irq = wled->cfg.sc_irq;
>> +	int ovp_irq = wled->cfg.ovp_irq;
>> 
>>  	rc = regmap_update_bits(wled->regmap,
>>  			wled->ctrl_addr + QCOM_WLED_CTRL_OVP,
>> @@ -367,6 +427,25 @@ static int qcom_wled_setup(struct qcom_wled 
>> *wled)
>>  		}
>>  	}
>> 
>> +	if (ovp_irq >= 0) {
> 
> As with the previous patch.
> 
> [..]
Sure. Will change it in the next series.
>> @@ -539,6 +618,10 @@ static int qcom_wled_configure(struct qcom_wled 
>> *wled, struct device *dev)
> [..]
>> +	wled->cfg.ovp_irq = platform_get_irq_byname(wled->pdev, "ovp-irq");
>> +	if (wled->cfg.ovp_irq < 0)
>> +		dev_dbg(&wled->pdev->dev, "ovp irq is not used\n");
>> +
> 
> Regards,
> Bjorn

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
  2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
                     ` (2 preceding siblings ...)
  2017-12-05  2:01   ` Bjorn Andersson
@ 2017-12-15 20:30   ` Pavel Machek
  3 siblings, 0 replies; 28+ messages in thread
From: Pavel Machek @ 2017-12-15 20:30 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: bjorn.andersson, linux-arm-msm, Lee Jones, Daniel Thompson,
	Jingoo Han, Richard Purdie, Jacek Anaszewski, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

[-- Attachment #1: Type: text/plain, Size: 2530 bytes --]

On Thu 2017-11-16 17:48:34, Kiran Gunda wrote:
> WLED driver provides the interface to the display driver to
> adjust the brightness of the display backlight.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |  90 ++++
>  drivers/video/backlight/Kconfig                    |   9 +
>  drivers/video/backlight/Makefile                   |   1 +
>  drivers/video/backlight/qcom-spmi-wled.c           | 504 +++++++++++++++++++++
>  4 files changed, 604 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>  create mode 100644 drivers/video/backlight/qcom-spmi-wled.c
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> new file mode 100644
> index 0000000..f1ea25b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> @@ -0,0 +1,90 @@
> +Binding for Qualcomm WLED driver
> +
> +WLED (White Light Emitting Diode) driver is used for controlling display
> +backlight that is part of PMIC on Qualcomm Technologies reference platforms.
> +The PMIC is connected to the host processor via SPMI bus.
> +
> +- compatible
> +	Usage:      required
> +	Value type: <string>
> +	Definition: should be "qcom,pm8998-spmi-wled".
> +
> +- reg
> +	Usage:      required
> +	Value type: <prop-encoded-array>
> +	Definition:  Base address and size of the WLED modules.
> +
> +- reg-names
> +	Usage:      required
> +	Value type: <string>
> +	Definition:  Names associated with base addresses. should be
> +		     "qcom-wled-ctrl-base", "qcom-wled-sink-base".
> +
> +- label
> +	Usage:      required
> +	Value type: <string>
> +	Definition: The name of the backlight device.
> +
> +- default-brightness
> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: brightness value on boot, value from: 0-4095
> +		    default: 2048
> +
> +- qcom,fs-current-limit

I'd add "-uA" suffix here.

> +	Usage:      optional
> +	Value type: <u32>
> +	Definition: per-string full scale current limit in uA. value from
> +		    0 to 30000 with 5000 uA resolution. default: 25000 uA
> +
> +- qcom,current-boost-limit

And -mA suffix here. Similar for other units.

Thanks,
								Pavel


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2017-12-05  5:40   ` Bjorn Andersson
@ 2018-04-19 10:45     ` kgunda
  2018-04-19 15:58       ` Bjorn Andersson
  0 siblings, 1 reply; 28+ messages in thread
From: kgunda @ 2018-04-19 10:45 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner


On 2017-12-05 11:10, Bjorn Andersson wrote:
> On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> 
>> The auto-calibration algorithm checks if the current WLED sink
>> configuration is valid. It tries enabling every sink and checks
>> if the OVP fault is observed. Based on this information it
>> detects and enables the valid sink configuration. Auto calibration
>> will be triggered when the OVP fault interrupts are seen frequently
>> thereby it tries to fix the sink configuration.
>> 
> 
> So it's not auto "calibration" it's auto "detection" of strings?
> 
Hi Bjorn,
Sorry for late response. Please find my answers.

Correct. This is the auto detection, This is the name given by the 
HW/systems team.
> When is this feature needed?
> 
This feature is needed if the string configuration is given wrong in the 
DT node by the
user.
>> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> ---
>>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>>  drivers/video/backlight/qcom-spmi-wled.c           | 304 
>> ++++++++++++++++++++-
>>  2 files changed, 306 insertions(+), 3 deletions(-)
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> index d39ee93..f06c0cd 100644
>> --- 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> +++ 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via 
>> SPMI bus.
>>  	Definition: Interrupt names associated with the interrupts.
>>  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> 
>> +- qcom,auto-calibration
> 
> qcom,auto-string-detect?
> 
ok. Will address in the next patch.
>> +	Usage:      optional
>> +	Value type: <bool>
>> +	Definition: Enables auto-calibration of the WLED sink configuration.
>> +
>>  Example:
>> 
>>  qcom-wled@d800 {
>> diff --git a/drivers/video/backlight/qcom-spmi-wled.c 
>> b/drivers/video/backlight/qcom-spmi-wled.c
>> index 8b2a77a..aee5c56 100644
>> --- a/drivers/video/backlight/qcom-spmi-wled.c
>> +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> @@ -38,11 +38,14 @@
>>  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>> 
>>  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>> +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
> 
> The use of BIT() makes this a mask and not a bit number, so if you just
> drop that you can afford to spell out the "FAULT" like the data sheet
> does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
> 
ok. Will change it in the next series.
>> 
>>  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>>  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>>  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> 
>> +#define QCOM_WLED_CTRL_FDBK_OP			0x48
> 
> This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
> unreadable?
> 
Ok. Will address it in next series.
>> +
>>  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>>  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> 
>> @@ -99,6 +102,7 @@ struct qcom_wled_config {
>>  	int ovp_irq;
>>  	bool en_cabc;
>>  	bool ext_pfet_sc_pro_en;
>> +	bool auto_calib_enabled;
>>  };
>> 
>>  struct qcom_wled {
>> @@ -108,18 +112,25 @@ struct qcom_wled {
>>  	struct mutex lock;
>>  	struct qcom_wled_config cfg;
>>  	ktime_t last_sc_event_time;
>> +	ktime_t start_ovp_fault_time;
>>  	u16 sink_addr;
>>  	u16 ctrl_addr;
>> +	u16 auto_calibration_ovp_count;
>>  	u32 brightness;
>>  	u32 sc_count;
>>  	bool prev_state;
>>  	bool ovp_irq_disabled;
>> +	bool auto_calib_done;
>> +	bool force_mod_disable;
>>  };
>> 
>>  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>>  {
>>  	int rc;
>> 
>> +	if (wled->force_mod_disable)
>> +		return 0;
>> +
>>  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>>  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>>  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct 
>> qcom_wled *wled, u16 brightness)
>>  	v[1] = (brightness >> 8) & 0xf;
>> 
>>  	for (i = 0; (string_cfg >> i) != 0; i++) {
>> -		if (string_cfg & BIT(i)) {
> 
> Why was this check here in the first place, if it's now fine to
> configure the brightness of all strings?
> 
> Also, a single-string config of 0b0001 will only set brightness on the
> first string, while 0b1000 will set brightness on all strings.
> 
I will correct/remove it next series.
>>  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>>  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>>  			if (rc < 0)
>>  				return rc;
>> -		}
>>  	}
>> 
>>  	return 0;
>> @@ -294,6 +303,262 @@ static irqreturn_t qcom_wled_sc_irq_handler(int 
>> irq, void *_wled)
>>  	return IRQ_HANDLED;
>>  }
>> 
>> +#define AUTO_CALIB_BRIGHTNESS		200
>> +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>> +{
>> +	int rc = 0, i;
>> +	u32 sink_config = 0, int_sts;
>> +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>> +	u8 string_cfg = wled->cfg.string_cfg;
>> +
>> +	/* read configured sink configuration */
>> +	rc = regmap_read(wled->regmap, wled->sink_addr +
>> +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
>> +	if (rc < 0) {
>> +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	/* disable the module before starting calibration */
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> +	if (rc < 0) {
>> +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>> +		goto failed_calib;
>> +	}
> 
> Any error handling beyond this point seems to leave the backlight off
> (indefinitely?), this does seem like potentially bad user experience...
Ok. will address in next series.
> 
> In particular I wonder about the case when this would happen at some
> random time, minutes, hours, days, months after the device was booted.
> 
This will happen for every reboot.
>> +
>> +	/* set low brightness across all sinks */
>> +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
>> +	if (rc < 0) {
>> +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	if (wled->cfg.en_cabc) {
>> +		for (i = 0; (string_cfg >> i) != 0; i++) {
>> +			reg = 0;
>> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> +					QCOM_WLED_SINK_CABC_REG(i),
>> +					QCOM_WLED_SINK_CABC_MASK, reg);
> 
> Just replace "reg" with 0.
Ok. will address in next series.
> 
>> +			if (rc < 0)
>> +				goto failed_calib;
>> +		}
>> +	}
>> +
>> +	/* disable all sinks */
>> +	rc = regmap_write(wled->regmap,
>> +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
>> +	if (rc < 0) {
>> +		pr_err("Failed to disable all sinks rc=%d\n", rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	/* iterate through the strings one by one */
>> +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> 
> BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> 
Will address in next series.
>> +
>> +		/* Enable feedback control */
>> +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
>> +		if (rc < 0) {
>> +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
>> +				i + 1, rc);
>> +			goto failed_calib;
>> +		}
>> +
>> +		/* enable the sink */
>> +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
>> +		if (rc < 0) {
>> +			pr_err("Failed to configure SINK %d rc=%d\n",
>> +						i + 1, rc);
>> +			goto failed_calib;
>> +		}
>> +
>> +		/* Enable the module */
>> +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> +				QCOM_WLED_CTRL_MOD_ENABLE,
>> +				QCOM_WLED_CTRL_MOD_EN_MASK,
>> +				QCOM_WLED_CTRL_MOD_EN_MASK);
> 
> I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
> makes me wonder why it's done differently in qcom_wled_module_enable().
> 
will address it in the next series.
>> +		if (rc < 0) {
>> +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +
>> +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>> +
>> +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> +		if (rc < 0) {
>> +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +
>> +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>> +			pr_debug("WLED OVP fault detected with SINK %d\n",
>> +						i + 1);
>> +		else
>> +			sink_valid |= sink_test;
>> +
>> +		/* Disable the module */
>> +		rc = regmap_update_bits(wled->regmap,
>> +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> +		if (rc < 0) {
>> +			pr_err("Failed to disable WLED module rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +	}
>> +
>> +	if (sink_valid == sink_config) {
>> +		pr_debug("WLED auto-calibration complete, default sink-config=%x 
>> OK!\n",
>> +						sink_config);
>> +	} else {
>> +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
>> +						sink_config, sink_valid);
>> +		sink_config = sink_valid;
>> +	}
>> +
>> +	if (!sink_config) {
>> +		pr_warn("No valid WLED sinks found\n");
>> +		wled->force_mod_disable = true;
>> +		goto failed_calib;
>> +	}
>> +
>> +	/* write the new sink configuration */
>> +	rc = regmap_write(wled->regmap,
>> +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
>> +			sink_config);
>> +	if (rc < 0) {
>> +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	/* MODULATOR_EN setting for valid sinks */
> 
> "Enable valid sinks"
> 
Will address it in the next series.
>> +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> +		if (wled->cfg.en_cabc) {
>> +			reg = QCOM_WLED_SINK_CABC_EN;
> 
> "reg" is a bad name of a variable holding the "value" to be written to 
> a
> register.
> 
Will address it in the next series.
>> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> +					QCOM_WLED_SINK_CABC_REG(i),
>> +					QCOM_WLED_SINK_CABC_MASK, reg);
> 
> Again, just inline the value in the function call.
> 
Will address it in the next series.
>> +			if (rc < 0)
>> +				goto failed_calib;
>> +		}
>> +
>> +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
> 
> BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
> 
Will address it in the next series.
>> +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>> +		else
>> +			reg = 0x0; /* disable modulator_en for unused sink */
>> +
>> +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>> +		if (rc < 0) {
>> +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +	}
>> +
>> +	/* restore the feedback setting */
>> +	rc = regmap_write(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
>> +	if (rc < 0) {
>> +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	/* restore  brightness */
>> +	rc = qcom_wled_set_brightness(wled, wled->brightness);
>> +	if (rc < 0) {
>> +		pr_err("Failed to set brightness after calibration rc=%d\n",
>> +			rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	rc = regmap_update_bits(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> +			QCOM_WLED_CTRL_MOD_EN_MASK,
>> +			QCOM_WLED_CTRL_MOD_EN_MASK);
>> +	if (rc < 0) {
>> +		pr_err("Failed to enable WLED module rc=%d\n", rc);
>> +		goto failed_calib;
>> +	}
>> +
>> +	/* delay for WLED soft-start */
> 
> What comes after this that you want to delay?
> 
> This delay is used to make the OVP IRQ not fire immediately, but as
> we've now successfully executed the string auto detection run we're
> never going to do anything in the OVP handler.
> 
Will correct it in the next series.
>> +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>> +
>> +failed_calib:
>> +	return rc;
>> +}
>> +
>> +#define WLED_AUTO_CAL_OVP_COUNT		5
>> +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>> +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>> +{
>> +	s64 elapsed_time_us;
>> +
>> +	/*
>> +	 * Check if the OVP fault was an occasional one
>> +	 * or if its firing continuously, the latter qualifies
>> +	 * for an auto-calibration check.
>> +	 */
>> +	if (!wled->auto_calibration_ovp_count) {
>> +		wled->start_ovp_fault_time = ktime_get();
>> +		wled->auto_calibration_ovp_count++;
>> +	} else {
>> +		elapsed_time_us = ktime_us_delta(ktime_get(),
>> +				wled->start_ovp_fault_time);
>> +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>> +			wled->auto_calibration_ovp_count = 0;
>> +		else
>> +			wled->auto_calibration_ovp_count++;
>> +
>> +		if (wled->auto_calibration_ovp_count >=
>> +				WLED_AUTO_CAL_OVP_COUNT) {
>> +			wled->auto_calibration_ovp_count = 0;
>> +			return true;
>> +		}
>> +	}
>> +
>> +	return false;
>> +}
>> +
>> +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
> 
> I presume this function is expected to detect if there is a invalid
> configuration at boot and try to figure out which strings are actually
> wired.
> 
Correct.
>> +{
>> +	int rc;
>> +	u32 fault_status = 0, rt_status = 0;
>> +
>> +	if (!wled->cfg.auto_calib_enabled)
>> +		return 0;
>> +
>> +	rc = regmap_read(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>> +			&rt_status);
>> +	if (rc < 0)
>> +		pr_err("Failed to read RT status rc=%d\n", rc);
>> +
>> +	rc = regmap_read(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>> +			&fault_status);
>> +	if (rc < 0)
>> +		pr_err("Failed to read fault status rc=%d\n", rc);
>> +
>> +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>> +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
> 
> You should be able to drop the extra () around these.
> 
Ok. Will remove it in the next series.
>> +		mutex_lock(&wled->lock);
>> +		rc = qcom_wled_auto_calibrate(wled);
>> +		if (rc < 0)
>> +			pr_err("Failed auto-calibration rc=%d\n", rc);
> 
> qcom_wled_auto_calibrate() did already print, no need to repeat this.
> 
Ok. Will remove this in the next series.
>> +		else
>> +			wled->auto_calib_done = true;
>> +		mutex_unlock(&wled->lock);
>> +	}
>> +
>> +	return rc;
>> +}
>> +
>>  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>>  {
>>  	struct qcom_wled *wled = _wled;
>> @@ -319,6 +584,33 @@ static irqreturn_t qcom_wled_ovp_irq_handler(int 
>> irq, void *_wled)
>>  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>>  			int_sts, fault_sts);
>> 
>> +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>> +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>> +			if (qcom_wled_auto_cal_required(wled)) {
> 
> So this will be invoked only once, iff we didn't boot with a faulty
> configuration in which case the qcom_wled_auto_calibrate_at_init() has
> already done this step and set auto_calib_done.
> 
> 
> Which also would mean that all logic in this handler, beyond the
> printouts, are only ever going to be executed zero or one times.
> 
> Why don't you just do auto-detection during probe (iff the flag is set
> in DT) and you can remove all this extra logic?
> 
I think we have seen a issue, where the OVP interrupt is not getting set
some times during the execution of this function at boot. In that case 
the auto calibration is
done bit later. That's why this code is added.
>> +				mutex_lock(&wled->lock);
>> +				if (wled->cfg.ovp_irq > 0 &&
>> +						!wled->ovp_irq_disabled) {
>> +					disable_irq_nosync(wled->cfg.ovp_irq);
>> +					wled->ovp_irq_disabled = true;
>> +				}
>> +
>> +				rc = qcom_wled_auto_calibrate(wled);
>> +				if (rc < 0)
>> +					pr_err("Failed auto-calibration rc=%d\n",
>> +						rc);
> 
> qcom_wled_auto_calibrate() did already print.
> 
Ok. I will remove it in the next series.
>> +				else
>> +					wled->auto_calib_done = true;
>> +
>> +				if (wled->cfg.ovp_irq > 0 &&
>> +						wled->ovp_irq_disabled) {
>> +					enable_irq(wled->cfg.ovp_irq);
>> +					wled->ovp_irq_disabled = false;
>> +				}
>> +				mutex_unlock(&wled->lock);
>> +			}
>> +		}
>> +	}
>> +
> 
> Regards,
> Bjorn
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2018-04-19 10:45     ` kgunda
@ 2018-04-19 15:58       ` Bjorn Andersson
  2018-04-20  5:43         ` kgunda
  0 siblings, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2018-04-19 15:58 UTC (permalink / raw)
  To: kgunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:

> 
> On 2017-12-05 11:10, Bjorn Andersson wrote:
> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> > 
> > > The auto-calibration algorithm checks if the current WLED sink
> > > configuration is valid. It tries enabling every sink and checks
> > > if the OVP fault is observed. Based on this information it
> > > detects and enables the valid sink configuration. Auto calibration
> > > will be triggered when the OVP fault interrupts are seen frequently
> > > thereby it tries to fix the sink configuration.
> > > 
> > 
> > So it's not auto "calibration" it's auto "detection" of strings?
> > 
> Hi Bjorn,
> Sorry for late response. Please find my answers.
> 

No worries, happy to hear back from you!

> Correct. This is the auto detection, This is the name given by the
> HW/systems team.

I think the name should be considered a "hardware bug", that we can work
around in software (give it a useful name and document what the original
name was).

> > When is this feature needed?
> > 
> This feature is needed if the string configuration is given wrong in
> the DT node by the user.

DT describes the hardware and for all other nodes it must do so
accurately.

For cases where the hardware supports auto detection of functionality we
remove information from DT and rely on that logic to figure out the
hardware. We do not use it to reconfigure the hardware once we detect an
error. So when auto-detection is enabled it should always be used to
probe the hardware.

Regards,
Bjorn

> > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> > > ---
> > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
> > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
> > > ++++++++++++++++++++-
> > >  2 files changed, 306 insertions(+), 3 deletions(-)
> > > 
> > > diff --git
> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > index d39ee93..f06c0cd 100644
> > > ---
> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > +++
> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
> > > SPMI bus.
> > >  	Definition: Interrupt names associated with the interrupts.
> > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
> > > 
> > > +- qcom,auto-calibration
> > 
> > qcom,auto-string-detect?
> > 
> ok. Will address in the next patch.
> > > +	Usage:      optional
> > > +	Value type: <bool>
> > > +	Definition: Enables auto-calibration of the WLED sink configuration.
> > > +
> > >  Example:
> > > 
> > >  qcom-wled@d800 {
> > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
> > > b/drivers/video/backlight/qcom-spmi-wled.c
> > > index 8b2a77a..aee5c56 100644
> > > --- a/drivers/video/backlight/qcom-spmi-wled.c
> > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
> > > @@ -38,11 +38,14 @@
> > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
> > > 
> > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
> > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
> > 
> > The use of BIT() makes this a mask and not a bit number, so if you just
> > drop that you can afford to spell out the "FAULT" like the data sheet
> > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
> > 
> ok. Will change it in the next series.
> > > 
> > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
> > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
> > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
> > > 
> > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
> > 
> > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
> > unreadable?
> > 
> Ok. Will address it in next series.
> > > +
> > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
> > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
> > > 
> > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
> > >  	int ovp_irq;
> > >  	bool en_cabc;
> > >  	bool ext_pfet_sc_pro_en;
> > > +	bool auto_calib_enabled;
> > >  };
> > > 
> > >  struct qcom_wled {
> > > @@ -108,18 +112,25 @@ struct qcom_wled {
> > >  	struct mutex lock;
> > >  	struct qcom_wled_config cfg;
> > >  	ktime_t last_sc_event_time;
> > > +	ktime_t start_ovp_fault_time;
> > >  	u16 sink_addr;
> > >  	u16 ctrl_addr;
> > > +	u16 auto_calibration_ovp_count;
> > >  	u32 brightness;
> > >  	u32 sc_count;
> > >  	bool prev_state;
> > >  	bool ovp_irq_disabled;
> > > +	bool auto_calib_done;
> > > +	bool force_mod_disable;
> > >  };
> > > 
> > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
> > >  {
> > >  	int rc;
> > > 
> > > +	if (wled->force_mod_disable)
> > > +		return 0;
> > > +
> > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
> > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
> > > qcom_wled *wled, u16 brightness)
> > >  	v[1] = (brightness >> 8) & 0xf;
> > > 
> > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > -		if (string_cfg & BIT(i)) {
> > 
> > Why was this check here in the first place, if it's now fine to
> > configure the brightness of all strings?
> > 
> > Also, a single-string config of 0b0001 will only set brightness on the
> > first string, while 0b1000 will set brightness on all strings.
> > 
> I will correct/remove it next series.
> > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
> > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
> > >  			if (rc < 0)
> > >  				return rc;
> > > -		}
> > >  	}
> > > 
> > >  	return 0;
> > > @@ -294,6 +303,262 @@ static irqreturn_t
> > > qcom_wled_sc_irq_handler(int irq, void *_wled)
> > >  	return IRQ_HANDLED;
> > >  }
> > > 
> > > +#define AUTO_CALIB_BRIGHTNESS		200
> > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
> > > +{
> > > +	int rc = 0, i;
> > > +	u32 sink_config = 0, int_sts;
> > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
> > > +	u8 string_cfg = wled->cfg.string_cfg;
> > > +
> > > +	/* read configured sink configuration */
> > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
> > > +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	/* disable the module before starting calibration */
> > > +	rc = regmap_update_bits(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
> > > +		goto failed_calib;
> > > +	}
> > 
> > Any error handling beyond this point seems to leave the backlight off
> > (indefinitely?), this does seem like potentially bad user experience...
> Ok. will address in next series.
> > 
> > In particular I wonder about the case when this would happen at some
> > random time, minutes, hours, days, months after the device was booted.
> > 
> This will happen for every reboot.
> > > +
> > > +	/* set low brightness across all sinks */
> > > +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	if (wled->cfg.en_cabc) {
> > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
> > > +			reg = 0;
> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > 
> > Just replace "reg" with 0.
> Ok. will address in next series.
> > 
> > > +			if (rc < 0)
> > > +				goto failed_calib;
> > > +		}
> > > +	}
> > > +
> > > +	/* disable all sinks */
> > > +	rc = regmap_write(wled->regmap,
> > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to disable all sinks rc=%d\n", rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	/* iterate through the strings one by one */
> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > 
> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > 
> Will address in next series.
> > > +
> > > +		/* Enable feedback control */
> > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
> > > +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
> > > +				i + 1, rc);
> > > +			goto failed_calib;
> > > +		}
> > > +
> > > +		/* enable the sink */
> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
> > > +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to configure SINK %d rc=%d\n",
> > > +						i + 1, rc);
> > > +			goto failed_calib;
> > > +		}
> > > +
> > > +		/* Enable the module */
> > > +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> > > +				QCOM_WLED_CTRL_MOD_ENABLE,
> > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
> > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
> > 
> > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
> > makes me wonder why it's done differently in qcom_wled_module_enable().
> > 
> will address it in the next series.
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +
> > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > +
> > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
> > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> > > +		if (rc < 0) {
> > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +
> > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
> > > +			pr_debug("WLED OVP fault detected with SINK %d\n",
> > > +						i + 1);
> > > +		else
> > > +			sink_valid |= sink_test;
> > > +
> > > +		/* Disable the module */
> > > +		rc = regmap_update_bits(wled->regmap,
> > > +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> > > +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to disable WLED module rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +	}
> > > +
> > > +	if (sink_valid == sink_config) {
> > > +		pr_debug("WLED auto-calibration complete, default sink-config=%x
> > > OK!\n",
> > > +						sink_config);
> > > +	} else {
> > > +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
> > > +						sink_config, sink_valid);
> > > +		sink_config = sink_valid;
> > > +	}
> > > +
> > > +	if (!sink_config) {
> > > +		pr_warn("No valid WLED sinks found\n");
> > > +		wled->force_mod_disable = true;
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	/* write the new sink configuration */
> > > +	rc = regmap_write(wled->regmap,
> > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
> > > +			sink_config);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	/* MODULATOR_EN setting for valid sinks */
> > 
> > "Enable valid sinks"
> > 
> Will address it in the next series.
> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > +		if (wled->cfg.en_cabc) {
> > > +			reg = QCOM_WLED_SINK_CABC_EN;
> > 
> > "reg" is a bad name of a variable holding the "value" to be written to a
> > register.
> > 
> Will address it in the next series.
> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > 
> > Again, just inline the value in the function call.
> > 
> Will address it in the next series.
> > > +			if (rc < 0)
> > > +				goto failed_calib;
> > > +		}
> > > +
> > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
> > 
> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
> > 
> Will address it in the next series.
> > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
> > > +		else
> > > +			reg = 0x0; /* disable modulator_en for unused sink */
> > > +
> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
> > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +	}
> > > +
> > > +	/* restore the feedback setting */
> > > +	rc = regmap_write(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	/* restore  brightness */
> > > +	rc = qcom_wled_set_brightness(wled, wled->brightness);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to set brightness after calibration rc=%d\n",
> > > +			rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	rc = regmap_update_bits(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
> > > +			QCOM_WLED_CTRL_MOD_EN_MASK);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to enable WLED module rc=%d\n", rc);
> > > +		goto failed_calib;
> > > +	}
> > > +
> > > +	/* delay for WLED soft-start */
> > 
> > What comes after this that you want to delay?
> > 
> > This delay is used to make the OVP IRQ not fire immediately, but as
> > we've now successfully executed the string auto detection run we're
> > never going to do anything in the OVP handler.
> > 
> Will correct it in the next series.
> > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > +
> > > +failed_calib:
> > > +	return rc;
> > > +}
> > > +
> > > +#define WLED_AUTO_CAL_OVP_COUNT		5
> > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
> > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
> > > +{
> > > +	s64 elapsed_time_us;
> > > +
> > > +	/*
> > > +	 * Check if the OVP fault was an occasional one
> > > +	 * or if its firing continuously, the latter qualifies
> > > +	 * for an auto-calibration check.
> > > +	 */
> > > +	if (!wled->auto_calibration_ovp_count) {
> > > +		wled->start_ovp_fault_time = ktime_get();
> > > +		wled->auto_calibration_ovp_count++;
> > > +	} else {
> > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
> > > +				wled->start_ovp_fault_time);
> > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
> > > +			wled->auto_calibration_ovp_count = 0;
> > > +		else
> > > +			wled->auto_calibration_ovp_count++;
> > > +
> > > +		if (wled->auto_calibration_ovp_count >=
> > > +				WLED_AUTO_CAL_OVP_COUNT) {
> > > +			wled->auto_calibration_ovp_count = 0;
> > > +			return true;
> > > +		}
> > > +	}
> > > +
> > > +	return false;
> > > +}
> > > +
> > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
> > 
> > I presume this function is expected to detect if there is a invalid
> > configuration at boot and try to figure out which strings are actually
> > wired.
> > 
> Correct.
> > > +{
> > > +	int rc;
> > > +	u32 fault_status = 0, rt_status = 0;
> > > +
> > > +	if (!wled->cfg.auto_calib_enabled)
> > > +		return 0;
> > > +
> > > +	rc = regmap_read(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
> > > +			&rt_status);
> > > +	if (rc < 0)
> > > +		pr_err("Failed to read RT status rc=%d\n", rc);
> > > +
> > > +	rc = regmap_read(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
> > > +			&fault_status);
> > > +	if (rc < 0)
> > > +		pr_err("Failed to read fault status rc=%d\n", rc);
> > > +
> > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
> > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
> > 
> > You should be able to drop the extra () around these.
> > 
> Ok. Will remove it in the next series.
> > > +		mutex_lock(&wled->lock);
> > > +		rc = qcom_wled_auto_calibrate(wled);
> > > +		if (rc < 0)
> > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
> > 
> > qcom_wled_auto_calibrate() did already print, no need to repeat this.
> > 
> Ok. Will remove this in the next series.
> > > +		else
> > > +			wled->auto_calib_done = true;
> > > +		mutex_unlock(&wled->lock);
> > > +	}
> > > +
> > > +	return rc;
> > > +}
> > > +
> > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > >  {
> > >  	struct qcom_wled *wled = _wled;
> > > @@ -319,6 +584,33 @@ static irqreturn_t
> > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
> > >  			int_sts, fault_sts);
> > > 
> > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
> > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
> > > +			if (qcom_wled_auto_cal_required(wled)) {
> > 
> > So this will be invoked only once, iff we didn't boot with a faulty
> > configuration in which case the qcom_wled_auto_calibrate_at_init() has
> > already done this step and set auto_calib_done.
> > 
> > 
> > Which also would mean that all logic in this handler, beyond the
> > printouts, are only ever going to be executed zero or one times.
> > 
> > Why don't you just do auto-detection during probe (iff the flag is set
> > in DT) and you can remove all this extra logic?
> > 
> I think we have seen a issue, where the OVP interrupt is not getting set
> some times during the execution of this function at boot. In that case the
> auto calibration is
> done bit later. That's why this code is added.
> > > +				mutex_lock(&wled->lock);
> > > +				if (wled->cfg.ovp_irq > 0 &&
> > > +						!wled->ovp_irq_disabled) {
> > > +					disable_irq_nosync(wled->cfg.ovp_irq);
> > > +					wled->ovp_irq_disabled = true;
> > > +				}
> > > +
> > > +				rc = qcom_wled_auto_calibrate(wled);
> > > +				if (rc < 0)
> > > +					pr_err("Failed auto-calibration rc=%d\n",
> > > +						rc);
> > 
> > qcom_wled_auto_calibrate() did already print.
> > 
> Ok. I will remove it in the next series.
> > > +				else
> > > +					wled->auto_calib_done = true;
> > > +
> > > +				if (wled->cfg.ovp_irq > 0 &&
> > > +						wled->ovp_irq_disabled) {
> > > +					enable_irq(wled->cfg.ovp_irq);
> > > +					wled->ovp_irq_disabled = false;
> > > +				}
> > > +				mutex_unlock(&wled->lock);
> > > +			}
> > > +		}
> > > +	}
> > > +
> > 
> > Regards,
> > Bjorn
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> > in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2018-04-19 15:58       ` Bjorn Andersson
@ 2018-04-20  5:43         ` kgunda
  2018-04-20 16:03           ` Bjorn Andersson
  2018-04-23 10:35           ` kgunda
  0 siblings, 2 replies; 28+ messages in thread
From: kgunda @ 2018-04-20  5:43 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2018-04-19 21:28, Bjorn Andersson wrote:
> On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
> 
>> 
>> On 2017-12-05 11:10, Bjorn Andersson wrote:
>> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
>> >
>> > > The auto-calibration algorithm checks if the current WLED sink
>> > > configuration is valid. It tries enabling every sink and checks
>> > > if the OVP fault is observed. Based on this information it
>> > > detects and enables the valid sink configuration. Auto calibration
>> > > will be triggered when the OVP fault interrupts are seen frequently
>> > > thereby it tries to fix the sink configuration.
>> > >
>> >
>> > So it's not auto "calibration" it's auto "detection" of strings?
>> >
>> Hi Bjorn,
>> Sorry for late response. Please find my answers.
>> 
> 
> No worries, happy to hear back from you!
> 
Thanks!
>> Correct. This is the auto detection, This is the name given by the
>> HW/systems team.
> 
> I think the name should be considered a "hardware bug", that we can 
> work
> around in software (give it a useful name and document what the 
> original
> name was).
> 
I don't think this is the "hardware bug". Rather we can say HW doesn't 
support it.
Hence, we are implementing it as a SW feature to detect the strings 
present on the
display panel, if the user fails to give the correct strings. As you 
suggested I will
rename this to "auto detection" instead of "auto calibration".

>> > When is this feature needed?
>> >
>> This feature is needed if the string configuration is given wrong in
>> the DT node by the user.
> 
> DT describes the hardware and for all other nodes it must do so
> accurately.
> 
But the user may not be aware of the strings present on the display 
panel or
may be using the same software on different devices which have different 
strings
present.
> For cases where the hardware supports auto detection of functionality 
> we
> remove information from DT and rely on that logic to figure out the
> hardware. We do not use it to reconfigure the hardware once we detect 
> an
> error. So when auto-detection is enabled it should always be used to
> probe the hardware.
> 
The auto string detection is not supported in any qcom hardware and i 
don't
think there is a plan to introduce in new hardware also.

> Regards,
> Bjorn
> 
>> > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> > > ---
>> > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>> > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
>> > > ++++++++++++++++++++-
>> > >  2 files changed, 306 insertions(+), 3 deletions(-)
>> > >
>> > > diff --git
>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > index d39ee93..f06c0cd 100644
>> > > ---
>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > +++
>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
>> > > SPMI bus.
>> > >  	Definition: Interrupt names associated with the interrupts.
>> > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> > >
>> > > +- qcom,auto-calibration
>> >
>> > qcom,auto-string-detect?
>> >
>> ok. Will address in the next patch.
>> > > +	Usage:      optional
>> > > +	Value type: <bool>
>> > > +	Definition: Enables auto-calibration of the WLED sink configuration.
>> > > +
>> > >  Example:
>> > >
>> > >  qcom-wled@d800 {
>> > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
>> > > b/drivers/video/backlight/qcom-spmi-wled.c
>> > > index 8b2a77a..aee5c56 100644
>> > > --- a/drivers/video/backlight/qcom-spmi-wled.c
>> > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> > > @@ -38,11 +38,14 @@
>> > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>> > >
>> > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>> > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
>> >
>> > The use of BIT() makes this a mask and not a bit number, so if you just
>> > drop that you can afford to spell out the "FAULT" like the data sheet
>> > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
>> >
>> ok. Will change it in the next series.
>> > >
>> > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>> > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>> > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> > >
>> > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
>> >
>> > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
>> > unreadable?
>> >
>> Ok. Will address it in next series.
>> > > +
>> > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>> > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> > >
>> > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
>> > >  	int ovp_irq;
>> > >  	bool en_cabc;
>> > >  	bool ext_pfet_sc_pro_en;
>> > > +	bool auto_calib_enabled;
>> > >  };
>> > >
>> > >  struct qcom_wled {
>> > > @@ -108,18 +112,25 @@ struct qcom_wled {
>> > >  	struct mutex lock;
>> > >  	struct qcom_wled_config cfg;
>> > >  	ktime_t last_sc_event_time;
>> > > +	ktime_t start_ovp_fault_time;
>> > >  	u16 sink_addr;
>> > >  	u16 ctrl_addr;
>> > > +	u16 auto_calibration_ovp_count;
>> > >  	u32 brightness;
>> > >  	u32 sc_count;
>> > >  	bool prev_state;
>> > >  	bool ovp_irq_disabled;
>> > > +	bool auto_calib_done;
>> > > +	bool force_mod_disable;
>> > >  };
>> > >
>> > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>> > >  {
>> > >  	int rc;
>> > >
>> > > +	if (wled->force_mod_disable)
>> > > +		return 0;
>> > > +
>> > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>> > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
>> > > qcom_wled *wled, u16 brightness)
>> > >  	v[1] = (brightness >> 8) & 0xf;
>> > >
>> > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > -		if (string_cfg & BIT(i)) {
>> >
>> > Why was this check here in the first place, if it's now fine to
>> > configure the brightness of all strings?
>> >
>> > Also, a single-string config of 0b0001 will only set brightness on the
>> > first string, while 0b1000 will set brightness on all strings.
>> >
>> I will correct/remove it next series.
>> > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>> > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>> > >  			if (rc < 0)
>> > >  				return rc;
>> > > -		}
>> > >  	}
>> > >
>> > >  	return 0;
>> > > @@ -294,6 +303,262 @@ static irqreturn_t
>> > > qcom_wled_sc_irq_handler(int irq, void *_wled)
>> > >  	return IRQ_HANDLED;
>> > >  }
>> > >
>> > > +#define AUTO_CALIB_BRIGHTNESS		200
>> > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>> > > +{
>> > > +	int rc = 0, i;
>> > > +	u32 sink_config = 0, int_sts;
>> > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>> > > +	u8 string_cfg = wled->cfg.string_cfg;
>> > > +
>> > > +	/* read configured sink configuration */
>> > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
>> > > +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	/* disable the module before starting calibration */
>> > > +	rc = regmap_update_bits(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>> > > +		goto failed_calib;
>> > > +	}
>> >
>> > Any error handling beyond this point seems to leave the backlight off
>> > (indefinitely?), this does seem like potentially bad user experience...
>> Ok. will address in next series.
>> >
>> > In particular I wonder about the case when this would happen at some
>> > random time, minutes, hours, days, months after the device was booted.
>> >
>> This will happen for every reboot.
>> > > +
>> > > +	/* set low brightness across all sinks */
>> > > +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	if (wled->cfg.en_cabc) {
>> > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > +			reg = 0;
>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> >
>> > Just replace "reg" with 0.
>> Ok. will address in next series.
>> >
>> > > +			if (rc < 0)
>> > > +				goto failed_calib;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	/* disable all sinks */
>> > > +	rc = regmap_write(wled->regmap,
>> > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to disable all sinks rc=%d\n", rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	/* iterate through the strings one by one */
>> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> >
>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> >
>> Will address in next series.
>> > > +
>> > > +		/* Enable feedback control */
>> > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> > > +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
>> > > +				i + 1, rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +
>> > > +		/* enable the sink */
>> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> > > +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to configure SINK %d rc=%d\n",
>> > > +						i + 1, rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +
>> > > +		/* Enable the module */
>> > > +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> > > +				QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
>> >
>> > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
>> > makes me wonder why it's done differently in qcom_wled_module_enable().
>> >
>> will address it in the next series.
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +
>> > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > +
>> > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +
>> > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>> > > +			pr_debug("WLED OVP fault detected with SINK %d\n",
>> > > +						i + 1);
>> > > +		else
>> > > +			sink_valid |= sink_test;
>> > > +
>> > > +		/* Disable the module */
>> > > +		rc = regmap_update_bits(wled->regmap,
>> > > +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to disable WLED module rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	if (sink_valid == sink_config) {
>> > > +		pr_debug("WLED auto-calibration complete, default sink-config=%x
>> > > OK!\n",
>> > > +						sink_config);
>> > > +	} else {
>> > > +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
>> > > +						sink_config, sink_valid);
>> > > +		sink_config = sink_valid;
>> > > +	}
>> > > +
>> > > +	if (!sink_config) {
>> > > +		pr_warn("No valid WLED sinks found\n");
>> > > +		wled->force_mod_disable = true;
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	/* write the new sink configuration */
>> > > +	rc = regmap_write(wled->regmap,
>> > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
>> > > +			sink_config);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	/* MODULATOR_EN setting for valid sinks */
>> >
>> > "Enable valid sinks"
>> >
>> Will address it in the next series.
>> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > +		if (wled->cfg.en_cabc) {
>> > > +			reg = QCOM_WLED_SINK_CABC_EN;
>> >
>> > "reg" is a bad name of a variable holding the "value" to be written to a
>> > register.
>> >
>> Will address it in the next series.
>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> >
>> > Again, just inline the value in the function call.
>> >
>> Will address it in the next series.
>> > > +			if (rc < 0)
>> > > +				goto failed_calib;
>> > > +		}
>> > > +
>> > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
>> >
>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
>> >
>> Will address it in the next series.
>> > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>> > > +		else
>> > > +			reg = 0x0; /* disable modulator_en for unused sink */
>> > > +
>> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	/* restore the feedback setting */
>> > > +	rc = regmap_write(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	/* restore  brightness */
>> > > +	rc = qcom_wled_set_brightness(wled, wled->brightness);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to set brightness after calibration rc=%d\n",
>> > > +			rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	rc = regmap_update_bits(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to enable WLED module rc=%d\n", rc);
>> > > +		goto failed_calib;
>> > > +	}
>> > > +
>> > > +	/* delay for WLED soft-start */
>> >
>> > What comes after this that you want to delay?
>> >
>> > This delay is used to make the OVP IRQ not fire immediately, but as
>> > we've now successfully executed the string auto detection run we're
>> > never going to do anything in the OVP handler.
>> >
>> Will correct it in the next series.
>> > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > +
>> > > +failed_calib:
>> > > +	return rc;
>> > > +}
>> > > +
>> > > +#define WLED_AUTO_CAL_OVP_COUNT		5
>> > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>> > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>> > > +{
>> > > +	s64 elapsed_time_us;
>> > > +
>> > > +	/*
>> > > +	 * Check if the OVP fault was an occasional one
>> > > +	 * or if its firing continuously, the latter qualifies
>> > > +	 * for an auto-calibration check.
>> > > +	 */
>> > > +	if (!wled->auto_calibration_ovp_count) {
>> > > +		wled->start_ovp_fault_time = ktime_get();
>> > > +		wled->auto_calibration_ovp_count++;
>> > > +	} else {
>> > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
>> > > +				wled->start_ovp_fault_time);
>> > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>> > > +			wled->auto_calibration_ovp_count = 0;
>> > > +		else
>> > > +			wled->auto_calibration_ovp_count++;
>> > > +
>> > > +		if (wled->auto_calibration_ovp_count >=
>> > > +				WLED_AUTO_CAL_OVP_COUNT) {
>> > > +			wled->auto_calibration_ovp_count = 0;
>> > > +			return true;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	return false;
>> > > +}
>> > > +
>> > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
>> >
>> > I presume this function is expected to detect if there is a invalid
>> > configuration at boot and try to figure out which strings are actually
>> > wired.
>> >
>> Correct.
>> > > +{
>> > > +	int rc;
>> > > +	u32 fault_status = 0, rt_status = 0;
>> > > +
>> > > +	if (!wled->cfg.auto_calib_enabled)
>> > > +		return 0;
>> > > +
>> > > +	rc = regmap_read(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>> > > +			&rt_status);
>> > > +	if (rc < 0)
>> > > +		pr_err("Failed to read RT status rc=%d\n", rc);
>> > > +
>> > > +	rc = regmap_read(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>> > > +			&fault_status);
>> > > +	if (rc < 0)
>> > > +		pr_err("Failed to read fault status rc=%d\n", rc);
>> > > +
>> > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>> > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
>> >
>> > You should be able to drop the extra () around these.
>> >
>> Ok. Will remove it in the next series.
>> > > +		mutex_lock(&wled->lock);
>> > > +		rc = qcom_wled_auto_calibrate(wled);
>> > > +		if (rc < 0)
>> > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
>> >
>> > qcom_wled_auto_calibrate() did already print, no need to repeat this.
>> >
>> Ok. Will remove this in the next series.
>> > > +		else
>> > > +			wled->auto_calib_done = true;
>> > > +		mutex_unlock(&wled->lock);
>> > > +	}
>> > > +
>> > > +	return rc;
>> > > +}
>> > > +
>> > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > >  {
>> > >  	struct qcom_wled *wled = _wled;
>> > > @@ -319,6 +584,33 @@ static irqreturn_t
>> > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>> > >  			int_sts, fault_sts);
>> > >
>> > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>> > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>> > > +			if (qcom_wled_auto_cal_required(wled)) {
>> >
>> > So this will be invoked only once, iff we didn't boot with a faulty
>> > configuration in which case the qcom_wled_auto_calibrate_at_init() has
>> > already done this step and set auto_calib_done.
>> >
>> >
>> > Which also would mean that all logic in this handler, beyond the
>> > printouts, are only ever going to be executed zero or one times.
>> >
>> > Why don't you just do auto-detection during probe (iff the flag is set
>> > in DT) and you can remove all this extra logic?
>> >
>> I think we have seen a issue, where the OVP interrupt is not getting 
>> set
>> some times during the execution of this function at boot. In that case 
>> the
>> auto calibration is
>> done bit later. That's why this code is added.
>> > > +				mutex_lock(&wled->lock);
>> > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > +						!wled->ovp_irq_disabled) {
>> > > +					disable_irq_nosync(wled->cfg.ovp_irq);
>> > > +					wled->ovp_irq_disabled = true;
>> > > +				}
>> > > +
>> > > +				rc = qcom_wled_auto_calibrate(wled);
>> > > +				if (rc < 0)
>> > > +					pr_err("Failed auto-calibration rc=%d\n",
>> > > +						rc);
>> >
>> > qcom_wled_auto_calibrate() did already print.
>> >
>> Ok. I will remove it in the next series.
>> > > +				else
>> > > +					wled->auto_calib_done = true;
>> > > +
>> > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > +						wled->ovp_irq_disabled) {
>> > > +					enable_irq(wled->cfg.ovp_irq);
>> > > +					wled->ovp_irq_disabled = false;
>> > > +				}
>> > > +				mutex_unlock(&wled->lock);
>> > > +			}
>> > > +		}
>> > > +	}
>> > > +
>> >
>> > Regards,
>> > Bjorn
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>> > in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2018-04-20  5:43         ` kgunda
@ 2018-04-20 16:03           ` Bjorn Andersson
  2018-04-23 11:26             ` kgunda
  2018-04-23 10:35           ` kgunda
  1 sibling, 1 reply; 28+ messages in thread
From: Bjorn Andersson @ 2018-04-20 16:03 UTC (permalink / raw)
  To: kgunda
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On Thu 19 Apr 22:43 PDT 2018, kgunda@codeaurora.org wrote:

> On 2018-04-19 21:28, Bjorn Andersson wrote:
> > On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
> > > On 2017-12-05 11:10, Bjorn Andersson wrote:
[..]
> > > > When is this feature needed?
> > > >
> > > This feature is needed if the string configuration is given wrong in
> > > the DT node by the user.
> > 
> > DT describes the hardware and for all other nodes it must do so
> > accurately.
> > 
> But the user may not be aware of the strings present on the display panel or
> may be using the same software on different devices which have different
> strings present.

Swapping display board would still require an update to the DTS, to
support the new panel. But I think that if you're implementing auto
string detection, it should be used as the mechanism to detect the
strings, not something that is run at some later point in time when we
detect a failure.

> > For cases where the hardware supports auto detection of functionality we
> > remove information from DT and rely on that logic to figure out the
> > hardware. We do not use it to reconfigure the hardware once we detect an
> > error. So when auto-detection is enabled it should always be used to
> > probe the hardware.
> > 
> The auto string detection is not supported in any qcom hardware and i don't
> think there is a plan to introduce in new hardware also.
> 

Sorry, I don't follow. Who is using auto string detection then?

Regards,
Bjorn

> > Regards,
> > Bjorn
> > 
> > > > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> > > > > ---
> > > > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
> > > > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
> > > > > ++++++++++++++++++++-
> > > > >  2 files changed, 306 insertions(+), 3 deletions(-)
> > > > >
> > > > > diff --git
> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > index d39ee93..f06c0cd 100644
> > > > > ---
> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > +++
> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
> > > > > SPMI bus.
> > > > >  	Definition: Interrupt names associated with the interrupts.
> > > > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
> > > > >
> > > > > +- qcom,auto-calibration
> > > >
> > > > qcom,auto-string-detect?
> > > >
> > > ok. Will address in the next patch.
> > > > > +	Usage:      optional
> > > > > +	Value type: <bool>
> > > > > +	Definition: Enables auto-calibration of the WLED sink configuration.
> > > > > +
> > > > >  Example:
> > > > >
> > > > >  qcom-wled@d800 {
> > > > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
> > > > > b/drivers/video/backlight/qcom-spmi-wled.c
> > > > > index 8b2a77a..aee5c56 100644
> > > > > --- a/drivers/video/backlight/qcom-spmi-wled.c
> > > > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
> > > > > @@ -38,11 +38,14 @@
> > > > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
> > > > >
> > > > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
> > > > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
> > > >
> > > > The use of BIT() makes this a mask and not a bit number, so if you just
> > > > drop that you can afford to spell out the "FAULT" like the data sheet
> > > > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
> > > >
> > > ok. Will change it in the next series.
> > > > >
> > > > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
> > > > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
> > > > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
> > > > >
> > > > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
> > > >
> > > > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
> > > > unreadable?
> > > >
> > > Ok. Will address it in next series.
> > > > > +
> > > > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
> > > > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
> > > > >
> > > > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
> > > > >  	int ovp_irq;
> > > > >  	bool en_cabc;
> > > > >  	bool ext_pfet_sc_pro_en;
> > > > > +	bool auto_calib_enabled;
> > > > >  };
> > > > >
> > > > >  struct qcom_wled {
> > > > > @@ -108,18 +112,25 @@ struct qcom_wled {
> > > > >  	struct mutex lock;
> > > > >  	struct qcom_wled_config cfg;
> > > > >  	ktime_t last_sc_event_time;
> > > > > +	ktime_t start_ovp_fault_time;
> > > > >  	u16 sink_addr;
> > > > >  	u16 ctrl_addr;
> > > > > +	u16 auto_calibration_ovp_count;
> > > > >  	u32 brightness;
> > > > >  	u32 sc_count;
> > > > >  	bool prev_state;
> > > > >  	bool ovp_irq_disabled;
> > > > > +	bool auto_calib_done;
> > > > > +	bool force_mod_disable;
> > > > >  };
> > > > >
> > > > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
> > > > >  {
> > > > >  	int rc;
> > > > >
> > > > > +	if (wled->force_mod_disable)
> > > > > +		return 0;
> > > > > +
> > > > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> > > > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
> > > > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> > > > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
> > > > > qcom_wled *wled, u16 brightness)
> > > > >  	v[1] = (brightness >> 8) & 0xf;
> > > > >
> > > > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > -		if (string_cfg & BIT(i)) {
> > > >
> > > > Why was this check here in the first place, if it's now fine to
> > > > configure the brightness of all strings?
> > > >
> > > > Also, a single-string config of 0b0001 will only set brightness on the
> > > > first string, while 0b1000 will set brightness on all strings.
> > > >
> > > I will correct/remove it next series.
> > > > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
> > > > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
> > > > >  			if (rc < 0)
> > > > >  				return rc;
> > > > > -		}
> > > > >  	}
> > > > >
> > > > >  	return 0;
> > > > > @@ -294,6 +303,262 @@ static irqreturn_t
> > > > > qcom_wled_sc_irq_handler(int irq, void *_wled)
> > > > >  	return IRQ_HANDLED;
> > > > >  }
> > > > >
> > > > > +#define AUTO_CALIB_BRIGHTNESS		200
> > > > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
> > > > > +{
> > > > > +	int rc = 0, i;
> > > > > +	u32 sink_config = 0, int_sts;
> > > > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
> > > > > +	u8 string_cfg = wled->cfg.string_cfg;
> > > > > +
> > > > > +	/* read configured sink configuration */
> > > > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
> > > > > +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	/* disable the module before starting calibration */
> > > > > +	rc = regmap_update_bits(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > >
> > > > Any error handling beyond this point seems to leave the backlight off
> > > > (indefinitely?), this does seem like potentially bad user experience...
> > > Ok. will address in next series.
> > > >
> > > > In particular I wonder about the case when this would happen at some
> > > > random time, minutes, hours, days, months after the device was booted.
> > > >
> > > This will happen for every reboot.
> > > > > +
> > > > > +	/* set low brightness across all sinks */
> > > > > +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	if (wled->cfg.en_cabc) {
> > > > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > +			reg = 0;
> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > > >
> > > > Just replace "reg" with 0.
> > > Ok. will address in next series.
> > > >
> > > > > +			if (rc < 0)
> > > > > +				goto failed_calib;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	/* disable all sinks */
> > > > > +	rc = regmap_write(wled->regmap,
> > > > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to disable all sinks rc=%d\n", rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	/* iterate through the strings one by one */
> > > > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > > >
> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > > >
> > > Will address in next series.
> > > > > +
> > > > > +		/* Enable feedback control */
> > > > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
> > > > > +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
> > > > > +				i + 1, rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		/* enable the sink */
> > > > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
> > > > > +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to configure SINK %d rc=%d\n",
> > > > > +						i + 1, rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		/* Enable the module */
> > > > > +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> > > > > +				QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
> > > >
> > > > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
> > > > makes me wonder why it's done differently in qcom_wled_module_enable().
> > > >
> > > will address it in the next series.
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > > > +
> > > > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
> > > > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
> > > > > +			pr_debug("WLED OVP fault detected with SINK %d\n",
> > > > > +						i + 1);
> > > > > +		else
> > > > > +			sink_valid |= sink_test;
> > > > > +
> > > > > +		/* Disable the module */
> > > > > +		rc = regmap_update_bits(wled->regmap,
> > > > > +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to disable WLED module rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	if (sink_valid == sink_config) {
> > > > > +		pr_debug("WLED auto-calibration complete, default sink-config=%x
> > > > > OK!\n",
> > > > > +						sink_config);
> > > > > +	} else {
> > > > > +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
> > > > > +						sink_config, sink_valid);
> > > > > +		sink_config = sink_valid;
> > > > > +	}
> > > > > +
> > > > > +	if (!sink_config) {
> > > > > +		pr_warn("No valid WLED sinks found\n");
> > > > > +		wled->force_mod_disable = true;
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	/* write the new sink configuration */
> > > > > +	rc = regmap_write(wled->regmap,
> > > > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
> > > > > +			sink_config);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	/* MODULATOR_EN setting for valid sinks */
> > > >
> > > > "Enable valid sinks"
> > > >
> > > Will address it in the next series.
> > > > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > +		if (wled->cfg.en_cabc) {
> > > > > +			reg = QCOM_WLED_SINK_CABC_EN;
> > > >
> > > > "reg" is a bad name of a variable holding the "value" to be written to a
> > > > register.
> > > >
> > > Will address it in the next series.
> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > > >
> > > > Again, just inline the value in the function call.
> > > >
> > > Will address it in the next series.
> > > > > +			if (rc < 0)
> > > > > +				goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
> > > >
> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
> > > >
> > > Will address it in the next series.
> > > > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
> > > > > +		else
> > > > > +			reg = 0x0; /* disable modulator_en for unused sink */
> > > > > +
> > > > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
> > > > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	/* restore the feedback setting */
> > > > > +	rc = regmap_write(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	/* restore  brightness */
> > > > > +	rc = qcom_wled_set_brightness(wled, wled->brightness);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to set brightness after calibration rc=%d\n",
> > > > > +			rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	rc = regmap_update_bits(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to enable WLED module rc=%d\n", rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > > > +
> > > > > +	/* delay for WLED soft-start */
> > > >
> > > > What comes after this that you want to delay?
> > > >
> > > > This delay is used to make the OVP IRQ not fire immediately, but as
> > > > we've now successfully executed the string auto detection run we're
> > > > never going to do anything in the OVP handler.
> > > >
> > > Will correct it in the next series.
> > > > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > > > +
> > > > > +failed_calib:
> > > > > +	return rc;
> > > > > +}
> > > > > +
> > > > > +#define WLED_AUTO_CAL_OVP_COUNT		5
> > > > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
> > > > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
> > > > > +{
> > > > > +	s64 elapsed_time_us;
> > > > > +
> > > > > +	/*
> > > > > +	 * Check if the OVP fault was an occasional one
> > > > > +	 * or if its firing continuously, the latter qualifies
> > > > > +	 * for an auto-calibration check.
> > > > > +	 */
> > > > > +	if (!wled->auto_calibration_ovp_count) {
> > > > > +		wled->start_ovp_fault_time = ktime_get();
> > > > > +		wled->auto_calibration_ovp_count++;
> > > > > +	} else {
> > > > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
> > > > > +				wled->start_ovp_fault_time);
> > > > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
> > > > > +			wled->auto_calibration_ovp_count = 0;
> > > > > +		else
> > > > > +			wled->auto_calibration_ovp_count++;
> > > > > +
> > > > > +		if (wled->auto_calibration_ovp_count >=
> > > > > +				WLED_AUTO_CAL_OVP_COUNT) {
> > > > > +			wled->auto_calibration_ovp_count = 0;
> > > > > +			return true;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	return false;
> > > > > +}
> > > > > +
> > > > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
> > > >
> > > > I presume this function is expected to detect if there is a invalid
> > > > configuration at boot and try to figure out which strings are actually
> > > > wired.
> > > >
> > > Correct.
> > > > > +{
> > > > > +	int rc;
> > > > > +	u32 fault_status = 0, rt_status = 0;
> > > > > +
> > > > > +	if (!wled->cfg.auto_calib_enabled)
> > > > > +		return 0;
> > > > > +
> > > > > +	rc = regmap_read(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
> > > > > +			&rt_status);
> > > > > +	if (rc < 0)
> > > > > +		pr_err("Failed to read RT status rc=%d\n", rc);
> > > > > +
> > > > > +	rc = regmap_read(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
> > > > > +			&fault_status);
> > > > > +	if (rc < 0)
> > > > > +		pr_err("Failed to read fault status rc=%d\n", rc);
> > > > > +
> > > > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
> > > > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
> > > >
> > > > You should be able to drop the extra () around these.
> > > >
> > > Ok. Will remove it in the next series.
> > > > > +		mutex_lock(&wled->lock);
> > > > > +		rc = qcom_wled_auto_calibrate(wled);
> > > > > +		if (rc < 0)
> > > > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
> > > >
> > > > qcom_wled_auto_calibrate() did already print, no need to repeat this.
> > > >
> > > Ok. Will remove this in the next series.
> > > > > +		else
> > > > > +			wled->auto_calib_done = true;
> > > > > +		mutex_unlock(&wled->lock);
> > > > > +	}
> > > > > +
> > > > > +	return rc;
> > > > > +}
> > > > > +
> > > > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > > > >  {
> > > > >  	struct qcom_wled *wled = _wled;
> > > > > @@ -319,6 +584,33 @@ static irqreturn_t
> > > > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > > > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
> > > > >  			int_sts, fault_sts);
> > > > >
> > > > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
> > > > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
> > > > > +			if (qcom_wled_auto_cal_required(wled)) {
> > > >
> > > > So this will be invoked only once, iff we didn't boot with a faulty
> > > > configuration in which case the qcom_wled_auto_calibrate_at_init() has
> > > > already done this step and set auto_calib_done.
> > > >
> > > >
> > > > Which also would mean that all logic in this handler, beyond the
> > > > printouts, are only ever going to be executed zero or one times.
> > > >
> > > > Why don't you just do auto-detection during probe (iff the flag is set
> > > > in DT) and you can remove all this extra logic?
> > > >
> > > I think we have seen a issue, where the OVP interrupt is not getting
> > > set
> > > some times during the execution of this function at boot. In that
> > > case the
> > > auto calibration is
> > > done bit later. That's why this code is added.
> > > > > +				mutex_lock(&wled->lock);
> > > > > +				if (wled->cfg.ovp_irq > 0 &&
> > > > > +						!wled->ovp_irq_disabled) {
> > > > > +					disable_irq_nosync(wled->cfg.ovp_irq);
> > > > > +					wled->ovp_irq_disabled = true;
> > > > > +				}
> > > > > +
> > > > > +				rc = qcom_wled_auto_calibrate(wled);
> > > > > +				if (rc < 0)
> > > > > +					pr_err("Failed auto-calibration rc=%d\n",
> > > > > +						rc);
> > > >
> > > > qcom_wled_auto_calibrate() did already print.
> > > >
> > > Ok. I will remove it in the next series.
> > > > > +				else
> > > > > +					wled->auto_calib_done = true;
> > > > > +
> > > > > +				if (wled->cfg.ovp_irq > 0 &&
> > > > > +						wled->ovp_irq_disabled) {
> > > > > +					enable_irq(wled->cfg.ovp_irq);
> > > > > +					wled->ovp_irq_disabled = false;
> > > > > +				}
> > > > > +				mutex_unlock(&wled->lock);
> > > > > +			}
> > > > > +		}
> > > > > +	}
> > > > > +
> > > >
> > > > Regards,
> > > > Bjorn
> > > > --
> > > > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> > > > in
> > > > the body of a message to majordomo@vger.kernel.org
> > > > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> > in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2018-04-20  5:43         ` kgunda
  2018-04-20 16:03           ` Bjorn Andersson
@ 2018-04-23 10:35           ` kgunda
  1 sibling, 0 replies; 28+ messages in thread
From: kgunda @ 2018-04-23 10:35 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2018-04-20 11:13, kgunda@codeaurora.org wrote:
> On 2018-04-19 21:28, Bjorn Andersson wrote:
>> On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
>> 
>>> 
>>> On 2017-12-05 11:10, Bjorn Andersson wrote:
>>> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
>>> >
>>> > > The auto-calibration algorithm checks if the current WLED sink
>>> > > configuration is valid. It tries enabling every sink and checks
>>> > > if the OVP fault is observed. Based on this information it
>>> > > detects and enables the valid sink configuration. Auto calibration
>>> > > will be triggered when the OVP fault interrupts are seen frequently
>>> > > thereby it tries to fix the sink configuration.
>>> > >
>>> >
>>> > So it's not auto "calibration" it's auto "detection" of strings?
>>> >
>>> Hi Bjorn,
>>> Sorry for late response. Please find my answers.
>>> 
>> 
>> No worries, happy to hear back from you!
>> 
> Thanks!
>>> Correct. This is the auto detection, This is the name given by the
>>> HW/systems team.
>> 
>> I think the name should be considered a "hardware bug", that we can 
>> work
>> around in software (give it a useful name and document what the 
>> original
>> name was).
>> 
> I don't think this is the "hardware bug". Rather we can say HW doesn't
> support it.
> Hence, we are implementing it as a SW feature to detect the strings
> present on the
> display panel, if the user fails to give the correct strings. As you
> suggested I will
> rename this to "auto detection" instead of "auto calibration".
> 
>>> > When is this feature needed?
>>> >
>>> This feature is needed if the string configuration is given wrong in
>>> the DT node by the user.
>> 
>> DT describes the hardware and for all other nodes it must do so
>> accurately.
>> 
> But the user may not be aware of the strings present on the display 
> panel or
> may be using the same software on different devices which have 
> different strings
> present.
>> For cases where the hardware supports auto detection of functionality 
>> we
>> remove information from DT and rely on that logic to figure out the
>> hardware. We do not use it to reconfigure the hardware once we detect 
>> an
>> error. So when auto-detection is enabled it should always be used to
>> probe the hardware.
>> 
> The auto string detection is not supported in any qcom hardware and i 
> don't
> think there is a plan to introduce in new hardware also.
> 
>> Regards,
>> Bjorn
>> 
>>> > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>>> > > ---
>>> > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>>> > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
>>> > > ++++++++++++++++++++-
>>> > >  2 files changed, 306 insertions(+), 3 deletions(-)
>>> > >
>>> > > diff --git
>>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > index d39ee93..f06c0cd 100644
>>> > > ---
>>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > +++
>>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
>>> > > SPMI bus.
>>> > >  	Definition: Interrupt names associated with the interrupts.
>>> > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>>> > >
>>> > > +- qcom,auto-calibration
>>> >
>>> > qcom,auto-string-detect?
>>> >
>>> ok. Will address in the next patch.
>>> > > +	Usage:      optional
>>> > > +	Value type: <bool>
>>> > > +	Definition: Enables auto-calibration of the WLED sink configuration.
>>> > > +
>>> > >  Example:
>>> > >
>>> > >  qcom-wled@d800 {
>>> > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
>>> > > b/drivers/video/backlight/qcom-spmi-wled.c
>>> > > index 8b2a77a..aee5c56 100644
>>> > > --- a/drivers/video/backlight/qcom-spmi-wled.c
>>> > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
>>> > > @@ -38,11 +38,14 @@
>>> > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>>> > >
>>> > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>>> > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
>>> >
>>> > The use of BIT() makes this a mask and not a bit number, so if you just
>>> > drop that you can afford to spell out the "FAULT" like the data sheet
>>> > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
>>> >
>>> ok. Will change it in the next series.
>>> > >
>>> > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>>> > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>>> > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>>> > >
>>> > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
>>> >
>>> > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
>>> > unreadable?
>>> >
>>> Ok. Will address it in next series.
>>> > > +
>>> > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>>> > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>>> > >
>>> > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
>>> > >  	int ovp_irq;
>>> > >  	bool en_cabc;
>>> > >  	bool ext_pfet_sc_pro_en;
>>> > > +	bool auto_calib_enabled;
>>> > >  };
>>> > >
>>> > >  struct qcom_wled {
>>> > > @@ -108,18 +112,25 @@ struct qcom_wled {
>>> > >  	struct mutex lock;
>>> > >  	struct qcom_wled_config cfg;
>>> > >  	ktime_t last_sc_event_time;
>>> > > +	ktime_t start_ovp_fault_time;
>>> > >  	u16 sink_addr;
>>> > >  	u16 ctrl_addr;
>>> > > +	u16 auto_calibration_ovp_count;
>>> > >  	u32 brightness;
>>> > >  	u32 sc_count;
>>> > >  	bool prev_state;
>>> > >  	bool ovp_irq_disabled;
>>> > > +	bool auto_calib_done;
>>> > > +	bool force_mod_disable;
>>> > >  };
>>> > >
>>> > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>>> > >  {
>>> > >  	int rc;
>>> > >
>>> > > +	if (wled->force_mod_disable)
>>> > > +		return 0;
>>> > > +
>>> > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>>> > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>>> > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>>> > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
>>> > > qcom_wled *wled, u16 brightness)
>>> > >  	v[1] = (brightness >> 8) & 0xf;
>>> > >
>>> > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > -		if (string_cfg & BIT(i)) {
>>> >
>>> > Why was this check here in the first place, if it's now fine to
>>> > configure the brightness of all strings?
>>> >
>>> > Also, a single-string config of 0b0001 will only set brightness on the
>>> > first string, while 0b1000 will set brightness on all strings.
>>> >
>>> I will correct/remove it next series.
>>> > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>>> > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>>> > >  			if (rc < 0)
>>> > >  				return rc;
>>> > > -		}
>>> > >  	}
>>> > >
>>> > >  	return 0;
>>> > > @@ -294,6 +303,262 @@ static irqreturn_t
>>> > > qcom_wled_sc_irq_handler(int irq, void *_wled)
>>> > >  	return IRQ_HANDLED;
>>> > >  }
>>> > >
>>> > > +#define AUTO_CALIB_BRIGHTNESS		200
>>> > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>>> > > +{
>>> > > +	int rc = 0, i;
>>> > > +	u32 sink_config = 0, int_sts;
>>> > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>>> > > +	u8 string_cfg = wled->cfg.string_cfg;
>>> > > +
>>> > > +	/* read configured sink configuration */
>>> > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
>>> > > +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	/* disable the module before starting calibration */
>>> > > +	rc = regmap_update_bits(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> >
>>> > Any error handling beyond this point seems to leave the backlight off
>>> > (indefinitely?), this does seem like potentially bad user experience...
>>> Ok. will address in next series.
>>> >
It is very unlikely to hit the error path in the below code. As the 
below code
just deals with the registers read/writes through the SPMI interface, 
which is a stable
interface. In worst case if see the errors, there is no use of bypassing 
the error handling
as the register write itself doesn't go through and the complete system 
gets effected. Having
error handling at-least gives some information.
>>> > In particular I wonder about the case when this would happen at some
>>> > random time, minutes, hours, days, months after the device was booted.
>>> >
>>> This will happen for every reboot.
>>> > > +
>>> > > +	/* set low brightness across all sinks */
>>> > > +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	if (wled->cfg.en_cabc) {
>>> > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > +			reg = 0;
>>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>>> >
>>> > Just replace "reg" with 0.
>>> Ok. will address in next series.
>>> >
>>> > > +			if (rc < 0)
>>> > > +				goto failed_calib;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	/* disable all sinks */
>>> > > +	rc = regmap_write(wled->regmap,
>>> > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to disable all sinks rc=%d\n", rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	/* iterate through the strings one by one */
>>> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>>> >
>>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>>> >
>>> Will address in next series.
>>> > > +
>>> > > +		/* Enable feedback control */
>>> > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>>> > > +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
>>> > > +				i + 1, rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		/* enable the sink */
>>> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>>> > > +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to configure SINK %d rc=%d\n",
>>> > > +						i + 1, rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		/* Enable the module */
>>> > > +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>>> > > +				QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
>>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
>>> >
>>> > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
>>> > makes me wonder why it's done differently in qcom_wled_module_enable().
>>> >
>>> will address it in the next series.
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>>> > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>>> > > +
>>> > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>>> > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>>> > > +			pr_debug("WLED OVP fault detected with SINK %d\n",
>>> > > +						i + 1);
>>> > > +		else
>>> > > +			sink_valid |= sink_test;
>>> > > +
>>> > > +		/* Disable the module */
>>> > > +		rc = regmap_update_bits(wled->regmap,
>>> > > +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to disable WLED module rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	if (sink_valid == sink_config) {
>>> > > +		pr_debug("WLED auto-calibration complete, default sink-config=%x
>>> > > OK!\n",
>>> > > +						sink_config);
>>> > > +	} else {
>>> > > +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
>>> > > +						sink_config, sink_valid);
>>> > > +		sink_config = sink_valid;
>>> > > +	}
>>> > > +
>>> > > +	if (!sink_config) {
>>> > > +		pr_warn("No valid WLED sinks found\n");
>>> > > +		wled->force_mod_disable = true;
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	/* write the new sink configuration */
>>> > > +	rc = regmap_write(wled->regmap,
>>> > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
>>> > > +			sink_config);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	/* MODULATOR_EN setting for valid sinks */
>>> >
>>> > "Enable valid sinks"
>>> >
>>> Will address it in the next series.
>>> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > +		if (wled->cfg.en_cabc) {
>>> > > +			reg = QCOM_WLED_SINK_CABC_EN;
>>> >
>>> > "reg" is a bad name of a variable holding the "value" to be written to a
>>> > register.
>>> >
>>> Will address it in the next series.
>>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>>> >
>>> > Again, just inline the value in the function call.
>>> >
>>> Will address it in the next series.
>>> > > +			if (rc < 0)
>>> > > +				goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
>>> >
>>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
>>> >
>>> Will address it in the next series.
>>> > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>>> > > +		else
>>> > > +			reg = 0x0; /* disable modulator_en for unused sink */
>>> > > +
>>> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>>> > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	/* restore the feedback setting */
>>> > > +	rc = regmap_write(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	/* restore  brightness */
>>> > > +	rc = qcom_wled_set_brightness(wled, wled->brightness);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to set brightness after calibration rc=%d\n",
>>> > > +			rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	rc = regmap_update_bits(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
>>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to enable WLED module rc=%d\n", rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> > > +
>>> > > +	/* delay for WLED soft-start */
>>> >
>>> > What comes after this that you want to delay?
>>> >
>>> > This delay is used to make the OVP IRQ not fire immediately, but as
>>> > we've now successfully executed the string auto detection run we're
>>> > never going to do anything in the OVP handler.
>>> >
>>> Will correct it in the next series.
>>> > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>>> > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>>> > > +
>>> > > +failed_calib:
>>> > > +	return rc;
>>> > > +}
>>> > > +
>>> > > +#define WLED_AUTO_CAL_OVP_COUNT		5
>>> > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>>> > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>>> > > +{
>>> > > +	s64 elapsed_time_us;
>>> > > +
>>> > > +	/*
>>> > > +	 * Check if the OVP fault was an occasional one
>>> > > +	 * or if its firing continuously, the latter qualifies
>>> > > +	 * for an auto-calibration check.
>>> > > +	 */
>>> > > +	if (!wled->auto_calibration_ovp_count) {
>>> > > +		wled->start_ovp_fault_time = ktime_get();
>>> > > +		wled->auto_calibration_ovp_count++;
>>> > > +	} else {
>>> > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
>>> > > +				wled->start_ovp_fault_time);
>>> > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>>> > > +			wled->auto_calibration_ovp_count = 0;
>>> > > +		else
>>> > > +			wled->auto_calibration_ovp_count++;
>>> > > +
>>> > > +		if (wled->auto_calibration_ovp_count >=
>>> > > +				WLED_AUTO_CAL_OVP_COUNT) {
>>> > > +			wled->auto_calibration_ovp_count = 0;
>>> > > +			return true;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	return false;
>>> > > +}
>>> > > +
>>> > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
>>> >
>>> > I presume this function is expected to detect if there is a invalid
>>> > configuration at boot and try to figure out which strings are actually
>>> > wired.
>>> >
>>> Correct.
>>> > > +{
>>> > > +	int rc;
>>> > > +	u32 fault_status = 0, rt_status = 0;
>>> > > +
>>> > > +	if (!wled->cfg.auto_calib_enabled)
>>> > > +		return 0;
>>> > > +
>>> > > +	rc = regmap_read(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>>> > > +			&rt_status);
>>> > > +	if (rc < 0)
>>> > > +		pr_err("Failed to read RT status rc=%d\n", rc);
>>> > > +
>>> > > +	rc = regmap_read(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>>> > > +			&fault_status);
>>> > > +	if (rc < 0)
>>> > > +		pr_err("Failed to read fault status rc=%d\n", rc);
>>> > > +
>>> > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>>> > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
>>> >
>>> > You should be able to drop the extra () around these.
>>> >
>>> Ok. Will remove it in the next series.
>>> > > +		mutex_lock(&wled->lock);
>>> > > +		rc = qcom_wled_auto_calibrate(wled);
>>> > > +		if (rc < 0)
>>> > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
>>> >
>>> > qcom_wled_auto_calibrate() did already print, no need to repeat this.
>>> >
>>> Ok. Will remove this in the next series.
>>> > > +		else
>>> > > +			wled->auto_calib_done = true;
>>> > > +		mutex_unlock(&wled->lock);
>>> > > +	}
>>> > > +
>>> > > +	return rc;
>>> > > +}
>>> > > +
>>> > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>>> > >  {
>>> > >  	struct qcom_wled *wled = _wled;
>>> > > @@ -319,6 +584,33 @@ static irqreturn_t
>>> > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
>>> > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>>> > >  			int_sts, fault_sts);
>>> > >
>>> > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>>> > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>>> > > +			if (qcom_wled_auto_cal_required(wled)) {
>>> >
>>> > So this will be invoked only once, iff we didn't boot with a faulty
>>> > configuration in which case the qcom_wled_auto_calibrate_at_init() has
>>> > already done this step and set auto_calib_done.
>>> >
>>> >
>>> > Which also would mean that all logic in this handler, beyond the
>>> > printouts, are only ever going to be executed zero or one times.
>>> >
>>> > Why don't you just do auto-detection during probe (iff the flag is set
>>> > in DT) and you can remove all this extra logic?
>>> >
>>> I think we have seen a issue, where the OVP interrupt is not getting 
>>> set
>>> some times during the execution of this function at boot. In that 
>>> case the
>>> auto calibration is
>>> done bit later. That's why this code is added.
>>> > > +				mutex_lock(&wled->lock);
>>> > > +				if (wled->cfg.ovp_irq > 0 &&
>>> > > +						!wled->ovp_irq_disabled) {
>>> > > +					disable_irq_nosync(wled->cfg.ovp_irq);
>>> > > +					wled->ovp_irq_disabled = true;
>>> > > +				}
>>> > > +
>>> > > +				rc = qcom_wled_auto_calibrate(wled);
>>> > > +				if (rc < 0)
>>> > > +					pr_err("Failed auto-calibration rc=%d\n",
>>> > > +						rc);
>>> >
>>> > qcom_wled_auto_calibrate() did already print.
>>> >
>>> Ok. I will remove it in the next series.
>>> > > +				else
>>> > > +					wled->auto_calib_done = true;
>>> > > +
>>> > > +				if (wled->cfg.ovp_irq > 0 &&
>>> > > +						wled->ovp_irq_disabled) {
>>> > > +					enable_irq(wled->cfg.ovp_irq);
>>> > > +					wled->ovp_irq_disabled = false;
>>> > > +				}
>>> > > +				mutex_unlock(&wled->lock);
>>> > > +			}
>>> > > +		}
>>> > > +	}
>>> > > +
>>> >
>>> > Regards,
>>> > Bjorn
>>> > --
>>> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>>> > in
>>> > the body of a message to majordomo@vger.kernel.org
>>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> --
>> To unsubscribe from this list: send the line "unsubscribe 
>> linux-arm-msm" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support
  2018-04-20 16:03           ` Bjorn Andersson
@ 2018-04-23 11:26             ` kgunda
  0 siblings, 0 replies; 28+ messages in thread
From: kgunda @ 2018-04-23 11:26 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: linux-arm-msm, Lee Jones, Daniel Thompson, Jingoo Han,
	Richard Purdie, Jacek Anaszewski, Pavel Machek, Rob Herring,
	Mark Rutland, Bartlomiej Zolnierkiewicz, linux-leds, devicetree,
	linux-kernel, linux-fbdev, linux-arm-msm-owner

On 2018-04-20 21:33, Bjorn Andersson wrote:
> On Thu 19 Apr 22:43 PDT 2018, kgunda@codeaurora.org wrote:
> 
>> On 2018-04-19 21:28, Bjorn Andersson wrote:
>> > On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
>> > > On 2017-12-05 11:10, Bjorn Andersson wrote:
> [..]
>> > > > When is this feature needed?
>> > > >
>> > > This feature is needed if the string configuration is given wrong in
>> > > the DT node by the user.
>> >
>> > DT describes the hardware and for all other nodes it must do so
>> > accurately.
>> >
>> But the user may not be aware of the strings present on the display 
>> panel or
>> may be using the same software on different devices which have 
>> different
>> strings present.
> 
> Swapping display board would still require an update to the DTS, to
> support the new panel. But I think that if you're implementing auto
> string detection, it should be used as the mechanism to detect the
> strings, not something that is run at some later point in time when we
> detect a failure.
> 
Ok. got your point. Yes, the detection is done during the boot-up 
itself,
not later point.
>> > For cases where the hardware supports auto detection of functionality we
>> > remove information from DT and rely on that logic to figure out the
>> > hardware. We do not use it to reconfigure the hardware once we detect an
>> > error. So when auto-detection is enabled it should always be used to
>> > probe the hardware.
>> >
>> The auto string detection is not supported in any qcom hardware and i 
>> don't
>> think there is a plan to introduce in new hardware also.
>> 
> 
> Sorry, I don't follow. Who is using auto string detection then?
> 
I mean to say. The HW doesn't handle the auto-detection completely in 
the HW
without having the S/W intervention, which we are using now.

> Regards,
> Bjorn
> 
>> > Regards,
>> > Bjorn
>> >
>> > > > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> > > > > ---
>> > > > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>> > > > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
>> > > > > ++++++++++++++++++++-
>> > > > >  2 files changed, 306 insertions(+), 3 deletions(-)
>> > > > >
>> > > > > diff --git
>> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > index d39ee93..f06c0cd 100644
>> > > > > ---
>> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > +++
>> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
>> > > > > SPMI bus.
>> > > > >  	Definition: Interrupt names associated with the interrupts.
>> > > > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> > > > >
>> > > > > +- qcom,auto-calibration
>> > > >
>> > > > qcom,auto-string-detect?
>> > > >
>> > > ok. Will address in the next patch.
>> > > > > +	Usage:      optional
>> > > > > +	Value type: <bool>
>> > > > > +	Definition: Enables auto-calibration of the WLED sink configuration.
>> > > > > +
>> > > > >  Example:
>> > > > >
>> > > > >  qcom-wled@d800 {
>> > > > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > b/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > index 8b2a77a..aee5c56 100644
>> > > > > --- a/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > @@ -38,11 +38,14 @@
>> > > > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>> > > > >
>> > > > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>> > > > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
>> > > >
>> > > > The use of BIT() makes this a mask and not a bit number, so if you just
>> > > > drop that you can afford to spell out the "FAULT" like the data sheet
>> > > > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
>> > > >
>> > > ok. Will change it in the next series.
>> > > > >
>> > > > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>> > > > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>> > > > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> > > > >
>> > > > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
>> > > >
>> > > > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
>> > > > unreadable?
>> > > >
>> > > Ok. Will address it in next series.
>> > > > > +
>> > > > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>> > > > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> > > > >
>> > > > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
>> > > > >  	int ovp_irq;
>> > > > >  	bool en_cabc;
>> > > > >  	bool ext_pfet_sc_pro_en;
>> > > > > +	bool auto_calib_enabled;
>> > > > >  };
>> > > > >
>> > > > >  struct qcom_wled {
>> > > > > @@ -108,18 +112,25 @@ struct qcom_wled {
>> > > > >  	struct mutex lock;
>> > > > >  	struct qcom_wled_config cfg;
>> > > > >  	ktime_t last_sc_event_time;
>> > > > > +	ktime_t start_ovp_fault_time;
>> > > > >  	u16 sink_addr;
>> > > > >  	u16 ctrl_addr;
>> > > > > +	u16 auto_calibration_ovp_count;
>> > > > >  	u32 brightness;
>> > > > >  	u32 sc_count;
>> > > > >  	bool prev_state;
>> > > > >  	bool ovp_irq_disabled;
>> > > > > +	bool auto_calib_done;
>> > > > > +	bool force_mod_disable;
>> > > > >  };
>> > > > >
>> > > > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>> > > > >  {
>> > > > >  	int rc;
>> > > > >
>> > > > > +	if (wled->force_mod_disable)
>> > > > > +		return 0;
>> > > > > +
>> > > > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> > > > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> > > > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
>> > > > > qcom_wled *wled, u16 brightness)
>> > > > >  	v[1] = (brightness >> 8) & 0xf;
>> > > > >
>> > > > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > -		if (string_cfg & BIT(i)) {
>> > > >
>> > > > Why was this check here in the first place, if it's now fine to
>> > > > configure the brightness of all strings?
>> > > >
>> > > > Also, a single-string config of 0b0001 will only set brightness on the
>> > > > first string, while 0b1000 will set brightness on all strings.
>> > > >
>> > > I will correct/remove it next series.
>> > > > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>> > > > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>> > > > >  			if (rc < 0)
>> > > > >  				return rc;
>> > > > > -		}
>> > > > >  	}
>> > > > >
>> > > > >  	return 0;
>> > > > > @@ -294,6 +303,262 @@ static irqreturn_t
>> > > > > qcom_wled_sc_irq_handler(int irq, void *_wled)
>> > > > >  	return IRQ_HANDLED;
>> > > > >  }
>> > > > >
>> > > > > +#define AUTO_CALIB_BRIGHTNESS		200
>> > > > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>> > > > > +{
>> > > > > +	int rc = 0, i;
>> > > > > +	u32 sink_config = 0, int_sts;
>> > > > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>> > > > > +	u8 string_cfg = wled->cfg.string_cfg;
>> > > > > +
>> > > > > +	/* read configured sink configuration */
>> > > > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
>> > > > > +			QCOM_WLED_SINK_CURR_SINK_EN, &sink_config);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to read SINK configuration rc=%d\n", rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* disable the module before starting calibration */
>> > > > > +	rc = regmap_update_bits(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > >
>> > > > Any error handling beyond this point seems to leave the backlight off
>> > > > (indefinitely?), this does seem like potentially bad user experience...
>> > > Ok. will address in next series.
>> > > >
>> > > > In particular I wonder about the case when this would happen at some
>> > > > random time, minutes, hours, days, months after the device was booted.
>> > > >
>> > > This will happen for every reboot.
>> > > > > +
>> > > > > +	/* set low brightness across all sinks */
>> > > > > +	rc = qcom_wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to set brightness for calibration rc=%d\n", rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	if (wled->cfg.en_cabc) {
>> > > > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > +			reg = 0;
>> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> > > >
>> > > > Just replace "reg" with 0.
>> > > Ok. will address in next series.
>> > > >
>> > > > > +			if (rc < 0)
>> > > > > +				goto failed_calib;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	/* disable all sinks */
>> > > > > +	rc = regmap_write(wled->regmap,
>> > > > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN, 0);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to disable all sinks rc=%d\n", rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* iterate through the strings one by one */
>> > > > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > +		sink_test = 1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> > > >
>> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> > > >
>> > > Will address in next series.
>> > > > > +
>> > > > > +		/* Enable feedback control */
>> > > > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> > > > > +				QCOM_WLED_CTRL_FDBK_OP, i + 1);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to enable feedback for SINK %d rc = %d\n",
>> > > > > +				i + 1, rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		/* enable the sink */
>> > > > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> > > > > +				QCOM_WLED_SINK_CURR_SINK_EN, sink_test);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to configure SINK %d rc=%d\n",
>> > > > > +						i + 1, rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		/* Enable the module */
>> > > > > +		rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> > > > > +				QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
>> > > >
>> > > > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
>> > > > makes me wonder why it's done differently in qcom_wled_module_enable().
>> > > >
>> > > will address it in the next series.
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > > > +
>> > > > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> > > > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>> > > > > +			pr_debug("WLED OVP fault detected with SINK %d\n",
>> > > > > +						i + 1);
>> > > > > +		else
>> > > > > +			sink_valid |= sink_test;
>> > > > > +
>> > > > > +		/* Disable the module */
>> > > > > +		rc = regmap_update_bits(wled->regmap,
>> > > > > +				wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to disable WLED module rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	if (sink_valid == sink_config) {
>> > > > > +		pr_debug("WLED auto-calibration complete, default sink-config=%x
>> > > > > OK!\n",
>> > > > > +						sink_config);
>> > > > > +	} else {
>> > > > > +		pr_warn("Invalid WLED default sink config=%x changing it to=%x\n",
>> > > > > +						sink_config, sink_valid);
>> > > > > +		sink_config = sink_valid;
>> > > > > +	}
>> > > > > +
>> > > > > +	if (!sink_config) {
>> > > > > +		pr_warn("No valid WLED sinks found\n");
>> > > > > +		wled->force_mod_disable = true;
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* write the new sink configuration */
>> > > > > +	rc = regmap_write(wled->regmap,
>> > > > > +			wled->sink_addr + QCOM_WLED_SINK_CURR_SINK_EN,
>> > > > > +			sink_config);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* MODULATOR_EN setting for valid sinks */
>> > > >
>> > > > "Enable valid sinks"
>> > > >
>> > > Will address it in the next series.
>> > > > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > +		if (wled->cfg.en_cabc) {
>> > > > > +			reg = QCOM_WLED_SINK_CABC_EN;
>> > > >
>> > > > "reg" is a bad name of a variable holding the "value" to be written to a
>> > > > register.
>> > > >
>> > > Will address it in the next series.
>> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> > > >
>> > > > Again, just inline the value in the function call.
>> > > >
>> > > Will address it in the next series.
>> > > > > +			if (rc < 0)
>> > > > > +				goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
>> > > >
>> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
>> > > >
>> > > Will address it in the next series.
>> > > > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>> > > > > +		else
>> > > > > +			reg = 0x0; /* disable modulator_en for unused sink */
>> > > > > +
>> > > > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> > > > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	/* restore the feedback setting */
>> > > > > +	rc = regmap_write(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FDBK_OP, 0);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to restore feedback setting rc=%d\n", rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* restore  brightness */
>> > > > > +	rc = qcom_wled_set_brightness(wled, wled->brightness);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to set brightness after calibration rc=%d\n",
>> > > > > +			rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	rc = regmap_update_bits(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to enable WLED module rc=%d\n", rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* delay for WLED soft-start */
>> > > >
>> > > > What comes after this that you want to delay?
>> > > >
>> > > > This delay is used to make the OVP IRQ not fire immediately, but as
>> > > > we've now successfully executed the string auto detection run we're
>> > > > never going to do anything in the OVP handler.
>> > > >
>> > > Will correct it in the next series.
>> > > > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > > > +
>> > > > > +failed_calib:
>> > > > > +	return rc;
>> > > > > +}
>> > > > > +
>> > > > > +#define WLED_AUTO_CAL_OVP_COUNT		5
>> > > > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>> > > > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>> > > > > +{
>> > > > > +	s64 elapsed_time_us;
>> > > > > +
>> > > > > +	/*
>> > > > > +	 * Check if the OVP fault was an occasional one
>> > > > > +	 * or if its firing continuously, the latter qualifies
>> > > > > +	 * for an auto-calibration check.
>> > > > > +	 */
>> > > > > +	if (!wled->auto_calibration_ovp_count) {
>> > > > > +		wled->start_ovp_fault_time = ktime_get();
>> > > > > +		wled->auto_calibration_ovp_count++;
>> > > > > +	} else {
>> > > > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
>> > > > > +				wled->start_ovp_fault_time);
>> > > > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>> > > > > +			wled->auto_calibration_ovp_count = 0;
>> > > > > +		else
>> > > > > +			wled->auto_calibration_ovp_count++;
>> > > > > +
>> > > > > +		if (wled->auto_calibration_ovp_count >=
>> > > > > +				WLED_AUTO_CAL_OVP_COUNT) {
>> > > > > +			wled->auto_calibration_ovp_count = 0;
>> > > > > +			return true;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	return false;
>> > > > > +}
>> > > > > +
>> > > > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
>> > > >
>> > > > I presume this function is expected to detect if there is a invalid
>> > > > configuration at boot and try to figure out which strings are actually
>> > > > wired.
>> > > >
>> > > Correct.
>> > > > > +{
>> > > > > +	int rc;
>> > > > > +	u32 fault_status = 0, rt_status = 0;
>> > > > > +
>> > > > > +	if (!wled->cfg.auto_calib_enabled)
>> > > > > +		return 0;
>> > > > > +
>> > > > > +	rc = regmap_read(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>> > > > > +			&rt_status);
>> > > > > +	if (rc < 0)
>> > > > > +		pr_err("Failed to read RT status rc=%d\n", rc);
>> > > > > +
>> > > > > +	rc = regmap_read(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>> > > > > +			&fault_status);
>> > > > > +	if (rc < 0)
>> > > > > +		pr_err("Failed to read fault status rc=%d\n", rc);
>> > > > > +
>> > > > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>> > > > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
>> > > >
>> > > > You should be able to drop the extra () around these.
>> > > >
>> > > Ok. Will remove it in the next series.
>> > > > > +		mutex_lock(&wled->lock);
>> > > > > +		rc = qcom_wled_auto_calibrate(wled);
>> > > > > +		if (rc < 0)
>> > > > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
>> > > >
>> > > > qcom_wled_auto_calibrate() did already print, no need to repeat this.
>> > > >
>> > > Ok. Will remove this in the next series.
>> > > > > +		else
>> > > > > +			wled->auto_calib_done = true;
>> > > > > +		mutex_unlock(&wled->lock);
>> > > > > +	}
>> > > > > +
>> > > > > +	return rc;
>> > > > > +}
>> > > > > +
>> > > > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > > > >  {
>> > > > >  	struct qcom_wled *wled = _wled;
>> > > > > @@ -319,6 +584,33 @@ static irqreturn_t
>> > > > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > > > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>> > > > >  			int_sts, fault_sts);
>> > > > >
>> > > > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>> > > > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>> > > > > +			if (qcom_wled_auto_cal_required(wled)) {
>> > > >
>> > > > So this will be invoked only once, iff we didn't boot with a faulty
>> > > > configuration in which case the qcom_wled_auto_calibrate_at_init() has
>> > > > already done this step and set auto_calib_done.
>> > > >
>> > > >
>> > > > Which also would mean that all logic in this handler, beyond the
>> > > > printouts, are only ever going to be executed zero or one times.
>> > > >
>> > > > Why don't you just do auto-detection during probe (iff the flag is set
>> > > > in DT) and you can remove all this extra logic?
>> > > >
>> > > I think we have seen a issue, where the OVP interrupt is not getting
>> > > set
>> > > some times during the execution of this function at boot. In that
>> > > case the
>> > > auto calibration is
>> > > done bit later. That's why this code is added.
>> > > > > +				mutex_lock(&wled->lock);
>> > > > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > > > +						!wled->ovp_irq_disabled) {
>> > > > > +					disable_irq_nosync(wled->cfg.ovp_irq);
>> > > > > +					wled->ovp_irq_disabled = true;
>> > > > > +				}
>> > > > > +
>> > > > > +				rc = qcom_wled_auto_calibrate(wled);
>> > > > > +				if (rc < 0)
>> > > > > +					pr_err("Failed auto-calibration rc=%d\n",
>> > > > > +						rc);
>> > > >
>> > > > qcom_wled_auto_calibrate() did already print.
>> > > >
>> > > Ok. I will remove it in the next series.
>> > > > > +				else
>> > > > > +					wled->auto_calib_done = true;
>> > > > > +
>> > > > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > > > +						wled->ovp_irq_disabled) {
>> > > > > +					enable_irq(wled->cfg.ovp_irq);
>> > > > > +					wled->ovp_irq_disabled = false;
>> > > > > +				}
>> > > > > +				mutex_unlock(&wled->lock);
>> > > > > +			}
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > >
>> > > > Regards,
>> > > > Bjorn
>> > > > --
>> > > > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>> > > > in
>> > > > the body of a message to majordomo@vger.kernel.org
>> > > > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>> > in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2018-04-23 11:26 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-16 12:18 [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver Kiran Gunda
2017-11-16 12:18 ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
2017-11-16 16:55   ` Bjorn Andersson
2017-11-17  6:36     ` kgunda
2017-11-17  6:56       ` Bjorn Andersson
2017-11-17  8:33         ` Lee Jones
2017-11-17 11:01           ` kgunda
2017-11-17  9:52         ` kgunda
2017-11-17 20:28   ` Rob Herring
2017-12-05  2:01   ` Bjorn Andersson
2017-12-11  9:11     ` kgunda
2017-12-15 20:30   ` Pavel Machek
2017-11-16 12:18 ` [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Kiran Gunda
2017-11-17 20:30   ` Rob Herring
2017-11-20 11:42     ` kgunda
2017-12-05  4:35   ` Bjorn Andersson
2017-12-11  9:28     ` kgunda
2017-11-16 12:18 ` [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling Kiran Gunda
2017-12-05  4:45   ` Bjorn Andersson
2017-12-11  9:31     ` kgunda
2017-11-16 12:18 ` [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support Kiran Gunda
2017-12-05  5:40   ` Bjorn Andersson
2018-04-19 10:45     ` kgunda
2018-04-19 15:58       ` Bjorn Andersson
2018-04-20  5:43         ` kgunda
2018-04-20 16:03           ` Bjorn Andersson
2018-04-23 11:26             ` kgunda
2018-04-23 10:35           ` kgunda

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).