| /* |
| * Fuel gauge driver for Maxim 17201/17205 |
| * |
| * Copyright (C) 2018 Google Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": %s " fmt, __func__ |
| |
| #include <linux/err.h> |
| #include <linux/i2c.h> |
| #include <linux/iio/consumer.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/power_supply.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/time.h> |
| |
| #include <linux/cdev.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> /* register_chrdev, unregister_chrdev */ |
| #include <linux/module.h> |
| #include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */ |
| |
| #ifdef CONFIG_DEBUG_FS |
| #include <linux/debugfs.h> |
| #endif |
| |
| #define MAX1720X_TRECALL_MS 5 |
| #define MAX1720X_TPOR_MS 150 |
| #define MAX1720X_TICLR_MS 500 |
| #define MAX1720X_I2C_DRIVER_NAME "max1720x_fg_irq" |
| #define MAX1720X_N_OF_HISTORY_PAGES 203 |
| #define MAX1720X_DELAY_INIT_MS 1000 |
| #define FULLCAPNOM_STABILIZE_CYCLES 5 |
| |
| #define HISTORY_DEVICENAME "maxfg_history" |
| |
| enum max1720x_register { |
| /* ModelGauge m5 Register */ |
| MAX1720X_STATUS = 0x00, |
| MAX1720X_VALRTTH = 0x01, |
| MAX1720X_TALRTTH = 0x02, |
| MAX1720X_SALRTTH = 0x03, |
| MAX1720X_ATRATE = 0x04, |
| MAX1720X_REPCAP = 0x05, |
| MAX1720X_REPSOC = 0x06, |
| MAX1720X_AGE = 0x07, |
| MAX1720X_TEMP = 0x08, |
| MAX1720X_VCELL = 0x09, |
| MAX1720X_CURRENT = 0x0A, |
| MAX1720X_AVGCURRENT = 0x0B, |
| MAX1720X_QRESIDUAL = 0x0C, |
| MAX1720X_MIXSOC = 0x0D, |
| MAX1720X_AVSOC = 0x0E, |
| MAX1720X_MIXCAP = 0x0F, |
| |
| MAX1720X_FULLCAP = 0x10, |
| MAX1720X_TTE = 0x11, |
| MAX1720X_QRTABLE00 = 0x12, |
| MAX1720X_FULLSOCTHR = 0x13, |
| MAX1720X_RCELL = 0x14, |
| MAX1720X_RFAST = 0x15, |
| MAX1720X_AVGTA = 0x16, |
| MAX1720X_CYCLES = 0x17, |
| MAX1720X_DESIGNCAP = 0x18, |
| MAX1720X_AVGVCELL = 0x19, |
| MAX1720X_MAXMINTEMP = 0x1A, |
| MAX1720X_MAXMINVOLT = 0x1B, |
| MAX1720X_MAXMINCURR = 0x1C, |
| MAX1720X_CONFIG = 0x1D, |
| MAX1720X_ICHGTERM = 0x1E, |
| MAX1720X_AVCAP = 0x1F, |
| |
| MAX1720X_TTF = 0x20, |
| MAX1720X_DEVNAME = 0x21, |
| MAX1720X_QRTABLE10 = 0x22, |
| MAX1720X_FULLCAPNOM = 0x23, |
| MAX1720X_AIN0 = 0x27, |
| MAX1720X_LEARNCFG = 0x28, |
| MAX1720X_FILTERCFG = 0x29, |
| MAX1720X_RELAXCFG = 0x2A, |
| MAX1720X_MISCCFG = 0x2B, |
| MAX1720X_TGAIN = 0x2C, |
| Max1720x_TOff = 0x2D, |
| MAX1720X_CGAIN = 0x2E, |
| MAX1720X_COFF = 0x2F, |
| |
| MAX1720X_QRTABLE20 = 0x32, |
| MAX1720X_FULLCAPREP = 0x35, |
| MAX1720X_IAVGEMPTY = 0x36, |
| MAX1720X_RCOMP0 = 0x38, |
| MAX1720X_TEMPCO = 0x39, |
| MAX1720X_VEMPTY = 0x3A, |
| MAX1720X_FSTAT = 0x3D, |
| MAX1720X_TIMER = 0x3E, |
| MAX1720X_SHDNTIMER = 0x3F, |
| |
| MAX1720X_QRTABLE30 = 0x42, |
| MAX1720X_DQACC = 0x45, |
| MAX1720X_DPACC = 0x46, |
| MAX1720X_VFREMCAP = 0x4A, |
| MAX1720X_QH = 0x4D, |
| |
| MAX1720X_STATUS2 = 0xB0, |
| MAX1720X_IALRTTH = 0xB4, |
| MAX1720X_VSHDNCFG = 0xB8, |
| MAX1720X_AGEFORECAST = 0xB9, |
| MAX1720X_HIBCFG = 0xBA, |
| MAX1720X_CONFIG2 = 0xBB, |
| MAX1720X_VRIPPLE = 0xBC, |
| MAX1720X_PACKCFG = 0xBD, |
| MAX1720X_TIMERH = 0xBE, |
| |
| MAX1720X_AVGCELL4 = 0xD1, |
| MAX1720X_AVGCELL3 = 0xD2, |
| MAX1720X_AVGCELL2 = 0xD3, |
| MAX1720X_AVGCELL1 = 0xD4, |
| MAX1720X_CELL4 = 0xD5, |
| MAX1720X_CELL3 = 0xD6, |
| MAX1720X_CELL2 = 0xD7, |
| MAX1720X_CELL1 = 0xD8, |
| MAX1720X_CELLX = 0xD9, |
| MAX1720X_BATT = 0xDA, |
| MAX1720X_ATQRESIDUAL = 0xDC, |
| MAX1720X_ATTTE = 0xDD, |
| MAX1720X_ATAVSOC = 0xDE, |
| MAX1720X_ATAVCAP = 0xDF, |
| |
| /* Individual Registers */ |
| MAX1720X_COMMAND = 0x60, |
| MAX1720X_COMMSTAT = 0x61, |
| MAX1720X_LOCK = 0x7F, |
| MAX1720X_ODSCTH = 0xF2, |
| MAX1720X_ODSCCFG = 0xF3, |
| MAX1720X_VFOCV = 0xFB, |
| MAX1720X_VFSOC = 0xFF, |
| }; |
| |
| enum max1720x_status_bits { |
| MAX1720X_STATUS_POR = BIT(1), |
| MAX1720X_STATUS_IMN = BIT(2), |
| MAX1720X_STATUS_BST = BIT(3), |
| MAX1720X_STATUS_IMX = BIT(6), |
| MAX1720X_STATUS_DSOCI = BIT(7), |
| MAX1720X_STATUS_VMN = BIT(8), |
| MAX1720X_STATUS_TMN = BIT(9), |
| MAX1720X_STATUS_SMN = BIT(10), |
| MAX1720X_STATUS_BI = BIT(11), |
| MAX1720X_STATUS_VMX = BIT(12), |
| MAX1720X_STATUS_TMX = BIT(13), |
| MAX1720X_STATUS_SMX = BIT(14), |
| MAX1720X_STATUS_BR = BIT(15), |
| }; |
| |
| enum max1720x_commstat_bits { |
| MAX1720X_COMMSTAT_NVBUSY = BIT(1), |
| MAX1720X_COMMSTAT_NVERROR = BIT(2), |
| }; |
| |
| enum max1720x_config_bits { |
| MAX1720X_CONFIG_BER = BIT(0), |
| MAX1720X_CONFIG_BEI = BIT(1), |
| MAX1720X_CONFIG_AEN = BIT(2), |
| MAX1720X_CONFIG_FTHRM = BIT(3), |
| MAX1720X_CONFIG_ETHRM = BIT(4), |
| MAX1720X_CONFIG_COMMSH = BIT(6), |
| MAX1720X_CONFIG_SHDN = BIT(7), |
| MAX1720X_CONFIG_TEX = BIT(8), |
| MAX1720X_CONFIG_TEN = BIT(9), |
| MAX1720X_CONFIG_AINSH = BIT(10), |
| MAX1720X_CONFIG_ALRTP = BIT(11), |
| MAX1720X_CONFIG_VS = BIT(12), |
| MAX1720X_CONFIG_TS = BIT(13), |
| MAX1720X_CONFIG_SS = BIT(14), |
| }; |
| |
| enum max1720x_nnvcfg0_bits { |
| MAX1720X_NNVCFG0_ENSBS = BIT(0), |
| MAX1720X_NNVCFG0_ENHCFG = BIT(1), |
| MAX1720X_NNVCFG0_ENAF = BIT(2), |
| MAX1720X_NNVCFG0_ENMC = BIT(3), |
| MAX1720X_NNVCFG0_ENDC = BIT(4), |
| MAX1720X_NNVCFG0_ENVE = BIT(5), |
| MAX1720X_NNVCFG0_ENCG = BIT(6), |
| MAX1720X_NNVCFG0_ENICT = BIT(7), |
| MAX1720X_NNVCFG0_ENLCFG = BIT(8), |
| MAX1720X_NNVCFG0_ENRCFG = BIT(9), |
| MAX1720X_NNVCFG0_ENFCFG = BIT(10), |
| MAX1720X_NNVCFG0_ENCFG = BIT(11), |
| MAX1720X_NNVCFG0_ENX = BIT(14), |
| MAX1720X_NNVCFG0_ENOCV = BIT(15), |
| }; |
| |
| enum max1720x_command_bits { |
| MAX1720X_COMMAND_FUEL_GAUGE_RESET = 0x0001, |
| MAX1720X_COMMAND_HARDWARE_RESET = 0x000F, |
| MAX1720X_COMMAND_QUERY_REMAINING_UPDATES = 0xE2FA, |
| MAX1720X_COMMAND_COPY_NV_BLOCK = 0xE904, |
| MAX1720X_COMMAND_HISTORY_RECALL_WRITE_0 = 0xE2FB, |
| MAX1720X_COMMAND_HISTORY_RECALL_WRITE_1 = 0xE2FC, |
| MAX1720X_COMMAND_HISTORY_RECALL_VALID_0 = 0xE2FC, |
| MAX1720X_COMMAND_HISTORY_RECALL_VALID_1 = 0xE2FD, |
| MAX1720X_COMMAND_HISTORY_RECALL_VALID_2 = 0xE2FE, |
| MAX1720X_READ_HISTORY_CMD_BASE = 0xE226, |
| }; |
| |
| /** Nonvolatile Register Memory Map */ |
| enum max1720x_nvram { |
| MAX1720X_NVRAM_START = 0x80, |
| MAX1720X_NUSER18C = 0x8C, |
| MAX1720X_NUSER18D = 0x8D, |
| MAX1720X_NODSCTH = 0x8E, |
| MAX1720X_NODSCCFG = 0x8F, |
| MAX1720X_NLEARNCFG = 0x9F, |
| MAX1720X_NMISCCFG = 0xB2, |
| MAX1720X_NHIBCFG = 0xB4, |
| MAX1720X_NCONVGCFG = 0xB7, |
| MAX1720X_NNVCFG0 = 0xB8, |
| MAX1720X_NUSER1C4 = 0xC4, |
| MAX1720X_NUSER1C5 = 0xC5, |
| MAX1720X_NCGAIN = 0xC8, |
| MAX1720X_NMANFCTRNAME0 = 0xCC, |
| MAX1720X_NMANFCTRNAME1 = 0xCD, |
| MAX1720X_NMANFCTRNAME2 = 0xCE, |
| MAX1720X_NRSENSE = 0xCF, |
| MAX1720X_NUSER1D0 = 0xD0, |
| MAX1720X_NUSER1D1 = 0xD1, |
| MAX1720X_NUSER1D4 = 0xD4, |
| MAX1720X_NMANFCTRDATE = 0xD6, |
| MAX1720X_NFIRSTUSED = 0xD7, |
| MAX1720X_NSERIALNUMBER0 = 0xD8, |
| MAX1720X_NSERIALNUMBER1 = 0xD9, |
| MAX1720X_NSERIALNUMBER2 = 0xDA, |
| MAX1720X_NDEVICENAME0 = 0xDB, |
| MAX1720X_NDEVICENAME1 = 0xDC, |
| MAX1720X_NDEVICENAME2 = 0xDD, |
| MAX1720X_NDEVICENAME3 = 0xDE, |
| MAX1720X_NDEVICENAME4 = 0xDF, |
| MAX1720X_NVRAM_END = 0xE0, |
| MAX1720X_NVRAM_HISTORY_WRITE_STATUS_START = 0xE1, |
| MAX1720X_NVRAM_HISTORY_WRITE_STATUS_END = 0xEA, |
| MAX1720X_NVRAM_HISTORY_VALID_STATUS_START = 0xEB, |
| MAX1720X_NVRAM_HISTORY_VALID_STATUS_END = 0xE4, |
| MAX1720X_NVRAM_REMAINING_UPDATES = 0xED, |
| MAX1720X_NVRAM_HISTORY_END = 0xEF, |
| }; |
| |
| #define BUCKET_COUNT 10 |
| |
| const unsigned int max1720x_cycle_counter_addr[BUCKET_COUNT] = { |
| MAX1720X_NODSCTH, |
| MAX1720X_NODSCCFG, |
| MAX1720X_NMISCCFG, |
| MAX1720X_NHIBCFG, |
| MAX1720X_NMANFCTRNAME1, |
| MAX1720X_NMANFCTRNAME2, |
| MAX1720X_NFIRSTUSED, |
| MAX1720X_NDEVICENAME4, |
| MAX1720X_NUSER1C4, |
| MAX1720X_NUSER1C5, |
| }; |
| |
| #define NVRAM_U16_INDEX(reg) (reg - MAX1720X_NVRAM_START) |
| #define NVRAM_BYTE_INDEX(reg) (NVRAM_U16_INDEX(reg) * sizeof(u16)) |
| #define NVRAM_BYTE_INDEX_FROM(reg, base) ((reg - base) * sizeof(u16)) |
| #define MAX1720X_NVRAM_U16_SIZE NVRAM_U16_INDEX(MAX1720X_NVRAM_END) |
| #define MAX1720X_NVRAM_SIZE NVRAM_BYTE_INDEX(MAX1720X_NVRAM_END) |
| |
| #define MAX1720X_HISTORY_PAGE_SIZE \ |
| (MAX1720X_NVRAM_HISTORY_END - MAX1720X_NVRAM_END + 1) |
| |
| #define MAX1720X_N_OF_HISTORY_FLAGS_REG \ |
| (MAX1720X_NVRAM_HISTORY_END - \ |
| MAX1720X_NVRAM_HISTORY_WRITE_STATUS_START + 1 + \ |
| MAX1720X_NVRAM_HISTORY_WRITE_STATUS_END - \ |
| MAX1720X_NVRAM_END + 1) |
| #define MAX1720X_N_OF_QRTABLES 4 |
| |
| struct max1720x_cyc_ctr_data { |
| u16 count[BUCKET_COUNT]; |
| struct mutex lock; |
| int prev_soc; |
| }; |
| |
| |
| struct max1720x_history { |
| loff_t history_index; |
| int history_count; |
| bool *page_status; |
| u16 *history; |
| }; |
| |
| struct max1720x_chip { |
| struct device *dev; |
| struct regmap *regmap; |
| struct power_supply *psy; |
| struct delayed_work init_work; |
| struct work_struct cycle_count_work; |
| struct i2c_client *primary; |
| struct i2c_client *secondary; |
| struct regmap *regmap_nvram; |
| struct device_node *batt_node; |
| struct iio_channel *iio_ch; |
| struct max1720x_cyc_ctr_data cyc_ctr; |
| |
| /* history */ |
| struct mutex history_lock; |
| int hcmajor; |
| struct cdev hcdev; |
| struct class *hcclass; |
| bool history_available; |
| bool history_added; |
| |
| u16 RSense; |
| u16 RConfig; |
| bool init_complete; |
| bool resume_complete; |
| u16 health_status; |
| int fake_capacity; |
| int previous_qh; |
| int current_capacity; |
| int prev_charge_status; |
| char serial_number[25]; |
| bool offmode_charger; |
| u32 convgcfg_hysteresis; |
| int nb_convgcfg; |
| int curr_convgcfg_idx; |
| s16 *temp_convgcfg; |
| u16 *convgcfg_values; |
| struct mutex convgcfg_lock; |
| unsigned int debug_irq_none_cnt; |
| }; |
| |
| static inline int max1720x_regmap_read(struct regmap *map, |
| unsigned int reg, |
| u16 *val, |
| const char *name) |
| { |
| unsigned int tmp; |
| int rtn = regmap_read(map, reg, &tmp); |
| |
| if (rtn) |
| pr_err("Failed to read %s\n", name); |
| else |
| *val = tmp; |
| |
| return rtn; |
| |
| } |
| #define REGMAP_READ(regmap, what, dst) \ |
| max1720x_regmap_read(regmap, what, dst, #what) |
| |
| #define REGMAP_WRITE(regmap, what, data) \ |
| do { \ |
| int rtn; \ |
| rtn = regmap_write(regmap, what, data); \ |
| if (rtn) { \ |
| pr_err("Failed to write %s\n", #what); \ |
| } \ |
| } while (0) |
| |
| static char *psy_status_str[] = { |
| "Unknown", "Charging", "Discharging", "NotCharging", "Full" |
| }; |
| |
| bool max1720x_is_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case MAX1720X_COMMAND: |
| case MAX1720X_COMMSTAT: |
| case MAX1720X_LOCK: |
| case MAX1720X_ODSCTH: |
| case MAX1720X_ODSCCFG: |
| case MAX1720X_VFOCV: |
| case MAX1720X_VFSOC: |
| case 0x00 ... 0x4F: |
| case 0xB0 ... 0xDF: |
| return true; |
| } |
| return false; |
| } |
| |
| static const struct regmap_config max1720x_regmap_cfg = { |
| .reg_bits = 8, |
| .val_bits = 16, |
| .val_format_endian = REGMAP_ENDIAN_NATIVE, |
| .max_register = MAX1720X_VFSOC, |
| .readable_reg = max1720x_is_reg, |
| .volatile_reg = max1720x_is_reg, |
| |
| }; |
| |
| bool max1720x_is_nvram_reg(struct device *dev, unsigned int reg) |
| { |
| return (reg >= MAX1720X_NVRAM_START |
| && reg <= MAX1720X_NVRAM_HISTORY_END); |
| } |
| |
| static const struct regmap_config max1720x_regmap_nvram_cfg = { |
| .reg_bits = 8, |
| .val_bits = 16, |
| .val_format_endian = REGMAP_ENDIAN_NATIVE, |
| .max_register = MAX1720X_NVRAM_HISTORY_END, |
| .readable_reg = max1720x_is_nvram_reg, |
| .volatile_reg = max1720x_is_nvram_reg, |
| }; |
| |
| static inline int reg_to_percentage(u16 val) |
| { |
| /* LSB: 1/256% */ |
| return val >> 8; |
| } |
| |
| static inline int reg_to_twos_comp_int(u16 val) |
| { |
| /* Convert u16 to twos complement */ |
| return -(val & 0x8000) + (val & 0x7FFF); |
| } |
| |
| static inline int reg_to_micro_amp_h(s16 val, u16 rsense) |
| { |
| /* LSB: 5.0μVh/RSENSE ; Rsense LSB is 10μΩ */ |
| return div_s64((s64) val * 500000, rsense); |
| } |
| |
| static inline int reg_to_micro_volt(u16 val) |
| { |
| /* LSB: 0.078125mV */ |
| return div_u64((u64) val * 78125, 1000); |
| } |
| |
| static inline int reg_to_micro_amp(s16 val, u16 rsense) |
| { |
| /* LSB: 1.5625μV/RSENSE ; Rsense LSB is 10μΩ */ |
| return div_s64((s64) val * 156250, rsense); |
| } |
| |
| static inline int reg_to_deci_deg_cel(s16 val) |
| { |
| /* LSB: 1/256°C */ |
| return div_s64((s64) val * 10, 256); |
| } |
| |
| static inline int reg_to_resistance_micro_ohms(s16 val, u16 rsense) |
| { |
| /* LSB: 1/4096 Ohm */ |
| return div_s64((s64) val * 1000 * rsense, 4096); |
| } |
| |
| static inline int reg_to_cycles(s16 val) |
| { |
| /* LSB: 16% of one cycle */ |
| return DIV_ROUND_CLOSEST((int) val * 16, 100); |
| } |
| |
| static inline int reg_to_seconds(s16 val) |
| { |
| /* LSB: 5.625 seconds */ |
| return DIV_ROUND_CLOSEST((int) val * 5625, 1000); |
| } |
| |
| static void max1720x_read_log_write_status(struct max1720x_chip *chip, |
| u16 *buffer) |
| { |
| int i; |
| u16 data = 0; |
| |
| REGMAP_WRITE(chip->regmap, MAX1720X_COMMAND, |
| MAX1720X_COMMAND_HISTORY_RECALL_WRITE_0); |
| msleep(MAX1720X_TRECALL_MS); |
| for (i = MAX1720X_NVRAM_HISTORY_WRITE_STATUS_START; |
| i <= MAX1720X_NVRAM_HISTORY_END; i++) { |
| (void) REGMAP_READ(chip->regmap_nvram, i, &data); |
| *buffer++ = data; |
| } |
| REGMAP_WRITE(chip->regmap, MAX1720X_COMMAND, |
| MAX1720X_COMMAND_HISTORY_RECALL_WRITE_1); |
| msleep(MAX1720X_TRECALL_MS); |
| for (i = MAX1720X_NVRAM_END; |
| i <= MAX1720X_NVRAM_HISTORY_WRITE_STATUS_END; i++) { |
| (void) REGMAP_READ(chip->regmap_nvram, i, &data); |
| *buffer++ = data; |
| } |
| } |
| |
| static void max1720x_read_log_valid_status(struct max1720x_chip *chip, |
| u16 *buffer) |
| { |
| int i; |
| u16 data = 0; |
| |
| REGMAP_WRITE(chip->regmap, MAX1720X_COMMAND, |
| MAX1720X_COMMAND_HISTORY_RECALL_VALID_0); |
| msleep(MAX1720X_TRECALL_MS); |
| for (i = MAX1720X_NVRAM_HISTORY_VALID_STATUS_START; |
| i <= MAX1720X_NVRAM_HISTORY_END; i++) { |
| (void) REGMAP_READ(chip->regmap_nvram, i, &data); |
| *buffer++ = data; |
| } |
| REGMAP_WRITE(chip->regmap, MAX1720X_COMMAND, |
| MAX1720X_COMMAND_HISTORY_RECALL_VALID_1); |
| msleep(MAX1720X_TRECALL_MS); |
| for (i = MAX1720X_NVRAM_END; i <= MAX1720X_NVRAM_HISTORY_END; i++) { |
| (void) REGMAP_READ(chip->regmap_nvram, i, &data); |
| *buffer++ = data; |
| } |
| REGMAP_WRITE(chip->regmap, MAX1720X_COMMAND, |
| MAX1720X_COMMAND_HISTORY_RECALL_VALID_2); |
| msleep(MAX1720X_TRECALL_MS); |
| for (i = MAX1720X_NVRAM_END; |
| i <= MAX1720X_NVRAM_HISTORY_VALID_STATUS_END; i++) { |
| (void) REGMAP_READ(chip->regmap_nvram, i, &data); |
| *buffer++ = data; |
| } |
| } |
| |
| /* @return the number of pages or negative for error */ |
| static int get_battery_history_status(struct max1720x_chip *chip, |
| bool *page_status) |
| { |
| u16 *write_status, *valid_status; |
| int i, addr_offset, bit_offset; |
| int valid_history_entry_count = 0; |
| |
| write_status = kmalloc_array(MAX1720X_N_OF_HISTORY_FLAGS_REG, |
| sizeof(u16), GFP_KERNEL); |
| if (!write_status) |
| return -ENOMEM; |
| |
| valid_status = kmalloc_array(MAX1720X_N_OF_HISTORY_FLAGS_REG, |
| sizeof(u16), GFP_KERNEL); |
| if (!valid_status) { |
| kfree(write_status); |
| return -ENOMEM; |
| } |
| |
| max1720x_read_log_write_status(chip, write_status); |
| max1720x_read_log_valid_status(chip, valid_status); |
| |
| /* Figure out the pages with valid history entry */ |
| for (i = 0; i < MAX1720X_N_OF_HISTORY_PAGES; i++) { |
| addr_offset = i / 8; |
| bit_offset = i % 8; |
| page_status[i] = |
| ((write_status[addr_offset] & BIT(bit_offset)) || |
| (write_status[addr_offset] & BIT(bit_offset + 8))) && |
| ((valid_status[addr_offset] & BIT(bit_offset)) || |
| (valid_status[addr_offset] & BIT(bit_offset + 8))); |
| if (page_status[i]) |
| valid_history_entry_count++; |
| } |
| |
| kfree(write_status); |
| kfree(valid_status); |
| |
| return valid_history_entry_count; |
| } |
| |
| static void get_battery_history(struct max1720x_chip *chip, |
| bool *page_status, u16 *history) |
| { |
| int i, j, index = 0; |
| u16 data = 0; |
| |
| for (i = 0; i < MAX1720X_N_OF_HISTORY_PAGES; i++) { |
| if (!page_status[i]) |
| continue; |
| REGMAP_WRITE(chip->regmap, MAX1720X_COMMAND, |
| MAX1720X_READ_HISTORY_CMD_BASE + i); |
| msleep(MAX1720X_TRECALL_MS); |
| for (j = 0; j < MAX1720X_HISTORY_PAGE_SIZE; j++) { |
| (void) REGMAP_READ(chip->regmap_nvram, |
| MAX1720X_NVRAM_END + j, |
| &data); |
| history[index * MAX1720X_HISTORY_PAGE_SIZE + j] = data; |
| } |
| index++; |
| } |
| } |
| |
| static int format_battery_history_entry(char *temp, int size, u16 *line) |
| { |
| int length = 0, i; |
| |
| for (i = 0; i < MAX1720X_HISTORY_PAGE_SIZE; i++) { |
| length += scnprintf(temp + length, |
| size - length, "%04x ", |
| line[i]); |
| } |
| |
| if (length > 0) |
| temp[--length] = 0; |
| return length; |
| } |
| |
| /* @return number of valid entries */ |
| static int max1720x_history_read(struct max1720x_chip *chip, |
| struct max1720x_history *hi) |
| { |
| memset(hi, 0, sizeof(*hi)); |
| |
| hi->page_status = kcalloc(MAX1720X_N_OF_HISTORY_PAGES, |
| sizeof(bool), GFP_KERNEL); |
| if (!hi->page_status) |
| return -ENOMEM; |
| |
| mutex_lock(&chip->history_lock); |
| |
| hi->history_count = get_battery_history_status(chip, hi->page_status); |
| if (hi->history_count < 0) { |
| goto error_exit; |
| } else if (hi->history_count != 0) { |
| const int size = hi->history_count * MAX1720X_HISTORY_PAGE_SIZE; |
| |
| hi->history = kmalloc_array(size, sizeof(u16), GFP_KERNEL); |
| if (!hi->history) { |
| hi->history_count = -ENOMEM; |
| goto error_exit; |
| } |
| |
| get_battery_history(chip, hi->page_status, hi->history); |
| } |
| |
| mutex_unlock(&chip->history_lock); |
| return hi->history_count; |
| |
| error_exit: |
| mutex_unlock(&chip->history_lock); |
| kfree(hi->page_status); |
| hi->page_status = NULL; |
| return hi->history_count; |
| |
| } |
| |
| static void max1720x_history_free(struct max1720x_history *hi) |
| { |
| kfree(hi->page_status); |
| kfree(hi->history); |
| |
| hi->history = NULL; |
| hi->page_status = NULL; |
| } |
| |
| static enum power_supply_property max1720x_battery_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_CHARGE_COUNTER, |
| POWER_SUPPLY_PROP_CHARGE_FULL, |
| POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
| POWER_SUPPLY_PROP_CURRENT_AVG, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_CYCLE_COUNT, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_RESISTANCE, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, |
| POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, |
| POWER_SUPPLY_PROP_VOLTAGE_AVG, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
| POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_VOLTAGE_OCV, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_SERIAL_NUMBER, |
| }; |
| |
| static void max1720x_cycle_count_work(struct work_struct *work) |
| { |
| int bucket, cnt, batt_soc; |
| u16 data; |
| struct max1720x_chip *chip = container_of(work, struct max1720x_chip, |
| cycle_count_work); |
| |
| if (REGMAP_READ(chip->regmap, MAX1720X_REPSOC, &data)) |
| return; |
| batt_soc = reg_to_percentage(data); |
| |
| mutex_lock(&chip->cyc_ctr.lock); |
| |
| if (chip->cyc_ctr.prev_soc != -1 && |
| batt_soc >= 0 && batt_soc <= 100 && |
| batt_soc > chip->cyc_ctr.prev_soc) { |
| for (cnt = batt_soc ; cnt > chip->cyc_ctr.prev_soc ; cnt--) { |
| bucket = cnt * BUCKET_COUNT / 100; |
| if (bucket >= BUCKET_COUNT) |
| bucket = BUCKET_COUNT - 1; |
| chip->cyc_ctr.count[bucket]++; |
| REGMAP_WRITE(chip->regmap_nvram, |
| max1720x_cycle_counter_addr[bucket], |
| chip->cyc_ctr.count[bucket]); |
| pr_debug("Stored count: prev_soc=%d, soc=%d bucket=%d count=%d\n", |
| chip->cyc_ctr.prev_soc, cnt, bucket, |
| chip->cyc_ctr.count[bucket]); |
| } |
| } |
| chip->cyc_ctr.prev_soc = batt_soc; |
| |
| mutex_unlock(&chip->cyc_ctr.lock); |
| } |
| |
| static void max1720x_restore_cycle_counter(struct max1720x_chip *chip) |
| { |
| int i; |
| u16 data = 0; |
| |
| mutex_lock(&chip->cyc_ctr.lock); |
| |
| for (i = 0; i < BUCKET_COUNT; i++) { |
| if (!REGMAP_READ(chip->regmap_nvram, |
| max1720x_cycle_counter_addr[i], |
| &data)) { |
| chip->cyc_ctr.count[i] = data; |
| pr_debug("max1720x_cycle_counter[%d], addr=0x%02X, count=%d\n", |
| i, max1720x_cycle_counter_addr[i], |
| chip->cyc_ctr.count[i]); |
| } |
| } |
| chip->cyc_ctr.prev_soc = -1; |
| |
| mutex_unlock(&chip->cyc_ctr.lock); |
| } |
| |
| static ssize_t max1720x_get_cycle_counts_bins(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct power_supply *psy; |
| struct max1720x_chip *chip; |
| int len = 0, i; |
| |
| psy = container_of(dev, struct power_supply, dev); |
| chip = power_supply_get_drvdata(psy); |
| |
| mutex_lock(&chip->cyc_ctr.lock); |
| |
| for (i = 0; i < BUCKET_COUNT; i++) { |
| |
| len += scnprintf(buf + len, PAGE_SIZE - len, "%d", |
| chip->cyc_ctr.count[i]); |
| |
| if (i == BUCKET_COUNT-1) |
| len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
| else |
| len += scnprintf(buf + len, PAGE_SIZE - len, " "); |
| } |
| |
| mutex_unlock(&chip->cyc_ctr.lock); |
| |
| return len; |
| } |
| |
| static ssize_t max1720x_set_cycle_counts_bins(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct power_supply *psy; |
| struct max1720x_chip *chip; |
| int val[BUCKET_COUNT], i; |
| |
| psy = container_of(dev, struct power_supply, dev); |
| chip = power_supply_get_drvdata(psy); |
| |
| if (sscanf(buf, "%d %d %d %d %d %d %d %d %d %d", |
| &val[0], &val[1], &val[2], &val[3], &val[4], |
| &val[5], &val[6], &val[7], &val[8], &val[9]) != BUCKET_COUNT) |
| return -EINVAL; |
| |
| mutex_lock(&chip->cyc_ctr.lock); |
| for (i = 0; i < BUCKET_COUNT; i++) { |
| if (val[i] >= 0 && val[i] < U16_MAX) { |
| chip->cyc_ctr.count[i] = val[i]; |
| REGMAP_WRITE(chip->regmap_nvram, |
| max1720x_cycle_counter_addr[i], |
| chip->cyc_ctr.count[i]); |
| } |
| } |
| mutex_unlock(&chip->cyc_ctr.lock); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(cycle_counts_bins, 0660, |
| max1720x_get_cycle_counts_bins, |
| max1720x_set_cycle_counts_bins); |
| |
| static ssize_t max1720x_get_offmode_charger(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct power_supply *psy = container_of(dev, struct power_supply, dev); |
| struct max1720x_chip *chip = power_supply_get_drvdata(psy); |
| |
| return scnprintf(buf, PAGE_SIZE, "%hhd\n", chip->offmode_charger); |
| } |
| |
| static ssize_t max1720x_set_offmode_charger(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct power_supply *psy = container_of(dev, struct power_supply, dev); |
| struct max1720x_chip *chip = power_supply_get_drvdata(psy); |
| |
| if (kstrtobool(buf, &chip->offmode_charger)) |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(offmode_charger, 0660, |
| max1720x_get_offmode_charger, |
| max1720x_set_offmode_charger); |
| |
| static int max1720x_get_battery_soc(struct max1720x_chip *chip) |
| { |
| u16 data; |
| int capacity, err; |
| |
| if (chip->fake_capacity >= 0 && chip->fake_capacity <= 100) |
| return chip->fake_capacity; |
| |
| err = REGMAP_READ(chip->regmap, MAX1720X_REPSOC, &data); |
| if (err) |
| return err; |
| capacity = reg_to_percentage(data); |
| |
| if (capacity == 100 && chip->offmode_charger) |
| chip->fake_capacity = 100; |
| |
| return capacity; |
| } |
| |
| static void max1720x_prime_battery_qh_capacity(struct max1720x_chip *chip, |
| int status) |
| { |
| u16 data = 0; |
| |
| (void) REGMAP_READ(chip->regmap, MAX1720X_MIXCAP, &data); |
| chip->current_capacity = data; |
| |
| REGMAP_WRITE(chip->regmap_nvram, MAX1720X_NUSER18C, ~data); |
| dev_info(chip->dev, "Capacity primed to %d on %s\n", |
| data, psy_status_str[status]); |
| |
| (void) REGMAP_READ(chip->regmap, MAX1720X_QH, &data); |
| chip->previous_qh = reg_to_twos_comp_int(data); |
| |
| REGMAP_WRITE(chip->regmap_nvram, MAX1720X_NUSER18D, data); |
| dev_info(chip->dev, "QH primed to %d on %s\n", |
| data, psy_status_str[status]); |
| } |
| |
| static int max1720x_get_battery_status(struct max1720x_chip *chip) |
| { |
| u16 data = 0; |
| int current_now, current_avg, ichgterm, soc, fullsocthr; |
| int status = POWER_SUPPLY_STATUS_UNKNOWN, err; |
| |
| /* negative is charging */ |
| err = REGMAP_READ(chip->regmap, MAX1720X_CURRENT, &data); |
| if (err) |
| return err; |
| current_now = -reg_to_micro_amp(data, chip->RSense); |
| |
| /* negative is charging */ |
| err = REGMAP_READ(chip->regmap, MAX1720X_AVGCURRENT, &data); |
| if (err) |
| return err; |
| current_avg = -reg_to_micro_amp(data, chip->RSense); |
| |
| err = REGMAP_READ(chip->regmap, MAX1720X_ICHGTERM, &data); |
| if (err) |
| return err; |
| ichgterm = reg_to_micro_amp(data, chip->RSense); |
| |
| if (chip->offmode_charger) { |
| fullsocthr = 100; |
| } else { |
| err = REGMAP_READ(chip->regmap, MAX1720X_FULLSOCTHR, &data); |
| if (err) |
| return err; |
| fullsocthr = reg_to_percentage(data); |
| } |
| |
| soc = max1720x_get_battery_soc(chip); |
| if (soc < 0) |
| return -EIO; |
| |
| if (current_avg > -ichgterm && current_avg <= 0) { |
| if (soc >= fullsocthr) { |
| const bool needs_prime = (chip->prev_charge_status == |
| POWER_SUPPLY_STATUS_CHARGING); |
| |
| status = POWER_SUPPLY_STATUS_FULL; |
| if (needs_prime) |
| max1720x_prime_battery_qh_capacity(chip, |
| status); |
| } else { |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| } |
| } else if (current_now >= -ichgterm) { |
| status = POWER_SUPPLY_STATUS_DISCHARGING; |
| } else { |
| status = POWER_SUPPLY_STATUS_CHARGING; |
| if (chip->prev_charge_status == POWER_SUPPLY_STATUS_DISCHARGING |
| && current_avg < -ichgterm) |
| max1720x_prime_battery_qh_capacity(chip, status); |
| } |
| |
| if (chip->prev_charge_status != status) { |
| dev_info(chip->dev, "s=%d->%d c=%d avg_c=%d ichgt=%d soc=%d fullsocthr=%d\n", |
| chip->prev_charge_status, status, current_now, |
| current_avg, ichgterm, soc, fullsocthr); |
| } |
| |
| chip->prev_charge_status = status; |
| |
| return status; |
| } |
| |
| static int max1720x_get_battery_health(struct max1720x_chip *chip) |
| { |
| /* For health report what ever was recently alerted and clear it */ |
| |
| if (chip->health_status & MAX1720X_STATUS_VMX) { |
| chip->health_status &= ~MAX1720X_STATUS_VMX; |
| return POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| } |
| |
| if (chip->health_status & MAX1720X_STATUS_TMN) { |
| chip->health_status &= ~MAX1720X_STATUS_TMN; |
| return POWER_SUPPLY_HEALTH_COLD; |
| } |
| |
| if (chip->health_status & MAX1720X_STATUS_TMX) { |
| chip->health_status &= ~MAX1720X_STATUS_TMX; |
| return POWER_SUPPLY_HEALTH_OVERHEAT; |
| } |
| |
| return POWER_SUPPLY_HEALTH_GOOD; |
| } |
| |
| static int max1720x_set_battery_soc(struct max1720x_chip *chip, |
| const union power_supply_propval *val) |
| { |
| chip->fake_capacity = val->intval; |
| |
| if (chip->psy) |
| power_supply_changed(chip->psy); |
| |
| return 0; |
| } |
| |
| static int max1720x_update_battery_qh_based_capacity(struct max1720x_chip *chip) |
| { |
| u16 data; |
| int current_qh, err = 0; |
| |
| err = REGMAP_READ(chip->regmap, MAX1720X_QH, &data); |
| if (err) |
| return err; |
| current_qh = reg_to_twos_comp_int(data); |
| |
| /* QH value accumulates as battery charges */ |
| chip->current_capacity -= (chip->previous_qh - current_qh); |
| chip->previous_qh = current_qh; |
| |
| return 0; |
| } |
| |
| static void max1720x_restore_battery_qh_capacity(struct max1720x_chip *chip) |
| { |
| u16 data = 0, nvram_capacity; |
| int current_qh, nvram_qh; |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NUSER18C, &data); |
| nvram_capacity = ~data; |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NUSER18D, &data); |
| nvram_qh = reg_to_twos_comp_int(data); |
| |
| (void) REGMAP_READ(chip->regmap, MAX1720X_QH, &data); |
| current_qh = reg_to_twos_comp_int(data); |
| |
| /* QH value accumulates as battery discharges */ |
| chip->current_capacity = (int) nvram_capacity - (nvram_qh - current_qh); |
| dev_info(chip->dev, "Capacity restored to %d\n", |
| chip->current_capacity); |
| chip->previous_qh = current_qh; |
| dev_info(chip->dev, "QH value restored to %d\n", |
| chip->previous_qh); |
| } |
| |
| static void max1720x_handle_update_nconvgcfg(struct max1720x_chip *chip, |
| int temp) |
| { |
| int idx = -1; |
| |
| if (chip->temp_convgcfg == NULL) |
| return; |
| |
| if (temp <= chip->temp_convgcfg[0]) { |
| idx = 0; |
| } else if (temp > chip->temp_convgcfg[chip->nb_convgcfg - 1]) { |
| idx = chip->nb_convgcfg - 1; |
| } else { |
| for (idx = 1 ; idx < chip->nb_convgcfg; idx++) { |
| if (temp > chip->temp_convgcfg[idx - 1] && |
| temp <= chip->temp_convgcfg[idx]) |
| break; |
| } |
| } |
| mutex_lock(&chip->convgcfg_lock); |
| /* We want to switch to higher slot only if above temp + hysteresis |
| * but when temperature drops, we want to change at the level |
| */ |
| if ((idx != chip->curr_convgcfg_idx) && |
| (chip->curr_convgcfg_idx == -1 || idx < chip->curr_convgcfg_idx || |
| temp >= chip->temp_convgcfg[chip->curr_convgcfg_idx] + |
| chip->convgcfg_hysteresis)) { |
| REGMAP_WRITE(chip->regmap_nvram, MAX1720X_NCONVGCFG, |
| chip->convgcfg_values[idx]); |
| chip->curr_convgcfg_idx = idx; |
| dev_info(chip->dev, "updating nConvgcfg to 0x%04x as temp is %d (idx:%d)\n", |
| chip->convgcfg_values[idx], temp, idx); |
| } |
| mutex_unlock(&chip->convgcfg_lock); |
| } |
| |
| static int max1720x_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct max1720x_chip *chip = power_supply_get_drvdata(psy); |
| struct regmap *map = chip->regmap; |
| u16 data = 0; |
| int err = 0; |
| |
| pm_runtime_get_sync(chip->dev); |
| if (!chip->init_complete || !chip->resume_complete) { |
| pm_runtime_put_sync(chip->dev); |
| return -EAGAIN; |
| } |
| pm_runtime_put_sync(chip->dev); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = max1720x_get_battery_status(chip); |
| if (val->intval < 0) |
| return val->intval; |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = max1720x_get_battery_health(chip); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = max1720x_get_battery_soc(chip); |
| if (val->intval < 0) |
| return val->intval; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
| err = max1720x_update_battery_qh_based_capacity(chip); |
| if (err < 0) |
| return err; |
| val->intval = reg_to_micro_amp_h(chip->current_capacity, |
| chip->RSense); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL: |
| /* |
| * Snap charge_full to DESIGNCAP during early charge cycles to |
| * prevent large fluctuations in FULLCAPNOM. MAX1720X_CYCLES LSB |
| * is 16% |
| */ |
| err = REGMAP_READ(map, MAX1720X_CYCLES, &data); |
| if (!err) { |
| if (reg_to_cycles(data) <= FULLCAPNOM_STABILIZE_CYCLES) |
| err = REGMAP_READ(map, MAX1720X_DESIGNCAP, |
| &data); |
| else |
| err = REGMAP_READ(map, MAX1720X_FULLCAPNOM, |
| &data); |
| val->intval = reg_to_micro_amp_h(data, chip->RSense); |
| } |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| err = REGMAP_READ(map, MAX1720X_DESIGNCAP, &data); |
| val->intval = reg_to_micro_amp_h(data, chip->RSense); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| err = REGMAP_READ(map, MAX1720X_AVGCURRENT, &data); |
| /* current is positive value when flowing to device */ |
| val->intval = -reg_to_micro_amp(data, chip->RSense); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| err = REGMAP_READ(map, MAX1720X_CURRENT, &data); |
| /* current is positive value when flowing to device */ |
| val->intval = -reg_to_micro_amp(data, chip->RSense); |
| break; |
| case POWER_SUPPLY_PROP_CYCLE_COUNT: |
| err = REGMAP_READ(map, MAX1720X_CYCLES, &data); |
| val->intval = reg_to_cycles(data); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| err = REGMAP_READ(map, MAX1720X_STATUS, &data); |
| val->intval = (((u16) data) & MAX1720X_STATUS_BST) ? 0 : 1; |
| break; |
| case POWER_SUPPLY_PROP_RESISTANCE: |
| err = REGMAP_READ(map, MAX1720X_RCELL, &data); |
| val->intval = reg_to_resistance_micro_ohms(data, chip->RSense); |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| REGMAP_READ(map, MAX1720X_TEMP, &data); |
| val->intval = reg_to_deci_deg_cel(data); |
| max1720x_handle_update_nconvgcfg(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: |
| err = REGMAP_READ(map, MAX1720X_TTE, &data); |
| val->intval = reg_to_seconds(data); |
| break; |
| case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: |
| err = REGMAP_READ(map, MAX1720X_TTF, &data); |
| val->intval = reg_to_seconds(data); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| err = REGMAP_READ(map, MAX1720X_AVGVCELL, &data); |
| val->intval = reg_to_micro_volt(data); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| err = REGMAP_READ(map, MAX1720X_MAXMINVOLT, &data); |
| /* LSB: 20mV */ |
| val->intval = ((data >> 8) & 0xFF) * 20000; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| err = REGMAP_READ(map, MAX1720X_MAXMINVOLT, &data); |
| /* LSB: 20mV */ |
| val->intval = (data & 0xFF) * 20000; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| err = REGMAP_READ(map, MAX1720X_VCELL, &data); |
| val->intval = reg_to_micro_volt(data); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
| err = REGMAP_READ(map, MAX1720X_VFOCV, &data); |
| val->intval = reg_to_micro_volt(data); |
| break; |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
| break; |
| case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
| val->strval = chip->serial_number; |
| break; |
| default: |
| return -EINVAL; |
| } |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| static int max1720x_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct max1720x_chip *chip = power_supply_get_drvdata(psy); |
| int rc = 0; |
| |
| pm_runtime_get_sync(chip->dev); |
| if (!chip->init_complete || !chip->resume_complete) { |
| pm_runtime_put_sync(chip->dev); |
| return -EAGAIN; |
| } |
| pm_runtime_put_sync(chip->dev); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CAPACITY: |
| rc = max1720x_set_battery_soc(chip, val); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int max1720x_property_is_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CAPACITY: |
| return 1; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * A fuel gauge reset resets only the fuel gauge operation without resetting IC |
| * hardware. This is useful for testing different configurations without writing |
| * nonvolatile memory. |
| */ |
| static void max1720x_fg_reset(struct max1720x_chip *chip) |
| { |
| REGMAP_WRITE(chip->regmap, MAX1720X_CONFIG2, |
| MAX1720X_COMMAND_FUEL_GAUGE_RESET); |
| msleep(MAX1720X_TPOR_MS); |
| } |
| |
| static irqreturn_t max1720x_fg_irq_thread_fn(int irq, void *obj) |
| { |
| struct max1720x_chip *chip = obj; |
| u16 fg_status, fg_status_clr; |
| int err = 0; |
| |
| if (!chip || irq != chip->primary->irq) { |
| WARN_ON_ONCE(1); |
| return IRQ_NONE; |
| } |
| |
| pm_runtime_get_sync(chip->dev); |
| if (!chip->init_complete || !chip->resume_complete) { |
| pm_runtime_put_sync(chip->dev); |
| return -EAGAIN; |
| } |
| pm_runtime_put_sync(chip->dev); |
| err = REGMAP_READ(chip->regmap, MAX1720X_STATUS, &fg_status); |
| if (err) |
| return IRQ_NONE; |
| if (fg_status == 0) { |
| chip->debug_irq_none_cnt++; |
| pr_debug("spurius: fg_status=0 cnt=%d\n", |
| chip->debug_irq_none_cnt); |
| /* rate limit spurius interrupts */ |
| msleep(MAX1720X_TICLR_MS); |
| return IRQ_HANDLED; |
| } |
| |
| /* only used to report health */ |
| chip->health_status |= fg_status; |
| /* write 0 to clear will loose interrupts when we don't write 1 to the |
| * bits that are not set. Just inverting fg_status cause an interrupt |
| * storm, only setting the bits marked as "host must clear" in the DS |
| * seems to work eg: |
| * |
| * fg_status_clr = fg_status |
| * fg_status_clr |= MAX1720X_STATUS_POR | MAX1720X_STATUS_DSOCI |
| * | MAX1720X_STATUS_BI; |
| * |
| * If the above logic is sound, we probably need to set also the bits |
| * that config mark as "host must clear". Maxim to confirm. |
| */ |
| fg_status_clr = fg_status; |
| |
| if (fg_status & MAX1720X_STATUS_POR) { |
| fg_status_clr &= ~MAX1720X_STATUS_POR; |
| pr_debug("POR is set\n"); |
| } |
| if (fg_status & MAX1720X_STATUS_IMN) |
| pr_debug("IMN is set\n"); |
| |
| if (fg_status & MAX1720X_STATUS_BST) |
| pr_debug("BST is set\n"); |
| |
| if (fg_status & MAX1720X_STATUS_IMX) |
| pr_debug("IMX is set\n"); |
| |
| if (fg_status & MAX1720X_STATUS_DSOCI) { |
| fg_status_clr &= ~MAX1720X_STATUS_DSOCI; |
| pr_debug("DSOCI is set\n"); |
| schedule_work(&chip->cycle_count_work); |
| } |
| if (fg_status & MAX1720X_STATUS_VMN) { |
| if (chip->RConfig & MAX1720X_CONFIG_VS) |
| fg_status_clr &= ~MAX1720X_STATUS_VMN; |
| pr_debug("VMN is set\n"); |
| } |
| if (fg_status & MAX1720X_STATUS_TMN) { |
| if (chip->RConfig & MAX1720X_CONFIG_TS) |
| fg_status_clr &= ~MAX1720X_STATUS_TMN; |
| pr_debug("TMN is set\n"); |
| } |
| if (fg_status & MAX1720X_STATUS_SMN) { |
| if (chip->RConfig & MAX1720X_CONFIG_SS) |
| fg_status_clr &= ~MAX1720X_STATUS_SMN; |
| pr_debug("SMN is set\n"); |
| } |
| if (fg_status & MAX1720X_STATUS_BI) |
| pr_debug("BI is set\n"); |
| |
| if (fg_status & MAX1720X_STATUS_VMX) { |
| if (chip->RConfig & MAX1720X_CONFIG_VS) |
| fg_status_clr &= ~MAX1720X_STATUS_VMX; |
| pr_debug("VMX is set\n"); |
| } |
| if (fg_status & MAX1720X_STATUS_TMX) { |
| if (chip->RConfig & MAX1720X_CONFIG_TS) |
| fg_status_clr &= ~MAX1720X_STATUS_TMX; |
| pr_debug("TMX is set\n"); |
| } |
| if (fg_status & MAX1720X_STATUS_SMX) { |
| if (chip->RConfig & MAX1720X_CONFIG_SS) |
| fg_status_clr &= ~MAX1720X_STATUS_SMX; |
| pr_debug("SMX is set\n"); |
| } |
| |
| if (fg_status & MAX1720X_STATUS_BR) |
| pr_debug("BR is set\n"); |
| |
| REGMAP_WRITE(chip->regmap, MAX1720X_STATUS, fg_status_clr); |
| |
| if (chip->psy) |
| power_supply_changed(chip->psy); |
| |
| /* oneshot w/o filter will unmask on return but gauge will take up |
| * to 351 ms to clear ALRM1. |
| */ |
| msleep(MAX1720X_TICLR_MS); |
| |
| return IRQ_HANDLED; |
| } |
| |
| |
| static int max1720x_handle_dt_batt_id(struct max1720x_chip *chip) |
| { |
| int ret, batt_id; |
| u32 batt_id_range = 20, batt_id_kohm; |
| const char *dt_iio_ch_name, *iio_ch_name; |
| struct device_node *node = chip->dev->of_node; |
| struct device_node *config_node, *child_node; |
| |
| ret = of_property_read_string(node, "io-channel-names", |
| &dt_iio_ch_name); |
| if (ret == -EINVAL) |
| return 0; |
| |
| if (ret) { |
| dev_warn(chip->dev, "failed to read io-channel-names\n"); |
| /* Don't fail probe on that, just ignore error */ |
| return 0; |
| } |
| |
| iio_ch_name = devm_kstrdup(chip->dev, dt_iio_ch_name, GFP_KERNEL); |
| if (!iio_ch_name) |
| /* Don't fail probe on that, just ignore error */ |
| return 0; |
| |
| chip->iio_ch = iio_channel_get(chip->dev, iio_ch_name); |
| if (PTR_ERR(chip->iio_ch) == -EPROBE_DEFER) { |
| dev_warn(chip->dev, "iio_channel_get %s not ready\n", |
| iio_ch_name); |
| devm_kfree(chip->dev, (void *) iio_ch_name); |
| return -EPROBE_DEFER; |
| } else if (IS_ERR(chip->iio_ch)) { |
| dev_warn(chip->dev, "iio_channel_get %s error: %ld\n", |
| iio_ch_name, PTR_ERR(chip->iio_ch)); |
| /* Don't fail probe on that, just ignore error */ |
| devm_kfree(chip->dev, (void *) iio_ch_name); |
| return 0; |
| } |
| |
| devm_kfree(chip->dev, (void *) iio_ch_name); |
| ret = iio_read_channel_processed(chip->iio_ch, &batt_id); |
| if (ret < 0) { |
| dev_warn(chip->dev, "Failed to read battery id: %d\n", ret); |
| return 0; |
| } |
| |
| batt_id /= 1000; |
| dev_info(chip->dev, "device battery RID: %d\n", batt_id); |
| |
| ret = of_property_read_u32(node, "maxim,batt-id-range-pct", |
| &batt_id_range); |
| if (ret && ret == -EINVAL) |
| dev_warn(chip->dev, "failed to read maxim,batt-id-range-pct\n"); |
| |
| config_node = of_find_node_by_name(node, "maxim,config"); |
| if (!config_node) { |
| dev_warn(chip->dev, "Failed to find maxim,config setting\n"); |
| return 0; |
| } |
| |
| for_each_child_of_node(config_node, child_node) { |
| ret = of_property_read_u32(child_node, "maxim,batt-id-kohm", |
| &batt_id_kohm); |
| if (!ret && |
| (batt_id < (batt_id_kohm * (100 + batt_id_range) / 100)) && |
| (batt_id > (batt_id_kohm * (100 - batt_id_range) / 100))) { |
| chip->batt_node = child_node; |
| break; |
| } |
| } |
| if (!chip->batt_node) |
| dev_warn(chip->dev, "No child node found matching ID\n"); |
| |
| return 0; |
| } |
| |
| static int max1720x_apply_regval_shadow(struct max1720x_chip *chip, |
| struct device_node *node, |
| u16 *nRAM, int nb) |
| { |
| int ret, idx; |
| u16 *regs; |
| |
| if (!node || nb <= 0) |
| return 0; |
| |
| if (nb & 1) { |
| dev_warn(chip->dev, "%s maxim,n_regval u16 elems count is not even: %d\n", |
| node->name, nb); |
| return -EINVAL; |
| } |
| |
| regs = kmalloc_array(nb, sizeof(u16), GFP_KERNEL); |
| if (!regs) |
| return -ENOMEM; |
| |
| ret = of_property_read_u16_array(node, "maxim,n_regval", regs, nb); |
| if (ret) { |
| dev_warn(chip->dev, "failed to read maxim,n_regval: %d\n", ret); |
| goto shadow_out; |
| } |
| |
| for (idx = 0; idx < nb; idx += 2) { |
| if ((regs[idx] >= MAX1720X_NVRAM_START) && |
| (regs[idx] < MAX1720X_NVRAM_END)) { |
| nRAM[regs[idx] - MAX1720X_NVRAM_START] = regs[idx + 1]; |
| } |
| } |
| shadow_out: |
| kfree(regs); |
| return ret; |
| } |
| |
| static int max1720x_handle_dt_shadow_config(struct max1720x_chip *chip) |
| { |
| int ret = 0; |
| u16 *nRAM_current, *nRAM_updated; |
| int batt_cnt = 0, glob_cnt; |
| |
| ret = max1720x_handle_dt_batt_id(chip); |
| if (ret) |
| return ret; |
| |
| if (chip->batt_node) |
| batt_cnt = of_property_count_elems_of_size(chip->batt_node, |
| "maxim,n_regval", |
| sizeof(u16)); |
| glob_cnt = of_property_count_elems_of_size(chip->dev->of_node, |
| "maxim,n_regval", |
| sizeof(u16)); |
| |
| nRAM_current = kmalloc_array(MAX1720X_NVRAM_U16_SIZE, |
| sizeof(u16), GFP_KERNEL); |
| if (!nRAM_current) |
| return -ENOMEM; |
| |
| nRAM_updated = kmalloc_array(MAX1720X_NVRAM_U16_SIZE, |
| sizeof(u16), GFP_KERNEL); |
| if (!nRAM_updated) { |
| ret = -ENOMEM; |
| goto error_out; |
| } |
| |
| ret = regmap_raw_read(chip->regmap_nvram, MAX1720X_NVRAM_START, |
| nRAM_current, MAX1720X_NVRAM_SIZE); |
| if (ret) { |
| dev_err(chip->dev, |
| "Failed to read config from shadow RAM\n"); |
| goto error_out; |
| } |
| memcpy(nRAM_updated, nRAM_current, MAX1720X_NVRAM_SIZE); |
| if (chip->batt_node) |
| max1720x_apply_regval_shadow(chip, chip->batt_node, |
| nRAM_updated, batt_cnt); |
| max1720x_apply_regval_shadow(chip, chip->dev->of_node, |
| nRAM_updated, glob_cnt); |
| |
| /* Ensure nCGain is not 0 if nNVCfg0.enCG is set */ |
| if ((nRAM_updated[NVRAM_U16_INDEX(MAX1720X_NNVCFG0)] & |
| MAX1720X_NNVCFG0_ENCG) && |
| ((nRAM_updated[NVRAM_U16_INDEX(MAX1720X_NCGAIN)] == 0) || |
| (nRAM_updated[NVRAM_U16_INDEX(MAX1720X_NCGAIN)] == 0x0400))) |
| nRAM_updated[NVRAM_U16_INDEX(MAX1720X_NCGAIN)] = 0x4000; |
| |
| if (memcmp(nRAM_updated, nRAM_current, MAX1720X_NVRAM_SIZE)) { |
| ret = regmap_raw_write(chip->regmap_nvram, MAX1720X_NVRAM_START, |
| nRAM_updated, MAX1720X_NVRAM_SIZE); |
| if (ret) { |
| dev_err(chip->dev, |
| "Failed to write config from shadow RAM\n"); |
| goto error_out; |
| } |
| /* nConvgCfg change take effect without resetting the gauge */ |
| nRAM_current[NVRAM_U16_INDEX(MAX1720X_NCONVGCFG)] = |
| nRAM_updated[NVRAM_U16_INDEX(MAX1720X_NCONVGCFG)]; |
| if (memcmp(nRAM_updated, nRAM_current, MAX1720X_NVRAM_SIZE)) { |
| dev_info(chip->dev, |
| "DT config differs from shadow, resetting\n"); |
| max1720x_fg_reset(chip); |
| } |
| } |
| |
| error_out: |
| kfree(nRAM_current); |
| kfree(nRAM_updated); |
| |
| return ret; |
| } |
| |
| static int max1720x_apply_regval_register(struct max1720x_chip *chip, |
| struct device_node *node) |
| { |
| int cnt, ret = 0, idx, err; |
| u16 *regs, data; |
| |
| cnt = of_property_count_elems_of_size(node, "maxim,r_regval", |
| sizeof(u16)); |
| if (!node || cnt <= 0) |
| return 0; |
| |
| if (cnt & 1) { |
| dev_warn(chip->dev, "%s maxim,r_regval u16 elems count is not even: %d\n", |
| node->name, cnt); |
| return -EINVAL; |
| } |
| |
| regs = kmalloc_array(cnt, sizeof(u16), GFP_KERNEL); |
| if (!regs) |
| return -ENOMEM; |
| |
| ret = of_property_read_u16_array(node, "maxim,r_regval", regs, cnt); |
| if (ret) { |
| dev_warn(chip->dev, "failed to read %s maxim,r_regval: %d\n", |
| node->name, ret); |
| goto register_out; |
| } |
| |
| for (idx = 0; idx < cnt; idx += 2) { |
| if (max1720x_is_reg(chip->dev, regs[idx])) { |
| err = REGMAP_READ(chip->regmap, regs[idx], &data); |
| if (!err && data != regs[idx + 1]) |
| REGMAP_WRITE(chip->regmap, regs[idx], |
| regs[idx + 1]); |
| } |
| } |
| register_out: |
| kfree(regs); |
| return ret; |
| } |
| |
| static int max1720x_handle_dt_register_config(struct max1720x_chip *chip) |
| { |
| int ret = 0; |
| |
| if (chip->batt_node) |
| ret = max1720x_apply_regval_register(chip, chip->batt_node); |
| |
| if (ret) |
| return ret; |
| |
| ret = max1720x_apply_regval_register(chip, chip->dev->of_node); |
| |
| return ret; |
| } |
| |
| static int max1720x_handle_dt_nconvgcfg(struct max1720x_chip *chip) |
| { |
| int ret = 0, i; |
| struct device_node *node = chip->dev->of_node; |
| |
| chip->curr_convgcfg_idx = -1; |
| mutex_init(&chip->convgcfg_lock); |
| chip->nb_convgcfg = |
| of_property_count_elems_of_size(node, "maxim,nconvgcfg-temp-limits", |
| sizeof(s16)); |
| if (!chip->nb_convgcfg) |
| return 0; |
| |
| ret = of_property_read_u32(node, "maxim,nconvgcfg-temp-hysteresis", |
| &chip->convgcfg_hysteresis); |
| if (ret < 0) |
| chip->convgcfg_hysteresis = 10; |
| |
| if (chip->nb_convgcfg != of_property_count_elems_of_size(node, |
| "maxim,nconvgcfg-values", |
| sizeof(u16))) { |
| dev_warn(chip->dev, "%s maxim,nconvgcfg-values and maxim,nconvgcfg-temp-limits are missmatching number of elements\n", |
| node->name); |
| return -EINVAL; |
| } |
| chip->temp_convgcfg = devm_kmalloc_array(chip->dev, chip->nb_convgcfg, |
| sizeof(s16), GFP_KERNEL); |
| if (!chip->temp_convgcfg) |
| return -ENOMEM; |
| |
| chip->convgcfg_values = devm_kmalloc_array(chip->dev, chip->nb_convgcfg, |
| sizeof(u16), GFP_KERNEL); |
| if (!chip->convgcfg_values) { |
| devm_kfree(chip->dev, chip->temp_convgcfg); |
| chip->temp_convgcfg = NULL; |
| return -ENOMEM; |
| } |
| |
| ret = of_property_read_u16_array(node, "maxim,nconvgcfg-temp-limits", |
| (u16 *) chip->temp_convgcfg, |
| chip->nb_convgcfg); |
| if (ret) { |
| dev_warn(chip->dev, "failed to read maxim,nconvgcfg-temp-limits: %d\n", |
| ret); |
| goto error; |
| } |
| |
| ret = of_property_read_u16_array(node, "maxim,nconvgcfg-values", |
| chip->convgcfg_values, |
| chip->nb_convgcfg); |
| if (ret) { |
| dev_warn(chip->dev, "failed to read maxim,nconvgcfg-values: %d\n", |
| ret); |
| goto error; |
| } |
| for (i = 1; i < chip->nb_convgcfg; i++) { |
| if (chip->temp_convgcfg[i] < chip->temp_convgcfg[i-1]) { |
| dev_warn(chip->dev, "nconvgcfg-temp-limits idx:%d < idx:%d\n", |
| i, i-1); |
| goto error; |
| } |
| if ((chip->temp_convgcfg[i] - chip->temp_convgcfg[i-1]) |
| <= chip->convgcfg_hysteresis) { |
| dev_warn(chip->dev, "nconvgcfg-temp-hysteresis smaller than idx:%d, idx:%d\n", |
| i, i-1); |
| goto error; |
| } |
| } |
| |
| error: |
| if (ret) { |
| devm_kfree(chip->dev, chip->temp_convgcfg); |
| devm_kfree(chip->dev, chip->convgcfg_values); |
| chip->temp_convgcfg = NULL; |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| static int get_irq_none_cnt(void *data, u64 *val) |
| { |
| struct max1720x_chip *chip = (struct max1720x_chip *)data; |
| |
| *val = chip->debug_irq_none_cnt; |
| return 0; |
| } |
| |
| static int set_irq_none_cnt(void *data, u64 val) |
| { |
| struct max1720x_chip *chip = (struct max1720x_chip *)data; |
| |
| if (val == 0) |
| chip->debug_irq_none_cnt = 0; |
| return 0; |
| } |
| |
| DEFINE_SIMPLE_ATTRIBUTE(irq_none_cnt_fops, get_irq_none_cnt, |
| set_irq_none_cnt, "%llu\n"); |
| #endif |
| |
| static int init_debugfs(struct max1720x_chip *chip) |
| { |
| #ifdef CONFIG_DEBUG_FS |
| struct dentry *de; |
| |
| de = debugfs_create_dir("max1720x", 0); |
| if (de) |
| debugfs_create_file("irq_none_cnt", 0644, de, |
| chip, &irq_none_cnt_fops); |
| #endif |
| return 0; |
| } |
| |
| static int max1720x_init_chip(struct max1720x_chip *chip) |
| { |
| u16 data = 0; |
| int ret; |
| |
| ret = max1720x_handle_dt_shadow_config(chip); |
| if (ret == -EPROBE_DEFER) |
| return ret; |
| |
| ret = max1720x_handle_dt_register_config(chip); |
| if (ret == -EPROBE_DEFER) |
| return ret; |
| |
| (void) max1720x_handle_dt_nconvgcfg(chip); |
| |
| chip->fake_capacity = -EINVAL; |
| chip->init_complete = true; |
| chip->resume_complete = true; |
| |
| ret = REGMAP_READ(chip->regmap, MAX1720X_STATUS, &data); |
| if (!ret && data & MAX1720X_STATUS_BR) { |
| dev_info(chip->dev, "Clearing Battery Removal bit\n"); |
| regmap_update_bits(chip->regmap, MAX1720X_STATUS, |
| MAX1720X_STATUS_BR, 0x0); |
| } |
| if (!ret && data & MAX1720X_STATUS_BI) { |
| dev_info(chip->dev, "Clearing Battery Insertion bit\n"); |
| regmap_update_bits(chip->regmap, MAX1720X_STATUS, |
| MAX1720X_STATUS_BI, 0x0); |
| } |
| if (!ret && data & MAX1720X_STATUS_POR) { |
| dev_info(chip->dev, "Clearing Power-On Reset bit\n"); |
| regmap_update_bits(chip->regmap, MAX1720X_STATUS, |
| MAX1720X_STATUS_POR, 0x0); |
| } |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NRSENSE, &chip->RSense); |
| dev_info(chip->dev, "RSense value %d micro Ohm\n", chip->RSense * 10); |
| (void) REGMAP_READ(chip->regmap, MAX1720X_CONFIG, &chip->RConfig); |
| dev_info(chip->dev, "Config: 0x%04x\n", chip->RConfig); |
| (void) REGMAP_READ(chip->regmap, MAX1720X_ICHGTERM, &data); |
| dev_info(chip->dev, "IChgTerm: %d\n", |
| reg_to_micro_amp(data, chip->RSense)); |
| (void) REGMAP_READ(chip->regmap, MAX1720X_VEMPTY, &data); |
| dev_info(chip->dev, "VEmpty: VE=%dmV VR=%dmV\n", |
| ((data >> 7) & 0x1ff) * 10, (data & 0x7f) * 40); |
| |
| /* |
| * Capacity data is stored as complement so it will not be zero. Using |
| * zero case to detect new un-primed pack |
| */ |
| ret = REGMAP_READ(chip->regmap_nvram, MAX1720X_NUSER18C, &data); |
| if (!ret && data == 0) |
| max1720x_prime_battery_qh_capacity(chip, |
| POWER_SUPPLY_STATUS_UNKNOWN); |
| else |
| max1720x_restore_battery_qh_capacity(chip); |
| |
| chip->prev_charge_status = POWER_SUPPLY_STATUS_UNKNOWN; |
| |
| init_debugfs(chip); |
| |
| /* Handle any IRQ that might have been set before init */ |
| max1720x_fg_irq_thread_fn(chip->primary->irq, chip); |
| |
| return 0; |
| } |
| |
| static void max1720x_set_serial_number(struct max1720x_chip *chip) |
| { |
| u16 data0 = 0, data1 = 0, data2 = 0; |
| int date, count = 0, shift, err = 0; |
| char cell_vendor; |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NMANFCTRNAME0, &data0); |
| if (data0 == 0x5357) /* "SW": SWD */ |
| shift = 0; |
| else if (data0 == 0x4257) /* "BW": DSY */ |
| shift = 8; |
| else |
| return; |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NSERIALNUMBER0, &data0); |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NSERIALNUMBER1, &data1); |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NSERIALNUMBER2, &data2); |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%02X%02X%02X", |
| data0 >> shift, data1 >> shift, data2 >> shift); |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NMANFCTRDATE, &data0); |
| date = (((((data0 >> 9) & 0x3f) + 1980) * 10000) + |
| ((data0 >> 5) & 0xf) * 100 + (data0 & 0x1F)); |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%d", date); |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NMANFCTRNAME0, &data0); |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%c%c", data0 >> 8, data0 & 0xFF); |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NDEVICENAME0, &data0); |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NDEVICENAME1, &data1); |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NDEVICENAME2, &data2); |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%c%c%c", |
| data0 >> shift, data1 >> shift, data2 >> shift); |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NDEVICENAME3, &data0); |
| if (data0 >> 8 == 0) |
| data0 = ('?' << 8) | (data0 & 0xFF); |
| if ((data0 & 0xFF) == 0) |
| data0 = (data0 & 0xFF00) | '?'; |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%c%c", data0 >> 8, data0 & 0xFF); |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NUSER1D1, &data0); |
| cell_vendor = (shift == 8) ? (data0 >> 8) : (data0 & 0xFF); |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%c", cell_vendor); |
| |
| (void) REGMAP_READ(chip->regmap_nvram, MAX1720X_NUSER1D0, &data0); |
| if (shift == 8) { |
| if (data0 >> 8 == 0xb1) { |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "B1"); |
| } else if (data0 >> 8 == 0xc1) { |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "C1"); |
| } else { |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "??"); |
| } |
| } else { |
| count += scnprintf(chip->serial_number + count, |
| sizeof(chip->serial_number) - count, |
| "%c%c", data0 >> 8, data0 & 0xFF); |
| } |
| if (err) |
| chip->serial_number[0] = '\0'; |
| } |
| |
| static struct power_supply_desc max1720x_psy_desc = { |
| .name = "maxfg", |
| .type = POWER_SUPPLY_TYPE_BATTERY, |
| .get_property = max1720x_get_property, |
| .set_property = max1720x_set_property, |
| .property_is_writeable = max1720x_property_is_writeable, |
| .properties = max1720x_battery_props, |
| .num_properties = ARRAY_SIZE(max1720x_battery_props), |
| }; |
| |
| |
| static void *ct_seq_start(struct seq_file *s, loff_t *pos) |
| { |
| struct max1720x_history *hi = |
| (struct max1720x_history *)s->private; |
| |
| if (*pos >= hi->history_count) |
| return NULL; |
| hi->history_index = *pos; |
| |
| return &hi->history_index; |
| } |
| |
| static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos) |
| { |
| loff_t *spos = (loff_t *)v; |
| struct max1720x_history *hi = |
| (struct max1720x_history *)s->private; |
| |
| *pos = ++*spos; |
| if (*pos >= hi->history_count) |
| return NULL; |
| |
| return spos; |
| } |
| |
| static void ct_seq_stop(struct seq_file *s, void *v) |
| { |
| /* iterator in hi, no need to free */ |
| } |
| |
| static int ct_seq_show(struct seq_file *s, void *v) |
| { |
| char temp[96]; |
| loff_t *spos = (loff_t *)v; |
| struct max1720x_history *hi = |
| (struct max1720x_history *)s->private; |
| const size_t offset = *spos * MAX1720X_HISTORY_PAGE_SIZE; |
| |
| format_battery_history_entry(temp, sizeof(temp), &hi->history[offset]); |
| seq_printf(s, "%s\n", temp); |
| |
| return 0; |
| } |
| |
| static const struct seq_operations ct_seq_ops = { |
| .start = ct_seq_start, |
| .next = ct_seq_next, |
| .stop = ct_seq_stop, |
| .show = ct_seq_show |
| }; |
| |
| static int history_dev_open(struct inode *inode, struct file *file) |
| { |
| struct max1720x_chip *chip = |
| container_of(inode->i_cdev, struct max1720x_chip, hcdev); |
| struct max1720x_history *hi; |
| int history_count; |
| |
| hi = __seq_open_private(file, &ct_seq_ops, sizeof(*hi)); |
| if (!hi) |
| return -ENOMEM; |
| |
| history_count = max1720x_history_read(chip, hi); |
| if (history_count < 0) { |
| return history_count; |
| } else if (history_count == 0) { |
| dev_info(chip->dev, |
| "No battery history has been recorded\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int history_dev_release(struct inode *inode, struct file *file) |
| { |
| struct max1720x_history *hi = |
| ((struct seq_file *)file->private_data)->private; |
| |
| if (hi) { |
| max1720x_history_free(hi); |
| seq_release_private(inode, file); |
| } |
| |
| return 0; |
| } |
| |
| static const struct file_operations hdev_fops = { |
| .open = history_dev_open, |
| .owner = THIS_MODULE, |
| .read = seq_read, |
| .release = history_dev_release, |
| }; |
| |
| static void max1720x_cleanup_history(struct max1720x_chip *chip) |
| { |
| if (chip->history_added) |
| cdev_del(&chip->hcdev); |
| if (chip->history_available) |
| device_destroy(chip->hcclass, chip->hcmajor); |
| if (chip->hcclass) |
| class_destroy(chip->hcclass); |
| if (chip->hcmajor != -1) |
| unregister_chrdev_region(chip->hcmajor, 1); |
| } |
| |
| static int max1720x_init_history(struct max1720x_chip *chip) |
| { |
| struct device *hcdev; |
| |
| mutex_init(&chip->history_lock); |
| |
| chip->hcmajor = -1; |
| |
| /* cat /proc/devices */ |
| if (alloc_chrdev_region(&chip->hcmajor, 0, 1, HISTORY_DEVICENAME) < 0) |
| goto no_history; |
| /* ls /sys/class */ |
| chip->hcclass = class_create(THIS_MODULE, HISTORY_DEVICENAME); |
| if (chip->hcclass == NULL) |
| goto no_history; |
| /* ls /dev/ */ |
| hcdev = device_create(chip->hcclass, NULL, chip->hcmajor, NULL, |
| HISTORY_DEVICENAME); |
| if (hcdev == NULL) |
| goto no_history; |
| |
| chip->history_available = true; |
| cdev_init(&chip->hcdev, &hdev_fops); |
| if (cdev_add(&chip->hcdev, chip->hcmajor, 1) == -1) |
| goto no_history; |
| |
| chip->history_added = true; |
| return 0; |
| |
| no_history: |
| max1720x_cleanup_history(chip); |
| return -ENODEV; |
| } |
| |
| static void max1720x_init_work(struct work_struct *work) |
| { |
| struct max1720x_chip *chip = container_of(work, struct max1720x_chip, |
| init_work.work); |
| int ret = max1720x_init_chip(chip); |
| |
| if (ret == -EPROBE_DEFER) { |
| schedule_delayed_work(&chip->init_work, |
| msecs_to_jiffies(MAX1720X_DELAY_INIT_MS)); |
| return; |
| } |
| |
| (void)max1720x_init_history(chip); |
| |
| if (chip->psy) |
| power_supply_changed(chip->psy); |
| } |
| |
| static int max1720x_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct max1720x_chip *chip; |
| struct device *dev = &client->dev; |
| struct power_supply_config psy_cfg = { }; |
| |
| int ret = 0; |
| |
| chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); |
| if (!chip) |
| return -ENOMEM; |
| |
| chip->dev = dev; |
| i2c_set_clientdata(client, chip); |
| chip->primary = client; |
| chip->secondary = i2c_new_secondary_device(client, "nvram", 0xb); |
| if (chip->secondary == NULL) { |
| dev_err(dev, "Failed to initialize secondary i2c device\n"); |
| return -EINVAL; |
| } |
| i2c_set_clientdata(chip->secondary, chip); |
| chip->regmap = devm_regmap_init_i2c(client, &max1720x_regmap_cfg); |
| if (IS_ERR(chip->regmap)) { |
| dev_err(dev, "Failed to initialize regmap\n"); |
| ret = -EINVAL; |
| goto i2c_unregister; |
| } |
| |
| chip->regmap_nvram = |
| devm_regmap_init_i2c(chip->secondary, &max1720x_regmap_nvram_cfg); |
| if (IS_ERR(chip->regmap_nvram)) { |
| dev_err(dev, "Failed to initialize nvram regmap\n"); |
| ret = -EINVAL; |
| goto i2c_unregister; |
| } |
| |
| mutex_init(&chip->cyc_ctr.lock); |
| max1720x_restore_cycle_counter(chip); |
| |
| if (chip->primary->irq) { |
| ret = request_threaded_irq(chip->primary->irq, NULL, |
| max1720x_fg_irq_thread_fn, |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
| MAX1720X_I2C_DRIVER_NAME, chip); |
| if (ret != 0) { |
| dev_err(chip->dev, "Unable to register IRQ handler\n"); |
| goto i2c_unregister; |
| } |
| enable_irq_wake(chip->primary->irq); |
| } |
| |
| if (of_property_read_bool(dev->of_node, "maxim,psy-type-unknown")) |
| max1720x_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; |
| |
| psy_cfg.drv_data = chip; |
| chip->psy = devm_power_supply_register(dev, |
| &max1720x_psy_desc, &psy_cfg); |
| if (IS_ERR(chip->psy)) { |
| dev_err(dev, "Couldn't register as power supply\n"); |
| ret = PTR_ERR(chip->psy); |
| goto irq_unregister; |
| } |
| |
| ret = device_create_file(&chip->psy->dev, &dev_attr_cycle_counts_bins); |
| if (ret) { |
| dev_err(dev, "Failed to create cycle_counts_bins attribute\n"); |
| goto psy_unregister; |
| } |
| |
| ret = device_create_file(&chip->psy->dev, &dev_attr_offmode_charger); |
| if (ret) { |
| dev_err(dev, "Failed to create offmode_charger attribute\n"); |
| goto psy_unregister; |
| } |
| |
| max1720x_set_serial_number(chip); |
| |
| INIT_WORK(&chip->cycle_count_work, max1720x_cycle_count_work); |
| INIT_DELAYED_WORK(&chip->init_work, max1720x_init_work); |
| schedule_delayed_work(&chip->init_work, |
| msecs_to_jiffies(MAX1720X_DELAY_INIT_MS)); |
| |
| return 0; |
| |
| psy_unregister: |
| power_supply_unregister(chip->psy); |
| irq_unregister: |
| free_irq(chip->primary->irq, chip); |
| i2c_unregister: |
| i2c_unregister_device(chip->secondary); |
| |
| return ret; |
| } |
| |
| static int max1720x_remove(struct i2c_client *client) |
| { |
| struct max1720x_chip *chip = i2c_get_clientdata(client); |
| |
| max1720x_cleanup_history(chip); |
| cancel_delayed_work(&chip->init_work); |
| iio_channel_release(chip->iio_ch); |
| if (chip->primary->irq) |
| free_irq(chip->primary->irq, chip); |
| power_supply_unregister(chip->psy); |
| i2c_unregister_device(chip->secondary); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id max1720x_of_match[] = { |
| { .compatible = "maxim,max1720x"}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, max1720x_of_match); |
| |
| static const struct i2c_device_id max1720x_id[] = { |
| {"max1720x", 0}, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(i2c, max1720x_id); |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int max1720x_pm_suspend(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct max1720x_chip *chip = i2c_get_clientdata(client); |
| |
| pm_runtime_get_sync(chip->dev); |
| chip->resume_complete = false; |
| pm_runtime_put_sync(chip->dev); |
| |
| return 0; |
| } |
| |
| static int max1720x_pm_resume(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct max1720x_chip *chip = i2c_get_clientdata(client); |
| |
| pm_runtime_get_sync(chip->dev); |
| chip->resume_complete = true; |
| pm_runtime_put_sync(chip->dev); |
| |
| return 0; |
| } |
| #endif |
| static const struct dev_pm_ops max1720x_pm_ops = { |
| SET_LATE_SYSTEM_SLEEP_PM_OPS(max1720x_pm_suspend, max1720x_pm_resume) |
| }; |
| |
| static struct i2c_driver max1720x_i2c_driver = { |
| .driver = { |
| .name = "max1720x", |
| .of_match_table = max1720x_of_match, |
| .pm = &max1720x_pm_ops, |
| .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
| }, |
| .id_table = max1720x_id, |
| .probe = max1720x_probe, |
| .remove = max1720x_remove, |
| }; |
| |
| module_i2c_driver(max1720x_i2c_driver); |
| MODULE_AUTHOR("Thierry Strudel <tstrudel@google.com>"); |
| MODULE_DESCRIPTION("MAX17201/MAX17205 Fuel Gauge"); |
| MODULE_LICENSE("GPL"); |