From patchwork Thu Nov 16 12:18:34 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kiran Gunda X-Patchwork-Id: 838528 Return-Path: X-Original-To: incoming-dt@patchwork.ozlabs.org Delivered-To: patchwork-incoming-dt@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=devicetree-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=codeaurora.org header.i=@codeaurora.org header.b="XiDXHgu1"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=codeaurora.org header.i=@codeaurora.org header.b="U6Sdx/sp"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yd0fx5Zv9z9s72 for ; Thu, 16 Nov 2017 23:19:37 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934290AbdKPMTf (ORCPT ); Thu, 16 Nov 2017 07:19:35 -0500 Received: from smtp.codeaurora.org ([198.145.29.96]:53230 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934164AbdKPMTd (ORCPT ); Thu, 16 Nov 2017 07:19:33 -0500 Received: by smtp.codeaurora.org (Postfix, from userid 1000) id DC8D76080B; Thu, 16 Nov 2017 12:19:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834772; bh=MfUMtL2nRqEs+6OARPoJA2QvNF0lOKaEIJddtlM54zg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XiDXHgu1hrsJ35nRpXzPsIKnP81CwoB9JJzsjvyr3Xjyh6QwQJmGBn822XBlD7MVK U0HCIGT2315iw6aNF1mNKw/OMXMju2sm9g9L53IEK6NCOM+68lVXIh6rOR/+vHOhZO PqiDEuWt6Bhs9TTwlEWsdiMOjg4ccIkLpd+E1Gik= X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on pdx-caf-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.8 required=2.0 tests=ALL_TRUSTED,BAYES_00, DKIM_SIGNED, T_DKIM_INVALID autolearn=no autolearn_force=no version=3.4.0 Received: from kgunda-linux.qualcomm.com (blr-c-bdr-fw-01_globalnat_allzones-outside.qualcomm.com [103.229.19.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) (Authenticated sender: kgunda@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id 19035600C9; Thu, 16 Nov 2017 12:19:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834769; bh=MfUMtL2nRqEs+6OARPoJA2QvNF0lOKaEIJddtlM54zg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=U6Sdx/spzOdNL64LgKPuL3LJfFnz5oy6u5TQFTxgJRKpyvoTUHw7CCM7/lSzRSUXH xV95s/xmptJpXY1K4SfBIhansXMqApqfyHcYf5IqXP44a7j99Hm1h1DKzx2U7pdNLK RGnb/TZzA2FQDdiHGumJC8JjHqj9zPAc8S1n43e8= DMARC-Filter: OpenDMARC Filter v1.3.2 smtp.codeaurora.org 19035600C9 Authentication-Results: pdx-caf-mail.web.codeaurora.org; dmarc=none (p=none dis=none) header.from=codeaurora.org Authentication-Results: pdx-caf-mail.web.codeaurora.org; spf=none smtp.mailfrom=kgunda@codeaurora.org From: Kiran Gunda To: bjorn.andersson@linaro.org, linux-arm-msm@vger.kernel.org, Lee Jones , Daniel Thompson , Jingoo Han , Richard Purdie , Jacek Anaszewski , Pavel Machek , Rob Herring , Mark Rutland , Bartlomiej Zolnierkiewicz , 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 Subject: [PATCH V1 1/4] qcom: spmi-wled: Add support for qcom wled driver Date: Thu, 16 Nov 2017 17:48:34 +0530 Message-Id: <1510834717-21765-2-git-send-email-kgunda@codeaurora.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> References: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org WLED driver provides the interface to the display driver to adjust the brightness of the display backlight. Signed-off-by: Kiran Gunda --- .../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: + Definition: should be "qcom,pm8998-spmi-wled". + +- reg + Usage: required + Value type: + Definition: Base address and size of the WLED modules. + +- reg-names + Usage: required + Value type: + Definition: Names associated with base addresses. should be + "qcom-wled-ctrl-base", "qcom-wled-sink-base". + +- label + Usage: required + Value type: + Definition: The name of the backlight device. + +- default-brightness + Usage: optional + Value type: + Definition: brightness value on boot, value from: 0-4095 + default: 2048 + +- qcom,fs-current-limit + Usage: optional + Value type: + 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: + 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: + 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: + Definition: Over-voltage protection limit in mV. values are 31100, + 29600, 19600, 18100. + default: 29600 mV + +- qcom,string-cfg + Usage: optional + Value type: + 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: + 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 +#include +#include +#include +#include +#include +#include + +/* 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"); From patchwork Thu Nov 16 12:18:35 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kiran Gunda X-Patchwork-Id: 838529 Return-Path: X-Original-To: incoming-dt@patchwork.ozlabs.org Delivered-To: patchwork-incoming-dt@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=devicetree-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=codeaurora.org header.i=@codeaurora.org header.b="lIZrJYCU"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=codeaurora.org header.i=@codeaurora.org header.b="Vxx9daZC"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yd0gQ676Zz9s71 for ; Thu, 16 Nov 2017 23:20:02 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934751AbdKPMTt (ORCPT ); Thu, 16 Nov 2017 07:19:49 -0500 Received: from smtp.codeaurora.org ([198.145.29.96]:53376 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934748AbdKPMTm (ORCPT ); Thu, 16 Nov 2017 07:19:42 -0500 Received: by smtp.codeaurora.org (Postfix, from userid 1000) id 4EEAF607EB; Thu, 16 Nov 2017 12:19:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834781; bh=5pBfb8vH6VYTE0JAJwvwuvvnorGBlfD/tLNZ/OGjF+s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lIZrJYCUKIm5KUvvzcsy9w7HcCgyFqSspeTsgtqx5/iOJ+6lxs5YuPDrOoZtdKw+n QN3jBALmM3QWYdZSRJiBecZynRE++YJQgOnKrAndCo+Ixp7qL+A6NMZRw9c3Ml2g3V dZq7EHllBkFHa9johqaRlbv/yKC1psgHj/qX5eBQ= X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on pdx-caf-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.8 required=2.0 tests=ALL_TRUSTED,BAYES_00, DKIM_SIGNED, T_DKIM_INVALID autolearn=no autolearn_force=no version=3.4.0 Received: from kgunda-linux.qualcomm.com (blr-c-bdr-fw-01_globalnat_allzones-outside.qualcomm.com [103.229.19.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) (Authenticated sender: kgunda@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id 269FE607E0; Thu, 16 Nov 2017 12:19:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834779; bh=5pBfb8vH6VYTE0JAJwvwuvvnorGBlfD/tLNZ/OGjF+s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Vxx9daZCkPatkxxu+z+vrZGyhqaLX6UrNIFYSsiqzlsSTqZXPcml0MJ3sgB5/OR1s zVfmqxSobvv31EzUCNGHq1pidrCyZXCECZN3GrBZqbqVD8aP8xf1iFe0mNK0NhdXUr KYgUTRzuLlwaWhjkIRxdl3W81KhjK/kInjv3Wfsw= DMARC-Filter: OpenDMARC Filter v1.3.2 smtp.codeaurora.org 269FE607E0 Authentication-Results: pdx-caf-mail.web.codeaurora.org; dmarc=none (p=none dis=none) header.from=codeaurora.org Authentication-Results: pdx-caf-mail.web.codeaurora.org; spf=none smtp.mailfrom=kgunda@codeaurora.org From: Kiran Gunda To: bjorn.andersson@linaro.org, linux-arm-msm@vger.kernel.org, Lee Jones , Daniel Thompson , Jingoo Han , Richard Purdie , Jacek Anaszewski , Pavel Machek , Rob Herring , Mark Rutland , Bartlomiej Zolnierkiewicz , 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 Subject: [PATCH V1 2/4] qcom: spmi-wled: Add support for short circuit handling Date: Thu, 16 Nov 2017 17:48:35 +0530 Message-Id: <1510834717-21765-3-git-send-email-kgunda@codeaurora.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> References: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org 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 --- .../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: + Definition: Specify if external PFET control for short circuit + protection is needed. + +- interrupts + Usage: optional + Value type: + 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: + 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 +#include +#include #include #include #include @@ -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; From patchwork Thu Nov 16 12:18:36 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kiran Gunda X-Patchwork-Id: 838530 Return-Path: X-Original-To: incoming-dt@patchwork.ozlabs.org Delivered-To: patchwork-incoming-dt@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=devicetree-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=codeaurora.org header.i=@codeaurora.org header.b="Oai7eR+U"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=codeaurora.org header.i=@codeaurora.org header.b="EyDamPSz"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yd0gY5PDcz9s4q for ; Thu, 16 Nov 2017 23:20:09 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934780AbdKPMUI (ORCPT ); Thu, 16 Nov 2017 07:20:08 -0500 Received: from smtp.codeaurora.org ([198.145.29.96]:53492 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934755AbdKPMTu (ORCPT ); Thu, 16 Nov 2017 07:19:50 -0500 Received: by smtp.codeaurora.org (Postfix, from userid 1000) id 59D73607E0; Thu, 16 Nov 2017 12:19:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834789; bh=jaB4yLLLNA831uo8rXnMTZ9G6JFs0oxP1a7P46qrv/A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Oai7eR+UCs7wOZ6tIu+FW/hyNDYWoxP1cFDbyDqX2GvoS69TezYB/3QAoRC20M3Y6 xP2+AnP2vjWeFWwjD3uXDmD0tLcT7CaCHT9CLSoGnpE5q9vvA0SQaAMcoqxADRlmUR 6RjrX2HDJ+K/oopYqs35bg0q1OH9gAuhrYjc/w80= X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on pdx-caf-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.8 required=2.0 tests=ALL_TRUSTED,BAYES_00, DKIM_SIGNED, T_DKIM_INVALID autolearn=no autolearn_force=no version=3.4.0 Received: from kgunda-linux.qualcomm.com (blr-c-bdr-fw-01_globalnat_allzones-outside.qualcomm.com [103.229.19.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) (Authenticated sender: kgunda@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id 846816071C; Thu, 16 Nov 2017 12:19:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834788; bh=jaB4yLLLNA831uo8rXnMTZ9G6JFs0oxP1a7P46qrv/A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EyDamPSz3ymQsM/LfQHH9I4s6zURJ03ZgvnMXAQLn/4uU1TNbQbl9+iiKaz7zXsy8 tFg03Ruvi0rbz2a4dGrksXPNu3ZKiVrZnZO8D+BVpKbcu/vnmm4RpgyHF1PqLB6smZ 4SMdQjZnFZ6lfsq5xvbc/PjwXQec+Tm/ZuPu5Fes= DMARC-Filter: OpenDMARC Filter v1.3.2 smtp.codeaurora.org 846816071C Authentication-Results: pdx-caf-mail.web.codeaurora.org; dmarc=none (p=none dis=none) header.from=codeaurora.org Authentication-Results: pdx-caf-mail.web.codeaurora.org; spf=none smtp.mailfrom=kgunda@codeaurora.org From: Kiran Gunda To: bjorn.andersson@linaro.org, linux-arm-msm@vger.kernel.org, Lee Jones , Daniel Thompson , Jingoo Han , Richard Purdie , Jacek Anaszewski , Pavel Machek , Rob Herring , Mark Rutland , Bartlomiej Zolnierkiewicz , 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 Subject: [PATCH V1 3/4] qcom: spmi-wled: Add support for OVP interrupt handling Date: Thu, 16 Nov 2017 17:48:36 +0530 Message-Id: <1510834717-21765-4-git-send-email-kgunda@codeaurora.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> References: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org 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 --- .../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: 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; } From patchwork Thu Nov 16 12:18:37 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kiran Gunda X-Patchwork-Id: 838531 Return-Path: X-Original-To: incoming-dt@patchwork.ozlabs.org Delivered-To: patchwork-incoming-dt@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=devicetree-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=codeaurora.org header.i=@codeaurora.org header.b="Vqqb1Qpv"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=codeaurora.org header.i=@codeaurora.org header.b="ZIy6ZdsR"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yd0gx6DWMz9s71 for ; Thu, 16 Nov 2017 23:20:29 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934794AbdKPMUN (ORCPT ); Thu, 16 Nov 2017 07:20:13 -0500 Received: from smtp.codeaurora.org ([198.145.29.96]:53688 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934406AbdKPMT7 (ORCPT ); Thu, 16 Nov 2017 07:19:59 -0500 Received: by smtp.codeaurora.org (Postfix, from userid 1000) id C83786080B; Thu, 16 Nov 2017 12:19:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834798; bh=/YQKaJAfnG9crKeKUBDKXdG3l3omjgeE3a7ftnQyPb4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Vqqb1QpvyN/lW28rh02zhIcTd8heOUERw38Z+bf6MUb+8blDW9vJyA7Skw+cKRtm8 JOK5mARxsJ7fWf5Qe69NBenQhHLgGhfTY6wCnG92+5e0TuZQbmgYczd3oHKHmZA7Y/ c5UzsQNQx2nIgiYzAqwJtzUkOpVTjtPhxk8pT084= X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on pdx-caf-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.8 required=2.0 tests=ALL_TRUSTED,BAYES_00, DKIM_SIGNED, T_DKIM_INVALID autolearn=no autolearn_force=no version=3.4.0 Received: from kgunda-linux.qualcomm.com (blr-c-bdr-fw-01_globalnat_allzones-outside.qualcomm.com [103.229.19.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) (Authenticated sender: kgunda@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id A5C4E6080B; Thu, 16 Nov 2017 12:19:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codeaurora.org; s=default; t=1510834796; bh=/YQKaJAfnG9crKeKUBDKXdG3l3omjgeE3a7ftnQyPb4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZIy6ZdsRYPfgFZMjKzF4tuE74ElwjKj1hYVyUYmxHVoteCoOAOlXRAlSm3LOaDAsw aVbKUVGm3G+VWluh+OhcgTRuRkAG9JOZKWC+KzwiLo+Mi7hq0MzILvunuSTCRjDL4r FVS4NfS0spWf/0cLq/Zl/FSGpFFY69TtDRTFGpFI= DMARC-Filter: OpenDMARC Filter v1.3.2 smtp.codeaurora.org A5C4E6080B Authentication-Results: pdx-caf-mail.web.codeaurora.org; dmarc=none (p=none dis=none) header.from=codeaurora.org Authentication-Results: pdx-caf-mail.web.codeaurora.org; spf=none smtp.mailfrom=kgunda@codeaurora.org From: Kiran Gunda To: bjorn.andersson@linaro.org, linux-arm-msm@vger.kernel.org, Lee Jones , Daniel Thompson , Jingoo Han , Richard Purdie , Jacek Anaszewski , Pavel Machek , Rob Herring , Mark Rutland , Bartlomiej Zolnierkiewicz , 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 Subject: [PATCH V1 4/4] qcom: spmi-wled: Add auto-calibration logic support Date: Thu, 16 Nov 2017 17:48:37 +0530 Message-Id: <1510834717-21765-5-git-send-email-kgunda@codeaurora.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> References: <1510834717-21765-1-git-send-email-kgunda@codeaurora.org> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org 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 --- .../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: + 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);