All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kiran Gunda <kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
To: bjorn.andersson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Lee Jones <lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	Daniel Thompson
	<daniel.thompson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	Jingoo Han <jingoohan1-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Richard Purdie <rpurdie-Fm38FmjxZ/leoWH0uzbU5w@public.gmane.org>,
	Jacek Anaszewski
	<jacek.anaszewski-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Pavel Machek <pavel-+ZI9xUNit7I@public.gmane.org>,
	Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>,
	Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>,
	Bartlomiej Zolnierkiewicz
	<b.zolnierkie-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>,
	linux-leds-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: linux-arm-msm-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Kiran Gunda <kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
Subject: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
Date: Thu, 16 Nov 2017 17:48:34 +0530	[thread overview]
Message-ID: <1510834717-21765-2-git-send-email-kgunda@codeaurora.org> (raw)
In-Reply-To: <1510834717-21765-1-git-send-email-kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>

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

Signed-off-by: Kiran Gunda <kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.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

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

WARNING: multiple messages have this Message-ID (diff)
From: Kiran Gunda <kgunda@codeaurora.org>
To: bjorn.andersson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Lee Jones <lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	Daniel Thompson
	<daniel.thompson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	Jingoo Han <jingoohan1-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Richard Purdie <rpurdie-Fm38FmjxZ/leoWH0uzbU5w@public.gmane.org>,
	Jacek Anaszewski
	<jacek.anaszewski-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Pavel Machek <pavel-+ZI9xUNit7I@public.gmane.org>,
	Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>,
	Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>,
	Bartlomiej Zolnierkiewicz
	<b.zolnierkie-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>,
	linux-leds-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: linux-arm-msm-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Kiran Gunda <kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
Subject: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
Date: Thu, 16 Nov 2017 12:30:34 +0000	[thread overview]
Message-ID: <1510834717-21765-2-git-send-email-kgunda@codeaurora.org> (raw)
In-Reply-To: <1510834717-21765-1-git-send-email-kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>

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


WARNING: multiple messages have this Message-ID (diff)
From: Kiran Gunda <kgunda@codeaurora.org>
To: bjorn.andersson@linaro.org, linux-arm-msm@vger.kernel.org,
	Lee Jones <lee.jones@linaro.org>,
	Daniel Thompson <daniel.thompson@linaro.org>,
	Jingoo Han <jingoohan1@gmail.com>,
	Richard Purdie <rpurdie@rpsys.net>,
	Jacek Anaszewski <jacek.anaszewski@gmail.com>,
	Pavel Machek <pavel@ucw.cz>, Rob Herring <robh+dt@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>,
	linux-leds@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-fbdev@vger.kernel.org
Cc: linux-arm-msm-owner@vger.kernel.org, Kiran Gunda <kgunda@codeaurora.org>
Subject: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver
Date: Thu, 16 Nov 2017 17:48:34 +0530	[thread overview]
Message-ID: <1510834717-21765-2-git-send-email-kgunda@codeaurora.org> (raw)
In-Reply-To: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org>

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

  parent reply	other threads:[~2017-11-16 12:18 UTC|newest]

Thread overview: 62+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-11-16 12:18 [PATCH V1 0/4] qcom: spmi-wled: Support for QCOM wled driver Kiran Gunda
     [not found] ` <1510834717-21765-1-git-send-email-kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2017-11-16 12:18   ` Kiran Gunda [this message]
2017-11-16 12:30     ` [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom " Kiran Gunda
2017-11-16 12:18     ` Kiran Gunda
2017-11-16 16:55     ` Bjorn Andersson
2017-11-16 16:55       ` Bjorn Andersson
2017-11-17  6:36       ` kgunda
2017-11-17  6:48         ` kgunda
2017-11-17  6:56         ` Bjorn Andersson
2017-11-17  6:56           ` Bjorn Andersson
2017-11-17  8:33           ` Lee Jones
2017-11-17  8:33             ` Lee Jones
2017-11-17 11:01             ` kgunda
2017-11-17 11:13               ` kgunda
2017-11-17  9:52           ` kgunda-sgV2jX0FEOL9JmXXK+q4OQ
2017-11-17  9:52             ` kgunda
2017-11-17  9:52             ` kgunda
2017-11-17 20:28     ` Rob Herring
2017-11-17 20:28       ` Rob Herring
2017-12-05  2:01     ` Bjorn Andersson
2017-12-05  2:01       ` Bjorn Andersson
2017-12-11  9:11       ` kgunda
2017-12-11  9:23         ` kgunda
     [not found]     ` <1510834717-21765-2-git-send-email-kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2017-12-15 20:30       ` Pavel Machek
2017-12-15 20:30         ` Pavel Machek
2017-12-15 20:30         ` Pavel Machek
2017-11-16 12:18   ` [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support Kiran Gunda
2017-11-16 12:30     ` Kiran Gunda
2017-11-16 12:18     ` Kiran Gunda
2017-12-05  5:40     ` Bjorn Andersson
2017-12-05  5:40       ` Bjorn Andersson
2018-04-19 10:45       ` kgunda
2018-04-19 10:57         ` kgunda
2018-04-19 15:58         ` Bjorn Andersson
2018-04-19 15:58           ` Bjorn Andersson
2018-04-20  5:43           ` kgunda
2018-04-20  5:55             ` kgunda
2018-04-20 16:03             ` Bjorn Andersson
2018-04-20 16:03               ` Bjorn Andersson
2018-04-23 11:26               ` kgunda
2018-04-23 11:38                 ` kgunda
2018-04-23 10:35             ` kgunda
2018-04-23 10:47               ` kgunda
2017-11-16 12:18 ` [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Kiran Gunda
2017-11-16 12:30   ` Kiran Gunda
     [not found]   ` <1510834717-21765-3-git-send-email-kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2017-11-17 20:30     ` Rob Herring
2017-11-17 20:30       ` Rob Herring
2017-11-17 20:30       ` Rob Herring
2017-11-20 11:42       ` kgunda
2017-11-20 11:54         ` kgunda
2017-12-05  4:35     ` Bjorn Andersson
2017-12-05  4:35       ` Bjorn Andersson
2017-12-05  4:35       ` Bjorn Andersson
2017-12-11  9:28       ` kgunda
2017-12-11  9:40         ` kgunda
2017-11-16 12:18 ` [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling Kiran Gunda
2017-11-16 12:30   ` Kiran Gunda
     [not found]   ` <1510834717-21765-4-git-send-email-kgunda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2017-12-05  4:45     ` Bjorn Andersson
2017-12-05  4:45       ` Bjorn Andersson
2017-12-05  4:45       ` Bjorn Andersson
2017-12-11  9:31       ` kgunda
2017-12-11  9:43         ` kgunda

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1510834717-21765-2-git-send-email-kgunda@codeaurora.org \
    --to=kgunda-sgv2jx0feol9jmxxk+q4oq@public.gmane.org \
    --cc=b.zolnierkie-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org \
    --cc=bjorn.andersson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
    --cc=daniel.thompson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
    --cc=devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=jacek.anaszewski-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=jingoohan1-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org \
    --cc=linux-arm-msm-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-leds-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=mark.rutland-5wv7dgnIgG8@public.gmane.org \
    --cc=pavel-+ZI9xUNit7I@public.gmane.org \
    --cc=robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org \
    --cc=rpurdie-Fm38FmjxZ/leoWH0uzbU5w@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.