diff --git a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml deleted file mode 100644 index 6ffadc4d28bcf..0000000000000 --- a/Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/hwmon/qcom,bcl-hwmon.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor - -maintainers: - - Manaf Meethalavalappu Pallikunhi - -description: | - SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and - alarm functionality for battery overcurrent and battery or system under - voltage conditions. It monitors battery voltage and current, and - can trigger interrupts when configurable thresholds are exceeded. - -properties: - compatible: - oneOf: - - description: v1 based BCL - items: - - enum: - - qcom,pm7250b-bcl - - qcom,pm8250b-bcl - - const: qcom,bcl-v1 - - - description: v2 based BCL - items: - - enum: - - qcom,pm8350b-bcl - - qcom,pm8350c-bcl - - const: qcom,bcl-v2 - - - description: v3 bmx based BCL - items: - - enum: - - qcom,pm8550b-bcl - - qcom,pm7550ba-bcl - - const: qcom,bcl-v3-bmx - - - description: v3 core based BCL - items: - - enum: - - qcom,pm8550-bc0l - - qcom,pm7550-bcl - - const: qcom,bcl-v3-core - - - description: v3 wb based BCL - items: - - enum: - - qcom,pmw5100-bcl - - const: qcom,bcl-v3-wb - - - description: v4 bmx based BCL - items: - - enum: - - qcom,pmih010-bcl - - const: qcom,bcl-v4-bmx - - - description: v4 bmx with different scale based BCL - items: - - enum: - - qcom,pmv010-bcl - - const: qcom,bcl-v4-pmv010 - - - description: v4 core based BCL - items: - - enum: - - qcom,pmh010-bcl - - const: qcom,bcl-v4-core - - - description: v4 wb based BCL - items: - - enum: - - qcom,pmw6100-bcl - - const: qcom,bcl-v4-wb - - reg: - maxItems: 1 - description: BCL base address in the SPMI PMIC register map - - interrupts: - minItems: 2 - maxItems: 2 - description: - BCL alarm interrupts for different threshold levels - - interrupt-names: - items: - - const: bcl-max-min - - const: bcl-critical - - overcurrent-thresholds-milliamp: - description: - Current thresholds in milliamperes for the two configurable current - alarm levels (max and critical). These values are used to override - default thresholds if a platform has different battery ocp specification. - $ref: /schemas/types.yaml#/definitions/uint32-array - minItems: 2 - maxItems: 2 - -required: - - compatible - - reg - - interrupts - - interrupt-names - -unevaluatedProperties: false - -examples: - - | - #include - - pmic { - #address-cells = <1>; - #size-cells = <0>; - - sensor@1d00 { - compatible = "qcom,pm7250b-bcl", "qcom,bcl-v1"; - reg = <0x1d00>; - interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, - <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "bcl-max-min", - "bcl-critical"; - overcurrent-thresholds-milliamp = <5500 6000>; - }; - }; diff --git a/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml b/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml new file mode 100644 index 0000000000000..3c069e94a34e8 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/qcom,pm7250b-bcl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SPMI PMIC Battery Current Limiting (BCL) Hardware Monitor + +maintainers: + - Manaf Meethalavalappu Pallikunhi + +description: + SPMI PMIC Battery Current Limiting (BCL) hardware provides monitoring and + alarm functionality for battery overcurrent and battery or system under + voltage conditions. It monitors battery voltage and current, and + can trigger interrupts when configurable thresholds are exceeded. + +properties: + compatible: + enum: + - qcom,pm7250b-bcl + - qcom,pm8350c-bcl + - qcom,pm8550-bcl + - qcom,pmh0101-bcl + - qcom,pmih0108-bcl + - qcom,smb2360-bcl + - qcom,smb2370-bcl + + reg: + maxItems: 1 + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: max-min + - const: critical + +required: + - compatible + - reg + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1d00 { + compatible = "qcom,pm7250b-bcl"; + reg = <0x1d00>; + interrupts = <0x2 0x1d 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x1d 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + }; diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index d10085d85a97e..946e1361a5334 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -187,7 +187,7 @@ patternProperties: "^sensor@[0-9a-f]+$": type: object - $ref: /schemas/hwmon/qcom,bcl-hwmon.yaml# + $ref: /schemas/hwmon/qcom,pm7250b-bcl.yaml# "^temp-alarm@[0-9a-f]+$": type: object diff --git a/MAINTAINERS b/MAINTAINERS index 35c3d72107252..d40821faed832 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21092,14 +21092,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 L: linux-hwmon@vger.kernel.org L: linux-arm-msm@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/hwmon/qcom,bcl-hwmon.yaml -F: drivers/hwmon/qcom-bcl-hwmon.c -F: drivers/hwmon/qcom-bcl-hwmon.h +F: Documentation/devicetree/bindings/hwmon/qcom,pm7250b-bcl.yaml QUALCOMM BLUETOOTH DRIVER L: linux-arm-msm@vger.kernel.org diff --git a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi index 621890ada1536..cbe91e8631420 100644 --- a/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi +++ b/arch/arm64/boot/dts/qcom/hamoa-pmics.dtsi @@ -495,6 +495,15 @@ status = "disabled"; + sensor@4700 { + compatible = "qcom,smb2360-bcl"; + reg = <0x4700>; + interrupts = <0x7 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x7 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + smb2360_0_eusb2_repeater: phy@fd00 { compatible = "qcom,smb2360-eusb2-repeater"; reg = <0xfd00>; diff --git a/arch/arm64/boot/dts/qcom/pm7250b.dtsi b/arch/arm64/boot/dts/qcom/pm7250b.dtsi index dffd84dd87a85..2fa92f51c6191 100644 --- a/arch/arm64/boot/dts/qcom/pm7250b.dtsi +++ b/arch/arm64/boot/dts/qcom/pm7250b.dtsi @@ -90,6 +90,15 @@ status = "disabled"; }; + sensor@1d00 { + compatible = "qcom,pm7250b-bcl"; + reg = <0x1d00>; + interrupts = , + ; + interrupt-names = "max-min", + "critical"; + }; + pm7250b_temp: temp-alarm@2400 { compatible = "qcom,spmi-temp-alarm"; reg = <0x2400>; @@ -202,16 +211,6 @@ interrupt-controller; #interrupt-cells = <2>; }; - - sensor@1d00 { - compatible = "qcom,pm7250b-bcl", "qcom,bcl-v1"; - reg = <0x1d00>; - interrupts = , - ; - interrupt-names = "bcl-max-min", - "bcl-critical"; - overcurrent-thresholds-milliamp = <5500 6000>; - }; }; pmic@PM7250B_SID1 { diff --git a/arch/arm64/boot/dts/qcom/pm8350c.dtsi b/arch/arm64/boot/dts/qcom/pm8350c.dtsi index f0cf55a7fc9e5..8815868f9ad64 100644 --- a/arch/arm64/boot/dts/qcom/pm8350c.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8350c.dtsi @@ -20,6 +20,15 @@ #thermal-sensor-cells = <0>; }; + sensor@4700 { + compatible = "qcom,pm8350c-bcl"; + reg = <0x4700>; + interrupts = <0x2 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + pm8350c_gpios: gpio@8800 { compatible = "qcom,pm8350c-gpio", "qcom,spmi-gpio"; reg = <0x8800>; @@ -42,14 +51,6 @@ status = "disabled"; }; - sensor@4700 { - compatible = "qcom,pm8350c-bcl", "qcom,bcl-v2"; - reg = <0x4700>; - interrupts = <0x2 0x47 0x0 IRQ_TYPE_EDGE_RISING>, - <0x2 0x47 0x1 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "bcl-max-min", - "bcl-critical"; - }; }; }; diff --git a/arch/arm64/boot/dts/qcom/pm8550.dtsi b/arch/arm64/boot/dts/qcom/pm8550.dtsi index 896bcacb64902..fde76da37bacb 100644 --- a/arch/arm64/boot/dts/qcom/pm8550.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8550.dtsi @@ -45,6 +45,15 @@ #thermal-sensor-cells = <0>; }; + sensor@4700 { + compatible = "qcom,pm8550-bcl"; + reg = <0x4700>; + interrupts = <0x1 0x47 0x0 IRQ_TYPE_EDGE_RISING>, + <0x1 0x47 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "max-min", + "critical"; + }; + pm8550_gpios: gpio@8800 { compatible = "qcom,pm8550-gpio", "qcom,spmi-gpio"; reg = <0x8800>; diff --git a/arch/arm64/configs/qcom.config b/arch/arm64/configs/qcom.config index 70af068f10e02..20aca64dd6b60 100644 --- a/arch/arm64/configs/qcom.config +++ b/arch/arm64/configs/qcom.config @@ -65,7 +65,7 @@ CONFIG_STM_SOURCE_FTRACE=m CONFIG_STM_SOURCE_HEARTBEAT=m CONFIG_SENSORS_AMC6821=y CONFIG_SENSORS_EMC2305=y -CONFIG_SENSORS_QCOM_BCL=y +CONFIG_SENSORS_QCOM_SPMI_BCL=y CONFIG_SWIOTLB_DYNAMIC=y CONFIG_TRACE_MMIO_ACCESS=y CONFIG_UCLAMP_TASK_GROUP=y diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index fdb3ecc2bb041..e5238546fd99b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1893,8 +1893,10 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. -config SENSORS_QCOM_BCL - tristate "Qualcomm BCL hardware monitoring" +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. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e2d01688fdb75..f9cd9550731aa 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -196,7 +196,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o -obj-$(CONFIG_SENSORS_QCOM_BCL) += qcom-bcl-hwmon.o +obj-$(CONFIG_SENSORS_QCOM_SPMI_BCL) += qcom-bcl-hwmon.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o diff --git a/drivers/hwmon/qcom-bcl-hwmon.c b/drivers/hwmon/qcom-bcl-hwmon.c index 9894b1d14d124..33bbdd8196d16 100644 --- a/drivers/hwmon/qcom-bcl-hwmon.c +++ b/drivers/hwmon/qcom-bcl-hwmon.c @@ -1,11 +1,12 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* - * Qualcomm pmic BCL hardware driver for battery overcurrent and + * Qualcomm pmic BCL driver for battery overcurrent and * battery or system under voltage monitor * - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ +#include #include #include #include @@ -19,33 +20,296 @@ #include #include -#include "qcom-bcl-hwmon.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 -ADD_BCL_HWMON_ALARM_MAPS(in, min, lcrit); -ADD_BCL_HWMON_ALARM_MAPS(curr, max, crit); +/** + * 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, -/* Interrupt names for each alarm level */ -static const char * const bcl_int_names[ALARM_MAX] = { - [LVL0] = "bcl-max-min", - [LVL1] = "bcl-critical", + 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 char * const bcl_channel_label[CHANNEL_MAX] = { - "BCL Voltage", - "BCL Current", +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", }; -/* Common Reg Fields */ -static const struct reg_field common_reg_fields[COMMON_FIELD_MAX] = { +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), -}; - -/* BCL Version/Modes specific fields */ -static const struct reg_field bcl_v1_reg_fields[] = { [F_IN_MON_EN] = REG_FIELD(MODE_CTL1, 0, 2), [F_IN_L0_THR] = REG_FIELD(VADC_L0_THR, 0, 7), [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), @@ -57,7 +321,12 @@ static const struct reg_field bcl_v1_reg_fields[] = { [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), }; -static const struct reg_field bcl_v2_reg_fields[] = { +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), @@ -69,311 +338,323 @@ static const struct reg_field bcl_v2_reg_fields[] = { [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), }; -static const struct reg_field bcl_v3_bmx_reg_fields[] = { +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(VADC_L0_THR, 0, 7), + [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), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR_GEN3, 0, 7), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), }; -static const struct reg_field bcl_v3_wb_reg_fields[] = { +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(VADC_L0_THR, 0, 7), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), + [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), - [F_CURR_H0_THR] = REG_FIELD(IADC_H0_THR, 0, 7), - [F_CURR_H1_THR] = REG_FIELD(IADC_H1_THR, 0, 3), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), }; -static const struct reg_field bcl_v3_core_reg_fields[] = { +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(VCMP_L0_THR, 0, 5), + [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_v4_bmx_reg_fields[] = { +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, 15), + [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, 15), + [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 7), }; -static const struct reg_field bcl_v4_wb_reg_fields[] = { +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, 6), + [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 5), [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), + [F_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, 0, 4), - [F_CURR_INPUT] = REG_FIELD(IADC_DATA1, 0, 15), -}; - -static const struct reg_field bcl_v4_core_reg_fields[] = { - [F_IN_MON_EN] = REG_FIELD(VCMP_CTL, 0, 2), - [F_IN_L0_THR] = REG_FIELD(VCMP_L0_THR, 0, 6), - [F_IN_L1_THR] = REG_FIELD(VCMP_L1_THR, 0, 6), - [F_IN_INPUT_EN] = REG_FIELD(PARAM_1, 0, 0), - [F_IN_INPUT] = REG_FIELD(VADC_DATA1, 0, 15), - [F_CURR_MON_EN] = REG_FIELD(PARAM_1, 1, 1), + [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), }; -/* V1 BMX and core */ static const struct bcl_desc pm7250b_data = { - .reg_fields = bcl_v1_reg_fields, - .num_reg_fields = F_MAX_FIELDS, + .reg_fields = bcl_pm7250b_reg_fields, + .num_reg_fields = F_CURR_INPUT + 1, .data_field_bits_size = 8, .thresh_field_bits_size = 7, - .channel[IN] = { + .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 = {ADC, INDEX}, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, }, - .channel[CURR] = { + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { .max = 10000, .default_scale_nu = 305180, - .thresh_type = {ADC, ADC}, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, }, }; -/* V2 BMX and core */ -static const struct bcl_desc pm8350_data = { - .reg_fields = bcl_v2_reg_fields, - .num_reg_fields = F_MAX_FIELDS, +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, - .channel[IN] = { + .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 = {ADC, INDEX}, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, }, - .channel[CURR] = { + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { .max = 10000, .default_scale_nu = 305180, - .thresh_type = {ADC, ADC}, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, }, }; -/* V3 BMX */ -static const struct bcl_desc pm8550b_data = { - .reg_fields = bcl_v3_bmx_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .max = 12000, - .default_scale_nu = 366220, - .thresh_type = {ADC, ADC}, - }, -}; - -/* V3 WB */ -static const struct bcl_desc pmw5100_data = { - .reg_fields = bcl_v3_wb_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 8, - .thresh_field_bits_size = 8, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .base = 800, - .max = 2000, - .step = 100, - .default_scale_nu = 61035, - .thresh_type = {ADC, INDEX}, - }, -}; - -/* V3 CORE */ static const struct bcl_desc pm8550_data = { - .reg_fields = bcl_v3_core_reg_fields, + .reg_fields = bcl_pm8550_reg_fields, .num_reg_fields = F_CURR_MON_EN + 1, - .data_field_bits_size = 8, + .data_field_bits_size = 0, .thresh_field_bits_size = 8, - .channel[IN] = { + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { .base = 2250, .max = 3600, .step = 25, - .thresh_type = {INDEX, INDEX}, + .thresh_type = {THRESH_TYPE_INDEX, THRESH_TYPE_INDEX}, }, }; -/* V4 BMX */ -static const struct bcl_desc pmih010_data = { - .reg_fields = bcl_v4_bmx_reg_fields, +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, - .channel[IN] = { + .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 = {ADC, INDEX}, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_INDEX}, }, - .channel[CURR] = { + .channel_cfg[BCL_BATT_1S][CHANNEL_CURR] = { .max = 20000, .default_scale_nu = 610370, - .thresh_type = {ADC, ADC}, + .thresh_type = {THRESH_TYPE_ADC, THRESH_TYPE_ADC}, }, }; -/* V4 WB */ -static const struct bcl_desc pmw6100_data = { - .reg_fields = bcl_v4_wb_reg_fields, - .num_reg_fields = F_MAX_FIELDS, - .data_field_bits_size = 16, +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, - .channel[IN] = { + .battery_config_field = REG_FIELD(0, 0, 0), + .channel_cfg[BCL_BATT_1S][CHANNEL_IN] = { .base = 1500, .max = 4000, .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, - }, - .channel[CURR] = { - .base = 900, - .max = 3300, - .step = 150, - .default_scale_nu = 152586, - .thresh_type = {ADC, INDEX}, + .thresh_type = {THRESH_TYPE_INDEX, THRESH_TYPE_INDEX}, }, }; -/* V4 CORE */ -static const struct bcl_desc pmh010_data = { - .reg_fields = bcl_v4_core_reg_fields, - .num_reg_fields = F_CURR_MON_EN + 1, +/* 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, - .channel[IN] = { - .base = 1500, - .max = 4000, - .step = 25, - .thresh_type = {INDEX, INDEX}, + .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}, }, }; -/* V4 BMX with different scale */ -static const struct bcl_desc pmv010_data = { - .reg_fields = bcl_v4_bmx_reg_fields, +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, - .channel[IN] = { - .base = 2250, - .max = 3600, - .step = 25, - .default_scale_nu = 194637, - .thresh_type = {ADC, INDEX}, + .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[CURR] = { - .max = 12000, - .default_scale_nu = 366220, - .thresh_type = {ADC, ADC}, + .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 - * @desc: BCL device descriptor - * @raw_val: Raw ADC value from hardware + * @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 unsigned int bcl_convert_raw_to_milliunit(const struct bcl_desc *desc, int raw_val, - enum bcl_channel_type type, u8 field_width) +static int bcl_convert_raw_to_milliunit(const struct bcl_device *bcl, + s32 raw_val, + enum bcl_channel type, + u8 field_width) { - u32 def_scale = desc->channel[type].default_scale_nu; - u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; - u32 scaling_factor = def_scale * lsb_weight; + 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_s64((s64)raw_val * scaling_factor, 1000000); + return DIV_ROUND_CLOSEST((s64)raw_val * scaling_factor, 1000000); } /** * bcl_convert_milliunit_to_raw - Convert milli unit to raw value - * @desc: BCL device descriptor - * @ma_val: threshold value in milli unit + * @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_desc *desc, int mval, - enum bcl_channel_type type, u8 field_width) +static unsigned int bcl_convert_milliunit_to_raw(const struct bcl_device *bcl, + int mval, + enum bcl_channel type, + u8 field_width) { - u32 def_scale = desc->channel[type].default_scale_nu; - u32 lsb_weight = field_width > 8 ? 1 : 1 << field_width; - u32 scaling_factor = def_scale * lsb_weight; + 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_s64((s64)mval * 1000000, scaling_factor); + return DIV_ROUND_CLOSEST_ULL((u64)mval * 1000000, scaling_factor); } /** * bcl_convert_milliunit_to_index - Convert milliunit to in or curr index - * @desc: BCL device descriptor + * @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: Voltage index value + * Return: Index value */ -static unsigned int bcl_convert_milliunit_to_index(const struct bcl_desc *desc, int val, - enum bcl_channel_type type) +static unsigned int bcl_convert_milliunit_to_index(const struct bcl_device *bcl, + int val, + enum bcl_channel type) { - return div_s64((s64)val - desc->channel[type].base, desc->channel[type].step); + 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 - * @desc: BCL device descriptor + * @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: Voltage value in millivolts + * Return: Value in millivolts or milliamps */ -static unsigned int bcl_convert_index_to_milliunit(const struct bcl_desc *desc, int val, - enum bcl_channel_type type) +static unsigned int bcl_convert_index_to_milliunit(const struct bcl_device *bcl, + int val, + enum bcl_channel type) { - return desc->channel[type].base + val * desc->channel[type].step; + 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) @@ -381,13 +662,14 @@ static int bcl_in_thresh_write(struct bcl_device *bcl, long value, enum bcl_limi const struct bcl_desc *desc = bcl->desc; u32 raw_val; - int thresh = clamp_val(value, desc->channel[IN].base, desc->channel[IN].max); + 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[IN].thresh_type[lvl] == ADC) - raw_val = bcl_convert_milliunit_to_raw(desc, thresh, IN, + 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(desc, thresh, IN); + raw_val = bcl_convert_milliunit_to_index(bcl, thresh, CHANNEL_IN); return regmap_field_write(bcl->fields[F_IN_L0_THR + lvl], raw_val); } @@ -397,14 +679,13 @@ static int bcl_curr_thresh_write(struct bcl_device *bcl, long value, enum bcl_li const struct bcl_desc *desc = bcl->desc; u32 raw_val; - /* Clamp only to curr max */ - int thresh = clamp_val(value, value, desc->channel[CURR].max); + int thresh = clamp_val(value, 0, desc->channel_cfg[bcl->batt_config][CHANNEL_CURR].max); - if (desc->channel[CURR].thresh_type[lvl] == ADC) - raw_val = bcl_convert_milliunit_to_raw(desc, thresh, CURR, + 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(desc, thresh, CURR); + raw_val = bcl_convert_milliunit_to_index(bcl, thresh, CHANNEL_CURR); return regmap_field_write(bcl->fields[F_CURR_H0_THR + lvl], raw_val); } @@ -415,15 +696,15 @@ static int bcl_in_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl, u32 raw_val = 0; const struct bcl_desc *desc = bcl->desc; - ret = bcl_read_field_value(bcl, F_IN_L0_THR + lvl, &raw_val); + ret = regmap_field_read(bcl->fields[F_IN_L0_THR + lvl], &raw_val); if (ret) return ret; - if (desc->channel[IN].thresh_type[lvl] == ADC) - thresh = bcl_convert_raw_to_milliunit(desc, raw_val, IN, + 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(desc, raw_val, IN); + thresh = bcl_convert_index_to_milliunit(bcl, raw_val, CHANNEL_IN); *out = thresh; @@ -436,15 +717,15 @@ static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl u32 raw_val = 0; const struct bcl_desc *desc = bcl->desc; - ret = bcl_read_field_value(bcl, F_CURR_H0_THR + lvl, &raw_val); + ret = regmap_field_read(bcl->fields[F_CURR_H0_THR + lvl], &raw_val); if (ret) return ret; - if (desc->channel[CURR].thresh_type[lvl] == ADC) - thresh = bcl_convert_raw_to_milliunit(desc, raw_val, CURR, + 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(desc, raw_val, CURR); + thresh = bcl_convert_index_to_milliunit(bcl, raw_val, CHANNEL_CURR); *out = thresh; @@ -454,21 +735,38 @@ static int bcl_curr_thresh_read(struct bcl_device *bcl, enum bcl_limit_alarm lvl static int bcl_curr_input_read(struct bcl_device *bcl, long *out) { int ret; - u32 raw_val = 0; + u32 raw_val = 0, msb = 0; + s32 signed_val; const struct bcl_desc *desc = bcl->desc; - ret = bcl_read_field_value(bcl, F_CURR_INPUT, &raw_val); + /* 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); + } + /* - * The sensor sometime can read a value 0 if there are - * consecutive reads + * Current ADC reading is in 2's complement form. + * Sign extend the value based on data field bit size. */ - if (raw_val != 0) - bcl->last_curr_input = - bcl_convert_raw_to_milliunit(desc, raw_val, CURR, - desc->data_field_bits_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; @@ -478,17 +776,31 @@ static int bcl_curr_input_read(struct bcl_device *bcl, long *out) static int bcl_in_input_read(struct bcl_device *bcl, long *out) { int ret; - u32 raw_val = 0; + u32 raw_val = 0, msb = 0; const struct bcl_desc *desc = bcl->desc; - ret = bcl_read_field_value(bcl, F_IN_INPUT, &raw_val); + /* 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; - if (raw_val < GENMASK(desc->data_field_bits_size - 1, 0)) - bcl->last_in_input = - bcl_convert_raw_to_milliunit(desc, raw_val, IN, - desc->data_field_bits_size); + /* 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; @@ -501,7 +813,7 @@ static int bcl_read_alarm_status(struct bcl_device *bcl, int ret; u32 raw_val = 0; - ret = bcl_read_field_value(bcl, F_LVL0_ALARM + lvl, &raw_val); + ret = regmap_field_read(bcl->fields[F_LVL0_ALARM + lvl], &raw_val); if (ret) return ret; @@ -514,7 +826,7 @@ static unsigned int bcl_get_version_major(const struct bcl_device *bcl) { u32 raw_val = 0; - bcl_read_field_value(bcl, F_V_MAJOR, &raw_val); + regmap_field_read(bcl->fields[F_V_MAJOR], &raw_val); return raw_val; } @@ -523,17 +835,18 @@ static unsigned int bcl_get_version_minor(const struct bcl_device *bcl) { u32 raw_val = 0; - bcl_read_field_value(bcl, F_V_MINOR, &raw_val); + 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_mon_enabled) + if (bcl->in_attrs) hwmon_notify_event(bcl->hwmon_dev, hwmon_in, in_lvl_to_attr_map[alarm], 0); - if (bcl->curr_mon_enabled) + + if (bcl->curr_attrs) hwmon_notify_event(bcl->hwmon_dev, hwmon_curr, curr_lvl_to_attr_map[alarm], 0); } @@ -542,48 +855,74 @@ static void bcl_alarm_enable_poll(struct work_struct *work) { struct bcl_alarm_data *alarm = container_of(work, struct bcl_alarm_data, alarm_poll_work.work); - struct bcl_device *bcl = alarm->device; + struct bcl_device *bcl = (struct bcl_device *)alarm->device; long status; + int ret; - guard(mutex)(&bcl->lock); - - if (bcl_read_alarm_status(bcl, alarm->type, &status)) - goto re_schedule; + scoped_guard(mutex, &bcl->lock) + ret = bcl_read_alarm_status(bcl, alarm->type, &status); - if (!status & !alarm->irq_enabled) { - bcl_enable_irq(alarm); - bcl_hwmon_notify_event(bcl, alarm->type); + /* 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; } -re_schedule: - schedule_delayed_work(&alarm->alarm_poll_work, - msecs_to_jiffies(BCL_ALARM_POLLING_MS)); + /* 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 = alarm->device; + 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; - if (!bcl->hwmon_dev) + bcl_hwmon_notify_event(bcl, alarm->type); + + guard(mutex)(&alarm->a_lock); + if (alarm->shutting_down) return IRQ_HANDLED; - bcl_hwmon_notify_event(bcl, alarm->type); + 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; + } - bcl_disable_irq(alarm); schedule_delayed_work(&alarm->alarm_poll_work, msecs_to_jiffies(BCL_ALARM_POLLING_MS)); - dev_dbg(bcl->dev, "Irq:%d triggered for bcl type:%d\n", - irq, alarm->type); - return IRQ_HANDLED; } @@ -591,15 +930,10 @@ static umode_t bcl_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { - const struct bcl_device *bcl = data; - switch (type) { case hwmon_in: - if (!bcl->in_mon_enabled) - return 0; switch (attr) { case hwmon_in_input: - return bcl->in_input_enabled ? 0444 : 0; case hwmon_in_label: case hwmon_in_min_alarm: case hwmon_in_lcrit_alarm: @@ -611,8 +945,6 @@ static umode_t bcl_hwmon_is_visible(const void *data, return 0; } case hwmon_curr: - if (!bcl->curr_mon_enabled) - return 0; switch (attr) { case hwmon_curr_input: case hwmon_curr_label: @@ -634,7 +966,6 @@ static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct bcl_device *bcl = dev_get_drvdata(dev); - int ret = -EOPNOTSUPP; guard(mutex)(&bcl->lock); @@ -643,100 +974,86 @@ static int bcl_hwmon_write(struct device *dev, enum hwmon_sensor_types type, switch (attr) { case hwmon_in_min: case hwmon_in_lcrit: - ret = bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); - break; + return bcl_in_thresh_write(bcl, val, in_attr_to_lvl_map[attr]); default: - ret = -EOPNOTSUPP; + return -EOPNOTSUPP; } - break; case hwmon_curr: switch (attr) { case hwmon_curr_max: case hwmon_curr_crit: - ret = bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); - break; + return bcl_curr_thresh_write(bcl, val, curr_attr_to_lvl_map[attr]); default: - ret = -EOPNOTSUPP; + return -EOPNOTSUPP; } - break; default: - break; + return -EOPNOTSUPP; } +} - return ret; +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); - int ret; - - guard(mutex)(&bcl->lock); switch (type) { case hwmon_in: - switch (attr) { - case hwmon_in_input: - ret = bcl_in_input_read(bcl, value); - break; - case hwmon_in_min: - case hwmon_in_lcrit: - ret = bcl_in_thresh_read(bcl, in_attr_to_lvl_map[attr], value); - break; - case hwmon_in_min_alarm: - case hwmon_in_lcrit_alarm: - ret = bcl_read_alarm_status(bcl, in_attr_to_lvl_map[attr], value); - break; - default: - ret = -EOPNOTSUPP; - } - break; + return bcl_in_read(bcl, attr, value); case hwmon_curr: - switch (attr) { - case hwmon_curr_input: - ret = bcl_curr_input_read(bcl, value); - break; - case hwmon_curr_max: - case hwmon_curr_crit: - ret = bcl_curr_thresh_read(bcl, curr_attr_to_lvl_map[attr], value); - break; - case hwmon_curr_max_alarm: - case hwmon_curr_crit_alarm: - ret = bcl_read_alarm_status(bcl, curr_attr_to_lvl_map[attr], value); - break; - default: - ret = -EOPNOTSUPP; - } - break; + return bcl_curr_read(bcl, attr, value); default: - ret = -EOPNOTSUPP; - break; + return -EOPNOTSUPP; } - - return ret; } static int bcl_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { - switch (type) { - case hwmon_in: - if (attr != hwmon_in_label) - break; - *str = bcl_channel_label[IN]; - return 0; - case hwmon_curr: - if (attr != hwmon_curr_label) - break; - *str = bcl_channel_label[CURR]; - return 0; - default: - break; - } + 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 -EOPNOTSUPP; + return *str ? 0 : -EOPNOTSUPP; } static const struct hwmon_ops bcl_hwmon_ops = { @@ -746,77 +1063,240 @@ static const struct hwmon_ops bcl_hwmon_ops = { .write = bcl_hwmon_write, }; -static const struct hwmon_channel_info *bcl_hwmon_info[] = { - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_MIN | - HWMON_I_LCRIT | HWMON_I_MIN_ALARM | - HWMON_I_LCRIT_ALARM), - HWMON_CHANNEL_INFO(curr, - HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_MAX | - HWMON_C_CRIT | HWMON_C_MAX_ALARM | - HWMON_C_CRIT_ALARM), - NULL, -}; +static int bcl_detect_battery_config(struct bcl_device *bcl) +{ + u32 reg_val = 0; + int ret; -static const struct hwmon_chip_info bcl_hwmon_chip_info = { - .ops = &bcl_hwmon_ops, - .info = bcl_hwmon_info, -}; + 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; + } -static int bcl_curr_thresh_update(struct bcl_device *bcl) + /* + * 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) { - int ret, i; + const struct bcl_desc *desc = bcl->desc; + int ret; - if (!bcl->curr_thresholds[0]) + /* 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; + } - for (i = 0; i < ALARM_MAX; i++) { - ret = bcl_curr_thresh_write(bcl, bcl->curr_thresholds[i], i); - if (ret < 0) + /* 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_hw_channel_mon_init(struct bcl_device *bcl) +static void bcl_alarm_work_cleanup_action(void *data) { - bcl->in_mon_enabled = bcl_in_monitor_enabled(bcl); - bcl->in_input_enabled = bcl_in_input_enabled(bcl); - bcl->curr_mon_enabled = bcl_curr_monitor_enabled(bcl); + 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 = 0, irq_num = 0, i = 0; + int ret, irq_num, i; struct bcl_alarm_data *alarm; - for (i = LVL0; i < ALARM_MAX; i++) { + for (i = 0; i < ARRAY_SIZE(bcl->bcl_alarms); i++) { alarm = &bcl->bcl_alarms[i]; alarm->type = i; alarm->device = bcl; - - ret = devm_delayed_work_autocancel(bcl->dev, &alarm->alarm_poll_work, - bcl_alarm_enable_poll); + 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) - continue; + 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) { - dev_err(&pdev->dev, "Error requesting irq(%s).err:%d\n", - bcl_int_names[i], ret); + 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; - } - alarm->irq = irq_num; - enable_irq_wake(alarm->irq); - alarm->irq_enabled = true; } return 0; @@ -826,39 +1306,25 @@ static int bcl_regmap_field_init(struct device *dev, struct bcl_device *bcl, const struct bcl_desc *data) { int i; - struct reg_field fields[F_MAX_FIELDS]; - - BUILD_BUG_ON(ARRAY_SIZE(common_reg_fields) != COMMON_FIELD_MAX); + /* + * 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++) { - if (i < COMMON_FIELD_MAX) - fields[i] = common_reg_fields[i]; - else - fields[i] = data->reg_fields[i]; - - /* Need to adjust BCL base from regmap dynamically */ - fields[i].reg += bcl->base; - } + struct reg_field field = data->reg_fields[i]; - return devm_regmap_field_bulk_alloc(dev, bcl->regmap, bcl->fields, - fields, data->num_reg_fields); -} - -static int bcl_get_device_property_data(struct platform_device *pdev, - struct bcl_device *bcl) -{ - struct device *dev = &pdev->dev; - int ret; - u32 reg; + /* Skip uninitialized fields */ + if (field.reg == 0 && field.lsb == 0 && field.msb == 0) + continue; - ret = device_property_read_u32(dev, "reg", ®); - if (ret < 0) - return ret; + field.reg += bcl->base; - bcl->base = reg; + bcl->fields[i] = devm_regmap_field_alloc(dev, bcl->regmap, field); + if (IS_ERR(bcl->fields[i])) + return PTR_ERR(bcl->fields[i]); + } - device_property_read_u32_array(dev, "overcurrent-thresholds-milliamp", - bcl->curr_thresholds, 2); return 0; } @@ -866,6 +1332,7 @@ 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) @@ -874,60 +1341,60 @@ static int bcl_probe(struct platform_device *pdev) bcl->dev = &pdev->dev; bcl->desc = device_get_match_data(&pdev->dev); if (!bcl->desc) - return -EINVAL; + 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) { - dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); - return -EINVAL; - } + if (!bcl->regmap) + return dev_err_probe(&pdev->dev, -EINVAL, "Couldn't get parent's regmap\n"); - ret = bcl_get_device_property_data(pdev, bcl); + ret = device_property_read_u32(&pdev->dev, "reg", ®); if (ret < 0) - return ret; + return dev_err_probe(&pdev->dev, ret, "Failed to read 'reg' property\n"); - ret = bcl_regmap_field_init(bcl->dev, bcl, bcl->desc); - if (ret < 0) { - dev_err(&pdev->dev, "Unable to allocate regmap fields, err:%d\n", ret); - return ret; - } + bcl->base = reg; - if (!bcl_hw_is_enabled(bcl)) - return -ENODEV; + 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 = bcl_curr_thresh_update(bcl); + ret = regmap_field_read(bcl->fields[F_CTL_EN], ®); if (ret < 0) - return ret; + return dev_err_probe(&pdev->dev, ret, "Failed to read BCL enable status\n"); - ret = bcl_alarm_irq_init(pdev, bcl); + 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 ret; + return dev_err_probe(&pdev->dev, ret, "Failed to update scaling factors\n"); - bcl_hw_channel_mon_init(bcl); + 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)) { - dev_err(&pdev->dev, "Failed to sanitize hwmon name\n"); + 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, + &bcl->hwmon_chip_info, NULL); - if (IS_ERR(bcl->hwmon_dev)) { - dev_err(&pdev->dev, "Failed to register hwmon device: %ld\n", - PTR_ERR(bcl->hwmon_dev)); - return PTR_ERR(bcl->hwmon_dev); - } + 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)); @@ -937,32 +1404,26 @@ static int bcl_probe(struct platform_device *pdev) static const struct of_device_id bcl_match[] = { { - .compatible = "qcom,bcl-v1", + .compatible = "qcom,pm7250b-bcl", .data = &pm7250b_data, }, { - .compatible = "qcom,bcl-v2", - .data = &pm8350_data, - }, { - .compatible = "qcom,bcl-v3-bmx", - .data = &pm8550b_data, + .compatible = "qcom,pm8350c-bcl", + .data = &pm8350c_data, }, { - .compatible = "qcom,bcl-v3-wb", - .data = &pmw5100_data, - }, { - .compatible = "qcom,bcl-v3-core", + .compatible = "qcom,pm8550-bcl", .data = &pm8550_data, }, { - .compatible = "qcom,bcl-v4-bmx", - .data = &pmih010_data, + .compatible = "qcom,pmh0101-bcl", + .data = &pmh0101_data, }, { - .compatible = "qcom,bcl-v4-wb", - .data = &pmw6100_data, + .compatible = "qcom,pmih0108-bcl", + .data = &pmih0108_data, }, { - .compatible = "qcom,bcl-v4-core", - .data = &pmh010_data, + .compatible = "qcom,smb2360-bcl", + .data = &smb2360_data, }, { - .compatible = "qcom,bcl-v4-pmv010", - .data = &pmv010_data, + .compatible = "qcom,smb2370-bcl", + .data = &smb2370_data, }, { } }; @@ -971,12 +1432,12 @@ MODULE_DEVICE_TABLE(of, bcl_match); static struct platform_driver bcl_driver = { .probe = bcl_probe, .driver = { - .name = BCL_DRIVER_NAME, + .name = "qcom-bcl-hwmon", .of_match_table = bcl_match, }, }; -MODULE_AUTHOR("Manaf Meethalavalappu Pallikunhi "); -MODULE_DESCRIPTION("QCOM BCL HWMON driver"); module_platform_driver(bcl_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI BCL HWMON driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/qcom-bcl-hwmon.h b/drivers/hwmon/qcom-bcl-hwmon.h deleted file mode 100644 index 28a7154d9486a..0000000000000 --- a/drivers/hwmon/qcom-bcl-hwmon.h +++ /dev/null @@ -1,311 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. - */ - -#ifndef __QCOM_BCL_HWMON_H__ -#define __QCOM_BCL_HWMON_H__ - -#define BCL_DRIVER_NAME "qcom-bcl-hwmon" - -/* BCL common regmap offset */ -#define REVISION1 0x0 -#define REVISION2 0x1 -#define STATUS 0x8 -#define INT_RT_STS 0x10 -#define EN_CTL1 0x46 - -/* BCL GEN1 regmap offsets */ -#define MODE_CTL1 0x41 -#define VADC_L0_THR 0x48 -#define VCMP_L1_THR 0x49 -#define IADC_H0_THR 0x4b -#define IADC_H1_THR 0x4c -#define VADC_CONV_REQ 0x72 -#define IADC_CONV_REQ 0x82 -#define VADC_DATA1 0x76 -#define IADC_DATA1 0x86 - -/* BCL GEN3 regmap offsets */ -#define VCMP_CTL 0x44 -#define VCMP_L0_THR 0x47 -#define PARAM_1 0x0e -#define IADC_H1_THR_GEN3 0x4d - -#define BCL_IN_INC_MV 25 -#define BCL_ALARM_POLLING_MS 50 - -/** - * enum bcl_limit_alarm - BCL alarm threshold levels - * @LVL0: Level 0 alarm threshold (mapped to in_min_alarm or curr_max_alarm) - * @LVL1: Level 1 alarm threshold (mapped to in_lcrit_alarm or curr_crit_alarm) - * @ALARM_MAX: sentinel value - * - * Defines the three threshold levels for BCL monitoring. Each level corresponds - * to different severity of in or curr conditions. - */ -enum bcl_limit_alarm { - LVL0, - LVL1, - - ALARM_MAX, -}; - -/** - * enum bcl_channel_type - BCL supported sensor channel type - * @IN: in (voltage) channel - * @CURR: curr (current) channel - * @CHANNEL_MAX: sentinel value - * - * Defines the supported channel types for bcl. - */ -enum bcl_channel_type { - IN, - CURR, - - CHANNEL_MAX, -}; - -/** - * enum bcl_thresh_type - voltage or current threshold representation type - * @ADC: Raw ADC value representation - * @INDEX: Index-based voltage or current representation - * - * Specifies how voltage or current thresholds are stored and interpreted in - * registers. Some PMICs use raw ADC values while others use indexed values. - */ -enum bcl_thresh_type { - ADC, - INDEX, -}; - -/** - * enum bcl_fields - BCL register field identifiers - * @F_V_MAJOR: Major revision info field - * @F_V_MINOR: Minor revision info field - * @F_CTL_EN: Monitor enable control field - * @F_LVL0_ALARM: Level 0 alarm status field - * @F_LVL1_ALARM: Level 1 alarm status field - * @COMMON_FIELD_MAX: sentinel value for common fields - * @F_IN_MON_EN: voltage monitor enable control field - * @F_IN_L0_THR: voltage level 0 threshold field - * @F_IN_L1_THR: voltage level 1 threshold field - * @F_IN_INPUT_EN: voltage input enable control field - * @F_IN_INPUT: voltage input data field - * @F_CURR_MON_EN: current monitor enable control field - * @F_CURR_H0_THR: current level 0 threshold field - * @F_CURR_H1_THR: current level 1 threshold field - * @F_CURR_INPUT: current input data field - * @F_MAX_FIELDS: sentinel value - * - * Enumeration of all register fields used by the BCL driver for accessing - * registers through regmap fields. - */ -enum bcl_fields { - F_V_MAJOR, - F_V_MINOR, - - F_CTL_EN, - - /* common alarm for in and curr channel */ - F_LVL0_ALARM, - F_LVL1_ALARM, - - COMMON_FIELD_MAX, - - F_IN_MON_EN = COMMON_FIELD_MAX, - F_IN_L0_THR, - F_IN_L1_THR, - - F_IN_INPUT_EN, - F_IN_INPUT, - - F_CURR_MON_EN, - F_CURR_H0_THR, - F_CURR_H1_THR, - - F_CURR_INPUT, - - F_MAX_FIELDS -}; - -#define ADD_BCL_HWMON_ALARM_MAPS(_type, lvl0_attr, lvl1_attr) \ - \ -static const u8 _type##_attr_to_lvl_map[] = { \ - [hwmon_##_type##_##lvl0_attr] = LVL0, \ - [hwmon_##_type##_##lvl1_attr] = LVL1, \ - [hwmon_##_type##_##lvl0_attr##_alarm] = LVL0, \ - [hwmon_##_type##_##lvl1_attr##_alarm] = LVL1, \ -}; \ - \ -static const u8 _type##_lvl_to_attr_map[ALARM_MAX] = { \ - [LVL0] = hwmon_##_type##_##lvl0_attr##_alarm, \ - [LVL1] = hwmon_##_type##_##lvl1_attr##_alarm, \ -} - -/** - * struct bcl_channel_cfg - BCL channel related configuration - * @default_scale_nu: Default scaling factor in nano unit - * @base: Base threshold value in milli unit - * @max: Maximum threshold value in milli unit - * @step: step increment value between two indexed threshold value - * @thresh_type: Array specifying threshold representation type for each alarm level - * - * Contains hardware-specific configuration and scaling parameters for different - * channel(voltage and current).. - */ - -struct bcl_channel_cfg { - u32 default_scale_nu; - u32 base; - u32 max; - u32 step; - u8 thresh_type[ALARM_MAX]; -}; - -/** - * struct bcl_desc - BCL device descriptor - * @reg_fields: Array of register field definitions for this device variant - * @channel: Each channel specific(voltage or current) configuration - * @num_reg_fields: Number of register field definitions for this device variant - * @data_field_bits_size: data read register bit size - * @thresh_field_bits_size: lsb bit size those are not included in threshold register - * - * Contains hardware-specific configuration and scaling parameters for different - * BCL variants. Each PMIC model may have different register layouts and - * conversion factors. - */ - -struct bcl_desc { - const struct reg_field *reg_fields; - struct bcl_channel_cfg channel[CHANNEL_MAX]; - u8 num_reg_fields; - u8 data_field_bits_size; - u8 thresh_field_bits_size; -}; - -struct bcl_device; - -/** - * struct bcl_alarm_data - BCL alarm interrupt data - * @irq: IRQ number assigned to this alarm - * @irq_enabled: Flag indicating if IRQ is enabled - * @type: Alarm level type (LVL0, or LVL1) - * @device: Pointer to parent BCL device structure - * @alarm_poll_work: delayed_work to poll alarm status - * - * Stores interrupt-related information for each alarm threshold level. - * Used by the IRQ handler to identify which alarm triggered. - */ -struct bcl_alarm_data { - int irq; - bool irq_enabled; - enum bcl_limit_alarm type; - struct bcl_device *device; - struct delayed_work alarm_poll_work; -}; - -/** - * struct bcl_device - Main BCL device structure - * @dev: Pointer to device structure - * @regmap: Regmap for accessing PMIC registers - * @fields: Array of regmap fields for register access - * @bcl_alarms: Array of alarm data structures for each threshold level - * @lock: Mutex for protecting concurrent hardware access - * @in_mon_enabled: Flag indicating if voltage monitoring is enabled - * @curr_mon_enabled: Flag indicating if current monitoring is enabled - * @curr_thresholds: Current threshold values in milliamps from dt-binding(LVL0 and LVL1) - * @base: the BCL regbase offset from regmap - * @in_input_enabled: Flag indicating if voltage input reading is enabled - * @last_in_input: Last valid voltage input reading in millivolts - * @last_curr_input: Last valid current input reading in milliamps - * @desc: Pointer to device descriptor with hardware-specific parameters - * @hwmon_dev: Pointer to registered hwmon device - * @hwmon_name: Sanitized name for hwmon device - * - * Main driver structure containing all state and configuration for a BCL - * monitoring instance. Manages voltage and current monitoring, thresholds, - * and alarm handling. - */ -struct bcl_device { - struct device *dev; - struct regmap *regmap; - u16 base; - struct regmap_field *fields[F_MAX_FIELDS]; - struct bcl_alarm_data bcl_alarms[ALARM_MAX]; - struct mutex lock; - u32 curr_thresholds[ALARM_MAX]; - u32 last_in_input; - u32 last_curr_input; - bool in_mon_enabled; - bool curr_mon_enabled; - bool in_input_enabled; - const struct bcl_desc *desc; - struct device *hwmon_dev; - char *hwmon_name; -}; - -/** - * bcl_read_field_value - Read alarm status for a given level - * @bcl: BCL device structure - * @id: Index in bcl->fields[] - * @val: Pointer to store val - * - * Return: 0 on success or regmap error code - */ -static inline int bcl_read_field_value(const struct bcl_device *bcl, enum bcl_fields id, u32 *val) -{ - return regmap_field_read(bcl->fields[id], val); -} - -/** - * bcl_field_enabled - Generic helper to check if a regmap field is enabled - * @bcl: BCL device structure - * @field: Index in bcl->fields[] - * - * Return: true if field is non-zero, false otherwise - */ -static inline bool bcl_field_enabled(const struct bcl_device *bcl, enum bcl_fields id) -{ - int ret; - u32 val = 0; - - ret = regmap_field_read(bcl->fields[id], &val); - if (ret) - return false; - - return !!val; -} - -#define bcl_in_input_enabled(bcl) bcl_field_enabled(bcl, F_IN_INPUT_EN) -#define bcl_curr_monitor_enabled(bcl) bcl_field_enabled(bcl, F_CURR_MON_EN) -#define bcl_in_monitor_enabled(bcl) bcl_field_enabled(bcl, F_IN_MON_EN) -#define bcl_hw_is_enabled(bcl) bcl_field_enabled(bcl, F_CTL_EN) - -/** - * bcl_enable_irq - Generic helper to enable alarm irq - * @alarm: BCL level alarm data - */ -static inline void bcl_enable_irq(struct bcl_alarm_data *alarm) -{ - if (alarm->irq_enabled) - return; - alarm->irq_enabled = true; - enable_irq(alarm->irq); - enable_irq_wake(alarm->irq); -} - -/** - * bcl_disable_irq - Generic helper to disable alarm irq - * @alarm: BCL level alarm data - */ -static inline void bcl_disable_irq(struct bcl_alarm_data *alarm) -{ - if (!alarm->irq_enabled) - return; - alarm->irq_enabled = false; - disable_irq_nosync(alarm->irq); - disable_irq_wake(alarm->irq); -} - -#endif /* __QCOM_BCL_HWMON_H__ */