Loading drivers/power/supply/qcom/Kconfig +12 −0 Original line number Diff line number Diff line Loading @@ -101,4 +101,16 @@ config SMB358_CHARGER The driver supports charger enable/disable. The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. config QTI_QBG tristate "QTI Battery Gauge" depends on MFD_SPMI_PMIC && IIO help Say Y here to enable the Qualcomm Technologies, Inc. Battery Gauge driver which uses the periodic samples of the battery voltage and current to determine the battery state-of-charge (SOC) and supports other battery management features. To compile this driver as a module, choose M here: the module will be called qti-qbg-main. endif drivers/power/supply/qcom/Makefile +2 −0 Original line number Diff line number Diff line Loading @@ -13,3 +13,5 @@ qcom-smb1355-charger-y += smb1355-charger.o pmic-voter.o obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o obj-$(CONFIG_SMB1390_CHARGE_PUMP_PSY) += qcom-smb1390-charger.o qcom-smb1390-charger-y += smb1390-charger-psy.o pmic-voter.o obj-$(CONFIG_QTI_QBG) += qti-qbg-main.o qti-qbg-main-y += qti-qbg.o qbg-sdam.o qbg-battery-profile.o battery-profile-loader.o drivers/power/supply/qcom/qbg-battery-profile.c 0 → 100644 +589 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "QBG-K: %s: " fmt, __func__ #include <linux/cdev.h> #include <linux/device.h> #include <linux/fcntl.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <uapi/linux/qbg-profile.h> #include "qbg-battery-profile.h" static int table_temperatures[] = { -20, -10, 0, 10, 25, 40, 50 }; static int qbg_battery_data_open(struct inode *inode, struct file *file) { struct qbg_battery_data *battery = container_of(inode->i_cdev, struct qbg_battery_data, battery_cdev); pr_debug("battery_data device opened\n"); file->private_data = battery; return 0; } static long qbg_battery_data_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct qbg_battery_data *battery = file->private_data; struct battery_config __user *profile_user; struct battery_profile_table bp_table; struct battery_profile_table __user *bp_table_user; struct battery_data_table *table; int rc = 0, table_index, table_type; if (!battery->profile_node) { pr_err("Invalid battery profile node\n"); return -EINVAL; } if (!arg) { pr_err("Invalid user pointer\n"); return -EINVAL; } switch (cmd) { case BPIOCXBP: profile_user = (struct battery_config __user *)arg; if (copy_to_user(profile_user, &battery->bp, sizeof(battery->bp))) { pr_err("Failed to copy battery profile to user\n"); return -EFAULT; } break; case BPIOCXBPTABLE: bp_table_user = (struct battery_profile_table __user *)arg; if (copy_from_user(&bp_table, bp_table_user, sizeof(bp_table))) { pr_err("Failed to copy battery_profile_table from user\n"); return -EFAULT; } table_index = bp_table.table_index; table_type = bp_table.table_type; if ((table_type != CHARGE_TABLE) && (table_type != DISCHARGE_TABLE)) return -EFAULT; if (((table_type == CHARGE_TABLE) && (table_index >= battery->num_ctables)) || ((table_type == DISCHARGE_TABLE) && (table_index >= battery->num_dtables))) return -EFAULT; table = (table_type == CHARGE_TABLE) ? battery->bp_charge_tables[table_index] : battery->bp_discharge_tables[table_index]; if (copy_to_user(bp_table.table, table, sizeof(*table))) { pr_err("Failed to copy battery profile table to user\n"); return -EFAULT; } pr_debug("Copied %s table %d to user\n", table_type == CHARGE_TABLE ? "Charge" : "Discharge", table_index); break; default: pr_err_ratelimited("IOCTL %u not supported\n", cmd); rc = -EINVAL; break; } return rc; } static int qbg_battery_data_release(struct inode *inode, struct file *file) { pr_debug("battery_data device closed\n"); return 0; } static const struct file_operations qbg_battery_data_fops = { .owner = THIS_MODULE, .open = qbg_battery_data_open, .unlocked_ioctl = qbg_battery_data_ioctl, .compat_ioctl = qbg_battery_data_ioctl, .release = qbg_battery_data_release, }; #define QBG_PON_TEMPERATURE 25 static int qbg_parse_table0(struct device_node *profile_node, char *table_name, struct battery_data_table0 *table) { struct device_node *node; int rc = 0, temperature; node = of_find_node_by_name(profile_node, table_name); if (!node) { pr_err("%s not found\n", table_name); return -ENODEV; } rc = of_property_read_s32(node, "qcom,temperature", &temperature); if (rc < 0) { pr_err("Failed to read %s temperature\n", table_name); goto out; } if (temperature != QBG_PON_TEMPERATURE) { pr_err("Invalid table0 found, temperature:%d\n", temperature); rc = -EINVAL; goto out; } rc = of_property_count_elems_of_size(node, "qcom,soc", sizeof(int)); if (rc < 0) { pr_err("Failed to get soc-length for %s, rc=%d\n", table_name, rc); goto out; } table->soc_length = rc; table->soc = kcalloc(table->soc_length, sizeof(*table->soc), GFP_KERNEL); if (!table->soc) { rc = -ENOMEM; goto out; } rc = of_property_read_u32_array(node, "qcom,soc", table->soc, table->soc_length); if (rc < 0) { pr_err("Failed to read qcom,soc\n"); rc = -EINVAL; goto cleanup_soc; } rc = of_property_count_elems_of_size(node, "qcom,ocv", sizeof(int)); if (rc < 0) { pr_err("Failed to get ocv-length for %s, rc=%d\n", table_name, rc); goto cleanup_soc; } table->ocv_length = rc; table->ocv = kcalloc(table->ocv_length, sizeof(*table->ocv), GFP_KERNEL); if (!table->ocv) { rc = -ENOMEM; goto cleanup_soc; } rc = of_property_read_u32_array(node, "qcom,ocv", table->ocv, table->ocv_length); if (rc < 0) { pr_err("Failed to read qcom,ocv\n"); rc = -EINVAL; goto cleanup_ocv; } return 0; cleanup_ocv: kfree(table->ocv); cleanup_soc: kfree(table->soc); out: of_node_put(node); return rc; } static int qbg_parse_table(struct device_node *profile_node, int index, char *table_name, struct battery_data_table *bp_table) { struct device_node *node; struct property *prop; const __be32 *data; int rc = 0, j, k, temperature; u32 rows, cols; node = of_find_node_by_name(profile_node, table_name); if (!node) { pr_err("%s not found\n", table_name); return -ENODEV; } rc = of_property_read_s32(node, "qcom,temperature", &temperature); if (rc < 0) { pr_err("Failed to read %s temperature\n", table_name); goto out; } if (temperature != table_temperatures[index]) { pr_err("Invalid table at wrong index %d temperature:%d\n", index, temperature); rc = -EINVAL; goto out; } rc = of_property_read_u32(node, "qcom,nrows", &rows); if (rc < 0) { pr_err("Failed to read %s\n", table_name); goto out; } bp_table->nrows = rows; rc = of_property_read_u32(node, "qcom,ncols", &cols); if (rc < 0) { pr_err("Failed to read %s\n", table_name); goto out; } bp_table->ncols = cols; rc = of_property_read_u32_array(node, "qcom,conv-factor", bp_table->unit_conv_factor, MAX_BP_LUT_COLS); if (rc < 0) { pr_err("Failed to read conv-factor\n"); rc = -EINVAL; goto out; } prop = of_find_property(node, "qcom,data", NULL); if (!prop) { pr_err("Failed to find lut-data\n"); rc = -EINVAL; goto out; } data = prop->value; for (j = 0; j < bp_table->nrows; j++) { for (k = 0; k < bp_table->ncols; k++) bp_table->table[j][k] = be32_to_cpup(data++); } pr_debug("Profile %s parsed rows=%d cols=%d\n", table_name, bp_table->nrows, bp_table->ncols); out: of_node_put(node); return rc; } static int qbg_parse_u32_dt_array(struct device_node *node, const char *prop_name, int *buf, int len) { int rc; rc = of_property_count_elems_of_size(node, prop_name, sizeof(u32)); if (rc < 0) { pr_err("Property %s not found, rc=%d\n", prop_name, rc); return rc; } else if (rc != len) { pr_err("Incorrect length %d for %s, rc=%d\n", len, prop_name, rc); return -EINVAL; } rc = of_property_read_u32_array(node, prop_name, buf, len); if (rc < 0) { pr_err("Error in reading %s, rc=%d\n", prop_name, rc); return rc; } return 0; } static int qbg_parse_battery_profile(struct qbg_battery_data *battery) { struct device_node *node = battery->profile_node; struct device_node *child; struct battery_config *bp = &battery->bp; char buf[32]; const char *battery_name = NULL; int rc, i = 0; u32 temp[2]; rc = of_property_read_string(node, "qcom,battery-type", &battery_name); if (rc < 0) { pr_err("Failed to get battery type, rc=%d\n", rc); return rc; } strlcpy(bp->bp_profile_name, battery_name, MAX_PROFILE_NAME_LENGTH); rc = of_property_read_u32(node, "qcom,batt-id-kohm", &bp->bp_batt_id); if (rc < 0) { pr_err("Failed to get battery id, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,capacity", &bp->capacity); if (rc < 0) { pr_err("Failed to get battery capacity, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,checksum", &bp->bp_checksum); if (rc < 0) { pr_err("Failed to get checksum, rc=%d\n", rc); return rc; } rc = qbg_parse_u32_dt_array(node, "qcom,soh-range", temp, 2); if (rc < 0) return rc; if (temp[0] > 100 || temp[1] > 100 || (temp[0] > temp[1])) { pr_err("Incorrect SOH range [%d %d]\n", temp[0], temp[1]); return -ERANGE; } bp->soh_range_low = temp[0]; bp->soh_range_high = temp[1]; rc = qbg_parse_u32_dt_array(node, "qcom,battery-impedance", temp, 2); if (rc < 0) return rc; bp->normal_impedance = temp[0]; bp->aged_impedance = temp[1]; rc = qbg_parse_u32_dt_array(node, "qcom,battery-capacity", temp, 2); if (rc < 0) return rc; bp->normal_capacity = temp[0]; bp->aged_capacity = temp[1]; rc = of_property_read_u32(node, "qcom,recharge-soc-delta", &bp->recharge_soc_delta); if (rc < 0) { pr_err("Failed to get recharege soc delta, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,recharge-vflt-delta", &bp->recharge_vflt_delta); if (rc < 0) { pr_err("Failed to get recharge vflt delta, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,recharge-iterm-ma", &bp->recharge_iterm); if (rc < 0) { pr_err("Failed to get recharge iterm, rc=%d\n", rc); return rc; } for_each_available_child_of_node(battery->profile_node, child) { if (of_node_name_prefix(child, "qcom,bp-c-table")) battery->num_ctables++; else if (of_node_name_prefix(child, "qcom,bp-d-table")) battery->num_dtables++; } if (!battery->num_ctables || !battery->num_dtables) { pr_err("ctable or dtable missing\n"); return -EINVAL; } /* Battery profile contains additional table (table0) */ if (battery->num_ctables) battery->num_ctables--; if (battery->num_dtables) battery->num_dtables--; battery->bp_discharge_tables = kcalloc(battery->num_dtables, sizeof(*battery->bp_discharge_tables), GFP_KERNEL); if (!battery->bp_discharge_tables) return -ENOMEM; battery->bp_charge_tables = kcalloc(battery->num_ctables, sizeof(*battery->bp_charge_tables), GFP_KERNEL); if (!battery->bp_charge_tables) return -ENOMEM; /* Parse c-table-0 */ scnprintf(buf, sizeof(buf), "qcom,bp-c-table-0"); rc = qbg_parse_table0(battery->profile_node, buf, &battery->table0[0]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); return rc; } /* Parse d-table-0 */ scnprintf(buf, sizeof(buf), "qcom,bp-d-table-0"); rc = qbg_parse_table0(battery->profile_node, buf, &battery->table0[1]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); goto cleanup_ctable; } for (i = 0; i < battery->num_dtables; i++) { battery->bp_discharge_tables[i] = kzalloc( sizeof(*battery->bp_discharge_tables[i]), GFP_KERNEL); if (!battery->bp_discharge_tables[i]) { rc = -ENOMEM; goto cleanup_ctable; } scnprintf(buf, sizeof(buf), "qcom,bp-d-table-%d", i + 1); rc = qbg_parse_table(battery->profile_node, i, buf, battery->bp_discharge_tables[i]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); goto cleanup_dtable; } } for (i = 0; i < battery->num_ctables; i++) { battery->bp_charge_tables[i] = kzalloc( sizeof(*battery->bp_charge_tables[i]), GFP_KERNEL); if (!battery->bp_charge_tables[i]) { rc = -ENOMEM; goto cleanup_ctable; } scnprintf(buf, sizeof(buf), "qcom,bp-c-table-%d", i + 1); rc = qbg_parse_table(battery->profile_node, i, buf, battery->bp_charge_tables[i]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); goto cleanup_ctable; } } return 0; cleanup_ctable: for (; i > 0; i--) kfree(battery->bp_charge_tables[i]); i = battery->num_dtables; cleanup_dtable: for (; i > 0; i--) kfree(battery->bp_discharge_tables[i]); return rc; } int qbg_batterydata_init(struct device_node *profile_node, struct qbg_battery_data *battery) { int rc = 0; /* char device to access battery-profile data */ rc = alloc_chrdev_region(&battery->dev_no, 0, 1, "qbg_battery"); if (rc < 0) { pr_err("Failed to allocate chrdev, rc=%d\n", rc); goto free_battery; } cdev_init(&battery->battery_cdev, &qbg_battery_data_fops); rc = cdev_add(&battery->battery_cdev, battery->dev_no, 1); if (rc) { pr_err("Failed to add battery_cdev, rc=%d\n", rc); goto unregister_chrdev; } battery->battery_class = class_create(THIS_MODULE, "qbg_battery"); if (IS_ERR_OR_NULL(battery->battery_class)) { pr_err("Failed to create qbg-battery class (%d)\n", PTR_ERR(battery->battery_class)); rc = -ENODEV; goto delete_cdev; } battery->battery_device = device_create(battery->battery_class, NULL, battery->dev_no, NULL, "qbg_battery"); if (IS_ERR_OR_NULL(battery->battery_device)) { pr_err("Failed to create battery_device device (%d)\n", PTR_ERR(battery->battery_device)); rc = -ENODEV; goto destroy_class; } battery->profile_node = profile_node; /* parse the battery profile */ rc = qbg_parse_battery_profile(battery); if (rc < 0) { pr_err("Failed to parse battery profile, rc=%d\n", rc); goto destroy_device; } pr_info("QBG Battery-profile loaded, id:%d name:%s\n", battery->bp.bp_batt_id, battery->bp.bp_profile_name); return 0; destroy_device: device_destroy(battery->battery_class, battery->dev_no); destroy_class: class_destroy(battery->battery_class); delete_cdev: cdev_del(&battery->battery_cdev); unregister_chrdev: unregister_chrdev_region(battery->dev_no, 1); free_battery: kfree(battery); return rc; } void qbg_batterydata_exit(struct qbg_battery_data *battery) { int i; if (!battery) { pr_err("Battery cannot be null\n"); return; } /* unregister the device node */ device_destroy(battery->battery_class, battery->dev_no); class_destroy(battery->battery_class); cdev_del(&battery->battery_cdev); unregister_chrdev_region(battery->dev_no, 1); /* delete all the battery profile memory */ for (i = 0; i < battery->num_ctables; i++) kfree(battery->bp_charge_tables[i]); for (i = 0; i < battery->num_dtables; i++) kfree(battery->bp_discharge_tables[i]); } int qbg_lookup_soc_ocv(struct qbg_battery_data *battery, int *pon_soc, int ocv, bool charging) { struct battery_data_table0 *lut; int i; *pon_soc = -EINVAL; lut = charging ? &battery->table0[0] : &battery->table0[1]; for (i = 0; i < lut->ocv_length; i++) { if (ocv == lut->ocv[i]) { *pon_soc = lut->soc[i]; break; } else if (is_between(lut->ocv[i], lut->ocv[i+1], ocv)) { *pon_soc = (lut->soc[i] + lut->soc[i+1]) / 2; break; } } if (*pon_soc == -EINVAL) { pr_debug("%d ocv wasn't found in the LUT returning 100%\n", ocv); *pon_soc = 10000; } return 0; } drivers/power/supply/qcom/qbg-battery-profile.h 0 → 100644 +55 −0 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. */ #ifndef __QBG_BATTERY_PROFILE_H__ #define __QBG_BATTERY_PROFILE_H__ #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ && (value) >= (right)) \ || ((left) <= (right) && (left) <= (value) \ && (value) <= (right))) struct battery_data_table0 { int soc_length; int ocv_length; int *soc; int *ocv; }; /** * struct qbg_battery_data - Structure for QBG battery data * @dev_no: Device number for QBG battery char device * @profile_node: Pointer to devicetree node handle of profile * @battery_class: Pointer to battery class * @battery_device: Pointer to battery class device * @battery_cdev: QBG battery char device * @bp: QBG battery configuration * @bp_charge_tables: Charge tables in QBG battery profile * @bp_discharge_tables: Discharge tables in QBG battery profile * @table0: Two tables for PON OCV to SOC mapping * @num_ctables: Number of charge tables * @num_dtables: Number of discharge tables */ struct qbg_battery_data { dev_t dev_no; struct device_node *profile_node; struct class *battery_class; struct device *battery_device; struct cdev battery_cdev; struct battery_config bp; struct battery_data_table **bp_charge_tables; struct battery_data_table **bp_discharge_tables; struct battery_data_table0 table0[2]; int num_ctables; int num_dtables; }; int qbg_batterydata_init(struct device_node *node, struct qbg_battery_data *battery); void qbg_batterydata_exit(struct qbg_battery_data *battery); int qbg_lookup_soc_ocv(struct qbg_battery_data *battery, int *pon_soc, int ocv, bool charging); #endif /* __QBG_BATTERY_PROFILE_H__ */ drivers/power/supply/qcom/qbg-core.h 0 → 100644 +178 −0 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. */ #ifndef __QBG_CORE_H__ #define __QBG_CORE_H__ #define qbg_dbg(chip, reason, fmt, ...) \ do { \ if (*chip->debug_mask & (reason)) \ pr_err(fmt, ##__VA_ARGS__); \ else \ pr_debug(fmt, ##__VA_ARGS__); \ } while (0) enum debug_mask { QBG_DEBUG_BUS_READ = BIT(0), QBG_DEBUG_BUS_WRITE = BIT(1), QBG_DEBUG_FIFO = BIT(2), QBG_DEBUG_IRQ = BIT(3), QBG_DEBUG_DEVICE = BIT(4), QBG_DEBUG_PROFILE = BIT(5), QBG_DEBUG_SOC = BIT(6), QBG_DEBUG_SDAM = BIT(7), QBG_DEBUG_STATUS = BIT(8), QBG_DEBUG_PON = BIT(9), }; enum qbg_sdam { SDAM_CTRL0 = 0, SDAM_CTRL1, SDAM_DATA0, SDAM_DATA1, SDAM_DATA2, SDAM_DATA3, SDAM_DATA4, }; /** * struct qti_qbg - Structure for QTI QBG device * @dev: Pointer to QBG device structure * @regmap: Pointer to regmap structure * @qbg_psy: Pointer to QBG power supply * @batt_psy: Pointer to Battery power supply * @qbg_class: Pointer to QBG class * @qbg_device: Pointer to QBG device * @qbg_cdev: Member for QBG char device * @dev_no: Device number for QBG char device * @batt_node: Pointer to battery device node * @indio_dev: Pointer to QBG IIO device * @iio_chan: Pointer to QBG IIO channels * @sdam: Pointer to multiple QBG SDAMs * @fifo: QBG FIFO data * @essential_params: QBG essential params * @status_change_work: Power supply status change work * @udata_work: User space data change work * @nb: Power supply notifier block * @kdata: QBG Kernel space data structure * @udata: QBG user space data structure * @battery: Pointer to QBG battery data structure * @fifo_lock: Lock for reading FIFO data * @data_lock: Lock for reading kdata from QBG char device * @batt_id_chan: IIO channel to read battery ID * @batt_temp_chan: IIO channel to read battery temperature * @rtc: RTC device to read real time * @last_fast_char_time: Timestamp of last time QBG in fast char mode * @qbg_wait_q: Wait queue for reads to QBG char device * @irq_name: QBG interrupt name * @batt_type_str: String array denoting battery type * @irq: QBG irq number * @base: Base address of QBG HW * @num_sdams: Number of sdams used for QBG * @batt_id_ohm: Battery resistance in ohms * @debug_mask: Debug mask to enable/disable debug prints * @pon_ocv: Power-on OCV of QBG device * @pon_ibat: Power-on current of QBG device * @pon_soc: Power-on SOC of QBG device * @soc: Monotonic SOC of QBG device * @batt_soc: Battery SOC * @sys_soc: Battery system SOC * @esr: Battery equivalent series resistance * @ocv_uv: Battery open circuit voltage * @voltage_now: Battery voltage * @current_now: Battery current * @tbat: Battery temperature * @charge_cycle_count: Battery charge cycle count * @nominal_capacity: Battery nominal capacity * @learned_capacity: Battery learned capacity * @ttf: Time to full * @tte: Time to empty * @soh: Battery state of health * @charge_type: Charging type * @float_volt_uv: Battery maximum voltage * @fastchg_curr_ma: Battery fast charge current * @vbat_cutoff_mv: Battery cutoff voltage * @ibat_cutoff_ma: Battery cutoff current * @vph_min_mv: Battery minimum power * @iterm_ma: Charge Termination current * @rconn_mohm: Battery connector resistance * @previous_ep_time: Previous timestamp when essential params stored * @current_time: Current time stamp * @profile_loaded: Flag to indicated battery profile is loaded * @battery_missing: Flag to indicate battery is missing * @data_ready: Flag to indicate QBG data is ready * @in_fast_char: Flag to indicate QBG is in fast char mode */ struct qti_qbg { struct device *dev; struct regmap *regmap; struct power_supply *qbg_psy; struct power_supply *batt_psy; struct class *qbg_class; struct device *qbg_device; struct cdev qbg_cdev; dev_t dev_no; struct device_node *batt_node; struct iio_dev *indio_dev; struct iio_chan_spec *iio_chan; struct nvmem_device **sdam; struct fifo_data fifo[MAX_FIFO_COUNT]; struct qbg_essential_params essential_params; struct work_struct status_change_work; struct work_struct udata_work; struct notifier_block nb; struct qbg_kernel_data kdata; struct qbg_user_data udata; struct qbg_battery_data *battery; struct mutex fifo_lock; struct mutex data_lock; struct iio_channel *batt_id_chan; struct iio_channel *batt_temp_chan; struct rtc_device *rtc; ktime_t last_fast_char_time; wait_queue_head_t qbg_wait_q; const char *irq_name; const char *batt_type_str; int irq; u32 base; u32 sdam_base; u32 num_sdams; u32 num_data_sdams; u32 batt_id_ohm; u32 *debug_mask; int pon_ocv; int pon_ibat; int pon_tbat; int pon_soc; int soc; int batt_soc; int sys_soc; int esr; int ocv_uv; int voltage_now; int current_now; int tbat; int charge_cycle_count; int nominal_capacity; int learned_capacity; int ttf; int tte; int soh; int charge_type; int float_volt_uv; int fastchg_curr_ma; int vbat_cutoff_mv; int ibat_cutoff_ma; int vph_min_mv; int iterm_ma; int rconn_mohm; unsigned long previous_ep_time; unsigned long current_time; bool profile_loaded; bool battery_missing; bool data_ready; bool in_fast_char; }; #endif /* __QBG_CORE_H__ */ Loading
drivers/power/supply/qcom/Kconfig +12 −0 Original line number Diff line number Diff line Loading @@ -101,4 +101,16 @@ config SMB358_CHARGER The driver supports charger enable/disable. The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. config QTI_QBG tristate "QTI Battery Gauge" depends on MFD_SPMI_PMIC && IIO help Say Y here to enable the Qualcomm Technologies, Inc. Battery Gauge driver which uses the periodic samples of the battery voltage and current to determine the battery state-of-charge (SOC) and supports other battery management features. To compile this driver as a module, choose M here: the module will be called qti-qbg-main. endif
drivers/power/supply/qcom/Makefile +2 −0 Original line number Diff line number Diff line Loading @@ -13,3 +13,5 @@ qcom-smb1355-charger-y += smb1355-charger.o pmic-voter.o obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o obj-$(CONFIG_SMB1390_CHARGE_PUMP_PSY) += qcom-smb1390-charger.o qcom-smb1390-charger-y += smb1390-charger-psy.o pmic-voter.o obj-$(CONFIG_QTI_QBG) += qti-qbg-main.o qti-qbg-main-y += qti-qbg.o qbg-sdam.o qbg-battery-profile.o battery-profile-loader.o
drivers/power/supply/qcom/qbg-battery-profile.c 0 → 100644 +589 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "QBG-K: %s: " fmt, __func__ #include <linux/cdev.h> #include <linux/device.h> #include <linux/fcntl.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <uapi/linux/qbg-profile.h> #include "qbg-battery-profile.h" static int table_temperatures[] = { -20, -10, 0, 10, 25, 40, 50 }; static int qbg_battery_data_open(struct inode *inode, struct file *file) { struct qbg_battery_data *battery = container_of(inode->i_cdev, struct qbg_battery_data, battery_cdev); pr_debug("battery_data device opened\n"); file->private_data = battery; return 0; } static long qbg_battery_data_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct qbg_battery_data *battery = file->private_data; struct battery_config __user *profile_user; struct battery_profile_table bp_table; struct battery_profile_table __user *bp_table_user; struct battery_data_table *table; int rc = 0, table_index, table_type; if (!battery->profile_node) { pr_err("Invalid battery profile node\n"); return -EINVAL; } if (!arg) { pr_err("Invalid user pointer\n"); return -EINVAL; } switch (cmd) { case BPIOCXBP: profile_user = (struct battery_config __user *)arg; if (copy_to_user(profile_user, &battery->bp, sizeof(battery->bp))) { pr_err("Failed to copy battery profile to user\n"); return -EFAULT; } break; case BPIOCXBPTABLE: bp_table_user = (struct battery_profile_table __user *)arg; if (copy_from_user(&bp_table, bp_table_user, sizeof(bp_table))) { pr_err("Failed to copy battery_profile_table from user\n"); return -EFAULT; } table_index = bp_table.table_index; table_type = bp_table.table_type; if ((table_type != CHARGE_TABLE) && (table_type != DISCHARGE_TABLE)) return -EFAULT; if (((table_type == CHARGE_TABLE) && (table_index >= battery->num_ctables)) || ((table_type == DISCHARGE_TABLE) && (table_index >= battery->num_dtables))) return -EFAULT; table = (table_type == CHARGE_TABLE) ? battery->bp_charge_tables[table_index] : battery->bp_discharge_tables[table_index]; if (copy_to_user(bp_table.table, table, sizeof(*table))) { pr_err("Failed to copy battery profile table to user\n"); return -EFAULT; } pr_debug("Copied %s table %d to user\n", table_type == CHARGE_TABLE ? "Charge" : "Discharge", table_index); break; default: pr_err_ratelimited("IOCTL %u not supported\n", cmd); rc = -EINVAL; break; } return rc; } static int qbg_battery_data_release(struct inode *inode, struct file *file) { pr_debug("battery_data device closed\n"); return 0; } static const struct file_operations qbg_battery_data_fops = { .owner = THIS_MODULE, .open = qbg_battery_data_open, .unlocked_ioctl = qbg_battery_data_ioctl, .compat_ioctl = qbg_battery_data_ioctl, .release = qbg_battery_data_release, }; #define QBG_PON_TEMPERATURE 25 static int qbg_parse_table0(struct device_node *profile_node, char *table_name, struct battery_data_table0 *table) { struct device_node *node; int rc = 0, temperature; node = of_find_node_by_name(profile_node, table_name); if (!node) { pr_err("%s not found\n", table_name); return -ENODEV; } rc = of_property_read_s32(node, "qcom,temperature", &temperature); if (rc < 0) { pr_err("Failed to read %s temperature\n", table_name); goto out; } if (temperature != QBG_PON_TEMPERATURE) { pr_err("Invalid table0 found, temperature:%d\n", temperature); rc = -EINVAL; goto out; } rc = of_property_count_elems_of_size(node, "qcom,soc", sizeof(int)); if (rc < 0) { pr_err("Failed to get soc-length for %s, rc=%d\n", table_name, rc); goto out; } table->soc_length = rc; table->soc = kcalloc(table->soc_length, sizeof(*table->soc), GFP_KERNEL); if (!table->soc) { rc = -ENOMEM; goto out; } rc = of_property_read_u32_array(node, "qcom,soc", table->soc, table->soc_length); if (rc < 0) { pr_err("Failed to read qcom,soc\n"); rc = -EINVAL; goto cleanup_soc; } rc = of_property_count_elems_of_size(node, "qcom,ocv", sizeof(int)); if (rc < 0) { pr_err("Failed to get ocv-length for %s, rc=%d\n", table_name, rc); goto cleanup_soc; } table->ocv_length = rc; table->ocv = kcalloc(table->ocv_length, sizeof(*table->ocv), GFP_KERNEL); if (!table->ocv) { rc = -ENOMEM; goto cleanup_soc; } rc = of_property_read_u32_array(node, "qcom,ocv", table->ocv, table->ocv_length); if (rc < 0) { pr_err("Failed to read qcom,ocv\n"); rc = -EINVAL; goto cleanup_ocv; } return 0; cleanup_ocv: kfree(table->ocv); cleanup_soc: kfree(table->soc); out: of_node_put(node); return rc; } static int qbg_parse_table(struct device_node *profile_node, int index, char *table_name, struct battery_data_table *bp_table) { struct device_node *node; struct property *prop; const __be32 *data; int rc = 0, j, k, temperature; u32 rows, cols; node = of_find_node_by_name(profile_node, table_name); if (!node) { pr_err("%s not found\n", table_name); return -ENODEV; } rc = of_property_read_s32(node, "qcom,temperature", &temperature); if (rc < 0) { pr_err("Failed to read %s temperature\n", table_name); goto out; } if (temperature != table_temperatures[index]) { pr_err("Invalid table at wrong index %d temperature:%d\n", index, temperature); rc = -EINVAL; goto out; } rc = of_property_read_u32(node, "qcom,nrows", &rows); if (rc < 0) { pr_err("Failed to read %s\n", table_name); goto out; } bp_table->nrows = rows; rc = of_property_read_u32(node, "qcom,ncols", &cols); if (rc < 0) { pr_err("Failed to read %s\n", table_name); goto out; } bp_table->ncols = cols; rc = of_property_read_u32_array(node, "qcom,conv-factor", bp_table->unit_conv_factor, MAX_BP_LUT_COLS); if (rc < 0) { pr_err("Failed to read conv-factor\n"); rc = -EINVAL; goto out; } prop = of_find_property(node, "qcom,data", NULL); if (!prop) { pr_err("Failed to find lut-data\n"); rc = -EINVAL; goto out; } data = prop->value; for (j = 0; j < bp_table->nrows; j++) { for (k = 0; k < bp_table->ncols; k++) bp_table->table[j][k] = be32_to_cpup(data++); } pr_debug("Profile %s parsed rows=%d cols=%d\n", table_name, bp_table->nrows, bp_table->ncols); out: of_node_put(node); return rc; } static int qbg_parse_u32_dt_array(struct device_node *node, const char *prop_name, int *buf, int len) { int rc; rc = of_property_count_elems_of_size(node, prop_name, sizeof(u32)); if (rc < 0) { pr_err("Property %s not found, rc=%d\n", prop_name, rc); return rc; } else if (rc != len) { pr_err("Incorrect length %d for %s, rc=%d\n", len, prop_name, rc); return -EINVAL; } rc = of_property_read_u32_array(node, prop_name, buf, len); if (rc < 0) { pr_err("Error in reading %s, rc=%d\n", prop_name, rc); return rc; } return 0; } static int qbg_parse_battery_profile(struct qbg_battery_data *battery) { struct device_node *node = battery->profile_node; struct device_node *child; struct battery_config *bp = &battery->bp; char buf[32]; const char *battery_name = NULL; int rc, i = 0; u32 temp[2]; rc = of_property_read_string(node, "qcom,battery-type", &battery_name); if (rc < 0) { pr_err("Failed to get battery type, rc=%d\n", rc); return rc; } strlcpy(bp->bp_profile_name, battery_name, MAX_PROFILE_NAME_LENGTH); rc = of_property_read_u32(node, "qcom,batt-id-kohm", &bp->bp_batt_id); if (rc < 0) { pr_err("Failed to get battery id, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,capacity", &bp->capacity); if (rc < 0) { pr_err("Failed to get battery capacity, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,checksum", &bp->bp_checksum); if (rc < 0) { pr_err("Failed to get checksum, rc=%d\n", rc); return rc; } rc = qbg_parse_u32_dt_array(node, "qcom,soh-range", temp, 2); if (rc < 0) return rc; if (temp[0] > 100 || temp[1] > 100 || (temp[0] > temp[1])) { pr_err("Incorrect SOH range [%d %d]\n", temp[0], temp[1]); return -ERANGE; } bp->soh_range_low = temp[0]; bp->soh_range_high = temp[1]; rc = qbg_parse_u32_dt_array(node, "qcom,battery-impedance", temp, 2); if (rc < 0) return rc; bp->normal_impedance = temp[0]; bp->aged_impedance = temp[1]; rc = qbg_parse_u32_dt_array(node, "qcom,battery-capacity", temp, 2); if (rc < 0) return rc; bp->normal_capacity = temp[0]; bp->aged_capacity = temp[1]; rc = of_property_read_u32(node, "qcom,recharge-soc-delta", &bp->recharge_soc_delta); if (rc < 0) { pr_err("Failed to get recharege soc delta, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,recharge-vflt-delta", &bp->recharge_vflt_delta); if (rc < 0) { pr_err("Failed to get recharge vflt delta, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,recharge-iterm-ma", &bp->recharge_iterm); if (rc < 0) { pr_err("Failed to get recharge iterm, rc=%d\n", rc); return rc; } for_each_available_child_of_node(battery->profile_node, child) { if (of_node_name_prefix(child, "qcom,bp-c-table")) battery->num_ctables++; else if (of_node_name_prefix(child, "qcom,bp-d-table")) battery->num_dtables++; } if (!battery->num_ctables || !battery->num_dtables) { pr_err("ctable or dtable missing\n"); return -EINVAL; } /* Battery profile contains additional table (table0) */ if (battery->num_ctables) battery->num_ctables--; if (battery->num_dtables) battery->num_dtables--; battery->bp_discharge_tables = kcalloc(battery->num_dtables, sizeof(*battery->bp_discharge_tables), GFP_KERNEL); if (!battery->bp_discharge_tables) return -ENOMEM; battery->bp_charge_tables = kcalloc(battery->num_ctables, sizeof(*battery->bp_charge_tables), GFP_KERNEL); if (!battery->bp_charge_tables) return -ENOMEM; /* Parse c-table-0 */ scnprintf(buf, sizeof(buf), "qcom,bp-c-table-0"); rc = qbg_parse_table0(battery->profile_node, buf, &battery->table0[0]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); return rc; } /* Parse d-table-0 */ scnprintf(buf, sizeof(buf), "qcom,bp-d-table-0"); rc = qbg_parse_table0(battery->profile_node, buf, &battery->table0[1]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); goto cleanup_ctable; } for (i = 0; i < battery->num_dtables; i++) { battery->bp_discharge_tables[i] = kzalloc( sizeof(*battery->bp_discharge_tables[i]), GFP_KERNEL); if (!battery->bp_discharge_tables[i]) { rc = -ENOMEM; goto cleanup_ctable; } scnprintf(buf, sizeof(buf), "qcom,bp-d-table-%d", i + 1); rc = qbg_parse_table(battery->profile_node, i, buf, battery->bp_discharge_tables[i]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); goto cleanup_dtable; } } for (i = 0; i < battery->num_ctables; i++) { battery->bp_charge_tables[i] = kzalloc( sizeof(*battery->bp_charge_tables[i]), GFP_KERNEL); if (!battery->bp_charge_tables[i]) { rc = -ENOMEM; goto cleanup_ctable; } scnprintf(buf, sizeof(buf), "qcom,bp-c-table-%d", i + 1); rc = qbg_parse_table(battery->profile_node, i, buf, battery->bp_charge_tables[i]); if (rc < 0) { pr_err("Failed to parse %s, rc=%d\n", buf, rc); goto cleanup_ctable; } } return 0; cleanup_ctable: for (; i > 0; i--) kfree(battery->bp_charge_tables[i]); i = battery->num_dtables; cleanup_dtable: for (; i > 0; i--) kfree(battery->bp_discharge_tables[i]); return rc; } int qbg_batterydata_init(struct device_node *profile_node, struct qbg_battery_data *battery) { int rc = 0; /* char device to access battery-profile data */ rc = alloc_chrdev_region(&battery->dev_no, 0, 1, "qbg_battery"); if (rc < 0) { pr_err("Failed to allocate chrdev, rc=%d\n", rc); goto free_battery; } cdev_init(&battery->battery_cdev, &qbg_battery_data_fops); rc = cdev_add(&battery->battery_cdev, battery->dev_no, 1); if (rc) { pr_err("Failed to add battery_cdev, rc=%d\n", rc); goto unregister_chrdev; } battery->battery_class = class_create(THIS_MODULE, "qbg_battery"); if (IS_ERR_OR_NULL(battery->battery_class)) { pr_err("Failed to create qbg-battery class (%d)\n", PTR_ERR(battery->battery_class)); rc = -ENODEV; goto delete_cdev; } battery->battery_device = device_create(battery->battery_class, NULL, battery->dev_no, NULL, "qbg_battery"); if (IS_ERR_OR_NULL(battery->battery_device)) { pr_err("Failed to create battery_device device (%d)\n", PTR_ERR(battery->battery_device)); rc = -ENODEV; goto destroy_class; } battery->profile_node = profile_node; /* parse the battery profile */ rc = qbg_parse_battery_profile(battery); if (rc < 0) { pr_err("Failed to parse battery profile, rc=%d\n", rc); goto destroy_device; } pr_info("QBG Battery-profile loaded, id:%d name:%s\n", battery->bp.bp_batt_id, battery->bp.bp_profile_name); return 0; destroy_device: device_destroy(battery->battery_class, battery->dev_no); destroy_class: class_destroy(battery->battery_class); delete_cdev: cdev_del(&battery->battery_cdev); unregister_chrdev: unregister_chrdev_region(battery->dev_no, 1); free_battery: kfree(battery); return rc; } void qbg_batterydata_exit(struct qbg_battery_data *battery) { int i; if (!battery) { pr_err("Battery cannot be null\n"); return; } /* unregister the device node */ device_destroy(battery->battery_class, battery->dev_no); class_destroy(battery->battery_class); cdev_del(&battery->battery_cdev); unregister_chrdev_region(battery->dev_no, 1); /* delete all the battery profile memory */ for (i = 0; i < battery->num_ctables; i++) kfree(battery->bp_charge_tables[i]); for (i = 0; i < battery->num_dtables; i++) kfree(battery->bp_discharge_tables[i]); } int qbg_lookup_soc_ocv(struct qbg_battery_data *battery, int *pon_soc, int ocv, bool charging) { struct battery_data_table0 *lut; int i; *pon_soc = -EINVAL; lut = charging ? &battery->table0[0] : &battery->table0[1]; for (i = 0; i < lut->ocv_length; i++) { if (ocv == lut->ocv[i]) { *pon_soc = lut->soc[i]; break; } else if (is_between(lut->ocv[i], lut->ocv[i+1], ocv)) { *pon_soc = (lut->soc[i] + lut->soc[i+1]) / 2; break; } } if (*pon_soc == -EINVAL) { pr_debug("%d ocv wasn't found in the LUT returning 100%\n", ocv); *pon_soc = 10000; } return 0; }
drivers/power/supply/qcom/qbg-battery-profile.h 0 → 100644 +55 −0 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. */ #ifndef __QBG_BATTERY_PROFILE_H__ #define __QBG_BATTERY_PROFILE_H__ #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ && (value) >= (right)) \ || ((left) <= (right) && (left) <= (value) \ && (value) <= (right))) struct battery_data_table0 { int soc_length; int ocv_length; int *soc; int *ocv; }; /** * struct qbg_battery_data - Structure for QBG battery data * @dev_no: Device number for QBG battery char device * @profile_node: Pointer to devicetree node handle of profile * @battery_class: Pointer to battery class * @battery_device: Pointer to battery class device * @battery_cdev: QBG battery char device * @bp: QBG battery configuration * @bp_charge_tables: Charge tables in QBG battery profile * @bp_discharge_tables: Discharge tables in QBG battery profile * @table0: Two tables for PON OCV to SOC mapping * @num_ctables: Number of charge tables * @num_dtables: Number of discharge tables */ struct qbg_battery_data { dev_t dev_no; struct device_node *profile_node; struct class *battery_class; struct device *battery_device; struct cdev battery_cdev; struct battery_config bp; struct battery_data_table **bp_charge_tables; struct battery_data_table **bp_discharge_tables; struct battery_data_table0 table0[2]; int num_ctables; int num_dtables; }; int qbg_batterydata_init(struct device_node *node, struct qbg_battery_data *battery); void qbg_batterydata_exit(struct qbg_battery_data *battery); int qbg_lookup_soc_ocv(struct qbg_battery_data *battery, int *pon_soc, int ocv, bool charging); #endif /* __QBG_BATTERY_PROFILE_H__ */
drivers/power/supply/qcom/qbg-core.h 0 → 100644 +178 −0 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. */ #ifndef __QBG_CORE_H__ #define __QBG_CORE_H__ #define qbg_dbg(chip, reason, fmt, ...) \ do { \ if (*chip->debug_mask & (reason)) \ pr_err(fmt, ##__VA_ARGS__); \ else \ pr_debug(fmt, ##__VA_ARGS__); \ } while (0) enum debug_mask { QBG_DEBUG_BUS_READ = BIT(0), QBG_DEBUG_BUS_WRITE = BIT(1), QBG_DEBUG_FIFO = BIT(2), QBG_DEBUG_IRQ = BIT(3), QBG_DEBUG_DEVICE = BIT(4), QBG_DEBUG_PROFILE = BIT(5), QBG_DEBUG_SOC = BIT(6), QBG_DEBUG_SDAM = BIT(7), QBG_DEBUG_STATUS = BIT(8), QBG_DEBUG_PON = BIT(9), }; enum qbg_sdam { SDAM_CTRL0 = 0, SDAM_CTRL1, SDAM_DATA0, SDAM_DATA1, SDAM_DATA2, SDAM_DATA3, SDAM_DATA4, }; /** * struct qti_qbg - Structure for QTI QBG device * @dev: Pointer to QBG device structure * @regmap: Pointer to regmap structure * @qbg_psy: Pointer to QBG power supply * @batt_psy: Pointer to Battery power supply * @qbg_class: Pointer to QBG class * @qbg_device: Pointer to QBG device * @qbg_cdev: Member for QBG char device * @dev_no: Device number for QBG char device * @batt_node: Pointer to battery device node * @indio_dev: Pointer to QBG IIO device * @iio_chan: Pointer to QBG IIO channels * @sdam: Pointer to multiple QBG SDAMs * @fifo: QBG FIFO data * @essential_params: QBG essential params * @status_change_work: Power supply status change work * @udata_work: User space data change work * @nb: Power supply notifier block * @kdata: QBG Kernel space data structure * @udata: QBG user space data structure * @battery: Pointer to QBG battery data structure * @fifo_lock: Lock for reading FIFO data * @data_lock: Lock for reading kdata from QBG char device * @batt_id_chan: IIO channel to read battery ID * @batt_temp_chan: IIO channel to read battery temperature * @rtc: RTC device to read real time * @last_fast_char_time: Timestamp of last time QBG in fast char mode * @qbg_wait_q: Wait queue for reads to QBG char device * @irq_name: QBG interrupt name * @batt_type_str: String array denoting battery type * @irq: QBG irq number * @base: Base address of QBG HW * @num_sdams: Number of sdams used for QBG * @batt_id_ohm: Battery resistance in ohms * @debug_mask: Debug mask to enable/disable debug prints * @pon_ocv: Power-on OCV of QBG device * @pon_ibat: Power-on current of QBG device * @pon_soc: Power-on SOC of QBG device * @soc: Monotonic SOC of QBG device * @batt_soc: Battery SOC * @sys_soc: Battery system SOC * @esr: Battery equivalent series resistance * @ocv_uv: Battery open circuit voltage * @voltage_now: Battery voltage * @current_now: Battery current * @tbat: Battery temperature * @charge_cycle_count: Battery charge cycle count * @nominal_capacity: Battery nominal capacity * @learned_capacity: Battery learned capacity * @ttf: Time to full * @tte: Time to empty * @soh: Battery state of health * @charge_type: Charging type * @float_volt_uv: Battery maximum voltage * @fastchg_curr_ma: Battery fast charge current * @vbat_cutoff_mv: Battery cutoff voltage * @ibat_cutoff_ma: Battery cutoff current * @vph_min_mv: Battery minimum power * @iterm_ma: Charge Termination current * @rconn_mohm: Battery connector resistance * @previous_ep_time: Previous timestamp when essential params stored * @current_time: Current time stamp * @profile_loaded: Flag to indicated battery profile is loaded * @battery_missing: Flag to indicate battery is missing * @data_ready: Flag to indicate QBG data is ready * @in_fast_char: Flag to indicate QBG is in fast char mode */ struct qti_qbg { struct device *dev; struct regmap *regmap; struct power_supply *qbg_psy; struct power_supply *batt_psy; struct class *qbg_class; struct device *qbg_device; struct cdev qbg_cdev; dev_t dev_no; struct device_node *batt_node; struct iio_dev *indio_dev; struct iio_chan_spec *iio_chan; struct nvmem_device **sdam; struct fifo_data fifo[MAX_FIFO_COUNT]; struct qbg_essential_params essential_params; struct work_struct status_change_work; struct work_struct udata_work; struct notifier_block nb; struct qbg_kernel_data kdata; struct qbg_user_data udata; struct qbg_battery_data *battery; struct mutex fifo_lock; struct mutex data_lock; struct iio_channel *batt_id_chan; struct iio_channel *batt_temp_chan; struct rtc_device *rtc; ktime_t last_fast_char_time; wait_queue_head_t qbg_wait_q; const char *irq_name; const char *batt_type_str; int irq; u32 base; u32 sdam_base; u32 num_sdams; u32 num_data_sdams; u32 batt_id_ohm; u32 *debug_mask; int pon_ocv; int pon_ibat; int pon_tbat; int pon_soc; int soc; int batt_soc; int sys_soc; int esr; int ocv_uv; int voltage_now; int current_now; int tbat; int charge_cycle_count; int nominal_capacity; int learned_capacity; int ttf; int tte; int soh; int charge_type; int float_volt_uv; int fastchg_curr_ma; int vbat_cutoff_mv; int ibat_cutoff_ma; int vph_min_mv; int iterm_ma; int rconn_mohm; unsigned long previous_ep_time; unsigned long current_time; bool profile_loaded; bool battery_missing; bool data_ready; bool in_fast_char; }; #endif /* __QBG_CORE_H__ */