// SPDX-License-Identifier: GPL-2.0 /* * BQ257XX Battery Charger Driver * Copyright (C) 2025 Chris Morgan */ #include #include #include #include #include #include #include #include #include /* Forward declaration of driver data. */ struct bq257xx_chg; /** * struct bq257xx_chip_info - chip specific routines * @default_iindpm_uA: default input current limit in microamps * @bq257xx_hw_init: init function for hw * @bq257xx_hw_shutdown: shutdown function for hw * @bq257xx_get_state: get and update state of hardware * @bq257xx_get_ichg: get maximum charge current (in uA) * @bq257xx_set_ichg: set maximum charge current (in uA) * @bq257xx_get_vbatreg: get maximum charge voltage (in uV) * @bq257xx_set_vbatreg: set maximum charge voltage (in uV) * @bq257xx_get_iindpm: get maximum input current (in uA) * @bq257xx_set_iindpm: set maximum input current (in uA) * @bq257xx_get_cur: get battery current from ADC (in uA) * @bq257xx_get_vbat: get battery voltage from ADC (in uV) * @bq257xx_get_min_vsys: get minimum system voltage (in uV) */ struct bq257xx_chip_info { int default_iindpm_uA; int (*bq257xx_hw_init)(struct bq257xx_chg *pdata); void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata); int (*bq257xx_get_state)(struct bq257xx_chg *pdata); int (*bq257xx_get_ichg)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_ichg)(struct bq257xx_chg *pdata, int ichg); int (*bq257xx_get_vbatreg)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_vbatreg)(struct bq257xx_chg *pdata, int vbatreg); int (*bq257xx_get_iindpm)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_set_iindpm)(struct bq257xx_chg *pdata, int iindpm); int (*bq257xx_get_cur)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_get_vbat)(struct bq257xx_chg *pdata, int *intval); int (*bq257xx_get_min_vsys)(struct bq257xx_chg *pdata, int *intval); }; /** * struct bq257xx_chg - driver data for charger * @chip: hw specific functions * @bq: parent MFD device * @charger: power supply device * @online: charger input is present * @charging: charger is actively charging the battery * @fast_charge: charger is in fast charge mode * @pre_charge: charger is in pre-charge mode * @overvoltage: overvoltage fault detected * @ov_fault: charger reports over voltage fault * @batoc_fault: charger reports battery over current fault * @oc_fault: charger reports over current fault * @usb_type: USB type reported from parent power supply * @supplied: Status of parent power supply * @iindpm_max: maximum input current limit (uA) * @vbat_max: maximum charge voltage (uV) * @ichg_max: maximum charge current (uA) * @vsys_min: minimum system voltage (uV) */ struct bq257xx_chg { const struct bq257xx_chip_info *chip; struct bq257xx_device *bq; struct power_supply *charger; bool online; bool charging; bool fast_charge; bool pre_charge; bool overvoltage; bool ov_fault; bool batoc_fault; bool oc_fault; int usb_type; int supplied; u32 iindpm_max; u32 vbat_max; u32 ichg_max; u32 vsys_min; }; /** * bq25792_read16() - Read a 16-bit value from device register * @pdata: driver platform data * @reg: register address to read from * @val: pointer to store the register value * * Read a 16-bit big-endian value from the BQ25792 device via regmap * and convert to CPU byte order. * * Return: Returns 0 on success or error on failure to read. */ static int bq25792_read16(struct bq257xx_chg *pdata, unsigned int reg, u16 *val) { __be16 regval; int ret; ret = regmap_raw_read(pdata->bq->regmap, reg, ®val, sizeof(regval)); if (ret) return ret; *val = be16_to_cpu(regval); return 0; } /** * bq25792_write16() - Write a 16-bit value to device register * @pdata: driver platform data * @reg: register address to write to * @val: 16-bit value to write in CPU byte order * * Convert the value to big-endian and write a 16-bit value to the * BQ25792 device via regmap. * * Return: Returns 0 on success or error on failure to write. */ static int bq25792_write16(struct bq257xx_chg *pdata, unsigned int reg, u16 val) { __be16 regval = cpu_to_be16(val); int ret; ret = regmap_raw_write(pdata->bq->regmap, reg, ®val, sizeof(regval)); if (ret) return ret; return 0; } /** * bq25703_get_state() - Get the current state of the device * @pdata: driver platform data * * Get the current state of the charger. Check if the charger is * powered, what kind of charge state (if any) the device is in, * and if there are any active faults. * * Return: Returns 0 on success, or error on failure to read device. */ static int bq25703_get_state(struct bq257xx_chg *pdata) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGER_STATUS, ®); if (ret) return ret; pdata->online = reg & BQ25703_STS_AC_STAT; pdata->fast_charge = reg & BQ25703_STS_IN_FCHRG; pdata->pre_charge = reg & BQ25703_STS_IN_PCHRG; pdata->charging = pdata->fast_charge || pdata->pre_charge; pdata->ov_fault = reg & BQ25703_STS_FAULT_ACOV; pdata->batoc_fault = reg & BQ25703_STS_FAULT_BATOC; pdata->overvoltage = pdata->ov_fault || pdata->batoc_fault; pdata->oc_fault = reg & BQ25703_STS_FAULT_ACOC; return 0; } /** * bq25792_get_state() - Get the current state of the device * @pdata: driver platform data * * Get the current state of the BQ25792 charger by reading status * registers. Updates the online, charging, overvoltage, and fault * status fields in the driver data structure. * * Return: Returns 0 on success or error on failure to read device. */ static int bq25792_get_state(struct bq257xx_chg *pdata) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25792_REG1B_CHARGER_STATUS_0, ®); if (ret) return ret; pdata->online = reg & BQ25792_REG1B_PG_STAT; ret = regmap_read(pdata->bq->regmap, BQ25792_REG1C_CHARGER_STATUS_1, ®); if (ret) return ret; pdata->charging = reg & BQ25792_REG1C_CHG_STAT_MASK; ret = regmap_read(pdata->bq->regmap, BQ25792_REG20_FAULT_STATUS_0, ®); if (ret) return ret; pdata->overvoltage = reg & BQ25792_REG20_OVERVOLTAGE_MASK; pdata->oc_fault = reg & BQ25792_REG20_OVERCURRENT_MASK; return 0; } /** * bq25703_get_min_vsys() - Get the minimum system voltage * @pdata: driver platform data * @intval: value for minimum voltage * * Return: Returns 0 on success or error on failure to read. */ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_MIN_VSYS, ®); if (ret) return ret; reg = FIELD_GET(BQ25703_MINVSYS_MASK, reg); *intval = (reg * BQ25703_MINVSYS_STEP_UV) + BQ25703_MINVSYS_MIN_UV; return ret; } /** * bq25792_get_min_vsys() - Get the minimum system voltage * @pdata: driver platform data * @intval: pointer to store the minimum voltage value * * Read the current minimum system voltage setting from the device * and return it in microvolts. * * Return: Returns 0 on success or error on failure to read. */ static int bq25792_get_min_vsys(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, ®); if (ret) return ret; reg = FIELD_GET(BQ25792_REG00_VSYSMIN_MASK, reg); *intval = (reg * BQ25792_MINVSYS_STEP_UV) + BQ25792_MINVSYS_MIN_UV; return ret; } /** * bq25703_set_min_vsys() - Set the minimum system voltage * @pdata: driver platform data * @vsys: voltage value to set in uV. * * This function takes a requested minimum system voltage value, clamps * it between the user defined minimum system value and the maximum supported * value by the charger, and then writes the value to the appropriate register. * * Return: Returns 0 on success or error if an error occurs. */ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) { unsigned int reg; int vsys_min = pdata->vsys_min; vsys = clamp(vsys, vsys_min, BQ25703_MINVSYS_MAX_UV); reg = ((vsys - BQ25703_MINVSYS_MIN_UV) / BQ25703_MINVSYS_STEP_UV); reg = FIELD_PREP(BQ25703_MINVSYS_MASK, reg); return regmap_write(pdata->bq->regmap, BQ25703_MIN_VSYS, reg); } /** * bq25792_set_min_vsys() - Set the minimum system voltage * @pdata: driver platform data * @vsys: voltage value to set in uV * * Set the minimum system voltage by clamping the requested value * between device limits and writing to the appropriate register. * * Return: Returns 0 on success or error on failure to write. */ static int bq25792_set_min_vsys(struct bq257xx_chg *pdata, int vsys) { unsigned int reg; int vsys_min = pdata->vsys_min; vsys = clamp(vsys, vsys_min, BQ25792_MINVSYS_MAX_UV); reg = ((vsys - BQ25792_MINVSYS_MIN_UV) / BQ25792_MINVSYS_STEP_UV); reg = FIELD_PREP(BQ25792_REG00_VSYSMIN_MASK, reg); return regmap_write(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, reg); } /** * bq25703_get_cur() - Get the reported current from the battery * @pdata: driver platform data * @intval: value of reported battery current * * Read the reported current from the battery. Since value is always * positive set sign to negative if discharging. * * Return: Returns 0 on success or error if unable to read value. */ static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_ADCIBAT_CHG, ®); if (ret < 0) return ret; if (pdata->online) *intval = FIELD_GET(BQ25703_ADCIBAT_CHG_MASK, reg) * BQ25703_ADCIBAT_CHG_STEP_UA; else *intval = -(FIELD_GET(BQ25703_ADCIBAT_DISCHG_MASK, reg) * BQ25703_ADCIBAT_DIS_STEP_UA); return ret; } /** * bq25792_get_cur() - Get the reported current from the battery * @pdata: driver platform data * @intval: pointer to store the battery current value * * Read the current ADC value from the device representing the battery * charge or discharge current and return it in microamps. * * Return: Returns 0 on success or error on failure to read. */ static int bq25792_get_cur(struct bq257xx_chg *pdata, int *intval) { u16 reg; int ret; ret = bq25792_read16(pdata, BQ25792_REG33_IBAT_ADC, ®); if (ret < 0) return ret; *intval = (s16)reg * BQ25792_ADCIBAT_STEP_UA; return ret; } /** * bq25703_get_ichg_cur() - Get the maximum reported charge current * @pdata: driver platform data * @intval: value of maximum reported charge current * * Get the maximum reported charge current from the battery. * * Return: Returns 0 on success or error if unable to read value. */ static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGE_CURRENT, ®); if (ret) return ret; *intval = FIELD_GET(BQ25703_ICHG_MASK, reg) * BQ25703_ICHG_STEP_UA; return ret; } /** * bq25792_get_ichg_cur() - Get the maximum reported charge current * @pdata: driver platform data * @intval: pointer to store the maximum charge current value * * Read the programmed maximum charge current limit from the device. * * Return: Returns 0 on success or error on failure to read value. */ static int bq25792_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) { u16 reg; int ret; ret = bq25792_read16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, ®); if (ret) return ret; *intval = FIELD_GET(BQ25792_REG03_ICHG_MASK, reg) * BQ25792_ICHG_STEP_UA; return ret; } /** * bq25703_set_ichg_cur() - Set the maximum charge current * @pdata: driver platform data * @ichg: current value to set in uA. * * This function takes a requested maximum charge current value, clamps * it between the minimum supported value by the charger and a user * defined maximum charging value, and then writes the value to the * appropriate register. * * Return: Returns 0 on success or error if an error occurs. */ static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) { unsigned int reg; int ichg_max = pdata->ichg_max; ichg = clamp(ichg, BQ25703_ICHG_MIN_UA, ichg_max); reg = FIELD_PREP(BQ25703_ICHG_MASK, (ichg / BQ25703_ICHG_STEP_UA)); return regmap_write(pdata->bq->regmap, BQ25703_CHARGE_CURRENT, reg); } /** * bq25792_set_ichg_cur() - Set the maximum charge current * @pdata: driver platform data * @ichg: current value to set in uA * * Set the maximum charge current by clamping the requested value * between device limits and writing to the appropriate register. * * Return: Returns 0 on success or error on failure to write. */ static int bq25792_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) { int ichg_max = pdata->ichg_max; u16 reg; ichg = clamp(ichg, BQ25792_ICHG_MIN_UA, ichg_max); reg = FIELD_PREP(BQ25792_REG03_ICHG_MASK, (ichg / BQ25792_ICHG_STEP_UA)); return bq25792_write16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, reg); } /** * bq25703_get_chrg_volt() - Get the maximum set charge voltage * @pdata: driver platform data * @intval: maximum charge voltage value * * Return: Returns 0 on success or error if unable to read value. */ static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_MAX_CHARGE_VOLT, ®); if (ret) return ret; *intval = FIELD_GET(BQ25703_MAX_CHARGE_VOLT_MASK, reg) * BQ25703_VBATREG_STEP_UV; return ret; } /** * bq25792_get_chrg_volt() - Get the maximum set charge voltage * @pdata: driver platform data * @intval: pointer to store the maximum charge voltage value * * Read the current charge voltage limit from the device. * * Return: Returns 0 on success or error on failure to read value. */ static int bq25792_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) { u16 reg; int ret; ret = bq25792_read16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, ®); if (ret) return ret; *intval = FIELD_GET(BQ25792_REG01_VREG_MASK, reg) * BQ25792_VBATREG_STEP_UV; return ret; } /** * bq25703_set_chrg_volt() - Set the maximum charge voltage * @pdata: driver platform data * @vbat: voltage value to set in uV. * * This function takes a requested maximum charge voltage value, clamps * it between the minimum supported value by the charger and a user * defined maximum charging value, and then writes the value to the * appropriate register. * * Return: Returns 0 on success or error if an error occurs. */ static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) { unsigned int reg; int vbat_max = pdata->vbat_max; vbat = clamp(vbat, BQ25703_VBATREG_MIN_UV, vbat_max); reg = FIELD_PREP(BQ25703_MAX_CHARGE_VOLT_MASK, (vbat / BQ25703_VBATREG_STEP_UV)); return regmap_write(pdata->bq->regmap, BQ25703_MAX_CHARGE_VOLT, reg); } /** * bq25792_set_chrg_volt() - Set the maximum charge voltage * @pdata: driver platform data * @vbat: voltage value to set in uV * * Set the maximum charge voltage by clamping the requested value * between device limits and writing to the appropriate register. * * Return: Returns 0 on success or error on failure to write. */ static int bq25792_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) { int vbat_max = pdata->vbat_max; u16 reg; vbat = clamp(vbat, BQ25792_VBATREG_MIN_UV, vbat_max); reg = FIELD_PREP(BQ25792_REG01_VREG_MASK, (vbat / BQ25792_VBATREG_STEP_UV)); return bq25792_write16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, reg); } /** * bq25703_get_iindpm() - Get the maximum set input current * @pdata: driver platform data * @intval: maximum input current value * * Read the actual input current limit from the device into intval. * This can differ from the value programmed due to some autonomous * functions that may be enabled (but are not currently). This is why * there is a different register used. * * Return: Returns 0 on success or error if unable to read register * value. */ static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_IIN_DPM, ®); if (ret) return ret; reg = FIELD_GET(BQ25703_IINDPM_MASK, reg); *intval = (reg * BQ25703_IINDPM_STEP_UA) + BQ25703_IINDPM_OFFSET_UA; return ret; } /** * bq25792_get_iindpm() - Get the maximum set input current * @pdata: driver platform data * @intval: pointer to store the maximum input current value * * Read the current input current limit from the device. * * Return: Returns 0 on success or error on failure to read value. */ static int bq25792_get_iindpm(struct bq257xx_chg *pdata, int *intval) { u16 reg; int ret; ret = bq25792_read16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, ®); if (ret) return ret; reg = FIELD_GET(BQ25792_REG06_IINDPM_MASK, reg); *intval = reg * BQ25792_IINDPM_STEP_UA; return ret; } /** * bq25703_set_iindpm() - Set the maximum input current * @pdata: driver platform data * @iindpm: current value in uA. * * This function takes a requested maximum input current value, clamps * it between the minimum supported value by the charger and a user * defined maximum input value, and then writes the value to the * appropriate register. * * Return: Returns 0 on success or error if an error occurs. */ static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm) { unsigned int reg; int iindpm_max = pdata->iindpm_max; iindpm = clamp(iindpm, BQ25703_IINDPM_MIN_UA, iindpm_max); reg = ((iindpm - BQ25703_IINDPM_OFFSET_UA) / BQ25703_IINDPM_STEP_UA); return regmap_write(pdata->bq->regmap, BQ25703_IIN_HOST, FIELD_PREP(BQ25703_IINDPM_MASK, reg)); } /** * bq25792_set_iindpm() - Set the maximum input current * @pdata: driver platform data * @iindpm: current value in uA * * Set the maximum input current by clamping the requested value * between device limits and writing to the appropriate register. * * Return: Returns 0 on success or error on failure to write. */ static int bq25792_set_iindpm(struct bq257xx_chg *pdata, int iindpm) { u16 reg; int iindpm_max = pdata->iindpm_max; iindpm = clamp(iindpm, BQ25792_IINDPM_MIN_UA, iindpm_max); reg = iindpm / BQ25792_IINDPM_STEP_UA; return bq25792_write16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, FIELD_PREP(BQ25792_REG06_IINDPM_MASK, reg)); } /** * bq25703_get_vbat() - Get the reported voltage from the battery * @pdata: driver platform data * @intval: value of reported battery voltage * * Read value of battery voltage into intval. * * Return: Returns 0 on success or error if unable to read value. */ static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval) { unsigned int reg; int ret; ret = regmap_read(pdata->bq->regmap, BQ25703_ADCVSYSVBAT, ®); if (ret) return ret; reg = FIELD_GET(BQ25703_ADCVBAT_MASK, reg); *intval = (reg * BQ25703_ADCVSYSVBAT_STEP) + BQ25703_ADCVSYSVBAT_OFFSET_UV; return ret; } /** * bq25792_get_vbat() - Get the reported voltage from the battery * @pdata: driver platform data * @intval: pointer to store the battery voltage value * * Read the current ADC value representing the battery voltage * and return it in microvolts. * * Return: Returns 0 on success or error on failure to read value. */ static int bq25792_get_vbat(struct bq257xx_chg *pdata, int *intval) { u16 reg; int ret; ret = bq25792_read16(pdata, BQ25792_REG3B_VBAT_ADC, ®); if (ret) return ret; *intval = reg * BQ25792_ADCVSYSVBAT_STEP_UV; return ret; } /** * bq25703_hw_init() - Set all the required registers to init the charger * @pdata: driver platform data * * Initialize the BQ25703 by first disabling the watchdog timer (which * shuts off the charger in the absence of periodic writes). Then, set * the charge current, charge voltage, minimum system voltage, and * input current limit. Disable low power mode to allow ADCs and * interrupts. Enable the ADC, start the ADC, set the ADC scale to * full, and enable each individual ADC channel. * * Return: Returns 0 on success or error code on error. */ static int bq25703_hw_init(struct bq257xx_chg *pdata) { struct regmap *regmap = pdata->bq->regmap; int ret = 0; regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0, BQ25703_WDTMR_ADJ_MASK, FIELD_PREP(BQ25703_WDTMR_ADJ_MASK, BQ25703_WDTMR_DISABLE)); ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); if (ret) return ret; ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); if (ret) return ret; ret = bq25703_set_min_vsys(pdata, pdata->vsys_min); if (ret) return ret; ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); if (ret) return ret; /* Disable low power mode by writing 0 to the register. */ regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0, BQ25703_EN_LWPWR, 0); /* Enable the ADC. */ regmap_update_bits(regmap, BQ25703_ADC_OPTION, BQ25703_ADC_CONV_EN, BQ25703_ADC_CONV_EN); /* Start the ADC. */ regmap_update_bits(regmap, BQ25703_ADC_OPTION, BQ25703_ADC_START, BQ25703_ADC_START); /* Set the scale of the ADC. */ regmap_update_bits(regmap, BQ25703_ADC_OPTION, BQ25703_ADC_FULL_SCALE, BQ25703_ADC_FULL_SCALE); /* Enable each of the ADC channels available. */ regmap_update_bits(regmap, BQ25703_ADC_OPTION, BQ25703_ADC_CH_MASK, (BQ25703_ADC_CMPIN_EN | BQ25703_ADC_VBUS_EN | BQ25703_ADC_PSYS_EN | BQ25703_ADC_IIN_EN | BQ25703_ADC_IDCHG_EN | BQ25703_ADC_ICHG_EN | BQ25703_ADC_VSYS_EN | BQ25703_ADC_VBAT_EN)); return ret; } /** * bq25792_hw_init() - Initialize BQ25792 hardware * @pdata: driver platform data * * Initialize the BQ25792 by disabling the watchdog, enabling discharge * current sensing with 5A limit, and configuring input current regulation. * Set the charge current, charge voltage, minimum system voltage, and * input current limit from platform data. Enable and configure the ADC * to measure all available channels. * * Return: Returns 0 on success or error code on error. */ static int bq25792_hw_init(struct bq257xx_chg *pdata) { struct regmap *regmap = pdata->bq->regmap; int ret = 0; u8 reg; /* Disable watchdog (TODO: make it work instead) */ ret = regmap_write(regmap, BQ25792_REG10_CHARGER_CONTROL_1, 0); if (ret) return ret; /* * Enable battery discharge current sensing, 5A discharge current * limit, input current regulation and ship FET functions */ ret = regmap_write(regmap, BQ25792_REG14_CHARGER_CONTROL_5, BQ25792_REG14_SFET_PRESENT | BQ25792_REG14_EN_IBAT | BQ25792_IBAT_5A | BQ25792_REG14_EN_IINDPM); if (ret) return ret; if (pdata->vbat_max < 5000000) { /* 1S batteries */ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_1S); } else if (pdata->vbat_max < 10000000) { /* 2S batteries */ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_2S); } else if (pdata->vbat_max < 14000000) { /* 3S batteries */ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_3S); } else { /* 4S batteries */ reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_4S); } /* Recharge voltage detection deglitch time (default 1024ms) */ reg |= FIELD_PREP(BQ25792_REG0A_TRECHG_MASK, BQ25792_TRECHG_1024MS); /* Recharge voltage offset: 5% of the set charge voltage */ reg |= FIELD_PREP(BQ25792_REG0A_VRECHG_MASK, (pdata->vbat_max / 20 - BQ25792_VRECHG_MIN_UV) / BQ25792_VRECHG_STEP_UV); ret = regmap_write(regmap, BQ25792_REG0A_RECHARGE_CONTROL, reg); if (ret) return ret; ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); if (ret) return ret; ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); if (ret) return ret; ret = bq25792_set_min_vsys(pdata, pdata->vsys_min); if (ret) return ret; ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); if (ret) return ret; /* Enable the Input Current Optimizer (the rest is at POR value) */ ret = regmap_write(regmap, BQ25792_REG0F_CHARGER_CONTROL_0, BQ25792_REG0F_EN_AUTO_IBATDIS | BQ25792_REG0F_EN_CHG | BQ25792_REG0F_EN_ICO | BQ25792_REG0F_EN_TERM); if (ret) return ret; /* Enable the ADC. */ ret = regmap_write(regmap, BQ25792_REG2E_ADC_CONTROL, BQ25792_REG2E_ADC_EN); if (ret) return ret; /* Clear per-channel ADC disable bits - enable all channels */ ret = regmap_write(regmap, BQ25792_REG2F_ADC_FUNCTION_DISABLE_0, 0); if (ret) return ret; ret = regmap_write(regmap, BQ25792_REG30_ADC_FUNCTION_DISABLE_1, 0); if (ret) return ret; return ret; } /** * bq25703_hw_shutdown() - Set registers for shutdown * @pdata: driver platform data * * Enable low power mode for the device while in shutdown. */ static void bq25703_hw_shutdown(struct bq257xx_chg *pdata) { regmap_update_bits(pdata->bq->regmap, BQ25703_CHARGE_OPTION_0, BQ25703_EN_LWPWR, BQ25703_EN_LWPWR); } /** * bq25792_hw_shutdown() - Shutdown BQ25792 hardware * @pdata: driver platform data * * Perform hardware shutdown for the BQ25792. Currently a no-op * as the device does not require special shutdown configuration. */ static void bq25792_hw_shutdown(struct bq257xx_chg *pdata) { /* Nothing to do here */ } /** * bq257xx_set_charger_property() - Set a power supply property * @psy: power supply device * @prop: power supply property to set * @val: value to set for the property * * Handle requests to set power supply properties such as input current * limit, constant charge voltage, and constant charge current. Routes * the request to the chip-specific implementation. * * Return: Returns 0 on success or -EINVAL if property is not supported. */ static int bq257xx_set_charger_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); switch (prop) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return pdata->chip->bq257xx_set_iindpm(pdata, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: return pdata->chip->bq257xx_set_vbatreg(pdata, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: return pdata->chip->bq257xx_set_ichg(pdata, val->intval); default: break; } return -EINVAL; } /** * bq257xx_get_charger_property() - Get a power supply property * @psy: power supply device * @psp: power supply property to get * @val: pointer to store the property value * * Handle requests to get power supply properties, including status, * health, manufacturer, online state, and various voltage/current * measurements. Reads current device state and routes chip-specific * property requests to appropriate handlers. * * Return: Returns 0 on success or -EINVAL if property is not supported. */ static int bq257xx_get_charger_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); int ret = 0; ret = pdata->chip->bq257xx_get_state(pdata); if (ret) return ret; switch (psp) { case POWER_SUPPLY_PROP_STATUS: if (!pdata->online) val->intval = POWER_SUPPLY_STATUS_DISCHARGING; else if (pdata->charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_HEALTH: if (pdata->overvoltage) val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else if (pdata->oc_fault) val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; else val->intval = POWER_SUPPLY_HEALTH_GOOD; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = "Texas Instruments"; break; case POWER_SUPPLY_PROP_ONLINE: val->intval = pdata->online; break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return pdata->chip->bq257xx_get_iindpm(pdata, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: return pdata->chip->bq257xx_get_vbatreg(pdata, &val->intval); case POWER_SUPPLY_PROP_CURRENT_NOW: return pdata->chip->bq257xx_get_cur(pdata, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_NOW: return pdata->chip->bq257xx_get_vbat(pdata, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: return pdata->chip->bq257xx_get_ichg(pdata, &val->intval); case POWER_SUPPLY_PROP_VOLTAGE_MIN: return pdata->chip->bq257xx_get_min_vsys(pdata, &val->intval); case POWER_SUPPLY_PROP_USB_TYPE: val->intval = pdata->usb_type; break; default: return -EINVAL; } return ret; } static enum power_supply_property bq257xx_power_supply_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_VOLTAGE_MIN, POWER_SUPPLY_PROP_USB_TYPE, }; /** * bq257xx_property_is_writeable() - Check if a property is writeable * @psy: power supply device * @prop: power supply property to check * * Determines which power supply properties can be written to. Only * charge current limit, charge voltage limit, and input current * limit are writeable. * * Return: Returns 1 if property is writeable, 0 otherwise. */ static int bq257xx_property_is_writeable(struct power_supply *psy, enum power_supply_property prop) { switch (prop) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return true; default: return false; } } /** * bq257xx_external_power_changed() - Handler for external power change * @psy: Power supply data * * When the external power into the charger is changed, check the USB * type so that it can be reported. Additionally, update the max input * current and max charging current to the value reported if it is a * USB PD charger, otherwise use the default value. Note that each time * a charger is removed the max charge current register is erased, so * it must be set again each time the input changes or the device will * not charge. */ static void bq257xx_external_power_changed(struct power_supply *psy) { struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); union power_supply_propval val; int ret; int imax = pdata->iindpm_max; pdata->chip->bq257xx_get_state(pdata); pdata->supplied = power_supply_am_i_supplied(pdata->charger); if (pdata->supplied < 0) return; if (pdata->supplied == 0) goto out; ret = power_supply_get_property_from_supplier(psy, POWER_SUPPLY_PROP_USB_TYPE, &val); if (ret) return; pdata->usb_type = val.intval; if ((pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD) || (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_DRP) || (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_PPS)) { ret = power_supply_get_property_from_supplier(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &val); if (ret) return; if (val.intval) imax = val.intval; } if (pdata->supplied) { pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); pdata->chip->bq257xx_set_iindpm(pdata, imax); pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); } out: power_supply_changed(psy); } /** * bq257xx_irq_handler_thread() - Handle charger interrupt * @irq: interrupt number * @private: pointer to driver private data * * Thread handler for charger interrupts. Triggers re-evaluation of * external power status and updates power supply state in response * to charger events. * * Return: Returns IRQ_HANDLED if interrupt was processed. */ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) { struct bq257xx_chg *pdata = private; bq257xx_external_power_changed(pdata->charger); return IRQ_HANDLED; } static const struct power_supply_desc bq257xx_power_supply_desc = { .name = "bq257xx-charger", .type = POWER_SUPPLY_TYPE_USB, .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | BIT(POWER_SUPPLY_USB_TYPE_PD) | BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) | BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = bq257xx_power_supply_props, .num_properties = ARRAY_SIZE(bq257xx_power_supply_props), .get_property = bq257xx_get_charger_property, .set_property = bq257xx_set_charger_property, .property_is_writeable = bq257xx_property_is_writeable, .external_power_changed = bq257xx_external_power_changed, }; static const struct bq257xx_chip_info bq25703_chip_info = { .default_iindpm_uA = BQ25703_IINDPM_DEFAULT_UA, .bq257xx_hw_init = &bq25703_hw_init, .bq257xx_hw_shutdown = &bq25703_hw_shutdown, .bq257xx_get_state = &bq25703_get_state, .bq257xx_get_ichg = &bq25703_get_ichg_cur, .bq257xx_set_ichg = &bq25703_set_ichg_cur, .bq257xx_get_vbatreg = &bq25703_get_chrg_volt, .bq257xx_set_vbatreg = &bq25703_set_chrg_volt, .bq257xx_get_iindpm = &bq25703_get_iindpm, .bq257xx_set_iindpm = &bq25703_set_iindpm, .bq257xx_get_cur = &bq25703_get_cur, .bq257xx_get_vbat = &bq25703_get_vbat, .bq257xx_get_min_vsys = &bq25703_get_min_vsys, }; static const struct bq257xx_chip_info bq25792_chip_info = { .default_iindpm_uA = BQ25792_IINDPM_DEFAULT_UA, .bq257xx_hw_init = &bq25792_hw_init, .bq257xx_hw_shutdown = &bq25792_hw_shutdown, .bq257xx_get_state = &bq25792_get_state, .bq257xx_get_ichg = &bq25792_get_ichg_cur, .bq257xx_set_ichg = &bq25792_set_ichg_cur, .bq257xx_get_vbatreg = &bq25792_get_chrg_volt, .bq257xx_set_vbatreg = &bq25792_set_chrg_volt, .bq257xx_get_iindpm = &bq25792_get_iindpm, .bq257xx_set_iindpm = &bq25792_set_iindpm, .bq257xx_get_cur = &bq25792_get_cur, .bq257xx_get_vbat = &bq25792_get_vbat, .bq257xx_get_min_vsys = &bq25792_get_min_vsys, }; /** * bq257xx_parse_dt() - Parse the device tree for required properties * @pdata: driver platform data * @psy_cfg: power supply config data * @dev: device struct * * Read the device tree to identify the minimum system voltage, the * maximum charge current, the maximum charge voltage, and the maximum * input current. * * Return: Returns 0 on success or error code on error. */ static int bq257xx_parse_dt(struct bq257xx_chg *pdata, struct power_supply_config *psy_cfg, struct device *dev) { struct power_supply_battery_info *bat_info; int ret; ret = power_supply_get_battery_info(pdata->charger, &bat_info); if (ret) return dev_err_probe(dev, ret, "Unable to get battery info\n"); if ((bat_info->voltage_min_design_uv <= 0) || (bat_info->constant_charge_voltage_max_uv <= 0) || (bat_info->constant_charge_current_max_ua <= 0)) return dev_err_probe(dev, -EINVAL, "Required bat info missing or invalid\n"); pdata->vsys_min = bat_info->voltage_min_design_uv; pdata->vbat_max = bat_info->constant_charge_voltage_max_uv; pdata->ichg_max = bat_info->constant_charge_current_max_ua; power_supply_put_battery_info(pdata->charger, bat_info); ret = device_property_read_u32(dev, "input-current-limit-microamp", &pdata->iindpm_max); if (ret) pdata->iindpm_max = pdata->chip->default_iindpm_uA; return 0; } /** * bq257xx_charger_probe() - Probe routine for charger platform device * @pdev: platform device * * Probe the charger device, allocate driver data structure, select the * appropriate chip-specific function pointers, register the power supply, * parse device tree properties for battery limits, initialize hardware, * and set up the interrupt handler if available. * * Return: Returns 0 on success or error code on failure. */ static int bq257xx_charger_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent); struct bq257xx_chg *pdata; struct power_supply_config psy_cfg = { }; int ret; device_set_of_node_from_dev(dev, pdev->dev.parent); pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; pdata->bq = bq; switch (bq->type) { case BQ25703A: pdata->chip = &bq25703_chip_info; break; case BQ25792: pdata->chip = &bq25792_chip_info; break; default: return dev_err_probe(dev, -EINVAL, "Unknown chip type\n"); } platform_set_drvdata(pdev, pdata); psy_cfg.drv_data = pdata; psy_cfg.fwnode = dev_fwnode(dev); pdata->charger = devm_power_supply_register(dev, &bq257xx_power_supply_desc, &psy_cfg); if (IS_ERR(pdata->charger)) return dev_err_probe(dev, PTR_ERR(pdata->charger), "Power supply register charger failed\n"); ret = bq257xx_parse_dt(pdata, &psy_cfg, dev); if (ret) return ret; ret = pdata->chip->bq257xx_hw_init(pdata); if (ret) return dev_err_probe(dev, ret, "Cannot initialize the charger\n"); platform_set_drvdata(pdev, pdata); if (bq->client->irq) { ret = devm_request_threaded_irq(dev, bq->client->irq, NULL, bq257xx_irq_handler_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&bq->client->dev), pdata); } return ret; } /** * bq257xx_charger_shutdown() - Shutdown routine for charger platform device * @pdev: platform device * * Called during system shutdown to perform charger cleanup, including * disabling watchdog timers or other chip-specific shutdown procedures. */ static void bq257xx_charger_shutdown(struct platform_device *pdev) { struct bq257xx_chg *pdata = platform_get_drvdata(pdev); pdata->chip->bq257xx_hw_shutdown(pdata); } static struct platform_driver bq257xx_chg_driver = { .driver = { .name = "bq257xx-charger", }, .probe = bq257xx_charger_probe, .shutdown = bq257xx_charger_shutdown, }; module_platform_driver(bq257xx_chg_driver); MODULE_DESCRIPTION("bq257xx charger driver"); MODULE_AUTHOR("Chris Morgan "); MODULE_LICENSE("GPL");