* [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver
@ 2026-07-21 18:30 Manaf Meethalavalappu Pallikunhi
2026-07-21 18:30 ` [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware Manaf Meethalavalappu Pallikunhi
` (9 more replies)
0 siblings, 10 replies; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
This series introduces support for the Qualcomm SPMI PMIC Battery Current
Limiting (BCL) hardware monitor present in various Qualcomm PMICs.
The BCL hardware ensures robust battery protection by monitoring battery
conditions in real time and triggering interrupts when predefined
thresholds are crossed. These alerts allow the system to respond
proactively, preventing battery degradation and preserving system
reliability under adverse power conditions.
The driver integrates with the standard hwmon subsystem, exposing battery
measurements and configurable alarm thresholds. This design enables
seamless integration with existing system monitoring tools and thermal
management frameworks.
Key features:
- Battery/system under voltage and over current monitoring support
- Real-time voltage and current readings support
- Configurable threshold-based alarms (warning and critical levels)
- Interrupt-driven notifications for threshold violations
- Automatic threshold management with polling-based recovery
- Hardware-specific scaling factors and threshold representations
Voltage and current monitoring can be independently enabled/disabled
by firmware, and the driver automatically detects the enabled features
at probe time.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
Changes in v2:
- Reworked the binding from the generic qcom,bcl-hwmon.yaml schema to a
PMIC-specific qcom,pm7250b-bcl.yaml schema using exact compatible strings.(Krzysztof)
- Dropped the DT-configured overcurrent-thresholds-milliamp property.(Konrad)
- Renamed interrupt names from "bcl-max-min"/"bcl-critical" to
"max-min"/"critical".(Krzysztof)
- Limited the binding and driver match table to the supported PMICs:
PM7250B, PM8350C, PM8550, PMH0101, PMIH0108, SMB2360 and SMB2370.(Daniel)
- Folded qcom-bcl-hwmon.h into qcom-bcl-hwmon.c and removed the
macro-generated alarm mapping helpers.(Krzysztof/Bjorn/Daniel)
- Reworked the driver around per-PMIC register descriptors instead of
generic BCL generation/mode descriptors.(Krzysztof/Bjorn)
- Added support for 16-bit voltage/current input registers and
SMB2360/SMB2370 2S/3S battery configuration based scaling.
- Build hwmon channel information dynamically from hardware enable state
instead of registering static voltage/current channel sets.(Daniel)
- Improved alarm IRQ handling with per-alarm locking, wake state tracking,
shutdown guards, and explicit delayed-work cleanup.
- Switched probe/error paths to dev_err_probe() and tightened BCL enable,
regmap-field, and hwmon registration handling.
- Link to v1: https://lore.kernel.org/r/20260206-qcom-bcl-hwmon-v1-0-7b426f0b77a1@oss.qualcomm.com
---
Manaf Meethalavalappu Pallikunhi (10):
dt-bindings: hwmon: Describe Qualcomm BCL hardware
hwmon: Add Qualcomm PMIC BCL driver
arm64: dts: qcom: pm7250b: Enable BCL sensor node
arm64: dts: qcom: hamoa-pmic: Enable BCL sensor node
arm64: dts: qcom: pm8350c: Enable BCL sensor node
arm64: dts: qcom: pmh0101: Enable BCL sensor node
arm64: dts: qcom: pm8550: Enable BCL sensor node
arm64: dts: qcom: pmih0108-kaanapali: Enable BCL sensor node
arm64: dts: qcom: pmih0108: Enable BCL sensor node
arm64: dts: qcom: smb2370: Enable BCL sensor node
.../bindings/hwmon/qcom,pm7250b-bcl.yaml | 64 +
.../devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 +
MAINTAINERS | 8 +
arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi | 9 +
arch/arm64/boot/dts/qcom/pm7250b.dtsi | 9 +
arch/arm64/boot/dts/qcom/pm8350c.dtsi | 10 +
arch/arm64/boot/dts/qcom/pm8550.dtsi | 9 +
arch/arm64/boot/dts/qcom/pmh0101.dtsi | 9 +
arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi | 9 +
arch/arm64/boot/dts/qcom/pmih0108.dtsi | 9 +
arch/arm64/boot/dts/qcom/smb2370.dtsi | 9 +
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/qcom-bcl-hwmon.c | 1443 ++++++++++++++++++++
14 files changed, 1604 insertions(+)
---
base-commit: 3fe08b9796f36ef437ab9328e7dd1e5ff2d66603
change-id: 20260720-qcom-bcl-hwmon-6d602c195eab
Best regards,
--
Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 18:36 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver Manaf Meethalavalappu Pallikunhi
` (8 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
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 <manaf.pallikunhi@oss.qualcomm.com>
---
.../bindings/hwmon/qcom,pm7250b-bcl.yaml | 64 ++++++++++++++++++++++
.../devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 ++
MAINTAINERS | 7 +++
3 files changed, 75 insertions(+)
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 000000000000..3c069e94a34e
--- /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 <manaf.pallikunhi@oss.qualcomm.com>
+
+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 <dt-bindings/interrupt-controller/irq.h>
+
+ 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 809be2756a0c..3caee21a02ad 100644
--- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
+++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
@@ -190,6 +190,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 16b73f5825d6..1b178d3a91ad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22176,6 +22176,13 @@ 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 <manaf.pallikunhi@oss.qualcomm.com>
+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
M: Bartosz Golaszewski <brgl@kernel.org>
L: linux-arm-msm@vger.kernel.org
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
2026-07-21 18:30 ` [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:20 ` sashiko-bot
2026-07-21 20:44 ` Guenter Roeck
2026-07-21 18:30 ` [PATCH v2 03/10] arm64: dts: qcom: pm7250b: Enable BCL sensor node Manaf Meethalavalappu Pallikunhi
` (7 subsequent siblings)
9 siblings, 2 replies; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
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 <manaf.pallikunhi@oss.qualcomm.com>
---
MAINTAINERS | 1 +
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/qcom-bcl-hwmon.c | 1443 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 1456 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 1b178d3a91ad..8b7d3c3c6252 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22182,6 +22182,7 @@ L: linux-hwmon@vger.kernel.org
L: linux-arm-msm@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml
+F: drivers/hwmon/qcom-bcl-hwmon.c
QUALCOMM BLUETOOTH DRIVER
M: Bartosz Golaszewski <brgl@kernel.org>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 50d744db3045..8618c88932c3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1955,6 +1955,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 63809eeec2f4..cb61983d437c 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -204,6 +204,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.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_SBTSI) += sbtsi_temp.o
diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c
new file mode 100644
index 000000000000..a21433b45482
--- /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 <linux/bitfield.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+
+/* 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");
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 03/10] arm64: dts: qcom: pm7250b: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
2026-07-21 18:30 ` [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware Manaf Meethalavalappu Pallikunhi
2026-07-21 18:30 ` [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:13 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 04/10] arm64: dts: qcom: hamoa-pmic: " Manaf Meethalavalappu Pallikunhi
` (6 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
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 <manaf.pallikunhi@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 43cab07126c5..1d358aeda9b2 100644
--- a/arch/arm64/boot/dts/qcom/pm7250b.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm7250b.dtsi
@@ -89,6 +89,15 @@ pm7250b_typec: typec@1500 {
status = "disabled";
};
+ sensor@1d00 {
+ compatible = "qcom,pm7250b-bcl";
+ reg = <0x1d00>;
+ interrupts = <PM7250B_SID 0x1d 0x0 IRQ_TYPE_EDGE_RISING>,
+ <PM7250B_SID 0x1d 0x1 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "max-min",
+ "critical";
+ };
+
pm7250b_temp: temp-alarm@2400 {
compatible = "qcom,spmi-temp-alarm";
reg = <0x2400>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 04/10] arm64: dts: qcom: hamoa-pmic: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (2 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 03/10] arm64: dts: qcom: pm7250b: Enable BCL sensor node Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 18:37 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 05/10] arm64: dts: qcom: pm8350c: " Manaf Meethalavalappu Pallikunhi
` (5 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
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 <manaf.pallikunhi@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 6a31a0adf8be..dbc4257c64ce 100644
--- a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi
+++ b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi
@@ -515,6 +515,15 @@ smb2360_0: pmic@7 {
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>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 05/10] arm64: dts: qcom: pm8350c: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (3 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 04/10] arm64: dts: qcom: hamoa-pmic: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:14 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 06/10] arm64: dts: qcom: pmh0101: " Manaf Meethalavalappu Pallikunhi
` (4 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
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 <manaf.pallikunhi@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 1a24e6439e36..8815868f9ad6 100644
--- a/arch/arm64/boot/dts/qcom/pm8350c.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8350c.dtsi
@@ -20,6 +20,15 @@ pm8350c_temp_alarm: temp-alarm@a00 {
#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 @@ pm8350c_pwm: pwm {
#pwm-cells = <2>;
status = "disabled";
};
+
};
};
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 06/10] arm64: dts: qcom: pmh0101: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (4 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 05/10] arm64: dts: qcom: pm8350c: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:17 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 07/10] arm64: dts: qcom: pm8550: " Manaf Meethalavalappu Pallikunhi
` (3 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
Add Battery Current Limiting (BCL) hardware monitor node for pmh0101
PMIC. The BCL monitors battery voltage and current, providing hardware
interrupts when configurable thresholds are violated.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/pmh0101.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/pmh0101.dtsi b/arch/arm64/boot/dts/qcom/pmh0101.dtsi
index b1ec41325958..fd632b592d2e 100644
--- a/arch/arm64/boot/dts/qcom/pmh0101.dtsi
+++ b/arch/arm64/boot/dts/qcom/pmh0101.dtsi
@@ -43,6 +43,15 @@ pmh0101_temp_alarm: temp-alarm@a00 {
#thermal-sensor-cells = <0>;
};
+ sensor@4700 {
+ compatible = "qcom,pmh0101-bcl";
+ reg = <0x4700>;
+ interrupts = <0x1 0x47 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x1 0x47 0x1 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "max-min",
+ "critical";
+ };
+
pmh0101_gpios: gpio@8800 {
compatible = "qcom,pmh0101-gpio", "qcom,spmi-gpio";
reg = <0x8800>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 07/10] arm64: dts: qcom: pm8550: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (5 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 06/10] arm64: dts: qcom: pmh0101: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:14 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: " Manaf Meethalavalappu Pallikunhi
` (2 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
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 <manaf.pallikunhi@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 896bcacb6490..fde76da37bac 100644
--- a/arch/arm64/boot/dts/qcom/pm8550.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8550.dtsi
@@ -45,6 +45,15 @@ pm8550_temp_alarm: temp-alarm@a00 {
#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>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (6 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 07/10] arm64: dts: qcom: pm8550: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:17 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 09/10] arm64: dts: qcom: pmih0108: " Manaf Meethalavalappu Pallikunhi
2026-07-21 18:30 ` [PATCH v2 10/10] arm64: dts: qcom: smb2370: " Manaf Meethalavalappu Pallikunhi
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
Add Battery Current Limiting (BCL) hardware monitor node for pmih0108
PMIC. The BCL monitors battery voltage and current, providing hardware
interrupts when configurable thresholds are violated.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi b/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi
index b73b0e82c3d3..67b77272b0cf 100644
--- a/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi
+++ b/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi
@@ -49,6 +49,15 @@ pmih0108_e1_temp_alarm: temp-alarm@a00 {
#thermal-sensor-cells = <0>;
};
+ sensor@4700 {
+ compatible = "qcom,pmih0108-bcl";
+ reg = <0x4700>;
+ interrupts = <0x7 0x47 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x7 0x47 0x1 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "max-min",
+ "critical";
+ };
+
pmih0108_e1_gpios: gpio@8800 {
compatible = "qcom,pmih0108-gpio", "qcom,spmi-gpio";
reg = <0x8800>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 09/10] arm64: dts: qcom: pmih0108: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (7 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:16 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 10/10] arm64: dts: qcom: smb2370: " Manaf Meethalavalappu Pallikunhi
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
Add Battery Current Limiting (BCL) hardware monitor node for pmih0108
PMIC. The BCL monitors battery voltage and current, providing hardware
interrupts when configurable thresholds are violated.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/pmih0108.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/pmih0108.dtsi b/arch/arm64/boot/dts/qcom/pmih0108.dtsi
index 1c875995d881..1f91e788010b 100644
--- a/arch/arm64/boot/dts/qcom/pmih0108.dtsi
+++ b/arch/arm64/boot/dts/qcom/pmih0108.dtsi
@@ -49,6 +49,15 @@ pmih0108_temp_alarm: temp-alarm@a00 {
#thermal-sensor-cells = <0>;
};
+ sensor@4700 {
+ compatible = "qcom,pmih0108-bcl";
+ reg = <0x4700>;
+ interrupts = <0x7 0x47 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x7 0x47 0x1 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "max-min",
+ "critical";
+ };
+
pmih0108_gpios: gpio@8800 {
compatible = "qcom,pmih0108-gpio", "qcom,spmi-gpio";
reg = <0x8800>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 10/10] arm64: dts: qcom: smb2370: Enable BCL sensor node
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
` (8 preceding siblings ...)
2026-07-21 18:30 ` [PATCH v2 09/10] arm64: dts: qcom: pmih0108: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:30 ` Manaf Meethalavalappu Pallikunhi
2026-07-21 19:17 ` sashiko-bot
9 siblings, 1 reply; 22+ messages in thread
From: Manaf Meethalavalappu Pallikunhi @ 2026-07-21 18:30 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio,
Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli, Manaf Meethalavalappu Pallikunhi
Add Battery Current Limiting (BCL) hardware monitor node for smb2370
PMIC. The BCL monitors battery voltage and current, providing hardware
interrupts when configurable thresholds are violated.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/smb2370.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/smb2370.dtsi b/arch/arm64/boot/dts/qcom/smb2370.dtsi
index 80f3fdae5705..79b013fa2c11 100644
--- a/arch/arm64/boot/dts/qcom/smb2370.dtsi
+++ b/arch/arm64/boot/dts/qcom/smb2370.dtsi
@@ -10,6 +10,15 @@ smb2370_j_e2: pmic@9 {
#address-cells = <1>;
#size-cells = <0>;
+ sensor@4700 {
+ compatible = "qcom,smb2370-bcl";
+ reg = <0x4700>;
+ interrupts = <0x9 0x47 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x9 0x47 0x1 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "max-min",
+ "critical";
+ };
+
smb2370_j_e2_eusb2_repeater: phy@fd00 {
compatible = "qcom,smb2370-eusb2-repeater";
reg = <0xfd00>;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware
2026-07-21 18:30 ` [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:36 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 18:36 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: robh, linux-hwmon, lee, devicetree, mfd, conor+dt
> 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 <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 04/10] arm64: dts: qcom: hamoa-pmic: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 04/10] arm64: dts: qcom: hamoa-pmic: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 18:37 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 18:37 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: linux-hwmon, mfd, lee, robh, conor+dt, devicetree
> 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 <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 03/10] arm64: dts: qcom: pm7250b: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 03/10] arm64: dts: qcom: pm7250b: Enable BCL sensor node Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:13 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:13 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: mfd, conor+dt, devicetree, lee, linux-hwmon, robh
> 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 <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 07/10] arm64: dts: qcom: pm8550: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 07/10] arm64: dts: qcom: pm8550: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:14 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:14 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: devicetree, linux-hwmon, mfd, lee, conor+dt, robh
> 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 <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=7
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 05/10] arm64: dts: qcom: pm8350c: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 05/10] arm64: dts: qcom: pm8350c: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:14 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:14 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: linux-hwmon, devicetree, lee, robh, mfd, conor+dt
> 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 <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 09/10] arm64: dts: qcom: pmih0108: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 09/10] arm64: dts: qcom: pmih0108: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:16 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:16 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: mfd, conor+dt, lee, linux-hwmon, devicetree, robh
> Add Battery Current Limiting (BCL) hardware monitor node for pmih0108
> PMIC. The BCL monitors battery voltage and current, providing hardware
> interrupts when configurable thresholds are violated.
>
> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=9
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:17 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:17 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: devicetree, linux-hwmon, lee, robh, mfd, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The BCL hardware monitor node is added to the `pmih0108-kaanapali.dtsi` variant but omitted from the base `pmih0108.dtsi` PMIC definition file.
--
--- Patch [8]: [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: Enable BCL sensor node ---
commit f763d763d5dd94fa5bdd7efdfec8819781ecf122
Author: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
arm64: dts: qcom: pmih0108-kaanapali: Enable BCL sensor node
This commit adds the Battery Current Limiting (BCL) hardware monitor node
for the pmih0108 PMIC in the pmih0108-kaanapali.dtsi file. This enables
battery voltage and current monitoring with hardware interrupts.
> diff --git a/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi b/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi
> index b73b0e82c3d31..67b77272b0cf9 100644
> --- a/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi
> +++ b/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi
> @@ -49,6 +49,15 @@ pmih0108_e1_temp_alarm: temp-alarm@a00 {
> #thermal-sensor-cells = <0>;
> };
>
> + sensor@4700 {
> + compatible = "qcom,pmih0108-bcl";
> + reg = <0x4700>;
> + interrupts = <0x7 0x47 0x0 IRQ_TYPE_EDGE_RISING>,
> + <0x7 0x47 0x1 IRQ_TYPE_EDGE_RISING>;
> + interrupt-names = "max-min",
> + "critical";
> + };
> +
[Severity: Medium]
Should this BCL sensor node also be added to the base pmih0108.dtsi PMIC
definition file?
It appears the node is being added to the pmih0108-kaanapali.dtsi variant,
but omitting it from the base pmih0108.dtsi might prevent boards using the
standard PMIC from receiving battery current and voltage hardware interrupts.
Without BCL monitoring, those systems might not be able to proactively
throttle the SoC during high current draw or low voltage, which could
potentially lead to unexpected shutdowns under heavy load.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=8
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 06/10] arm64: dts: qcom: pmh0101: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 06/10] arm64: dts: qcom: pmh0101: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:17 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:17 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: devicetree, linux-hwmon, mfd, lee, robh, conor+dt
> Add Battery Current Limiting (BCL) hardware monitor node for pmh0101
> PMIC. The BCL monitors battery voltage and current, providing hardware
> interrupts when configurable thresholds are violated.
>
> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=6
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 10/10] arm64: dts: qcom: smb2370: Enable BCL sensor node
2026-07-21 18:30 ` [PATCH v2 10/10] arm64: dts: qcom: smb2370: " Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:17 ` sashiko-bot
0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:17 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: linux-hwmon, mfd, lee, robh, conor+dt, devicetree
> Add Battery Current Limiting (BCL) hardware monitor node for smb2370
> PMIC. The BCL monitors battery voltage and current, providing hardware
> interrupts when configurable thresholds are violated.
>
> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=10
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver
2026-07-21 18:30 ` [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver Manaf Meethalavalappu Pallikunhi
@ 2026-07-21 19:20 ` sashiko-bot
2026-07-21 20:44 ` Guenter Roeck
1 sibling, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-07-21 19:20 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi
Cc: mfd, devicetree, robh, linux-hwmon, conor+dt, lee
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Uninitialized cache timestamps cause the driver to erroneously return cached 0 readings instead of hardware values for the first 5 minutes of system uptime.
- [High] Writing the maximum valid threshold value to certain sysfs attributes causes integer truncation when written to the hardware register, silently applying a dangerously low or zero limit.
- [Medium] Devres teardown ordering race allows the delayed polling work to re-enable `irq_wake` after it was disabled, causing an unbalanced wake count and triggering a kernel warning on driver unbind.
- [Medium] pmh0101_data is missing `.data_field_bits_size`, preventing voltage input readings from being exposed.
- [Low] Using `DIV_ROUND_CLOSEST` with a 64-bit signed dividend causes a link error on 32-bit architectures.
--
--- Patch [2]: [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver ---
commit d1c02d33d76b0d05ddfd0d17bd163dd47036945c
Author: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
Subject: hwmon: Add Qualcomm PMIC BCL driver
This commit adds a new driver for Qualcomm SPMI PMIC Battery Current Limiting
(BCL). It exposes battery voltage and current monitoring through the hwmon
interface, including threshold-based alarms and interrupt notifications.
> diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c
> new file mode 100644
> index 0000000000000..a21433b45482c
> --- /dev/null
> +++ b/drivers/hwmon/qcom-bcl-hwmon.c
[ ... ]
> +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] = {
[Severity: Medium]
Is the pmh0101_data descriptor missing a .data_field_bits_size initialization?
This descriptor defines F_IN_INPUT and F_IN_INPUT1 for 16-bit reads but
omits .data_field_bits_size. Since it zero-initializes to 0,
bcl_build_hwmon_info() will treat the input reading feature as disabled and
skip initializing the sensor, hiding the hardware functionality.
[ ... ]
> +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);
> +}
[Severity: Low]
Will this cause a build failure on 32-bit architectures?
Using DIV_ROUND_CLOSEST with a 64-bit signed dividend utilizes the standard
division operator, which translates to a 64-bit signed division instruction.
Since the kernel does not implement __divdi3 for 32-bit architectures,
should div_s64() be used instead to prevent link errors?
[ ... ]
> +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);
> +}
[Severity: High]
Can writing the maximum valid threshold value to the sysfs attributes cause
integer truncation when written to the hardware register?
For the pm7250b_data variant, the maximum threshold is set to 10000, which
translates to a raw value of 256. When regmap_field_write() writes this to
an 8-bit field, it gets masked to 0, silently applying a 0 mA threshold
instead of the maximum.
Similarly for smb2360_data, the max voltage is 8400, which translates to
index 78. This masks to 14 in a 6-bit field, setting a 5200mV threshold.
Could the driver verify the value fits in the destination regmap field
before writing?
[ ... ]
> +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;
> + }
[Severity: High]
Will this erroneously return cached 0 readings for the first 5 minutes of
system uptime?
The last_curr_updated timestamp is zero-initialized by devm_kzalloc(). The
kernel initializes jiffies to INITIAL_JIFFIES (typically 5 minutes before
wrap-around, e.g., -300 * HZ). Due to unsigned wrap-around comparison
semantics, time_before(jiffies, bcl->last_curr_updated + HZ) will evaluate
to true until jiffies reaches HZ.
This causes the driver to bypass hardware reads entirely and return the
uninitialized 0 value across all channels.
[ ... ]
> +static int bcl_alarm_irq_init(struct platform_device *pdev,
> + struct bcl_device *bcl)
> +{
[ ... ]
> + 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;
> +}
[Severity: Medium]
Does this devres teardown ordering allow the delayed polling work to
re-enable irq_wake after it was already disabled?
Since bcl_alarm_work_cleanup_action is registered before
bcl_alarm_wake_cleanup_action, the LIFO teardown executes the wake cleanup
first, disabling irq_wake.
Concurrently, the uncancelled alarm_poll_work can race to execute
bcl_alarm_enable_poll() and re-enable irq_wake before the work cleanup
action runs to cancel the work queue. Could this cause an unbalanced wake
count and trigger a kernel warning on driver unbind?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-qcom-bcl-hwmon-v2-0-febe2805e17b@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver
2026-07-21 18:30 ` [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver Manaf Meethalavalappu Pallikunhi
2026-07-21 19:20 ` sashiko-bot
@ 2026-07-21 20:44 ` Guenter Roeck
1 sibling, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2026-07-21 20:44 UTC (permalink / raw)
To: Manaf Meethalavalappu Pallikunhi, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd,
Bjorn Andersson, Konrad Dybcio, Daniel Lezcano
Cc: linux-hwmon, linux-arm-msm, devicetree, linux-kernel, mfd,
Gaurav Kohli
On 7/21/26 11:30, Manaf Meethalavalappu Pallikunhi wrote:
> 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 <manaf.pallikunhi@oss.qualcomm.com>
> ---
> MAINTAINERS | 1 +
> drivers/hwmon/Kconfig | 11 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/qcom-bcl-hwmon.c | 1443 ++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 1456 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1b178d3a91ad..8b7d3c3c6252 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22182,6 +22182,7 @@ L: linux-hwmon@vger.kernel.org
> L: linux-arm-msm@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml
> +F: drivers/hwmon/qcom-bcl-hwmon.c
>
> QUALCOMM BLUETOOTH DRIVER
> M: Bartosz Golaszewski <brgl@kernel.org>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 50d744db3045..8618c88932c3 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1955,6 +1955,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 63809eeec2f4..cb61983d437c 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -204,6 +204,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
> obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.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_SBTSI) += sbtsi_temp.o
> diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c
> new file mode 100644
> index 000000000000..a21433b45482
> --- /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 <linux/bitfield.h>
> +#include <linux/devm-helpers.h>
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/workqueue.h>
> +
> +/* 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;
> +}
Please drop those labels. Labels are intended to show _where_
a sensor is located or which current/voltage is monitored.
That a voltage is a voltage and that a current is a current
does not need a label and defeats the purpose of the label.
> +
> +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;
> +
I am curious: Why not use static initialization and use the is_visible
function to determine if an attribute is visible or not ?
From the earlier exchange I had the impression that there is a large
number of current and voltage channels, but it looks like there is only
one each. That doesn't really warrant or need all this dynamic code
to generate the description.
> + 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");
>
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-07-21 20:44 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 18:30 [PATCH v2 00/10] hwmon: Add Qualcomm SPMI BCL driver Manaf Meethalavalappu Pallikunhi
2026-07-21 18:30 ` [PATCH v2 01/10] dt-bindings: hwmon: Describe Qualcomm BCL hardware Manaf Meethalavalappu Pallikunhi
2026-07-21 18:36 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 02/10] hwmon: Add Qualcomm PMIC BCL driver Manaf Meethalavalappu Pallikunhi
2026-07-21 19:20 ` sashiko-bot
2026-07-21 20:44 ` Guenter Roeck
2026-07-21 18:30 ` [PATCH v2 03/10] arm64: dts: qcom: pm7250b: Enable BCL sensor node Manaf Meethalavalappu Pallikunhi
2026-07-21 19:13 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 04/10] arm64: dts: qcom: hamoa-pmic: " Manaf Meethalavalappu Pallikunhi
2026-07-21 18:37 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 05/10] arm64: dts: qcom: pm8350c: " Manaf Meethalavalappu Pallikunhi
2026-07-21 19:14 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 06/10] arm64: dts: qcom: pmh0101: " Manaf Meethalavalappu Pallikunhi
2026-07-21 19:17 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 07/10] arm64: dts: qcom: pm8550: " Manaf Meethalavalappu Pallikunhi
2026-07-21 19:14 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 08/10] arm64: dts: qcom: pmih0108-kaanapali: " Manaf Meethalavalappu Pallikunhi
2026-07-21 19:17 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 09/10] arm64: dts: qcom: pmih0108: " Manaf Meethalavalappu Pallikunhi
2026-07-21 19:16 ` sashiko-bot
2026-07-21 18:30 ` [PATCH v2 10/10] arm64: dts: qcom: smb2370: " Manaf Meethalavalappu Pallikunhi
2026-07-21 19:17 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox