diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index 50da9b959f06..73835d078caa 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -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 diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 3d80dfe553ed..f08dd44c9dba 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -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 diff --git a/drivers/power/supply/qcom/qbg-battery-profile.c b/drivers/power/supply/qcom/qbg-battery-profile.c new file mode 100644 index 000000000000..14b64f581e97 --- /dev/null +++ b/drivers/power/supply/qcom/qbg-battery-profile.c @@ -0,0 +1,589 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +} diff --git a/drivers/power/supply/qcom/qbg-battery-profile.h b/drivers/power/supply/qcom/qbg-battery-profile.h new file mode 100644 index 000000000000..c28c6d5864ac --- /dev/null +++ b/drivers/power/supply/qcom/qbg-battery-profile.h @@ -0,0 +1,55 @@ +/* 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__ */ diff --git a/drivers/power/supply/qcom/qbg-core.h b/drivers/power/supply/qcom/qbg-core.h new file mode 100644 index 000000000000..49a619373c08 --- /dev/null +++ b/drivers/power/supply/qcom/qbg-core.h @@ -0,0 +1,178 @@ +/* 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__ */ diff --git a/drivers/power/supply/qcom/qbg-iio.h b/drivers/power/supply/qcom/qbg-iio.h new file mode 100644 index 000000000000..a96a250da02c --- /dev/null +++ b/drivers/power/supply/qcom/qbg-iio.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021 The Linux Foundation. All rights reserved. + */ + +#ifndef __QBG_IIO_H__ +#define __QBG_IIO_H__ + +struct qbg_iio_channels { + const char *datasheet_name; + int channel_num; + enum iio_chan_type type; + long info_mask; +}; + +#define QBG_IIO_CHAN(_name, _num, _type, _mask) \ + { \ + .datasheet_name = _name, \ + .channel_num = _num, \ + .type = _type, \ + .info_mask = _mask, \ + }, + +#define QBG_CHAN_ENERGY(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_ENERGY, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_TEMP(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_TEMP, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_VOLT(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_TSTAMP(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_TIMESTAMP, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_RES(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_RESISTANCE, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_INDEX(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_INDEX, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_CUR(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_CURRENT, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define QBG_CHAN_COUNT(_name, _num) \ + QBG_IIO_CHAN(_name, _num, IIO_COUNT, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#endif /* __QBG_IIO_H__ */ diff --git a/drivers/power/supply/qcom/qbg-sdam.c b/drivers/power/supply/qcom/qbg-sdam.c new file mode 100644 index 000000000000..4baff57527c7 --- /dev/null +++ b/drivers/power/supply/qcom/qbg-sdam.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "qbg-core.h" +#include "qbg-sdam.h" + +int qbg_sdam_read(struct qti_qbg *chip, int offset, u8 *data, int len) +{ + int rc; + + rc = regmap_bulk_read(chip->regmap, offset, data, len); + if (rc < 0) + pr_err("Failed to read QBG SDAM offset:%04x rc=%d\n", + offset, rc); + + return rc; +} + +int qbg_sdam_write(struct qti_qbg *chip, int offset, u8 *data, int len) +{ + int rc; + + rc = regmap_bulk_write(chip->regmap, offset, data, len); + if (rc < 0) + pr_err("Failed to write QBG SDAM offset:%04x rc=%d\n", + offset, rc); + + return rc; +} + +int qbg_sdam_get_fifo_data(struct qti_qbg *chip, struct fifo_data *fifo, + u32 fifo_count) +{ + int rc, i; + u32 num_sdams, fifo_data_length, bytes_read = 0, bytes_remaining; + + fifo_data_length = fifo_count * QBG_ONE_FIFO_LENGTH; + num_sdams = fifo_data_length / QBG_SDAM_ONE_FIFO_REGION_SIZE; + + if (num_sdams > chip->num_sdams) { + pr_err("Fifo count exceeds QBG SDAMs\n"); + return -EINVAL; + } + + qbg_dbg(chip, QBG_DEBUG_FIFO, "Completely filled sdams=%d\n", + num_sdams); + + /* First read SDAMs that are completely filled */ + for (i = 0; i < num_sdams; i++) { + if (!chip->sdam[i]) { + pr_err("Number of SDAMs not sufficient to read FIFOs\n"); + return -ENODEV; + } + rc = qbg_sdam_read(chip, + QBG_SDAM_DATA_START_OFFSET(chip, SDAM_DATA0 + i), + (((u8 *)fifo) + bytes_read), + QBG_SDAM_ONE_FIFO_REGION_SIZE); + if (rc < 0) { + pr_err("Failed to read QBG SDAM%d, rc=%d\n", i, rc); + return rc; + } + bytes_read += QBG_SDAM_ONE_FIFO_REGION_SIZE; + + } + + /* Next read SDAM that is partially filled */ + bytes_remaining = fifo_data_length - bytes_read; + qbg_dbg(chip, QBG_DEBUG_FIFO, "Valid data in partially filled sdam:%d bytes\n", + bytes_remaining); + + if (bytes_remaining) { + rc = qbg_sdam_read(chip, + QBG_SDAM_DATA_START_OFFSET(chip, SDAM_DATA0 + i), + (((u8 *)fifo) + bytes_read), + bytes_remaining); + if (rc < 0) { + pr_err("Failed to read remaining bytes from QBG SDAM%d, rc=%d\n", + i, rc); + return rc; + } + bytes_read += bytes_remaining; + } + + qbg_dbg(chip, QBG_DEBUG_FIFO, "Total bytes read=%d\n", bytes_read); + + return rc; +} + +int qbg_sdam_get_essential_params(struct qti_qbg *chip, u8 *params) +{ + int rc; + + if (!chip || !params) + return -EINVAL; + + rc = qbg_sdam_read(chip, + chip->sdam_base + (SDAM_CTRL1 * 0x100) + + QBG_ESSENTIAL_PARAMS_START_OFFSET, + (u8 *)params, sizeof(struct qbg_essential_params)); + if (rc < 0) + pr_err("Failed to read QBG essential params, rc=%d\n", rc); + else + qbg_dbg(chip, QBG_DEBUG_FIFO, "Read QBG essential params\n"); + + return rc; +} + +int qbg_sdam_set_essential_params(struct qti_qbg *chip, u8 *params) +{ + int rc; + + if (!chip || !params) + return -EINVAL; + + rc = qbg_sdam_write(chip, + chip->sdam_base + (SDAM_CTRL1 * 0x100) + + QBG_ESSENTIAL_PARAMS_START_OFFSET, + (u8 *)params, sizeof(struct qbg_essential_params)); + if (rc < 0) + pr_err("Failed to write QBG essential params, rc=%d\n", rc); + else + qbg_dbg(chip, QBG_DEBUG_FIFO, "Written QBG essential params\n"); + + return rc; +} diff --git a/drivers/power/supply/qcom/qbg-sdam.h b/drivers/power/supply/qcom/qbg-sdam.h new file mode 100644 index 000000000000..882ae03ba96c --- /dev/null +++ b/drivers/power/supply/qcom/qbg-sdam.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021 The Linux Foundation. All rights reserved. + */ + +#ifndef __QBG_SDAM_H__ +#define __QBG_SDAM_H__ + +#define QBG_SDAM_PBS_STATUS_OFFSET 0x45 +#define QBG_SDAM_FIFO_COUNT_OFFSET 0x46 +#define QBG_ESSENTIAL_PARAMS_START_OFFSET 0x47 +#define QBG_SDAM_START_OFFSET 0x4b +#define QBG_SDAM_INT_TEST1 0xe0 +#define QBG_SDAM_INT_TEST_VAL 0xe1 +#define QBG_SDAM_DATA_PUSH_COUNTER_OFFSET 0x46 + +#define QBG_SDAM_TEST_VAL_1_SET 0x2 +#define QBG_SDAM_ONE_FIFO_REGION_SIZE 117 /* 117 bytes for FIFO starting from 0x4B */ +#define QBG_SDAM_ESR_PULSE_FIFO_INDEX 11 + +#define QBG_SDAM_DATA_START_OFFSET(chip, index) \ + (chip->sdam_base + (index * 0x100) + QBG_SDAM_START_OFFSET) + +int qbg_sdam_read(struct qti_qbg *chip, int offset, u8 *data, int length); +int qbg_sdam_write(struct qti_qbg *chip, int offset, u8 *data, int length); +int qbg_sdam_get_fifo_data(struct qti_qbg *chip, struct fifo_data *fifo, + u32 fifo_count); +int qbg_sdam_get_essential_params(struct qti_qbg *chip, u8 *params); +int qbg_sdam_set_essential_params(struct qti_qbg *chip, u8 *params); + +#endif /* __QBG_SDAM_H__ */ diff --git a/drivers/power/supply/qcom/qti-qbg.c b/drivers/power/supply/qcom/qti-qbg.c new file mode 100644 index 000000000000..6512d106030d --- /dev/null +++ b/drivers/power/supply/qcom/qti-qbg.c @@ -0,0 +1,1718 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "qbg-iio.h" +#include "qbg-battery-profile.h" +#include "qbg-core.h" +#include "qbg-sdam.h" +#include "battery-profile-loader.h" + +/* QBG register definitions */ +#define QBG_MAIN_STATUS1 0x08 +#define QBG_OK_BIT BIT(7) + +#define QBG_MAIN_STATUS2 0x09 +#define QBG_BATTERY_MISSING_BIT BIT(2) +#define QBG_ADC_CONV_FAULT_OCCURRED_BIT BIT(1) +#define QBG_DATA_RESET_BIT BIT(0) + +#define QBG_MAIN_STATUS3 0x0a +#define QBG_FSM_STATE_MASK GENMASK(3, 0) + +#define QBG_MAIN_QBG_STATE_FORCE_CMD 0x41 +#define FORCE_FAST_CHAR_MODE_BIT BIT(3) +#define FORCE_FAST_CHAR_SHIFT 3 + +#define QBG_MAIN_EN_CTL 0x46 +#define QBG_EN_BIT BIT(7) + +#define QBG_MAIN_MPM_TO_LPM_IBAT_THRESH 0x50 + +#define QBG_MAIN_LPM_TO_MPM_IBAT_THRESH 0x51 + +#define QBG_MAIN_HPM_TO_MPM_IBAT_THRESH 0x52 + +#define QBG_MAIN_MPM_TO_HPM_IBAT_THRESH 0x53 + +#define QBG_MAIN_VBAT_EMPTY_THRESH 0x56 + +#define QBG_MAIN_PON_OCV_ACC0_DATA0 0x70 + +#define QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0 0xA0 + +#define QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0 0xA4 + +#define QBG_FAST_CHAR_DELTA_MS 100000 +#define VBATT_1S_LSB 19463 +#define IBATT_10A_LSB 30518 +#define VBAT_0PCT_OCV 300000000ULL +#define ICHG_FS_10A 1 +#define TBAT_LSB 7500 +#define TBAT_DENOMINATOR 115600 + +enum qbg_data_tag { + QBG_DATA_TAG_FAST_CHAR, +}; + +static int qbg_debug_mask; + +static const struct qbg_iio_channels qbg_iio_psy_channels[] = { + QBG_CHAN_INDEX("debug_battery", PSY_IIO_DEBUG_BATTERY) + QBG_CHAN_ENERGY("capacity", PSY_IIO_CAPACITY) + QBG_CHAN_ENERGY("real_capacity", PSY_IIO_REAL_CAPACITY) + QBG_CHAN_TEMP("temp", PSY_IIO_TEMP) + QBG_CHAN_VOLT("voltage_max", PSY_IIO_VOLTAGE_MAX) + QBG_CHAN_VOLT("voltage_now", PSY_IIO_VOLTAGE_NOW) + QBG_CHAN_VOLT("voltage_ocv", PSY_IIO_VOLTAGE_OCV) + QBG_CHAN_CUR("current_now", PSY_IIO_CURRENT_NOW) + QBG_CHAN_RES("resistance_id", PSY_IIO_RESISTANCE_ID) + QBG_CHAN_TSTAMP("time_to_full_avg", PSY_IIO_TIME_TO_FULL_AVG) + QBG_CHAN_TSTAMP("time_to_full_now", PSY_IIO_TIME_TO_FULL_NOW) + QBG_CHAN_TSTAMP("time_to_empty_avg", PSY_IIO_TIME_TO_EMPTY_AVG) + QBG_CHAN_RES("esr", PSY_IIO_ESR_ACTUAL) + QBG_CHAN_INDEX("soh", PSY_IIO_SOH) + QBG_CHAN_COUNT("cycle_count", PSY_IIO_CYCLE_COUNT) + QBG_CHAN_ENERGY("charge_full", PSY_IIO_CHARGE_FULL) + QBG_CHAN_ENERGY("charge_full_design", PSY_IIO_CHARGE_FULL_DESIGN) + QBG_CHAN_ENERGY("charge_counter", PSY_IIO_CHARGE_COUNTER) +}; + +int qbg_read(struct qti_qbg *chip, u32 addr, u8 *val, int len) +{ + int rc; + + rc = regmap_bulk_read(chip->regmap, chip->base + addr, val, len); + if (rc < 0) { + pr_err("Failed to read from address %04x rc=%d\n", chip->base + addr, rc); + return rc; + } + + if (*chip->debug_mask & QBG_DEBUG_BUS_READ) + pr_info("length %d addr=%#x data:%*ph\n", len, chip->base + addr, len, val); + + return 0; +} + +int qbg_write(struct qti_qbg *chip, u32 addr, u8 *val, int len) +{ + int rc; + + rc = regmap_bulk_write(chip->regmap, chip->base + addr, val, len); + if (rc < 0) { + pr_err("Failed to write to address %04x rc=%d\n", + chip->base + addr, rc); + return rc; + } + + if (*chip->debug_mask & QBG_DEBUG_BUS_WRITE) + pr_info("length %d addr=%#x data:%*ph\n", len, chip->base + addr, len, val); + + return 0; +} + +static void qbg_notify_charger(struct qti_qbg *chip) +{ + union power_supply_propval prop = {0, }; + int rc; + + if (!chip->batt_psy || !chip->profile_loaded) + return; + + prop.intval = chip->float_volt_uv; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); + if (rc < 0) { + pr_err("Failed to set voltage_max property on batt_psy, rc=%d\n", + rc); + return; + } + + prop.intval = chip->fastchg_curr_ma * 1000; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop); + if (rc < 0) { + pr_err("Failed to set constant_charge_current_max property on batt_psy, rc=%d\n", + rc); + return; + } + + pr_debug("Notified charger on float voltage:%d uV and FCC:%d mA\n", + chip->float_volt_uv, chip->fastchg_curr_ma); +} + +static bool is_batt_available(struct qti_qbg *chip) +{ + if (chip->batt_psy) + return true; + + chip->batt_psy = power_supply_get_by_name("battery"); + if (!chip->batt_psy) + return false; + + /* batt_psy is initialized, set the fcc and fv */ + qbg_notify_charger(chip); + + return true; +} + +static void status_change_work(struct work_struct *work) +{ + struct qti_qbg *chip = container_of(work, struct qti_qbg, + status_change_work); + union power_supply_propval prop = {0, }; + int rc; + + if (!is_batt_available(chip)) + return; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); + if (rc < 0) + pr_err("Failed to get charge-type, rc=%d\n", rc); + else + chip->charge_type = prop.intval; +} + +static int qbg_get_fifo_count(struct qti_qbg *chip, u32 *fifo_count) +{ + int rc = 0; + u8 val[2]; + + rc = qbg_sdam_read(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_FIFO_COUNT_OFFSET, + val, 2); + if (rc < 0) { + pr_err("Failed to read QBG SDAM, rc=%d\n", rc); + return rc; + } + + *fifo_count = (val[1] << 8) | val[0]; + qbg_dbg(chip, QBG_DEBUG_FIFO, "FIFO count=%d\n", *fifo_count); + + return rc; +} + +static void process_udata_work(struct work_struct *work) +{ + struct qti_qbg *chip = container_of(work, struct qti_qbg, udata_work); + + if (chip->udata.param[QBG_PARAM_BATT_SOC].valid) + chip->batt_soc = chip->udata.param[QBG_PARAM_BATT_SOC].data; + + if (chip->udata.param[QBG_PARAM_SOC].valid || + chip->udata.param[QBG_PARAM_SYS_SOC].valid) { + + qbg_dbg(chip, QBG_DEBUG_SOC, "udata update: QBG_SOC=%d QBG_SYS_SOC=%d\n", + chip->udata.param[QBG_PARAM_SOC].valid ? + chip->udata.param[QBG_PARAM_SOC].data : -EINVAL, + chip->udata.param[QBG_PARAM_SYS_SOC].valid ? + chip->udata.param[QBG_PARAM_SYS_SOC].data : -EINVAL); + + if (chip->udata.param[QBG_PARAM_SYS_SOC].valid) + chip->sys_soc = chip->udata.param[QBG_PARAM_SYS_SOC].data; + + chip->soc = chip->udata.param[QBG_PARAM_SOC].data; + } + + if (chip->udata.param[QBG_PARAM_ESR].valid) { + chip->esr = chip->udata.param[QBG_PARAM_ESR].data; + + if (chip->qbg_psy) + power_supply_changed(chip->qbg_psy); + } + + if (chip->udata.param[QBG_PARAM_OCV_UV].valid) + chip->ocv_uv = chip->udata.param[QBG_PARAM_OCV_UV].data; + + if (chip->udata.param[QBG_PARAM_CHARGE_CYCLE_COUNT].valid) + chip->charge_cycle_count = + chip->udata.param[QBG_PARAM_CHARGE_CYCLE_COUNT].data; + + if (chip->udata.param[QBG_PARAM_LEARNED_CAPACITY].valid) + chip->learned_capacity = + chip->udata.param[QBG_PARAM_LEARNED_CAPACITY].data; + + if (chip->udata.param[QBG_PARAM_TTF_100MS].valid) + chip->ttf = chip->udata.param[QBG_PARAM_TTF_100MS].data; + + if (chip->udata.param[QBG_PARAM_TTE_100MS].valid) + chip->tte = chip->udata.param[QBG_PARAM_TTE_100MS].data; + + if (chip->udata.param[QBG_PARAM_SOH].valid) + chip->soh = chip->udata.param[QBG_PARAM_SOH].data; + + if (chip->udata.param[QBG_PARAM_TBAT].valid) + chip->tbat = chip->udata.param[QBG_PARAM_TBAT].data; + + qbg_dbg(chip, QBG_DEBUG_STATUS, "udata update: batt_soc=%d sys_soc=%d soc=%d qbg_esr=%d\n", + (chip->batt_soc != INT_MIN) ? chip->batt_soc : -EINVAL, + (chip->sys_soc != INT_MIN) ? chip->sys_soc : -EINVAL, + chip->soc, chip->esr); +} + +static int qbg_decode_fifo_data(struct fifo_data fifo, unsigned int *vbat1, + unsigned int *vbat2, int *ibat, + unsigned int *tbat, unsigned int *ibat_t) +{ + unsigned short int acc0_data = 0, acc1_data = 0, acc2_data = 0, + tbat_data = 0; + unsigned int ibat_temp; + + if (!vbat1 || !vbat2 || !ibat || !tbat || !ibat_t) + return -EINVAL; + + acc0_data = fifo.v1; + acc1_data = fifo.v2; + acc2_data = fifo.i; + tbat_data = fifo.tbat; + + ibat_temp = acc2_data; + + *vbat1 = acc0_data * VBATT_1S_LSB; + *ibat = ((int16_t)ibat_temp) * IBATT_10A_LSB * ICHG_FS_10A; + *tbat = tbat_data; + *ibat_t = fifo.ibat; + + return 0; +} + +static int get_rtc_time(struct qti_qbg *chip, unsigned long *rtc_time) +{ + struct rtc_time tm; + struct rtc_device *rtc; + int rc; + + if (!chip->rtc) { + pr_err("Invalid RTC handle\n"); + return -EINVAL; + } + + rc = rtc_read_time(chip->rtc, &tm); + if (rc) { + pr_err("Failed to read rtc time (%s) : %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + rtc_tm_to_time(&tm, rtc_time); + + return rc; + +close_time: + rtc_class_close(chip->rtc); + chip->rtc = NULL; + + return rc; +} + +static int qbg_process_fifo(struct qti_qbg *chip, u32 fifo_count) +{ + struct fifo_data *fifo; + int rc, i, ibat, ibat_esr; + unsigned char data_tag; + unsigned int vbat1, vbat2, tbat, ibat_t, esr; + unsigned int vbat1_esr, vbat2_esr, tbat_esr, ibat_t_esr; + unsigned long timestamp; + + if (!fifo_count) { + qbg_dbg(chip, QBG_DEBUG_FIFO, "No FIFO data\n"); + return -EINVAL; + } + + fifo = kcalloc(fifo_count, sizeof(*fifo), GFP_KERNEL); + if (!fifo) + return -ENOMEM; + + rc = get_rtc_time(chip, ×tamp); + if (rc < 0) { + pr_err("Failed to read rtc time, rc=%d\n", rc); + return rc; + } + + chip->kdata.param[QBG_PARAM_FIFO_TIMESTAMP].valid = true; + chip->kdata.param[QBG_PARAM_FIFO_TIMESTAMP].data = timestamp; + + /* Read raw FIFO data from SDAM */ + rc = qbg_sdam_get_fifo_data(chip, fifo, fifo_count); + if (rc < 0) { + pr_err("Failed to get QBG FIFO data, rc=%d\n", rc); + goto ret; + } + + data_tag = fifo[0].data_tag; + if (fifo_count > QBG_SDAM_ESR_PULSE_FIFO_INDEX) + data_tag = fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX].data_tag; + + if (data_tag == QBG_DATA_TAG_FAST_CHAR) { + if (fifo_count < QBG_SDAM_ESR_PULSE_FIFO_INDEX) { + pr_err("Not enough FIFO samples in fast char mode\n"); + goto ret; + } + + rc = qbg_decode_fifo_data(fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX - 1], + &vbat1, &vbat2, &ibat, &tbat, + &ibat_t); + if (rc < 0) { + pr_err("Couldn't decode FIFO before ESR pulse in fast char mode, rc=%d\n", + rc); + goto ret; + } + + rc = qbg_decode_fifo_data(fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX], + &vbat1_esr, &vbat2_esr, + &ibat_esr, &tbat_esr, + &ibat_t_esr); + if (rc < 0) { + pr_err("Couldn't decode FIFO before ESR pulse in fast char mode, rc=%d\n", + rc); + goto ret; + } + + if (ibat != ibat_esr && (vbat1 > vbat1_esr)) { + esr = (vbat1 - vbat1_esr) / ((ibat_esr - ibat) * 10000); + esr *= 10000; + chip->esr = esr; + qbg_dbg(chip, QBG_DEBUG_FIFO, "ESR:%u, ibat=%d ibat_esr=%d vbat1:%u vbat1_esr:%u\n", + esr, ibat, ibat_esr, vbat1, vbat1_esr); + } else { + pr_err("Couldn't calculate ESR, ibat=%d ibat_esr=%d vbat1:%u vbat1_esr:%u\n", + ibat, ibat_esr, vbat1, vbat1_esr); + goto ret; + } + } + + for (i = 0; i < fifo_count; i++) { + rc = qbg_decode_fifo_data(fifo[i], &vbat1, &vbat2, &ibat, &tbat, + &ibat_t); + if (rc < 0) { + pr_err("Failed to decode fifo %d, rc=%d\n", i, rc); + goto ret; + } + + qbg_dbg(chip, QBG_DEBUG_FIFO, "vbat1:%u ibat:%d tbat:%u ibat_t:%u\n", + vbat1, ibat, tbat, ibat_t); + + chip->kdata.fifo[i].v1 = vbat1; + chip->kdata.fifo[i].v2 = vbat2; + chip->kdata.fifo[i].i = ibat; + chip->kdata.fifo[i].tbat = tbat; + chip->kdata.fifo[i].ibat = ibat_t; + } + +ret: + kfree(fifo); + return rc; +} + +static int qbg_clear_fifo_data(struct qti_qbg *chip) +{ + int rc = 0, i; + u8 val[2] = {0, 0}; + + rc = qbg_sdam_write(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_FIFO_COUNT_OFFSET, + val, 2); + if (rc < 0) { + pr_err("Failed to clear QBG FIFO Count, rc=%d\n", rc); + return rc; + } + + for (i = 0; i < 10; i++) { + val[0] = 0; + rc = qbg_sdam_write(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL, + val, 1); + if (rc < 0) { + pr_err("Failed to SDAM0 test val to 0, rc=%d\n", rc); + return rc; + } + + /* Handshake with PBS to access FIFO data */ + rc = qbg_sdam_read(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL, + val, 1); + if (rc < 0) { + pr_err("Failed to read QBG SDAM, rc=%d\n", rc); + return rc; + } + + if (val[0] == 0) + break; + } + if (i == 10) + return -EINVAL; + + val[0] = QBG_SDAM_START_OFFSET; + val[1] = 0x0; + rc = qbg_sdam_write(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_DATA0) + QBG_SDAM_DATA_PUSH_COUNTER_OFFSET, + val, 2); + if (rc < 0) { + pr_err("Failed to configure QBG data push counter, rc=%d\n", rc); + return rc; + } + val[0] = 0x0; + rc = qbg_sdam_write(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_PBS_STATUS_OFFSET, + val, 1); + if (rc < 0) { + pr_err("Failed to set QBG PBS status, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int qbg_init_sdam(struct qti_qbg *chip) +{ + int rc = 0; + u8 val[2] = {0}; + + val[0] = 0x80; + rc = qbg_sdam_write(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_INT_TEST1, val, 1); + if (rc < 0) { + pr_err("Failed to write QBG SDAM, rc=%d\n", rc); + return rc; + } + + val[0] = 0; + rc = qbg_sdam_read(chip, + QBG_SDAM_DATA_START_OFFSET(SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL, + val, 1); + if (rc < 0) { + pr_err("Faiiled to read QBG SDAM, rc=%d\n", rc); + return rc; + } + + if (val[0] == QBG_SDAM_TEST_VAL_1_SET) { + rc = qbg_clear_fifo_data(chip); + if (rc < 0) { + pr_err("Failed to clear QBG FIFO data, rc=%d\n", rc); + return rc; + } + } + + return rc; +} + +static int qbg_force_fast_char(struct qti_qbg *chip, bool force) +{ + int rc = 0; + u8 val = force ? (1 << FORCE_FAST_CHAR_SHIFT) : 0; + + rc = qbg_write(chip, QBG_MAIN_QBG_STATE_FORCE_CMD, &val, 1); + if (rc < 0) + pr_err("Failed to %s QBG fast char mode, rc=%d\n", + force ? "enable" : "disable", rc); + + return rc; +} + +static int qbg_handle_fast_char(struct qti_qbg *chip) +{ + int rc = 0; + ktime_t now; + + if (chip->in_fast_char) { + rc = qbg_force_fast_char(chip, false); + if (rc < 0) { + pr_err("Failed to get out of fast char mode, rc=%d\n", + rc); + return rc; + } + chip->in_fast_char = false; + } else if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE) { + now = ktime_get(); + if ((ktime_ms_delta(now, chip->last_fast_char_time) > + QBG_FAST_CHAR_DELTA_MS) && !chip->in_fast_char) { + + rc = qbg_force_fast_char(chip, true); + if (rc < 0) { + pr_err("Failed to put QBG to fast char mode, rc=%d\n", + rc); + return rc; + } + chip->in_fast_char = true; + chip->last_fast_char_time = now; + } + } + + return rc; +} + +static irqreturn_t qbg_data_full_irq_handler(int irq, void *_chip) +{ + struct qti_qbg *chip = _chip; + int rc; + u32 fifo_count = 0; + + qbg_dbg(chip, QBG_DEBUG_IRQ, "DATA FULL IRQ triggered\n"); + + rc = qbg_handle_fast_char(chip); + if (rc < 0) { + pr_err("Failed to handle QBG fast char, rc=%d\n", rc); + return IRQ_HANDLED; + } + + mutex_lock(&chip->fifo_lock); + memset(&chip->kdata.fifo, 0, sizeof(struct k_fifo_data)); + + rc = qbg_get_fifo_count(chip, &fifo_count); + if (rc < 0) { + pr_err("Failed to get QBG FIFO length, rc=%d\n", rc); + goto done; + } + + rc = qbg_process_fifo(chip, fifo_count); + if (rc < 0) { + pr_err("Failed to process QBG FIFO, rc=%d\n", rc); + goto done; + } + chip->kdata.fifo_count = fifo_count; + + rc = qbg_init_sdam(chip); + if (rc < 0) { + pr_err("Failed to initialize QBG sdam, rc=%d\n", rc); + goto done; + } + + chip->data_ready = true; + /* Wakeup the read thread */ + wake_up_interruptible(&chip->qbg_wait_q); +done: + mutex_unlock(&chip->fifo_lock); + return IRQ_HANDLED; +} + +static int qbg_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct power_supply *psy = data; + struct qti_qbg *chip = container_of(nb, struct qti_qbg, nb); + + if (event != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (work_pending(&chip->status_change_work)) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "battery") == 0) || + (strcmp(psy->desc->name, "usb") == 0) || + (strcmp(psy->desc->name, "dc") == 0)) { + schedule_work(&chip->status_change_work); + } + + return NOTIFY_OK; +} + +static int qbg_load_battery_profile(struct qti_qbg *chip) +{ + struct device_node *node = chip->dev->of_node; + struct device_node *profile_node; + int rc; + u32 temp[2]; + + chip->batt_node = of_find_node_by_name(node, "qcom,battery-data"); + if (!chip->batt_node) { + pr_err("Batterydata not available\n"); + return -ENXIO; + } + + profile_node = of_batterydata_get_best_profile(chip->batt_node, + chip->batt_id_ohm, NULL); + if (IS_ERR(profile_node)) { + rc = PTR_ERR(profile_node); + pr_err("Failed to detect valid QBG battery profile, rc=%d\n", + rc); + goto out; + } + + chip->battery = devm_kzalloc(chip->dev, sizeof(*chip->battery), GFP_KERNEL); + if (!chip->battery) { + rc = -ENOMEM; + goto out; + } + + rc = qbg_batterydata_init(profile_node, chip->battery); + if (rc < 0) { + pr_err("Failed to load batterydata, rc=%d\n", rc); + chip->profile_loaded = false; + goto out; + } + + rc = of_property_read_string(profile_node, "qcom,battery-type", + &chip->batt_type_str); + if (rc < 0) { + pr_err("Failed to get battery name, rc=%d\n", rc); + goto out; + } + + rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", + &chip->float_volt_uv); + if (rc < 0) { + pr_err("Failed to read battery float-voltage, rc:%d\n", rc); + chip->float_volt_uv = -EINVAL; + goto out; + } + + rc = of_property_read_u32(profile_node, "qcom,fastchg-curr-ma", + &chip->fastchg_curr_ma); + if (rc < 0) { + pr_err("Failed to read battery fastcharge current, rc:%d\n", rc); + chip->fastchg_curr_ma = -EINVAL; + goto out; + } + + rc = of_property_read_u32_array(profile_node, "qcom,battery-capacity", temp, 2); + if (rc) { + pr_err("Failed to read battery nominal capacity, rc=%d\n", rc); + goto out; + } + chip->nominal_capacity = temp[0]; + +out: + of_node_put(chip->batt_node); + return rc; +} + + +#define DEBUG_BATT_ID_LOW 6000 +#define DEBUG_BATT_ID_HIGH 8500 +static bool is_debug_batt_id(struct qti_qbg *chip) +{ + if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH, + chip->batt_id_ohm)) + return true; + + return false; +} + +#define DEFAULT_BATT_TYPE "Unknown Battery" +#define MISSING_BATT_TYPE "Missing Battery" +#define DEBUG_BATT_TYPE "Debug Board" +static const char *qbg_get_battery_type(struct qti_qbg *chip) +{ + if (chip->battery_missing) + return MISSING_BATT_TYPE; + + if (is_debug_batt_id(chip)) + return DEBUG_BATT_TYPE; + + if (chip->batt_type_str && chip->profile_loaded) + return chip->batt_type_str; + + return DEFAULT_BATT_TYPE; +} + +#define DEBUG_BATT_SOC 67 +#define BATT_MISSING_SOC 50 +static int qbg_get_battery_capacity(struct qti_qbg *chip, int *soc) +{ + if (is_debug_batt_id(chip)) { + *soc = DEBUG_BATT_SOC; + return 0; + } + + if (chip->battery_missing || !chip->profile_loaded) { + *soc = BATT_MISSING_SOC; + return 0; + } + + *soc = chip->soc; + + return 0; +} + +#define TEN_NANO_TO_MICRO 100 +static int qbg_get_battery_voltage(struct qti_qbg *chip, int *vbat_uv) +{ + int rc; + unsigned short acc0_data; + u8 buf[2]; + + if (chip->battery_missing) { + *vbat_uv = 3700000; + return 0; + } + + rc = qbg_read(chip, QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0, buf, 2); + if (rc < 0) { + pr_err("Failed to read average vbatt, rc=%d\n", rc); + return rc; + } + + acc0_data = buf[0] | (buf[1] << 8); + *vbat_uv = acc0_data * VBATT_1S_LSB; + *vbat_uv = *vbat_uv / TEN_NANO_TO_MICRO; + + return 0; +} + +static int qbg_get_battery_current(struct qti_qbg *chip, int *ibat_ua) +{ + int rc; + unsigned short acc2_data; + u8 buf[2]; + + if (chip->battery_missing) { + *ibat_ua = 0; + return 0; + } + + rc = qbg_read(chip, QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0, buf, 2); + if (rc < 0) { + pr_err("Failed to read average ibatt, rc=%d\n", rc); + return rc; + } + + acc2_data = buf[0] | (buf[1] << 8); + *ibat_ua = ((int16_t)acc2_data) * IBATT_10A_LSB * ICHG_FS_10A; + *ibat_ua = *ibat_ua / TEN_NANO_TO_MICRO; + + return 0; +} + +#define TENTH_FACTOR 10 +static int qbg_get_charge_counter(struct qti_qbg *chip, int *charge_count) +{ + + if (is_debug_batt_id(chip) || chip->battery_missing) { + *charge_count = -EINVAL; + return 0; + } + + *charge_count = chip->learned_capacity * chip->soc / TENTH_FACTOR; + return 0; +} + +static bool is_battery_present(struct qti_qbg *chip) +{ + bool present = false; + u8 reg; + int rc; + + rc = qbg_read(chip, QBG_MAIN_STATUS2, ®, 1); + if (rc < 0) + pr_err("Failed to read battery presence, rc=%d\n", rc); + else + present = !(reg & QBG_BATTERY_MISSING_BIT); + + return present; +} + +#define BID_RPULL_OHM 100000 +#define BID_VREF_MV 1875 +static int get_batt_id_ohm(struct qti_qbg *chip, u32 *batt_id_ohm) +{ + int rc, batt_id_mv; + int64_t denom; + + if (!chip->batt_id_chan) + return -EINVAL; + + /* Read battery-id */ + rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id_mv); + if (rc < 0) { + pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc); + return rc; + } + + batt_id_mv = div_s64(batt_id_mv, 1000); + if (batt_id_mv == 0) { + *batt_id_ohm = 0; + pr_debug("batt_id_mv = 0 from ADC\n"); + return 0; + } + + denom = div64_s64(BID_VREF_MV * 1000, batt_id_mv) - 1000; + if (denom <= 0) { + /* batt id connector might be open, return 0 kohms */ + *batt_id_ohm = 0; + return 0; + } + + *batt_id_ohm = div64_u64(BID_RPULL_OHM * 1000 + denom / 2, denom); + chip->batt_id_ohm = *batt_id_ohm; + + qbg_dbg(chip, QBG_DEBUG_PROFILE, "batt_id_mv=%d, batt_id_ohm=%d\n", + batt_id_mv, *batt_id_ohm); + + return 0; +} + +static int qbg_setup_battery(struct qti_qbg *chip) +{ + int rc = 0; + + chip->profile_loaded = false; + + if (!is_battery_present(chip)) { + qbg_dbg(chip, QBG_DEBUG_PROFILE, "Battery Missing!\n"); + chip->battery_missing = true; + } else { + /* battery present */ + rc = get_batt_id_ohm(chip, &chip->batt_id_ohm); + if (rc < 0) { + pr_err("Failed to detect batt_id rc=%d\n", rc); + } else { + rc = qbg_load_battery_profile(chip); + if (rc < 0) + pr_err("Failed to load battery-profile, rc=%d\n", rc); + else + chip->profile_loaded = true; + } + } + + qbg_dbg(chip, QBG_DEBUG_PROFILE, "battery_missing=%s batt_id=%d Ohm profile_loaded=%d battery_type=%s\n", + chip->battery_missing ? "true" : "false", chip->batt_id_ohm, + chip->profile_loaded, chip->batt_type_str); + + return rc; +} + +static int qbg_get_pon_reading(struct qti_qbg *chip, unsigned int *ocv, + unsigned int *ibat) +{ + int rc; + unsigned short int ocv0_data, ibat_data; + u8 acc_data[6]; + + rc = qbg_read(chip, QBG_MAIN_PON_OCV_ACC0_DATA0, (u8 *)acc_data, 6); + if (rc < 0) { + pr_err("Failed to read PON accumulator data, rc=%d\n", rc); + return rc; + } + + ocv0_data = (acc_data[1] << 8) | acc_data[0]; + ibat_data = (acc_data[5] << 8) | acc_data[4]; + + *ocv = ocv0_data * VBATT_1S_LSB; + if (*ocv < VBAT_0PCT_OCV) + *ocv = VBAT_0PCT_OCV; + + *ibat = ibat_data * IBATT_10A_LSB * ICHG_FS_10A; + qbg_dbg(chip, QBG_DEBUG_PON, "ocv:%u ibat:%u\n", ocv, ibat); + + return 0; +} + +static int qbg_determine_pon_soc(struct qti_qbg *chip) +{ + int rc; + union power_supply_propval prop = {0, }; + unsigned long rtc_sec, time_diff = 0; + unsigned int pon_ocv; + + /* Get RTC time here */ + rc = get_rtc_time(chip, &rtc_sec); + if (rc < 0) { + pr_err("Failed to read rtc time, rc=%d\n", rc); + return rc; + } + + if (!chip->profile_loaded) { + qbg_dbg(chip, QBG_DEBUG_SOC, "No Profile, skipping PON soc\n"); + return 0; + } + + rc = qbg_sdam_get_essential_params(chip, (u8 *)&chip->essential_params); + if (rc < 0) { + pr_err("Failed to read essential params, rc=%d\n", rc); + return rc; + } + + rc = qbg_get_pon_reading(chip, &chip->pon_ocv, &chip->pon_ibat); + if (rc < 0) { + pr_err("Failed to get QBG PON reading, rc=%d\n", rc); + return rc; + } + + /* Read battery-temp */ + rc = iio_read_channel_processed(chip->batt_temp_chan, &batt_temp); + if (rc < 0) { + pr_err("Failed to read BATT_TEMP over ADC, rc=%d\n", rc); + return rc; + } + chip->pon_tbat = (unsigned int)batt_temp; + + if (is_batt_available(chip)) { + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &prop); + if (rc < 0) + pr_err("Failed to get charger status, rc=%d\n", rc); + } + + pon_ocv = chip->pon_ocv / 10000; + rc = qbg_lookup_soc_ocv(chip->battery, &chip->pon_soc, pon_ocv, + prop.intval == POWER_SUPPLY_STATUS_CHARGING); + if (rc < 0) { + pr_err("Failed to lookup PON SOC, rc=%d\n", rc); + return rc; + } + chip->pon_soc = DIV_ROUND_CLOSEST(chip->pon_soc, 100); + chip->soc = chip->pon_soc; + + qbg_dbg(chip, QBG_DEBUG_PON, "Shutdown: SOC=%d OCV=%duV Ibat:%duA timediff=%lusecs, time_now=%lusecs\n", + chip->pon_soc, chip->pon_ocv, chip->pon_ibat, time_diff, rtc_sec); + + return 0; +} + +static int qbg_psy_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *pval) +{ + return 0; +} + +static int qbg_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *pval) +{ + if (psp == POWER_SUPPLY_PROP_TYPE) + pval->intval = POWER_SUPPLY_TYPE_MAINS; + + return 0; +} + +static enum power_supply_property qbg_psy_props[] = { + POWER_SUPPLY_PROP_TYPE, +}; + +static struct power_supply_desc qbg_psy_desc = { + .name = "bms", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = qbg_psy_props, + .num_properties = ARRAY_SIZE(qbg_psy_props), + .get_property = qbg_psy_get_prop, + .set_property = qbg_psy_set_prop, +}; + +static int qbg_init_psy(struct qti_qbg *chip) +{ + struct power_supply_config qbg_psy_cfg = {}; + int rc; + + qbg_psy_cfg.drv_data = chip; + chip->qbg_psy = devm_power_supply_register(chip->dev, &qbg_psy_desc, + &qbg_psy_cfg); + if (IS_ERR_OR_NULL(chip->qbg_psy)) { + pr_err("Failed to register qbg_psy, rc = %d\n", + PTR_ERR(chip->qbg_psy)); + return PTR_ERR(chip->qbg_psy); + } + + chip->nb.notifier_call = qbg_notifier_cb; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) + pr_err("Failed to register psy notifier rc = %d\n", rc); + + return rc; +} + +static int qbg_iio_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val1, + int val2, long mask) +{ + return 0; +} + +static int qbg_iio_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val1, + int *val2, long mask) +{ + struct qti_qbg *chip = iio_priv(indio_dev); + int rc = 0; + + *val1 = 0; + + switch (chan->channel) { + case PSY_IIO_DEBUG_BATTERY: + *val1 = is_debug_batt_id(chip); + break; + case PSY_IIO_CAPACITY: + rc = qbg_get_battery_capacity(chip, val1); + break; + case PSY_IIO_REAL_CAPACITY: + rc = qbg_get_battery_capacity(chip, val1); + break; + case PSY_IIO_TEMP: + *val1 = chip->tbat; + break; + case PSY_IIO_VOLTAGE_MAX: + *val1 = chip->float_volt_uv; + break; + case PSY_IIO_VOLTAGE_OCV: + *val1 = chip->ocv_uv; + break; + case PSY_IIO_VOLTAGE_NOW: + rc = qbg_get_battery_voltage(chip, val1); + break; + case PSY_IIO_CURRENT_NOW: + rc = qbg_get_battery_current(chip, val1); + break; + case PSY_IIO_RESISTANCE_ID: + *val1 = chip->batt_id_ohm; + break; + case PSY_IIO_TIME_TO_FULL_AVG: + *val1 = chip->ttf; + break; + case PSY_IIO_TIME_TO_FULL_NOW: + *val1 = chip->ttf; + break; + case PSY_IIO_TIME_TO_EMPTY_AVG: + *val1 = chip->tte; + break; + case PSY_IIO_ESR_ACTUAL: + *val1 = chip->esr; + break; + case PSY_IIO_SOH: + *val1 = chip->soh; + break; + case PSY_IIO_CHARGE_COUNTER: + rc = qbg_get_charge_counter(chip, val1); + break; + case PSY_IIO_CYCLE_COUNT: + *val1 = chip->charge_cycle_count; + break; + case PSY_IIO_CHARGE_FULL: + *val1 = chip->learned_capacity; + break; + case PSY_IIO_CHARGE_FULL_DESIGN: + *val1 = chip->nominal_capacity; + break; + default: + pr_err_ratelimited("Unsupported property %d\n", chan->channel); + rc = -EINVAL; + break; + } + + return (rc < 0) ? rc : IIO_VAL_INT; +} + +static int qbg_iio_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + struct qti_qbg *chip = iio_priv(indio_dev); + struct iio_chan_spec *iio_chan = chip->iio_chan; + int i; + + for (i = 0; i < ARRAY_SIZE(qbg_iio_psy_channels); i++, iio_chan++) { + if (iio_chan->channel == iiospec->args[0]) + return i; + } + + return -EINVAL; +} + +static const struct iio_info qbg_iio_info = { + .read_raw = qbg_iio_read_raw, + .write_raw = qbg_iio_write_raw, + .of_xlate = qbg_iio_of_xlate, +}; + +static int qbg_init_iio(struct qti_qbg *chip, + struct platform_device *pdev) +{ + struct iio_dev *indio_dev = chip->indio_dev; + struct iio_chan_spec *chan; + int qbg_num_iio_channels = ARRAY_SIZE(qbg_iio_psy_channels); + int rc, i; + + chip->iio_chan = devm_kcalloc(chip->dev, qbg_num_iio_channels, + sizeof(*chip->iio_chan), GFP_KERNEL); + if (!chip->iio_chan) + return -ENOMEM; + + indio_dev->info = &qbg_iio_info; + indio_dev->dev.parent = chip->dev; + indio_dev->dev.of_node = chip->dev->of_node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = chip->iio_chan; + indio_dev->num_channels = qbg_num_iio_channels; + + for (i = 0; i < qbg_num_iio_channels; i++) { + chan = &chip->iio_chan[i]; + chan->address = i; + chan->channel = qbg_iio_psy_channels[i].channel_num; + chan->type = qbg_iio_psy_channels[i].type; + chan->datasheet_name = + qbg_iio_psy_channels[i].datasheet_name; + chan->extend_name = + qbg_iio_psy_channels[i].datasheet_name; + chan->info_mask_separate = + qbg_iio_psy_channels[i].info_mask; + } + + rc = devm_iio_device_register(chip->dev, indio_dev); + if (rc < 0) + pr_err("Failed to register QBG IIO device, rc=%d\n", rc); + + return rc; +} + +static ssize_t qbg_device_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int rc; + struct qti_qbg *chip = file->private_data; + unsigned long data_size = sizeof(chip->kdata); + + if (count < data_size) { + pr_err("Invalid datasize %lu, expected lesser then %zu\n", + data_size, count); + return -EINVAL; + } + + /* non-blocking access, return */ + if (!chip->data_ready && (file->f_flags & O_NONBLOCK)) + return 0; + + /* blocking access wait on data_ready */ + if (!(file->f_flags & O_NONBLOCK)) { + rc = wait_event_interruptible(chip->qbg_wait_q, + chip->data_ready); + if (rc < 0) { + pr_debug("Failed wait! rc=%d\n", rc); + return rc; + } + } + + mutex_lock(&chip->data_lock); + if (!chip->data_ready) { + pr_debug("No Data, false wakeup\n"); + rc = -ENODATA; + goto fail_read; + } + + if (copy_to_user(buf, &chip->kdata, data_size)) { + pr_err("Failed to copy_to_user\n"); + rc = -EFAULT; + goto fail_read; + } + chip->data_ready = false; + + qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device read complete Size=%ld\n", + data_size); + + /* clear data */ + memset(&chip->kdata, 0, sizeof(chip->kdata)); + + rc = data_size; + +fail_read: + mutex_unlock(&chip->data_lock); + return rc; +} + +static ssize_t qbg_device_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int rc = -EINVAL; + struct qti_qbg *chip = file->private_data; + unsigned long data_size = sizeof(chip->udata); + + mutex_lock(&chip->data_lock); + if (count == 0) { + pr_err("No data!\n"); + rc = -ENODATA; + goto fail; + } + + if (count < data_size) { + pr_err("Invalid datasize %zu expected %lu\n", count, data_size); + goto fail; + } + + if (copy_from_user(&chip->udata, buf, data_size)) { + pr_err("Failed to copy_from_user\n"); + rc = -EFAULT; + goto fail; + } + + rc = data_size; + schedule_work(&chip->udata_work); + + qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG write complete size=%d\n", rc); +fail: + mutex_unlock(&chip->data_lock); + return rc; +} + +static unsigned int qbg_device_poll(struct file *file, poll_table *wait) +{ + struct qti_qbg *chip = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &chip->qbg_wait_q, wait); + + if (chip->data_ready) + mask = POLLIN | POLLRDNORM; + + return mask; +} + +static int qbg_device_open(struct inode *inode, struct file *file) +{ + struct qti_qbg *chip = container_of(inode->i_cdev, + struct qti_qbg, qbg_cdev); + + file->private_data = chip; + + qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device opened!\n"); + + return 0; +} + +static int qbg_device_release(struct inode *inode, struct file *file) +{ + struct qti_qbg *chip = container_of(inode->i_cdev, + struct qti_qbg, qbg_cdev); + + file->private_data = NULL; + + qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device closed!\n"); + + return 0; +} + +static long qbg_device_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct qti_qbg *chip = file->private_data; + struct qbg_config __user *config_user; + struct qbg_config config; + struct qbg_essential_params __user *params_user; + unsigned long rtc_sec; + int rc = 0; + + if (!chip) { + pr_err("Device private data not set!\n"); + return -EINVAL; + } + + if (!arg) { + pr_err("Invalid user pointer\n"); + return -EINVAL; + } + + /* Get RTC time here */ + rc = get_rtc_time(chip, &rtc_sec); + if (rc < 0) { + pr_err("Failed to read rtc time, rc=%d\n", rc); + return rc; + } + + switch (cmd) { + case QBGIOCXCFG: + config_user = (struct qbg_config __user *)arg; + config.current_time = rtc_sec; + config.batt_id = chip->batt_id_ohm; + config.pon_ocv = chip->pon_ocv; + config.pon_ibat = chip->pon_ibat; + config.pon_tbat = chip->pon_tbat; + config.pon_soc = chip->pon_soc; + config.float_volt_uv = chip->float_volt_uv; + config.vbat_cutoff_mv = chip->vbat_cutoff_mv; + config.ibat_cutoff_ma = chip->ibat_cutoff_ma; + config.vph_min_mv = chip->vph_min_mv; + config.iterm_ma = chip->iterm_ma; + config.rconn_mohm = chip->rconn_mohm; + + if (copy_to_user(config_user, (void *)&config, sizeof(config))) { + pr_err("Failed to copy QBG config to user\n"); + return -EFAULT; + } + + qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXCFG: battid:%u pon_ocv:%u pon_ibat:%u pon_soc:%u vbatt_cutoff_mv:%u iterm_ma:%u\n", + config.batt_id, config.pon_ocv, + config.pon_ibat, config.pon_soc, + config.vbat_cutoff_mv, config.iterm_ma); + break; + case QBGIOCXEPR: + params_user = (struct qbg_essential_params __user *)arg; + rc = qbg_sdam_get_essential_params(chip, + (u8 *)&chip->essential_params); + if (rc < 0) { + pr_err("Failed to read essential params, rc=%d\n", rc); + return -EFAULT; + } + + if (copy_to_user(params_user, (void *)&chip->essential_params, + sizeof(chip->essential_params))) { + pr_err("Failed to copy QBG essential params to user\n"); + return -EFAULT; + } + + break; + case QBGIOCXEPW: + params_user = (struct qbg_essential_params __user *)arg; + if (copy_from_user((void *)&chip->essential_params, params_user, + sizeof(chip->essential_params))) { + pr_err("Failed to copy QBG essential params from user\n"); + return -EFAULT; + } + chip->previous_ep_time = rtc_sec; + chip->essential_params.rtc_time = rtc_sec; + + rc = qbg_sdam_set_essential_params(chip, + (u8 *)&chip->essential_params); + if (rc < 0) { + pr_err("Failed to write essential params, rc=%d\n", rc); + return -EFAULT; + } + qbg_dbg(chip, QBG_DEBUG_SDAM, "Essential params written, time:%lu secs\n", + rtc_sec); + + break; + default: + pr_err("IOCTL %d not supported\n", cmd); + rc = -EINVAL; + } + + return rc; +} + +static const struct file_operations qbg_fops = { + .owner = THIS_MODULE, + .open = qbg_device_open, + .release = qbg_device_release, + .read = qbg_device_read, + .write = qbg_device_write, + .poll = qbg_device_poll, + .unlocked_ioctl = qbg_device_ioctl, + .compat_ioctl = qbg_device_ioctl, +}; + +static int qbg_register_device(struct qti_qbg *chip) +{ + int rc; + + rc = alloc_chrdev_region(&chip->dev_no, 0, 1, "qbg"); + if (rc < 0) { + pr_err("Failed to allocate chardev rc=%d\n", rc); + return rc; + } + + cdev_init(&chip->qbg_cdev, &qbg_fops); + rc = cdev_add(&chip->qbg_cdev, chip->dev_no, 1); + if (rc < 0) { + pr_err("Failed to cdev_add rc=%d\n", rc); + goto unregister_chrdev; + } + + chip->qbg_class = class_create(THIS_MODULE, "qbg"); + if (IS_ERR_OR_NULL(chip->qbg_class)) { + pr_err("Failed to create qbg class\n"); + rc = -EINVAL; + goto delete_cdev; + } + + chip->qbg_device = device_create(chip->qbg_class, NULL, chip->dev_no, + NULL, "qbg"); + if (IS_ERR(chip->qbg_device)) { + pr_err("Failed to create qbg_device\n"); + rc = -EINVAL; + goto destroy_class; + } + + qbg_dbg(chip, QBG_DEBUG_DEVICE, "'/dev/qbg' successfully created\n"); + + return 0; + +destroy_class: + class_destroy(chip->qbg_class); +delete_cdev: + cdev_del(&chip->qbg_cdev); +unregister_chrdev: + unregister_chrdev_region(chip->dev_no, 1); + + return rc; +} + +static int qbg_register_interrupts(struct qti_qbg *chip) +{ + int rc; + + rc = devm_request_threaded_irq(chip->dev, chip->irq, NULL, + qbg_data_full_irq_handler, IRQF_ONESHOT, + "qbg-sdam", chip); + if (rc < 0) + dev_err(chip->dev, "Failed to request IRQ(qbg-sdam), rc=%d\n", + rc); + + return rc; +} + +static int qbg_parse_sdam_dt(struct qti_qbg *chip, struct device_node *node) +{ + int rc; + + chip->irq = of_irq_get_byname(node, "qbg-sdam"); + if (chip->irq < 0) { + pr_err("Failed to get irq for QBG, rc=%d\n", chip->irq); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,num-data-sdams", + &chip->num_data_sdams); + if (rc < 0) { + pr_err("Failed to read number of DATA SDAMs for QBG, rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(node, "sdam-base", &chip->sdam_base); + if (rc < 0) { + pr_err("Failed to read SDAM base address, rc=%d\n", rc); + return rc; + } + + return rc; +} + +#define QBG_DEFAULT_VBAT_CUTOFF_MV 3100 +#define QBG_DEFAULT_IBAT_CUTOFF_MA 150 +#define QBG_DEFAULT_VPH_MIN_MV 2700 +#define QBG_DEFAULT_ITERM_MA 100 +#define QBG_DEFAULT_RCONN_MOHM 0 +static int qbg_parse_dt(struct qti_qbg *chip) +{ + struct device_node *node = chip->dev->of_node; + int rc = 0, i; + u32 val; + + rc = qbg_parse_sdam_dt(chip, node); + if (rc < 0) { + pr_err("Failed to QBG SDAM DT, rc=%d\n", rc); + return rc; + } + + chip->vbat_cutoff_mv = QBG_DEFAULT_VBAT_CUTOFF_MV; + rc = of_property_read_u32(node, "qcom,vbat-cutoff-mv", &val); + if (!rc) + chip->vbat_cutoff_mv = val; + + chip->ibat_cutoff_ma = QBG_DEFAULT_IBAT_CUTOFF_MA; + rc = of_property_read_u32(node, "qcom,ibat-cutoff-ma", &val); + if (!rc) + chip->ibat_cutoff_ma = val; + + chip->vph_min_mv = QBG_DEFAULT_VPH_MIN_MV; + rc = of_property_read_u32(node, "qcom,vph-min-mv", &val); + if (!rc) + chip->vph_min_mv = val; + + chip->iterm_ma = QBG_DEFAULT_ITERM_MA; + rc = of_property_read_u32(node, "qcom,iterm-ma", &val); + if (!rc) + chip->iterm_ma = val; + + chip->rconn_mohm = QBG_DEFAULT_RCONN_MOHM; + rc = of_property_read_u32(node, "qcom,rconn-mohm", &val); + if (!rc) + chip->rconn_mohm = val; + + return 0; +} + +static int qti_qbg_probe(struct platform_device *pdev) +{ + struct qti_qbg *chip; + struct iio_dev *indio_dev; + int rc; + u32 val; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->dev = &pdev->dev; + chip->indio_dev = indio_dev; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Failed to get regmap\n"); + return -EINVAL; + } + + rc = of_property_read_u32(pdev->dev.of_node, "reg", &val); + if (rc < 0) { + pr_err("Failed to get base address for QBG, rc = %d\n", rc); + return rc; + } + chip->base = val; + + INIT_WORK(&chip->status_change_work, status_change_work); + INIT_WORK(&chip->udata_work, process_udata_work); + mutex_init(&chip->fifo_lock); + mutex_init(&chip->data_lock); + dev_set_drvdata(chip->dev, chip); + init_waitqueue_head(&chip->qbg_wait_q); + + chip->debug_mask = &qbg_debug_mask; + + chip->soc = INT_MIN; + chip->batt_soc = INT_MIN; + chip->sys_soc = INT_MIN; + chip->esr = INT_MIN; + chip->ocv_uv = INT_MIN; + chip->pon_ocv = INT_MIN; + chip->charge_cycle_count = INT_MIN; + chip->nominal_capacity = INT_MIN; + chip->learned_capacity = INT_MIN; + chip->ttf = INT_MIN; + chip->tte = INT_MIN; + chip->soh = INT_MIN; + chip->tbat = INT_MIN; + + rc = qbg_parse_dt(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to parse QBG devicetree, rc=%d\n", rc); + return rc; + } + + /* ADC for Battery-ID */ + chip->batt_id_chan = devm_iio_channel_get(&pdev->dev, "batt-id"); + if (IS_ERR(chip->batt_id_chan)) { + rc = PTR_ERR(chip->batt_id_chan); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "batt-id channel unavailable, rc=%d\n", rc); + chip->batt_id_chan = NULL; + return rc; + } + + /* ADC for Battery-Temp */ + chip->batt_temp_chan = devm_iio_channel_get(&pdev->dev, "batt-temp"); + if (IS_ERR(chip->batt_temp_chan)) { + rc = PTR_ERR(chip->batt_temp_chan); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "batt-temp channel unavailable, rc=%d\n", rc); + chip->batt_temp_chan = NULL; + return rc; + } + + chip->rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + if (chip->rtc == NULL) + return -EPROBE_DEFER; + + rc = qbg_init_sdam(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to initialize QBG sdam, rc=%d\n", rc); + return rc; + } + + rc = qbg_setup_battery(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to setup battery for QBG, rc=%d\n", rc); + return rc; + } + + rc = qbg_determine_pon_soc(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to determine initial state, rc=%d\n", rc); + return rc; + } + + rc = qbg_register_device(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to register QBG device, rc=%d\n", rc); + return rc; + } + + rc = qbg_init_iio(chip, pdev); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to initialize QBG IIO device, rc=%d\n", rc); + return rc; + } + + rc = qbg_init_psy(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to initialize QBG PSY, rc=%d\n", rc); + return rc; + } + + rc = qbg_register_interrupts(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to register QBG interrupts, rc=%d\n", rc); + return rc; + } + + dev_info(&pdev->dev, "QBG initialized! battery_profile=%s SOC=%d\n", + qbg_get_battery_type(chip), chip->soc); + + return rc; +} + +static int qti_qbg_remove(struct platform_device *pdev) +{ + struct qti_qbg *chip = platform_get_drvdata(pdev); + + if (chip->rtc) + rtc_class_close(chip->rtc); + cancel_work_sync(&chip->status_change_work); + cancel_work_sync(&chip->udata_work); + mutex_destroy(&chip->fifo_lock); + mutex_destroy(&chip->data_lock); + cdev_del(&chip->qbg_cdev); + unregister_chrdev_region(chip->dev_no, 1); + + return 0; +} + +static const struct of_device_id qbg_match_table[] = { + { .compatible = "qcom,qbg", }, + { }, +}; + +static struct platform_driver qti_qbg_driver = { + .driver = { + .name = "qti_qbg", + .of_match_table = qbg_match_table, + }, + .probe = qti_qbg_probe, + .remove = qti_qbg_remove, +}; +module_platform_driver(qti_qbg_driver); + +MODULE_DESCRIPTION("QTI QBG (Qualcomm Battery Gauging) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/uapi/linux/qbg-profile.h b/include/uapi/linux/qbg-profile.h new file mode 100644 index 000000000000..02906ce35009 --- /dev/null +++ b/include/uapi/linux/qbg-profile.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QBG_PROFILE_H__ +#define __QBG_PROFILE_H__ + +#define MAX_BP_LUT_ROWS 35 +#define MAX_BP_LUT_COLS 8 +#define MAX_PROFILE_NAME_LENGTH 256 + +enum profile_table_type { + CHARGE_TABLE = 0, + DISCHARGE_TABLE, +}; + +struct battery_data_table { + unsigned short int table[MAX_BP_LUT_ROWS][MAX_BP_LUT_COLS]; + int unit_conv_factor[MAX_BP_LUT_COLS]; + unsigned short int nrows; + unsigned short int ncols; +}; + +struct battery_config { + char bp_profile_name[MAX_PROFILE_NAME_LENGTH]; + int bp_batt_id; + int capacity; + int bp_checksum; + int soh_range_high; + int soh_range_low; + int normal_impedance; + int aged_impedance; + int normal_capacity; + int aged_capacity; + int recharge_soc_delta; + int recharge_vflt_delta; + int recharge_iterm; +}; + +struct battery_profile_table { + enum profile_table_type table_type; + int table_index; + struct battery_data_table *table; +}; + +/* IOCTLs to query battery profile data */ +#define BPIOCXBP _IOWR('B', 0x01, struct battery_config) /* Battery configuration */ +#define BPIOCXBPTABLE _IOWR('B', 0x02, struct battery_profile_table) /* Battery profile table */ + +#endif diff --git a/include/uapi/linux/qbg.h b/include/uapi/linux/qbg.h new file mode 100644 index 000000000000..f8bbb4e1a90c --- /dev/null +++ b/include/uapi/linux/qbg.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QBG_H__ +#define __QBG_H__ + +#define MAX_FIFO_COUNT 36 + +enum QBG_SDAM_DATA_OFFSET { + QBG_ACC0_OFFSET = 0, + QBG_ACC1_OFFSET = 2, + QBG_ACC2_OFFSET = 4, + QBG_TBAT_OFFSET = 6, + QBG_IBAT_OFFSET = 8, + QBG_VREF_OFFSET = 10, + QBG_DATA_TAG_OFFSET = 12, + QBG_QG_STS_OFFSET, + QBG_STS1_OFFSET, + QBG_STS2_OFFSET, + QBG_STS3_OFFSET, + QBG_ONE_FIFO_LENGTH, +}; + +enum qbg { + QBG_PARAM_SOC, + QBG_PARAM_BATT_SOC, + QBG_PARAM_SYS_SOC, + QBG_PARAM_ESR, + QBG_PARAM_OCV_UV, + QBG_PARAM_MAX_LOAD_NOW, + QBG_PARAM_MAX_LOAD_AVG, + QBG_PARAM_HOLD_SOC_100PCT, + QBG_PARAM_CHARGE_CYCLE_COUNT, + QBG_PARAM_LEARNED_CAPACITY, + QBG_PARAM_TTF_100MS, + QBG_PARAM_TTE_100MS, + QBG_PARAM_SOH, + QBG_PARAM_TBAT, + QBG_PARAM_SYS_SOC_HOLD_100PCT, + QBG_PARAM_JEITA_COOL_THRESHOLD, + QBG_PARAM_TOTAL_IMPEDANCE, + QBG_PARAM_PON_OCV, + QBG_PARAM_PON_IBAT, + QBG_PARAM_PON_SOC, + QBG_PARAM_BATT_ID, + QBG_PARAM_FIFO_TIMESTAMP, + QBG_PARAM_MAX, +}; + +struct qbg_essential_params { + short int msoc; + short int cutoff_soc; + short int full_soc; + short int x0; + short int x1; + short int x2; + short int soh_r; + short int soh_c; + short int theta0; + short int theta1; + short int theta2; + short int i1full; + short int i2full; + short int i1cutoff; + short int i2cutoff; + short int syssoc; + int discharge_cycle_count; + int charge_cycle_count; + unsigned int rtc_time; + short int batt_therm; + unsigned short int ocv; +} __attribute__ ((__packed__)); + +struct fifo_data { + unsigned short int v1; + unsigned short int v2; + unsigned short int i; + unsigned short int tbat; + unsigned short int ibat; + unsigned short int vref; + char data_tag; + char qg_sts; + char sts1; + char sts2; + char sts3; +} __attribute__ ((__packed__)); + +struct k_fifo_data { + unsigned int v1; + unsigned int v2; + unsigned int i; + unsigned int tbat; + unsigned int ibat; + unsigned int vref; + unsigned int data_tag; + unsigned int qg_sts; + unsigned int sts1; + unsigned int sts2; + unsigned int sts3; +} __attribute__ ((__packed__)); + +struct qbg_config { + unsigned int batt_id; + unsigned int pon_ocv; + unsigned int pon_ibat; + unsigned int pon_tbat; + unsigned int pon_soc; + unsigned int float_volt_uv; + unsigned int fastchg_curr_ma; + unsigned int vbat_cutoff_mv; + unsigned int ibat_cutoff_ma; + unsigned int vph_min_mv; + unsigned int iterm_ma; + unsigned int rconn_mohm; + unsigned long current_time; +} __attribute__ ((__packed__)); + +struct qbg_param { + unsigned int data; + _Bool valid; +}; + +struct qbg_kernel_data { + unsigned int seq_no; + unsigned int fifo_time; + unsigned int fifo_count; + struct k_fifo_data fifo[MAX_FIFO_COUNT]; + struct qbg_param param[QBG_PARAM_MAX]; +} __attribute__ ((__packed__)); + +struct qbg_user_data { + struct qbg_param param[QBG_PARAM_MAX]; +} __attribute__ ((__packed__)); + +/* IOCTLs to read & write QBG config and essential params */ +#define QBGIOCXCFG _IOR('B', 0x01, struct qbg_config) +#define QBGIOCXEPR _IOR('B', 0x02, struct qbg_essential_params) +#define QBGIOCXEPW _IOWR('B', 0x03, struct qbg_essential_params) + +#endif