From patchwork Fri Sep 29 00:38:56 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anjelique Melendez X-Patchwork-Id: 727845 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 135A0A49 for ; Fri, 29 Sep 2023 00:39:40 +0000 (UTC) Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F243EB4; Thu, 28 Sep 2023 17:39:38 -0700 (PDT) Received: from pps.filterd (m0279872.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 38T0b6ef017758; Fri, 29 Sep 2023 00:39:30 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=SFxRXi0Xdzvgay0VASoEQKjHGfML5gI3QgQ3NW/jOns=; b=dgBYw3gO+XkamvmKXNgiDbvrBLW3wvbTjsk9/SxUNSaEFO6cqmIwCAEXi6hHKKjqnPhl EnzlxsFVLNLwSUkH2OcF6A+D/TSoba0+t6VoKHXENBMXx0rCXJbUAODJ+6k2eiLttt5s vrPGE2crl6nWnNT1P7O/lK8nJIKURk/JhS6HYkKRU+pot1L1Qce56SOH2WA3Rcz1JRmJ nFu/XMB7iCqZogBofDXZkeRFkXNlqOwlmVfkE/Q3pwtCewzkKdFHv6K65+qlGHXSGkps Iod0G0lKKrKrEmcc2nEkUWwianQMb7h3NKLIBfoxuL4O+Sa/ljXNHGW2gtZUIZpWvu+k RQ== Received: from nalasppmta03.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3tcvg9ay74-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Sep 2023 00:39:30 +0000 Received: from nalasex01a.na.qualcomm.com (nalasex01a.na.qualcomm.com [10.47.209.196]) by NALASPPMTA03.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 38T0dTHU029570 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Sep 2023 00:39:29 GMT Received: from hu-amelende-lv.qualcomm.com (10.49.16.6) by nalasex01a.na.qualcomm.com (10.47.209.196) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.36; Thu, 28 Sep 2023 17:39:26 -0700 From: Anjelique Melendez To: , , , , , , , CC: , , , , , , , , , , , Anjelique Melendez Subject: [PATCH v5 3/7] soc: qcom: add QCOM PBS driver Date: Thu, 28 Sep 2023 17:38:56 -0700 Message-ID: <20230929003901.15086-4-quic_amelende@quicinc.com> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230929003901.15086-1-quic_amelende@quicinc.com> References: <20230929003901.15086-1-quic_amelende@quicinc.com> Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01a.na.qualcomm.com (10.47.209.196) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-ORIG-GUID: pGAPs4BHKSAaE9s6U5_FLCxcxwYcLgEX X-Proofpoint-GUID: pGAPs4BHKSAaE9s6U5_FLCxcxwYcLgEX X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.267,Aquarius:18.0.980,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2023-09-28_23,2023-09-28_03,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 impostorscore=0 priorityscore=1501 lowpriorityscore=0 phishscore=0 adultscore=0 bulkscore=0 suspectscore=0 mlxscore=0 mlxlogscore=999 spamscore=0 malwarescore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2309180000 definitions=main-2309290002 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Add the Qualcomm PBS (Programmable Boot Sequencer) driver. The QCOM PBS driver supports configuring software PBS trigger events through PBS RAM on Qualcomm Technologies, Inc (QTI) PMICs. Signed-off-by: Anjelique Melendez --- drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qcom-pbs.c | 243 ++++++++++++++++++++++++++++++ include/linux/soc/qcom/qcom-pbs.h | 30 ++++ 4 files changed, 283 insertions(+) create mode 100644 drivers/soc/qcom/qcom-pbs.c create mode 100644 include/linux/soc/qcom/qcom-pbs.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 715348869d04..21254aa441ad 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -272,6 +272,15 @@ config QCOM_APR used by audio driver to configure QDSP6 ASM, ADM and AFE modules. +config QCOM_PBS + tristate "PBS trigger support for Qualcomm PMIC" + depends on SPMI + help + This driver supports configuring software programmable boot sequencer (PBS) + trigger event through PBS RAM on Qualcomm Technologies, Inc. PMICs. + This module provides the APIs to the client drivers that wants to send the + PBS trigger event to the PBS RAM. + config QCOM_ICC_BWMON tristate "QCOM Interconnect Bandwidth Monitor driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index bbca2e1e55bb..dc332fc4b203 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) += kryo-l2-accessors.o +obj-$(CONFIG_QCOM_PBS) += qcom-pbs.o obj-$(CONFIG_QCOM_ICC_BWMON) += icc-bwmon.o qcom_ice-objs += ice.o obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE) += qcom_ice.o diff --git a/drivers/soc/qcom/qcom-pbs.c b/drivers/soc/qcom/qcom-pbs.c new file mode 100644 index 000000000000..b49d14bb7d08 --- /dev/null +++ b/drivers/soc/qcom/qcom-pbs.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PBS_CLIENT_TRIG_CTL 0x42 +#define PBS_CLIENT_SW_TRIG_BIT BIT(7) +#define PBS_CLIENT_SCRATCH1 0x50 +#define PBS_CLIENT_SCRATCH2 0x51 +#define PBS_CLIENT_SCRATCH2_ERROR 0xFF + +struct pbs_dev { + struct device *dev; + struct regmap *regmap; + struct mutex lock; + struct device_link *link; + + u32 base; +}; + +static int qcom_pbs_wait_for_ack(struct pbs_dev *pbs, u8 bit_pos) +{ + int ret, retries = 2000, delay = 1100; + unsigned int val; + + ret = regmap_read_poll_timeout(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, + val, val & BIT(bit_pos), delay, delay * retries); + + if (ret < 0) { + dev_err(pbs->dev, "Timeout for PBS ACK/NACK for bit %u\n", bit_pos); + return -ETIMEDOUT; + } + + if (val == PBS_CLIENT_SCRATCH2_ERROR) { + ret = regmap_write(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, 0); + dev_err(pbs->dev, "NACK from PBS for bit %u\n", bit_pos); + return -EINVAL; + } + + dev_dbg(pbs->dev, "PBS sequence for bit %u executed!\n", bit_pos); + return 0; +} + +/** + * qcom_pbs_trigger_event() - Trigger the PBS RAM sequence + * @pbs: Pointer to PBS device + * @bitmap: bitmap + * + * This function is used to trigger the PBS RAM sequence to be + * executed by the client driver. + * + * The PBS trigger sequence involves + * 1. setting the PBS sequence bit in PBS_CLIENT_SCRATCH1 + * 2. Initiating the SW PBS trigger + * 3. Checking the equivalent bit in PBS_CLIENT_SCRATCH2 for the + * completion of the sequence. + * 4. If PBS_CLIENT_SCRATCH2 == 0xFF, the PBS sequence failed to execute + * + * Returns: 0 on success, < 0 on failure + */ +int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap) +{ + unsigned int val; + u16 bit_pos; + int ret; + + if (!bitmap) { + dev_err(pbs->dev, "Invalid bitmap passed by client\n"); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(pbs)) + return -EINVAL; + + mutex_lock(&pbs->lock); + ret = regmap_read(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, &val); + if (ret < 0) + goto out; + + if (val == PBS_CLIENT_SCRATCH2_ERROR) { + /* PBS error - clear SCRATCH2 register */ + ret = regmap_write(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, 0); + if (ret < 0) + goto out; + } + + for (bit_pos = 0; bit_pos < 8; bit_pos++) { + if (!(bitmap & BIT(bit_pos))) + continue; + + /* Clear the PBS sequence bit position */ + ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, + BIT(bit_pos), 0); + if (ret < 0) + goto error; + + /* Set the PBS sequence bit position */ + ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, + BIT(bit_pos), BIT(bit_pos)); + if (ret < 0) + goto error; + + /* Initiate the SW trigger */ + ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_TRIG_CTL, + PBS_CLIENT_SW_TRIG_BIT, PBS_CLIENT_SW_TRIG_BIT); + if (ret < 0) + goto error; + + ret = qcom_pbs_wait_for_ack(pbs, bit_pos); + if (ret < 0) + goto error; + + /* Clear the PBS sequence bit position */ + ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, + BIT(bit_pos), 0); + if (ret < 0) + goto error; + + /* Clear the PBS sequence bit position */ + ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, + BIT(bit_pos), 0); + if (ret < 0) + goto error; + } + +error: + /* Clear all the requested bitmap */ + ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, bitmap, 0); + +out: + mutex_unlock(&pbs->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_pbs_trigger_event); + +/** + * get_pbs_client_device() - Get the PBS device used by client + * @dev: Client device + * + * This function is used to get the PBS device that is being + * used by the client. + * + * Returns: pbs_dev on success, ERR_PTR on failure + */ +struct pbs_dev *get_pbs_client_device(struct device *dev) +{ + struct device_node *pbs_dev_node; + struct platform_device *pdev; + struct pbs_dev *pbs; + + pbs_dev_node = of_parse_phandle(dev->of_node, "qcom,pbs", 0); + if (!pbs_dev_node) { + dev_err(dev, "Missing qcom,pbs property\n"); + return ERR_PTR(-ENODEV); + } + + pdev = of_find_device_by_node(pbs_dev_node); + if (!pdev) { + dev_err(dev, "Unable to find PBS dev_node\n"); + pbs = ERR_PTR(-EPROBE_DEFER); + goto out; + } + + pbs = platform_get_drvdata(pdev); + if (!pbs) { + dev_err(dev, "Cannot get pbs instance from %s\n", dev_name(&pdev->dev)); + platform_device_put(pdev); + pbs = ERR_PTR(-EPROBE_DEFER); + goto out; + } + + pbs->link = device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); + if (!pbs->link) { + dev_err(&pdev->dev, "Failed to create device link to consumer %s\n", dev_name(dev)); + platform_device_put(pdev); + pbs = ERR_PTR(-EINVAL); + goto out; + } + +out: + of_node_put(pbs_dev_node); + return pbs; +} +EXPORT_SYMBOL_GPL(get_pbs_client_device); + +static int qcom_pbs_probe(struct platform_device *pdev) +{ + struct pbs_dev *pbs; + u32 val; + int ret; + + pbs = devm_kzalloc(&pdev->dev, sizeof(*pbs), GFP_KERNEL); + if (!pbs) + return -ENOMEM; + + pbs->dev = &pdev->dev; + pbs->regmap = dev_get_regmap(pbs->dev->parent, NULL); + if (!pbs->regmap) { + dev_err(pbs->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + ret = device_property_read_u32(pbs->dev, "reg", &val); + if (ret < 0) { + dev_err(pbs->dev, "Couldn't find reg, ret = %d\n", ret); + return ret; + } + pbs->base = val; + mutex_init(&pbs->lock); + + platform_set_drvdata(pdev, pbs); + + return 0; +} + +static const struct of_device_id qcom_pbs_match_table[] = { + { .compatible = "qcom,pbs" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_pbs_match_table); + +static struct platform_driver qcom_pbs_driver = { + .driver = { + .name = "qcom-pbs", + .of_match_table = qcom_pbs_match_table, + }, + .probe = qcom_pbs_probe, +}; +module_platform_driver(qcom_pbs_driver) + +MODULE_DESCRIPTION("QCOM PBS DRIVER"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/soc/qcom/qcom-pbs.h b/include/linux/soc/qcom/qcom-pbs.h new file mode 100644 index 000000000000..8a46209ccf13 --- /dev/null +++ b/include/linux/soc/qcom/qcom-pbs.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _QCOM_PBS_H +#define _QCOM_PBS_H + +#include +#include + +struct device_node; +struct pbs_dev; + +#if IS_ENABLED(CONFIG_QCOM_PBS) +int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap); +struct pbs_dev *get_pbs_client_device(struct device *client_dev); +#else +static inline int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap) +{ + return -ENODEV; +} + +static inline struct pbs_dev *get_pbs_client_device(struct device *client_dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + +#endif From patchwork Fri Sep 29 00:38:57 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anjelique Melendez X-Patchwork-Id: 727842 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5FAF915A3 for ; Fri, 29 Sep 2023 00:39:59 +0000 (UTC) Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 15018CE2; Thu, 28 Sep 2023 17:39:53 -0700 (PDT) Received: from pps.filterd (m0279867.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 38T0ATdk026983; Fri, 29 Sep 2023 00:39:31 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=uqfX+X1rfrc1KxOZEza+wOZ4UlSz2cBCE6iDwFPzT0c=; b=AQwNKbxCK7ROwAHi3Tsa9ogYYzRwdzHTfKubobaGRRlDCF47dBKnvdxZExkqyX4w4HBP 8X8DNMT7M0h9z0ps7YU1DVG8bSVw636fqhzvzH+R7qXQYudBTR0lORDA6R2ZsPTYb+9D FeT6dTEYTrFoQ+9QTLEp73Z+esfZXESMpOg/gLKlz6YeO3QxnW19kmH6N2R7OUu13nMC MUkHD9VrRNa9hrbqxGkneWk1wt4imOBIezQ7mhJlqunecsldEgS+Grw5izyOgUZ2FSAF W9bIbfo8lgs9c0w8pN39oZST3XM4P87DyBBhmCSlxwQMOz4xmzM4hOG52BcZKDjKNy5G og== Received: from nalasppmta04.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3td3ggaa72-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Sep 2023 00:39:31 +0000 Received: from nalasex01a.na.qualcomm.com (nalasex01a.na.qualcomm.com [10.47.209.196]) by NALASPPMTA04.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 38T0dUK4025919 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Sep 2023 00:39:30 GMT Received: from hu-amelende-lv.qualcomm.com (10.49.16.6) by nalasex01a.na.qualcomm.com (10.47.209.196) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.36; Thu, 28 Sep 2023 17:39:27 -0700 From: Anjelique Melendez To: , , , , , , , CC: , , , , , , , , , , , Anjelique Melendez Subject: [PATCH v5 4/7] leds: rgb: leds-qcom-lpg: Add support for single SDAM PPG Date: Thu, 28 Sep 2023 17:38:57 -0700 Message-ID: <20230929003901.15086-5-quic_amelende@quicinc.com> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230929003901.15086-1-quic_amelende@quicinc.com> References: <20230929003901.15086-1-quic_amelende@quicinc.com> Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01a.na.qualcomm.com (10.47.209.196) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-GUID: WWEW_r_wl8andVFRtMeHghlgY_V1Ptam X-Proofpoint-ORIG-GUID: WWEW_r_wl8andVFRtMeHghlgY_V1Ptam X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.267,Aquarius:18.0.980,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2023-09-28_24,2023-09-28_03,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 mlxlogscore=999 priorityscore=1501 bulkscore=0 malwarescore=0 clxscore=1015 lowpriorityscore=0 suspectscore=0 mlxscore=0 spamscore=0 adultscore=0 phishscore=0 impostorscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2309180000 definitions=main-2309290002 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net In some PMICs like pmi632, the pattern look up table (LUT) and LPG configuration is stored in a single SDAM module instead of LUT peripheral. Currently, PMICs without LUT peripheral will not be able to produce LED patterns. Add support for PBS Pattern Generation (PPG), which is the feature that allows PMICs without LUT peripherals to produce LED patterns from SDAM module. PPG uses the Qualcomm Programmable Boot Sequencer (PBS) to trigger LED pattern sequences. Signed-off-by: Anjelique Melendez --- drivers/leds/rgb/leds-qcom-lpg.c | 282 ++++++++++++++++++++++++++++--- 1 file changed, 261 insertions(+), 21 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index df469aaa7e6e..4d87686f916c 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -8,11 +8,13 @@ #include #include #include +#include #include #include #include #include #include +#include #define LPG_SUBTYPE_REG 0x05 #define LPG_SUBTYPE_LPG 0x2 @@ -39,6 +41,8 @@ #define PWM_SEC_ACCESS_REG 0xd0 #define PWM_DTEST_REG(x) (0xe2 + (x) - 1) +#define SDAM_REG_PBS_SEQ_EN 0x42 + #define TRI_LED_SRC_SEL 0x45 #define TRI_LED_EN_CTL 0x46 #define TRI_LED_ATC_CTL 0x47 @@ -48,9 +52,25 @@ #define LPG_RESOLUTION_9BIT BIT(9) #define LPG_RESOLUTION_15BIT BIT(15) +#define PPG_MAX_LED_BRIGHTNESS 255 + #define LPG_MAX_M 7 #define LPG_MAX_PREDIV 6 +#define DEFAULT_TICK_DURATION_US 7800 +#define RAMP_STEP_DURATION(x) (((x) * 1000 / DEFAULT_TICK_DURATION_US) & 0xff) + +/* LPG common config settings for PPG */ +#define SDAM_REG_RAMP_STEP_DURATION 0x47 +#define SDAM_LPG_SDAM_LUT_PATTERN_OFFSET 0x80 + +/* LPG per channel config settings for PPG */ +#define SDAM_LUT_EN_OFFSET 0x0 +#define SDAM_PATTERN_CONFIG_OFFSET 0x1 +#define SDAM_END_INDEX_OFFSET 0x3 +#define SDAM_START_INDEX_OFFSET 0x4 +#define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6 + struct lpg_channel; struct lpg_data; @@ -64,7 +84,10 @@ struct lpg_data; * @lut_base: base address of the LUT block (optional) * @lut_size: number of entries in the LUT block * @lut_bitmap: allocation bitmap for LUT entries - * @triled_base: base address of the TRILED block (optional) + * @pbs_dev: PBS device + * @lpg_chan_sdam: LPG SDAM peripheral device + * @pbs_en_bitmap: bitmap for tracking PBS triggers + * @triled_base: base address of the TRILED block (optional) * @triled_src: power-source for the TRILED * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register @@ -85,6 +108,10 @@ struct lpg { u32 lut_size; unsigned long *lut_bitmap; + struct pbs_dev *pbs_dev; + struct nvmem_device *lpg_chan_sdam; + unsigned long pbs_en_bitmap; + u32 triled_base; u32 triled_src; bool triled_has_atc_ctl; @@ -101,6 +128,8 @@ struct lpg { * @triled_mask: mask in TRILED to enable this channel * @lut_mask: mask in LUT to start pattern generator for this channel * @subtype: PMIC hardware block subtype + * @sdam_offset: Channel offset in LPG SDAM + * @lpg_idx: index of the channel * @in_use: channel is exposed to LED framework * @color: color of the LED attached to this channel * @dtest_line: DTEST line for output, or 0 if disabled @@ -112,6 +141,7 @@ struct lpg { * @pre_div_sel: divider selector of the reference clock * @pre_div_exp: exponential divider of the reference clock * @pwm_resolution_sel: pwm resolution selector + * @pattern_set: true when setting pattern * @ramp_enabled: duty cycle is driven by iterating over lookup table * @ramp_ping_pong: reverse through pattern, rather than wrapping to start * @ramp_oneshot: perform only a single pass over the pattern @@ -129,6 +159,8 @@ struct lpg_channel { unsigned int triled_mask; unsigned int lut_mask; unsigned int subtype; + u32 sdam_offset; + u32 lpg_idx; bool in_use; @@ -146,6 +178,7 @@ struct lpg_channel { unsigned int pre_div_exp; unsigned int pwm_resolution_sel; + bool pattern_set; bool ramp_enabled; bool ramp_ping_pong; bool ramp_oneshot; @@ -180,8 +213,10 @@ struct lpg_led { * struct lpg_channel_data - per channel initialization data * @base: base address for PWM channel registers * @triled_mask: bitmask for controlling this channel in TRILED + * @sdam_offset: Channel offset in LPG SDAM */ struct lpg_channel_data { + unsigned int sdam_offset; unsigned int base; u8 triled_mask; }; @@ -206,6 +241,75 @@ struct lpg_data { const struct lpg_channel_data *channels; }; +#define PBS_SW_TRIG_BIT BIT(0) + +static int lpg_clear_pbs_trigger(struct lpg_channel *chan) +{ + u8 val = 0; + int rc; + + clear_bit(chan->lpg_idx, &chan->lpg->pbs_en_bitmap); + if (!chan->lpg->pbs_en_bitmap) { + rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val); + if (rc < 0) + return rc; + } + + return 0; +} + +static int lpg_set_pbs_trigger(struct lpg_channel *chan) +{ + u8 val = PBS_SW_TRIG_BIT; + int rc; + + if (!chan->lpg->pbs_en_bitmap) { + rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val); + if (rc < 0) + return rc; + + rc = qcom_pbs_trigger_event(chan->lpg->pbs_dev, val); + if (rc < 0) + return rc; + } + set_bit(chan->lpg_idx, &chan->lpg->pbs_en_bitmap); + + return 0; +} + +static int lpg_sdam_configure_triggers(struct lpg_channel *chan, bool set_trig) +{ + u32 addr = SDAM_LUT_EN_OFFSET + chan->sdam_offset; + u8 val; + int rc; + + if (chan->lpg->lut_base) + return 0; + + if (set_trig) { + val = 1; + rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, addr, 1, &val); + if (rc < 0) + return rc; + + rc = lpg_set_pbs_trigger(chan); + if (rc < 0) + return rc; + chan->pattern_set = false; + } else { + val = 0; + rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, addr, 1, &val); + if (rc < 0) + return rc; + + rc = lpg_clear_pbs_trigger(chan); + if (rc < 0) + return rc; + } + + return 0; +} + static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable) { /* Skip if we don't have a triled block */ @@ -216,6 +320,41 @@ static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable) mask, enable); } +static int lpg_lut_store_sdam(struct lpg *lpg, struct led_pattern *pattern, + size_t len, unsigned int *lo_idx, unsigned int *hi_idx) +{ + unsigned int idx; + u8 brightness; + int i, rc; + u16 addr; + + if (len > lpg->lut_size) { + dev_err(lpg->dev, "Pattern length (%zu) exceeds maximum pattern length (%d)\n", + len, lpg->lut_size); + return -EINVAL; + } + + idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size, + 0, len, 0); + if (idx >= lpg->lut_size) + return -ENOSPC; + + for (i = 0; i < len; i++) { + brightness = pattern[i].brightness; + addr = SDAM_LPG_SDAM_LUT_PATTERN_OFFSET + i + idx; + rc = nvmem_device_write(lpg->lpg_chan_sdam, addr, 1, &brightness); + if (rc < 0) + return rc; + } + + bitmap_set(lpg->lut_bitmap, idx, len); + + *lo_idx = idx; + *hi_idx = idx + len - 1; + + return 0; +} + static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern, size_t len, unsigned int *lo_idx, unsigned int *hi_idx) { @@ -462,6 +601,28 @@ static void lpg_apply_pwm_value(struct lpg_channel *chan) #define LPG_PATTERN_CONFIG_PAUSE_HI BIT(1) #define LPG_PATTERN_CONFIG_PAUSE_LO BIT(0) +static void lpg_sdam_apply_lut_control(struct lpg_channel *chan) +{ + struct nvmem_device *lpg_chan_sdam = chan->lpg->lpg_chan_sdam; + unsigned int lo_idx = chan->pattern_lo_idx; + unsigned int hi_idx = chan->pattern_hi_idx; + u8 val = 0, conf = 0; + + if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx) + return; + + if (!chan->ramp_oneshot) + conf |= LPG_PATTERN_CONFIG_REPEAT; + + nvmem_device_write(lpg_chan_sdam, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val); + nvmem_device_write(lpg_chan_sdam, SDAM_PATTERN_CONFIG_OFFSET + chan->sdam_offset, 1, &conf); + nvmem_device_write(lpg_chan_sdam, SDAM_END_INDEX_OFFSET + chan->sdam_offset, 1, &hi_idx); + nvmem_device_write(lpg_chan_sdam, SDAM_START_INDEX_OFFSET + chan->sdam_offset, 1, &lo_idx); + + val = RAMP_STEP_DURATION(chan->ramp_tick_ms); + nvmem_device_write(lpg_chan_sdam, SDAM_REG_RAMP_STEP_DURATION, 1, &val); +} + static void lpg_apply_lut_control(struct lpg_channel *chan) { struct lpg *lpg = chan->lpg; @@ -597,7 +758,10 @@ static void lpg_apply(struct lpg_channel *chan) lpg_apply_pwm_value(chan); lpg_apply_control(chan); lpg_apply_sync(chan); - lpg_apply_lut_control(chan); + if (chan->lpg->lpg_chan_sdam) + lpg_sdam_apply_lut_control(chan); + else + lpg_apply_lut_control(chan); lpg_enable_glitch(chan); } @@ -642,6 +806,9 @@ static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev, triled_mask |= chan->triled_mask; lpg_apply(chan); + + if (chan->pattern_set) + lpg_sdam_configure_triggers(chan, true); } /* Toggle triled lines */ @@ -836,18 +1003,24 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, * If the specified pattern is a palindrome the ping pong mode is * enabled. In this scenario the delta_t of the middle entry (i.e. the * last in the programmed pattern) determines the "high pause". + * + * SDAM devices supporting LUT do not support "low pause", "high pause" + * or "ping pong" */ /* Detect palindromes and use "ping pong" to reduce LUT usage */ - for (i = 0; i < len / 2; i++) { - brightness_a = pattern[i].brightness; - brightness_b = pattern[len - i - 1].brightness; - - if (brightness_a != brightness_b) { - ping_pong = false; - break; + if (lpg->lut_base) { + for (i = 0; i < len / 2; i++) { + brightness_a = pattern[i].brightness; + brightness_b = pattern[len - i - 1].brightness; + + if (brightness_a != brightness_b) { + ping_pong = false; + break; + } } - } + } else + ping_pong = false; /* The pattern length to be written to the LUT */ if (ping_pong) @@ -875,12 +1048,27 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, if (delta_t >= BIT(9)) goto out_free_pattern; - /* Find "low pause" and "high pause" in the pattern */ - lo_pause = pattern[0].delta_t; - hi_pause = pattern[actual_len - 1].delta_t; + /* + * Find "low pause" and "high pause" in the pattern in the LUT case. + * LPGs using SDAM for pattern require equal duration of all steps + */ + if (lpg->lut_base) { + lo_pause = pattern[0].delta_t; + hi_pause = pattern[actual_len - 1].delta_t; + } else { + if (delta_t != pattern[0].delta_t || + delta_t != pattern[actual_len - 1].delta_t) + goto out_free_pattern; + } + mutex_lock(&lpg->lock); - ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx); + + if (lpg->lpg_chan_sdam) + ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx); + else + ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx); + if (ret < 0) goto out_unlock; @@ -896,6 +1084,8 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, chan->pattern_lo_idx = lo_idx; chan->pattern_hi_idx = hi_idx; + + chan->pattern_set = true; } out_unlock: @@ -953,6 +1143,7 @@ static int lpg_pattern_clear(struct lpg_led *led) for (i = 0; i < led->num_channels; i++) { chan = led->channels[i]; + lpg_sdam_configure_triggers(chan, false); chan->pattern_lo_idx = 0; chan->pattern_hi_idx = 0; } @@ -1188,8 +1379,8 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->brightness_set_blocking = lpg_brightness_mc_set; cdev->blink_set = lpg_blink_mc_set; - /* Register pattern accessors only if we have a LUT block */ - if (lpg->lut_base) { + /* Register pattern accessors if we have a LUT block or when using PPG */ + if (lpg->lut_base || lpg->lpg_chan_sdam) { cdev->pattern_set = lpg_pattern_mc_set; cdev->pattern_clear = lpg_pattern_mc_clear; } @@ -1202,15 +1393,19 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->brightness_set_blocking = lpg_brightness_single_set; cdev->blink_set = lpg_blink_single_set; - /* Register pattern accessors only if we have a LUT block */ - if (lpg->lut_base) { + /* Register pattern accessors if we have a LUT block or when using PPG */ + if (lpg->lut_base || lpg->lpg_chan_sdam) { cdev->pattern_set = lpg_pattern_single_set; cdev->pattern_clear = lpg_pattern_single_clear; } } cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL); - cdev->max_brightness = LPG_RESOLUTION_9BIT - 1; + + if (lpg->lpg_chan_sdam) + cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS; + else + cdev->max_brightness = LPG_RESOLUTION_9BIT - 1; if (!of_property_read_string(np, "default-state", &state) && !strcmp(state, "on")) @@ -1251,6 +1446,8 @@ static int lpg_init_channels(struct lpg *lpg) chan->base = data->channels[i].base; chan->triled_mask = data->channels[i].triled_mask; chan->lut_mask = BIT(i); + chan->sdam_offset = data->channels[i].sdam_offset; + chan->lpg_idx = i; regmap_read(lpg->map, chan->base + LPG_SUBTYPE_REG, &chan->subtype); } @@ -1297,11 +1494,12 @@ static int lpg_init_lut(struct lpg *lpg) { const struct lpg_data *data = lpg->data; - if (!data->lut_base) + if (!data->lut_size) return 0; - lpg->lut_base = data->lut_base; lpg->lut_size = data->lut_size; + if (data->lut_base) + lpg->lut_base = data->lut_base; lpg->lut_bitmap = devm_bitmap_zalloc(lpg->dev, lpg->lut_size, GFP_KERNEL); if (!lpg->lut_bitmap) @@ -1310,6 +1508,44 @@ static int lpg_init_lut(struct lpg *lpg) return 0; } +static int lpg_init_sdam(struct lpg *lpg) +{ + struct lpg_channel *chan; + int i, sdam_count, rc; + u8 val = 0; + + sdam_count = of_property_count_strings(lpg->dev->of_node, "nvmem-names"); + if (sdam_count <= 0) + return 0; + + /* get the SDAM device for LPG/LUT config */ + lpg->lpg_chan_sdam = devm_nvmem_device_get(lpg->dev, "lpg_chan_sdam"); + if (IS_ERR(lpg->lpg_chan_sdam)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->lpg_chan_sdam), + "Failed to get lpg sdam device\n"); + + lpg->pbs_dev = get_pbs_client_device(lpg->dev); + if (IS_ERR(lpg->pbs_dev)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->pbs_dev), + "Failed to get PBS client device\n"); + + for (i = 0; i < lpg->num_channels; i++) { + chan = &lpg->channels[i]; + if (chan->sdam_offset) { + rc = nvmem_device_write(lpg->lpg_chan_sdam, + SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val); + if (rc < 0) + return rc; + + rc = lpg_sdam_configure_triggers(chan, false); + if (rc < 0) + return rc; + } + } + + return 0; +} + static int lpg_probe(struct platform_device *pdev) { struct device_node *np; @@ -1346,6 +1582,10 @@ static int lpg_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = lpg_init_sdam(lpg); + if (ret < 0) + return ret; + ret = lpg_init_lut(lpg); if (ret < 0) return ret; From patchwork Fri Sep 29 00:38:59 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anjelique Melendez X-Patchwork-Id: 727843 Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net [23.128.96.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C65C717CF for ; Fri, 29 Sep 2023 00:39:45 +0000 (UTC) Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1320F1A1; Thu, 28 Sep 2023 17:39:42 -0700 (PDT) Received: from pps.filterd (m0279873.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 38T0FRLg017690; Fri, 29 Sep 2023 00:39:33 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=R67bAOBo6mh67TDuYXmIz7oxh7KAgh6pMeijRDLC+Vw=; b=YkI+lkEcTXxuv9mTYEczucIqW6GDDxlJvTDX0SkZ7Y+9wVjryYXaDEM5laXjsXJyEOLy 3kyp5fEHDJLbqtLAgmpkvRfbGoGyYfUp9rgSWQswHBdb2vR5T4cwrqU4xhO/Z/c+q+v1 H80H0Iyf/dKMwsI2rB7EHlFLkLYg3Ay+GQwjB5h1QvCLxaRWoI961nAhBTwirxSFUy6w u8ZyMLeaf0dmOeTRhzQz44BmlaW2H2JrNSYe6JCdemfBKmrPnXGF4gurrMbP4hVd9342 mkmzNYnVhXQWSklioVPn5upzj49Ki8PgrhMC6n65Y6PkpEXy4b1xmLQBfUlwgUFaeI9A KQ== Received: from nalasppmta03.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3td24ua9wq-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Sep 2023 00:39:33 +0000 Received: from nalasex01a.na.qualcomm.com (nalasex01a.na.qualcomm.com [10.47.209.196]) by NALASPPMTA03.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 38T0dW60029588 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Sep 2023 00:39:32 GMT Received: from hu-amelende-lv.qualcomm.com (10.49.16.6) by nalasex01a.na.qualcomm.com (10.47.209.196) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.36; Thu, 28 Sep 2023 17:39:29 -0700 From: Anjelique Melendez To: , , , , , , , CC: , , , , , , , , , , , Anjelique Melendez Subject: [PATCH v5 6/7] leds: rgb: leds-qcom-lpg: Include support for PPG with dedicated LUT SDAM Date: Thu, 28 Sep 2023 17:38:59 -0700 Message-ID: <20230929003901.15086-7-quic_amelende@quicinc.com> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230929003901.15086-1-quic_amelende@quicinc.com> References: <20230929003901.15086-1-quic_amelende@quicinc.com> Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01a.na.qualcomm.com (10.47.209.196) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-GUID: -ESc67DequERn5Sbhspo4nMYsGEJzyfN X-Proofpoint-ORIG-GUID: -ESc67DequERn5Sbhspo4nMYsGEJzyfN X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.267,Aquarius:18.0.980,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2023-09-28_23,2023-09-28_03,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 malwarescore=0 suspectscore=0 impostorscore=0 lowpriorityscore=0 spamscore=0 priorityscore=1501 mlxscore=0 mlxlogscore=999 adultscore=0 bulkscore=0 phishscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2309180000 definitions=main-2309290002 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net On PMICs such as PM8350C, the pattern lookup table (LUT) is stored in a separate SDAM from the one where the lpg per-channel data is stored. Add support for PPG with a dedicated LUT SDAM while maintaining backward compatibility for those targets that use only a single SDAM. Co-developed-by: Guru Das Srinagesh Signed-off-by: Guru Das Srinagesh Signed-off-by: Anjelique Melendez Reviewed-by: Lee Jones --- drivers/leds/rgb/leds-qcom-lpg.c | 95 ++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index a6cea6bd7167..910c7cf740cc 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -42,6 +42,8 @@ #define PWM_DTEST_REG(x) (0xe2 + (x) - 1) #define SDAM_REG_PBS_SEQ_EN 0x42 +#define SDAM_PBS_TRIG_SET 0xe5 +#define SDAM_PBS_TRIG_CLR 0xe6 #define TRI_LED_SRC_SEL 0x45 #define TRI_LED_EN_CTL 0x46 @@ -61,7 +63,10 @@ #define RAMP_STEP_DURATION(x) (((x) * 1000 / DEFAULT_TICK_DURATION_US) & 0xff) /* LPG common config settings for PPG */ +#define SDAM_START_BASE 0x40 #define SDAM_REG_RAMP_STEP_DURATION 0x47 + +#define SDAM_LUT_SDAM_LUT_PATTERN_OFFSET 0x45 #define SDAM_LPG_SDAM_LUT_PATTERN_OFFSET 0x80 /* LPG per channel config settings for PPG */ @@ -70,6 +75,8 @@ #define SDAM_END_INDEX_OFFSET 0x3 #define SDAM_START_INDEX_OFFSET 0x4 #define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6 +#define SDAM_PAUSE_HI_MULTIPLIER_OFFSET 0x8 +#define SDAM_PAUSE_LO_MULTIPLIER_OFFSET 0x9 struct lpg_channel; struct lpg_data; @@ -86,6 +93,7 @@ struct lpg_data; * @lut_bitmap: allocation bitmap for LUT entries * @pbs_dev: PBS device * @lpg_chan_sdam: LPG SDAM peripheral device + * @lut_sdam: LUT SDAM peripheral device * @pbs_en_bitmap: bitmap for tracking PBS triggers * @triled_base: base address of the TRILED block (optional) * @triled_src: power-source for the TRILED @@ -110,6 +118,7 @@ struct lpg { struct pbs_dev *pbs_dev; struct nvmem_device *lpg_chan_sdam; + struct nvmem_device *lut_sdam; unsigned long pbs_en_bitmap; u32 triled_base; @@ -253,6 +262,13 @@ static int lpg_clear_pbs_trigger(struct lpg_channel *chan) rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val); if (rc < 0) return rc; + + if (chan->lpg->lut_sdam) { + val = PBS_SW_TRIG_BIT; + rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, SDAM_PBS_TRIG_CLR, 1, &val); + if (rc < 0) + return rc; + } } return 0; @@ -268,9 +284,15 @@ static int lpg_set_pbs_trigger(struct lpg_channel *chan) if (rc < 0) return rc; - rc = qcom_pbs_trigger_event(chan->lpg->pbs_dev, val); - if (rc < 0) - return rc; + if (chan->lpg->lut_sdam) { + rc = nvmem_device_write(chan->lpg->lpg_chan_sdam, SDAM_PBS_TRIG_SET, 1, &val); + if (rc < 0) + return rc; + } else { + rc = qcom_pbs_trigger_event(chan->lpg->pbs_dev, val); + if (rc < 0) + return rc; + } } set_bit(chan->lpg_idx, &chan->lpg->pbs_en_bitmap); @@ -341,8 +363,15 @@ static int lpg_lut_store_sdam(struct lpg *lpg, struct led_pattern *pattern, for (i = 0; i < len; i++) { brightness = pattern[i].brightness; - addr = SDAM_LPG_SDAM_LUT_PATTERN_OFFSET + i + idx; - rc = nvmem_device_write(lpg->lpg_chan_sdam, addr, 1, &brightness); + + if (lpg->lut_sdam) { + addr = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET + i + idx; + rc = nvmem_device_write(lpg->lut_sdam, addr, 1, &brightness); + } else { + addr = SDAM_LPG_SDAM_LUT_PATTERN_OFFSET + i + idx; + rc = nvmem_device_write(lpg->lpg_chan_sdam, addr, 1, &brightness); + } + if (rc < 0) return rc; } @@ -606,13 +635,28 @@ static void lpg_sdam_apply_lut_control(struct lpg_channel *chan) struct nvmem_device *lpg_chan_sdam = chan->lpg->lpg_chan_sdam; unsigned int lo_idx = chan->pattern_lo_idx; unsigned int hi_idx = chan->pattern_hi_idx; - u8 val = 0, conf = 0; + u8 val = 0, conf = 0, lut_offset = 0; + unsigned int hi_pause, lo_pause; + struct lpg *lpg = chan->lpg; if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx) return; + hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, chan->ramp_tick_ms); + lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, chan->ramp_tick_ms); + if (!chan->ramp_oneshot) conf |= LPG_PATTERN_CONFIG_REPEAT; + if (chan->ramp_hi_pause_ms && lpg->lut_sdam) + conf |= LPG_PATTERN_CONFIG_PAUSE_HI; + if (chan->ramp_lo_pause_ms && lpg->lut_sdam) + conf |= LPG_PATTERN_CONFIG_PAUSE_LO; + + if (lpg->lut_sdam) { + lut_offset = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET - SDAM_START_BASE; + hi_idx += lut_offset; + lo_idx += lut_offset; + } nvmem_device_write(lpg_chan_sdam, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val); nvmem_device_write(lpg_chan_sdam, SDAM_PATTERN_CONFIG_OFFSET + chan->sdam_offset, 1, &conf); @@ -621,6 +665,12 @@ static void lpg_sdam_apply_lut_control(struct lpg_channel *chan) val = RAMP_STEP_DURATION(chan->ramp_tick_ms); nvmem_device_write(lpg_chan_sdam, SDAM_REG_RAMP_STEP_DURATION, 1, &val); + + if (lpg->lut_sdam || lpg->lut_base) { + nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_HI_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &hi_pause); + nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_LO_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &lo_pause); + } + } static void lpg_apply_lut_control(struct lpg_channel *chan) @@ -1004,8 +1054,8 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, * enabled. In this scenario the delta_t of the middle entry (i.e. the * last in the programmed pattern) determines the "high pause". * - * SDAM devices supporting LUT do not support "low pause", "high pause" - * or "ping pong" + * SDAM-based devices do not support "ping pong", and only supports + * "low pause" and "high pause" with a dedicated SDAM LUT. */ /* Detect palindromes and use "ping pong" to reduce LUT usage */ @@ -1050,9 +1100,9 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, /* * Find "low pause" and "high pause" in the pattern in the LUT case. - * LPGs using SDAM for pattern require equal duration of all steps + * PPG LPGs without LUT SDAM require equal duration of all steps. */ - if (lpg->lut_base) { + if (lpg->lut_base || lpg->lut_sdam) { lo_pause = pattern[0].delta_t; hi_pause = pattern[actual_len - 1].delta_t; } else { @@ -1517,17 +1567,28 @@ static int lpg_init_sdam(struct lpg *lpg) sdam_count = of_property_count_strings(lpg->dev->of_node, "nvmem-names"); if (sdam_count <= 0) return 0; + if (sdam_count > 2) + return -EINVAL; - /* get the SDAM device for LPG/LUT config */ + /* get the 1st nvmem device for LPG/LUT config */ lpg->lpg_chan_sdam = devm_nvmem_device_get(lpg->dev, "lpg_chan_sdam"); if (IS_ERR(lpg->lpg_chan_sdam)) return dev_err_probe(lpg->dev, PTR_ERR(lpg->lpg_chan_sdam), - "Failed to get lpg sdam device\n"); - - lpg->pbs_dev = get_pbs_client_device(lpg->dev); - if (IS_ERR(lpg->pbs_dev)) - return dev_err_probe(lpg->dev, PTR_ERR(lpg->pbs_dev), - "Failed to get PBS client device\n"); + "Failed to get lpg_chan_sdam device\n"); + + if (sdam_count == 1) { + /* get PBS device node if single SDAM device */ + lpg->pbs_dev = get_pbs_client_device(lpg->dev); + if (IS_ERR(lpg->pbs_dev)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->pbs_dev), + "Failed to get PBS client device\n"); + } else if (sdam_count == 2) { + /* get the 2nd SDAM device for LUT pattern */ + lpg->lut_sdam = devm_nvmem_device_get(lpg->dev, "lut_sdam"); + if (IS_ERR(lpg->lut_sdam)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->lut_sdam), + "Failed to get lut_sdam device\n"); + } for (i = 0; i < lpg->num_channels; i++) { chan = &lpg->channels[i];