From 7d3015e4eb8351087497c77749df7646f09912c5 Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Mon, 3 Aug 2026 15:01:20 +0530 Subject: [PATCH 01/12] Revert "QCLINUX: defconfig: Enable Qualcomm BCL driver" This reverts commit 11e8f87d64c94d0e4910bc3abb4d4fa39e92a8d8. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- arch/arm64/configs/qcom.config | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/configs/qcom.config b/arch/arm64/configs/qcom.config index 70af068f10e02..0a4393ebcba0c 100644 --- a/arch/arm64/configs/qcom.config +++ b/arch/arm64/configs/qcom.config @@ -65,7 +65,6 @@ CONFIG_STM_SOURCE_FTRACE=m CONFIG_STM_SOURCE_HEARTBEAT=m CONFIG_SENSORS_AMC6821=y CONFIG_SENSORS_EMC2305=y -CONFIG_SENSORS_QCOM_BCL=y CONFIG_SWIOTLB_DYNAMIC=y CONFIG_TRACE_MMIO_ACCESS=y CONFIG_UCLAMP_TASK_GROUP=y From b1195419a570e944a4d199693c0ba39c4fe491aa Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Mon, 3 Aug 2026 10:18:57 +0530 Subject: [PATCH 02/12] Revert "FROMLIST: arm64: dts: qcom: pm8350c: Enable Qualcomm BCL device" This reverts commit f936a52c5bd27785aa8390453427c19da5d34df2. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- arch/arm64/boot/dts/qcom/pm8350c.dtsi | 9 --------- 1 file changed, 9 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/pm8350c.dtsi b/arch/arm64/boot/dts/qcom/pm8350c.dtsi index f0cf55a7fc9e5..1a24e6439e36d 100644 --- a/arch/arm64/boot/dts/qcom/pm8350c.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8350c.dtsi @@ -41,15 +41,6 @@ #pwm-cells = <2>; status = "disabled"; }; - - sensor@4700 { - compatible = "qcom,pm8350c-bcl", "qcom,bcl-v2"; - reg = <0x4700>; - interrupts = <0x2 0x47 0x0 IRQ_TYPE_EDGE_RISING>, - <0x2 0x47 0x1 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "bcl-max-min", - "bcl-critical"; - }; }; }; From 3cc6baccc252757893933014b5eba04ec0bb4eaf Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Mon, 3 Aug 2026 10:19:02 +0530 Subject: [PATCH 03/12] Revert "FROMLIST: arm64: dts: qcom: pm7250b: Enable Qualcomm BCL device" This reverts commit e02e7a0ab6b7dbb3dec275c873d0598bf1d0dd5b. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- arch/arm64/boot/dts/qcom/pm7250b.dtsi | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/pm7250b.dtsi b/arch/arm64/boot/dts/qcom/pm7250b.dtsi index dffd84dd87a85..0761e6b5fd8d1 100644 --- a/arch/arm64/boot/dts/qcom/pm7250b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm7250b.dtsi @@ -202,16 +202,6 @@ interrupt-controller; #interrupt-cells = <2>; }; - - sensor@1d00 { - compatible = "qcom,pm7250b-bcl", "qcom,bcl-v1"; - reg = <0x1d00>; - interrupts = , - ; - interrupt-names = "bcl-max-min", - "bcl-critical"; - overcurrent-thresholds-milliamp = <5500 6000>; - }; }; pmic@PM7250B_SID1 { From 363c3dc1ba6a8d1b4c669f8e6e183ee1b5941a67 Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Mon, 3 Aug 2026 10:19:08 +0530 Subject: [PATCH 04/12] Revert "FROMLIST: hwmon: Add Qualcomm PMIC BCL hardware monitor driver" This reverts commit 37c3507f8f50ad2c939cd94ec4007f3d4bc1404b. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- MAINTAINERS | 9 - drivers/hwmon/Kconfig | 9 - drivers/hwmon/Makefile | 1 - drivers/hwmon/qcom-bcl-hwmon.c | 982 --------------------------------- drivers/hwmon/qcom-bcl-hwmon.h | 311 ----------- 5 files changed, 1312 deletions(-) delete mode 100644 drivers/hwmon/qcom-bcl-hwmon.c delete mode 100644 drivers/hwmon/qcom-bcl-hwmon.h diff --git a/MAINTAINERS b/MAINTAINERS index 35c3d72107252..d863f8f9f5824 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21092,15 +21092,6 @@ S: Maintained F: Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml F: drivers/net/wwan/qcom_bam_dmux.c -QUALCOMM BCL HARDWARE MONITOR DRIVER -M: Manaf Meethalavalappu Pallikunhi -L: linux-hwmon@vger.kernel.org -L: linux-arm-msm@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml -F: drivers/hwmon/qcom-bcl-hwmon.c -F: drivers/hwmon/qcom-bcl-hwmon.h - QUALCOMM BLUETOOTH DRIVER L: linux-arm-msm@vger.kernel.org S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index fdb3ecc2bb041..2a71b6e834b08 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1893,15 +1893,6 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. -config SENSORS_QCOM_BCL - tristate "Qualcomm BCL hardware monitoring" - help - Say yes here to enable support for Qualcomm battery over current - and under voltage alarms monitor. - - This driver can also be built as a module. If so, the module - will be called qcom-bcl-hwmon. - config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e2d01688fdb75..73b2abdcc6dd9 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -196,7 +196,6 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o -obj-$(CONFIG_SENSORS_QCOM_BCL) += qcom-bcl-hwmon.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c deleted file mode 100644 index 9894b1d14d124..0000000000000 --- a/drivers/hwmon/qcom-bcl-hwmon.c +++ /dev/null @@ -1,982 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Qualcomm pmic BCL hardware driver for battery overcurrent and - * battery or system under voltage monitor - * - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qcom-bcl-hwmon.h" - -ADD_BCL_HWMON_ALARM_MAPS(in, min, lcrit); -ADD_BCL_HWMON_ALARM_MAPS(curr, max, crit); - -/* Interrupt names for each alarm level */ -static const char * const bcl_int_names[ALARM_MAX] = { - [LVL0] = "bcl-max-min", - [LVL1] = "bcl-critical", -}; - -static const char * const bcl_channel_label[CHANNEL_MAX] = { - "BCL Voltage", - "BCL Current", -}; - -/* Common Reg Fields */ -static const struct reg_field common_reg_fields[COMMON_FIELD_MAX] = { - [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), - [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), - [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), - [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), - [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), -}; - -/* BCL Version/Modes specific fields */ -static const struct reg_field bcl_v1_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(MODE_CTL1, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v2_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 1), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v3_bmx_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v3_wb_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 3), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), -}; - -static const struct reg_field bcl_v3_core_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 5), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), -}; - -static const struct reg_field bcl_v4_bmx_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), -}; - -static const struct reg_field bcl_v4_wb_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 4), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), -}; - -static const struct reg_field bcl_v4_core_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 6), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), -}; - -/* V1 BMX and core */ -static const struct bcl_desc pm7250b_data = { - .reg_fields = bcl_v1_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 7, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 10000, - .default_scale_nu = 305180, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V2 BMX and core */ -static const struct bcl_desc pm8350_data = { - .reg_fields = bcl_v2_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 10000, - .default_scale_nu = 305180, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V3 BMX */ -static const struct bcl_desc pm8550b_data = { - .reg_fields = bcl_v3_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 12000, - .default_scale_nu = 366220, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V3 WB */ -static const struct bcl_desc pmw5100_data = { - .reg_fields = bcl_v3_wb_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .base = 800, - .max = 2000, - .step = 100, - .default_scale_nu = 61035, - .thresh_type = {ADC, INDEX}, - }, -}; - -/* V3 CORE */ -static const struct bcl_desc pm8550_data = { - .reg_fields = bcl_v3_core_reg_fields, - .num_reg_fields = F_CURR_MON_EN + 1, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .thresh_type = {INDEX, INDEX}, - }, -}; - -/* V4 BMX */ -static const struct bcl_desc pmih010_data = { - .reg_fields = bcl_v4_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 20000, - .default_scale_nu = 610370, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V4 WB */ -static const struct bcl_desc pmw6100_data = { - .reg_fields = bcl_v4_wb_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 1500, - .max = 4000, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .base = 900, - .max = 3300, - .step = 150, - .default_scale_nu = 152586, - .thresh_type = {ADC, INDEX}, - }, -}; - -/* V4 CORE */ -static const struct bcl_desc pmh010_data = { - .reg_fields = bcl_v4_core_reg_fields, - .num_reg_fields = F_CURR_MON_EN + 1, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 1500, - .max = 4000, - .step = 25, - .thresh_type = {INDEX, INDEX}, - }, -}; - -/* V4 BMX with different scale */ -static const struct bcl_desc pmv010_data = { - .reg_fields = bcl_v4_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 12000, - .default_scale_nu = 366220, - .thresh_type = {ADC, ADC}, - }, -}; - -/** - * bcl_convert_raw_to_milliunit - Convert raw value to milli unit - * @desc: BCL device descriptor - * @raw_val: Raw ADC value from hardware - * @type: type of the channel, in or curr - * @field_width: bits size for data or threshold field - * - * Return: value in milli unit - */ -static unsigned int bcl_convert_raw_to_milliunit(const struct bcl_desc *desc, int raw_val, - enum bcl_channel_type type, u8 field_width) -{ - u32 def_scale = desc->channel[type].default_scale_nu; - u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; - u32 scaling_factor = def_scale * lsb_weight; - - return div_s64((s64)raw_val * scaling_factor, 1000000); -} - -/** - * bcl_convert_milliunit_to_raw - Convert milli unit to raw value - * @desc: BCL device descriptor - * @ma_val: threshold value in milli unit - * @type: type of the channel, in or curr - * @field_width: bits size for data or threshold field - * - * Return: Raw ADC value for hardware - */ -static unsigned int bcl_convert_milliunit_to_raw(const struct bcl_desc *desc, int mval, - enum bcl_channel_type type, u8 field_width) -{ - u32 def_scale = desc->channel[type].default_scale_nu; - u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; - u32 scaling_factor = def_scale * lsb_weight; - - return div_s64((s64)mval * 1000000, scaling_factor); -} - -/** - * bcl_convert_milliunit_to_index - Convert milliunit to in or curr index - * @desc: BCL device descriptor - * @val: in or curr value in milli unit - * @type: type of the channel, in or curr - * - * Converts a value in milli unit to an index for BCL that use indexed thresholds. - * - * Return: Voltage index value - */ -static unsigned int bcl_convert_milliunit_to_index(const struct bcl_desc *desc, int val, - enum bcl_channel_type type) -{ - return div_s64((s64)val - desc->channel[type].base, desc->channel[type].step); -} - -/** - * bcl_convert_index_to_milliunit - Convert in or curr index to milli unit - * @desc: BCL device descriptor - * @val: index value - * @type: type of the channel, in or curr - * - * Converts an index value to milli unit for BCL that use indexed thresholds. - * - * Return: Voltage value in millivolts - */ -static unsigned int bcl_convert_index_to_milliunit(const struct bcl_desc *desc, int val, - enum bcl_channel_type type) -{ - return desc->channel[type].base + val * desc->channel[type].step; -} - -static int bcl_in_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) -{ - const struct bcl_desc *desc = bcl->desc; - u32 raw_val; - - int thresh = clamp_val(value, desc->channel[IN].base, desc->channel[IN].max); - - if (desc->channel[IN].thresh_type[lvl] == ADC) - raw_val = bcl_convert_milliunit_to_raw(desc, thresh, IN, - desc->thresh_field_bits_size); - else - raw_val = bcl_convert_milliunit_to_index(desc, thresh, IN); - - return regmap_field_write(bcl->fields[F_IN_L0_THR + lvl], raw_val); -} - -static int bcl_curr_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) -{ - const struct bcl_desc *desc = bcl->desc; - u32 raw_val; - - /* Clamp only to curr max */ - int thresh = clamp_val(value, value, desc->channel[CURR].max); - - if (desc->channel[CURR].thresh_type[lvl] == ADC) - raw_val = bcl_convert_milliunit_to_raw(desc, thresh, CURR, - desc->thresh_field_bits_size); - else - raw_val = bcl_convert_milliunit_to_index(desc, thresh, CURR); - - return regmap_field_write(bcl->fields[F_CURR_H0_THR + lvl], raw_val); -} - -static int bcl_in_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) -{ - int ret, thresh; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_IN_L0_THR + lvl, &raw_val); - if (ret) - return ret; - - if (desc->channel[IN].thresh_type[lvl] == ADC) - thresh = bcl_convert_raw_to_milliunit(desc, raw_val, IN, - desc->thresh_field_bits_size); - else - thresh = bcl_convert_index_to_milliunit(desc, raw_val, IN); - - *out = thresh; - - return 0; -} - -static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) -{ - int ret, thresh; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_CURR_H0_THR + lvl, &raw_val); - if (ret) - return ret; - - if (desc->channel[CURR].thresh_type[lvl] == ADC) - thresh = bcl_convert_raw_to_milliunit(desc, raw_val, CURR, - desc->thresh_field_bits_size); - else - thresh = bcl_convert_index_to_milliunit(desc, raw_val, CURR); - - *out = thresh; - - return 0; -} - -static int bcl_curr_input_read(struct bcl_device *bcl, long *out) -{ - int ret; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_CURR_INPUT, &raw_val); - if (ret) - return ret; - - /* - * The sensor sometime can read a value 0 if there are - * consecutive reads - */ - if (raw_val != 0) - bcl->last_curr_input = - bcl_convert_raw_to_milliunit(desc, raw_val, CURR, - desc->data_field_bits_size); - - *out = bcl->last_curr_input; - - return 0; -} - -static int bcl_in_input_read(struct bcl_device *bcl, long *out) -{ - int ret; - u32 raw_val = 0; - const struct bcl_desc *desc = bcl->desc; - - ret = bcl_read_field_value(bcl, F_IN_INPUT, &raw_val); - if (ret) - return ret; - - if (raw_val < GENMASK(desc->data_field_bits_size - 1, 0)) - bcl->last_in_input = - bcl_convert_raw_to_milliunit(desc, raw_val, IN, - desc->data_field_bits_size); - - *out = bcl->last_in_input; - - return 0; -} - -static int bcl_read_alarm_status(struct bcl_device *bcl, - enum bcl_limit_alarm lvl, long *status) -{ - int ret; - u32 raw_val = 0; - - ret = bcl_read_field_value(bcl, F_LVL0_ALARM + lvl, &raw_val); - if (ret) - return ret; - - *status = raw_val; - - return 0; -} - -static unsigned int bcl_get_version_major(const struct bcl_device *bcl) -{ - u32 raw_val = 0; - - bcl_read_field_value(bcl, F_V_MAJOR, &raw_val); - - return raw_val; -} - -static unsigned int bcl_get_version_minor(const struct bcl_device *bcl) -{ - u32 raw_val = 0; - - bcl_read_field_value(bcl, F_V_MINOR, &raw_val); - - return raw_val; -} - -static void bcl_hwmon_notify_event(struct bcl_device *bcl, enum bcl_limit_alarm alarm) -{ - if (bcl->in_mon_enabled) - hwmon_notify_event(bcl->hwmon_dev, hwmon_in, - in_lvl_to_attr_map[alarm], 0); - if (bcl->curr_mon_enabled) - hwmon_notify_event(bcl->hwmon_dev, hwmon_curr, - curr_lvl_to_attr_map[alarm], 0); -} - -static void bcl_alarm_enable_poll(struct work_struct *work) -{ - struct bcl_alarm_data *alarm = container_of(work, struct bcl_alarm_data, - alarm_poll_work.work); - struct bcl_device *bcl = alarm->device; - long status; - - guard(mutex)(&bcl->lock); - - if (bcl_read_alarm_status(bcl, alarm->type, &status)) - goto re_schedule; - - if (!status & !alarm->irq_enabled) { - bcl_enable_irq(alarm); - bcl_hwmon_notify_event(bcl, alarm->type); - return; - } - -re_schedule: - schedule_delayed_work(&alarm->alarm_poll_work, - msecs_to_jiffies(BCL_ALARM_POLLING_MS)); -} - -static irqreturn_t bcl_handle_alarm(int irq, void *data) -{ - struct bcl_alarm_data *alarm = data; - struct bcl_device *bcl = alarm->device; - long status; - - guard(mutex)(&bcl->lock); - - if (bcl_read_alarm_status(bcl, alarm->type, &status) || !status) - return IRQ_HANDLED; - - if (!bcl->hwmon_dev) - return IRQ_HANDLED; - - bcl_hwmon_notify_event(bcl, alarm->type); - - bcl_disable_irq(alarm); - schedule_delayed_work(&alarm->alarm_poll_work, - msecs_to_jiffies(BCL_ALARM_POLLING_MS)); - - dev_dbg(bcl->dev, "Irq:%d triggered for bcl type:%d\n", - irq, alarm->type); - - return IRQ_HANDLED; -} - -static umode_t bcl_hwmon_is_visible(const void *data, - enum hwmon_sensor_types type, - u32 attr, int channel) -{ - const struct bcl_device *bcl = data; - - switch (type) { - case hwmon_in: - if (!bcl->in_mon_enabled) - return 0; - switch (attr) { - case hwmon_in_input: - return bcl->in_input_enabled ? 0444 : 0; - case hwmon_in_label: - case hwmon_in_min_alarm: - case hwmon_in_lcrit_alarm: - return 0444; - case hwmon_in_min: - case hwmon_in_lcrit: - return 0644; - default: - return 0; - } - case hwmon_curr: - if (!bcl->curr_mon_enabled) - return 0; - switch (attr) { - case hwmon_curr_input: - case hwmon_curr_label: - case hwmon_curr_max_alarm: - case hwmon_curr_crit_alarm: - return 0444; - case hwmon_curr_max: - case hwmon_curr_crit: - return 0644; - default: - return 0; - } - default: - return 0; - } -} - -static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long val) -{ - struct bcl_device *bcl = dev_get_drvdata(dev); - int ret = -EOPNOTSUPP; - - guard(mutex)(&bcl->lock); - - switch (type) { - case hwmon_in: - switch (attr) { - case hwmon_in_min: - case hwmon_in_lcrit: - ret = bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); - break; - default: - ret = -EOPNOTSUPP; - } - break; - case hwmon_curr: - switch (attr) { - case hwmon_curr_max: - case hwmon_curr_crit: - ret = bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); - break; - default: - ret = -EOPNOTSUPP; - } - break; - default: - break; - } - - return ret; -} - -static int bcl_hwmon_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long *value) -{ - struct bcl_device *bcl = dev_get_drvdata(dev); - int ret; - - guard(mutex)(&bcl->lock); - - switch (type) { - case hwmon_in: - switch (attr) { - case hwmon_in_input: - ret = bcl_in_input_read(bcl, value); - break; - case hwmon_in_min: - case hwmon_in_lcrit: - ret = bcl_in_thresh_read(bcl, in_attr_to_lvl_map[attr], value); - break; - case hwmon_in_min_alarm: - case hwmon_in_lcrit_alarm: - ret = bcl_read_alarm_status(bcl, in_attr_to_lvl_map[attr], value); - break; - default: - ret = -EOPNOTSUPP; - } - break; - case hwmon_curr: - switch (attr) { - case hwmon_curr_input: - ret = bcl_curr_input_read(bcl, value); - break; - case hwmon_curr_max: - case hwmon_curr_crit: - ret = bcl_curr_thresh_read(bcl, curr_attr_to_lvl_map[attr], value); - break; - case hwmon_curr_max_alarm: - case hwmon_curr_crit_alarm: - ret = bcl_read_alarm_status(bcl, curr_attr_to_lvl_map[attr], value); - break; - default: - ret = -EOPNOTSUPP; - } - break; - default: - ret = -EOPNOTSUPP; - break; - } - - return ret; -} - -static int bcl_hwmon_read_string(struct device *dev, - enum hwmon_sensor_types type, - u32 attr, int channel, const char **str) -{ - switch (type) { - case hwmon_in: - if (attr != hwmon_in_label) - break; - *str = bcl_channel_label[IN]; - return 0; - case hwmon_curr: - if (attr != hwmon_curr_label) - break; - *str = bcl_channel_label[CURR]; - return 0; - default: - break; - } - - return -EOPNOTSUPP; -} - -static const struct hwmon_ops bcl_hwmon_ops = { - .is_visible = bcl_hwmon_is_visible, - .read = bcl_hwmon_read, - .read_string = bcl_hwmon_read_string, - .write = bcl_hwmon_write, -}; - -static const struct hwmon_channel_info *bcl_hwmon_info[] = { - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_MIN | - HWMON_I_LCRIT | HWMON_I_MIN_ALARM | - HWMON_I_LCRIT_ALARM), - HWMON_CHANNEL_INFO(curr, - HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_MAX | - HWMON_C_CRIT | HWMON_C_MAX_ALARM | - HWMON_C_CRIT_ALARM), - NULL, -}; - -static const struct hwmon_chip_info bcl_hwmon_chip_info = { - .ops = &bcl_hwmon_ops, - .info = bcl_hwmon_info, -}; - -static int bcl_curr_thresh_update(struct bcl_device *bcl) -{ - int ret, i; - - if (!bcl->curr_thresholds[0]) - return 0; - - for (i = 0; i < ALARM_MAX; i++) { - ret = bcl_curr_thresh_write(bcl, bcl->curr_thresholds[i], i); - if (ret < 0) - return ret; - } - - return 0; -} - -static void bcl_hw_channel_mon_init(struct bcl_device *bcl) -{ - bcl->in_mon_enabled = bcl_in_monitor_enabled(bcl); - bcl->in_input_enabled = bcl_in_input_enabled(bcl); - bcl->curr_mon_enabled = bcl_curr_monitor_enabled(bcl); -} - -static int bcl_alarm_irq_init(struct platform_device *pdev, - struct bcl_device *bcl) -{ - int ret = 0, irq_num = 0, i = 0; - struct bcl_alarm_data *alarm; - - for (i = LVL0; i < ALARM_MAX; i++) { - alarm = &bcl->bcl_alarms[i]; - alarm->type = i; - alarm->device = bcl; - - ret = devm_delayed_work_autocancel(bcl->dev, &alarm->alarm_poll_work, - bcl_alarm_enable_poll); - if (ret) - return ret; - - irq_num = platform_get_irq_byname(pdev, bcl_int_names[i]); - if (irq_num <= 0) - continue; - - ret = devm_request_threaded_irq(&pdev->dev, irq_num, NULL, - bcl_handle_alarm, IRQF_ONESHOT, - bcl_int_names[i], alarm); - if (ret) { - dev_err(&pdev->dev, "Error requesting irq(%s).err:%d\n", - bcl_int_names[i], ret); - return ret; - } - alarm->irq = irq_num; - enable_irq_wake(alarm->irq); - alarm->irq_enabled = true; - } - - return 0; -} - -static int bcl_regmap_field_init(struct device *dev, struct bcl_device *bcl, - const struct bcl_desc *data) -{ - int i; - struct reg_field fields[F_MAX_FIELDS]; - - BUILD_BUG_ON(ARRAY_SIZE(common_reg_fields) != COMMON_FIELD_MAX); - - for (i = 0; i < data->num_reg_fields; i++) { - if (i < COMMON_FIELD_MAX) - fields[i] = common_reg_fields[i]; - else - fields[i] = data->reg_fields[i]; - - /* Need to adjust BCL base from regmap dynamically */ - fields[i].reg += bcl->base; - } - - return devm_regmap_field_bulk_alloc(dev, bcl->regmap, bcl->fields, - fields, data->num_reg_fields); -} - -static int bcl_get_device_property_data(struct platform_device *pdev, - struct bcl_device *bcl) -{ - struct device *dev = &pdev->dev; - int ret; - u32 reg; - - ret = device_property_read_u32(dev, "reg", ®); - if (ret < 0) - return ret; - - bcl->base = reg; - - device_property_read_u32_array(dev, "overcurrent-thresholds-milliamp", - bcl->curr_thresholds, 2); - return 0; -} - -static int bcl_probe(struct platform_device *pdev) -{ - struct bcl_device *bcl; - int ret; - - bcl = devm_kzalloc(&pdev->dev, sizeof(*bcl), GFP_KERNEL); - if (!bcl) - return -ENOMEM; - - bcl->dev = &pdev->dev; - bcl->desc = device_get_match_data(&pdev->dev); - if (!bcl->desc) - return -EINVAL; - - ret = devm_mutex_init(bcl->dev, &bcl->lock); - if (ret) - return ret; - - bcl->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!bcl->regmap) { - dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); - return -EINVAL; - } - - ret = bcl_get_device_property_data(pdev, bcl); - if (ret < 0) - return ret; - - ret = bcl_regmap_field_init(bcl->dev, bcl, bcl->desc); - if (ret < 0) { - dev_err(&pdev->dev, "Unable to allocate regmap fields, err:%d\n", ret); - return ret; - } - - if (!bcl_hw_is_enabled(bcl)) - return -ENODEV; - - ret = bcl_curr_thresh_update(bcl); - if (ret < 0) - return ret; - - ret = bcl_alarm_irq_init(pdev, bcl); - if (ret < 0) - return ret; - - bcl_hw_channel_mon_init(bcl); - - dev_set_drvdata(&pdev->dev, bcl); - - bcl->hwmon_name = devm_hwmon_sanitize_name(&pdev->dev, - dev_name(bcl->dev)); - if (IS_ERR(bcl->hwmon_name)) { - dev_err(&pdev->dev, "Failed to sanitize hwmon name\n"); - return PTR_ERR(bcl->hwmon_name); - } - - bcl->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, - bcl->hwmon_name, - bcl, - &bcl_hwmon_chip_info, - NULL); - if (IS_ERR(bcl->hwmon_dev)) { - dev_err(&pdev->dev, "Failed to register hwmon device: %ld\n", - PTR_ERR(bcl->hwmon_dev)); - return PTR_ERR(bcl->hwmon_dev); - } - - dev_dbg(&pdev->dev, "BCL hwmon device with version: %u.%u registered\n", - bcl_get_version_major(bcl), bcl_get_version_minor(bcl)); - - return 0; -} - -static const struct of_device_id bcl_match[] = { - { - .compatible = "qcom,bcl-v1", - .data = &pm7250b_data, - }, { - .compatible = "qcom,bcl-v2", - .data = &pm8350_data, - }, { - .compatible = "qcom,bcl-v3-bmx", - .data = &pm8550b_data, - }, { - .compatible = "qcom,bcl-v3-wb", - .data = &pmw5100_data, - }, { - .compatible = "qcom,bcl-v3-core", - .data = &pm8550_data, - }, { - .compatible = "qcom,bcl-v4-bmx", - .data = &pmih010_data, - }, { - .compatible = "qcom,bcl-v4-wb", - .data = &pmw6100_data, - }, { - .compatible = "qcom,bcl-v4-core", - .data = &pmh010_data, - }, { - .compatible = "qcom,bcl-v4-pmv010", - .data = &pmv010_data, - }, - { } -}; -MODULE_DEVICE_TABLE(of, bcl_match); - -static struct platform_driver bcl_driver = { - .probe = bcl_probe, - .driver = { - .name = BCL_DRIVER_NAME, - .of_match_table = bcl_match, - }, -}; - -MODULE_AUTHOR("Manaf Meethalavalappu Pallikunhi "); -MODULE_DESCRIPTION("QCOM BCL HWMON driver"); -module_platform_driver(bcl_driver); -MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/qcom-bcl-hwmon.h b/drivers/hwmon/qcom-bcl-hwmon.h deleted file mode 100644 index 28a7154d9486a..0000000000000 --- a/drivers/hwmon/qcom-bcl-hwmon.h +++ /dev/null @@ -1,311 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. - */ - -#ifndef __QCOM_BCL_HWMON_H__ -#define __QCOM_BCL_HWMON_H__ - -#define BCL_DRIVER_NAME "qcom-bcl-hwmon" - -/* BCL common regmap offset */ -#define REVISION1 0x0 -#define REVISION2 0x1 -#define STATUS 0x8 -#define INT_RT_STS 0x10 -#define EN_CTL1 0x46 - -/* BCL GEN1 regmap offsets */ -#define MODE_CTL1 0x41 -#define VADC_L0_THR 0x48 -#define VCMP_L1_THR 0x49 -#define IADC_H0_THR 0x4b -#define IADC_H1_THR 0x4c -#define VADC_CONV_REQ 0x72 -#define IADC_CONV_REQ 0x82 -#define VADC_DATA1 0x76 -#define IADC_DATA1 0x86 - -/* BCL GEN3 regmap offsets */ -#define VCMP_CTL 0x44 -#define VCMP_L0_THR 0x47 -#define PARAM_1 0x0e -#define IADC_H1_THR_GEN3 0x4d - -#define BCL_IN_INC_MV 25 -#define BCL_ALARM_POLLING_MS 50 - -/** - * enum bcl_limit_alarm - BCL alarm threshold levels - * @LVL0: Level 0 alarm threshold (mapped to in_min_alarm or curr_max_alarm) - * @LVL1: Level 1 alarm threshold (mapped to in_lcrit_alarm or curr_crit_alarm) - * @ALARM_MAX: sentinel value - * - * Defines the three threshold levels for BCL monitoring. Each level corresponds - * to different severity of in or curr conditions. - */ -enum bcl_limit_alarm { - LVL0, - LVL1, - - ALARM_MAX, -}; - -/** - * enum bcl_channel_type - BCL supported sensor channel type - * @IN: in (voltage) channel - * @CURR: curr (current) channel - * @CHANNEL_MAX: sentinel value - * - * Defines the supported channel types for bcl. - */ -enum bcl_channel_type { - IN, - CURR, - - CHANNEL_MAX, -}; - -/** - * enum bcl_thresh_type - voltage or current threshold representation type - * @ADC: Raw ADC value representation - * @INDEX: Index-based voltage or current representation - * - * Specifies how voltage or current thresholds are stored and interpreted in - * registers. Some PMICs use raw ADC values while others use indexed values. - */ -enum bcl_thresh_type { - ADC, - INDEX, -}; - -/** - * enum bcl_fields - BCL register field identifiers - * @F_V_MAJOR: Major revision info field - * @F_V_MINOR: Minor revision info field - * @F_CTL_EN: Monitor enable control field - * @F_LVL0_ALARM: Level 0 alarm status field - * @F_LVL1_ALARM: Level 1 alarm status field - * @COMMON_FIELD_MAX: sentinel value for common fields - * @F_IN_MON_EN: voltage monitor enable control field - * @F_IN_L0_THR: voltage level 0 threshold field - * @F_IN_L1_THR: voltage level 1 threshold field - * @F_IN_INPUT_EN: voltage input enable control field - * @F_IN_INPUT: voltage input data field - * @F_CURR_MON_EN: current monitor enable control field - * @F_CURR_H0_THR: current level 0 threshold field - * @F_CURR_H1_THR: current level 1 threshold field - * @F_CURR_INPUT: current input data field - * @F_MAX_FIELDS: sentinel value - * - * Enumeration of all register fields used by the BCL driver for accessing - * registers through regmap fields. - */ -enum bcl_fields { - F_V_MAJOR, - F_V_MINOR, - - F_CTL_EN, - - /* common alarm for in and curr channel */ - F_LVL0_ALARM, - F_LVL1_ALARM, - - COMMON_FIELD_MAX, - - F_IN_MON_EN = COMMON_FIELD_MAX, - F_IN_L0_THR, - F_IN_L1_THR, - - F_IN_INPUT_EN, - F_IN_INPUT, - - F_CURR_MON_EN, - F_CURR_H0_THR, - F_CURR_H1_THR, - - F_CURR_INPUT, - - F_MAX_FIELDS -}; - -#define ADD_BCL_HWMON_ALARM_MAPS(_type, lvl0_attr, lvl1_attr) \ - \ -static const u8 _type##_attr_to_lvl_map[] = { \ - [hwmon_##_type##_##lvl0_attr] = LVL0, \ - [hwmon_##_type##_##lvl1_attr] = LVL1, \ - [hwmon_##_type##_##lvl0_attr##_alarm] = LVL0, \ - [hwmon_##_type##_##lvl1_attr##_alarm] = LVL1, \ -}; \ - \ -static const u8 _type##_lvl_to_attr_map[ALARM_MAX] = { \ - [LVL0] = hwmon_##_type##_##lvl0_attr##_alarm, \ - [LVL1] = hwmon_##_type##_##lvl1_attr##_alarm, \ -} - -/** - * struct bcl_channel_cfg - BCL channel related configuration - * @default_scale_nu: Default scaling factor in nano unit - * @base: Base threshold value in milli unit - * @max: Maximum threshold value in milli unit - * @step: step increment value between two indexed threshold value - * @thresh_type: Array specifying threshold representation type for each alarm level - * - * Contains hardware-specific configuration and scaling parameters for different - * channel(voltage and current).. - */ - -struct bcl_channel_cfg { - u32 default_scale_nu; - u32 base; - u32 max; - u32 step; - u8 thresh_type[ALARM_MAX]; -}; - -/** - * struct bcl_desc - BCL device descriptor - * @reg_fields: Array of register field definitions for this device variant - * @channel: Each channel specific(voltage or current) configuration - * @num_reg_fields: Number of register field definitions for this device variant - * @data_field_bits_size: data read register bit size - * @thresh_field_bits_size: lsb bit size those are not included in threshold register - * - * Contains hardware-specific configuration and scaling parameters for different - * BCL variants. Each PMIC model may have different register layouts and - * conversion factors. - */ - -struct bcl_desc { - const struct reg_field *reg_fields; - struct bcl_channel_cfg channel[CHANNEL_MAX]; - u8 num_reg_fields; - u8 data_field_bits_size; - u8 thresh_field_bits_size; -}; - -struct bcl_device; - -/** - * struct bcl_alarm_data - BCL alarm interrupt data - * @irq: IRQ number assigned to this alarm - * @irq_enabled: Flag indicating if IRQ is enabled - * @type: Alarm level type (LVL0, or LVL1) - * @device: Pointer to parent BCL device structure - * @alarm_poll_work: delayed_work to poll alarm status - * - * Stores interrupt-related information for each alarm threshold level. - * Used by the IRQ handler to identify which alarm triggered. - */ -struct bcl_alarm_data { - int irq; - bool irq_enabled; - enum bcl_limit_alarm type; - struct bcl_device *device; - struct delayed_work alarm_poll_work; -}; - -/** - * struct bcl_device - Main BCL device structure - * @dev: Pointer to device structure - * @regmap: Regmap for accessing PMIC registers - * @fields: Array of regmap fields for register access - * @bcl_alarms: Array of alarm data structures for each threshold level - * @lock: Mutex for protecting concurrent hardware access - * @in_mon_enabled: Flag indicating if voltage monitoring is enabled - * @curr_mon_enabled: Flag indicating if current monitoring is enabled - * @curr_thresholds: Current threshold values in milliamps from dt-binding(LVL0 and LVL1) - * @base: the BCL regbase offset from regmap - * @in_input_enabled: Flag indicating if voltage input reading is enabled - * @last_in_input: Last valid voltage input reading in millivolts - * @last_curr_input: Last valid current input reading in milliamps - * @desc: Pointer to device descriptor with hardware-specific parameters - * @hwmon_dev: Pointer to registered hwmon device - * @hwmon_name: Sanitized name for hwmon device - * - * Main driver structure containing all state and configuration for a BCL - * monitoring instance. Manages voltage and current monitoring, thresholds, - * and alarm handling. - */ -struct bcl_device { - struct device *dev; - struct regmap *regmap; - u16 base; - struct regmap_field *fields[F_MAX_FIELDS]; - struct bcl_alarm_data bcl_alarms[ALARM_MAX]; - struct mutex lock; - u32 curr_thresholds[ALARM_MAX]; - u32 last_in_input; - u32 last_curr_input; - bool in_mon_enabled; - bool curr_mon_enabled; - bool in_input_enabled; - const struct bcl_desc *desc; - struct device *hwmon_dev; - char *hwmon_name; -}; - -/** - * bcl_read_field_value - Read alarm status for a given level - * @bcl: BCL device structure - * @id: Index in bcl->fields[] - * @val: Pointer to store val - * - * Return: 0 on success or regmap error code - */ -static inline int bcl_read_field_value(const struct bcl_device *bcl, enum bcl_fields id, u32 *val) -{ - return regmap_field_read(bcl->fields[id], val); -} - -/** - * bcl_field_enabled - Generic helper to check if a regmap field is enabled - * @bcl: BCL device structure - * @field: Index in bcl->fields[] - * - * Return: true if field is non-zero, false otherwise - */ -static inline bool bcl_field_enabled(const struct bcl_device *bcl, enum bcl_fields id) -{ - int ret; - u32 val = 0; - - ret = regmap_field_read(bcl->fields[id], &val); - if (ret) - return false; - - return !!val; -} - -#define bcl_in_input_enabled(bcl) bcl_field_enabled(bcl, F_IN_INPUT_EN) -#define bcl_curr_monitor_enabled(bcl) bcl_field_enabled(bcl, F_CURR_MON_EN) -#define bcl_in_monitor_enabled(bcl) bcl_field_enabled(bcl, F_IN_MON_EN) -#define bcl_hw_is_enabled(bcl) bcl_field_enabled(bcl, F_CTL_EN) - -/** - * bcl_enable_irq - Generic helper to enable alarm irq - * @alarm: BCL level alarm data - */ -static inline void bcl_enable_irq(struct bcl_alarm_data *alarm) -{ - if (alarm->irq_enabled) - return; - alarm->irq_enabled = true; - enable_irq(alarm->irq); - enable_irq_wake(alarm->irq); -} - -/** - * bcl_disable_irq - Generic helper to disable alarm irq - * @alarm: BCL level alarm data - */ -static inline void bcl_disable_irq(struct bcl_alarm_data *alarm) -{ - if (!alarm->irq_enabled) - return; - alarm->irq_enabled = false; - disable_irq_nosync(alarm->irq); - disable_irq_wake(alarm->irq); -} - -#endif /* __QCOM_BCL_HWMON_H__ */ From 5c91732bd46dcfc1659c2874663bbfc7c8347c9f Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Mon, 3 Aug 2026 10:26:39 +0530 Subject: [PATCH 05/12] Revert "FROMLIST: dt-bindings: hwmon: Add qcom,bcl-hwmon yaml bindings" This reverts commit 25dd59f7a90ac77b73965a56f7747198c8cf9dc4. These changes are superseded by the BCL v2 patch series. Signed-off-by: Dipa Ramesh Mantre --- .../bindings/hwmon/qcom,bcl-hwmon.yaml | 128 ------------------ .../bindings/mfd/qcom,spmi-pmic.yaml | 4 - 2 files changed, 132 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml diff --git a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml deleted file mode 100644 index 6ffadc4d28bcf..0000000000000 --- a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/hwmon/qcom,bcl-hwmon.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor - -maintainers: - - Manaf Meethalavalappu Pallikunhi - -description: | - SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and - alarm functionality for battery overcurrent and battery or system under - voltage conditions. It monitors battery voltage and current, and - can trigger interrupts when configurable thresholds are exceeded. - -properties: - compatible: - oneOf: - - description: v1 based BCL - items: - - enum: - - qcom,pm7250b-bcl - - qcom,pm8250b-bcl - - const: qcom,bcl-v1 - - - description: v2 based BCL - items: - - enum: - - qcom,pm8350b-bcl - - qcom,pm8350c-bcl - - const: qcom,bcl-v2 - - - description: v3 bmx based BCL - items: - - enum: - - qcom,pm8550b-bcl - - qcom,pm7550ba-bcl - - const: qcom,bcl-v3-bmx - - - description: v3 core based BCL - items: - - enum: - - qcom,pm8550-bc0l - - qcom,pm7550-bcl - - const: qcom,bcl-v3-core - - - description: v3 wb based BCL - items: - - enum: - - qcom,pmw5100-bcl - - const: qcom,bcl-v3-wb - - - description: v4 bmx based BCL - items: - - enum: - - qcom,pmih010-bcl - - const: qcom,bcl-v4-bmx - - - description: v4 bmx with different scale based BCL - items: - - enum: - - qcom,pmv010-bcl - - const: qcom,bcl-v4-pmv010 - - - description: v4 core based BCL - items: - - enum: - - qcom,pmh010-bcl - - const: qcom,bcl-v4-core - - - description: v4 wb based BCL - items: - - enum: - - qcom,pmw6100-bcl - - const: qcom,bcl-v4-wb - - reg: - maxItems: 1 - description: BCL base address in the SPMI PMIC register map - - interrupts: - minItems: 2 - maxItems: 2 - description: - BCL alarm interrupts for different threshold levels - - interrupt-names: - items: - - const: bcl-max-min - - const: bcl-critical - - overcurrent-thresholds-milliamp: - description: - Current thresholds in milliamperes for the two configurable current - alarm levels (max and critical). These values are used to override - default thresholds if a platform has different battery ocp specification. - $ref: /schemas/types.yaml#/definitions/uint32-array - minItems: 2 - maxItems: 2 - -required: - - compatible - - reg - - interrupts - - interrupt-names - -unevaluatedProperties: false - -examples: - - | - #include - - pmic { - #address-cells = <1>; - #size-cells = <0>; - - sensor@1d00 { - compatible = "qcom,pm7250b-bcl", "qcom,bcl-v1"; - reg = <0x1d00>; - interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, - <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "bcl-max-min", - "bcl-critical"; - overcurrent-thresholds-milliamp = <5500 6000>; - }; - }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index d10085d85a97e..5ede256e4f41b 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -185,10 +185,6 @@ patternProperties: type: object $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml# - "^sensor@[0-9a-f]+$": - type: object - $ref: /schemas/hwmon/qcom,bcl-hwmon.yaml# - "^temp-alarm@[0-9a-f]+$": type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# From 7af1b91bb3c85926cc313705abdd991923d8372d Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:06 +0530 Subject: [PATCH 06/12] FROMLIST: dt-bindings: hwmon: Describe Qualcomm BCL hardware Qualcomm SPMI PMIC BCL (Battery Current Limiting) is a safety feature that monitors battery or system voltage and current to alert system for overcurrent or undervoltage conditions. It provides: - Real-time voltage and current monitoring - Configurable thresholds per channel - Hardware interrupts when thresholds are violated Add a DeviceTree binding to describe the BCL on Qualcomm's PMICs. Add sensor type to SPMI device list for BCL device. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-1-febe2805e17b@oss.qualcomm.com --- .../bindings/hwmon/qcom,pm7250b-bcl.yaml | 64 +++++++++++++++++++ .../bindings/mfd/qcom,spmi-pmic.yaml | 4 ++ MAINTAINERS | 8 +++ 3 files changed, 76 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml diff --git a/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml b/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml new file mode 100644 index 0000000000000..3c069e94a34e8 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/qcom,pm7250b-bcl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor + +maintainers: + - Manaf Meethalavalappu Pallikunhi + +description: + SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and + alarm functionality for battery overcurrent and battery or system under + voltage conditions. It monitors battery voltage and current, and + can trigger interrupts when configurable thresholds are exceeded. + +properties: + compatible: + enum: + - qcom,pm7250b-bcl + - qcom,pm8350c-bcl + - qcom,pm8550-bcl + - qcom,pmh0101-bcl + - qcom,pmih0108-bcl + - qcom,smb2360-bcl + - qcom,smb2370-bcl + + reg: + maxItems: 1 + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: max-min + - const: critical + +required: + - compatible + - reg + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1d00 { + compatible = "qcom,pm7250b-bcl"; + reg = <0x1d00>; + interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 5ede256e4f41b..946e1361a5334 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -185,6 +185,10 @@ patternProperties: type: object $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml# + "^sensor@[0-9a-f]+$": + type: object + $ref: /schemas/hwmon/qcom,pm7250b-bcl.yaml# + "^temp-alarm@[0-9a-f]+$": type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# diff --git a/MAINTAINERS b/MAINTAINERS index d863f8f9f5824..d40821faed832 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21092,6 +21092,14 @@ S: Maintained F: Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml F: drivers/net/wwan/qcom_bam_dmux.c + +QUALCOMM BCL HARDWARE MONITOR DRIVER +M: Manaf Meethalavalappu Pallikunhi +L: linux-hwmon@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml + QUALCOMM BLUETOOTH DRIVER L: linux-arm-msm@vger.kernel.org S: Maintained From 62d01450875c7210b8fbd117455ca732a758379c Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:07 +0530 Subject: [PATCH 07/12] FROMLIST: hwmon: Add Qualcomm PMIC BCL driver Add driver for Qualcomm SPMI PMIC Battery Current Limiting (BCL) hardware monitor. The driver exposes battery voltage and current monitoring through hwmon interface. The BCL driver provides - Real-time voltage and current readings - Configurable threshold-based alarms - Interrupt-driven notifications when thresholds are exceeded - Automatic threshold management with polling-based recovery - Hardware-specific scaling factors and threshold representations Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-2-febe2805e17b@oss.qualcomm.com --- drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/qcom-bcl-hwmon.c | 1443 ++++++++++++++++++++++++++++++++ 3 files changed, 1455 insertions(+) create mode 100644 drivers/hwmon/qcom-bcl-hwmon.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2a71b6e834b08..e5238546fd99b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1893,6 +1893,17 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. +config SENSORS_QCOM_SPMI_BCL + tristate "Qualcomm SPMI BCL hardware monitoring" + depends on SPMI + select REGMAP_SPMI + help + Say yes here to enable support for Qualcomm battery over current + and under voltage alarms monitor. + + This driver can also be built as a module. If so, the module + will be called qcom-bcl-hwmon. + config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 73b2abdcc6dd9..f9cd9550731aa 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -196,6 +196,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o +obj-$(CONFIG_SENSORS_QCOM_SPMI_BCL) += qcom-bcl-hwmon.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c new file mode 100644 index 0000000000000..33bbdd8196d16 --- /dev/null +++ b/drivers/hwmon/qcom-bcl-hwmon.c @@ -0,0 +1,1443 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm pmic BCL driver for battery overcurrent and + * battery or system under voltage monitor + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* BCL common regmap offset */ +#define REVISION1 0x0 +#define REVISION2 0x1 +#define STATUS 0x8 +#define INT_RT_STS 0x10 +#define EN_CTL1 0x46 + +/* BCL GEN1 regmap offsets */ +#define MODE_CTL1 0x41 +#define VADC_L0_THR 0x48 +#define VCMP_L1_THR 0x49 +#define IADC_H0_THR 0x4b +#define IADC_H1_THR 0x4c +#define VADC_CONV_REQ 0x72 +#define IADC_CONV_REQ 0x82 +#define VADC_DATA1 0x76 +#define IADC_DATA1 0x86 + +/* BCL GEN3 regmap offsets */ +#define VCMP_CTL 0x44 +#define VCMP_L0_THR 0x47 +#define PARAM_1 0x0e +#define IADC_H1_THR_GEN3 0x4d + +#define BCL_IN_INC_MV 25 +#define BCL_ALARM_POLLING_MS 50 + +/** + * enum bcl_limit_alarm - BCL alarm threshold levels + * @BCL_LIMIT_ALARM_LVL0: Level 0 alarm threshold + * @BCL_LIMIT_ALARM_LVL1: Level 1 alarm threshold + * @BCL_LIMIT_ALARM_MAX: sentinel value + * + * Defines the two threshold levels for BCL monitoring. Each level corresponds + * to different severity of in or curr conditions. + */ +enum bcl_limit_alarm { + BCL_LIMIT_ALARM_LVL0, + BCL_LIMIT_ALARM_LVL1, + + BCL_LIMIT_ALARM_MAX, +}; + +/** + * enum bcl_channel - BCL supported sensor channel type + * @CHANNEL_IN: in (voltage) channel + * @CHANNEL_CURR: curr (current) channel + * @CHANNEL_MAX: sentinel value + * + * Defines the supported channel types for bcl. + */ +enum bcl_channel { + CHANNEL_IN, + CHANNEL_CURR, + + CHANNEL_MAX, +}; + +/** + * enum bcl_thresh_type - voltage or current threshold representation type + * @THRESH_TYPE_ADC: Raw ADC value representation + * @THRESH_TYPE_INDEX: Index-based voltage or current representation + * + * Specifies how voltage or current thresholds are stored and interpreted in + * registers. Some PMICs use raw ADC values while others use indexed values. + */ +enum bcl_thresh_type { + THRESH_TYPE_ADC, + THRESH_TYPE_INDEX, +}; + +/** + * enum bcl_battery_config - Battery configuration types + * @BCL_BATT_1S: Single cell battery + * @BCL_BATT_2S: Two cells in series + * @BCL_BATT_3S: Three cells in series + * @BCL_BATT_UNKNOWN: Unknown or not applicable + */ +enum bcl_battery_config { + BCL_BATT_1S, + BCL_BATT_2S, + BCL_BATT_3S, + BCL_BATT_UNKNOWN, +}; + +/** + * enum bcl_fields - BCL register field identifiers + * @F_V_MAJOR: Major revision info field + * @F_V_MINOR: Minor revision info field + * @F_CTL_EN: Monitor enable control field + * @F_LVL0_ALARM: Level 0 alarm status field + * @F_LVL1_ALARM: Level 1 alarm status field + * @F_IN_MON_EN: voltage monitor enable control field + * @F_IN_L0_THR: voltage level 0 threshold field + * @F_IN_L1_THR: voltage level 1 threshold field + * @F_IN_INPUT_EN: voltage input enable control field + * @F_IN_INPUT: voltage input data field (LSB for 16-bit data) + * @F_IN_INPUT1: voltage input data MSB for 16-bit voltage data + * @F_CURR_MON_EN: current monitor enable control field + * @F_CURR_H0_THR: current level 0 threshold field + * @F_CURR_H1_THR: current level 1 threshold field + * @F_CURR_INPUT: current input data field (LSB for 16-bit data) + * @F_CURR_INPUT1: current input data MSB for 16-bit current data + * @F_MAX_FIELDS: sentinel value + * + * Enumeration of all register fields used by the BCL driver for accessing + * registers through regmap fields. + */ +enum bcl_fields { + /* Common fields - present in all BCL variants */ + F_V_MAJOR, + F_V_MINOR, + F_CTL_EN, + F_LVL0_ALARM, + F_LVL1_ALARM, + + /* Voltage monitoring fields */ + F_IN_MON_EN, + F_IN_L0_THR, + F_IN_L1_THR, + F_IN_INPUT_EN, + F_IN_INPUT, + F_IN_INPUT1, /* MSB for 16-bit voltage data */ + + /* Current monitoring fields */ + F_CURR_MON_EN, + F_CURR_H0_THR, + F_CURR_H1_THR, + F_CURR_INPUT, + F_CURR_INPUT1, /* MSB for 16-bit current data */ + + F_MAX_FIELDS +}; + +/** + * struct bcl_channel_cfg - BCL channel related configuration + * @default_scale_nu: Default scaling factor in nano unit + * @base: Base threshold value in milli unit + * @max: Maximum threshold value in milli unit + * @step: step increment value between two indexed threshold value + * @thresh_type: Array specifying threshold representation type for each alarm level + * + * Contains hardware-specific configuration and scaling parameters for different + * channel(voltage and current).. + */ +struct bcl_channel_cfg { + u32 default_scale_nu; + u32 base; + u32 max; + u32 step; + u8 thresh_type[BCL_LIMIT_ALARM_MAX]; +}; + +/** + * struct bcl_desc - BCL device descriptor + * @reg_fields: Array of register field definitions for this device variant + * @channel_cfg: Array of channel configurations indexed by battery config + * PMICs without battery detection: only [BCL_BATT_1S] is defined + * PMICs with battery detection: [BCL_BATT_2S], [BCL_BATT_3S] + * @battery_config_field: Register field for battery configuration detection + * NOTE: This is an ABSOLUTE address, not relative to BCL base + * Set to REG_FIELD(0, 0, 0) if not used + * @num_reg_fields: Number of register field definitions for this device variant + * @data_field_bits_size: data read register bit size + * @thresh_field_bits_size: lsb bit size those are not included in threshold register + * + * Contains hardware-specific configuration and scaling parameters for different + * BCL variants. Each PMIC model may have different register layouts and + * conversion factors. + */ +struct bcl_desc { + const struct reg_field *reg_fields; + struct bcl_channel_cfg channel_cfg[BCL_BATT_3S + 1][CHANNEL_MAX]; + const struct reg_field battery_config_field; + u8 num_reg_fields; + u8 data_field_bits_size; + u8 thresh_field_bits_size; +}; + +/** + * struct bcl_alarm_data - BCL alarm interrupt data + * @irq: IRQ number assigned to this alarm + * @irq_enabled: Flag indicating if IRQ is enabled + * @irq_wake_enabled: Flag indicating if IRQ wake is enabled + * @shutting_down: Flag preventing work from re-enabling IRQ during teardown + * @type: Alarm level type (LVL0, or LVL1) + * @device: Pointer to parent BCL device structure + * @a_lock: Mutex for protecting alarm state + * @alarm_poll_work: delayed_work to poll alarm status + * + * Stores interrupt-related information for each alarm threshold level. + * Used by the IRQ handler to identify which alarm triggered. + */ +struct bcl_alarm_data { + int irq; + bool irq_enabled; + bool irq_wake_enabled; + bool shutting_down; + enum bcl_limit_alarm type; + void *device; + /* Protects alarm IRQ enable/disable state */ + struct mutex a_lock; + struct delayed_work alarm_poll_work; +}; + +/** + * struct bcl_device - Main BCL device structure + * @dev: Pointer to device structure + * @regmap: Regmap for accessing PMIC registers + * @fields: Array of regmap fields for register access + * @bcl_alarms: Array of alarm data structures for each threshold level + * @lock: Mutex for protecting concurrent hardware access + * @base: the BCL regbase offset from regmap + * @last_in_input: Last valid voltage input reading in millivolts + * @last_curr_input: Last valid current input reading in milliamps + * @last_in_updated: Timestamp of last voltage input update + * @last_curr_updated: Timestamp of last current input update + * @desc: Pointer to device descriptor with hardware-specific parameters + * @hwmon_dev: Pointer to registered hwmon device + * @hwmon_info: Dynamically built hwmon channel info array + * @hwmon_chip_info: Dynamically built hwmon chip info structure + * @hwmon_name: Sanitized name for hwmon device + * @batt_config: Detected battery configuration (only for SMB2360/2370) + * @batt_config_regfield: Regmap field for battery configuration register + * @in_attrs: Voltage channel attributes mask + * @curr_attrs: Current channel attributes mask + * + * Main driver structure containing all state and configuration for a BCL + * monitoring instance. Manages voltage and current monitoring, thresholds, + * and alarm handling. + */ +struct bcl_device { + struct device *dev; + struct regmap *regmap; + u16 base; + struct regmap_field *fields[F_MAX_FIELDS]; + struct bcl_alarm_data bcl_alarms[BCL_LIMIT_ALARM_MAX]; + /* Protects hardware register access and device state */ + struct mutex lock; + u32 last_in_input; + s32 last_curr_input; + unsigned long last_in_updated; + unsigned long last_curr_updated; + const struct bcl_desc *desc; + struct device *hwmon_dev; + const struct hwmon_channel_info **hwmon_info; + struct hwmon_chip_info hwmon_chip_info; + char *hwmon_name; + enum bcl_battery_config batt_config; + struct regmap_field *batt_config_regfield; + u32 in_attrs; + u32 curr_attrs; +}; + +static const u8 in_attr_to_lvl_map[] = { + [hwmon_in_min] = BCL_LIMIT_ALARM_LVL0, + [hwmon_in_lcrit] = BCL_LIMIT_ALARM_LVL1, + [hwmon_in_min_alarm] = BCL_LIMIT_ALARM_LVL0, + [hwmon_in_lcrit_alarm] = BCL_LIMIT_ALARM_LVL1, +}; + +static const u8 in_lvl_to_attr_map[BCL_LIMIT_ALARM_MAX] = { + [BCL_LIMIT_ALARM_LVL0] = hwmon_in_min_alarm, + [BCL_LIMIT_ALARM_LVL1] = hwmon_in_lcrit_alarm, +}; + +static const u8 curr_attr_to_lvl_map[] = { + [hwmon_curr_max] = BCL_LIMIT_ALARM_LVL0, + [hwmon_curr_crit] = BCL_LIMIT_ALARM_LVL1, + [hwmon_curr_max_alarm] = BCL_LIMIT_ALARM_LVL0, + [hwmon_curr_crit_alarm] = BCL_LIMIT_ALARM_LVL1, +}; + +static const u8 curr_lvl_to_attr_map[BCL_LIMIT_ALARM_MAX] = { + [BCL_LIMIT_ALARM_LVL0] = hwmon_curr_max_alarm, + [BCL_LIMIT_ALARM_LVL1] = hwmon_curr_crit_alarm, +}; + +/* Interrupt names for each alarm level */ +static const char * const bcl_int_names[BCL_LIMIT_ALARM_MAX] = { + [BCL_LIMIT_ALARM_LVL0] = "max-min", + [BCL_LIMIT_ALARM_LVL1] = "critical", +}; + +static const struct reg_field bcl_pm7250b_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(MODE_CTL1, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_pm8350c_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 1), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(VADC_CONV_REQ, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(IADC_CONV_REQ, 0, 0), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_pm8550_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 5), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), +}; + +static const struct reg_field bcl_pmh0101_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 6), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_IN_INPUT1] = REG_FIELD(VADC_DATA1 + 1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), +}; + +static const struct reg_field bcl_pmih0108_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_IN_INPUT1] = REG_FIELD(VADC_DATA1 + 1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), + [F_CURR_INPUT1] = REG_FIELD(IADC_DATA1 + 1, 0, 7), +}; + +static const struct reg_field bcl_smb2360_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), +}; + +static const struct reg_field bcl_smb2370_reg_fields[] = { + [F_V_MAJOR] = REG_FIELD(REVISION2, 0, 7), + [F_V_MINOR] = REG_FIELD(REVISION1, 0, 7), + [F_CTL_EN] = REG_FIELD(EN_CTL1, 7, 7), + [F_LVL0_ALARM] = REG_FIELD(STATUS, 0, 0), + [F_LVL1_ALARM] = REG_FIELD(STATUS, 1, 1), + [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), + [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), + [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 7), + [F_IN_INPUT1] = REG_FIELD(VADC_DATA1 + 1, 0, 7), + [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), + [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), + [F_CURR_INPUT1] = REG_FIELD(IADC_DATA1 + 1, 0, 7), +}; + +static const struct bcl_desc pm7250b_data = { + .reg_fields = bcl_pm7250b_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 7, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { + .max = 10000, + .default_scale_nu = 305180, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc pm8350c_data = { + .reg_fields = bcl_pm8350c_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { + .max = 10000, + .default_scale_nu = 305180, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc pm8550_data = { + .reg_fields = bcl_pm8550_reg_fields, + .num_reg_fields = F_CURR_MON_EN + 1, + .data_field_bits_size = 0, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .thresh_type = {THRESH_TYPE_INDEX, THRESH_TYPE_INDEX}, + }, +}; + +static const struct bcl_desc pmih0108_data = { + .reg_fields = bcl_pmih0108_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 2250, + .max = 3600, + .step = 25, + .default_scale_nu = 194637, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 610370, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc pmh0101_data = { + .reg_fields = bcl_pmh0101_reg_fields, + .num_reg_fields = F_CURR_MON_EN + 1, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { + .base = 1500, + .max = 4000, + .step = 25, + .thresh_type = {THRESH_TYPE_INDEX, THRESH_TYPE_INDEX}, + }, +}; + +/* Register 0x2a50 mapping: 0 -> 2S, 1 -> 3S */ +static const struct bcl_desc smb2360_data = { + .reg_fields = bcl_smb2360_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, + .data_field_bits_size = 8, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0x2a50, 0, 1), + .channel_cfg[BCL_BATT_2S][CHANNEL_IN] = { + .base = 4500, + .max = 8400, + .step = 50, + .default_scale_nu = 432918, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_2S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 540679, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_IN] = { + .base = 6750, + .max = 12600, + .step = 75, + .default_scale_nu = 648790, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 540679, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +static const struct bcl_desc smb2370_data = { + .reg_fields = bcl_smb2370_reg_fields, + .num_reg_fields = F_MAX_FIELDS, + .data_field_bits_size = 16, + .thresh_field_bits_size = 8, + .battery_config_field = REG_FIELD(0x2a50, 0, 1), + .channel_cfg[BCL_BATT_2S][CHANNEL_IN] = { + .base = 4500, + .max = 8400, + .step = 50, + .default_scale_nu = 432918, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_2S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 1441603, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_IN] = { + .base = 6750, + .max = 12600, + .step = 75, + .default_scale_nu = 648790, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, + }, + .channel_cfg[BCL_BATT_3S][CHANNEL_CURR] = { + .max = 20000, + .default_scale_nu = 1441603, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, + }, +}; + +/** + * bcl_convert_raw_to_milliunit - Convert raw value to milli unit + * @bcl: BCL device structure + * @raw_val: Raw ADC value from hardware (signed for current, unsigned for voltage) + * @type: type of the channel, in or curr + * @field_width: bits size for data or threshold field + * + * Return: value in milli unit + */ +static int bcl_convert_raw_to_milliunit(const struct bcl_device *bcl, + s32 raw_val, + enum bcl_channel type, + u8 field_width) +{ + const struct bcl_desc *desc = bcl->desc; + u32 def_scale = desc->channel_cfg[bcl->batt_config][type].default_scale_nu; + u32 scaling_factor = (field_width > 8) ? def_scale : (def_scale << field_width); + + return DIV_ROUND_CLOSEST((s64)raw_val * scaling_factor, 1000000); +} + +/** + * bcl_convert_milliunit_to_raw - Convert milli unit to raw value + * @bcl: BCL device structure + * @mval: threshold value in milli unit + * @type: type of the channel, in or curr + * @field_width: bits size for data or threshold field + * + * Return: Raw ADC value for hardware + */ +static unsigned int bcl_convert_milliunit_to_raw(const struct bcl_device *bcl, + int mval, + enum bcl_channel type, + u8 field_width) +{ + const struct bcl_desc *desc = bcl->desc; + u32 def_scale = desc->channel_cfg[bcl->batt_config][type].default_scale_nu; + u32 scaling_factor = (field_width > 8) ? def_scale : (def_scale << field_width); + + return DIV_ROUND_CLOSEST_ULL((u64)mval * 1000000, scaling_factor); +} + +/** + * bcl_convert_milliunit_to_index - Convert milliunit to in or curr index + * @bcl: BCL device structure + * @val: in or curr value in milli unit + * @type: type of the channel, in or curr + * + * Converts a value in milli unit to an index for BCL that use indexed thresholds. + * + * Return: Index value + */ +static unsigned int bcl_convert_milliunit_to_index(const struct bcl_device *bcl, + int val, + enum bcl_channel type) +{ + const struct bcl_desc *desc = bcl->desc; + const struct bcl_channel_cfg *cfg = &desc->channel_cfg[bcl->batt_config][type]; + u64 diff = (u64)val - cfg->base; + + return DIV_ROUND_CLOSEST_ULL(diff, cfg->step); +} + +/** + * bcl_convert_index_to_milliunit - Convert in or curr index to milli unit + * @bcl: BCL device structure + * @val: index value + * @type: type of the channel, in or curr + * + * Converts an index value to milli unit for BCL that use indexed thresholds. + * + * Return: Value in millivolts or milliamps + */ +static unsigned int bcl_convert_index_to_milliunit(const struct bcl_device *bcl, + int val, + enum bcl_channel type) +{ + const struct bcl_desc *desc = bcl->desc; + const struct bcl_channel_cfg *cfg = &desc->channel_cfg[bcl->batt_config][type]; + + return cfg->base + val * cfg->step; +} + +static int bcl_in_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) +{ + const struct bcl_desc *desc = bcl->desc; + u32 raw_val; + + int thresh = clamp_val(value, desc->channel_cfg[bcl->batt_config][CHANNEL_IN].base, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].max); + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_IN].thresh_type[lvl] == THRESH_TYPE_ADC) + raw_val = bcl_convert_milliunit_to_raw(bcl, thresh, CHANNEL_IN, + desc->thresh_field_bits_size); + else + raw_val = bcl_convert_milliunit_to_index(bcl, thresh, CHANNEL_IN); + + return regmap_field_write(bcl->fields[F_IN_L0_THR + lvl], raw_val); +} + +static int bcl_curr_thresh_write(struct bcl_device *bcl, long value, enum bcl_limit_alarm lvl) +{ + const struct bcl_desc *desc = bcl->desc; + u32 raw_val; + + int thresh = clamp_val(value, 0, desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].max); + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].thresh_type[lvl] == THRESH_TYPE_ADC) + raw_val = bcl_convert_milliunit_to_raw(bcl, thresh, CHANNEL_CURR, + desc->thresh_field_bits_size); + else + raw_val = bcl_convert_milliunit_to_index(bcl, thresh, CHANNEL_CURR); + + return regmap_field_write(bcl->fields[F_CURR_H0_THR + lvl], raw_val); +} + +static int bcl_in_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) +{ + int ret, thresh; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = regmap_field_read(bcl->fields[F_IN_L0_THR + lvl], &raw_val); + if (ret) + return ret; + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_IN].thresh_type[lvl] == THRESH_TYPE_ADC) + thresh = bcl_convert_raw_to_milliunit(bcl, raw_val, CHANNEL_IN, + desc->thresh_field_bits_size); + else + thresh = bcl_convert_index_to_milliunit(bcl, raw_val, CHANNEL_IN); + + *out = thresh; + + return 0; +} + +static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, long *out) +{ + int ret, thresh; + u32 raw_val = 0; + const struct bcl_desc *desc = bcl->desc; + + ret = regmap_field_read(bcl->fields[F_CURR_H0_THR + lvl], &raw_val); + if (ret) + return ret; + + if (desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].thresh_type[lvl] == THRESH_TYPE_ADC) + thresh = bcl_convert_raw_to_milliunit(bcl, raw_val, CHANNEL_CURR, + desc->thresh_field_bits_size); + else + thresh = bcl_convert_index_to_milliunit(bcl, raw_val, CHANNEL_CURR); + + *out = thresh; + + return 0; +} + +static int bcl_curr_input_read(struct bcl_device *bcl, long *out) +{ + int ret; + u32 raw_val = 0, msb = 0; + s32 signed_val; + const struct bcl_desc *desc = bcl->desc; + + /* Return cached value if read too soon after last update */ + if (time_before(jiffies, bcl->last_curr_updated + HZ)) { + *out = bcl->last_curr_input; + return 0; + } + + ret = regmap_field_read(bcl->fields[F_CURR_INPUT], &raw_val); + if (ret) + return ret; + + /* For 16-bit data, read MSB and combine with LSB */ + if (desc->data_field_bits_size == 16) { + ret = regmap_field_read(bcl->fields[F_CURR_INPUT1], &msb); + if (ret) + return ret; + raw_val |= FIELD_PREP(GENMASK(15, 8), msb); + } + + /* + * Current ADC reading is in 2's complement form. + * Sign extend the value based on data field bit size. + */ + signed_val = sign_extend32(raw_val, desc->data_field_bits_size - 1); + + bcl->last_curr_input = + bcl_convert_raw_to_milliunit(bcl, signed_val, CHANNEL_CURR, + desc->data_field_bits_size); + bcl->last_curr_updated = jiffies; + + *out = bcl->last_curr_input; + + return 0; +} + +static int bcl_in_input_read(struct bcl_device *bcl, long *out) +{ + int ret; + u32 raw_val = 0, msb = 0; + const struct bcl_desc *desc = bcl->desc; + + /* Return cached value if read too soon after last update */ + if (time_before(jiffies, bcl->last_in_updated + HZ)) { + *out = bcl->last_in_input; + return 0; + } + + ret = regmap_field_read(bcl->fields[F_IN_INPUT], &raw_val); + if (ret) + return ret; + + /* For 16-bit data, read MSB and combine with LSB */ + if (desc->data_field_bits_size == 16) { + ret = regmap_field_read(bcl->fields[F_IN_INPUT1], &msb); + if (ret) + return ret; + raw_val |= FIELD_PREP(GENMASK(15, 8), msb); + } + + bcl->last_in_input = + bcl_convert_raw_to_milliunit(bcl, raw_val, CHANNEL_IN, + desc->data_field_bits_size); + bcl->last_in_updated = jiffies; + + *out = bcl->last_in_input; + + return 0; +} + +static int bcl_read_alarm_status(struct bcl_device *bcl, + enum bcl_limit_alarm lvl, long *status) +{ + int ret; + u32 raw_val = 0; + + ret = regmap_field_read(bcl->fields[F_LVL0_ALARM + lvl], &raw_val); + if (ret) + return ret; + + *status = raw_val; + + return 0; +} + +static unsigned int bcl_get_version_major(const struct bcl_device *bcl) +{ + u32 raw_val = 0; + + regmap_field_read(bcl->fields[F_V_MAJOR], &raw_val); + + return raw_val; +} + +static unsigned int bcl_get_version_minor(const struct bcl_device *bcl) +{ + u32 raw_val = 0; + + regmap_field_read(bcl->fields[F_V_MINOR], &raw_val); + + return raw_val; +} + +static void bcl_hwmon_notify_event(struct bcl_device *bcl, enum bcl_limit_alarm alarm) +{ + if (bcl->in_attrs) + hwmon_notify_event(bcl->hwmon_dev, hwmon_in, + in_lvl_to_attr_map[alarm], 0); + + if (bcl->curr_attrs) + hwmon_notify_event(bcl->hwmon_dev, hwmon_curr, + curr_lvl_to_attr_map[alarm], 0); +} + +static void bcl_alarm_enable_poll(struct work_struct *work) +{ + struct bcl_alarm_data *alarm = container_of(work, struct bcl_alarm_data, + alarm_poll_work.work); + struct bcl_device *bcl = (struct bcl_device *)alarm->device; + long status; + int ret; + + scoped_guard(mutex, &bcl->lock) + ret = bcl_read_alarm_status(bcl, alarm->type, &status); + + /* If read failed or shutting down, reschedule */ + if (ret || READ_ONCE(alarm->shutting_down)) { + if (!READ_ONCE(alarm->shutting_down)) + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); + return; + } + + /* Check if alarm cleared and IRQ needs re-enabling */ + scoped_guard(mutex, &alarm->a_lock) { + if (!status && !alarm->irq_enabled) { + alarm->irq_enabled = true; + enable_irq(alarm->irq); + + if (!alarm->irq_wake_enabled && !enable_irq_wake(alarm->irq)) + alarm->irq_wake_enabled = true; + + bcl_hwmon_notify_event(bcl, alarm->type); + return; + } + } + + /* Alarm still active, reschedule polling */ + if (!READ_ONCE(alarm->shutting_down)) + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); +} + +static irqreturn_t bcl_handle_alarm(int irq, void *data) +{ + struct bcl_alarm_data *alarm = data; + struct bcl_device *bcl = (struct bcl_device *)alarm->device; + long status; + + if (READ_ONCE(alarm->shutting_down)) + return IRQ_HANDLED; + + guard(mutex)(&bcl->lock); + + if (bcl_read_alarm_status(bcl, alarm->type, &status) || !status) + return IRQ_HANDLED; + + bcl_hwmon_notify_event(bcl, alarm->type); + + guard(mutex)(&alarm->a_lock); + if (alarm->shutting_down) + return IRQ_HANDLED; + + if (alarm->irq_enabled) { + alarm->irq_enabled = false; + disable_irq_nosync(alarm->irq); + } + + if (alarm->irq_wake_enabled) { + disable_irq_wake(alarm->irq); + alarm->irq_wake_enabled = false; + } + + schedule_delayed_work(&alarm->alarm_poll_work, + msecs_to_jiffies(BCL_ALARM_POLLING_MS)); + + return IRQ_HANDLED; +} + +static umode_t bcl_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + case hwmon_in_min_alarm: + case hwmon_in_lcrit_alarm: + return 0444; + case hwmon_in_min: + case hwmon_in_lcrit: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_label: + case hwmon_curr_max_alarm: + case hwmon_curr_crit_alarm: + return 0444; + case hwmon_curr_max: + case hwmon_curr_crit: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct bcl_device *bcl = dev_get_drvdata(dev); + + guard(mutex)(&bcl->lock); + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_min: + case hwmon_in_lcrit: + return bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); + default: + return -EOPNOTSUPP; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_max: + case hwmon_curr_crit: + return bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int bcl_in_read(struct bcl_device *bcl, u32 attr, long *value) +{ + guard(mutex)(&bcl->lock); + + switch (attr) { + case hwmon_in_input: + return bcl_in_input_read(bcl, value); + case hwmon_in_min: + case hwmon_in_lcrit: + return bcl_in_thresh_read(bcl, in_attr_to_lvl_map[attr], value); + case hwmon_in_min_alarm: + case hwmon_in_lcrit_alarm: + return bcl_read_alarm_status(bcl, in_attr_to_lvl_map[attr], value); + default: + return -EOPNOTSUPP; + } +} + +static int bcl_curr_read(struct bcl_device *bcl, u32 attr, long *value) +{ + guard(mutex)(&bcl->lock); + + switch (attr) { + case hwmon_curr_input: + return bcl_curr_input_read(bcl, value); + case hwmon_curr_max: + case hwmon_curr_crit: + return bcl_curr_thresh_read(bcl, curr_attr_to_lvl_map[attr], value); + case hwmon_curr_max_alarm: + case hwmon_curr_crit_alarm: + return bcl_read_alarm_status(bcl, curr_attr_to_lvl_map[attr], value); + default: + return -EOPNOTSUPP; + } +} + +static int bcl_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct bcl_device *bcl = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + return bcl_in_read(bcl, attr, value); + case hwmon_curr: + return bcl_curr_read(bcl, attr, value); + default: + return -EOPNOTSUPP; + } +} + +static int bcl_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type == hwmon_in && attr == hwmon_in_label) + *str = "Voltage"; + else if (type == hwmon_curr && attr == hwmon_curr_label) + *str = "Current"; + else + *str = NULL; + + return *str ? 0 : -EOPNOTSUPP; +} + +static const struct hwmon_ops bcl_hwmon_ops = { + .is_visible = bcl_hwmon_is_visible, + .read = bcl_hwmon_read, + .read_string = bcl_hwmon_read_string, + .write = bcl_hwmon_write, +}; + +static int bcl_detect_battery_config(struct bcl_device *bcl) +{ + u32 reg_val = 0; + int ret; + + ret = regmap_field_read(bcl->batt_config_regfield, ®_val); + if (ret) { + dev_err(bcl->dev, "Failed to read battery config register: %d\n", ret); + return ret; + } + + /* + * Map register value to battery configuration. + * As per hardware documentation: + * 0 -> 2S, 1 -> 3S, 2 -> 4S (unsupported) + */ + switch (reg_val) { + case 0: + bcl->batt_config = BCL_BATT_2S; + return 0; + case 1: + bcl->batt_config = BCL_BATT_3S; + return 0; + default: + dev_err(bcl->dev, "Unsupported battery configuration: 0x%x\n", + reg_val); + return -EINVAL; + } +} + +static int bcl_update_scaling_factors(struct bcl_device *bcl) +{ + const struct bcl_desc *desc = bcl->desc; + int ret; + + /* Check if battery detection is supported */ + if (desc->battery_config_field.reg == 0) { + /* No battery detection - use BCL_BATT_1S */ + bcl->batt_config = BCL_BATT_1S; + dev_dbg(bcl->dev, "Using default 1S battery configuration\n"); + return 0; + } + + bcl->batt_config_regfield = devm_regmap_field_alloc(bcl->dev, + bcl->regmap, + desc->battery_config_field); + if (IS_ERR(bcl->batt_config_regfield)) { + dev_err(bcl->dev, "Failed to allocate battery config regmap field\n"); + return PTR_ERR(bcl->batt_config_regfield); + } + + ret = bcl_detect_battery_config(bcl); + if (ret < 0) + return ret; + + dev_dbg(bcl->dev, + "%dS battery: BASE=%u mV,MAX=%u mV,STEP=%u mV,V_SCALE=%u nV, I_SCALE=%u nA\n", + bcl->batt_config + 1, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].base, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].max, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].step, + desc->channel_cfg[bcl->batt_config][CHANNEL_IN].default_scale_nu, + desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].default_scale_nu); + + return 0; +} + +static int bcl_build_hwmon_info(struct bcl_device *bcl) +{ + const struct bcl_desc *desc = bcl->desc; + struct hwmon_channel_info *in_info = NULL, *curr_info = NULL; + const struct hwmon_channel_info **info_array; + int num_channels = 0; + u32 val = 0; + bool in_mon_enabled = false; + bool in_input_enabled = false; + bool curr_mon_enabled = false; + int ret; + + /* Check if voltage monitoring is enabled */ + ret = regmap_field_read(bcl->fields[F_IN_MON_EN], &val); + if (ret) + return ret; + in_mon_enabled = !!val; + + /* Check if voltage input reading is enabled */ + if (desc->data_field_bits_size != 0) { + ret = regmap_field_read(bcl->fields[F_IN_INPUT_EN], &val); + if (ret) + return ret; + in_input_enabled = !!val; + } + + /* Check if current monitoring is enabled */ + if (desc->num_reg_fields > F_CURR_H0_THR) { + ret = regmap_field_read(bcl->fields[F_CURR_MON_EN], &val); + if (ret) + return ret; + curr_mon_enabled = !!val; + } + + /* Ensure at least one channel is enabled */ + if (!in_mon_enabled && !curr_mon_enabled) { + dev_err(bcl->dev, "No BCL channels enabled in hardware\n"); + return -ENODEV; + } + + if (in_mon_enabled) { + u32 *in_config; + + bcl->in_attrs = HWMON_I_LABEL | HWMON_I_MIN | HWMON_I_LCRIT | + HWMON_I_MIN_ALARM | HWMON_I_LCRIT_ALARM; + + if (in_input_enabled) + bcl->in_attrs |= HWMON_I_INPUT; + + in_info = devm_kzalloc(bcl->dev, sizeof(*in_info), GFP_KERNEL); + if (!in_info) + return -ENOMEM; + + in_config = devm_kzalloc(bcl->dev, 2 * sizeof(u32), GFP_KERNEL); + if (!in_config) + return -ENOMEM; + + in_config[0] = bcl->in_attrs; + in_info->type = hwmon_in; + in_info->config = in_config; + num_channels++; + } + + if (curr_mon_enabled) { + u32 *curr_config; + + bcl->curr_attrs = HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_MAX | + HWMON_C_CRIT | HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM; + + curr_info = devm_kzalloc(bcl->dev, sizeof(*curr_info), GFP_KERNEL); + if (!curr_info) + return -ENOMEM; + + curr_config = devm_kzalloc(bcl->dev, 2 * sizeof(u32), GFP_KERNEL); + if (!curr_config) + return -ENOMEM; + + curr_config[0] = bcl->curr_attrs; + curr_info->type = hwmon_curr; + curr_info->config = curr_config; + num_channels++; + } + + /* Allocate info array (num_channels + 1 for NULL terminator) */ + info_array = devm_kcalloc(bcl->dev, num_channels + 1, + sizeof(*info_array), GFP_KERNEL); + if (!info_array) + return -ENOMEM; + + num_channels = 0; + if (in_info) + info_array[num_channels++] = in_info; + if (curr_info) + info_array[num_channels++] = curr_info; + + bcl->hwmon_info = info_array; + + bcl->hwmon_chip_info.ops = &bcl_hwmon_ops; + bcl->hwmon_chip_info.info = bcl->hwmon_info; + + return 0; +} + +static void bcl_alarm_work_cleanup_action(void *data) +{ + struct bcl_alarm_data *alarm = data; + + /* Set shutting_down flag to prevent work from being rescheduled */ + scoped_guard(mutex, &alarm->a_lock) + WRITE_ONCE(alarm->shutting_down, true); + + cancel_delayed_work_sync(&alarm->alarm_poll_work); +} + +static void bcl_alarm_wake_cleanup_action(void *data) +{ + struct bcl_alarm_data *alarm = data; + + guard(mutex)(&alarm->a_lock); + if (alarm->irq_wake_enabled) { + disable_irq_wake(alarm->irq); + alarm->irq_wake_enabled = false; + } +} + +static int bcl_alarm_irq_init(struct platform_device *pdev, + struct bcl_device *bcl) +{ + int ret, irq_num, i; + struct bcl_alarm_data *alarm; + + for (i = 0; i < ARRAY_SIZE(bcl->bcl_alarms); i++) { + alarm = &bcl->bcl_alarms[i]; + alarm->type = i; + alarm->device = bcl; + ret = devm_mutex_init(bcl->dev, &alarm->a_lock); + if (ret) + return ret; + + /* Initialize work before IRQ request */ + INIT_DELAYED_WORK(&alarm->alarm_poll_work, bcl_alarm_enable_poll); + + irq_num = platform_get_irq_byname(pdev, bcl_int_names[i]); + if (irq_num < 0) + return irq_num; + + alarm->irq = irq_num; + alarm->irq_enabled = true; + + ret = devm_request_threaded_irq(&pdev->dev, irq_num, NULL, + bcl_handle_alarm, IRQF_ONESHOT, + bcl_int_names[i], alarm); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, bcl_alarm_work_cleanup_action, + alarm); + if (ret) + return ret; + + if (!enable_irq_wake(irq_num)) + alarm->irq_wake_enabled = true; + + ret = devm_add_action_or_reset(&pdev->dev, bcl_alarm_wake_cleanup_action, + alarm); + if (ret) + return ret; + } + + return 0; +} + +static int bcl_regmap_field_init(struct device *dev, struct bcl_device *bcl, + const struct bcl_desc *data) +{ + int i; + + /* + * Note: We use a mutable local copy of struct reg_field (not const) + * because we need to modify the .reg field to add the BCL base offset. + */ + for (i = 0; i < data->num_reg_fields; i++) { + struct reg_field field = data->reg_fields[i]; + + /* Skip uninitialized fields */ + if (field.reg == 0 && field.lsb == 0 && field.msb == 0) + continue; + + field.reg += bcl->base; + + bcl->fields[i] = devm_regmap_field_alloc(dev, bcl->regmap, field); + if (IS_ERR(bcl->fields[i])) + return PTR_ERR(bcl->fields[i]); + } + + return 0; +} + +static int bcl_probe(struct platform_device *pdev) +{ + struct bcl_device *bcl; + int ret; + u32 reg; + + bcl = devm_kzalloc(&pdev->dev, sizeof(*bcl), GFP_KERNEL); + if (!bcl) + return -ENOMEM; + + bcl->dev = &pdev->dev; + bcl->desc = device_get_match_data(&pdev->dev); + if (!bcl->desc) + return dev_err_probe(&pdev->dev, -EINVAL, "Failed to get device match data\n"); + + ret = devm_mutex_init(bcl->dev, &bcl->lock); + if (ret) + return ret; + + bcl->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!bcl->regmap) + return dev_err_probe(&pdev->dev, -EINVAL, "Couldn't get parent's regmap\n"); + + ret = device_property_read_u32(&pdev->dev, "reg", ®); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to read 'reg' property\n"); + + bcl->base = reg; + + ret = bcl_regmap_field_init(bcl->dev, bcl, bcl->desc); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Unable to allocate regmap fields\n"); + + ret = regmap_field_read(bcl->fields[F_CTL_EN], ®); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to read BCL enable status\n"); + + if (!reg) + return dev_err_probe(&pdev->dev, -ENODEV, "BCL is not enabled by bootloader\n"); + + ret = bcl_update_scaling_factors(bcl); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to update scaling factors\n"); + + ret = bcl_build_hwmon_info(bcl); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to build hwmon info\n"); + + dev_set_drvdata(&pdev->dev, bcl); + + bcl->hwmon_name = devm_hwmon_sanitize_name(&pdev->dev, + dev_name(bcl->dev)); + if (IS_ERR(bcl->hwmon_name)) + return PTR_ERR(bcl->hwmon_name); + + bcl->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + bcl->hwmon_name, + bcl, + &bcl->hwmon_chip_info, + NULL); + if (IS_ERR(bcl->hwmon_dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(bcl->hwmon_dev), + "Failed to register hwmon device\n"); + + ret = bcl_alarm_irq_init(pdev, bcl); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to initialize alarm IRQs\n"); + + dev_dbg(&pdev->dev, "BCL hwmon device with version: %u.%u registered\n", + bcl_get_version_major(bcl), bcl_get_version_minor(bcl)); + + return 0; +} + +static const struct of_device_id bcl_match[] = { + { + .compatible = "qcom,pm7250b-bcl", + .data = &pm7250b_data, + }, { + .compatible = "qcom,pm8350c-bcl", + .data = &pm8350c_data, + }, { + .compatible = "qcom,pm8550-bcl", + .data = &pm8550_data, + }, { + .compatible = "qcom,pmh0101-bcl", + .data = &pmh0101_data, + }, { + .compatible = "qcom,pmih0108-bcl", + .data = &pmih0108_data, + }, { + .compatible = "qcom,smb2360-bcl", + .data = &smb2360_data, + }, { + .compatible = "qcom,smb2370-bcl", + .data = &smb2370_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, bcl_match); + +static struct platform_driver bcl_driver = { + .probe = bcl_probe, + .driver = { + .name = "qcom-bcl-hwmon", + .of_match_table = bcl_match, + }, +}; + +module_platform_driver(bcl_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI BCL HWMON driver"); +MODULE_LICENSE("GPL"); From a246a066841b79f5d308b26fc7624fd4af3c1e79 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:08 +0530 Subject: [PATCH 08/12] FROMLIST: arm64: dts: qcom: pm7250b: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for pm7250b PMIC. The BCL monitors battery voltage and current, providing hardware interrupts when configurable thresholds are violated. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-3-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/pm7250b.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pm7250b.dtsi b/arch/arm64/boot/dts/qcom/pm7250b.dtsi index 0761e6b5fd8d1..2fa92f51c6191 100644 --- a/arch/arm64/boot/dts/qcom/pm7250b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm7250b.dtsi @@ -90,6 +90,15 @@ status = "disabled"; }; + sensor@1d00 { + compatible = "qcom,pm7250b-bcl"; + reg = <0x1d00>; + interrupts = , + ; + interrupt-names = "max-min", + "critical"; + }; + pm7250b_temp: temp-alarm@2400 { compatible = "qcom,spmi-temp-alarm"; reg = <0x2400>; From 5c0e5327e525fbe8081cbec341fac530e802f816 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:09 +0530 Subject: [PATCH 09/12] FROMLIST: arm64: dts: qcom: hamoa-pmic: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for hamoa-pmic. The BCL monitors battery voltage and current, providing hardware interrupts when configurable thresholds are violated. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-4-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi index 621890ada1536..cbe91e8631420 100644 --- a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi @@ -495,6 +495,15 @@ status = "disabled"; + sensor@4700 { + compatible = "qcom,smb2360-bcl"; + reg = <0x4700>; + interrupts = <0x7 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x7 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + smb2360_0_eusb2_repeater: phy@fd00 { compatible = "qcom,smb2360-eusb2-repeater"; reg = <0xfd00>; From 756e3420047063aec7e29915c0536258384977a7 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:10 +0530 Subject: [PATCH 10/12] FROMLIST: arm64: dts: qcom: pm8350c: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for pm8350c PMIC. The BCL monitors battery voltage and current, providing hardware interrupts when configurable thresholds are violated. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-5-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/pm8350c.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pm8350c.dtsi b/arch/arm64/boot/dts/qcom/pm8350c.dtsi index 1a24e6439e36d..8815868f9ad64 100644 --- a/arch/arm64/boot/dts/qcom/pm8350c.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8350c.dtsi @@ -20,6 +20,15 @@ #thermal-sensor-cells = <0>; }; + sensor@4700 { + compatible = "qcom,pm8350c-bcl"; + reg = <0x4700>; + interrupts = <0x2 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + pm8350c_gpios: gpio@8800 { compatible = "qcom,pm8350c-gpio", "qcom,spmi-gpio"; reg = <0x8800>; @@ -41,6 +50,7 @@ #pwm-cells = <2>; status = "disabled"; }; + }; }; From 7b0ce044437f6ee55a444e4c0d7ab826bde95096 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Wed, 22 Jul 2026 00:00:12 +0530 Subject: [PATCH 11/12] FROMLIST: arm64: dts: qcom: pm8550: Enable BCL sensor node Add Battery Current Limiting (BCL) hardware monitor node for pm8550 PMIC. The BCL monitors battery voltage and current, providing hardware interrupts when configurable thresholds are violated. Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://lore.kernel.org/r/20260722-qcom-bcl-hwmon-v2-7-febe2805e17b@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/pm8550.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/pm8550.dtsi b/arch/arm64/boot/dts/qcom/pm8550.dtsi index 896bcacb64902..fde76da37bacb 100644 --- a/arch/arm64/boot/dts/qcom/pm8550.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8550.dtsi @@ -45,6 +45,15 @@ #thermal-sensor-cells = <0>; }; + sensor@4700 { + compatible = "qcom,pm8550-bcl"; + reg = <0x4700>; + interrupts = <0x1 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x1 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + pm8550_gpios: gpio@8800 { compatible = "qcom,pm8550-gpio", "qcom,spmi-gpio"; reg = <0x8800>; From 1ecfd4268595ee6186476d33fe11e59980709975 Mon Sep 17 00:00:00 2001 From: Dipa Ramesh Mantre Date: Mon, 3 Aug 2026 16:49:41 +0530 Subject: [PATCH 12/12] QCLINUX: defconfig: Enable Qualcomm BCL driver Enable Qualcomm BCL driver config. Signed-off-by: Dipa Ramesh Mantre --- arch/arm64/configs/qcom.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/qcom.config b/arch/arm64/configs/qcom.config index 0a4393ebcba0c..20aca64dd6b60 100644 --- a/arch/arm64/configs/qcom.config +++ b/arch/arm64/configs/qcom.config @@ -65,6 +65,7 @@ CONFIG_STM_SOURCE_FTRACE=m CONFIG_STM_SOURCE_HEARTBEAT=m CONFIG_SENSORS_AMC6821=y CONFIG_SENSORS_EMC2305=y +CONFIG_SENSORS_QCOM_SPMI_BCL=y CONFIG_SWIOTLB_DYNAMIC=y CONFIG_TRACE_MMIO_ACCESS=y CONFIG_UCLAMP_TASK_GROUP=y