This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

BQ25180: Linux code compatible kernel 6.1.55

Part Number: BQ25180

Tool/software:

HI team,

Do we have Linux code base on kernel 6.1.55 for BQ25180?

Thanks,

Leo

  • Hi TI,

    I paste code of bq2518x_set_precharge_current :

    ------------------------------------------------------
    #define BQ2518X_PERCENTAGE 100
    static int bq2518x_set_precharge_current(struct bq2518x_device *bq2518x,
    int val)
    {
    int ret;
    unsigned int icharge_value;
    unsigned int times_of_icharge;

    icharge_value = bq2518x_get_const_charge_current(bq2518x);

    times_of_icharge = (icharge_value / val) * BQ2518X_PERCENTAGE;

    switch (times_of_icharge) {
    case BQ2518X_TIME_OF_ICHARGE01:
    ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_1X_TERM |
    BQ2518X_TERMINATION_CURRENT_PERCENT05));
    break;

    case BQ2518X_TIME_OF_ICHARGE02:
    ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_1X_TERM |
    BQ2518X_TERMINATION_CURRENT_PERCENT10));
    break;

    case BQ2518X_TIME_OF_ICHARGE03:
    ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_1X_TERM |
    BQ2518X_TERMINATION_CURRENT_PERCENT20));
    break;

    case BQ2518X_TIME_OF_ICHARGE04:
    ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_2X_TERM |
    BQ2518X_TERMINATION_CURRENT_PERCENT20));
    break;

    default:
    ret = -EINVAL;
    break;
    }
    return ret;
    }

    ------------------------------------------------------

    I don't understand why times_of_icharge = (icharge_value / val) * 100 in 2518x driver ? Because of this, the value of times_of_icharge can’t match BQ2518X_TIME_OF_ICHARGE01 ~ BQ2518X_TIME_OF_ICHARGE04 (5/10/20/40), so ret = -EINVAL and loading driver failed.
    Could you help to confirm it and explain the cause of this issue ? Becasue of this, I'm not sure what value to configure in DTS to ensure that the driver can run properly .

    WNC/Jason

  • Hello Leo,

    The BQ25180 driver we have was developed for Linux kernel 5.15. It may work without modifications on 6.1.55, but there could be compatibility issues due to kernel API changes.

    BQ2518x driver: https://e2e.ti.com/cfs-file/__key/communityserver-discussions-components-files/196/bq2518x_5F00_driver.tar.gz2

    I recommend trying to compile it to check for compatibility. Let me know if you encounter any issues.

    Best regards,

    Alec

  • Hello Jason,

    It appears the issue is related to the times_of_icharge calculation.

    If val is configured to represent the precharge current in your DTS, then the correct calculation should be:

    times_of_icharge = (val / icharge_value) * BQ2518X_PERCENTAGE;

    For example, if you want the precharge current to be 5% of a 1A fast charge current (where icharge_value = 1,000,000uA), the calculation would be:

    times_of_icharge = (50,000uA / 1,000,000uA) * 100 = 5%

    This would match BQ2518X_TIME_OF_ICHARGE01, and properly set the precharge current.

    I recommend modifying the calculation as shown above to ensure the correct configuration of the precharge current.

    Without this adjustment, it doesn't seem that times_of_icharge will ever produce a valid value (5, 10, 20, or 40).

    Let me know if this works and if you still encounter any issues.

    Best regards,

    Alec

  • Hi Alec,

    1. BQ2518x driver you provided can not compile under kernel 6.1.55, because power_supply_get_battery_info is defined differently between kernel5.15 and kernel6.1.55. And I'm not sure if there are other underlying problems.

    2.   #define BQ2518X_PERCENTAGE 100
          static int bq2518x_set_precharge_current(struct bq2518x_device *bq2518x, int val)
         {
          int ret;
          unsigned int icharge_value;
          unsigned int times_of_icharge;

          times_of_icharge = (icharge_value / val) * BQ2518X_PERCENTAGE

          case BQ2518X_TIME_OF_ICHARGE01 //5

          case BQ2518X_TIME_OF_ICHARGE02 //10

          case BQ2518X_TIME_OF_ICHARGE03 //20

          case BQ2518X_TIME_OF_ICHARGE04 //40

           default:
                ret = -EINVAL;

          .........

    No matter what values of icharge_value  and val, times_of_icharge is an integer greater than 100 or 0 rather than 5/10/20/40 . So ret = -EINVAL and loading driver failed .

    Could you please tell me your email and discuss with teams online ?

    Thanks!

    WNC/Jason

  • Hello Jason,

    Thank you for the update and for pointing out that the definition of power_supply_get_battery_info has changed between kernel versions. It's possible that this change could be affecting the driver compilation, but I'll need more details to confirm if it's a breaking change or if there's another underlying issue.

    Could you provide the error logs or or compilation messages that specifically mention the issue with power_supply_get_battery_info?

    Just to clarify, the expression to calculate times_of_icharge could result in values greater than 5, 10, 20, or 40. To avoid this, I recommend flipping the expression inside the parentheses, like this:

    times_of_icharge = (val / icharge_value) * BQ2518X_PERCENTAGE;

    I'll send my email to you through private messaging so we can continue the discussion offline.

    Best regards,

    Alec

  • Hi Alec,

        I have fixed the compilation issue caused by power_supply_get_battery_info with reference to 2515x driver in kernel 6.1.55. 

    ------------------------------

    static int bq2518x_set_precharge_current(struct bq2518x_device *bq2518x,
    int val)
    {
    int ret;
    unsigned int icharge_value;
    unsigned int times_of_icharge;

    icharge_value = bq2518x_get_const_charge_current(bq2518x);

    times_of_icharge = (val / icharge_value) * BQ2518X_PERCENTAGE;

    printk("icharge_value=%d,val=%d,times_of_icharge=%d,%s,%d\n", icharge_value, val, times_of_icharge, __func__,__LINE__);

    ------------------------------

    debug info : .icharge_value=770, val=50000, times_of_icharge=6400

    My dts configuration is as follow :

    ------------------------------
    bat: battery {
    compatible = "simple-battery";
    constant-charge-current-max-microamp = <1000000>;
    precharge-current-microamp = <50000>;
    constant-charge-voltage-max-microvolt = <4650000>;
    };

    &lpi2c1 {
    #address-cells = <1>;
    #size-cells = <0>;
    clock-frequency = <400000>;
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&pinctrl_lpi2c1>;
    pinctrl-1 = <&pinctrl_lpi2c1>;
    status = "okay";

    .........

    bq25180: charger@6a {
    compatible = "ti,bq25180";
    reg = <0x6a>;
    monitored-battery = <&bat>;
    input-current-limit-microamp = <1000000>;
    };
    };

    ------------------------------

    Is the value of times_of_icharge invalid because my DTS configuration is incorrect ?

    BTW,  Is the unit of current and voltage in DTS and driver micro or milli ? It seems the units aren't unified. such as

    #define BQ2518X_ICHG_MAX_UA 1000

    #define BQ2518X_VBAT_REG_MAX 4650000

    And now I have a serious problem: the battery cannot be charged. Is it related to the issue above? Could you help with some suggestions to solve the problem .

    current property :

    root@imx93-14x14-lpddr4x-evk:~# cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/uevent
    POWER_SUPPLY_NAME=bq2518x-mains
    POWER_SUPPLY_TYPE=Mains
    POWER_SUPPLY_ONLINE=1
    POWER_SUPPLY_STATUS=Not charging
    POWER_SUPPLY_HEALTH=Good
    POWER_SUPPLY_INPUT_CURRENT_LIMIT=1100000
    POWER_SUPPLY_MODEL_NAME=bq25180
    POWER_SUPPLY_MANUFACTURER=Texas Instruments
    POWER_SUPPLY_CONSTANT_CHARGE_VOLTAGE=3500000
    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT=370
    POWER_SUPPLY_PRECHARGE_CURRENT=18

  • Hello Jason,

    Thank you for the update. I'm glad to hear that the compilation issue has been resolved.

    Is the value of times_of_icharge invalid because my DTS configuration is incorrect ?

    From your DTS configuration, val = 50,000 is correctly set for 50mA precharge current. However, it seems that icharge_value = 770 is being set in mA, while your DTS defines the charge current as 1,000,000 uA.

    The issue is likely that icharge_value is not being set correctly in the driver, or there's some inconsistency between the DTS and the driver code. Although your DTS specifies 1,000,000 for the charge current, it looks like the driver is reading 770mA (or 770,000uA). This discrepancy is causing the times_of_icharge calculation to be incorrect.

    I would suggest double-checking the icharge_value being passed from the DTS to the driver. It seems like the driver might not be correctly interpreting the constant charge current value from the DTS. Once this value is correctly set, the times_of_icharge calculation should work as expected.

    BTW,  Is the unit of current and voltage in DTS and driver micro or milli ? It seems the units aren't unified. such as

    In the Linux kernel power supply class, the units for current and voltage should always be micro. Your DTS appears to be using microamps for current, which is correct.

    And now I have a serious problem: the battery cannot be charged. Is it related to the issue above? Could you help with some suggestions to solve the problem .

    This issue is likely related to the DTS configuration or the driver issue. Once we've resolved the driver issue, we can determine whether the battery charging problem is related to that or if it's something else entirely. It would be helpful if you could provide a register dump of the device when the battery is not charging. This will help provide more insight into what's happening at the hardware level. If the battery still isn't charging after resolving the driver issue, we can take further steps to investigate the issue.

    Best regards,

    Alec

  • Hi Alec,

        Thanks for your support !

        The dts configured value is different from the displayed value because driver will convert it and then display it (voltage is similar to current ). The code is as follows :

    ------------------------------

    static int bq2518x_get_const_charge_current(struct bq2518x_device *bq2518x)
    {.........
    ret = regmap_read(bq2518x->regmap, BQ2518X_ICHG_CTRL, &ichgctrl);
    if (ret)
    return ret;
    ichg_code = ichgctrl & BQ2518X_ICHG_MASK;
    if ((ichg_code + BQ2518X_ICHG_MIN_STEP) <= BQ2518X_ICHG_LIMIT)
    ichg = ichg_code + BQ2518X_ICHG_MIN_STEP;
    else
    ichg = BQ2518X_ICHG_MAX_START + ((ichg_code - BQ2518X_ICHG_MAX_COMPARE)
    * BQ2518X_ICHG_MAX_STEP);

    return ichg;

    }

    static int bq2518x_set_const_charge_current(struct bq2518x_device *bq2518x,
    int val)
    {.........
    if (val > BQ2518X_ICHG_MAX_UA || val < BQ2518X_ICHG_MIN_UA) {
    return -EINVAL;
    }

    bq2518x_set_charge_disable(bq2518x, 1);

    ret = regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL,
    BQ2518X_ICHG_MASK, val);
    if (ret)
    return ret;
    return bq2518x_set_charge_disable(bq2518x, 0);
    }

    ------------------------------

    My test :

    echo 1000000 > /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/constant_charge_current

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/constant_charge_current

                    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT=370

    echo 1000 > /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/constant_charge_current

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/constant_charge_current

                    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT=770

    i2cdump -y -f 0 0x6a

                    root@imx93-14x14-lpddr4x-evk:~# i2cdump -y -f 0 0x6a
                    No size specified (using byte-data access)
                            0 1    2   3   4   5   6   7   8   9   a   b   c  d e f    0123456789abcdef
                    00: 61 00 00 00 7f 20 56 87 07 11 60 00 c0 ff ff ff   !@B.@ V???`.?...
                    10: ff   ff   ff   ff    ff   ff   ff   ff   ff    ff  ff    ff   ff  ff ff ff ................
                    20: ff   ff   ff   ff    ff   ff   ff   ff   ff    ff  ff    ff   ff  ff ff ff ................

  • Hello Jason,

    Let's continue this discussion next week.

    Best regards,

    Alec

  • Hello Jason,

    I see what's happening here.

    The driver isn't converting a physical current value (in uA) into the proper register code. Instead, it writes the value directly into the register. Later, when reading back, the "get" function converts that 7-bit register code (which is in the range 0-127) back into a physical current using a specific formula.

    The driver's range check in the "set" function uses:

    • Minimum: BQ2518X_ICHG_MIN_UA = 5
    • Maximum: BQ2518X_ICHG_MAX_UA = 1000

    This means the driver is not intrepreting the written value as a physical current in uA; it treats the value as an ICHG code (a register value).

    When you read back the current, the process is as follows:

    1. The driver masks the ICHG_CTRL register to extract the "ichg_code."
    2. If (ichg_code + 5 ) ≤ 35, it computes:
      • current = ichg_code + 5
    3. Otherwise, it computes:
      • current = 40 + ((ichg_code - 31) * 10)

    For example:

    • If the register holds a code of 64:
      • Calculation: 40 + ((64 - 31) * 10) = 40 + (33 * 10) = 370
    • If the register holds a code of 104:
      • Calculation: 40 + ((104 - 31) * 10) = 40 + (73 * 10) = 770

    The register field uses a 7-bit mask (BQ2518X_ICHG_MASK = 0x7f), so only the lowest 7 bits of any written value are stored. In other words, any number larger than 127 is reduced to its remainder when divided by 128, which is mathematically equivalent to taking the number modulo 128.

    For example:

    • Writing 1,000,000:
      • 1,000,000 mod 128 = 64, so 64 is stored in the register.
    • Writing 1000:
      • 1000 mod 128 = 104, so 104 is stored in the register.

    That's why you see the values 64 and 104 in the register, which the "get" function converts to 370 and 770, respectively. Essentially, the value echoed isn't being converted from uA into a proper register code; it's just being reduced to a number between 0 and 127.

    Best regards,

    Alec

  • Hi Alec,

        Could you help to update the driver for the issues above ? Now, I'm writing to the register by command of i2cset, otherwise if it is configured by DTS, the register value will not match the datasheet after driver running.such as : 

    static int bq2518x_set_batt_reg(struct bq2518x_device *bq2518x, int val)
    {
    int vbat_reg_code;
    int ret;

    if (val > BQ2518X_VBAT_REG_MAX || val < BQ2518X_VBAT_REG_MIN)
    return -EINVAL;

    ret = regmap_read(bq2518x->regmap, BQ2518X_VBAT_CTRL, &vbat_reg_code);
    if (ret)
    return ret;

    vbat_reg_code = ((val - BQ2518X_VBAT_BASE_VOLT) / BQ2518X_VBAT_STEP_UV) &
    (vbat_reg_code & BQ2518X_PG_MODE);

    return regmap_write(bq2518x->regmap, BQ2518X_VBAT_CTRL, vbat_reg_code);
    }

  • Hello Jason,

    I have updated the set function to properly convert the physical current value (in mA) into the register code (ichg_code) expected by the hardware. The updated naming now reflects that the values are in mA. Below is the new set function code:

    #define BQ2518X_ICHG_MIN_MA 		5
    #define BQ2518X_ICHG_MAX_MA 		1000
    #define BQ2518X_ICHG_MIN_STEP		5
    #define BQ2518X_ICHG_LIMIT		    35
    #define BQ2518X_ICHG_MAX_START	    40
    #define BQ2518X_ICHG_MAX_COMPARE	31
    #define BQ2518X_ICHG_MAX_STEP		10
    
    
    static int bq2518x_set_const_charge_current(struct bq2518x_device *bq2518x, int val)
    {
    	int ret;
    	int ichg_code;
    
    	if (val < BQ2518X_ICHG_MIN_MA || val > BQ2518X_ICHG_MAX_MA)
    		return -EINVAL;
    
    	if (val <= BQ2518X_ICHG_LIMIT) {
    		ichg_code = val - BQ2518X_ICHG_MIN_STEP;
    	} else {
    		ichg_code = ((val - BQ2518X_ICHG_MAX_START) / BQ2518X_ICHG_MAX_STEP) + BQ2518X_ICHG_MAX_COMPARE;
    	}
    
    	ichg_code &= BQ2518X_ICHG_MASK;
    
    	ret = bq2518x_set_charge_disable(bq2518x, 1);
    	if (ret)
    		return ret;
    
    	ret = regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL, BQ2518X_ICHG_MASK, ichg_code);
    	if (ret)
    		return ret;
    
    	return bq2518x_set_charge_disable(bq2518x, 0);
    }

    Please test this updated code and let me know if it resolves the issue.

    Best regards,

    Alec

  • Hello Alec,

    1. I have updated the function of bq2518x_set_const_charge_current, but the final register value is not what I want. Could you tell me how to config dts to ensure that the register values are as follows:

    The current configuration of my DTS is as follows :
    bat: battery {
    compatible = "simple-battery";
    constant-charge-current-max-microamp = <1000>;
    precharge-current-microamp = <5000>;
    constant-charge-voltage-max-microvolt = <4200000>;
    };

    ...............
    bq25180: charger@6a {
    compatible = "ti,bq25180";
    reg = <0x6a>;
    monitored-battery = <&bat>;
    input-current-limit-microamp = <1000000>;
    };

    2.There seems to be something wrong with the function of "bq2518x_set_charge_disable" which is also used in the code you provided above.

    static int bq2518x_set_charge_disable(struct bq2518x_device *bq2518x, int val)
    {

    if (val)
    val = 0;
    else
    val = BQ2518X_CHG_DIS;

    return regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL,
    BQ2518X_CHG_DIS_MASK, val);
    }

    ----------------------------------------------

    ret = bq2518x_set_charge_disable(bq2518x, 1);
    ret = regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL, BQ2518X_ICHG_MASK, ichg_code);
    return bq2518x_set_charge_disable(bq2518x, 0);

    ----------------------------------------------

    bq2518x_set_charge_disable(bq2518x, 1)  sets 0 to bit(7) of register BQ2518X_ICHG_CTRL. This means Battery Charging Enabled.

    bq2518x_set_charge_disable(bq2518x, 0) means Battery Charging Disabled. Doesn't the logic seem right ?

    3. I updated default value, and comment "bq2518x_hw_init", but the register value was still modifed, such as BQ2518X_ICHG_CTRL, BQ2518X_CHARGERCTRL1, BQ2518X_IC_CTRL and BQ2518X_IC_CTRL. Could you tell me where else changed the value of  register except "bq2518x_hw_init" ?

  • Hello Jason,

    Even if you comment out bq2518x_hw_init(), the regmap default cache still applies. The driver has an array of "default" register values (bq25180_reg_defaults[]), and regmap writes these to the hardware on probe — regardless of whether bq2518x_hw_init() is called. That explains why you're still seeing the registers change from their true silicon defaults.

    With the changes below, you should see the correct register values for your configuration:

    1. Charge disable

    By default, bit 7 (CHG_DIS) = 1 means "disable," but the driver's logic was inverted.

    static int bq2518x_set_charge_disable(struct bq2518x_device *bq2518x, int val)
    {
        if (val)
            val = BQ2518X_CHG_DIS;
        else
            val = 0;
    
        return regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL, BQ2518X_CHG_DIS_MASK, val);
    }

    This inverts the logic so passing 1 truly disables charging.

    2. Constant charge current

    Before, the code wrote val directly, assuming it was the register code. Now, we divide by 1000 to convert the DTS value (uA) into mA, then compute the register code.

    static int bq2518x_set_const_charge_current(struct bq2518x_device *bq2518x, int val)
    {
        int ret;
        val /= 1000;
        
        ...
        
        return ret;
    }

    3. Precharge current

    Before, the function didn't convert uA to mA, and the fraction formula was reversed.

    static int bq2518x_set_precharge_current(struct bq2518x_device *bq2518x, int val)
    {
        int ret;
        unsigned int icharge_value;
        unsigned int times_of_icharge;
        
        val /= 1000;
        
        icharge_value = bq2518x_get_const_charge_current(bq2518x);
        
        times_of_icharge = (val / icharge_value) * BQ2518X_PERCENTAGE;
        
        ...
        
        return ret;
    }

    DTS Example

    bat: battery {
        compatible = "simple-battery";
        constant-charge-voltage-max-microvolt = <4200000>;
        constant-charge-current-max-microamp = <1000000>;
        precharge-current-microamp = <100000>;
    };
    
    bq25180: charger@6b {
        compatible = "ti,bq25180";
        reg = <0x6a>;
        monitored-battery = <&battery>;
        input-current-limit-microamp = <1000000>;
    };

    Let me know if this resolves your issue or if you have any further questions.

    Best regards,

    Alec

  • Hello Alec,

    With the changes above, register values are shown below

    However, the values I expect are shown below:

    Please help to check whether the dts configuration is correct. How should I configure to get the register values I want ?

    bat: battery {
    compatible = "simple-battery";
    constant-charge-voltage-max-microvolt = <4200000>;
    constant-charge-current-max-microamp = <1000000>;
    precharge-current-microamp = <100000>;
    };

    bq25180: charger@6b {
    compatible = "ti,bq25180";
    reg = <0x6a>;
    monitored-battery = <&battery>;
    input-current-limit-microamp = <1000000>;
    };

  • Hello Jason,

    Thanks for your patience as we work through this.

    It seems that Reg0x4, Reg0x6, Reg0x7, and Reg0x8 are not being set correctly, meaning that ICHG, BATOCP, Watchdog, and ILIM aren't being configured as expected.

    Fix Reg0x8 (ILIM bits):

    1. To get the expected register value for Reg0x8, we need to change the DTS to have "input-current-limit-microamp=<1100000>;" since 1100mA is the highest ILIM setting on the device.
    2. Additionally, we need to use regmap_update_bits() instead of regmap_write() in "bq2518x_set_ilim_lvl()" so only the ILIM bits ([2:0]) are changed in the TMR_ILM register. The function should look like this:

    static int bq2518x_set_ilim_lvl(struct bq2518x_device *bq2518x, int val)
    {
    	int i = 0;
    	unsigned int array_size = ARRAY_SIZE(bq2518x_ilim_lvl_values);
    
    	for (i = array_size - 1; i > 0; i--) {
    		if (val >= bq2518x_ilim_lvl_values[i])
    			break;
    	}
    	return regmap_update_bits(bq2518x->regmap, BQ2518X_TMR_ILIM, BQ2518X_ILIM_MASK, i);
    }

    Fix Reg0x4 (ICHG bits):

    1. This register is set to its default value, which seems to indicate that the logic in "bq2518x_hw_init()" is setting ICHG to its default value. In bq2518x_hw_init(), I suggest replacing "ret = power_supply_get_battery_info(bq2518x->mains, &bat_info);" with:

    ret = power_supply_get_battery_info(bq2518x->battery, &bat_info);

    I believe this will cause the driver to read the battery properties (like constant-charge-current-max-microamp) from the battery node (bq2518x->battery), where they're actually defined. Let me know if this fixes Reg0x4.

    Fix Reg0x7 (Watchdog bits):

    1. I noticed from your previous reply that you have {BQ2518X_IC_CTRL, 0x87} in reg_default. It seems possible that Reg0x7 is being set to 0x87, then the logic of bq2518x_disable_watchdog_timers() is flipping the watchdog bits ([1:0]) back to 00, which is causing Reg0x7 = 0x84. I recommend setting {BQ2518X_IC_CTRL, 0x84} to see if this causes Reg0x7 to set properly.

    Fix Reg0x6 (BATOCP bits):

    There are a few options to configure the BATOCP bits in Reg0x6. However, the easiest option is likely to add the following function to the driver:

    #define BQ2518X_BATOCP_MASK     0xc0
    #define BQ2518X_BATOCP_1500MA   BIT(7)
    
    static int bq2518x_set_batocp_1500ma(struct bq2518x_device *bq2518x)
    {
        return regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL1, BQ2518X_BATOCP_MASK, BQ2518X_BATOCP_1500MA);
    
    }
    
    

    Then, we can call this function in "bq2518x_hw_init()":

    static int bq2518x_hw_init(struct bq2518x_device *bq2518x)
    {
        int ret;
        
        /* Disable watchdog timers */
        /* Set ILIM */
        
        /* ... */
        
        ret = bq2518x_set_batocp_1500ma(bq2518x);
        if (ret)
            return ret;
        
        /* ... */    
    }

    Let me know if these changes configure the registers as expected, and we can work from there.

    Best regards,

    Alec

  • Hi Alec,

    Thanks for your support !

    With the changes you suggested , register values are shown below:

    only battery power

    It seems that Reg0x4, Reg0x6, Reg0x7, and Reg0x8 are being set correctly, but it seems that Reg0x3 is not correct .

    My current dts configuration:

    bat: battery {
    compatible = "simple-battery";
    constant-charge-current-max-microamp = <1000000>;
    precharge-current-microamp = <100000>;
    constant-charge-voltage-max-microvolt = <4200000>;
    };

    bq25180: charger@6a {
    compatible = "ti,bq25180";
    reg = <0x6a>;
    monitored-battery = <&bat>;
    input-current-limit-microamp = <1100000>;
    };

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/uevent

    POWER_SUPPLY_NAME=bq2518x-mains
    POWER_SUPPLY_TYPE=Mains
    POWER_SUPPLY_ONLINE=0
    POWER_SUPPLY_STATUS=Discharging
    POWER_SUPPLY_HEALTH=Good
    POWER_SUPPLY_INPUT_CURRENT_LIMIT=1100000
    POWER_SUPPLY_MODEL_NAME=bq25180
    POWER_SUPPLY_MANUFACTURER=Texas Instruments
    POWER_SUPPLY_CONSTANT_CHARGE_VOLTAGE=3500000
    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT=370
    POWER_SUPPLY_PRECHARGE_CURRENT=18

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-battery/uevent

    POWER_SUPPLY_NAME=bq2518x-battery
    POWER_SUPPLY_TYPE=Battery
    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT_MAX=1000000
    POWER_SUPPLY_CONSTANT_CHARGE_VOLTAGE_MAX=4200000

    external power + battery

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/uevent

    POWER_SUPPLY_NAME=bq2518x-mains
    POWER_SUPPLY_TYPE=Mains
    POWER_SUPPLY_ONLINE=1
    POWER_SUPPLY_STATUS=Not charging
    POWER_SUPPLY_HEALTH=Good
    POWER_SUPPLY_INPUT_CURRENT_LIMIT=1100000
    POWER_SUPPLY_MODEL_NAME=bq25180
    POWER_SUPPLY_MANUFACTURER=Texas Instruments
    POWER_SUPPLY_CONSTANT_CHARGE_VOLTAGE=3500000
    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT=370
    POWER_SUPPLY_PRECHARGE_CURRENT=18

    only external power

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/uevent

    POWER_SUPPLY_NAME=bq2518x-mains
    POWER_SUPPLY_TYPE=Mains
    POWER_SUPPLY_ONLINE=1
    POWER_SUPPLY_STATUS=Charging
    POWER_SUPPLY_HEALTH=Good
    POWER_SUPPLY_INPUT_CURRENT_LIMIT=1100000
    POWER_SUPPLY_MODEL_NAME=bq25180
    POWER_SUPPLY_MANUFACTURER=Texas Instruments
    POWER_SUPPLY_CONSTANT_CHARGE_VOLTAGE=3500000
    POWER_SUPPLY_CONSTANT_CHARGE_CURRENT=370
    POWER_SUPPLY_PRECHARGE_CURRENT=18

    Finally as described above,is POWER_SUPPLY_STATUS incorrect ?  By the way, how do I check the battery's remaining power and the conditions that trigger charging ?

  • Hello Jason,

    I'm happy to hear that those registers are now being set correctly with the recent fixes.

    Regarding Reg0x3, the current logic incorrectly clears all the bits, which is why we are seeing 0x00. Here's an updated function that preserves bit 7 and sets the lower bits correctly:

    static int bq2518x_set_batt_reg(struct bq2518x_device *bq2518x, int val)
    {
        int vbat_reg_code;
        int ret;
    
        if (val > BQ2518X_VBAT_REG_MAX || val < BQ2518X_VBAT_REG_MIN)
        return -EINVAL;
    
        ret = regmap_read(bq2518x->, BQ2518X_VBAT_CTRL, &vbat_reg_code);
        if (ret)
            return ret;
    
        vbat_reg_code = (vbat_reg_code & BQ2518X_PG_MODE) | (((val - BQ2518X_VBAT_BASE_VOLT) / BQ2518X_VBAT_STEP_UV) & ~BQ2518X_PG_MODE);
            
        return regmap_write(bq2518x->regmap, BQ2518X_VBAT_CTRL, vbat_reg_code);
    }

    With this change, Reg0x3 should reflect the intended battery regulation voltage rather than 0x00.

    As for POWER_SUPPLY_STATUS, it appears correct in each case. When no battery is present, the charger might report "constant voltage" or "charge done," which explains why POWER_SUPPLY_STATUS=Charging with only the input source connected. The BQ25180 can indicate charging states and faults, but it doesn't measure the battery's state of charge. For exact battery percentage or remaining capacity, you'd need a separate fuel gauge IC.

    Let me know if this update resolves Reg0x3.

    Best regards,

    Alec

  • Hi Alec,

    Thanks for your support !

    I'm very sorry to tell you that I forgot to delete the script which automatically configured the registers, so the result last time was the role of the script rather than the role of modifying the code. I deleted the automatic script and reconfirmed the register values:

    With the changes, Reg0x7 has been configured as expected. The values of other registers have changed but have not yet reached the expected value.

  • Hello Jason,

    No problem — thanks for the update. It looks like Reg0x4, Reg0x5, Reg0x6, and Reg0x8 still aren't at the desired values when you disable your script.

    Reg0x5:

    Try changing the precharge current mask to:

    #define BQ2518X_IPRECHG_MASK 0x70

    I believe this mask should only cause the IPRECHG and ITERM bits to be updated.

    Reg0x4:

    Ensure that you are dividing by 1000 in bq2518x_set_const_charge_current() (i.e., val /= 1000;), so 1000000uA in DTS becomes 1000mA.

    Reg0x6:

    Ensure that you are using regmap_update_bits() with mask 0xC0 to set the BATOCP bits.

    Reg0x8:

    Ensure that your DTS sets input-current-limit-microamp = <1100000> for ILIM = 1.1A. Also, ensure that bq2518x_set_ilim_lvl() uses regmap_update_bits() to only change bits [2:0].

    If you can share your updated functions (e.g., bq2518x_set_const_charge_current(), bq2518x_set_precharge_current(), etc.), I can quickly confirm the logic. Let me know if you have any questions — I'm here to help.

    Best regards,

    Alec

  • Hello Alec,

    Thanks for your patient help during this time ! I'm sorry to tell you that I need to deal with an urgent matter temporarily, so I have to suspend BQ25180 driver debugging and I'll be back as soon as I've taken care of the urgent matter.

    According to the modifications you provided above,  driver initialized failed. The debug info is as follows :

          wnc_debug100......icharge_value=1000,val=100,times_of_icharge=0,bq2518x_set_precharge_current,409

          bq2518x-charger 0-006a: Cannot initialize the chip

          bq2518x-charger: probe of 0-006a failed with error -22

    I think that it may be interrupted at bq2518x_set_precharge_current. I attach the file of bq2518x_charger.c below, please help to check it. 

    Thanks.

    // SPDX-License-Identifier: GPL-2.0
    // bq2518x Battery Charger Driver
    // Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com/
    
    #include <linux/err.h>
    #include <linux/i2c.h>
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/gpio/consumer.h>
    #include <linux/power_supply.h>
    #include <linux/regmap.h>
    #include <linux/types.h>
    
    #define BQ2518X_MANUFACTURER "Texas Instruments"
    
    #define BQ2518X_STAT0		0x00
    #define BQ2518X_STAT1		0x01
    #define BQ2518X_FLAG0		0x02
    #define BQ2518X_VBAT_CTRL	0x03
    #define BQ2518X_ICHG_CTRL	0x04
    #define BQ2518X_CHARGERCTRL0	0x05
    #define BQ2518X_CHARGERCTRL1	0x06
    #define BQ2518X_IC_CTRL		0x07
    #define BQ2518X_TMR_ILIM		0x08
    #define BQ2518X_SHIP_RST		0x09
    #define BQ2518X_SYS_REG			0x0A
    #define BQ2518X_TS_CONTROL		0x0B
    #define BQ2518X_MASK_ID			0x0C
    
    #define BQ2518X_DEFAULT_ICHG_UA		10000
    #define BQ25180_DEFAULT_ILIM_UA		100000
    #define BQ2518X_DEFAULT_VBAT_REG_UV	4200000
    #define BQ2518X_DEFAULT_IPRECHARGE_UA	2500
    
    #define BQ2518X_DIVISOR				65536
    #define BQ2518X_VBAT_BASE_VOLT			3500000
    #define BQ2518X_VBAT_REG_MAX			4650000
    #define BQ2518X_VBAT_REG_MIN			3600000
    #define BQ2518X_VBAT_STEP_UV			10000
    #define BQ2518X_UV_FACTOR			1000000
    #define BQ2518X_VBAT_MULTIPLIER			6
    #define BQ2518X_ICHG_DIVISOR			52429
    #define BQ2518X_ICHG_CURR_STEP_THRESH_UA	318750
    #define BQ2518X_ICHG_MIN_UA			5
    #define BQ2518X_ICHG_MAX_UA			1000
    //jason
    #define BQ2518X_ICHG_MIN_MA 			5
    #define BQ2518X_ICHG_MAX_MA 			1000
    #define BQ2518X_ICHG_RNG_1B0_UA			1250
    #define BQ2518X_ICHG_RNG_1B1_UA			2500
    #define BQ2518X_VLOWV_SEL_1B0_UV		3000000
    #define BQ2518X_VLOWV_SEL_1B1_UV		2800000
    #define BQ2518X_PRECHRG_ICHRG_RNGE_1875_UA	18750
    #define BQ2518X_PRECHRG_ICHRG_RNGE_3750_UA	37500
    #define BQ2518X_TWAKE2_MIN_US			1700000
    #define BQ2518X_TWAKE2_MAX_US			2300000
    
    #define BQ2518X_ILIM_150MA	0x2
    #define BQ2518X_ILIM_MASK	0x7
    #define BQ2518X_ILIM_MIN	50000
    #define BQ2518X_ILIM_MAX	600000
    #define BQ2518X_HEALTH_MASK	0xff
    #define BQ2518X_ICHGRNG_MASK	0x80
    #define BQ2518X_STAT0_MASK	0xf0
    #define BQ2518X_STAT1_MASK	0x1f
    #define BQ2518X_PRECHARGE_MASK	0x1f
    
    #define BQ2518X_CHG_STAT_STEP	4
    #define BQ2518X_CHG_STAT		(BIT(5) | BIT(6))
    
    #define BQ2518X_CHG_STAT_NOT_CHARGING				0x00
    #define BQ2518X_CHG_STAT_CONSTANT_CURRENT_CHARGING	0x01
    #define BQ2518X_CHG_STAT_CONSTANT_VOLTAGE_CHARGING	0x02
    #define BQ2518X_CHG_STAT_CHARGE_DONE				0x03
    
    #define BQ2518X_PG_MODE				BIT(7)
    #define BQ2518X_TS_COOL_STAT		BIT(2)
    #define BQ2518X_TS_WARM_STAT		BIT(3)
    #define BQ2518X_TS_COLD_STAT		(BIT(4) | BIT(5))
    #define BQ2518X_TS_HOT_STAT			(BIT(6) | BIT(7))
    #define BQ2518X_SAFETY_TIMER_EXP	BIT(2)
    
    
    
    #define BQ2518X_EN_VBAT_READ		BIT(3)
    #define BQ2518X_EN_ICHG_READ		BIT(5)
    
    #define BQ2518X_VIN_GOOD		BIT(0)
    #define BQ2518X_CHRG_DONE		BIT(5)
    #define BQ2518X_CV_CHRG_MODE		BIT(6)
    
    #define BQ2518X_VIN_OVP_STAT	BIT(7)
    
    #define BQ2518X_WATCHDOG_DISABLE	(BIT(0) | BIT(1))
    
    #define BQ2518X_ICHARGE_RANGE		BIT(7)
    
    #define BQ2518X_VLOWV_SEL		BIT(5)
    
    #define BQ2518X_CHG_DIS			BIT(7)
    
    #define BQ2518X_WATCHDOG_15S_ENABLE		BIT(1)
    
    #define BQ2518X_CHG_DIS BIT(7)
    #define BQ2518X_ICHG_MASK 0x7f
    
    #define BQ2518X_TIME_OF_ICHARGE01 5
    #define BQ2518X_TIME_OF_ICHARGE02 10
    #define BQ2518X_TIME_OF_ICHARGE03 20
    #define BQ2518X_TIME_OF_ICHARGE04 40
    
    #define BQ2518X_TIME_OF_TERM_MASK 0x40
    #define BQ2518X_TIME_OF_2X_TERM 0
    #define BQ2518X_TIME_OF_1X_TERM BIT(6)
    #define BQ2518X_TIME_OF_2X_TERM_VALUE 2
    #define BQ2518X_TIME_OF_1X_TERM_VALUE 1
    
    #define BQ2518X_TERMINATION_CURRENT_MASK 0x30
    #define BQ2518X_TERMINATION_CURRENT_DISABLE		0
    #define BQ2518X_TERMINATION_CURRENT_PERCENT05	BIT(4)
    #define BQ2518X_TERMINATION_CURRENT_PERCENT10	BIT(5)
    #define BQ2518X_TERMINATION_CURRENT_PERCENT20	(BIT(4) | BIT(5))
    
    //#define BQ2518X_IPRECHG_MASK 0x8f
    #define BQ2518X_IPRECHG_MASK 0x70
    
    #define BQ2518X_ICHG_LIMIT	35
    #define BQ2518X_ICHG_MIN_STEP 5
    
    #define BQ2518X_ICHG_MAX_STEP 10
    #define BQ2518X_ICHG_MAX_START 40
    #define BQ2518X_ICHG_MAX_COMPARE 31
    
    #define BQ2518X_CHG_DIS_MASK 0x80
    
    static const int bq2518x_ilim_lvl_values[] = {
    	50000, 100000, 200000, 300000, 400000, 500000, 700000, 1100000
    };
    
    /**
     * struct bq2518x_init_data -
     * @ilim: input current limit
     * @ichg: fast charge current
     * @vbatreg: battery regulation voltage
     * @iprechg: precharge current
     */
    struct bq2518x_init_data {
    	int ilim;
    	int ichg;
    	int vbatreg;
    	int iprechg;
    };
    
    enum bq2518x_id {
    	BQ25180,
    };
    
    /**
     * struct bq2518x_device -
     * @mains: mains properties
     * @battery: battery properties
     * @regmap: register map structure
     * @dev: device structure
     *
     *
     * @model_name: string value describing device model
     * @device_id: value of device_id
     * @mains_online: boolean value indicating power supply online
     *
     * @init_data: charger initialization data structure
     */
    struct bq2518x_device {
    	struct power_supply *mains;
    	struct power_supply *battery;
    	struct regmap *regmap;
    	struct device *dev;
    
    	char model_name[I2C_NAME_SIZE];
    	int device_id;
    	bool mains_online;
    
    	struct bq2518x_init_data init_data;
    };
    
    static const struct reg_default bq25180_reg_defaults[] = {
    	{BQ2518X_STAT0, 0x0},
    	{BQ2518X_STAT1, 0x0},
    	{BQ2518X_VBAT_CTRL, 0x46},
    	{BQ2518X_ICHG_CTRL, 0x7F},
    	{BQ2518X_CHARGERCTRL0, 0x2C},
    	{BQ2518X_CHARGERCTRL1, 0x96},
    	{BQ2518X_IC_CTRL, 0x84},
    	{BQ2518X_TMR_ILIM, 0x4F},
    	{BQ2518X_SHIP_RST, 0x11},
    	{BQ2518X_SYS_REG, 0x40},
    	{BQ2518X_TS_CONTROL, 0x0},
    	{BQ2518X_MASK_ID, 0xC0},
    
    };
    
    static int bq2518x_wake_up(struct bq2518x_device *bq2518x)
    {
    	int ret;
    	int val;
    
    	/* Read the STAT register if we can read it then the device is out
    	 * of ship mode
    	 */
    	ret = regmap_read(bq2518x->regmap, BQ2518X_STAT0, &val);
    	if (ret)
    		return ret;
    
    
    	return 0;
    }
    
    static int bq2518x_update_ps_status(struct bq2518x_device *bq2518x)
    {
    	bool dc = false;
    	unsigned int val;
    	int ret;
    
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_STAT0, &val);
    	if (ret)
    		return ret;
    
    
    	dc = val & BQ2518X_VIN_GOOD;
    
    	ret = bq2518x->mains_online != dc;
    
    	bq2518x->mains_online = dc;
    
    	return ret;
    }
    
    static int bq2518x_disable_watchdog_timers(struct bq2518x_device *bq2518x)
    {
    	int ret;
    
    	ret = regmap_update_bits(bq2518x->regmap, BQ2518X_IC_CTRL,
    			BQ2518X_WATCHDOG_DISABLE, BQ2518X_WATCHDOG_DISABLE);
    	if (ret)
    		return ret;
    
    	return regmap_update_bits(bq2518x->regmap, BQ2518X_SYS_REG,
    						BQ2518X_WATCHDOG_15S_ENABLE, 0);
    }
    
    static bool bq2518x_get_charge_disable(struct bq2518x_device *bq2518x)
    {
    	int ret;
    	int ichgctrl;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_ICHG_CTRL, &ichgctrl);
    	if (ret)
    		return ret;
    
    	if (ichgctrl & BQ2518X_CHG_DIS)
    		return false;
    
    	return true;
    }
    
    static int bq2518x_set_charge_disable(struct bq2518x_device *bq2518x, int val)
    {
    
    	if (val)
    //		val = 0;	//jason
    		val = BQ2518X_CHG_DIS;
    	else
    //		val = BQ2518X_CHG_DIS;	//jason
    		val = 0;
    
    	return regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL,
    					BQ2518X_CHG_DIS_MASK, val);
    }
    
    static int bq2518x_get_const_charge_current(struct bq2518x_device *bq2518x)
    {
    	int ret;
    	unsigned int ichgctrl;
    	unsigned int ichg_code;
    	unsigned int ichg;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_ICHG_CTRL, &ichgctrl);
    	if (ret)
    		return ret;
    	ichg_code = ichgctrl & BQ2518X_ICHG_MASK;
    	if ((ichg_code + BQ2518X_ICHG_MIN_STEP) <= BQ2518X_ICHG_LIMIT)
    		ichg = ichg_code + BQ2518X_ICHG_MIN_STEP;
    	else
    		ichg = BQ2518X_ICHG_MAX_START + ((ichg_code - BQ2518X_ICHG_MAX_COMPARE)
    				* BQ2518X_ICHG_MAX_STEP);
    
    	return ichg;
    
    }
    
    //jason
    #if 0
    static int bq2518x_set_const_charge_current(struct bq2518x_device *bq2518x,
    								int val)
    {
    	int ret;
    
    
    	if (val > BQ2518X_ICHG_MAX_UA || val < BQ2518X_ICHG_MIN_UA)
    		return -EINVAL;
    
    	bq2518x_set_charge_disable(bq2518x, 1);
    
    	ret = regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL,
    					BQ2518X_ICHG_MASK, val);
    	if (ret)
    		return ret;
    
    
    	return bq2518x_set_charge_disable(bq2518x, 0);
    }
    #else
    static int bq2518x_set_const_charge_current(struct bq2518x_device *bq2518x, int val)
    {
    	int ret;
    	int ichg_code;
    	val /= 1000;
    
    	if (val < BQ2518X_ICHG_MIN_MA || val > BQ2518X_ICHG_MAX_MA)
    		return -EINVAL;
    
    	if (val <= BQ2518X_ICHG_LIMIT) {
    		ichg_code = val - BQ2518X_ICHG_MIN_STEP;
    	} else {
    		ichg_code = ((val - BQ2518X_ICHG_MAX_START) / BQ2518X_ICHG_MAX_STEP) + BQ2518X_ICHG_MAX_COMPARE;
    	}
    
    	ichg_code &= BQ2518X_ICHG_MASK;
    
    	ret = bq2518x_set_charge_disable(bq2518x, 1);
    	if (ret)
    		return ret;
    
    	ret = regmap_update_bits(bq2518x->regmap, BQ2518X_ICHG_CTRL, BQ2518X_ICHG_MASK, ichg_code);
    	if (ret)
    		return ret;
    
    	return bq2518x_set_charge_disable(bq2518x, 0);
    }
    #endif
    
    static int bq2518x_get_precharge_current(struct bq2518x_device *bq2518x)
    {
    	int ret;
    	int icharge_value;
    	unsigned int chargectrl0;
    	unsigned int persent;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_CHARGERCTRL0, &chargectrl0);
    	if (ret)
    		return ret;
    
    	icharge_value = bq2518x_get_const_charge_current(bq2518x);
    
    	if (chargectrl0 & BQ2518X_TIME_OF_TERM_MASK)
    		persent = BQ2518X_TIME_OF_1X_TERM_VALUE;
    	else
    		persent = BQ2518X_TIME_OF_2X_TERM_VALUE;
    
    	switch (chargectrl0 & BQ2518X_TERMINATION_CURRENT_MASK) {
    	case BQ2518X_TERMINATION_CURRENT_DISABLE:
    		icharge_value = 0;
    		break;
    
    	case BQ2518X_TERMINATION_CURRENT_PERCENT05:
    		persent *= BQ2518X_TIME_OF_ICHARGE03;
    		break;
    
    	case BQ2518X_TERMINATION_CURRENT_PERCENT10:
    		persent *= BQ2518X_TIME_OF_ICHARGE02;
    		break;
    
    	case BQ2518X_TERMINATION_CURRENT_PERCENT20:
    		persent *= BQ2518X_TIME_OF_ICHARGE01;
    		break;
    
    	default:
    		icharge_value = 0;
    		break;
    	}
    
    	return (icharge_value / persent);
    }
    #define BQ2518X_PERCENTAGE 100
    static int bq2518x_set_precharge_current(struct bq2518x_device *bq2518x,
    					int val)
    {
    	int ret;
    	unsigned int icharge_value;
    	unsigned int times_of_icharge;
    	val /= 1000;
    
    	icharge_value = bq2518x_get_const_charge_current(bq2518x);
    
    //	times_of_icharge = (icharge_value / val) * BQ2518X_PERCENTAGE;
    	times_of_icharge = (val / icharge_value) * BQ2518X_PERCENTAGE;//jason
    
    	printk("wnc_debug100......icharge_value=%d,val=%d,times_of_icharge=%d,%s,%d\n", icharge_value, val, times_of_icharge, __func__,__LINE__);
    	switch (times_of_icharge) {
    	case BQ2518X_TIME_OF_ICHARGE01:
    		ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    				BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_1X_TERM |
    				BQ2518X_TERMINATION_CURRENT_PERCENT05));
    		break;
    
    	case BQ2518X_TIME_OF_ICHARGE02:
    		ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    				BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_1X_TERM |
    				BQ2518X_TERMINATION_CURRENT_PERCENT10));
    		break;
    
    	case BQ2518X_TIME_OF_ICHARGE03:
    		ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    				BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_1X_TERM |
    				BQ2518X_TERMINATION_CURRENT_PERCENT20));
    		break;
    
    	case BQ2518X_TIME_OF_ICHARGE04:
    		ret = regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL0,
    				BQ2518X_IPRECHG_MASK, (BQ2518X_TIME_OF_2X_TERM |
    				BQ2518X_TERMINATION_CURRENT_PERCENT20));
    		break;
    
    	default:
    		ret = -EINVAL;
    		break;
    	}
    	return ret;
    }
    
    static int bq2518x_charging_status(struct bq2518x_device *bq2518x,
    				   union power_supply_propval *val)
    {
    	unsigned int status;
    	int ret;
    
    	if (!bq2518x->mains_online) {
    		val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
    		return 0;
    	}
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_STAT0, &status);
    	if (ret)
    		return ret;
    
    	if (status & BQ2518X_STAT0_MASK) {
    		switch ((status & BQ2518X_CHG_STAT) >> BQ2518X_CHG_STAT_STEP) {
    		case BQ2518X_CHG_STAT_NOT_CHARGING:
    			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
    			break;
    
    		case BQ2518X_CHG_STAT_CONSTANT_CURRENT_CHARGING:
    			val->intval = POWER_SUPPLY_STATUS_CHARGING;
    			break;
    
    		case BQ2518X_CHG_STAT_CONSTANT_VOLTAGE_CHARGING:
    			val->intval = POWER_SUPPLY_STATUS_CHARGING;
    			break;
    
    		case BQ2518X_CHG_STAT_CHARGE_DONE:
    			val->intval = POWER_SUPPLY_STATUS_FULL;
    			break;
    
    		default:
    			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
    			break;
    	}
    	}
    	return 0;
    }
    
    static int bq2518x_get_batt_reg(struct bq2518x_device *bq2518x)
    {
    	int vbat_reg_code;
    	int ret;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_VBAT_CTRL, &vbat_reg_code);
    	if (ret)
    		return ret;
    
    	return (BQ2518X_VBAT_BASE_VOLT + ((vbat_reg_code&(~BQ2518X_PG_MODE)) *
    			BQ2518X_VBAT_STEP_UV));
    }
    
    static int bq2518x_set_batt_reg(struct bq2518x_device *bq2518x, int val)
    {
    	int vbat_reg_code;
    	int ret;
    
    	if (val > BQ2518X_VBAT_REG_MAX || val < BQ2518X_VBAT_REG_MIN)
    		return -EINVAL;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_VBAT_CTRL, &vbat_reg_code);
    	if (ret)
    		return ret;
    
    //	vbat_reg_code = ((val - BQ2518X_VBAT_BASE_VOLT) / BQ2518X_VBAT_STEP_UV) &
    //					(vbat_reg_code & BQ2518X_PG_MODE);
    
    	vbat_reg_code = (vbat_reg_code & BQ2518X_PG_MODE) | (((val - BQ2518X_VBAT_BASE_VOLT) / BQ2518X_VBAT_STEP_UV) & ~BQ2518X_PG_MODE);//jason
    	return regmap_write(bq2518x->regmap, BQ2518X_VBAT_CTRL, vbat_reg_code);
    }
    
    static int bq2518x_get_ilim_lvl(struct bq2518x_device *bq2518x)
    {
    	int ret;
    	int ilimctrl;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_TMR_ILIM, &ilimctrl);
    	if (ret)
    		return ret;
    
    	return bq2518x_ilim_lvl_values[ilimctrl & BQ2518X_ILIM_MASK];
    }
    
    static int bq2518x_set_ilim_lvl(struct bq2518x_device *bq2518x, int val)
    {
    	int i = 0;
    	unsigned int array_size = ARRAY_SIZE(bq2518x_ilim_lvl_values);
    
    	for (i = array_size - 1; i > 0; i--) {
    		if (val >= bq2518x_ilim_lvl_values[i])
    			break;
    	}
    	//return regmap_write(bq2518x->regmap, BQ2518X_TMR_ILIM, i);
    	return regmap_update_bits(bq2518x->regmap, BQ2518X_TMR_ILIM, BQ2518X_ILIM_MASK, i);//jason
    }
    
    static int bq2518x_power_supply_property_is_writeable(struct power_supply *psy,
    					enum power_supply_property prop)
    {
    	switch (prop) {
    	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
    	case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
    		return true;
    	default:
    		return false;
    	}
    }
    
    static int bq2518x_charger_get_health(struct bq2518x_device *bq2518x,
    				      union power_supply_propval *val)
    {
    	int health = POWER_SUPPLY_HEALTH_GOOD;
    	int ret;
    	unsigned int stat1;
    	unsigned int ts_control;
    
    	if (!bq2518x->mains_online)
    		bq2518x_wake_up(bq2518x);
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_TS_CONTROL, &ts_control);
    	if (ret)
    		return ret;
    
    	ret = regmap_read(bq2518x->regmap, BQ2518X_STAT1, &stat1);
    	if (ret)
    		return ret;
    
    	if (ts_control & BQ2518X_HEALTH_MASK) {
    		switch (ts_control & BQ2518X_HEALTH_MASK) {
    		case BQ2518X_TS_COOL_STAT:
    			health = POWER_SUPPLY_HEALTH_COOL;
    			break;
    		case BQ2518X_TS_WARM_STAT:
    			health = POWER_SUPPLY_HEALTH_WARM;
    			break;
    
    		default:
    			if (ts_control & BQ2518X_TS_COLD_STAT) {
    				health = POWER_SUPPLY_HEALTH_COLD;
    				break;
    			}
    			if (ts_control & BQ2518X_TS_HOT_STAT) {
    				health = POWER_SUPPLY_HEALTH_HOT;
    				break;
    			}
    			health = POWER_SUPPLY_HEALTH_UNKNOWN;
    			break;
    		}
    	}
    
    	if (stat1 & BQ2518X_VIN_OVP_STAT)
    		health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
    
    	if (stat1 & BQ2518X_SAFETY_TIMER_EXP)
    		health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
    
    	val->intval = health;
    	return 0;
    }
    
    static int bq2518x_mains_set_property(struct power_supply *psy,
    		enum power_supply_property prop,
    		const union power_supply_propval *val)
    {
    	struct bq2518x_device *bq2518x = power_supply_get_drvdata(psy);
    	int ret;
    
    	switch (prop) {
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
    		ret = bq2518x_set_batt_reg(bq2518x, val->intval);
    		break;
    
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
    		ret = bq2518x_set_const_charge_current(bq2518x, val->intval);
    		break;
    
    	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
    		ret = bq2518x_set_ilim_lvl(bq2518x, val->intval);
    		break;
    
    	case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
    		ret = bq2518x_set_precharge_current(bq2518x, val->intval);
    		break;
    
    	default:
    		return -EINVAL;
    	}
    
    	return ret;
    }
    
    static int bq2518x_mains_get_property(struct power_supply *psy,
    				     enum power_supply_property prop,
    				     union power_supply_propval *val)
    {
    	struct bq2518x_device *bq2518x = power_supply_get_drvdata(psy);
    	int ret = 0;
    
    	switch (prop) {
    
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
    		ret = bq2518x_get_const_charge_current(bq2518x);
    		if (ret < 0)
    			return ret;
    
    		val->intval = ret;
    		break;
    
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
    		ret = bq2518x_get_batt_reg(bq2518x);
    		if (ret < 0)
    			return ret;
    		val->intval = ret;
    		break;
    
    	case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
    		ret = bq2518x_get_precharge_current(bq2518x);
    		if (ret < 0)
    			return ret;
    		val->intval = ret;
    		break;
    
    	case POWER_SUPPLY_PROP_ONLINE:
    		val->intval = bq2518x->mains_online;
    		break;
    
    	case POWER_SUPPLY_PROP_HEALTH:
    		ret = bq2518x_charger_get_health(bq2518x, val);
    		if (ret)
    			val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
    		break;
    
    	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
    		ret = bq2518x_get_ilim_lvl(bq2518x);
    		if (ret < 0)
    			return ret;
    		val->intval = ret;
    		break;
    
    	case POWER_SUPPLY_PROP_MODEL_NAME:
    		val->strval = bq2518x->model_name;
    		break;
    
    	case POWER_SUPPLY_PROP_MANUFACTURER:
    		val->strval = BQ2518X_MANUFACTURER;
    		break;
    
    	case POWER_SUPPLY_PROP_STATUS:
    		ret = bq2518x_charging_status(bq2518x, val);
    		if (ret)
    			return ret;
    		break;
    
    	default:
    		return -EINVAL;
    	}
    
    	return ret;
    }
    
    static int bq2518x_battery_get_property(struct power_supply *psy,
    				       enum power_supply_property prop,
    				       union power_supply_propval *val)
    {
    	struct bq2518x_device *bq2518x = power_supply_get_drvdata(psy);
    	int ret;
    
    	ret = bq2518x_update_ps_status(bq2518x);
    	if (ret)
    		return ret;
    
    	switch (prop) {
    
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
    		ret = bq2518x->init_data.vbatreg;
    		if (ret < 0)
    			return ret;
    		val->intval = ret;
    		break;
    
    	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
    		ret = bq2518x->init_data.ichg;
    		if (ret < 0)
    			return ret;
    		val->intval = ret;
    		break;
    	default:
    		return -EINVAL;
    	}
    	return 0;
    }
    
    static const enum power_supply_property bq2518x_battery_properties[] = {
    	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
    	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
    };
    
    static const enum power_supply_property bq2518x_mains_properties[] = {
    	POWER_SUPPLY_PROP_ONLINE,
    	POWER_SUPPLY_PROP_STATUS,
    	POWER_SUPPLY_PROP_HEALTH,
    	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
    	POWER_SUPPLY_PROP_MODEL_NAME,
    	POWER_SUPPLY_PROP_MANUFACTURER,
    	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
    	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
    	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
    };
    
    static const struct power_supply_desc bq2518x_mains_desc = {
    	.name			= "bq2518x-mains",
    	.type			= POWER_SUPPLY_TYPE_MAINS,
    	.get_property		= bq2518x_mains_get_property,
    	.set_property		= bq2518x_mains_set_property,
    	.properties		= bq2518x_mains_properties,
    	.num_properties		= ARRAY_SIZE(bq2518x_mains_properties),
    	.property_is_writeable	= bq2518x_power_supply_property_is_writeable,
    };
    
    static const struct power_supply_desc bq2518x_battery_desc = {
    	.name			= "bq2518x-battery",
    	.type			= POWER_SUPPLY_TYPE_BATTERY,
    	.get_property		= bq2518x_battery_get_property,
    	.properties		= bq2518x_battery_properties,
    	.num_properties		= ARRAY_SIZE(bq2518x_battery_properties),
    	.property_is_writeable	= bq2518x_power_supply_property_is_writeable,
    };
    
    static int bq2518x_power_supply_register(struct bq2518x_device *bq2518x,
    		struct device *dev, struct power_supply_config psy_cfg)
    {
    	bq2518x->mains = devm_power_supply_register(bq2518x->dev,
    						    &bq2518x_mains_desc,
    						    &psy_cfg);
    	if (IS_ERR(bq2518x->mains))
    		return -EINVAL;
    
    	bq2518x->battery = devm_power_supply_register(bq2518x->dev,
    						      &bq2518x_battery_desc,
    						      &psy_cfg);
    	if (IS_ERR(bq2518x->battery))
    		return -EINVAL;
    
    	return 0;
    }
    
    #define BQ2518X_BATOCP_MASK     0xc0
    #define BQ2518X_BATOCP_1500MA   BIT(7)
    static int bq2518x_set_batocp_1500ma(struct bq2518x_device *bq2518x)
    {
        return regmap_update_bits(bq2518x->regmap, BQ2518X_CHARGERCTRL1, BQ2518X_BATOCP_MASK, BQ2518X_BATOCP_1500MA);
    
    }
    
    static int bq2518x_hw_init(struct bq2518x_device *bq2518x)
    {
    	int ret;
    //	struct power_supply_battery_info bat_info = { };	
    	struct power_supply_battery_info *bat_info;//jason
    
    	ret = bq2518x_disable_watchdog_timers(bq2518x);
    	if (ret)
    		return ret;
    
    	if (bq2518x->init_data.ilim) {
    		ret = bq2518x_set_ilim_lvl(bq2518x, bq2518x->init_data.ilim);
    		if (ret)
    			return ret;
    	}
    
    	ret = bq2518x_set_batocp_1500ma(bq2518x);
    	if (ret)
    		return ret;
    
    //	ret = power_supply_get_battery_info(bq2518x->mains, &bat_info);
    	ret = power_supply_get_battery_info(bq2518x->battery, &bat_info);//jason
    	if (ret) {
    		dev_warn(bq2518x->dev, "battery info missing, default values will be applied\n");
    
    		bq2518x->init_data.ichg = BQ2518X_DEFAULT_ICHG_UA;
    
    		bq2518x->init_data.vbatreg = BQ2518X_DEFAULT_VBAT_REG_UV;
    
    		bq2518x->init_data.iprechg = BQ2518X_DEFAULT_IPRECHARGE_UA;
    
    	} else {
    		bq2518x->init_data.ichg =
    				bat_info->constant_charge_current_max_ua;
    
    		bq2518x->init_data.vbatreg =
    				bat_info->constant_charge_voltage_max_uv;
    
    		bq2518x->init_data.iprechg =
    				bat_info->precharge_current_ua;
    	}
    
    	ret = bq2518x_set_const_charge_current(bq2518x,
    						bq2518x->init_data.ichg);
    	if (ret)
    		return ret;
    
    	ret = bq2518x_set_batt_reg(bq2518x, bq2518x->init_data.vbatreg);
    	if (ret)
    		return ret;
    
    	return bq2518x_set_precharge_current(bq2518x,
    						bq2518x->init_data.iprechg);
    }
    
    static int bq2518x_read_properties(struct bq2518x_device *bq2518x)
    {
    	int ret;
    
    	ret = device_property_read_u32(bq2518x->dev,
    				      "input-current-limit-microamp",
    				      &bq2518x->init_data.ilim);
    	if (ret) {
    		switch (bq2518x->device_id) {
    		case BQ25180:
    			bq2518x->init_data.ilim = BQ25180_DEFAULT_ILIM_UA;
    			break;
    
    		}
    	}
    
    
    
    	return 0;
    }
    
    static bool bq2518x_volatile_register(struct device *dev, unsigned int reg)
    {
    	switch (reg) {
    	case BQ2518X_STAT0 ... BQ2518X_FLAG0:
    		return true;
    	default:
    		return false;
    	}
    }
    
    static const struct regmap_config bq25180_regmap_config = {
    	.reg_bits = 8,
    	.val_bits = 8,
    
    	.max_register		= BQ2518X_MASK_ID,
    	.reg_defaults		= bq25180_reg_defaults,
    	.num_reg_defaults	= ARRAY_SIZE(bq25180_reg_defaults),
    	.cache_type		= REGCACHE_RBTREE,
    	.volatile_reg		= bq2518x_volatile_register,
    };
    
    static int bq2518x_probe(struct i2c_client *client,
    			 const struct i2c_device_id *id)
    {
    	struct device *dev = &client->dev;
    	struct bq2518x_device *bq2518x;
    	struct power_supply_config charger_cfg = {};
    	int ret;
    
    	bq2518x = devm_kzalloc(dev, sizeof(*bq2518x), GFP_KERNEL);
    	if (!bq2518x)
    		return -ENOMEM;
    
    	bq2518x->dev = dev;
    
    	strncpy(bq2518x->model_name, id->name, I2C_NAME_SIZE);
    
    	bq2518x->device_id = id->driver_data;
    
    	switch (bq2518x->device_id) {
    	case BQ25180:
    		bq2518x->regmap = devm_regmap_init_i2c(client,
    						&bq25180_regmap_config);
    		break;
    
    	}
    
    	if (IS_ERR(bq2518x->regmap)) {
    		dev_err(dev, "failed to allocate register map\n");
    		return PTR_ERR(bq2518x->regmap);
    	}
    
    	i2c_set_clientdata(client, bq2518x);
    
    	charger_cfg.drv_data = bq2518x;
    	charger_cfg.of_node = dev->of_node;
    
    	ret = bq2518x_read_properties(bq2518x);
    	if (ret) {
    		dev_err(dev, "Failed to read device tree properties %d\n",
    									ret);
    		return ret;
    	}
    
    	ret = bq2518x_power_supply_register(bq2518x, dev, charger_cfg);
    	if (ret) {
    		dev_err(dev, "failed to register power supply\n");
    		return ret;
    	}
    
    	ret = bq2518x_hw_init(bq2518x);
    	if (ret) {
    		dev_err(dev, "Cannot initialize the chip\n");
    		return ret;
    	}
    
    	return 0;
    }
    
    static const struct i2c_device_id bq2518x_i2c_ids[] = {
    	{ "bq25180", BQ25180, },
    	{},
    };
    MODULE_DEVICE_TABLE(i2c, bq2518x_i2c_ids);
    
    static const struct of_device_id bq2518x_of_match[] = {
    	{ .compatible = "ti,bq25180", },
    	{ },
    };
    MODULE_DEVICE_TABLE(of, bq2518x_of_match);
    
    static struct i2c_driver bq2518x_driver = {
    	.driver = {
    		.name = "bq2518x-charger",
    		.of_match_table = bq2518x_of_match,
    	},
    	.probe = bq2518x_probe,
    	.id_table = bq2518x_i2c_ids,
    };
    module_i2c_driver(bq2518x_driver);
    
    MODULE_AUTHOR("Texas Instruments");
    MODULE_DESCRIPTION("bq2518x charger driver");
    MODULE_LICENSE("GPL v2");
    
    

  • Hello Alec,

    Append my current dts configuration :

    bat: battery {
    compatible = "simple-battery";
    constant-charge-current-max-microamp = <1000000>;
    precharge-current-microamp = <100000>;
    constant-charge-voltage-max-microvolt = <4200000>;
    };

    bq25180: charger@6a {
    compatible = "ti,bq25180";
    reg = <0x6a>;
    monitored-battery = <&bat>;
    input-current-limit-microamp = <1100000>;
    };

    Thanks.

  • Hello Jason,

    Not a problem! Thanks so much for all your patience and collaboration throughout this process!

    You're right—there's an integer division issue with bq2518x_set_precharge_current().

    Right now, bq2518x_set_precharge_current() has this:

    times_of_icharge = (val / icharge_value) * 100;

    Since val and icharge_value are both integers, (100 / 1000) becomes 0 rather than 0.1. That leads to times_of_icharge being zero and the function returning an error. To fix this, simply multiply first, then divide:

    times_of_icharge = (val * BQ2518X_PERCENTAGE) / icharge_value;

    This should result in (100 * 100) / 1000 = 10, correctly giving you 10% precharge. The switch statement should then pick the proper fraction (5%, 10%, 20%, 40%) instead of returning -EINVAL.

    Let me know if this resolves the precharge current issue—always happy to help if anything comes up.

    Best regards,

    Alec

  • Hello Alec,

    Thanks for your patience and professional support !

    As expected,  the precharge current issue has been resolved with the changes you suggested.

    However, it seems that Reg0x6 and Reg0x8 are not being set correctly.

    The code has been attached last time, please help to check this issue.

    Thanks.

  • Hello Jason,

    No problem—I'm glad to hear that Reg0x04 is now being set correctly.

    I will look into this and follow up with you tomorrow (3/11).

    Best regards,

    Alec

  • Hello Alec,

    By the way, could you provide the method to read the current temperature ?

    Thanks.

  • Hello Jason,

    Thanks for your patience.

    It would be helpful if you could add debug print statements in bq2518x_hw_init() after each function call that updates Reg0x06 or Reg0x08. For example:

    ret = bq2518x_set_batocp_1500ma(bq2518x);
    dev_info(bq2518x->dev, "bq2518x_set_batocp_1500ma ret=%d\n", ret);
    if (!ret) {
        unsigned int reg_val;
        regmap_read(bq2518x->regmap, BQ2518X_CHARGERCTRL1, &reg_val);
        dev_info(bq2518x->dev, "CHARGERCTRL1 (Reg0x06) after BATOCP=0x%x\n", reg_val);
    }
    
    ...
    
    ret = bq2518x_set_ilim_lvl(bq2518x, bq2518x->init_data.ilim);
    dev_info(bq2518x->dev, "bq2518x_set_ilim_lvl ret=%d\n", ret);
    if (!ret) {
        unsigned int reg_val;
        regmap_read(bq2518x->regmap, BQ2518X_TMR_ILIM, &reg_val);
        dev_info(bq2518x->dev, "TMR_ILIM (Reg0x08) after ILIM=0x%x\n", reg_val);
    }

    If the registers show the correct values right after each call but revert later, it means something else is overwriting them. If they never show the updated bits at all, we know the calls didn't succeed or were skipped.

    Regarding the battery temperature, the BQ25180 does not have an integrated ADC to report the temperature in degrees. Instead, it monitors the TS pin (typically connected to the battery pack's thermistor) and reports discrete temperature states in STAT1 (Reg0x01, bits [4:3]). These status bits report whether the battery is HOT, COLD, WARM, COOL, or NORMAL—but the charger can't provide an exact temperature value. If you need a more precise temperature measurement, you'd need a dedicated fuel gauge or external ADC to read the TS pin voltage.

    Let me know what you find in the logs.

    Best regards,

    Alec

  • Hello Alec,

    Added the debugging code above and the following information was printed :

    It seems that something else is overwriting them. Where modified the two registers after bq2518x_hw_init() ?

    Thanks.

  • Hello Jason,

    After reviewing the driver, there's no code that writes to Reg0x06 (CHARGERCTRL1) or Reg0x08 (TMR_ILIM) after bq2518x_hw_init() completes. This means that something else is changing the values in these registers.

    Check if you have any custom scripts or i2cset commands that could be writing to these registers. If you compile your kernel with CONFIG_REGMAP_DEBUG, you can see each register read/write in the kernel log (dmesg). This should help us pinpoint what is writing those values.

    Let me know if you find any suspicious writes in the logs.

    Best regards,

    Alec

  • Hello Alec,

    I'm sure the script related to register settings has been closed. I wonder if the problem is caused by watchdog, because I have encountered before, once watchdog is turned on( IC_CTRL REG=0x84), the register value of Reg0x06 and Reg0x08 will be changed. I hope this inspires you and helps us solve the problem, and I'm analyzing it in that direction.

    Thanks.

  • Hello Jason,

    Thanks for pointing that out! Yes, if the watchdog timer is enabled and there's no I2C transaction to "refresh" it before the 160s timeout, the BQ25180 will reset its registers to their default values. This is expected behavior. I initially assumed the watchdog was disabled, so I didn't consider this earlier.

    Best regards,

    Alec

  • Hello Alec,

    Thanks so much for your professional explanation. It seems that root cause has been found and I'll look forward to your updating code.

    Thanks.

  • Hello Jason,

    Is the driver now working as expected? Do you have any remaining issues or questions, or has everything been resolved?

    Best regards,

    Alec

  • Hello  Alec,

    I tried to remove bq2518x_disable_watchdog_timers() from bq2518x_hw_init(), but Reg0x6 and Reg0x8 are still incorrect.

    Could you help to give me some suggestion ?

    Thanks.

  • Hello Alec,

    Could you help to update driver code for the issue of Reg0x6 and reg0x8 incorrcet value ?

    Thanks.

  • Hello Jason,

    I notice from the STAT registers that the device is in TS HOT and DPPM. Could you try removing the input source and then reading the registers again?

    Also, could you add bq2518x_disable_watchdog_timers() back into hw_init() to see if that prevents the registers from resetting?

    Finally, I'm curious about your reg_defaults array—could you share those values as well?

    Best regards,

    Alec

  • Hello Alec,

    I added bq2518x_disable_watchdog_timers() back into hw_init()  and removed the input source, then read the registers :

    my reg_defaults array :

    static const struct reg_default bq25180_reg_defaults[] = {
    {BQ2518X_STAT0, 0x0},
    {BQ2518X_STAT1, 0x0},
    {BQ2518X_VBAT_CTRL, 0x46},
    {BQ2518X_ICHG_CTRL, 0x7F},
    {BQ2518X_CHARGERCTRL0, 0x2C},
    {BQ2518X_CHARGERCTRL1, 0x96},
    {BQ2518X_IC_CTRL, 0x84},
    {BQ2518X_TMR_ILIM, 0x4F},
    {BQ2518X_SHIP_RST, 0x11},
    {BQ2518X_SYS_REG, 0x40},
    {BQ2518X_TS_CONTROL, 0x0},
    {BQ2518X_MASK_ID, 0xC0},

    };

    Thanks.

  • Hello Jason,

    Thanks for sharing your registers. It appears that Reg0x06 and Reg0x08 are still being overwritten by something.

    There's nothing that should be overwriting Reg0x06 and Reg0x08 in the driver. I recommend searching your codebase and scripts for any references to these registers or the charger's I2C address (0x6A).

    If you find anything referencing CHARGERCTRL1 (Reg0x06) or TMR_ILIM (Reg0x08), let me know. I suspect that's what's causing the unexpected revert.

    Best regards,

    Alec

  • Hello Alec,

    Thanks for your support.

    My script has been disabled, and I have not found anything referencing CHARGERCTRL1 (Reg0x06) or TMR_ILIM (Reg0x08). But I find that Reg0x06 and Reg0x08 were obviously reset. I think that if the two registers have been modified somewhere else, they  wouldn't have been so coincidentally set to 0x56, 0x4d. Could you tell me what mechanism might reset them ? or any other suggestion ?

    Thanks.

  • Hello Jason,

    The watchdog timer will reset the registers if it expires, which could explain why Reg0x06 and Reg0x08 are reverting to their default values. Could you try writing ILIM and BATOCP while the adapter is connected (so VIN is valid) and then read them back to confirm whether they stay at your configured values?

    Best regards,

    Alec

  • Hello Alec,

    I tried writing ILIM and BATOCP while the adapter was connected and then read them back, they stayed at the configured values instead of being reset.

    I think the registers will be reset to "reg_defaults" when watchdog timer expires. However, the default values of Reg0x06 and Reg0x08 in "reg_defaults" are 0x96 and 0x4F. On the other side, the reset values of Reg0x06 and Reg0x08 are defined as 0x56 and 0x4D in datasheet. Does this mean Reg0x06 and Reg0x08 were reset by hardware? Could you think about what would trigger a hardware reset ?

     I have not found anything referencing CHARGERCTRL1 (Reg0x06) or TMR_ILIM (Reg0x08) in my codebase, could you please give me some suggestion to solve this issue ?

    Thanks.

  • Hello Alec,

    In addition, Reg0x06 and Reg0x08 were overwrited after insmoding driver. After that, if reverting to 0x96 and 0x4D with "i2cset", they will not been overwrited again.

    Thanks.

  • Hello Jason,

    Thanks for the information.

    On the other side, the reset values of Reg0x06 and Reg0x08 are defined as 0x56 and 0x4D in datasheet. Does this mean Reg0x06 and Reg0x08 were reset by hardware? Could you think about what would trigger a hardware reset ?

    Yes, given that Reg0x06 and Reg0x08 are reset to their hardware default values (0x56 and 0x4D) instead of reg_defaults (0x96 and 0x4F), it seems that they are being reset by hardware. There are a few ways that the BQ25180 can reset its registers to their default values:

    • Watchdog timer expiring
    • Hardware reset command via Reg0x09[bits 6-5 set to 11]
    • Push button long press (can be configured to trigger a hardware reset via Reg0x09[bits 4-3 set to 01]
    • Software reset command via Reg0x09[bit 7 set to 1]
    • Device waking from shutdown mode

    Best regards,

    Alec

  • Hello Jason,

    I wanted to check in and see if you found what was overwriting those registers.

    Let me know if you have any questions.

    Best regards,

    Alec

  • Hi Alec,

    I'm sorry for the late update because of an urgent project. But now I'm back to debug the BQ25180 driver.

    I have not found anything referencing CHARGERCTRL1 (Reg0x06) or TMR_ILIM (Reg0x08) in my codebase, and I have not find anything overwriting those registers. I wonder that if the reset is caused by watchdog, why are the other registers not reset ? 

    I tried configuring Reg0x09[bit0 set to 0, bit7 set to 0, bit4-3 set to 00, bit6-5 set to 00] to avoid resetting,  but the issue still hasn't been solved .

    Could you please give me some suggestion to solve this issue ?

    I tried the following test:

    i2cdump -y -f 0 0x6a

    cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/uevent

    POWER_SUPPLY_INPUT_CURRENT_LIMIT=1100000,

    It appear that the instruction does not dynamically refresh, so the printed information does not match the register value.

    echo 1100000 > /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/input_current_limit

    i2cdump -y -f 0 0x6a

    echo 700000 > /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/input_current_limit

    echo 1100000 > /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/input_current_limit 

  • Hi Jason,

    Thanks for all of the information.

    Could you try modifying bq2518x_set_ilim_lvl() with the following? It performs a read-modify-write on the TMR_ILIM register, ensuring that only the ILIM bits are updated while preserving the other bits.

    static int bq2518x_set_ilim_lvl(struct bq2518x_device *bq2518x, int val)
    {
        int ret, i = 0, tmr_val;
        unsigned int array_size = ARRAY_SIZE(bq2518x_ilim_lvl_values);
    
        for (i = array_size - 1; i > 0; i--) {
            if (val >= bq2518x_ilim_lvl_values[i])
                break;
        }
    
        ret = regmap_read(bq2518x->regmap, BQ2518X_TMR_ILIM, &tmr_val);
        if (ret)
            return ret;
    
        tmr_val = (tmr_val & ~BQ2518X_ILIM_MASK) | (i & BQ2518X_ILIM_MASK);
    
        return regmap_write(bq2518x->regmap, BQ2518X_TMR_ILIM, tmr_val);
    }
    

    Let me know if this allows ILIM to be set correctly.

    Best regards,

    Alec

  • Hi Alec,

    Thanks for  your support.

    The ILIM has been set correctly with the modification you provided. 

    Now only issue of Reg06 remains, please help to solve it.

    Thanks.

  • Hi Jason,

    Great! I'm glad to hear that ILIM is now being set correctly.

    To resolve the Reg0x06 issue, please try modifying bq2518x_set_batocp_1500ma() with the following change.

    static int bq2518x_set_batocp_1500mA(struct bq2518x_device *bq2518x)
    {
        int ret, reg_val;
        
        ret = regmap_read(bq2518x->regmap, BQ2518X_CHARGERCTRL1, &reg_val);
        if (ret)
            return ret;
            
        reg_val = (reg_val & ~BQ2518X_BATOCP_MASK) | (BQ2518X_BATOCP_1500MA & BQ2518X_BATOCP_MASK);
        
        return regmap_write(bq2518x->regmap, BQ2518X_CHARGERCTRL1, reg_val);
    }

    Similar to the ILIM fix, this update performs a read-modify-write so that only the BATOCP bits are updated while preserving the other bits in the register.

    Let me know if this resolves the issue.

    Best regards,

    Alec

  • Hi Alec,

    Thanks for your professional support. I'm glad to tell you that the Reg06 issue has been resolved with your last change.

    I still have some questions that I need your help.

    1. I set register value with the command of i2cset, then executed "cat /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/uevent". I found that "cat" result was not updated in time. 

    Conversely, I excuted "echo 4200000 > /sys/devices/platform/soc@0/44000000.bus/44340000.i2c/i2c-0/0-006a/power_supply/bq2518x-mains/constant_charge_voltage", then register value was updated in time.

    I'm worried that the "cat" result is not updated in time, like now I never see the "charging" state of POWER_SUPPLY_STATUS when the battery has worked for a long time and connected input source. So I can't judge whether POWER_SUPPLY_STATUS is right now.

    2. As you said last time, I need to read the battery temperature from the register of STAT(Reg0x01, bits [4:3]). But I found that Reg01=00 after removing the input source. Whether the input source must be connected to read Reg01 ?  and could you help to explain the meaning of these values ? How can I check whether the battery is HOT, COLD, WARM, COOL, or NORMAL ?

    Thanks.

  • Hi Jason,

    I'm glad that all of the registers are now being set correctly! Thanks for your patience throughout this debug.

    1. Thanks for pointing this out. The CHG_STAT field is represented by bits 6-5 in the STAT0 register (Reg0x00). The current implementation shifts the value by 4 bits, which mis-aligns these bits. To correctly extract the two-bit value into the least-significant bits (positions 0 and 1), we need to shift right by 5 bits instead. This change ensures that bit 6 moves to position 1 and bit 5 moves to position 0, yielding the proper numeric value for the charging state.

    Please change the following line in the driver:

    #define BQ2518X_CHG_STAT_STEP 4

    to

    #define BQ2518X_CHG_STAT_STEP 5

    This adjustment should allow the driver to correctly interpret the charging state from the STAT0 register.

    Let me know if this resolves the issue.

    2. This behavior is expected. The BQ25180 monitors the TS pin voltage, which is influenced by the NTC and any external resistor compensation networks, to determine whether the battery is COLD, COOL, WARM, HOT, or NORMAL. Battery temperature is only actively monitored when an input source is connected (i.e., during charging). To test whether the firmware correctly identifies the battery temperature state, you can bias the TS pin above or below the voltage thresholds specified in the datasheet (see section 8.3.13.1 of the BQ25180 datasheet for more details).

    Best regards,

    Alec