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

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

Merge "qcom: spmi-wled: Add support for wled driver"

parents 3cdc9e0f 45c186cc
Loading
Loading
Loading
Loading
+90 −0
Original line number Diff line number Diff line
Bindings for Qualcomm Technologies, Inc. WLED driver

WLED (White Light Emitting Diode) driver is used for controlling display
backlight that is part of PMIC on Qualcomm Technologies, Inc. reference
platforms. The PMIC is connected to the host processor via SPMI bus.

- compatible
	Usage:      required
	Value type: <string>
	Definition: should be "qcom,pmi8998-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
		     "wled-ctrl-base", "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,pmi8998-spmi-wled";
	reg = <0xd800 0xd900>;
	reg-names = "wled-ctrl-base", "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>;
};
+9 −0
Original line number Diff line number Diff line
@@ -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 Technologies, Inc. WLED Driver"
	select REGMAP
	help
	  If you have the Qualcomm Technologies, Inc. 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
+1 −0
Original line number Diff line number Diff line
@@ -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
+507 −0
Original line number Diff line number Diff line
/* Copyright (c) 2015, Sony Mobile Communications, AB.
 *
 * Copyright (c) 2018, 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.
 */

#define pr_fmt(fmt)	"WLED: %s: " fmt, __func__

#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 WLED_DEFAULT_BRIGHTNESS		2048
#define  WLED_MAX_BRIGHTNESS		4095

/* WLED control registers */
#define WLED_CTRL_MOD_ENABLE		0x46
#define  WLED_CTRL_MOD_EN_MASK		BIT(7)
#define  WLED_CTRL_MODULE_EN_SHIFT		7

#define WLED_CTRL_SWITCH_FREQ		0x4c
#define  WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)

#define WLED_CTRL_OVP			0x4d
#define  WLED_CTRL_OVP_MASK		GENMASK(1, 0)

#define WLED_CTRL_ILIM			0x4e
#define  WLED_CTRL_ILIM_MASK		GENMASK(2, 0)

/* WLED sink registers */
#define WLED_SINK_CURR_SINK_EN		0x46
#define  WLED_SINK_CURR_SINK_MASK		GENMASK(7, 4)
#define  WLED_SINK_CURR_SINK_SHFT		0x04

#define WLED_SINK_SYNC			0x47
#define  WLED_SINK_SYNC_MASK		GENMASK(3, 0)
#define  WLED_SINK_SYNC_LED1		BIT(0)
#define  WLED_SINK_SYNC_LED2		BIT(1)
#define  WLED_SINK_SYNC_LED3		BIT(2)
#define  WLED_SINK_SYNC_LED4		BIT(3)
#define  WLED_SINK_SYNC_CLEAR		0x00

#define WLED_SINK_MOD_EN_REG(n)		(0x50 + (n * 0x10))
#define  WLED_SINK_REG_STR_MOD_MASK	BIT(7)
#define  WLED_SINK_REG_STR_MOD_EN		BIT(7)

#define WLED_SINK_SYNC_DLY_REG(n)		(0x51 + (n * 0x10))
#define WLED_SINK_FS_CURR_REG(n)		(0x52 + (n * 0x10))
#define  WLED_SINK_FS_MASK			GENMASK(3, 0)

#define WLED_SINK_CABC_REG(n)		(0x56 + (n * 0x10))
#define  WLED_SINK_CABC_MASK		BIT(7)
#define  WLED_SINK_CABC_EN			BIT(7)

#define WLED_SINK_BRIGHT_LSB_REG(n)	(0x57 + (n * 0x10))
#define WLED_SINK_BRIGHT_MSB_REG(n)	(0x58 + (n * 0x10))

struct wled_config {
	u32 i_boost_limit;
	u32 ovp;
	u32 switch_freq;
	u32 fs_current;
	u32 string_cfg;
	bool en_cabc;
};

struct wled {
	const char *name;
	struct platform_device *pdev;
	struct regmap *regmap;
	u16 sink_addr;
	u16 ctrl_addr;
	u32 brightness;
	bool prev_state;

	struct wled_config cfg;
};

static int wled_module_enable(struct wled *wled, int val)
{
	int rc;

	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
			WLED_CTRL_MOD_ENABLE, WLED_CTRL_MOD_EN_MASK,
			val << WLED_CTRL_MODULE_EN_SHIFT);
	return rc;
}

static int wled_get_brightness(struct backlight_device *bl)
{
	struct wled *wled = bl_get_data(bl);

	return wled->brightness;
}

static int wled_sync_toggle(struct wled *wled)
{
	int rc;

	rc = regmap_update_bits(wled->regmap,
			wled->sink_addr + WLED_SINK_SYNC,
			WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_MASK);
	if (rc < 0)
		return rc;

	rc = regmap_update_bits(wled->regmap,
			wled->sink_addr + WLED_SINK_SYNC,
			WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_CLEAR);

	return rc;
}

static int wled_set_brightness(struct wled *wled, u16 brightness)
{
	int rc, i;
	u16 low_limit = 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 +
					WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
			if (rc < 0)
				return rc;
		}
	}

	return 0;
}

static int wled_update_status(struct backlight_device *bl)
{
	struct 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 = 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 = wled_module_enable(wled, !!brightness);
			if (rc < 0) {
				pr_err("wled enable failed rc:%d\n", rc);
				return rc;
			}
		}
	} else {
		rc = wled_module_enable(wled, brightness);
		if (rc < 0) {
			pr_err("wled disable failed rc:%d\n", rc);
			return rc;
		}
	}

	wled->prev_state = !!brightness;

	rc = 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 wled_setup(struct 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 + WLED_CTRL_OVP,
			WLED_CTRL_OVP_MASK, wled->cfg.ovp);
	if (rc < 0)
		return rc;

	rc = regmap_update_bits(wled->regmap,
			wled->ctrl_addr + WLED_CTRL_ILIM,
			WLED_CTRL_ILIM_MASK, wled->cfg.i_boost_limit);
	if (rc < 0)
		return rc;

	rc = regmap_update_bits(wled->regmap,
			wled->ctrl_addr + WLED_CTRL_SWITCH_FREQ,
			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 +
					WLED_SINK_MOD_EN_REG(i);

			rc = regmap_update_bits(wled->regmap, addr,
					WLED_SINK_REG_STR_MOD_MASK,
					WLED_SINK_REG_STR_MOD_EN);
			if (rc < 0)
				return rc;

			addr = wled->sink_addr +
					WLED_SINK_FS_CURR_REG(i);
			rc = regmap_update_bits(wled->regmap, addr,
					WLED_SINK_FS_MASK,
					wled->cfg.fs_current);
			if (rc < 0)
				return rc;

			addr = wled->sink_addr +
					WLED_SINK_CABC_REG(i);
			rc = regmap_update_bits(wled->regmap, addr,
					WLED_SINK_CABC_MASK,
					wled->cfg.en_cabc ?
					WLED_SINK_CABC_EN : 0);
			if (rc)
				return rc;

			temp = i + WLED_SINK_CURR_SINK_SHFT;
			sink_en |= 1 << temp;
		}
	}

	rc = regmap_update_bits(wled->regmap,
			wled->sink_addr + WLED_SINK_CURR_SINK_EN,
			WLED_SINK_CURR_SINK_MASK, sink_en);
	if (rc < 0)
		return rc;

	rc = 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 wled_config wled_config_defaults = {
	.i_boost_limit = 4,
	.fs_current = 10,
	.ovp = 1,
	.switch_freq = 11,
	.string_cfg = 0xf,
	.en_cabc = 0,
};

struct 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 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 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 wled_var_cfg wled_ovp_cfg = {
	.values = wled_ovp_values,
	.size = ARRAY_SIZE(wled_ovp_values),
};

static u32 wled_switch_freq_values_fn(u32 idx)
{
	return 9600 / (1 + idx);
}

static const struct wled_var_cfg wled_switch_freq_cfg = {
	.fn = wled_switch_freq_values_fn,
	.size = 16,
};

static const struct wled_var_cfg wled_string_cfg = {
	.size = 16,
};

static u32 wled_values(const struct 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 wled_configure(struct wled *wled, struct device *dev)
{
	struct 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 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 = 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 wled_ops = {
	.update_status = wled_update_status,
	.get_brightness = wled_get_brightness,
};

static int wled_probe(struct platform_device *pdev)
{
	struct backlight_properties props;
	struct backlight_device *bl;
	struct 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 = wled_configure(wled, &pdev->dev);
	if (rc < 0) {
		pr_err("wled configure failed rc:%d\n", rc);
		return rc;
	}

	rc = wled_setup(wled);
	if (rc < 0) {
		pr_err("wled setup failed rc:%d\n", rc);
		return rc;
	}

	val = 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 = WLED_MAX_BRIGHTNESS;
	bl = devm_backlight_device_register(&pdev->dev, pdev->name,
					    &pdev->dev, wled,
					    &wled_ops, &props);
	return PTR_ERR_OR_ZERO(bl);
}

static const struct of_device_id wled_match_table[] = {
	{ .compatible = "qcom,pmi8998-spmi-wled",},
	{ },
};

static struct platform_driver wled_driver = {
	.probe = wled_probe,
	.driver	= {
		.name = "qcom-spmi-wled",
		.of_match_table	= wled_match_table,
	},
};

module_platform_driver(wled_driver);

MODULE_DESCRIPTION("Qualcomm Technologies, Inc. SPMI PMIC WLED driver");
MODULE_LICENSE("GPL v2");