Init the google-modules/bms codebase
Porting driver/power/supply/google/* from
s-dev-gs-pixel-4.19 branch. The last commit ID is
c030bdb806ef12bfed9d3f33ed0a783cbff269d5
Bug: 160835098
Change-Id: I14a6204dcd9cefa3aecad825e8dfeb8d8c97e61e
Signed-off-by: Ken Tsou <[email protected]>
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..3832afc
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,117 @@
+menuconfig GOOGLE_BMS
+ bool "Google, Inc. Charging and Fuel Gauge support"
+ help
+ Say Y here to enable Google, Inc. support for Battery and Charging.
+
+if GOOGLE_BMS
+
+config GOOGLE_BATTERY_CHARGING
+ tristate "Google Battery and Charging Control"
+ select GOOGLE_CHARGER
+ select GOOGLE_BATTERY
+ help
+ Say Y here to enable new Google Gen Features.
+ This option selects Google Battery and Multi Step Charging enabling
+ new features such as battery controlled charge tables and charge path
+ resistance compensation. New gen features require configuration in
+ device tree.
+
+config GOOGLE_CHARGER
+ tristate "Google Multi Step Charging"
+ help
+ Say Y here to choose the Google Multi Step Charging control.
+ The driver implements a multi CC-CV steps charging using battery
+ temperature and voltage to set the charger constant charging
+ current and float voltage.
+
+config GOOGLE_BATTERY
+ tristate "Google Battery"
+ help
+ Say Y here to choose the Google Battery.
+ The driver handles charge tables, recharge threshold, state of charge,
+ battery statistics such as bin count and other features common to all
+ google battery powered devices.
+
+config GOOGLE_CPM
+ tristate "Google Charging Policy Manager"
+ help
+ Say Y here to enable the Charging Policy Manager.
+ The driver manages multiple chargers (eg primary, secondary).
+
+config USB_OVERHEAT_MITIGATION
+ tristate "Google USB Overheat Mitigation"
+ help
+ Say Y here to enable Google USB overheat mitigation
+
+config GOOGLE_LOGBUFFER
+ tristate "debugfs logbuffer"
+ help
+ Say Y here to enable logbuffer.
+ This option enables a module that exposes an API to maintain driver
+ specific logs in an exclusive ring buffer and exposes it through
+ debugfs.
+
+config GOOGLE_PMIC_VOTER_COMPAT
+ tristate "Voter compatibility shim"
+ help
+ Say Y here to enable voter compatibility shim layer.
+ This option enables a call interface between the legacy voter
+ interface and the Google Voting interface.
+
+config GOOGLE_BEE
+ tristate "Google Battery EEPROM"
+ help
+ Say Y here to enable the support for google battery EEPROM
+ The EEPROM contains the battery type, lifetime data and other
+ infomation.
+
+endif # GOOGLE_BMS
+
+config CHARGER_P9221
+ tristate "IDT P9221 wireless power receiver driver"
+ depends on I2C
+ help
+ This driver provides support for the IDT P9221 wireless
+ power receiver.
+
+config CHARGER_MAX77729
+ tristate "Maxim MAX77729 battery charger driver"
+ depends on I2C && OF
+ help
+ Say Y to enable support for the Maxim MAX77729 battery charger.
+
+config CHARGER_MAX77759
+ tristate "Maxim MAX77759 battery charger driver"
+ depends on I2C && OF
+ help
+ Say Y to enable support for the Maxim MAX77759 battery charger.
+
+config PMIC_MAX77729
+ tristate "Maxim MAX77729 PMIC driver"
+ depends on I2C && OF
+ help
+ Say Y to enable support for the Maxim MAX77729 PMIC.
+
+config UIC_MAX77729
+ tristate "Maxim MAX77729 TCPCI driver"
+ depends on I2C && OF
+ help
+ Say Y to enable support for the Maxim MAX77729 TCPCI.
+
+config PCA9468
+ tristate "NXP PCA9468 Direct charger"
+ depends on I2C && OF
+ help
+ Say Y to enable support for the PCA9648 direct charger.
+
+config MAX20339
+ tristate "Maxim MAX20339 input protection chip"
+ depends on I2C && OF
+ help
+ Say Y to enable support for Max20339 OVP and LS circuits.
+
+config MAXQ_MAX77759
+ tristate "Maxim MAX77759 MAXQ"
+ depends on I2C && PMIC_MAX77729
+ help
+ Say Y to enable support for MAX77759 MAXQ support.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..621f3f5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+obj-$(CONFIG_GOOGLE_BMS) += google_bms.o gbms_storage.o gvotable.o
+obj-$(CONFIG_GOOGLE_BMS) += google_dc_pps.o
+obj-$(CONFIG_GOOGLE_CPM) += google_cpm.o
+obj-$(CONFIG_GOOGLE_CHARGER) += google_charger.o
+obj-$(CONFIG_GOOGLE_BATTERY) += google_battery.o google_ttf.o
+obj-$(CONFIG_GOOGLE_BEE) += google_eeprom.o
+obj-$(CONFIG_USB_OVERHEAT_MITIGATION) += overheat_mitigation.o
+obj-$(CONFIG_ARCH_SM8150) += sm8150_bms.o
+obj-$(CONFIG_GOOGLE_LOGBUFFER) += logbuffer.o
+obj-$(CONFIG_CHARGER_P9221) += p9221_charger.o
+obj-$(CONFIG_CHARGER_MAX77729) += max77729_charger.o
+obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o
+obj-$(CONFIG_PMIC_MAX77729) += max77729_pmic.o
+obj-$(CONFIG_MAXQ_MAX77759) += max77759_maxq.o
+obj-$(CONFIG_UIC_MAX77729) += max77729_uic.o
+obj-$(CONFIG_BATTERY_MAX1720X) += max_m5.o
+obj-$(CONFIG_GOOGLE_PMIC_VOTER_COMPAT) += pmic-voter-compat.o
+obj-$(CONFIG_PCA9468) += pca9468_charger.o
+obj-$(CONFIG_MAX20339) += max20339.o
diff --git a/gbms_storage.c b/gbms_storage.c
new file mode 100644
index 0000000..e201216
--- /dev/null
+++ b/gbms_storage.c
@@ -0,0 +1,1298 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 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 ": " fmt
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <linux/debugfs.h>
+#include <linux/genalloc.h>
+#include <linux/hashtable.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h> /* register_chrdev, unregister_chrdev */
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
+#include <linux/log2.h>
+#include "google_bms.h"
+
+struct gbms_storage_provider {
+ const char *name;
+ struct gbms_storage_desc *dsc;
+ bool offline;
+ void *ptr;
+};
+
+struct gbms_cache_entry {
+ struct hlist_node hnode;
+ void *provider;
+ gbms_tag_t tag;
+ size_t count;
+ size_t addr;
+};
+
+#define GBMS_PROVIDER_NAME_MAX 32
+
+#define GBMS_PROVIDERS_MAX 4
+static spinlock_t providers_lock;
+static bool gbms_storage_init_done;
+
+static int gbms_providers_count;
+static struct gbms_storage_provider gbms_providers[GBMS_PROVIDERS_MAX];
+static struct dentry *rootdir;
+
+/* 1 << 5 = 64 entries */
+#define GBMS_HASHTABLE_SIZE 5
+DECLARE_HASHTABLE(gbms_cache, GBMS_HASHTABLE_SIZE);
+static struct gen_pool *gbms_cache_pool;
+static void *gbms_cache_mem;
+
+/* use this as a temporary buffer for converting a tag to a string */
+typedef char gbms_tag_cstr_t[sizeof(gbms_tag_t) + 1];
+
+static char *tag2cstr(gbms_tag_cstr_t buff, gbms_tag_t tag)
+{
+ const u32 tmp = cpu_to_le32(tag);
+
+ buff[3] = tmp & 0xff;
+ buff[2] = (tmp >> 8) & 0xff;
+ buff[1] = (tmp >> 16) & 0xff;
+ buff[0] = (tmp >> 24) & 0xff;
+ buff[4] = 0;
+
+ return buff;
+}
+
+static gbms_tag_t cstr2tag(gbms_tag_cstr_t buff)
+{
+ gbms_tag_t tag;
+
+ tag = (u8)buff[0];
+ tag = (tag << 8) + (u8)buff[1];
+ tag = (tag << 8) + (u8)buff[2];
+ tag = (tag << 8) + (u8)buff[3];
+
+ return tag;
+}
+
+/* will return EACCESS to everything */
+struct gbms_storage_desc gbms_dummy_dsc;
+
+/* ------------------------------------------------------------------------- */
+
+static inline u64 gbms_cache_hash(gbms_tag_t tag)
+{
+ return tag;
+}
+
+/* TODO: caching */
+static struct gbms_cache_entry *gbms_cache_lookup(gbms_tag_t tag, size_t *addr)
+{
+ unsigned long flags;
+ struct gbms_cache_entry *ce;
+ const u64 hash = gbms_cache_hash(tag);
+
+ spin_lock_irqsave(&providers_lock, flags);
+
+ hash_for_each_possible(gbms_cache, ce, hnode, hash) {
+ if (ce->tag == tag) {
+ spin_unlock_irqrestore(&providers_lock, flags);
+ return ce;
+ }
+ }
+
+ spin_unlock_irqrestore(&providers_lock, flags);
+ return NULL;
+}
+
+/* call only on a cache miss */
+static struct gbms_cache_entry *gbms_cache_add(gbms_tag_t tag,
+ struct gbms_storage_provider *slot)
+{
+ unsigned long flags;
+ struct gbms_cache_entry *entry;
+
+ if (!gbms_cache_pool || !slot)
+ return 0;
+
+ entry = (struct gbms_cache_entry *)
+ gen_pool_alloc(gbms_cache_pool, sizeof(*entry));
+ if (!entry)
+ return NULL;
+
+ /* cache provider */
+ memset(entry, 0, sizeof(*entry));
+ entry->provider = slot;
+ entry->tag = tag;
+ entry->addr = GBMS_STORAGE_ADDR_INVALID;
+
+ /* cache location if available */
+ if (slot->dsc->fetch && slot->dsc->store && slot->dsc->info) {
+ size_t addr, count;
+ int ret;
+
+ ret = slot->dsc->info(tag, &addr, &count, slot->ptr);
+ if (ret == 0) {
+ entry->count = count;
+ entry->addr = addr;
+ }
+ }
+
+ spin_lock_irqsave(&providers_lock, flags);
+ hash_add(gbms_cache, &entry->hnode, gbms_cache_hash(tag));
+ spin_unlock_irqrestore(&providers_lock, flags);
+
+ return entry;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* TODO: check for duplicates in the tag
+ */
+static int gbms_storage_check_dupes(struct gbms_storage_provider *provider)
+{
+ return 0;
+}
+
+/* TODO: resolve references in the tag cache. Prefill the cache with the raw
+ * mappings (TAG:<provider_name>:addr:size) for top-down organization.
+ */
+static int gbms_storage_resolve_refs(struct gbms_storage_provider *provider)
+{
+ /* enumerate the elements in cache, resolve references */
+ return 0;
+}
+
+static int gbms_storage_find_slot(const char *name)
+{
+ int index;
+ struct gbms_storage_provider *slot;
+
+ for (index = 0; index < gbms_providers_count; index++) {
+ slot = &gbms_providers[index];
+
+ if (strncmp(slot->name, name, strlen(slot->name)) == 0) {
+ if (!slot->dsc)
+ break;
+
+ return -EBUSY;
+ }
+ }
+
+ if (index == GBMS_PROVIDERS_MAX)
+ return -ENOMEM;
+
+ return index;
+}
+
+int gbms_storage_register_internal(struct gbms_storage_desc *desc,
+ const char *name,
+ void *ptr)
+{
+ int index;
+ unsigned long flags;
+ int refs = 0, dupes = 0;
+ struct gbms_storage_provider *slot;
+
+ if (!name || strlen(name) >= GBMS_PROVIDER_NAME_MAX)
+ return -EINVAL;
+
+ spin_lock_irqsave(&providers_lock, flags);
+ index = gbms_storage_find_slot(name);
+ if (index < 0) {
+ spin_unlock_irqrestore(&providers_lock, flags);
+ return index;
+ }
+
+ slot = &gbms_providers[index];
+ slot->name = name;
+ slot->dsc = desc;
+ slot->ptr = ptr;
+
+ /* resolve refs and check dupes only on real providers */
+ if (slot->dsc && desc) {
+ /* will not check for self consistency */
+ if (gbms_providers_count > 0)
+ dupes = gbms_storage_check_dupes(slot);
+
+ refs = gbms_storage_resolve_refs(slot);
+ }
+
+ pr_info("%s %s registered at %d, dupes=%d, refs=%d\n",
+ (desc) ? "storage" : "ref",
+ name,
+ index,
+ dupes, refs);
+
+ if (index == gbms_providers_count)
+ gbms_providers_count += 1;
+
+#ifdef CONFIG_DEBUG_FS
+ if (!IS_ERR_OR_NULL(rootdir) && name) {
+ /* TODO: create debugfs entries for the providers */
+ }
+#endif
+ spin_unlock_irqrestore(&providers_lock, flags);
+
+ return 0;
+}
+
+int gbms_storage_register(struct gbms_storage_desc *desc, const char *name,
+ void *ptr)
+{
+ if (!desc)
+ return -EINVAL;
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+
+ return gbms_storage_register_internal(desc, name, ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+
+static int gbms_cache_read(gbms_tag_t tag, void *data, size_t count)
+{
+ struct gbms_cache_entry *ce;
+ struct gbms_storage_provider *slot;
+ size_t addr = GBMS_STORAGE_ADDR_INVALID;
+ int ret;
+
+ /* the cache can only contain true providers */
+ ce = gbms_cache_lookup(tag, &addr);
+ if (!ce)
+ return -ENOENT;
+
+ slot = (struct gbms_storage_provider *)ce->provider;
+ if (slot->offline)
+ return -ENODEV;
+
+ if (slot->dsc->fetch && addr != GBMS_STORAGE_ADDR_INVALID)
+ ret = slot->dsc->fetch(data, addr, count, slot->ptr);
+ else if (!slot->dsc->read)
+ ret = -EACCES;
+ else
+ ret = slot->dsc->read(tag, data, count, slot->ptr);
+
+ return ret;
+}
+
+/* needs a lock on the provider */
+int gbms_storage_read(gbms_tag_t tag, void *data, size_t count)
+{
+ int ret;
+ bool late_inits = false;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+ /* non-data transfers must have zero count and data */
+ if (!data && count)
+ return -EINVAL;
+
+ ret = gbms_cache_read(tag, data, count);
+ if (ret == -ENOENT) {
+ const int max = gbms_providers_count;
+ struct gbms_storage_desc *dsc;
+ int i;
+
+ for (i = 0, ret = -ENOENT; ret == -ENOENT && i < max; i++) {
+ dsc = gbms_providers[i].dsc;
+ if (!dsc) {
+ late_inits = true;
+ } else if (dsc->read) {
+ /* -ENOENT = next, <0 err, >=0 #n bytes */
+ ret = dsc->read(tag, data, count,
+ gbms_providers[i].ptr);
+ if (ret >= 0)
+ gbms_cache_add(tag, &gbms_providers[i]);
+ }
+ }
+
+ }
+
+ if (late_inits && ret == -ENOENT)
+ ret = -EPROBE_DEFER;
+
+ return ret;
+}
+
+/* needs a lock on the provider */
+int gbms_storage_read_data(gbms_tag_t tag, void *data, size_t count, int idx)
+{
+ struct gbms_storage_desc *dsc;
+ const int max_count = gbms_providers_count;
+ bool late_inits = false;
+ int ret, i;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+ if (!data && count)
+ return -EINVAL;
+
+ for (i = 0, ret = -ENOENT; ret == -ENOENT && i < max_count; i++) {
+ if (gbms_providers[i].offline)
+ continue;
+
+ dsc = gbms_providers[i].dsc;
+ if (!dsc) {
+ late_inits = true;
+ } else if (dsc->read_data) {
+ /* -ENOENT = next, <0 err, >=0 #n bytes */
+ ret = dsc->read_data(tag, data, count, idx,
+ gbms_providers[i].ptr);
+
+ /* TODO: cache the provider */
+ }
+ }
+
+ if (late_inits && ret == -ENOENT)
+ ret = -EPROBE_DEFER;
+
+ return ret;
+}
+
+static int gbms_cache_write(gbms_tag_t tag, const void *data, size_t count)
+{
+ struct gbms_cache_entry *ce;
+ struct gbms_storage_provider *slot;
+ size_t addr = GBMS_STORAGE_ADDR_INVALID;
+ int ret;
+
+ ce = gbms_cache_lookup(tag, &addr);
+ if (!ce)
+ return -ENOENT;
+
+ slot = (struct gbms_storage_provider *)ce->provider;
+ if (slot->offline)
+ return -ENODEV;
+
+ if (slot->dsc->store && addr != GBMS_STORAGE_ADDR_INVALID)
+ ret = slot->dsc->store(data, addr, count, slot->ptr);
+ else if (!slot->dsc->write)
+ ret = -EACCES;
+ else
+ ret = slot->dsc->write(tag, data, count, slot->ptr);
+
+ return ret;
+}
+
+/* needs a lock on the provider */
+int gbms_storage_write(gbms_tag_t tag, const void *data, size_t count)
+{
+ int ret;
+ bool late_inits = false;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+ if (!data && count)
+ return -EINVAL;
+
+ ret = gbms_cache_write(tag, data, count);
+ if (ret == -ENOENT) {
+ const int max = gbms_providers_count;
+ struct gbms_storage_desc *dsc;
+ int i;
+
+ for (i = 0, ret = -ENOENT; ret == -ENOENT && i < max; i++) {
+ if (gbms_providers[i].offline)
+ continue;
+
+ dsc = gbms_providers[i].dsc;
+ if (!dsc) {
+ late_inits = true;
+ } else if (dsc->write) {
+ /* -ENOENT = next, <0 err, >=0 #n bytes */
+ ret = dsc->write(tag, data, count,
+ gbms_providers[i].ptr);
+ if (ret >= 0)
+ gbms_cache_add(tag, &gbms_providers[i]);
+ }
+
+ }
+ }
+
+ if (late_inits && ret == -ENOENT)
+ ret = -EPROBE_DEFER;
+
+ return ret;
+}
+
+/* needs a lock on the provider */
+int gbms_storage_write_data(gbms_tag_t tag, const void *data, size_t count,
+ int idx)
+{
+ const int max_count = gbms_providers_count;
+ struct gbms_storage_desc *dsc;
+ bool late_inits = false;
+ int ret, i;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+ if (!data && count)
+ return -EINVAL;
+
+ for (i = 0, ret = -ENOENT; ret == -ENOENT && i < max_count; i++) {
+ if (gbms_providers[i].offline)
+ continue;
+
+ dsc = gbms_providers[i].dsc;
+ if (!dsc) {
+ late_inits = true;
+ } else if (dsc->write_data) {
+ /* -ENOENT = next, <0 err, >=0 #n bytes */
+ ret = dsc->write_data(tag, data, count, idx,
+ gbms_providers[i].ptr);
+
+ /* TODO: cache the provider */
+ }
+
+ }
+
+ if (late_inits && ret == -ENOENT)
+ ret = -EPROBE_DEFER;
+
+ return ret;
+}
+
+static int gbms_storage_flush_provider(struct gbms_storage_provider *slot,
+ bool force)
+{
+ if (slot->offline)
+ return 0;
+
+ if (!slot->dsc || !slot->dsc->flush)
+ return 0;
+
+ return slot->dsc->flush(force, slot->ptr);
+}
+
+static int gbms_storage_flush_all_internal(bool force)
+{
+ int ret, i;
+ bool success = true;
+ struct gbms_storage_provider *slot;
+
+ for (i = 0; i < gbms_providers_count ; i++) {
+ slot = &gbms_providers[i];
+
+ ret = gbms_storage_flush_provider(slot, force);
+ if (ret < 0) {
+ pr_err("flush of %s failed (%d)\n", slot->name, ret);
+ success = false;
+ }
+ }
+
+ return success ? 0 : -EIO;
+}
+
+int gbms_storage_flush(gbms_tag_t tag)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+
+ spin_lock_irqsave(&providers_lock, flags);
+
+ /* TODO: search for the provider */
+
+ ret = gbms_storage_flush_all_internal(false);
+ spin_unlock_irqrestore(&providers_lock, flags);
+
+ return 0;
+}
+
+int gbms_storage_flush_all(void)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+
+ spin_lock_irqsave(&providers_lock, flags);
+ ret = gbms_storage_flush_all_internal(false);
+ spin_unlock_irqrestore(&providers_lock, flags);
+
+ return ret;
+}
+
+int gbms_storage_offline(const char *name, bool flush)
+{
+ unsigned long flags;
+ int ret = 0, index;
+
+ if (!gbms_storage_init_done)
+ return -EPROBE_DEFER;
+
+ spin_lock_irqsave(&providers_lock, flags);
+ index = gbms_storage_find_slot(name);
+ if (index < 0) {
+ spin_unlock_irqrestore(&providers_lock, flags);
+ return index;
+ }
+
+ if (flush)
+ ret = gbms_storage_flush_provider(&gbms_providers[index],
+ false);
+ if (ret == 0)
+ gbms_providers[index].offline = true;
+
+ spin_unlock_irqrestore(&providers_lock, flags);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+#ifdef CONFIG_DEBUG_FS
+
+static int gbms_storage_show_cache(struct seq_file *m, void *data)
+{
+ int bucket;
+ gbms_tag_cstr_t tname;
+ unsigned long flags;
+ struct gbms_cache_entry *ce;
+ struct gbms_storage_provider *slot;
+
+ spin_lock_irqsave(&providers_lock, flags);
+
+ hash_for_each(gbms_cache, bucket, ce, hnode) {
+
+ slot = (struct gbms_storage_provider *)ce->provider;
+ seq_printf(m, slot->offline ? " (%s): %s" : " %s: %s",
+ slot->name, tag2cstr(tname, ce->tag));
+
+ if (ce->count != 0)
+ seq_printf(m, "[%d:%d]", ce->addr, ce->count);
+ seq_printf(m, "\n");
+ }
+
+ spin_unlock_irqrestore(&providers_lock, flags);
+ return 0;
+}
+
+static int gbms_storage_cache_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gbms_storage_show_cache, inode->i_private);
+}
+static const struct file_operations gbms_cache_status_ops = {
+ .owner = THIS_MODULE,
+ .open = gbms_storage_cache_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void gbms_show_storage_provider(struct seq_file *m,
+ struct gbms_storage_provider *slot,
+ bool verbose)
+{
+ gbms_tag_cstr_t tname;
+ gbms_tag_t tag;
+ int ret = 0, i;
+
+ for (i = 0 ; ret == 0; i++) {
+ ret = slot->dsc->iter(i, &tag, slot->ptr);
+ if (ret < 0)
+ break;
+
+ seq_printf(m, "%s ", tag2cstr(tname, tag));
+
+ if (verbose && slot->dsc->info) {
+ size_t addr, count;
+
+ ret = slot->dsc->info(tag, &addr, &count, slot->ptr);
+ if (ret < 0)
+ continue;
+
+ seq_printf(m, "[%d,%d] ", addr, count);
+ }
+ }
+}
+
+static int gbms_show_storage_clients(struct seq_file *m, void *data)
+{
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&providers_lock, flags);
+
+ for (i = 0; i < gbms_providers_count; i++) {
+
+ seq_printf(m, gbms_providers[i].offline ? "%d (%s):" : "%d %s:",
+ i, gbms_providers[i].name);
+
+ if (!gbms_providers[i].dsc || !gbms_providers[i].dsc->iter)
+ continue;
+
+ gbms_show_storage_provider(m, &gbms_providers[i], false);
+
+ seq_printf(m, "\n");
+ }
+
+ spin_unlock_irqrestore(&providers_lock, flags);
+
+ return 0;
+}
+
+static int gbms_storage_clients_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gbms_show_storage_clients, inode->i_private);
+}
+
+static const struct file_operations gbms_providers_status_ops = {
+ .owner = THIS_MODULE,
+ .open = gbms_storage_clients_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define GBMS_DEBUG_ATTRIBUTE(name, fn_read, fn_write) \
+static const struct file_operations name = { \
+ .owner = THIS_MODULE, \
+ .open = simple_open, \
+ .llseek = no_llseek, \
+ .read = fn_read, \
+ .write = fn_write, \
+}
+
+static ssize_t debug_set_offline(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char name[GBMS_PROVIDER_NAME_MAX];
+ int ret;
+
+ ret = simple_write_to_buffer(name, sizeof(name), ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ ret = gbms_storage_offline(name, true);
+ if (ret == 0)
+ ret = count;
+
+ return 0;
+
+}
+
+GBMS_DEBUG_ATTRIBUTE(gbms_providers_offline_ops, NULL, debug_set_offline);
+
+static int debug_set_tag_size(void *data, u64 val)
+{
+ struct gbms_cache_entry *ce = data;
+
+ ce->count = val;
+ return 0;
+}
+
+static int debug_show_tag_size(void *data, u64 *val)
+{
+ struct gbms_cache_entry *ce = data;
+
+ *val = ce->count;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_tag_size_ops, debug_show_tag_size,
+ debug_set_tag_size, "%llu\n");
+
+static ssize_t debug_read_tag_data(struct file *filp,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct gbms_cache_entry *ce = filp->private_data;
+ char buf[ce->count];
+ int ret;
+
+ if (!ce->count)
+ return -ENODATA;
+
+ ret = gbms_storage_read(ce->tag, buf, ce->count);
+ if (ret < 0)
+ return ret;
+
+ return simple_read_from_buffer(user_buf, count, ppos, buf, ce->count);
+}
+
+static ssize_t debug_write_tag_data(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct gbms_cache_entry *ce = filp->private_data;
+ char buf[ce->count];
+ int ret;
+
+ if (!ce->count)
+ return -ENODATA;
+
+ ret = simple_write_to_buffer(buf, ce->count, ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ ret = gbms_storage_write(ce->tag, buf, ce->count);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+GBMS_DEBUG_ATTRIBUTE(debug_tag_data_ops, debug_read_tag_data,
+ debug_write_tag_data);
+
+static int gbms_find(struct gbms_storage_provider *slot, gbms_tag_t tag)
+{
+ int ret = 0, i;
+ gbms_tag_t tmp;
+
+ for (i = 0; ret == 0; i++) {
+ ret = slot->dsc->iter(i, &tmp, slot->ptr);
+ if (ret < 0)
+ break;
+ if (tag == tmp)
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct gbms_cache_entry *gbms_cache_preload_tag(gbms_tag_t tag)
+{
+ struct gbms_storage_provider *slot = NULL;
+ int ret, i;
+
+ for (i = 0; !slot && i < gbms_providers_count; i++) {
+ struct gbms_storage_desc *dsc;
+
+ dsc = gbms_providers[i].dsc;
+ if (!dsc || !dsc->iter)
+ continue;
+
+ ret = gbms_find(&gbms_providers[i], tag);
+ if (ret == 1)
+ slot = &gbms_providers[i];
+ }
+
+ return gbms_cache_add(tag, slot);
+}
+
+
+/* [ugo]+TAG_NAME | -TAG_NAME | TAG_NAME */
+static ssize_t debug_export_tag(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ size_t addr = GBMS_STORAGE_ADDR_INVALID;
+ struct gbms_cache_entry *ce;
+ gbms_tag_cstr_t name = { 0 };
+ struct dentry *de;
+ gbms_tag_t tag;
+ char temp[32];
+ int ret;
+
+ if (!rootdir)
+ return -ENODEV;
+
+ ret = simple_write_to_buffer(temp, sizeof(temp), ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ if (temp[0] == '-') {
+ /* remove tag */
+ return -EINVAL;
+ }
+
+ memcpy(name, temp + (temp[0] == '+'), 4);
+ tag = cstr2tag(name);
+
+ ce = gbms_cache_lookup(tag, &addr);
+ if (!ce) {
+ ce = gbms_cache_preload_tag(tag);
+ if (!ce)
+ return -ENOMEM;
+ }
+
+ de = debugfs_create_dir(name, rootdir);
+ if (!de) {
+ pr_err("cannot create debufsentry for %s\n", name);
+ return -ENODEV;
+ }
+
+ debugfs_create_file("size", 0400, de, ce, &debug_tag_size_ops);
+ debugfs_create_file("data", 0600, de, ce, &debug_tag_data_ops);
+
+ return count;
+}
+
+GBMS_DEBUG_ATTRIBUTE(gbms_providers_export_ops, NULL, debug_export_tag);
+
+#endif
+
+/* ------------------------------------------------------------------------ */
+
+struct gbms_storage_device {
+ struct gbms_cache_entry entry;
+ loff_t index;
+ loff_t count;
+
+ struct mutex gdev_lock;
+ int hcmajor;
+ struct cdev hcdev;
+ struct class *hcclass;
+ bool available;
+ bool added;
+
+ void (*show_fn)(struct seq_file *s, const u8 *data, size_t count);
+};
+
+static void *ct_seq_start(struct seq_file *s, loff_t *pos)
+{
+ int ret;
+ struct gbms_storage_device *gdev =
+ (struct gbms_storage_device *)s->private;
+
+ ret = gbms_storage_read_data(gdev->entry.tag, NULL, 0, 0);
+ if (ret < 0) {
+ gbms_tag_cstr_t buff;
+
+ pr_err("cannot init %s iterator data (%d)\n",
+ tag2cstr(buff, gdev->entry.tag), ret);
+ return NULL;
+ }
+
+ if (*pos >= ret)
+ return NULL;
+
+ gdev->count = ret;
+ gdev->index = *pos;
+
+ return &gdev->index;
+}
+
+static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ loff_t *spos = (loff_t *)v;
+ struct gbms_storage_device *gdev =
+ (struct gbms_storage_device *)s->private;
+
+ *pos = ++*spos;
+ if (*pos >= gdev->count)
+ spos = NULL;
+
+ return spos;
+}
+
+static void ct_seq_stop(struct seq_file *s, void *v)
+{
+ int ret;
+ struct gbms_storage_device *gdev =
+ (struct gbms_storage_device *)s->private;
+
+ ret = gbms_storage_read_data(gdev->entry.tag, NULL, 0,
+ GBMS_STORAGE_INDEX_INVALID);
+ if (ret < 0) {
+ gbms_tag_cstr_t buff;
+
+ pr_err("cannot free %s iterator data (%d)\n",
+ tag2cstr(buff, gdev->entry.tag), ret);
+ }
+}
+
+static int ct_seq_show(struct seq_file *s, void *v)
+{
+ struct gbms_storage_device *gdev =
+ (struct gbms_storage_device *)s->private;
+ u8 data[gdev->entry.count];
+ loff_t *spos = (loff_t *)v;
+ int ret;
+
+ ret = gbms_storage_read_data(gdev->entry.tag, data, sizeof(data),
+ *spos);
+ if (ret < 0)
+ return ret;
+
+ if (gdev->show_fn)
+ gdev->show_fn(s, data, ret);
+
+ 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 gbms_storage_dev_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ struct gbms_storage_device *gdev =
+ container_of(inode->i_cdev, struct gbms_storage_device, hcdev);
+
+ ret = seq_open(file, &ct_seq_ops);
+ if (ret == 0) {
+ struct seq_file *seq = file->private_data;
+ seq->private = gdev;
+ }
+
+ return ret;
+}
+
+static int gbms_storage_dev_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+static const struct file_operations hdev_fops = {
+ .open = gbms_storage_dev_open,
+ .owner = THIS_MODULE,
+ .read = seq_read,
+ .release = gbms_storage_dev_release,
+};
+
+void gbms_storage_cleanup_device(struct gbms_storage_device *gdev)
+{
+ if (gdev->added)
+ cdev_del(&gdev->hcdev);
+ if (gdev->available)
+ device_destroy(gdev->hcclass, gdev->hcmajor);
+ if (gdev->hcclass)
+ class_destroy(gdev->hcclass);
+ if (gdev->hcmajor != -1)
+ unregister_chrdev_region(gdev->hcmajor, 1);
+ kfree(gdev);
+}
+
+static int gbms_storage_device_init(struct gbms_storage_device *gdev,
+ const char *name)
+{
+ struct device *hcdev;
+
+ mutex_init(&gdev->gdev_lock);
+ gdev->hcmajor = -1;
+
+ /* cat /proc/devices */
+ if (alloc_chrdev_region(&gdev->hcmajor, 0, 1, name) < 0)
+ goto no_gdev;
+ /* ls /sys/class */
+ gdev->hcclass = class_create(THIS_MODULE, name);
+ if (gdev->hcclass == NULL)
+ goto no_gdev;
+ /* ls /dev/ */
+ hcdev = device_create(gdev->hcclass, NULL, gdev->hcmajor, NULL, name);
+ if (hcdev == NULL)
+ goto no_gdev;
+
+ gdev->available = true;
+ cdev_init(&gdev->hcdev, &hdev_fops);
+ if (cdev_add(&gdev->hcdev, gdev->hcmajor, 1) == -1)
+ goto no_gdev;
+
+ gdev->added = true;
+ return 0;
+
+no_gdev:
+ gbms_storage_cleanup_device(gdev);
+ return -ENODEV;
+}
+
+static void ct_dev_show(struct seq_file *s, const u8 *d, size_t count)
+{
+ int i;
+ u16 *data = (u16 *)d;
+
+ for (i = 0; i < count / 2; i++)
+ seq_printf(s, "%04x ", data[i]);
+ seq_printf(s, "\n");
+}
+
+struct gbms_storage_device *gbms_storage_create_device(const char *name,
+ gbms_tag_t tag)
+{
+ int i, ret;
+ struct gbms_storage_device *gdev;
+ const int max_count = gbms_providers_count;
+
+ gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
+ if (!gdev)
+ return NULL;
+
+ ret = gbms_storage_device_init(gdev, name);
+ if (ret < 0)
+ return NULL;
+
+ for (ret = -ENOENT, i = 0; ret == -ENOENT && i < max_count; i++) {
+ size_t addr, count;
+ struct gbms_storage_desc *dsc;
+
+ dsc = gbms_providers[i].dsc;
+ if (!dsc || !dsc->info)
+ continue;
+
+ ret = dsc->info(tag, &addr, &count, gbms_providers[i].ptr);
+ if (ret == 0) {
+ gdev->entry.provider = &gbms_providers[i];
+ gdev->entry.count = count;
+ gdev->entry.addr = addr;
+ gdev->entry.tag = tag;
+ }
+ }
+
+ if (!gdev->entry.provider) {
+ gbms_storage_cleanup_device(gdev);
+ return NULL;
+ }
+
+ /* TODO: caller to customize */
+ gdev->show_fn = ct_dev_show;
+
+ return gdev;
+}
+
+/* ------------------------------------------------------------------------ */
+
+enum gbee_status {
+ GBEE_STATUS_NOENT = 0,
+ GBEE_STATUS_PROBE = 1,
+ GBEE_STATUS_OK,
+};
+
+#define GBEE_POLL_RETRIES 5
+#define GBEE_POLL_INTERVAL_MS 200
+
+/* only one battery eeprom for now */
+static struct gbee_data {
+ struct device_node *node;
+ const char *bee_name;
+ enum gbee_status bee_status;
+ struct nvmem_device *bee_nvram;
+} bee_data;
+
+struct delayed_work bee_work;
+static struct mutex bee_lock;
+
+
+/*
+ * lookup for battery eeprom.
+ * TODO: extend this to multiple NVM like providers
+ */
+static void gbee_probe_work(struct work_struct *work)
+{
+ struct gbee_data *beed = &bee_data;
+ struct nvmem_device *bee_nvram;
+ int ret;
+
+ mutex_lock(&bee_lock);
+ if (beed->bee_status != GBEE_STATUS_PROBE) {
+ mutex_unlock(&bee_lock);
+ return;
+ }
+
+ bee_nvram = of_nvmem_device_get(beed->node, beed->bee_name);
+ if (IS_ERR(bee_nvram)) {
+ static int bee_poll_retries = GBEE_POLL_RETRIES;
+
+ if (!bee_poll_retries) {
+ ret = gbms_storage_register_internal(&gbms_dummy_dsc,
+ beed->bee_name,
+ NULL);
+ pr_err("gbee %s lookup failed, dummy=%d\n",
+ beed->bee_name, ret);
+ } else {
+ pr_debug("gbee %s retry lookup... (%d)\n",
+ beed->bee_name, PTR_ERR(bee_nvram));
+ schedule_delayed_work(&bee_work,
+ msecs_to_jiffies(GBEE_POLL_INTERVAL_MS));
+ bee_poll_retries -= 1;
+ }
+ mutex_unlock(&bee_lock);
+ return;
+ }
+
+ /* TODO: use nvram cells to resolve GBMS_TAGS*/
+ ret = gbee_register_device(beed->bee_name, bee_nvram);
+ if (ret < 0) {
+ pr_err("gbee %s ERROR %d\n", beed->bee_name, ret);
+ beed->bee_status = GBEE_STATUS_NOENT;
+ nvmem_device_put(bee_nvram);
+ mutex_unlock(&bee_lock);
+ return;
+ }
+
+ beed->bee_nvram = bee_nvram;
+ beed->bee_status = GBEE_STATUS_OK;
+ mutex_unlock(&bee_lock);
+ pr_info("gbee@ %s OK\n", beed->bee_name);
+}
+
+void gbee_destroy(struct gbee_data *beed)
+{
+ gbms_storage_offline(beed->bee_name, true);
+ nvmem_device_put(beed->bee_nvram);
+ kfree(beed->bee_name);
+}
+
+/* ------------------------------------------------------------------------ */
+
+#define entry_size(x) (ilog2(x) + (((x) & ((x) - 1)) != 0))
+
+static void gbms_storage_parse_provider_refs(struct device_node *node)
+{
+ const char *s;
+ int i, ret, count;
+
+ count = of_property_count_strings(node, "google,gbms-providers");
+ if (count < 0)
+ return;
+
+ for (i = 0; i < count; i++) {
+ ret = of_property_read_string_index(node,
+ "google,gbms-providers",
+ i, &s);
+ if (ret < 0) {
+ pr_err("cannot parse index %d\n", i);
+ continue;
+ }
+
+ ret = gbms_storage_register_internal(NULL, s, NULL);
+ if (ret < 0)
+ pr_err("cannot add a reference to %s (%d)\n", s, ret);
+ }
+}
+
+static int __init gbms_storage_init(void)
+{
+ struct device_node *node;
+ const int pe_size = entry_size(sizeof(struct gbms_cache_entry));
+ bool has_bee = false;
+
+ pr_info("initialize gbms_storage\n");
+
+ spin_lock_init(&providers_lock);
+
+ mutex_init(&bee_lock);
+ INIT_DELAYED_WORK(&bee_work, gbee_probe_work);
+
+ gbms_cache_pool = gen_pool_create(pe_size, -1);
+ if (gbms_cache_pool) {
+ size_t mem_size = (1 << GBMS_HASHTABLE_SIZE) * pe_size;
+
+ gbms_cache_mem = kzalloc(mem_size, GFP_KERNEL);
+ if (!gbms_cache_mem) {
+ gen_pool_destroy(gbms_cache_pool);
+ gbms_cache_pool = NULL;
+ } else {
+ gen_pool_add(gbms_cache_pool,
+ (unsigned long)gbms_cache_mem,
+ mem_size, -1);
+ hash_init(gbms_cache);
+ }
+ }
+
+ if (!gbms_cache_pool)
+ pr_err("unable to create cache\n");
+
+ node = of_find_node_by_name(NULL, "google_bms");
+ if (node) {
+ const char *bee_name = NULL;
+ int ret;
+
+ /*
+ * TODO: prefill cache with static entries for top-down.
+ * NOTE: providers for top-down tags make the late_init list
+ * as well
+ */
+
+ /*
+ * only one battery EEPROM now.
+ * TODO: map this as a cache entry
+ */
+ ret = of_property_read_string(node, "google,bee-name",
+ &bee_name);
+ if (ret == 0) {
+ struct gbee_data *beed = &bee_data;
+
+ beed->bee_name = kstrdup(bee_name, GFP_KERNEL);
+ if (!beed->bee_name)
+ return -ENOMEM;
+ beed->bee_status = GBEE_STATUS_PROBE;
+ beed->node = node;
+
+ /* add the bee to the late arrivals */
+ gbms_storage_register_internal(NULL, beed->bee_name,
+ NULL);
+ has_bee = true;
+ }
+
+ /* late init list */
+ gbms_storage_parse_provider_refs(node);
+ }
+
+ if (has_bee)
+ schedule_delayed_work(&bee_work, msecs_to_jiffies(0));
+
+ gbms_storage_init_done = true;
+ pr_info("init done\n");
+
+#ifdef CONFIG_DEBUG_FS
+ rootdir = debugfs_create_dir("gbms_storage", NULL);
+ if (!IS_ERR_OR_NULL(rootdir)) {
+ debugfs_create_file("cache", S_IFREG | 0444, rootdir,
+ NULL, &gbms_cache_status_ops);
+ debugfs_create_file("providers", S_IFREG | 0444, rootdir,
+ NULL, &gbms_providers_status_ops);
+ debugfs_create_file("offline", S_IFREG | 0200, rootdir,
+ NULL, &gbms_providers_offline_ops);
+ debugfs_create_file("export", S_IFREG | 0200, rootdir,
+ NULL, &gbms_providers_export_ops);
+ }
+#endif
+
+ return 0;
+}
+
+static void __exit gbms_storage_exit(void)
+{
+ int ret;
+
+#ifdef CONFIG_DEBUG_FS
+ if (!IS_ERR_OR_NULL(rootdir))
+ debugfs_remove(rootdir);
+#endif
+
+ ret = gbms_storage_flush_all_internal(true);
+ if (ret < 0)
+ pr_err("flush all failed");
+
+ /* TODO: free the list instead */
+ if (bee_data.bee_status == GBEE_STATUS_OK)
+ gbee_destroy(&bee_data);
+
+ if (gbms_cache_pool) {
+ gen_pool_destroy(gbms_cache_pool);
+ kfree(gbms_cache_mem);
+ }
+
+ gbms_providers_count = 0;
+}
+
+module_init(gbms_storage_init);
+module_exit(gbms_storage_exit);
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_DESCRIPTION("Google BMS Storage");
diff --git a/gbms_storage.h b/gbms_storage.h
new file mode 100644
index 0000000..e2d87e5
--- /dev/null
+++ b/gbms_storage.h
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Google Battery Management System
+ *
+ * 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.
+ */
+
+#ifndef __GBMS_STORAGE_H__
+#define __GBMS_STORAGE_H__
+
+/*
+ * GBMS Storage API
+ * The API provides functions to access to data stored in the persistent and
+ * semi-persistent storage of a device in a cross-platform and
+ * location-independent fashion. Clients in kernel and userspace use this
+ * directly and indirectly to retrieve battery serial number, cell chemistry
+ * type, cycle bin count, battery lifetime history and other battery related
+ * data.
+ */
+
+#define GBMS_STORAGE_ADDR_INVALID -1
+#define GBMS_STORAGE_INDEX_INVALID -1
+
+/* Battery Google Part Number */
+#define GBMS_BGPN_LEN 10
+/* Battery device info length */
+#define GBMS_DINF_LEN 32
+/* Battery device info length */
+#define GBMS_MINF_LEN 32
+/* Gauge Model State Restore */
+#define GBMS_GMSR_LEN 22
+
+/*
+ * Tags are u32 constants: hardcoding as hex since characters constants of more
+ * than one byte such as 'BGCE' are frown upon.
+ */
+typedef uint32_t gbms_tag_t;
+
+enum gbms_tags {
+ GBMS_TAG_BCNT = 0x42434e54,
+ GBMS_TAG_BGCE = 0x42474345,
+ GBMS_TAG_BGPN = 0x4247504e,
+ GBMS_TAG_BRES = 0x42524553,
+ GBMS_TAG_BRID = 0x42524944,
+ GBMS_TAG_SNUM = 0x534e554d,
+ GBMS_TAG_HIST = 0x48495354,
+ GBMS_TAG_DSNM = 0x44534e4d,
+ GBMS_TAG_MINF = 0x4d494e46,
+ GBMS_TAG_DINF = 0x44494e46,
+ GBMS_TAG_GMSR = 0x474d5352,
+
+ /* Reboot scratch */
+ GBMS_TAG_RRS0 = 0x52525330,
+ GBMS_TAG_RRS1 = 0x52525331,
+ GBMS_TAG_RRS2 = 0x52525332,
+ GBMS_TAG_RRS3 = 0x52525333,
+ GBMS_TAG_RRS4 = 0x52525334,
+ GBMS_TAG_RRS5 = 0x52525335,
+ GBMS_TAG_RRS6 = 0x52525336,
+ GBMS_TAG_RRS7 = 0x52525337,
+};
+
+/*
+ * struct gbms_storage_desc - callbacks for a GBMS storage provider.
+ *
+ * Fields not used should be initialized with NULL. The provider name and the
+ * iter callback are optional but strongly recommended. The write, fetch, store
+ * and flush callbacks are optional, descriptors with a non NULL write/store
+ * callback should have a non NULL read/fetch callback.
+ *
+ * The iterator callback (iter) is used to list the tags stored in the provider
+ * and can be used to detect duplicates. The list of tags exported from iter
+ * can be expected to be static (i.e. tags can be enumerated once on
+ * registration).
+ *
+ * The read and write callbacks transfer the data associated with a tag. The
+ * calls must return -ENOENT when called with a tag that is not known to the
+ * provider, a negative number on every other error or the number of bytes
+ * read or written to the device. The tag lookup for the read and write
+ * callbacks must be very efficient (i.e. consider implementation that use hash
+ * or switch statements).
+ *
+ * Fetch and store callbacks are used to grant non-mediated access to a range
+ * of consecutive addresses in storage space. The implementation must return a
+ * negative number on error or the number of bytes transferred with the
+ * operation. Support caching of the tag data location requires non NULL fetch
+ * and not NULL info callbacks.
+ *
+ * The read_data and write_data callbacks transfer the data associated with an
+ * enumerator. The calls must return -ENOENT when called with a tag that is not
+ * known to the provider, a negative number on every other error or the number
+ * of bytes read or written to the device during data transfers.
+ *
+ * Clients can only access keys that are available on a device (i.e. clients
+ * cannot create new tags) and the API returns -ENOENT when trying to access a
+ * tag that is not available on a device, -EGAIN while the storage is not fully
+ * initialized.
+ *
+ * @iter: callback, return the tags known from this provider
+ * @info: callback, return address and size for tags (used for caching)
+ * @read: callback, read data from a tag
+ * @write: callback, write data to a tag
+ * @fetch: callback, read up to count data bytes from an address
+ * @store: callback, write up to count data bytes to an address
+ * @flush: callback, request a fush of data to permanent storage
+ * @read_data: callback, read the elements of an enumerations
+ * @write_data: callback, write to the elements of an enumeration
+ */
+struct gbms_storage_desc {
+ int (*iter)(int index, gbms_tag_t *tag, void *ptr);
+ int (*info)(gbms_tag_t tag, size_t *addr, size_t *size, void *ptr);
+ int (*read)(gbms_tag_t tag, void *data, size_t count, void *ptr);
+ int (*write)(gbms_tag_t tag, const void *data, size_t count, void *ptr);
+ int (*fetch)(void *data, size_t addr, size_t count, void *ptr);
+ int (*store)(const void *data, size_t addr, size_t count, void *ptr);
+ int (*flush)(bool force, void *ptr);
+
+ int (*read_data)(gbms_tag_t tag, void *data, size_t count, int idx,
+ void *ptr);
+ int (*write_data)(gbms_tag_t tag, const void *data, size_t count,
+ int idx, void *ptr);
+};
+
+int gbms_storage_register(struct gbms_storage_desc *desc, const char *name,
+ void *ptr);
+int gbms_storage_offline(const char *name, bool flush);
+
+int gbms_storage_read(gbms_tag_t tag, void *data, size_t count);
+int gbms_storage_write(gbms_tag_t tag, const void *data, size_t count);
+
+int gbms_storage_read_data(gbms_tag_t tag, void *data, size_t count, int idx);
+int gbms_storage_write_data(gbms_tag_t tag, const void *data, size_t count,
+ int idx);
+int gbms_storage_flush(gbms_tag_t tag);
+int gbms_storage_flush_all(void);
+
+struct gbms_storage_device;
+
+/* standard device implementation that read data from an enumeration */
+struct gbms_storage_device *gbms_storage_create_device(const char *name,
+ gbms_tag_t tag);
+void gbms_storage_cleanup_device(struct gbms_storage_device *gdev);
+
+#endif /* __GBMS_STORAGE_H__ */
diff --git a/google_battery.c b/google_battery.c
new file mode 100644
index 0000000..3bc7a45
--- /dev/null
+++ b/google_battery.c
@@ -0,0 +1,4023 @@
+/*
+ * Copyright 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 ": " fmt
+
+#ifdef CONFIG_PM_SLEEP
+#define SUPPORT_PM_SLEEP 1
+#endif
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/pm_wakeup.h>
+#include <linux/pmic-voter.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include "google_bms.h"
+#include "google_psy.h"
+#include "qmath.h"
+#include "logbuffer.h"
+#include <crypto/hash.h>
+
+#include <linux/debugfs.h>
+
+#define BATT_DELAY_INIT_MS 250
+#define BATT_WORK_FAST_RETRY_CNT 30
+#define BATT_WORK_FAST_RETRY_MS 1000
+#define BATT_WORK_ERROR_RETRY_MS 1000
+
+#define DEFAULT_BATT_FAKE_CAPACITY 50
+#define DEFAULT_BATT_UPDATE_INTERVAL 30000
+#define DEFAULT_BATT_DRV_RL_SOC_THRESHOLD 97
+#define DEFAULT_HIGH_TEMP_UPDATE_THRESHOLD 550
+
+#define MSC_ERROR_UPDATE_INTERVAL 5000
+#define MSC_DEFAULT_UPDATE_INTERVAL 30000
+
+/* qual time is 15 minutes of charge or 15% increase in SOC */
+#define DEFAULT_CHG_STATS_MIN_QUAL_TIME (15 * 60)
+#define DEFAULT_CHG_STATS_MIN_DELTA_SOC 15
+
+/* Voters */
+#define MSC_LOGIC_VOTER "msc_logic"
+#define SW_JEITA_VOTER "sw_jeita"
+#define RL_STATE_VOTER "rl_state"
+
+#define UICURVE_MAX 3
+
+/* Initial data of history cycle count */
+#define HCC_DEFAULT_DELTA_CYCLE_CNT 25
+
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "androidboot."
+#define DEV_SN_LENGTH 20
+static char dev_sn[DEV_SN_LENGTH];
+module_param_string(serialno, dev_sn, DEV_SN_LENGTH, 0000);
+
+#if (GBMS_CCBIN_BUCKET_COUNT < 1) || (GBMS_CCBIN_BUCKET_COUNT > 100)
+#error "GBMS_CCBIN_BUCKET_COUNT needs to be a value from 1-100"
+#endif
+
+#define get_boot_sec() div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC)
+
+struct ssoc_uicurve {
+ qnum_t real;
+ qnum_t ui;
+};
+
+enum batt_rl_status {
+ BATT_RL_STATUS_NONE = 0,
+ BATT_RL_STATUS_DISCHARGE = -1,
+ BATT_RL_STATUS_RECHARGE = 1,
+};
+
+#define RL_DELTA_SOC_MAX 8
+
+struct batt_ssoc_rl_state {
+ /* rate limiter state */
+ qnum_t rl_ssoc_target;
+ time_t rl_ssoc_last_update;
+
+ /* rate limiter flags */
+ bool rl_no_zero;
+ int rl_fast_track;
+ int rl_track_target;
+ /* rate limiter config */
+ int rl_delta_max_time;
+ qnum_t rl_delta_max_soc;
+
+ int rl_delta_soc_ratio[RL_DELTA_SOC_MAX];
+ qnum_t rl_delta_soc_limit[RL_DELTA_SOC_MAX];
+ int rl_delta_soc_cnt;
+
+ qnum_t rl_ft_low_limit;
+ qnum_t rl_ft_delta_limit;
+};
+
+#define SSOC_STATE_BUF_SZ 128
+
+struct batt_ssoc_state {
+ /* output of gauge data filter */
+ qnum_t ssoc_gdf;
+ /* UI Curves */
+ int ssoc_curve_type; /*<0 dsg, >0 chg, 0? */
+ struct ssoc_uicurve ssoc_curve[UICURVE_MAX];
+ qnum_t ssoc_uic;
+ /* output of rate limiter */
+ qnum_t ssoc_rl;
+ struct batt_ssoc_rl_state ssoc_rl_state;
+
+ /* output of rate limiter */
+ int rl_rate;
+ int rl_last_ssoc;
+ time_t rl_last_update;
+
+ /* connected or disconnected */
+ int buck_enabled;
+
+ /* recharge logic */
+ int rl_soc_threshold;
+ enum batt_rl_status rl_status;
+
+ /* buff */
+ char ssoc_state_cstr[SSOC_STATE_BUF_SZ];
+};
+
+struct gbatt_ccbin_data {
+ u16 count[GBMS_CCBIN_BUCKET_COUNT];
+ char cyc_ctr_cstr[GBMS_CCBIN_CSTR_SIZE];
+ struct mutex lock;
+ int prev_soc;
+};
+
+#define DEFAULT_RES_TEMP_HIGH 390
+#define DEFAULT_RES_TEMP_LOW 350
+#define DEFAULT_RES_SSOC_THR 75
+#define DEFAULT_RES_FILT_LEN 10
+
+struct batt_res {
+ bool estimate_requested;
+
+ /* samples */
+ int sample_accumulator;
+ int sample_count;
+
+ /* registers */
+ int filter_count;
+ int resistance_avg;
+
+ /* configuration */
+ int estimate_filter;
+ int ssoc_threshold;
+ int res_temp_low;
+ int res_temp_high;
+};
+
+enum batt_paired_state {
+ BATT_PAIRING_WRITE_ERROR = -4,
+ BATT_PAIRING_READ_ERROR = -3,
+ BATT_PAIRING_MISMATCH = -2,
+ BATT_PAIRING_DISABLED = -1,
+ BATT_PAIRING_ENABLED = 0,
+ BATT_PAIRING_PAIRED = 1,
+ BATT_PAIRING_RESET = 2,
+};
+
+enum batt_lfcollect_status {
+ BATT_LFCOLLECT_NOT_AVAILABLE = -1,
+ BATT_LFCOLLECT_DISABLED = 0,
+ BATT_LFCOLLECT_ENABLED = 1,
+ BATT_LFCOLLECT_COLLECT = 2,
+};
+
+/* battery driver state */
+struct batt_drv {
+ struct device *device;
+ struct power_supply *psy;
+
+ const char *fg_psy_name;
+ struct power_supply *fg_psy;
+ struct notifier_block fg_nb;
+
+ struct delayed_work init_work;
+ struct delayed_work batt_work;
+
+ struct wakeup_source msc_ws;
+ struct wakeup_source batt_ws;
+ struct wakeup_source taper_ws;
+ struct wakeup_source poll_ws;
+ bool hold_taper_ws;
+
+ /* TODO: b/111407333, will likely need to adjust SOC% on wakeup */
+ bool init_complete;
+ bool resume_complete;
+ bool batt_present;
+
+ struct mutex batt_lock;
+ struct mutex chg_lock;
+
+ /* battery work */
+ int fg_status;
+ int batt_fast_update_cnt;
+ u32 batt_update_interval;
+ /* update high temperature in time */
+ int batt_temp;
+ u32 batt_update_high_temp_threshold;
+ /* fake battery temp for thermal testing */
+ int fake_temp;
+ /* triger for recharge logic next update from charger */
+ bool batt_full;
+ struct batt_ssoc_state ssoc_state;
+ /* bin count */
+ struct gbatt_ccbin_data cc_data;
+ /* fg cycle count */
+ int cycle_count;
+
+ /* props */
+ int soh;
+ int fake_capacity;
+ bool dead_battery;
+ int capacity_level;
+ bool chg_done;
+
+ /* temp outside the charge table */
+ int jeita_stop_charging;
+
+ /* MSC charging */
+ u32 battery_capacity;
+ struct gbms_chg_profile chg_profile;
+ union gbms_charger_state chg_state;
+
+ int temp_idx;
+ int vbatt_idx;
+ int checked_cv_cnt;
+ int checked_ov_cnt;
+ int checked_tier_switch_cnt;
+
+ int fv_uv;
+ int cc_max;
+ int msc_update_interval;
+
+ bool disable_votes;
+ struct votable *msc_interval_votable;
+ struct votable *fcc_votable;
+ struct votable *fv_votable;
+
+ /* stats */
+ int msc_state;
+ int msc_irdrop_state;
+ struct mutex stats_lock;
+ struct gbms_charging_event ce_data;
+ struct gbms_charging_event ce_qual;
+
+ /* time to full */
+ struct batt_ttf_stats ttf_stats;
+
+ /* logging */
+ struct logbuffer *ttf_log;
+ struct logbuffer *ssoc_log;
+
+ /* thermal */
+ struct thermal_zone_device *tz_dev;
+
+ /* Resistance */
+ struct batt_res res_state;
+
+ /* used to detect battery replacements and reset statistics */
+ enum batt_paired_state pairing_state;
+
+ /* collect battery history/lifetime data (history) */
+ enum batt_lfcollect_status blf_state;
+ int hist_delta_cycle_cnt;
+ int hist_data_max_cnt;
+ void *hist_data;
+
+ /* Battery device info */
+ u8 dev_info_check[GBMS_DINF_LEN];
+
+ /* History Device */
+ struct gbms_storage_device *history;
+};
+
+static inline void batt_update_cycle_count(struct batt_drv *batt_drv)
+{
+ batt_drv->cycle_count = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_CYCLE_COUNT);
+}
+
+static int google_battery_tz_get_cycle_count(void *data, int *cycle_count)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+
+ if (!cycle_count) {
+ pr_err("Cycle Count NULL");
+ return -EINVAL;
+ }
+
+ if (batt_drv->cycle_count < 0)
+ return batt_drv->cycle_count;
+
+ *cycle_count = batt_drv->cycle_count;
+
+ return 0;
+}
+
+static int psy_changed(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct power_supply *psy = data;
+ struct batt_drv *batt_drv = container_of(nb, struct batt_drv, fg_nb);
+
+ pr_debug("name=%s evt=%lu\n", psy->desc->name, action);
+
+ if ((action != PSY_EVENT_PROP_CHANGED) ||
+ (psy == NULL) || (psy->desc == NULL) || (psy->desc->name == NULL))
+ return NOTIFY_OK;
+
+ if (action == PSY_EVENT_PROP_CHANGED &&
+ (!strcmp(psy->desc->name, batt_drv->fg_psy_name))) {
+ mod_delayed_work(system_wq, &batt_drv->batt_work, 0);
+ }
+
+ return NOTIFY_OK;
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define SSOC_TRUE 15
+#define SSOC_SPOOF 95
+#define SSOC_FULL 100
+#define UICURVE_BUF_SZ (UICURVE_MAX * 15 + 1)
+
+enum ssoc_uic_type {
+ SSOC_UIC_TYPE_DSG = -1,
+ SSOC_UIC_TYPE_NONE = 0,
+ SSOC_UIC_TYPE_CHG = 1,
+};
+
+const qnum_t ssoc_point_true = qnum_rconst(SSOC_TRUE);
+const qnum_t ssoc_point_spoof = qnum_rconst(SSOC_SPOOF);
+const qnum_t ssoc_point_full = qnum_rconst(SSOC_FULL);
+
+static struct ssoc_uicurve chg_curve[UICURVE_MAX] = {
+ { ssoc_point_true, ssoc_point_true },
+ { ssoc_point_spoof, ssoc_point_spoof },
+ { ssoc_point_full, ssoc_point_full },
+};
+
+static struct ssoc_uicurve dsg_curve[UICURVE_MAX] = {
+ { ssoc_point_true, ssoc_point_true },
+ { ssoc_point_spoof, ssoc_point_full },
+ { ssoc_point_full, ssoc_point_full },
+};
+
+static char *ssoc_uicurve_cstr(char *buff, size_t size,
+ struct ssoc_uicurve *curve)
+{
+ int i, len = 0;
+
+ for (i = 0; i < UICURVE_MAX ; i++) {
+ len += scnprintf(&buff[len], size - len,
+ "[" QNUM_CSTR_FMT " " QNUM_CSTR_FMT "]",
+ qnum_toint(curve[i].real),
+ qnum_fracdgt(curve[i].real),
+ qnum_toint(curve[i].ui),
+ qnum_fracdgt(curve[i].ui));
+ if (len >= size)
+ break;
+ }
+
+ buff[len] = 0;
+ return buff;
+}
+
+/* NOTE: no bounds checks on this one */
+static int ssoc_uicurve_find(qnum_t real, struct ssoc_uicurve *curve)
+{
+ int i;
+
+ for (i = 1; i < UICURVE_MAX ; i++) {
+ if (real == curve[i].real)
+ return i;
+ if (real > curve[i].real)
+ continue;
+ break;
+ }
+
+ return i-1;
+}
+
+static qnum_t ssoc_uicurve_map(qnum_t real, struct ssoc_uicurve *curve)
+{
+ qnum_t slope = 0, delta_ui, delta_re;
+ int i;
+
+ if (real < curve[0].real)
+ return real;
+ if (real >= curve[UICURVE_MAX - 1].ui)
+ return curve[UICURVE_MAX - 1].ui;
+
+ i = ssoc_uicurve_find(real, curve);
+ if (curve[i].real == real)
+ return curve[i].ui;
+
+ delta_ui = curve[i + 1].ui - curve[i].ui;
+ delta_re = curve[i + 1].real - curve[i].real;
+ if (delta_re)
+ slope = qnum_div(delta_ui, delta_re);
+
+ return curve[i].ui + qnum_mul(slope, (real - curve[i].real));
+}
+
+/* "optimized" to work on 3 element curves */
+static void ssoc_uicurve_splice(struct ssoc_uicurve *curve, qnum_t real,
+ qnum_t ui)
+{
+ if (real < curve[0].real || real > curve[2].real)
+ return;
+
+#if UICURVE_MAX != 3
+#error ssoc_uicurve_splice() only support UICURVE_MAX == 3
+#endif
+
+ /* splice only when real is within the curve range */
+ curve[1].real = real;
+ curve[1].ui = ui;
+}
+
+static void ssoc_uicurve_dup(struct ssoc_uicurve *dst,
+ struct ssoc_uicurve *curve)
+{
+ if (dst != curve)
+ memcpy(dst, curve, sizeof(*dst)*UICURVE_MAX);
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+/* could also use the rate of change for this */
+static qnum_t ssoc_rl_max_delta(const struct batt_ssoc_rl_state *rls,
+ int bucken, time_t delta_time)
+{
+ int i;
+ const qnum_t max_delta = (rls->rl_delta_max_soc * delta_time) /
+ rls->rl_delta_max_time;
+
+ if (rls->rl_fast_track)
+ return max_delta;
+
+ /* might have one table for charging and one for discharging */
+ for (i = 0; i < rls->rl_delta_soc_cnt; i++) {
+ if (rls->rl_delta_soc_limit[i] == 0)
+ break;
+
+ if (rls->rl_ssoc_target < rls->rl_delta_soc_limit[i])
+ return (max_delta * 10) /
+ rls->rl_delta_soc_ratio[i];
+ }
+
+ return max_delta;
+}
+
+static qnum_t ssoc_apply_rl(struct batt_ssoc_state *ssoc)
+{
+ const time_t now = get_boot_sec();
+ struct batt_ssoc_rl_state *rls = &ssoc->ssoc_rl_state;
+ qnum_t rl_val;
+
+ /* track ssoc_uic when buck is enabled or the minimum value of uic */
+ if (ssoc->buck_enabled ||
+ (!ssoc->buck_enabled && ssoc->ssoc_uic < rls->rl_ssoc_target))
+ rls->rl_ssoc_target = ssoc->ssoc_uic;
+
+ /* sanity on the target */
+ if (rls->rl_ssoc_target > qnum_fromint(100))
+ rls->rl_ssoc_target = qnum_fromint(100);
+ if (rls->rl_ssoc_target < qnum_fromint(0))
+ rls->rl_ssoc_target = qnum_fromint(0);
+
+ /* closely track target */
+ if (rls->rl_track_target) {
+ rl_val = rls->rl_ssoc_target;
+ } else {
+ qnum_t step;
+ const time_t delta_time = now - rls->rl_ssoc_last_update;
+ const time_t max_delta = ssoc_rl_max_delta(rls,
+ ssoc->buck_enabled,
+ delta_time);
+
+ /* apply the rate limiter, delta_soc to target */
+ step = rls->rl_ssoc_target - ssoc->ssoc_rl;
+ if (step < -max_delta)
+ step = -max_delta;
+ else if (step > max_delta)
+ step = max_delta;
+
+ rl_val = ssoc->ssoc_rl + step;
+ }
+
+ /* do not increase when not connected */
+ if (!ssoc->buck_enabled && rl_val > ssoc->ssoc_rl)
+ rl_val = ssoc->ssoc_rl;
+
+ /* will report 0% when rl_no_zero clears */
+ if (rls->rl_no_zero && rl_val <= qnum_fromint(1))
+ rl_val = qnum_fromint(1);
+
+ rls->rl_ssoc_last_update = now;
+ return rl_val;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* a statement :-) */
+static int ssoc_get_real(const struct batt_ssoc_state *ssoc)
+{
+ return qnum_toint(ssoc->ssoc_gdf);
+}
+
+static qnum_t ssoc_get_capacity_raw(const struct batt_ssoc_state *ssoc)
+{
+ return ssoc->ssoc_rl;
+}
+
+/* reported to userspace: call while holding batt_lock */
+static int ssoc_get_capacity(const struct batt_ssoc_state *ssoc)
+{
+ const qnum_t raw = ssoc_get_capacity_raw(ssoc);
+ return qnum_roundint(raw, 0.005);
+}
+
+/* ------------------------------------------------------------------------- */
+
+void dump_ssoc_state(struct batt_ssoc_state *ssoc_state, struct logbuffer *log)
+{
+ char buff[UICURVE_BUF_SZ] = { 0 };
+
+ scnprintf(ssoc_state->ssoc_state_cstr,
+ sizeof(ssoc_state->ssoc_state_cstr),
+ "SSOC: l=%d%% gdf=%d.%02d uic=%d.%02d rl=%d.%02d ct=%d curve:%s rls=%d",
+ ssoc_get_capacity(ssoc_state),
+ qnum_toint(ssoc_state->ssoc_gdf),
+ qnum_fracdgt(ssoc_state->ssoc_gdf),
+ qnum_toint(ssoc_state->ssoc_uic),
+ qnum_fracdgt(ssoc_state->ssoc_uic),
+ qnum_toint(ssoc_state->ssoc_rl),
+ qnum_fracdgt(ssoc_state->ssoc_rl),
+ ssoc_state->ssoc_curve_type,
+ ssoc_uicurve_cstr(buff, sizeof(buff), ssoc_state->ssoc_curve),
+ ssoc_state->rl_status);
+
+ if (log) {
+ logbuffer_log(log, "%s", ssoc_state->ssoc_state_cstr);
+ } else {
+ pr_info("%s\n", ssoc_state->ssoc_state_cstr);
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* call while holding batt_lock */
+static void ssoc_update(struct batt_ssoc_state *ssoc, qnum_t soc)
+{
+ struct batt_ssoc_rl_state *rls = &ssoc->ssoc_rl_state;
+ qnum_t delta;
+
+ /* low pass filter */
+ ssoc->ssoc_gdf = soc;
+ /* spoof UI @ EOC */
+ ssoc->ssoc_uic = ssoc_uicurve_map(ssoc->ssoc_gdf, ssoc->ssoc_curve);
+
+ /* first target is current UIC */
+ if (rls->rl_ssoc_target == -1) {
+ rls->rl_ssoc_target = ssoc->ssoc_uic;
+ ssoc->ssoc_rl = ssoc->ssoc_uic;
+ }
+
+ /* enable fast track when target under configured limit */
+ rls->rl_fast_track |= rls->rl_ssoc_target < rls->rl_ft_low_limit;
+
+ /*
+ * delta fast tracking during charge
+ * NOTE: might use the stats from TTF to determine the maximum rate
+ */
+ delta = rls->rl_ssoc_target - ssoc->ssoc_rl;
+ if (rls->rl_ft_delta_limit && ssoc->buck_enabled && delta > 0) {
+ /* only when SOC increase */
+ rls->rl_fast_track |= delta > rls->rl_ft_delta_limit;
+ } else if (rls->rl_ft_delta_limit && !ssoc->buck_enabled && delta < 0) {
+ /* enable fast track when target under configured limit */
+ rls->rl_fast_track |= -delta > rls->rl_ft_delta_limit;
+ }
+
+ /*
+ * Right now a simple test on target metric falling under 0.5%
+ * TODO: add a filter that decrements no_zero when a specific
+ * condition is met (ex rl_ssoc_target < 1%).
+ */
+ if (rls->rl_no_zero)
+ rls->rl_no_zero = rls->rl_ssoc_target > qnum_from_q8_8(128);
+
+ /* monotonicity and rate of change */
+ ssoc->ssoc_rl = ssoc_apply_rl(ssoc);
+}
+
+/*
+ * Maxim could need:
+ * 1fh AvCap, 10h FullCap. 23h FullCapNom
+ * QC could need:
+ * QG_CC_SOC, QG_Raw_SOC, QG_Bat_SOC, QG_Sys_SOC, QG_Mon_SOC
+ */
+static int ssoc_work(struct batt_ssoc_state *ssoc_state,
+ struct power_supply *fg_psy)
+{
+ int soc_q8_8;
+ qnum_t soc_raw;
+
+ /*
+ * TODO: POWER_SUPPLY_PROP_CAPACITY_RAW should return a qnum_t
+ * TODO: add an array here configured in DT with the properties
+ * to query and their weights, make soc_raw come from fusion.
+ */
+ soc_q8_8 = GPSY_GET_PROP(fg_psy, POWER_SUPPLY_PROP_CAPACITY_RAW);
+ if (soc_q8_8 < 0)
+ return -EINVAL;
+
+ /*
+ * soc_raw can come from fusion:
+ * soc_raw = m1 * w1 + m2 * w2 + ...
+ *
+ * where m1, m2 are gauge metrics, w1,w1 are weights that change
+ * with temperature, state of charge, battery health etc.
+ */
+ soc_raw = qnum_from_q8_8(soc_q8_8);
+
+ ssoc_update(ssoc_state, soc_raw);
+ return 0;
+}
+
+/*
+ * Called on connect and disconnect to adjust the UI curve. Splice at GDF less
+ * a fixed delta while UI is at 100% (i.e. in RL) to avoid showing 100% for
+ * "too long" after disconnect.
+ */
+#define SSOC_DELTA 3
+void ssoc_change_curve(struct batt_ssoc_state *ssoc_state,
+ enum ssoc_uic_type type)
+{
+ struct ssoc_uicurve *new_curve;
+ qnum_t gdf = ssoc_state->ssoc_gdf; /* actual battery level */
+ const qnum_t ssoc_level = ssoc_get_capacity(ssoc_state);
+
+ /* force dsg curve when connect/disconnect with battery at 100% */
+ if (ssoc_level >= SSOC_FULL) {
+ const qnum_t rlt = qnum_fromint(ssoc_state->rl_soc_threshold);
+
+ gdf -= qnum_rconst(SSOC_DELTA);
+ if (gdf > rlt)
+ gdf = rlt;
+ type = SSOC_UIC_TYPE_DSG;
+ }
+
+ new_curve = (type == SSOC_UIC_TYPE_DSG) ? dsg_curve : chg_curve;
+ ssoc_uicurve_dup(ssoc_state->ssoc_curve, new_curve);
+ ssoc_state->ssoc_curve_type = type;
+
+ /* splice at (->ssoc_gdf,->ssoc_rl) because past spoof */
+ ssoc_uicurve_splice(ssoc_state->ssoc_curve,
+ gdf,
+ ssoc_get_capacity_raw(ssoc_state));
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * enter recharge logic in BATT_RL_STATUS_DISCHARGE on charger_DONE,
+ * enter BATT_RL_STATUS_RECHARGE on Fuel Gauge FULL
+ * NOTE: batt_rl_update_status() doesn't call this, it flip from DISCHARGE
+ * to recharge on its own.
+ * NOTE: call holding chg_lock
+ * @pre rl_status != BATT_RL_STATUS_NONE
+ */
+static bool batt_rl_enter(struct batt_ssoc_state *ssoc_state,
+ enum batt_rl_status rl_status)
+{
+ const int rl_current = ssoc_state->rl_status;
+
+ /*
+ * NOTE: NO_OP when RL=DISCHARGE since batt_rl_update_status() flip
+ * between BATT_RL_STATUS_DISCHARGE and BATT_RL_STATUS_RECHARGE
+ * directly.
+ */
+ if (rl_current == rl_status || rl_current == BATT_RL_STATUS_DISCHARGE)
+ return false;
+
+ /*
+ * NOTE: rl_status transition from *->DISCHARGE on charger FULL (during
+ * charge or at the end of recharge) and transition from
+ * NONE->RECHARGE when battery is full (SOC==100%) before charger is.
+ */
+ if (rl_status == BATT_RL_STATUS_DISCHARGE) {
+ ssoc_uicurve_dup(ssoc_state->ssoc_curve, dsg_curve);
+ ssoc_state->ssoc_curve_type = SSOC_UIC_TYPE_DSG;
+ }
+
+ ssoc_update(ssoc_state, ssoc_state->ssoc_gdf);
+ ssoc_state->rl_status = rl_status;
+
+ return true;
+}
+
+static int ssoc_rl_read_dt(struct batt_ssoc_rl_state *rls,
+ struct device_node *node)
+{
+ u32 tmp, delta_soc[RL_DELTA_SOC_MAX];
+ int ret, i;
+
+ ret = of_property_read_u32(node, "google,rl_delta-max-soc", &tmp);
+ if (ret == 0)
+ rls->rl_delta_max_soc = qnum_fromint(tmp);
+
+ ret = of_property_read_u32(node, "google,rl_delta-max-time", &tmp);
+ if (ret == 0)
+ rls->rl_delta_max_time = tmp;
+
+ if (!rls->rl_delta_max_soc || !rls->rl_delta_max_time)
+ return -EINVAL;
+
+ rls->rl_no_zero = of_property_read_bool(node, "google,rl_no-zero");
+ rls->rl_track_target = of_property_read_bool(node,
+ "google,rl_track-target");
+
+ ret = of_property_read_u32(node, "google,rl_ft-low-limit", &tmp);
+ if (ret == 0)
+ rls->rl_ft_low_limit = qnum_fromint(tmp);
+
+ ret = of_property_read_u32(node, "google,rl_ft-delta-limit", &tmp);
+ if (ret == 0)
+ rls->rl_ft_delta_limit = qnum_fromint(tmp);
+
+ rls->rl_delta_soc_cnt = of_property_count_elems_of_size(node,
+ "google,rl_soc-limits",
+ sizeof(u32));
+ tmp = of_property_count_elems_of_size(node, "google,rl_soc-rates",
+ sizeof(u32));
+ if (rls->rl_delta_soc_cnt != tmp || tmp == 0) {
+ rls->rl_delta_soc_cnt = 0;
+ goto done;
+ }
+
+ if (rls->rl_delta_soc_cnt > RL_DELTA_SOC_MAX)
+ return -EINVAL;
+
+ ret = of_property_read_u32_array(node, "google,rl_soc-limits",
+ delta_soc,
+ rls->rl_delta_soc_cnt);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < rls->rl_delta_soc_cnt; i++)
+ rls->rl_delta_soc_limit[i] = qnum_fromint(delta_soc[i]);
+
+ ret = of_property_read_u32_array(node, "google,rl_soc-rates",
+ delta_soc,
+ rls->rl_delta_soc_cnt);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < rls->rl_delta_soc_cnt; i++)
+ rls->rl_delta_soc_ratio[i] = delta_soc[i];
+
+done:
+ return 0;
+}
+
+
+/*
+ * NOTE: might need to use SOC from bootloader as starting point to avoid UI
+ * SSOC jumping around or taking long time to coverge. Could technically read
+ * charger voltage and estimate SOC% based on empty and full voltage.
+ */
+static int ssoc_init(struct batt_ssoc_state *ssoc_state,
+ struct device_node *node,
+ struct power_supply *fg_psy)
+{
+ int ret, capacity;
+
+ ret = ssoc_rl_read_dt(&ssoc_state->ssoc_rl_state, node);
+ if (ret < 0)
+ ssoc_state->ssoc_rl_state.rl_track_target = 1;
+ ssoc_state->ssoc_rl_state.rl_ssoc_target = -1;
+
+ /*
+ * ssoc_work() needs a curve: start with the charge curve to prevent
+ * SSOC% from increasing after a reboot. Curve type must be NONE until
+ * battery knows the charger BUCK_EN state.
+ */
+ ssoc_uicurve_dup(ssoc_state->ssoc_curve, chg_curve);
+ ssoc_state->ssoc_curve_type = SSOC_UIC_TYPE_NONE;
+
+ ret = ssoc_work(ssoc_state, fg_psy);
+ if (ret < 0)
+ return -EIO;
+
+ capacity = ssoc_get_capacity(ssoc_state);
+ if (capacity >= SSOC_FULL) {
+ /* consistent behavior when booting without adapter */
+ ssoc_uicurve_dup(ssoc_state->ssoc_curve, dsg_curve);
+ } else if (capacity < SSOC_TRUE) {
+ /* no split */
+ } else if (capacity < SSOC_SPOOF) {
+ /* mark the initial point if under spoof */
+ ssoc_uicurve_splice(ssoc_state->ssoc_curve,
+ ssoc_state->ssoc_gdf,
+ ssoc_state->ssoc_rl);
+
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * just reset state, no PS notifications no changes in the UI curve. This is
+ * called on startup and on disconnect when the charge driver state is reset
+ * NOTE: call holding chg_lock
+ */
+static void batt_rl_reset(struct batt_drv *batt_drv)
+{
+ batt_drv->ssoc_state.rl_status = BATT_RL_STATUS_NONE;
+}
+
+/* RL recharge: after SSOC work, restart charging.
+ * NOTE: call holding chg_lock
+ */
+static void batt_rl_update_status(struct batt_drv *batt_drv)
+{
+ struct batt_ssoc_state *ssoc_state = &batt_drv->ssoc_state;
+ int soc;
+
+ /* already in _RECHARGE or _NONE, done */
+ if (ssoc_state->rl_status != BATT_RL_STATUS_DISCHARGE)
+ return;
+
+ /* recharge logic work on real soc */
+ soc = ssoc_get_real(ssoc_state);
+ if (ssoc_state->rl_soc_threshold &&
+ soc <= ssoc_state->rl_soc_threshold) {
+
+ /* change state (will restart charge) on trigger */
+ ssoc_state->rl_status = BATT_RL_STATUS_RECHARGE;
+ if (batt_drv->psy)
+ power_supply_changed(batt_drv->psy);
+ }
+
+}
+
+/* ------------------------------------------------------------------------- */
+
+static int batt_ttf_estimate(time_t *res, const struct batt_drv *batt_drv)
+{
+ int rc;
+ time_t estimate = batt_drv->ttf_stats.ttf_fake;
+
+ if (batt_drv->ssoc_state.buck_enabled != 1)
+ return -EINVAL;
+
+ if (batt_drv->ttf_stats.ttf_fake != -1)
+ goto done;
+
+ rc = ttf_soc_estimate(&estimate, &batt_drv->ttf_stats,
+ &batt_drv->ce_data,
+ ssoc_get_capacity_raw(&batt_drv->ssoc_state),
+ ssoc_point_full);
+ if (rc < 0)
+ estimate = -1;
+
+done:
+ *res = estimate;
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static void batt_chg_stats_init(struct gbms_charging_event *ce_data,
+ const struct gbms_chg_profile *profile)
+{
+ int i;
+
+ memset(ce_data, 0, sizeof(*ce_data));
+
+ ce_data->chg_profile = profile;
+ ce_data->charging_stats.voltage_in = -1;
+ ce_data->charging_stats.ssoc_in = -1;
+ ce_data->charging_stats.voltage_out = -1;
+ ce_data->charging_stats.ssoc_out = -1;
+
+ ttf_soc_init(&ce_data->soc_stats);
+ ce_data->last_soc = -1;
+
+ for (i = 0; i < GBMS_STATS_TIER_COUNT ; i++) {
+ ce_data->tier_stats[i].temp_idx = -1;
+ ce_data->tier_stats[i].vtier_idx = i;
+ ce_data->tier_stats[i].soc_in = -1;
+ }
+
+}
+
+static void batt_chg_stats_start(struct batt_drv *batt_drv)
+{
+ union gbms_ce_adapter_details ad;
+ struct gbms_charging_event *ce_data = &batt_drv->ce_data;
+ const time_t now = get_boot_sec();
+ int vin, cc_in;
+
+ mutex_lock(&batt_drv->stats_lock);
+ ad.v = batt_drv->ce_data.adapter_details.v;
+ batt_chg_stats_init(ce_data, &batt_drv->chg_profile);
+ batt_drv->ce_data.adapter_details.v = ad.v;
+
+ vin = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ ce_data->charging_stats.voltage_in = (vin < 0) ? -1 : vin / 1000;
+ ce_data->charging_stats.ssoc_in =
+ ssoc_get_capacity(&batt_drv->ssoc_state);
+ cc_in = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER);
+ ce_data->charging_stats.cc_in = (cc_in < 0) ? -1 : cc_in / 1000;
+
+ ce_data->charging_stats.ssoc_out = -1;
+ ce_data->charging_stats.voltage_out = -1;
+
+ ce_data->first_update = now;
+ ce_data->last_update = now;
+
+ mutex_unlock(&batt_drv->stats_lock);
+}
+
+/* call holding stats_lock */
+static bool batt_chg_stats_qual(const struct gbms_charging_event *ce_data)
+{
+ const long elap = ce_data->last_update - ce_data->first_update;
+ const long ssoc_delta = ce_data->charging_stats.ssoc_out -
+ ce_data->charging_stats.ssoc_in;
+
+ return elap >= ce_data->chg_sts_qual_time ||
+ ssoc_delta >= ce_data->chg_sts_delta_soc;
+}
+
+/* call holding stats_lock */
+static void batt_chg_stats_tier(struct gbms_ce_tier_stats *tier,
+ int msc_state,
+ time_t elap)
+{
+ if (msc_state < 0 || msc_state >= MSC_STATES_COUNT)
+ return;
+
+ tier->msc_cnt[msc_state] += 1;
+ tier->msc_elap[msc_state] += elap;
+}
+
+/* call holding stats_lock */
+static void batt_chg_stats_soc_update(struct gbms_charging_event *ce_data,
+ qnum_t soc, time_t elap, int tier_index,
+ int cc)
+{
+ int index;
+ const int last_soc = ce_data->last_soc;
+
+ index = qnum_toint(soc);
+ if (index < 0)
+ index = 0;
+ if (index > 100)
+ index = 100;
+ if (index < last_soc)
+ return;
+
+ if (ce_data->soc_stats.elap[index] == 0) {
+ ce_data->soc_stats.ti[index] = tier_index;
+ ce_data->soc_stats.cc[index] = cc;
+ }
+
+ if (last_soc != -1)
+ ce_data->soc_stats.elap[last_soc] += elap;
+
+ ce_data->last_soc = index;
+}
+
+/* call holding stats_lock */
+static void batt_chg_stats_update(struct batt_drv *batt_drv,
+ int temp_idx, int tier_idx,
+ int ibatt_ma, int temp, time_t elap)
+{
+ int cc;
+ const int msc_state = batt_drv->msc_state;
+ const uint16_t icl_settled = batt_drv->chg_state.f.icl;
+ struct gbms_ce_tier_stats *tier =
+ &batt_drv->ce_data.tier_stats[tier_idx];
+
+ /* TODO: read at start of tier and update cc_total of previous */
+ cc = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER);
+ if (cc < 0) {
+ pr_info("MSC_STAT cannot read cc=%d\n", cc);
+ return;
+ }
+ cc = cc / 1000;
+
+ /* book to previous soc unless discharging */
+ if (msc_state != MSC_DSG) {
+ qnum_t soc = ssoc_get_capacity_raw(&batt_drv->ssoc_state);
+
+ /* TODO: should I use ssoc instead? */
+ batt_chg_stats_soc_update(&batt_drv->ce_data, soc, elap,
+ tier_idx, cc);
+ }
+
+ /* book to previous state */
+ batt_chg_stats_tier(tier, msc_state, elap);
+
+ if (tier->soc_in == -1) {
+ int soc_in;
+
+ soc_in = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_CAPACITY_RAW);
+ if (soc_in < 0) {
+ pr_info("MSC_STAT cannot read soc_in=%d\n", soc_in);
+ return;
+ }
+
+ tier->temp_idx = temp_idx;
+
+ tier->temp_in = temp;
+ tier->temp_min = temp;
+ tier->temp_max = temp;
+
+ tier->ibatt_min = ibatt_ma;
+ tier->ibatt_max = ibatt_ma;
+
+ tier->icl_min = icl_settled;
+ tier->icl_max = icl_settled;
+
+ tier->soc_in = soc_in;
+ tier->cc_in = cc;
+ tier->cc_total = 0;
+ } else {
+ const u8 flags = batt_drv->chg_state.f.flags;
+
+ /* crossed temperature tier */
+ if (temp_idx != tier->temp_idx)
+ tier->temp_idx = -1;
+
+ if (flags & GBMS_CS_FLAG_CC) {
+ tier->time_fast += elap;
+ } else if (flags & GBMS_CS_FLAG_CV) {
+ tier->time_taper += elap;
+ } else {
+ tier->time_other += elap;
+ }
+
+ /*
+ * averages: temp < 100. icl_settled < 3000, sum(ibatt)
+ * is bound to battery capacity, elap in seconds, sums
+ * are stored in an s64. For icl_settled I need a tier
+ * to last for more than ~97M years.
+ */
+ if (temp < tier->temp_min)
+ tier->temp_min = temp;
+ if (temp > tier->temp_max)
+ tier->temp_max = temp;
+ tier->temp_sum += temp * elap;
+
+ if (icl_settled < tier->icl_min)
+ tier->icl_min = icl_settled;
+ if (icl_settled > tier->icl_max)
+ tier->icl_max = icl_settled;
+ tier->icl_sum += icl_settled * elap;
+
+ if (ibatt_ma < tier->ibatt_min)
+ tier->ibatt_min = ibatt_ma;
+ if (ibatt_ma > tier->ibatt_max)
+ tier->ibatt_max = ibatt_ma;
+ tier->ibatt_sum += ibatt_ma * elap;
+
+ tier->cc_total = cc - tier->cc_in;
+ }
+
+ tier->sample_count += 1;
+}
+
+/* Only the qualified copy gets the timestamp and the exit voltage. */
+static bool batt_chg_stats_close(struct batt_drv *batt_drv,
+ char *reason,
+ bool force)
+{
+ bool publish;
+ const int vout = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ const int cc_out = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER);
+
+ /* book last period to the current tier
+ * NOTE: vbatt_idx != -1 -> temp_idx != -1
+ */
+ if (batt_drv->vbatt_idx != -1 && batt_drv->temp_idx != -1) {
+ const time_t now = get_boot_sec();
+ const time_t elap = now - batt_drv->ce_data.last_update;
+
+ batt_chg_stats_update(batt_drv,
+ batt_drv->temp_idx, batt_drv->vbatt_idx,
+ 0, 0, elap);
+ batt_drv->ce_data.last_update = now;
+ }
+
+ /* record the closing in data (and qual) */
+ batt_drv->ce_data.charging_stats.voltage_out =
+ (vout < 0) ? -1 : vout / 1000;
+ batt_drv->ce_data.charging_stats.ssoc_out =
+ ssoc_get_capacity(&batt_drv->ssoc_state);
+ batt_drv->ce_data.charging_stats.cc_out =
+ (cc_out < 0) ? -1 : cc_out / 1000;
+
+ /* TODO: add a field to ce_data to qual weird charge sessions */
+ publish = force || batt_chg_stats_qual(&batt_drv->ce_data);
+ if (publish) {
+ struct gbms_charging_event *ce_qual = &batt_drv->ce_qual;
+
+ memcpy(ce_qual, &batt_drv->ce_data, sizeof(*ce_qual));
+
+ pr_info("MSC_STAT %s: elap=%ld ssoc=%d->%d v=%d->%d c=%d->%d\n",
+ reason,
+ ce_qual->last_update - ce_qual->first_update,
+ ce_qual->charging_stats.ssoc_in,
+ ce_qual->charging_stats.ssoc_out,
+ ce_qual->charging_stats.voltage_in,
+ ce_qual->charging_stats.voltage_out,
+ ce_qual->charging_stats.cc_in,
+ ce_qual->charging_stats.cc_out);
+ }
+
+ return publish;
+}
+
+static void batt_chg_stats_pub(struct batt_drv *batt_drv,
+ char *reason,
+ bool force)
+{
+ bool publish;
+
+ mutex_lock(&batt_drv->stats_lock);
+ publish = batt_chg_stats_close(batt_drv, reason, force);
+ if (publish) {
+ ttf_stats_update(&batt_drv->ttf_stats,
+ &batt_drv->ce_qual, false);
+
+ kobject_uevent(&batt_drv->device->kobj, KOBJ_CHANGE);
+ }
+ mutex_unlock(&batt_drv->stats_lock);
+}
+
+
+static int batt_chg_stats_soc_next(const struct gbms_charging_event *ce_data,
+ int i)
+{
+ int soc_next;
+
+ if (i == GBMS_STATS_TIER_COUNT -1)
+ return ce_data->last_soc;
+
+ soc_next = ce_data->tier_stats[i + 1].soc_in >> 8;
+ if (soc_next <= 0)
+ return ce_data->last_soc;
+
+ return soc_next;
+}
+
+static int batt_chg_stats_cstr(char *buff, int size,
+ const struct gbms_charging_event *ce_data,
+ bool verbose)
+{
+ int i, j, len = 0;
+ static char *codes[] = { "n", "s", "d", "l", "v", "vo", "p", "f", "t",
+ "dl", "st", "tc", "r", "w", "rs", "n", "ny" };
+
+ if (verbose) {
+ const char *adapter_name =
+ gbms_chg_ev_adapter_s(ce_data->adapter_details.ad_type);
+
+ len += scnprintf(&buff[len], size - len, "A: %s,",
+ adapter_name);
+ }
+
+ len += scnprintf(&buff[len], size - len, "%d,%d,%d",
+ ce_data->adapter_details.ad_type,
+ ce_data->adapter_details.ad_voltage * 100,
+ ce_data->adapter_details.ad_amperage * 100);
+
+ len += scnprintf(&buff[len], size - len, "%s%hu,%hu, %hu,%hu",
+ (verbose) ? "\nS: " : ", ",
+ ce_data->charging_stats.ssoc_in,
+ ce_data->charging_stats.voltage_in,
+ ce_data->charging_stats.ssoc_out,
+ ce_data->charging_stats.voltage_out);
+
+
+ if (verbose) {
+ len += scnprintf(&buff[len], size - len, " %hu,%hu",
+ ce_data->charging_stats.cc_in,
+ ce_data->charging_stats.cc_out);
+
+ len += scnprintf(&buff[len], size - len, " %ld,%ld",
+ ce_data->first_update,
+ ce_data->last_update);
+ }
+
+ for (i = 0; i < GBMS_STATS_TIER_COUNT; i++) {
+ const int soc_next = batt_chg_stats_soc_next(ce_data, i);
+ const int soc_in = ce_data->tier_stats[i].soc_in >> 8;
+ const long elap = ce_data->tier_stats[i].time_fast +
+ ce_data->tier_stats[i].time_taper +
+ ce_data->tier_stats[i].time_other;
+
+ if (!elap)
+ continue;
+
+ len += scnprintf(&buff[len], size - len, "\n%d%c ",
+ ce_data->tier_stats[i].vtier_idx,
+ (verbose) ? ':' : ',');
+
+ len += scnprintf(&buff[len], size - len,
+ "%d.%d,%d,%d, %d,%d,%d, %d,%ld,%d, %d,%ld,%d, %d,%ld,%d",
+ soc_in,
+ ce_data->tier_stats[i].soc_in & 0xff,
+ ce_data->tier_stats[i].cc_in,
+ ce_data->tier_stats[i].temp_in,
+ ce_data->tier_stats[i].time_fast,
+ ce_data->tier_stats[i].time_taper,
+ ce_data->tier_stats[i].time_other,
+ ce_data->tier_stats[i].temp_min,
+ ce_data->tier_stats[i].temp_sum / elap,
+ ce_data->tier_stats[i].temp_max,
+ ce_data->tier_stats[i].ibatt_min,
+ ce_data->tier_stats[i].ibatt_sum / elap,
+ ce_data->tier_stats[i].ibatt_max,
+ ce_data->tier_stats[i].icl_min,
+ ce_data->tier_stats[i].icl_sum / elap,
+ ce_data->tier_stats[i].icl_max);
+
+ if (!verbose)
+ continue;
+
+ /* time spent in every multi step charging state */
+ len += scnprintf(&buff[len], size - len, "\n%d:",
+ ce_data->tier_stats[i].vtier_idx);
+
+ for (j = 0; j < MSC_STATES_COUNT; j++)
+ len += scnprintf(&buff[len], size - len, " %s=%d",
+ codes[j], ce_data->tier_stats[i].msc_elap[j]);
+
+ /* count spent in each step charging state */
+ len += scnprintf(&buff[len], size - len, "\n%d:",
+ ce_data->tier_stats[i].vtier_idx);
+
+ for (j = 0; j < MSC_STATES_COUNT; j++)
+ len += scnprintf(&buff[len], size - len, " %s=%d",
+ codes[j], ce_data->tier_stats[i].msc_cnt[j]);
+
+ if (soc_next) {
+ len += scnprintf(&buff[len], size - len, "\n");
+ len += ttf_soc_cstr(&buff[len], size - len,
+ &ce_data->soc_stats,
+ soc_in, soc_next);
+ }
+ }
+
+ len += scnprintf(&buff[len], size - len, "\n");
+
+ return len;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static void batt_res_dump_logs(struct batt_res *rstate)
+{
+ pr_info("RES: req:%d, sample:%d[%d], filt_cnt:%d, res_avg:%d\n",
+ rstate->estimate_requested, rstate->sample_accumulator,
+ rstate->sample_count, rstate->filter_count,
+ rstate->resistance_avg);
+}
+
+static void batt_res_state_set(struct batt_res *rstate, bool breq)
+{
+ rstate->estimate_requested = breq;
+ rstate->sample_accumulator = 0;
+ rstate->sample_count = 0;
+ batt_res_dump_logs(rstate);
+}
+
+static void batt_res_store_data(struct batt_res *rstate,
+ struct power_supply *fg_psy)
+{
+ int ret = 0;
+ int filter_estimate = 0;
+ int total_estimate = 0;
+ long new_estimate = 0;
+ union power_supply_propval val;
+
+ new_estimate = rstate->sample_accumulator / rstate->sample_count;
+ filter_estimate = rstate->resistance_avg * rstate->filter_count;
+
+ rstate->filter_count++;
+ if (rstate->filter_count > rstate->estimate_filter) {
+ rstate->filter_count = rstate->estimate_filter;
+ filter_estimate -= rstate->resistance_avg;
+ }
+ total_estimate = filter_estimate + new_estimate;
+ rstate->resistance_avg = total_estimate / rstate->filter_count;
+
+ /* Save to NVRam*/
+ val.intval = rstate->resistance_avg;
+ ret = power_supply_set_property(fg_psy,
+ POWER_SUPPLY_PROP_RESISTANCE_AVG,
+ &val);
+ if (ret < 0)
+ pr_err("failed to write resistance_avg\n");
+
+ val.intval = rstate->filter_count;
+ ret = power_supply_set_property(fg_psy,
+ POWER_SUPPLY_PROP_RES_FILTER_COUNT,
+ &val);
+ if (ret < 0)
+ pr_err("failed to write resistenace filt_count\n");
+
+ batt_res_dump_logs(rstate);
+}
+
+static int batt_res_load_data(struct batt_res *rstate,
+ struct power_supply *fg_psy)
+{
+ union power_supply_propval val;
+ int ret = 0;
+
+ ret = power_supply_get_property(fg_psy,
+ POWER_SUPPLY_PROP_RESISTANCE_AVG,
+ &val);
+ if (ret < 0) {
+ pr_err("failed to get resistance_avg(%d)\n", ret);
+ return ret;
+ }
+ rstate->resistance_avg = val.intval;
+
+ ret = power_supply_get_property(fg_psy,
+ POWER_SUPPLY_PROP_RES_FILTER_COUNT,
+ &val);
+ if (ret < 0) {
+ rstate->resistance_avg = 0;
+ pr_err("failed to get resistance filt_count(%d)\n", ret);
+ return ret;
+ }
+ rstate->filter_count = val.intval;
+
+ batt_res_dump_logs(rstate);
+ return 0;
+}
+
+static void batt_res_work(struct batt_drv *batt_drv)
+{
+ int temp, ret, resistance;
+ struct batt_res *rstate = &batt_drv->res_state;
+ const int ssoc_threshold = rstate->ssoc_threshold;
+ const int res_temp_low = rstate->res_temp_low;
+ const int res_temp_high = rstate->res_temp_high;
+
+ temp = GPSY_GET_INT_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_TEMP, &ret);
+ if (ret < 0 || temp < res_temp_low || temp > res_temp_high) {
+ if (ssoc_get_real(&batt_drv->ssoc_state) > ssoc_threshold) {
+ if (rstate->sample_count > 0) {
+ /* update the filter */
+ batt_res_store_data(&batt_drv->res_state,
+ batt_drv->fg_psy);
+ batt_res_state_set(rstate, false);
+ }
+ }
+ return;
+ }
+
+ resistance = GPSY_GET_INT_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_RESISTANCE, &ret);
+ if (ret < 0)
+ return;
+
+ if (ssoc_get_real(&batt_drv->ssoc_state) < ssoc_threshold) {
+ rstate->sample_accumulator += resistance / 100;
+ rstate->sample_count++;
+ batt_res_dump_logs(rstate);
+ } else {
+ if (rstate->sample_count > 0) {
+ /* update the filter here */
+ batt_res_store_data(&batt_drv->res_state,
+ batt_drv->fg_psy);
+ }
+ batt_res_state_set(rstate, false);
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* should not reset rl state */
+static inline void batt_reset_chg_drv_state(struct batt_drv *batt_drv)
+{
+ /* the wake assertion will be released on disconnect and on SW JEITA */
+ if (batt_drv->hold_taper_ws) {
+ batt_drv->hold_taper_ws = false;
+ __pm_relax(&batt_drv->taper_ws);
+ }
+
+ /* polling */
+ batt_drv->batt_fast_update_cnt = 0;
+ batt_drv->fg_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ batt_drv->chg_done = false;
+ /* algo */
+ batt_drv->temp_idx = -1;
+ batt_drv->vbatt_idx = -1;
+ batt_drv->fv_uv = -1;
+ batt_drv->cc_max = -1;
+ batt_drv->msc_update_interval = -1;
+ batt_drv->jeita_stop_charging = -1;
+ /* timers */
+ batt_drv->checked_cv_cnt = 0;
+ batt_drv->checked_ov_cnt = 0;
+ batt_drv->checked_tier_switch_cnt = 0;
+ /* stats */
+ batt_drv->msc_state = -1;
+}
+
+/*
+ * software JEITA, disable charging when outside the charge table.
+ * NOTE: ->jeita_stop_charging is either -1 (init or disable) or 0
+ * TODO: need to be able to disable (leave to HW)
+ */
+static bool msc_logic_soft_jeita(const struct batt_drv *batt_drv, int temp)
+{
+ const struct gbms_chg_profile *profile = &batt_drv->chg_profile;
+
+ if (temp < profile->temp_limits[0] ||
+ temp > profile->temp_limits[profile->temp_nb_limits - 1]) {
+ if (batt_drv->jeita_stop_charging < 0) {
+ pr_info("MSC_JEITA temp=%d off limits, do not enable charging\n",
+ temp);
+ } else if (batt_drv->jeita_stop_charging == 0) {
+ pr_info("MSC_JEITA temp=%d off limits, disabling charging\n",
+ temp);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+/* TODO: only change batt_drv->checked_ov_cnt, an */
+static int msc_logic_irdrop(struct batt_drv *batt_drv,
+ int vbatt, int ibatt, int temp_idx,
+ int *vbatt_idx, int *fv_uv,
+ int *update_interval)
+{
+ int msc_state = MSC_NONE;
+ const bool match_enable = batt_drv->chg_state.f.vchrg != 0;
+ const struct gbms_chg_profile *profile = &batt_drv->chg_profile;
+ const int vtier = profile->volt_limits[*vbatt_idx];
+ const int chg_type = batt_drv->chg_state.f.chg_type;
+ const int utv_margin = profile->cv_range_accuracy;
+ const int otv_margin = profile->cv_otv_margin;
+ const int switch_cnt = profile->cv_tier_switch_cnt;
+
+ if ((vbatt - vtier) > otv_margin) {
+ /* OVER: vbatt over vtier for more than margin */
+ const int cc_max = GBMS_CCCM_LIMITS(profile, temp_idx,
+ *vbatt_idx);
+
+ /*
+ * pullback when over tier voltage, fast poll, penalty
+ * on TAPER_RAISE and no cv debounce (so will consider
+ * switching voltage tiers if the current is right).
+ * NOTE: lowering voltage might cause a small drop in
+ * current (we should remain under next tier)
+ */
+ *fv_uv = gbms_msc_round_fv_uv(profile, vtier,
+ *fv_uv - profile->fv_uv_resolution);
+ if (*fv_uv < vtier)
+ *fv_uv = vtier;
+
+ *update_interval = profile->cv_update_interval;
+ batt_drv->checked_ov_cnt = profile->cv_tier_ov_cnt;
+ batt_drv->checked_cv_cnt = 0;
+
+ if (batt_drv->checked_tier_switch_cnt > 0 || !match_enable) {
+ /* no pullback, next tier if already counting */
+ msc_state = MSC_VSWITCH;
+ *vbatt_idx = batt_drv->vbatt_idx + 1;
+
+ pr_info("MSC_VSWITCH vt=%d vb=%d ibatt=%d\n",
+ vtier, vbatt, ibatt);
+ } else if (-ibatt == cc_max) {
+ /* pullback, double penalty if at full current */
+ msc_state = MSC_VOVER;
+ batt_drv->checked_ov_cnt *= 2;
+
+ pr_info("MSC_VOVER vt=%d vb=%d ibatt=%d fv_uv=%d->%d\n",
+ vtier, vbatt, ibatt,
+ batt_drv->fv_uv, *fv_uv);
+ } else {
+ msc_state = MSC_PULLBACK;
+ pr_info("MSC_PULLBACK vt=%d vb=%d ibatt=%d fv_uv=%d->%d\n",
+ vtier, vbatt, ibatt,
+ batt_drv->fv_uv, *fv_uv);
+ }
+
+ /*
+ * might get here after windup because algo will track the
+ * voltage drop caused from load as IRDROP.
+ * TODO: make sure that being current limited clear
+ * the taper condition.
+ */
+
+ } else if (chg_type == POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ /*
+ * FAST: usual compensation (vchrg is vqcom)
+ * NOTE: there is a race in reading from charger and
+ * data might not be consistent (b/110318684)
+ * NOTE: could add PID loop for management of thermals
+ */
+ const int vchrg = batt_drv->chg_state.f.vchrg * 1000;
+
+ msc_state = MSC_FAST;
+
+ /* invalid or 0 vchg disable IDROP compensation */
+ if (vchrg <= 0) {
+ /* could keep it steady instead */
+ *fv_uv = vtier;
+ } else if (vchrg > vbatt) {
+ *fv_uv = gbms_msc_round_fv_uv(profile, vtier,
+ vtier + (vchrg - vbatt));
+ }
+
+ /* no tier switch during fast charge */
+ if (batt_drv->checked_cv_cnt == 0)
+ batt_drv->checked_cv_cnt = 1;
+
+ pr_info("MSC_FAST vt=%d vb=%d fv_uv=%d->%d vchrg=%d cv_cnt=%d\n",
+ vtier, vbatt, batt_drv->fv_uv, *fv_uv,
+ batt_drv->chg_state.f.vchrg,
+ batt_drv->checked_cv_cnt);
+
+ } else if (chg_type == POWER_SUPPLY_CHARGE_TYPE_TRICKLE) {
+ /*
+ * Precharge: charging current/voltage are limited in
+ * hardware, no point in applying irdrop compensation.
+ * Just wait for battery voltage to raise over the
+ * precharge to fast charge threshold.
+ */
+ msc_state = MSC_TYPE;
+
+ /* no tier switching in trickle */
+ if (batt_drv->checked_cv_cnt == 0)
+ batt_drv->checked_cv_cnt = 1;
+
+ pr_info("MSC_PRE vt=%d vb=%d fv_uv=%d chg_type=%d\n",
+ vtier, vbatt, *fv_uv, chg_type);
+ } else if (chg_type != POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+ /*
+ * Not fast, taper or precharge: in *_UNKNOWN and *_NONE
+ * set checked_cv_cnt=0 and check current to avoid early
+ * termination in case of lack of headroom
+ * NOTE: this can cause early switch on low ilim
+ * TODO: check if we are really lacking hedrooom.
+ */
+ msc_state = MSC_TYPE;
+ *update_interval = profile->cv_update_interval;
+ batt_drv->checked_cv_cnt = 0;
+
+ pr_info("MSC_TYPE vt=%d vb=%d fv_uv=%d chg_type=%d\n",
+ vtier, vbatt, *fv_uv, chg_type);
+
+ } else if (batt_drv->checked_ov_cnt) {
+ /*
+ * TAPER_DLY: countdown to raise fv_uv and/or check
+ * for tier switch, will keep steady...
+ */
+ pr_info("MSC_DLY vt=%d vb=%d fv_uv=%d margin=%d cv_cnt=%d, ov_cnt=%d\n",
+ vtier, vbatt, *fv_uv, profile->cv_range_accuracy,
+ batt_drv->checked_cv_cnt,
+ batt_drv->checked_ov_cnt);
+
+ msc_state = MSC_DLY;
+ batt_drv->checked_ov_cnt -= 1;
+ *update_interval = profile->cv_update_interval;
+
+ } else if ((vtier - vbatt) < utv_margin) {
+ /* TAPER_STEADY: close enough to tier */
+
+ msc_state = MSC_STEADY;
+ *update_interval = profile->cv_update_interval;
+
+ pr_info("MSC_STEADY vt=%d vb=%d fv_uv=%d margin=%d\n",
+ vtier, vbatt, *fv_uv,
+ profile->cv_range_accuracy);
+ } else if (batt_drv->checked_tier_switch_cnt >= (switch_cnt - 1)) {
+ /*
+ * TAPER_TIERCNTING: prepare to switch to next tier
+ * so not allow to raise vfloat to prevent battery
+ * voltage over than tier
+ */
+ msc_state = MSC_TIERCNTING;
+ *update_interval = profile->cv_update_interval;
+
+ pr_info("MSC_TIERCNTING vt=%d vb=%d fv_uv=%d margin=%d\n",
+ vtier, vbatt, *fv_uv,
+ profile->cv_range_accuracy);
+ } else if (match_enable) {
+ /*
+ * TAPER_RAISE: under tier vlim, raise one click &
+ * debounce taper (see above handling of STEADY)
+ */
+ msc_state = MSC_RAISE;
+ *fv_uv = gbms_msc_round_fv_uv(profile, vtier,
+ *fv_uv + profile->fv_uv_resolution);
+ *update_interval = profile->cv_update_interval;
+
+ /* debounce next taper voltage adjustment */
+ batt_drv->checked_cv_cnt = profile->cv_debounce_cnt;
+
+ pr_info("MSC_RAISE vt=%d vb=%d fv_uv=%d->%d\n",
+ vtier, vbatt, batt_drv->fv_uv, *fv_uv);
+ } else {
+ msc_state = MSC_STEADY;
+ pr_info("MSC_DISB vt=%d vb=%d fv_uv=%d->%d\n",
+ vtier, vbatt, batt_drv->fv_uv, *fv_uv);
+ }
+
+ return msc_state;
+}
+
+static int msc_pm_hold(int msc_state)
+{
+ int pm_state = -1;
+
+ switch (msc_state) {
+ case MSC_RAISE:
+ case MSC_VOVER:
+ case MSC_PULLBACK:
+ pm_state = 1; /* __pm_stay_awake */
+ break;
+ case MSC_SEED:
+ case MSC_DSG:
+ case MSC_VSWITCH:
+ case MSC_NEXT:
+ case MSC_LAST:
+ pm_state = 0; /* pm_relax */
+ break;
+ }
+
+ return pm_state;
+}
+
+/* TODO: this function is too long and need to be split (b/117897301) */
+static int msc_logic_internal(struct batt_drv *batt_drv)
+{
+ bool sw_jeita;
+ int msc_state = MSC_NONE;
+ struct power_supply *fg_psy = batt_drv->fg_psy;
+ struct gbms_chg_profile *profile = &batt_drv->chg_profile;
+ int vbatt_idx = batt_drv->vbatt_idx, fv_uv = batt_drv->fv_uv, temp_idx;
+ int temp, ibatt, vbatt, ioerr;
+ int update_interval = MSC_DEFAULT_UPDATE_INTERVAL;
+ const time_t now = get_boot_sec();
+ time_t elap = now - batt_drv->ce_data.last_update;
+
+ temp = GPSY_GET_INT_PROP(fg_psy, POWER_SUPPLY_PROP_TEMP, &ioerr);
+ if (ioerr < 0)
+ return -EIO;
+
+ /*
+ * driver state is (was) reset when we hit the SW jeita limit.
+ * NOTE: resetting driver state will release the wake assertion
+ */
+ sw_jeita = msc_logic_soft_jeita(batt_drv, temp);
+ if (sw_jeita) {
+ /* reset batt_drv->jeita_stop_charging to -1 */
+ if (batt_drv->jeita_stop_charging == 0)
+ batt_reset_chg_drv_state(batt_drv);
+
+ return 0;
+ } else if (batt_drv->jeita_stop_charging) {
+ pr_info("MSC_JEITA temp=%d ok, enabling charging\n", temp);
+ batt_drv->jeita_stop_charging = 0;
+ }
+
+ ibatt = GPSY_GET_INT_PROP(fg_psy, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &ioerr);
+ if (ioerr < 0)
+ return -EIO;
+
+ vbatt = GPSY_GET_PROP(fg_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ if (vbatt < 0)
+ return -EIO;
+
+ /*
+ * Multi Step Charging with IRDROP compensation when vchrg is != 0
+ * vbatt_idx = batt_drv->vbatt_idx, fv_uv = batt_drv->fv_uv
+ */
+ temp_idx = gbms_msc_temp_idx(profile, temp);
+ if (temp_idx != batt_drv->temp_idx || batt_drv->fv_uv == -1 ||
+ batt_drv->vbatt_idx == -1) {
+
+ msc_state = MSC_SEED;
+
+ /* seed voltage only on connect, book 0 time */
+ if (batt_drv->vbatt_idx == -1)
+ vbatt_idx = gbms_msc_voltage_idx(profile, vbatt);
+
+ pr_info("MSC_SEED temp=%d vbatt=%d temp_idx:%d->%d, vbatt_idx:%d->%d\n",
+ temp, vbatt, batt_drv->temp_idx, temp_idx,
+ batt_drv->vbatt_idx, vbatt_idx);
+
+ /* Debounce tier switch only when not already switching */
+ if (batt_drv->checked_tier_switch_cnt == 0)
+ batt_drv->checked_cv_cnt = profile->cv_debounce_cnt;
+ } else if (ibatt > 0) {
+ /*
+ * Track battery voltage if discharging is due to system load,
+ * low ILIM or lack of headroom; stop charging work and reset
+ * batt_drv state() when discharging is due to disconnect.
+ * NOTE: POWER_SUPPLY_PROP_STATUS return *_DISCHARGING only on
+ * disconnect.
+ * NOTE: same vbat_idx will not change fv_uv
+ */
+ msc_state = MSC_DSG;
+ vbatt_idx = gbms_msc_voltage_idx(profile, vbatt);
+
+ pr_info("MSC_DSG vbatt_idx:%d->%d vbatt=%d ibatt=%d fv_uv=%d cv_cnt=%d ov_cnt=%d\n",
+ batt_drv->vbatt_idx, vbatt_idx,
+ vbatt, ibatt, fv_uv,
+ batt_drv->checked_cv_cnt,
+ batt_drv->checked_ov_cnt);
+ } else if (batt_drv->vbatt_idx == profile->volt_nb_limits - 1) {
+ const int chg_type = batt_drv->chg_state.f.chg_type;
+
+ /*
+ * will not adjust charger voltage only in the configured
+ * last tier.
+ * NOTE: might not be the "real" last tier since can I have
+ * tiers with max charge current == 0.
+ * NOTE: should I use a voltage limit instead?
+ */
+
+ if (chg_type == POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ msc_state = MSC_FAST;
+ } else if (chg_type != POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+ msc_state = MSC_TYPE;
+ } else {
+ msc_state = MSC_LAST;
+ }
+
+ pr_info("MSC_LAST vbatt=%d ibatt=%d fv_uv=%d\n",
+ vbatt, ibatt, fv_uv);
+
+ } else {
+ const int tier_idx = batt_drv->vbatt_idx;
+ const int vtier = profile->volt_limits[vbatt_idx];
+ const int switch_cnt = profile->cv_tier_switch_cnt;
+ const int cc_next_max = GBMS_CCCM_LIMITS(profile, temp_idx,
+ vbatt_idx + 1);
+
+ /* book elapsed time to previous tier & msc_irdrop_state */
+ msc_state = msc_logic_irdrop(batt_drv,
+ vbatt, ibatt, temp_idx,
+ &vbatt_idx, &fv_uv,
+ &update_interval);
+
+ if (msc_pm_hold(msc_state) == 1 && !batt_drv->hold_taper_ws) {
+ __pm_stay_awake(&batt_drv->taper_ws);
+ batt_drv->hold_taper_ws = true;
+ }
+
+ mutex_lock(&batt_drv->stats_lock);
+ batt_chg_stats_tier(&batt_drv->ce_data.tier_stats[tier_idx],
+ batt_drv->msc_irdrop_state, elap);
+ batt_drv->msc_irdrop_state = msc_state;
+ mutex_unlock(&batt_drv->stats_lock);
+
+ /*
+ * Basic multi step charging: switch to next tier when ibatt
+ * is under next tier cc_max.
+ */
+ if (batt_drv->checked_cv_cnt > 0) {
+ /* debounce period on tier switch */
+ msc_state = MSC_WAIT;
+ batt_drv->checked_cv_cnt -= 1;
+
+ pr_info("MSC_WAIT vt=%d vb=%d fv_uv=%d ibatt=%d cv_cnt=%d ov_cnt=%d t_cnt=%d\n",
+ vtier, vbatt, fv_uv, ibatt,
+ batt_drv->checked_cv_cnt,
+ batt_drv->checked_ov_cnt,
+ batt_drv->checked_tier_switch_cnt);
+
+ if (-ibatt > cc_next_max)
+ batt_drv->checked_tier_switch_cnt = 0;
+
+ } else if (-ibatt > cc_next_max) {
+ /* current over next tier, reset tier switch count */
+ msc_state = MSC_RSTC;
+ batt_drv->checked_tier_switch_cnt = 0;
+
+ pr_info("MSC_RSTC vt=%d vb=%d fv_uv=%d ibatt=%d cc_next_max=%d t_cnt=%d\n",
+ vtier, vbatt, fv_uv, ibatt, cc_next_max,
+ batt_drv->checked_tier_switch_cnt);
+ } else if (batt_drv->checked_tier_switch_cnt >= switch_cnt) {
+ /* next tier, fv_uv detemined at MSC_SET */
+ msc_state = MSC_NEXT;
+ vbatt_idx = batt_drv->vbatt_idx + 1;
+
+ pr_info("MSC_NEXT tier vb=%d ibatt=%d vbatt_idx=%d->%d\n",
+ vbatt, ibatt, batt_drv->vbatt_idx, vbatt_idx);
+ } else {
+ /* current under next tier, +1 on tier switch count */
+ msc_state = MSC_NYET;
+ batt_drv->checked_tier_switch_cnt++;
+
+ pr_info("MSC_NYET ibatt=%d cc_next_max=%d t_cnt=%d\n",
+ ibatt, cc_next_max,
+ batt_drv->checked_tier_switch_cnt);
+ }
+
+ }
+
+ if (msc_pm_hold(msc_state) == 0 && batt_drv->hold_taper_ws) {
+ batt_drv->hold_taper_ws = false;
+ __pm_relax(&batt_drv->taper_ws);
+ }
+
+ /* need a new fv_uv only on a new voltage tier. */
+ if (vbatt_idx != batt_drv->vbatt_idx) {
+ fv_uv = profile->volt_limits[vbatt_idx];
+ batt_drv->checked_tier_switch_cnt = 0;
+ batt_drv->checked_ov_cnt = 0;
+ }
+
+ /* book elapsed time to previous tier & msc_state
+ * NOTE: temp_idx != -1 but batt_drv->msc_state could be -1
+ */
+ mutex_lock(&batt_drv->stats_lock);
+ if (vbatt_idx != -1 && vbatt_idx < GBMS_STATS_TIER_COUNT) {
+ int tier_idx = batt_drv->vbatt_idx;
+
+ /* this is the seed after the connect */
+ if (batt_drv->vbatt_idx == -1) {
+ tier_idx = vbatt_idx;
+ elap = 0;
+ }
+
+ batt_chg_stats_update(batt_drv, temp_idx, tier_idx,
+ ibatt / 1000, temp,
+ elap);
+
+ }
+
+ batt_drv->msc_state = msc_state;
+ batt_drv->ce_data.last_update = now;
+ mutex_unlock(&batt_drv->stats_lock);
+
+ pr_debug("MSC_LOGIC cv_cnt=%d ov_cnt=%d temp_idx:%d->%d, vbatt_idx:%d->%d, fv=%d->%d, ui=%d->%d\n",
+ batt_drv->checked_cv_cnt, batt_drv->checked_ov_cnt,
+ batt_drv->temp_idx, temp_idx, batt_drv->vbatt_idx,
+ vbatt_idx, batt_drv->fv_uv, fv_uv, batt_drv->cc_max,
+ update_interval);
+
+ /* next update */
+ batt_drv->msc_update_interval = update_interval;
+ batt_drv->vbatt_idx = vbatt_idx;
+ batt_drv->temp_idx = temp_idx;
+ batt_drv->cc_max = GBMS_CCCM_LIMITS(profile, temp_idx, vbatt_idx);
+ batt_drv->fv_uv = fv_uv;
+
+ return 0;
+}
+
+/* called holding chg_lock */
+static int msc_logic(struct batt_drv *batt_drv)
+{
+ int err = 0;
+ bool changed = false;
+ union gbms_charger_state *chg_state = &batt_drv->chg_state;
+
+ if (!batt_drv->chg_profile.cccm_limits)
+ return -EINVAL;
+
+ __pm_stay_awake(&batt_drv->msc_ws);
+
+ pr_info("MSC_DIN chg_state=%lx f=0x%x chg_s=%s chg_t=%s vchg=%d icl=%d\n",
+ (unsigned long)chg_state->v,
+ chg_state->f.flags,
+ gbms_chg_status_s(chg_state->f.chg_status),
+ gbms_chg_type_s(chg_state->f.chg_type),
+ chg_state->f.vchrg,
+ chg_state->f.icl);
+
+ if ((batt_drv->chg_state.f.flags & GBMS_CS_FLAG_BUCK_EN) == 0) {
+
+ if (batt_drv->ssoc_state.buck_enabled == 0)
+ goto msc_logic_exit;
+
+ /* here on: disconnect */
+ batt_chg_stats_pub(batt_drv, "disconnect", false);
+ batt_res_state_set(&batt_drv->res_state, false);
+
+ /* change curve before changing the state */
+ ssoc_change_curve(&batt_drv->ssoc_state, SSOC_UIC_TYPE_DSG);
+ batt_reset_chg_drv_state(batt_drv);
+ batt_update_cycle_count(batt_drv);
+ batt_rl_reset(batt_drv);
+
+ /* this will trigger another capacity learning. */
+ err = GPSY_SET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_BATT_CE_CTRL,
+ false);
+ if (err < 0)
+ pr_err("Cannot set the BATT_CE_CTRL.\n");
+
+ batt_drv->ssoc_state.buck_enabled = 0;
+ changed = true;
+
+ goto msc_logic_done;
+ }
+
+ /* here when connected to power supply */
+ if (batt_drv->ssoc_state.buck_enabled <= 0) {
+
+ ssoc_change_curve(&batt_drv->ssoc_state, SSOC_UIC_TYPE_CHG);
+
+ if (batt_drv->res_state.estimate_filter)
+ batt_res_state_set(&batt_drv->res_state, true);
+
+ batt_chg_stats_start(batt_drv);
+ err = GPSY_SET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_BATT_CE_CTRL,
+ true);
+ if (err < 0)
+ pr_err("Cannot set the BATT_CE_CTRL.\n");
+
+ /* released in battery_work() */
+ __pm_stay_awake(&batt_drv->poll_ws);
+ batt_drv->batt_fast_update_cnt = BATT_WORK_FAST_RETRY_CNT;
+ mod_delayed_work(system_wq, &batt_drv->batt_work,
+ BATT_WORK_FAST_RETRY_MS);
+
+ batt_drv->ssoc_state.buck_enabled = 1;
+ changed = true;
+ }
+
+ /*
+ * enter RL in DISCHARGE on charger DONE and enter RL in RECHARGE on
+ * battery FULL (i.e. SSOC==100%). charger DONE forces the discharge
+ * curve while RECHARGE will not modify the current curve.
+ */
+ if ((batt_drv->chg_state.f.flags & GBMS_CS_FLAG_DONE) != 0) {
+ changed = batt_rl_enter(&batt_drv->ssoc_state,
+ BATT_RL_STATUS_DISCHARGE);
+ batt_drv->chg_done = true;
+ } else if (batt_drv->batt_full) {
+ changed = batt_rl_enter(&batt_drv->ssoc_state,
+ BATT_RL_STATUS_RECHARGE);
+ if (changed)
+ batt_chg_stats_pub(batt_drv, "100%", false);
+ }
+
+ err = msc_logic_internal(batt_drv);
+ if (err < 0) {
+ /* NOTE: google charger will poll again. */
+ batt_drv->msc_update_interval = -1;
+
+ pr_err("MSC_DOUT ERROR=%d fv_uv=%d cc_max=%d update_interval=%d\n",
+ err, batt_drv->fv_uv, batt_drv->cc_max,
+ batt_drv->msc_update_interval);
+
+ goto msc_logic_exit;
+ }
+
+msc_logic_done:
+ /* set ->cc_max = 0 on RL and SW_JEITA, no vote on interval in RL_DSG */
+ if (batt_drv->ssoc_state.rl_status == BATT_RL_STATUS_DISCHARGE) {
+ batt_drv->msc_update_interval = -1;
+ batt_drv->cc_max = 0;
+ }
+ if (batt_drv->jeita_stop_charging)
+ batt_drv->cc_max = 0;
+
+ pr_info("%s msc_state=%d cv_cnt=%d ov_cnt=%d temp_idx:%d, vbatt_idx:%d fv_uv=%d cc_max=%d update_interval=%d\n",
+ (batt_drv->disable_votes) ? "MSC_DOUT" : "MSC_VOTE",
+ batt_drv->msc_state,
+ batt_drv->checked_cv_cnt, batt_drv->checked_ov_cnt,
+ batt_drv->temp_idx, batt_drv->vbatt_idx,
+ batt_drv->fv_uv, batt_drv->cc_max,
+ batt_drv->msc_update_interval);
+
+ /*
+ * google_charger has voted(<=0) on msc_interval_votable and the
+ * votes on fcc and fv_uv will not be applied until google_charger
+ * votes a non-zero value.
+ *
+ * SW_JEITA: ->jeita_stop_charging != 0
+ * . ->msc_update_interval = -1 , fv_uv = -1 and ->cc_max = 0
+ * . vote(0) on ->fcc_votable with SW_JEITA_VOTER
+ * BATT_RL: rl_status == BATT_RL_STATUS_DISCHARGE
+ * . ->msc_update_interval = -1 , fv_uv = -1 and ->cc_max = 0
+ * . vote(0) on ->fcc_votable with SW_JEITA_VOTER
+ *
+ * Votes for MSC_LOGIC_VOTER will be all disabled.
+ */
+ if (!batt_drv->fv_votable)
+ batt_drv->fv_votable = find_votable(VOTABLE_MSC_FV);
+ if (batt_drv->fv_votable)
+ vote(batt_drv->fv_votable, MSC_LOGIC_VOTER,
+ !batt_drv->disable_votes && (batt_drv->fv_uv != -1),
+ batt_drv->fv_uv);
+
+ if (!batt_drv->fcc_votable)
+ batt_drv->fcc_votable = find_votable(VOTABLE_MSC_FCC);
+ if (batt_drv->fcc_votable) {
+ enum batt_rl_status rl_status = batt_drv->ssoc_state.rl_status;
+
+ /* while in RL => ->cc_max != -1 && ->fv_uv != -1 */
+ vote(batt_drv->fcc_votable, RL_STATE_VOTER,
+ !batt_drv->disable_votes &&
+ (rl_status == BATT_RL_STATUS_DISCHARGE),
+ 0);
+
+ /* jeita_stop_charging != 0 => ->fv_uv = -1 && cc_max == -1 */
+ vote(batt_drv->fcc_votable, SW_JEITA_VOTER,
+ !batt_drv->disable_votes &&
+ (batt_drv->jeita_stop_charging != 0),
+ 0);
+
+ vote(batt_drv->fcc_votable, MSC_LOGIC_VOTER,
+ !batt_drv->disable_votes && (batt_drv->cc_max != -1),
+ batt_drv->cc_max);
+ }
+
+ if (!batt_drv->msc_interval_votable)
+ batt_drv->msc_interval_votable =
+ find_votable(VOTABLE_MSC_INTERVAL);
+ if (batt_drv->msc_interval_votable)
+ vote(batt_drv->msc_interval_votable, MSC_LOGIC_VOTER,
+ !batt_drv->disable_votes &&
+ (batt_drv->msc_update_interval != -1),
+ batt_drv->msc_update_interval);
+
+msc_logic_exit:
+
+ if (changed) {
+ dump_ssoc_state(&batt_drv->ssoc_state, batt_drv->ssoc_log);
+ if (batt_drv->psy)
+ power_supply_changed(batt_drv->psy);
+ }
+
+ __pm_relax(&batt_drv->msc_ws);
+ return err;
+}
+
+/* charge profile not in battery */
+static int batt_init_chg_profile(struct batt_drv *batt_drv)
+{
+ struct device_node *node = batt_drv->device->of_node;
+ struct gbms_chg_profile *profile = &batt_drv->chg_profile;
+ int ret = 0;
+
+ /* handle retry */
+ if (!profile->cccm_limits) {
+ ret = gbms_init_chg_profile(profile, node);
+ if (ret < 0)
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(node, "google,chg-battery-capacity",
+ &batt_drv->battery_capacity);
+ if (ret < 0)
+ pr_warn("read chg-battery-capacity from gauge\n");
+
+ /*
+ * use battery FULL design when is not specified in DT. When battery is
+ * not present use default capacity from DT (if present) or disable
+ * charging altogether.
+ */
+ if (batt_drv->battery_capacity == 0) {
+ u32 fc = 0;
+ struct power_supply *fg_psy = batt_drv->fg_psy;
+
+ if (batt_drv->batt_present) {
+ fc = GPSY_GET_PROP(fg_psy,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN);
+ if (fc == -EAGAIN)
+ return -EPROBE_DEFER;
+ if (fc > 0) {
+ pr_info("successfully read charging profile:\n");
+ /* convert uA to mAh*/
+ batt_drv->battery_capacity = fc / 1000;
+ }
+
+ }
+
+ if (batt_drv->battery_capacity == 0) {
+ struct device_node *node = batt_drv->device->of_node;
+
+ ret = of_property_read_u32(node,
+ "google,chg-battery-default-capacity",
+ &batt_drv->battery_capacity);
+ if (ret < 0)
+ pr_warn("battery not present, no default capacity, zero charge table\n");
+ else
+ pr_warn("battery not present, using default capacity:\n");
+ }
+ }
+
+ /* NOTE: with NG charger tolerance is applied from "charger" */
+ gbms_init_chg_table(&batt_drv->chg_profile, batt_drv->battery_capacity);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* call holding mutex_unlock(&ccd->lock); */
+static int batt_cycle_count_store(struct gbatt_ccbin_data *ccd)
+{
+ int ret;
+
+ ret = gbms_storage_write(GBMS_TAG_BCNT, ccd->count, sizeof(ccd->count));
+ if (ret < 0 && ret != -ENOENT) {
+ pr_err("failed to set bin_counts ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* call holding mutex_unlock(&ccd->lock); */
+static int batt_cycle_count_load(struct gbatt_ccbin_data *ccd)
+{
+ int ret;
+
+ ret = gbms_storage_read(GBMS_TAG_BCNT, ccd->count, sizeof(ccd->count));
+ if (ret < 0 && ret != -ENOENT) {
+ pr_err("failed to get bin_counts ret=%d\n", ret);
+ return ret;
+ }
+
+ ccd->prev_soc = -1;
+ return 0;
+}
+
+/* update only when SSOC is increasing, not need to check charging */
+static void batt_cycle_count_update(struct batt_drv *batt_drv, int soc)
+{
+ struct gbatt_ccbin_data *ccd = &batt_drv->cc_data;
+
+ if (soc < 0 || soc > 100)
+ return;
+
+ mutex_lock(&ccd->lock);
+
+ if (ccd->prev_soc != -1 && soc > ccd->prev_soc) {
+ int bucket, cnt;
+
+ for (cnt = soc ; cnt > ccd->prev_soc ; cnt--) {
+ /* cnt decremented by 1 for bucket symmetry */
+ bucket = (cnt - 1) * GBMS_CCBIN_BUCKET_COUNT / 100;
+ ccd->count[bucket]++;
+ }
+
+ /* NOTE: could store on FULL or disconnect instead */
+ (void)batt_cycle_count_store(ccd);
+ }
+
+ ccd->prev_soc = soc;
+
+ mutex_unlock(&ccd->lock);
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define BATTERY_DEBUG_ATTRIBUTE(name, fn_read, fn_write) \
+static const struct file_operations name = { \
+ .open = simple_open, \
+ .llseek = no_llseek, \
+ .read = fn_read, \
+ .write = fn_write, \
+}
+
+static ssize_t batt_cycle_count_set_bins(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ char buf[GBMS_CCBIN_CSTR_SIZE];
+ int ret;
+
+ ret = simple_write_to_buffer(buf, sizeof(buf), ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ mutex_lock(&batt_drv->cc_data.lock);
+
+ ret = gbms_cycle_count_sscan(batt_drv->cc_data.count, buf);
+ if (ret == 0) {
+ ret = batt_cycle_count_store(&batt_drv->cc_data);
+ if (ret < 0)
+ pr_err("cannot store bin count ret=%d\n", ret);
+ }
+
+ if (ret == 0)
+ ret = count;
+
+ mutex_unlock(&batt_drv->cc_data.lock);
+
+ return ret;
+}
+
+BATTERY_DEBUG_ATTRIBUTE(cycle_count_bins_fops,
+ NULL, batt_cycle_count_set_bins);
+
+
+static int cycle_count_bins_store(void *data, u64 val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+ int ret;
+
+ mutex_lock(&batt_drv->cc_data.lock);
+ ret = batt_cycle_count_store(&batt_drv->cc_data);
+ if (ret < 0)
+ pr_err("cannot store bin count ret=%d\n", ret);
+ mutex_unlock(&batt_drv->cc_data.lock);
+
+ return ret;
+}
+
+static int cycle_count_bins_reload(void *data, u64 *val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+ int ret;
+
+ mutex_lock(&batt_drv->cc_data.lock);
+ ret = batt_cycle_count_load(&batt_drv->cc_data);
+ if (ret < 0)
+ pr_err("cannot restore bin count ret=%d\n", ret);
+ mutex_unlock(&batt_drv->cc_data.lock);
+ *val = ret;
+
+ return ret;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(cycle_count_bins_sync_fops,
+ cycle_count_bins_reload,
+ cycle_count_bins_store, "%llu\n");
+
+
+static int debug_get_ssoc_gdf(void *data, u64 *val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+ *val = batt_drv->ssoc_state.ssoc_gdf;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_ssoc_gdf_fops, debug_get_ssoc_gdf, NULL, "%u\n");
+
+
+static int debug_get_ssoc_uic(void *data, u64 *val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+ *val = batt_drv->ssoc_state.ssoc_uic;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_ssoc_uic_fops, debug_get_ssoc_uic, NULL, "%u\n");
+
+static int debug_get_ssoc_rls(void *data, u64 *val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+
+ mutex_lock(&batt_drv->chg_lock);
+ *val = batt_drv->ssoc_state.rl_status;
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return 0;
+}
+
+static int debug_set_ssoc_rls(void *data, u64 val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+
+ if (val < 0 || val > 2)
+ return -EINVAL;
+
+ mutex_lock(&batt_drv->chg_lock);
+ batt_drv->ssoc_state.rl_status = val;
+ if (!batt_drv->fcc_votable)
+ batt_drv->fcc_votable = find_votable(VOTABLE_MSC_FCC);
+ if (batt_drv->fcc_votable)
+ vote(batt_drv->fcc_votable, RL_STATE_VOTER,
+ batt_drv->ssoc_state.rl_status ==
+ BATT_RL_STATUS_DISCHARGE,
+ 0);
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_ssoc_rls_fops,
+ debug_get_ssoc_rls, debug_set_ssoc_rls, "%u\n");
+
+static int debug_force_psy_update(void *data, u64 val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)data;
+
+ if (!batt_drv->psy)
+ return -EINVAL;
+
+ power_supply_changed(batt_drv->psy);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_force_psy_update_fops,
+ NULL, debug_force_psy_update, "%u\n");
+
+
+static ssize_t debug_get_ssoc_uicurve(struct file *filp,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ char tmp[UICURVE_BUF_SZ] = { 0 };
+
+ mutex_lock(&batt_drv->chg_lock);
+ ssoc_uicurve_cstr(tmp, sizeof(tmp), batt_drv->ssoc_state.ssoc_curve);
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return simple_read_from_buffer(buf, count, ppos, tmp, strlen(tmp));
+}
+
+static ssize_t debug_set_ssoc_uicurve(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ int ret, curve_type;
+ char buf[8];
+
+ ret = simple_write_to_buffer(buf, sizeof(buf), ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ mutex_lock(&batt_drv->chg_lock);
+
+ curve_type = (int)simple_strtoull(buf, NULL, 10);
+ if (curve_type >= -1 && curve_type <= 1)
+ ssoc_change_curve(&batt_drv->ssoc_state, curve_type);
+ else
+ ret = -EINVAL;
+
+ mutex_unlock(&batt_drv->chg_lock);
+
+ if (ret == 0)
+ ret = count;
+
+ return 0;
+}
+
+BATTERY_DEBUG_ATTRIBUTE(debug_ssoc_uicurve_cstr_fops,
+ debug_get_ssoc_uicurve,
+ debug_set_ssoc_uicurve);
+
+static ssize_t debug_get_fake_temp(struct file *filp,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ char tmp[8];
+
+ mutex_lock(&batt_drv->chg_lock);
+ scnprintf(tmp, sizeof(tmp), "%d\n", batt_drv->fake_temp);
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return simple_read_from_buffer(buf, count, ppos, tmp, strlen(tmp));
+}
+
+static ssize_t debug_set_fake_temp(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ int ret = 0, val;
+ char buf[8];
+
+ ret = simple_write_to_buffer(buf, sizeof(buf), ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ buf[ret] = '\0';
+ ret = kstrtoint(buf, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&batt_drv->chg_lock);
+ batt_drv->fake_temp = val;
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return count;
+}
+
+BATTERY_DEBUG_ATTRIBUTE(debug_fake_temp_fops,
+ debug_get_fake_temp, debug_set_fake_temp);
+
+
+static enum batt_paired_state
+batt_reset_pairing_state(const struct batt_drv *batt_drv)
+{
+ char dev_info[GBMS_DINF_LEN];
+ int ret = 0;
+
+ memset(dev_info, 0xff, sizeof(dev_info));
+ ret = gbms_storage_write(GBMS_TAG_DINF, dev_info, sizeof(dev_info));
+ if (ret < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static ssize_t debug_set_pairing_state(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ int ret = 0, val;
+ char buf[8];
+
+ ret = simple_write_to_buffer(buf, sizeof(buf), ppos, user_buf, count);
+ if (ret <= 0)
+ return ret;
+
+ buf[ret] = '\0';
+ ret = kstrtoint(buf, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&batt_drv->chg_lock);
+
+ if (val == BATT_PAIRING_ENABLED) {
+ batt_drv->pairing_state = BATT_PAIRING_ENABLED;
+ mod_delayed_work(system_wq, &batt_drv->batt_work, 0);
+ } else if (val == BATT_PAIRING_RESET) {
+
+ /* send a paring enable to re-pair OR reboot */
+ ret = batt_reset_pairing_state(batt_drv);
+ if (ret == 0)
+ batt_drv->pairing_state = BATT_PAIRING_DISABLED;
+ else
+ count = -EIO;
+ } else {
+ count = -EINVAL;
+ }
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return count;
+}
+
+BATTERY_DEBUG_ATTRIBUTE(debug_pairing_fops, 0, debug_set_pairing_state);
+
+/* TODO: add write to stop/start collection, erase history etc. */
+static ssize_t debug_get_blf_state(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)filp->private_data;
+ char tmp[8];
+
+ mutex_lock(&batt_drv->chg_lock);
+ scnprintf(tmp, sizeof(tmp), "%d\n", batt_drv->blf_state);
+ mutex_unlock(&batt_drv->chg_lock);
+
+ return simple_read_from_buffer(buf, count, ppos, tmp, strlen(tmp));
+}
+BATTERY_DEBUG_ATTRIBUTE(debug_blf_state_fops, debug_get_blf_state, 0);
+
+/* TODO: add writes to restart pairing (i.e. provide key) */
+static ssize_t batt_pairing_state_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv = power_supply_get_drvdata(psy);
+ int len;
+
+ mutex_lock(&batt_drv->chg_lock);
+ len = scnprintf(buf, PAGE_SIZE, "%d\n", batt_drv->pairing_state);
+ mutex_unlock(&batt_drv->chg_lock);
+ return len;
+}
+
+static const DEVICE_ATTR(pairing_state, 0444, batt_pairing_state_show, NULL);
+
+
+static ssize_t batt_ctl_chg_stats_actual(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 batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+
+ if (count < 1)
+ return -ENODATA;
+
+ switch (buf[0]) {
+ case 'p': /* publish data to qual */
+ case 'P': /* force publish data to qual */
+ batt_chg_stats_pub(batt_drv, "debug cmd", buf[0] == 'P');
+ break;
+ default:
+ count = -EINVAL;
+ break;
+ }
+
+ return count;
+}
+
+static ssize_t batt_show_chg_stats_actual(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ int len;
+
+ mutex_lock(&batt_drv->stats_lock);
+ len = batt_chg_stats_cstr(buf, PAGE_SIZE, &batt_drv->ce_data, false);
+ mutex_unlock(&batt_drv->stats_lock);
+
+ return len;
+}
+
+static const DEVICE_ATTR(charge_stats_actual, 0664,
+ batt_show_chg_stats_actual,
+ batt_ctl_chg_stats_actual);
+
+static ssize_t batt_ctl_chg_stats(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 batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+
+ if (count < 1)
+ return -ENODATA;
+
+ mutex_lock(&batt_drv->stats_lock);
+ switch (buf[0]) {
+ case 0:
+ case '0': /* invalidate current qual */
+ batt_chg_stats_init(&batt_drv->ce_qual, &batt_drv->chg_profile);
+ break;
+ }
+ mutex_unlock(&batt_drv->stats_lock);
+
+ return count;
+}
+
+static ssize_t batt_show_chg_stats(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ int len = -ENODATA;
+
+ mutex_lock(&batt_drv->stats_lock);
+
+ if (batt_drv->ce_qual.last_update - batt_drv->ce_qual.first_update)
+ len = batt_chg_stats_cstr(buf,
+ PAGE_SIZE,
+ &batt_drv->ce_qual, false);
+
+ mutex_unlock(&batt_drv->stats_lock);
+
+ return len;
+}
+
+static const DEVICE_ATTR(charge_stats, 0664, batt_show_chg_stats,
+ batt_ctl_chg_stats);
+
+static ssize_t batt_show_chg_details(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ const bool qual_valid = (batt_drv->ce_qual.last_update -
+ batt_drv->ce_qual.first_update) != 0;
+ int len = 0;
+
+ mutex_lock(&batt_drv->stats_lock);
+
+ len += batt_chg_stats_cstr(&buf[len], PAGE_SIZE - len,
+ &batt_drv->ce_data, true);
+ if (qual_valid)
+ len += batt_chg_stats_cstr(&buf[len], PAGE_SIZE - len,
+ &batt_drv->ce_qual, true);
+
+ mutex_unlock(&batt_drv->stats_lock);
+
+ return len;
+}
+
+static const DEVICE_ATTR(charge_details, 0444, batt_show_chg_details,
+ NULL);
+
+/* tier and soc details */
+static ssize_t batt_show_ttf_details(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv = (struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ struct batt_ttf_stats *ttf_stats;
+ int i, len = 0;
+
+ if (!batt_drv->ssoc_state.buck_enabled)
+ return -ENODATA;
+
+ ttf_stats = kzalloc(sizeof(*ttf_stats), GFP_KERNEL);
+ if (!ttf_stats)
+ return -ENOMEM;
+
+ mutex_lock(&batt_drv->stats_lock);
+ /* update a private copy of ttf stats */
+ ttf_stats_update(ttf_stats_dup(ttf_stats, &batt_drv->ttf_stats),
+ &batt_drv->ce_data, true);
+ mutex_unlock(&batt_drv->stats_lock);
+
+ /* interleave tier with SOC data */
+ for (i = 0; i < GBMS_STATS_TIER_COUNT; i++) {
+ int next_soc_in;
+
+ len += scnprintf(&buf[len], PAGE_SIZE - len, "%d: ", i);
+ len += ttf_tier_cstr(&buf[len], PAGE_SIZE - len,
+ &ttf_stats->tier_stats[i]);
+ len += scnprintf(&buf[len], PAGE_SIZE - len, "\n");
+
+ /* continue only first */
+ if (ttf_stats->tier_stats[i].avg_time == 0)
+ continue;
+
+ if (i == GBMS_STATS_TIER_COUNT - 1) {
+ next_soc_in = -1;
+ } else {
+ next_soc_in = ttf_stats->tier_stats[i + 1].soc_in >> 8;
+ if (next_soc_in == 0)
+ next_soc_in = -1;
+ }
+
+ if (next_soc_in == -1)
+ next_soc_in = batt_drv->ce_data.last_soc - 1;
+
+ len += ttf_soc_cstr(&buf[len], PAGE_SIZE - len,
+ &ttf_stats->soc_stats,
+ ttf_stats->tier_stats[i].soc_in >> 8,
+ next_soc_in);
+ }
+
+ kfree(ttf_stats);
+
+ return len;
+}
+
+static const DEVICE_ATTR(ttf_details, 0444, batt_show_ttf_details,
+ NULL);
+
+/* house stats */
+static ssize_t batt_show_ttf_stats(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ const int verbose = true;
+ int i, len = 0;
+
+ mutex_lock(&batt_drv->stats_lock);
+
+ for (i = 0; i < GBMS_STATS_TIER_COUNT; i++)
+ len += ttf_tier_cstr(&buf[len], PAGE_SIZE,
+ &batt_drv->ttf_stats.tier_stats[i]);
+
+ len += scnprintf(&buf[len], PAGE_SIZE - len, "\n");
+
+ if (verbose)
+ len += ttf_soc_cstr(&buf[len], PAGE_SIZE - len,
+ &batt_drv->ttf_stats.soc_stats,
+ 0, 99);
+
+ mutex_unlock(&batt_drv->stats_lock);
+
+ return len;
+}
+
+/* userspace restore the TTF data with this */
+static ssize_t batt_ctl_ttf_stats(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int res;
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ struct batt_drv *batt_drv =(struct batt_drv *)
+ power_supply_get_drvdata(psy);
+
+ if (count < 1)
+ return -ENODATA;
+ if (!batt_drv->ssoc_state.buck_enabled)
+ return -ENODATA;
+
+ mutex_lock(&batt_drv->stats_lock);
+ switch (buf[0]) {
+ case 'u':
+ case 'U': /* force update */
+ ttf_stats_update(&batt_drv->ttf_stats, &batt_drv->ce_data,
+ (buf[0] == 'U'));
+ break;
+ default:
+ /* TODO: userspace restore of the data */
+ res = ttf_stats_sscan(&batt_drv->ttf_stats, buf, count);
+ if (res < 0)
+ count = res;
+ break;
+ }
+ mutex_unlock(&batt_drv->stats_lock);
+
+ return count;
+}
+
+static const DEVICE_ATTR(ttf_stats, 0664, batt_show_ttf_stats,
+ batt_ctl_ttf_stats);
+
+static int batt_init_fs(struct batt_drv *batt_drv)
+{
+ struct dentry *de = NULL;
+ int ret;
+
+ /* stats */
+ ret = device_create_file(&batt_drv->psy->dev, &dev_attr_charge_stats);
+ if (ret)
+ dev_err(&batt_drv->psy->dev,
+ "Failed to create charge_stats\n");
+
+ ret = device_create_file(&batt_drv->psy->dev,
+ &dev_attr_charge_stats_actual);
+ if (ret)
+ dev_err(&batt_drv->psy->dev,
+ "Failed to create charge_stats_actual\n");
+
+ ret = device_create_file(&batt_drv->psy->dev,
+ &dev_attr_charge_details);
+ if (ret)
+ dev_err(&batt_drv->psy->dev,
+ "Failed to create charge_details\n");
+
+ /* time to full */
+ ret = device_create_file(&batt_drv->psy->dev, &dev_attr_ttf_stats);
+ if (ret)
+ dev_err(&batt_drv->psy->dev,
+ "Failed to create ttf_stats\n");
+
+ ret = device_create_file(&batt_drv->psy->dev, &dev_attr_ttf_details);
+ if (ret)
+ dev_err(&batt_drv->psy->dev,
+ "Failed to create ttf_details\n");
+
+ ret = device_create_file(&batt_drv->psy->dev,
+ &dev_attr_pairing_state);
+ if (ret)
+ dev_err(&batt_drv->psy->dev,
+ "Failed to create pairing_state\n");
+
+ de = debugfs_create_dir("google_battery", 0);
+ if (IS_ERR_OR_NULL(de))
+ return 0;
+
+ debugfs_create_file("cycle_count_bins", 0400, de,
+ batt_drv, &cycle_count_bins_fops);
+ debugfs_create_file("cycle_count_sync", 0600, de,
+ batt_drv, &cycle_count_bins_sync_fops);
+ debugfs_create_file("ssoc_gdf", 0600, de,
+ batt_drv, &debug_ssoc_gdf_fops);
+ debugfs_create_file("ssoc_uic", 0600, de,
+ batt_drv, &debug_ssoc_uic_fops);
+ debugfs_create_file("ssoc_rls", 0400, de,
+ batt_drv, &debug_ssoc_rls_fops);
+ debugfs_create_file("ssoc_uicurve", 0600, de,
+ batt_drv, &debug_ssoc_uicurve_cstr_fops);
+ debugfs_create_file("force_psy_update", 0400, de,
+ batt_drv, &debug_force_psy_update_fops);
+ debugfs_create_file("pairing_state", 0200, de,
+ batt_drv, &debug_pairing_fops);
+ debugfs_create_file("blf_state", 0400, de,
+ batt_drv, &debug_blf_state_fops);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* could also use battery temperature, age */
+static bool gbatt_check_dead_battery(const struct batt_drv *batt_drv)
+{
+ return ssoc_get_capacity(&batt_drv->ssoc_state) == 0;
+}
+
+#define SSOC_LEVEL_FULL SSOC_SPOOF
+#define SSOC_LEVEL_HIGH 80
+#define SSOC_LEVEL_NORMAL 30
+#define SSOC_LEVEL_LOW 0
+
+/*
+ * could also use battery temperature, age.
+ * NOTE: this implementation looks at the SOC% but it might be looking to
+ * other quantities or flags.
+ * NOTE: CRITICAL_LEVEL implies BATTERY_DEAD but BATTERY_DEAD doesn't imply
+ * CRITICAL.
+ */
+static int gbatt_get_capacity_level(struct batt_ssoc_state *ssoc_state,
+ int fg_status)
+{
+ const int ssoc = ssoc_get_capacity(ssoc_state);
+ int capacity_level;
+
+ if (ssoc >= SSOC_LEVEL_FULL) {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ } else if (ssoc > SSOC_LEVEL_HIGH) {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+ } else if (ssoc > SSOC_LEVEL_NORMAL) {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ } else if (ssoc > SSOC_LEVEL_LOW) {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ } else if (ssoc_state->buck_enabled == 0) {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ } else if (ssoc_state->buck_enabled == -1) {
+ /* only at startup, this should not happen */
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ } else if (fg_status == POWER_SUPPLY_STATUS_DISCHARGING ||
+ fg_status == POWER_SUPPLY_STATUS_UNKNOWN) {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ } else {
+ capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ }
+
+ return capacity_level;
+}
+
+static int gbatt_get_temp(struct batt_drv *batt_drv, int *temp)
+{
+ int err = 0;
+ union power_supply_propval val;
+
+ if (batt_drv->fake_temp) {
+ *temp = batt_drv->fake_temp;
+ } else if (!batt_drv->fg_psy) {
+ err = -EINVAL;
+ } else {
+ err = power_supply_get_property(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_TEMP, &val);
+ if (err == 0)
+ *temp = val.intval;
+ }
+
+ return err;
+}
+
+void log_ttf_estimate(const char *label, int ssoc, struct batt_drv *batt_drv)
+{
+ int cc, err;
+ time_t res = 0;
+
+
+ err = batt_ttf_estimate(&res, batt_drv);
+ if (err < 0) {
+ logbuffer_log(batt_drv->ttf_log, "%s ssoc=%d time=%ld err=%d",
+ (label) ? label : "", ssoc, get_boot_sec(), err);
+ return;
+ }
+
+ cc = GPSY_GET_PROP(batt_drv->fg_psy, POWER_SUPPLY_PROP_CHARGE_COUNTER);
+ logbuffer_log(batt_drv->ttf_log,
+ "%s ssoc=%d cc=%d time=%ld %d:%d:%d (est=%ld)",
+ (label) ? label : "", ssoc, cc / 1000, get_boot_sec(),
+ res / 3600, (res % 3600) / 60, (res % 3600) % 60,
+ res);
+}
+
+static int batt_do_sha256(const u8 *data, unsigned int len, u8 *result)
+{
+ struct crypto_shash *tfm;
+ struct shash_desc *shash;
+ int size, ret = 0;
+
+ tfm = crypto_alloc_shash("sha256", 0, 0);
+ if (IS_ERR(tfm)) {
+ pr_err("Error SHA-256 transform: %ld\n", PTR_ERR(tfm));
+ return PTR_ERR(tfm);
+ }
+
+ size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm);
+ shash = kmalloc(size, GFP_KERNEL);
+ if (!shash)
+ return -ENOMEM;
+
+ shash->tfm = tfm;
+ ret = crypto_shash_digest(shash, data, len, result);
+ kfree(shash);
+ crypto_free_shash(tfm);
+
+ return ret;
+}
+
+
+/* called with a lock on ->chg_lock */
+static enum batt_paired_state
+batt_check_pairing_state(struct batt_drv *batt_drv)
+{
+ const int len = strlen(dev_sn);
+ char dev_info[GBMS_DINF_LEN];
+ char mfg_info[GBMS_MINF_LEN];
+ u8 *dev_info_check = batt_drv->dev_info_check;
+ int ret;
+
+ ret = gbms_storage_read(GBMS_TAG_DINF, dev_info, GBMS_DINF_LEN);
+ if (ret < 0) {
+ pr_err("Read device pairing info failed, ret=%d\n", ret);
+ return BATT_PAIRING_READ_ERROR;
+ }
+
+ if (batt_drv->dev_info_check[0] == 0) {
+ char data[len + GBMS_MINF_LEN];
+
+ ret = gbms_storage_read(GBMS_TAG_MINF, mfg_info,
+ GBMS_MINF_LEN);
+ if (ret < 0) {
+ pr_err("read mfg info. fail, ret=%d\n", ret);
+ return BATT_PAIRING_READ_ERROR;
+ }
+
+ memcpy(data, dev_sn, len);
+ memcpy(&data[len], mfg_info, GBMS_MINF_LEN);
+
+ ret = batt_do_sha256(data, len + GBMS_MINF_LEN, dev_info_check);
+ if (ret < 0) {
+ pr_err("execute batt_do_sha256 fail, ret=%d\n", ret);
+ return BATT_PAIRING_MISMATCH;
+ }
+
+ pr_info("dev_info_check: %s\n", dev_info_check);
+ }
+
+ /* new battery: pair the battery to this device */
+ if (dev_info[0] == 0xFF) {
+
+ ret = gbms_storage_write(GBMS_TAG_DINF, dev_info_check,
+ strlen(dev_info_check));
+ if (ret < 0) {
+ pr_err("Pairing to this device failed, ret=%d\n", ret);
+ return BATT_PAIRING_WRITE_ERROR;
+ }
+
+ /* recycled battery */
+ } else if (strncmp(dev_info, dev_info_check, strlen(dev_info_check))) {
+ pr_warn("dev_sn=%*s\n", strlen(dev_sn), dev_sn);
+ pr_warn("dev_info=%*s\n", GBMS_DINF_LEN, dev_info);
+ pr_warn("Battery paired to a different device\n");
+
+ return BATT_PAIRING_MISMATCH;
+ }
+
+ return BATT_PAIRING_PAIRED;
+}
+
+/* battery history data collection */
+static int batt_history_data_work(struct batt_drv *batt_drv)
+{
+ int cycle_cnt, idx, ret;
+
+ /* TODO: google_battery caches cycle count, should use that */
+ cycle_cnt = GPSY_GET_PROP(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_CYCLE_COUNT);
+ if (cycle_cnt < 0)
+ return -EIO;
+
+ idx = cycle_cnt / batt_drv->hist_delta_cycle_cnt;
+
+ /* check if the cycle_cnt is valid */
+ if (idx >= batt_drv->hist_data_max_cnt)
+ return -ENOENT;
+
+ /* TODO: too many arguments, redesign API affter specs */
+ ret = batt_hist_data_collect(batt_drv->hist_data, cycle_cnt, idx,
+ batt_drv->fg_psy);
+ if (ret < 0)
+ pr_debug("Data collection failure %d\n", ret);
+
+ return 0;
+}
+
+/*
+ * poll the battery, run SOC%, dead battery, critical.
+ * scheduled from psy_changed and from timer
+ */
+static void google_battery_work(struct work_struct *work)
+{
+ struct batt_drv *batt_drv =
+ container_of(work, struct batt_drv, batt_work.work);
+ struct power_supply *fg_psy = batt_drv->fg_psy;
+ struct batt_ssoc_state *ssoc_state = &batt_drv->ssoc_state;
+ int update_interval = batt_drv->batt_update_interval;
+ const int prev_ssoc = ssoc_get_capacity(ssoc_state);
+ bool notify_psy_changed = false;
+ int fg_status, ret, batt_temp;
+
+ pr_debug("battery work item\n");
+
+ __pm_stay_awake(&batt_drv->batt_ws);
+
+ fg_status = GPSY_GET_INT_PROP(fg_psy, POWER_SUPPLY_PROP_STATUS, &ret);
+ if (ret < 0)
+ goto reschedule;
+
+ if (fg_status != batt_drv->fg_status)
+ notify_psy_changed = true;
+ batt_drv->fg_status = fg_status;
+
+ /* chg_lock protect msc_logic */
+ mutex_lock(&batt_drv->chg_lock);
+ /* batt_lock protect SSOC code etc. */
+ mutex_lock(&batt_drv->batt_lock);
+
+ /* TODO: poll rate should be min between ->batt_update_interval and
+ * whatever ssoc_work() decides (typically rls->rl_delta_max_time)
+ */
+ ret = ssoc_work(ssoc_state, fg_psy);
+ if (ret < 0) {
+ update_interval = BATT_WORK_ERROR_RETRY_MS;
+ } else {
+ bool full;
+ int ssoc, level;
+
+ /* handle charge/recharge */
+ batt_rl_update_status(batt_drv);
+
+ ssoc = ssoc_get_capacity(ssoc_state);
+ if (prev_ssoc != ssoc) {
+ if (ssoc > prev_ssoc)
+ log_ttf_estimate("SSOC", ssoc, batt_drv);
+ notify_psy_changed = true;
+ }
+
+ /* TODO(b/138860602): clear ->chg_done to enforce the
+ * same behavior during the transition 99 -> 100 -> Full
+ */
+
+ level = gbatt_get_capacity_level(&batt_drv->ssoc_state,
+ fg_status);
+ if (level != batt_drv->capacity_level) {
+ batt_drv->capacity_level = level;
+ notify_psy_changed = true;
+ }
+
+ if (batt_drv->dead_battery) {
+ batt_drv->dead_battery =
+ gbatt_check_dead_battery(batt_drv);
+ if (!batt_drv->dead_battery)
+ notify_psy_changed = true;
+ }
+
+ /* fuel gauge triggered recharge logic. */
+ full = (ssoc == SSOC_FULL);
+ if (full && !batt_drv->batt_full)
+ log_ttf_estimate("Full", ssoc, batt_drv);
+ batt_drv->batt_full = full;
+ }
+
+ /* TODO: poll other data here if needed */
+
+ ret = gbatt_get_temp(batt_drv, &batt_temp);
+ if (ret == 0 && batt_temp != batt_drv->batt_temp) {
+ const int limit = batt_drv->batt_update_high_temp_threshold;
+
+ batt_drv->batt_temp = batt_temp;
+ if (batt_drv->batt_temp > limit)
+ notify_psy_changed = true;
+ }
+
+ mutex_unlock(&batt_drv->batt_lock);
+
+ /*
+ * wait for timeout or state equal to CHARGING, FULL or UNKNOWN
+ * (which will likely not happen) even on ssoc error. msc_logic
+ * hold poll_ws wakelock during this time.
+ */
+ if (batt_drv->batt_fast_update_cnt) {
+
+ if (fg_status != POWER_SUPPLY_STATUS_DISCHARGING &&
+ fg_status != POWER_SUPPLY_STATUS_NOT_CHARGING) {
+ log_ttf_estimate("Start", prev_ssoc, batt_drv);
+ batt_drv->batt_fast_update_cnt = 0;
+ } else {
+ update_interval = BATT_WORK_FAST_RETRY_MS;
+ batt_drv->batt_fast_update_cnt -= 1;
+ }
+ }
+
+ /* acquired in msc_logic */
+ if (batt_drv->batt_fast_update_cnt == 0)
+ __pm_relax(&batt_drv->poll_ws);
+
+ if (batt_drv->res_state.estimate_requested)
+ batt_res_work(batt_drv);
+
+ /* check only once and when/if the pairing state is reset */
+ if (batt_drv->pairing_state == BATT_PAIRING_ENABLED) {
+ enum batt_paired_state state;
+
+ state = batt_check_pairing_state(batt_drv);
+ switch (state) {
+ /* somethig is wrong with eeprom comms, HW problem? */
+ case BATT_PAIRING_READ_ERROR:
+ break;
+ /* somethig is wrong with eeprom, HW problem? */
+ case BATT_PAIRING_WRITE_ERROR:
+ break;
+ default:
+ batt_drv->pairing_state = state;
+ break;
+ }
+ }
+
+ mutex_unlock(&batt_drv->chg_lock);
+
+ batt_cycle_count_update(batt_drv, ssoc_get_real(ssoc_state));
+ dump_ssoc_state(ssoc_state, batt_drv->ssoc_log);
+
+reschedule:
+
+ if (notify_psy_changed)
+ power_supply_changed(batt_drv->psy);
+
+
+ /* collect lifetime and write to storage */
+ if (batt_drv->blf_state == BATT_LFCOLLECT_ENABLED) {
+ int cnt;
+
+ /* gbms_storage will return -EPROBE_DEFER during init */
+ cnt = gbms_storage_read_data(GBMS_TAG_HIST, NULL, 0, 0);
+ if (cnt == -EPROBE_DEFER) {
+ /* wait until storage is up */
+ } else if (cnt < 0) {
+ batt_drv->blf_state = BATT_LFCOLLECT_NOT_AVAILABLE;
+ } else {
+ batt_drv->blf_state = BATT_LFCOLLECT_COLLECT;
+ batt_drv->hist_data_max_cnt = cnt;
+ }
+ }
+
+ if (batt_drv->blf_state == BATT_LFCOLLECT_COLLECT) {
+ ret = batt_history_data_work(batt_drv);
+ if (ret == -ENOENT) {
+ batt_drv->blf_state = BATT_LFCOLLECT_NOT_AVAILABLE;
+ pr_info("Battery data collection disabled\n");
+ } else if (ret < 0) {
+ pr_debug("cannot collect battery data %d\n", ret);
+ }
+ }
+
+ if (update_interval) {
+ pr_debug("rerun battery work in %d ms\n", update_interval);
+ schedule_delayed_work(&batt_drv->batt_work,
+ msecs_to_jiffies(update_interval));
+ }
+
+ __pm_relax(&batt_drv->batt_ws);
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Keep the number of properies under UEVENT_NUM_ENVP (minus # of
+ * standard uevent variables) i.e 26. Removed the following from
+ * sysnodes
+ *
+ * POWER_SUPPLY_PROP_ADAPTER_DETAILS, gbms
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, gbms
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, gbms
+ *
+ * POWER_SUPPLY_PROP_CHARGE_TYPE,
+ * POWER_SUPPLY_PROP_CURRENT_AVG,
+ * POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ * POWER_SUPPLY_PROP_RESISTANCE_ID, use BRID tag
+ * POWER_SUPPLY_PROP_SOH,
+ * POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ * POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ * POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ * POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ */
+
+static enum power_supply_property gbatt_battery_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL_ESTIMATE, /* google */
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_CYCLE_COUNTS,
+ POWER_SUPPLY_PROP_DEAD_BATTERY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_RESISTANCE,
+ POWER_SUPPLY_PROP_RESISTANCE_AVG, /* google */
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, /* No need for this? */
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, /* 23 */
+
+ /* hard limit to 26 */
+};
+
+/*
+ * status is:
+ * . _UNKNOWN during init
+ * . _DISCHARGING when not connected
+ * when connected to a power supply status is
+ * . _FULL (until disconnect) after the charger flags DONE if SSOC=100%
+ * . _CHARGING if FG reports _FULL but SSOC < 100% (should not happen)
+ * . _CHARGING if FG reports _NOT_CHARGING
+ * . _NOT_CHARGING if FG report _DISCHARGING
+ * . same as FG state otherwise
+ */
+static int gbatt_get_status(struct batt_drv *batt_drv,
+ union power_supply_propval *val)
+{
+ int err, ssoc;
+
+ if (batt_drv->ssoc_state.buck_enabled == 0) {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ if (batt_drv->ssoc_state.buck_enabled == -1) {
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ return 0;
+ }
+
+ /* ->buck_enabled = 1, from here ownward device is connected */
+ if (!batt_drv->fg_psy)
+ return -EINVAL;
+
+ ssoc = ssoc_get_capacity(&batt_drv->ssoc_state);
+
+ /* FULL when the charger said so and SSOC == 100% */
+ if (batt_drv->chg_done && ssoc == SSOC_FULL) {
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ return 0;
+ }
+
+ err = power_supply_get_property(batt_drv->fg_psy,
+ POWER_SUPPLY_PROP_STATUS,
+ val);
+ if (err != 0)
+ return err;
+
+ if (val->intval == POWER_SUPPLY_STATUS_FULL) {
+
+ /* not full unless the charger says so */
+ if (!batt_drv->chg_done)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+
+ /* NOTE: FG driver could flag FULL before GDF is at 100% when
+ * gauge is not tuned or when capacity estimates are wrong.
+ */
+ if (ssoc != SSOC_FULL)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+
+ } else if (val->intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+ /* smooth transition between charging and full */
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else if (val->intval == POWER_SUPPLY_STATUS_DISCHARGING) {
+ /* connected and discharging is NOT charging */
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+
+ return 0;
+}
+
+static int gbatt_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ struct batt_ssoc_state *ssoc_state = &batt_drv->ssoc_state;
+ int rc, err = 0;
+
+ pm_runtime_get_sync(batt_drv->device);
+ if (!batt_drv->init_complete || !batt_drv->resume_complete) {
+ pm_runtime_put_sync(batt_drv->device);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(batt_drv->device);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ADAPTER_DETAILS:
+ val->intval = batt_drv->ce_data.adapter_details.v;
+ break;
+
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ if (batt_drv->cycle_count < 0)
+ err = batt_drv->cycle_count;
+ else
+ val->intval = batt_drv->cycle_count;
+ break;
+
+ case POWER_SUPPLY_PROP_CYCLE_COUNTS:
+ mutex_lock(&batt_drv->cc_data.lock);
+ (void)gbms_cycle_count_cstr(batt_drv->cc_data.cyc_ctr_cstr,
+ sizeof(batt_drv->cc_data.cyc_ctr_cstr),
+ batt_drv->cc_data.count);
+ val->strval = batt_drv->cc_data.cyc_ctr_cstr;
+ mutex_unlock(&batt_drv->cc_data.lock);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (batt_drv->fake_capacity >= 0 &&
+ batt_drv->fake_capacity <= 100)
+ val->intval = batt_drv->fake_capacity;
+ else {
+ mutex_lock(&batt_drv->batt_lock);
+ val->intval = ssoc_get_capacity(ssoc_state);
+ mutex_unlock(&batt_drv->batt_lock);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_DEAD_BATTERY:
+ val->intval = batt_drv->dead_battery;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = batt_drv->capacity_level;
+ break;
+
+ /*
+ * ng charging:
+ * 1) write to POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ * 2) read POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT and
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE
+ */
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ val->intval = batt_drv->chg_state.v;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ mutex_lock(&batt_drv->chg_lock);
+ val->intval = batt_drv->cc_max;
+ mutex_unlock(&batt_drv->chg_lock);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ mutex_lock(&batt_drv->chg_lock);
+ val->intval = batt_drv->fv_uv;
+ mutex_unlock(&batt_drv->chg_lock);
+ break;
+
+ /*
+ * POWER_SUPPLY_PROP_CHARGE_DONE comes from the charger BUT battery
+ * has also an idea about it. Now using a software state: charge is
+ * DONE when we are in the discharge phase of the recharge logic.
+ * NOTE: might change to keep DONE while rl_status != NONE
+ */
+ case POWER_SUPPLY_PROP_CHARGE_DONE:
+ mutex_lock(&batt_drv->chg_lock);
+ val->intval = batt_drv->chg_done;
+ mutex_unlock(&batt_drv->chg_lock);
+ break;
+ /*
+ * compat: POWER_SUPPLY_PROP_CHARGE_TYPE comes from the charger so
+ * using the last value reported from the CHARGER. This (of course)
+ * means that NG charging needs to be enabled.
+ */
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ mutex_lock(&batt_drv->chg_lock);
+ val->intval = batt_drv->chg_state.f.chg_type;
+ mutex_unlock(&batt_drv->chg_lock);
+ break;
+
+ /* compat, for *_CURRENT_LIMITED could return this one:
+ * (batt_drv->chg_state.f.flags & GBMS_CS_FLAG_ILIM)
+ */
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ val->intval = 0;
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ err = gbatt_get_status(batt_drv, val);
+ break;
+
+ /* health */
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (!batt_drv->fg_psy)
+ return -EINVAL;
+ err = power_supply_get_property(batt_drv->fg_psy, psp, val);
+ if (err == 0)
+ batt_drv->soh = val->intval;
+ break;
+ /* define this better */
+ case POWER_SUPPLY_PROP_SOH:
+ val->intval = batt_drv->soh;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_ESTIMATE:
+ if (!batt_drv->fg_psy)
+ return -EINVAL;
+ err = power_supply_get_property(batt_drv->fg_psy, psp, val);
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE_AVG:
+ if (batt_drv->res_state.filter_count <
+ batt_drv->res_state.estimate_filter)
+ val->intval = 0;
+ else
+ val->intval = batt_drv->res_state.resistance_avg;
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: {
+ time_t res;
+
+ rc = batt_ttf_estimate(&res, batt_drv);
+ if (rc == 0) {
+ val->intval = res;
+ } else if (!batt_drv->fg_psy) {
+ val->intval = -1;
+ } else {
+ rc = power_supply_get_property(batt_drv->fg_psy,
+ psp, val);
+ if (rc < 0)
+ val->intval = -1;
+ }
+ } break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ err = gbatt_get_temp(batt_drv, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (!batt_drv->fg_psy)
+ return -EINVAL;
+ err = power_supply_get_property(batt_drv->fg_psy, psp, val);
+ if (err == 0)
+ val->intval = -val->intval;
+ break;
+
+ /*
+ * TODO: "charger" will expose this but I'd rather use an API from
+ * google_bms.h. Right now route it to fg_psy: just make sure that
+ * fg_psy doesn't look it up in google_battery
+ */
+ case POWER_SUPPLY_PROP_RESISTANCE_ID:
+ /* fall through */
+ default:
+ if (!batt_drv->fg_psy)
+ return -EINVAL;
+ err = power_supply_get_property(batt_drv->fg_psy, psp, val);
+ break;
+ }
+
+ if (err < 0) {
+ pr_debug("gbatt: get_prop cannot read psp=%d\n", psp);
+ return err;
+ }
+
+ return 0;
+}
+
+static int gbatt_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct batt_drv *batt_drv = (struct batt_drv *)
+ power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ pm_runtime_get_sync(batt_drv->device);
+ if (!batt_drv->init_complete || !batt_drv->resume_complete) {
+ pm_runtime_put_sync(batt_drv->device);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(batt_drv->device);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ADAPTER_DETAILS:
+ mutex_lock(&batt_drv->stats_lock);
+ batt_drv->ce_data.adapter_details.v = val->intval;
+ mutex_unlock(&batt_drv->stats_lock);
+ break;
+
+ /* NG Charging, where it all begins */
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ mutex_lock(&batt_drv->chg_lock);
+ batt_drv->chg_state.v = val->int64val;
+ ret = msc_logic(batt_drv);
+ mutex_unlock(&batt_drv->chg_lock);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ batt_drv->fake_capacity = val->intval;
+ if (batt_drv->psy)
+ power_supply_changed(batt_drv->psy);
+ break;
+ /* TODO: compat */
+ case POWER_SUPPLY_PROP_CYCLE_COUNTS:
+ ret = gbms_cycle_count_sscan(batt_drv->cc_data.count,
+ val->strval);
+ if (ret == 0) {
+ ret = batt_cycle_count_store(&batt_drv->cc_data);
+ if (ret < 0)
+ pr_err("cannot store bin count ret=%d\n", ret);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ if (val->intval <= 0)
+ batt_drv->ttf_stats.ttf_fake = -1;
+ else
+ batt_drv->ttf_stats.ttf_fake = val->intval;
+ pr_info("time_to_full = %ld\n", batt_drv->ttf_stats.ttf_fake);
+ if (batt_drv->psy)
+ power_supply_changed(batt_drv->psy);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret < 0) {
+ pr_debug("gbatt: get_prop cannot write psp=%d\n", psp);
+ return ret;
+ }
+
+
+ return 0;
+}
+
+static int gbatt_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_ADAPTER_DETAILS:
+ case POWER_SUPPLY_PROP_CYCLE_COUNTS:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct power_supply_desc gbatt_psy_desc = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = gbatt_get_property,
+ .set_property = gbatt_set_property,
+ .property_is_writeable = gbatt_property_is_writeable,
+ .properties = gbatt_battery_props,
+ .num_properties = ARRAY_SIZE(gbatt_battery_props),
+};
+
+/* ------------------------------------------------------------------------ */
+
+static void google_battery_init_work(struct work_struct *work)
+{
+ struct batt_drv *batt_drv = container_of(work, struct batt_drv,
+ init_work.work);
+ struct device_node *node = batt_drv->device->of_node;
+ struct power_supply *fg_psy = batt_drv->fg_psy;
+ bool has_eeprom;
+ int ret = 0;
+
+ batt_rl_reset(batt_drv);
+ batt_drv->dead_battery = true; /* clear in batt_work() */
+ batt_drv->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ batt_drv->ssoc_state.buck_enabled = -1;
+ batt_drv->hold_taper_ws = false;
+ batt_drv->fake_temp = 0;
+ batt_reset_chg_drv_state(batt_drv);
+
+ mutex_init(&batt_drv->chg_lock);
+ mutex_init(&batt_drv->batt_lock);
+ mutex_init(&batt_drv->stats_lock);
+ mutex_init(&batt_drv->cc_data.lock);
+
+ if (!batt_drv->fg_psy) {
+
+ fg_psy = power_supply_get_by_name(batt_drv->fg_psy_name);
+ if (!fg_psy) {
+ pr_info("failed to get \"%s\" power supply, retrying...\n",
+ batt_drv->fg_psy_name);
+ goto retry_init_work;
+ }
+
+ batt_drv->fg_psy = fg_psy;
+ }
+
+ if (!batt_drv->batt_present) {
+ ret = GPSY_GET_PROP(fg_psy, POWER_SUPPLY_PROP_PRESENT);
+ if (ret == -EAGAIN)
+ goto retry_init_work;
+
+ batt_drv->batt_present = (ret > 0);
+ if (!batt_drv->batt_present)
+ pr_warn("battery not present (ret=%d)\n", ret);
+ }
+
+ ret = of_property_read_u32(node, "google,recharge-soc-threshold",
+ &batt_drv->ssoc_state.rl_soc_threshold);
+ if (ret < 0)
+ batt_drv->ssoc_state.rl_soc_threshold =
+ DEFAULT_BATT_DRV_RL_SOC_THRESHOLD;
+
+ /* cycle count is cached: read here bc SSOC, chg_profile might use it */
+ batt_update_cycle_count(batt_drv);
+
+ ret = ssoc_init(&batt_drv->ssoc_state, node, fg_psy);
+ if (ret < 0 && batt_drv->batt_present)
+ goto retry_init_work;
+
+ dump_ssoc_state(&batt_drv->ssoc_state, batt_drv->ssoc_log);
+
+ /* could read EEPROM and history here */
+
+ ret = batt_init_chg_profile(batt_drv);
+ if (ret == -EPROBE_DEFER)
+ goto retry_init_work;
+
+ if (ret < 0) {
+ pr_err("charging profile disabled, ret=%d\n", ret);
+ } else if (batt_drv->battery_capacity) {
+ gbms_dump_chg_profile(&batt_drv->chg_profile);
+ }
+
+ batt_chg_stats_init(&batt_drv->ce_data, &batt_drv->chg_profile);
+ batt_chg_stats_init(&batt_drv->ce_qual, &batt_drv->chg_profile);
+
+ batt_drv->fg_nb.notifier_call = psy_changed;
+ ret = power_supply_reg_notifier(&batt_drv->fg_nb);
+ if (ret < 0)
+ pr_err("cannot register power supply notifer, ret=%d\n",
+ ret);
+
+ wakeup_source_init(&batt_drv->batt_ws, gbatt_psy_desc.name);
+ wakeup_source_init(&batt_drv->taper_ws, "Taper");
+ wakeup_source_init(&batt_drv->poll_ws, "Poll");
+ wakeup_source_init(&batt_drv->msc_ws, "MSC");
+
+ mutex_lock(&batt_drv->cc_data.lock);
+ ret = batt_cycle_count_load(&batt_drv->cc_data);
+ if (ret < 0)
+ pr_err("cannot restore bin count ret=%d\n", ret);
+ mutex_unlock(&batt_drv->cc_data.lock);
+
+ batt_drv->fake_capacity = (batt_drv->batt_present) ? -EINVAL
+ : DEFAULT_BATT_FAKE_CAPACITY;
+
+ /* charging configuration */
+ ret = of_property_read_u32(node, "google,update-interval",
+ &batt_drv->batt_update_interval);
+ if (ret < 0)
+ batt_drv->batt_update_interval = DEFAULT_BATT_UPDATE_INTERVAL;
+
+ /* high temperature notify configuration */
+ ret = of_property_read_u32(batt_drv->device->of_node,
+ "google,update-high-temp-threshold",
+ &batt_drv->batt_update_high_temp_threshold);
+ if (ret < 0)
+ batt_drv->batt_update_high_temp_threshold =
+ DEFAULT_HIGH_TEMP_UPDATE_THRESHOLD;
+ /* charge statistics */
+ ret = of_property_read_u32(node, "google,chg-stats-qual-time",
+ &batt_drv->ce_data.chg_sts_qual_time);
+ if (ret < 0)
+ batt_drv->ce_data.chg_sts_qual_time =
+ DEFAULT_CHG_STATS_MIN_QUAL_TIME;
+
+ ret = of_property_read_u32(node, "google,chg-stats-delta-soc",
+ &batt_drv->ce_data.chg_sts_delta_soc);
+ if (ret < 0)
+ batt_drv->ce_data.chg_sts_delta_soc =
+ DEFAULT_CHG_STATS_MIN_DELTA_SOC;
+
+ /* time to full */
+ ret = ttf_stats_init(&batt_drv->ttf_stats, batt_drv->device,
+ batt_drv->battery_capacity);
+ if (ret < 0)
+ pr_info("time to full not available\n");
+
+ /* google_resistance */
+ batt_res_load_data(&batt_drv->res_state, batt_drv->fg_psy);
+
+ /* override setting google,battery-roundtrip = 0 in device tree */
+ batt_drv->disable_votes =
+ of_property_read_bool(node, "google,disable-votes");
+ if (batt_drv->disable_votes)
+ pr_info("battery votes disabled\n");
+
+ /* TODO: split pairing and collect, not all EEPROMS support it */
+ has_eeprom = of_property_read_bool(node, "google,eeprom-inside");
+ if (has_eeprom) {
+ batt_drv->pairing_state = BATT_PAIRING_ENABLED;
+ batt_drv->blf_state = BATT_LFCOLLECT_ENABLED;
+ } else {
+ batt_drv->pairing_state = BATT_PAIRING_DISABLED;
+ }
+
+ /* TODO: use delta cycle count to enable collecting history */
+ ret = of_property_read_u32(batt_drv->device->of_node,
+ "google,history-delta-cycle-count",
+ &batt_drv->hist_delta_cycle_cnt);
+ if (ret < 0)
+ batt_drv->hist_delta_cycle_cnt = HCC_DEFAULT_DELTA_CYCLE_CNT;
+ else
+ batt_drv->blf_state = BATT_LFCOLLECT_ENABLED;
+
+ /* TODO: use delta cycle count to enable collecting history */
+ if (batt_drv->blf_state == BATT_LFCOLLECT_ENABLED) {
+ batt_drv->hist_data = batt_hist_init_data(batt_drv->device);
+ if (!batt_drv->hist_data) {
+ batt_drv->blf_state = BATT_LFCOLLECT_DISABLED;
+ pr_err("Cannot collect history data\n");
+ }
+ }
+
+ /* google_battery expose history via a standard device */
+ batt_drv->history = gbms_storage_create_device("battery_history",
+ GBMS_TAG_HIST);
+ if (!batt_drv->history)
+ pr_err("history not available\n");
+
+ /* debugfs */
+ (void)batt_init_fs(batt_drv);
+
+ pr_info("init_work done\n");
+
+ batt_drv->init_complete = true;
+ batt_drv->resume_complete = true;
+
+ schedule_delayed_work(&batt_drv->batt_work, 0);
+
+ return;
+
+retry_init_work:
+ schedule_delayed_work(&batt_drv->init_work,
+ msecs_to_jiffies(BATT_DELAY_INIT_MS));
+}
+
+static struct thermal_zone_of_device_ops google_battery_tz_ops = {
+ .get_temp = google_battery_tz_get_cycle_count,
+};
+
+static int google_battery_probe(struct platform_device *pdev)
+{
+ const char *fg_psy_name, *psy_name = NULL;
+ struct batt_drv *batt_drv;
+ int ret;
+ struct power_supply_config psy_cfg = {};
+
+ batt_drv = devm_kzalloc(&pdev->dev, sizeof(*batt_drv), GFP_KERNEL);
+ if (!batt_drv)
+ return -ENOMEM;
+
+ batt_drv->device = &pdev->dev;
+
+ ret = of_property_read_string(pdev->dev.of_node,
+ "google,fg-psy-name", &fg_psy_name);
+ if (ret != 0) {
+ pr_err("cannot read google,fg-psy-name, ret=%d\n", ret);
+ return -EINVAL;
+ }
+
+ batt_drv->fg_psy_name =
+ devm_kstrdup(&pdev->dev, fg_psy_name, GFP_KERNEL);
+ if (!batt_drv->fg_psy_name)
+ return -ENOMEM;
+
+ /* change name and type for debug/test */
+ if (of_property_read_bool(pdev->dev.of_node, "google,psy-type-unknown"))
+ gbatt_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
+
+ ret = of_property_read_string(pdev->dev.of_node,
+ "google,psy-name", &psy_name);
+ if (ret == 0) {
+ gbatt_psy_desc.name =
+ devm_kstrdup(&pdev->dev, psy_name, GFP_KERNEL);
+ }
+
+ INIT_DELAYED_WORK(&batt_drv->init_work, google_battery_init_work);
+ INIT_DELAYED_WORK(&batt_drv->batt_work, google_battery_work);
+ platform_set_drvdata(pdev, batt_drv);
+
+ psy_cfg.drv_data = batt_drv;
+ psy_cfg.of_node = pdev->dev.of_node;
+
+ batt_drv->psy = devm_power_supply_register(batt_drv->device,
+ &gbatt_psy_desc, &psy_cfg);
+ if (IS_ERR(batt_drv->psy)) {
+ ret = PTR_ERR(batt_drv->psy);
+ if (ret == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ /* TODO: fail with -ENODEV */
+ dev_err(batt_drv->device,
+ "Couldn't register as power supply, ret=%d\n", ret);
+ }
+
+ batt_drv->ssoc_log = debugfs_logbuffer_register("ssoc");
+ if (IS_ERR(batt_drv->ssoc_log)) {
+ ret = PTR_ERR(batt_drv->ssoc_log);
+ dev_err(batt_drv->device,
+ "failed to create ssoc_log, ret=%d\n", ret);
+ batt_drv->ssoc_log = NULL;
+ }
+
+ batt_drv->ttf_log = debugfs_logbuffer_register("ttf");
+ if (IS_ERR(batt_drv->ttf_log)) {
+ ret = PTR_ERR(batt_drv->ttf_log);
+ dev_err(batt_drv->device,
+ "failed to create ttf_log, ret=%d\n", ret);
+ batt_drv->ttf_log = NULL;
+ }
+
+ /* Resistance Estimation configuration */
+ ret = of_property_read_u32(pdev->dev.of_node, "google,res-temp-hi",
+ &batt_drv->res_state.res_temp_high);
+ if (ret < 0)
+ batt_drv->res_state.res_temp_high = DEFAULT_RES_TEMP_HIGH;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "google,res-temp-lo",
+ &batt_drv->res_state.res_temp_low);
+ if (ret < 0)
+ batt_drv->res_state.res_temp_low = DEFAULT_RES_TEMP_LOW;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "google,res-soc-thresh",
+ &batt_drv->res_state.ssoc_threshold);
+ if (ret < 0)
+ batt_drv->res_state.ssoc_threshold = DEFAULT_RES_SSOC_THR;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "google,res-filt-length",
+ &batt_drv->res_state.estimate_filter);
+ if (ret < 0)
+ batt_drv->res_state.estimate_filter = DEFAULT_RES_FILT_LEN;
+
+ batt_drv->tz_dev = thermal_zone_of_sensor_register(batt_drv->device,
+ 0, batt_drv, &google_battery_tz_ops);
+ if (IS_ERR(batt_drv->tz_dev)) {
+ pr_err("battery tz register failed. err:%ld\n",
+ PTR_ERR(batt_drv->tz_dev));
+ ret = PTR_ERR(batt_drv->tz_dev);
+ batt_drv->tz_dev = NULL;
+ } else {
+ thermal_zone_device_update(batt_drv->tz_dev, THERMAL_DEVICE_UP);
+ }
+ /* give time to fg driver to start */
+ schedule_delayed_work(&batt_drv->init_work,
+ msecs_to_jiffies(BATT_DELAY_INIT_MS));
+
+ return 0;
+}
+
+static int google_battery_remove(struct platform_device *pdev)
+{
+ struct batt_drv *batt_drv = platform_get_drvdata(pdev);
+
+ if (!batt_drv)
+ return 0;
+
+ if (batt_drv->ssoc_log)
+ debugfs_logbuffer_unregister(batt_drv->ssoc_log);
+ if (batt_drv->ttf_log)
+ debugfs_logbuffer_unregister(batt_drv->ttf_log);
+ if (batt_drv->tz_dev)
+ thermal_zone_of_sensor_unregister(batt_drv->device,
+ batt_drv->tz_dev);
+ if (batt_drv->history)
+ gbms_storage_cleanup_device(batt_drv->history);
+
+ if (batt_drv->fg_psy)
+ power_supply_put(batt_drv->fg_psy);
+
+ batt_hist_free_data(batt_drv->hist_data);
+
+ gbms_free_chg_profile(&batt_drv->chg_profile);
+
+ wakeup_source_trash(&batt_drv->msc_ws);
+ wakeup_source_trash(&batt_drv->batt_ws);
+ wakeup_source_trash(&batt_drv->taper_ws);
+ wakeup_source_trash(&batt_drv->poll_ws);
+
+ if (batt_drv->tz_dev)
+ thermal_zone_of_sensor_unregister(batt_drv->device,
+ batt_drv->tz_dev);
+ return 0;
+}
+
+#ifdef SUPPORT_PM_SLEEP
+static int gbatt_pm_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct batt_drv *batt_drv = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(batt_drv->device);
+ batt_drv->resume_complete = false;
+ pm_runtime_put_sync(batt_drv->device);
+
+ return 0;
+}
+
+static int gbatt_pm_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct batt_drv *batt_drv = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(batt_drv->device);
+ batt_drv->resume_complete = true;
+ pm_runtime_put_sync(batt_drv->device);
+
+ mod_delayed_work(system_wq, &batt_drv->batt_work, 0);
+
+ return 0;
+}
+
+static const struct dev_pm_ops gbatt_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(gbatt_pm_suspend, gbatt_pm_resume)
+};
+#endif
+
+
+static const struct of_device_id google_charger_of_match[] = {
+ {.compatible = "google,battery"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, google_charger_of_match);
+
+
+static struct platform_driver google_battery_driver = {
+ .driver = {
+ .name = "google,battery",
+ .owner = THIS_MODULE,
+ .of_match_table = google_charger_of_match,
+#ifdef SUPPORT_PM_SLEEP
+ .pm = &gbatt_pm_ops,
+#endif
+ /* .probe_type = PROBE_PREFER_ASYNCHRONOUS, */
+ },
+ .probe = google_battery_probe,
+ .remove = google_battery_remove,
+};
+
+static int __init google_battery_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&google_battery_driver);
+ if (ret < 0) {
+ pr_err("device registration failed: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void __init google_battery_exit(void)
+{
+ platform_driver_unregister(&google_battery_driver);
+ pr_info("unregistered platform driver\n");
+}
+
+module_init(google_battery_init);
+module_exit(google_battery_exit);
+MODULE_DESCRIPTION("Google Battery Driver");
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/google_bms.c b/google_bms.c
new file mode 100644
index 0000000..d26bcb5
--- /dev/null
+++ b/google_bms.c
@@ -0,0 +1,463 @@
+/*
+ * Google Battery Management System
+ *
+ * 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 gbms_owner(p) ((p)->owner_name ? (p)->owner_name : "google_bms")
+
+#define gbms_info(p, fmt, ...) \
+ pr_info("%s: " fmt, gbms_owner(p), ##__VA_ARGS__)
+#define gbms_warn(p, fmt, ...) \
+ pr_warn("%s: " fmt, gbms_owner(p), ##__VA_ARGS__)
+#define gbms_err(p, fmt, ...) \
+ pr_err("%s: " fmt, gbms_owner(p), ##__VA_ARGS__)
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include "google_psy.h"
+#include "google_bms.h"
+
+#define GBMS_DEFAULT_FV_UV_RESOLUTION 25000
+#define GBMS_DEFAULT_FV_UV_MARGIN_DPCT 1020
+#define GBMS_DEFAULT_CV_DEBOUNCE_CNT 3
+#define GBMS_DEFAULT_CV_UPDATE_INTERVAL 2000
+#define GBMS_DEFAULT_CV_TIER_OV_CNT 10
+#define GBMS_DEFAULT_CV_TIER_SWITCH_CNT 3
+#define GBMS_DEFAULT_CV_OTV_MARGIN 0
+
+static const char *psy_chgt_str[] = {
+ "Unknown", "None", "Trickle", "Fast", "Taper"
+};
+
+const char *gbms_chg_type_s(int cgh_type)
+{
+ if (cgh_type < 0 || cgh_type > ARRAY_SIZE(psy_chgt_str))
+ return "<err>";
+ return psy_chgt_str[cgh_type];
+}
+
+static const char *psy_chgs_str[] = {
+ "Unknown", "Charging", "Discharging", "Not Charging", "Full"
+};
+
+const char *gbms_chg_status_s(int chg_status)
+{
+ if (chg_status < 0 || chg_status > ARRAY_SIZE(psy_chgs_str))
+ return "<err>";
+ return psy_chgs_str[chg_status];
+}
+
+
+const char *gbms_chg_ev_adapter_s(int adapter)
+{
+ static char *chg_ev_adapter_type_str[] = {
+ FOREACH_CHG_EV_ADAPTER(CHG_EV_ADAPTER_STRING)
+ };
+
+ if (adapter < 0 || adapter > ARRAY_SIZE(chg_ev_adapter_type_str))
+ return "<err>";
+ return chg_ev_adapter_type_str[adapter];
+}
+
+/* convert C rates to current. Caller can account for tolerances reducing
+ * battery_capacity. fv_uv_resolution is used to create discrete steps.
+ * NOTE: the call covert C rates to chanrge currents IN PLACE, ie you cannot
+ * call this twice.
+ */
+void gbms_init_chg_table(struct gbms_chg_profile *profile, u32 capacity_ma)
+{
+ u32 ccm;
+ int vi, ti;
+ const int fv_uv_step = profile->fv_uv_resolution;
+
+ profile->capacity_ma = capacity_ma;
+
+ /* chg-battery-capacity is in mAh, chg-cc-limits relative to 100 */
+ for (ti = 0; ti < profile->temp_nb_limits - 1; ti++) {
+ for (vi = 0; vi < profile->volt_nb_limits; vi++) {
+ ccm = GBMS_CCCM_LIMITS(profile, ti, vi);
+ ccm *= capacity_ma * 10;
+
+ /* round to the nearest resolution */
+ if (fv_uv_step)
+ ccm = DIV_ROUND_CLOSEST(ccm, fv_uv_step)
+ * fv_uv_step;
+
+ GBMS_CCCM_LIMITS(profile, ti, vi) = ccm;
+ }
+ }
+}
+
+/* configure standard device charge profile properties */
+static int gbms_read_cccm_limits(struct gbms_chg_profile *profile,
+ struct device_node *node)
+{
+ int ret;
+
+ profile->temp_nb_limits =
+ of_property_count_elems_of_size(node, "google,chg-temp-limits",
+ sizeof(u32));
+ if (profile->temp_nb_limits <= 0) {
+ ret = profile->temp_nb_limits;
+ gbms_err(profile, "cannot read chg-temp-limits, ret=%d\n", ret);
+ return ret;
+ }
+ if (profile->temp_nb_limits > GBMS_CHG_TEMP_NB_LIMITS_MAX) {
+ gbms_err(profile, "chg-temp-nb-limits exceeds driver max: %d\n",
+ GBMS_CHG_TEMP_NB_LIMITS_MAX);
+ return -EINVAL;
+ }
+ ret = of_property_read_u32_array(node, "google,chg-temp-limits",
+ (u32 *)profile->temp_limits,
+ profile->temp_nb_limits);
+ if (ret < 0) {
+ gbms_err(profile, "cannot read chg-temp-limits table, ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ profile->volt_nb_limits =
+ of_property_count_elems_of_size(node, "google,chg-cv-limits",
+ sizeof(u32));
+ if (profile->volt_nb_limits <= 0) {
+ ret = profile->volt_nb_limits;
+ gbms_err(profile, "cannot read chg-cv-limits, ret=%d\n", ret);
+ return ret;
+ }
+ if (profile->volt_nb_limits > GBMS_CHG_VOLT_NB_LIMITS_MAX) {
+ gbms_err(profile, "chg-cv-nb-limits exceeds driver max: %d\n",
+ GBMS_CHG_VOLT_NB_LIMITS_MAX);
+ return -EINVAL;
+ }
+ ret = of_property_read_u32_array(node, "google,chg-cv-limits",
+ (u32 *)profile->volt_limits,
+ profile->volt_nb_limits);
+ if (ret < 0) {
+ gbms_err(profile, "cannot read chg-cv-limits table, ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int gbms_init_chg_profile_internal(struct gbms_chg_profile *profile,
+ struct device_node *node,
+ const char *owner_name)
+{
+ int ret, vi;
+ u32 cccm_array_size, mem_size;
+
+ profile->owner_name = owner_name;
+
+ ret = gbms_read_cccm_limits(profile, node);
+ if (ret < 0)
+ return ret;
+
+ cccm_array_size = (profile->temp_nb_limits - 1)
+ * profile->volt_nb_limits;
+ mem_size = sizeof(s32) * cccm_array_size;
+
+ profile->cccm_limits = kzalloc(mem_size, GFP_KERNEL);
+ if (!profile->cccm_limits)
+ return -ENOMEM;
+
+ /* load C rates into profile->cccm_limits */
+ ret = of_property_read_u32_array(node, "google,chg-cc-limits",
+ profile->cccm_limits,
+ cccm_array_size);
+ if (ret < 0) {
+ gbms_err(profile, "cannot read chg-cc-limits table, ret=%d\n",
+ ret);
+ kfree(profile->cccm_limits);
+ profile->cccm_limits = 0;
+ return -EINVAL;
+ }
+
+ /* for irdrop compensation in taper step */
+ ret = of_property_read_u32(node, "google,fv-uv-resolution",
+ &profile->fv_uv_resolution);
+ if (ret < 0)
+ profile->fv_uv_resolution = GBMS_DEFAULT_FV_UV_RESOLUTION;
+
+ /* how close to tier voltage is close enough */
+ ret = of_property_read_u32(node, "google,cv-range-accuracy",
+ &profile->cv_range_accuracy);
+ if (ret < 0)
+ profile->cv_range_accuracy = profile->fv_uv_resolution / 2;
+
+ /* IEEE1725, default to 1020, cap irdrop offset */
+ ret = of_property_read_u32(node, "google,fv-uv-margin-dpct",
+ &profile->fv_uv_margin_dpct);
+ if (ret < 0)
+ profile->fv_uv_margin_dpct = GBMS_DEFAULT_FV_UV_MARGIN_DPCT;
+
+ /* debounce tier switch */
+ ret = of_property_read_u32(node, "google,cv-debounce-cnt",
+ &profile->cv_debounce_cnt);
+ if (ret < 0)
+ profile->cv_debounce_cnt = GBMS_DEFAULT_CV_DEBOUNCE_CNT;
+
+ /* how fast to poll in taper */
+ ret = of_property_read_u32(node, "google,cv-update-interval",
+ &profile->cv_update_interval);
+ if (ret < 0)
+ profile->cv_update_interval = GBMS_DEFAULT_CV_UPDATE_INTERVAL;
+
+ /* tier over voltage penalty */
+ ret = of_property_read_u32(node, "google,cv-tier-ov-cnt",
+ &profile->cv_tier_ov_cnt);
+ if (ret < 0)
+ profile->cv_tier_ov_cnt = GBMS_DEFAULT_CV_TIER_OV_CNT;
+
+ /* how many samples under next tier to wait before switching */
+ ret = of_property_read_u32(node, "google,cv-tier-switch-cnt",
+ &profile->cv_tier_switch_cnt);
+ if (ret < 0)
+ profile->cv_tier_switch_cnt = GBMS_DEFAULT_CV_TIER_SWITCH_CNT;
+
+ /* allow being "a little" over tier voltage, experimental */
+ ret = of_property_read_u32(node, "google,cv-otv-margin",
+ &profile->cv_otv_margin);
+ if (ret < 0)
+ profile->cv_otv_margin = GBMS_DEFAULT_CV_OTV_MARGIN;
+
+ /* sanity on voltages (should warn?) */
+ for (vi = 0; vi < profile->volt_nb_limits; vi++)
+ profile->volt_limits[vi] = profile->volt_limits[vi] /
+ profile->fv_uv_resolution * profile->fv_uv_resolution;
+
+ return 0;
+}
+
+void gbms_free_chg_profile(struct gbms_chg_profile *profile)
+{
+ kfree(profile->cccm_limits);
+ profile->cccm_limits = 0;
+}
+
+/* NOTE: I should really pass the scale */
+void gbms_dump_raw_profile(const struct gbms_chg_profile *profile, int scale)
+{
+ const int tscale = (scale == 1) ? 1 : 10;
+ /* with scale == 1 voltage takes 7 bytes, add 7 bytes of temperature */
+ char buff[GBMS_CHG_VOLT_NB_LIMITS_MAX * 9 + 7];
+ int ti, vi, count, len = sizeof(buff);
+
+ gbms_info(profile, "Profile constant charge limits:\n");
+ count = 0;
+ for (vi = 0; vi < profile->volt_nb_limits; vi++) {
+ count += scnprintf(buff + count, len - count, " %4d",
+ profile->volt_limits[vi] / scale);
+ }
+ gbms_info(profile, "|T \\ V%s\n", buff);
+
+ for (ti = 0; ti < profile->temp_nb_limits - 1; ti++) {
+ count = 0;
+ count += scnprintf(buff + count, len - count, "|%2d:%2d",
+ profile->temp_limits[ti] / tscale,
+ profile->temp_limits[ti + 1] / tscale);
+ for (vi = 0; vi < profile->volt_nb_limits; vi++) {
+ count += scnprintf(buff + count, len - count, " %4d",
+ GBMS_CCCM_LIMITS(profile, ti, vi)
+ / scale);
+ }
+ gbms_info(profile, "%s\n", buff);
+ }
+}
+
+int gbms_msc_round_fv_uv(const struct gbms_chg_profile *profile,
+ int vtier, int fv_uv)
+{
+ int result;
+ const unsigned int fv_uv_max = (vtier / 1000)
+ * profile->fv_uv_margin_dpct;
+
+ if (fv_uv_max != 0 && fv_uv > fv_uv_max)
+ fv_uv = fv_uv_max;
+
+ result = fv_uv - (fv_uv % profile->fv_uv_resolution);
+
+ if (fv_uv_max != 0)
+ gbms_info(profile, "MSC_ROUND: fv_uv=%d vtier=%d fv_uv_max=%d -> %d\n",
+ fv_uv, vtier, fv_uv_max, result);
+
+ return result;
+}
+
+/* charge profile idx based on the battery temperature
+ * TODO: return -1 when temperature is lower than profile->temp_limits[0] or
+ * higher than profile->temp_limits[profile->temp_nb_limits - 1]
+ */
+int gbms_msc_temp_idx(const struct gbms_chg_profile *profile, int temp)
+{
+ int temp_idx = 0;
+
+ while (temp_idx < profile->temp_nb_limits - 1 &&
+ temp >= profile->temp_limits[temp_idx + 1])
+ temp_idx++;
+
+ return temp_idx;
+}
+
+/* Compute the step index given the battery voltage
+ * When selecting an index need to make sure that headroom for the tier voltage
+ * will allow to send to the battery _at least_ next tier max FCC current and
+ * well over charge termination current.
+ */
+int gbms_msc_voltage_idx(const struct gbms_chg_profile *profile, int vbatt)
+{
+ int vbatt_idx = 0;
+
+ while (vbatt_idx < profile->volt_nb_limits - 1 &&
+ vbatt > profile->volt_limits[vbatt_idx])
+ vbatt_idx++;
+
+ /* assumes that 3 times the hardware resolution is ok
+ * TODO: make it configurable? tune?
+ */
+ if (vbatt_idx != profile->volt_nb_limits - 1) {
+ const int vt = profile->volt_limits[vbatt_idx];
+ const int headr = profile->fv_uv_resolution * 3;
+
+ if ((vt - vbatt) < headr)
+ vbatt_idx += 1;
+ }
+
+ return vbatt_idx;
+}
+
+uint8_t gbms_gen_chg_flags(int chg_status, int chg_type)
+{
+ uint8_t flags = 0;
+
+ if (chg_status != POWER_SUPPLY_STATUS_DISCHARGING) {
+ flags |= GBMS_CS_FLAG_BUCK_EN;
+
+ /* FULL makes sense only when charging is enabled */
+ if (chg_status == POWER_SUPPLY_STATUS_FULL)
+ flags |= GBMS_CS_FLAG_DONE;
+ }
+ if (chg_type == POWER_SUPPLY_CHARGE_TYPE_FAST)
+ flags |= GBMS_CS_FLAG_CC;
+ if (chg_type == POWER_SUPPLY_CHARGE_TYPE_TAPER)
+ flags |= GBMS_CS_FLAG_CV;
+
+ return flags;
+}
+
+static int gbms_gen_state(union gbms_charger_state *chg_state,
+ struct power_supply *chg_psy)
+{
+ int vchrg, chg_type, chg_status, ioerr;
+
+ /* TODO: if (chg_drv->chg_mode == CHG_DRV_MODE_NOIRDROP) vchrg = 0; */
+ /* Battery needs to know charger voltage and state to run the irdrop
+ * compensation code, can disable here sending a 0 vchgr
+ */
+ vchrg = GPSY_GET_PROP(chg_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ chg_type = GPSY_GET_PROP(chg_psy, POWER_SUPPLY_PROP_CHARGE_TYPE);
+ chg_status = GPSY_GET_INT_PROP(chg_psy, POWER_SUPPLY_PROP_STATUS,
+ &ioerr);
+ if (vchrg < 0 || chg_type < 0 || ioerr < 0) {
+ pr_err("MSC_CHG error vchrg=%d chg_type=%d chg_status=%d\n",
+ vchrg, chg_type, chg_status);
+ return -EINVAL;
+ }
+
+ chg_state->f.chg_status = chg_status;
+ chg_state->f.chg_type = chg_type;
+ chg_state->f.flags = gbms_gen_chg_flags(chg_state->f.chg_status,
+ chg_state->f.chg_type);
+ chg_state->f.vchrg = vchrg / 1000; /* vchrg is in uA, f.vchrg us mA */
+
+ return 0;
+}
+
+/* read or generate charge state */
+int gbms_read_charger_state(union gbms_charger_state *chg_state,
+ struct power_supply *chg_psy)
+{
+ union power_supply_propval val;
+ int ret = 0;
+
+ ret = power_supply_get_property(chg_psy,
+ POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ &val);
+ if (ret == 0) {
+ chg_state->v = val.int64val;
+ } else {
+ int ichg;
+
+ ret = gbms_gen_state(chg_state, chg_psy);
+ if (ret < 0)
+ return ret;
+
+ ichg = GPSY_GET_PROP(chg_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
+ if (ichg > 0)
+ chg_state->f.icl = ichg / 1000;
+
+ pr_info("MSC_CHG chg_state=%lx [0x%x:%d:%d:%d] ichg=%d\n",
+ (unsigned long)chg_state->v,
+ chg_state->f.flags,
+ chg_state->f.chg_type,
+ chg_state->f.chg_status,
+ chg_state->f.vchrg,
+ ichg);
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* convert cycle counts array to string */
+int gbms_cycle_count_cstr_bc(char *buf, size_t size,
+ const u16 *ccount, int bcnt)
+{
+ int len = 0, i;
+
+ for (i = 0; i < bcnt; i++)
+ len += scnprintf(buf + len, size - len, "%d ", ccount[i]);
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+/* parse the result of gbms_cycle_count_cstr_bc() back to array */
+int gbms_cycle_count_sscan_bc(u16 *ccount, int bcnt, const char *buff)
+{
+ int i, val[bcnt];
+
+ /* sscanf has 10 fixed conversions */
+ if (bcnt != 10)
+ return -ERANGE;
+
+ if (sscanf(buff, "%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])
+ != bcnt)
+ return -EINVAL;
+
+ for (i = 0; i < bcnt ; i++)
+ if (val[i] >= 0 && val[i] < U16_MAX)
+ ccount[i] = val[i];
+
+ return 0;
+}
diff --git a/google_bms.h b/google_bms.h
new file mode 100644
index 0000000..3cb507e
--- /dev/null
+++ b/google_bms.h
@@ -0,0 +1,366 @@
+/*
+ * Google Battery Management System
+ *
+ * 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.
+ */
+
+#ifndef __GOOGLE_BMS_H_
+#define __GOOGLE_BMS_H_
+
+#include <linux/types.h>
+#include <linux/usb/pd.h>
+#include <linux/power_supply.h>
+#include "qmath.h"
+#include "gbms_storage.h"
+
+struct device_node;
+
+#define GBMS_CHG_TEMP_NB_LIMITS_MAX 10
+#define GBMS_CHG_VOLT_NB_LIMITS_MAX 5
+
+struct gbms_chg_profile {
+ const char *owner_name;
+
+ int temp_nb_limits;
+ s32 temp_limits[GBMS_CHG_TEMP_NB_LIMITS_MAX];
+ int volt_nb_limits;
+ s32 volt_limits[GBMS_CHG_VOLT_NB_LIMITS_MAX];
+ /* Array of constant current limits */
+ s32 *cccm_limits;
+ /* used to fill table */
+ u32 capacity_ma;
+
+ /* behavior */
+ u32 fv_uv_margin_dpct;
+ u32 cv_range_accuracy;
+ u32 cv_debounce_cnt;
+ u32 cv_update_interval;
+ u32 cv_tier_ov_cnt;
+ u32 cv_tier_switch_cnt;
+ /* taper step */
+ u32 fv_uv_resolution;
+ /* experimental */
+ u32 cv_otv_margin;
+};
+
+#define WLC_BPP_THRESHOLD_UV 700000
+#define WLC_EPP_THRESHOLD_UV 1100000
+
+#define FOREACH_CHG_EV_ADAPTER(S) \
+ S(UNKNOWN), \
+ S(USB), \
+ S(USB_SDP), \
+ S(USB_DCP), \
+ S(USB_CDP), \
+ S(USB_ACA), \
+ S(USB_C), \
+ S(USB_PD), \
+ S(USB_PD_DRP), \
+ S(USB_PD_PPS), \
+ S(USB_BRICKID), \
+ S(USB_HVDCP), \
+ S(USB_HVDCP3), \
+ S(USB_FLOAT), \
+ S(WLC), \
+ S(WLC_EPP), \
+ S(WLC_SPP), \
+
+#define CHG_EV_ADAPTER_STRING(s) #s
+#define _CHG_EV_ADAPTER_PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
+
+/* Enums will start with CHG_EV_ADAPTER_TYPE_ */
+#define CHG_EV_ADAPTER_ENUM(e) \
+ _CHG_EV_ADAPTER_PRIMITIVE_CAT(CHG_EV_ADAPTER_TYPE_,e)
+
+enum chg_ev_adapter_type_t {
+ FOREACH_CHG_EV_ADAPTER(CHG_EV_ADAPTER_ENUM)
+};
+
+enum gbms_msc_states_t {
+ MSC_NONE = 0,
+ MSC_SEED,
+ MSC_DSG,
+ MSC_LAST,
+ MSC_VSWITCH,
+ MSC_VOVER,
+ MSC_PULLBACK,
+ MSC_FAST,
+ MSC_TYPE,
+ MSC_DLY, /* in taper */
+ MSC_STEADY, /* in taper */
+ MSC_TIERCNTING, /* in taper */
+ MSC_RAISE, /* in taper */
+ MSC_WAIT, /* in taper */
+ MSC_RSTC, /* in taper */
+ MSC_NEXT, /* in taper */
+ MSC_NYET, /* in taper */
+ MSC_STATES_COUNT,
+};
+
+union gbms_ce_adapter_details {
+ uint32_t v;
+ struct {
+ uint8_t ad_type;
+ uint8_t pad;
+ uint8_t ad_voltage;
+ uint8_t ad_amperage;
+ };
+};
+
+struct gbms_ce_stats {
+ uint16_t voltage_in;
+ uint16_t ssoc_in;
+ uint16_t cc_in;
+ uint16_t voltage_out;
+ uint16_t ssoc_out;
+ uint16_t cc_out;
+};
+
+struct ttf_tier_stat {
+ int16_t soc_in;
+ int cc_in;
+ int cc_total;
+ time_t avg_time;
+};
+
+struct gbms_ce_tier_stats {
+ int8_t temp_idx;
+ uint8_t vtier_idx;
+
+ int16_t soc_in; /* 8.8 */
+ uint16_t cc_in;
+ uint16_t cc_total;
+
+ uint16_t time_fast;
+ uint16_t time_taper;
+ uint16_t time_other;
+
+ int16_t temp_in;
+ int16_t temp_min;
+ int16_t temp_max;
+
+ int16_t ibatt_min;
+ int16_t ibatt_max;
+
+ uint16_t icl_min;
+ uint16_t icl_max;
+
+ int64_t icl_sum;
+ int64_t temp_sum;
+ int64_t ibatt_sum;
+ uint32_t sample_count;
+
+ uint16_t msc_cnt[MSC_STATES_COUNT];
+ uint32_t msc_elap[MSC_STATES_COUNT];
+};
+
+#define GBMS_STATS_TIER_COUNT 3
+#define GBMS_SOC_STATS_LEN 101
+
+/* time to full */
+
+/* collected in charging event */
+struct ttf_soc_stats {
+ int ti[GBMS_SOC_STATS_LEN]; /* charge tier at each soc */
+ int cc[GBMS_SOC_STATS_LEN]; /* coulomb count at each soc */
+ time_t elap[GBMS_SOC_STATS_LEN]; /* time spent at soc */
+};
+
+/* reference data for soc estimation */
+struct ttf_adapter_stats {
+ u32 *soc_table;
+ u32 *elap_table;
+ int table_count;
+};
+
+/* updated when the device publish the charge stats
+ * NOTE: soc_stats and tier_stats are only valid for the given chg_profile
+ * since tier, coulumb count and elap time spent at each SOC depends on the
+ * maximum amout of current that can be pushed to the battery.
+ */
+struct batt_ttf_stats {
+ time_t ttf_fake;
+
+ struct ttf_soc_stats soc_ref; /* gold: soc->elap,cc */
+ int ref_temp_idx;
+ int ref_watts;
+
+ struct ttf_soc_stats soc_stats; /* rolling */
+ struct ttf_tier_stat tier_stats[GBMS_STATS_TIER_COUNT];
+};
+
+struct gbms_charging_event {
+ union gbms_ce_adapter_details adapter_details;
+
+ /* profile used for this charge event */
+ const struct gbms_chg_profile *chg_profile;
+ /* charge event and tier tracking */
+ struct gbms_ce_stats charging_stats;
+ struct gbms_ce_tier_stats tier_stats[GBMS_STATS_TIER_COUNT];
+ /* soc tracking for time to full */
+ struct ttf_soc_stats soc_stats;
+ int last_soc;
+
+ time_t first_update;
+ time_t last_update;
+ uint32_t chg_sts_qual_time;
+ uint32_t chg_sts_delta_soc;
+};
+
+#define GBMS_CCCM_LIMITS(profile, ti, vi) \
+ profile->cccm_limits[(ti * profile->volt_nb_limits) + vi]
+
+/* newgen charging */
+#define GBMS_CS_FLAG_BUCK_EN (1 << 0)
+#define GBMS_CS_FLAG_DONE (1 << 1)
+#define GBMS_CS_FLAG_CC (1 << 2)
+#define GBMS_CS_FLAG_CV (1 << 3)
+#define GBMS_CS_FLAG_ILIM (1 << 4)
+
+union gbms_charger_state {
+ uint64_t v;
+ struct {
+ uint8_t flags;
+ uint8_t pad;
+ uint8_t chg_status;
+ uint8_t chg_type;
+ uint16_t vchrg;
+ uint16_t icl;
+ } f;
+};
+
+int gbms_init_chg_profile_internal(struct gbms_chg_profile *profile,
+ struct device_node *node, const char *owner_name);
+#define gbms_init_chg_profile(p, n) \
+ gbms_init_chg_profile_internal(p, n, KBUILD_MODNAME)
+
+void gbms_init_chg_table(struct gbms_chg_profile *profile, u32 capacity);
+
+void gbms_free_chg_profile(struct gbms_chg_profile *profile);
+
+void gbms_dump_raw_profile(const struct gbms_chg_profile *profile, int scale);
+#define gbms_dump_chg_profile(profile) gbms_dump_raw_profile(profile, 1000)
+
+/* newgen charging: charge profile */
+int gbms_msc_temp_idx(const struct gbms_chg_profile *profile, int temp);
+int gbms_msc_voltage_idx(const struct gbms_chg_profile *profile, int vbatt);
+int gbms_msc_round_fv_uv(const struct gbms_chg_profile *profile,
+ int vtier, int fv_uv);
+
+/* newgen charging: charger flags */
+uint8_t gbms_gen_chg_flags(int chg_status, int chg_type);
+/* newgen charging: read/gen charger state */
+int gbms_read_charger_state(union gbms_charger_state *chg_state,
+ struct power_supply *chg_psy);
+
+/* debug/print */
+const char *gbms_chg_type_s(int chg_type);
+const char *gbms_chg_status_s(int chg_status);
+const char *gbms_chg_ev_adapter_s(int adapter);
+
+/* Votables */
+#define VOTABLE_MSC_CHG_DISABLE "MSC_CHG_DISABLE"
+#define VOTABLE_MSC_PWR_DISABLE "MSC_PWR_DISABLE"
+#define VOTABLE_MSC_INTERVAL "MSC_INTERVAL"
+#define VOTABLE_MSC_FCC "MSC_FCC"
+#define VOTABLE_MSC_FV "MSC_FV"
+
+/* Binned cycle count */
+#define GBMS_CCBIN_BUCKET_COUNT 10
+#define GBMS_CCBIN_CSTR_SIZE (GBMS_CCBIN_BUCKET_COUNT * 6 + 2)
+
+int gbms_cycle_count_sscan_bc(u16 *ccount, int bcnt, const char *buff);
+int gbms_cycle_count_cstr_bc(char *buff, size_t size,
+ const u16 *ccount, int bcnt);
+
+#define gbms_cycle_count_sscan(cc, buff) \
+ gbms_cycle_count_sscan_bc(cc, GBMS_CCBIN_BUCKET_COUNT, buff)
+
+#define gbms_cycle_count_cstr(buff, size, cc) \
+ gbms_cycle_count_cstr_bc(buff, size, cc, GBMS_CCBIN_BUCKET_COUNT)
+
+
+/* Time to full */
+int ttf_soc_cstr(char *buff, int size, const struct ttf_soc_stats *soc_stats,
+ int start, int end);
+
+int ttf_soc_estimate(time_t *res,
+ const struct batt_ttf_stats *stats,
+ const struct gbms_charging_event *ce_data,
+ qnum_t soc, qnum_t last);
+
+void ttf_soc_init(struct ttf_soc_stats *dst);
+
+int ttf_tier_cstr(char *buff, int size, struct ttf_tier_stat *t_stat);
+
+int ttf_tier_estimate(time_t *res,
+ const struct batt_ttf_stats *ttf_stats,
+ int temp_idx, int vbatt_idx,
+ int capacity, int full_capacity);
+
+int ttf_stats_init(struct batt_ttf_stats *stats,
+ struct device *device,
+ int capacity_ma);
+
+void ttf_stats_update(struct batt_ttf_stats *stats,
+ struct gbms_charging_event *ce_data,
+ bool force);
+
+int ttf_stats_cstr(char *buff, int size, const struct batt_ttf_stats *stats,
+ bool verbose);
+
+int ttf_stats_sscan(struct batt_ttf_stats *stats,
+ const char *buff, size_t size);
+
+struct batt_ttf_stats *ttf_stats_dup(struct batt_ttf_stats *dst,
+ const struct batt_ttf_stats *src);
+
+/* Google battery EEPROM API */
+
+struct nvmem_device;
+
+int gbee_register_device(const char *name, struct nvmem_device *nvram);
+void gbee_destroy_device(void);
+
+/*
+ * Battery history
+ *
+ */
+
+void *batt_hist_init_data(struct device *dev);
+int batt_hist_data_collect(void *h, int cycle_cnt, int idx,
+ struct power_supply *fg_psy);
+void batt_hist_free_data(void *p);
+
+/*
+ * Charger modes
+ *
+ */
+
+enum gbms_charger_modes {
+ GBMS_CHGR_MODE_ALL_OFF = 0x00,
+ GBMS_CHGR_MODE_BUCK_ON = 0x04,
+ GBMS_CHGR_MODE_CHGR_BUCK_ON = 0x05,
+ GBMS_CHGR_MODE_BOOST_UNO_ON = 0x08,
+ GBMS_CHGR_MODE_BOOST_ON = 0x09,
+ GBMS_CHGR_MODE_OTG_BOOST_ON = 0x0a,
+ GBMS_CHGR_MODE_BUCK_BOOST_UNO_ON = 0x0c,
+ GBMS_CHGR_MODE_CHGR_BUCK_BOOST_UNO_ON = 0x0d,
+ GBMS_CHGR_MODE_OTG_BUCK_BOOST_ON = 0x0e,
+ GBMS_CHGR_MODE_CHGR_OTG_BUCK_BOOST_ON = 0x0f,
+ GBMS_CHGR_MODE_CHGR_DC= 0x20,
+};
+
+#define GBMS_MODE_VOTABLE "CHARGER_MODE"
+
+#endif /* __GOOGLE_BMS_H_ */
diff --git a/google_charger.c b/google_charger.c
new file mode 100644
index 0000000..56dbc9c
--- /dev/null
+++ b/google_charger.c
@@ -0,0 +1,2592 @@
+/*
+ * Copyright 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 ": " fmt
+#ifdef CONFIG_PM_SLEEP
+#define SUPPORT_PM_SLEEP 1
+#endif
+
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/thermal.h>
+#include <linux/pm_wakeup.h>
+#include <linux/pmic-voter.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/tcpm.h>
+#include <linux/alarmtimer.h>
+#include "google_bms.h"
+#include "google_dc_pps.h"
+#include "google_psy.h"
+#include "logbuffer.h"
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#endif
+
+/* 200 * 250 = 50 seconds of logging */
+#define CHG_LOG_PSY_RATELIMIT_CNT 200
+#define CHG_DELAY_INIT_MS 250
+#define CHG_DELAY_INIT_DETECT_MS 1000
+
+#define DEFAULT_CHARGE_STOP_LEVEL 100
+#define DEFAULT_CHARGE_START_LEVEL 0
+
+#define CHG_DRV_EAGAIN_RETRIES 3
+#define CHG_WORK_ERROR_RETRY_MS 1000
+
+
+#define CHG_DRV_CC_HW_TOLERANCE_MAX 250
+
+#define CHG_DRV_MODE_NOIRDROP 1
+#define CHG_DRV_MODE_DISABLED 2
+#define CHG_DRV_MODE_NOOP 3
+
+#define DRV_DEFAULTCC_UPDATE_INTERVAL 30000
+#define DRV_DEFAULTCV_UPDATE_INTERVAL 2000
+
+#define MAX_VOTER "MAX_VOTER"
+#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER"
+#define USER_VOTER "USER_VOTER" /* same as QCOM */
+#define MSC_CHG_VOTER "msc_chg"
+#define MSC_CHG_FULL_VOTER "msc_chg_full"
+#define MSC_USER_VOTER "msc_user"
+#define MSC_USER_CHG_LEVEL_VOTER "msc_user_chg_level"
+#define MSC_CHG_TERM_VOTER "msc_chg_term"
+
+#define CHG_TERM_LONG_DELAY_MS 300000 /* 5 min */
+#define CHG_TERM_SHORT_DELAY_MS 60000 /* 1 min */
+#define CHG_TERM_RETRY_MS 2000 /* 2 sec */
+#define CHG_TERM_RETRY_CNT 5
+
+#define FCC_OF_CDEV_NAME "google,charger"
+#define FCC_CDEV_NAME "fcc"
+#define WLC_OF_CDEV_NAME "google,wlc_charger"
+#define WLC_CDEV_NAME "dc_icl"
+
+#define PPS_CC_TOLERANCE_PCT_DEFAULT 5
+#define PPS_CC_TOLERANCE_PCT_MAX 10
+
+#define get_boot_sec() div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC)
+
+struct chg_drv;
+
+enum chg_thermal_devices {
+ CHG_TERMAL_DEVICES_COUNT = 2,
+ CHG_TERMAL_DEVICE_FCC = 0,
+ CHG_TERMAL_DEVICE_DC_IN = 1,
+};
+
+struct chg_thermal_device {
+ struct chg_drv *chg_drv;
+
+ struct thermal_cooling_device *tcd;
+ int *thermal_mitigation;
+ int thermal_levels;
+ int current_level;
+};
+
+struct chg_termination {
+ bool enable;
+ bool alarm_start;
+ struct work_struct work;
+ struct alarm alarm;
+ int cc_full_ref;
+ int retry_cnt;
+ int usb_5v;
+};
+
+/* re-evaluate FCC when switching power supplies */
+static int chg_therm_update_fcc(struct chg_drv *chg_drv);
+static void chg_reset_termination_data(struct chg_drv *chg_drv);
+static int chg_vote_input_suspend(struct chg_drv *chg_drv,
+ char *voter, bool suspend);
+
+struct chg_drv {
+ struct device *device;
+
+ struct power_supply *chg_psy;
+ const char *chg_psy_name;
+ struct power_supply *wlc_psy;
+ const char *wlc_psy_name;
+ struct power_supply *bat_psy;
+ const char *bat_psy_name;
+ struct power_supply *tcpm_psy;
+ const char *tcpm_psy_name;
+ int log_psy_ratelimit;
+
+ struct notifier_block psy_nb;
+ struct delayed_work init_work;
+ struct delayed_work chg_work;
+ struct work_struct chg_psy_work;
+ struct wakeup_source chg_ws;
+ struct alarm chg_wakeup_alarm;
+ u32 tcpm_phandle;
+
+ struct power_supply *usb_psy;
+ const char *usb_psy_name;
+ bool usb_skip_probe;
+
+ /* */
+ struct chg_thermal_device thermal_devices[CHG_TERMAL_DEVICES_COUNT];
+ bool therm_wlc_override_fcc;
+
+ /* */
+ u32 cv_update_interval;
+ u32 cc_update_interval;
+ union gbms_ce_adapter_details adapter_details;
+
+ struct votable *msc_interval_votable;
+ struct votable *msc_fv_votable;
+ struct votable *msc_fcc_votable;
+ struct votable *msc_chg_disable_votable;
+ struct votable *msc_pwr_disable_votable;
+ struct votable *usb_icl_votable;
+ struct votable *dc_suspend_votable;
+ struct votable *dc_icl_votable;
+
+ bool batt_present;
+ bool dead_battery;
+ int batt_profile_fcc_ua; /* max/default fcc */
+ int batt_profile_fv_uv; /* max/default fv_uv */
+ int fv_uv;
+ int cc_max;
+ int chg_cc_tolerance;
+ int chg_mode; /* debug */
+ int stop_charging; /* no power source */
+ int egain_retries;
+
+ /* retail */
+ int disable_charging; /* from retail */
+ int disable_pwrsrc; /* from retail */
+ bool lowerdb_reached; /* user charge level */
+ int charge_stop_level; /* user charge level */
+ int charge_start_level; /* user charge level */
+
+ /* pps charging */
+ struct pd_pps_data pps_data;
+ unsigned int pps_cc_tolerance_pct;
+ union gbms_charger_state chg_state;
+
+ /* override voltage and current */
+ bool enable_user_fcc_fv;
+ int user_fv_uv;
+ int user_cc_max;
+ int user_interval;
+
+ /* prevent overcharge */
+ struct chg_termination chg_term;
+
+ /* debug */
+ struct dentry *debug_entry;
+};
+
+static void reschedule_chg_work(struct chg_drv *chg_drv)
+{
+ cancel_delayed_work_sync(&chg_drv->chg_work);
+ schedule_delayed_work(&chg_drv->chg_work, 0);
+}
+
+static enum alarmtimer_restart
+google_chg_alarm_handler(struct alarm *alarm, ktime_t time)
+{
+ struct chg_drv *chg_drv =
+ container_of(alarm, struct chg_drv, chg_wakeup_alarm);
+
+ __pm_stay_awake(&chg_drv->chg_ws);
+
+ schedule_delayed_work(&chg_drv->chg_work, 0);
+
+ return ALARMTIMER_NORESTART;
+}
+
+static void chg_psy_work(struct work_struct *work)
+{
+ struct chg_drv *chg_drv =
+ container_of(work, struct chg_drv, chg_psy_work);
+ reschedule_chg_work(chg_drv);
+}
+
+/* cannot block: run in atomic context when called from chg_psy_changed() */
+static int chg_psy_changed(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct power_supply *psy = data;
+ struct chg_drv *chg_drv = container_of(nb, struct chg_drv, psy_nb);
+
+ pr_debug("name=%s evt=%lu\n", psy->desc->name, action);
+
+ if ((action != PSY_EVENT_PROP_CHANGED) ||
+ (psy == NULL) || (psy->desc == NULL) || (psy->desc->name == NULL))
+ return NOTIFY_OK;
+
+ if (action == PSY_EVENT_PROP_CHANGED &&
+ (!strcmp(psy->desc->name, chg_drv->chg_psy_name) ||
+ !strcmp(psy->desc->name, chg_drv->bat_psy_name) ||
+ (chg_drv->usb_psy_name &&
+ !strcmp(psy->desc->name, chg_drv->usb_psy_name)) ||
+ (chg_drv->tcpm_psy_name &&
+ !strcmp(psy->desc->name, chg_drv->tcpm_psy_name)) ||
+ (chg_drv->wlc_psy_name &&
+ !strcmp(psy->desc->name, chg_drv->wlc_psy_name)))) {
+ schedule_work(&chg_drv->chg_psy_work);
+ }
+ return NOTIFY_OK;
+}
+
+#if 0
+static char *psy_usb_type_str[] = {
+ "Unknown", "Battery", "UPS", "Mains", "USB",
+ "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
+ "USB_PD", "USB_PD_DRP", "BrickID",
+ "USB_HVDCP", "USB_HVDCP_3", "Wireless", "USB_FLOAT",
+ "BMS", "Parallel", "Main", "Wipower", "USB_C_UFP", "USB_C_DFP",
+};
+#endif
+
+static char *psy_usb_type_str[] = {
+ [POWER_SUPPLY_USB_TYPE_UNKNOWN] = "Unknown",
+ [POWER_SUPPLY_USB_TYPE_SDP] = "USB",
+ [POWER_SUPPLY_USB_TYPE_DCP] = "USB_DCP",
+ [POWER_SUPPLY_USB_TYPE_CDP] = "USB_CDP",
+ [POWER_SUPPLY_USB_TYPE_ACA] = "USB_ACA",
+ [POWER_SUPPLY_USB_TYPE_C] = "USB_C",
+ [POWER_SUPPLY_USB_TYPE_PD] = "USB_PD",
+ [POWER_SUPPLY_USB_TYPE_PD_DRP] = "USB_PD_DRP",
+ [POWER_SUPPLY_USB_TYPE_PD_PPS] = "PPS",
+ [POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID] = "BrickID",
+};
+
+static char *psy_usbc_type_str[] = {
+ "Unknown", "SDP", "DCP", "CDP", "ACA", "C",
+ "PD", "PD_DRP", "PD_PPS", "BrickID"
+};
+
+/* called on google_charger_init_work() and on every disconnect */
+static inline void chg_init_state(struct chg_drv *chg_drv)
+{
+ /* reset retail state */
+ chg_drv->disable_charging = -1;
+ chg_drv->disable_pwrsrc = -1;
+ chg_drv->lowerdb_reached = true;
+
+ /* reset charging parameters */
+ chg_drv->fv_uv = -1;
+ chg_drv->cc_max = -1;
+ chg_drv->chg_state.v = 0;
+ vote(chg_drv->msc_fv_votable, MSC_CHG_VOTER, false, chg_drv->fv_uv);
+ vote(chg_drv->msc_fcc_votable, MSC_CHG_VOTER, false, chg_drv->cc_max);
+ chg_drv->egain_retries = 0;
+
+ /* reset and re-enable PPS detection */
+ pps_init_state(&chg_drv->pps_data);
+ if (chg_drv->pps_data.nr_snk_pdo)
+ chg_drv->pps_data.stage = PPS_NONE;
+}
+
+/* NOTE: doesn't reset chg_drv->adapter_details.v = 0 see chg_work() */
+static inline void chg_reset_state(struct chg_drv *chg_drv)
+{
+ union gbms_charger_state chg_state = { .v = 0 };
+
+ chg_init_state(chg_drv);
+
+ if (chg_drv->chg_term.enable)
+ chg_reset_termination_data(chg_drv);
+
+ if (!pps_is_disabled(chg_drv->pps_data.stage)) {
+ unsigned int nr_pdo = chg_drv->pps_data.default_pps_pdo ?
+ PDO_PPS : PDO_FIXED_HIGH_VOLTAGE;
+
+ chg_update_capability(chg_drv->tcpm_psy, nr_pdo,
+ chg_drv->pps_data.default_pps_pdo);
+ }
+
+ if (chg_drv->chg_term.usb_5v == 1)
+ chg_drv->chg_term.usb_5v = 0;
+
+ /* TODO: handle interaction with PPS code */
+ vote(chg_drv->msc_interval_votable, CHG_PPS_VOTER, false, 0);
+ /* when/if enabled */
+ GPSY_SET_PROP(chg_drv->chg_psy,
+ POWER_SUPPLY_PROP_TAPER_CONTROL,
+ POWER_SUPPLY_TAPER_CONTROL_OFF);
+ /* make sure the battery knows that it's disconnected */
+ GPSY_SET_INT64_PROP(chg_drv->bat_psy,
+ POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ chg_state.v);
+}
+
+static int info_usb_ad_type(int usb_type, int usbc_type)
+{
+ switch (usb_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ return CHG_EV_ADAPTER_TYPE_USB_SDP;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ return CHG_EV_ADAPTER_TYPE_USB_CDP;
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ return CHG_EV_ADAPTER_TYPE_USB_DCP;
+ case POWER_SUPPLY_TYPE_USB_PD:
+ return (usbc_type == POWER_SUPPLY_USB_TYPE_PD_PPS) ?
+ CHG_EV_ADAPTER_TYPE_USB_PD_PPS :
+ CHG_EV_ADAPTER_TYPE_USB_PD;
+ case POWER_SUPPLY_TYPE_USB_FLOAT:
+ return CHG_EV_ADAPTER_TYPE_USB_FLOAT;
+ default:
+ return CHG_EV_ADAPTER_TYPE_USB;
+ }
+}
+
+static int info_usb_state(union gbms_ce_adapter_details *ad,
+ struct power_supply *usb_psy,
+ struct power_supply *tcpm_psy)
+{
+ const char *usb_type_str = psy_usb_type_str[0];
+ int usb_type, voltage_max = -1, amperage_max = -1;
+ int usbc_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+
+ if (usb_psy) {
+ int voltage_now, current_now;
+
+ /* TODO: handle POWER_SUPPLY_PROP_REAL_TYPE in qc-compat */
+ usb_type = GPSY_GET_PROP(usb_psy, POWER_SUPPLY_PROP_USB_TYPE);
+ if (tcpm_psy)
+ usbc_type = GPSY_GET_PROP(tcpm_psy,
+ POWER_SUPPLY_PROP_USB_TYPE);
+
+ voltage_max = GPSY_GET_PROP(usb_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX);
+ amperage_max = GPSY_GET_PROP(usb_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX);
+ voltage_now = GPSY_GET_PROP(usb_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ current_now = GPSY_GET_PROP(usb_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_NOW);
+
+ if (usb_type < ARRAY_SIZE(psy_usb_type_str))
+ usb_type_str = psy_usb_type_str[usb_type];
+
+ pr_info("usbchg=%s typec=%s usbv=%d usbc=%d usbMv=%d usbMc=%d\n",
+ usb_type_str,
+ tcpm_psy ? psy_usbc_type_str[usbc_type] : "null",
+ voltage_now / 1000,
+ current_now / 1000,
+ voltage_max / 1000,
+ amperage_max / 1000);
+ }
+
+ ad->ad_voltage = (voltage_max < 0) ? voltage_max
+ : voltage_max / 100000;
+ ad->ad_amperage = (amperage_max < 0) ? amperage_max
+ : amperage_max / 100000;
+
+ if (voltage_max < 0 || amperage_max < 0) {
+ ad->ad_type = CHG_EV_ADAPTER_TYPE_UNKNOWN;
+ return -EINVAL;
+ }
+
+ ad->ad_type = info_usb_ad_type(usb_type, usbc_type);
+
+ return 0;
+}
+
+static int info_wlc_state(union gbms_ce_adapter_details *ad,
+ struct power_supply *wlc_psy)
+{
+ int voltage_max, amperage_max;
+
+ voltage_max = GPSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX);
+ amperage_max = GPSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_CURRENT_MAX);
+
+ pr_info("wlcv=%d wlcc=%d wlcMv=%d wlcMc=%d wlct=%d\n",
+ GPSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW) / 1000,
+ GPSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_CURRENT_NOW) / 1000,
+ voltage_max / 1000,
+ amperage_max / 1000,
+ GPSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_TEMP));
+
+ if (voltage_max < 0 || amperage_max < 0) {
+ ad->ad_type = CHG_EV_ADAPTER_TYPE_UNKNOWN;
+ ad->ad_voltage = voltage_max;
+ ad->ad_amperage = amperage_max;
+ return -EINVAL;
+ }
+
+ if (amperage_max >= WLC_EPP_THRESHOLD_UV) {
+ ad->ad_type = CHG_EV_ADAPTER_TYPE_WLC_EPP;
+ } else if (amperage_max >= WLC_BPP_THRESHOLD_UV) {
+ ad->ad_type = CHG_EV_ADAPTER_TYPE_WLC_SPP;
+ }
+
+ ad->ad_voltage = voltage_max / 100000;
+ ad->ad_amperage = amperage_max / 100000;
+
+ return 0;
+}
+
+/* NOTE: do not call this directly */
+static int chg_set_charger(struct power_supply *chg_psy, int fv_uv, int cc_max)
+{
+ int rc;
+
+ /* TAPER CONTROL is in the charger */
+ rc = GPSY_SET_PROP(chg_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, cc_max);
+ if (rc != 0) {
+ pr_err("MSC_CHG cannot set charging current rc=%d\n", rc);
+ return -EIO;
+ }
+
+ rc = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, fv_uv);
+ if (rc != 0) {
+ pr_err("MSC_CHG cannot set float voltage rc=%d\n", rc);
+ return -EIO;
+ }
+
+ return rc;
+}
+
+static int chg_update_charger(struct chg_drv *chg_drv, int fv_uv, int cc_max)
+{
+ int rc = 0;
+ struct power_supply *chg_psy = chg_drv->chg_psy;
+
+ if (chg_drv->fv_uv != fv_uv || chg_drv->cc_max != cc_max) {
+ int fcc = cc_max;
+
+ /* when set cc_tolerance needs to be applied to everything */
+ if (chg_drv->chg_cc_tolerance)
+ fcc = (cc_max / 1000) *
+ (1000 - chg_drv->chg_cc_tolerance);
+
+ /* TODO:
+ * when cc_max < chg_drv->cc_max, set current, voltage
+ * when cc_max > chg_drv->cc_max, set voltage, current
+ */
+ rc = chg_set_charger(chg_psy, fv_uv, fcc);
+ if (rc == 0) {
+ pr_info("MSC_CHG fv_uv=%d->%d cc_max=%d->%d rc=%d\n",
+ chg_drv->fv_uv, fv_uv,
+ chg_drv->cc_max, cc_max,
+ rc);
+
+ chg_drv->cc_max = cc_max;
+ chg_drv->fv_uv = fv_uv;
+ }
+ }
+
+ return rc;
+}
+
+/* b/117985113 */
+static int chg_usb_online(struct power_supply *usb_psy)
+{
+ int usb_online, mode = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+ int rc = -EINVAL;
+
+ if (!usb_psy)
+ return 1;
+
+#ifdef CONFIG_USB_ONLINE_IS_TYPEC_MODE
+ mode = GPSY_GET_INT_PROP(usb_psy, POWER_SUPPLY_PROP_TYPEC_MODE, &rc);
+ pr_info("TYPEC mode=%d rc=%d\n", mode, rc);
+#else
+ mode = GPSY_GET_INT_PROP(usb_psy, POWER_SUPPLY_PROP_ONLINE, &rc);
+ if (mode)
+ mode = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+#endif
+ switch (mode) {
+ case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+ case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+ case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+ case POWER_SUPPLY_TYPEC_DAM_MEDIUM:
+ usb_online = 1;
+ break;
+ default:
+ usb_online = 0;
+ break;
+ }
+
+ if (rc < 0)
+ pr_info("online mode=%d rc=%d\n", usb_online, rc);
+
+ return usb_online;
+}
+/* returns 1 if charging should be disabled given the current battery capacity
+ * given in percent, return 0 if charging should happen
+ */
+static int chg_work_is_charging_disabled(struct chg_drv *chg_drv, int capacity)
+{
+ int disable_charging = 0;
+ int upperbd = chg_drv->charge_stop_level;
+ int lowerbd = chg_drv->charge_start_level;
+
+ /* disabled */
+ if ((upperbd == DEFAULT_CHARGE_STOP_LEVEL) &&
+ (lowerbd == DEFAULT_CHARGE_START_LEVEL))
+ return 0;
+
+ /* invalid */
+ if ((upperbd < lowerbd) ||
+ (upperbd > DEFAULT_CHARGE_STOP_LEVEL) ||
+ (lowerbd < DEFAULT_CHARGE_START_LEVEL))
+ return 0;
+
+ if (chg_drv->lowerdb_reached && upperbd <= capacity) {
+ pr_info("MSC_CHG lowerbd=%d, upperbd=%d, capacity=%d, lowerdb_reached=1->0, charging off\n",
+ lowerbd, upperbd, capacity);
+ disable_charging = 1;
+ chg_drv->lowerdb_reached = false;
+ } else if (!chg_drv->lowerdb_reached && lowerbd < capacity) {
+ pr_info("MSC_CHG lowerbd=%d, upperbd=%d, capacity=%d, charging off\n",
+ lowerbd, upperbd, capacity);
+ disable_charging = 1;
+ } else if (!chg_drv->lowerdb_reached && capacity <= lowerbd) {
+ pr_info("MSC_CHG lowerbd=%d, upperbd=%d, capacity=%d, lowerdb_reached=0->1, charging on\n",
+ lowerbd, upperbd, capacity);
+ chg_drv->lowerdb_reached = true;
+ } else {
+ pr_info("MSC_CHG lowerbd=%d, upperbd=%d, capacity=%d, charging on\n",
+ lowerbd, upperbd, capacity);
+ }
+
+ return disable_charging;
+}
+
+static void chg_termination_work(struct work_struct *work)
+{
+ struct chg_termination *chg_term =
+ container_of(work, struct chg_termination, work);
+ struct chg_drv *chg_drv =
+ container_of(chg_term, struct chg_drv, chg_term);
+ struct power_supply *bat_psy = chg_drv->bat_psy;
+ int rc, cc, full, delay = CHG_TERM_LONG_DELAY_MS;
+
+ cc = GPSY_GET_INT_PROP(bat_psy, POWER_SUPPLY_PROP_CHARGE_COUNTER, &rc);
+ if (rc == -EAGAIN) {
+ chg_term->retry_cnt++;
+
+ if (chg_term->retry_cnt <= CHG_TERM_RETRY_CNT) {
+ pr_info("Get CHARGE_COUNTER fail, try_cnt=%d, rc=%d\n",
+ chg_term->retry_cnt, rc);
+ /* try again and keep the pm_stay_awake */
+ alarm_start_relative(&chg_term->alarm,
+ ms_to_ktime(CHG_TERM_RETRY_MS));
+ return;
+ } else {
+ goto error;
+ }
+ } else if (rc < 0) {
+ goto error;
+ }
+
+ /* Reset count if read successfully */
+ chg_term->retry_cnt = 0;
+
+ if (chg_term->cc_full_ref == 0)
+ chg_term->cc_full_ref = cc;
+
+ /*
+ * Suspend/Unsuspend USB input to keep cc_soc within the 0.5% to 0.75%
+ * overshoot range of the cc_soc value at termination, to prevent
+ * overcharging.
+ */
+ full = chg_term->cc_full_ref;
+ if ((long)cc < DIV_ROUND_CLOSEST((long)full * 10050, 10000)) {
+ chg_vote_input_suspend(chg_drv, MSC_CHG_TERM_VOTER, false);
+ delay = CHG_TERM_LONG_DELAY_MS;
+ } else if ((long)cc > DIV_ROUND_CLOSEST((long)full * 10075, 10000)) {
+ chg_vote_input_suspend(chg_drv, MSC_CHG_TERM_VOTER, true);
+ delay = CHG_TERM_SHORT_DELAY_MS;
+ }
+
+ pr_info("Prevent overcharge data: cc: %d, cc_full_ref: %d, delay: %d\n",
+ cc, chg_term->cc_full_ref, delay);
+
+ alarm_start_relative(&chg_term->alarm, ms_to_ktime(delay));
+
+ pm_relax(chg_drv->device);
+ return;
+
+error:
+ pr_info("Get CHARGE_COUNTER fail, rc=%d\n", rc);
+ chg_reset_termination_data(chg_drv);
+}
+
+static enum alarmtimer_restart chg_termination_alarm_cb(struct alarm *alarm,
+ ktime_t now)
+{
+ struct chg_termination *chg_term =
+ container_of(alarm, struct chg_termination, alarm);
+ struct chg_drv *chg_drv =
+ container_of(chg_term, struct chg_drv, chg_term);
+
+ pr_info("Prevent overcharge alarm triggered %lld\n",
+ ktime_to_ms(now));
+
+ pm_stay_awake(chg_drv->device);
+ schedule_work(&chg_term->work);
+
+ return ALARMTIMER_NORESTART;
+}
+
+static void chg_reset_termination_data(struct chg_drv *chg_drv)
+{
+ if (!chg_drv->chg_term.alarm_start)
+ return;
+
+ chg_drv->chg_term.alarm_start = false;
+ alarm_cancel(&chg_drv->chg_term.alarm);
+ cancel_work_sync(&chg_drv->chg_term.work);
+ chg_vote_input_suspend(chg_drv, MSC_CHG_TERM_VOTER, false);
+ chg_drv->chg_term.cc_full_ref = 0;
+ chg_drv->chg_term.retry_cnt = 0;
+ pm_relax(chg_drv->device);
+}
+
+static void chg_eval_chg_termination(struct chg_termination *chg_term)
+{
+ if (chg_term->alarm_start)
+ return;
+
+ /*
+ * Post charge termination, switch to BSM mode triggers the risk of
+ * over charging as BATFET opening may take some time post the necessity
+ * of staying in supplemental mode, leading to unintended charging of
+ * battery. Trigger the function once charging is completed
+ * to prevent overcharing.
+ */
+ alarm_start_relative(&chg_term->alarm,
+ ms_to_ktime(CHG_TERM_LONG_DELAY_MS));
+ chg_term->alarm_start = true;
+ chg_term->cc_full_ref = 0;
+ chg_term->retry_cnt = 0;
+}
+
+static int chg_work_batt_roundtrip(const union gbms_charger_state *chg_state,
+ struct power_supply *bat_psy,
+ int *fv_uv, int *cc_max)
+{
+ int rc;
+
+ rc = GPSY_SET_INT64_PROP(bat_psy,
+ POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ chg_state->v);
+ if (rc == -EAGAIN) {
+ return -EAGAIN;
+ } else if (rc < 0) {
+ pr_err("MSC_CHG error cannot set CHARGE_CHARGER_STATE rc=%d\n",
+ rc);
+ return -EINVAL;
+ }
+
+ /* battery can return negative values for cc_max and fv_uv. */
+ *cc_max = GPSY_GET_INT_PROP(bat_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ &rc);
+ if (rc < 0) {
+ pr_err("MSC_CHG error reading cc_max (%d)\n", rc);
+ return -EIO;
+ }
+
+ /* ASSERT: (chg_state.f.flags&GBMS_CS_FLAG_DONE) && cc_max == 0 */
+
+ *fv_uv = GPSY_GET_INT_PROP(bat_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ &rc);
+ if (rc < 0) {
+ pr_err("MSC_CHG error reading fv_uv (%d)\n", rc);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/* 0 stop charging, positive keep going */
+static int chg_work_next_interval(const struct chg_drv *chg_drv,
+ union gbms_charger_state *chg_state)
+{
+ int update_interval = chg_drv->cc_update_interval;
+
+ switch (chg_state->f.chg_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ update_interval = 0;
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ update_interval = chg_drv->cv_update_interval;
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ /* DISCHARGING only when not connected -> stop charging */
+ update_interval = 0;
+ break;
+ case POWER_SUPPLY_STATUS_UNKNOWN:
+ update_interval = chg_drv->cv_update_interval;
+ break;
+ default:
+ pr_err("invalid charging status %d\n", chg_state->f.chg_status);
+ update_interval = chg_drv->cv_update_interval;
+ break;
+ }
+
+ return update_interval;
+}
+
+static void chg_work_adapter_details(union gbms_ce_adapter_details *ad,
+ int usb_online, int wlc_online,
+ struct chg_drv *chg_drv)
+{
+ /* print adapter details, route after at the end */
+ if (wlc_online)
+ (void)info_wlc_state(ad, chg_drv->wlc_psy);
+ if (usb_online)
+ (void)info_usb_state(ad, chg_drv->usb_psy, chg_drv->tcpm_psy);
+}
+
+/* not executed when battery is NOT present */
+static int chg_work_roundtrip(struct chg_drv *chg_drv,
+ union gbms_charger_state *chg_state)
+{
+ int fv_uv = -1, cc_max = -1, update_interval, rc;
+
+ rc = gbms_read_charger_state(chg_state, chg_drv->chg_psy);
+ if (rc < 0)
+ return rc;
+
+ /* NOIRDROP is default for remote sensing */
+ if (chg_drv->chg_mode == CHG_DRV_MODE_NOIRDROP)
+ chg_state->f.vchrg = 0;
+
+ /* might return negative values in fv_uv and cc_max */
+ rc = chg_work_batt_roundtrip(chg_state, chg_drv->bat_psy,
+ &fv_uv, &cc_max);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * on fv_uv < 0 (eg JEITA tripped) in the middle of charging keep
+ * charging voltage steady. fv_uv will get the max value (if set in
+ * device tree) if routtrip return a negative value on connect.
+ * Sanity check on current make sure that cc_max doesn't jump to max.
+ */
+ if (fv_uv < 0)
+ fv_uv = chg_drv->fv_uv;
+ if (cc_max < 0)
+ cc_max = chg_drv->cc_max;
+
+ /*
+ * battery has already voted on these with MSC_LOGIC
+ * TODO: could use fv_uv<0 to enable/disable a safe charge voltage
+ * TODO: could use cc_max<0 to enable/disable a safe charge current
+ */
+ vote(chg_drv->msc_fv_votable, MSC_CHG_VOTER,
+ (chg_drv->user_fv_uv == -1) && (fv_uv > 0), fv_uv);
+ vote(chg_drv->msc_fcc_votable, MSC_CHG_VOTER,
+ (chg_drv->user_cc_max == -1) && (cc_max >= 0), cc_max);
+
+ /* update_interval <= 0 means stop charging */
+ update_interval = chg_work_next_interval(chg_drv, chg_state);
+ if (update_interval <= 0)
+ return update_interval;
+
+ /* no ping will cause timeout when pps_is_disabled() */
+ if (chg_drv->tcpm_psy && !pps_is_disabled(chg_drv->pps_data.stage)) {
+ int pps_ui;
+
+ pps_ui = pps_work(&chg_drv->pps_data, chg_drv->tcpm_psy);
+ if (pps_ui < 0)
+ pps_ui = MSEC_PER_SEC;
+
+ vote(chg_drv->msc_interval_votable,
+ CHG_PPS_VOTER,
+ (pps_ui != 0),
+ pps_ui);
+ }
+
+ /*
+ * update_interval <= 0 means stop charging
+ * NOTE: pps code moved to msc_update_charger_cb()
+ */
+ return update_interval;
+}
+
+/* true if still in dead battery */
+#define DEAD_BATTERY_DEADLINE_SEC (45 * 60)
+
+static bool chg_update_dead_battery(const struct chg_drv *chg_drv)
+{
+ int dead = 0;
+ const time_t uptime = get_boot_sec();
+
+ if (uptime < DEAD_BATTERY_DEADLINE_SEC)
+ dead = GPSY_GET_PROP(chg_drv->bat_psy,
+ POWER_SUPPLY_PROP_DEAD_BATTERY);
+ if (dead == 0 && chg_drv->usb_psy) {
+ dead = GPSY_SET_PROP(chg_drv->usb_psy,
+ POWER_SUPPLY_PROP_DEAD_BATTERY, 0);
+ if (dead == 0)
+ pr_info("dead battery cleared uptime=%ld\n", uptime);
+ }
+
+ return (dead != 0);
+}
+
+static int chg_work_read_soc(struct power_supply *bat_psy, int *soc)
+{
+ union power_supply_propval val;
+ int ret = 0;
+
+ ret = power_supply_get_property(bat_psy,
+ POWER_SUPPLY_PROP_CAPACITY,
+ &val);
+ if (ret == 0)
+ *soc = val.intval;
+
+ return ret;
+}
+
+/* No op on battery not present */
+static void chg_work(struct work_struct *work)
+{
+ struct chg_drv *chg_drv =
+ container_of(work, struct chg_drv, chg_work.work);
+ struct power_supply *usb_psy = chg_drv->usb_psy;
+ struct power_supply *wlc_psy = chg_drv->wlc_psy;
+ struct power_supply *bat_psy = chg_drv->bat_psy;
+ union gbms_ce_adapter_details ad = { .v = 0 };
+ int soc, disable_charging = 0, disable_pwrsrc = 0;
+ int usb_online, wlc_online = 0;
+ int update_interval = -1;
+ bool chg_done = false;
+ int success, rc = 0;
+
+ __pm_stay_awake(&chg_drv->chg_ws);
+ pr_debug("battery charging work item\n");
+
+ if (!chg_drv->batt_present) {
+ /* -EGAIN = NOT ready, <0 don't know yet */
+ rc = GPSY_GET_PROP(bat_psy, POWER_SUPPLY_PROP_PRESENT);
+ if (rc < 0)
+ goto rerun_error;
+
+ chg_drv->batt_present = (rc > 0);
+ if (!chg_drv->batt_present)
+ goto exit_chg_work;
+
+ pr_info("MSC_CHG battery present\n");
+ }
+
+ if (chg_drv->dead_battery)
+ chg_drv->dead_battery = chg_update_dead_battery(chg_drv);
+
+ if (chg_drv->chg_mode == CHG_DRV_MODE_DISABLED)
+ goto exit_skip;
+
+ /* cause msc_update_charger_cb to ignore updates */
+ vote(chg_drv->msc_interval_votable, MSC_CHG_VOTER, true, 0);
+
+ usb_online = chg_usb_online(usb_psy);
+ if (wlc_psy)
+ wlc_online = GPSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_ONLINE);
+
+ if (usb_online < 0 || wlc_online < 0) {
+ pr_err("MSC_CHG error reading usb=%d wlc=%d\n",
+ usb_online, wlc_online);
+
+ /* TODO: maybe disable charging when this happens? */
+ goto rerun_error;
+ } else if (!usb_online && !wlc_online) {
+
+ if (chg_drv->stop_charging != 1) {
+ pr_info("MSC_CHG no power source, disabling charging\n");
+
+ vote(chg_drv->msc_chg_disable_votable,
+ MSC_CHG_VOTER, true, 0);
+
+ chg_reset_state(chg_drv);
+ chg_drv->stop_charging = 1;
+ }
+
+ goto exit_chg_work;
+ } else if (chg_drv->stop_charging != 0) {
+ /* will re-enable charging after setting FCC,CC_MAX */
+
+ if (chg_drv->therm_wlc_override_fcc)
+ (void)chg_therm_update_fcc(chg_drv);
+ }
+
+ /* retry for max CHG_DRV_EGAIN_RETRIES * CHG_WORK_ERROR_RETRY_MS on
+ * -EGAIN (race on wake). Retry is silent until we exceed the
+ * threshold, ->egain_retries is reset on every wakeup.
+ * NOTE: -EGAIN after this should be flagged
+ */
+ rc = chg_work_read_soc(bat_psy, &soc);
+ if (rc == -EAGAIN) {
+ chg_drv->egain_retries += 1;
+ if (chg_drv->egain_retries < CHG_DRV_EAGAIN_RETRIES)
+ goto rerun_error;
+ }
+
+ if (rc < 0) {
+ /* update_interval = -1, will reschedule */
+ pr_err("MSC_CHG cannot get capacity (%d)\n", rc);
+ goto update_charger;
+ }
+
+ chg_drv->egain_retries = 0;
+
+ /* fast drain to the stop level */
+ disable_charging = chg_work_is_charging_disabled(chg_drv, soc);
+ if (disable_charging && soc > chg_drv->charge_stop_level)
+ disable_pwrsrc = 1;
+ else
+ disable_pwrsrc = 0;
+
+ /* disable charging is set in retail mode */
+ if (disable_charging != chg_drv->disable_charging) {
+ pr_info("MSC_CHG disable_charging %d -> %d",
+ chg_drv->disable_charging, disable_charging);
+
+ /* voted but not applied since msc_interval_votable <= 0 */
+ vote(chg_drv->msc_fcc_votable,
+ MSC_USER_CHG_LEVEL_VOTER,
+ disable_charging != 0, 0);
+ }
+ chg_drv->disable_charging = disable_charging;
+
+ /* when disable_pwrsrc is set, disable_charging is set also */
+ if (disable_pwrsrc != chg_drv->disable_pwrsrc) {
+ pr_info("MSC_CHG disable_pwrsrc %d -> %d",
+ chg_drv->disable_pwrsrc, disable_pwrsrc);
+
+ /* applied right away */
+ vote(chg_drv->msc_pwr_disable_votable,
+ MSC_USER_CHG_LEVEL_VOTER,
+ disable_pwrsrc != 0, 0);
+ }
+ chg_drv->disable_pwrsrc = disable_pwrsrc;
+
+ /* make sure ->fv_uv and ->cc_max are always correct */
+ chg_work_adapter_details(&ad, usb_online, wlc_online, chg_drv);
+
+ /* determine the next update interval */
+ update_interval = chg_work_roundtrip(chg_drv, &chg_drv->chg_state);
+ if (update_interval >= 0)
+ chg_done = !!(chg_drv->chg_state.f.flags & GBMS_CS_FLAG_DONE);
+
+ /* update_interval=0 when disconnected or on EOC (check for races)
+ * update_interval=-1 on an error (from roundtrip or reading soc)
+ * NOTE: might have cc_max==0 from the roundtrip on JEITA
+ */
+update_charger:
+ if (!disable_charging && update_interval > 0) {
+
+ /* msc_update_charger_cb will write to charger and reschedule */
+ vote(chg_drv->msc_interval_votable,
+ MSC_CHG_VOTER, true,
+ update_interval);
+
+ if (chg_drv->stop_charging != 0) {
+ pr_info("MSC_CHG power source usb=%d wlc=%d, enabling charging\n",
+ usb_online, wlc_online);
+
+ vote(chg_drv->msc_chg_disable_votable,
+ MSC_CHG_VOTER, false, 0);
+ chg_drv->stop_charging = 0;
+ }
+ } else {
+ int res;
+
+ /* connected but needs to disable_charging */
+ res = chg_update_charger(chg_drv, chg_drv->fv_uv, 0);
+ if (res < 0)
+ pr_err("MSC_CHG cannot update charger (%d)\n", res);
+ if (res < 0 || rc < 0 || update_interval < 0)
+ goto rerun_error;
+
+ }
+
+ /* tied to the charger: could tie to battery @ 100% instead */
+ if ((chg_drv->chg_term.usb_5v == 0) && chg_done) {
+ pr_info("MSC_CHG switch to 5V on full\n");
+ chg_update_capability(chg_drv->tcpm_psy, PDO_FIXED_5V, 0);
+ chg_drv->chg_term.usb_5v = 1;
+ } else if (chg_drv->pps_data.stage == PPS_ACTIVE && chg_done) {
+ pr_info("MSC_CHG switch to Fixed Profile on full\n");
+ chg_drv->pps_data.stage = PPS_DISABLED;
+ chg_update_capability(chg_drv->tcpm_psy, PDO_FIXED_HIGH_VOLTAGE,
+ 0);
+ }
+
+ /* WAR: battery overcharge on a weak adapter */
+ if (chg_drv->chg_term.enable && chg_done && (soc == 100))
+ chg_eval_chg_termination(&chg_drv->chg_term);
+
+ goto exit_chg_work;
+
+rerun_error:
+ success = schedule_delayed_work(&chg_drv->chg_work,
+ CHG_WORK_ERROR_RETRY_MS);
+
+ /* no need to reschedule the pending after an error
+ * NOTE: rc is the return code from battery properties
+ */
+ if (rc != -EAGAIN)
+ pr_err("MSC_CHG error rerun=%d in %d ms (%d)\n",
+ success, CHG_WORK_ERROR_RETRY_MS, rc);
+
+ /*
+ * If stay_awake is false, we are safe to ping the adapter
+ * NOTE: this probably needs to be changed to pps_keep_alive()
+ */
+ if (!chg_drv->pps_data.stay_awake &&
+ chg_drv->pps_data.stage == PPS_ACTIVE)
+ pps_ping(&chg_drv->pps_data, chg_drv->tcpm_psy);
+
+ pr_debug("chg_work reschedule\n");
+ return;
+
+exit_chg_work:
+ /* Route adapter details after the roundtrip since google_battery
+ * might overwrite the value when it starts a new cycle.
+ * NOTE: chg_reset_state() must not set chg_drv->adapter_details.v
+ * to zero. Fix the odd dependency when handling failure in setting
+ * POWER_SUPPLY_PROP_ADAPTER_DETAILS.
+ */
+ if (rc == 0 && ad.v != chg_drv->adapter_details.v) {
+
+ rc = GPSY_SET_PROP(chg_drv->bat_psy,
+ POWER_SUPPLY_PROP_ADAPTER_DETAILS,
+ (int)ad.v);
+
+ /* TODO: handle failure rescheduling chg_work */
+ if (rc < 0)
+ pr_err("MSC_CHG no adapter details (%d)\n", rc);
+ else
+ chg_drv->adapter_details.v = ad.v;
+ }
+
+exit_skip:
+ pr_debug("chg_work done\n");
+ __pm_relax(&chg_drv->chg_ws);
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* return negative when using ng charging */
+static int chg_init_chg_profile(struct chg_drv *chg_drv)
+{
+ struct device *dev = chg_drv->device;
+ struct device_node *node = dev->of_node;
+ u32 temp;
+ int ret;
+
+ /* chg_work will use the minimum between all votess */
+ ret = of_property_read_u32(node, "google,cv-update-interval",
+ &chg_drv->cv_update_interval);
+ if (ret < 0 || chg_drv->cv_update_interval == 0)
+ chg_drv->cv_update_interval = DRV_DEFAULTCV_UPDATE_INTERVAL;
+
+ ret = of_property_read_u32(node, "google,cc-update-interval",
+ &chg_drv->cc_update_interval);
+ if (ret < 0 || chg_drv->cc_update_interval == 0)
+ chg_drv->cc_update_interval = DRV_DEFAULTCC_UPDATE_INTERVAL;
+
+ /* when set will reduce cc_max by
+ * cc_max = cc_max * (1000 - chg_cc_tolerance) / 1000;
+ *
+ * this adds a "safety" margin for C rates if the charger doesn't do it.
+ */
+ ret = of_property_read_u32(node, "google,chg-cc-tolerance", &temp);
+ if (ret < 0)
+ chg_drv->chg_cc_tolerance = 0;
+ else if (temp > CHG_DRV_CC_HW_TOLERANCE_MAX)
+ chg_drv->chg_cc_tolerance = CHG_DRV_CC_HW_TOLERANCE_MAX;
+ else
+ chg_drv->chg_cc_tolerance = temp;
+
+ /* max charging current. This will be programmed to the charger when
+ * there is no charge table.
+ */
+ ret = of_property_read_u32(node, "google,fcc-max-ua", &temp);
+ if (ret < 0)
+ chg_drv->batt_profile_fcc_ua = -EINVAL;
+ else
+ chg_drv->batt_profile_fcc_ua = temp;
+
+ /* max and default charging voltage: this is what will be programmed to
+ * the charger when fv_uv is invalid.
+ */
+ ret = of_property_read_u32(node, "google,fv-max-uv", &temp);
+ if (ret < 0)
+ chg_drv->batt_profile_fv_uv = -EINVAL;
+ else
+ chg_drv->batt_profile_fv_uv = temp;
+
+ chg_drv->chg_term.enable =
+ of_property_read_bool(node, "google,chg-termination-enable");
+
+ /* fallback to 5V on charge termination */
+ chg_drv->chg_term.usb_5v =
+ of_property_read_bool(node, "google,chg-termination-5v");
+ if (!chg_drv->chg_term.usb_5v) {
+ chg_drv->chg_term.usb_5v = -1;
+ } else {
+ pr_info("renegotiate on full\n");
+ chg_drv->chg_term.usb_5v = 0;
+ }
+
+ pr_info("charging profile in the battery\n");
+ return 0;
+}
+
+static ssize_t show_charge_stop_level(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct chg_drv *chg_drv = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", chg_drv->charge_stop_level);
+}
+
+static ssize_t set_charge_stop_level(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct chg_drv *chg_drv = dev_get_drvdata(dev);
+ int ret = 0, val;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ if (!chg_drv->bat_psy) {
+ pr_err("chg_drv->bat_psy is not ready");
+ return -ENODATA;
+ }
+
+ if ((val == chg_drv->charge_stop_level) ||
+ (val <= chg_drv->charge_start_level) ||
+ (val > DEFAULT_CHARGE_STOP_LEVEL))
+ return count;
+
+ chg_drv->charge_stop_level = val;
+ if (chg_drv->bat_psy)
+ power_supply_changed(chg_drv->bat_psy);
+
+ return count;
+}
+
+static DEVICE_ATTR(charge_stop_level, 0660, show_charge_stop_level,
+ set_charge_stop_level);
+
+static ssize_t
+show_charge_start_level(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct chg_drv *chg_drv = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", chg_drv->charge_start_level);
+}
+
+static ssize_t set_charge_start_level(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct chg_drv *chg_drv = dev_get_drvdata(dev);
+ int ret = 0, val;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ if (!chg_drv->bat_psy) {
+ pr_err("chg_drv->bat_psy is not ready");
+ return -ENODATA;
+ }
+
+ if ((val == chg_drv->charge_start_level) ||
+ (val >= chg_drv->charge_stop_level) ||
+ (val < DEFAULT_CHARGE_START_LEVEL))
+ return count;
+
+ chg_drv->charge_start_level = val;
+ if (chg_drv->bat_psy)
+ power_supply_changed(chg_drv->bat_psy);
+
+ return count;
+}
+
+static DEVICE_ATTR(charge_start_level, 0660,
+ show_charge_start_level, set_charge_start_level);
+
+/* TODO: now created in qcom code, create in chg_create_votables() */
+static int chg_find_votables(struct chg_drv *chg_drv)
+{
+ if (!chg_drv->usb_icl_votable)
+ chg_drv->usb_icl_votable = find_votable("USB_ICL");
+ if (!chg_drv->dc_suspend_votable)
+ chg_drv->dc_suspend_votable = find_votable("DC_SUSPEND");
+
+ return (!chg_drv->usb_icl_votable || !chg_drv->dc_suspend_votable)
+ ? -EINVAL : 0;
+}
+
+/* input suspend votes 0 ICL and call suspend on DC_ICL */
+static int chg_vote_input_suspend(struct chg_drv *chg_drv,
+ char *voter, bool suspend)
+{
+ int rc;
+
+ if (chg_find_votables(chg_drv) < 0)
+ return -EINVAL;
+
+ rc = vote(chg_drv->usb_icl_votable, voter, suspend, 0);
+ if (rc < 0) {
+ dev_err(chg_drv->device, "Couldn't vote to %s USB rc=%d\n",
+ suspend ? "suspend" : "resume", rc);
+ return rc;
+ }
+
+ rc = vote(chg_drv->dc_suspend_votable, voter, suspend, 0);
+ if (rc < 0) {
+ dev_err(chg_drv->device, "Couldn't vote to %s DC rc=%d\n",
+ suspend ? "suspend" : "resume", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int chg_get_input_suspend(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if (chg_find_votables(chg_drv) < 0)
+ return -EINVAL;
+
+ *val = (get_client_vote(chg_drv->usb_icl_votable, USER_VOTER) == 0)
+ && get_client_vote(chg_drv->dc_suspend_votable, USER_VOTER);
+
+ return 0;
+}
+
+static int chg_set_input_suspend(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+ int rc;
+
+ if (chg_find_votables(chg_drv) < 0)
+ return -EINVAL;
+
+ rc = chg_vote_input_suspend(chg_drv, USER_VOTER, val != 0);
+
+ if (chg_drv->chg_psy)
+ power_supply_changed(chg_drv->chg_psy);
+
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(chg_is_fops, chg_get_input_suspend,
+ chg_set_input_suspend, "%llu\n");
+
+
+static int chg_get_chg_suspend(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if (!chg_drv->msc_fcc_votable)
+ return -EINVAL;
+
+ /* can also set POWER_SUPPLY_PROP_CHARGE_DISABLE to charger */
+ *val = get_client_vote(chg_drv->msc_fcc_votable, USER_VOTER) == 0;
+
+ return 0;
+}
+
+static int chg_set_chg_suspend(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+ int rc;
+
+ if (!chg_drv->msc_fcc_votable)
+ return -EINVAL;
+
+ /* can also set POWER_SUPPLY_PROP_CHARGE_DISABLE to charger */
+ rc = vote(chg_drv->msc_fcc_votable, USER_VOTER, val != 0, 0);
+ if (rc < 0) {
+ dev_err(chg_drv->device,
+ "Couldn't vote %s to chg_suspend rc=%d\n",
+ val ? "suspend" : "resume", rc);
+ return rc;
+ }
+
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(chg_cs_fops, chg_get_chg_suspend,
+ chg_set_chg_suspend, "%llu\n");
+
+
+static int chg_get_update_interval(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if (!chg_drv->msc_interval_votable)
+ return -EINVAL;
+
+ /* can also set POWER_SUPPLY_PROP_CHARGE_DISABLE to charger */
+ *val = get_client_vote(chg_drv->msc_interval_votable, USER_VOTER) == 0;
+
+ return 0;
+}
+
+static int chg_set_update_interval(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+ int rc;
+
+ if (val < 0)
+ return -ERANGE;
+ if (!chg_drv->msc_interval_votable)
+ return -EINVAL;
+
+ /* can also set POWER_SUPPLY_PROP_CHARGE_DISABLE to charger */
+ rc = vote(chg_drv->msc_interval_votable, USER_VOTER, val, 0);
+ if (rc < 0) {
+ dev_err(chg_drv->device,
+ "Couldn't vote %d to update_interval rc=%d\n",
+ val, rc);
+ return rc;
+ }
+
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(chg_ui_fops, chg_get_update_interval,
+ chg_set_update_interval, "%llu\n");
+
+
+/* use qcom VS maxim fg and more... */
+static int get_chg_mode(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ *val = chg_drv->chg_mode;
+ return 0;
+}
+
+static int set_chg_mode(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ chg_drv->chg_mode = val;
+ reschedule_chg_work(chg_drv);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(chg_mode_fops, get_chg_mode, set_chg_mode, "%llu\n");
+
+static int chg_get_fv_uv(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ *val = chg_drv->user_fv_uv;
+ return 0;
+}
+
+static int chg_set_fv_uv(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if ((((int)val < -1)) || ((chg_drv->batt_profile_fv_uv > 0) &&
+ (val > chg_drv->batt_profile_fv_uv)))
+ return -ERANGE;
+ if (chg_drv->user_fv_uv == val)
+ return 0;
+
+ vote(chg_drv->msc_fv_votable, MSC_USER_VOTER, (val > 0), val);
+ chg_drv->user_fv_uv = val;
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fv_uv_fops, chg_get_fv_uv,
+ chg_set_fv_uv, "%d\n");
+
+static int chg_get_cc_max(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ *val = chg_drv->user_cc_max;
+ return 0;
+}
+
+static int chg_set_cc_max(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if ((((int)val < -1)) || ((chg_drv->batt_profile_fcc_ua > 0) &&
+ (val > chg_drv->batt_profile_fcc_ua)))
+ return -ERANGE;
+ if (chg_drv->user_cc_max == val)
+ return 0;
+
+ vote(chg_drv->msc_fcc_votable, MSC_USER_VOTER, (val >= 0), val);
+ chg_drv->user_cc_max = val;
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(cc_max_fops, chg_get_cc_max,
+ chg_set_cc_max, "%d\n");
+
+
+static int chg_get_interval(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ *val = chg_drv->user_interval;
+ return 0;
+}
+
+static int chg_set_interval(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if (chg_drv->user_interval == val)
+ return 0;
+
+ vote(chg_drv->msc_interval_votable, MSC_USER_VOTER, (val >= 0), val);
+ chg_drv->user_interval = val;
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(chg_interval_fops,
+ chg_get_interval,
+ chg_set_interval, "%d\n");
+
+
+static int chg_reschedule_work(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ reschedule_chg_work(chg_drv);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(chg_reschedule_work_fops,
+ NULL, chg_reschedule_work, "%d\n");
+
+#endif
+
+static int debug_get_pps_cc_tolerance(void *data, u64 *val)
+{
+ struct chg_drv *chg_drv = data;
+
+ *val = chg_drv->pps_cc_tolerance_pct;
+ return 0;
+}
+
+static int debug_set_pps_cc_tolerance(void *data, u64 val)
+{
+ struct chg_drv *chg_drv = data;
+
+ chg_drv->pps_cc_tolerance_pct = val;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_pps_cc_tolerance_fops,
+ debug_get_pps_cc_tolerance,
+ debug_set_pps_cc_tolerance, "%u\n");
+
+
+
+static int chg_init_fs(struct chg_drv *chg_drv)
+{
+ int ret;
+
+ ret = device_create_file(chg_drv->device, &dev_attr_charge_stop_level);
+ if (ret != 0) {
+ pr_err("Failed to create charge_stop_level files, ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ ret = device_create_file(chg_drv->device, &dev_attr_charge_start_level);
+ if (ret != 0) {
+ pr_err("Failed to create charge_start_level files, ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ chg_drv->debug_entry = debugfs_create_dir("google_charger", 0);
+ if (IS_ERR_OR_NULL(chg_drv->debug_entry)) {
+ chg_drv->debug_entry = NULL;
+ return 0;
+ }
+
+ debugfs_create_file("chg_mode", 0644, chg_drv->debug_entry,
+ chg_drv, &chg_mode_fops);
+ debugfs_create_file("input_suspend", 0644, chg_drv->debug_entry,
+ chg_drv, &chg_is_fops);
+ debugfs_create_file("chg_suspend", 0644, chg_drv->debug_entry,
+ chg_drv, &chg_cs_fops);
+ debugfs_create_file("update_interval", 0644, chg_drv->debug_entry,
+ chg_drv, &chg_ui_fops);
+ debugfs_create_file("force_reschedule", 0600, chg_drv->debug_entry,
+ chg_drv, &chg_reschedule_work_fops);
+
+ debugfs_create_bool("usb_skip_probe", 0600, chg_drv->debug_entry,
+ &chg_drv->usb_skip_probe);
+
+ debugfs_create_file("pps_cc_tolerance", 0600, chg_drv->debug_entry,
+ chg_drv, &debug_pps_cc_tolerance_fops);
+
+
+ if (chg_drv->enable_user_fcc_fv) {
+ debugfs_create_file("fv_uv", 0644, chg_drv->debug_entry,
+ chg_drv, &fv_uv_fops);
+ debugfs_create_file("cc_max", 0644,
+ chg_drv->debug_entry,
+ chg_drv, &cc_max_fops);
+ debugfs_create_file("interval", 0644,
+ chg_drv->debug_entry,
+ chg_drv, &chg_interval_fops);
+ }
+
+ return 0;
+}
+
+
+/*
+ * barebone initial pps policy.
+ * Works only on output voltage keeping output current to the max.
+ * Extend to hande CC
+ */
+static int pps_policy(struct chg_drv *chg_drv, int fv_uv, int cc_max,
+ struct power_supply *tcpm_psy,
+ struct power_supply *bat_psy)
+{
+ struct pd_pps_data *pps_data = &chg_drv->pps_data;
+ const int ratio = 100 - chg_drv->pps_cc_tolerance_pct;
+ const uint8_t flags = chg_drv->chg_state.f.flags;
+ int ibatt, vbatt, ioerr;
+ unsigned long exp_mw;
+ int ret = 0;
+
+ /* TODO: policy for negative/invalid targets? */
+ if (cc_max <= 0) {
+ logbuffer_log(pps_data->log, "negative cc_max=%d", cc_max);
+ return 0;
+ }
+
+ /*
+ * TODO: Now we only need to adjust the pps in CC state.
+ * Consider CV state in the future.
+ */
+ if (!(flags & GBMS_CS_FLAG_CC)) {
+ logbuffer_log(pps_data->log, "waiting for CC flags=%x", flags);
+ return 0;
+ }
+
+ ibatt = GPSY_GET_INT_PROP(bat_psy, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &ioerr);
+ vbatt = GPSY_GET_PROP(bat_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+
+ if (ioerr < 0 || vbatt < 0) {
+ logbuffer_log(pps_data->log, "Failed to get ibatt (%d) or vbatt=%d",
+ ioerr, vbatt);
+ return -EIO;
+ }
+
+ /* TODO: should we compensate for the round down here? */
+ exp_mw = ((unsigned long)vbatt * (unsigned long)cc_max) * 11 /
+ 10000000000;
+
+ logbuffer_log(pps_data->log,
+ "ibatt %d, vbatt %d, vbatt*cc_max*1.1 %lu mw, adapter %ld, keep_alive_cnt %d",
+ ibatt, vbatt, exp_mw,
+ (long)pps_data->out_uv * (long)pps_data->op_ua / 1000000000,
+ pps_data->keep_alive_cnt);
+
+ /* negative current is discharging, should chase the load instead? */
+ if (ibatt <= 0)
+ return 0;
+
+ /* always maximize the input current first */
+ /* TODO: b/134799977 adjust the max current */
+ if (pps_data->op_ua < pps_data->max_ua) {
+ pps_data->op_ua = pps_data->max_ua;
+ return 0;
+ }
+
+ /* demand more power */
+ if ((ibatt < (cc_max * ratio) / 100) || flags & GBMS_CS_FLAG_ILIM) {
+
+ if (pps_data->out_uv == pps_data->max_uv) {
+ ret = pps_switch_profile(pps_data, tcpm_psy, true);
+ return (ret == 0) ? -ECANCELED : 0;
+ }
+
+ pps_adjust_volt(pps_data, 100000);
+
+ /* TODO: b/134799977 adjust the max current */
+
+ /* input power is enough */
+ } else {
+ /* everything is fine; try to lower the Vout if
+ * battery is satisfied for a period of time */
+ if (pps_data->keep_alive_cnt < PPS_KEEP_ALIVE_MAX)
+ return 0;
+
+ ret = pps_switch_profile(pps_data, tcpm_psy, false);
+ if (ret == 0)
+ return -ECANCELED;
+
+ pps_adjust_volt(pps_data, -100000);
+
+ /* TODO: b/134799977 adjust the max current */
+
+ }
+
+ return ret;
+}
+
+/*
+ * NOTE: when PPS is active we need to ping the adapter or it will revert
+ * to fixed profile.
+ */
+static int msc_update_pps(struct chg_drv *chg_drv, int fv_uv, int cc_max)
+{
+ struct pd_pps_data *pps_data = &chg_drv->pps_data;
+ int rc;
+
+ /* the policy determines the adapter output voltage and current */
+ rc = pps_policy(chg_drv, fv_uv, cc_max,
+ chg_drv->tcpm_psy, chg_drv->bat_psy);
+ if (rc == -ECANCELED)
+ return -ECANCELED;
+
+ /* adjust PPS out for target demand */
+ rc = pps_update_adapter(pps_data, pps_data->out_uv, pps_data->op_ua,
+ chg_drv->tcpm_psy);
+ if (rc == -EAGAIN)
+ rc = PPS_ERROR_RETRY_MS;
+
+ return rc;
+}
+
+/*
+ * NOTE: chg_work() vote 0 at the beginning of each loop to gate the updates
+ * to the charger
+ */
+static int msc_update_charger_cb(struct votable *votable,
+ void *data, int interval,
+ const char *client)
+{
+ int update_interval, rc = -EINVAL, fv_uv = -1, cc_max = -1;
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ __pm_stay_awake(&chg_drv->chg_ws);
+
+ update_interval =
+ get_effective_result_locked(chg_drv->msc_interval_votable);
+ if (update_interval <= 0)
+ goto msc_done;
+ if (chg_drv->chg_mode == CHG_DRV_MODE_NOOP)
+ goto msc_reschedule;
+
+ /*
+ * = fv_uv will be at last charger tier in RL, last tier before JEITA
+ * when JEITA hits, the default (max) value when temperature fall
+ * outside JEITA limits on connect or negative when a value is not
+ * specified in device tree. Setting max on JEITA violation is not
+ * a problem because HW wil set the charge current to 0, the charger
+ * driver does sanity checking as well (but we should not rely on it)
+ * = cc_max should not be negative here and is 0 on RL and on JEITA.
+ */
+ fv_uv = get_effective_result_locked(chg_drv->msc_fv_votable);
+ cc_max = get_effective_result_locked(chg_drv->msc_fcc_votable);
+
+ /* invalid values will cause the adapter to exit PPS */
+ if (cc_max < 0 || fv_uv < 0) {
+ update_interval = CHG_WORK_ERROR_RETRY_MS;
+ goto msc_reschedule;
+ }
+
+ if (chg_drv->pps_data.stage == PPS_ACTIVE) {
+ int pps_ui;
+
+ pps_ui = msc_update_pps(chg_drv, fv_uv, cc_max);
+ /* I am not sure that is the case */
+ if (pps_ui == -ECANCELED)
+ goto msc_done;
+ /* force ping */
+ if (pps_ui >= 0 && pps_ui < update_interval)
+ update_interval = pps_ui;
+ }
+
+ /* adjust charger for target demand */
+ rc = chg_update_charger(chg_drv, fv_uv, cc_max);
+ if (rc < 0)
+ update_interval = CHG_WORK_ERROR_RETRY_MS;
+
+msc_reschedule:
+ alarm_try_to_cancel(&chg_drv->chg_wakeup_alarm);
+ alarm_start_relative(&chg_drv->chg_wakeup_alarm,
+ ms_to_ktime(update_interval));
+
+ pr_info("MSC_CHG fv_uv=%d, cc_max=%d, rerun in %d ms (%d)\n",
+ fv_uv, cc_max, update_interval, rc);
+
+msc_done:
+ __pm_relax(&chg_drv->chg_ws);
+ return 0;
+}
+
+/* NOTE: we need a single source of truth. Charging can be disabled via the
+ * votable and directy setting the property.
+ */
+static int msc_chg_disable_cb(struct votable *votable, void *data,
+ int chg_disable, const char *client)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+ int rc;
+
+ if (!chg_drv->chg_psy)
+ return 0;
+
+ rc = GPSY_SET_PROP(chg_drv->chg_psy,
+ POWER_SUPPLY_PROP_CHARGE_DISABLE, chg_disable);
+ if (rc < 0) {
+ dev_err(chg_drv->device, "Couldn't %s charging rc=%d\n",
+ chg_disable ? "disable" : "enable", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int msc_pwr_disable_cb(struct votable *votable, void *data,
+ int pwr_disable, const char *client)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)data;
+
+ if (!chg_drv->chg_psy)
+ return 0;
+
+ return chg_vote_input_suspend(chg_drv, MSC_CHG_VOTER, pwr_disable);
+}
+
+static int chg_disable_std_votables(struct chg_drv *chg_drv)
+{
+ struct votable *qc_votable;
+ bool std_votables;
+
+ std_votables = of_property_read_bool(chg_drv->device->of_node,
+ "google,has-std-votables");
+ if (!std_votables)
+ return 0;
+
+ qc_votable = find_votable("FV");
+ if (!qc_votable)
+ return -EPROBE_DEFER;
+
+ vote(qc_votable, MSC_CHG_VOTER, true, -1);
+
+ qc_votable = find_votable("FCC");
+ if (!qc_votable)
+ return -EPROBE_DEFER;
+
+ vote(qc_votable, MSC_CHG_VOTER, true, -1);
+
+ return 0;
+}
+
+/* TODO: qcom/battery.c mostly handles PL charging: we don't need it.
+ * In order to remove and keep using QCOM code, create "USB_ICL",
+ * "PL_DISABLE", "PL_AWAKE" and "PL_ENABLE_INDIRECT" in a new function called
+ * qcom_batt_init(). Also might need to change the names of our votables for
+ * FCC, FV to match QCOM.
+ * NOTE: Battery also register "qcom-battery" class so it might not be too
+ * straightforward to remove all dependencies.
+ */
+static int chg_create_votables(struct chg_drv *chg_drv)
+{
+ int ret;
+
+ chg_drv->msc_fv_votable =
+ create_votable(VOTABLE_MSC_FV,
+ VOTE_MIN,
+ NULL,
+ chg_drv);
+ if (IS_ERR(chg_drv->msc_fv_votable)) {
+ ret = PTR_ERR(chg_drv->msc_fv_votable);
+ chg_drv->msc_fv_votable = NULL;
+ goto error_exit;
+ }
+
+ chg_drv->msc_fcc_votable =
+ create_votable(VOTABLE_MSC_FCC,
+ VOTE_MIN,
+ NULL,
+ chg_drv);
+ if (IS_ERR(chg_drv->msc_fcc_votable)) {
+ ret = PTR_ERR(chg_drv->msc_fcc_votable);
+ chg_drv->msc_fcc_votable = NULL;
+ goto error_exit;
+ }
+
+ chg_drv->msc_interval_votable =
+ create_votable(VOTABLE_MSC_INTERVAL,
+ VOTE_MIN,
+ msc_update_charger_cb,
+ chg_drv);
+ if (IS_ERR(chg_drv->msc_interval_votable)) {
+ ret = PTR_ERR(chg_drv->msc_interval_votable);
+ chg_drv->msc_interval_votable = NULL;
+ goto error_exit;
+ }
+
+ chg_drv->msc_chg_disable_votable =
+ create_votable(VOTABLE_MSC_CHG_DISABLE,
+ VOTE_SET_ANY,
+ msc_chg_disable_cb,
+ chg_drv);
+ if (IS_ERR(chg_drv->msc_chg_disable_votable)) {
+ ret = PTR_ERR(chg_drv->msc_chg_disable_votable);
+ chg_drv->msc_chg_disable_votable = NULL;
+ goto error_exit;
+ }
+
+ chg_drv->msc_pwr_disable_votable =
+ create_votable(VOTABLE_MSC_PWR_DISABLE,
+ VOTE_SET_ANY,
+ msc_pwr_disable_cb,
+ chg_drv);
+ if (IS_ERR(chg_drv->msc_pwr_disable_votable)) {
+ ret = PTR_ERR(chg_drv->msc_pwr_disable_votable);
+ chg_drv->msc_pwr_disable_votable = NULL;
+ goto error_exit;
+ }
+
+ return 0;
+
+error_exit:
+ destroy_votable(chg_drv->msc_fv_votable);
+ destroy_votable(chg_drv->msc_fcc_votable);
+ destroy_votable(chg_drv->msc_interval_votable);
+ destroy_votable(chg_drv->msc_chg_disable_votable);
+ destroy_votable(chg_drv->msc_pwr_disable_votable);
+
+ chg_drv->msc_fv_votable = NULL;
+ chg_drv->msc_fcc_votable = NULL;
+ chg_drv->msc_interval_votable = NULL;
+ chg_drv->msc_chg_disable_votable = NULL;
+ chg_drv->msc_pwr_disable_votable = NULL;
+
+ return ret;
+}
+
+static void chg_init_votables(struct chg_drv *chg_drv)
+{
+ /* prevent all changes until the first roundtrip with real state */
+ vote(chg_drv->msc_interval_votable, MSC_CHG_VOTER, true, 0);
+
+ /* will not be applied until we vote non-zero msc_interval */
+ vote(chg_drv->msc_fv_votable, MAX_VOTER,
+ chg_drv->batt_profile_fv_uv > 0, chg_drv->batt_profile_fv_uv);
+ vote(chg_drv->msc_fcc_votable, MAX_VOTER,
+ chg_drv->batt_profile_fcc_ua > 0, chg_drv->batt_profile_fcc_ua);
+
+}
+
+static int chg_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long *lvl)
+{
+ struct chg_thermal_device *tdev =
+ (struct chg_thermal_device *)tcd->devdata;
+ *lvl = tdev->thermal_levels;
+ return 0;
+}
+
+static int chg_get_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long *lvl)
+{
+ struct chg_thermal_device *tdev =
+ (struct chg_thermal_device *)tcd->devdata;
+ *lvl = tdev->current_level;
+ return 0;
+}
+
+/* Wireless and wired limits are linked when therm_wlc_override_fcc is true.
+ * This means that charging from WLC (wlc_psy is ONLINE) will disable the
+ * the thermal vote on MSC_FCC (b/128350180)
+ */
+static int chg_therm_update_fcc(struct chg_drv *chg_drv)
+{
+ struct chg_thermal_device *tdev =
+ &chg_drv->thermal_devices[CHG_TERMAL_DEVICE_FCC];
+ int ret, wlc_online = 0, fcc = -1;
+
+ if (chg_drv->wlc_psy && chg_drv->therm_wlc_override_fcc)
+ wlc_online = GPSY_GET_PROP(chg_drv->wlc_psy,
+ POWER_SUPPLY_PROP_ONLINE);
+
+ if (wlc_online <= 0 && tdev->current_level > 0)
+ fcc = tdev->thermal_mitigation[tdev->current_level];
+
+ ret = vote(chg_drv->msc_fcc_votable,
+ THERMAL_DAEMON_VOTER,
+ (fcc != -1),
+ fcc);
+ return ret;
+}
+
+static int chg_set_fcc_charge_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long lvl)
+{
+ int ret = 0;
+ struct chg_thermal_device *tdev =
+ (struct chg_thermal_device *)tcd->devdata;
+ struct chg_drv *chg_drv = tdev->chg_drv;
+ const bool changed = (tdev->current_level != lvl);
+
+ if (lvl < 0 || tdev->thermal_levels <= 0 || lvl > tdev->thermal_levels)
+ return -EINVAL;
+
+ tdev->current_level = lvl;
+
+ if (tdev->current_level == tdev->thermal_levels) {
+ pr_info("MSC_THERM_FCC lvl=%d charge disable\n", lvl);
+ return vote(chg_drv->msc_chg_disable_votable,
+ THERMAL_DAEMON_VOTER, true, 0);
+ }
+
+ vote(chg_drv->msc_chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0);
+
+ ret = chg_therm_update_fcc(chg_drv);
+ if (ret < 0 || changed)
+ pr_info("MSC_THERM_FCC lvl=%d (%d)\n",
+ tdev->current_level,
+ ret);
+
+ /* force to apply immediately */
+ reschedule_chg_work(chg_drv);
+ return ret;
+}
+
+static int chg_set_dc_in_charge_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long lvl)
+{
+ struct chg_thermal_device *tdev =
+ (struct chg_thermal_device *)tcd->devdata;
+ const bool changed = (tdev->current_level != lvl);
+ struct chg_drv *chg_drv = tdev->chg_drv;
+ union power_supply_propval pval;
+ int dc_icl = -1, ret;
+
+ if (lvl < 0 || tdev->thermal_levels <= 0 || lvl > tdev->thermal_levels)
+ return -EINVAL;
+
+ if (!chg_drv->dc_icl_votable)
+ chg_drv->dc_icl_votable = find_votable("DC_ICL");
+
+ tdev->current_level = lvl;
+
+ if (tdev->current_level == tdev->thermal_levels) {
+ if (chg_drv->dc_icl_votable)
+ vote(chg_drv->dc_icl_votable,
+ THERMAL_DAEMON_VOTER, true, 0);
+
+ /* WLC set the wireless charger offline b/119501863 */
+ if (chg_drv->wlc_psy) {
+ pval.intval = 0;
+ power_supply_set_property(chg_drv->wlc_psy,
+ POWER_SUPPLY_PROP_ONLINE, &pval);
+ }
+
+ pr_info("MSC_THERM_DC lvl=%d dc disable\n", lvl);
+
+ return 0;
+ }
+
+ if (chg_drv->wlc_psy) {
+ pval.intval = 1;
+ power_supply_set_property(chg_drv->wlc_psy,
+ POWER_SUPPLY_PROP_ONLINE, &pval);
+ }
+
+ if (!chg_drv->dc_icl_votable)
+ return 0;
+
+ if (tdev->current_level != 0)
+ dc_icl = tdev->thermal_mitigation[tdev->current_level];
+
+ ret = vote(chg_drv->dc_icl_votable, THERMAL_DAEMON_VOTER,
+ (dc_icl != -1),
+ dc_icl);
+
+ if (ret < 0 || changed)
+ pr_info("MSC_THERM_DC lvl=%d dc_icl=%d (%d)\n",
+ lvl, dc_icl, ret);
+
+ /* make sure that fcc is reset to max when charging from WLC*/
+ if (ret ==0)
+ (void)chg_therm_update_fcc(chg_drv);
+
+ return 0;
+}
+
+int chg_tdev_init(struct chg_thermal_device *tdev,
+ const char *name,
+ struct chg_drv *chg_drv)
+{
+ int rc, byte_len;
+
+ if (!of_find_property(chg_drv->device->of_node, name, &byte_len)) {
+ dev_err(chg_drv->device, "No cooling device for %s\n", name);
+ return -ENOENT;
+ }
+
+ tdev->thermal_mitigation = devm_kzalloc(chg_drv->device, byte_len,
+ GFP_KERNEL);
+ if (!tdev->thermal_mitigation)
+ return -ENOMEM;
+
+ tdev->thermal_levels = byte_len / sizeof(u32);
+
+ rc = of_property_read_u32_array(chg_drv->device->of_node,
+ name,
+ tdev->thermal_mitigation,
+ tdev->thermal_levels);
+ if (rc < 0) {
+ dev_err(chg_drv->device,
+ "Couldn't read limits for %s rc = %d\n", name, rc);
+ return -ENODATA;
+ }
+
+ tdev->chg_drv = chg_drv;
+
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops chg_fcc_tcd_ops = {
+ .get_max_state = chg_get_max_charge_cntl_limit,
+ .get_cur_state = chg_get_cur_charge_cntl_limit,
+ .set_cur_state = chg_set_fcc_charge_cntl_limit,
+};
+
+static const struct thermal_cooling_device_ops chg_dc_icl_tcd_ops = {
+ .get_max_state = chg_get_max_charge_cntl_limit,
+ .get_cur_state = chg_get_cur_charge_cntl_limit,
+ .set_cur_state = chg_set_dc_in_charge_cntl_limit,
+};
+
+/* ls /sys/devices/virtual/thermal/cdev-by-name/ */
+int chg_thermal_device_init(struct chg_drv *chg_drv)
+{
+ int rc;
+ struct device_node *cooling_node = NULL;
+
+ rc = chg_tdev_init(&chg_drv->thermal_devices[CHG_TERMAL_DEVICE_FCC],
+ "google,thermal-mitigation", chg_drv);
+ if (rc == 0) {
+ struct chg_thermal_device *fcc =
+ &chg_drv->thermal_devices[CHG_TERMAL_DEVICE_FCC];
+
+ cooling_node = of_find_node_by_name(NULL, FCC_OF_CDEV_NAME);
+ if (!cooling_node) {
+ pr_err("No %s OF node for cooling device\n",
+ FCC_OF_CDEV_NAME);
+ }
+
+ fcc->tcd = thermal_of_cooling_device_register(
+ cooling_node,
+ FCC_CDEV_NAME,
+ fcc,
+ &chg_fcc_tcd_ops);
+ if (!fcc->tcd) {
+ pr_err("error registering fcc cooling device\n");
+ return -EINVAL;
+ }
+ }
+
+ rc = chg_tdev_init(&chg_drv->thermal_devices[CHG_TERMAL_DEVICE_DC_IN],
+ "google,wlc-thermal-mitigation", chg_drv);
+ if (rc == 0) {
+ struct chg_thermal_device *dc_icl =
+ &chg_drv->thermal_devices[CHG_TERMAL_DEVICE_DC_IN];
+ cooling_node = NULL;
+ cooling_node = of_find_node_by_name(NULL, WLC_OF_CDEV_NAME);
+ if (!cooling_node) {
+ pr_err("No %s OF node for cooling device\n",
+ WLC_OF_CDEV_NAME);
+ }
+
+ dc_icl->tcd = thermal_of_cooling_device_register(
+ cooling_node,
+ WLC_CDEV_NAME,
+ dc_icl,
+ &chg_dc_icl_tcd_ops);
+ if (!dc_icl->tcd)
+ goto error_exit;
+ }
+
+ chg_drv->therm_wlc_override_fcc =
+ of_property_read_bool(chg_drv->device->of_node,
+ "google,therm-wlc-overrides-fcc");
+ if (chg_drv->therm_wlc_override_fcc)
+ pr_info("WLC overrides FCC\n");
+
+ return 0;
+
+error_exit:
+ pr_err("error registering dc_icl cooling device\n");
+ thermal_cooling_device_unregister(
+ chg_drv->thermal_devices[CHG_TERMAL_DEVICE_FCC].tcd);
+
+ return -EINVAL;
+}
+
+static struct power_supply *psy_get_by_name(struct chg_drv *chg_drv,
+ const char *name)
+{
+ struct power_supply *psy;
+
+ psy = power_supply_get_by_name(name);
+ if (!psy && chg_drv->log_psy_ratelimit) {
+ pr_warn_ratelimited("failed to get \"%s\" power supply, retrying...\n",
+ name);
+ chg_drv->log_psy_ratelimit -= 1;
+ }
+
+ return psy;
+}
+
+/* TODO: refactor to use pps_get_tcpm_psy() */
+static struct power_supply *get_tcpm_psy(struct chg_drv *chg_drv)
+{
+ struct power_supply *psy[2];
+ int i, ret;
+ struct power_supply *tcpm_psy = NULL;
+
+ ret = power_supply_get_by_phandle_array(chg_drv->device->of_node,
+ "google,tcpm-power-supply", psy,
+ ARRAY_SIZE(psy));
+ if (ret < 0 && !chg_drv->usb_skip_probe) {
+ if (!chg_drv->log_psy_ratelimit)
+ return ERR_PTR(ret);
+
+ pr_info("failed to get tcpm power supply, retrying... ret:%d\n",
+ ret);
+ /*
+ * Accessed from the same execution context i.e.
+ * google_charger_init_workor else needs to be protected
+ * along with access in psy_get_by_name.
+ */
+ chg_drv->log_psy_ratelimit -= 1;
+
+ return ERR_PTR(ret);
+ } else if (ret > 0) {
+ for (i = 0; i < ret; i++) {
+ if (psy[i]->desc && psy[i]->desc->name && !strncmp(
+ psy[i]->desc->name, "tcpm", strlen(
+ "tcpm")))
+ tcpm_psy = psy[i];
+ else
+ power_supply_put(psy[i]);
+ }
+
+ chg_drv->tcpm_psy_name = tcpm_psy->desc->name;
+ pr_info("tcpm psy_name: %s\n", chg_drv->tcpm_psy_name);
+ }
+
+ return tcpm_psy;
+}
+
+static void google_charger_init_work(struct work_struct *work)
+{
+ struct chg_drv *chg_drv = container_of(work, struct chg_drv,
+ init_work.work);
+ struct power_supply *chg_psy = NULL, *usb_psy = NULL;
+ struct power_supply *wlc_psy = NULL, *bat_psy = NULL;
+ struct power_supply *tcpm_psy = NULL;
+ bool pps_enable;
+ int ret = 0;
+
+ chg_psy = psy_get_by_name(chg_drv, chg_drv->chg_psy_name);
+ if (!chg_psy)
+ goto retry_init_work;
+
+ bat_psy = psy_get_by_name(chg_drv, chg_drv->bat_psy_name);
+ if (!bat_psy)
+ goto retry_init_work;
+
+ if (chg_drv->usb_psy_name) {
+ usb_psy = psy_get_by_name(chg_drv, chg_drv->usb_psy_name);
+ if (!usb_psy && !chg_drv->usb_skip_probe)
+ goto retry_init_work;
+ }
+
+ if (chg_drv->wlc_psy_name) {
+ wlc_psy = psy_get_by_name(chg_drv, chg_drv->wlc_psy_name);
+ if (!wlc_psy)
+ goto retry_init_work;
+ }
+
+ if (chg_drv->tcpm_phandle) {
+ tcpm_psy = get_tcpm_psy(chg_drv);
+ if (IS_ERR(tcpm_psy)) {
+ tcpm_psy = NULL;
+ goto retry_init_work;
+ }
+ }
+
+ /* TODO: make this optional since we don't need to do this anymore */
+ ret = chg_disable_std_votables(chg_drv);
+ if (ret == -EPROBE_DEFER)
+ goto retry_init_work;
+
+ chg_drv->chg_psy = chg_psy;
+ chg_drv->wlc_psy = wlc_psy;
+ chg_drv->usb_psy = usb_psy;
+ chg_drv->bat_psy = bat_psy;
+ chg_drv->tcpm_psy = tcpm_psy;
+
+ /* PPS negotiation handled in google_charger */
+ pps_enable = of_property_read_bool(chg_drv->device->of_node,
+ "google,pps-enable");
+ if (!tcpm_psy) {
+ pr_info("PPS not available\n");
+ } else if (!pps_enable) {
+ pr_info("PPS not enabled\n");
+ } else {
+ const char *name = tcpm_psy->desc->name;
+
+ ret = pps_init(&chg_drv->pps_data, chg_drv->device);
+ if (ret == 0 && chg_drv->debug_entry)
+ pps_init_fs(&chg_drv->pps_data, chg_drv->debug_entry);
+ if (ret < 0)
+ pr_err("PPS init failure for %s (%d)\n", name, ret);
+ else
+ pr_info("PPS available for %s\n", name);
+ }
+
+ ret = chg_thermal_device_init(chg_drv);
+ if (ret < 0)
+ pr_err("Cannot register thermal devices, ret=%d\n", ret);
+
+ chg_drv->dead_battery = chg_update_dead_battery(chg_drv);
+ if (chg_drv->dead_battery)
+ pr_info("dead battery mode\n");
+
+ chg_init_state(chg_drv);
+ chg_drv->stop_charging = -1;
+ chg_drv->charge_stop_level = DEFAULT_CHARGE_STOP_LEVEL;
+ chg_drv->charge_start_level = DEFAULT_CHARGE_START_LEVEL;
+
+ /* reset override charging parameters */
+ chg_drv->user_fv_uv = -1;
+ chg_drv->user_cc_max = -1;
+
+ chg_drv->psy_nb.notifier_call = chg_psy_changed;
+ ret = power_supply_reg_notifier(&chg_drv->psy_nb);
+ if (ret < 0)
+ pr_err("Cannot register power supply notifer, ret=%d\n", ret);
+
+ pr_info("init_work done\n");
+
+ /* catch state changes that happened before registering the notifier */
+ schedule_delayed_work(&chg_drv->chg_work,
+ msecs_to_jiffies(CHG_DELAY_INIT_DETECT_MS));
+ return;
+
+retry_init_work:
+ if (chg_psy)
+ power_supply_put(chg_psy);
+ if (bat_psy)
+ power_supply_put(bat_psy);
+ if (usb_psy)
+ power_supply_put(usb_psy);
+ if (wlc_psy)
+ power_supply_put(wlc_psy);
+ if (tcpm_psy)
+ power_supply_put(tcpm_psy);
+ schedule_delayed_work(&chg_drv->init_work,
+ msecs_to_jiffies(CHG_DELAY_INIT_MS));
+}
+
+static int google_charger_probe(struct platform_device *pdev)
+{
+ const char *chg_psy_name, *bat_psy_name;
+ const char *usb_psy_name = NULL, *wlc_psy_name = NULL;
+ struct chg_drv *chg_drv;
+ int ret;
+
+ chg_drv = devm_kzalloc(&pdev->dev, sizeof(*chg_drv), GFP_KERNEL);
+ if (!chg_drv)
+ return -ENOMEM;
+
+ chg_drv->device = &pdev->dev;
+
+ ret = of_property_read_string(pdev->dev.of_node,
+ "google,chg-power-supply",
+ &chg_psy_name);
+ if (ret != 0) {
+ pr_err("cannot read google,chg-power-supply, ret=%d\n", ret);
+ return -EINVAL;
+ }
+ chg_drv->chg_psy_name =
+ devm_kstrdup(&pdev->dev, chg_psy_name, GFP_KERNEL);
+ if (!chg_drv->chg_psy_name)
+ return -ENOMEM;
+
+ ret = of_property_read_string(pdev->dev.of_node,
+ "google,bat-power-supply",
+ &bat_psy_name);
+ if (ret != 0) {
+ pr_err("cannot read google,bat-power-supply, ret=%d\n", ret);
+ return -EINVAL;
+ }
+ chg_drv->bat_psy_name =
+ devm_kstrdup(&pdev->dev, bat_psy_name, GFP_KERNEL);
+ if (!chg_drv->bat_psy_name)
+ return -ENOMEM;
+
+ ret = of_property_read_string(pdev->dev.of_node,
+ "google,wlc-power-supply",
+ &wlc_psy_name);
+ if (ret != 0)
+ pr_warn("google,wlc-power-supply not defined\n");
+ if (wlc_psy_name) {
+ chg_drv->wlc_psy_name =
+ devm_kstrdup(&pdev->dev, wlc_psy_name, GFP_KERNEL);
+ if (!chg_drv->wlc_psy_name)
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_string(pdev->dev.of_node,
+ "google,usb-power-supply",
+ &usb_psy_name);
+ if (ret != 0)
+ pr_warn("google,usb-power-supply not defined\n");
+ if (usb_psy_name) {
+ chg_drv->usb_psy_name =
+ devm_kstrdup(&pdev->dev, usb_psy_name, GFP_KERNEL);
+ if (!chg_drv->usb_psy_name)
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "google,tcpm-power-supply",
+ &chg_drv->tcpm_phandle);
+ if (ret < 0)
+ pr_warn("google,tcpm-power-supply not defined\n");
+
+ /*
+ * when set will reduce the comparison value for ibatt by
+ * cc_max * (100 - pps_cc_tolerance_pct) / 100
+ * only used from the PPS policy
+ */
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "google,pps-cc-tolerance-pct",
+ &chg_drv->pps_cc_tolerance_pct);
+ if (ret < 0)
+ chg_drv->pps_cc_tolerance_pct = PPS_CC_TOLERANCE_PCT_DEFAULT;
+ else if (chg_drv->pps_cc_tolerance_pct > PPS_CC_TOLERANCE_PCT_MAX)
+ chg_drv->pps_cc_tolerance_pct = PPS_CC_TOLERANCE_PCT_MAX;
+
+ /* user fcc, fv uv are bound by battery votes.
+ * set google,disable_votes in battery node to disable battery votes.
+ */
+ chg_drv->enable_user_fcc_fv =
+ of_property_read_bool(pdev->dev.of_node,
+ "google,enable-user-fcc-fv");
+ if (chg_drv->enable_user_fcc_fv)
+ pr_info("User can override FCC and FV\n");
+
+ /* NOTE: newgen charging is configured in google_battery */
+ ret = chg_init_chg_profile(chg_drv);
+ if (ret < 0) {
+ pr_err("cannot read charging profile from dt, ret=%d\n", ret);
+ return ret;
+ }
+
+ if (chg_drv->chg_term.enable) {
+ INIT_WORK(&chg_drv->chg_term.work,
+ chg_termination_work);
+
+ if (alarmtimer_get_rtcdev()) {
+ alarm_init(&chg_drv->chg_term.alarm,
+ ALARM_BOOTTIME,
+ chg_termination_alarm_cb);
+ } else {
+ /* keep the driver init even if get alarmtimer fail */
+ chg_drv->chg_term.enable = false;
+ cancel_work_sync(&chg_drv->chg_term.work);
+ pr_err("Couldn't get rtc device\n");
+ }
+ }
+
+
+
+ INIT_DELAYED_WORK(&chg_drv->init_work, google_charger_init_work);
+ INIT_DELAYED_WORK(&chg_drv->chg_work, chg_work);
+ INIT_WORK(&chg_drv->chg_psy_work, chg_psy_work);
+ platform_set_drvdata(pdev, chg_drv);
+
+ alarm_init(&chg_drv->chg_wakeup_alarm, ALARM_BOOTTIME,
+ google_chg_alarm_handler);
+
+ /* votables and chg_work need a wakeup source */
+ wakeup_source_init(&chg_drv->chg_ws, "google-charger");
+
+ /* create the votables before talking to google_battery */
+ ret = chg_create_votables(chg_drv);
+ if (ret < 0)
+ pr_err("Failed to create votables, ret=%d\n", ret);
+ else
+ chg_init_votables(chg_drv);
+
+ /* sysfs & debug */
+ chg_init_fs(chg_drv);
+
+ /* ratelimit makes init work quiet */
+ chg_drv->log_psy_ratelimit = CHG_LOG_PSY_RATELIMIT_CNT;
+ schedule_delayed_work(&chg_drv->init_work,
+ msecs_to_jiffies(CHG_DELAY_INIT_MS));
+
+ dev_info(chg_drv->device, "probe work done");
+ return 0;
+}
+
+static void chg_destroy_votables(struct chg_drv *chg_drv)
+{
+ destroy_votable(chg_drv->msc_interval_votable);
+ destroy_votable(chg_drv->msc_fv_votable);
+ destroy_votable(chg_drv->msc_fcc_votable);
+ destroy_votable(chg_drv->msc_chg_disable_votable);
+ destroy_votable(chg_drv->msc_pwr_disable_votable);
+}
+
+static int google_charger_remove(struct platform_device *pdev)
+{
+ struct chg_drv *chg_drv = (struct chg_drv *)platform_get_drvdata(pdev);
+
+ if (chg_drv) {
+ if (chg_drv->chg_term.enable) {
+ alarm_cancel(&chg_drv->chg_term.alarm);
+ cancel_work_sync(&chg_drv->chg_term.work);
+ }
+
+ chg_destroy_votables(chg_drv);
+
+ if (chg_drv->chg_psy)
+ power_supply_put(chg_drv->chg_psy);
+ if (chg_drv->bat_psy)
+ power_supply_put(chg_drv->bat_psy);
+ if (chg_drv->usb_psy)
+ power_supply_put(chg_drv->usb_psy);
+ if (chg_drv->wlc_psy)
+ power_supply_put(chg_drv->wlc_psy);
+ if (chg_drv->tcpm_psy)
+ power_supply_put(chg_drv->tcpm_psy);
+
+ wakeup_source_trash(&chg_drv->chg_ws);
+ wakeup_source_trash(&chg_drv->pps_data.pps_ws);
+
+ alarm_try_to_cancel(&chg_drv->chg_wakeup_alarm);
+
+ if (chg_drv->pps_data.log)
+ debugfs_logbuffer_unregister(chg_drv->pps_data.log);
+ }
+
+ return 0;
+}
+
+#ifdef SUPPORT_PM_SLEEP
+static int chg_pm_suspend(struct device *dev)
+{
+
+ return 0;
+}
+
+static int chg_pm_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct chg_drv *chg_drv = platform_get_drvdata(pdev);
+
+ chg_drv->egain_retries = 0;
+ reschedule_chg_work(chg_drv);
+
+ return 0;
+}
+
+static const struct dev_pm_ops chg_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(chg_pm_suspend, chg_pm_resume)
+};
+#endif
+
+
+static const struct of_device_id match_table[] = {
+ {.compatible = "google,charger"},
+ {},
+};
+
+static struct platform_driver charger_driver = {
+ .driver = {
+ .name = "google,charger",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+#ifdef SUPPORT_PM_SLEEP
+ .pm = &chg_pm_ops,
+#endif
+ },
+ .probe = google_charger_probe,
+ .remove = google_charger_remove,
+};
+
+static int __init google_charger_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&charger_driver);
+ if (ret < 0) {
+ pr_err("device registration failed: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void __init google_charger_exit(void)
+{
+ platform_driver_unregister(&charger_driver);
+ pr_info("unregistered platform driver\n");
+}
+
+module_init(google_charger_init);
+module_exit(google_charger_exit);
+MODULE_DESCRIPTION("Multi-step battery charger driver");
+MODULE_AUTHOR("Thierry Strudel <[email protected]>");
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/google_cpm.c b/google_cpm.c
new file mode 100644
index 0000000..db24468
--- /dev/null
+++ b/google_cpm.c
@@ -0,0 +1,1291 @@
+/*
+ * Copyright 2020 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 ": " fmt
+
+#ifdef CONFIG_PM_SLEEP
+#define SUPPORT_PM_SLEEP 1
+#endif
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/pm_wakeup.h>
+#include <linux/pmic-voter.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include "gvotable.h"
+#include "google_bms.h"
+#include "google_dc_pps.h"
+#include "google_psy.h"
+#include "logbuffer.h"
+
+#include <linux/debugfs.h>
+
+#define GCPM_MAX_CHARGERS 4
+#define GCPM_DEFAULT_TA_DC_LIMIT 0
+
+/* TODO: move to configuration */
+#define DC_TA_VMAX_MV 9800000
+/* TODO: move to configuration */
+#define DC_TA_VMIN_MV 8000000
+/* TODO: move to configuration */
+#define DC_VBATT_HEADROOM_MV 500000
+
+enum gcpm_dc_state_t {
+ DC_DISABLED = 0,
+ DC_ENABLE,
+ DC_RUNNING,
+ DC_ENABLE_PASSTHROUGH,
+ DC_PASSTHROUGH,
+};
+
+struct gcpm_drv {
+ struct device *device;
+ struct power_supply *psy;
+ struct delayed_work init_work;
+
+ int chg_psy_retries;
+ struct power_supply *chg_psy_avail[GCPM_MAX_CHARGERS];
+ const char *chg_psy_names[GCPM_MAX_CHARGERS];
+ struct mutex chg_psy_lock;
+ int chg_psy_active;
+ int chg_psy_count;
+ /* force a charger, this might have side effects */
+ int force_active;
+
+ /* TCPM state for PPS charging */
+ struct power_supply *tcpm_psy;
+ const char *tcpm_psy_name;
+ int log_psy_ratelimit;
+ u32 tcpm_phandle;
+
+ /* set to force PPS negoatiation */
+ bool force_pps;
+ /* pps state and detect */
+ struct pd_pps_data pps_data;
+ struct delayed_work pps_work;
+ /* request of output ua, */
+ int out_ua;
+ int out_uv;
+
+ int dcen_gpio;
+ u32 dcen_gpio_default;
+
+ /* >0 when enabled */
+ int dc_index;
+ /* dc_charging state */
+ int dc_state;
+ /* force disable */
+ bool taper_control;
+ /* policy: power demand limit for DC charging */
+ u32 ta_dc_limit;
+
+ /* cc_max and fv_uv are demand from google_charger */
+ int cc_max;
+ int fv_uv;
+
+ struct notifier_block chg_nb;
+ bool init_complete;
+ bool resume_complete;
+
+ /* tie up to charger mode */
+ struct gvotable_election *gbms_mode;
+
+ /* debug fs */
+ struct dentry *debug_entry;
+};
+
+/* TODO: place a lock around the operation? */
+static struct power_supply *gcpm_chg_get_active(struct gcpm_drv *gcpm)
+{
+ if (gcpm->chg_psy_active == -1)
+ return NULL;
+
+ return gcpm->chg_psy_avail[gcpm->chg_psy_active];
+}
+
+static int gcpm_chg_ping(struct gcpm_drv *gcpm, int index, bool online)
+{
+ struct power_supply *chg_psy;
+ int ret;
+
+ chg_psy = gcpm->chg_psy_avail[index];
+ if (!chg_psy)
+ return 0;
+
+ ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_ONLINE, 0);
+ if (ret < 0)
+ pr_debug("adapter %d cannot ping (%d)", index, ret);
+
+ return 0;
+}
+
+/*
+ * Switch between chargers using ONLINE.
+ * NOTE: online doesn't enable charging.
+ * NOTE: call holding a lock on charger
+ */
+static int gcpm_chg_offline(struct gcpm_drv *gcpm)
+{
+ struct power_supply *chg_psy;
+ int ret;
+
+ chg_psy = gcpm_chg_get_active(gcpm);
+ if (!chg_psy)
+ return 0;
+
+ ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_ONLINE, 0);
+ if (ret == 0)
+ gcpm->chg_psy_active = -1;
+
+ pr_debug("active=%d offline=%d\n", gcpm->chg_psy_active, ret == 0);
+ return ret;
+}
+
+/* turn current offline (if a current exists), switch to new */
+static int gcpm_chg_set_online(struct gcpm_drv *gcpm, int index)
+{
+ const int index_old = gcpm->chg_psy_active;
+ struct power_supply *active;
+ int ret;
+
+ if (index < 0 || index >= gcpm->chg_psy_count)
+ return -ERANGE;
+
+ if (!gcpm->chg_psy_avail[index]) {
+ pr_err("invalid index %d\n", index);
+ return -EINVAL;
+ }
+
+ ret = gcpm_chg_offline(gcpm);
+ if (ret < 0) {
+ pr_err("cannot turn %d offline\n", index_old);
+ return -EIO;
+ }
+
+ active = gcpm->chg_psy_avail[index];
+
+ ret = GPSY_SET_PROP(active, POWER_SUPPLY_PROP_ONLINE, 1);
+ if (ret < 0) {
+ /* TODO: re-enable the old one if this fail??? */
+ goto error_exit;
+ }
+
+ gcpm->chg_psy_active = index;
+
+error_exit:
+ pr_info("active charger %d->%d (%d)\n", index_old, index, ret);
+ return ret;
+}
+
+/* use the charger one when avaiable or fallback to the generated one */
+static uint64_t gcpm_get_charger_state(const struct gcpm_drv *gcpm,
+ struct power_supply *chg_psy)
+{
+ union gbms_charger_state chg_state;
+ int rc;
+
+ rc = gbms_read_charger_state(&chg_state, chg_psy);
+ if (rc < 0)
+ return 0;
+
+ return chg_state.v;
+}
+
+/*
+ * Select the DC charger using the thermal policy.
+ * NOTE: program target before enabling chaging.
+ */
+static int gcpm_chg_dc_select(const struct gcpm_drv *gcpm, int ta_demand)
+{
+ bool dc_req = !gcpm->taper_control && gcpm->ta_dc_limit &&
+ ta_demand > gcpm->ta_dc_limit;
+ int index = 0; /* 0 is the default */
+
+ /* could select different modes here depending on capabilities */
+ if (dc_req && gcpm->chg_psy_count > 1)
+ index = 1;
+
+ /* add margin .... debounce etc... */
+
+ return index;
+}
+
+/* Enable DirectCharge mode, PPS and DC charger must be already initialized */
+static int gcpm_dc_enable(struct gcpm_drv *gcpm, bool enabled)
+{
+ if (!gcpm->gbms_mode) {
+ struct gvotable_election *v;
+
+ v = gvotable_election_get_handle(GBMS_MODE_VOTABLE);
+ if (IS_ERR_OR_NULL(v))
+ return -ENODEV;
+ gcpm->gbms_mode = v;
+ }
+
+ return gvotable_cast_vote(gcpm->gbms_mode, "GCPM",
+ (void*)GBMS_CHGR_MODE_CHGR_DC,
+ enabled);
+}
+
+/* disable DC, and switch back to the default charger */
+/* NOTE: call with a lock around gcpm->chg_psy_lock */
+static int gcpm_dc_stop(struct gcpm_drv *gcpm)
+{
+ int ret;
+
+ /* enabled in dc_ready after programming the charger */
+ if (gcpm->dcen_gpio >= 0 && !gcpm->dcen_gpio_default)
+ gpio_set_value(gcpm->dcen_gpio, 0);
+
+ switch (gcpm->dc_state) {
+ case DC_RUNNING:
+ case DC_PASSTHROUGH:
+ ret = gcpm_dc_enable(gcpm, false);
+ if (ret < 0) {
+ pr_err("DC_PPS: Cannot disable DC (%d)", ret);
+ break;
+ }
+ gcpm->dc_state = DC_ENABLE;
+ /* Fall Through */
+ case DC_ENABLE:
+ case DC_ENABLE_PASSTHROUGH:
+ ret = gcpm_chg_set_online(gcpm, 0);
+ if (ret < 0) {
+ pr_err("DC_PPS: Cannot enable default charger (%d)",
+ ret);
+ break;
+ }
+ /* Fall Through */
+ default:
+ gcpm->dc_state = DC_DISABLED;
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+/* NOTE: call with a lock around gcpm->chg_psy_lock */
+static int gcpm_dc_start(struct gcpm_drv *gcpm, int index)
+{
+ struct power_supply *dc_psy;
+ int ret;
+
+ ret = gcpm_chg_set_online(gcpm, index);
+ if (ret < 0) {
+ pr_err("PPS_DC: cannot online index=%d (%d)\n", index, ret);
+ return ret;
+ }
+
+ dc_psy = gcpm_chg_get_active(gcpm);
+ if (!dc_psy) {
+ pr_err("PPS_DC: gcpm->dc_state == DC_READY, no adapter\n");
+ return -ENODEV;
+ }
+
+ /* VFLOAT = vbat */
+ ret = GPSY_SET_PROP(dc_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ gcpm->fv_uv);
+ if (ret < 0) {
+ pr_err("PPS_DC: no fv_uv (%d)\n", ret);
+ return ret;
+ }
+
+ /* ICHG_CHG = cc_max */
+ ret = GPSY_SET_PROP(dc_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ gcpm->cc_max);
+ if (ret < 0) {
+ pr_err("PPS_DC: no cc_max (%d)\n", ret);
+ return ret;
+ }
+
+ /* set IIN_CFG, */
+ ret = GPSY_SET_PROP(dc_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ gcpm->out_ua);
+ if (ret < 0) {
+ pr_err("PPS_DC: no IIN (%d)\n", ret);
+ return ret;
+ }
+
+ /* enabled in dc_ready after programming the charger */
+ if (gcpm->dcen_gpio >= 0 && !gcpm->dcen_gpio_default)
+ gpio_set_value(gcpm->dcen_gpio, 1);
+
+ ret = gcpm_dc_enable(gcpm, true);
+ if (ret < 0) {
+ pr_info("PPS_DC: dc_ready failed=%d\n", ret);
+ return ret;
+ }
+
+ pr_info("PPS_DC: dc_ready ok fv_uv=%d cc_max=%d, out_ua=%d\n",
+ gcpm->dc_state, gcpm->fv_uv, gcpm->cc_max, gcpm->out_ua);
+
+ return 0;
+}
+
+/* NOTE: call with a lock around gcpm->chg_psy_lock */
+static int gcpm_dc_charging(struct gcpm_drv *gcpm)
+{
+ struct power_supply *dc_psy;
+ int vchg, ichg, status;
+
+ dc_psy = gcpm_chg_get_active(gcpm);
+ if (!dc_psy) {
+ pr_err("DC_CHG: invalid charger\n");
+ return -ENODEV;
+ }
+
+ vchg = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ ichg = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
+ status = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_STATUS);
+
+ pr_err("DC_CHG: vchg=%d, ichg=%d status=%d\n",
+ vchg, ichg, status);
+
+ return 0;
+}
+
+/* Will need to handle capabilities based on index number */
+#define GCPM_INDEX_DC_ENABLE 1
+
+
+/* TODO: do not enable PPS when ->taper_control is set? */
+/* TODO: revert to fixed profile if cc_max is 0, remember failures */
+/* TODO: use a structure to describe charger types "needs_pps", "is_dc" etc */
+static int gcpm_chg_pps_check_enable(const struct gcpm_drv *gcpm, int index)
+{
+ return gcpm->force_pps || index == GCPM_INDEX_DC_ENABLE;
+}
+
+/* TODO: handle PPS-DC and WLC-DC, return dc_stage*/
+/* NOTE: DC requires PPS, disable DC in taper control */
+static int gcpm_chg_dc_check_enable(const struct gcpm_drv *gcpm, int index)
+{
+ return index == GCPM_INDEX_DC_ENABLE;
+}
+
+/*
+ * need to run through the whole function even when index == gcpm->force_active
+ * because I have multiple steps and multiple failure points.
+ * Call with lock on chg_psy_lock
+ */
+static int gcpm_chg_check(struct gcpm_drv *gcpm)
+{
+ struct pd_pps_data *pps_data = &gcpm->pps_data;
+ bool schedule_pps_dc = false;
+ bool dc_ena, pps_ena;
+ int ret = 0, index;
+
+ /* might have more than one policy */
+ if (gcpm->force_active >= 0)
+ index = gcpm->force_active;
+ else
+ index = gcpm_chg_dc_select(gcpm, gcpm->cc_max * gcpm->fv_uv);
+
+ pr_debug("CHG_CHK: index:%d->%d\n", gcpm->chg_psy_active, index);
+
+ /* first check if PPS needs to be enabled (or disable it) */
+ pps_ena = gcpm->tcpm_psy && gcpm_chg_pps_check_enable(gcpm, index);
+ pr_debug("CHG_CHK: PPS pps_stage=%d pps_ena=%d\n",
+ pps_data->stage, pps_ena);
+
+ if (pps_ena && pps_data->stage != PPS_NOTSUPP) {
+
+ /* Could reset gcpm->out_uv and gcpm->out_ua to -1 */
+ if (pps_data->stage == PPS_DISABLED) {
+ pps_data->stage = PPS_NONE;
+ schedule_pps_dc = true;
+ }
+
+ } else if (pps_data->stage == PPS_NOTSUPP) {
+ /* keep PPS_NOTSUPP, continue to make sure DC is disabled */
+ } else if (pps_data->stage != PPS_DISABLED) {
+
+ /* force state, PPS_DC will take cate of it */
+
+ ret = pps_prog_offline(&gcpm->pps_data, gcpm->tcpm_psy);
+ pr_debug("CHG_CHK: PPS offline ret=%d\n", ret);
+ if (ret < 0) {
+ pr_debug("CHG_CHK: PPS pps_ena=%d dc_ena=%d->%d ret=%d\n",
+ pps_ena, gcpm->dc_index, dc_ena, ret);
+
+ /* force state, PPS_DC will take care of it */
+ pps_data->stage = PPS_DISABLED;
+
+ /*
+ * transitions between MAIN<->PPS<->PPS+DC are handled
+ * in gcpm_pps_dc_work() and the state of DC for this
+ * index must be handled regardless of the success of
+ * the immediate disable of pps.
+ */
+ }
+
+ /* needs to continue of DC was enabled */
+ schedule_pps_dc = true;
+ }
+
+ /*
+ * NOTE: disabling DC might need to transition to charger mode 0
+ * same might apply when switching between WLC-DC and PPS-DC.
+ */
+ dc_ena = gcpm_chg_dc_check_enable(gcpm, index);
+ pr_debug("CHG_CHK: DC pps_ena=%d dc_ena=%d->%d \n",
+ pps_ena, gcpm->dc_index, dc_ena);
+ if (!dc_ena) {
+
+ if (gcpm->dc_index) {
+ gcpm->dc_index = 0;
+ schedule_pps_dc = true;
+ }
+ } else if (gcpm->dc_state == DC_DISABLED) {
+ gcpm->out_ua = -1;
+ gcpm->out_uv = -1;
+
+ /* TODO: DC_ENABLE or DC_PASSTHROUGH depending on index */
+ gcpm->dc_state = DC_ENABLE_PASSTHROUGH;
+ gcpm->dc_index = index;
+ schedule_pps_dc = true;
+ }
+
+ if (schedule_pps_dc)
+ mod_delayed_work(system_wq, &gcpm->pps_work, 0);
+
+ return ret;
+}
+
+#define PPS_ERROR_RETRY_MS 1000
+
+/* DC_ERROR_RETRY_MS <= DC_RUN_DELAY_MS */
+#define DC_RUN_DELAY_MS 5000
+#define DC_ERROR_RETRY_MS PPS_ERROR_RETRY_MS
+
+/*
+ * pps_data->stage:
+ * PPS_NONE -> PPS_AVAILABLE -> PPS_ACTIVE
+ * -> PPS_DISABLED -> PPS_DISABLED
+ */
+static void gcpm_pps_dc_work(struct work_struct *work)
+{
+ struct gcpm_drv *gcpm =
+ container_of(work, struct gcpm_drv, pps_work.work);
+ struct pd_pps_data *pps_data = &gcpm->pps_data;
+ struct power_supply *tcpm_psy = gcpm->tcpm_psy;
+ const bool log_on_done = tcpm_psy != 0 && pps_data->pd_online &&
+ (gcpm->pps_data.stage || gcpm->dc_state);
+ const int pre_out_ua = pps_data->op_ua;
+ const int pre_out_uv = pps_data->out_uv;
+ int ret, pps_ui = -ENODEV;
+
+ mutex_lock(&gcpm->chg_psy_lock);
+
+ pr_info("PPS_Work: tcpm_psy_ok=%d pd_online=%d pps_stage=%d dc_state=%d",
+ tcpm_psy != 0, pps_data->pd_online, gcpm->pps_data.stage,
+ gcpm->dc_state);
+
+ /* should not be calling this with !tcpm_psy */
+ if (!gcpm->tcpm_psy)
+ goto pps_dc_done;
+
+ /* disable DC if not enabled */
+ if (gcpm->dc_index <= 0 && gcpm->dc_state != DC_DISABLED) {
+ const int dc_state = gcpm->dc_state;
+
+ ret = gcpm_dc_stop(gcpm);
+ if (ret < 0 || gcpm->dc_state != DC_DISABLED) {
+ pr_info("PPS_DC: fail disable dc_state=%d (%d)\n",
+ gcpm->dc_state, ret);
+
+ pps_ui = DC_ERROR_RETRY_MS;
+ goto pps_dc_reschedule;
+ }
+
+ pr_info("PPS_DC: disable DC dc_state=%d->%d\n",
+ dc_state, gcpm->dc_state);
+ }
+
+ if (pps_is_disabled(pps_data->stage))
+ goto pps_dc_done;
+
+ /* DC depends on PPS so run PPS first.
+ * - read source capabilities when stage transition from PPS_NONE to
+ * PPS_AVAILABLE (NOTE: pd_online=TCPM_PSY_PROG_ONLINE in this case)
+ * DISABLED ->DISABLED
+ * ->NONE
+ * NONE ->AVAILABLE
+ * ->DISABLED
+ * ->NOTSUPP
+ * AVAILABLE ->ACTIVE
+ * ->DISABLED
+ * NOTSUPP ->NOTSUPP
+ */
+ pps_ui = pps_work(pps_data, tcpm_psy);
+ pr_info("PPS_Work: pps_ui=%d pd_online=%d pps_stage=%d dc_state=%d\n",
+ pps_ui, pps_data->pd_online,
+ gcpm->pps_data.stage, gcpm->dc_state);
+ if (pps_ui < 0) {
+ pps_ui = PPS_ERROR_RETRY_MS;
+ } else if (!pps_data->pd_online) {
+
+ /* disable DC, revert to default charger */
+ if (gcpm->dc_state != DC_DISABLED) {
+ ret = gcpm_dc_stop(gcpm);
+ if (ret < 0 || gcpm->dc_state != DC_DISABLED) {
+ pr_info("PPS_DC: fail disable dc_state=%d (%d)\n",
+ gcpm->dc_state, ret);
+
+ /* will keep trying to disable */
+ pps_ui = DC_ERROR_RETRY_MS;
+ }
+ }
+
+ /* reset state on offline (should be already done) */
+ pr_info("PPS_Work: PPS Offline\n");
+ pps_init_state(pps_data);
+ } else if (pps_data->stage == PPS_DISABLED) {
+ /* PPS was disabled (not available) for this TA */
+ pr_info("PPS_Work: PPS Disabled dc_ena=%d, dc_state=%d\n",
+ gcpm->dc_index, gcpm->dc_state);
+
+ /* reschedule to clear dc_ena for this session */
+ if (gcpm->dc_index > 0) {
+ pps_ui = PPS_ERROR_RETRY_MS;
+ gcpm->dc_index = 0;
+ }
+ } else if (pps_data->stage != PPS_ACTIVE) {
+ /*
+ * DC run only when PPS is active, the only sane state for DC
+ * when PPS is not ACTIVE is DC_DISABLE:
+ * DC state is forced to DC_DISABLE when ->dc_index <= 0 at
+ * the beginning of the loop
+ */
+
+ pr_info("PPS_Work: PPS Wait stage=%d, pps_ui=%d dc_ena=%d dc_state=%d \n",
+ pps_data->stage, pps_ui, gcpm->dc_index, gcpm->dc_state);
+
+ /* TODO: keep track of time waiting for ACTIVE */
+ } else if (gcpm->dc_state == DC_ENABLE) {
+ struct pd_pps_data *pps_data = &gcpm->pps_data;
+ bool pwr_ok;
+
+ /* must run at the end of PPS negotiation */
+ if (gcpm->out_ua == -1)
+ gcpm->out_ua = min(gcpm->cc_max, pps_data->max_ua);
+ if (gcpm->out_uv == -1) {
+ struct power_supply *chg_psy =
+ gcpm_chg_get_active(gcpm);
+ unsigned long ta_max_v, value;
+ int vbatt = -1;
+
+ ta_max_v = pps_data->max_ua * pps_data->max_uv;
+ ta_max_v /= gcpm->out_ua;
+ if (ta_max_v > DC_TA_VMAX_MV)
+ ta_max_v = DC_TA_VMAX_MV;
+
+ if (chg_psy)
+ vbatt = GPSY_GET_PROP(chg_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ if (vbatt < 0)
+ vbatt = gcpm->fv_uv;
+ if (vbatt < 0)
+ vbatt = 0;
+
+ /* good for pca9468 */
+ value = 2 * vbatt + DC_VBATT_HEADROOM_MV;
+ if (value < DC_TA_VMIN_MV)
+ value = DC_TA_VMIN_MV;
+
+ /* PPS voltage in 20mV steps */
+ gcpm->out_uv = value - value % 20000;
+ }
+
+ pr_info("CHG_CHK: max_uv=%d,max_ua=%d out_uv=%d,out_ua=%d\n",
+ pps_data->max_uv, pps_data->max_ua,
+ gcpm->out_uv, gcpm->out_ua);
+
+ pps_ui = pps_update_adapter(pps_data, gcpm->out_uv,
+ gcpm->out_ua, tcpm_psy);
+ if (pps_ui < 0)
+ pps_ui = PPS_ERROR_RETRY_MS;
+
+ /* wait until adapter is at or over request */
+ pwr_ok = pps_data->out_uv == gcpm->out_uv &&
+ pps_data->op_ua == gcpm->out_ua;
+ if (pwr_ok) {
+ ret = gcpm_chg_offline(gcpm);
+ if (ret == 0)
+ ret = gcpm_dc_start(gcpm, gcpm->dc_index);
+ if (ret == 0) {
+ gcpm->dc_state = DC_RUNNING;
+ pps_ui = DC_RUN_DELAY_MS;
+ } else if (pps_ui > DC_ERROR_RETRY_MS) {
+ pps_ui = DC_ERROR_RETRY_MS;
+ }
+ }
+
+ /*
+ * TODO: add retries and switch to DC_ENABLE again or to
+ * DC_DISABLED on timeout.
+ */
+
+ pr_info("PPS_DC: dc_state=%d out_uv=%d %d->%d, out_ua=%d %d->%d\n",
+ gcpm->dc_state,
+ pps_data->out_uv, pre_out_uv, gcpm->out_uv,
+ pps_data->op_ua, pre_out_ua, gcpm->out_ua);
+ } else if (gcpm->dc_state == DC_RUNNING) {
+
+ ret = gcpm_chg_ping(gcpm, 0, 0);
+ if (ret < 0)
+ pr_err("PPS_DC: ping failed with %d\n", ret);
+
+ /* update gcpm->out_uv, gcpm->out_ua */
+ pr_info("PPS_DC: dc_state=%d out_uv=%d %d->%d out_ua=%d %d->%d\n",
+ gcpm->dc_state,
+ pps_data->out_uv, pre_out_uv, gcpm->out_uv,
+ pps_data->op_ua, pre_out_ua, gcpm->out_ua);
+
+ ret = gcpm_dc_charging(gcpm);
+ if (ret < 0)
+ pps_ui = DC_ERROR_RETRY_MS;
+
+ ret = pps_update_adapter(&gcpm->pps_data,
+ gcpm->out_uv, gcpm->out_ua,
+ tcpm_psy);
+ if (ret < 0)
+ pps_ui = PPS_ERROR_RETRY_MS;
+ } else if (gcpm->dc_state == DC_ENABLE_PASSTHROUGH) {
+ struct pd_pps_data *pps_data = &gcpm->pps_data;
+
+ pps_ui = pps_update_adapter(pps_data, gcpm->out_uv,
+ gcpm->out_ua, tcpm_psy);
+ if (pps_ui < 0)
+ pps_ui = PPS_ERROR_RETRY_MS;
+
+ /* TODO: handoff handling of PPS to the DC charger */
+ ret = gcpm_chg_offline(gcpm);
+ if (ret == 0)
+ ret = gcpm_dc_start(gcpm, gcpm->dc_index);
+ if (ret == 0) {
+ gcpm->dc_state = DC_PASSTHROUGH;
+ pps_ui = DC_RUN_DELAY_MS;
+ } else if (pps_ui > DC_ERROR_RETRY_MS) {
+ pps_ui = DC_ERROR_RETRY_MS;
+ }
+ } else if (gcpm->dc_state == DC_PASSTHROUGH) {
+ struct power_supply *dc_psy;
+
+ dc_psy = gcpm_chg_get_active(gcpm);
+ if (!dc_psy) {
+ pr_err("PPS_Work: no adapter while in DC_PASSTHROUGH\n");
+ pps_ui = DC_ERROR_RETRY_MS;
+ } else {
+ ret = GPSY_SET_PROP(dc_psy,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ 1);
+ if (ret == 0) {
+ ret = gcpm_chg_ping(gcpm, 0, 0);
+ if (ret < 0)
+ pr_err("PPS_DC: ping failed with %d\n",
+ ret);
+
+ } else if (ret == -EBUSY) {
+ pps_ui = DC_ERROR_RETRY_MS;
+ } else {
+ pr_err("PPS_Work: cannot enable DC_charging\n");
+
+ ret = gcpm_chg_set_online(gcpm, 0);
+ if (ret < 0)
+ pr_err("PPS_Work: online default %d\n",
+ ret);
+ }
+ }
+
+ } else {
+ /* steady on PPS, DC is not enabled */
+ pps_ui = pps_update_adapter(&gcpm->pps_data, -1, -1, tcpm_psy);
+
+ pr_info("PPS_Work: STEADY pd_online=%d pps_ui=%d dc_ena=%d dc_state=%d\n",
+ pps_data->pd_online, pps_ui, gcpm->dc_index,
+ gcpm->dc_state);
+ if (pps_ui < 0)
+ pps_ui = PPS_ERROR_RETRY_MS;
+ }
+
+pps_dc_reschedule:
+ if (pps_ui <= 0) {
+ pr_info("PPS_Work: done=%d pps_stage=%d dc_state=%d",
+ pps_ui, gcpm->pps_data.stage, gcpm->dc_state);
+ if (log_on_done)
+ pps_log(pps_data, "PPS_Work: done=%d pd_ol=%d pps_stage=%d dc_state=%d\n",
+ pps_ui, pps_data->pd_online, gcpm->pps_data.stage,
+ gcpm->dc_state);
+ } else {
+ pr_info("PPS_Work: reschedule in %d pps_stage=%d (%d:%d) dc_state=%d (%d:%d)",
+ pps_ui, gcpm->pps_data.stage,
+ gcpm->pps_data.out_uv, gcpm->pps_data.op_ua,
+ gcpm->dc_state, gcpm->out_uv, gcpm->out_ua);
+ schedule_delayed_work(&gcpm->pps_work,
+ msecs_to_jiffies(pps_ui));
+ }
+
+pps_dc_done:
+ mutex_unlock(&gcpm->chg_psy_lock);
+}
+
+static int gcpm_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ struct gcpm_drv *gcpm = power_supply_get_drvdata(psy);
+ bool taper_control, ta_check = false;
+ struct power_supply *chg_psy = NULL;
+ bool route = true;
+ int ret = 0;
+
+ pm_runtime_get_sync(gcpm->device);
+ if (!gcpm->init_complete || !gcpm->resume_complete) {
+ pm_runtime_put_sync(gcpm->device);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(gcpm->device);
+
+ mutex_lock(&gcpm->chg_psy_lock);
+ switch (psp) {
+
+ /* do not route to the active charger */
+ case POWER_SUPPLY_PROP_TAPER_CONTROL:
+ taper_control = (pval->intval != 0);
+ ta_check = taper_control != gcpm->taper_control;
+ gcpm->taper_control = taper_control;
+ route = false;
+ break;
+
+ /* also route to the active charger */
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ psp = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX;
+ /* compat, fall through */
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ta_check = gcpm->fv_uv != pval->intval;
+ gcpm->fv_uv = pval->intval;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ta_check = gcpm->cc_max != pval->intval;
+ gcpm->cc_max = pval->intval;
+ break;
+
+ /* just route to the active charger */
+ default:
+ break;
+ }
+
+ /* logic that select the active charging */
+ if (ta_check)
+ gcpm_chg_check(gcpm);
+ /* route to active charger when needed */
+ if (route)
+ chg_psy = gcpm_chg_get_active(gcpm);
+ if (chg_psy) {
+ ret = power_supply_set_property(chg_psy, psp, pval);
+ if (ret < 0) {
+ const char *name= (chg_psy->desc && chg_psy->desc->name) ?
+ chg_psy->desc->name : "???";
+
+ pr_err("cannot route prop=%d to %d:%s\n",
+ psp, gcpm->chg_psy_active, name);
+ }
+ } else {
+ pr_err("invalid charger=%d for prop=%d\n",
+ gcpm->chg_psy_active, psp);
+ }
+
+ /* the charger should not call into gcpm: this can change though */
+ mutex_unlock(&gcpm->chg_psy_lock);
+ return ret;
+}
+
+static int gcpm_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *pval)
+{
+ struct gcpm_drv *gcpm = power_supply_get_drvdata(psy);
+ struct power_supply *chg_psy;
+
+ pm_runtime_get_sync(gcpm->device);
+ if (!gcpm->init_complete || !gcpm->resume_complete) {
+ pm_runtime_put_sync(gcpm->device);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(gcpm->device);
+
+ mutex_lock(&gcpm->chg_psy_lock);
+ chg_psy = gcpm_chg_get_active(gcpm);
+ mutex_unlock(&gcpm->chg_psy_lock);
+ if (!chg_psy)
+ return -ENODEV;
+
+ switch (psp) {
+
+ /* handle locally for now */
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ pval->int64val = gcpm_get_charger_state(gcpm, chg_psy);
+ return 0;
+
+ /* route to the active charger */
+ default:
+ break;
+ }
+
+ return power_supply_get_property(chg_psy, psp, pval);
+}
+
+static int gcpm_psy_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ case POWER_SUPPLY_PROP_TAPER_CONTROL:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * TODO: POWER_SUPPLY_PROP_RERUN_AICL, POWER_SUPPLY_PROP_TEMP
+ */
+static enum power_supply_property gcpm_psy_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_DISABLE,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ /* pixel battery management subsystem */
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, /* cc_max */
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, /* fv_uv */
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ POWER_SUPPLY_PROP_CURRENT_MAX, /* input current limit */
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, /* set float voltage, compat */
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static struct power_supply_desc gcpm_psy_desc = {
+ .name = "gcpm",
+ .type = POWER_SUPPLY_TYPE_UNKNOWN,
+ .get_property = gcpm_psy_get_property,
+ .set_property = gcpm_psy_set_property,
+ .property_is_writeable = gcpm_psy_is_writeable,
+ .properties = gcpm_psy_properties,
+ .num_properties = ARRAY_SIZE(gcpm_psy_properties),
+};
+
+static int gcpm_psy_changed(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct gcpm_drv *gcpm = container_of(nb, struct gcpm_drv, chg_nb);
+ const int index = gcpm->chg_psy_active;
+ struct power_supply *psy = data;
+
+ if (index == -1)
+ return NOTIFY_OK;
+
+ if ((action != PSY_EVENT_PROP_CHANGED) ||
+ (psy == NULL) || (psy->desc == NULL) || (psy->desc->name == NULL))
+ return NOTIFY_OK;
+
+ if (strcmp(psy->desc->name, gcpm->chg_psy_names[index]) == 0) {
+ /* route upstream when the charger active and found */
+ if (gcpm->chg_psy_avail[index])
+ power_supply_changed(gcpm->psy);
+ mod_delayed_work(system_wq, &gcpm->pps_work, 0);
+ } else if (strcmp(psy->desc->name, gcpm->chg_psy_names[0]) == 0) {
+ /* something is up with the default charger */
+ mod_delayed_work(system_wq, &gcpm->pps_work, 0);
+ } else if (gcpm->tcpm_psy_name &&
+ !strcmp(psy->desc->name, gcpm->tcpm_psy_name))
+ {
+ /* kick off PPS */
+ mod_delayed_work(system_wq, &gcpm->pps_work, 0);
+ }
+
+ return NOTIFY_OK;
+}
+
+#define INIT_DELAY_MS 100
+#define INIT_RETRY_DELAY_MS 1000
+
+#define GCPM_TCPM_PSY_MAX 2
+
+static void gcpm_init_work(struct work_struct *work)
+{
+ struct gcpm_drv *gcpm = container_of(work, struct gcpm_drv,
+ init_work.work);
+ int i, found = 0, ret = 0;
+
+ /*
+ * could call pps_init() in probe() and use lazy init for ->tcpm_psy
+ * when the device an APDO in the sink capabilities.
+ */
+ if (gcpm->tcpm_phandle && !gcpm->tcpm_psy) {
+ struct power_supply *tcpm_psy;
+
+ tcpm_psy = pps_get_tcpm_psy(gcpm->device->of_node,
+ GCPM_TCPM_PSY_MAX);
+ if (!IS_ERR_OR_NULL(tcpm_psy)) {
+ gcpm->tcpm_psy_name = tcpm_psy->desc->name;
+ gcpm->tcpm_psy = tcpm_psy;
+
+ /* PPS charging: needs an APDO */
+ ret = pps_init(&gcpm->pps_data, gcpm->device);
+ if (ret == 0 && gcpm->debug_entry)
+ pps_init_fs(&gcpm->pps_data, gcpm->debug_entry);
+ if (ret < 0) {
+ pr_err("PPS init failure for %s (%d)\n",
+ tcpm_psy->desc->name, ret);
+ } else {
+ pps_init_state(&gcpm->pps_data);
+ pr_info("PPS available for %s\n",
+ gcpm->tcpm_psy_name);
+ }
+
+ } else if (!tcpm_psy || !gcpm->log_psy_ratelimit) {
+ pr_warn("PPS not available\n");
+ gcpm->tcpm_phandle = 0;
+ } else {
+ pr_warn("tcpm power supply not found, retrying... ret:%d\n",
+ ret);
+ gcpm->log_psy_ratelimit--;
+ }
+
+ }
+
+ /* default is index 0 */
+ for (i = 0; i < gcpm->chg_psy_count; i++) {
+ if (!gcpm->chg_psy_avail[i]) {
+ const char *name = gcpm->chg_psy_names[i];
+
+ gcpm->chg_psy_avail[i] = power_supply_get_by_name(name);
+ if (gcpm->chg_psy_avail[i])
+ pr_info("init_work found %d:%s\n", i, name);
+ }
+
+ found += !!gcpm->chg_psy_avail[i];
+ }
+
+ /* we done when we have (at least) the primary */
+ if (gcpm->chg_psy_avail[0]) {
+
+ /* register the notifier only when have one (the default) */
+ if (!gcpm->init_complete) {
+ gcpm->chg_nb.notifier_call = gcpm_psy_changed;
+ ret = power_supply_reg_notifier(&gcpm->chg_nb);
+ if (ret < 0)
+ pr_err("cannot register power supply notifer, ret=%d\n",
+ ret);
+ }
+
+ gcpm->resume_complete = true;
+ gcpm->init_complete = true;
+ }
+
+ /* keep looking for late arrivals && TCPM */
+ if (found != gcpm->chg_psy_count && gcpm->chg_psy_retries)
+ gcpm->chg_psy_retries--;
+
+ if (gcpm->chg_psy_retries || (gcpm->tcpm_phandle && !gcpm->tcpm_psy)) {
+ const unsigned long jif = msecs_to_jiffies(INIT_RETRY_DELAY_MS);
+
+ schedule_delayed_work(&gcpm->init_work, jif);
+ } else {
+ pr_info("init_work done %d/%d pps=%d\n", found,
+ gcpm->chg_psy_count, !!gcpm->tcpm_psy);
+ }
+}
+
+static int gcpm_debug_get_active(void *data, u64 *val)
+{
+ struct gcpm_drv *gcpm = data;
+
+ mutex_lock(&gcpm->chg_psy_lock);
+ *val = gcpm->chg_psy_active;
+ mutex_unlock(&gcpm->chg_psy_lock);
+ return 0;
+}
+
+static int gcpm_debug_set_active(void *data, u64 val)
+{
+ struct gcpm_drv *gcpm = data;
+ const int intval = val;
+ int ret;
+
+ if (intval != -1 && (intval < 0 || intval >= gcpm->chg_psy_count))
+ return -ERANGE;
+ if (intval != -1 && !gcpm->chg_psy_avail[intval])
+ return -EINVAL;
+
+ mutex_lock(&gcpm->chg_psy_lock);
+ gcpm->force_active = val;
+ ret = gcpm_chg_check(gcpm);
+ mutex_unlock(&gcpm->chg_psy_lock);
+
+ return (ret < 0) ? ret : 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(gcpm_debug_active_fops, gcpm_debug_get_active,
+ gcpm_debug_set_active, "%llu\n");
+
+static int gcpm_debug_pps_stage_get(void *data, u64 *val)
+{
+ struct gcpm_drv *gcpm = data;
+
+ mutex_lock(&gcpm->chg_psy_lock);
+ *val = gcpm->pps_data.stage;
+ mutex_unlock(&gcpm->chg_psy_lock);
+ return 0;
+}
+
+static int gcpm_debug_pps_stage_set(void *data, u64 val)
+{
+ struct gcpm_drv *gcpm = data;
+ const int intval = (int)val;
+
+ if (intval < PPS_DISABLED || intval > PPS_ACTIVE)
+ return -EINVAL;
+
+ mutex_lock(&gcpm->chg_psy_lock);
+ gcpm->pps_data.stage = intval;
+ gcpm->force_pps = !pps_is_disabled(intval);
+ mod_delayed_work(system_wq, &gcpm->pps_work, 0);
+ mutex_unlock(&gcpm->chg_psy_lock);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(gcpm_debug_pps_stage_fops, gcpm_debug_pps_stage_get,
+ gcpm_debug_pps_stage_set, "%llu\n");
+
+static struct dentry *gcpm_init_fs(struct gcpm_drv *gcpm)
+{
+ struct dentry *de;
+
+ de = debugfs_create_dir("google_cpm", 0);
+ if (IS_ERR_OR_NULL(de))
+ return NULL;
+
+ debugfs_create_file("active", 0644, de, gcpm, &gcpm_debug_active_fops);
+ debugfs_create_u32("dc_ta_limit", 0644, de, &gcpm->ta_dc_limit);
+ debugfs_create_file("pps_stage", 0644, de, gcpm,
+ &gcpm_debug_pps_stage_fops);
+
+ return de;
+}
+
+static int gcpm_probe_psy_names(struct gcpm_drv *gcpm)
+{
+ struct device *dev = gcpm->device;
+ int i, count, ret;
+
+ if (!gcpm->device)
+ return -EINVAL;
+
+ count = of_property_count_strings(dev->of_node,
+ "google,chg-power-supplies");
+ if (count <= 0 || count > GCPM_MAX_CHARGERS)
+ return -ERANGE;
+
+ ret = of_property_read_string_array(dev->of_node,
+ "google,chg-power-supplies",
+ (const char**)&gcpm->chg_psy_names,
+ count);
+ if (ret != count)
+ return -ERANGE;
+
+ for (i = 0; i < count; i++)
+ dev_info(gcpm->device, "%d:%s\n", i, gcpm->chg_psy_names[i]);
+
+ return count;
+}
+
+#define LOG_PSY_RATELIMIT_CNT 200
+
+static int google_cpm_probe(struct platform_device *pdev)
+{
+ struct power_supply_config psy_cfg = { 0 };
+ const char *tmp_name = NULL;
+ struct gcpm_drv *gcpm;
+ int ret;
+
+ gcpm = devm_kzalloc(&pdev->dev, sizeof(*gcpm), GFP_KERNEL);
+ if (!gcpm)
+ return -ENOMEM;
+
+ gcpm->device = &pdev->dev;
+ gcpm->force_active = -1;
+ gcpm->log_psy_ratelimit = LOG_PSY_RATELIMIT_CNT;
+ gcpm->chg_psy_retries = 10; /* chg_psy_retries * INIT_RETRY_DELAY_MS */
+ gcpm->out_uv = -1;
+ gcpm->out_ua = -1;
+ INIT_DELAYED_WORK(&gcpm->pps_work, gcpm_pps_dc_work);
+ INIT_DELAYED_WORK(&gcpm->init_work, gcpm_init_work);
+ mutex_init(&gcpm->chg_psy_lock);
+
+ /* this is my name */
+ ret = of_property_read_string(pdev->dev.of_node, "google,psy-name",
+ &tmp_name);
+ if (ret == 0) {
+ gcpm_psy_desc.name = devm_kstrdup(&pdev->dev, tmp_name,
+ GFP_KERNEL);
+ if (!gcpm_psy_desc.name)
+ return -ENOMEM;
+ }
+
+ /* sub power supply names */
+ gcpm->chg_psy_count = gcpm_probe_psy_names(gcpm);
+ if (gcpm->chg_psy_count <= 0)
+ return -ENODEV;
+
+ /* PPS needs a TCPM power supply */
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "google,tcpm-power-supply",
+ &gcpm->tcpm_phandle);
+ if (ret < 0)
+ pr_warn("google,tcpm-power-supply not defined\n");
+
+ /* Direct Charging: valid only with PPS */
+ gcpm->dcen_gpio = of_get_named_gpio(pdev->dev.of_node,
+ "google,dc-en", 0);
+ if (gcpm->dcen_gpio >= 0) {
+ of_property_read_u32(pdev->dev.of_node, "google,dc-en-value",
+ &gcpm->dcen_gpio_default);
+
+ /* make sure that the DC is DISABLED before doing this */
+ ret = gpio_direction_output(gcpm->dcen_gpio,
+ gcpm->dcen_gpio_default);
+ pr_info("google,dc-en value = %d ret=%d\n",
+ gcpm->dcen_gpio_default, ret);
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node, "google,dc-limit",
+ &gcpm->ta_dc_limit);
+ if (ret == 0)
+ gcpm->ta_dc_limit = GCPM_DEFAULT_TA_DC_LIMIT;
+
+ /* sysfs & debug */
+ gcpm->debug_entry = gcpm_init_fs(gcpm);
+ if (!gcpm->debug_entry)
+ pr_warn("No debug control\n");
+
+ platform_set_drvdata(pdev, gcpm);
+
+ psy_cfg.drv_data = gcpm;
+ psy_cfg.of_node = pdev->dev.of_node;
+ gcpm->psy = devm_power_supply_register(gcpm->device, &gcpm_psy_desc,
+ &psy_cfg);
+ if (IS_ERR(gcpm->psy)) {
+ ret = PTR_ERR(gcpm->psy);
+ if (ret == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ /* TODO: fail with -ENODEV */
+ dev_err(gcpm->device, "Couldn't register as power supply, ret=%d\n",
+ ret);
+ }
+
+ /* give time to fg driver to start */
+ schedule_delayed_work(&gcpm->init_work,
+ msecs_to_jiffies(INIT_DELAY_MS));
+
+ return 0;
+}
+
+static int google_cpm_remove(struct platform_device *pdev)
+{
+ struct gcpm_drv *gcpm = platform_get_drvdata(pdev);
+ int i;
+
+ if (!gcpm)
+ return 0;
+
+ for (i = 0; i < gcpm->chg_psy_count; i++) {
+ if (!gcpm->chg_psy_avail[i])
+ continue;
+
+ power_supply_put(gcpm->chg_psy_avail[i]);
+ gcpm->chg_psy_avail[i] = NULL;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id google_cpm_of_match[] = {
+ {.compatible = "google,cpm"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, google_cpm_of_match);
+
+
+static struct platform_driver google_cpm_driver = {
+ .driver = {
+ .name = "google_cpm",
+ .owner = THIS_MODULE,
+ .of_match_table = google_cpm_of_match,
+#ifdef SUPPORT_PM_SLEEP
+ /* .pm = &gcpm_pm_ops, */
+#endif
+ /* .probe_type = PROBE_PREFER_ASYNCHRONOUS, */
+ },
+ .probe = google_cpm_probe,
+ .remove = google_cpm_remove,
+};
+
+static int __init google_cpm_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&google_cpm_driver);
+ if (ret < 0) {
+ pr_err("device registration failed: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void __init google_cpm_exit(void)
+{
+ platform_driver_unregister(&google_cpm_driver);
+ pr_info("unregistered platform driver\n");
+}
+
+module_init(google_cpm_init);
+module_exit(google_cpm_exit);
+MODULE_DESCRIPTION("Google Charging Policy Manager");
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/google_dc_pps.c b/google_dc_pps.c
new file mode 100644
index 0000000..1175098
--- /dev/null
+++ b/google_dc_pps.c
@@ -0,0 +1,888 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeup.h>
+#include <linux/time.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/tcpm.h>
+#include <linux/alarmtimer.h>
+#include "google_bms.h"
+#include "google_psy.h"
+#include "google_dc_pps.h"
+#include "logbuffer.h"
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#endif
+
+#define PPS_UPDATE_DELAY_MS 2000
+
+#define OP_SNK_MW 7600 /* see b/159863291 */
+#define PD_SNK_MAX_MV 9000
+#define PD_SNK_MIN_MV 5000
+#define PD_SNK_MAX_MA 3000
+#define PD_SNK_MAX_MA_9V 2200
+
+
+#define PDO_FIXED_FLAGS \
+ (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM)
+
+/*
+ * There is a similar one in tcpm.c
+ * NOTE: we don't really need to replicate the values in tcpm.c in the
+ * internal state, we just need to know to set 2 to the TCPM power supply.
+ */
+enum tcpm_psy_online_states {
+ TCPM_PSY_OFFLINE = 0,
+ TCPM_PSY_FIXED_ONLINE,
+ TCPM_PSY_PROG_ONLINE,
+};
+
+#define get_boot_sec() div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC)
+
+void pps_log(struct pd_pps_data *pps, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!pps || !pps->log)
+ return;
+
+ va_start(args, fmt);
+ logbuffer_vlog(pps->log, fmt, args);
+ va_end(args);
+}
+
+/* SW enable detect setting ->stage = PPS_NONE and calling pps_work() */
+void pps_init_state(struct pd_pps_data *pps_data)
+{
+ /* pps_data->src_caps can be NULL */
+ tcpm_put_partner_src_caps(&pps_data->src_caps);
+
+ pps_data->pd_online = TCPM_PSY_OFFLINE;
+ pps_data->stage = PPS_DISABLED;
+ pps_data->keep_alive_cnt = 0;
+ pps_data->nr_src_cap = 0;
+ pps_data->src_caps = NULL;
+
+ /* not reference counted */
+ if (pps_data->stay_awake)
+ __pm_relax(&pps_data->pps_ws);
+
+}
+EXPORT_SYMBOL_GPL(pps_init_state);
+
+struct tcpm_port *chg_get_tcpm_port(struct power_supply *tcpm_psy)
+{
+ return tcpm_psy ? power_supply_get_drvdata(tcpm_psy) : NULL;
+}
+
+/* PUBLIC TODO: pass the actual PDO, deprecate */
+int chg_update_capability(struct power_supply *tcpm_psy,
+ unsigned int nr_pdo,
+ u32 pps_cap)
+{
+ struct tcpm_port *port = chg_get_tcpm_port(tcpm_psy);
+ const u32 pdo[] = {PDO_FIXED(5000, PD_SNK_MAX_MA, PDO_FIXED_FLAGS),
+ PDO_FIXED(PD_SNK_MAX_MV, PD_SNK_MAX_MA_9V, 0),
+ pps_cap};
+
+ if (!port)
+ return -ENODEV;
+
+ if (!nr_pdo || nr_pdo > PDO_MAX_SUPP)
+ return -EINVAL;
+
+ return tcpm_update_sink_capabilities(port, pdo, nr_pdo, OP_SNK_MW);
+}
+
+/* false when not present or error (either way don't run) */
+static unsigned int pps_is_avail(struct pd_pps_data *pps,
+ struct power_supply *tcpm_psy)
+{
+ pps->max_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX);
+ pps->min_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_MIN);
+ pps->max_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_MAX);
+ pps->out_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ pps->op_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
+ if (pps->max_uv < 0 || pps->min_uv < 0 || pps->max_ua < 0 ||
+ pps->out_uv < 0 || pps->op_ua < 0)
+ return PPS_NONE;
+
+ /* TODO: lower the loglevel after the development stage */
+ pps_log(pps, "max_v %d, min_v %d, max_c %d, out_v %d, op_c %d",
+ pps->max_uv, pps->min_uv, pps->max_ua, pps->out_uv,
+ pps->op_ua);
+
+ /* FIXME: set interval to PD_T_PPS_TIMEOUT here may cause timeout */
+ return PPS_AVAILABLE;
+}
+
+/* make sure that the adapter doesn't revert back to FIXED PDO */
+int pps_ping(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
+{
+ int rc;
+
+ if (!tcpm_psy)
+ return -ENODEV;
+
+ rc = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
+ TCPM_PSY_PROG_ONLINE);
+ if (rc == 0)
+ pps->pd_online = TCPM_PSY_PROG_ONLINE;
+ else if (rc != -EAGAIN && rc != -EOPNOTSUPP)
+ pps_log(pps, "failed to ping, ret = %d", rc);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(pps_ping);
+
+int pps_get_src_cap(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
+{
+ struct tcpm_port *port = chg_get_tcpm_port(tcpm_psy);
+
+ if (!pps || !port)
+ return -EINVAL;
+
+ pps->nr_src_cap = tcpm_get_partner_src_caps(port, &pps->src_caps);
+
+ return pps->nr_src_cap;
+}
+EXPORT_SYMBOL_GPL(pps_get_src_cap);
+
+/* assume that we are already online and in PPS stage */
+bool pps_prog_check_online(struct pd_pps_data *pps_data,
+ struct power_supply *tcpm_psy)
+{
+ int pd_online = 0;
+
+ pd_online = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE);
+ if (pd_online == 0) {
+ pps_init_state(pps_data);
+ return false;
+ }
+
+ if (pd_online != TCPM_PSY_PROG_ONLINE)
+ goto not_supp;
+
+ if (pps_data->stage != PPS_ACTIVE) {
+ int rc;
+
+ pps_data->stage = pps_is_avail(pps_data, tcpm_psy);
+ if (pps_data->stage != PPS_AVAILABLE) {
+ pr_debug("%s: not available\n", __func__);
+ goto not_supp;
+ }
+
+ rc = pps_ping(pps_data, tcpm_psy);
+ if (rc < 0) {
+ pr_debug("%s: ping failed %d\n", __func__, rc);
+ goto not_supp;
+ }
+
+ pps_data->last_update = get_boot_sec();
+ rc = pps_get_src_cap(pps_data, tcpm_psy);
+ if (rc < 0) {
+ pr_debug("%s: no source caps %d\n", __func__, rc);
+ goto not_supp;
+ }
+
+ pps_data->pd_online = TCPM_PSY_PROG_ONLINE;
+ pps_data->stage = PPS_ACTIVE;
+ }
+
+ return true;
+not_supp:
+ pps_init_state(pps_data);
+ pps_data->stage = PPS_NOTSUPP;
+ return false;
+}
+EXPORT_SYMBOL_GPL(pps_prog_check_online);
+
+/* enable PPS prog mode (Internal), also start the negotiation */
+static int pps_prog_online(struct pd_pps_data *pps,
+ struct power_supply *tcpm_psy)
+{
+ int ret;
+
+ ret = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
+ TCPM_PSY_PROG_ONLINE);
+ if (ret == 0) {
+ pps->pd_online = TCPM_PSY_PROG_ONLINE;
+ pps->stage = PPS_NONE;
+ }
+
+ pps->last_update = get_boot_sec();
+ return ret;
+}
+
+/* Disable PPS prog mode, will end up in PPS_NOTSUP */
+int pps_prog_offline(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
+{
+ int ret;
+
+ if (!tcpm_psy)
+ return -ENODEV;
+
+ /* pd_online = TCPM_PSY_OFFLINE, stage = PPS_NONE; */
+ ret = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
+ TCPM_PSY_FIXED_ONLINE);
+ if (ret == -EOPNOTSUPP)
+ ret = 0;
+ if (ret == 0)
+ pps_init_state(pps);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pps_prog_offline);
+
+
+void pps_adjust_volt(struct pd_pps_data *pps, int mod)
+{
+ if (mod > 0) {
+ pps->out_uv = (pps->out_uv + mod) < pps->max_uv ?
+ (pps->out_uv + mod) : pps->max_uv;
+ } else if (mod < 0) {
+ pps->out_uv = (pps->out_uv + mod) > pps->min_uv ?
+ (pps->out_uv + mod) : pps->min_uv;
+ }
+}
+
+/*
+ * Note: Some adapters have several PPS profiles providing different voltage
+ * ranges and different maximal currents. If the device demands more power from
+ * the adapter but reached the maximum it can get in the current profile,
+ * search if there exists another profile providing more power. If it demands
+ * less power, search if there exists another profile providing enough power
+ * with higher current.
+ *
+ * return negative on errors or no suitable profile
+ * return 0 on successful profile switch
+ */
+int pps_switch_profile(struct pd_pps_data *pps, struct power_supply *tcpm_psy,
+ bool more_pwr)
+{
+ int i, ret = -ENODATA;
+ u32 pdo;
+ u32 max_mv, max_ma, max_mw;
+ u32 current_mw, current_ma;
+
+ if (pps->nr_src_cap < 2)
+ return -EINVAL;
+
+ current_ma = pps->op_ua / 1000;
+ current_mw = (pps->out_uv / 1000) * current_ma / 1000;
+
+ for (i = 1; i < pps->nr_src_cap; i++) {
+ pdo = pps->src_caps[i];
+
+ if (pdo_type(pdo) != PDO_TYPE_APDO)
+ continue;
+
+ /* TODO: use pps_data sink capabilities */
+ max_mv = min_t(u32, PD_SNK_MAX_MV,
+ pdo_pps_apdo_max_voltage(pdo));
+ /* TODO: use pps_data sink capabilities */
+ max_ma = min_t(u32, PD_SNK_MAX_MA,
+ pdo_pps_apdo_max_current(pdo));
+ max_mw = max_mv * max_ma / 1000;
+
+ if (more_pwr && max_mw > current_mw) {
+ /* export maximal capability, TODO: use sink cap */
+ pdo = PDO_PPS_APDO(PD_SNK_MIN_MV,
+ PD_SNK_MAX_MV,
+ PD_SNK_MAX_MA);
+ ret = chg_update_capability(tcpm_psy, PDO_PPS, pdo);
+ if (ret < 0)
+ pps_log(pps, "Failed to update sink caps, ret %d",
+ ret);
+ break;
+ } else if (!more_pwr && max_mw >= current_mw &&
+ max_ma > current_ma) {
+
+ /* TODO: tune the max_mv, fix this */
+ pdo = PDO_PPS_APDO(PD_SNK_MIN_MV, 6000, PD_SNK_MAX_MA);
+ ret = chg_update_capability(tcpm_psy, PDO_PPS, pdo);
+ if (ret < 0)
+ pps_log(pps, "Failed to update sink caps, ret %d",
+ ret);
+ break;
+ }
+ }
+
+ if (ret == 0) {
+ pps->keep_alive_cnt = 0;
+ pps->stage = PPS_NONE;
+ }
+
+ return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int debug_get_pps_out_uv(void *data, u64 *val)
+{
+ struct pd_pps_data *pps_data = data;
+
+ *val = pps_data->out_uv;
+ return 0;
+}
+
+static int debug_set_pps_out_uv(void *data, u64 val)
+{
+ struct pd_pps_data *pps_data = data;
+
+ /* TODO: use votable */
+ pps_data->out_uv = val;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_pps_out_uv_fops,
+ debug_get_pps_out_uv,
+ debug_set_pps_out_uv, "%llu\n");
+
+static int debug_get_pps_op_ua(void *data, u64 *val)
+{
+ struct pd_pps_data *pps_data = data;
+
+ *val = pps_data->op_ua;
+ return 0;
+}
+
+static int debug_set_pps_op_ua(void *data, u64 val)
+{
+ struct pd_pps_data *pps_data = data;
+
+ /* TODO: use votable */
+ pps_data->op_ua = val;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_pps_op_ua_fops,
+ debug_get_pps_op_ua,
+ debug_set_pps_op_ua, "%llu\n");
+
+int pps_init_fs(struct pd_pps_data *pps_data, struct dentry *de)
+{
+ if (!de)
+ return -ENODEV;
+
+ debugfs_create_file("pps_out_uv", 0600, de, pps_data,
+ &debug_pps_out_uv_fops);
+ debugfs_create_file("pps_out_ua", 0600, de, pps_data,
+ &debug_pps_op_ua_fops);
+ debugfs_create_file("pps_op_ua", 0600, de, pps_data,
+ &debug_pps_op_ua_fops);
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int pps_get_sink_pdo(u32 *snk_pdo, int max, struct device_node *node)
+{
+ struct device_node *dn, *conn;
+ unsigned int nr_snk_pdo;
+ const __be32 *prop;
+ int length, ret;
+
+ prop = of_get_property(node, "google,usbc-connector", NULL);
+ if (!prop)
+ prop = of_get_property(node, "google,usb-c-connector", NULL);
+ if (!prop)
+ prop = of_get_property(node, "google,tcpm-power-supply", NULL);
+ if (!prop) {
+ pr_err("Couldn't find property \n");
+ return -ENOENT;
+ }
+
+ dn = of_find_node_by_phandle(be32_to_cpup(prop));
+ if (!dn) {
+ pr_err("Couldn't find usb_con node\n");
+ return -ENOENT;
+ }
+
+ conn = of_get_child_by_name(dn, "connector");
+ if (conn) {
+ of_node_put(dn);
+ dn = conn;
+ }
+
+ prop = of_get_property(dn, "sink-pdos", &length);
+ if (!prop) {
+ pr_err("Couldn't find sink-pdos property\n");
+ of_node_put(dn);
+ return -ENOENT;
+ }
+
+ nr_snk_pdo = length / sizeof(u32);
+ if (nr_snk_pdo == 0 || nr_snk_pdo > max) {
+ pr_err("Invalid length of sink-pdos\n");
+ of_node_put(dn);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(dn, "sink-pdos", snk_pdo, nr_snk_pdo);
+ of_node_put(dn);
+ if (ret < 0)
+ return ret;
+
+ return nr_snk_pdo;
+}
+
+static int pps_find_apdo(struct pd_pps_data *pps_data, int nr_snk_pdo)
+{
+ int i;
+
+ for (i = 0; i < nr_snk_pdo; i++) {
+ u32 pdo = pps_data->snk_pdo[i];
+
+ if (pdo_type(pdo) != PDO_TYPE_FIXED)
+ pr_debug("%s %d type=%d", __func__, i, pdo_type(pdo));
+ else
+ pr_debug("%s %d FIXED v=%d c=%d", __func__, i,
+ pdo_fixed_voltage(pdo),
+ pdo_max_current(pdo));
+
+ if (pdo_type(pdo) == PDO_TYPE_APDO) {
+ /* TODO: check APDO_TYPE_PPS */
+ pps_data->default_pps_pdo = pdo;
+ return 0;
+ }
+ }
+
+ return -ENODATA;
+}
+
+static int pps_init_node(struct pd_pps_data *pps_data,
+ struct device_node *node)
+{
+ int ret, nr_snk_pdo;
+
+ memset(pps_data, 0, sizeof(*pps_data));
+
+ nr_snk_pdo = pps_get_sink_pdo(pps_data->snk_pdo, PDO_MAX_OBJECTS, node);
+ if (nr_snk_pdo < 0) {
+ pr_err("Couldn't read sink-pdos, ret %d\n", nr_snk_pdo);
+ return -ENOENT;
+ }
+
+ ret = pps_find_apdo(pps_data, nr_snk_pdo);
+ if (ret < 0) {
+ pr_err("nr_sink_pdo=%d sink APDO not found ret=%d\n",
+ nr_snk_pdo, ret);
+ return -ENOENT;
+ }
+
+ /*
+ * The port needs to ping or update the PPS adapter every 10 seconds
+ * (maximum). However, Qualcomm PD phy returns error when system is
+ * waking up. To prevent the timeout when system is resumed from
+ * suspend, hold a wakelock while PPS is active.
+ *
+ * Remove this wakeup source once we fix the Qualcomm PD phy issue.
+ */
+ pps_data->stay_awake = of_property_read_bool(node, "google,pps-awake");
+ if (pps_data->stay_awake)
+ wakeup_source_init(&pps_data->pps_ws, "google-pps");
+
+ pps_data->log = debugfs_logbuffer_register("pps");
+ if (IS_ERR(pps_data->log))
+ pps_data->log = NULL;
+
+ pps_data->nr_snk_pdo = nr_snk_pdo;
+
+ return 0;
+}
+
+/* Look for the connector and retrieve source capabilities.
+ * pps_data->nr_snk_pdo == 0 means no PPS
+ */
+int pps_init(struct pd_pps_data *pps_data, struct device *dev)
+{
+ return pps_init_node(pps_data, dev->of_node);
+}
+EXPORT_SYMBOL_GPL(pps_init);
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * This is the first part of the DC/PPS charging state machine.
+ * Detect and configure the PPS adapter for the profile.
+ *
+ * returns:
+ * . the max update interval pps should vote for
+ * . 0 to disable the PPS update interval voter
+ * . <0 for error
+ */
+int pps_work(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
+{
+ int pd_online, usbc_type;
+ unsigned int stage;
+
+ if (!tcpm_psy)
+ return -ENODEV;
+
+ /*
+ * 2) pps->pd_online == TCPM_PSY_PROG_ONLINE && stage == PPS_NONE
+ * If the source really support PPS (set in 1): set stage to
+ * PPS_AVAILABLE and reschedule after PD_T_PPS_TIMEOUT
+ */
+ if (pps->pd_online == TCPM_PSY_PROG_ONLINE && pps->stage == PPS_NONE) {
+ int rc;
+
+ pps->stage = pps_is_avail(pps, tcpm_psy);
+ if (pps->stage == PPS_AVAILABLE) {
+ rc = pps_ping(pps, tcpm_psy);
+ if (rc < 0) {
+ pps->pd_online = TCPM_PSY_FIXED_ONLINE;
+ return 0;
+ }
+
+ if (pps->stay_awake)
+ __pm_stay_awake(&pps->pps_ws);
+
+ pps->last_update = get_boot_sec();
+ rc = pps_get_src_cap(pps, tcpm_psy);
+ if (rc < 0)
+ pps_log(pps, "Cannot get partner src caps");
+ }
+
+ return PD_T_PPS_TIMEOUT;
+ }
+
+ /*
+ * no need to retry (error) when I cannot read POWER_SUPPLY_PROP_ONLINE.
+ * The prop is set to TCPM_PSY_PROG_ONLINE (from TCPM_PSY_FIXED_ONLINE)
+ * when usbc_type is POWER_SUPPLY_USB_TYPE_PD_PPS.
+ */
+ pd_online = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE);
+ if (pd_online < 0)
+ return 0;
+
+ /*
+ * 3) pd_online == TCPM_PSY_PROG_ONLINE == pps->pd_online
+ * pps is active now, we are done here. pd_online will change to
+ * if pd_online is !TCPM_PSY_PROG_ONLINE go back to 1) OR exit.
+ */
+ stage = (pd_online == pps->pd_online) &&
+ (pd_online == TCPM_PSY_PROG_ONLINE) &&
+ (pps->stage == PPS_AVAILABLE || pps->stage == PPS_ACTIVE) ?
+ PPS_ACTIVE : PPS_NONE;
+ if (stage != pps->stage) {
+ pps_log(pps, "work: pd_online %d->%d stage %d->%d", __func__,
+ pps->pd_online, pd_online,
+ pps->stage, stage);
+ pps->stage = stage;
+ }
+
+ if (pps->stage == PPS_ACTIVE)
+ return 0;
+
+ /*
+ * 1) stage == PPS_NONE && pps->pd_online!=TCPM_PSY_PROG_ONLINE
+ * If usbc_type is POWER_SUPPLY_USB_TYPE_PD_PPS and pd_online is
+ * TCPM_PSY_FIXED_ONLINE, enable PSPS (set POWER_SUPPLY_PROP_ONLINE to
+ * TCPM_PSY_PROG_ONLINE and reschedule in PD_T_PPS_TIMEOUT.
+ */
+ usbc_type = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_USB_TYPE);
+ if (pd_online == TCPM_PSY_FIXED_ONLINE &&
+ usbc_type == POWER_SUPPLY_USB_TYPE_PD_PPS) {
+ int rc;
+
+ rc = pps_prog_online(pps, tcpm_psy);
+ switch (rc) {
+ case 0:
+ return PD_T_PPS_TIMEOUT;
+ case -EAGAIN:
+ pps_log(pps, "work: not in SNK_READY, rerun");
+ return rc;
+
+ case -EOPNOTSUPP:
+ pps->stage = PPS_NOTSUPP;
+ if (pps->stay_awake)
+ __pm_relax(&pps->pps_ws);
+
+ pps_log(pps, "work: PPS not supported for this TA");
+ break;
+ default:
+ pps_log(pps, "work: PROP_ONLINE (%d)", rc);
+ break;
+ }
+
+ return 0;
+ }
+
+ pps->pd_online = pd_online;
+ return 0;
+}
+
+int pps_keep_alive(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
+{
+ int ret;
+
+ if (!tcpm_psy)
+ return -ENODEV;
+
+ ret = pps_ping(pps, tcpm_psy);
+ if (ret < 0) {
+ pps->pd_online = TCPM_PSY_FIXED_ONLINE;
+ pps->keep_alive_cnt = 0;
+ return ret;
+ }
+
+ pps->keep_alive_cnt += (pps->keep_alive_cnt < UINT_MAX);
+ pps->last_update = get_boot_sec();
+ return 0;
+}
+
+int pps_check_adapter(struct pd_pps_data *pps,
+ int pending_uv, int pending_ua,
+ struct power_supply *tcpm_psy)
+{
+ const int scale = 50000; /* HACK */
+ int out_uv, op_ua;
+
+ out_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ op_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
+
+ pr_debug("%s: mv=%d->%d ua=%d,%d\n", __func__,
+ out_uv, pending_uv, op_ua, pending_ua);
+
+ if (out_uv < 0 || op_ua < 0)
+ return -EIO;
+
+ return (out_uv / scale) >= (pending_uv /scale) &&
+ (op_ua / scale) >= (pending_ua / scale);
+}
+
+static int pps_set_prop(struct pd_pps_data *pps,
+ enum power_supply_property prop, int val,
+ struct power_supply *tcpm_psy)
+{
+ int ret;
+
+ ret = GPSY_SET_PROP(tcpm_psy, prop, val);
+ if (ret == 0) {
+ pps->keep_alive_cnt = 0;
+ } else if (ret == -EOPNOTSUPP) {
+ pps->pd_online = TCPM_PSY_FIXED_ONLINE;
+ pps->keep_alive_cnt = 0;
+ if (pps->stay_awake)
+ __pm_relax(&pps->pps_ws);
+ }
+
+ return ret;
+}
+
+/*
+ * return negative values on errors
+ * return PD_T_PPS_TIMEOUT after successful updates or pings
+ * return PPS_UPDATE_DELAY_MS when the update interval is less than
+ * PPS_UPDATE_DELAY_MS
+ * return the delta to the nex ping deadline otherwise
+ */
+int pps_update_adapter(struct pd_pps_data *pps,
+ int pending_uv, int pending_ua,
+ struct power_supply *tcpm_psy)
+{
+ int interval = get_boot_sec() - pps->last_update;
+ int ret;
+
+ pps->out_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+ pps->op_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
+ if (pps->out_uv < 0 || pps->op_ua < 0)
+ return -EIO;
+
+ if (pending_uv < 0)
+ pending_uv = pps->out_uv;
+ if (pending_ua < 0)
+ pending_ua = pps->op_ua;
+
+ pr_debug("%s: mv=%d->%d ua=%d->%d interval=%d\n", __func__,
+ pps->out_uv, pending_uv, pps->op_ua, pending_ua, interval);
+
+ /*
+ * TCPM accepts one change per power negotiation cycle.
+ * TODO: change voltage, current or current, voltage depending on
+ * the values.
+ */
+ if (pps->op_ua != pending_ua) {
+ if (interval * 1000 < PPS_UPDATE_DELAY_MS)
+ return PPS_UPDATE_DELAY_MS;
+
+ ret = pps_set_prop(pps, POWER_SUPPLY_PROP_CURRENT_NOW,
+ pending_ua, tcpm_psy);
+ pr_debug("%s: SET_UA out_ua %d->%d, ret=%d", __func__,
+ pps->op_ua, pending_ua, ret);
+
+ pps_log(pps, "SET_UA out_ua %d->%d, ret=%d",
+ pps->op_ua, pending_ua, ret);
+
+ if (ret == 0) {
+ pps->last_update = get_boot_sec();
+ pps->op_ua = pending_ua;
+ return PD_T_PPS_TIMEOUT;
+ }
+
+ if (ret != -EAGAIN && ret != -EOPNOTSUPP)
+ pps_log(pps, "failed to set CURRENT_NOW, ret = %d",
+ ret);
+ } else if (pps->out_uv != pending_uv) {
+ if (interval * 1000 < PPS_UPDATE_DELAY_MS)
+ return PPS_UPDATE_DELAY_MS;
+
+ ret = pps_set_prop(pps, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ pending_uv, tcpm_psy);
+ pr_debug("%s: SET_UV out_v %d->%d, ret=%d\n", __func__,
+ pps->out_uv, pending_uv, ret);
+
+ pps_log(pps, "SET_UV out_v %d->%d, ret=%d",
+ pps->out_uv, pending_uv, ret);
+
+ if (ret == 0) {
+ pps->last_update = get_boot_sec();
+ pps->out_uv = pending_uv;
+ return PD_T_PPS_TIMEOUT;
+ }
+
+ if (ret != -EAGAIN && ret != -EOPNOTSUPP)
+ pps_log(pps, "failed to set CURRENT_NOW, ret = %d",
+ ret);
+
+ } else if (interval < PD_T_PPS_DEADLINE_S) {
+ /* TODO: tune this, now assume that PD_T_PPS_TIMEOUT >= 7s */
+ return PD_T_PPS_TIMEOUT - (interval * MSEC_PER_SEC);
+ } else {
+ ret = pps_keep_alive(pps, tcpm_psy);
+
+ pr_debug("%s: KEEP ALIVE out_v %d, op_c %d (%d)", __func__,
+ pps->out_uv, pps->op_ua, ret);
+ pps_log(pps, "KEEP ALIVE out_v %d, op_c %d (%d)",
+ pps->out_uv, pps->op_ua, ret);
+
+ if (ret == 0)
+ return PD_T_PPS_TIMEOUT;
+ }
+
+ if (ret == -EOPNOTSUPP)
+ pps_log(pps, "PPS deactivated while updating");
+
+ return ret;
+}
+
+struct power_supply *pps_get_tcpm_psy(struct device_node *node, size_t size)
+{
+ const char *propname = "google,tcpm-power-supply";
+ struct power_supply *tcpm_psy = NULL;
+ struct power_supply *psy[size];
+ int i, ret;
+
+ ret = power_supply_get_by_phandle_array(node, propname, psy,
+ ARRAY_SIZE(psy));
+ if (ret < 0)
+ return ERR_PTR(-EAGAIN);
+
+ for (i = 0; i < ret; i++) {
+ const char *name = psy[i]->desc ? psy[i]->desc->name : NULL;
+
+ if (!tcpm_psy && name && !strncmp(name, "tcpm", 4))
+ tcpm_psy = psy[i];
+ else
+ power_supply_put(psy[i]);
+ }
+
+ return tcpm_psy;
+}
+EXPORT_SYMBOL_GPL(pps_get_tcpm_psy);
+
+/* TODO: */
+int pps_request_pdo(struct pd_pps_data *pps_data, unsigned int ta_idx,
+ unsigned int ta_max_vol, unsigned int ta_max_cur,
+ struct power_supply *tcpm_psy)
+{
+ struct tcpm_port *port = chg_get_tcpm_port(tcpm_psy);
+ const unsigned int max_mw = ta_max_vol * ta_max_cur;
+
+ if (!port)
+ return -ENODEV;
+ if (ta_idx > PDO_MAX_OBJECTS)
+ return -EINVAL;
+
+ /* max_mw was, now using the max OP_SNK_MW */
+ return tcpm_update_sink_capabilities(port, pps_data->snk_pdo,
+ ta_idx, max_mw);
+}
+EXPORT_SYMBOL_GPL(pps_request_pdo);
+
+
+/* ------------------------------------------------------------------------- */
+
+/* max APDO power from the TCPM source */
+int pps_get_apdo_max_power(struct pd_pps_data *pps_data, unsigned int *ta_idx,
+ unsigned int *ta_max_vol, unsigned int *ta_max_cur,
+ unsigned long *ta_max_pwr)
+{
+ int max_current, max_voltage, max_power;
+ const int ta_max_vol_mv = *ta_max_vol / 1000;
+ int i;
+
+ if (pps_data->nr_src_cap <= 0)
+ return -ENOENT;
+
+ /* already done that */
+ if (*ta_idx != 0)
+ return -ENOTSUPP;
+
+ /* find the new max_uv, max_ua, and max_pwr */
+ for (i = 0; i < pps_data->nr_src_cap; i++) {
+ const u32 pdo = pps_data->src_caps[i];
+
+ if (pdo_type(pdo) != PDO_TYPE_FIXED)
+ continue;
+
+ max_voltage = pdo_max_voltage(pdo); /* mV */
+ max_current = pdo_max_current(pdo); /* mA */
+ max_power = pdo_max_power(pdo); /* mW */
+ *ta_max_pwr = max_power * 1000; /* uW */
+ }
+
+ /* Get the TA maximum current and voltage for APDOs */
+ for (i = 0; i < pps_data->nr_src_cap; i++) {
+ const u32 pdo = pps_data->src_caps[i];
+
+ if (pdo_type(pdo) != PDO_TYPE_APDO)
+ continue;
+
+ max_current = pdo_pps_apdo_max_current(pdo); /* mA */
+ max_voltage = pdo_pps_apdo_max_voltage(pdo); /* mV */
+ /* stop on first */
+ if (max_voltage > ta_max_vol_mv) {
+ *ta_max_vol = max_voltage * 1000; /* uV */
+ *ta_max_cur = max_current * 1000; /* uA */
+ *ta_idx = i + 1;
+ return 0;
+ }
+ }
+
+ pr_debug("%s: max_uv (%u) and max_ua (%u) out of APDO src caps\n",
+ __func__, *ta_max_vol, *ta_max_cur);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(pps_get_apdo_max_power);
\ No newline at end of file
diff --git a/google_dc_pps.h b/google_dc_pps.h
new file mode 100644
index 0000000..dd8ef45
--- /dev/null
+++ b/google_dc_pps.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2020 Google, LLC
+ *
+ * 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.
+ */
+
+#ifndef __GOOGLE_DC_PPS_H_
+#define __GOOGLE_DC_PPS_H_
+
+#include <linux/usb/pd.h>
+#include <linux/pm_wakeup.h>
+#include "logbuffer.h"
+
+#define PD_T_PPS_TIMEOUT 9000 /* Maximum of 10 seconds */
+#define PD_T_PPS_DEADLINE_S 7
+
+#define PPS_KEEP_ALIVE_MAX 3
+#define PPS_ERROR_RETRY_MS 1000
+#define CHG_PPS_VOTER "pps_chg"
+
+enum pd_pps_stage {
+ PPS_NOTSUPP = -1, /* tried and failed */
+ PPS_DISABLED = 0, /* default state, never tried */
+ PPS_NONE, /* try to enable */
+ PPS_AVAILABLE,
+ PPS_ACTIVE,
+};
+
+enum pd_nr_pdo {
+ PDO_FIXED_5V = 1,
+ PDO_FIXED_HIGH_VOLTAGE,
+ PDO_PPS,
+
+ PDO_MAX_SUPP = PDO_PPS,
+ PDO_MAX = PDO_MAX_OBJECTS, /* 7 */
+};
+
+struct pd_pps_data {
+ struct wakeup_source pps_ws;
+ bool stay_awake;
+
+ int nr_src_cap;
+ u32 *src_caps;
+ u32 snk_pdo[PDO_MAX_OBJECTS];
+ unsigned int nr_snk_pdo;
+ u32 default_pps_pdo;
+
+ int pd_online;
+ enum pd_pps_stage stage;
+ unsigned int keep_alive_cnt;
+ time_t last_update;
+
+ /* from TA */
+ int min_uv;
+ int max_uv;
+ int max_ua;
+
+ /* to TA */
+ int out_uv;
+ int op_ua;
+
+ /* logging client */
+ struct logbuffer *log;
+};
+
+/* */
+#define pps_is_disabled(x) (((x) == PPS_NOTSUPP) || ((x) == PPS_DISABLED))
+
+struct dentry;
+int pps_init(struct pd_pps_data *pps_data, struct device *dev);
+int pps_init_fs(struct pd_pps_data *pps_data, struct dentry *de);
+/* reset state and leave in DISABLED */
+void pps_init_state(struct pd_pps_data *pps_data);
+
+/* Run the PPS state machine */
+int pps_work(struct pd_pps_data *pps, struct power_supply *tcpm_psy);
+
+/* rougly equivalent */
+int pps_ping(struct pd_pps_data *pps, struct power_supply *tcpm_psy);
+int pps_keep_alive(struct pd_pps_data *pps, struct power_supply *tcpm_psy);
+
+/* update the PPS adapter */
+int pps_update_adapter(struct pd_pps_data *pps_data,
+ int pending_uv, int pending_ua,
+ struct power_supply *tcpm_psy);
+int pps_check_adapter(struct pd_pps_data *pps,
+ int pending_uv, int pending_ua,
+ struct power_supply *tcpm_psy);
+
+/* */
+int pps_prog_offline(struct pd_pps_data *pps, struct power_supply *tcpm_psy);
+
+void pps_adjust_volt(struct pd_pps_data *pps, int mod);
+
+int pps_switch_profile(struct pd_pps_data *pps, struct power_supply *tcpm_psy,
+ bool more_pwr);
+
+int pps_get_apdo_max_power(struct pd_pps_data *pps, unsigned int *ta_idx,
+ unsigned int *ta_max_vol, unsigned int *ta_max_cur,
+ unsigned long *ta_max_pwr);
+
+bool pps_prog_check_online(struct pd_pps_data *pps_data,
+ struct power_supply *tcpm_psy);
+
+int pps_get_src_cap(struct pd_pps_data *pps, struct power_supply *tcpm_psy);
+
+void pps_log(struct pd_pps_data *pps, const char *fmt, ...);
+
+/* probe */
+struct power_supply *pps_get_tcpm_psy(struct device_node *node, size_t size);
+
+int pps_request_pdo(struct pd_pps_data *pps_data, unsigned int ta_idx,
+ unsigned int ta_max_vol, unsigned int ta_max_cur,
+ struct power_supply *tcpm_psy);
+
+
+
+/* might just need to be in google_charger */
+int chg_update_capability(struct power_supply *tcpm_psy, unsigned int nr_pdo,
+ u32 pps_cap);
+
+#endif /* __GOOGLE_DC_PPS_H_ */
\ No newline at end of file
diff --git a/google_eeprom.c b/google_eeprom.c
new file mode 100644
index 0000000..7a0c897
--- /dev/null
+++ b/google_eeprom.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright 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.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/nvmem-consumer.h>
+#include "google_bms.h"
+
+#define BATT_TOTAL_HIST_LEN 928
+#define BATT_ONE_HIST_LEN 28
+#define BATT_MAX_HIST_CNT \
+ (BATT_TOTAL_HIST_LEN / BATT_ONE_HIST_LEN) // 33.14
+
+#define BATT_EEPROM_TAG_BRID_OFFSET 0x17
+#define BATT_EEPROM_TAG_BRID_LEN 1
+#define BATT_EEPROM_TAG_MINF_OFFSET 0x00
+#define BATT_EEPROM_TAG_MINF_LEN GBMS_MINF_LEN
+#define BATT_EEPROM_TAG_DINF_OFFSET 0x20
+#define BATT_EEPROM_TAG_DINF_LEN GBMS_DINF_LEN
+#define BATT_EEPROM_TAG_BCNT_OFFSET 0x40
+#define BATT_EEPROM_TAG_BCNT_LEN (GBMS_CCBIN_BUCKET_COUNT * 2)
+#define BATT_EEPROM_TAG_GMSR_OFFSET 0x54
+#define BATT_EEPROM_TAG_GMSR_LEN GBMS_GMSR_LEN
+#define BATT_EEPROM_TAG_HIST_OFFSET 0x6A
+#define BATT_EEPROM_TAG_HIST_LEN BATT_ONE_HIST_LEN
+#define BATT_EEPROM_TAG_BGPN_OFFSET 0x03
+#define BATT_EEPROM_TAG_BGPN_LEN GBMS_BGPN_LEN
+
+static int gbee_storage_info(gbms_tag_t tag, size_t *addr, size_t *count,
+ void *ptr)
+{
+ int ret = 0;
+
+ switch (tag) {
+ case GBMS_TAG_MINF:
+ *addr = BATT_EEPROM_TAG_MINF_OFFSET;
+ *count = BATT_EEPROM_TAG_MINF_LEN;
+ break;
+ case GBMS_TAG_DINF:
+ *addr = BATT_EEPROM_TAG_DINF_OFFSET;
+ *count = BATT_EEPROM_TAG_DINF_LEN;
+ break;
+ case GBMS_TAG_HIST:
+ *addr = BATT_EEPROM_TAG_HIST_OFFSET;
+ *count = BATT_EEPROM_TAG_HIST_LEN;
+ break;
+ case GBMS_TAG_SNUM:
+ case GBMS_TAG_BGPN:
+ *addr = BATT_EEPROM_TAG_BGPN_OFFSET;
+ *count = BATT_EEPROM_TAG_BGPN_LEN;
+ break;
+ case GBMS_TAG_GMSR:
+ *addr = BATT_EEPROM_TAG_GMSR_OFFSET;
+ *count = BATT_EEPROM_TAG_GMSR_LEN;
+ break;
+ case GBMS_TAG_BCNT:
+ *addr = BATT_EEPROM_TAG_BCNT_OFFSET;
+ *count = BATT_EEPROM_TAG_BCNT_LEN;
+ break;
+ default:
+ ret = -ENOENT;
+ break;
+ }
+
+ return ret;
+}
+
+static int gbee_storage_iter(int index, gbms_tag_t *tag, void *ptr)
+{
+ static gbms_tag_t keys[] = { GBMS_TAG_BGPN, GBMS_TAG_MINF,
+ GBMS_TAG_DINF, GBMS_TAG_HIST,
+ GBMS_TAG_BRID, GBMS_TAG_SNUM,
+ GBMS_TAG_GMSR, GBMS_TAG_BCNT };
+ const int count = ARRAY_SIZE(keys);
+
+ if (index < 0 || index >= count)
+ return -ENOENT;
+
+ *tag = keys[index];
+ return 0;
+}
+
+static int gbee_storage_read(gbms_tag_t tag, void *buff, size_t size,
+ void *ptr)
+{
+ struct nvmem_device *nvmem = ptr;
+ size_t offset = 0, len = 0;
+ int ret;
+
+ if (tag == GBMS_TAG_BRID) {
+ u8 temp;
+
+ if (size != sizeof(u32))
+ return -ENOMEM;
+
+ ret = nvmem_device_read(nvmem, BATT_EEPROM_TAG_BRID_OFFSET,
+ 1, &temp);
+ pr_err("cannot read nvram %d\n", ret);
+ if (ret < 0)
+ return ret;
+
+ ((u32*)buff)[0] = temp;
+ return len;
+ }
+
+ ret = gbee_storage_info(tag, &offset, &len, ptr);
+ if (ret < 0)
+ return ret;
+ if (!len)
+ return -ENOENT;
+ if (len > size)
+ return -ENOMEM;
+
+ ret = nvmem_device_read(nvmem, offset, len, buff);
+ if (ret == 0)
+ ret = len;
+
+ return ret;
+}
+
+static int gbee_storage_write(gbms_tag_t tag, const void *buff, size_t size,
+ void *ptr)
+{
+ struct nvmem_device *nvmem = ptr;
+ size_t offset = 0, len = 0;
+ int ret;
+
+ if ((tag != GBMS_TAG_DINF) && (tag != GBMS_TAG_GMSR) &&
+ (tag != GBMS_TAG_BCNT))
+ return -ENOENT;
+
+ ret = gbee_storage_info(tag, &offset, &len, ptr);
+ if (ret < 0)
+ return ret;
+ if (size > len)
+ return -ENOMEM;
+
+ ret = nvmem_device_write(nvmem, offset, size, (void *)buff);
+ if (ret == 0)
+ ret = size;
+
+ return ret;
+}
+
+static int gbee_storage_read_data(gbms_tag_t tag, void *data, size_t count,
+ int idx, void *ptr)
+{
+ struct nvmem_device *nvmem = ptr;
+ size_t offset = 0, len = 0;
+ int ret;
+
+ switch (tag) {
+ case GBMS_TAG_HIST:
+ ret = gbee_storage_info(tag, &offset, &len, ptr);
+ break;
+ default:
+ ret = -ENOENT;
+ break;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (!data || !count) {
+ if (idx == GBMS_STORAGE_INDEX_INVALID)
+ return 0;
+ else
+ return BATT_MAX_HIST_CNT;
+ }
+
+ if (idx < 0)
+ return -EINVAL;
+
+ /* index == 0 is ok here */
+ if (idx >= BATT_MAX_HIST_CNT)
+ return -ENODATA;
+
+ if (len > count)
+ return -EINVAL;
+
+ offset += len * idx;
+
+ ret = nvmem_device_read(nvmem, offset, len, data);
+ if (ret == 0)
+ ret = len;
+
+ return ret;
+}
+
+static int gbee_storage_write_data(gbms_tag_t tag, const void *data,
+ size_t count, int idx, void *ptr)
+{
+ struct nvmem_device *nvmem = ptr;
+ size_t offset = 0, len = 0;
+ int ret;
+
+ switch (tag) {
+ case GBMS_TAG_HIST:
+ ret = gbee_storage_info(tag, &offset, &len, ptr);
+ break;
+ default:
+ ret = -ENOENT;
+ break;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (idx < 0 || !data || !count)
+ return -EINVAL;
+
+ /* index == 0 is ok here */
+ if (idx >= BATT_MAX_HIST_CNT)
+ return -ENODATA;
+
+ if (count > len)
+ return -EINVAL;
+
+ offset += len * idx;
+
+ ret = nvmem_device_write(nvmem, offset, len, (void *)data);
+ if (ret == 0)
+ ret = len;
+
+ return ret;
+}
+
+
+static struct gbms_storage_desc gbee_storage_dsc = {
+ .info = gbee_storage_info,
+ .iter = gbee_storage_iter,
+ .read = gbee_storage_read,
+ .write = gbee_storage_write,
+ .read_data = gbee_storage_read_data,
+ .write_data = gbee_storage_write_data,
+};
+
+/*
+ * Caller will use something like of_nvmem_device_get() to retrieve the
+ * nvmem_device instance.
+ * TODO: map nvram cells to tags
+ */
+int gbee_register_device(const char *name, struct nvmem_device *nvram)
+{
+ return gbms_storage_register(&gbee_storage_dsc, name, nvram);
+}
+
+void gbee_destroy_device(void)
+{
+
+}
diff --git a/google_psy.h b/google_psy.h
new file mode 100644
index 0000000..f63373b
--- /dev/null
+++ b/google_psy.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef __GOOGLE_PSY_H_
+#define __GOOGLE_PSY_H_
+
+#include <linux/printk.h>
+#include <linux/power_supply.h>
+
+static inline int gpsy_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval val,
+ const char *prop_name)
+{
+ int ret = 0;
+
+ if (!psy)
+ return -EINVAL;
+ pr_debug("set %s for '%s' to %ld\n", prop_name, psy->desc->name,
+ val.int64val);
+ ret = power_supply_set_property(psy, psp, &val);
+ if (ret < 0) {
+ pr_err("failed to set %s for '%s', ret=%d\n",
+ prop_name, psy->desc->name, ret);
+ return ret;
+ }
+ return 0;
+}
+
+#define GPSY_SET_PROP(psy, psp, val) \
+ gpsy_set_prop(psy, psp, (union power_supply_propval) \
+ { .intval = (val) }, #psp)
+#define GPSY_SET_INT64_PROP(psy, psp, val) \
+ gpsy_set_prop(psy, psp, (union power_supply_propval) \
+ { .int64val = (int64_t)(val) }, #psp)
+
+static inline int gpsy_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const char *prop_name,
+ int *err)
+{
+ union power_supply_propval val;
+ int ret = 0;
+
+ ret = (psy) ? power_supply_get_property(psy, psp, &val) : -EINVAL;
+ if (err)
+ *err = ret;
+ if (ret < 0) {
+ pr_err("failed to get %s from '%s', ret=%d\n",
+ prop_name, psy->desc->name, ret);
+ return ret;
+ }
+
+ pr_debug("get %s for '%s' => %d\n",
+ prop_name, psy->desc->name, val.intval);
+
+ return val.intval;
+}
+
+static inline int64_t gpsy_get_int64_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const char *prop_name)
+{
+ union power_supply_propval val;
+ int ret = 0;
+
+ if (!psy)
+ return -EINVAL;
+ ret = power_supply_get_property(psy, psp, &val);
+ if (ret < 0) {
+ pr_err("failed to get %s from '%s', ret=%d\n",
+ prop_name, psy->desc->name, ret);
+ return ret;
+ }
+
+ pr_debug("get %s for '%s' => %d\n",
+ prop_name, psy->desc->name, val.intval);
+ return val.int64val;
+}
+
+
+#define GPSY_GET_PROP(psy, psp) gpsy_get_prop(psy, psp, #psp, 0)
+/* use GPSY_GET_INT_PROP() for properties that can be negative */
+#define GPSY_GET_INT_PROP(psy, psp, err) gpsy_get_prop(psy, psp, #psp, err)
+#define GPSY_GET_INT64_PROP(psy, psp) gpsy_get_int64_prop(psy, psp, #psp)
+
+#endif /* __GOOGLE_PSY_H_ */
diff --git a/google_ttf.c b/google_ttf.c
new file mode 100644
index 0000000..c18262d
--- /dev/null
+++ b/google_ttf.c
@@ -0,0 +1,766 @@
+/*
+ * Copyright 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/pm_wakeup.h>
+#include <linux/pmic-voter.h>
+#include <linux/thermal.h>
+#include "google_bms.h"
+#include "google_psy.h"
+#include "qmath.h"
+#include "logbuffer.h"
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#endif
+
+#define ELAP_LIMIT_S 60
+
+/* actual adapter current capability for this charging event
+ * NOTE: peformance for a tier are known only after entering the tier
+ */
+static int ttf_pwr_icl(const struct gbms_charging_event *ce_data,
+ int temp_idx, int vbatt_idx)
+{
+ const struct gbms_ce_tier_stats *ts = &ce_data->tier_stats[vbatt_idx];
+ int elap, amperage;
+
+ elap = ts->time_fast + ts->time_taper;
+ if (elap <= ELAP_LIMIT_S)
+ amperage = ce_data->adapter_details.ad_amperage * 100;
+ else
+ amperage = ts->icl_sum / (elap + ts->time_other);
+
+ return amperage;
+}
+
+/* NOTE: the current in taper might need to be accounted in a different way */
+static int ttf_pwr_ibatt(const struct gbms_charging_event *ce_data,
+ int temp_idx, int vbatt_idx)
+{
+ const struct gbms_ce_tier_stats *ts = &ce_data->tier_stats[vbatt_idx];
+ int avg_ibatt, elap, sign = 1;
+
+ elap = ts->time_fast + ts->time_taper;
+ if (elap <= ELAP_LIMIT_S) {
+ pr_debug("%d,%d: fast=%d taper=%d other=%d limit=%d\n",
+ vbatt_idx, temp_idx,
+ ts->time_fast, ts->time_taper, ts->time_other,
+ ELAP_LIMIT_S);
+ return 0;
+ }
+
+ /* actual */
+ avg_ibatt = ts->ibatt_sum / (elap + ts->time_other);
+ if (avg_ibatt < 0)
+ sign = -1;
+
+ pr_debug("%d,%d: fast=%d taper=%d other=%d avg_ibatt=%d\n",
+ vbatt_idx, temp_idx,
+ ts->time_fast, ts->time_taper, ts->time_other,
+ avg_ibatt * sign);
+
+ return avg_ibatt * sign;
+}
+
+/* nominal voltage tier index for this soc */
+static int ttf_pwr_tier(const struct batt_ttf_stats *stats, int soc)
+{
+ int i;
+
+ for (i = 1; i < GBMS_STATS_TIER_COUNT; i++)
+ if (soc < stats->tier_stats[i].soc_in >> 8)
+ break;
+
+ return i - 1;
+}
+
+/* nominal average current demand for this tier at max rate
+ * NOTE: tier and soc stats keep track of aging (might not need)
+ */
+static int ttf_pwr_avg_cc(const struct batt_ttf_stats *stats, int soc)
+{
+ const struct ttf_soc_stats *sstat = NULL;
+ int delta_cc;
+
+ /* soc average current demand */
+ if (stats->soc_stats.cc[soc] && stats->soc_stats.elap[soc])
+ sstat = &stats->soc_stats;
+ else if (stats->soc_ref.cc[soc] && stats->soc_ref.elap[soc])
+ sstat = &stats->soc_ref;
+ else
+ return 0;
+
+ delta_cc = (sstat->cc[soc + 1] - sstat->cc[soc]);
+
+ return (delta_cc * 3600) / sstat->elap[soc];
+}
+
+/* time scaling factor for available power and SOC demand.
+ * NOTE: usually called when soc < ssoc_in && soc > ce_data->last_soc
+ * TODO: this is very inefficient
+ */
+static int ttf_pwr_ratio(const struct batt_ttf_stats *stats,
+ const struct gbms_charging_event *ce_data,
+ int soc)
+{
+ int ratio;
+ int avg_cc, pwr_demand;
+ int act_icl, pwr_avail;
+ int act_ibatt, cc_max;
+ int vbatt_idx, temp_idx;
+ const struct gbms_chg_profile *profile = ce_data->chg_profile;
+
+ vbatt_idx = ttf_pwr_tier(stats, soc);
+ if (vbatt_idx < 0)
+ return -1;
+
+ /* TODO: compensate with average increase/decrease of temperature? */
+ temp_idx = ce_data->tier_stats[vbatt_idx].temp_idx;
+ if (temp_idx == -1) {
+ const int elap = ce_data->tier_stats[vbatt_idx].time_fast +
+ ce_data->tier_stats[vbatt_idx].time_taper +
+ ce_data->tier_stats[vbatt_idx].time_other;
+ int64_t t_avg = ce_data->tier_stats[vbatt_idx].temp_sum / elap;
+
+ if (ce_data->tier_stats[vbatt_idx].temp_sum == 0 || elap == 0)
+ t_avg = ce_data->tier_stats[vbatt_idx].temp_in;
+ if (t_avg == 0)
+ t_avg = 250;
+
+ temp_idx = gbms_msc_temp_idx(profile, t_avg);
+
+ pr_debug("%d: temp_idx=%d t_avg=%ld sum=%ld elap=%d\n",
+ soc, temp_idx, t_avg,
+ ce_data->tier_stats[vbatt_idx].temp_sum,
+ elap);
+
+ if (temp_idx < 0)
+ return -1;
+ }
+
+ /* max tier demand at this temperature index */
+ cc_max = GBMS_CCCM_LIMITS(profile, temp_idx, vbatt_idx) / 1000;
+ /* statistical demand for soc, account for taper */
+ avg_cc = ttf_pwr_avg_cc(stats, soc);
+ if (avg_cc <= 0 || avg_cc > cc_max) {
+ pr_debug("%d: demand use default avg_cc=%d->%d\n",
+ soc, avg_cc, cc_max);
+ avg_cc = cc_max;
+ }
+
+ /* actual battery power demand */
+ pwr_demand = (profile->volt_limits[vbatt_idx] / 10000) * avg_cc;
+ pr_debug("%d:%d,%d: pwr_demand=%d avg_cc=%d cc_max=%d\n",
+ soc, temp_idx, vbatt_idx, pwr_demand,
+ avg_cc, cc_max);
+
+ /* actual adapter current capabilities for this tier */
+ act_icl = ttf_pwr_icl(ce_data, temp_idx, vbatt_idx);
+ if (act_icl <= 0) {
+ pr_debug("%d: negative, null act_icl=%d\n", soc, act_icl);
+ return -1;
+ }
+
+ /* compensate for temperature (might not need) */
+ if (temp_idx != stats->ref_temp_idx && cc_max < act_icl) {
+ pr_debug("%d: temp_idx=%d, reduce icl %d->%d\n",
+ soc, temp_idx, act_icl, cc_max);
+ act_icl = cc_max;
+ }
+
+ /* compensate for system load
+ * NOTE: act_ibatt = 0 means no system load, need to fix later
+ */
+ act_ibatt = ttf_pwr_ibatt(ce_data, temp_idx, vbatt_idx);
+ if (act_ibatt < 0) {
+ pr_debug("%d: ibatt=%d, discharging\n", soc, act_ibatt);
+ return -1;
+ } else if (act_ibatt > 0 && act_ibatt < act_icl) {
+ pr_debug("%d: sysload ibatt=%d, avg_cc=%d, reduce icl %d->%d\n",
+ soc, act_ibatt, avg_cc, act_icl, act_ibatt);
+ act_icl = act_ibatt;
+ }
+
+ pwr_avail = (ce_data->adapter_details.ad_voltage * 10 ) * act_icl;
+
+ /* TODO: scale for efficiency? */
+
+ if (pwr_avail < pwr_demand)
+ ratio = (stats->ref_watts * 100000) / pwr_avail;
+ else
+ ratio = 100;
+
+ pr_debug("%d: pwr_avail=%d, pwr_demand=%d ratio=%d\n",
+ soc, pwr_avail, pwr_demand, ratio);
+
+ return ratio;
+}
+
+/* SOC estimates --------------------------------------------------------- */
+
+int ttf_elap(time_t *estimate, int i,
+ const struct batt_ttf_stats *stats,
+ const struct gbms_charging_event *ce_data)
+{
+ int ratio;
+ time_t elap;
+
+ if (i < 0 || i >= 100) {
+ *estimate = 0;
+ return 0;
+ }
+
+ elap = stats->soc_stats.elap[i];
+ if (elap == 0)
+ elap = stats->soc_ref.elap[i];
+
+ ratio = ttf_pwr_ratio(stats, ce_data, i);
+ if (ratio < 0) {
+ pr_debug("%d: negative ratio=%d\n", i, ratio);
+ return -EINVAL;
+ }
+
+ pr_debug("i=%d elap=%ld ratio=%d\n", i, elap, ratio);
+ *estimate = elap * ratio;
+
+ return 0;
+}
+
+/* time to full from SOC%
+ * NOTE: prediction is based stats and corrected with the ce_data
+ * NOTE: usually called with soc > ce_data->last_soc
+ */
+int ttf_soc_estimate(time_t *res,
+ const struct batt_ttf_stats *stats,
+ const struct gbms_charging_event *ce_data,
+ qnum_t soc, qnum_t last)
+{
+ const int ssoc_in = ce_data->charging_stats.ssoc_in;
+ const int end = qnum_toint(last);
+ const int frac = qnum_fracdgt(soc);
+ int i = qnum_toint(soc);
+ time_t estimate = 0;
+ int ret;
+
+ if (end > 100)
+ return -EINVAL;
+
+ ret = ttf_elap(&estimate, i, stats, ce_data);
+ if (ret < 0)
+ return ret;
+
+ /* add ttf_elap starting from i + 1 */
+ estimate = (estimate * (100 - frac)) / 100;
+ for (i += 1; i < end; i++) {
+ time_t elap;
+
+ if (i >= ssoc_in && i < ce_data->last_soc) {
+ /* use real data if within charging event */
+ elap = ce_data->soc_stats.elap[i] * 100;
+ } else {
+ /* future (and soc before ssoc_in) */
+ ret = ttf_elap(&elap, i, stats, ce_data);
+ if (ret < 0)
+ return ret;
+ }
+
+ estimate += elap;
+ }
+
+ *res = estimate / 100;
+ return 0;
+}
+
+int ttf_soc_cstr(char *buff, int size,
+ const struct ttf_soc_stats *soc_stats,
+ int start, int end)
+{
+ int i, len = 0, split = 100;
+
+ if (start < 0 || start >= GBMS_SOC_STATS_LEN ||
+ end < 0 || end >= GBMS_SOC_STATS_LEN ||
+ start > end)
+ return 0;
+
+ /* only one way to print data @ 100 */
+ if (end == 100 && start != 100)
+ end = 99;
+ /* std newline every 10 entries */
+ if (start == 0 && end == 99)
+ split = 10;
+
+ for (i = start; i <= end; i++) {
+ if (i % split == 0 || i == start) {
+ len += scnprintf(&buff[len], size - len, "T:");
+ if (split == 10)
+ len += scnprintf(&buff[len], size - len,
+ "%d", i / 10);
+ }
+
+ len += scnprintf(&buff[len], size - len, " %4ld",
+ soc_stats->elap[i]);
+ if (i != end && (i + 1) % split == 0)
+ len += scnprintf(&buff[len], size - len, "\n");
+ }
+
+ len += scnprintf(&buff[len], size - len, "\n");
+
+ for (i = start; i <= end; i++) {
+ if (i % split == 0 || i == start) {
+ len += scnprintf(&buff[len], size - len, "C:");
+ if (split == 10)
+ len += scnprintf(&buff[len], size - len,
+ "%d", i / 10);
+ }
+
+ len += scnprintf(&buff[len], size - len, " %4d",
+ soc_stats->cc[i]);
+ if (i != end && (i + 1) % split == 0)
+ len += scnprintf(&buff[len], size - len, "\n");
+ }
+
+ len += scnprintf(&buff[len], size - len, "\n");
+
+ return len;
+}
+
+/* update soc_stats using the charging event
+ * NOTE: first_soc and last_soc are inclusive, will skip socs that have no
+ * elap and no cc.
+ */
+static void ttf_soc_update(struct batt_ttf_stats *stats,
+ const struct gbms_charging_event *ce_data,
+ int first_soc, int last_soc)
+{
+ const struct ttf_soc_stats *src = &ce_data->soc_stats;
+ struct ttf_soc_stats *dst = &stats->soc_stats;
+ int i;
+
+ for (i = first_soc; i <= last_soc; i++) {
+
+ /* TODO: qualify src->elap[i], src->cc[i] */
+ /* TODO: bound the changes */
+
+ /* average the weighted time at soc */
+ if (src->elap[i]) {
+ int ratio;
+ time_t elap;
+
+ ratio = ttf_pwr_ratio(stats, ce_data, i);
+ if (ratio < 0)
+ continue;
+
+ if (dst->elap[i] <= 0)
+ dst->elap[i] = stats->soc_ref.elap[i];
+
+ elap = (src->elap[i] * 100) / ratio;
+
+ pr_debug("%d: dst->elap=%ld, ref_elap=%ld, elap=%ld, src_elap=%ld ratio=%d\n",
+ i, dst->elap[i], stats->soc_ref.elap[i],
+ elap, src->elap[i], ratio);
+
+ dst->elap[i] = (dst->elap[i] + elap) / 2;
+ }
+
+ /* average the coulumb count at soc entry */
+ if (src->cc[i]) {
+ if (dst->cc[i] <= 0)
+ dst->cc[i] = stats->soc_ref.cc[i];
+
+ dst->cc[i] = (dst->cc[i] + src->cc[i]) / 2;
+ }
+ }
+}
+
+void ttf_soc_init(struct ttf_soc_stats *dst)
+{
+ memset(dst, 0, sizeof(*dst));
+}
+
+/* Tier estimates --------------------------------------------------------- */
+
+#define TTF_STATS_FMT "[%d,%d %d %ld]"
+
+#define BATT_TTF_TS_VALID(ts) \
+ (ts->cc_total != 0 && ts->avg_time != 0)
+
+/* TODO: adjust for adapter capability */
+static time_t ttf_tier_accumulate(const struct ttf_tier_stat *ts,
+ int vbatt_idx,
+ const struct batt_ttf_stats *stats)
+{
+ time_t estimate = 0;
+
+ if (vbatt_idx >= GBMS_STATS_TIER_COUNT)
+ return 0;
+
+ for (; vbatt_idx < GBMS_STATS_TIER_COUNT; vbatt_idx++) {
+
+ /* no data in this tier, sorry */
+ if (!BATT_TTF_TS_VALID(ts))
+ return -ENODATA;
+
+ estimate += ts[vbatt_idx].avg_time;
+ }
+
+ return estimate;
+}
+
+/* */
+static int ttf_tier_sscan(struct batt_ttf_stats *stats,
+ const char *buff,
+ size_t size)
+{
+ int i, j, t, cnt, len = 0;
+
+ memset(&stats->tier_stats, 0, sizeof(*stats));
+
+ cnt = sscanf(&buff[len], "%d:", &t);
+ if (t != i)
+ i = t - 1;
+ while (buff[len] != '[' && len < size)
+ len++;
+
+ for (j = 0; j < GBMS_STATS_TIER_COUNT; j++) {
+ cnt = sscanf(&buff[len], TTF_STATS_FMT,
+ &stats->tier_stats[j].soc_in,
+ &stats->tier_stats[j].cc_in,
+ &stats->tier_stats[j].cc_total,
+ &stats->tier_stats[j].avg_time);
+
+ len += sizeof(TTF_STATS_FMT) - 1;
+ }
+
+ return 0;
+}
+
+int ttf_tier_cstr(char *buff, int size, struct ttf_tier_stat *tier_stats)
+{
+ int len = 0;
+
+ len += scnprintf(&buff[len], size - len,
+ TTF_STATS_FMT,
+ tier_stats->soc_in >> 8,
+ tier_stats->cc_in,
+ tier_stats->cc_total,
+ tier_stats->avg_time);
+ return len;
+}
+
+/* tier statistics keep track of average capacity at entry of */
+static void ttf_tier_update_stats(struct ttf_tier_stat *ttf_ts,
+ const struct gbms_ce_tier_stats *chg_ts,
+ bool force)
+{
+ int elap;
+
+ if (!force) {
+ if (chg_ts->cc_total == 0)
+ return;
+
+ /* TODO: check dsg, qualify with adapter? */
+ }
+
+ /* TODO: check with -1 */
+ if (ttf_ts->soc_in == 0)
+ ttf_ts->soc_in = chg_ts->soc_in;
+ ttf_ts->soc_in = (ttf_ts->soc_in + chg_ts->soc_in) / 2;
+
+ /* TODO: check with -1 */
+ if (ttf_ts->cc_in == 0)
+ ttf_ts->cc_in = chg_ts->cc_in;
+ ttf_ts->cc_in = (ttf_ts->cc_in + chg_ts->cc_in) / 2;
+
+ if (ttf_ts->cc_total == 0)
+ ttf_ts->cc_total = chg_ts->cc_total;
+ ttf_ts->cc_total = (ttf_ts->cc_total + chg_ts->cc_total) / 2;
+
+ /* */
+ elap = chg_ts->time_fast + chg_ts->time_taper + chg_ts->time_other;
+ if (ttf_ts->avg_time == 0)
+ ttf_ts->avg_time = elap;
+
+ /* qualify time with ratio */
+ ttf_ts->avg_time =(ttf_ts->avg_time + elap) / 2;
+}
+
+/* updated tier stats using the charging event
+ * NOTE: the ce has data from 1+ charging voltage and temperature tiers */
+static void ttf_tier_update(struct batt_ttf_stats *stats,
+ const struct gbms_charging_event *data,
+ bool force)
+{
+ int i;
+
+ for (i = 0; i < GBMS_STATS_TIER_COUNT; i++) {
+ const bool last_tier = i == (GBMS_STATS_TIER_COUNT - 1);
+ const struct gbms_ce_tier_stats *chg_ts = &data->tier_stats[i];
+ const struct gbms_ce_stats *chg_s = &data->charging_stats;
+ long elap;
+
+ /* skip data that has a temperature switch */
+ if (chg_ts->temp_idx == -1)
+ continue;
+ /* or entries that have no actual charging */
+ elap = chg_ts->time_fast + chg_ts->time_taper;
+ if (!elap)
+ continue;
+ /* update first tier stats only at low soc_in */
+ if (!force && i == 0 && (chg_ts->soc_in >> 8) > 1)
+ continue;
+ /* update last tier stats only at full */
+ if (!force && last_tier && ((chg_s->ssoc_out >> 8) != 100))
+ continue;
+
+ /* */
+ ttf_tier_update_stats(&stats->tier_stats[i], chg_ts, false);
+ }
+
+}
+
+/* tier estimates only, */
+int ttf_tier_estimate(time_t *res, const struct batt_ttf_stats *stats,
+ int temp_idx, int vbatt_idx,
+ int capacity, int full_capacity)
+{
+ time_t estimate = 0;
+ const struct ttf_tier_stat *ts;
+
+ /* tier estimates, only when in tier */
+ if (vbatt_idx == -1 && temp_idx == -1)
+ return -EINVAL;
+
+ ts = &stats->tier_stats[vbatt_idx];
+ if (!ts || !BATT_TTF_TS_VALID(ts))
+ return -ENODATA;
+
+ /* accumulate next tier */
+ estimate = ttf_tier_accumulate(ts, vbatt_idx + 1, stats);
+ if (estimate < 0)
+ return -ENODATA;
+
+ /* eyeball current tier
+ * estimate =
+ * (ts->cc_in + ts->cc_total - capacity) *
+ * rs->avg_time) / ts->cc_total
+ */
+
+ /* TODO: adjust for crossing thermals? */
+
+ *res = estimate;
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* update ttf tier and soc stats using the charging event.
+ * call holding stats->lock
+ */
+void ttf_stats_update(struct batt_ttf_stats *stats,
+ struct gbms_charging_event *ce_data,
+ bool force)
+{
+ int first_soc = ce_data->charging_stats.ssoc_in;
+
+ /* ignore the fist non zero because it's partial */
+ for ( ; first_soc <= ce_data->last_soc; first_soc++)
+ if (ce_data->soc_stats.elap[first_soc] != 0)
+ break;
+
+ /* ignore fist and last entry because they are partial */
+ ttf_soc_update(stats, ce_data, first_soc + 1, ce_data->last_soc - 1);
+
+ ttf_tier_update(stats, ce_data, force);
+}
+
+static int ttf_init_soc_parse_dt(struct ttf_adapter_stats *as,
+ struct device *device)
+{
+ int table_count;
+ int ret;
+
+ table_count = of_property_count_elems_of_size(device->of_node,
+ "google,ttf-soc-table",
+ sizeof(u32));
+ if (table_count <= 0)
+ return -EINVAL;
+ if (table_count % 2)
+ return -EINVAL;
+
+ as->soc_table = devm_kzalloc(device, table_count * 2 * sizeof(u32),
+ GFP_KERNEL);
+ if (!as->soc_table)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_array(device->of_node,
+ "google,ttf-soc-table",
+ as->soc_table, table_count);
+ if (ret < 0) {
+ pr_err("cannot read google,ttf-soc-table %d\n", ret);
+ return ret;
+ }
+
+ as->elap_table = &as->soc_table[table_count];
+ ret = of_property_read_u32_array(device->of_node,
+ "google,ttf-elap-table",
+ as->elap_table, table_count);
+ if (ret < 0) {
+ pr_err("cannot read google,ttf-elap-table %d\n", ret);
+ return ret;
+ }
+
+ as->table_count = table_count;
+ return 0;
+}
+
+int ttf_stats_sscan(struct batt_ttf_stats *stats,
+ const char *buff,
+ size_t size)
+{
+ /* TODO: scan ttf_soc_* data as well */
+
+ return ttf_tier_sscan(stats, buff, size);
+}
+
+static int ttf_as_default(struct ttf_adapter_stats *as, int i, int table_i)
+{
+ while (i > as->soc_table[table_i] && table_i < as->table_count)
+ table_i++;
+
+ return table_i;
+}
+
+static int ttf_init_tier_parse_dt(struct batt_ttf_stats *stats,
+ struct device *device)
+{
+ int i, count, ret;
+ u32 tier_table[GBMS_STATS_TIER_COUNT];
+
+ count = of_property_count_elems_of_size(device->of_node,
+ "google,ttf-tier-table",
+ sizeof(u32));
+ if (count != GBMS_STATS_TIER_COUNT)
+ return -EINVAL;
+
+ ret = of_property_read_u32_array(device->of_node,
+ "google,ttf-tier-table",
+ tier_table, count);
+ if (ret < 0) {
+ pr_err("cannot read google,ttf-tier-table %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < GBMS_STATS_TIER_COUNT; i++)
+ stats->tier_stats[i].soc_in = tier_table[i] << 8;
+
+ return 0;
+}
+
+/* clone and clear the stats */
+struct batt_ttf_stats *ttf_stats_dup(struct batt_ttf_stats *dst,
+ const struct batt_ttf_stats *src)
+{
+ memcpy(dst, src, sizeof(*dst));
+ memset(&dst->soc_stats, 0, sizeof(dst->soc_stats));
+ memset(&dst->tier_stats, 0, sizeof(dst->tier_stats));
+ return dst;
+}
+
+void ttf_init_ref_table(struct batt_ttf_stats *stats,
+ struct ttf_adapter_stats *as, int capacity_ma)
+{
+ int i, table_i = 0;
+ const int cc = (capacity_ma * 100) / GBMS_SOC_STATS_LEN;
+
+ for (i = 0; i < GBMS_SOC_STATS_LEN; i++) {
+ table_i = ttf_as_default(as, i, table_i);
+
+ stats->soc_ref.elap[i] = as->elap_table[table_i];
+
+ /* assume same cc for each soc */
+ stats->soc_ref.cc[i] = (cc * i) / 100;
+ }
+
+ /* TODO: allocate as->soc_table witk kzalloc, free here */
+}
+
+/* must come after charge profile */
+int ttf_stats_init(struct batt_ttf_stats *stats, struct device *device,
+ int capacity_ma)
+{
+ struct ttf_adapter_stats as;
+ u32 value;
+ int ret;
+
+ memset(stats, 0, sizeof(*stats));
+ stats->ttf_fake = -1;
+
+ /* reference adapter */
+ ret = of_property_read_u32(device->of_node, "google,ttf-adapter",
+ &value);
+ if (ret < 0)
+ return ret;
+
+ stats->ref_watts = value;
+
+ /* reference temperature */
+ ret = of_property_read_u32(device->of_node, "google,ttf-temp-idx",
+ &value);
+ if (ret < 0)
+ return ret;
+
+ stats->ref_temp_idx = value;
+
+ /* reference soc estimates */
+ ret = ttf_init_soc_parse_dt(&as, device);
+ if (ret < 0)
+ return ret;
+
+ /* reference tier-based statistics */
+ ret = ttf_init_tier_parse_dt(stats, device);
+ if (ret < 0)
+ return ret;
+
+ /* initialize the reference stats for the reference soc estimates */
+ ttf_init_ref_table(stats, &as, capacity_ma);
+
+
+ /* TODO: use the soc stats to calculate cc_in */
+ stats->tier_stats[0].cc_in = 0;
+ stats->tier_stats[1].cc_in = (capacity_ma *
+ stats->tier_stats[1].soc_in) /
+ 100;
+ stats->tier_stats[2].cc_in = (capacity_ma *
+ stats->tier_stats[2].soc_in) /
+ 100;
+
+ /* TODO: use the soc stats to calculate cc_total */
+ stats->tier_stats[0].cc_total = 0;
+ stats->tier_stats[1].cc_total = (capacity_ma *
+ (stats->tier_stats[2].soc_in -
+ stats->tier_stats[1].soc_in)) /
+ 100;
+ stats->tier_stats[2].cc_total = capacity_ma -
+ stats->tier_stats[2].cc_in;
+
+
+ return 0;
+}
diff --git a/gvotable.c b/gvotable.c
new file mode 100644
index 0000000..9183e16
--- /dev/null
+++ b/gvotable.c
@@ -0,0 +1,1458 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#include "gvotable.h"
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/stringhash.h>
+
+#ifdef CONFIG_DEBUG_FS
+# include <linux/debugfs.h>
+# include <linux/seq_file.h>
+#endif
+
+#define VOTES_HISTORY_DEPTH 1
+#define MAX_NAME_LEN 16
+#define MAX_VOTE2STR_LEN 16
+
+
+#define DEBUGFS_CAST_VOTE_REASON "DEBUGFS"
+#define DEBUGFS_FORCE_VOTE_REASON "DEBUGFS_FORCE"
+
+static const char default_reason[] = "Default";
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *debugfs_root;
+#endif
+
+static DEFINE_MUTEX(gvotable_lock);
+static LIST_HEAD(gvotables);
+
+/* a ballot is associated to a reason */
+struct ballot {
+ bool enabled;
+ uint32_t reason_hash;
+ char reason[GVOTABLE_MAX_REASON_LEN];
+
+ uint32_t idx;
+ void *vote[VOTES_HISTORY_DEPTH];
+ int vote_size; /* !=0 when copy is requested */
+
+ uint32_t num_votes;
+
+ struct list_head list;
+};
+
+struct gvotable_election {
+ uint32_t hash;
+
+ int vote_size; /* actual vote size */
+ bool use_alloc; /* if true, use kalloc() for result and votes */
+
+ char name[MAX_NAME_LEN];
+ bool has_name; /* only elections with names are visible */
+
+ struct mutex cb_lock; /* see lock_result(), lock_election() */
+ struct mutex re_lock; /* see lock_result(), lock_election() */
+ void *owner;
+
+ void *result; /* current result and reason */
+ char reason[GVOTABLE_MAX_REASON_LEN];
+ bool result_is_valid;
+
+ void *data; /* _get_data() */
+
+ gvotable_callback_fn callback;
+ int (*cmp)(void *, void *);
+
+ bool auto_callback; /* allow disabling callbacks (internal) */
+ void *default_vote;
+ int has_default_vote; /* -1 no, 1 yes */
+
+ void *force_result;
+ bool force_result_is_enabled;
+
+ struct list_head votes; /* actual ballots */
+ uint32_t num_voters; /* number of ballots */
+
+ uint32_t num_votes; /* number of votes */
+
+ gvotable_v2sfn_t vote2str;
+ bool is_int_type; /* int-type debugfs entries */
+ bool is_bool_type;
+};
+
+#define gvotable_lock_result(el) mutex_lock(&(el)->re_lock)
+#define gvotable_unlock_result(el) mutex_unlock(&(el)->re_lock);
+
+#define CONFIG_DEBUG_GVOTABLE_LOCKS
+#ifdef CONFIG_DEBUG_GVOTABLE_LOCKS
+void gvotable_lock_election(struct gvotable_election *el)
+{
+ int ret;
+
+ ret = mutex_trylock(&el->cb_lock);
+ if (WARN(ret == 0 && el->owner == get_current(),
+ "%s cannot call this function from the callback\n",
+ el->has_name ? el->name : "<>"))
+ mutex_lock(&el->cb_lock);
+
+ el->owner = get_current();
+ gvotable_lock_result(el);
+}
+#else
+#define gvotable_lock_election(el) do { \
+ mutex_lock(&(el)->cb_lock); \
+ mutex_lock(&(el)->re_lock); \
+} while (0)
+
+#endif
+
+#define gvotable_unlock_callback(el) mutex_unlock(&(el)->cb_lock);
+#define gvotable_unlock_election(el) do { \
+ mutex_unlock(&(el)->re_lock); \
+ mutex_unlock(&(el)->cb_lock); \
+} while (0)
+
+
+struct election_slot {
+ struct gvotable_election *el;
+ struct list_head list;
+ struct dentry *de;
+};
+
+int gvotable_comparator_uint_max(void *l, void *r)
+{
+ unsigned int a = *((unsigned int *) &l);
+ unsigned int b = *((unsigned int *) &r);
+
+ if (a > b)
+ return 1;
+ else if (a < b)
+ return (-1);
+ else
+ return 0;
+}
+
+int gvotable_comparator_int(void *l, void *r)
+{
+ int a = *((int *) &l);
+ int b = *((int *) &r);
+
+ if (a > b)
+ return 1;
+ else if (a < b)
+ return (-1);
+ else
+ return 0;
+}
+
+/* compares l and r as integers */
+int gvotable_comparator_int_max(void *a, void *b)
+{
+ return -gvotable_comparator_int(a, b);
+}
+
+/* compares l and r as integers */
+int gvotable_comparator_int_min(void *a, void *b)
+{
+ return gvotable_comparator_int(a, b);
+}
+
+/* compares l and r as integers */
+int gvotable_comparator_uint_min(void *a, void *b)
+{
+ return -gvotable_comparator_uint_max(a, b);
+}
+
+/* Always add new elements on head of the list */
+int gvotable_comparator_most_recent(void *a, void *b)
+{
+ return (-1);
+}
+
+/* Always add new element on tail of the list */
+int gvotable_comparator_least_recent(void *a, void *b)
+{
+ return 1;
+}
+
+/*
+ * Here we use the cheapest choice, i.e. list head. bool elections return
+ * 0 when there are NO votes active and a 1 value when there is at least
+ * one vote.
+ */
+static int gvotable_comparator_bool(void *a, void *b)
+{
+ return 1;
+}
+
+/* NONE add to the head of the list */
+static int gvotable_comparator_none(void *a, void *b)
+{
+ return (-1);
+}
+
+int gvotable_v2s_int(char *str, size_t len, const void *vote)
+{
+ return scnprintf(str, len, "%ld", (unsigned long)vote);
+}
+
+int gvotable_v2s_uint(char *str, size_t len, const void *vote)
+{
+ return scnprintf(str, len, "%lu", (unsigned long)vote);
+}
+
+int gvotable_v2s_uint_hex(char *str, size_t len, const void *vote)
+{
+ return scnprintf(str, len, "0x%lx", (unsigned long)vote);
+}
+
+/* GVotable internal hashing function */
+static uint32_t gvotable_internal_hash(const char *str)
+{
+ return full_name_hash(NULL, str, strlen(str));
+}
+
+static void gvotable_internal_update_reason(struct gvotable_election *el,
+ const char *new_reason)
+{
+ strlcpy(el->reason, new_reason, GVOTABLE_MAX_REASON_LEN);
+}
+
+static void gvotable_internal_copy_result(struct gvotable_election *el,
+ void **result,
+ void *new_result)
+{
+ if (el->use_alloc)
+ memcpy(*result, new_result, el->vote_size);
+ else
+ *result = new_result;
+}
+
+static void gvotable_internal_update_result(struct gvotable_election *el,
+ void *new_result)
+{
+ gvotable_internal_copy_result(el, &el->result, new_result);
+ el->result_is_valid = true;
+}
+
+#define GVOTABLE_BOOL_TRUE_VALUE ((void *)1)
+#define GVOTABLE_BOOL_FALSE_VALUE ((void *)0)
+
+/*
+ * Determine the new result for the election, return true if the el->callback
+ * needs to be called for the election false otherwise. MUST return false if
+ * the el->callback is invalid (NULL).
+ * requires &->re_lock and &->cb_lock
+ */
+static bool gvotable_internal_run_election(struct gvotable_election *el)
+{
+ struct ballot *ballot;
+ bool callback_required = false;
+
+ if (el->force_result_is_enabled)
+ return false;
+ /* TODO: FIX this hack */
+ if (el->cmp == gvotable_comparator_none)
+ return true;
+
+ list_for_each_entry(ballot, &el->votes, list) {
+ if (!ballot->enabled)
+ continue;
+
+ /* Update reason if needed TODO: call *_set_result() */
+ if (!el->result_is_valid ||
+ strncmp(el->reason, ballot->reason,
+ GVOTABLE_MAX_REASON_LEN)) {
+
+ gvotable_internal_update_reason(el, ballot->reason);
+ callback_required = el->auto_callback;
+ }
+
+ /* Update result if needed TODO: call *_set_result() */
+ if (callback_required ||
+ el->cmp(el->result, ballot->vote[ballot->idx]) != 0) {
+ void *new_result;
+
+ /* any-type elections have a default int-type value */
+ if (el->is_bool_type)
+ new_result = GVOTABLE_BOOL_TRUE_VALUE;
+ else
+ new_result = ballot->vote[ballot->idx];
+
+ gvotable_internal_update_result(el, new_result);
+ callback_required = el->auto_callback;
+ }
+
+ /* updated also in gvotable_internal_update_result() */
+ el->result_is_valid = true;
+ goto exit_done;
+ }
+
+ /*
+ * Could not find a vote: use default if when set.
+ * bool-type elections always have a default int-type default vote.
+ */
+ if (el->has_default_vote == 1) {
+
+ /* TODO: call *_set_result() */
+ if (!el->result_is_valid ||
+ strncmp(el->reason, default_reason,
+ GVOTABLE_MAX_REASON_LEN)) {
+
+ gvotable_internal_update_reason(el, default_reason);
+ callback_required = el->auto_callback;
+ }
+
+ /* TODO: call *_set_result() */
+ if (callback_required ||
+ el->cmp(el->result, el->default_vote) != 0) {
+
+ gvotable_internal_update_result(el, el->default_vote);
+ callback_required = el->auto_callback;
+ }
+
+ /* updated also in gvotable_internal_update_result() */
+ el->result_is_valid = true;
+ } else {
+ callback_required = el->result_is_valid && el->auto_callback;
+ el->result_is_valid = false;
+ el->reason[0] = 0; /* default to null reason */
+ }
+
+exit_done:
+ return callback_required && el->callback;
+}
+
+/* requires &gvotable_lock */
+static struct election_slot *gvotable_find_internal(const char *name)
+{
+ struct election_slot *slot;
+ struct gvotable_election *el;
+ unsigned int hash;
+
+ if (!name)
+ return NULL;
+
+ hash = gvotable_internal_hash(name);
+
+ list_for_each_entry(slot, &gvotables, list) {
+ el = slot->el;
+ if ((hash == el->hash) && el->has_name &&
+ (strncmp(el->name, name, MAX_NAME_LEN) == 0))
+ return slot;
+ }
+
+ return NULL;
+}
+
+/* requires &gvotable_lock */
+static struct election_slot *gvotable_find_internal_ptr(struct gvotable_election *el)
+{
+ struct election_slot *slot;
+
+ list_for_each_entry(slot, &gvotables, list)
+ if (slot->el == el)
+ return slot;
+
+ return NULL;
+}
+
+/* requires &gvotable_lock */
+static void gvotable_add_internal(struct election_slot *slot)
+{
+ list_add(&slot->list, &gvotables);
+}
+
+/* requires &gvotable_lock */
+static void gvotable_delete_internal(struct election_slot *slot)
+{
+ list_del(&slot->list);
+ kfree(slot);
+}
+
+/* reader lock on election */
+static struct ballot *gvotable_ballot_find_internal(struct gvotable_election *el,
+ const char *reason)
+{
+ struct ballot *ballot;
+ uint32_t reason_hash;
+
+ reason_hash = gvotable_internal_hash(reason);
+
+ list_for_each_entry(ballot, &el->votes, list) {
+
+ if ((reason_hash == ballot->reason_hash) &&
+ (strncmp(ballot->reason, reason,
+ GVOTABLE_MAX_REASON_LEN) == 0))
+ return ballot;
+ }
+ return NULL;
+}
+
+void gvotable_election_for_each(struct gvotable_election *el,
+ gvotable_foreach_callback_fn callback_fn,
+ void *cb_data)
+{
+ struct ballot *ballot;
+ int ret;
+
+ if (el->force_result_is_enabled) {
+ callback_fn(cb_data, DEBUGFS_FORCE_VOTE_REASON,
+ el->force_result);
+ return;
+ }
+
+ /* TODO: LOCK list? */
+ list_for_each_entry(ballot, &el->votes, list) {
+ if (!ballot->enabled)
+ continue;
+
+ ret = callback_fn(cb_data, ballot->reason,
+ ballot->vote[ballot->idx]);
+ if (ret < 0)
+ break;
+ }
+}
+
+
+#ifdef CONFIG_DEBUG_FS
+static int gvotable_debugfs_create_el_int(struct election_slot *slot);
+static void gvotable_debugfs_create_el(struct election_slot *slot);
+static void gvotable_debugfs_delete_el(struct election_slot *slot);
+#else
+static int gvotable_debugfs_create_el_int(struct election_slot *slot)
+{
+}
+static void gvotable_debugfs_create_el(struct election_slot *slot)
+{
+}
+static void gvotable_debugfs_delete_el(struct election_slot *slot)
+{
+}
+#endif
+
+/* Allow redefining the allocator: required for testing */
+#ifndef gvotable_kzalloc
+#define gvotable_kzalloc(p, f) kzalloc(sizeof(*p), f)
+#endif
+
+/* Allow redefining the allocator: required for testing */
+#ifndef gvotable_needs_alloc
+#define gvotable_needs_alloc(vote_size) \
+ ((vote_size) > sizeof(((struct ballot *)0)->vote[0]))
+#endif
+
+struct gvotable_election *gvotable_create_election(const char *name,
+ int vote_size,
+ int (*cmp_fn)(void *, void *),
+ gvotable_callback_fn callback_fn,
+ void *data)
+{
+ struct gvotable_election *el = NULL;
+ struct election_slot *slot;
+
+ if (!cmp_fn)
+ cmp_fn = gvotable_comparator_none;
+
+ mutex_lock(&gvotable_lock);
+
+ if (name && gvotable_find_internal(name))
+ goto done_exit;
+
+ slot = gvotable_kzalloc(slot, GFP_KERNEL);
+ if (!slot)
+ goto done_exit;
+
+ slot->el = gvotable_kzalloc(slot->el, GFP_KERNEL);
+ if (!slot->el) {
+ kfree(slot);
+ goto done_exit;
+ }
+
+ mutex_init(&(slot->el->re_lock));
+ mutex_init(&(slot->el->cb_lock));
+ INIT_LIST_HEAD(&slot->el->votes);
+ slot->el->callback = callback_fn;
+ slot->el->auto_callback = true;
+ slot->el->cmp = cmp_fn;
+ slot->el->data = data;
+ slot->el->has_default_vote = -1;
+ slot->el->vote_size = vote_size;
+ slot->el->use_alloc = gvotable_needs_alloc(vote_size);
+
+ /* preallocate result */
+ if (slot->el->use_alloc) {
+ slot->el->result = kzalloc(vote_size, GFP_KERNEL);
+ if (!slot->el->result) {
+ kfree(slot->el);
+ kfree(slot);
+ goto done_exit;
+ }
+ }
+
+ if (name) {
+ slot->el->has_name = true;
+ slot->el->hash = gvotable_internal_hash(name);
+ strlcpy(slot->el->name, name, MAX_NAME_LEN);
+
+ gvotable_debugfs_create_el(slot);
+ }
+
+ gvotable_add_internal(slot);
+ el = slot->el;
+
+done_exit:
+ mutex_unlock(&gvotable_lock);
+ return el;
+}
+
+struct gvotable_election *gvotable_create_int_election(const char *name,
+ int (*cmp_fn)(void *, void *),
+ gvotable_callback_fn cb_fn,
+ void *data)
+{
+ struct gvotable_election *el;
+
+ el = gvotable_create_election(name, sizeof(int), cmp_fn, cb_fn, data);
+ if (!el)
+ return NULL;
+
+ el->is_int_type = true;
+ if (name) {
+ struct election_slot *slot;
+
+ slot = gvotable_find_internal(name);
+ if (slot)
+ gvotable_debugfs_create_el_int(slot);
+ }
+
+ return el;
+}
+
+/*
+ * "bool" elections return 1 when there is at least one vote active and 0
+ * otherwise. Actual votes are ignored and the result is always the state
+ * of the votes.
+ */
+struct gvotable_election *gvotable_create_bool_election(const char *name,
+ gvotable_callback_fn cb_fn,
+ void *data)
+{
+ struct gvotable_election *el;
+
+ el = gvotable_create_election(name, sizeof(int),
+ gvotable_comparator_bool, cb_fn, data);
+ if (!el)
+ return NULL;
+
+ /* the fist call to set_default doesn't run election */
+ gvotable_set_default(el, GVOTABLE_BOOL_FALSE_VALUE);
+ /* run the election to update the actual vote */
+ gvotable_internal_run_election(el);
+ el->is_bool_type = true;
+ return el;
+}
+
+/*
+ * Destroying an election involves removing all ballots and removing the
+ * election (and all its links) from the election slot.
+ * TODO: calls to the election API should validate the *el pointer with
+ * find_internal before accessing the election.
+ */
+int gvotable_destroy_election(struct gvotable_election *el)
+{
+ struct ballot *tmp, *ballot;
+ struct election_slot *slot;
+
+ if (!el)
+ return -EINVAL;
+
+ gvotable_lock_result(el);
+
+ /* TODO: mark el as pending deletion and fail all operations */
+ list_for_each_entry_safe(ballot, tmp, &el->votes, list) {
+
+ if (ballot->vote_size) {
+ int i;
+
+ for (i = 0; i < VOTES_HISTORY_DEPTH; i++) {
+ if (ballot->vote[i]) {
+ kfree(ballot->vote[i]);
+ ballot->vote[i] = NULL;
+ }
+ }
+ }
+
+ kfree(ballot);
+ }
+
+ gvotable_unlock_result(el);
+
+ /* Find slots associated with this handle and remove them */
+ mutex_lock(&gvotable_lock);
+ slot = gvotable_find_internal_ptr(el);
+ while (slot) {
+ gvotable_debugfs_delete_el(slot);
+ gvotable_delete_internal(slot);
+ slot = gvotable_find_internal_ptr(el);
+ }
+ mutex_unlock(&gvotable_lock);
+
+ if (el->use_alloc)
+ kfree(el->result);
+ kfree(el);
+ return 0;
+}
+
+
+/*
+ * Get a public election
+ * TODO: the election can be destroyed while in use so SW needs to mark the
+ * election as invalid somehow. One way to do this is to use find_internal()
+ * to validate the election before accessing the fields (must make an
+ * exception with nameless elections)
+ */
+struct gvotable_election *gvotable_election_get_handle(const char *name)
+{
+ struct election_slot *slot;
+
+ if (!name)
+ return NULL;
+
+ mutex_lock(&gvotable_lock);
+ slot = gvotable_find_internal(name);
+ mutex_unlock(&gvotable_lock);
+
+ return (slot) ? slot->el : NULL;
+}
+
+/* Set name of an election (makes election available for lookup) */
+int gvotable_election_set_name(struct gvotable_election *el, const char *name)
+{
+ struct election_slot *slot;
+
+ if (!el || !name)
+ return -EINVAL;
+
+ mutex_lock(&gvotable_lock);
+ if (el->has_name || gvotable_find_internal(name)) {
+ mutex_unlock(&gvotable_lock);
+ return -EEXIST;
+ }
+
+ el->has_name = true;
+ el->hash = gvotable_internal_hash(name);
+ strlcpy(el->name, name, MAX_NAME_LEN);
+
+ /* el->has_name ==> find internal will now find the election */
+ slot = gvotable_find_internal(name);
+ if (slot) {
+ gvotable_debugfs_create_el(slot);
+ if (slot->el->is_int_type)
+ gvotable_debugfs_create_el_int(slot);
+ }
+
+ mutex_unlock(&gvotable_lock);
+
+ return 0;
+}
+
+void gvotable_set_vote2str(struct gvotable_election *el,
+ gvotable_v2sfn_t vote2str)
+{
+ el->vote2str = vote2str;
+}
+
+static void gvotable_run_callback(struct gvotable_election *el)
+{
+ if (el->result_is_valid)
+ el->callback(el, el->reason, el->result);
+ else
+ el->callback(el, NULL, NULL);
+}
+
+/* Set the default value, rerun the election when the value changes */
+int gvotable_set_default(struct gvotable_election *el, void *default_val)
+{
+ bool changed;
+
+ /* boolean elections don't allow changing the default value */
+ if (!el || el->is_bool_type)
+ return -EINVAL;
+
+ gvotable_lock_election(el);
+
+ changed = el->has_default_vote == 1 && el->default_vote != default_val;
+ el->default_vote = default_val;
+
+ if (changed) {
+ if (gvotable_internal_run_election(el)) {
+ gvotable_unlock_result(el);
+ gvotable_run_callback(el);
+ }
+ } else {
+ gvotable_unlock_result(el);
+ }
+
+ el->has_default_vote = 1;
+ gvotable_unlock_callback(el);
+ return 0;
+}
+
+int gvotable_get_default(struct gvotable_election *el, void **result)
+{
+ int ret;
+
+ if (!el)
+ return -EINVAL;
+
+ gvotable_lock_election(el);
+ if (el->has_default_vote) {
+ gvotable_internal_copy_result(el, result, el->default_vote);
+ ret = 0;
+ } else {
+ ret = -EINVAL;
+ }
+
+ gvotable_unlock_election(el);
+ return ret;
+}
+
+/* Enable or disable usage of a default value for a given election */
+int gvotable_use_default(struct gvotable_election *el, bool default_is_enabled)
+{
+
+ /* boolean elections don't allow changing the default value */
+ if (!el || el->is_bool_type)
+ return -EINVAL;
+
+ gvotable_lock_election(el);
+
+ el->has_default_vote = default_is_enabled;
+ if (gvotable_internal_run_election(el)) {
+ gvotable_unlock_result(el);
+ gvotable_run_callback(el);
+ } else {
+ gvotable_unlock_result(el);
+ }
+
+ gvotable_unlock_callback(el);
+ return 0;
+}
+
+/* Retrieve data for an election */
+void* gvotable_get_data(struct gvotable_election *el)
+{
+ return el ? el->data: NULL;
+}
+
+/* NULL (0) is a valid value when votes are integers */
+static int gvotable_get_current_result_unlocked(struct gvotable_election *el,
+ const void **result)
+{
+ if (el->force_result_is_enabled)
+ *result = el->force_result;
+ else if (el->result_is_valid)
+ *result = el->result;
+ else
+ return -EAGAIN;
+
+ return 0;
+}
+
+int gvotable_get_current_vote(struct gvotable_election *el, const void **vote)
+{
+ int ret;
+
+ if (!el || !vote)
+ return -EINVAL;
+
+ gvotable_lock_result(el);
+ ret = gvotable_get_current_result_unlocked(el, vote);
+ gvotable_unlock_result(el);
+
+ return 0;
+}
+
+int gvotable_get_current_int_vote(struct gvotable_election *el)
+{
+ const void *ptr;
+ int ret;
+
+ ret = gvotable_get_current_vote(el, &ptr);
+ return (ret) ? ret : (uintptr_t)ptr;
+}
+
+/* copy the actual current vote */
+int gvotable_copy_current_result(struct gvotable_election *el, void *vote,
+ int vote_size)
+{
+ const void *tmp;
+ int ret;
+
+ if (!el || !vote)
+ return -EINVAL;
+ if (vote_size != el->vote_size)
+ return -ERANGE;
+
+ gvotable_lock_result(el);
+ ret = gvotable_get_current_result_unlocked(el, &tmp);
+ if (ret == 0)
+ memcpy(vote, tmp, vote_size);
+ gvotable_unlock_result(el);
+
+ return 0;
+}
+
+static int gvotable_get_current_reason_unlocked(struct gvotable_election *el,
+ char *reason, int max_len)
+{
+ char *r = NULL;
+
+ if (el->force_result_is_enabled)
+ r = DEBUGFS_FORCE_VOTE_REASON;
+ else if (el->result_is_valid)
+ r = el->reason;
+
+ return r ? strlcpy(reason, r, max_len) : -EAGAIN;
+}
+
+
+/* Retrieve current reason for election result. */
+int gvotable_get_current_reason(struct gvotable_election *el, char *reason,
+ int max_len)
+{
+ int len;
+
+ if (!el || !reason)
+ return -EINVAL;
+
+ gvotable_lock_result(el);
+ len = gvotable_get_current_reason_unlocked(el, reason, max_len);
+ gvotable_unlock_result(el);
+ return len;
+}
+
+/* Get vote associated with a specific reason */
+int gvotable_get_vote(struct gvotable_election *el, const char *reason,
+ void **vote)
+{
+ struct ballot *ballot;
+
+ if (!el || !reason || !vote)
+ return -EINVAL;
+
+ gvotable_lock_result(el);
+ ballot = gvotable_ballot_find_internal(el, reason);
+ if (!ballot) {
+ gvotable_unlock_result(el);
+ return -ENODEV;
+ }
+
+ *vote = ballot->vote[ballot->idx];
+ gvotable_unlock_result(el);
+ return 0;
+}
+
+/* Determine the reason is enabled */
+int gvotable_is_enabled(struct gvotable_election *el, const char *reason,
+ bool *enabled)
+{
+ struct ballot *ballot;
+
+ if (!el || !reason || !enabled)
+ return -EINVAL;
+
+ gvotable_lock_result(el);
+ ballot = gvotable_ballot_find_internal(el, reason);
+ if (!ballot) {
+ gvotable_unlock_result(el);
+ return -ENODEV;
+ }
+
+ *enabled = ballot->enabled;
+ gvotable_unlock_result(el);
+ return 0;
+}
+
+/* requires &el->re_lock */
+static int gvotable_update_ballot(struct ballot *ballot, void *vote,
+ bool enabled)
+{
+ const int idx = (ballot->idx + 1) % VOTES_HISTORY_DEPTH;
+
+ ballot->enabled = enabled;
+
+ if (ballot->vote_size == 0) {
+ ballot->vote[idx] = vote;
+ goto exit_done;
+ }
+
+ if (!ballot->vote[idx]) {
+ ballot->vote[idx] = kzalloc(ballot->vote_size, GFP_KERNEL);
+ if (!ballot->vote[idx])
+ return -ENOMEM;
+ }
+
+ memcpy(ballot->vote[idx], vote, ballot->vote_size);
+
+exit_done:
+ ballot->idx = idx;
+ ballot->num_votes++;
+ return 0;
+}
+
+/* requires &el->re_lock */
+static void gvotable_add_ballot(struct gvotable_election *el,
+ struct ballot *ballot)
+{
+ struct ballot *last, *tmp;
+ void *vote = ballot->vote[ballot->idx];
+
+ /* If this is the only element, just add */
+ if (list_empty(&el->votes)) {
+ list_add(&ballot->list, &el->votes);
+ return;
+ }
+
+ /* strcmp() behavior, most recent (-1), least recent (1), min */
+ list_for_each_entry(tmp, &el->votes, list) {
+ if (el->cmp(vote, tmp->vote[tmp->idx]) < 0) {
+ /* Add new element before current one */
+ list_add_tail(&ballot->list, &tmp->list);
+ return;
+ }
+ }
+
+ /* Add new element after the last one */
+ last = list_last_entry(&el->votes, struct ballot, list);
+ list_add(&ballot->list, &last->list);
+
+ el->num_votes++;
+}
+
+static int gvotable_recast_ballot(struct gvotable_election *el,
+ const char *reason,
+ bool enabled)
+{
+ struct ballot *ballot;
+ int ret;
+
+ gvotable_lock_election(el);
+
+ ballot = gvotable_ballot_find_internal(el, reason);
+ if (!ballot) {
+ gvotable_unlock_election(el);
+ return -EINVAL;
+ }
+
+ list_del(&ballot->list);
+ ret = gvotable_update_ballot(ballot, ballot->vote[ballot->idx],
+ enabled);
+ if (ret < 0) {
+ gvotable_unlock_election(el);
+ return ret;
+ }
+
+ gvotable_add_ballot(el, ballot);
+
+ if (gvotable_internal_run_election(el)) {
+ gvotable_unlock_result(el);
+ gvotable_run_callback(el);
+ } else {
+ gvotable_unlock_result(el);
+ }
+
+ gvotable_unlock_callback(el);
+ return 0;
+}
+
+#define gvotable_ballot_size_ok(size) ((size) <= sizeof(void *))
+
+/* This is generally used for shortcute so doesn't belong to the API */
+void gvotable_run_election(struct gvotable_election *el)
+{
+ bool callback;
+
+ gvotable_lock_election(el);
+ callback = gvotable_internal_run_election(el);
+ gvotable_unlock_result(el);
+ if (callback)
+ gvotable_run_callback(el);
+ gvotable_unlock_callback(el);
+}
+
+/* This can only be called while in the callback: the API here is not great */
+int gvotable_election_set_result(struct gvotable_election *el,
+ const char *reason, void *result)
+{
+ if (!el || !reason || reason[0] == 0)
+ return -EINVAL;
+ /* a NULL vote is ok when we are not using copy */
+ if (el->use_alloc && !result)
+ return -EINVAL;
+ if (el->cmp != gvotable_comparator_none) {
+ WARN_ONCE(1, "Setting the result is not supported for a votable of this type");
+ return -EINVAL;
+ }
+
+ gvotable_internal_update_reason(el, reason);
+ gvotable_internal_update_result(el, result);
+ return 0;
+}
+
+int gvotable_cast_vote(struct gvotable_election *el, const char *reason,
+ void *vote, bool enabled)
+{
+ struct ballot *ballot;
+ int ret;
+
+ if (!el || !reason || reason[0] == 0)
+ return -EINVAL;
+ /* a NULL vote is ok when we are not using copy */
+ if (el->use_alloc && !vote)
+ return -EINVAL;
+
+ gvotable_lock_election(el);
+
+ ballot = gvotable_ballot_find_internal(el, reason);
+ if (ballot) {
+ list_del(&ballot->list);
+ } else {
+ ballot = gvotable_kzalloc(ballot, GFP_KERNEL);
+ if (!ballot) {
+ gvotable_unlock_election(el);
+ return -ENOMEM;
+ }
+
+ ballot->reason_hash = gvotable_internal_hash(reason);
+ strlcpy(ballot->reason, reason, GVOTABLE_MAX_REASON_LEN);
+ if (el->use_alloc)
+ ballot->vote_size = el->vote_size;
+ el->num_voters++;
+ }
+
+ ret = gvotable_update_ballot(ballot, vote, enabled);
+ if (ret < 0) {
+ /* TODO: free the first vote on failure */
+ gvotable_unlock_election(el);
+ return ret;
+ }
+
+ gvotable_add_ballot(el, ballot);
+
+ if (gvotable_internal_run_election(el)) {
+ gvotable_unlock_result(el);
+ gvotable_run_callback(el);
+ } else {
+ gvotable_unlock_result(el);
+ }
+
+ gvotable_unlock_callback(el);
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define GVOTABLE_DEBUG_ATTRIBUTE(name, fn_read, fn_write) \
+static const struct file_operations name = { \
+ .open = simple_open, \
+ .llseek = no_llseek, \
+ .read = fn_read, \
+ .write = fn_write, \
+}
+
+/* requires lock on election */
+static int gvotable_dump_election(char *buf, size_t len,
+ struct gvotable_election *el)
+{
+ gvotable_v2sfn_t vote2str = el->vote2str;
+ char reason[GVOTABLE_MAX_REASON_LEN];
+ int rc, count = 0;
+ const void *vote;
+
+ count += scnprintf(&buf[count], len - count, "%s:",
+ el->has_name ? el->name : " :");
+
+ rc = gvotable_get_current_reason_unlocked(el, reason, sizeof(reason));
+ if (rc < 0)
+ count += scnprintf(&buf[count], len - count, " <%d>", rc);
+ else
+ count += scnprintf(&buf[count], len - count, " current=%s",
+ reason);
+
+ rc = gvotable_get_current_result_unlocked(el, &vote);
+ if (rc < 0) {
+ count += scnprintf(&buf[count], len - count, " <%d>", rc);
+ } else {
+ count += scnprintf(&buf[count], len - count, " v=");
+ if (vote2str)
+ count += vote2str(&buf[count], len - count, vote);
+ else
+ count += scnprintf(&buf[count], len - count, "<>");
+ }
+
+ /* bool elections always have a default (0) vote */
+ if (!el->is_bool_type && el->has_default_vote == 1) {
+ count += scnprintf(&buf[count], len - count, " d=");
+ if (vote2str)
+ count += vote2str(&buf[count], len - count,
+ el->default_vote);
+ else
+ count += scnprintf(&buf[count], len - count, "<>");
+ }
+
+ count += scnprintf(&buf[count], len - count, "\n");
+ return count;
+}
+
+/* requires &gvotable_lock */
+static int gvotable_list_elections(char *buf, size_t len)
+{
+ struct election_slot *slot;
+ int count = 0;
+
+ if (list_empty(&gvotables))
+ return 0;
+
+ list_for_each_entry(slot, &gvotables, list) {
+ gvotable_lock_result(slot->el);
+
+ count += gvotable_dump_election(&buf[count], len - count,
+ slot->el);
+
+ gvotable_unlock_result(slot->el);
+ }
+
+ return count;
+}
+
+static ssize_t debugfs_list_elections(struct file *filp,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ const int buf_size = 4096;
+ char *buf;
+ int len;
+
+ buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&gvotable_lock);
+ len = gvotable_list_elections(buf, buf_size);
+ if (!len)
+ len = scnprintf(buf, buf_size, "data not available\n");
+ mutex_unlock(&gvotable_lock);
+
+ count = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+ kfree(buf);
+ return count;
+}
+
+GVOTABLE_DEBUG_ATTRIBUTE(debugfs_elections_fops, debugfs_list_elections, NULL);
+
+
+/* requires lock on election */
+static int gvotable_dump_ballot(char *buf, size_t len, struct ballot *ballot,
+ gvotable_v2sfn_t vote2str)
+{
+ int count = 0;
+
+ count += scnprintf(&buf[count], len - count, " %s", ballot->reason);
+ count += scnprintf(&buf[count], len - count, " en=%d val=",
+ ballot->enabled);
+ count += vote2str(&buf[count], len - count, ballot->vote[ballot->idx]);
+ count += scnprintf(&buf[count], len - count, " #votes=%d",
+ ballot->num_votes);
+
+ return count;
+}
+
+/* requires lock on election */
+static int gvotable_list_ballots(char *buf, size_t len,
+ struct gvotable_election *el,
+ gvotable_v2sfn_t vote2str)
+{
+ struct ballot *ballot;
+ int count = 0;
+
+ if (!vote2str)
+ vote2str = el->vote2str;
+ if (!el || !vote2str)
+ return -EINVAL;
+
+ list_for_each_entry(ballot, &el->votes, list) {
+
+ count += scnprintf(&buf[count], len - count, "%s:",
+ el->has_name ? el->name : " :");
+ count += gvotable_dump_ballot(&buf[count], len - count, ballot,
+ vote2str);
+ count += scnprintf(&buf[count], len - count, "\n");
+ }
+
+ return count;
+}
+
+static ssize_t debugfs_list_ballots(struct file *filp,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct election_slot *slot = filp->private_data;
+ const int buf_size = 4096;
+ char *buf;
+ int len;
+
+ buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ gvotable_lock_result(slot->el);
+
+ len = gvotable_list_ballots(buf, buf_size, slot->el, NULL);
+ if (len < 0) {
+ len = scnprintf(buf, buf_size, "data not available (%d)\n",
+ len);
+ } else {
+ len += gvotable_dump_election(&buf[len], buf_size - len,
+ slot->el);
+ }
+
+ gvotable_unlock_result(slot->el);
+
+ count = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+ kfree(buf);
+
+ return count;
+}
+
+GVOTABLE_DEBUG_ATTRIBUTE(debugfs_ballots_fops, debugfs_list_ballots, NULL);
+
+static ssize_t debugfs_enable_vote(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct election_slot *slot = filp->private_data;
+ char reason[GVOTABLE_MAX_REASON_LEN] = { 0 };
+ int ret;
+
+ ret = simple_write_to_buffer(reason, sizeof(reason), ppos, user_buf,
+ count);
+ if (ret < 0)
+ return -EFAULT;
+
+ ret = gvotable_recast_ballot(slot->el, reason, true);
+ if (ret < 0) {
+ pr_err("cannot recast %s (%d)\n", reason, ret);
+ return ret;
+ }
+
+ return count;
+}
+
+GVOTABLE_DEBUG_ATTRIBUTE(debugs_enable_vote_fops, NULL, debugfs_enable_vote);
+
+
+static ssize_t debugfs_disable_vote(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct election_slot *slot = filp->private_data;
+ char reason[GVOTABLE_MAX_REASON_LEN] = { 0 };
+ int ret;
+
+ ret = simple_write_to_buffer(reason, sizeof(reason), ppos, user_buf,
+ count);
+ if (ret < 0)
+ return -EFAULT;
+
+ ret = gvotable_recast_ballot(slot->el, reason, false);
+ if (ret < 0) {
+ pr_err("cannot recast %s (%d)\n", reason, ret);
+ return ret;
+ }
+
+ return count;
+}
+
+GVOTABLE_DEBUG_ATTRIBUTE(debugs_disable_vote_fops, NULL, debugfs_disable_vote);
+
+/* TODO: only enable for int votes */
+static int debugfs_cast_int_vote(void *data, u64 val)
+{
+ struct election_slot *slot = data;
+ bool enabled = false;
+ int ret;
+
+ ret = gvotable_is_enabled(slot->el, DEBUGFS_CAST_VOTE_REASON,
+ &enabled);
+ if (ret < 0)
+ pr_debug("vote not present\n");
+
+ return gvotable_cast_vote(slot->el, DEBUGFS_CAST_VOTE_REASON,
+ (void*)val, enabled);
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debugfs_cast_int_vote_fops,
+ NULL,
+ debugfs_cast_int_vote, "%llu\n");
+
+/* TODO: only enable for int votes */
+static int debugfs_force_int_value(void *data, u64 val)
+{
+ struct election_slot *slot = data;
+
+ slot->el->force_result = (void*)val;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debugfs_force_int_value_fops,
+ NULL,
+ debugfs_force_int_value, "%llu\n");
+
+static int debugfs_force_int_active(void *data, u64 val)
+{
+ struct election_slot *slot = data;
+
+
+ gvotable_lock_election(slot->el);
+
+ slot->el->force_result_is_enabled = !!val;
+ gvotable_unlock_result(slot->el);
+
+ if (!slot->el->callback)
+ goto exit_done;
+
+ if (slot->el->force_result_is_enabled) {
+ slot->el->callback(slot->el, DEBUGFS_FORCE_VOTE_REASON,
+ slot->el->force_result);
+ } else {
+ gvotable_run_callback(slot->el);
+ }
+
+exit_done:
+ gvotable_unlock_callback(slot->el);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debugfs_force_int_active_fops,
+ NULL,
+ debugfs_force_int_active, "%llu\n");
+
+static void gvotable_debugfs_cleanup(void)
+{
+ if (debugfs_root)
+ debugfs_remove_recursive(debugfs_root);
+ debugfs_root = NULL;
+}
+
+static int gvotable_debugfs_create_el_int(struct election_slot *slot)
+{
+ if (!slot->de)
+ return -ENOENT;
+
+ debugfs_create_file("cast_int_vote", 0200, slot->de, slot,
+ &debugfs_cast_int_vote_fops);
+ debugfs_create_file("force_int_value", 0200, slot->de, slot,
+ &debugfs_force_int_value_fops);
+ debugfs_create_file("force_int_active", 0200, slot->de, slot,
+ &debugfs_force_int_active_fops);
+
+ return 0;
+}
+
+static void gvotable_debugfs_create_el(struct election_slot *slot)
+{
+ /* TODO: add anon to a special directory */
+ if (!slot->el->has_name)
+ return;
+
+ if (!debugfs_root) {
+ debugfs_root = debugfs_create_dir("gvotables", 0);
+ if (!debugfs_root) {
+ pr_err("cannot create gvotables debug directory\n");
+ return;
+ }
+
+ /* add: list all the elections including the anon ones */
+ debugfs_create_file("elections", 0444, debugfs_root, NULL,
+ &debugfs_elections_fops);
+
+ pr_err("gvotables debug directory OK\n");
+ }
+
+ slot->de = debugfs_create_dir(slot->el->name, debugfs_root);
+ if (!slot->de) {
+ pr_err("cannot create debugfs entry for slot=%p\n", slot);
+ return;
+ }
+
+ debugfs_create_file("status", 0444, slot->de, slot,
+ &debugfs_ballots_fops);
+ debugfs_create_file("enable_vote", 0200, slot->de, slot,
+ &debugs_enable_vote_fops);
+ debugfs_create_file("disable_vote", 0200, slot->de, slot,
+ &debugs_disable_vote_fops);
+}
+
+static void gvotable_debugfs_delete_el(struct election_slot *slot)
+{
+ if (!slot->de)
+ return;
+
+ debugfs_remove_recursive(slot->de);
+ slot->de = NULL;
+}
+
+
+#else /* CONFIG_DEBUG_FS */
+static inline void gvotable_debugfs_cleanup(void)
+{
+
+}
+#endif
+
+
+static int __init gvotable_init(void)
+{
+ return 0;
+}
+
+static void __exit gvotable_exit(void)
+{
+ struct election_slot *slot, *tmp;
+
+ gvotable_debugfs_cleanup();
+ list_for_each_entry_safe(slot, tmp, &gvotables, list) {
+ pr_debug("Destroying %p\n", slot->el);
+ gvotable_destroy_election(slot->el);
+ }
+ pr_info("Deinit completed\n\n");
+ return;
+}
+
+module_init(gvotable_init);
+module_exit(gvotable_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Luigi Zevola <[email protected]>");
+MODULE_DESCRIPTION("Election library for shared resources");
+MODULE_VERSION("0.01");
diff --git a/gvotable.h b/gvotable.h
new file mode 100644
index 0000000..ca89e5f
--- /dev/null
+++ b/gvotable.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef __GOOGLE_GVOTABLE_H_
+#define __GOOGLE_GVOTABLE_H_
+
+#include <linux/types.h>
+#include <linux/mutex.h>
+
+#define GVOTABLE_MAX_REASON_LEN 16
+
+struct gvotable_election;
+
+typedef int (*gvotable_cmp_fn)(void *a, void *b);
+typedef void (*gvotable_callback_fn)(struct gvotable_election *el,
+ const char *reason,
+ void *vote);
+
+struct gvotable_election *gvotable_create_election(const char *name,
+ int vote_size,
+ int (*cmp_fn)(void *, void *),
+ gvotable_callback_fn callback_fn,
+ void *data);
+
+#define _GVOTABLE_EPC(a, ...) a ## __VA_ARGS__
+#define GVOTABLE__ELECTION_HELPER(t) \
+ _GVOTABLE_EPC(_GVOTABLE_EPC(gvotable_create_,t)_election)(name, cmp_fn, cb_fn, data)
+
+/* NOTE: cannot change the data after init to avoid adding a lock for it */
+struct gvotable_election *gvotable_create_int_election(const char *name,
+ gvotable_cmp_fn cmp_fn,
+ gvotable_callback_fn callback_fn,
+ void *data);
+
+struct gvotable_election *gvotable_create_bool_election(const char *name,
+ gvotable_callback_fn callback_fn,
+ void *data);
+
+
+int gvotable_destroy_election(struct gvotable_election *el);
+
+struct gvotable_election *gvotable_election_get_handle(const char *name);
+
+/* TODO: redesign this API */
+typedef int (*gvotable_foreach_callback_fn)(void *data, const char *reason,
+ void *vote);
+void gvotable_election_for_each(struct gvotable_election *el,
+ gvotable_foreach_callback_fn callback_fn,
+ void *callback_data);
+int gvotable_election_set_result(struct gvotable_election *el,
+ const char *reason, void *vote);
+
+
+void *gvotable_get_data(struct gvotable_election *el);
+
+int gvotable_get_current_reason(struct gvotable_election *el,
+ char *reason,
+ int max_reason_len);
+
+int gvotable_set_default(struct gvotable_election *el, void *default_val);
+int gvotable_get_default(struct gvotable_election *el, void **default_val);
+
+int gvotable_election_set_name(struct gvotable_election *el,
+ const char *name);
+
+int gvotable_use_default(struct gvotable_election *el,
+ bool default_is_enabled);
+
+int gvotable_cast_vote(struct gvotable_election *el,
+ const char *reason,
+ void *vote,
+ bool enabled);
+
+int gvotable_get_vote(struct gvotable_election *el,
+ const char *reason,
+ void **vote);
+
+int gvotable_is_enabled(struct gvotable_election *el,
+ const char *reason,
+ bool *enabled);
+
+int gvotable_get_current_int_vote(struct gvotable_election *el);
+int gvotable_get_current_vote(struct gvotable_election *el, const void **vote);
+int gvotable_copy_current_result(struct gvotable_election *el, void *vote,
+ int vote_size);
+
+int gvotable_comparator_uint_max(void *a, void *b);
+int gvotable_comparator_uint_min(void *a, void *b);
+int gvotable_comparator_int_max(void *a, void *b);
+int gvotable_comparator_int_min(void *a, void *b);
+int gvotable_comparator_most_recent(void *a, void *b);
+int gvotable_comparator_least_recent(void *a, void *b);
+
+/* dump, debug */
+typedef int (*gvotable_v2sfn_t)(char *str, size_t len, const void *);
+int gvotable_v2s_int(char *str, size_t len, const void *vote);
+int gvotable_v2s_uint(char *str, size_t len, const void *vote);
+int gvotable_v2s_uint_hex(char *str, size_t len, const void *vote);
+void gvotable_set_vote2str(struct gvotable_election *el,
+ gvotable_v2sfn_t vote2str);
+
+#endif /* __GOOGLE_GVOTABLE_H_*/
diff --git a/logbuffer.c b/logbuffer.c
new file mode 100644
index 0000000..b80084d
--- /dev/null
+++ b/logbuffer.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2019 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 ": " fmt
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/rtc.h>
+#include <linux/sched/clock.h>
+#include <linux/seq_file.h>
+#include <linux/suspend.h>
+#include <linux/slab.h>
+#include <linux/syscore_ops.h>
+#include <linux/vmalloc.h>
+
+#define LOG_BUFFER_ENTRIES 1024
+#define LOG_BUFFER_ENTRY_SIZE 256
+#define ID_LENGTH 50
+
+struct logbuffer {
+ int logbuffer_head;
+ int logbuffer_tail;
+ // protects buffer
+ spinlock_t logbuffer_lock;
+ u8 *buffer;
+ struct dentry *file;
+ char id[ID_LENGTH];
+ struct list_head entry;
+};
+
+/*
+ * Rootdir for the log files.
+ */
+struct dentry *rootdir;
+
+/*
+ * Device suspended since last logged.
+ */
+bool suspend_since_last_logged;
+
+/*
+ * List to maintain logbuffer intance.
+ */
+static LIST_HEAD(instances);
+
+/*
+ * Protects instances list.
+ */
+static spinlock_t instances_lock;
+
+static void __logbuffer_log(struct logbuffer *instance,
+ const char *tmpbuffer, bool record_utc)
+{
+ u64 ts_nsec = local_clock();
+ unsigned long rem_nsec = do_div(ts_nsec, 1000000000);
+
+ if (record_utc) {
+ struct timespec ts;
+ struct rtc_time tm;
+
+ getnstimeofday(&ts);
+ rtc_time_to_tm(ts.tv_sec, &tm);
+ scnprintf(instance->buffer + (instance->logbuffer_head *
+ LOG_BUFFER_ENTRY_SIZE),
+ LOG_BUFFER_ENTRY_SIZE,
+ "[%5lu.%06lu] %d-%02d-%02d %02d:%02d:%02d.%09lu UTC",
+ (unsigned long)ts_nsec, rem_nsec / 1000,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
+ } else {
+ scnprintf(instance->buffer + (instance->logbuffer_head *
+ LOG_BUFFER_ENTRY_SIZE),
+ LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s",
+ (unsigned long)ts_nsec, rem_nsec / 1000,
+ tmpbuffer);
+ }
+
+ instance->logbuffer_head = (instance->logbuffer_head + 1)
+ % LOG_BUFFER_ENTRIES;
+ if (instance->logbuffer_head == instance->logbuffer_tail) {
+ instance->logbuffer_tail = (instance->logbuffer_tail + 1)
+ % LOG_BUFFER_ENTRIES;
+ }
+}
+
+void logbuffer_vlog(struct logbuffer *instance, const char *fmt,
+ va_list args)
+{
+ char tmpbuffer[LOG_BUFFER_ENTRY_SIZE];
+ unsigned long flags;
+
+ if (!instance)
+ return;
+
+ /* Empty log msgs are passed from TCPM to log RTC.
+ * The RTC is printed if thats the first message
+ * printed after resume.
+ */
+ if (fmt)
+ vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args);
+
+ spin_lock_irqsave(&instance->logbuffer_lock, flags);
+ if (instance->logbuffer_head < 0 ||
+ instance->logbuffer_head >= LOG_BUFFER_ENTRIES) {
+ pr_warn("Bad log buffer index %d\n", instance->logbuffer_head);
+ goto abort;
+ }
+
+ /* Print UTC at the start of the buffer */
+ if ((instance->logbuffer_head == instance->logbuffer_tail) ||
+ (instance->logbuffer_head == LOG_BUFFER_ENTRIES - 1)) {
+ __logbuffer_log(instance, tmpbuffer, true);
+ /* Print UTC when logging after suspend */
+ } else if (suspend_since_last_logged) {
+ __logbuffer_log(instance, tmpbuffer, true);
+ suspend_since_last_logged = false;
+ } else if (!fmt) {
+ goto abort;
+ }
+
+ __logbuffer_log(instance, tmpbuffer, false);
+
+abort:
+ spin_unlock_irqrestore(&instance->logbuffer_lock, flags);
+}
+EXPORT_SYMBOL_GPL(logbuffer_vlog);
+
+void logbuffer_log(struct logbuffer *instance, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ logbuffer_vlog(instance, fmt, args);
+ va_end(args);
+}
+EXPORT_SYMBOL_GPL(logbuffer_log);
+
+static int logbuffer_seq_show(struct seq_file *s, void *v)
+{
+ struct logbuffer *instance = (struct logbuffer *)s->private;
+ int tail;
+
+ spin_lock(&instance->logbuffer_lock);
+ tail = instance->logbuffer_tail;
+ while (tail != instance->logbuffer_head) {
+ seq_printf(s, "%s\n", instance->buffer +
+ (tail * LOG_BUFFER_ENTRY_SIZE));
+ tail = (tail + 1) % LOG_BUFFER_ENTRIES;
+ }
+
+ spin_unlock(&instance->logbuffer_lock);
+
+ return 0;
+}
+
+static int logbuffer_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, logbuffer_seq_show, inode->i_private);
+}
+
+static const struct file_operations logbuffer_debug_operations = {
+ .open = logbuffer_debug_open,
+ .llseek = seq_lseek,
+ .read = seq_read,
+ .release = single_release,
+};
+
+struct logbuffer *debugfs_logbuffer_register(char *name)
+{
+ struct logbuffer *instance;
+ unsigned long flags;
+
+ if (IS_ERR_OR_NULL(rootdir)) {
+ pr_err("rootdir not found\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ instance = kzalloc(sizeof(struct logbuffer), GFP_KERNEL);
+ if (!instance) {
+ pr_err("fialed to create instance %s\n", name);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ instance->buffer = vzalloc(LOG_BUFFER_ENTRIES * LOG_BUFFER_ENTRY_SIZE);
+ if (!instance->buffer) {
+ pr_err("failed to create buffer %s\n", name);
+ instance = ERR_PTR(-ENOMEM);
+ goto free_instance;
+ }
+
+ instance->file = debugfs_create_file(name,
+ 0444, rootdir, instance,
+ &logbuffer_debug_operations);
+ if (IS_ERR_OR_NULL(instance->file)) {
+ pr_err("Failed to create debugfs file:%s err:%ld\n", name,
+ PTR_ERR(instance->file));
+ goto free_buffer;
+ }
+
+ strlcpy(instance->id, name, sizeof(instance->id));
+
+ spin_lock_init(&instance->logbuffer_lock);
+
+ spin_lock_irqsave(&instances_lock, flags);
+ list_add(&instance->entry, &instances);
+ spin_unlock_irqrestore(&instances_lock, flags);
+
+ pr_info(" id:%s registered\n", name);
+ return instance;
+
+free_buffer:
+ vfree(instance->buffer);
+free_instance:
+ kfree(instance);
+
+ return ERR_PTR(-ENOMEM);
+}
+EXPORT_SYMBOL_GPL(debugfs_logbuffer_register);
+
+void debugfs_logbuffer_unregister(struct logbuffer *instance)
+{
+ unsigned long flags;
+
+ if (!instance) {
+ pr_err("Instance ptr null\n");
+ return;
+ }
+
+ debugfs_remove(instance->file);
+ vfree(instance->buffer);
+ spin_lock_irqsave(&instances_lock, flags);
+ list_del(&instance->entry);
+ spin_unlock_irqrestore(&instances_lock, flags);
+ pr_info(" id:%s unregistered\n", instance->id);
+ kfree(instance);
+}
+EXPORT_SYMBOL_GPL(debugfs_logbuffer_unregister);
+
+int logbuffer_suspend(void)
+{
+ suspend_since_last_logged = true;
+ return 0;
+}
+
+static struct syscore_ops logbuffer_ops = {
+ .suspend = logbuffer_suspend,
+};
+
+static int __init logbuffer_debugfs_init(void)
+{
+ spin_lock_init(&instances_lock);
+
+ rootdir = debugfs_create_dir("logbuffer", NULL);
+ if (IS_ERR_OR_NULL(rootdir)) {
+ pr_err("Unable to create rootdir %ld\n", PTR_ERR(rootdir));
+ return PTR_ERR(rootdir);
+ }
+
+ register_syscore_ops(&logbuffer_ops);
+
+ return 0;
+}
+
+static void logbuffer_debugfs_exit(void)
+{
+ struct logbuffer *instance, *next;
+ unsigned long flags;
+
+ spin_lock_irqsave(&instances_lock, flags);
+ list_for_each_entry_safe(instance, next, &instances, entry) {
+ debugfs_remove(instance->file);
+ vfree(instance->buffer);
+ list_del(&instance->entry);
+ pr_info(" id:%s unregistered\n", instance->id);
+ kfree(instance);
+ }
+ spin_unlock_irqrestore(&instances_lock, flags);
+ debugfs_remove(rootdir);
+}
+early_initcall(logbuffer_debugfs_init);
+module_exit(logbuffer_debugfs_exit);
diff --git a/logbuffer.h b/logbuffer.h
new file mode 100644
index 0000000..2c99495
--- /dev/null
+++ b/logbuffer.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef __GOOGLE_LOGBUFFER_H_
+#define __GOOGLE_LOGBUFFER_H_
+
+#include <stdarg.h>
+
+struct logbuffer;
+void logbuffer_log(struct logbuffer *instance, const char *fmt, ...);
+void logbuffer_vlog(struct logbuffer *instance, const char *fmt,
+ va_list args);
+/*
+ * Registers a new log buffer entry.
+ * param name: name of the file in the /d/logbuffer/ directory.
+ * returns the pointer to the logbuffer metadata.
+ */
+struct logbuffer *debugfs_logbuffer_register(char *name);
+
+void debugfs_logbuffer_unregister(struct logbuffer *instance);
+#endif /* __GOOGLE_LOGBUFFER_H_ */
+
diff --git a/max1720x.h b/max1720x.h
new file mode 100644
index 0000000..76e9a3b
--- /dev/null
+++ b/max1720x.h
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2019 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.
+ */
+
+
+#ifndef MAX1720X_H_
+#define MAX1720X_H_
+
+#include "max1720x_battery.h"
+
+#define MAX1720X_N_OF_HISTORY_PAGES 203
+
+
+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_ALARM = 0xFD,
+ 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, /* reserved for QH capacity */
+ MAX1720X_NUSER18D = 0x8D, /* reserved for QH QH */
+ MAX1720X_NODSCTH = 0x8E, /* CCLC */
+ MAX1720X_NODSCCFG = 0x8F, /* CCLC */
+ MAX1720X_NLEARNCFG = 0x9F, /* not referred here */
+ MAX1720X_NMISCCFG = 0xB2, /* CCLC */
+ MAX1720X_NHIBCFG = 0xB4, /* CCLC */
+ MAX1720X_NCONVGCFG = 0xB7, /* convergence configuration */
+ MAX1720X_NNVCFG0 = 0xB8, /* 'NCG0' with NCG1 */
+ MAX1720X_NUSER1C4 = 0xC4, /* CCLC */
+ MAX1720X_NUSER1C5 = 0xC5, /* CCLC */
+ MAX1720X_NTTFCFG = 0xC7, /* Average resistance */
+ MAX1720X_NCGAIN = 0xC8, /* .... */
+ MAX1720X_NMANFCTRNAME0 = 0xCC, /* SNUM */
+ MAX1720X_NMANFCTRNAME1 = 0xCD, /* CCLC */
+ MAX1720X_NMANFCTRNAME2 = 0xCE, /* CCLC */
+ MAX1720X_NRSENSE = 0xCF, /* value of sense resistor */
+ MAX1720X_NUSER1D0 = 0xD0, /* SNUM */
+ MAX1720X_NUSER1D1 = 0xD1, /* SNUM */
+ MAX1720X_NAGEFCCFG = 0xD2,
+ MAX1720X_NUSER1D4 = 0xD4, /* URST */
+ MAX1720X_NMANFCTRDATE = 0xD6, /* SNUM */
+ MAX1720X_NFIRSTUSED = 0xD7, /* CCLC */
+ MAX1720X_NSERIALNUMBER0 = 0xD8, /* SNUM */
+ MAX1720X_NSERIALNUMBER1 = 0xD9, /* SNUM */
+ MAX1720X_NSERIALNUMBER2 = 0xDA, /* SNUM */
+ MAX1720X_NDEVICENAME0 = 0xDB, /* SNUM */
+ MAX1720X_NDEVICENAME1 = 0xDC, /* SNUM */
+ MAX1720X_NDEVICENAME2 = 0xDD, /* SNUM */
+ MAX1720X_NDEVICENAME3 = 0xDE, /* SNUM */
+ MAX1720X_NDEVICENAME4 = 0xDF, /* CCLC */
+ MAX1720X_NVRAM_END = MAX1720X_NDEVICENAME4,
+
+ MAX1720X_HISTORY_START = 0xE0,
+ MAX1720X_NVRAM_HISTORY_WRITE_STATUS_START = 0xE1,
+ MAX1720X_NVRAM_HISTORY_VALID_STATUS_END = 0xE4,
+ MAX1720X_NVRAM_HISTORY_WRITE_STATUS_END = 0xEA,
+ MAX1720X_NVRAM_HISTORY_VALID_STATUS_START = 0xEB,
+ MAX1720X_NVRAM_REMAINING_UPDATES = 0xED,
+ MAX1720X_NVRAM_HISTORY_END = 0xEF,
+};
+
+#define MAX1720X_HISTORY_PAGE_SIZE \
+ (MAX1720X_NVRAM_HISTORY_END - MAX1720X_HISTORY_START + 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_HISTORY_START + 1)
+
+#define MAX1720X_N_OF_QRTABLES 4
+
+/** ------------------------------------------------------------------------ */
+
+static 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 MAX1720X_ALARM:
+ 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,
+};
+
+static bool max1720x_is_nvram_reg(struct device *dev, unsigned int reg)
+{
+ return (reg >= MAX1720X_NVRAM_START &&
+ reg <= MAX1720X_NVRAM_HISTORY_END);
+}
+
+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 const struct max17x0x_reg max1720x[] = {
+ [MAX17X0X_TAG_avgc] = { ATOM_INIT_REG16(MAX1720X_AVGCURRENT)},
+ [MAX17X0X_TAG_cnfg] = { ATOM_INIT_REG16(MAX1720X_CONFIG)},
+ [MAX17X0X_TAG_mmdv] = { ATOM_INIT_REG16(MAX1720X_MAXMINVOLT)},
+ [MAX17X0X_TAG_vcel] = { ATOM_INIT_REG16(MAX1720X_VCELL)},
+ [MAX17X0X_TAG_temp] = { ATOM_INIT_REG16(MAX1720X_TEMP)},
+ [MAX17X0X_TAG_curr] = { ATOM_INIT_REG16(MAX1720X_CURRENT)},
+ [MAX17X0X_TAG_mcap] = { ATOM_INIT_REG16(MAX1720X_MIXCAP)},
+ [MAX17X0X_TAG_avgr] = { ATOM_INIT_REG16(MAX1720X_NTTFCFG)},
+ [MAX17X0X_TAG_vfsoc] = { ATOM_INIT_REG16(MAX1720X_VFSOC)},
+ [MAX17X0X_TAG_vfocv] = { ATOM_INIT_REG16(MAX1720X_VFOCV)},
+
+ [MAX17X0X_TAG_BCNT] = { ATOM_INIT_MAP(0x8e, 0x8f, 0xb2, 0xb4, 0xc4,
+ 0xc5, 0xcd, 0xce, 0xd7, 0xdf) },
+ [MAX17X0X_TAG_SNUM] = { ATOM_INIT_MAP(0xcc, 0xd8, 0xd9, 0xda, 0xd6,
+ 0xdb, 0xdc, 0xdd, 0xde, 0xd1,
+ 0xd0) },
+
+ [MAX17X0X_TAG_HSTY] = { ATOM_INIT_SET(0xe0, 0xe1, 0xe4, 0xea, 0xeb,
+ 0xed, 0xef) },
+ [MAX17X0X_TAG_BCEA] = { ATOM_INIT_SET(MAX1720X_NUSER1D4,
+ MAX1720X_NAGEFCCFG,
+ MAX1720X_NMISCCFG) },
+ [MAX17X0X_TAG_rset] = { ATOM_INIT_SET16(MAX1720X_CONFIG2,
+ MAX1720X_COMMAND_FUEL_GAUGE_RESET,
+ 700)},
+ [MAX17X0X_TAG_BRES] = { ATOM_INIT_SET(MAX1720X_NTTFCFG,
+ MAX1720X_NUSER1D4) },
+};
+
+
+#endif
\ No newline at end of file
diff --git a/max1720x_battery.c b/max1720x_battery.c
new file mode 100644
index 0000000..ad85039
--- /dev/null
+++ b/max1720x_battery.c
@@ -0,0 +1,4186 @@
+/*
+ * 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/of_gpio.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/seq_file.h> /* seq_read, seq_lseek, single_release */
+#include <google/google_bms.h>
+#include <google/logbuffer.h>
+#include "google/max1720x_battery.h"
+
+#include <linux/debugfs.h>
+
+#define MAX17X0X_TPOR_MS 150
+
+#define MAX1720X_TRECALL_MS 5
+#define MAX1730X_TRECALL_MS 5
+#define MAX1720X_TICLR_MS 500
+#define MAX1720X_I2C_DRIVER_NAME "max_fg_irq"
+#define MAX1720X_DELAY_INIT_MS 1000
+#define FULLCAPNOM_STABILIZE_CYCLES 5
+#define CYCLE_BUCKET_SIZE 200
+#define TEMP_BUCKET_SIZE 5 /* unit is 0.1 degree C */
+#define NB_CYCLE_BUCKETS 4
+
+#define HISTORY_DEVICENAME "maxfg_history"
+
+#include "google/max1720x.h"
+#include "google/max1730x.h"
+#include "google/max_m5.h"
+
+enum max17xxx_register {
+ MAX17XXX_COMMAND = MAX1720X_COMMAND,
+};
+
+enum max17xxx_nvram {
+ MAX17XXX_QHCA = MAX1720X_NUSER18C,
+ MAX17XXX_QHQH = MAX1720X_NUSER18D,
+};
+
+enum max17xxx_command_bits {
+ MAX17XXX_COMMAND_NV_RECALL = 0xE001,
+};
+
+/* Capacity Estimation */
+struct gbatt_capacity_estimation {
+ const struct max17x0x_reg *bcea;
+ struct mutex batt_ce_lock;
+ struct delayed_work settle_timer;
+ int cap_tsettle;
+ int cap_filt_length;
+ int estimate_state;
+ bool cable_in;
+ int delta_cc_sum;
+ int delta_vfsoc_sum;
+ int cap_filter_count;
+ int start_cc;
+ int start_vfsoc;
+};
+
+#define DEFAULT_BATTERY_ID -1
+#define DEFAULT_BATTERY_ID_RETRIES 5
+
+#define DEFAULT_CAP_SETTLE_INTERVAL 3
+#define DEFAULT_CAP_FILTER_LENGTH 12
+
+#define ESTIMATE_DONE 2
+#define ESTIMATE_PENDING 1
+#define ESTIMATE_NONE 0
+
+#define CE_CAP_FILTER_COUNT 0
+#define CE_DELTA_CC_SUM_REG 1
+#define CE_DELTA_VFSOC_SUM_REG 2
+
+#define CE_FILTER_COUNT_MAX 15
+
+
+struct max1720x_history {
+ int page_size;
+
+ loff_t history_index;
+ int history_count;
+ bool *page_status;
+ u16 *history;
+};
+
+
+struct max1720x_chip {
+ struct device *dev;
+ bool irq_shared;
+ struct i2c_client *primary;
+ struct i2c_client *secondary;
+
+ int gauge_type; /* -1 not present, 0=max1720x, 1=max1730x */
+ struct max17x0x_regmap regmap;
+ struct max17x0x_regmap regmap_nvram;
+
+ struct power_supply *psy;
+ struct delayed_work init_work;
+ struct device_node *batt_node;
+
+ u16 devname;
+ struct max17x0x_cache_data nRAM_por;
+ bool needs_reset;
+ int (*fixups_fn)(struct max1720x_chip *chip);
+
+ /* config */
+ void *model_data;
+ struct mutex model_lock;
+ struct delayed_work model_work;
+ long model_next_update;
+ /* also used to restore model state from permanent storage */
+ u16 reg_prop_capacity_raw;
+ int model_reload;
+
+ /* history */
+ struct mutex history_lock;
+ int hcmajor;
+ struct cdev hcdev;
+ struct class *hcclass;
+ bool history_available;
+ bool history_added;
+ int history_page_size;
+ int nb_history_pages;
+ int nb_history_flag_reg;
+
+ /* for storage interface */
+ struct max1720x_history history_storage;
+
+ u16 RSense;
+ u16 RConfig;
+
+ int batt_id;
+ int batt_id_defer_cnt;
+ int cycle_count;
+
+ 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;
+ s32 convgcfg_hysteresis;
+ int nb_convgcfg;
+ int curr_convgcfg_idx;
+ s16 *temp_convgcfg;
+ u16 *convgcfg_values;
+ struct mutex convgcfg_lock;
+ bool shadow_override;
+ int nb_empty_voltage;
+ u16 *empty_voltage;
+
+ unsigned int debug_irq_none_cnt;
+ unsigned long icnt;
+ int zero_irq;
+
+
+ /* Capacity Estimation */
+ struct gbatt_capacity_estimation cap_estimate;
+ struct logbuffer *ce_log;
+};
+
+#define MAX1720_EMPTY_VOLTAGE(profile, temp, cycle) \
+ profile->empty_voltage[temp * NB_CYCLE_BUCKETS + cycle]
+
+
+static bool max17x0x_reglog_init(struct max1720x_chip *chip)
+{
+ chip->regmap.reglog =
+ devm_kzalloc(chip->dev, sizeof(*chip->regmap.reglog),
+ GFP_KERNEL);
+ chip->regmap_nvram.reglog =
+ devm_kzalloc(chip->dev, sizeof(*chip->regmap.reglog),
+ GFP_KERNEL);
+
+ return chip->regmap.reglog && chip->regmap_nvram.reglog;
+}
+
+/* ------------------------------------------------------------------------- */
+
+
+/* TODO: split between NV and Volatile? */
+
+
+const struct max17x0x_reg *max17x0x_find_by_index(struct max17x0x_regtags *tags,
+ int index)
+{
+ if (index < 0 || !tags || index >= tags->max)
+ return NULL;
+
+ return &tags->map[index];
+}
+
+const struct max17x0x_reg *max17x0x_find_by_tag(struct max17x0x_regmap *map,
+ enum max17x0x_reg_tags tag)
+{
+ return max17x0x_find_by_index(&map->regtags, tag);
+}
+
+static inline int max17x0x_reg_read(struct max17x0x_regmap *map,
+ enum max17x0x_reg_tags tag,
+ u16 *val)
+{
+ const struct max17x0x_reg *reg;
+ unsigned int tmp;
+ int rtn;
+
+ reg = max17x0x_find_by_tag(map, tag);
+ if (!reg)
+ return -EINVAL;
+
+ rtn = regmap_read(map->regmap, reg->reg, &tmp);
+ if (rtn)
+ pr_err("Failed to read %x\n", reg->reg);
+ else
+ *val = tmp;
+
+ return rtn;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * offset of the register in this atom.
+ * NOTE: this is the byte offset regardless of the size of the register
+ */
+static int max17x0x_reg_offset_of(const struct max17x0x_reg *a,
+ unsigned int reg)
+{
+ int i;
+
+ switch (a->type) {
+ case GBMS_ATOM_TYPE_REG:
+ return (reg == a->reg) ? 0 : -EINVAL;
+ case GBMS_ATOM_TYPE_ZONE:
+ if (reg >= a->base && reg < a->base + a->size)
+ return (reg - a->base) * 2;
+ break;
+ case GBMS_ATOM_TYPE_MAP:
+ for (i = 0 ; i < a->size ; i++)
+ if (a->map[i] == reg)
+ return i * 2;
+ break;
+ }
+
+ return -ERANGE;
+}
+
+static int max17x0x_reg_store_sz(struct max17x0x_regmap *map,
+ const struct max17x0x_reg *a,
+ const void *data,
+ int size)
+{
+ int i, ret;
+
+ if (size > a->size)
+ size = a->size;
+
+ if (a->type == GBMS_ATOM_TYPE_MAP) {
+ const u16 *b = (u16 *)data;
+
+ if (size % 2)
+ return -ERANGE;
+
+ for (i = 0; i < size / 2 ; i++) {
+ ret = regmap_write(map->regmap, a->map[i], b[i]);
+ if (ret < 0)
+ break;
+
+ max17x0x_reglog_log(map->reglog, a->map[i], b[i], ret);
+ }
+ } else if (a->type == GBMS_ATOM_TYPE_SET) {
+ ret = -EINVAL;
+ } else {
+ ret = regmap_raw_write(map->regmap, a->base, data, size);
+
+ if (map->reglog) {
+ const u16 *b = (u16 *)data;
+
+ for (i = 0; i < size ; i += 2)
+ max17x0x_reglog_log(map->reglog, a->base + i,
+ b[i], ret);
+ }
+ }
+
+ return ret;
+}
+
+static int max17x0x_reg_load_sz(struct max17x0x_regmap *map,
+ const struct max17x0x_reg *a,
+ void *data,
+ int size)
+{
+ int ret;
+
+ if (size > a->size)
+ size = a->size;
+
+ if (a->type == GBMS_ATOM_TYPE_MAP) {
+ int i;
+ unsigned int tmp;
+ u16 *b = (u16 *)data;
+
+ if (size % 2)
+ return -ERANGE;
+
+ for (i = 0; i < size / 2 ; i++) {
+ ret = regmap_read(map->regmap,
+ (unsigned int)a->map[i],
+ &tmp);
+ if (ret < 0)
+ break;
+ b[i] = tmp;
+ }
+ } else if (a->type == GBMS_ATOM_TYPE_SET) {
+ ret = -EINVAL;
+ } else {
+ ret = regmap_raw_read(map->regmap, a->base, data, size);
+ }
+
+ return ret;
+}
+
+#define max17x0x_reg_store(map, a, data) \
+ max17x0x_reg_store_sz(map, a, data, (a)->size)
+
+#define max17x0x_reg_load(map, a, data) \
+ max17x0x_reg_load_sz(map, a, data, (a)->size)
+
+
+static u16 *batt_alloc_array(int count, int size)
+{
+ return (u16 *)kmalloc_array(count, size, GFP_KERNEL);
+}
+
+/* CACHE ----------------------------------------------------------------- */
+
+static int max17x0x_cache_index_of(const struct max17x0x_cache_data *cache,
+ unsigned int reg)
+{
+ const int offset = max17x0x_reg_offset_of(&cache->atom, reg);
+
+ return (offset < 0) ? offset : offset / 2;
+}
+
+#define max17x0x_cache_store(cache, regmap) \
+ max17x0x_reg_store(regmap, &(cache)->atom, (cache)->cache_data)
+
+#define max17x0x_cache_load(cache, regmap) \
+ max17x0x_reg_load(regmap, &(cache)->atom, (cache)->cache_data)
+
+#define max17x0x_cache_memcmp(src, dst) \
+ memcmp((src)->cache_data, (dst)->cache_data, (src)->atom.size)
+
+static void max17x0x_cache_free(struct max17x0x_cache_data *cache)
+{
+ kfree(cache->cache_data);
+ cache->cache_data = NULL;
+}
+
+static int max17x0x_cache_dup(struct max17x0x_cache_data *dst,
+ const struct max17x0x_cache_data *src)
+{
+ memcpy(dst, src, sizeof(*dst));
+
+ dst->cache_data = (u16 *)kmalloc(src->atom.size, GFP_KERNEL);
+ if (!dst->cache_data)
+ return -ENOMEM;
+
+ memcpy(dst->cache_data, src->cache_data, src->atom.size);
+ return 0;
+}
+
+static int max17x0x_cache_init(struct max17x0x_cache_data *cache,
+ u16 start, int end)
+{
+ const int count = end - start + 1; /* includes end */
+
+ memset(cache, 0, sizeof(*cache));
+
+ cache->cache_data = batt_alloc_array(count, sizeof(u16));
+ if (!cache->cache_data)
+ return -ENOMEM;
+
+ cache->atom.type = GBMS_ATOM_TYPE_ZONE;
+ cache->atom.size = count * sizeof(u16);
+ cache->atom.base = start;
+
+ return 0;
+}
+
+static int max17x0x_nvram_cache_init(struct max17x0x_cache_data *cache,
+ int gauge_type)
+{
+ int ret;
+
+ if (gauge_type == MAX1730X_GAUGE_TYPE) {
+ ret = max17x0x_cache_init(cache,
+ MAX1730X_NVRAM_START,
+ MAX1730X_NVRAM_END);
+ } else {
+ ret = max17x0x_cache_init(cache,
+ MAX1720X_NVRAM_START,
+ MAX1720X_NVRAM_END);
+ }
+
+ return ret;
+}
+
+/* ------------------------------------------------------------------------- */
+
+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(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 inline int reg_to_vempty(u16 val)
+{
+ return ((val >> 7) & 0x1FF) * 10;
+}
+
+static inline int reg_to_vrecovery(u16 val)
+{
+ return (val & 0x7F) * 40;
+}
+
+static void max1730x_read_log_write_status(struct max1720x_chip *chip,
+ u16 *buffer)
+{
+ u16 i;
+ u16 data = 0;
+ const struct max17x0x_reg *hsty;
+
+ hsty = max17x0x_find_by_tag(&chip->regmap_nvram, MAX17X0X_TAG_HSTY);
+ if (!hsty)
+ return;
+
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ MAX1730X_COMMAND_HISTORY_RECALL_WRITE_0);
+ msleep(MAX1730X_TRECALL_MS);
+ for (i = hsty->map[1]; i <= hsty->map[3]; i++) {
+ (void)REGMAP_READ(&chip->regmap_nvram, i, &data);
+ *buffer++ = data;
+ }
+}
+
+static void max1730x_read_log_valid_status(struct max1720x_chip *chip,
+ u16 *buffer)
+{
+ u16 i;
+ u16 data = 0;
+ const struct max17x0x_reg *hsty;
+
+ hsty = max17x0x_find_by_tag(&chip->regmap_nvram, MAX17X0X_TAG_HSTY);
+
+ if (!hsty)
+ return;
+
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ MAX1730X_COMMAND_HISTORY_RECALL_VALID_0);
+ msleep(MAX1730X_TRECALL_MS);
+ (void)REGMAP_READ(&chip->regmap_nvram, hsty->map[4], &data);
+ *buffer++ = data;
+
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ MAX1730X_COMMAND_HISTORY_RECALL_VALID_1);
+ msleep(MAX1730X_TRECALL_MS);
+ for (i = hsty->map[0]; i <= hsty->map[2]; i++) {
+ (void)REGMAP_READ(&chip->regmap_nvram, i, &data);
+ *buffer++ = data;
+ }
+}
+
+static void max1720x_read_log_write_status(struct max1720x_chip *chip,
+ u16 *buffer)
+{
+ int i;
+ u16 data = 0;
+
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_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, MAX17XXX_COMMAND,
+ MAX1720X_COMMAND_HISTORY_RECALL_WRITE_1);
+ msleep(MAX1720X_TRECALL_MS);
+ for (i = MAX1720X_HISTORY_START;
+ 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, MAX17XXX_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, MAX17XXX_COMMAND,
+ MAX1720X_COMMAND_HISTORY_RECALL_VALID_1);
+ msleep(MAX1720X_TRECALL_MS);
+ for (i = MAX1720X_HISTORY_START;
+ i <= MAX1720X_NVRAM_HISTORY_END; i++) {
+ (void)REGMAP_READ(&chip->regmap_nvram, i, &data);
+ *buffer++ = data;
+ }
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ MAX1720X_COMMAND_HISTORY_RECALL_VALID_2);
+ msleep(MAX1720X_TRECALL_MS);
+ for (i = MAX1720X_HISTORY_START;
+ 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, nb_history_pages;
+ int valid_history_entry_count = 0;
+
+ write_status = batt_alloc_array(chip->nb_history_flag_reg, sizeof(u16));
+ if (!write_status)
+ return -ENOMEM;
+
+ valid_status = batt_alloc_array(chip->nb_history_flag_reg, sizeof(u16));
+ if (!valid_status) {
+ kfree(write_status);
+ return -ENOMEM;
+ }
+
+ if (chip->gauge_type == MAX1730X_GAUGE_TYPE) {
+ max1730x_read_log_write_status(chip, write_status);
+ max1730x_read_log_valid_status(chip, valid_status);
+ nb_history_pages = MAX1730X_N_OF_HISTORY_PAGES;
+ } else {
+ max1720x_read_log_write_status(chip, write_status);
+ max1720x_read_log_valid_status(chip, valid_status);
+ nb_history_pages = MAX1720X_N_OF_HISTORY_PAGES;
+ }
+
+ /* Figure out the pages with valid history entry */
+ for (i = 0; i < nb_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;
+ const struct max17x0x_reg *hsty;
+ u16 command_base = (chip->gauge_type == MAX1730X_GAUGE_TYPE)
+ ? MAX1730X_READ_HISTORY_CMD_BASE
+ : MAX1720X_READ_HISTORY_CMD_BASE;
+
+ hsty = max17x0x_find_by_tag(&chip->regmap_nvram, MAX17X0X_TAG_HSTY);
+ if (!hsty)
+ return;
+
+ for (i = 0; i < chip->nb_history_pages; i++) {
+ if (!page_status[i])
+ continue;
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ command_base + i);
+ msleep(MAX1720X_TRECALL_MS);
+ for (j = 0; j < chip->history_page_size; j++) {
+ (void)REGMAP_READ(&chip->regmap_nvram,
+ (unsigned int)hsty->map[0] + j,
+ &data);
+ history[index * chip->history_page_size + j] = data;
+ }
+ index++;
+ }
+}
+
+static int format_battery_history_entry(char *temp, int size,
+ int page_size, u16 *line)
+{
+ int length = 0, i;
+
+ for (i = 0; i < 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(chip->nb_history_pages,
+ sizeof(bool), GFP_KERNEL);
+ if (!hi->page_status)
+ return -ENOMEM;
+
+
+ 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 * chip->history_page_size;
+
+ hi->history = batt_alloc_array(size, sizeof(u16));
+ if (!hi->history) {
+ hi->history_count = -ENOMEM;
+ goto error_exit;
+ }
+
+ get_battery_history(chip, hi->page_status, hi->history);
+ }
+
+ return hi->history_count;
+
+error_exit:
+ 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;
+ hi->history_count = -1;
+ hi->history_index = 0;
+}
+
+
+/*
+ * Removed the following properties:
+ * POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG
+ * POWER_SUPPLY_PROP_TIME_TO_FULL_AVG
+ * POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ * POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ * Need to keep the number of properies under UEVENT_NUM_ENVP (minus # of
+ * standard uevent variables).
+ */
+static enum power_supply_property max1720x_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY, /* replace with _RAW */
+ POWER_SUPPLY_PROP_CAPACITY_RAW,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, /* used from gbattery */
+ POWER_SUPPLY_PROP_CURRENT_AVG, /* candidate for tier switch */
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_RESISTANCE_ID,
+ POWER_SUPPLY_PROP_RESISTANCE,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_DELTA_CC_SUM,
+ POWER_SUPPLY_PROP_DELTA_VFSOC_SUM,
+ POWER_SUPPLY_PROP_CHARGE_FULL_ESTIMATE,
+ POWER_SUPPLY_PROP_RES_FILTER_COUNT,
+ POWER_SUPPLY_PROP_RESISTANCE_AVG, /* 24 */
+};
+
+/* ------------------------------------------------------------------------- */
+
+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 ssize_t max1720x_model_show_state(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);
+ ssize_t len = 0;
+
+ if (!chip->model_data)
+ return -EINVAL;
+
+ mutex_lock(&chip->model_lock);
+ len += scnprintf(&buf[len], PAGE_SIZE, "ModelNextUpdate: %ld\n",
+ chip->model_next_update);
+ len += max_m5_model_state_cstr(&buf[len], PAGE_SIZE - len,
+ chip->model_data);
+ mutex_unlock(&chip->model_lock);
+
+ return len;
+}
+
+static void max1720x_model_reload(struct max1720x_chip *chip, bool force)
+{
+ const bool pending = chip->model_reload == MAX_M5_LOAD_MODEL_REQUEST;
+ const bool disabled = chip->model_reload == MAX_M5_LOAD_MODEL_DISABLED;
+
+ pr_debug("model_reload=%d force=%d pending=%d disabled=%d\n",
+ chip->model_reload, force, pending, disabled);
+
+ if (!force && (pending || disabled))
+ return;
+
+ /* REQUEST -> IDLE or set to the number of retries */
+ dev_info(chip->dev, "Reload FG Model for ID=%d\n", chip->batt_id);
+ chip->model_reload = MAX_M5_LOAD_MODEL_REQUEST;
+ mod_delayed_work(system_wq, &chip->model_work, 0);
+}
+
+static ssize_t max1720x_model_set_state(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);
+ int ret;
+
+ if (!chip->model_data)
+ return -EINVAL;
+
+ mutex_lock(&chip->model_lock);
+
+ /* read current state from gauge */
+ ret = max_m5_model_read_state(chip->model_data);
+ if (ret < 0) {
+ mutex_unlock(&chip->model_lock);
+ return ret;
+ }
+
+ /* overwrite with userland, will commit at cycle count */
+ ret = max_m5_model_state_sscan(chip->model_data, buf, count);
+ if (ret == 0)
+ max1720x_model_reload(chip, true);
+
+ mutex_unlock(&chip->model_lock);
+ return count;
+}
+
+static DEVICE_ATTR(m5_model_state, 0640, max1720x_model_show_state,
+ max1720x_model_set_state);
+
+
+/* lsb 1/256, race with max1720x_model_work() */
+static int max1720x_get_capacity_raw(struct max1720x_chip *chip, u16 *data)
+{
+ return REGMAP_READ(&chip->regmap, chip->reg_prop_capacity_raw, data);
+}
+
+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 int max1720x_get_battery_vfsoc(struct max1720x_chip *chip)
+{
+ u16 data;
+ int capacity, err;
+
+
+ err = max17x0x_reg_read(&chip->regmap, MAX17X0X_TAG_vfsoc, &data);
+ if (err)
+ return err;
+ capacity = reg_to_percentage(data);
+
+ return capacity;
+}
+
+/* TODO: factor with the one in google_bms.c */
+static char *psy_status_str[] = {
+ "Unknown", "Charging", "Discharging", "NotCharging", "Full"
+};
+
+static void max1720x_prime_battery_qh_capacity(struct max1720x_chip *chip,
+ int status)
+{
+ u16 mcap = 0, data = 0;
+
+ (void)max17x0x_reg_read(&chip->regmap, MAX17X0X_TAG_mcap, &mcap);
+ chip->current_capacity = mcap;
+
+ (void)REGMAP_READ(&chip->regmap, MAX1720X_QH, &data);
+ chip->previous_qh = reg_to_twos_comp_int(data);
+
+ if (chip->regmap_nvram.regmap) {
+ REGMAP_WRITE(&chip->regmap_nvram, MAX17XXX_QHCA, ~mcap);
+ dev_info(chip->dev, "Capacity primed to %d on %s\n",
+ mcap, psy_status_str[status]);
+
+ REGMAP_WRITE(&chip->regmap_nvram, MAX17XXX_QHQH, data);
+ dev_info(chip->dev, "QH primed to %d on %s\n",
+ data, psy_status_str[status]);
+ }
+}
+
+/* NOTE: the gauge doesn't know if we are current limited to */
+static int max1720x_get_battery_status(struct max1720x_chip *chip)
+{
+ u16 data = 0;
+ int current_now, current_avg, ichgterm, vfsoc, soc, fullsocthr;
+ int status = POWER_SUPPLY_STATUS_UNKNOWN, err;
+
+ err = max17x0x_reg_read(&chip->regmap, MAX17X0X_TAG_curr, &data);
+ if (err)
+ return -EIO;
+ current_now = -reg_to_micro_amp(data, chip->RSense);
+
+ err = max17x0x_reg_read(&chip->regmap, MAX17X0X_TAG_avgc, &data);
+ if (err)
+ return -EIO;
+ current_avg = -reg_to_micro_amp(data, chip->RSense);
+
+ err = REGMAP_READ(&chip->regmap, MAX1720X_ICHGTERM, &data);
+ if (err)
+ return -EIO;
+ ichgterm = reg_to_micro_amp(data, chip->RSense);
+
+ err = REGMAP_READ(&chip->regmap, MAX1720X_FULLSOCTHR, &data);
+ if (err)
+ return -EIO;
+ fullsocthr = reg_to_percentage(data);
+
+ soc = max1720x_get_battery_soc(chip);
+ if (soc < 0)
+ return -EIO;
+
+ vfsoc = max1720x_get_battery_vfsoc(chip);
+ if (vfsoc < 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 (status != chip->prev_charge_status)
+ dev_info(chip->dev, "s=%d->%d c=%d avg_c=%d ichgt=%d vfsoc=%d soc=%d fullsocthr=%d\n",
+ chip->prev_charge_status,
+ status, current_now, current_avg,
+ ichgterm, vfsoc, 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_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)
+{
+ int ret;
+ int current_qh, nvram_qh;
+ u16 data = 0, nvram_capacity;
+
+ if (!chip->regmap_nvram.regmap) {
+ max1720x_prime_battery_qh_capacity(chip,
+ POWER_SUPPLY_STATUS_UNKNOWN);
+ return;
+ }
+
+
+ /* 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, MAX17XXX_QHCA, &data);
+ if (!ret && data == 0) {
+ max1720x_prime_battery_qh_capacity(chip,
+ POWER_SUPPLY_STATUS_UNKNOWN);
+ return;
+ }
+
+ nvram_capacity = ~data;
+
+ ret = REGMAP_READ(&chip->regmap_nvram, MAX17XXX_QHQH, &data);
+ if (ret == 0)
+ nvram_qh = reg_to_twos_comp_int(data);
+
+ ret = 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, hysteresis_temp;
+
+ 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
+ */
+ hysteresis_temp = chip->temp_convgcfg[chip->curr_convgcfg_idx] +
+ chip->convgcfg_hysteresis;
+ 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);
+}
+
+#define MAXIM_CYCLE_COUNT_RESET 655
+#define MAX17201_HIST_CYCLE_COUNT_OFFSET 0x4
+#define MAX17201_HIST_TIME_OFFSET 0xf
+
+/* WA for cycle count reset.
+ * max17201 fuel gauge rolls over the cycle count to 0 and burns
+ * an history entry with 0 cycles when the cycle count exceeds
+ * 655. This code workaround the issue adding 655 to the cycle
+ * count if the fuel gauge history has an entry with 0 cycles and
+ * non 0 time-in-field.
+ */
+static int max1720x_get_cycle_count_offset(const struct max1720x_chip *chip)
+{
+ int offset = 0;
+
+ return offset;
+}
+
+static int max1720x_get_cycle_count(struct max1720x_chip *chip)
+{
+ int err, cycle_count;
+ u16 temp;
+
+ err = REGMAP_READ(&chip->regmap, MAX1720X_CYCLES, &temp);
+ if (err < 0)
+ return err;
+
+ cycle_count = reg_to_cycles(temp);
+ if (chip->cycle_count == -1 || cycle_count < chip->cycle_count)
+ cycle_count += max1720x_get_cycle_count_offset(chip);
+
+ chip->cycle_count = cycle_count;
+ return cycle_count;
+}
+
+static void max1720x_handle_update_empty_voltage(struct max1720x_chip *chip,
+ int temp)
+{
+ int cycle, cycle_idx, temp_idx, chg_st, ret = 0;
+ u16 empty_volt_cfg, reg, vempty = 0;
+
+ if (chip->empty_voltage == NULL)
+ return;
+
+ chg_st = max1720x_get_battery_status(chip);
+ if (chg_st < 0)
+ return;
+
+ cycle = max1720x_get_cycle_count(chip);
+ if (cycle < 0)
+ return;
+
+ ret = REGMAP_READ(&chip->regmap, MAX1720X_VEMPTY, &vempty);
+ if (ret < 0)
+ return;
+
+ cycle_idx = cycle / CYCLE_BUCKET_SIZE;
+ if (cycle_idx > (NB_CYCLE_BUCKETS - 1))
+ cycle_idx = NB_CYCLE_BUCKETS - 1;
+
+ if (temp < 0) {
+ temp_idx = 0;
+ } else {
+ const int idx = temp / TEMP_BUCKET_SIZE + 1;
+ const int temp_buckets = chip->nb_empty_voltage /
+ NB_CYCLE_BUCKETS;
+
+ temp_idx = idx < (temp_buckets - 1) ? idx : (temp_buckets - 1);
+ }
+
+ empty_volt_cfg = MAX1720_EMPTY_VOLTAGE(chip, temp_idx, cycle_idx);
+ reg = (empty_volt_cfg / 10) << 7 | (vempty & 0x7F);
+ if ((reg > vempty) ||
+ (reg < vempty && chg_st != POWER_SUPPLY_STATUS_DISCHARGING)) {
+ REGMAP_WRITE(&chip->regmap, MAX1720X_VEMPTY, reg);
+
+ pr_debug("updating empty_voltage to %d(0x%04X), temp:%d(%d), cycle:%d(%d)\n",
+ empty_volt_cfg, reg,
+ temp, temp_idx,
+ cycle, cycle_idx);
+ }
+}
+
+/* Capacity Estimation functions*/
+static int batt_ce_regmap_read(struct max17x0x_regmap *map,
+ const struct max17x0x_reg *bcea,
+ u32 reg, u16 *data)
+{
+ int err;
+ u16 val;
+
+ if (!bcea)
+ return -EINVAL;
+
+ err = REGMAP_READ(map, bcea->map[reg], &val);
+ if (err)
+ return err;
+
+ switch(reg) {
+ case CE_DELTA_CC_SUM_REG:
+ case CE_DELTA_VFSOC_SUM_REG:
+ *data = val;
+ break;
+ case CE_CAP_FILTER_COUNT:
+ val = val & 0x0F00;
+ *data = val >> 8;
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static int batt_ce_regmap_write(struct max17x0x_regmap *map,
+ const struct max17x0x_reg *bcea,
+ u32 reg, u16 data)
+{
+ int err = -EINVAL;
+ u16 val;
+
+ if (!bcea)
+ return -EINVAL;
+
+ switch(reg) {
+ case CE_DELTA_CC_SUM_REG:
+ case CE_DELTA_VFSOC_SUM_REG:
+ err = REGMAP_WRITE(map, bcea->map[reg], data);
+ break;
+ case CE_CAP_FILTER_COUNT:
+ err = REGMAP_READ(map, bcea->map[reg], &val);
+ if (err)
+ return err;
+ val = val & 0xF0FF;
+ if (data > CE_FILTER_COUNT_MAX)
+ val = val | 0x0F00;
+ else
+ val = val | (data << 8);
+ err = REGMAP_WRITE(map, bcea->map[reg], val);
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static void batt_ce_dump_data(const struct gbatt_capacity_estimation *cap_esti,
+ struct logbuffer *log)
+{
+ logbuffer_log(log, "cap_filter_count: %d"
+ " start_cc: %d"
+ " start_vfsoc: %d"
+ " delta_cc_sum: %d"
+ " delta_vfsoc_sum: %d"
+ " state: %d"
+ " cable: %d\n",
+ cap_esti->cap_filter_count,
+ cap_esti->start_cc,
+ cap_esti->start_vfsoc,
+ cap_esti->delta_cc_sum,
+ cap_esti->delta_vfsoc_sum,
+ cap_esti->estimate_state,
+ cap_esti->cable_in);
+}
+
+static int batt_ce_load_data(struct max17x0x_regmap *map,
+ struct gbatt_capacity_estimation *cap_esti)
+{
+ u16 data;
+ const struct max17x0x_reg *bcea = cap_esti->bcea;
+
+ cap_esti->estimate_state = ESTIMATE_NONE;
+ if (batt_ce_regmap_read(map, bcea, CE_DELTA_CC_SUM_REG, &data) == 0)
+ cap_esti->delta_cc_sum = data;
+ else
+ cap_esti->delta_cc_sum = 0;
+
+ if (batt_ce_regmap_read(map, bcea, CE_DELTA_VFSOC_SUM_REG, &data) == 0)
+ cap_esti->delta_vfsoc_sum = data;
+ else
+ cap_esti->delta_vfsoc_sum = 0;
+
+ if (batt_ce_regmap_read(map, bcea, CE_CAP_FILTER_COUNT, &data) == 0)
+ cap_esti->cap_filter_count = data;
+ else
+ cap_esti->cap_filter_count = 0;
+ return 0;
+}
+
+/* call holding &cap_esti->batt_ce_lock */
+static void batt_ce_store_data(struct max17x0x_regmap *map,
+ struct gbatt_capacity_estimation *cap_esti)
+{
+ if (cap_esti->cap_filter_count <= CE_FILTER_COUNT_MAX) {
+ batt_ce_regmap_write(map, cap_esti->bcea,
+ CE_CAP_FILTER_COUNT,
+ cap_esti->cap_filter_count);
+ }
+
+ batt_ce_regmap_write(map, cap_esti->bcea,
+ CE_DELTA_VFSOC_SUM_REG,
+ cap_esti->delta_vfsoc_sum);
+ batt_ce_regmap_write(map, cap_esti->bcea,
+ CE_DELTA_CC_SUM_REG,
+ cap_esti->delta_cc_sum);
+}
+
+/* call holding &cap_esti->batt_ce_lock */
+static void batt_ce_stop_estimation(struct gbatt_capacity_estimation *cap_esti,
+ int reason)
+{
+ cap_esti->estimate_state = reason;
+ cap_esti->start_vfsoc = 0;
+ cap_esti->start_cc = 0;
+}
+
+static int batt_ce_full_estimate(struct gbatt_capacity_estimation *ce)
+{
+ return (ce->cap_filter_count > 0) && (ce->delta_vfsoc_sum > 0) ?
+ ce->delta_cc_sum / ce->delta_vfsoc_sum : -1;
+}
+
+/* Measure the deltaCC, deltaVFSOC and CapacityFiltered */
+static void batt_ce_capacityfiltered_work(struct work_struct *work)
+{
+ struct max1720x_chip *chip = container_of(work, struct max1720x_chip,
+ cap_estimate.settle_timer.work);
+ struct gbatt_capacity_estimation *cap_esti = &chip->cap_estimate;
+ int settle_cc = 0, settle_vfsoc = 0;
+ int delta_cc = 0, delta_vfsoc = 0;
+ int cc_sum = 0, vfsoc_sum = 0;
+ bool valid_estimate = false;
+ int rc = 0;
+ u16 data;
+
+ mutex_lock(&cap_esti->batt_ce_lock);
+
+ /* race with disconnect */
+ if (!cap_esti->cable_in ||
+ cap_esti->estimate_state != ESTIMATE_PENDING) {
+ goto exit;
+ }
+
+ rc = max1720x_update_battery_qh_based_capacity(chip);
+ if (rc < 0)
+ goto ioerr;
+
+ settle_cc = reg_to_micro_amp_h(chip->current_capacity, chip->RSense);
+
+ data = max1720x_get_battery_vfsoc(chip);
+ if (data < 0)
+ goto ioerr;
+
+ settle_vfsoc = data;
+ settle_cc = settle_cc / 1000;
+ delta_cc = settle_cc - cap_esti->start_cc;
+ delta_vfsoc = settle_vfsoc - cap_esti->start_vfsoc;
+
+ if ((delta_cc > 0) && (delta_vfsoc > 0)) {
+
+ cc_sum = delta_cc + cap_esti->delta_cc_sum;
+ vfsoc_sum = delta_vfsoc + cap_esti->delta_vfsoc_sum;
+
+ if (cap_esti->cap_filter_count >= cap_esti->cap_filt_length) {
+ const int filter_divisor = cap_esti->cap_filt_length;
+
+ cc_sum -= cap_esti->delta_cc_sum/filter_divisor;
+ vfsoc_sum -= cap_esti->delta_vfsoc_sum/filter_divisor;
+ }
+
+ cap_esti->cap_filter_count++;
+ cap_esti->delta_cc_sum = cc_sum;
+ cap_esti->delta_vfsoc_sum = vfsoc_sum;
+ batt_ce_store_data(&chip->regmap_nvram, &chip->cap_estimate);
+
+ valid_estimate = true;
+ }
+
+ioerr:
+ batt_ce_stop_estimation(cap_esti, ESTIMATE_DONE);
+
+exit:
+ logbuffer_log(chip->ce_log,
+ "valid=%d settle[cc=%d, vfsoc=%d], delta[cc=%d,vfsoc=%d] ce[%d]=%d\n",
+ valid_estimate,
+ settle_cc, settle_vfsoc, delta_cc, delta_vfsoc,
+ cap_esti->cap_filter_count,
+ batt_ce_full_estimate(cap_esti));
+
+ mutex_unlock(&cap_esti->batt_ce_lock);
+
+ /* force to update uevent to framework side. */
+ if (valid_estimate)
+ power_supply_changed(chip->psy);
+}
+
+/*
+ * batt_ce_init(): estimate_state = ESTIMATE_NONE
+ * batt_ce_start(): estimate_state = ESTIMATE_NONE -> ESTIMATE_PENDING
+ * batt_ce_capacityfiltered_work(): ESTIMATE_PENDING->ESTIMATE_DONE
+ */
+static int batt_ce_start(struct gbatt_capacity_estimation *cap_esti,
+ int cap_tsettle_ms)
+{
+ mutex_lock(&cap_esti->batt_ce_lock);
+
+ /* Still has cable and estimate is not pending or cancelled */
+ if (!cap_esti->cable_in || cap_esti->estimate_state != ESTIMATE_NONE)
+ goto done;
+
+ pr_info("EOC: Start the settle timer\n");
+ cap_esti->estimate_state = ESTIMATE_PENDING;
+ schedule_delayed_work(&cap_esti->settle_timer,
+ msecs_to_jiffies(cap_tsettle_ms));
+
+done:
+ mutex_unlock(&cap_esti->batt_ce_lock);
+ return 0;
+}
+
+static int batt_ce_init(struct gbatt_capacity_estimation *cap_esti,
+ struct max1720x_chip *chip)
+{
+ int rc;
+ u16 vfsoc = 0;
+
+ rc = max1720x_update_battery_qh_based_capacity(chip);
+ if (rc < 0)
+ return -EIO;
+
+ vfsoc = max1720x_get_battery_vfsoc(chip);
+ if (vfsoc < 0)
+ return -EIO;
+
+ cap_esti->start_vfsoc = vfsoc;
+ cap_esti->start_cc = reg_to_micro_amp_h(chip->current_capacity,
+ chip->RSense) / 1000;
+ /* Capacity Estimation starts only when the state is NONE */
+ cap_esti->estimate_state = ESTIMATE_NONE;
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+#define SEL_RES_AVG 0
+#define SEL_RES_FILTER_COUNT 1
+static int batt_res_registers(struct max1720x_chip *chip, bool bread,
+ int isel, int *data)
+{
+ int err = -EINVAL;
+ const struct max17x0x_reg *bres;
+ u16 res_filtered, res_filt_count, val;
+
+ bres = max17x0x_find_by_tag(&chip->regmap_nvram, MAX17X0X_TAG_BRES);
+ if (!bres)
+ return err;
+
+ switch (isel) {
+ case SEL_RES_AVG:
+ if (bread) {
+ err = REGMAP_READ(&chip->regmap_nvram, bres->map[0],
+ &res_filtered);
+ if (err)
+ return err;
+
+ *data = res_filtered;
+ return 0;
+ }
+ err = REGMAP_WRITE(&chip->regmap_nvram, bres->map[0], *data);
+ break;
+ case SEL_RES_FILTER_COUNT:
+ err = REGMAP_READ(&chip->regmap_nvram, bres->map[1], &val);
+ if (err)
+ return err;
+
+ if (bread) {
+ res_filt_count = (val & 0xF000) >> 12;
+ *data = res_filt_count;
+ return 0;
+ }
+ res_filt_count = (val & 0x0FFF) | (*data << 12);
+ err = REGMAP_WRITE(&chip->regmap_nvram, bres->map[1],
+ res_filt_count);
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static int max1720x_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)
+ power_supply_get_drvdata(psy);
+ struct max17x0x_regmap *map = &chip->regmap;
+ int rc, err = 0;
+ u16 data = 0;
+ int idata;
+
+ 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);
+
+ mutex_lock(&chip->model_lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ err = max1720x_get_battery_status(chip);
+ if (err < 0)
+ break;
+
+ /*
+ * Capacity estimation must run only once.
+ * NOTE: this is a getter with a side effect
+ */
+ val->intval = err;
+ if (err == POWER_SUPPLY_STATUS_FULL)
+ batt_ce_start(&chip->cap_estimate,
+ chip->cap_estimate.cap_tsettle);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = max1720x_get_battery_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_RAW:
+ err = max1720x_get_capacity_raw(chip, &data);
+ if (err == 0)
+ val->intval = (int)data;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ idata = max1720x_get_battery_soc(chip);
+ if (idata < 0) {
+ err = idata;
+ break;
+ }
+
+ val->intval = idata;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ err = max1720x_update_battery_qh_based_capacity(chip);
+ if (err < 0)
+ break;
+
+ 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 = max1720x_get_cycle_count(chip);
+ if (err < 0)
+ break;
+
+ /* err is cycle_count */
+ if (err <= FULLCAPNOM_STABILIZE_CYCLES)
+ err = REGMAP_READ(map, MAX1720X_DESIGNCAP, &data);
+ else
+ err = REGMAP_READ(map, MAX1720X_FULLCAPNOM, &data);
+
+ if (err == 0)
+ 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);
+ if (err == 0)
+ val->intval = reg_to_micro_amp_h(data, chip->RSense);
+ break;
+ /* current is positive value when flowing to device */
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ err = max17x0x_reg_read(map, MAX17X0X_TAG_avgc, &data);
+ if (err == 0)
+ val->intval = -reg_to_micro_amp(data, chip->RSense);
+ break;
+ /* current is positive value when flowing to device */
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ err = max17x0x_reg_read(map, MAX17X0X_TAG_curr, &data);
+ if (err == 0)
+ val->intval = -reg_to_micro_amp(data, chip->RSense);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ err = max1720x_get_cycle_count(chip);
+ if (err < 0)
+ break;
+ /* err is cycle_count */
+ val->intval = err;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (chip->gauge_type == -1) {
+ val->intval = 0;
+ } else {
+ err = REGMAP_READ(map, MAX1720X_STATUS, &data);
+ if (err < 0)
+ break;
+
+ /* BST is 0 when the battery is present */
+ val->intval = (((u16) data) & MAX1720X_STATUS_BST)
+ ? 0 : 1;
+ }
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE_ID:
+ val->intval = chip->batt_id;
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE:
+ err = REGMAP_READ(map, MAX1720X_RCELL, &data);
+ if (err < 0)
+ break;
+
+ val->intval = reg_to_resistance_micro_ohms(data, chip->RSense);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ err = max17x0x_reg_read(map, MAX17X0X_TAG_temp, &data);
+ if (err < 0)
+ break;
+
+ val->intval = reg_to_deci_deg_cel(data);
+ max1720x_handle_update_nconvgcfg(chip, val->intval);
+ max1720x_handle_update_empty_voltage(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ err = REGMAP_READ(map, MAX1720X_TTE, &data);
+ if (err == 0)
+ val->intval = reg_to_seconds(data);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ err = REGMAP_READ(map, MAX1720X_TTF, &data);
+ if (err == 0)
+ val->intval = reg_to_seconds(data);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ err = REGMAP_READ(map, MAX1720X_AVGVCELL, &data);
+ if (err == 0)
+ val->intval = reg_to_micro_volt(data);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ /* LSB: 20mV */
+ err = max17x0x_reg_read(map, MAX17X0X_TAG_mmdv, &data);
+ if (err == 0)
+ val->intval = ((data >> 8) & 0xFF) * 20000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ /* LSB: 20mV */
+ err = max17x0x_reg_read(map, MAX17X0X_TAG_mmdv, &data);
+ if (err == 0)
+ val->intval = (data & 0xFF) * 20000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ err = max17x0x_reg_read(map, MAX17X0X_TAG_vcel, &data);
+ if (err == 0)
+ val->intval = reg_to_micro_volt(data);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ rc = max17x0x_reg_read(map, MAX17X0X_TAG_vfocv, &data);
+ if (rc == -EINVAL) {
+ val->intval = -1;
+ break;
+ }
+ 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;
+ case POWER_SUPPLY_PROP_DELTA_CC_SUM:
+ val->intval = chip->cap_estimate.delta_cc_sum;
+ break;
+ case POWER_SUPPLY_PROP_DELTA_VFSOC_SUM:
+ val->intval = chip->cap_estimate.delta_vfsoc_sum;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_ESTIMATE:
+ val->intval = batt_ce_full_estimate(&chip->cap_estimate);
+ if (val->intval > 0)
+ val->intval *= 100000;
+ break;
+ case POWER_SUPPLY_PROP_RES_FILTER_COUNT:
+ rc = batt_res_registers(chip, true, SEL_RES_FILTER_COUNT,
+ &idata);
+ if (rc == -EINVAL)
+ idata = -1;
+
+ val->intval = idata;
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE_AVG:
+ rc = batt_res_registers(chip, true, SEL_RES_AVG, &idata);
+ if (rc == -EINVAL)
+ idata = -1;
+ val->intval = idata;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ if (err < 0)
+ pr_debug("error %d reading prop %d\n", err, psp);
+
+ mutex_unlock(&chip->model_lock);
+ return err;
+}
+
+static int max1720x_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)
+ power_supply_get_drvdata(psy);
+ struct gbatt_capacity_estimation *ce = &chip->cap_estimate;
+ int rc = 0;
+ int idata;
+
+ 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_BATT_CE_CTRL:
+ mutex_lock(&ce->batt_ce_lock);
+ if (val->intval) {
+
+ if (!ce->cable_in) {
+ rc = batt_ce_init(ce, chip);
+ ce->cable_in = (rc == 0);
+ }
+
+ } else if (ce->cable_in) {
+ /* check cycle count and save state if needed */
+ mod_delayed_work(system_wq, &chip->model_work, 0);
+
+ if (ce->estimate_state == ESTIMATE_PENDING)
+ cancel_delayed_work_sync(&ce->settle_timer);
+
+ /* race with batt_ce_capacityfiltered_work() */
+ batt_ce_stop_estimation(ce, ESTIMATE_NONE);
+ batt_ce_dump_data(ce, chip->ce_log);
+ ce->cable_in = false;
+ }
+ mutex_unlock(&ce->batt_ce_lock);
+ break;
+ case POWER_SUPPLY_PROP_RES_FILTER_COUNT:
+ idata = val->intval;
+ rc = batt_res_registers(chip, false, SEL_RES_FILTER_COUNT,
+ &idata);
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE_AVG:
+ idata = val->intval;
+ rc = batt_res_registers(chip, false, SEL_RES_AVG, &idata);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static int max1720x_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_BATT_CE_CTRL:
+ case POWER_SUPPLY_PROP_RES_FILTER_COUNT:
+ case POWER_SUPPLY_PROP_RESISTANCE_AVG:
+ 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.
+ * TODO: add a lock around fg_reset to prevent SW from accessing the gauge until
+ * the delay for volatile register access (rset->map[2]) expires. Need a lock
+ * only if using this after _init()
+ */
+static int max17x0x_fg_reset(struct max1720x_chip *chip)
+{
+ const struct max17x0x_reg *rset;
+ bool done = false;
+ int err;
+
+ rset = max17x0x_find_by_tag(&chip->regmap_nvram, MAX17X0X_TAG_rset);
+ if (!rset)
+ return -EINVAL;
+
+ dev_info(chip->dev, "FG_RESET addr=%x value=%x delay=%d\n",
+ rset->map16[0], rset->map16[1], rset->map16[2]);
+
+ err = REGMAP_WRITE(&chip->regmap, rset->map16[0], rset->map16[1]);
+ if (err < 0) {
+ dev_err(chip->dev, "FG_RESET error writing Config2 (%d)\n",
+ err);
+ } else {
+ int loops = 10; /* 10 * MAX17X0X_TPOR_MS = 1.5 secs */
+ u16 cfg2 = 0;
+
+ for ( ; loops ; loops--) {
+ msleep(MAX17X0X_TPOR_MS);
+
+ err = REGMAP_READ(&chip->regmap, rset->map16[0], &cfg2);
+ done = (err == 0) && !(cfg2 & rset->map16[1]);
+ if (done) {
+ msleep(rset->map16[2]);
+ break;
+ }
+ }
+
+ if (!done)
+ dev_err(chip->dev, "FG_RESET error rst not clearing\n");
+ else
+ dev_info(chip->dev, "FG_RESET cleared in %dms\n",
+ loops * MAX17X0X_TPOR_MS + rset->map16[2]);
+
+ }
+
+ return 0;
+}
+
+/*
+ * A full reset restores the ICs to their power-up state the same as if power
+ * had been cycled.
+ */
+static int max1720x_full_reset(struct max1720x_chip *chip)
+{
+ if (chip->gauge_type == MAX1730X_GAUGE_TYPE) {
+ /*
+ * a full (hw) reset on max1730x cause charge and discharge FET
+ * to toggle and the device will lose power. Will need to
+ * connect the device to a charger to get max1730x firmware to
+ * start and max1730x to close the FETs. Never send a HW reset
+ * to a 1730x while in system...
+ */
+ dev_warn(chip->dev, "ignore full reset of fuel gauge\n");
+ return 0;
+ }
+
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ MAX1720X_COMMAND_HARDWARE_RESET);
+
+ msleep(MAX17X0X_TPOR_MS);
+
+ return 0;
+}
+
+#define IRQ_STORM_TRIGGER_SECONDS 60
+#define IRQ_STORM_TRIGGER_MAX_COUNTS 50
+static bool max1720x_fg_irq_storm_check(struct max1720x_chip *chip)
+{
+ int now_time = 0, interval_time, irq_cnt;
+ bool storm = false;
+ static int stime;
+
+ chip->icnt++;
+
+ now_time = div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC);
+ if (now_time < IRQ_STORM_TRIGGER_SECONDS) {
+ stime = now_time;
+ chip->icnt = 0;
+ }
+
+ interval_time = now_time - stime;
+ if (interval_time > IRQ_STORM_TRIGGER_SECONDS) {
+ irq_cnt = chip->icnt * 100;
+ irq_cnt /= (interval_time * 100 / IRQ_STORM_TRIGGER_SECONDS);
+
+ storm = irq_cnt > IRQ_STORM_TRIGGER_MAX_COUNTS;
+ if (!storm) {
+ stime = now_time;
+ chip->icnt = 0;
+ }
+ }
+
+ return storm;
+}
+
+static irqreturn_t max1720x_fg_irq_thread_fn(int irq, void *obj)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)obj;
+ u16 fg_status, fg_status_clr;
+ bool storm = false;
+ int err = 0;
+
+ if (!chip || irq != chip->primary->irq) {
+ WARN_ON_ONCE(1);
+ return IRQ_NONE;
+ }
+
+ if (chip->gauge_type == -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;
+
+ /* disable storm check and spurius with shared interrupts */
+ if (!chip->irq_shared) {
+
+ storm = max1720x_fg_irq_storm_check(chip);
+ if (storm) {
+ u16 fg_alarm = 0;
+
+ if (chip->gauge_type != MAX_M5_GAUGE_TYPE)
+ err = REGMAP_READ(&chip->regmap, MAX1720X_ALARM,
+ &fg_alarm);
+
+ dev_warn(chip->dev, "sts:%04x, alarm:%04x, cnt:%d err=%d\n",
+ fg_status, fg_alarm, chip->icnt, err);
+ }
+
+ 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;
+ }
+ } else if (fg_status == 0) {
+ /*
+ * Disable rate limiting for when interrupt is shared.
+ * NOTE: this might need to be re-evaluated at some later point
+ */
+ return IRQ_NONE;
+ }
+
+ /* 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) {
+ pr_debug("POR is set\n");
+
+ /* trigger model load */
+ mutex_lock(&chip->model_lock);
+ if (chip->gauge_type == MAX_M5_GAUGE_TYPE)
+ max1720x_model_reload(chip, false);
+ else
+ fg_status_clr &= ~MAX1720X_STATUS_POR;
+ mutex_unlock(&chip->model_lock);
+ }
+
+ 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");
+ }
+ 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");
+
+ /* NOTE: should always clear everything even if we lose state */
+ REGMAP_WRITE(&chip->regmap, MAX1720X_STATUS, fg_status_clr);
+
+ if (storm && (fg_status & MAX1720X_STATUS_DSOCI)) {
+ pr_debug("Force power_supply_change in storm\n");
+ storm = false;
+ }
+
+ if (chip->psy && !storm)
+ power_supply_changed(chip->psy);
+
+ /*
+ * oneshot w/o filter will unmask on return but gauge will take up
+ * to 351 ms to clear ALRM1.
+ * NOTE: can do this masking on gauge side (Config, 0x1D) and using a
+ * workthread to re-enable.
+ */
+ msleep(MAX1720X_TICLR_MS);
+
+ return IRQ_HANDLED;
+}
+
+/* used to find batt_node and chemistry dependent FG overrides */
+static int max1720x_read_batt_id(int *batt_id, const struct max1720x_chip *chip)
+{
+ bool defer;
+ int rc = 0;
+ struct device_node *node = chip->dev->of_node;
+ u32 temp_id = 0;
+
+ /* force the value in kohm */
+ rc = of_property_read_u32(node, "maxim,force-batt-id", &temp_id);
+ if (rc == 0) {
+ dev_warn(chip->dev, "forcing battery RID %d\n", temp_id);
+ *batt_id = temp_id;
+ return 0;
+ }
+
+ /* return the value in kohm */
+ rc = gbms_storage_read(GBMS_TAG_BRID, &temp_id, sizeof(temp_id));
+ defer = (rc == -EPROBE_DEFER) ||
+ (rc == -EINVAL) ||
+ ((rc == 0) && (temp_id == -EINVAL));
+ if (defer)
+ return -EPROBE_DEFER;
+
+ if (rc < 0) {
+ dev_err(chip->dev, "failed to get batt-id rc=%d\n", rc);
+ *batt_id = -1;
+ return -EINVAL;
+ }
+
+ *batt_id = temp_id;
+ return 0;
+}
+
+static struct device_node *max1720x_find_batt_node(struct device *dev,
+ int batt_id)
+{
+ int ret;
+ u32 batt_id_range = 20, batt_id_kohm;
+ struct device_node *node = dev->of_node;
+ struct device_node *config_node, *child_node;
+
+ ret = of_property_read_u32(node, "maxim,batt-id-range-pct",
+ &batt_id_range);
+ if (ret && ret == -EINVAL)
+ dev_warn(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(dev, "Failed to find maxim,config setting\n");
+ return NULL;
+ }
+
+ 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 != 0)
+ continue;
+ if (!batt_id_range && batt_id == batt_id_kohm)
+ return child_node;
+ if ((batt_id < (batt_id_kohm * (100 + batt_id_range) / 100)) &&
+ (batt_id > (batt_id_kohm * (100 - batt_id_range) / 100)))
+ return child_node;
+ }
+
+ return NULL;
+}
+
+static int max17x0x_apply_regval_shadow(struct max1720x_chip *chip,
+ struct device_node *node,
+ struct max17x0x_cache_data *nRAM,
+ int nb)
+{
+ u16 *regs;
+ int ret, i;
+ const char *propname = (chip->gauge_type == MAX1730X_GAUGE_TYPE) ?
+ "maxim,n_regval_1730x" : "maxim,n_regval_1720x";
+
+ if (!node || nb <= 0)
+ return 0;
+
+ if (nb & 1) {
+ dev_warn(chip->dev, "%s %s u16 elems count is not even: %d\n",
+ node->name, propname, nb);
+ return -EINVAL;
+ }
+
+ regs = batt_alloc_array(nb, sizeof(u16));
+ if (!regs)
+ return -ENOMEM;
+
+ ret = of_property_read_u16_array(node, propname, regs, nb);
+ if (ret) {
+ dev_warn(chip->dev, "failed to read %s: %d\n", propname, ret);
+ goto shadow_out;
+ }
+
+ for (i = 0; i < nb; i += 2) {
+ const int idx = max17x0x_cache_index_of(nRAM, regs[i]);
+ nRAM->cache_data[idx] = regs[i + 1];
+ }
+
+shadow_out:
+ kfree(regs);
+ return ret;
+}
+
+/* support for initial batch of ill configured max1720x packs */
+static void max1720x_consistency_check(struct max17x0x_cache_data *cache)
+{
+ int nvcfg_idx = max17x0x_cache_index_of(cache, MAX1720X_NNVCFG0);
+ int ncgain_idx = max17x0x_cache_index_of(cache, MAX1720X_NCGAIN);
+ u16 *nRAM_updated = cache->cache_data;
+
+ if ((nRAM_updated[nvcfg_idx] & MAX1720X_NNVCFG0_ENCG) &&
+ ((nRAM_updated[ncgain_idx] == 0) ||
+ (nRAM_updated[ncgain_idx] == 0x0400)))
+ nRAM_updated[ncgain_idx] = 0x4000;
+}
+
+static int max17x0x_read_dt_version(struct device_node *node,
+ int gauge_type, u8 *reg, u8 *val)
+{
+ int ret;
+ const char *propname;
+ u8 version[2];
+
+ if (gauge_type == MAX1730X_GAUGE_TYPE) {
+ propname = "maxim,n_regval_1730x_ver";
+ } else if (gauge_type == MAX1720X_GAUGE_TYPE) {
+ propname = "maxim,n_regval_1720x_ver";
+ } else {
+ return -ENOTSUPP;
+ }
+
+ ret = of_property_read_u8_array(node, propname,
+ version,
+ sizeof(version));
+ if (ret < 0)
+ return -ENODATA;
+
+ *reg = version[0];
+ *val = version[1];
+
+ return 0;
+}
+
+static int max17x0x_read_dt_version_por(struct device_node *node,
+ int gauge_type, u8 *reg, u8 *val)
+{
+ int ret;
+ const char *propname;
+ u8 version[2];
+
+ if (gauge_type == MAX1730X_GAUGE_TYPE) {
+ propname = "maxim,n_regval_1730x_ver_por";
+ } else if (gauge_type == MAX1720X_GAUGE_TYPE) {
+ propname = "maxim,n_regval_1720x_ver_por";
+ } else {
+ return -ENOTSUPP;
+ }
+
+ ret = of_property_read_u8_array(node, propname,
+ version,
+ sizeof(version));
+ if (ret < 0)
+ return -ENODATA;
+
+ *reg = version[0];
+ *val = version[1];
+
+ return 0;
+}
+
+static int max17x0x_handle_dt_shadow_config(struct max1720x_chip *chip)
+{
+ int ret, rc, glob_cnt;
+ const char *propname = NULL;
+ struct max17x0x_cache_data nRAM_c;
+ struct max17x0x_cache_data nRAM_u;
+ int ver_idx = -1;
+ u8 vreg, vval;
+
+ /* for devices that don't support max1720x_fg_reset() */
+ if (!chip->shadow_override || chip->gauge_type == -1)
+ return 0;
+
+ if (chip->gauge_type == MAX1730X_GAUGE_TYPE)
+ propname = "maxim,n_regval_1730x";
+ else
+ propname = "maxim,n_regval_1720x";
+
+ ret = max17x0x_nvram_cache_init(&nRAM_c, chip->gauge_type);
+ if (ret < 0)
+ return ret;
+
+ ret = max17x0x_cache_load(&nRAM_c, &chip->regmap_nvram);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to read config from shadow RAM\n");
+ goto error_out;
+ }
+
+ ret = max17x0x_cache_dup(&nRAM_u, &nRAM_c);
+ if (ret < 0)
+ goto error_out;
+
+ /* apply overrides */
+ if (chip->batt_node) {
+ int batt_cnt;
+
+ batt_cnt = of_property_count_elems_of_size(chip->batt_node,
+ propname,
+ sizeof(u16));
+ max17x0x_apply_regval_shadow(chip, chip->batt_node,
+ &nRAM_u,
+ batt_cnt);
+ }
+
+ glob_cnt = of_property_count_elems_of_size(chip->dev->of_node,
+ propname,
+ sizeof(u16));
+ max17x0x_apply_regval_shadow(chip, chip->dev->of_node,
+ &nRAM_u,
+ glob_cnt);
+
+ if (chip->gauge_type == MAX1720X_GAUGE_TYPE)
+ max1720x_consistency_check(&nRAM_u);
+
+ rc = max17x0x_read_dt_version(chip->dev->of_node,
+ chip->gauge_type, &vreg, &vval);
+ if (rc == 0) {
+ /*
+ * Versioning enforced: reset the gauge (and overwrite
+ * version) only if the version in device tree is
+ * greater than the version in the gauge.
+ */
+ ver_idx = max17x0x_cache_index_of(&nRAM_u, vreg);
+ if (ver_idx < 0) {
+ dev_err(chip->dev, "version register %x is not mapped\n",
+ vreg);
+ } else if ((nRAM_u.cache_data[ver_idx] & 0xff) < vval) {
+ /*
+ * force version in dt, will write (and reset fg)
+ * only when less than the version in nRAM_c
+ */
+ dev_info(chip->dev,
+ "DT version updated %d -> %d\n",
+ nRAM_u.cache_data[ver_idx] & 0xff,
+ vval);
+
+ nRAM_u.cache_data[ver_idx] &= 0xff00;
+ nRAM_u.cache_data[ver_idx] |= vval;
+ chip->needs_reset = true;
+ }
+ }
+
+ if (max17x0x_cache_memcmp(&nRAM_c, &nRAM_u)) {
+ bool fg_reset = false;
+
+ if (ver_idx < 0) {
+ /*
+ * Versioning not enforced: nConvgCfg take effect
+ * without resetting the gauge
+ */
+ const int idx = max17x0x_cache_index_of(&nRAM_u,
+ MAX1720X_NCONVGCFG);
+ nRAM_c.cache_data[idx] = nRAM_u.cache_data[idx];
+ fg_reset = max17x0x_cache_memcmp(&nRAM_u, &nRAM_c) != 0;
+ }
+
+ ret = max17x0x_cache_store(&nRAM_u, &chip->regmap_nvram);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Failed to write config from shadow RAM\n");
+ goto error_out;
+ }
+
+ /* different reason for reset */
+ if (fg_reset) {
+ chip->needs_reset = true;
+ dev_info(chip->dev,
+ "DT config differs from shadow, resetting\n");
+ }
+ }
+
+error_out:
+ max17x0x_cache_free(&nRAM_c);
+ max17x0x_cache_free(&nRAM_u);
+
+ return ret;
+}
+
+static int max17x0x_apply_regval_register(struct max1720x_chip *chip,
+ struct device_node *node)
+{
+ int cnt, ret = 0, idx, err;
+ u16 *regs, data;
+ const char *propname;
+
+ propname = (chip->gauge_type == MAX1730X_GAUGE_TYPE) ?
+ "maxim,r_regval_1730x" : "maxim,r_regval_1720x";
+
+ cnt = of_property_count_elems_of_size(node, propname, sizeof(u16));
+ if (!node || cnt <= 0)
+ return 0;
+
+ if (cnt & 1) {
+ dev_warn(chip->dev, "%s %s u16 elems count is not even: %d\n",
+ node->name, propname, cnt);
+ return -EINVAL;
+ }
+
+ regs = batt_alloc_array(cnt, sizeof(u16));
+ if (!regs)
+ return -ENOMEM;
+
+ ret = of_property_read_u16_array(node, propname, regs, cnt);
+ if (ret) {
+ dev_warn(chip->dev, "failed to read %s %s: %d\n",
+ node->name, propname, 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 max17x0x_handle_dt_register_config(struct max1720x_chip *chip)
+{
+ int ret = 0;
+
+ if (chip->batt_node)
+ ret = max17x0x_apply_regval_register(chip, chip->batt_node);
+
+ if (ret)
+ return ret;
+
+ ret = max17x0x_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);
+
+ ret = of_property_read_u32(node, "google,cap-tsettle",
+ (u32 *)&chip->cap_estimate.cap_tsettle);
+ if (ret < 0)
+ chip->cap_estimate.cap_tsettle = DEFAULT_CAP_SETTLE_INTERVAL;
+ chip->cap_estimate.cap_tsettle =
+ chip->cap_estimate.cap_tsettle * 60 * 1000;
+
+ ret = of_property_read_u32(node, "google,cap-filt-length",
+ (u32 *)&chip->cap_estimate.cap_filt_length);
+ if (ret < 0)
+ chip->cap_estimate.cap_filt_length = DEFAULT_CAP_FILTER_LENGTH;
+
+ 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_s32(node, "maxim,nconvgcfg-temp-hysteresis",
+ &chip->convgcfg_hysteresis);
+ if (ret < 0)
+ chip->convgcfg_hysteresis = 10;
+ else if (chip->convgcfg_hysteresis < 0)
+ chip->convgcfg_hysteresis = 10;
+ if (ret == 0)
+ dev_info(chip->dev, "%s maxim,nconvgcfg-temp-hysteresis = %d\n",
+ node->name, chip->convgcfg_hysteresis);
+
+ 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 = (s16 *)devm_kmalloc_array(chip->dev,
+ chip->nb_convgcfg,
+ sizeof(s16),
+ GFP_KERNEL);
+ if (!chip->temp_convgcfg)
+ return -ENOMEM;
+
+ chip->convgcfg_values = (u16 *)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;
+ }
+ }
+
+ chip->nb_empty_voltage = of_property_count_elems_of_size(node,
+ "maxim,empty-voltage",
+ sizeof(u16));
+ if (chip->nb_empty_voltage > 0 &&
+ chip->nb_empty_voltage % NB_CYCLE_BUCKETS == 0) {
+ chip->empty_voltage = (u16 *)devm_kmalloc_array(chip->dev,
+ chip->nb_empty_voltage,
+ sizeof(u16),
+ GFP_KERNEL);
+ if (!chip->empty_voltage)
+ goto error;
+
+ ret = of_property_read_u16_array(node, "maxim,empty-voltage",
+ chip->empty_voltage,
+ chip->nb_empty_voltage);
+ if (ret) {
+ dev_warn(chip->dev,
+ "failed to read maxim,empty-voltage: %d\n",
+ ret);
+ }
+ } else
+ dev_warn(chip->dev,
+ "maxim,empty-voltage is missmatching the number of elements, nb = %d\n",
+ chip->nb_empty_voltage);
+error:
+ if (ret) {
+ devm_kfree(chip->dev, chip->temp_convgcfg);
+ devm_kfree(chip->dev, chip->convgcfg_values);
+ chip->temp_convgcfg = NULL;
+ }
+
+ return ret;
+}
+
+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");
+
+
+static int debug_fg_reset(void *data, u64 val)
+{
+ struct max1720x_chip *chip = data;
+ int ret;
+
+ if (val == 0)
+ ret = max17x0x_fg_reset(chip);
+ else if (val == 1)
+ ret = max1720x_full_reset(chip);
+ else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_fg_reset_fops, NULL, debug_fg_reset, "%llu\n");
+
+static int debug_ce_start(void *data, u64 val)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)data;
+
+ batt_ce_start(&chip->cap_estimate, val);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_ce_start_fops, NULL, debug_ce_start, "%llu\n");
+
+/* Model reload will be disabled if the node is not found */
+static int max1720x_init_model(struct max1720x_chip *chip)
+{
+ void *model_data;
+
+ /* ->batt_id negative for no lookup */
+ if (chip->batt_id >= 0) {
+ chip->batt_node = max1720x_find_batt_node(chip->dev,
+ chip->batt_id);
+ pr_debug("node found=%d for ID=%d\n", !!chip->batt_node,
+ chip->batt_id);
+ }
+
+ /* TODO: split allocation and initialization */
+ model_data = max_m5_init_data(chip->dev, chip->batt_node ?
+ chip->batt_node : chip->dev->of_node,
+ &chip->regmap);
+ if (IS_ERR(model_data))
+ return PTR_ERR(model_data);
+
+ chip->model_data = model_data;
+ if (!chip->batt_node) {
+ dev_warn(chip->dev, "No child node for ID=%d\n", chip->batt_id);
+ chip->model_reload = MAX_M5_LOAD_MODEL_DISABLED;
+ } else {
+ pr_debug("model_data ok for ID=%d", chip->batt_id);
+ chip->model_reload = MAX_M5_LOAD_MODEL_IDLE;
+ }
+
+ return 0;
+}
+
+/* change battery_id and cause reload of the FG model */
+static int debug_batt_id_set(void *data, u64 val)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)data;
+ int ret;
+
+ if (chip->gauge_type != MAX_M5_GAUGE_TYPE)
+ return -EINVAL;
+
+ mutex_lock(&chip->model_lock);
+
+ /* reset state (if needed) */
+ if (chip->model_data)
+ max_m5_free_data(chip->model_data);
+ chip->batt_id = val;
+
+ /* re-init the model data (lookup in DT) */
+ ret = max1720x_init_model(chip);
+ if (ret == 0)
+ max1720x_model_reload(chip, true);
+
+ mutex_unlock(&chip->model_lock);
+
+ dev_info(chip->dev, "Force model for batt_id=%d (%d)\n", val, ret);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_batt_id_fops, NULL, debug_batt_id_set, "%llu\n");
+
+
+#define BATTERY_DEBUG_ATTRIBUTE(name, fn_read, fn_write) \
+static const struct file_operations name = { \
+ .open = simple_open, \
+ .llseek = no_llseek, \
+ .read = fn_read, \
+ .write = fn_write, \
+}
+
+/*
+ * dump with "cat /d/max1720x/nvram_por | xxd"
+ * NOTE: for testing add a setter that initialize chip->nRAM_por (if not
+ * initialized) and use _load() to read NVRAM.
+ */
+static ssize_t debug_get_nvram_por(struct file *filp,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)filp->private_data;
+ int size;
+
+ if (!chip || !chip->nRAM_por.cache_data)
+ return -ENODATA;
+
+ size = chip->nRAM_por.atom.size > count ?
+ count : chip->nRAM_por.atom.size;
+
+ return simple_read_from_buffer(buf, count, ppos,
+ chip->nRAM_por.cache_data,
+ size);
+}
+
+BATTERY_DEBUG_ATTRIBUTE(debug_nvram_por_fops, debug_get_nvram_por, NULL);
+
+static void max17x0x_reglog_dump(struct max17x0x_reglog *regs,
+ size_t size,
+ char *buff)
+{
+ int i, len = 0;
+
+ for (i = 0; i < NB_REGMAP_MAX; i++) {
+ if (size <= len)
+ break;
+ if (test_bit(i, regs->valid))
+ len += scnprintf(&buff[len], size - len, "%02X:%04X\n",
+ i, regs->data[i]);
+ }
+
+ if (len == 0)
+ scnprintf(buff, size, "No record\n");
+}
+
+static ssize_t debug_get_reglog_writes(struct file *filp,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char *buff;
+ ssize_t rc = 0;
+ struct max17x0x_reglog *reglog =
+ (struct max17x0x_reglog *)filp->private_data;
+
+ buff = kmalloc(count, GFP_KERNEL);
+ if (!buff)
+ return -ENOMEM;
+
+ max17x0x_reglog_dump(reglog, count, buff);
+ rc = simple_read_from_buffer(buf, count, ppos, buff, strlen(buff));
+
+ kfree(buff);
+
+ return rc;
+}
+
+BATTERY_DEBUG_ATTRIBUTE(debug_reglog_writes_fops,
+ debug_get_reglog_writes, NULL);
+
+static ssize_t max1720x_show_custom_model(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)filp->private_data;
+ char *tmp;
+ int len;
+
+ if (!chip->model_data)
+ return -EINVAL;
+
+ tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ mutex_lock(&chip->model_lock);
+ len = max_m5_fg_model_cstr(tmp, PAGE_SIZE, chip->model_data);
+ mutex_unlock(&chip->model_lock);
+
+ if (len > 0)
+ len = simple_read_from_buffer(buf, count, ppos, tmp, len);
+
+ kfree(tmp);
+
+ return len;
+}
+
+static ssize_t max1720x_set_custom_model(struct file *filp,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)filp->private_data;
+ char *tmp;
+ int ret;
+
+ if (!chip->model_data)
+ return -EINVAL;
+
+ tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ ret = simple_write_to_buffer(tmp, PAGE_SIZE, ppos, user_buf, count);
+ if (!ret)
+ return -EFAULT;
+
+ mutex_lock(&chip->model_lock);
+ ret = max_m5_fg_model_sscan(chip->model_data, tmp, count);
+ if (ret < 0)
+ count = ret;
+ mutex_unlock(&chip->model_lock);
+
+ kfree(tmp);
+
+ return count;
+}
+
+BATTERY_DEBUG_ATTRIBUTE(debug_m5_custom_model_fops, max1720x_show_custom_model,
+ max1720x_set_custom_model);
+
+
+static int max17x0x_init_debugfs(struct max1720x_chip *chip)
+{
+ struct dentry *de;
+
+ de = debugfs_create_dir("max1720x", 0);
+ if (IS_ERR_OR_NULL(de))
+ return -ENOENT;
+
+ debugfs_create_file("irq_none_cnt", 0644, de,
+ chip, &irq_none_cnt_fops);
+ debugfs_create_file("nvram_por", 0440, de,
+ chip, &debug_nvram_por_fops);
+ debugfs_create_file("fg_reset", 0400, de,
+ chip, &debug_fg_reset_fops);
+ debugfs_create_file("ce_start", 0400, de,
+ chip, &debug_ce_start_fops);
+ debugfs_create_file("batt_id", 0600, de,
+ chip, &debug_batt_id_fops);
+
+ if (chip->regmap.reglog)
+ debugfs_create_file("regmap_writes", 0440, de,
+ chip->regmap.reglog,
+ &debug_reglog_writes_fops);
+
+ if (chip->regmap_nvram.reglog)
+ debugfs_create_file("regmap_nvram_writes", 0440, de,
+ chip->regmap_nvram.reglog,
+ &debug_reglog_writes_fops);
+
+ if (chip->gauge_type == MAX_M5_GAUGE_TYPE) {
+ debugfs_create_file("fg_model", 0440, de, chip,
+ &debug_m5_custom_model_fops);
+ }
+
+ return 0;
+}
+
+static u16 max1720x_read_rsense(const struct max1720x_chip *chip)
+{
+ u16 rsense = 0;
+ u32 rsense_default = 0;
+
+ (void)of_property_read_u32(chip->dev->of_node, "maxim,rsense-default",
+ &rsense_default);
+ if (chip->regmap_nvram.regmap) {
+ int ret;
+
+ ret = REGMAP_READ(&chip->regmap_nvram, MAX1720X_NRSENSE, &rsense);
+ if (rsense_default && (ret < 0 || rsense != rsense_default)) {
+ rsense = rsense_default;
+ dev_warn(chip->dev, "RSense, forcing %d micro Ohm (%d)\n",
+ rsense * 10, ret);
+ }
+ }
+
+ if (!rsense)
+ rsense = 500;
+
+ return rsense;
+}
+
+/*
+ * The BCNT, QHQH and QHCA for 17202 modify these common registers
+ * 0x18C, 0x18D, 0x18E, 0x18F, 0x1B2, 0x1B4 which will default to 0 after
+ * the recovery procedure. The map changes the content of 0x1C4, 0x1C5 which
+ * are not used (and should be 0 unless used by a future version of SW) as well
+ * as 0x1CD, 0x1CE used for nManfctrName1/2 which might change. 0x1DF is the
+ * INI checksum that will change with updates and cannot be used for this test.
+ * The only register left is 0x1D7 (MAX1730X_NPROTCFG), that is the register
+ * that the code will used to determine the corruption.
+ */
+static int max1730x_check_prot(struct max1720x_chip *chip, u16 devname)
+{
+ int needs_recall = 0;
+ u16 nprotcfg;
+ int ret;
+
+ /* check protection registers */
+ ret = REGMAP_READ(&chip->regmap_nvram, MAX1730X_NPROTCFG, &nprotcfg);
+ if (ret < 0)
+ return -EIO;
+
+ if (devname == MAX1730X_GAUGE_PASS1)
+ needs_recall = (nprotcfg != MAX1730X_NPROTCFG_PASS1);
+ else /* pass2 */
+ needs_recall = (nprotcfg != MAX1730X_NPROTCFG_PASS2);
+
+ return needs_recall;
+}
+
+static int max17x0x_nvram_recall(struct max1720x_chip *chip)
+{
+ REGMAP_WRITE(&chip->regmap,
+ MAX17XXX_COMMAND,
+ MAX17XXX_COMMAND_NV_RECALL);
+ msleep(MAX17X0X_TPOR_MS);
+ return 0;
+}
+
+/* TODO: move to google/max1730x_battery.c, enable with build config */
+static int max1730x_fixups(struct max1720x_chip *chip)
+{
+ int ret;
+
+ struct device_node *node = chip->dev->of_node;
+ bool write_back = false, write_ndata = false;
+ const u16 val = 0x280B;
+ u16 ndata = 0, bak = 0;
+
+ /* b/123026365 */
+ if (of_property_read_bool(node, "maxim,enable-nv-check")) {
+ const u16 devname = (chip->devname >> 4);
+ int needs_recall;
+
+ needs_recall = max1730x_check_prot(chip, devname);
+ if (needs_recall < 0) {
+ dev_err(chip->dev, "error reading fg NV configuration\n");
+ } else if (needs_recall == 0) {
+ /* all good.. */
+ } else if (devname == MAX1730X_GAUGE_PASS1) {
+ dev_err(chip->dev, "***********************************************\n");
+ dev_err(chip->dev, "WARNING: need to restore FG NV configuration to\n");
+ dev_err(chip->dev, "default values. THE DEVICE WILL LOOSE POWER.\n");
+ dev_err(chip->dev, "*******************************************\n");
+ msleep(MSEC_PER_SEC);
+ REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
+ MAX1720X_COMMAND_HARDWARE_RESET);
+ msleep(MAX17X0X_TPOR_MS);
+ } else {
+ dev_err(chip->dev, "Restoring FG NV configuration to sane values\n");
+
+ if (!chip->needs_reset) {
+ ret = max17x0x_nvram_recall(chip);
+ if (ret == 0)
+ chip->needs_reset = true;
+ }
+
+ REGMAP_WRITE(&chip->regmap_nvram,
+ MAX1730X_NPROTCFG,
+ MAX1730X_NPROTCFG_PASS2);
+ }
+ } else {
+ dev_err(chip->dev, "nv-check disabled\n");
+ }
+
+ /* b/122605202 */
+ ret = REGMAP_READ(&chip->regmap_nvram, MAX1730X_NVPRTTH1, &ndata);
+ if (ret == 0)
+ ret = REGMAP_READ(&chip->regmap, MAX1730X_NVPRTTH1BAK, &bak);
+ if (ret < 0)
+ return -EIO;
+
+ if (ndata == MAX1730X_NVPRTTH1_CHARGING)
+ write_back = (bak != val);
+ else
+ write_ndata = (ndata != val);
+
+ if (write_back || write_ndata) {
+ ret = REGMAP_WRITE(&chip->regmap,
+ MAX1730X_NVPRTTH1BAK, val);
+ if (ret == 0)
+ ret = REGMAP_WRITE(&chip->regmap_nvram,
+ MAX1730X_NVPRTTH1, val);
+ if (ret == 0)
+ ret = REGMAP_WRITE(&chip->regmap,
+ MAX1730X_NVPRTTH1BAK, val);
+ }
+
+ if (ret < 0)
+ dev_info(chip->dev, "failed to update 0x0D6=%x 0x1D0=%x to %x (%d)\n",
+ bak, ndata, val, ret);
+ else if (write_back)
+ dev_info(chip->dev, "0x0D6=%x 0x1D0=%x updated to %x (%d)\n",
+ bak, ndata, val, ret);
+ else if (write_ndata)
+ dev_info(chip->dev, "0x1D0=%x updated to %x (%d)\n",
+ ndata, val, ret);
+
+ return ret;
+}
+
+static int max17x0x_dump_param(struct max1720x_chip *chip)
+{
+ int ret;
+ u16 data;
+
+ ret = max17x0x_reg_read(&chip->regmap, MAX17X0X_TAG_cnfg,
+ &chip->RConfig);
+ if (ret < 0)
+ return ret;
+
+ dev_info(chip->dev, "Config: 0x%04x\n", chip->RConfig);
+
+ ret = REGMAP_READ(&chip->regmap, MAX1720X_ICHGTERM, &data);
+ if (ret < 0)
+ return ret;
+
+ dev_info(chip->dev, "IChgTerm: %d\n",
+ reg_to_micro_amp(data, chip->RSense));
+
+ ret = REGMAP_READ(&chip->regmap, MAX1720X_VEMPTY, &data);
+ if (ret < 0)
+ return ret;
+
+ dev_info(chip->dev, "VEmpty: VE=%dmV VR=%dmV\n",
+ reg_to_vempty(data), reg_to_vrecovery(data));
+
+ return 0;
+}
+
+static int max1720x_clear_por(struct max1720x_chip *chip)
+{
+ u16 data;
+ int ret;
+
+ ret = REGMAP_READ(&chip->regmap, MAX1720X_STATUS, &data);
+ if (ret < 0)
+ return ret;
+
+ if ((data & MAX1720X_STATUS_POR) == 0)
+ return 0;
+
+ return regmap_update_bits(chip->regmap.regmap,
+ MAX1720X_STATUS,
+ MAX1720X_STATUS_POR,
+ 0x0);
+}
+
+static int max1720x_model_load(struct max1720x_chip *chip)
+{
+ int ret;
+
+ /* retrieve model state from permanent storage only on boot */
+ if (chip->reg_prop_capacity_raw != MAX1720X_REPSOC) {
+
+ /* will retry on -EAGAIN as long as model_reload > _IDLE */
+ ret = max_m5_load_state_data(chip->model_data);
+ if (ret < 0) {
+ dev_err(chip->dev, "Cannot Load Model State (%d)\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* failure on the gauge: retry as long as model_reload > IDLE */
+ ret = max_m5_load_gauge_model(chip->model_data);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s: Load Gauge Model Failed rc=%d\n",
+ __func__, ret);
+ return -EAGAIN;
+ }
+
+ /* mark model state as "safe" */
+ chip->reg_prop_capacity_raw = MAX1720X_REPSOC;
+ return 0;
+}
+
+static void max1720x_model_work(struct work_struct *work)
+{
+ struct max1720x_chip *chip = container_of(work, struct max1720x_chip,
+ model_work.work);
+ int cycle_count;
+ int rc;
+
+ if (!chip->model_data)
+ return;
+
+ cycle_count = max1720x_get_cycle_count(chip);
+ if (cycle_count < 0)
+ cycle_count = chip->model_next_update;
+
+ pr_debug("%s: reload=%d cycle_count=%d\n", __func__,
+ chip->model_reload, cycle_count);
+
+ mutex_lock(&chip->model_lock);
+
+ /* set model_reload to the #attempts */
+ if (chip->model_reload >= MAX_M5_LOAD_MODEL_REQUEST) {
+
+ rc = max1720x_model_load(chip);
+ if (rc == 0) {
+ rc = max1720x_clear_por(chip);
+ dev_info(chip->dev, "%s Power-On Reset clear (%d)\n",
+ __func__, rc);
+
+ /* TODO: keep trying to clear POR if the above fail */
+ chip->model_reload = MAX_M5_LOAD_MODEL_IDLE;
+ } else if (rc != -EAGAIN) {
+ chip->model_reload = MAX_M5_LOAD_MODEL_DISABLED;
+ } else {
+ chip->model_reload -= 1;
+ }
+ }
+
+ if (cycle_count > chip->model_next_update) {
+
+ pr_debug("%s: cycle_count=%d next_update=%d\n", __func__,
+ cycle_count, chip->model_next_update);
+
+ /* read new state from Fuel gauge, save to storage */
+ rc = max_m5_model_read_state(chip->model_data);
+ if (rc == 0)
+ rc = max_m5_save_state_data(chip->model_data);
+ if (rc == 0) {
+ chip->model_next_update = (cycle_count + (1 << 6)) &
+ ~((1 << 6) - 1);
+ power_supply_changed(chip->psy);
+ }
+
+ }
+
+ if (chip->model_reload >= MAX_M5_LOAD_MODEL_REQUEST) {
+ const unsigned long delay = msecs_to_jiffies(60 * 1000);
+
+ schedule_delayed_work(&chip->model_work, delay);
+ }
+
+ mutex_unlock(&chip->model_lock);
+}
+
+static int max1720x_init_chip(struct max1720x_chip *chip)
+{
+ int ret;
+ u8 vreg, vpor;
+ u16 data = 0, tmp;
+ bool por = false, force_recall = false;
+
+ if (of_property_read_bool(chip->dev->of_node, "maxim,force-hard-reset"))
+ max1720x_full_reset(chip);
+
+ ret = REGMAP_READ(&chip->regmap, MAX1720X_STATUS, &data);
+ if (ret < 0)
+ return -EPROBE_DEFER;
+ por = (data & MAX1720X_STATUS_POR) != 0;
+ if (por && chip->regmap_nvram.regmap) {
+ dev_err(chip->dev, "Recall: POR bit is set\n");
+ force_recall = true;
+ }
+
+ /* TODO: disable with maxim,fix-ignore-zero-rsense */
+ chip->RSense = max1720x_read_rsense(chip);
+ if (chip->RSense == 0) {
+ dev_err(chip->dev, "Recall: RSense value 0 micro Ohm\n");
+ force_recall = true;
+ }
+
+ /* read por force recall and reset when version is the por */
+ ret = max17x0x_read_dt_version_por(chip->dev->of_node,
+ chip->gauge_type, &vreg, &vpor);
+ if (ret == 0) {
+ ret = REGMAP_READ(&chip->regmap_nvram, vreg, &tmp);
+ if ((ret == 0) && (vpor == (tmp & 0x00ff))) {
+ dev_err(chip->dev, "Recall: POR version %d\n", vpor);
+ force_recall = true;
+ }
+ }
+
+ /* b/129384855 fix mismatch between pack INI file and overrides */
+ if (of_property_read_bool(chip->dev->of_node, "maxim,fix-vempty")) {
+ ret = REGMAP_READ(&chip->regmap, MAX1720X_VEMPTY, &data);
+ if ((ret == 0) && (reg_to_vrecovery(data) == 0)) {
+ dev_err(chip->dev, "Recall: zero vrecovery\n");
+ force_recall = true;
+ }
+ }
+
+ if (force_recall && chip->regmap_nvram.regmap) {
+ /* debug only */
+ ret = max17x0x_nvram_cache_init(&chip->nRAM_por,
+ chip->gauge_type);
+ if (ret == 0)
+ ret = max17x0x_cache_load(&chip->nRAM_por,
+ &chip->regmap_nvram);
+ if (ret < 0) {
+ dev_err(chip->dev, "POR: Failed to backup config\n");
+ return -EPROBE_DEFER;
+ }
+
+ dev_info(chip->dev, "Recall Battery NVRAM\n");
+ ret = max17x0x_nvram_recall(chip);
+ if (ret == 0)
+ chip->needs_reset = true;
+
+ /* TODO: enable with maxim,fix-nagefccfg */
+ if (chip->gauge_type == MAX1720X_GAUGE_TYPE)
+ REGMAP_WRITE(&chip->regmap_nvram,
+ MAX1720X_NAGEFCCFG, 0);
+ }
+
+ /* */
+ if (chip->fixups_fn) {
+ ret = chip->fixups_fn(chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "Fixups failed (%d)\n", ret);
+ return ret;
+ }
+ }
+
+ /* set maxim,force-batt-id in DT to not delay the probe */
+ ret = max1720x_read_batt_id(&chip->batt_id, chip);
+ if (ret == -EPROBE_DEFER) {
+ if (chip->batt_id_defer_cnt) {
+ chip->batt_id_defer_cnt -= 1;
+ return -EPROBE_DEFER;
+ }
+
+ chip->batt_id = DEFAULT_BATTERY_ID;
+ dev_info(chip->dev, "default device battery ID = %d\n",
+ chip->batt_id);
+ } else {
+ dev_info(chip->dev, "device battery RID: %d kohm\n",
+ chip->batt_id);
+ }
+
+ /* init model data, needs batt_id */
+ mutex_init(&chip->model_lock);
+ max1720x_init_model(chip);
+
+ /* not needed for FG with NVRAM */
+ ret = max17x0x_handle_dt_shadow_config(chip);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ ret = max17x0x_handle_dt_register_config(chip);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ (void) max1720x_handle_dt_nconvgcfg(chip);
+
+ /* recall, force & reset SW */
+ if (chip->needs_reset) {
+ max17x0x_fg_reset(chip);
+
+ if (chip->RSense == 0)
+ chip->RSense = max1720x_read_rsense(chip);
+ }
+
+ ret = max17x0x_dump_param(chip);
+ if (ret < 0)
+ return -EPROBE_DEFER;
+ dev_info(chip->dev, "RSense value %d micro Ohm\n", chip->RSense * 10);
+
+ 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.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.regmap, MAX1720X_STATUS,
+ MAX1720X_STATUS_BI, 0x0);
+ }
+
+ if (!ret && data & MAX1720X_STATUS_POR) {
+
+ if (chip->gauge_type != MAX_M5_GAUGE_TYPE) {
+ ret = regmap_update_bits(chip->regmap.regmap,
+ MAX1720X_STATUS,
+ MAX1720X_STATUS_POR, 0x0);
+ dev_info(chip->dev,
+ "Clearing Power-On Reset bit (%d)\n", ret);
+
+ chip->reg_prop_capacity_raw = MAX1720X_REPSOC;
+ }
+
+ /* POR for M5 is handled in the irq_thread() */
+ }
+
+ max1720x_restore_battery_qh_capacity(chip);
+
+ return 0;
+}
+
+static int max1730x_decode_sn(char *serial_number,
+ unsigned int max,
+ const u16 *data)
+{
+ int tmp, count = 0;
+
+ if (data[0] != 0x4257) /* "BW": DSY */
+ return -EINVAL;
+
+ count += scnprintf(serial_number + count, max - count, "%02X%02X%02X",
+ data[3] & 0xFF,
+ data[4] & 0xFF,
+ data[5] & 0xFF);
+
+ tmp = (((((data[1] >> 9) & 0x3f) + 1980) * 10000) +
+ ((data[1] >> 5) & 0xf) * 100 + (data[1] & 0x1F));
+ count += scnprintf(serial_number + count, max - count, "%d",
+ tmp);
+
+ count += scnprintf(serial_number + count, max - count, "%c%c",
+ data[0] >> 8, data[0] & 0xFF);
+
+ count += scnprintf(serial_number + count, max - count, "%c%c%c%c",
+ data[7] >> 8,
+ data[8] >> 8,
+ data[9] >> 8,
+ data[9] & 0xFF);
+
+ count += scnprintf(serial_number + count, max - count, "%c",
+ data[2] & 0xFF);
+
+ count += scnprintf(serial_number + count, max - count, "%c%c",
+ data[6] >> 8, data[6] & 0xFF);
+ return 0;
+}
+
+static int max1720x_decode_sn(char *serial_number,
+ unsigned int max,
+ const u16 *data)
+{
+ int tmp, count = 0, shift;
+ char cell_vendor;
+
+ if (data[0] == 0x5357) /* "SW": SWD */
+ shift = 0;
+ else if (data[0] == 0x4257) /* "BW": DSY */
+ shift = 8;
+ else
+ return -EINVAL;
+
+ count += scnprintf(serial_number + count, max - count, "%02X%02X%02X",
+ data[1] >> shift,
+ data[2] >> shift,
+ data[3] >> shift);
+
+ tmp = (((((data[4] >> 9) & 0x3f) + 1980) * 10000) +
+ ((data[4] >> 5) & 0xf) * 100 + (data[4] & 0x1F));
+ count += scnprintf(serial_number + count, max - count, "%d",
+ tmp);
+
+ count += scnprintf(serial_number + count, max - count, "%c%c",
+ data[0] >> 8,
+ data[0] & 0xFF);
+
+ count += scnprintf(serial_number + count, max - count, "%c%c%c",
+ data[5] >> shift,
+ data[6] >> shift,
+ data[7] >> shift);
+
+ tmp = data[8];
+ if (tmp >> 8 == 0)
+ tmp = ('?' << 8) | (tmp & 0xFF);
+ if ((tmp & 0xFF) == 0)
+ tmp = (tmp & 0xFF00) | '?';
+ count += scnprintf(serial_number + count, max - count, "%c%c",
+ tmp >> 8,
+ tmp & 0xFF);
+
+ cell_vendor = (shift == 8) ? (data[9] >> 8) : (data[9] & 0xFF);
+ count += scnprintf(serial_number + count, max - count, "%c",
+ cell_vendor);
+
+ if (shift == 8) {
+ count += scnprintf(serial_number + count, max - count, "%02X",
+ data[10] >> 8);
+ } else {
+ count += scnprintf(serial_number + count, max - count, "%c%c",
+ data[10] >> 8, data[10] & 0xFF);
+ }
+
+ return 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 * hi->page_size;
+
+ format_battery_history_entry(temp, sizeof(temp),
+ hi->page_size, &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;
+
+ mutex_lock(&chip->history_lock);
+ history_count = max1720x_history_read(chip, hi);
+ if (history_count < 0) {
+ mutex_unlock(&chip->history_lock);
+ return history_count;
+ } else if (history_count == 0) {
+ dev_info(chip->dev, "No battery history has been recorded\n");
+ }
+ mutex_unlock(&chip->history_lock);
+
+ 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_device(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 int max1720x_init_history(struct max1720x_chip *chip)
+{
+ if (chip->gauge_type == MAX1730X_GAUGE_TYPE) {
+ chip->nb_history_pages = MAX1730X_N_OF_HISTORY_PAGES;
+ chip->history_page_size = MAX1730X_HISTORY_PAGE_SIZE;
+ chip->nb_history_flag_reg = MAX1730X_N_OF_HISTORY_FLAGS_REG;
+ } else if (chip->gauge_type == MAX1720X_GAUGE_TYPE) {
+ chip->nb_history_pages = MAX1720X_N_OF_HISTORY_PAGES;
+ chip->history_page_size = MAX1720X_HISTORY_PAGE_SIZE;
+ chip->nb_history_flag_reg = MAX1720X_N_OF_HISTORY_FLAGS_REG;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max17x0x_storage_info(gbms_tag_t tag, size_t *addr, size_t *count,
+ void *ptr)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)ptr;
+
+ if (!chip->history_available)
+ return -ENOENT;
+
+ *count = chip->history_page_size * 2; /* storage is in byte */
+ *addr = -1;
+ return 0;
+}
+
+static int max17x0x_storage_iter(int index, gbms_tag_t *tag, void *ptr)
+{
+ struct max1720x_chip *chip = (struct max1720x_chip *)ptr;
+ static gbms_tag_t keys[] = { GBMS_TAG_SNUM, GBMS_TAG_BCNT };
+ const int count = ARRAY_SIZE(keys);
+
+ if (index >= 0 && index < count) {
+ *tag = keys[index];
+ } else if (chip->history_available && index == count) {
+ *tag = GBMS_TAG_HIST;
+ } else {
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+/*
+ * The standard device call this with !data && !size && index=0 on start and
+ * !data && !size && index<0 on stop. The call on start free and reload the
+ * history from the gauge potentially increasing the number of entries (note
+ * clients will not see that until they call start). On close the code just
+ * release the allocated memory and entries: this is not a problem for cliets
+ * that might be open because the data will be reloaded on next access.
+ * This might create some churn but it's ok since we should not have more than
+ * one client for this.
+ */
+static int max17x0x_storage_history_read(void *buff, size_t size, int index,
+ struct max1720x_chip *chip)
+{
+ struct max1720x_history *hi = &chip->history_storage;
+
+ /* (!buff || !size) -> free the memory
+ * if index == INVALID -> return 0
+ * if index < 0 -> return -EIVAL
+ * if index >= 0 -> re-read history
+ */
+ if (!buff || !size) {
+ max1720x_history_free(hi);
+ if (index == GBMS_STORAGE_INDEX_INVALID)
+ return 0;
+ }
+
+ if (index < 0)
+ return -EINVAL;
+
+ /* read history if needed */
+ if (hi->history_count < 0) {
+ int ret;
+
+ ret = max1720x_history_read(chip, hi);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* index == 0 is ok here */
+ if (index >= hi->history_count)
+ return -ENODATA;
+
+ /* !buff, !size to read iterator count */
+ if (!size || !buff)
+ return hi->history_count;
+
+ memcpy(buff, &hi->history[index * chip->history_page_size], size);
+ return size;
+}
+
+static int max17x0x_storage_read_data(gbms_tag_t tag, void *buff, size_t size,
+ int index, void *ptr)
+{
+ int ret;
+ struct max1720x_chip *chip = (struct max1720x_chip *)ptr;
+
+ switch (tag) {
+ case GBMS_TAG_HIST:
+ /* short reads are invalid */
+ if (size && size != chip->history_page_size * 2)
+ return -EINVAL;
+
+ mutex_lock(&chip->history_lock);
+ ret = max17x0x_storage_history_read(buff, size, index, chip);
+ mutex_unlock(&chip->history_lock);
+ return ret;
+ default:
+ ret = -ENOENT;
+ break;
+ }
+
+ return ret;
+}
+
+static int max17x0x_storage_read(gbms_tag_t tag, void *buff, size_t size,
+ void *ptr)
+{
+ int ret;
+ const struct max17x0x_reg *reg;
+ struct max1720x_chip *chip = (struct max1720x_chip *)ptr;
+
+ switch (tag) {
+ case GBMS_TAG_SNUM:
+ reg = max17x0x_find_by_tag(&chip->regmap_nvram,
+ MAX17X0X_TAG_SNUM);
+ if (reg && reg->size > size)
+ return -ERANGE;
+ break;
+ case GBMS_TAG_BCNT:
+ reg = max17x0x_find_by_tag(&chip->regmap_nvram,
+ MAX17X0X_TAG_BCNT);
+ if (reg && reg->size != size)
+ return -ERANGE;
+ break;
+ default:
+ reg = NULL;
+ break;
+ }
+
+ if (!reg)
+ return -ENOENT;
+
+ ret = max17x0x_reg_load(&chip->regmap_nvram, reg, buff);
+ if (ret == 0)
+ ret = reg->size;
+
+ return ret;
+}
+
+static int max17x0x_storage_write(gbms_tag_t tag, const void *buff, size_t size,
+ void *ptr)
+{
+ int ret;
+ const struct max17x0x_reg *reg;
+ struct max1720x_chip *chip = (struct max1720x_chip *)ptr;
+
+ switch (tag) {
+ case GBMS_TAG_BCNT:
+ reg = max17x0x_find_by_tag(&chip->regmap_nvram,
+ MAX17X0X_TAG_BCNT);
+ if (reg && reg->size != size)
+ return -ERANGE;
+ break;
+ default:
+ reg = NULL;
+ break;
+ }
+
+ if (!reg)
+ return -ENOENT;
+
+ ret = max17x0x_reg_store(&chip->regmap_nvram, reg, buff);
+ if (ret == 0)
+ ret = reg->size;
+
+ return ret;
+}
+
+static struct gbms_storage_desc max17x0x_storage_dsc = {
+ .info = max17x0x_storage_info,
+ .iter = max17x0x_storage_iter,
+ .read = max17x0x_storage_read,
+ .write = max17x0x_storage_write,
+ .read_data = max17x0x_storage_read_data,
+};
+
+/* this must be not blocking */
+static void max17x0x_read_serial_number(struct max1720x_chip *chip)
+{
+ char buff[32] = {0};
+ int ret;
+
+ ret = gbms_storage_read(GBMS_TAG_SNUM, buff, sizeof(buff));
+ if (ret < 0) {
+ /* do nothing */
+ } else if (chip->gauge_type == MAX1730X_GAUGE_TYPE) {
+ ret = max1730x_decode_sn(chip->serial_number,
+ sizeof(chip->serial_number),
+ (u16 *)buff);
+ } else if (chip->gauge_type == MAX1720X_GAUGE_TYPE) {
+ ret = max1720x_decode_sn(chip->serial_number,
+ sizeof(chip->serial_number),
+ (u16 *)buff);
+ } else {
+ if (ret > sizeof(chip->serial_number))
+ ret = sizeof(chip->serial_number);
+ strncpy(chip->serial_number, buff, ret);
+ }
+
+ if (ret < 0)
+ chip->serial_number[0] = '\0';
+}
+
+static void max1720x_init_work(struct work_struct *work)
+{
+ struct max1720x_chip *chip = container_of(work, struct max1720x_chip,
+ init_work.work);
+ int ret = 0;
+
+ if (chip->gauge_type != -1) {
+
+ if (chip->regmap_nvram.regmap) {
+ ret = gbms_storage_register(&max17x0x_storage_dsc,
+ "maxfg", chip);
+ if (ret == -EBUSY)
+ ret = 0;
+ }
+
+ if (ret == 0)
+ ret = max1720x_init_chip(chip);
+ if (ret == -EPROBE_DEFER) {
+ schedule_delayed_work(&chip->init_work,
+ msecs_to_jiffies(MAX1720X_DELAY_INIT_MS));
+ return;
+ }
+ }
+
+ /* serial number might not be stored in the FG */
+ max17x0x_read_serial_number(chip);
+
+ mutex_init(&chip->cap_estimate.batt_ce_lock);
+ chip->prev_charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ chip->fake_capacity = -EINVAL;
+ chip->resume_complete = true;
+ chip->init_complete = true;
+ max17x0x_init_debugfs(chip);
+
+ /*
+ * Handle any IRQ that might have been set before init
+ * NOTE: will clear the POR bit and trigger model load if needed
+ */
+ max1720x_fg_irq_thread_fn(chip->primary->irq, chip);
+
+ dev_info(chip->dev, "init_work done\n");
+ if (chip->gauge_type == -1)
+ return;
+
+ /* Init History and Capacity Estimate only when gauge type is known. */
+ ret = max1720x_init_history(chip);
+ if (ret == 0)
+ (void)max1720x_init_history_device(chip);
+
+ ret = batt_ce_load_data(&chip->regmap_nvram, &chip->cap_estimate);
+ if (ret == 0)
+ batt_ce_dump_data(&chip->cap_estimate, chip->ce_log);
+}
+
+/* TODO: fix detection of 17301 for non samples looking at FW version too */
+static int max17xxx_read_gauge_type(struct max1720x_chip *chip)
+{
+ u8 reg = MAX1720X_DEVNAME;
+ struct i2c_msg xfer[2];
+ uint8_t buf[2] = { };
+ int ret, gauge_type;
+
+ ret = of_property_read_u32(chip->dev->of_node, "maxim,gauge-type",
+ &gauge_type);
+ if (ret == 0) {
+ dev_warn(chip->dev, "forced gauge type to %d\n", gauge_type);
+ return gauge_type;
+ }
+
+ /* some maxim IF-PMIC corrupt reads w/o Rs b/152373060 */
+ xfer[0].addr = chip->primary->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = ®
+
+ xfer[1].addr = chip->primary->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 2;
+ xfer[1].buf = buf;
+
+ ret = i2c_transfer(chip->primary->adapter, xfer, 2);
+ if (ret != 2)
+ return -EIO;
+
+ chip->devname = buf[1] << 8 | buf[0];
+ if (!chip->devname)
+ return -ENODEV;
+
+ switch (chip->devname >> 4) {
+ case 0x404: /* max1730x sample */
+ case 0x405: /* max1730x pass2 silicon initial samples */
+ case 0x406: /* max1730x pass2 silicon */
+ gauge_type = MAX1730X_GAUGE_TYPE;
+ break;
+ case 0x620: /* m5 algo */
+ gauge_type = MAX_M5_GAUGE_TYPE;
+ break;
+ default: /* default to max1720x */
+ dev_warn(chip->dev, "devname=%x defaults to max17201\n",
+ chip->devname);
+ gauge_type = MAX1720X_GAUGE_TYPE;
+ break;
+ }
+
+ return gauge_type;
+}
+
+
+/* NOTE: NEED TO COME BEFORE REGISTER ACCESS */
+static int max17x0x_regmap_init(struct max1720x_chip *chip)
+{
+ int secondary_address = 0xb;
+ struct device *dev = chip->dev;
+
+ if (chip->gauge_type == MAX1730X_GAUGE_TYPE) {
+ /* redefine primary for max1730x */
+ chip->regmap.regmap = devm_regmap_init_i2c(chip->primary,
+ &max1730x_regmap_cfg);
+ if (IS_ERR(chip->regmap.regmap)) {
+ dev_err(chip->dev, "Failed to re-initialize regmap (%d)\n",
+ IS_ERR_VALUE(chip->regmap.regmap));
+ return -EINVAL;
+ }
+
+ /* shadow override is not supported on early samples */
+ chip->shadow_override = (chip->devname >> 4) != 0x404;
+
+ chip->regmap.regtags.max = ARRAY_SIZE(max1730x);
+ chip->regmap.regtags.map = max1730x;
+ chip->fixups_fn = max1730x_fixups;
+ } else if (chip->gauge_type == MAX_M5_GAUGE_TYPE) {
+ int ret;
+
+ ret = max_m5_regmap_init(&chip->regmap, chip->primary);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to re-initialize regmap (%d)\n",
+ IS_ERR_VALUE(chip->regmap.regmap));
+ return -EINVAL;
+ }
+
+ chip->shadow_override = false;
+ secondary_address = 0;
+ } else if (chip->gauge_type == MAX1720X_GAUGE_TYPE) {
+
+ chip->regmap.regmap = devm_regmap_init_i2c(chip->primary,
+ &max1720x_regmap_cfg);
+ if (IS_ERR(chip->regmap.regmap)) {
+ dev_err(chip->dev, "Failed to initialize primary regmap (%d)\n",
+ IS_ERR_VALUE(chip->regmap.regmap));
+ return -EINVAL;
+ }
+
+ /* max1720x is default map */
+ chip->regmap.regtags.max = ARRAY_SIZE(max1720x);
+ chip->regmap.regtags.map = max1720x;
+ }
+
+ /* todo read secondary address from DT */
+ if (!secondary_address) {
+ dev_warn(chip->dev, "Device 0x%x has no permanent storage\n",
+ chip->devname);
+ return 0;
+ }
+
+ chip->secondary = i2c_new_secondary_device(chip->primary,
+ "nvram",
+ secondary_address);
+ if (chip->secondary == NULL) {
+ dev_err(dev, "Failed to initialize secondary i2c device\n");
+ return -ENODEV;
+ }
+
+ i2c_set_clientdata(chip->secondary, chip);
+
+ if (chip->gauge_type == MAX1730X_GAUGE_TYPE) {
+ chip->regmap_nvram.regmap =
+ devm_regmap_init_i2c(chip->secondary,
+ &max1730x_regmap_nvram_cfg);
+ if (IS_ERR(chip->regmap_nvram.regmap)) {
+ dev_err(chip->dev, "Failed to initialize nvram regmap (%d)\n",
+ IS_ERR_VALUE(chip->regmap_nvram.regmap));
+ return -EINVAL;
+ }
+
+ chip->regmap_nvram.regtags.max = ARRAY_SIZE(max1730x);
+ chip->regmap_nvram.regtags.map = max1730x;
+ } else {
+ chip->regmap_nvram.regmap =
+ devm_regmap_init_i2c(chip->secondary,
+ &max1720x_regmap_nvram_cfg);
+ if (IS_ERR(chip->regmap_nvram.regmap)) {
+ dev_err(chip->dev, "Failed to initialize nvram regmap (%d)\n",
+ IS_ERR_VALUE(chip->regmap_nvram.regmap));
+ return -EINVAL;
+ }
+
+ chip->regmap_nvram.regtags.max = ARRAY_SIZE(max1720x);
+ chip->regmap_nvram.regtags.map = max1720x;
+ }
+
+ return 0;
+}
+
+static int max1720x_init_irq(struct max1720x_chip *chip)
+{
+ unsigned long irqf = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
+ int ret, irqno;
+
+ chip->irq_shared = of_property_read_bool(chip->dev->of_node,
+ "maxim,irqf-shared");
+ irqno = chip->primary->irq;
+ if (!irqno) {
+ int irq_gpio;
+
+ irq_gpio = of_get_named_gpio(chip->dev->of_node,
+ "maxim,irq-gpio", 0);
+ if (irq_gpio >= 0) {
+ chip->primary->irq = gpio_to_irq(irq_gpio);
+ if (chip->primary->irq <= 0) {
+ chip->primary->irq = 0;
+ dev_warn(chip->dev, "fg irq not avalaible\n");
+ return 0;
+ }
+ }
+ }
+
+ if (chip->irq_shared)
+ irqf |= IRQF_SHARED;
+
+ ret = request_threaded_irq(chip->primary->irq, NULL,
+ max1720x_fg_irq_thread_fn, irqf,
+ MAX1720X_I2C_DRIVER_NAME, chip);
+ dev_info(chip->dev, "FG irq handler registered at %d (%d)\n",
+ chip->primary->irq, ret);
+ if (ret == 0)
+ enable_irq_wake(chip->primary->irq);
+
+ return ret;
+}
+
+void *max1720x_get_model_data(struct i2c_client *client)
+{
+ struct max1720x_chip *chip = i2c_get_clientdata(client);
+ return chip->model_data;
+}
+EXPORT_SYMBOL_GPL(max1720x_get_model_data);
+
+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 = { };
+ const struct max17x0x_reg *reg;
+ int ret = 0;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ chip->primary = client;
+ chip->batt_id_defer_cnt = DEFAULT_BATTERY_ID_RETRIES;
+ i2c_set_clientdata(client, chip);
+
+ /* NOTE: < 0 not avalable, it could be a bare MLB */
+ chip->gauge_type = max17xxx_read_gauge_type(chip);
+ if (chip->gauge_type < 0)
+ chip->gauge_type = -1;
+
+ /* needs chip->primary and (optional) chip->secondary */
+ ret = max17x0x_regmap_init(chip);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize regmap(s)\n");
+ goto i2c_unregister;
+ }
+
+ dev_warn(chip->dev, "device gauge_type: %d shadow_override=%d\n",
+ chip->gauge_type, chip->shadow_override);
+
+ if (of_property_read_bool(dev->of_node, "maxim,log_writes")) {
+ bool debug_reglog;
+
+ debug_reglog = max17x0x_reglog_init(chip);
+ dev_info(dev, "write log %savailable\n",
+ debug_reglog ? "" : "not ");
+ }
+
+ /* M5 requires zero IRQ */
+ chip->zero_irq = -1;
+ if (chip->gauge_type == MAX_M5_GAUGE_TYPE)
+ chip->zero_irq = 1;
+ if (chip->zero_irq == -1)
+ chip->zero_irq = of_property_read_bool(chip->dev->of_node,
+ "maxim,zero-irq");
+
+ /* TODO: do not request the interrupt if the gauge is not present */
+ ret = max1720x_init_irq(chip);
+ if (ret < 0) {
+ dev_err(dev, "cannot allocate irq\n");
+ return ret;
+ }
+
+ 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;
+ psy_cfg.of_node = chip->dev->of_node;
+ 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_offmode_charger);
+ if (ret) {
+ dev_err(dev, "Failed to create offmode_charger attribute\n");
+ goto psy_unregister;
+ }
+
+ /* M5 battery model needs batt_id and is setup during init() */
+ chip->model_reload = MAX_M5_LOAD_MODEL_DISABLED;
+ if (chip->gauge_type == MAX_M5_GAUGE_TYPE) {
+ ret = device_create_file(&chip->psy->dev,
+ &dev_attr_m5_model_state);
+ if (ret)
+ dev_err(dev, "Failed to create model_state, ret=%d\n",
+ ret);
+ }
+
+ chip->ce_log = debugfs_logbuffer_register("batt_ce");
+ if (IS_ERR(chip->ce_log)) {
+ ret = PTR_ERR(chip->ce_log);
+ dev_err(dev, "failed to obtain logbuffer, ret=%d\n", ret);
+ chip->ce_log = NULL;
+ }
+
+ /* use VFSOC until it can confirm that FG Model is running */
+ reg = max17x0x_find_by_tag(&chip->regmap, MAX17X0X_TAG_vfsoc);
+ chip->reg_prop_capacity_raw = (reg) ? reg->reg : MAX1720X_REPSOC;
+
+ INIT_DELAYED_WORK(&chip->cap_estimate.settle_timer,
+ batt_ce_capacityfiltered_work);
+ INIT_DELAYED_WORK(&chip->init_work, max1720x_init_work);
+ INIT_DELAYED_WORK(&chip->model_work, max1720x_model_work);
+
+ schedule_delayed_work(&chip->init_work, 0);
+
+ 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);
+
+ if (chip->ce_log) {
+ debugfs_logbuffer_unregister(chip->ce_log);
+ chip->ce_log = NULL;
+ }
+
+ max1720x_cleanup_history(chip);
+ max_m5_free_data(chip->model_data);
+ cancel_delayed_work(&chip->init_work);
+ cancel_delayed_work(&chip->model_work);
+
+ if (chip->primary->irq)
+ free_irq(chip->primary->irq, chip);
+ power_supply_unregister(chip->psy);
+
+ if (chip->secondary)
+ i2c_unregister_device(chip->secondary);
+
+ return 0;
+}
+
+static const struct of_device_id max1720x_of_match[] = {
+ { .compatible = "maxim,max1720x"},
+ { .compatible = "maxim,max77729f"},
+ { .compatible = "maxim,max77759"},
+ {},
+};
+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 <[email protected]>");
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_DESCRIPTION("MAX17x01/MAX17x05 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/max1720x_battery.h b/max1720x_battery.h
new file mode 100644
index 0000000..86b6576
--- /dev/null
+++ b/max1720x_battery.h
@@ -0,0 +1,236 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Google Battery Management System
+ *
+ * Copyright 2020 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.
+ */
+
+#ifndef MAX1720X_BATTERY_H_
+#define MAX1720X_BATTERY_H_
+
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+#define MAX1720X_GAUGE_TYPE 0
+#define MAX1730X_GAUGE_TYPE 1
+#define MAX_M5_GAUGE_TYPE 2
+
+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);
+}
+
+
+enum max17x0x_reg_tags {
+ MAX17X0X_TAG_avgc,
+ MAX17X0X_TAG_cnfg,
+ MAX17X0X_TAG_mmdv,
+ MAX17X0X_TAG_vcel,
+ MAX17X0X_TAG_temp,
+ MAX17X0X_TAG_curr,
+ MAX17X0X_TAG_mcap,
+ MAX17X0X_TAG_avgr,
+ MAX17X0X_TAG_vfsoc,
+ MAX17X0X_TAG_vfocv,
+
+ MAX17X0X_TAG_BCNT,
+ MAX17X0X_TAG_SNUM,
+ MAX17X0X_TAG_HSTY,
+ MAX17X0X_TAG_BCEA,
+ MAX17X0X_TAG_rset,
+ MAX17X0X_TAG_BRES,
+};
+
+enum max17x0x_reg_types {
+ GBMS_ATOM_TYPE_MAP = 0,
+ GBMS_ATOM_TYPE_REG = 1,
+ GBMS_ATOM_TYPE_ZONE = 2,
+ GBMS_ATOM_TYPE_SET = 3,
+};
+
+/* this is a map for u16 registers */
+#define ATOM_INIT_MAP(...) \
+ .type = GBMS_ATOM_TYPE_MAP, \
+ .size = 2 * sizeof((u8[]){__VA_ARGS__}),\
+ .map = (u8[]){__VA_ARGS__}
+
+#define ATOM_INIT_REG16(r) \
+ .type = GBMS_ATOM_TYPE_REG, \
+ .size = 2, \
+ .reg = r
+
+#define ATOM_INIT_ZONE(start, sz) \
+ .type = GBMS_ATOM_TYPE_ZONE, \
+ .size = sz, \
+ .base = start
+
+/* a set has no storage and cannot be used in load/store */
+#define ATOM_INIT_SET(...) \
+ .type = GBMS_ATOM_TYPE_SET, \
+ .size = 0, \
+ .map = (u8[]){__VA_ARGS__}
+
+#define ATOM_INIT_SET16(...) \
+ .type = GBMS_ATOM_TYPE_SET, \
+ .size = 0, \
+ .map16 = (u16[]){__VA_ARGS__}
+
+struct max17x0x_reg {
+ int type;
+ int size;
+ union {
+ unsigned int base;
+ unsigned int reg;
+ const u16 *map16;
+ const u8 *map;
+ };
+};
+
+struct max17x0x_cache_data {
+ struct max17x0x_reg atom;
+ u16 *cache_data;
+};
+
+#define NB_REGMAP_MAX 256
+
+struct max17x0x_reglog {
+ u16 data[NB_REGMAP_MAX];
+ DECLARE_BITMAP(valid, NB_REGMAP_MAX);
+ int errors[NB_REGMAP_MAX];
+ int count[NB_REGMAP_MAX];
+};
+
+struct max17x0x_regtags {
+ const struct max17x0x_reg *map;
+ unsigned int max;
+};
+
+struct max17x0x_regmap {
+ struct regmap *regmap;
+ struct max17x0x_regtags regtags;
+ struct max17x0x_reglog *reglog;
+};
+
+/* */
+#ifdef CONFIG_MAX1720X_REGLOG_LOG
+static inline void max17x0x_reglog_log(struct max17x0x_reglog *reglog,
+ unsigned int reg, u16 data, int rtn)
+{
+ if (!reglog)
+ return;
+
+ reglog->count[reg] += 1;
+ if (rtn != 0) {
+ reglog->errors[reg] += 1;
+ } else {
+ __set_bit(reg, reglog->valid);
+ reglog->data[reg] = data;
+ }
+
+}
+
+#else
+static inline void max17x0x_reglog_log(struct max17x0x_reglog *reglog,
+ unsigned int reg, u16 data, int rtn)
+{
+
+}
+#endif
+
+static inline int max17x0x_regmap_read(const struct max17x0x_regmap *map,
+ unsigned int reg,
+ u16 *val,
+ const char *name)
+{
+ int rtn;
+ unsigned int tmp;
+
+ if (!map->regmap) {
+ pr_err("Failed to read %s, no regmap\n", name);
+ return -EIO;
+ }
+
+ rtn = regmap_read(map->regmap, reg, &tmp);
+ if (rtn)
+ pr_err("Failed to read %s\n", name);
+ else
+ *val = tmp;
+
+ return rtn;
+}
+
+#define REGMAP_READ(regmap, what, dst) \
+ max17x0x_regmap_read(regmap, what, dst, #what)
+
+static inline int max17x0x_regmap_write(const struct max17x0x_regmap *map,
+ unsigned int reg,
+ u16 data,
+ const char *name)
+{
+ int rtn;
+
+ if (!map->regmap) {
+ pr_err("Failed to write %s, no regmap\n", name);
+ return -EIO;
+ }
+
+ rtn = regmap_write(map->regmap, reg, data);
+ if (rtn)
+ pr_err("Failed to write %s\n", name);
+
+ max17x0x_reglog_log(map->reglog, reg, data, rtn);
+
+ return rtn;
+}
+
+#define REGMAP_WRITE(regmap, what, value) \
+ max17x0x_regmap_write(regmap, what, value, #what)
+
+static inline int max1720x_regmap_writeverify(const struct max17x0x_regmap *map,
+ unsigned int reg,
+ u16 data,
+ const char *name)
+{
+ int tmp, ret;
+
+ if (!map->regmap) {
+ pr_err("Failed to write %s, no regmap\n", name);
+ return -EINVAL;
+ }
+
+ ret = regmap_write(map->regmap, reg, data);
+ if (ret < 0) {
+ pr_err("Failed to write %s\n", name);
+ return -EIO;
+ }
+
+ ret = regmap_read(map->regmap, reg, &tmp);
+ if (ret < 0) {
+ pr_err("Failed to read %s\n", name);
+ return -EIO;
+ }
+
+ return (tmp == data) ? 0 : -EIO;
+}
+
+#define REGMAP_WRITE_VERIFY(regmap, what, value) \
+ max1720x_regmap_writeverify(regmap, what, value, #what)
+
+#endif
\ No newline at end of file
diff --git a/max1730x.h b/max1730x.h
new file mode 100644
index 0000000..fd2a0e4
--- /dev/null
+++ b/max1730x.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef MAX1730X_H_
+#define MAX1730X_H_
+
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include "max1720x.h"
+
+#define MAX1730X_N_OF_HISTORY_PAGES 100
+
+#define MAX1730X_GAUGE_PASS1 0x404
+#define MAX1730X_NVPRTTH1_CHARGING 0x0008
+#define MAX1730X_NPROTCFG_PASS1 0x6EA3
+#define MAX1730X_NPROTCFG_PASS2 0x0A04
+
+enum max1730x_register {
+ MAX1730X_MAXMINVOLT = 0x08,
+ MAX1730X_MAXMINTEMP = 0x09,
+ MAX1730X_MAXMINCURR = 0x0A,
+ MAX1730X_CONFIG = 0x0B,
+ MAX1730X_FULLCAPREP = 0x10,
+ MAX1730X_VCELL = 0x1A,
+ MAX1730X_TEMP = 0x1B,
+ MAX1730X_CURRENT = 0x1C,
+ MAX1730X_AVGCURRENT = 0x1D,
+ MAX1730X_MIXCAP = 0x2B,
+ MAX1730X_FULLCAP = 0x35,
+ MAX1730X_LEARNCFG = 0xA1,
+ MAX1730X_MAXPEAKPWR = 0xA4,
+ MAX1730X_SUSPEAKPWR = 0xA5,
+ MAX1730X_PACKRESISTANCE = 0xA6,
+ MAX1730X_SYSRESISTANCE = 0xA7,
+ MAX1730X_MINSYSVOLTAGE = 0xA8,
+ MAX1730X_MPPCURRENT = 0xA9,
+ MAX1730X_SPPCURRENT = 0xAA,
+ MAX1730X_CONFIG2 = 0xAB,
+ MAX1730X_IALRTTH = 0xAC,
+ MAX1730X_MINVOLT = 0xAD,
+ MAX1730X_MINCURR = 0xAE,
+ MAX1730X_NVPRTTH1BAK = 0xD6,
+ MAX1730X_NPROTCFG = 0xD7,
+
+};
+
+enum max1730x_nvram {
+ MAX1730X_NVRAM_START = 0x80,
+ MAX1730X_NMANFCTRNAME0 = 0xCC,
+ MAX1730X_NMANFCTRNAME1 = 0xCD,
+ MAX1730X_NVPRTTH1 = 0xD0,
+ MAX1730X_NDPLIMIT = 0xE0,
+ MAX1730X_NSCOCVLIM = 0xE1,
+
+ MAX1730X_NVRAM_END = 0xEF,
+ MAX1730X_HISTORY_START = 0xF0,
+ MAX1730X_HISTORY_WRITE_STATUS_START = 0xF2,
+ MAX1730X_HISTORY_VALID_STATUS_END = 0xFB,
+ MAX1730X_HISTORY_WRITE_STATUS_END = 0xFE,
+ MAX1730X_HISTORY_END = 0xFF,
+};
+
+enum max1730x_command_bits {
+ MAX1730X_COMMAND_FUEL_GAUGE_RESET = 0x8000,
+ MAX1730X_READ_HISTORY_CMD_BASE = 0xE22E,
+ MAX1730X_COMMAND_HISTORY_RECALL_WRITE_0 = 0xE29C,
+ MAX1730X_COMMAND_HISTORY_RECALL_VALID_0 = 0xE29C,
+ MAX1730X_COMMAND_HISTORY_RECALL_VALID_1 = 0xE29D,
+};
+
+#define MAX1730X_HISTORY_PAGE_SIZE \
+ (MAX1730X_HISTORY_END - MAX1730X_HISTORY_START + 1)
+
+#define MAX1730X_N_OF_HISTORY_FLAGS_REG \
+ (MAX1730X_HISTORY_END - \
+ MAX1730X_HISTORY_END + 1 + \
+ MAX1730X_HISTORY_VALID_STATUS_END - \
+ MAX1730X_HISTORY_START + 1)
+
+/** ------------------------------------------------------------------------ */
+
+static bool max1730x_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 0xA0 ... 0xAE:
+ case 0xB0 ... 0xDF:
+ case 0xF0:
+ case 0xF5:
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config max1730x_regmap_cfg = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = MAX1720X_VFSOC,
+ .readable_reg = max1730x_is_reg,
+ .volatile_reg = max1730x_is_reg,
+};
+
+static bool max1730x_is_nvram_reg(struct device *dev, unsigned int reg)
+{
+ return (reg >= MAX1730X_NVRAM_START &&
+ reg <= MAX1730X_HISTORY_END);
+}
+
+const struct regmap_config max1730x_regmap_nvram_cfg = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = MAX1730X_HISTORY_END,
+ .readable_reg = max1730x_is_nvram_reg,
+ .volatile_reg = max1730x_is_nvram_reg,
+};
+
+/** ------------------------------------------------------------------------ */
+
+/* see b/119416045 for layout */
+static const struct max17x0x_reg max1730x[] = {
+ [MAX17X0X_TAG_avgc] = { ATOM_INIT_REG16(MAX1730X_AVGCURRENT)},
+ [MAX17X0X_TAG_cnfg] = { ATOM_INIT_REG16(MAX1730X_CONFIG)},
+ [MAX17X0X_TAG_mmdv] = { ATOM_INIT_REG16(MAX1730X_MAXMINVOLT)},
+ [MAX17X0X_TAG_vcel] = { ATOM_INIT_REG16(MAX1730X_VCELL)},
+ [MAX17X0X_TAG_temp] = { ATOM_INIT_REG16(MAX1730X_TEMP)},
+ [MAX17X0X_TAG_curr] = { ATOM_INIT_REG16(MAX1730X_CURRENT)},
+ [MAX17X0X_TAG_mcap] = { ATOM_INIT_REG16(MAX1730X_MIXCAP)},
+ [MAX17X0X_TAG_avgr] = { ATOM_INIT_REG16(MAX1730X_NMANFCTRNAME1) },
+ [MAX17X0X_TAG_vfsoc] = { ATOM_INIT_REG16(MAX1720X_VFSOC)},
+ [MAX17X0X_TAG_vfocv] = { ATOM_INIT_REG16(MAX1720X_VFOCV)},
+
+ [MAX17X0X_TAG_BCNT] = { ATOM_INIT_MAP(0x8e, 0x8f, 0x9d, 0x9e, 0x9f,
+ 0xb2, 0xb4, 0xb6, 0xc7, 0xe2)},
+ [MAX17X0X_TAG_SNUM] = { ATOM_INIT_MAP(0xce, 0xe6, 0xe7, 0xe8, 0xe9,
+ 0xea, 0xeb, 0xec, 0xed, 0xee,
+ 0xef) },
+
+ [MAX17X0X_TAG_HSTY] = { ATOM_INIT_SET(0xf0, 0xf2, 0xfb, 0xfe, 0xff) },
+ [MAX17X0X_TAG_BCEA] = { ATOM_INIT_SET(MAX1730X_NMANFCTRNAME0,
+ MAX1730X_NDPLIMIT,
+ MAX1730X_NSCOCVLIM) },
+ [MAX17X0X_TAG_rset] = { ATOM_INIT_SET16(MAX1730X_CONFIG2,
+ MAX1730X_COMMAND_FUEL_GAUGE_RESET,
+ 700)},
+ [MAX17X0X_TAG_BRES] = { ATOM_INIT_SET(MAX1730X_NMANFCTRNAME1,
+ MAX1730X_NMANFCTRNAME0) },
+};
+
+#endif
\ No newline at end of file
diff --git a/max20339.c b/max20339.c
new file mode 100644
index 0000000..ff39b6d
--- /dev/null
+++ b/max20339.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020, Google Inc
+ *
+ * MAX20339 OVP and LS driver
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s " fmt, __func__
+
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+
+#define MAX20339_OVLOSEL 0x11
+#define MAX20339_OVLOSEL_INOVLOSEL_14_5 0x2
+#define MAX20339_IN_CTR 0x10
+
+#define MAX20339_POLL_ATTEMPTS 10
+#define MAX20339_INT2_REG 0x5
+#define MAX20339_INT2_LSW1CLOSEDI (1 << 0)
+#define MAX20339_INT3_REG 0x6
+#define MAX20339_INT3_LSW2CLOSEDI (1 << 0)
+
+#define MAX20339_SW_CNTL_REG 0xA
+#define MAX20339_SW_CNTL_LSW1_EN_SHIFT 0
+#define MAX20339_SW_CNTL_LSW1_EN_MASK 0x1
+#define MAX20339_SW_CNTL_LSW1_OV_SHIFT 1
+#define MAX20338_SW_CNTL_LSW1_OV_EN_MASK 0x2
+#define MAX20339_SW_CNTL_LSW2_EN_SHIFT 4
+#define MAX20339_SW_CNTL_LSW2_EN_MASK 0x10
+#define MAX20338_SW_CNTL_LSW2_OV_EN_SHIFT 5
+#define MAX20338_SW_CNTL_LSW2_OV_EN_MASK 0x20
+
+#define MAX20339_MIN_GPIO 0
+#define MAX20339_MAX_GPIO 1
+#define MAX20339_NUM_GPIOS 2
+#define MAX20339_LSW1_OFF 0
+#define MAX20339_LSW2_OFF 1
+
+struct max20339_ovp {
+ struct i2c_client *client;
+ struct regmap *regmap;
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio;
+#endif
+};
+
+static const struct regmap_range max20339_ovp_range[] = {
+ regmap_reg_range(0x0, 0x2f)
+};
+
+const struct regmap_access_table max20339_ovp_write_table = {
+ .yes_ranges = max20339_ovp_range,
+ .n_yes_ranges = ARRAY_SIZE(max20339_ovp_range),
+};
+
+static const struct regmap_config max20339_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x2f,
+ .wr_table = &max20339_ovp_write_table,
+};
+
+static int max20339_init_regs(struct regmap *regmap, struct device *dev)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(regmap, MAX20339_OVLOSEL, &val);
+ if (ret < 0) {
+ dev_err(dev, "OVLSEL read error: ret %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "OVLOSEL default: %#x\n", val);
+
+ ret = regmap_write(regmap, MAX20339_OVLOSEL,
+ MAX20339_OVLOSEL_INOVLOSEL_14_5);
+ if (ret < 0) {
+ dev_err(dev, "OVLSEL write error: ret %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(regmap, MAX20339_IN_CTR, &val);
+ if (ret < 0) {
+ dev_err(dev, "IN_CTR read error: ret %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "IN_CTR default: %#x\n", val);
+
+ /* Disable & enable to make OVLOSEL reflect */
+ ret = regmap_write(regmap, MAX20339_IN_CTR, 0);
+ if (ret < 0) {
+ dev_err(dev, "IN_CTR write error: ret %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_write(regmap, MAX20339_IN_CTR, val);
+ if (ret < 0) {
+ dev_err(dev, "IN_CTR write error: ret %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_GPIOLIB
+static int max20339_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ return GPIOF_DIR_OUT;
+}
+
+static int max20339_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+ unsigned int val;
+ u8 mask;
+ u8 shift;
+ struct max20339_ovp *ovp = gpiochip_get_data(chip);
+
+ switch (offset) {
+ case MAX20339_LSW1_OFF:
+ mask = MAX20339_SW_CNTL_LSW1_EN_MASK;
+ shift = MAX20339_SW_CNTL_LSW1_EN_SHIFT;
+ break;
+ case MAX20339_LSW2_OFF:
+ mask = MAX20339_SW_CNTL_LSW2_EN_MASK;
+ shift = MAX20339_SW_CNTL_LSW2_EN_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_read(ovp->regmap, MAX20339_SW_CNTL_REG, &val);
+ if (ret < 0) {
+ dev_err(&ovp->client->dev, "SW_CNTL read error: ret %d\n", ret);
+ return ret;
+ }
+
+ return (val & mask) >> shift;
+}
+
+static void max20339_gpio_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret;
+ unsigned int tmp;
+ bool change;
+ u8 mask;
+ u8 shift;
+ u8 int_reg; /* regulator to poll for update */
+ u8 closed_fld; /* field to read for closed change */
+ int i;
+ struct max20339_ovp *ovp = gpiochip_get_data(chip);
+
+ switch (offset) {
+ case MAX20339_LSW1_OFF:
+ mask = MAX20339_SW_CNTL_LSW1_EN_MASK;
+ shift = MAX20339_SW_CNTL_LSW1_EN_SHIFT;
+ int_reg = MAX20339_INT2_REG;
+ closed_fld = MAX20339_INT2_LSW1CLOSEDI;
+ break;
+ case MAX20339_LSW2_OFF:
+ mask = MAX20339_SW_CNTL_LSW2_EN_MASK;
+ shift = MAX20339_SW_CNTL_LSW2_EN_SHIFT;
+ int_reg = MAX20339_INT3_REG;
+ closed_fld = MAX20339_INT3_LSW2CLOSEDI;
+ break;
+ default:
+ return;
+ }
+
+ tmp = (!!value << shift);
+ ret = regmap_update_bits_base(ovp->regmap, MAX20339_SW_CNTL_REG, mask, tmp,
+ &change, false, false);
+ if (ret < 0)
+ dev_err(&ovp->client->dev, "SW_CNTL update error: ret %d\n", ret);
+
+ /* poll until update seen */
+ for (i = 0; i < MAX20339_POLL_ATTEMPTS; i++) {
+ ret = regmap_read(ovp->regmap, int_reg, &tmp);
+ if (tmp & closed_fld)
+ break;
+ mdelay(20);
+ }
+
+}
+#endif
+
+static int max20339_probe(struct i2c_client *client,
+ const struct i2c_device_id *i2c_id)
+{
+ struct max20339_ovp *ovp;
+ int ret = 0;
+
+ ovp = devm_kzalloc(&client->dev, sizeof(*ovp), GFP_KERNEL);
+ if (!ovp)
+ return -ENOMEM;
+
+ ovp->client = client;
+ ovp->regmap = devm_regmap_init_i2c(client,
+ &max20339_regmap_config);
+ if (IS_ERR(ovp->regmap)) {
+ dev_err(&client->dev, "Regmap init failed\n");
+ return PTR_ERR(ovp->regmap);
+ }
+
+ max20339_init_regs(ovp->regmap, &client->dev);
+
+#ifdef CONFIG_GPIOLIB
+ /* Setup GPIO cotroller */
+ ovp->gpio.owner = THIS_MODULE;
+ ovp->gpio.parent = &client->dev;
+ ovp->gpio.label = "max20339_gpio";
+ ovp->gpio.get_direction = max20339_gpio_get_direction;
+ ovp->gpio.get = max20339_gpio_get;
+ ovp->gpio.set = max20339_gpio_set;
+ ovp->gpio.base = -1;
+ ovp->gpio.ngpio = MAX20339_NUM_GPIOS;
+ ovp->gpio.can_sleep = true;
+ ovp->gpio.of_node = of_find_node_by_name(client->dev.of_node,
+ ovp->gpio.label);
+ if (!ovp->gpio.of_node)
+ dev_err(&client->dev, "Failed to find %s DT node\n",
+ ovp->gpio.label);
+
+ ret = devm_gpiochip_add_data(&client->dev, &ovp->gpio, ovp);
+ if (ret)
+ dev_err(&client->dev, "Failed to initialize gpio chip\n");
+#endif
+
+ return ret;
+}
+
+static int max20339_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+static const struct i2c_device_id max20339_id[] = {
+ { "max20339ovp", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max20339_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id max20339_of_match[] = {
+ { .compatible = "max20339ovp", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, max20339_of_match);
+#endif
+
+static struct i2c_driver max20339_i2c_driver = {
+ .driver = {
+ .name = "max20339ovp",
+ .of_match_table = of_match_ptr(max20339_of_match),
+ },
+ .probe = max20339_probe,
+ .remove = max20339_remove,
+ .id_table = max20339_id,
+};
+module_i2c_driver(max20339_i2c_driver);
+
+MODULE_AUTHOR("Badhri Jagan Sridharan <[email protected]>");
diff --git a/max77729_charger.c b/max77729_charger.c
new file mode 100644
index 0000000..75ecde1
--- /dev/null
+++ b/max77729_charger.c
@@ -0,0 +1,1230 @@
+/*
+ * Copyright 2019 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/ctype.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include "gvotable.h"
+
+#ifdef CONFIG_DEBUG_FS
+# include <linux/debugfs.h>
+# include <linux/seq_file.h>
+#endif
+
+struct max77729_chgr_data {
+ struct device *dev;
+ struct power_supply *psy;
+ struct regmap *regmap;
+ int irq_gpio;
+
+ struct gvotable_election *mode_votable;
+ struct gvotable_election *dc_suspend_votable;
+ struct gvotable_election *dc_icl_votable;
+
+ bool input_suspend;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *de;
+ u64 irq_count;
+ u64 irq_seen;
+#endif
+
+ struct mutex io_lock;
+};
+
+enum max77729_chg_register {
+ MAX77729_CHG_INT = 0xB0,
+ MAX77729_CHG_INT_MASK = 0xB1,
+ MAX77729_CHG_INT_OK = 0xB2,
+ MAX77729_CHG_DETAILS_00 = 0xB3,
+ MAX77729_CHG_DETAILS_01 = 0xB4,
+ MAX77729_CHG_DETAILS_02 = 0xB5,
+ MAX77729_CHG_CNFG_00 = 0xB7,
+ MAX77729_CHG_CNFG_01 = 0xB8,
+ MAX77729_CHG_CNFG_02 = 0xB9,
+ MAX77729_CHG_CNFG_03 = 0xBA,
+ MAX77729_CHG_CNFG_04 = 0xBB,
+ MAX77729_CHG_CNFG_05 = 0xBC,
+ MAX77729_CHG_CNFG_06 = 0xBD,
+ MAX77729_CHG_CNFG_07 = 0xBE,
+ MAX77729_CHG_CNFG_08 = 0xBF,
+ MAX77729_CHG_CNFG_09 = 0xC0,
+ MAX77729_CHG_CNFG_10 = 0xC1,
+ MAX77729_CHG_CNFG_11 = 0xC2,
+ MAX77729_CHG_CNFG_12 = 0xC3,
+};
+
+/*
+ * function to return bits:
+ * name: name used for function
+ * h: high value of bit range
+ * l: low value of bit range
+ * for single bit h == l
+ *
+ * given BIT_RANGE_FUNCS(test, 6, 5) generates:
+ * _test_get(uint8_t r) and _test_set(uint8_t r, uint8_t v)
+ */
+#define BIT_RANGE_FUNCS(name, h, l) \
+ static inline uint8_t _ ## name ## _set(uint8_t r, uint8_t v) \
+ { \
+ return ((r & ~GENMASK(h, l)) | v << l); \
+ } \
+ \
+ static inline uint8_t _ ## name ## _get(uint8_t r) \
+ { \
+ return ((r & GENMASK(h, l)) >> l); \
+ }
+
+BIT_RANGE_FUNCS(chg_int_ok_aicl_ok, 7, 7)
+BIT_RANGE_FUNCS(chg_int_ok_chgin_ok, 6, 6)
+BIT_RANGE_FUNCS(chg_int_ok_wcin_ok, 5, 5)
+BIT_RANGE_FUNCS(chg_int_ok_chg_ok, 4, 4)
+BIT_RANGE_FUNCS(chg_int_ok_bat_ok, 3, 3)
+BIT_RANGE_FUNCS(chg_int_ok_batp_ok, 2, 2)
+BIT_RANGE_FUNCS(chg_int_ok_disqbat_ok, 1, 1)
+BIT_RANGE_FUNCS(chg_int_ok_byp_ok, 0, 0)
+
+BIT_RANGE_FUNCS(details_00_no_autoibus, 7, 7)
+BIT_RANGE_FUNCS(details_00_chgin_dtls, 6, 5)
+BIT_RANGE_FUNCS(details_00_wcin_dtls, 4, 3)
+BIT_RANGE_FUNCS(details_00_spsn_dtls, 2, 1)
+BIT_RANGE_FUNCS(details_00_nobat, 0, 0)
+
+BIT_RANGE_FUNCS(details_01_treg, 7, 7)
+BIT_RANGE_FUNCS(details_01_batt_dtls, 6, 4)
+BIT_RANGE_FUNCS(details_01_chrg_dtls, 3, 0)
+
+BIT_RANGE_FUNCS(details_02_rsrv, 7, 4)
+BIT_RANGE_FUNCS(details_02_byp_dtls, 3, 0)
+
+BIT_RANGE_FUNCS(cnfg_00_spsn_det_en, 7, 7)
+BIT_RANGE_FUNCS(cnfg_00_disibs, 6, 6)
+BIT_RANGE_FUNCS(cnfg_00_spr, 5, 5)
+BIT_RANGE_FUNCS(cnfg_00_wdten, 4, 4)
+BIT_RANGE_FUNCS(cnfg_00_mode, 3, 0)
+
+BIT_RANGE_FUNCS(cnfg_01_pqen, 7, 7)
+BIT_RANGE_FUNCS(cnfg_01_lsel, 6, 6)
+BIT_RANGE_FUNCS(cnfg_01_rstrt, 5, 4)
+BIT_RANGE_FUNCS(cnfg_01_recycle_en, 3, 3)
+BIT_RANGE_FUNCS(cnfg_01_fchgtime, 2, 0)
+
+BIT_RANGE_FUNCS(cnfg_02_otg_ilim, 7, 6)
+BIT_RANGE_FUNCS(cnfg_02_chrgcc, 5, 0)
+
+BIT_RANGE_FUNCS(cnfg_03_sys_track_dis, 7, 7)
+BIT_RANGE_FUNCS(cnfg_03_auto_fship_mode_en, 6, 6)
+BIT_RANGE_FUNCS(cnfg_03_to_time, 5, 3)
+BIT_RANGE_FUNCS(cnfg_03_to_ith, 2, 0)
+
+BIT_RANGE_FUNCS(cnfg_04_chg_cv_prm, 5, 0)
+
+BIT_RANGE_FUNCS(cnfg_05_dis_ir_ctrl, 6, 4)
+BIT_RANGE_FUNCS(cnfg_05_uno_ilim, 6, 4)
+BIT_RANGE_FUNCS(cnfg_05_b2sovrc, 3, 0)
+
+BIT_RANGE_FUNCS(cnfg_06_b2sovrc_dtc, 7, 7)
+BIT_RANGE_FUNCS(cnfg_06_slowlx, 6, 5)
+BIT_RANGE_FUNCS(cnfg_06_dis_aicl, 4, 4)
+BIT_RANGE_FUNCS(cnfg_06_chgprot, 3, 2)
+BIT_RANGE_FUNCS(cnfg_06_wdtclr, 1, 0)
+
+BIT_RANGE_FUNCS(cnfg_07_wd_qbatoff, 7, 7)
+BIT_RANGE_FUNCS(cnfg_07_regtemp, 6, 3)
+BIT_RANGE_FUNCS(cnfg_07_fmbst, 2, 2)
+BIT_RANGE_FUNCS(cnfg_07_fgsrc, 1, 1)
+BIT_RANGE_FUNCS(cnfg_07_fship_mode, 0, 0)
+
+BIT_RANGE_FUNCS(cnfg_08_rsvd, 7, 2)
+BIT_RANGE_FUNCS(cnfg_08_fsw, 1, 0)
+
+BIT_RANGE_FUNCS(cnfg_09_chg_en, 7, 7)
+BIT_RANGE_FUNCS(cnfg_09_chgin_ilim, 6, 0)
+
+BIT_RANGE_FUNCS(cnfg_10_inlim_clk, 7, 6)
+BIT_RANGE_FUNCS(cnfg_10_wcin_ilim, 5, 0)
+
+BIT_RANGE_FUNCS(cnfg_11_en_fg_ilim_ctrl, 7, 7)
+BIT_RANGE_FUNCS(cnfg_11_vbypset, 6, 0)
+
+BIT_RANGE_FUNCS(cnfg_12_spr, 7, 7)
+BIT_RANGE_FUNCS(cnfg_12_wcinsel, 6, 6)
+BIT_RANGE_FUNCS(cnfg_12_chginsel, 5, 5)
+BIT_RANGE_FUNCS(cnfg_12_vchgin_reg, 4, 3)
+BIT_RANGE_FUNCS(cnfg_12_wcin_reg, 2, 1)
+BIT_RANGE_FUNCS(cnfg_12_diskip, 0, 0)
+
+/* CHG_DETAILS_00:CHGIN_DTLS */
+#define CHGIN_DTLS_VBUS_INVALID_VCHGIN_RISING 0x00
+#define CHGIN_DTLS_VBUS_INVALID_VCHGIN_FALLING 0x01
+#define CHGIN_DTLS_VBUS_INVALID_VCHGIN_OVLO 0x02
+#define CHGIN_DTLS_VBUS_VALID 0x04
+
+/* CHG_DETAILS_01:CHG_DTLS */
+#define CHGR_DTLS_DEAD_BATTERY_MODE 0x00
+#define CHGR_DTLS_FAST_CHARGE_CONST_CURRENT_MODE 0x01
+#define CHGR_DTLS_FAST_CHARGE_CONST_VOLTAGE_MODE 0x02
+#define CHGR_DTLS_TOP_OFF_MODE 0x03
+#define CHGR_DTLS_DONE_MODE 0x04
+#define CHGR_DTLS_TIMER_FAULT_MODE 0x06
+#define CHGR_DTLS_DETBAT_HIGH_SUSPEND_MODE 0x07
+#define CHGR_DTLS_OFF_MODE 0x08
+#define CHGR_DTLS_OFF_HIGH_TEMP_MODE 0x0a
+#define CHGR_DTLS_OFF_WATCHDOG_MODE 0x0b
+
+/* CHG_CNFG_00:MODE */
+#define CHGR_MODE_ALL_OFF 0x00
+#define CHGR_MODE_ALL_OFF_1 0x01
+#define CHGR_MODE_ALL_OFF_2 0x02
+#define CHGR_MODE_ALL_OFF_3 0x03
+#define CHGR_MODE_BUCK_ON 0x04
+#define CHGR_MODE_CHGR_BUCK_ON 0x05
+#define CHGR_MODE_CHGR_BUCK_ON_6 0x06
+#define CHGR_MODE_CHGR_BUCK_ON_7 0x07
+#define CHGR_MODE_BOOST_UNO_ON 0x08
+#define CHGR_MODE_BOOST_ON 0x09
+#define CHGR_MODE_OTG_BOOST_ON 0x0a
+#define CHGR_MODE_RESERVED_B 0x0b
+#define CHGR_MODE_BUCK_BOOST_UNO_ON 0x0c
+#define CHGR_MODE_CHGR_BUCK_BOOST_UNO_ON 0x0d
+#define CHGR_MODE_OTG_BUCK_BOOST_ON 0x0e
+#define CHGR_MODE_CHGR_OTG_BUCK_BOOST_ON 0x0f
+
+/* CHG_CNFG_02 */
+#define CHGCC_50_RANGE_MIN_STEP 0x02
+#define CHGCC_50_RANGE_MIN_UA 100000
+#define CHGCC_50_RANGE_INC_UA 50000
+#define CHGCC_50_RANGE_MAX_STEP 0x3f
+#define CHGCC_50_RANGE_MAX_UA 3150000
+
+/* CHG_CNFG_04 */
+#define CHG_CV_PRM_CONT_100_RANGE_MIN_STEP 0x38
+#define CHG_CV_PRM_CONT_100_RANGE_MIN_UV 3800000
+#define CHG_CV_PRM_CONT_100_RANGE_MAX_STEP 0x39
+#define CHG_CV_PRM_CONT_100_RANGE_MAX_UV 3900000
+#define CHG_CV_PRM_CONT_100_RANGE_INC_UV 100000
+
+#define CHG_CV_PRM_CONT_50_RANGE_MIN_STEP 0x00
+#define CHG_CV_PRM_CONT_50_RANGE_MIN_UV 4000000
+#define CHG_CV_PRM_CONT_50_RANGE_MAX_STEP 0x04
+#define CHG_CV_PRM_CONT_50_RANGE_MAX_UV 4200000
+#define CHG_CV_PRM_CONT_50_RANGE_INC_UV 50000
+
+#define CHG_CV_PRM_CONT_10_RANGE_MIN_STEP 0x05
+#define CHG_CV_PRM_CONT_10_RANGE_MIN_UV 4200000
+#define CHG_CV_PRM_CONT_10_RANGE_MAX_STEP 0x23
+#define CHG_CV_PRM_CONT_10_RANGE_MAX_UV 4500000
+#define CHG_CV_PRM_CONT_10_RANGE_INC_UV 10000
+
+/* CHG_CNFG_09:CHGIN_ILIM */
+#define CHGIN_ILIM_25_RANGE_MIN_STEP 0x03
+#define CHGIN_ILIM_25_RANGE_MIN_UA 100000
+#define CHGIN_ILIM_25_RANGE_MAX_STEP 0x7F
+#define CHGIN_ILIM_25_RANGE_INC_UA 25000
+#define CHGIN_ILIM_25_RANGE_MAX_UA 3200000
+
+static inline int max77729_reg_read(struct max77729_chgr_data *data,
+ uint8_t reg, uint8_t *val)
+{
+ int ret;
+ int ival;
+ struct regmap *regmap = data->regmap;
+
+ ret = regmap_read(regmap, reg, &ival);
+ if (!ret)
+ *val = 0xFF & ival;
+
+ return ret;
+}
+
+static inline int max77729_reg_write(struct max77729_chgr_data *data,
+ uint8_t reg, uint8_t val)
+{
+ int ret;
+ struct regmap *regmap = data->regmap;
+
+ ret = regmap_write(regmap, reg, val);
+
+ return ret;
+}
+
+static inline int max77729_reg_update(struct max77729_chgr_data *data,
+ uint8_t reg, uint8_t msk, uint8_t val)
+{
+ int ret;
+ unsigned tmp;
+ struct regmap *regmap = data->regmap;
+
+ mutex_lock(&data->io_lock);
+ ret = regmap_read(regmap, reg, &tmp);
+ if (!ret) {
+ tmp &= ~msk;
+ tmp |= val;
+ ret = regmap_write(regmap, reg, tmp);
+ }
+ mutex_unlock(&data->io_lock);
+
+ return ret;
+}
+
+bool max77729_chg_is_reg(struct device *dev, unsigned int reg)
+{
+ return (reg >= 0xB0) && (reg <= 0xC3);
+}
+
+static const struct regmap_config max77729_chg_regmap_cfg = {
+ .name = "max77729_charger",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = 0xC3,
+ .readable_reg = max77729_chg_is_reg,
+ .volatile_reg = max77729_chg_is_reg,
+
+};
+
+static inline int chgr_x2y(int x, int min_x, int min_y, int slope)
+{
+ return x < min_x ? min_y : ((x - min_x) * slope + min_y);
+}
+
+static inline int chgr_y2x(int y, int min_x, int min_y, int m)
+{
+ return y < min_y ? min_x : ((y - min_y) / m + min_x);
+}
+
+/* return not online (no error) when not present */
+static int max77729_is_online(struct max77729_chgr_data *data, int *online)
+{
+ uint8_t reg;
+
+ *online = (max77729_reg_read(data, MAX77729_CHG_INT_OK, ®) == 0) &&
+ (_chg_int_ok_wcin_ok_get(reg) ||
+ _chg_int_ok_chgin_ok_get(reg));
+
+ return 0;
+}
+
+
+static int max77729_get_status(struct max77729_chgr_data *data, int *status)
+{
+ uint8_t val;
+ int online, ret;
+
+ ret = max77729_is_online(data, &online);
+ if (ret < 0 || !online) {
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ ret = max77729_reg_read(data, MAX77729_CHG_DETAILS_01, &val);
+ if (ret < 0)
+ return ret;
+
+ switch (_details_01_chrg_dtls_get(val)) {
+ case CHGR_DTLS_DEAD_BATTERY_MODE:
+ case CHGR_DTLS_FAST_CHARGE_CONST_CURRENT_MODE:
+ case CHGR_DTLS_FAST_CHARGE_CONST_VOLTAGE_MODE:
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case CHGR_DTLS_TOP_OFF_MODE:
+ case CHGR_DTLS_DONE_MODE:
+ *status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case CHGR_DTLS_TIMER_FAULT_MODE:
+ case CHGR_DTLS_DETBAT_HIGH_SUSPEND_MODE:
+ case CHGR_DTLS_OFF_MODE:
+ case CHGR_DTLS_OFF_HIGH_TEMP_MODE:
+ case CHGR_DTLS_OFF_WATCHDOG_MODE:
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ *status = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return ret;
+}
+
+static int max77729_get_charge_done(struct max77729_chgr_data *data, int *done)
+{
+ int ret;
+ int status;
+
+ ret = max77729_get_status(data, &status);
+ if (ret < 0)
+ return ret;
+
+ *done = (status == CHGR_DTLS_TOP_OFF_MODE) ||
+ (status == CHGR_DTLS_DONE_MODE);
+ return 0;
+}
+
+/*
+ * right now only accept CHGR_MODE_
+ * TODO: use a comparator that add/remove capabilities, might require some
+ * changes to the votables implementation since the current vote will be
+ * created based on the actual votes instead of being one of the vote.
+ */
+int max77729_mode_comparator(void *l, void *r)
+{
+ const int a = *((int *) &l);
+ const int b = *((int *) &r);
+ int ret;
+
+ if (a == CHGR_MODE_ALL_OFF) {
+ ret = (b == CHGR_MODE_ALL_OFF) ? 0 : -1;
+ } else if (b == CHGR_MODE_ALL_OFF) {
+ ret = 1;
+ } else {
+ ret = gvotable_comparator_most_recent(l, r);
+ }
+
+ return ret;
+}
+
+static int max77729_reset_charger_state(struct max77729_chgr_data *data)
+{
+ uint8_t reg, reg_new;
+ int ret;
+
+ mutex_lock(&data->io_lock);
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_00, ®);
+ if (ret < 0) {
+ dev_err(data->dev, "cannot read CNFG_00 (%d)\n", ret);
+ goto unlock_done;
+ }
+
+ reg_new = _cnfg_00_mode_set(reg, CHGR_MODE_BUCK_ON);
+ ret = max77729_reg_write(data, MAX77729_CHG_CNFG_00, reg_new);
+ if (ret < 0) {
+ dev_err(data->dev, "cannot disable charging (%d)\n", ret);
+ goto unlock_done;
+ }
+
+ ret = max77729_reg_write(data, MAX77729_CHG_CNFG_00, reg);
+ if (ret < 0) {
+ dev_err(data->dev, "cannot restore charging (%d)\n", ret);
+ goto unlock_done;
+ }
+
+unlock_done:
+ mutex_unlock(&data->io_lock);
+ return ret;
+}
+
+static void max77729_mode_callback(struct gvotable_election *el,
+ const char *reason, void *value)
+{
+ struct max77729_chgr_data *data = gvotable_get_data(el);
+ int mode = (int)value;
+ uint8_t reg, reg_new;
+ int ret;
+
+ switch (mode) {
+ case CHGR_MODE_ALL_OFF:
+ case CHGR_MODE_BUCK_ON:
+ case CHGR_MODE_CHGR_BUCK_ON:
+ case CHGR_MODE_BOOST_UNO_ON:
+ case CHGR_MODE_BOOST_ON:
+ case CHGR_MODE_OTG_BOOST_ON:
+ case CHGR_MODE_BUCK_BOOST_UNO_ON:
+ case CHGR_MODE_CHGR_BUCK_BOOST_UNO_ON:
+ case CHGR_MODE_OTG_BUCK_BOOST_ON:
+ case CHGR_MODE_CHGR_OTG_BUCK_BOOST_ON:
+ break;
+ default:
+ dev_err(data->dev, "mode %d not supported\n", mode);
+ return;
+ }
+
+ dev_info(data->dev, "Vote CHARGER_MODE %d reason=%s\n", mode,
+ reason ? reason : "");
+
+ mutex_lock(&data->io_lock);
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_00, ®);
+ if (ret < 0) {
+ dev_err(data->dev, "cannot read CNFG_00 (%d)\n", ret);
+ goto unlock_done;
+ }
+
+ /*
+ * the test is somewhat redundant since the callaback is called only
+ * when the vote change
+ */
+ reg_new = _cnfg_00_mode_set(reg, mode);
+ if (reg_new != reg)
+ ret = max77729_reg_write(data, MAX77729_CHG_CNFG_00, reg_new);
+
+ if (ret < 0)
+ dev_err(data->dev, "cannot set CNFG_00 (%d)\n", ret);
+
+unlock_done:
+ mutex_unlock(&data->io_lock);
+}
+
+
+static void max77729_dc_suspend_vote_callback(struct gvotable_election *el,
+ const char *reason, void *value)
+{
+ struct max77729_chgr_data *data = gvotable_get_data(el);
+ int result = (int)value;
+
+ /* TODO: disable WCIN */
+
+ dev_info(data->dev, "DC_SUSPEND reason=%s, value=%d suspend=%d\n",
+ reason ? reason : "", result, result >= 0);
+}
+
+static void max77729_dcicl_callback(struct gvotable_election *el,
+ const char *reason, void *value)
+{
+ struct max77729_chgr_data *data = gvotable_get_data(el);
+ int dc_icl = (int)value;
+ const bool suspend = dc_icl == 0;
+ int ret;
+
+ ret = gvotable_cast_vote(data->dc_suspend_votable, "DC_ICL",
+ (void *)1,
+ suspend);
+ if (ret < 0)
+ dev_err(data->dev, "cannot set DC_SUSPEND=%d (%d)\n",
+ suspend, ret);
+}
+
+static int max77729_set_charge_enabled(struct max77729_chgr_data *data,
+ int enabled, const char *reason)
+{
+ return gvotable_cast_vote(data->mode_votable, reason,
+ (void *)CHGR_MODE_CHGR_BUCK_ON,
+ enabled);
+}
+
+static int max77729_input_suspend(struct max77729_chgr_data *data,
+ bool enabled, const char *reason)
+{
+ data->input_suspend = enabled;
+ return gvotable_cast_vote(data->mode_votable, reason,
+ (void *)CHGR_MODE_ALL_OFF,
+ enabled);
+}
+
+
+static int max77729_set_ilim_max_ua(struct max77729_chgr_data *data, int ua)
+{
+ const bool suspend = ua == 0;
+ uint8_t reg, reg_new;
+ int ret, steps;
+
+ if (ua < 0)
+ return 0;
+
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_09, ®);
+ if (ret < 0)
+ return ret;
+
+ ua = min(ua, CHGIN_ILIM_25_RANGE_MAX_UA);
+
+ steps = chgr_y2x(ua, CHGIN_ILIM_25_RANGE_MIN_STEP,
+ CHGIN_ILIM_25_RANGE_MIN_UA,
+ CHGIN_ILIM_25_RANGE_INC_UA);
+
+ /* do not cache the values*/
+ reg_new = _cnfg_09_chgin_ilim_set(reg, steps);
+ ret = max77729_reg_write(data, MAX77729_CHG_CNFG_09, reg_new);
+ if (ret == 0)
+ ret = max77729_input_suspend(data, suspend, "ILIM");
+
+ return ret;
+}
+
+
+static int max77729_get_ilim_max_ua(struct max77729_chgr_data *data, int *ua)
+{
+ uint8_t reg;
+ int ret, steps;
+
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_09, ®);
+ if (ret < 0)
+ return ret;
+
+ steps = _cnfg_09_chgin_ilim_get(reg);
+ *ua = chgr_x2y(steps, CHGIN_ILIM_25_RANGE_MIN_STEP,
+ CHGIN_ILIM_25_RANGE_MIN_UA, CHGIN_ILIM_25_RANGE_INC_UA);
+
+ if (data->input_suspend)
+ *ua = 0;
+
+ return 0;
+}
+
+/*
+ * TODO: now return the limit set in 09 since we only use this for irdrop
+ * compensation and debug. We should really read this from the FG in the same
+ * way the bootloader does.
+ */
+static int max77729_get_current_now_ua(struct max77729_chgr_data *data, int *ua)
+{
+ return max77729_get_ilim_max_ua(data, ua);
+}
+
+static int max77729_get_charge_voltage_max_uv(struct max77729_chgr_data *data, int *uv)
+{
+ uint8_t reg;
+ int ret, steps;
+
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_04, ®);
+ if (ret < 0)
+ return ret;
+
+ steps = _cnfg_04_chg_cv_prm_get(reg);
+ if (steps >= CHG_CV_PRM_CONT_100_RANGE_MIN_STEP) {
+ *uv = chgr_x2y(steps, CHG_CV_PRM_CONT_100_RANGE_MIN_STEP,
+ CHG_CV_PRM_CONT_100_RANGE_MIN_UV,
+ CHG_CV_PRM_CONT_100_RANGE_INC_UV);
+ } else if (steps >= CHG_CV_PRM_CONT_10_RANGE_MIN_STEP) {
+ *uv = chgr_x2y(steps, CHG_CV_PRM_CONT_10_RANGE_MIN_STEP,
+ CHG_CV_PRM_CONT_10_RANGE_MIN_UV,
+ CHG_CV_PRM_CONT_10_RANGE_INC_UV);
+ } else {
+ *uv = chgr_x2y(steps, CHG_CV_PRM_CONT_50_RANGE_MIN_STEP,
+ CHG_CV_PRM_CONT_50_RANGE_MIN_UV,
+ CHG_CV_PRM_CONT_50_RANGE_INC_UV);
+ }
+
+ return ret;
+}
+
+static int max77729_set_charge_voltage_max_uv(struct max77729_chgr_data *data, int uv)
+{
+ uint8_t reg, reg_new;
+ int ret, steps;
+
+ if (uv < 0)
+ return 0;
+
+ mutex_lock(&data->io_lock);
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_04, ®);
+ if (ret < 0) {
+ mutex_unlock(&data->io_lock);
+ return ret;
+ }
+
+ if (uv < CHG_CV_PRM_CONT_50_RANGE_MIN_UV) {
+ steps = chgr_y2x(uv, CHG_CV_PRM_CONT_100_RANGE_MIN_STEP,
+ CHG_CV_PRM_CONT_100_RANGE_MIN_UV,
+ CHG_CV_PRM_CONT_100_RANGE_INC_UV);
+ } else if (uv < CHG_CV_PRM_CONT_10_RANGE_MIN_UV) {
+ steps = chgr_y2x(uv, CHG_CV_PRM_CONT_50_RANGE_MIN_STEP,
+ CHG_CV_PRM_CONT_50_RANGE_MIN_UV,
+ CHG_CV_PRM_CONT_50_RANGE_INC_UV);
+ } else {
+ uv = min(uv, CHG_CV_PRM_CONT_10_RANGE_MAX_UV);
+
+ steps = chgr_y2x(uv, CHG_CV_PRM_CONT_10_RANGE_MIN_STEP,
+ CHG_CV_PRM_CONT_10_RANGE_MIN_UV,
+ CHG_CV_PRM_CONT_10_RANGE_INC_UV);
+ }
+
+ /* do not cache the values*/
+ reg_new = _cnfg_04_chg_cv_prm_set(reg, steps);
+ ret = max77729_reg_write(data, MAX77729_CHG_CNFG_04, reg_new);
+
+ mutex_unlock(&data->io_lock);
+ return ret;
+}
+
+static int max77729_get_charge_enabled(struct max77729_chgr_data *data,
+ int *enabled)
+{
+ int ret;
+ const void *vote = (const void *)0;
+
+ ret = gvotable_get_current_vote(data->mode_votable, &vote);
+ if (ret < 0)
+ return ret;
+
+ switch ((uintptr_t)vote) {
+ case CHGR_MODE_CHGR_BUCK_ON:
+ case CHGR_MODE_CHGR_BUCK_BOOST_UNO_ON:
+ case CHGR_MODE_CHGR_OTG_BUCK_BOOST_ON:
+ *enabled = 1;
+ break;
+ default:
+ *enabled = 0;
+ break;
+ }
+
+ return ret;
+}
+
+/* 0 ua suspend charging using mode */
+static int max77729_set_charge_current_max_ua(struct max77729_chgr_data *data, int ua)
+{
+ const int enabled = ua != 0;
+ uint8_t reg;
+ int ret;
+
+ if (ua < 0)
+ return 0;
+
+ mutex_lock(&data->io_lock);
+ /* CHG_EN (1<<7) is set in the BL */
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_02, ®);
+ if (ret == 0) {
+ uint8_t reg_new;
+ int steps;
+
+ ua = min(ua, CHGCC_50_RANGE_MAX_UA);
+ steps = chgr_y2x(ua, CHGCC_50_RANGE_MIN_STEP,
+ CHGCC_50_RANGE_MIN_UA,
+ CHGCC_50_RANGE_INC_UA);
+
+ /* do not cache the values*/
+ reg_new = _cnfg_02_chrgcc_set(reg, steps);
+ ret = max77729_reg_write(data, MAX77729_CHG_CNFG_02, reg_new);
+ }
+
+ mutex_unlock(&data->io_lock);
+ if (ret == 0)
+ max77729_set_charge_enabled(data, enabled, "CC_MAX");
+
+ return ret;
+}
+
+static int max77729_get_charge_current_max_ua(struct max77729_chgr_data *data,
+ int *ua)
+{
+ uint8_t reg;
+ int ret;
+
+ ret = max77729_reg_read(data, MAX77729_CHG_CNFG_02, ®);
+ if (ret < 0)
+ return ret;
+
+ if (reg == 0) {
+ *ua = 0;
+ } else {
+ const int steps = _cnfg_02_chrgcc_get(reg);
+
+ *ua = chgr_x2y(steps, CHGCC_50_RANGE_MIN_STEP,
+ CHGCC_50_RANGE_MIN_UA, CHGCC_50_RANGE_INC_UA);
+
+ }
+
+ return ret;
+}
+
+
+static int max77729_get_charge_type(struct max77729_chgr_data *data, int *type)
+{
+ int ret;
+ uint8_t reg;
+
+ ret = max77729_reg_read(data, MAX77729_CHG_DETAILS_01, ®);
+ if (ret < 0)
+ return ret;
+
+ switch(_details_01_chrg_dtls_get(reg)) {
+ case CHGR_DTLS_DEAD_BATTERY_MODE:
+ *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case CHGR_DTLS_FAST_CHARGE_CONST_CURRENT_MODE:
+ *type = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case CHGR_DTLS_FAST_CHARGE_CONST_VOLTAGE_MODE:
+ *type = POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ break;
+ /* This is really DONE, */
+ case CHGR_DTLS_TOP_OFF_MODE:
+ *type = POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ break;
+ case CHGR_DTLS_DONE_MODE:
+ *type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ case CHGR_DTLS_TIMER_FAULT_MODE:
+ case CHGR_DTLS_DETBAT_HIGH_SUSPEND_MODE:
+ case CHGR_DTLS_OFF_MODE:
+ case CHGR_DTLS_OFF_HIGH_TEMP_MODE:
+ case CHGR_DTLS_OFF_WATCHDOG_MODE:
+ *type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ *type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/* TODO: return if AICL is running */
+static int max77729_get_current_limit(struct max77729_chgr_data *data,
+ int *limited)
+{
+ *limited = 0;
+ return 0;
+}
+
+static int max77729_get_present(struct max77729_chgr_data *data, int *present)
+{
+ *present = 1;
+ return 0;
+}
+
+static enum power_supply_property max77729_psy_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_DISABLE,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static int max77729_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *pval)
+{
+ struct max77729_chgr_data *data = power_supply_get_drvdata(psy);
+ int enabled;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = max77729_is_online(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = max77729_get_present(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = max77729_get_current_now_ua(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = max77729_get_ilim_max_ua(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = max77729_get_charge_current_max_ua(data,
+ &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = max77729_get_charge_voltage_max_uv(data,
+ &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ ret = max77729_get_charge_enabled(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ ret = max77729_get_charge_enabled(data, &enabled);
+ if (ret == 0)
+ pval->intval = !enabled;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = max77729_get_status(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = max77729_get_charge_type(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_DONE:
+ ret = max77729_get_charge_done(data, &pval->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ ret = max77729_get_current_limit(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = max77729_get_charge_voltage_max_uv(data,
+ &pval->intval);
+ break;
+
+ /* TODO: implement charger state, fix *_PROP_VOLTAGE_MAX */
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ ret = -EINVAL;
+ break;
+
+ case POWER_SUPPLY_PROP_TAPER_CONTROL:
+ default:
+ dev_err(data->dev, "property (%d) unsupported.\n", psp);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int max77729_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ struct max77729_chgr_data *data =
+ (struct max77729_chgr_data *)power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = max77729_set_ilim_max_ua(data, pval->intval);
+ pr_info("ilim=%d (%d)\n", pval->intval, ret);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = max77729_set_charge_current_max_ua(data,
+ pval->intval);
+ pr_info("charge_current=%d (%d)\n", pval->intval, ret);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = max77729_set_charge_voltage_max_uv(data,
+ pval->intval);
+ pr_info("charge_voltage=%d (%d)\n", pval->intval, ret);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ ret = max77729_set_charge_enabled(data, pval->intval,
+ "USER");
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE: /* ext */
+ ret = max77729_set_charge_enabled(data, !pval->intval,
+ "USER");
+ break;
+ case POWER_SUPPLY_PROP_TAPER_CONTROL:
+ ret = 0;
+ break;
+ default:
+ dev_err(data->dev, "unsupported property: %d\n", psp);
+ ret = -EINVAL;
+ break;
+ };
+
+ return ret;
+}
+
+static int max77729_psy_property_is_writable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int writeable = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_MAX: /* ILIM */
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX: /* same as CHARGE_* */
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE: /* ext */
+ case POWER_SUPPLY_PROP_TAPER_CONTROL:
+ writeable = 1;
+ break;
+ default:
+ break;
+ }
+
+ return writeable;
+}
+
+static struct power_supply_desc max77729_psy_desc = {
+ .name = "max77729-charger",
+ .type = POWER_SUPPLY_TYPE_UNKNOWN,
+ .properties = max77729_psy_props,
+ .num_properties = ARRAY_SIZE(max77729_psy_props),
+ .get_property = max77729_psy_get_property,
+ .set_property = max77729_psy_set_property,
+ .property_is_writeable = max77729_psy_property_is_writable,
+};
+
+
+#ifdef CONFIG_DEBUG_FS
+
+static int max77729_dbg_reset_charger_state(void *d, u64 val)
+{
+ struct max77729_chgr_data *data = d;
+ max77729_reset_charger_state(data);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(max77729_debug_reset_charger_state_fops,
+ NULL, max77729_dbg_reset_charger_state, "%u\n");
+
+static int dbg_init_fs(struct max77729_chgr_data *data)
+{
+ data->de = debugfs_create_dir("max77729_chg", 0);
+ if (!data->de)
+ return -EINVAL;
+
+ debugfs_create_u64("irq_count", 0444, data->de, &data->irq_count);
+ debugfs_create_u64("irq_seen", 0444, data->de, &data->irq_seen);
+ debugfs_create_file("chg_reset", 0200, data->de, data,
+ &max77729_debug_reset_charger_state_fops);
+
+ return 0;
+}
+
+static void dbg_cleanup_fs(struct max77729_chgr_data *data)
+{
+ if (data->de)
+ debugfs_remove_recursive(data->de);
+ data->de = NULL;
+}
+
+#define DBG_INC(count) count++
+#else /* CONFIG_DEBUG_FS */
+static inline int dbg_init_fs(struct max77729_chgr_data *data)
+{
+ return 0;
+}
+static inline void dbg_remove_fs(struct max77729_chgr_data *data) { }
+#define DBG_INC(count)
+#endif
+
+static irqreturn_t max77729_chgr_irq(int irq, void *d)
+{
+ struct max77729_chgr_data *data = (struct max77729_chgr_data *)d;
+ uint8_t reg = 0;
+ int ret;
+
+ DBG_INC(data->irq_seen);
+
+ /* reading the interrupt clears it */
+ ret = max77729_reg_read(data, MAX77729_CHG_INT, ®);
+ pr_debug("IRQ reg=%x (%d)\n", reg, ret);
+ if (ret < 0)
+ dev_err_ratelimited(data->dev, "failed reading CHG_INT ret=%d\n",
+ ret);
+ if (!reg)
+ return IRQ_NONE;
+
+ DBG_INC(data->irq_count);
+
+ power_supply_changed(data->psy);
+ return IRQ_HANDLED;
+}
+
+static int max77729_init_irq(struct i2c_client *client)
+{
+ struct max77729_chgr_data *data = i2c_get_clientdata(client);
+ struct device *dev = &client->dev;
+ int ret = 0;
+
+ data->irq_gpio =
+ of_get_named_gpio(dev->of_node, "max77729,irq-gpio", 0);
+ if (data->irq_gpio < 0) {
+ dev_err(dev, "failed get irq_gpio\n");
+ return -EINVAL;
+ }
+
+ client->irq = gpio_to_irq(data->irq_gpio);
+ ret = devm_request_threaded_irq(data->dev, client->irq, NULL,
+ max77729_chgr_irq,
+ IRQF_TRIGGER_LOW |
+ IRQF_SHARED |
+ IRQF_ONESHOT,
+ dev_name(data->dev), data);
+ if (ret == 0)
+ enable_irq_wake(client->irq);
+
+ return ret;
+}
+
+static int max77729_setup_votables(struct max77729_chgr_data *data)
+{
+ int ret;
+
+ /* DC_SUSPEND votes on CHARGER_MODE */
+ data->mode_votable =
+ gvotable_create_int_election(NULL, max77729_mode_comparator,
+ max77729_mode_callback,
+ data);
+ if (IS_ERR_OR_NULL(data->mode_votable)) {
+ dev_err(data->dev, "Failed to initialize mode votable\n");
+ ret = PTR_ERR(data->mode_votable);
+ data->mode_votable = NULL;
+ return ret;
+ }
+
+ gvotable_set_vote2str(data->mode_votable, gvotable_v2s_uint);
+ gvotable_set_default(data->mode_votable, (void *)CHGR_MODE_BUCK_ON);
+ gvotable_election_set_name(data->mode_votable, "CHARGER_MODE");
+
+ /* DC_ICL votes on DC_SUSPEND */
+ data->dc_suspend_votable =
+ gvotable_create_bool_election(NULL,
+ max77729_dc_suspend_vote_callback,
+ data);
+ if (IS_ERR_OR_NULL(data->dc_suspend_votable)) {
+ ret = PTR_ERR(data->dc_suspend_votable);
+ dev_err(data->dev, "no dc_suspend votable (%d)\n", ret);
+ return ret;
+ }
+
+ gvotable_set_vote2str(data->dc_suspend_votable, gvotable_v2s_int);
+ gvotable_election_set_name(data->dc_suspend_votable, "DC_SUSPEND");
+ gvotable_use_default(data->dc_suspend_votable, true);
+
+ data->dc_icl_votable =
+ gvotable_create_int_election(NULL, gvotable_comparator_int_min,
+ max77729_dcicl_callback,
+ data);
+ if (IS_ERR_OR_NULL(data->dc_icl_votable)) {
+ ret = PTR_ERR(data->dc_icl_votable);
+ dev_err(data->dev, "no dc_icl votable (%d)\n", ret);
+ return ret;
+ }
+
+ gvotable_set_vote2str(data->dc_icl_votable, gvotable_v2s_uint);
+ gvotable_set_default(data->dc_icl_votable, (void *)700000);
+ gvotable_election_set_name(data->dc_icl_votable, "DC_ICL");
+ gvotable_use_default(data->dc_icl_votable, true);
+
+ return 0;
+}
+
+static int max77729_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct max77729_chgr_data *data;
+ struct device *dev = &client->dev;
+ struct power_supply_config chgr_psy_cfg = { };
+ const char *psy_name;
+ int ret = 0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->io_lock);
+
+ data->regmap = devm_regmap_init_i2c(client, &max77729_chg_regmap_cfg);
+ if (IS_ERR(data->regmap)) {
+ dev_err(dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_string(dev->of_node, "max77729,psy-name",
+ &psy_name);
+ if (ret == 0)
+ max77729_psy_desc.name = devm_kstrdup(dev, psy_name,
+ GFP_KERNEL);
+
+ chgr_psy_cfg.drv_data = data;
+ chgr_psy_cfg.supplied_to = NULL;
+ chgr_psy_cfg.num_supplicants = 0;
+ data->psy = devm_power_supply_register(dev, &max77729_psy_desc,
+ &chgr_psy_cfg);
+ if (IS_ERR(data->psy)) {
+ dev_err(dev, "Failed to register psy rc = %ld\n",
+ PTR_ERR(data->psy));
+ goto exit;
+ }
+
+ ret = max77729_setup_votables(data);
+ if (ret < 0)
+ return -EINVAL;
+
+ ret = max77729_reg_write(data, MAX77729_CHG_INT_MASK, 0x00);
+ if (ret) {
+ dev_err(dev, "failed to set intmask\n");
+ goto exit;
+ }
+
+ ret = dbg_init_fs(data);
+ if (ret)
+ dev_err(dev, "Failed to initialize debug fs\n");
+
+ if (max77729_init_irq(client) < 0) {
+ dev_err(dev, "failed to initialize irq\n");
+ goto exit;
+ }
+
+ dev_info(dev, "registered as %s\n", max77729_psy_desc.name);
+
+exit:
+ return ret;
+}
+
+static int max77729_charger_remove(struct i2c_client *client)
+{
+ struct max77729_chgr_data *data = i2c_get_clientdata(client);
+
+ gvotable_destroy_election(data->mode_votable);
+
+ if (data->psy)
+ power_supply_unregister(data->psy);
+
+ dbg_cleanup_fs(data);
+
+ return 0;
+}
+
+static const struct of_device_id max77729_charger_of_match_table[] = {
+ { .compatible = "maxim,max77729chrg"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77729_charger_of_match_table);
+
+static const struct i2c_device_id max77729_id[] = {
+ {"max77729_charger", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, max77729_id);
+
+#if defined CONFIG_PM
+static int max77729_charger_pm_suspend(struct device *dev)
+{
+ /* TODO: is there anything to do here? */
+ return 0;
+}
+
+static int max77729_charger_pm_resume(struct device *dev)
+{
+ /* TODO: is there anything to do here? */
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops max77729_charger_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(
+ max77729_charger_pm_suspend,
+ max77729_charger_pm_resume)
+};
+
+static struct i2c_driver max77729_charger_i2c_driver = {
+ .driver = {
+ .name = "max77729-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = max77729_charger_of_match_table,
+#ifdef CONFIG_PM
+ .pm = &max77729_charger_pm_ops,
+#endif
+ },
+ .id_table = max77729_id,
+ .probe = max77729_charger_probe,
+ .remove = max77729_charger_remove,
+};
+
+module_i2c_driver(max77729_charger_i2c_driver);
+
+MODULE_DESCRIPTION("Maxim 77729 Charger Driver");
+MODULE_AUTHOR("Jim Wylder [email protected]");
+MODULE_AUTHOR("AleX Pelosi [email protected]");
+MODULE_LICENSE("GPL");
diff --git a/max77729_pmic.c b/max77729_pmic.c
new file mode 100644
index 0000000..0462d66
--- /dev/null
+++ b/max77729_pmic.c
@@ -0,0 +1,821 @@
+/*
+ * Copyright 2020 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/ctype.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include "max77759.h"
+#include "max77759_maxq.h"
+#include "max_m5.h"
+
+enum max77729_pmic_register {
+ MAX77729_PMIC_ID = 0x00,
+ MAX77729_PMIC_REVISION = 0x01,
+ MAX77729_PMIC_MAINCTRL = 0x02,
+ MAX77729_PMIC_INTSRC = 0x22,
+ MAX77729_PMIC_INTSRCMASK = 0x23,
+ MAX77729_PMIC_TOPSYS_INT = 0x24,
+ MAX77729_PMIC_TOPSYS_INT_MASK = 0x26,
+};
+
+#define MUSBC 0x08
+#define MFUEL 0x04
+#define MTOPS 0x02
+#define MCHGR 0x01
+
+#define MAX77759_GPIO_DIR_IN 0
+#define MAX77759_GPIO_DIR_OUT 1
+
+#define MAX77759_GPIO5_DIR_MASK (1 << 2)
+#define MAX77759_GPIO5_DIR(x) ((x) << 2)
+#define MAX77759_GPIO5_VAL_MASK (1 << 3)
+#define MAX77759_GPIO5_VAL(x) ((x) << 3)
+#define MAX77759_GPIO6_DIR_MASK (1 << 4)
+#define MAX77759_GPIO6_DIR(x) ((x) << 4)
+#define MAX77759_GPIO6_VAL_MASK (1 << 5)
+#define MAX77759_GPIO6_VAL(x) ((x) << 5)
+
+#define MAX77759_GPIO5_OFF 4
+#define MAX77759_GPIO6_OFF 5
+#define MAX77759_MIN_GPIO_OFF 4
+#define MAX77759_MAX_GPIO_OFF 5
+#define MAX77759_NUM_GPIOS 6
+
+#define MAX77759_GPIO_CONTROL_READ 0x23
+#define MAX77759_GPIO_CONTROL_WRITE 0x24
+
+#define MDEFAULT (0xF0 | ~(MUSBC | MFUEL | MCHGR))
+
+
+#define MAX77729_PMIC_INTSRCMASK_DEFAULT MDEFAULT
+#define MAX77729_PMIC_TOPSYS_INT_MASK_DEFAULT 0xff
+
+#define MAX77759_PMIC_INTSRCMASK_DEFAULT \
+ ~(MAX77759_PMIC_INTSRCMASK_MAXQ_INT_M | \
+ MAX77759_PMIC_INTSRCMASK_CHGR_INT_M)
+
+/* b/156527175: *_PMIC_TOPSYS_INT_MASK_SPR_7 is reserved */
+#define MAX77759_PMIC_TOPSYS_INT_MASK_MASK \
+ (MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M | \
+ MAX77759_PMIC_TOPSYS_INT_MASK_SYSOVLO_INT_M | \
+ MAX77759_PMIC_TOPSYS_INT_MASK_SYSUVLO_INT_M)
+
+#define MAX77759_PMIC_TOPSYS_INT_MASK_DEFAULT \
+ (MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M)
+
+struct max77729_pmic_data {
+ struct device *dev;
+ struct regmap *regmap;
+ uint8_t pmic_id;
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio;
+#endif
+ struct max77759_maxq *maxq;
+
+ struct i2c_client *fg_i2c_client;
+ struct mutex io_lock;
+ int batt_id;
+
+ atomic_t sysuvlo_cnt;
+ atomic_t sysovlo_cnt;
+ struct dentry *de;
+};
+
+bool max77729_pmic_is_reg(struct device *dev, unsigned int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case MAX77729_PMIC_ID:
+ case MAX77729_PMIC_REVISION:
+ case MAX77729_PMIC_MAINCTRL:
+ case MAX77729_PMIC_INTSRC:
+ case MAX77729_PMIC_INTSRCMASK:
+ case MAX77729_PMIC_TOPSYS_INT:
+ case MAX77729_PMIC_TOPSYS_INT_MASK:
+ ret = true;
+ break;
+ case MAX77759_PMIC_I2C_CNFG:
+ case MAX77759_PMIC_SWRESET:
+ case MAX77759_PMIC_CONTROL_FG:
+ ret = true;
+ break;
+ case MAX77759_PMIC_DEVICE_ID:
+ case MAX77759_PMIC_DEVICE_REV:
+ case MAX77759_PMIC_UIC_INT1...MAX77759_PMIC_UIC_INT4_M:
+ case MAX77759_PMIC_AP_DATAOUT0...MAX77759_PMIC_AP_DATAIN32:
+ case MAX77759_PMIC_UIC_SWRST:
+ ret = true;
+ break;
+ default:
+ ret = false;
+ break;
+ }
+
+ return ret;
+}
+
+static struct regmap_config max777x9_pmic_regmap_cfg = {
+ .name = "max777x9_pmic",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = MAX77729_PMIC_INTSRCMASK,
+ .readable_reg = max77729_pmic_is_reg,
+ .volatile_reg = max77729_pmic_is_reg,
+};
+
+static inline int max77729_pmic_readn(struct max77729_pmic_data *data,
+ int addr, u8 *val, int len)
+{
+ int rc;
+
+ rc = regmap_bulk_read(data->regmap, addr, val, len);
+ if (rc < 0)
+ pr_err("regmap_read failed for address %04x rc=%d\n",
+ addr, rc);
+
+ return rc;
+}
+
+static inline int max77729_pmic_writen(struct max77729_pmic_data *data,
+ int addr, const u8 *val, int len)
+{
+ int rc;
+
+ rc = regmap_bulk_write(data->regmap, addr, val, len);
+ if (rc < 0)
+ pr_err("regmap_write failed for address %04x rc=%d\n",
+ addr, rc);
+
+ return 0;
+}
+
+
+#define max77729_pmic_rd8(data, addr, val) \
+ max77729_pmic_readn(data, addr, val, 1)
+#define max77729_pmic_wr8(data, addr, val) \
+ max77729_pmic_writen(data, addr, (const u8[]){ val }, 1)
+
+/* no need for caching */
+static inline int max77729_pmic_rmw8(struct max77729_pmic_data *data,
+ int reg, u8 mask, u8 value)
+{
+ return regmap_write_bits(data->regmap, reg, mask, value);
+}
+
+int max777x9_pmic_reg_read(struct i2c_client *client,
+ u8 addr, u8 *val, int len)
+{
+ struct max77729_pmic_data *data = i2c_get_clientdata(client);
+
+ if (!data || !data->regmap)
+ return -ENXIO;
+
+ return max77729_pmic_readn(data, addr, val, len);
+}
+EXPORT_SYMBOL_GPL(max777x9_pmic_reg_read);
+
+int max777x9_pmic_reg_write(struct i2c_client *client,
+ u8 addr, const u8 *val, int len)
+{
+ struct max77729_pmic_data *data = i2c_get_clientdata(client);
+
+ if (!data || !data->regmap)
+ return -ENXIO;
+
+ return max77729_pmic_writen(data, addr, val, len);
+}
+EXPORT_SYMBOL_GPL(max777x9_pmic_reg_write);
+
+int max777x9_pmic_reg_update(struct i2c_client *client,
+ u8 reg, u8 mask, u8 value)
+{
+ struct max77729_pmic_data *data = i2c_get_clientdata(client);
+
+ if (!data || !data->regmap)
+ return -ENXIO;
+
+ return max77729_pmic_rmw8(data, reg, mask, value);
+}
+EXPORT_SYMBOL_GPL(max777x9_pmic_reg_update);
+
+
+/* this interrupt is read to clear, in max77759 it should be write to clear */
+static irqreturn_t max777x9_pmic_irq(int irq, void *ptr)
+{
+ struct max77729_pmic_data *data = ptr;
+ uint8_t intsrc = 0, uic_int[4];
+ int ret;
+
+ /* INTSRC is read to clear on MW and max77729f */
+ ret = max77729_pmic_rd8(data, MAX77729_PMIC_INTSRC, &intsrc);
+ if (ret < 0) {
+ dev_err_ratelimited(data->dev, "INTSRC: read error %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ if (intsrc == 0)
+ return IRQ_NONE;
+
+ /* just clear for max77729f */
+ pr_debug("INTSRC:%x\n", intsrc);
+ if (data->pmic_id != MAX77759_PMIC_PMIC_ID_MW)
+ return IRQ_HANDLED;
+
+ /* UIC_INT are write to clear */
+ if (intsrc & MAX77759_PMIC_INTSRC_MAXQ_INT) {
+ ret = max77729_pmic_readn(data, MAX77759_PMIC_UIC_INT1,
+ uic_int, sizeof(uic_int));
+ if (ret < 0) {
+ dev_err_ratelimited(data->dev,
+ "UIC_INT1: read error %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ /* TODO: implement / handle comms with maxq */
+ if (uic_int[0] & MAX77759_PMIC_UIC_INT1_APCMDRESI) {
+ maxq_irq(data->maxq);
+ max77729_pmic_wr8(data, MAX77759_PMIC_UIC_INT1,
+ MAX77759_PMIC_UIC_INT1_APCMDRESI);
+ }
+ }
+
+ if (intsrc & MAX77759_PMIC_TOPSYS_INT_SYSUVLO_INT)
+ atomic_inc(&data->sysuvlo_cnt);
+
+ if (intsrc & MAX77759_PMIC_TOPSYS_INT_SYSOVLO_INT)
+ atomic_inc(&data->sysovlo_cnt);
+
+ if (intsrc & MAX77759_PMIC_INTSRC_TOPSYS_INT) {
+ uint8_t tsi;
+
+ ret = max77729_pmic_rd8(data, MAX77729_PMIC_TOPSYS_INT, &tsi);
+ if (ret < 0) {
+ dev_err_ratelimited(data->dev,
+ "TOPSYS_INT: read error %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ ret = max77729_pmic_wr8(data, MAX77729_PMIC_TOPSYS_INT, tsi);
+ if (ret < 0) {
+ dev_err_ratelimited(data->dev,
+ "TOPSYS_INT:%x clr error %d\n", tsi, ret);
+ return IRQ_NONE;
+ }
+
+ pr_info("TOPSYS_INT:%x\n", tsi);
+
+ /* TODO: handle TSHDN_INT, SYSOVLO_INT, SYSUVLO_INT */
+ }
+
+ /* just clear CHG_INT, no FG intr for MW */
+
+ return IRQ_HANDLED;
+}
+
+/* Bootloader has everything masked clear this on boot */
+static int max777x9_pmic_set_irqmask(struct max77729_pmic_data *data)
+{
+ int ret;
+
+ if (data->pmic_id == MAX77759_PMIC_PMIC_ID_MW) {
+ const uint8_t uic_mask[] = {0x7f, 0xff, 0xff, 0xff};
+ uint8_t reg;
+
+ ret = max77729_pmic_rd8(data, MAX77759_PMIC_INTSRC,
+ ®);
+ if (ret < 0 || reg)
+ dev_info(data->dev, "INTSRC :%x (%d)\n", reg, ret);
+ ret = max77729_pmic_rd8(data, MAX77759_PMIC_TOPSYS_INT,
+ ®);
+ if (ret < 0 || reg)
+ dev_info(data->dev, "TOPSYS_INT :%x (%d)\n", reg, ret);
+
+ ret = max77729_pmic_wr8(data, MAX77759_PMIC_INTSRCMASK,
+ MAX77759_PMIC_INTSRCMASK_DEFAULT);
+
+ /* b/156527175, *_PMIC_TOPSYS_INT_MASK_SPR_7 is reserved */
+ ret |= max77729_pmic_rmw8(data, MAX77759_PMIC_TOPSYS_INT_MASK,
+ MAX77759_PMIC_TOPSYS_INT_MASK_MASK,
+ MAX77759_PMIC_TOPSYS_INT_MASK_DEFAULT);
+ ret |= max77729_pmic_wr8(data, MAX77759_PMIC_UIC_INT1,
+ MAX77759_PMIC_UIC_INT1_APCMDRESI);
+ ret |= max77729_pmic_writen(data, MAX77759_PMIC_UIC_INT1_M,
+ uic_mask, sizeof(uic_mask));
+ } else {
+ ret = max77729_pmic_wr8(data, MAX77729_PMIC_INTSRCMASK,
+ MAX77729_PMIC_INTSRCMASK_DEFAULT);
+ ret |= max77729_pmic_wr8(data, MAX77729_PMIC_TOPSYS_INT_MASK,
+ MAX77729_PMIC_TOPSYS_INT_MASK_DEFAULT);
+ }
+
+ return ret ? -EIO : 0;
+}
+
+static int max77759_find_fg(struct max77729_pmic_data *data)
+{
+ struct device_node *dn;
+
+ if (data->fg_i2c_client)
+ return 0;
+
+ dn = of_parse_phandle(data->dev->of_node, "max77759,max_m5", 0);
+ if (!dn)
+ return -ENXIO;
+
+ data->fg_i2c_client = of_find_i2c_device_by_node(dn);
+ if (!data->fg_i2c_client)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int max77759_read_thm(struct max77729_pmic_data *data, int mux,
+ unsigned int *value)
+{
+ unsigned int ain0, config;
+ int tmp, ret;
+ u8 pmic_ctrl;
+
+ if (!data->fg_i2c_client)
+ return -EINVAL;
+
+ /* set TEX=1 in Config 0x1D */
+ ret = max_m5_reg_read(data->fg_i2c_client, MAX77759_FG_CONFIG, &config);
+ if (ret == 0) {
+ const u16 val = _fg_config_tex_set(config, 1);
+
+ ret = max_m5_reg_write(data->fg_i2c_client, MAX77759_FG_CONFIG,
+ val);
+ }
+ if (ret < 0) {
+ pr_err("%s: cannot change FG config (%d)\n", __func__, ret);
+ return -EIO;
+ }
+
+ /* set THMIO_MUX */
+ ret = max77729_pmic_rd8(data, MAX77759_PMIC_CONTROL_FG, &pmic_ctrl);
+ if (ret == 0) {
+ const u8 val = _pmic_control_fg_thmio_mux_set(pmic_ctrl, mux);
+
+ ret = max77729_pmic_wr8(data, MAX77759_PMIC_CONTROL_FG, val);
+ }
+
+ if (ret < 0) {
+ pr_err("%s: cannot change MUX config (%d)\n", __func__, ret);
+ goto restore_fg;
+ }
+
+ /* this is not good */
+ msleep(1500);
+
+ ret = max_m5_reg_read(data->fg_i2c_client, MAX77759_FG_AIN0, &ain0);
+ pr_debug("%s: AIN0=%d (%d)\n", __func__, ain0, ret);
+
+ /* AIN0 is ratiometric on THM, 0xffff = 100%, lsb is 2^-16 */
+ *value = (100000 * (unsigned long)(ain0)) / (0x10000 - ain0);
+
+ /* restore THMIO_MUX */
+ tmp = max77729_pmic_wr8(data, MAX77759_PMIC_CONTROL_FG, pmic_ctrl);
+ WARN_ON(tmp != 0);
+
+restore_fg:
+ /* set TEX=0 in Config 0x1D */
+ tmp = max_m5_reg_write(data->fg_i2c_client, MAX77759_FG_CONFIG, config);
+ WARN_ON(tmp != 0);
+
+ return ret;
+}
+
+
+/* THMIO_MUX=1 in CONTROL_FG (0x51) */
+int max77759_read_usb_temp(struct i2c_client *client, int *temp)
+{
+ struct max77729_pmic_data *data = i2c_get_clientdata(client);
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&data->io_lock);
+ ret = max77759_find_fg(data);
+ if (ret == 0)
+ ret = max77759_read_thm(data, THMIO_MUX_USB_TEMP, &val);
+ mutex_unlock(&data->io_lock);
+
+ if (ret == 0) {
+ /* TODO: b/160737498 convert voltage to temperature */
+ *temp = val;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(max77759_read_usb_temp);
+
+static int read_batt_id(struct max77729_pmic_data *data, unsigned int *id)
+{
+ if (data->batt_id == -1) {
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&data->io_lock);
+ ret = max77759_find_fg(data);
+ if (ret == 0)
+ ret = max77759_read_thm(data, THMIO_MUX_BATT_ID, &val);
+ mutex_unlock(&data->io_lock);
+
+ if (ret < 0)
+ return ret;
+
+ data->batt_id = val;
+ }
+
+ *id = data->batt_id;
+ return 0;
+}
+
+/* THMIO_MUX=2 in CONTROL_FG (0x51) */
+int max77759_read_batt_id(struct i2c_client *client, unsigned int *id)
+{
+ struct max77729_pmic_data *data = i2c_get_clientdata(client);
+
+ return read_batt_id(data, id);
+}
+EXPORT_SYMBOL_GPL(max77759_read_batt_id);
+
+
+/* must use repeated starts b/152373060 */
+static int max77729_pmic_read_id(struct i2c_client *i2c)
+{
+ struct i2c_msg xfer[2];
+ u8 reg = MAX77729_PMIC_ID;
+ u8 pmic_id;
+ int ret;
+
+ xfer[0].addr = i2c->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = ®
+
+ xfer[1].addr = i2c->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 1;
+ xfer[1].buf = &pmic_id;
+
+ ret = i2c_transfer(i2c->adapter, xfer, 2);
+ if (ret == 2)
+ return pmic_id;
+ return -EIO;
+}
+
+static int debug_batt_id_get(void *d, u64 *val)
+{
+ struct max77729_pmic_data *data = d;
+ unsigned int batt_id;
+ int ret;
+
+ ret = read_batt_id(data, &batt_id);
+ if (ret == 0)
+ *val = batt_id;
+
+ return ret;
+}
+DEFINE_SIMPLE_ATTRIBUTE(debug_batt_id_fops, debug_batt_id_get, NULL, "%llu\n");
+
+static int dbg_init_fs(struct max77729_pmic_data *data)
+{
+ data->de = debugfs_create_dir("max77729_pmic", 0);
+ if (IS_ERR_OR_NULL(data->de))
+ return -EINVAL;
+
+ debugfs_create_atomic_t("sysuvlo_cnt", 0644, data->de,
+ &data->sysuvlo_cnt);
+ debugfs_create_atomic_t("sysovlo_cnt", 0644, data->de,
+ &data->sysovlo_cnt);
+
+ debugfs_create_file("batt_id", 0400, data->de, data,
+ &debug_batt_id_fops);
+
+ return 0;
+}
+
+#ifdef CONFIG_GPIOLIB
+
+static int max77759_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct max77729_pmic_data *data = gpiochip_get_data(chip);
+ int rc;
+ uint8_t val;
+
+ if ((offset < MAX77759_MIN_GPIO_OFF) || (offset > MAX77759_MAX_GPIO_OFF))
+ return -EINVAL;
+
+ rc = maxq_gpio_control_read(data->maxq, &val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode read 0x23 failed\n");
+ return rc;
+ }
+
+ if (offset == MAX77759_GPIO5_OFF)
+ return !(val & MAX77759_GPIO5_DIR_MASK);
+
+ return !(val & MAX77759_GPIO6_DIR_MASK);
+}
+
+static int max77759_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct max77729_pmic_data *data = gpiochip_get_data(chip);
+ int rc;
+ uint8_t val;
+
+ if ((offset < MAX77759_MIN_GPIO_OFF) || (offset > MAX77759_MAX_GPIO_OFF))
+ return -EINVAL;
+
+ rc = maxq_gpio_control_read(data->maxq, &val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode read 0x23 failed\n");
+ return rc;
+ }
+
+ if (offset == MAX77759_GPIO5_OFF)
+ return !!(val & MAX77759_GPIO5_VAL_MASK);
+
+ return !!(val & MAX77759_GPIO6_VAL_MASK);
+}
+
+static void max77759_gpio_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct max77729_pmic_data *data = gpiochip_get_data(chip);
+ int rc;
+ uint8_t val;
+ uint8_t new_val;
+ uint8_t dir;
+
+ if ((offset < MAX77759_MIN_GPIO_OFF) || (offset > MAX77759_MAX_GPIO_OFF))
+ return;
+
+ rc = maxq_gpio_control_read(data->maxq, &val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode read 0x23 failed\n");
+ return;
+ }
+
+ if (offset == MAX77759_GPIO5_OFF) {
+ dir = !(val & MAX77759_GPIO5_DIR_MASK);
+ if (dir != GPIOF_DIR_OUT) {
+ dev_err(data->dev, "not output\n");
+ return;
+ }
+ new_val = val & ~MAX77759_GPIO5_VAL_MASK;
+ new_val |= MAX77759_GPIO5_VAL(value);
+ } else { /* MAX77759_GPIO6_OFF */
+ dir = !(val & MAX77759_GPIO6_DIR_MASK);
+ if (dir != GPIOF_DIR_OUT) {
+ dev_err(data->dev, "not output\n");
+ return;
+ }
+ new_val = val & ~MAX77759_GPIO6_VAL_MASK;
+ new_val |= MAX77759_GPIO6_VAL(value);
+ }
+
+ if (new_val != val) {
+ rc = maxq_gpio_control_write(data->maxq, new_val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode write 0x24 failed\n");
+ return;
+ }
+ }
+
+}
+
+static int max77759_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct max77729_pmic_data *data = gpiochip_get_data(chip);
+ int rc;
+ uint8_t val;
+ uint8_t new_val;
+
+ if ((offset < MAX77759_MIN_GPIO_OFF) || (offset > MAX77759_MAX_GPIO_OFF))
+ return -EINVAL;
+
+ rc = maxq_gpio_control_read(data->maxq, &val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode read 0x23 failed\n");
+ return rc;
+ }
+
+ if (offset == MAX77759_GPIO5_OFF) {
+ new_val = val & ~MAX77759_GPIO5_DIR_MASK;
+ new_val |= MAX77759_GPIO5_DIR(MAX77759_GPIO_DIR_IN);
+ } else { /* MAX77759_GPIO6_OFF */
+ new_val = val & ~MAX77759_GPIO6_DIR_MASK;
+ new_val |= MAX77759_GPIO6_DIR(MAX77759_GPIO_DIR_IN);
+ }
+
+ if (new_val != val) {
+ rc = maxq_gpio_control_write(data->maxq, new_val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode write 0x24 failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int max77759_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct max77729_pmic_data *data = gpiochip_get_data(chip);
+ int rc;
+ uint8_t val;
+ uint8_t new_val;
+
+ if ((offset < MAX77759_MIN_GPIO_OFF) || (offset > MAX77759_MAX_GPIO_OFF))
+ return -EINVAL;
+
+ rc = maxq_gpio_control_read(data->maxq, &val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode read 0x23 failed\n");
+ return rc;
+ }
+
+ if (offset == MAX77759_GPIO5_OFF) {
+ new_val = val & ~MAX77759_GPIO5_DIR_MASK;
+ new_val |= MAX77759_GPIO5_DIR(MAX77759_GPIO_DIR_OUT);
+ } else { /* MAX77759_GPIO6_OFF */
+ new_val = val & ~MAX77759_GPIO6_DIR_MASK;
+ new_val |= MAX77759_GPIO6_DIR(MAX77759_GPIO_DIR_OUT);
+ }
+
+ if (new_val != val) {
+ rc = maxq_gpio_control_write(data->maxq, new_val);
+ if (rc < 0) {
+ dev_err(data->dev, "opcode write 0x24 failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static int max77729_pmic_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct max77729_pmic_data *data;
+ int irq_gpio, pmic_id, ret =0;
+
+ pmic_id = max77729_pmic_read_id(client);
+ if (pmic_id < 0)
+ return -ENODEV;
+ if (pmic_id == MAX77759_PMIC_PMIC_ID_MW)
+ max777x9_pmic_regmap_cfg.max_register = 0xe0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ data->pmic_id = pmic_id;
+ data->batt_id = -1;
+ mutex_init(&data->io_lock);
+ atomic_set(&data->sysuvlo_cnt, 0);
+ atomic_set(&data->sysovlo_cnt, 0);
+ i2c_set_clientdata(client, data);
+
+ data->regmap = devm_regmap_init_i2c(client, &max777x9_pmic_regmap_cfg);
+ if (IS_ERR(data->regmap)) {
+ dev_err(dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
+ irq_gpio = of_get_named_gpio(dev->of_node, "max777x9,irq-gpio", 0);
+ if (irq_gpio < 0) {
+ dev_err(dev, "irq is not defined\n");
+ } else {
+ client->irq = gpio_to_irq(irq_gpio);
+
+ ret = devm_request_threaded_irq(data->dev, client->irq, NULL,
+ max777x9_pmic_irq,
+ IRQF_TRIGGER_LOW |
+ IRQF_SHARED |
+ IRQF_ONESHOT,
+ "max777x9_pmic",
+ data);
+ if (ret < 0) {
+ dev_err(dev, "failed get irq thread\n");
+ } else {
+ /* force clear pending before unmasking */
+ max777x9_pmic_irq(0, data);
+
+ ret = max777x9_pmic_set_irqmask(data);
+ if (ret < 0)
+ dev_err(dev, "failed to apply irq mask\n");
+ }
+ }
+
+#ifdef CONFIG_GPIOLIB
+ if (pmic_id == MAX77759_PMIC_PMIC_ID_MW) {
+ /* Setup GPIO cotroller */
+ data->gpio.owner = THIS_MODULE;
+ data->gpio.parent = dev;
+ data->gpio.label = "max777x9_gpio";
+ data->gpio.get_direction = max77759_gpio_get_direction;
+ data->gpio.direction_input = max77759_gpio_direction_input;
+ data->gpio.direction_output = max77759_gpio_direction_output;
+ data->gpio.get = max77759_gpio_get;
+ data->gpio.set = max77759_gpio_set;
+ data->gpio.base = -1;
+ data->gpio.ngpio = MAX77759_NUM_GPIOS;
+ data->gpio.can_sleep = true;
+ data->gpio.of_node = of_find_node_by_name(dev->of_node,
+ data->gpio.label);
+ if (!data->gpio.of_node)
+ dev_err(dev, "Failed to find %s DT node\n",
+ data->gpio.label);
+
+ ret = devm_gpiochip_add_data(dev, &data->gpio, data);
+ if (ret)
+ dev_err(dev, "Failed to initialize gpio chip\n");
+ }
+#endif
+
+ ret = dbg_init_fs(data);
+ if (ret < 0)
+ dev_err(dev, "Failed to initialize debug fs\n");
+
+ if (pmic_id == MAX77759_PMIC_PMIC_ID_MW) {
+ const int poll_en = of_property_read_bool(dev->of_node,
+ "goog,maxq-poll");
+ data->maxq = maxq_init(dev, data->regmap, poll_en);
+ if (IS_ERR_OR_NULL(data->maxq)) {
+ dev_err(dev, "Maxq init failed!\n");
+ ret = PTR_ERR(data->maxq);
+ }
+ }
+
+ dev_info(dev, "probe_done pmic_id = %x\n", pmic_id);
+ return ret;
+}
+
+static int max77729_pmic_remove(struct i2c_client *client)
+{
+ struct max77729_pmic_data *data = i2c_get_clientdata(client);
+
+ maxq_remove(data->maxq);
+ return 0;
+}
+
+static const struct of_device_id max77729_pmic_of_match_table[] = {
+ { .compatible = "maxim,max77729pmic" },
+ { .compatible = "maxim,max77759pmic" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77729_pmic_of_match_table);
+
+static const struct i2c_device_id max77729_pmic_id[] = {
+ {"max77729_pmic", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, max77729_pmic_id);
+
+static struct i2c_driver max77729_pmic_i2c_driver = {
+ .driver = {
+ .name = "max777x9-pmic",
+ .owner = THIS_MODULE,
+ .of_match_table = max77729_pmic_of_match_table,
+ },
+ .id_table = max77729_pmic_id,
+ .probe = max77729_pmic_probe,
+ .remove = max77729_pmic_remove,
+};
+
+module_i2c_driver(max77729_pmic_i2c_driver);
+MODULE_DESCRIPTION("Maxim 77729 PMIC driver");
+MODULE_LICENSE("GPL");
diff --git a/max77729_uic.c b/max77729_uic.c
new file mode 100644
index 0000000..b70849d
--- /dev/null
+++ b/max77729_uic.c
@@ -0,0 +1,732 @@
+/*
+ * Copyright 2020 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/bitops.h>
+#include <linux/completion.h>
+#include <linux/ctype.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include <linux/power_supply.h>
+#include "gbms_storage.h"
+
+#define MAX77729_UIC_HW_REV 0x00
+#define MAX77729_UIC_FW_REV 0x01
+#define MAX77729_UIC_INT 0x02
+#define MAX77729_CC_INT 0x03
+#define MAX77729_PD_INT 0x04
+#define MAX77729_VDM_INT 0x05
+#define MAX77729_USBC_STATUS1 0x06
+#define MAX77729_USBC_STATUS2 0x07
+#define MAX77729_BC_STATUS 0x08
+#define MAX77729_UIC_FW_REV2 0x09
+#define MAX77729_CC_STATUS1 0x0a
+#define MAX77729_CC_STATUS2 0x0b
+#define MAX77729_PD_STATUS1 0x0c
+#define MAX77729_PD_STATUS2 0x0d
+#define MAX77729_UIC_INT_M 0x0e
+#define MAX77729_CC_INT_M 0x0f
+#define MAX77729_PD_INT_M 0x10
+
+#define MAX77729_AP_DATAOUT0 0x21
+#define MAX77729_AP_DATAOUT1 0x22
+#define MAX77729_AP_DATAOUT32 0x41
+#define MAX77729_AP_DATAIN0 0x51
+#define MAX77729_AP_DATAIN1 0x52
+
+#define MAX77729_UIC_BC_CTRL1_CONFIG_READ 0x01
+#define MAX77729_UIC_BC_CTRL1_CONFIG_WRITE 0x02
+
+#define NOAUTOIBUS_SHIFT 5
+#define WAIT_STEP_MS 10
+#define WAIT_MAX_TRIES 10
+
+#define MAX77729_UIC_INT_APCMDRESI 0x80
+#define MAX77729_UIC_INT_SYSMSGI 0x40
+#define MAX77729_UIC_INT_VBUSDETI 0x20
+#define MAX77729_UIC_INT_VBADCI 0x10
+#define MAX77729_UIC_INT_DCDTMOI 0x08
+#define MAX77729_UIC_INT_FAKEVBUSI 0x04
+#define MAX77729_UIC_INT_CHGTYPI 0x02
+#define MAX77729_UIC_INT_UIDADCI 0x01
+
+#define MAX77729_BC_STATUS_VBUSDET 0x80
+
+#define NAI_DWELL_TIME 3000
+#define BC_CTRL1_DWELL_TIME 1000
+#define BC_CTRL1_DEFAULT 0xe5
+#define OP_CC_CTRL_WRITE 0x0c
+#define OP_CC_CTRL_CCSRCSNK 0x10
+
+#define CHGTYP_MASK GENMASK(1, 0)
+#define CHGTYP_NONE 0x0
+#define CHGTYP_SDP 0x1
+#define CHGTYP_CDP 0x2
+#define CHGTYP_DCP 0x3
+
+#define OP_GPIOX_READ 0x23
+#define OP_GPIOX_WRITE 0x24
+#define GPIO_DIR_OUT 1
+#define GPIO_DIR_IN 0
+#define GPIO_OUT_HI 1
+#define GPIO_OUT_LO 0
+
+#define OP_CC_CTRL3_READ 0xF
+#define CC_LP_MODE BIT(4)
+#define OP_CC_CTRL3_WRITE 0x10
+
+#define MAX77729_STORAGE_SIZE 8
+#define MAX77729_STORAGE_BASE (MAX77729_AP_DATAOUT0 + MAX77729_STORAGE_SIZE)
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#endif
+
+#define MAX77729_INT_DEFAULT_MASK (MAX77729_UIC_INT_VBUSDETI | \
+ MAX77729_UIC_INT_APCMDRESI | \
+ MAX77729_UIC_INT_CHGTYPI | \
+ MAX77729_UIC_INT_DCDTMOI)
+
+struct max77729_uic_data {
+ struct device *dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ int irq;
+ int irq_gpio;
+ uint8_t bc_ctrl1;
+ uint8_t cmd_pending;
+ struct delayed_work noautoibus_work;
+ struct completion cmd_done;
+ struct mutex io_lock;
+ struct power_supply *usb_psy;
+ struct mutex gpio_lock;
+ struct mutex cc_ctrl3_lock;
+ bool probe_done;
+
+ struct dentry *de;
+};
+
+bool max77729_uic_is_reg(struct device *dev, unsigned int reg)
+{
+ int ret;
+
+ switch (reg) {
+ case 0x00 ... 0x71:
+ ret = true;
+ break;
+ default:
+ ret = false;
+ break;
+ }
+ return ret;
+}
+
+static const struct regmap_config max77729_uic_regmap_cfg = {
+ .name = "max77729_uic",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = 0x71,
+ .readable_reg = max77729_uic_is_reg,
+ .volatile_reg = max77729_uic_is_reg,
+};
+
+static inline int max77729_uic_read(struct regmap *uic_regmap,
+ int addr, u8 *val, int len)
+{
+ int rc;
+
+ if (!uic_regmap)
+ return -ENXIO;
+
+ rc = regmap_bulk_read(uic_regmap, addr, val, len);
+ if (rc < 0) {
+ pr_err("regmap_read failed for address %04x rc=%d\n",
+ addr, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static inline int max77729_uic_write(struct regmap *uic_regmap, int addr,
+ const u8 *val, int len)
+{
+ int rc;
+
+ if (!uic_regmap)
+ return -ENXIO;
+
+ rc = regmap_bulk_write(uic_regmap, addr, val, len);
+ if (rc < 0) {
+ pr_err("regmap_write failed for address %04x rc=%d\n",
+ addr, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static inline int max77729_uic_wait(struct i2c_client *client)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+ unsigned long rc;
+
+ rc = wait_for_completion_timeout(&data->cmd_done,
+ msecs_to_jiffies(BC_CTRL1_DWELL_TIME));
+ if (!rc) {
+ dev_err(data->dev, "timeout waiting for cmd 0x%02x\n",
+ data->cmd_pending);
+ return -ETIME;
+ }
+ return 0;
+}
+
+/* Adopted from one of the earlier patches from Jim */
+static int max77729_uic_opcode_read(struct i2c_client *client,
+ uint8_t op, uint8_t *val)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+ int rc;
+ uint8_t buf[2];
+
+ mutex_lock(&data->io_lock);
+ buf[0] = MAX77729_AP_DATAOUT0;
+ buf[1] = op;
+ rc = i2c_master_send(client, buf, 2);
+ if (rc < 0)
+ goto opcode_read_out;
+ buf[0] = MAX77729_AP_DATAOUT32;
+ buf[1] = 0x00;
+ rc = i2c_master_send(client, buf, 2);
+ if (rc < 0)
+ goto opcode_read_out;
+
+ rc = max77729_uic_wait(client);
+ if (rc < 0)
+ goto opcode_read_out;
+
+ buf[0] = MAX77729_AP_DATAIN1;
+ rc = i2c_master_send(client, buf, 1);
+ if (rc < 0)
+ goto opcode_read_out;
+ rc = i2c_master_recv(client, buf, 1);
+ if (rc < 0)
+ goto opcode_read_out;
+
+ *val = buf[0];
+
+opcode_read_out:
+ mutex_unlock(&data->io_lock);
+ return rc;
+}
+
+static int max77729_uic_opcode_write(struct i2c_client *client,
+ uint8_t op, uint8_t val)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+ int rc;
+ uint8_t buf[2];
+
+ mutex_lock(&data->io_lock);
+ data->cmd_pending = op;
+ reinit_completion(&data->cmd_done);
+
+ /* write updated result */
+ buf[0] = MAX77729_AP_DATAOUT0;
+ buf[1] = op;
+ rc = i2c_master_send(client, buf, 2);
+ if (rc < 0)
+ goto opcode_write_out;
+ buf[0] = MAX77729_AP_DATAOUT1;
+ buf[1] = val;
+ rc = i2c_master_send(client, buf, 2);
+ if (rc < 0)
+ goto opcode_write_out;
+ buf[0] = MAX77729_AP_DATAOUT32;
+ buf[1] = 0x00;
+ rc = i2c_master_send(client, buf, 2);
+ if (rc < 0)
+ goto opcode_write_out;
+
+ rc = max77729_uic_wait(client);
+
+ data->cmd_pending = 0;
+opcode_write_out:
+ mutex_unlock(&data->io_lock);
+
+ return rc;
+}
+
+int max77729_disable_water_detection(struct i2c_client *client)
+{
+ uint8_t ctrl3_write, ctrl3_readback;
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+
+ if (!data || !data->probe_done)
+ return -EAGAIN;
+
+ mutex_lock(&data->cc_ctrl3_lock);
+ max77729_uic_opcode_read(client, OP_CC_CTRL3_READ,
+ &ctrl3_write);
+ ctrl3_write = ctrl3_write & ~CC_LP_MODE;
+
+ max77729_uic_opcode_write(client, OP_CC_CTRL3_WRITE,
+ ctrl3_write);
+ max77729_uic_opcode_read(client, OP_CC_CTRL3_READ,
+ &ctrl3_readback);
+ mutex_unlock(&data->cc_ctrl3_lock);
+
+ return ctrl3_write == ctrl3_readback ? 0 : -1;
+}
+EXPORT_SYMBOL_GPL(max77729_disable_water_detection);
+
+int max77729_gpio_set(struct i2c_client *client, unsigned int gpio,
+ bool dir_out, bool out_hi)
+{
+ uint8_t gpio_dir_val = 0, gpio_out_val = 0, gpio_val = 0;
+ uint8_t gpio_dir_offset = 0, gpio_current_setting = 0,
+ gpio_read_back;
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+
+ if (!data || !data->probe_done)
+ return -EAGAIN;
+
+ gpio_dir_val = dir_out ? GPIO_DIR_OUT : GPIO_DIR_IN;
+ if (dir_out)
+ gpio_out_val = out_hi ? GPIO_OUT_HI : GPIO_OUT_LO;
+
+ gpio_dir_offset = (gpio - 1) * 2;
+
+ gpio_val = ((gpio_out_val << 1) | gpio_dir_val) << gpio_dir_offset;
+
+ mutex_lock(&data->gpio_lock);
+ max77729_uic_opcode_read(client, OP_GPIOX_READ,
+ &gpio_current_setting);
+
+ /* Leave other gpios in the same state */
+ gpio_current_setting &= (0xFF & ~(3 << gpio_dir_offset));
+ gpio_current_setting |= gpio_val;
+
+ max77729_uic_opcode_write(client, OP_GPIOX_WRITE,
+ gpio_current_setting);
+
+ max77729_uic_opcode_read(client, OP_GPIOX_READ,
+ &gpio_read_back);
+ mutex_unlock(&data->gpio_lock);
+
+ return gpio_current_setting == gpio_read_back ? 0 : -1;
+}
+EXPORT_SYMBOL_GPL(max77729_gpio_set);
+
+/* TODO: implement this */
+static int max77729_uic_noautoibus_get(struct i2c_client *client)
+{
+ return 1;
+}
+
+static int max77729_uic_noautoibus_set(struct i2c_client *client)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+ uint8_t bc_stat = 0;
+ int ret;
+
+ ret = max77729_uic_read(data->regmap, MAX77729_BC_STATUS, &bc_stat, 1);
+ if (ret < 0)
+ return ret;
+
+ if (bc_stat & MAX77729_BC_STATUS_VBUSDET)
+ max77729_uic_opcode_write(data->client, OP_CC_CTRL_WRITE,
+ OP_CC_CTRL_CCSRCSNK);
+
+ max77729_uic_opcode_write(data->client,
+ MAX77729_UIC_BC_CTRL1_CONFIG_WRITE,
+ data->bc_ctrl1);
+ return 0;
+}
+
+static inline void max77729_cmd_complete(struct i2c_client *client)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+ int rc;
+ uint8_t buf[1];
+
+ buf[0] = MAX77729_AP_DATAIN0;
+ rc = i2c_master_send(client, buf, 1);
+ if (rc < 0)
+ return;
+ rc = i2c_master_recv(client, buf, 1);
+ if (rc < 0)
+ return;
+
+ if (buf[0] == data->cmd_pending)
+ complete_all(&data->cmd_done);
+}
+
+static void max77729_report_chgtype(struct max77729_uic_data *data)
+{
+ union power_supply_propval val = { 0 };
+ enum power_supply_usb_type usb_type;
+ uint8_t bc_status = 0;
+ int ret;
+
+ ret = max77729_uic_read(data->regmap, MAX77729_BC_STATUS, &bc_status,
+ 1);
+ dev_info(data->dev, "report_chgtype bc_status:%x ret:%d\n",
+ bc_status, ret);
+ if (ret < 0)
+ return;
+
+ switch (bc_status & CHGTYP_MASK) {
+ case CHGTYP_NONE:
+ usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ break;
+ case CHGTYP_SDP:
+ usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ break;
+ case CHGTYP_CDP:
+ usb_type = POWER_SUPPLY_USB_TYPE_CDP;
+ break;
+ case CHGTYP_DCP:
+ usb_type = POWER_SUPPLY_USB_TYPE_DCP;
+ break;
+ default:
+ usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ break;
+ }
+
+ val.intval = usb_type;
+ ret = power_supply_set_property(data->usb_psy,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ &val);
+ if (ret)
+ dev_err(data->dev, "BC12: usb_psy update failed (%d)", ret);
+}
+
+static irqreturn_t max77729_uic_irq(int irq, void *client)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+ uint8_t pd_int = 0, cc_int = 0, vdm_int = 0, uic_int;
+ int ret;
+
+ /* clear any ints we don't care about */
+ max77729_uic_read(data->regmap, MAX77729_PD_INT, &pd_int, 1);
+ max77729_uic_read(data->regmap, MAX77729_CC_INT, &cc_int, 1);
+ max77729_uic_read(data->regmap, MAX77729_VDM_INT, &vdm_int, 1);
+
+ ret = max77729_uic_read(data->regmap, MAX77729_UIC_INT, &uic_int, 1);
+ if (ret < 0) {
+ dev_err_ratelimited(data->dev,
+ "failed to read register 0x%02x\n", MAX77729_UIC_INT);
+ return IRQ_NONE;
+ }
+ if (!uic_int)
+ return IRQ_NONE;
+
+ if (uic_int & MAX77729_UIC_INT_VBUSDETI)
+ mod_delayed_work(system_wq, &data->noautoibus_work,
+ msecs_to_jiffies(NAI_DWELL_TIME));
+
+ if (uic_int & MAX77729_UIC_INT_APCMDRESI)
+ max77729_cmd_complete(client);
+
+ if (uic_int & MAX77729_UIC_INT_CHGTYPI) {
+ dev_info(data->dev, "BC1.2 CHGTYPI\n");
+ max77729_report_chgtype(data);
+ }
+
+ if (uic_int & MAX77729_UIC_INT_DCDTMOI)
+ dev_err(data->dev, "BC1.2 DCDTMO\n");
+
+ return IRQ_HANDLED;
+}
+
+static uint8_t max77729_get_bc_ctrl1(struct device *dev)
+{
+ struct property *np;
+ uint32_t val;
+ uint8_t cfg = BC_CTRL1_DEFAULT;
+
+ np = of_find_property(dev->of_node, "bc1_config", NULL);
+ if (np) {
+ if (!of_property_read_u32(dev->of_node, "bc1_config", &val))
+ cfg = val & 0xff;
+ }
+ return cfg;
+}
+
+static void max77729_noautoibus_worker(struct work_struct *work)
+{
+ struct max77729_uic_data *data = container_of(to_delayed_work(work),
+ struct max77729_uic_data, noautoibus_work);
+ int ret, nai = -1;
+
+ ret = max77729_uic_noautoibus_set(data->client);
+ if (ret == 0)
+ nai = max77729_uic_noautoibus_get(data->client);
+ if (nai <= 0)
+ mod_delayed_work(system_wq, &data->noautoibus_work,
+ msecs_to_jiffies(BC_CTRL1_DWELL_TIME));
+
+ dev_err(data->dev, "NoAutoIbus WORK ret = %d, nai=%d\n", ret, nai);
+}
+
+
+#ifdef CONFIG_DEBUG_FS
+static int max77729_dbg_set_noautoibus(void *d, u64 val)
+{
+ struct max77729_uic_data *data = d;
+ int ret, nai = -1;
+
+ ret = max77729_uic_noautoibus_set(data->client);
+ if (ret == 0)
+ nai = max77729_uic_noautoibus_get(data->client);
+
+ dev_err(data->dev, "NoAutoIbus = %d\n", nai);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(max77729_noautoibus_fops, NULL,
+ max77729_dbg_set_noautoibus, "%u\n");
+
+static int dbg_init_fs(struct max77729_uic_data *data)
+{
+ data->de = debugfs_create_dir("max77729_maxq", NULL);
+ if (!data->de)
+ return -EINVAL;
+
+ debugfs_create_file("noautoibus", 0644, data->de, data,
+ &max77729_noautoibus_fops);
+ return 0;
+}
+
+#else
+static int dbg_init_fs(struct max77729_chgr_data *data)
+{
+ return 0;
+}
+#endif
+
+static int max77729_uic_storage_iter(int index, gbms_tag_t *tag, void *ptr)
+{
+ if (index < 0 || index > (GBMS_TAG_RRS7 - GBMS_TAG_RRS0))
+ return -ENOENT;
+ *tag = GBMS_TAG_RRS0 + index;
+ return 0;
+}
+
+static int max77729_uic_storage_read(gbms_tag_t tag, void *buff, size_t size,
+ void *ptr)
+{
+ const int base = MAX77729_STORAGE_BASE + tag - GBMS_TAG_RRS0;
+ struct max77729_uic_data *data = ptr;
+ int ret;
+
+ if (tag < GBMS_TAG_RRS0 || tag > GBMS_TAG_RRS7)
+ return -ENOENT;
+ if (tag + size > GBMS_TAG_RRS7)
+ return -ERANGE;
+
+ ret = max77729_uic_read(data->regmap, base, buff, size);
+ if (ret < 0)
+ ret = -EIO;
+ return ret;
+}
+
+static int max77729_uic_storage_write(gbms_tag_t tag, const void *buff,
+ size_t size, void *ptr)
+{
+ const int base = MAX77729_STORAGE_BASE + tag - GBMS_TAG_RRS0;
+ struct max77729_uic_data *data = ptr;
+ int ret;
+
+ if (tag < GBMS_TAG_RRS0 || tag > GBMS_TAG_RRS7)
+ return -ENOENT;
+ if (tag + size > GBMS_TAG_RRS7)
+ return -ERANGE;
+
+ ret = max77729_uic_write(data->regmap, base, buff, size);
+ if (ret < 0)
+ ret = -EIO;
+ return ret;
+}
+
+static struct gbms_storage_desc max77729_uic_storage_dsc = {
+ .iter = max77729_uic_storage_iter,
+ .read = max77729_uic_storage_read,
+ .write = max77729_uic_storage_write,
+};
+
+static int max77729_uic_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct max77729_uic_data *data;
+ struct device *dev = &client->dev;
+ struct power_supply *usb_psy;
+ const char *usb_psy_name;
+ uint8_t irq_mask = 0x0; /* unmask all */
+ uint8_t hw_rev = 0;
+ int ret;
+
+ usb_psy_name = of_get_property(dev->of_node, "usb-psy-name", NULL);
+ if (!usb_psy_name) {
+ dev_err(dev, "usb-psy-name not set\n");
+ return -EINVAL;
+ }
+
+ usb_psy = power_supply_get_by_name(usb_psy_name);
+ if (!usb_psy) {
+ dev_err(&client->dev, "usb psy not up, retrying....\n");
+ return -EPROBE_DEFER;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ i2c_set_clientdata(client, data);
+ data->client = client;
+ data->usb_psy = usb_psy;
+
+ data->regmap = devm_regmap_init_i2c(client, &max77729_uic_regmap_cfg);
+ if (IS_ERR(data->regmap)) {
+ dev_err(dev, "Failed to initialize regmap\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* check chip is available */
+ ret = max77729_uic_read(data->regmap, MAX77729_UIC_HW_REV, &hw_rev, 1);
+ if (ret < 0) {
+ dev_err(dev, "device not available\n");
+ return ret;
+ }
+
+ init_completion(&data->cmd_done);
+ INIT_DELAYED_WORK(&data->noautoibus_work, max77729_noautoibus_worker);
+ mutex_init(&data->io_lock);
+ mutex_init(&data->gpio_lock);
+ mutex_init(&data->cc_ctrl3_lock);
+
+ data->bc_ctrl1 = max77729_get_bc_ctrl1(dev);
+
+ data->irq_gpio = of_get_named_gpio(dev->of_node,
+ "max77729,irq-gpio", 0);
+ if (data->irq_gpio < 0) {
+ dev_err(dev, "failed get irq_gpio\n");
+ return -EINVAL;
+ }
+ client->irq = gpio_to_irq(data->irq_gpio);
+
+ ret = devm_request_threaded_irq(data->dev, client->irq, NULL,
+ max77729_uic_irq,
+ IRQF_TRIGGER_LOW |
+ IRQF_SHARED |
+ IRQF_ONESHOT,
+ "maxq", client);
+ if (ret == 0)
+ enable_irq_wake(client->irq);
+
+ /* handle pending interrupts, unmask the interesting ones */
+ max77729_uic_irq(-1, client);
+ ret = max77729_uic_write(data->regmap, MAX77729_UIC_INT_M,
+ &irq_mask, 1);
+ if (ret < 0)
+ dev_err(dev, "cannot reset irq mask %d", ret);
+ /* report port type since the irq might have been cleared in BL */
+ max77729_report_chgtype(data);
+
+ /* TODO: move at the beginning of probe */
+ ret = gbms_storage_register(&max77729_uic_storage_dsc,
+ "max77729uic", data);
+
+ dev_info(dev, "maxq: hw_rev=%x mask=%x st=%d init_work done\n",
+ hw_rev, irq_mask, ret);
+
+ if (ret == -EBUSY)
+ ret = 0;
+
+ dbg_init_fs(data);
+ data->probe_done = true;
+exit:
+ return ret;
+}
+
+static int max77729_uic_remove(struct i2c_client *client)
+{
+ struct max77729_uic_data *data = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&data->noautoibus_work);
+
+ return 0;
+}
+
+static const struct of_device_id max77729_uic_of_match_table[] = {
+ { .compatible = "maxim,max77729uic"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77729_uic_of_match_table);
+
+static const struct i2c_device_id max77729_uic_id[] = {
+ {"max77729_uic", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, max77729_uic_id);
+
+#if defined CONFIG_PM
+static int max77729_uic_pm_suspend(struct device *dev)
+{
+ return 0; /* TODO */
+}
+
+static int max77729_uic_pm_resume(struct device *dev)
+{
+ return 0; /* TODO */
+}
+#endif
+
+static const struct dev_pm_ops max77729_uic_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(
+ max77729_uic_pm_suspend,
+ max77729_uic_pm_resume)
+};
+
+static struct i2c_driver max77729_uic_i2c_driver = {
+ .driver = {
+ .name = "max77729-uic",
+ .owner = THIS_MODULE,
+ .of_match_table = max77729_uic_of_match_table,
+#ifdef CONFIG_PM
+ .pm = &max77729_uic_pm_ops,
+#endif
+ },
+ .id_table = max77729_uic_id,
+ .probe = max77729_uic_probe,
+ .remove = max77729_uic_remove,
+};
+
+module_i2c_driver(max77729_uic_i2c_driver);
+MODULE_DESCRIPTION("Maxim 77729 UIC driver");
+MODULE_AUTHOR("Jim Wylder [email protected]");
+MODULE_LICENSE("GPL");
diff --git a/max77759.h b/max77759.h
new file mode 100644
index 0000000..ac16959
--- /dev/null
+++ b/max77759.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 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.
+ */
+
+#ifndef MAX77759_H_
+#define MAX77759_H_
+
+#include <linux/i2c.h>
+
+#define SCNPRINTF(...) scnprintf(__VA_ARGS__)
+#include "max77759_regs.h"
+
+#define MAX77759_CHG_INT_COUNT 2
+#define MAX77759_PMIC_PMIC_ID_MW 0x3b
+
+/*
+ * b/156527175: workaround for read only MAX77759_CHG_DETAILS_03
+ * MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7 is used to detect exit from fshipmode.
+ * The register (MAX77759_PMIC_TOPSYS_INT_MASK) is type S and the bit is reset
+ * to 1 on power loss. The reports MAX77759_CHG_DETAILS_03 when the bit
+ * is 1 and report 0 when the bit is set to 0.
+ */
+#define MAX77759_FSHIP_EXIT_DTLS MAX77759_PMIC_TOPSYS_INT_MASK
+#define MAX77759_FSHIP_EXIT_DTLS_RD \
+ MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7
+#define MAX77759_FSHIP_EXIT_DTLS_RD_SHIFT \
+ MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7_SHIFT
+#define MAX77759_FSHIP_EXIT_DTLS_RD_MASK \
+ MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7_MASK
+#define MAX77759_FSHIP_EXIT_DTLS_RD_CLEAR \
+ MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7_CLEAR
+
+int max777x9_pmic_reg_read(struct i2c_client *client,
+ u8 addr, u8 *val, int len);
+int max777x9_pmic_reg_write(struct i2c_client *client,
+ u8 addr, const u8 *val, int len);
+int max777x9_pmic_reg_update(struct i2c_client *client,
+ u8 reg, u8 mask, u8 value);
+
+/* mux configuration in MAX77759_PMIC_CONTROL_FG */
+#define THMIO_MUX_BATT_PACK 0
+#define THMIO_MUX_USB_TEMP 1
+#define THMIO_MUX_BATT_ID 2
+
+
+#endif
\ No newline at end of file
diff --git a/max77759_charger.c b/max77759_charger.c
new file mode 100644
index 0000000..e58a8ce
--- /dev/null
+++ b/max77759_charger.c
@@ -0,0 +1,1649 @@
+/*
+ * Copyright 2020 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/ctype.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_wakeup.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include "google_bms.h"
+#include "max_m5.h"
+#include "max77759.h"
+#include "gvotable.h"
+
+/* CHG_DETAILS_01:CHG_DTLS */
+#define CHGR_DTLS_DEAD_BATTERY_MODE 0x00
+#define CHGR_DTLS_FAST_CHARGE_CONST_CURRENT_MODE 0x01
+#define CHGR_DTLS_FAST_CHARGE_CONST_VOLTAGE_MODE 0x02
+#define CHGR_DTLS_TOP_OFF_MODE 0x03
+#define CHGR_DTLS_DONE_MODE 0x04
+#define CHGR_DTLS_TIMER_FAULT_MODE 0x06
+#define CHGR_DTLS_DETBAT_HIGH_SUSPEND_MODE 0x07
+#define CHGR_DTLS_OFF_MODE 0x08
+#define CHGR_DTLS_OFF_HIGH_TEMP_MODE 0x0a
+#define CHGR_DTLS_OFF_WATCHDOG_MODE 0x0b
+#define CHGR_DTLS_OFF_JEITA 0x0c
+#define CHGR_DTLS_OFF_TEMP 0x0d
+
+struct max77759_chgr_data {
+ struct device *dev;
+ struct power_supply *psy;
+ struct power_supply *wcin_psy;
+ struct power_supply *chgin_psy;
+
+ struct power_supply *fg_psy;
+ struct power_supply *wlc_psy;
+ struct regmap *regmap;
+
+ struct gvotable_election *mode_votable;
+ enum gbms_charger_modes last_mode;
+
+ struct gvotable_election *dc_icl_votable;
+ struct gvotable_election *dc_suspend_votable;
+
+ bool chgin_input_suspend;
+ bool wcin_input_suspend;
+
+ int irq_gpio;
+
+ struct i2c_client *fg_i2c_client;
+ struct i2c_client *pmic_i2c_client;
+
+ struct dentry *de;
+ atomic_t sysuvlo1_cnt;
+ atomic_t sysuvlo2_cnt;
+
+ struct mutex io_lock;
+ bool resume_complete;
+ bool init_complete;
+
+ int fship_dtls;
+ bool online;
+ bool wden;
+};
+
+static inline int max77759_reg_read(struct regmap *regmap, uint8_t reg,
+ uint8_t *val)
+{
+ int ret, ival;
+
+ ret = regmap_read(regmap, reg, &ival);
+ if (ret == 0)
+ *val = 0xFF & ival;
+
+ return ret;
+}
+
+static inline int max77759_reg_write(struct regmap *regmap, uint8_t reg,
+ uint8_t val)
+{
+ return regmap_write(regmap, reg, val);
+}
+
+static inline int max77759_readn(struct regmap *regmap, uint8_t reg,
+ uint8_t *val, int count)
+{
+ return regmap_bulk_read(regmap, reg, val, count);
+}
+
+static inline int max77759_writen(struct regmap *regmap, uint8_t reg,
+ const uint8_t *val, int count)
+{
+ return regmap_bulk_write(regmap, reg, val, count);
+}
+
+static inline int max77759_reg_update(struct max77759_chgr_data *data,
+ uint8_t reg, uint8_t msk, uint8_t val)
+{
+ int ret;
+ unsigned tmp;
+
+ mutex_lock(&data->io_lock);
+ ret = regmap_read(data->regmap, reg, &tmp);
+ if (!ret) {
+ tmp &= ~msk;
+ tmp |= val;
+ ret = regmap_write(data->regmap, reg, tmp);
+ }
+ mutex_unlock(&data->io_lock);
+
+ return ret;
+}
+
+struct max77759_foreach_callback_data {
+ struct gvotable_election *el;
+ const char *reason;
+ bool force_off;
+ bool chgin_off;
+ bool wcin_off;
+ bool cp_ena;
+ int mode;
+ u8 reg;
+};
+
+/* set WDTEN in CHG_CNFG_18 (0xCB), tWD = 80s */
+static int max77759_wdt_enable(struct max77759_chgr_data *data, bool enable)
+{
+ int ret;
+ u8 reg;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_18, ®);
+ if (ret < 0)
+ return -EIO;
+
+ if ((!!_chg_cnfg_18_wdten_get(reg)) == enable)
+ return 0;
+
+ /* this register is protected, read back to check if it worked */
+ reg = _chg_cnfg_18_wdten_set(reg, enable);
+ ret = max77759_reg_write(data->regmap, MAX77759_CHG_CNFG_18, reg);
+ if (ret < 0)
+ return -EIO;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_18, ®);
+ if (ret < 0)
+ return -EIO;
+
+ return (ret == 0 && (!!_chg_cnfg_18_wdten_get(reg)) == enable) ?
+ 0 : -EINVAL;
+}
+
+static int max77759_foreach_callback(void *data, const char *reason,
+ void *vote)
+{
+ struct max77759_foreach_callback_data *cb_data = data;
+ int mode = (int)vote; /* max77759_mode is an int election */
+
+ pr_info("cb_data(reg=0x%x mode=%d reason=%s) mode=%x reason=%s\n",
+ cb_data->reg, cb_data->mode, cb_data->reason,
+ mode, reason ? reason : "");
+
+ switch (mode) {
+ case GBMS_CHGR_MODE_ALL_OFF:
+ cb_data->force_off = true;
+ /* fall through */
+ case GBMS_CHGR_MODE_BUCK_ON:
+ case GBMS_CHGR_MODE_CHGR_BUCK_ON:
+ case GBMS_CHGR_MODE_BOOST_UNO_ON:
+ if (cb_data->force_off || cb_data->cp_ena)
+ mode = GBMS_CHGR_MODE_ALL_OFF;
+
+ cb_data->mode = mode;
+ cb_data->reason = reason;
+ cb_data->reg = _chg_cnfg_00_cp_en_set(cb_data->reg,
+ cb_data->cp_ena);
+ cb_data->reg = _chg_cnfg_00_mode_set(cb_data->reg, mode);
+ break;
+
+ case GBMS_CHGR_MODE_BOOST_ON:
+ case GBMS_CHGR_MODE_OTG_BOOST_ON:
+ case GBMS_CHGR_MODE_BUCK_BOOST_UNO_ON:
+ case GBMS_CHGR_MODE_CHGR_BUCK_BOOST_UNO_ON:
+ case GBMS_CHGR_MODE_OTG_BUCK_BOOST_ON:
+ case GBMS_CHGR_MODE_CHGR_OTG_BUCK_BOOST_ON:
+ pr_err("mode=%x not implemented\n", mode);
+ break;
+
+ /* DC Charging: mode=0, set CP_EN. TODO: enable WDTEN */
+ case GBMS_CHGR_MODE_CHGR_DC:
+ cb_data->mode = GBMS_CHGR_MODE_ALL_OFF;
+ cb_data->reason = reason;
+ cb_data->cp_ena = true;
+ cb_data->reg = _chg_cnfg_00_mode_set(cb_data->reg,
+ GBMS_CHGR_MODE_ALL_OFF);
+ cb_data->reg = _chg_cnfg_00_cp_en_set(cb_data->reg, 1);
+ break;
+ default:
+ pr_err("mode=%x not supported\n", mode);
+ break;
+ }
+
+ return 0;
+}
+
+/* I am using a the comparator_none, need scan all the votes */
+static void max77759_mode_callback(struct gvotable_election *el,
+ const char *reason, void *value)
+{
+ struct max77759_chgr_data *data = gvotable_get_data(el);
+ struct max77759_foreach_callback_data cb_data = { 0 };
+ const char *final_reason = NULL;
+ int ret;
+ u8 reg;
+
+ mutex_lock(&data->io_lock);
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_00, ®);
+ if (ret < 0) {
+ dev_err(data->dev, "cannot read CNFG_00 (%d)\n", ret);
+ goto unlock_done;
+ }
+
+ cb_data.mode = -1; /* invalid */
+ cb_data.reg = reg;
+ cb_data.el = el;
+
+ gvotable_election_for_each(el, max77759_foreach_callback, &cb_data);
+
+ /* use gvotable_get_default() when available */
+ if (cb_data.mode == -1)
+ cb_data.mode = GBMS_CHGR_MODE_ALL_OFF;
+
+ if (cb_data.reason)
+ final_reason = cb_data.reason;
+ else
+ final_reason = "MODE_CB";
+
+ /* the election is an int election */
+ ret = gvotable_election_set_result(el, final_reason,
+ (void*)(uintptr_t)cb_data.mode);
+ if (ret < 0) {
+ dev_info(data->dev, "max77759_charger: cannot update result %d\n", ret);
+ goto unlock_done;
+ }
+
+ dev_info(data->dev, "max77759_charger: CHARGER_MODE=%d reason=%s reg:%x->%x\n",
+ cb_data.mode, cb_data.reason ? cb_data.reason : "",
+ reg, cb_data.reg);
+
+ /* TODO: state machine that handle transition between states */
+
+ if (cb_data.reg != reg) {
+ ret = max77759_reg_write(data->regmap, MAX77759_CHG_CNFG_00,
+ cb_data.reg);
+ if (ret < 0)
+ dev_err(data->dev, "cannot set CNFG_00 (%d)\n", ret);
+ }
+
+ data->last_mode = cb_data.mode;
+
+unlock_done:
+ mutex_unlock(&data->io_lock);
+}
+
+static int max77759_get_charge_enabled(struct max77759_chgr_data *data,
+ int *enabled)
+{
+ int ret;
+ const void *vote = (const void *)0;
+
+ ret = gvotable_get_current_vote(data->mode_votable, &vote);
+ if (ret < 0)
+ return ret;
+
+ switch ((uintptr_t)vote) {
+ case GBMS_CHGR_MODE_CHGR_BUCK_ON:
+ case GBMS_CHGR_MODE_CHGR_BUCK_BOOST_UNO_ON:
+ case GBMS_CHGR_MODE_CHGR_OTG_BUCK_BOOST_ON:
+ *enabled = 1;
+ break;
+ default:
+ *enabled = 0;
+ break;
+ }
+
+ return ret;
+}
+
+/* called from gcpm when switching to DC amd when CC_MAX changes */
+static int max77759_set_charge_enabled(struct max77759_chgr_data *data,
+ int enabled, const char *reason)
+{
+ return gvotable_cast_vote(data->mode_votable, reason,
+ (void*)GBMS_CHGR_MODE_CHGR_BUCK_ON,
+ enabled);
+}
+
+/* called from google_charger on disconnect */
+static int max77759_set_charge_disable(struct max77759_chgr_data *data,
+ int enabled, const char *reason)
+{
+ return gvotable_cast_vote(data->mode_votable, reason,
+ (void*)GBMS_CHGR_MODE_ALL_OFF,
+ enabled);
+}
+
+/* turn off CHGIN_INSEL: works when max77559 registers are not protected */
+static int max77759_chgin_input_suspend(struct max77759_chgr_data *data,
+ bool enabled, const char *reason)
+{
+ const u8 value = (!enabled) << MAX77759_CHG_CNFG_12_CHGINSEL_SHIFT;
+
+ data->chgin_input_suspend = enabled; /* cache */
+
+ return max77759_reg_update(data, MAX77759_CHG_CNFG_12,
+ MAX77759_CHG_CNFG_12_CHGINSEL_MASK,
+ value);
+}
+
+/* turn off WCIN_INSEL: works when max77559 registers are not protected */
+static int max77759_wcin_input_suspend(struct max77759_chgr_data *data,
+ bool enabled, const char *reason)
+{
+ const u8 value = (!enabled) << MAX77759_CHG_CNFG_12_WCINSEL_SHIFT;
+
+ data->wcin_input_suspend = enabled; /* cache */
+
+ return max77759_reg_update(data, MAX77759_CHG_CNFG_12,
+ MAX77759_CHG_CNFG_12_WCINSEL_MASK,
+ value);
+}
+
+static int max77759_set_regulation_voltage(struct max77759_chgr_data *data,
+ int voltage_uv)
+{
+ u8 value;
+
+ if (voltage_uv >= 4500000)
+ value = 0x32;
+ else if (voltage_uv < 4000000)
+ value = 0x38 + (voltage_uv - 3800000) / 100000;
+ else
+ value = (voltage_uv - 4000000) / 10000;
+
+ value = VALUE2FIELD(MAX77759_CHG_CNFG_04_CHG_CV_PRM, value);
+ return max77759_reg_update(data, MAX77759_CHG_CNFG_04,
+ MAX77759_CHG_CNFG_04_CHG_CV_PRM_MASK,
+ value);
+}
+
+static int max77759_get_regulation_voltage_uv(struct max77759_chgr_data *data,
+ int *voltage_uv)
+{
+ u8 value;
+ int ret;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_04, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value < 0x33)
+ *voltage_uv = (4000 + value * 10) * 1000;
+ else if (value == 0x38)
+ *voltage_uv = 3800 * 1000;
+ else if (value == 0x39)
+ *voltage_uv = 3900 * 1000;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+/* set charging current to 0 to disable charging (MODE=0) */
+static int max77759_set_charger_current_max_ua(struct max77759_chgr_data *data,
+ int current_ua)
+{
+ const int enabled = current_ua != 0;
+ u8 value;
+ int ret;
+
+ if (current_ua < 0)
+ return 0;
+
+ /* ilim=0 -> switch to mode 0 and suspend charging */
+ if (current_ua == 0)
+ value = 0x0;
+ else if (current_ua <= 200000)
+ value = 0x03;
+ else if (current_ua > 4000000)
+ value = 0x3F;
+ else
+ value = 0x3 + (current_ua - 200000) / 66670;
+
+ value = VALUE2FIELD(MAX77759_CHG_CNFG_02_CHGCC, value);
+ ret = max77759_reg_update(data, MAX77759_CHG_CNFG_02,
+ MAX77759_CHG_CNFG_02_CHGCC_MASK,
+ value);
+ if (ret == 0)
+ ret = max77759_set_charge_enabled(data, enabled, "CC_MAX");
+
+ return ret;
+}
+
+static int max77759_get_charger_current_max_ua(struct max77759_chgr_data *data,
+ int *current_ua)
+{
+ u8 value;
+ int ret;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_02,
+ &value);
+ if (ret < 0)
+ return ret;
+
+ /* TODO: fix the rounding */
+ value = VALUE2FIELD(MAX77759_CHG_CNFG_02_CHGCC, value);
+
+ /* ilim=0 -> mode 0 with charging suspended */
+ if (value == 0)
+ *current_ua = 0;
+ else if (value < 3)
+ *current_ua = 133 * 1000;
+ else if (value >= 0x3C)
+ *current_ua = 4000 * 1000;
+ else
+ *current_ua = 133000 + (value - 2) * 66670;
+
+ return 0;
+}
+
+/* enable autoibus and charger mode */
+static int max77759_chgin_set_ilim_max_ua(struct max77759_chgr_data *data,
+ int ilim_ua)
+{
+ const bool suspend = ilim_ua == 0;
+ u8 value;
+ int ret;
+
+ /* TODO: disable charging */
+ if (ilim_ua < 0)
+ return 0;
+
+ if (ilim_ua == 0)
+ value = 0x00;
+ else if (ilim_ua > 3200000)
+ value = 0x7f;
+ else
+ value = 0x04 + (ilim_ua - 125000) / 25000;
+
+ value = VALUE2FIELD(MAX77759_CHG_CNFG_09_NO_AUTOIBUS, 1) |
+ VALUE2FIELD(MAX77759_CHG_CNFG_09_CHGIN_ILIM, value);
+ ret = max77759_reg_update(data, MAX77759_CHG_CNFG_09,
+ MAX77759_CHG_CNFG_09_NO_AUTOIBUS |
+ MAX77759_CHG_CNFG_09_CHGIN_ILIM_MASK,
+ value);
+ if (ret == 0)
+ ret = max77759_chgin_input_suspend(data, suspend, "ILIM");
+
+ return ret;
+}
+
+static int max77759_chgin_get_ilim_max_ua(struct max77759_chgr_data *data,
+ int *ilim_ua)
+{
+ int icl, ret;
+ u8 value;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_09, &value);
+ if (ret < 0)
+ return ret;
+
+ value = FIELD2VALUE(MAX77759_CHG_CNFG_09_CHGIN_ILIM, value);
+ if (value == 0)
+ icl = 0;
+ else if (value > 3)
+ icl = 100 + (value - 3) * 25;
+ *ilim_ua = icl * 1000;
+
+ if (data->chgin_input_suspend)
+ *ilim_ua = 0;
+
+ return 0;
+}
+
+static int max77759_wcin_set_ilim_max_ua(struct max77759_chgr_data *data,
+ int ilim_ua)
+{
+ const bool suspend = ilim_ua == 0;
+ u8 value;
+ int ret;
+
+ if (ilim_ua < 0)
+ return -EINVAL;
+
+ if (ilim_ua == 0)
+ value = 0x00;
+ else if (ilim_ua <= 125000)
+ value = 0x01;
+ else
+ value = 0x3 + (ilim_ua - 125000) / 31250;
+
+ value = VALUE2FIELD(MAX77759_CHG_CNFG_10_WCIN_ILIM, value);
+ ret = max77759_reg_update(data, MAX77759_CHG_CNFG_10,
+ MAX77759_CHG_CNFG_10_WCIN_ILIM_MASK,
+ value);
+
+ if (ret == 0)
+ ret = max77759_wcin_input_suspend(data, suspend, "DC_ICL");
+
+ return ret;
+}
+
+static int max77759_wcin_get_ilim_max_ua(struct max77759_chgr_data *data,
+ int *ilim_ua)
+{
+ int ret;
+ u8 value;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_10, &value);
+ if (ret < 0)
+ return ret;
+
+ value = FIELD2VALUE(MAX77759_CHG_CNFG_10_WCIN_ILIM, value);
+ if (value == 0)
+ *ilim_ua = 0;
+ else if (value < 4)
+ *ilim_ua = 125000;
+ else
+ *ilim_ua = 125000 + (value - 3) * 31250;
+
+ if (data->wcin_input_suspend)
+ *ilim_ua = 0;
+
+ return 0;
+}
+
+/* default is no suspend, any valid vote will suspend */
+static void max77759_dc_suspend_vote_callback(struct gvotable_election *el,
+ const char *reason, void *value)
+{
+ struct max77759_chgr_data *data = gvotable_get_data(el);
+ int ret, result = (int)value;
+
+ ret = max77759_wcin_input_suspend(data, result > 0, "DC_SUSPEND");
+
+ dev_info(data->dev, "DC_SUSPEND reason=%s, value=%d suspend=%d (%d)\n",
+ reason ? reason : "", result, result >= 0, ret);
+}
+
+static void max77759_dcicl_callback(struct gvotable_election *el,
+ const char *reason,
+ void *value)
+{
+ struct max77759_chgr_data *data = gvotable_get_data(el);
+ int dc_icl = (int)value;
+ const bool suspend = dc_icl == 0;
+ int ret;
+
+ pr_debug("%s: dc_icl=%d suspend=%d (%d)\n", __func__,
+ dc_icl, suspend, ret);
+
+ ret = max77759_wcin_set_ilim_max_ua(data, dc_icl);
+ if (ret < 0)
+ dev_err(data->dev, "cannot set DC_ICL (%d)\n", ret);
+
+ ret = gvotable_cast_vote(data->dc_suspend_votable, "DC_ICL",
+ (void *)1,
+ suspend);
+
+ if (ret < 0)
+ dev_err(data->dev, "cannot set DC_SUSPEND=%d (%d)\n",
+ suspend, ret);
+}
+
+/*************************
+ * WCIN PSY REGISTRATION *
+ *************************/
+static enum power_supply_property max77759_wcin_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_REAL_TYPE,
+ POWER_SUPPLY_PROP_DC_RESET,
+};
+
+static int max77759_wcin_is_present(struct max77759_chgr_data *data)
+{
+ uint8_t int_ok;
+ int ret;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_INT_OK, &int_ok);
+ return (ret == 0) && _chg_int_ok_wcin_ok_get(int_ok);
+}
+
+static int max77759_wcin_is_online(struct max77759_chgr_data *data)
+{
+ uint8_t val;
+ int ret;
+
+ ret = max77759_wcin_is_present(data) &&
+ max77759_reg_read(data->regmap, MAX77759_CHG_DETAILS_02, &val);
+ return (ret == 0) && _chg_details_02_wcin_sts_get(val);
+}
+
+static int max77759_wcin_voltage_max(struct max77759_chgr_data *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->wlc_psy) {
+ chg->wlc_psy = power_supply_get_by_name("wireless");
+ if (!chg->wlc_psy)
+ return -ENODEV;
+ }
+
+ rc = power_supply_get_property(chg->wlc_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, val);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get VOLTAGE_MAX, rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int max77759_wcin_voltage_now(struct max77759_chgr_data *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->wlc_psy) {
+ chg->wlc_psy = power_supply_get_by_name("wireless");
+ if (!chg->wlc_psy)
+ return -ENODEV;
+ }
+
+ rc = power_supply_get_property(chg->wlc_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get VOLTAGE_NOW, rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int max77759_wcin_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77759_chgr_data *chgr = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = max77759_wcin_is_present(chgr);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = max77759_wcin_is_online(chgr);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = max77759_wcin_voltage_now(chgr, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = max77759_wcin_get_ilim_max_ua(chgr, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = max77759_wcin_voltage_max(chgr, val);
+ break;
+ /* TODO: this is a local extension, not used from the p9221 driver */
+ case POWER_SUPPLY_PROP_REAL_TYPE:
+ val->intval = POWER_SUPPLY_TYPE_WIRELESS;
+ break;
+ case POWER_SUPPLY_PROP_DC_RESET:
+ /* TBD */
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+ return 0;
+}
+
+static int max77759_wcin_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max77759_chgr_data *chgr = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = max77759_wcin_set_ilim_max_ua(chgr, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_DC_RESET:
+ /* TBD */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int max77759_wcin_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc max77759_wcin_psy_desc = {
+ .name = "dc",
+ .type = POWER_SUPPLY_TYPE_WIRELESS,
+ .properties = max77759_wcin_props,
+ .num_properties = ARRAY_SIZE(max77759_wcin_props),
+ .get_property = max77759_wcin_get_prop,
+ .set_property = max77759_wcin_set_prop,
+ .property_is_writeable = max77759_wcin_prop_is_writeable,
+};
+
+static int max77759_init_wcin_psy(struct max77759_chgr_data *data)
+{
+ struct power_supply_config wcin_cfg = {};
+
+ wcin_cfg.drv_data = data;
+ wcin_cfg.of_node = data->dev->of_node;
+ data->wcin_psy = devm_power_supply_register(
+ data->dev, &max77759_wcin_psy_desc, &wcin_cfg);
+ if (IS_ERR(data->wcin_psy)) {
+ pr_err("Couldn't register wlc power supply\n");
+ return PTR_ERR(data->wcin_psy);
+ }
+
+ return 0;
+}
+
+/*
+ * NOTE: could also check aicl to determine whether the adapter is, in fact,
+ * at fault. Possibly qualify this with battery voltage as subpar adapters
+ * are likely to flag AICL when the battery is at high voltage.
+ */
+static int max77759_is_limited(struct max77759_chgr_data *data)
+{
+ int ret;
+ u8 value;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_INT_OK, &value);
+ return (ret == 0) && _chg_int_ok_inlim_ok_get(value) == 0;
+}
+
+static int max77759_is_present(struct max77759_chgr_data *data)
+{
+ uint8_t int_ok;
+ int ret;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_INT_OK, &int_ok);
+ return (ret == 0) && (_chg_int_ok_chgin_ok_get(int_ok) ||
+ _chg_int_ok_wcin_ok_get(int_ok));
+}
+
+static int max77759_is_online(struct max77759_chgr_data *data)
+{
+ uint8_t val;
+ int ret;
+
+ ret = max77759_is_present(data) &&
+ max77759_reg_read(data->regmap, MAX77759_CHG_DETAILS_02, &val);
+ return (ret == 0) && (_chg_details_02_chgin_sts_get(val) ||
+ _chg_details_02_wcin_sts_get(val));
+}
+
+static int max77759_get_charge_type(struct max77759_chgr_data *data)
+{
+ int ret;
+ uint8_t reg;
+
+ if (!max77759_is_online(data))
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_DETAILS_01, ®);
+ if (ret < 0)
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+
+ switch(_chg_details_01_chg_dtls_get(reg)) {
+ case CHGR_DTLS_DEAD_BATTERY_MODE:
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ case CHGR_DTLS_FAST_CHARGE_CONST_CURRENT_MODE:
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ case CHGR_DTLS_FAST_CHARGE_CONST_VOLTAGE_MODE:
+ case CHGR_DTLS_TOP_OFF_MODE:
+ return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+
+ case CHGR_DTLS_DONE_MODE:
+ case CHGR_DTLS_TIMER_FAULT_MODE:
+ case CHGR_DTLS_DETBAT_HIGH_SUSPEND_MODE:
+ case CHGR_DTLS_OFF_MODE:
+ case CHGR_DTLS_OFF_HIGH_TEMP_MODE:
+ case CHGR_DTLS_OFF_WATCHDOG_MODE:
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+}
+
+static int max77759_get_status(struct max77759_chgr_data *data)
+{
+ uint8_t val;
+ int ret;
+
+ if (!max77759_is_online(data))
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_DETAILS_01, &val);
+ if (ret < 0)
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+
+ switch (_chg_details_01_chg_dtls_get(val)) {
+ case CHGR_DTLS_DEAD_BATTERY_MODE:
+ case CHGR_DTLS_FAST_CHARGE_CONST_CURRENT_MODE:
+ case CHGR_DTLS_FAST_CHARGE_CONST_VOLTAGE_MODE:
+ return POWER_SUPPLY_STATUS_CHARGING;
+ case CHGR_DTLS_TOP_OFF_MODE:
+ case CHGR_DTLS_DONE_MODE:
+ return POWER_SUPPLY_STATUS_FULL;
+ case CHGR_DTLS_TIMER_FAULT_MODE:
+ case CHGR_DTLS_DETBAT_HIGH_SUSPEND_MODE:
+ case CHGR_DTLS_OFF_MODE:
+ case CHGR_DTLS_OFF_HIGH_TEMP_MODE:
+ case CHGR_DTLS_OFF_WATCHDOG_MODE:
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+}
+
+static int max77759_read_from_fg(struct max77759_chgr_data *data,
+ enum power_supply_property psp,
+ union power_supply_propval *pval)
+{
+ int ret = -ENODEV;
+
+ if (!data->fg_psy)
+ data->fg_psy = power_supply_get_by_name("maxfg");
+ if (data->fg_psy)
+ ret = power_supply_get_property(data->fg_psy, psp, pval);
+ if (ret < 0)
+ pval->intval = -1;
+ return 0;
+}
+
+static int max77759_get_chg_chgr_state(struct max77759_chgr_data *data,
+ union gbms_charger_state *chg_state)
+{
+ int usb_present, usb_valid, dc_present, dc_valid;
+ union power_supply_propval pval;
+ const char *source = "";
+ uint8_t int_ok, dtls;
+ int icl = 0;
+ int rc;
+
+ chg_state->v = 0;
+ chg_state->f.chg_status = max77759_get_status(data);
+ chg_state->f.chg_type = max77759_get_charge_type(data);
+ chg_state->f.flags = gbms_gen_chg_flags(chg_state->f.chg_status,
+ chg_state->f.chg_type);
+
+ rc = max77759_reg_read(data->regmap, MAX77759_CHG_INT_OK, &int_ok);
+ if (rc == 0)
+ rc = max77759_reg_read(data->regmap, MAX77759_CHG_DETAILS_02,
+ &dtls);
+
+ /* present when connected, valid when FET is closed */
+ usb_present = (rc == 0) && _chg_int_ok_chgin_ok_get(int_ok);
+ usb_valid = usb_present && _chg_details_02_chgin_sts_get(dtls);
+
+ /* present if in field, valid when FET is closed */
+ dc_present = (rc == 0) && _chg_int_ok_wcin_ok_get(int_ok);
+ dc_valid = dc_present && _chg_details_02_wcin_sts_get(dtls);
+
+ rc = max77759_read_from_fg(data, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ &pval);
+ if (rc == 0)
+ chg_state->f.vchrg = pval.intval / 1000;
+
+ if (chg_state->f.chg_status == POWER_SUPPLY_STATUS_DISCHARGING)
+ goto exit_done;
+
+ rc = max77759_is_limited(data);
+ if (rc > 0)
+ chg_state->f.flags |= GBMS_CS_FLAG_ILIM;
+
+ /* TODO: b/ handle input MUX corner cases */
+ if (usb_valid) {
+ max77759_chgin_get_ilim_max_ua(data, &icl);
+ source = dc_present ? "Uw" : "u";
+ } else if (dc_valid) {
+ max77759_wcin_get_ilim_max_ua(data, &icl);
+ source = usb_present ? "uW" : "w";
+ } else if (usb_present && dc_present) {
+ source = "-";
+ } else if (usb_present) {
+ source = "u?";
+ } else if (dc_present) {
+ source = "w?";
+ }
+
+ chg_state->f.icl = icl / 1000;
+
+exit_done:
+ pr_debug("MSC_PCS chg_state=%lx [0x%x:%d:%d:%d:%d] chg=%s\n",
+ (unsigned long)chg_state->v,
+ chg_state->f.flags,
+ chg_state->f.chg_type,
+ chg_state->f.chg_status,
+ chg_state->f.vchrg,
+ chg_state->f.icl,
+ source);
+
+ return 0;
+}
+
+
+static int max77759_get_charge_done(struct max77759_chgr_data *data)
+{
+ int status;
+
+ status = max77759_get_status(data);
+ return (status == CHGR_DTLS_TOP_OFF_MODE) ||
+ (status == CHGR_DTLS_DONE_MODE);
+}
+
+static int max77759_find_pmic(struct max77759_chgr_data *data)
+{
+ if (!data->pmic_i2c_client) {
+ struct device_node *dn;
+
+ dn = of_parse_phandle(data->dev->of_node, "max77759,pmic", 0);
+ if (!dn)
+ return -ENXIO;
+
+ data->pmic_i2c_client = of_find_i2c_device_by_node(dn);
+ if (!data->pmic_i2c_client)
+ return -EAGAIN;
+ }
+
+ return !!data->pmic_i2c_client;
+}
+
+static int max77759_find_fg(struct max77759_chgr_data *data)
+{
+ struct device_node *dn;
+
+ if (data->fg_i2c_client)
+ return 0;
+
+ dn = of_parse_phandle(data->dev->of_node, "max77759,max_m5", 0);
+ if (!dn)
+ return -ENXIO;
+
+ data->fg_i2c_client = of_find_i2c_device_by_node(dn);
+ if (!data->fg_i2c_client)
+ return -EAGAIN;
+
+ return 0;
+}
+
+/* only valid in mode 5, 6, 7, e, f */
+static int max77759_read_iic_from_fg(struct max77759_chgr_data *data, int *iic)
+{
+ int ret;
+
+ ret = max77759_find_fg(data);
+ if (ret < 0)
+ return ret;
+
+ ret = max_m5_read_actual_input_current_ua(data->fg_i2c_client, iic);
+ if (ret < 0)
+ *iic = -1;
+
+ return ret;
+}
+
+static int max77759_wd_tickle(struct max77759_chgr_data *data)
+{
+ int ret;
+ u8 reg, reg_new;
+
+ mutex_lock(&data->io_lock);
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_CNFG_00, ®);
+ if (ret == 0) {
+ reg_new = _chg_cnfg_00_wdtclr_set(reg, 0x1);
+ ret = max77759_reg_write(data->regmap, MAX77759_CHG_CNFG_00,
+ reg_new);
+ }
+
+ if (ret < 0)
+ pr_err("WD Tickle failed %d\n", ret);
+
+ mutex_unlock(&data->io_lock);
+ return ret;
+}
+
+
+static int max77759_set_online(struct max77759_chgr_data *data, bool online)
+{
+ int ret;
+
+ ret = max77759_wd_tickle(data);
+ if (ret < 0)
+ pr_err("cannot tickle the watchdog\n");
+
+ if (data->online != online) {
+ ret = gvotable_cast_vote(data->mode_votable, "OFFLINE",
+ (void *)GBMS_CHGR_MODE_ALL_OFF,
+ !online);
+ data->online = online;
+ }
+
+ return ret;
+}
+
+static int max77759_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ struct max77759_chgr_data *data = power_supply_get_drvdata(psy);
+ int ret = -EINVAL;
+
+ pm_runtime_get_sync(data->dev);
+ if (!data->init_complete || !data->resume_complete) {
+ pm_runtime_put_sync(data->dev);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(data->dev);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = max77759_chgin_set_ilim_max_ua(data, pval->intval);
+ pr_debug("%s: icl=%d (%d)\n", __func__, pval->intval, ret);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = max77759_set_charger_current_max_ua(data, pval->intval);
+ pr_debug("%s: charge_current=%d (%d)\n",
+ __func__, pval->intval, ret);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = max77759_set_regulation_voltage(data, pval->intval);
+ pr_debug("%s: charge_voltage=%d (%d)\n",
+ __func__, pval->intval, ret);
+ break;
+ /* called from google_cpm when switching chargers */
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ ret = max77759_set_charge_enabled(data, pval->intval,
+ "PSP_ENABLED");
+ pr_debug("%s: charging_enabled=%d (%d)\n",
+ __func__, pval->intval, ret);
+ break;
+ /* called from google_charger on disconnect */
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ ret = max77759_set_charge_disable(data, pval->intval,
+ "PSP_DISABLE");
+ pr_debug("%s: charge_disable=%d (%d)\n",
+ __func__, pval->intval, ret);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = max77759_set_online(data, pval->intval != 0);
+ break;
+ default:
+ break;
+ }
+
+ if (ret == 0 && data->wden)
+ max77759_wd_tickle(data);
+
+
+ return ret;
+}
+
+static int max77759_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *pval)
+{
+ struct max77759_chgr_data *data = power_supply_get_drvdata(psy);
+ union gbms_charger_state chg_state;
+ int enabled, rc, ret = 0;
+
+ pm_runtime_get_sync(data->dev);
+ if (!data->init_complete || !data->resume_complete) {
+ pm_runtime_put_sync(data->dev);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(data->dev);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ ret = max77759_get_charge_enabled(data, &enabled);
+ if (ret == 0)
+ pval->intval = !enabled;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_DONE:
+ pval->intval = max77759_get_charge_done(data);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ pval->intval = max77759_get_charge_type(data);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ ret = max77759_get_charge_enabled(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ ret = max77759_get_chg_chgr_state(data, &chg_state);
+ if (ret == 0)
+ pval->int64val = chg_state.v;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = max77759_get_charger_current_max_ua(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = max77759_get_regulation_voltage_uv(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ pval->intval = max77759_is_online(data);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ pval->intval = max77759_is_present(data);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = max77759_chgin_get_ilim_max_ua(data, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ pval->intval = max77759_is_limited(data);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ pval->intval = max77759_get_status(data);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = max77759_read_from_fg(data, psp, pval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ rc = max77759_read_iic_from_fg(data, &pval->intval);
+ if (rc < 0)
+ dev_err(data->dev, "cannot read current rc=%d\n", rc);
+ break;
+ default:
+ dev_err(data->dev, "property (%d) unsupported.\n", psp);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int max77759_psy_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX: /* compat, same the next */
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * TODO: POWER_SUPPLY_PROP_RERUN_AICL, POWER_SUPPLY_PROP_TEMP
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX
+ */
+static enum power_supply_property max77759_psy_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_DISABLE,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, /* compat */
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static struct power_supply_desc max77759_psy_desc = {
+ .name = "max77759-charger",
+ .type = POWER_SUPPLY_TYPE_UNKNOWN,
+ .properties = max77759_psy_props,
+ .num_properties = ARRAY_SIZE(max77759_psy_props),
+ .get_property = max77759_psy_get_property,
+ .set_property = max77759_psy_set_property,
+ .property_is_writeable = max77759_psy_is_writeable,
+};
+
+static ssize_t show_fship_dtls(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max77759_chgr_data *data = dev_get_drvdata(dev);
+ static char *fship_reason[] = {"None", "PWRONB1", "PWRONB1", "PWR"};
+ u8 pmic_rd;
+ int ret;
+
+ if (data->fship_dtls != -1)
+ goto exit_done;
+
+
+ ret = max77759_find_pmic(data);
+ if (ret < 0)
+ return ret;
+
+ ret = max777x9_pmic_reg_read(data->pmic_i2c_client,
+ MAX77759_FSHIP_EXIT_DTLS,
+ &pmic_rd, 1);
+ if (ret < 0)
+ return -EIO;
+
+ if (pmic_rd & MAX77759_FSHIP_EXIT_DTLS_RD) {
+ u8 fship_dtls;
+
+ ret = max77759_reg_read(data->regmap, MAX77759_CHG_DETAILS_03,
+ &fship_dtls);
+ if (ret < 0)
+ return -EIO;
+
+ data->fship_dtls =
+ _chg_details_03_fship_exit_dtls_get(fship_dtls);
+
+ pmic_rd &= ~MAX77759_FSHIP_EXIT_DTLS_RD;
+ ret = max777x9_pmic_reg_write(data->pmic_i2c_client,
+ MAX77759_FSHIP_EXIT_DTLS,
+ &pmic_rd, 1);
+ if (ret < 0)
+ pr_err("FSHIP: cannot update RD (%d)\n", ret);
+
+ } else {
+ data->fship_dtls = 0;
+ }
+
+exit_done:
+ return scnprintf(buf, PAGE_SIZE, "%d %s\n", data->fship_dtls,
+ fship_reason[data->fship_dtls]);
+}
+
+static DEVICE_ATTR(fship_dtls, 0444, show_fship_dtls, NULL);
+
+static ssize_t show_sysuvlo1_cnt(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max77759_chgr_data *data = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n",
+ atomic_read(&data->sysuvlo1_cnt));
+}
+
+static DEVICE_ATTR(sysuvlo1_cnt, 0444, show_sysuvlo1_cnt, NULL);
+
+static ssize_t show_sysuvlo2_cnt(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max77759_chgr_data *data = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n",
+ atomic_read(&data->sysuvlo2_cnt));
+}
+
+static DEVICE_ATTR(sysuvlo2_cnt, 0444, show_sysuvlo2_cnt, NULL);
+
+
+static int dbg_init_fs(struct max77759_chgr_data *data)
+{
+ int ret;
+
+ ret = device_create_file(data->dev, &dev_attr_fship_dtls);
+ if (ret != 0)
+ pr_err("Failed to create fship_dtls, ret=%d\n", ret);
+ ret = device_create_file(data->dev, &dev_attr_sysuvlo1_cnt);
+ if (ret != 0)
+ pr_err("Failed to create sysuvlo1_cnt, ret=%d\n", ret);
+ ret = device_create_file(data->dev, &dev_attr_sysuvlo2_cnt);
+ if (ret != 0)
+ pr_err("Failed to create sysuvlo2_cnt, ret=%d\n", ret);
+
+ data->de = debugfs_create_dir("max77759_chg", 0);
+ if (IS_ERR_OR_NULL(data->de))
+ return -EINVAL;
+
+ debugfs_create_atomic_t("sysuvlo1_cnt", 0644, data->de,
+ &data->sysuvlo1_cnt);
+ debugfs_create_atomic_t("sysuvlo2_cnt", 0644, data->de,
+ &data->sysuvlo2_cnt);
+
+ return 0;
+}
+
+static bool max77759_chg_is_reg(struct device *dev, unsigned int reg)
+{
+ return (reg >= MAX77759_CHG_INT) && (reg <= MAX77759_CHG_CNFG_19);
+}
+
+static const struct regmap_config max77759_chg_regmap_cfg = {
+ .name = "max77759_charger",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = MAX77759_CHG_CNFG_19,
+ .readable_reg = max77759_chg_is_reg,
+ .volatile_reg = max77759_chg_is_reg,
+
+};
+
+static u8 max77759_int_mask[MAX77759_CHG_INT_COUNT] = {
+ ~(MAX77759_CHG_INT_MASK_CHGIN_M |
+ MAX77759_CHG_INT_MASK_WCIN_M |
+ MAX77759_CHG_INT_MASK_CHG_M |
+ MAX77759_CHG_INT_MASK_BAT_M),
+ ~(MAX77759_CHG_INT2_MASK_SYS_UVLO1_M |
+ MAX77759_CHG_INT2_MASK_SYS_UVLO2_M |
+ MAX77759_CHG_INT2_MASK_CHG_STA_CV_M |
+ MAX77759_CHG_INT2_MASK_CHG_STA_TO_M |
+ MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M),
+};
+
+static irqreturn_t max77759_chgr_irq(int irq, void *client)
+{
+ struct max77759_chgr_data *data = client;
+ u8 chg_int[MAX77759_CHG_INT_COUNT];
+ bool broadcast = false;
+ int ret;
+
+ ret = max77759_readn(data->regmap, MAX77759_CHG_INT, chg_int,
+ sizeof(chg_int));
+ if (ret < 0)
+ return IRQ_NONE;
+ ret = max77759_writen(data->regmap, MAX77759_CHG_INT, chg_int,
+ sizeof(chg_int));
+ if (ret < 0)
+ return IRQ_NONE;
+
+ pr_debug("INT : %x %x\n", chg_int[0], chg_int[1]);
+
+ if ((chg_int[0] & ~max77759_int_mask[0]) == 0 &&
+ (chg_int[1] & ~max77759_int_mask[1]) == 0)
+ return IRQ_NONE;
+
+ if (chg_int[1] & MAX77759_CHG_INT2_SYS_UVLO1_I)
+ atomic_inc(&data->sysuvlo1_cnt);
+
+ if (chg_int[1] & MAX77759_CHG_INT2_SYS_UVLO2_I)
+ atomic_inc(&data->sysuvlo2_cnt);
+
+ if (chg_int[1] & MAX77759_CHG_INT2_MASK_CHG_STA_TO_M) {
+ pr_debug("%s: TOP_OFF\n", __func__);
+ /*
+ * TODO: rewrite to FV_UV when if entering TOP off far
+ * from terminal voltage
+ */
+ }
+
+ if (chg_int[1] & MAX77759_CHG_INT2_MASK_CHG_STA_CV_M)
+ pr_debug("%s: CV_MODE\n", __func__);
+
+ if (chg_int[1] & MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M) {
+ pr_debug("%s: CHARGE DONE\n", __func__);
+
+ if (data->psy)
+ power_supply_changed(data->psy);
+ }
+
+ /* wired input is changed */
+ if (chg_int[0] & MAX77759_CHG_INT_MASK_CHGIN_M) {
+
+ if (data->chgin_psy)
+ power_supply_changed(data->chgin_psy);
+ else
+ power_supply_changed(data->psy);
+ }
+
+ /* wireless input is changed */
+ if (data->wcin_psy && (chg_int[0] & MAX77759_CHG_INT_MASK_WCIN_M))
+ power_supply_changed(data->wcin_psy);
+
+
+ /* someting else is changed */
+ broadcast = (chg_int[0] & MAX77759_CHG_INT_MASK_CHG_M) |
+ (chg_int[0] & MAX77759_CHG_INT_MASK_BAT_M);
+ if (data->psy && broadcast)
+ power_supply_changed(data->psy);
+
+ return IRQ_HANDLED;
+}
+
+static int max77759_setup_votables(struct max77759_chgr_data *data)
+{
+ int ret;
+
+ /* votes might change mode */
+ data->mode_votable = gvotable_create_int_election(NULL, NULL,
+ max77759_mode_callback,
+ data);
+ if (IS_ERR_OR_NULL(data->mode_votable)) {
+ ret = PTR_ERR(data->mode_votable);
+ dev_err(data->dev, "no mode votable (%d)\n");
+ return ret;
+ }
+
+ gvotable_set_vote2str(data->mode_votable, gvotable_v2s_uint);
+ /* will use gvotable_get_default() when available */
+ gvotable_set_default(data->mode_votable,
+ (void *)GBMS_CHGR_MODE_ALL_OFF);
+ gvotable_election_set_name(data->mode_votable, GBMS_MODE_VOTABLE);
+
+ /* Wireless charging, DC name is for compat */
+ data->dc_suspend_votable =
+ gvotable_create_bool_election(NULL,
+ max77759_dc_suspend_vote_callback,
+ data);
+ if (IS_ERR_OR_NULL(data->dc_suspend_votable)) {
+ ret = PTR_ERR(data->dc_suspend_votable);
+ dev_err(data->dev, "no dc_suspend votable (%d)\n", ret);
+ return ret;
+ }
+
+ gvotable_set_vote2str(data->dc_suspend_votable, gvotable_v2s_int);
+ gvotable_set_default(data->dc_suspend_votable, (void *)-1);
+ gvotable_election_set_name(data->dc_suspend_votable, "DC_SUSPEND");
+ gvotable_use_default(data->dc_suspend_votable, true);
+
+ data->dc_icl_votable =
+ gvotable_create_int_election(NULL, gvotable_comparator_int_min,
+ max77759_dcicl_callback,
+ data);
+ if (IS_ERR_OR_NULL(data->dc_icl_votable)) {
+ ret = PTR_ERR(data->dc_icl_votable);
+ dev_err(data->dev, "no dc_icl votable (%d)\n", ret);
+ return ret;
+ }
+
+ gvotable_set_vote2str(data->dc_icl_votable, gvotable_v2s_uint);
+ gvotable_set_default(data->dc_icl_votable, (void *)700000);
+ gvotable_election_set_name(data->dc_icl_votable, "DC_ICL");
+ gvotable_use_default(data->dc_icl_votable, true);
+
+ return 0;
+}
+
+static int max77759_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct power_supply_config chgr_psy_cfg = { 0 };
+ struct device *dev = &client->dev;
+ struct max77759_chgr_data *data;
+ struct regmap *regmap;
+ const char *psy_name;
+ int ret = 0;
+ u8 ping;
+
+ regmap = devm_regmap_init_i2c(client, &max77759_chg_regmap_cfg);
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
+ ret = max77759_reg_read(regmap, MAX77759_CHG_CNFG_00, &ping);
+ if (ret < 0)
+ return -ENODEV;
+
+ /* TODO: PING or read HW version from PMIC */
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ data->regmap = regmap;
+ data->fship_dtls = -1;
+ data->wden = false; /* TODO: read from DT */
+ mutex_init(&data->io_lock);
+ atomic_set(&data->sysuvlo1_cnt, 0);
+ atomic_set(&data->sysuvlo2_cnt, 0);
+ i2c_set_clientdata(client, data);
+
+ ret = of_property_read_string(dev->of_node, "max77759,psy-name",
+ &psy_name);
+ if (ret == 0)
+ max77759_psy_desc.name = devm_kstrdup(dev, psy_name,
+ GFP_KERNEL);
+
+ chgr_psy_cfg.drv_data = data;
+ chgr_psy_cfg.supplied_to = NULL;
+ chgr_psy_cfg.num_supplicants = 0;
+ data->psy = devm_power_supply_register(dev, &max77759_psy_desc,
+ &chgr_psy_cfg);
+ if (IS_ERR(data->psy)) {
+ dev_err(dev, "Failed to register psy rc = %ld\n",
+ PTR_ERR(data->psy));
+ return -EINVAL;
+ }
+
+ ret = max77759_setup_votables(data);
+ if (ret < 0)
+ return -EINVAL;
+
+ data->irq_gpio = of_get_named_gpio(dev->of_node, "max77759,irq-gpio",
+ 0);
+ if (data->irq_gpio < 0) {
+ dev_err(dev, "failed get irq_gpio\n");
+ } else {
+ client->irq = gpio_to_irq(data->irq_gpio);
+
+ ret = devm_request_threaded_irq(data->dev, client->irq, NULL,
+ max77759_chgr_irq,
+ IRQF_TRIGGER_LOW |
+ IRQF_SHARED |
+ IRQF_ONESHOT,
+ "max77759_charger",
+ data);
+ if (ret == 0) {
+ enable_irq_wake(client->irq);
+
+ /* might cause the isr to be called */
+ max77759_chgr_irq(-1, data);
+ ret = max77759_writen(regmap, MAX77759_CHG_INT_MASK,
+ max77759_int_mask,
+ sizeof(max77759_int_mask));
+ if (ret < 0)
+ dev_err(dev, "cannot set irq_mask (%d)\n", ret);
+ }
+ }
+
+ ret = dbg_init_fs(data);
+ if (ret < 0)
+ dev_err(dev, "Failed to initialize debug fs\n");
+
+ mutex_lock(&data->io_lock);
+ ret = max77759_wdt_enable(data, data->wden);
+ if (ret < 0)
+ dev_err(dev, "wd enable=%d failed %d\n", data->wden, ret);
+ mutex_unlock(&data->io_lock);
+
+ data->init_complete = 1;
+ data->resume_complete = 1;
+
+ dev_info(dev, "registered as %s\n", max77759_psy_desc.name);
+ max77759_init_wcin_psy(data);
+ return 0;
+}
+
+static int max77759_charger_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+
+static const struct of_device_id max77759_charger_of_match_table[] = {
+ { .compatible = "maxim,max77759chrg"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77759_charger_of_match_table);
+
+static const struct i2c_device_id max77759_id[] = {
+ {"max77759_charger", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, max77759_id);
+
+#if defined CONFIG_PM
+static int max77759_charger_pm_suspend(struct device *dev)
+{
+ /* TODO: is there anything to do here? */
+ return 0;
+}
+
+static int max77759_charger_pm_resume(struct device *dev)
+{
+ /* TODO: is there anything to do here? */
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops max77759_charger_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(
+ max77759_charger_pm_suspend,
+ max77759_charger_pm_resume)
+};
+
+static struct i2c_driver max77759_charger_i2c_driver = {
+ .driver = {
+ .name = "max77759-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = max77759_charger_of_match_table,
+#ifdef CONFIG_PM
+ .pm = &max77759_charger_pm_ops,
+#endif
+ },
+ .id_table = max77759_id,
+ .probe = max77759_charger_probe,
+ .remove = max77759_charger_remove,
+};
+
+module_i2c_driver(max77759_charger_i2c_driver);
+
+MODULE_DESCRIPTION("Maxim 77759 Charger Driver");
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/max77759_maxq.c b/max77759_maxq.c
new file mode 100644
index 0000000..2dfd58c
--- /dev/null
+++ b/max77759_maxq.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020, Google Inc
+ *
+ * MAX77759 MAXQ opcode management.
+ */
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+
+#include "logbuffer.h"
+#include "max77759_regs.h"
+
+#define PAYLOAD_REQUEST_LENGTH_BYTES 33
+#define PAYLOAD_RESPONSE_LENGTH_BYTES 3
+#define OPCODE_GPIO_CONTROL_READ 0x23
+#define OPCODE_GPIO_CONTROL_R_REQ_LEN 1
+#define OPCODE_GPIO_CONTROL_R_RES_LEN 2
+#define OPCODE_GPIO_CONTROL_R_RES_OFFSET 1
+#define OPCODE_GPIO_CONTROL_WRITE 0x24
+#define OPCODE_GPIO_CONTROL_W_REQ_LEN 2
+#define OPCODE_GPIO_CONTROL_W_REQ_OFFSET 1
+#define OPCODE_GPIO_CONTROL_W_RES_LEN 1
+#define OPCODE_CHECK_CC_AND_SBU 0x85
+#define OPCODE_CHECK_CC_AND_SBU_REQ_LEN 9
+#define OPCODE_CHECK_CC_AND_SBU_RES_LEN 5
+#define MAXQ_AP_DATAOUT0 0x81
+#define MAXQ_AP_DATAOUT32 0xA1
+#define MAXQ_AP_DATAIN0 0xB1
+#define MAXQ_REPLY_TIMEOUT_MS 50
+
+struct max77759_maxq {
+ struct completion reply_done;
+ /* Denotes the current request in progress. */
+ unsigned int req_no;
+ /* Updated by the irq handler. */
+ unsigned int req_done;
+ /* Protects req_no and req_done variables. */
+ struct mutex req_lock;
+ struct logbuffer *log;
+ bool init_done;
+ struct regmap *regmap;
+
+ /* To make sure that there is only one request in progress. */
+ struct mutex maxq_lock;
+ u8 request_opcode;
+ bool poll;
+};
+
+enum request_payload_offset {
+ REQUEST_OPCODE,
+ /* Following are specific to CHECK_CC_AND_SBU */
+ REQUEST_TYPE,
+ CC1RD,
+ CC2RD,
+ CCADCSKIPPED,
+ CC1ADC,
+ CC2ADC,
+ SBU1ADC,
+ SBU2ADC,
+ WRITE_BYTES
+};
+
+enum response_payload_offset {
+ RESPONSE_OPCODE,
+ RESPONSE_TYPE,
+ RESULT,
+ CC_THRESH,
+ SBU_THRESH
+};
+
+/* Caller holds req_lock */
+static int maxq_read_response_locked(struct max77759_maxq *maxq,
+ u8 *response, u8 response_len)
+{
+ int ret = 0;
+
+ ret = regmap_bulk_read(maxq->regmap, MAXQ_AP_DATAIN0, response,
+ response_len);
+ if (ret) {
+ logbuffer_log(maxq->log, "I2C request failed ret:%d", ret);
+ return -EINVAL;
+ }
+
+ if (response[RESPONSE_OPCODE] != maxq->request_opcode) {
+ logbuffer_log(maxq->log,
+ "OPCODE does not match request:%u response:%u",
+ maxq->request_opcode, response[RESPONSE_OPCODE]);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Caller holds req_lock */
+static int maxq_wait_for_response_locked(struct max77759_maxq *maxq,
+ unsigned int current_request,
+ u8 *response, u8 response_len)
+{
+ int ret = 0;
+ unsigned long timeout = MAXQ_REPLY_TIMEOUT_MS;
+
+ /* Wait for response from Maxq */
+ if (maxq->poll) {
+ msleep(timeout);
+ ret = maxq_read_response_locked(maxq, response, response_len);
+ goto exit;
+ }
+
+ /* IRQ from MAXQ */
+ while (timeout) {
+ mutex_unlock(&maxq->req_lock);
+ timeout = wait_for_completion_timeout(&maxq->reply_done,
+ timeout);
+ mutex_lock(&maxq->req_lock);
+ if (current_request == maxq->req_done) {
+ ret = maxq_read_response_locked(maxq, response,
+ response_len);
+ break;
+ } else if (!timeout) {
+ logbuffer_log(maxq->log,
+ "Timeout or Request number does not match current:req:%u req_done:%u",
+ current_request, maxq->req_done);
+ ret = -EINVAL;
+ }
+ }
+
+exit:
+ logbuffer_log(maxq->log, "MAXQ DONE: current_req: %u ret:%d",
+ current_request, ret);
+ return ret;
+}
+
+int maxq_issue_opcode_command(struct max77759_maxq *maxq, u8 *request,
+ u8 request_length, u8 *response,
+ u8 response_length)
+{
+ unsigned int current_request;
+ int ret = -ENODEV;
+
+ if (!maxq->init_done)
+ return ret;
+
+ mutex_lock(&maxq->maxq_lock);
+ maxq->request_opcode = request[REQUEST_OPCODE];
+ mutex_lock(&maxq->req_lock);
+ current_request = ++maxq->req_no;
+
+ logbuffer_log(maxq->log, "MAXQ REQ:current_req: %u opcode:%u",
+ current_request, maxq->request_opcode);
+ ret = regmap_bulk_write(maxq->regmap, MAXQ_AP_DATAOUT0, request,
+ request_length);
+ if (ret) {
+ logbuffer_log(maxq->log, "I2C request write failed");
+ goto req_unlock;
+ }
+
+ if (request_length < PAYLOAD_REQUEST_LENGTH_BYTES) {
+ ret = regmap_write(maxq->regmap, MAXQ_AP_DATAOUT32, 0);
+ if (ret) {
+ logbuffer_log(maxq->log,
+ "I2C request write DATAOUT32 failed");
+ goto req_unlock;
+ }
+ }
+
+ ret = maxq_wait_for_response_locked(maxq, current_request,
+ response, response_length);
+req_unlock:
+ mutex_unlock(&maxq->req_lock);
+ mutex_unlock(&maxq->maxq_lock);
+ return ret;
+}
+
+void maxq_irq(struct max77759_maxq *maxq)
+{
+ mutex_lock(&maxq->req_lock);
+ maxq->req_done = maxq->req_no;
+ logbuffer_log(maxq->log, "MAXQ IRQ: req_done: %u", maxq->req_done);
+ complete(&maxq->reply_done);
+ mutex_unlock(&maxq->req_lock);
+}
+EXPORT_SYMBOL_GPL(maxq_irq);
+
+int maxq_query_contaminant(struct max77759_maxq *maxq, u8 cc1_raw,
+ u8 cc2_raw, u8 sbu1_raw, u8 sbu2_raw, u8 cc1_rd,
+ u8 cc2_rd, u8 type, u8 cc_adc_skipped,
+ u8 *response, u8 response_len)
+{
+ int ret;
+ u8 payload[OPCODE_CHECK_CC_AND_SBU_REQ_LEN];
+
+ payload[REQUEST_OPCODE] = OPCODE_CHECK_CC_AND_SBU;
+ payload[REQUEST_TYPE] = type;
+ payload[CC1RD] = cc1_rd;
+ payload[CC2RD] = cc2_rd;
+ payload[CCADCSKIPPED] = cc_adc_skipped;
+ payload[CC1ADC] = cc1_raw;
+ payload[CC2ADC] = cc2_raw;
+ payload[SBU1ADC] = sbu1_raw;
+ payload[SBU2ADC] = sbu2_raw;
+
+ logbuffer_log(maxq->log,
+ "MAXQ opcode:%#x type:%#x cc1_rd:%#x cc2_rd:%#x ADCSKIPPED:%#x cc1adc:%#x cc2adc:%#x sbu1adc:%#x sbu2adc:%#x",
+ OPCODE_CHECK_CC_AND_SBU, type, cc1_rd, cc2_rd,
+ cc_adc_skipped, cc1_raw, cc2_raw, sbu1_raw, sbu2_raw);
+ ret = maxq_issue_opcode_command(maxq, payload,
+ OPCODE_CHECK_CC_AND_SBU_REQ_LEN,
+ response, response_len);
+ if (!ret)
+ logbuffer_log(maxq->log, "MAXQ Contaminant response:%u",
+ response[RESULT]);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(maxq_query_contaminant);
+
+int maxq_gpio_control_read(struct max77759_maxq *maxq, u8 *gpio)
+{
+ int ret;
+ u8 request = OPCODE_GPIO_CONTROL_READ;
+ u8 response[OPCODE_GPIO_CONTROL_R_RES_LEN];
+
+ logbuffer_log(maxq->log, "MAXQ gpio read opcode:%#x", request);
+ ret = maxq_issue_opcode_command(maxq, &request,
+ OPCODE_GPIO_CONTROL_R_REQ_LEN,
+ response,
+ OPCODE_GPIO_CONTROL_R_RES_LEN);
+ if (!ret)
+ *gpio = response[OPCODE_GPIO_CONTROL_R_RES_OFFSET];
+ logbuffer_log(maxq->log, "MAXQ GPIO:%#x", *gpio);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(maxq_gpio_control_read);
+
+int maxq_gpio_control_write(struct max77759_maxq *maxq, u8 gpio)
+{
+ int ret;
+ u8 request[OPCODE_GPIO_CONTROL_W_REQ_LEN];
+ u8 response[OPCODE_GPIO_CONTROL_W_RES_LEN];
+
+ request[REQUEST_OPCODE] = OPCODE_GPIO_CONTROL_WRITE;
+ request[OPCODE_GPIO_CONTROL_W_REQ_OFFSET] = gpio;
+ logbuffer_log(maxq->log, "MAXQ gpio write opcode:%#x val:%#x",
+ request[REQUEST_OPCODE],
+ request[OPCODE_GPIO_CONTROL_W_REQ_OFFSET]);
+ ret = maxq_issue_opcode_command(maxq, request,
+ OPCODE_GPIO_CONTROL_W_REQ_LEN,
+ response,
+ OPCODE_GPIO_CONTROL_W_RES_LEN);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(maxq_gpio_control_write);
+
+struct max77759_maxq *maxq_init(struct device *dev, struct regmap *regmap,
+ bool poll)
+{
+ struct max77759_maxq *maxq;
+
+ maxq = devm_kzalloc(dev, sizeof(*maxq), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(maxq))
+ return ERR_PTR(-ENOMEM);
+
+ maxq->log = debugfs_logbuffer_register("maxq");
+ if (IS_ERR_OR_NULL(maxq->log)) {
+ dev_err(dev, "MAXQ logbuffer register failed\n");
+ return (struct max77759_maxq *)maxq->log;
+ }
+ maxq->regmap = regmap;
+
+ init_completion(&maxq->reply_done);
+ mutex_init(&maxq->maxq_lock);
+ mutex_init(&maxq->req_lock);
+ maxq->poll = poll;
+ maxq->init_done = true;
+
+ logbuffer_log(maxq->log, "MAXQ: probe done");
+
+ return maxq;
+}
+EXPORT_SYMBOL_GPL(maxq_init);
+
+void maxq_remove(struct max77759_maxq *maxq)
+{
+ maxq->init_done = false;
+ debugfs_logbuffer_unregister(maxq->log);
+}
+EXPORT_SYMBOL_GPL(maxq_remove);
+MODULE_AUTHOR("Badhri Jagan Sridharan <[email protected]>");
+MODULE_DESCRIPTION("Maxim 77759 MAXQ OPCDOE management");
+MODULE_LICENSE("GPL");
diff --git a/max77759_maxq.h b/max77759_maxq.h
new file mode 100644
index 0000000..dd82ea1
--- /dev/null
+++ b/max77759_maxq.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * MAX77759 MAXQ interface.
+ */
+
+#ifdef CONFIG_MAXQ_MAX77759
+struct max77759_maxq;
+extern struct max77759_maxq *maxq_init(struct device *dev,
+ struct regmap *regmap,
+ bool poll);
+extern void maxq_remove(struct max77759_maxq *maxq);
+extern void maxq_irq(struct max77759_maxq *maxq);
+/* Helpers */
+extern int maxq_query_contaminant(struct max77759_maxq *maxq, u8 cc1_raw,
+ u8 cc2_raw, u8 sbu1_raw,
+ u8 sbu2_raw, u8 cc1_rd, u8 cc2_rd,
+ u8 type, u8 cc_adc_skipped,
+ u8 *response, u8 response_len);
+extern int maxq_gpio_control_read(struct max77759_maxq *maxq, u8 *gpio);
+extern int maxq_gpio_control_write(struct max77759_maxq *maxq, u8 gpio);
+# else
+static inline struct max77759_maxq *maxq_init(struct device *dev,
+ struct regmap *regmap,
+ bool poll)
+{
+ return -EINVAL;
+}
+static inline void maxq_remove(struct max77759_maxq *maxq)
+{
+ return -EINVAL;
+}
+static inline void maxq_irq(struct max77759_maxq *maxq)
+{
+ return -EINVAL;
+}
+/* Helpers */
+static inline int maxq_query_contaminant(struct max77759_maxq *maxq,
+ u8 cc1_raw, u8 cc2_raw,
+ u8 sbu1_raw, u8 sbu2_raw,
+ u8 cc1_rd, u8 cc2_rd,
+ u8 type, u8 cc_adc_skipped,
+ u8 *response, u8 response_len)
+{
+ return -EINVAL;
+}
+extern int maxq_gpio_control_read(struct max77759_maxq *maxq, u8 *gpio)
+{
+ return -EINVAL;
+}
+extern int maxq_gpio_control_write(struct max77759_maxq *maxq, u8 gpio)
+{
+ return -EINVAL;
+}
+#endif
diff --git a/max77759_regs.h b/max77759_regs.h
new file mode 100644
index 0000000..170a9f6
--- /dev/null
+++ b/max77759_regs.h
@@ -0,0 +1,7408 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * machine generated DO NOT MODIFY
+ * source MW_regmap_ds_v0p65_121919.csv
+ * 2020-03-19
+ */
+
+#ifndef MAX77759_REG_H_
+#define MAX77759_REG_H_
+
+#ifndef BITS_PER_LONG
+#define BITS_PER_LONG 32
+#endif
+#define GENMASK(h, l) \
+ (((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h))))
+#define MAX77759_BFF(name, h, l) \
+static inline uint8_t _ ## name ## _set(uint8_t r, uint8_t v) \
+{ \
+ return ((r & ~GENMASK(h, l)) | v << l); \
+} \
+\
+static inline uint8_t _ ## name ## _get(uint8_t r) \
+{ \
+ return ((r & GENMASK(h, l)) >> l); \
+}
+
+
+#define FIELD2VALUE(field,value) \
+ (((value) & field##_MASK) >> field##_SHIFT)
+#define VALUE2FIELD(field, value) \
+ (((value) << field##_SHIFT) & field##_MASK)
+
+
+
+/* section: PMIC */
+
+/*
+ * PMIC_ID,0x0,0b00111011,0x3b
+ * ID[7:0],,,,,,
+ */
+#define MAX77759_PMIC_PMIC_ID 0x0
+
+/*
+ * PMIC_REVISION,0x1,0b00000001,0x01
+ * VER[4:0],,,,,REV[2:0],
+ */
+#define MAX77759_PMIC_PMIC_REVISION 0x1
+
+#define MAX77759_PMIC_PMIC_REVISION_VER_SHIFT 3
+#define MAX77759_PMIC_PMIC_REVISION_VER_MASK (0x1f << 3)
+#define MAX77759_PMIC_PMIC_REVISION_VER_CLEAR (~(0x1f << 3))
+#define MAX77759_PMIC_PMIC_REVISION_REV_SHIFT 0
+#define MAX77759_PMIC_PMIC_REVISION_REV_MASK (0x7 << 0)
+#define MAX77759_PMIC_PMIC_REVISION_REV_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(pmic_pmic_revision_ver,7,3)
+MAX77759_BFF(pmic_pmic_revision_rev,2,0)
+static inline const char *
+max77759_pmic_pmic_revision_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VER=%x",
+ FIELD2VALUE(MAX77759_PMIC_PMIC_REVISION_VER, val));
+ i += SCNPRINTF(&buff[i], len - i, " REV=%x",
+ FIELD2VALUE(MAX77759_PMIC_PMIC_REVISION_REV, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * OTP_REVISION,0x2,0b00000000,0x00
+ * OTP_REV[7:0],,,,,,
+ */
+#define MAX77759_PMIC_OTP_REVISION 0x2
+
+/*
+ * INTSRC,0x22,0b00000000,0x00
+ * SPR_7_4[3:0],,,,MAXQ_INT,SPR_3,TOPSYS_INT
+ */
+#define MAX77759_PMIC_INTSRC 0x22
+#define MAX77759_PMIC_INTSRC_MAXQ_INT (0x1 << 3)
+#define MAX77759_PMIC_INTSRC_SPR_3 (0x1 << 2)
+#define MAX77759_PMIC_INTSRC_TOPSYS_INT (0x1 << 1)
+#define MAX77759_PMIC_INTSRC_CHGR_INT (0x1 << 0)
+
+#define MAX77759_PMIC_INTSRC_SPR_7_4_SHIFT 4
+#define MAX77759_PMIC_INTSRC_SPR_7_4_MASK (0xf << 4)
+#define MAX77759_PMIC_INTSRC_SPR_7_4_CLEAR (~(0xf << 4))
+#define MAX77759_PMIC_INTSRC_MAXQ_INT_SHIFT 3
+#define MAX77759_PMIC_INTSRC_MAXQ_INT_MASK (0x1 << 3)
+#define MAX77759_PMIC_INTSRC_MAXQ_INT_CLEAR (~(0x1 << 3))
+#define MAX77759_PMIC_INTSRC_SPR_3_SHIFT 2
+#define MAX77759_PMIC_INTSRC_SPR_3_MASK (0x1 << 2)
+#define MAX77759_PMIC_INTSRC_SPR_3_CLEAR (~(0x1 << 2))
+#define MAX77759_PMIC_INTSRC_TOPSYS_INT_SHIFT 1
+#define MAX77759_PMIC_INTSRC_TOPSYS_INT_MASK (0x1 << 1)
+#define MAX77759_PMIC_INTSRC_TOPSYS_INT_CLEAR (~(0x1 << 1))
+#define MAX77759_PMIC_INTSRC_CHGR_INT_SHIFT 0
+#define MAX77759_PMIC_INTSRC_CHGR_INT_MASK (0x1 << 0)
+#define MAX77759_PMIC_INTSRC_CHGR_INT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(pmic_intsrc_spr_7_4,7,4)
+MAX77759_BFF(pmic_intsrc_maxq_int,3,3)
+MAX77759_BFF(pmic_intsrc_spr_3,2,2)
+MAX77759_BFF(pmic_intsrc_topsys_int,1,1)
+MAX77759_BFF(pmic_intsrc_chgr_int,0,0)
+static inline const char *
+max77759_pmic_intsrc_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_4=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRC_SPR_7_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " MAXQ_INT=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRC_MAXQ_INT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRC_SPR_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " TOPSYS_INT=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRC_TOPSYS_INT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGR_INT=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRC_CHGR_INT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * INTSRCMASK,0x23,0b11111111,0xff
+ * SPR_7_4[3:0],,,,MAXQ_INT_M,SPR_3,TOPSYS_INT_M
+ */
+#define MAX77759_PMIC_INTSRCMASK 0x23
+#define MAX77759_PMIC_INTSRCMASK_MAXQ_INT_M (0x1 << 3)
+#define MAX77759_PMIC_INTSRCMASK_SPR_3 (0x1 << 2)
+#define MAX77759_PMIC_INTSRCMASK_TOPSYS_INT_M (0x1 << 1)
+#define MAX77759_PMIC_INTSRCMASK_CHGR_INT_M (0x1 << 0)
+
+#define MAX77759_PMIC_INTSRCMASK_SPR_7_4_SHIFT 4
+#define MAX77759_PMIC_INTSRCMASK_SPR_7_4_MASK (0xf << 4)
+#define MAX77759_PMIC_INTSRCMASK_SPR_7_4_CLEAR (~(0xf << 4))
+#define MAX77759_PMIC_INTSRCMASK_MAXQ_INT_M_SHIFT 3
+#define MAX77759_PMIC_INTSRCMASK_MAXQ_INT_M_MASK (0x1 << 3)
+#define MAX77759_PMIC_INTSRCMASK_MAXQ_INT_M_CLEAR (~(0x1 << 3))
+#define MAX77759_PMIC_INTSRCMASK_SPR_3_SHIFT 2
+#define MAX77759_PMIC_INTSRCMASK_SPR_3_MASK (0x1 << 2)
+#define MAX77759_PMIC_INTSRCMASK_SPR_3_CLEAR (~(0x1 << 2))
+#define MAX77759_PMIC_INTSRCMASK_TOPSYS_INT_M_SHIFT 1
+#define MAX77759_PMIC_INTSRCMASK_TOPSYS_INT_M_MASK (0x1 << 1)
+#define MAX77759_PMIC_INTSRCMASK_TOPSYS_INT_M_CLEAR (~(0x1 << 1))
+#define MAX77759_PMIC_INTSRCMASK_CHGR_INT_M_SHIFT 0
+#define MAX77759_PMIC_INTSRCMASK_CHGR_INT_M_MASK (0x1 << 0)
+#define MAX77759_PMIC_INTSRCMASK_CHGR_INT_M_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(pmic_intsrcmask_spr_7_4,7,4)
+MAX77759_BFF(pmic_intsrcmask_maxq_int_m,3,3)
+MAX77759_BFF(pmic_intsrcmask_spr_3,2,2)
+MAX77759_BFF(pmic_intsrcmask_topsys_int_m,1,1)
+MAX77759_BFF(pmic_intsrcmask_chgr_int_m,0,0)
+static inline const char *
+max77759_pmic_intsrcmask_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_4=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRCMASK_SPR_7_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " MAXQ_INT_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRCMASK_MAXQ_INT_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRCMASK_SPR_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " TOPSYS_INT_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRCMASK_TOPSYS_INT_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGR_INT_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_INTSRCMASK_CHGR_INT_M, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TOPSYS_INT,0x24,0b00000000,0x00
+ * SPR_7,TSHDN_INT,SYSOVLO_INT,SYSUVLO_INT,SPR_3_0[3:0],,
+ */
+#define MAX77759_PMIC_TOPSYS_INT 0x24
+#define MAX77759_PMIC_TOPSYS_INT_SPR_7 (0x1 << 7)
+#define MAX77759_PMIC_TOPSYS_INT_TSHDN_INT (0x1 << 6)
+#define MAX77759_PMIC_TOPSYS_INT_SYSOVLO_INT (0x1 << 5)
+#define MAX77759_PMIC_TOPSYS_INT_SYSUVLO_INT (0x1 << 4)
+
+#define MAX77759_PMIC_TOPSYS_INT_SPR_7_SHIFT 7
+#define MAX77759_PMIC_TOPSYS_INT_SPR_7_MASK (0x1 << 7)
+#define MAX77759_PMIC_TOPSYS_INT_SPR_7_CLEAR (~(0x1 << 7))
+#define MAX77759_PMIC_TOPSYS_INT_TSHDN_INT_SHIFT 6
+#define MAX77759_PMIC_TOPSYS_INT_TSHDN_INT_MASK (0x1 << 6)
+#define MAX77759_PMIC_TOPSYS_INT_TSHDN_INT_CLEAR (~(0x1 << 6))
+#define MAX77759_PMIC_TOPSYS_INT_SYSOVLO_INT_SHIFT 5
+#define MAX77759_PMIC_TOPSYS_INT_SYSOVLO_INT_MASK (0x1 << 5)
+#define MAX77759_PMIC_TOPSYS_INT_SYSOVLO_INT_CLEAR (~(0x1 << 5))
+#define MAX77759_PMIC_TOPSYS_INT_SYSUVLO_INT_SHIFT 4
+#define MAX77759_PMIC_TOPSYS_INT_SYSUVLO_INT_MASK (0x1 << 4)
+#define MAX77759_PMIC_TOPSYS_INT_SYSUVLO_INT_CLEAR (~(0x1 << 4))
+#define MAX77759_PMIC_TOPSYS_INT_SPR_3_0_SHIFT 0
+#define MAX77759_PMIC_TOPSYS_INT_SPR_3_0_MASK (0xf << 0)
+#define MAX77759_PMIC_TOPSYS_INT_SPR_3_0_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(pmic_topsys_int_spr_7,7,7)
+MAX77759_BFF(pmic_topsys_int_tshdn_int,6,6)
+MAX77759_BFF(pmic_topsys_int_sysovlo_int,5,5)
+MAX77759_BFF(pmic_topsys_int_sysuvlo_int,4,4)
+MAX77759_BFF(pmic_topsys_int_spr_3_0,3,0)
+static inline const char *
+max77759_pmic_topsys_int_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_SPR_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " TSHDN_INT=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_TSHDN_INT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYSOVLO_INT=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_SYSOVLO_INT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYSUVLO_INT=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_SYSUVLO_INT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3_0=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_SPR_3_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TOPSYS_INT_MASK,0x26,0b11111111,0xff
+ * SPR_7,TSHDN_INT_M,SYSOVLO_INT_M,SYSUVLO_INT_M,SPR_3_0[3:0],,
+ */
+#define MAX77759_PMIC_TOPSYS_INT_MASK 0x26
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7 (0x1 << 7)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M (0x1 << 6)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSOVLO_INT_M (0x1 << 5)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSUVLO_INT_M (0x1 << 4)
+
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7_SHIFT 7
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7_MASK (0x1 << 7)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7_CLEAR (~(0x1 << 7))
+#define MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M_SHIFT 6
+#define MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M_MASK (0x1 << 6)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M_CLEAR (~(0x1 << 6))
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSOVLO_INT_M_SHIFT 5
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSOVLO_INT_M_MASK (0x1 << 5)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSOVLO_INT_M_CLEAR (~(0x1 << 5))
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSUVLO_INT_M_SHIFT 4
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSUVLO_INT_M_MASK (0x1 << 4)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SYSUVLO_INT_M_CLEAR (~(0x1 << 4))
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_3_0_SHIFT 0
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_3_0_MASK (0xf << 0)
+#define MAX77759_PMIC_TOPSYS_INT_MASK_SPR_3_0_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(pmic_topsys_int_mask_spr_7,7,7)
+MAX77759_BFF(pmic_topsys_int_mask_tshdn_int_m,6,6)
+MAX77759_BFF(pmic_topsys_int_mask_sysovlo_int_m,5,5)
+MAX77759_BFF(pmic_topsys_int_mask_sysuvlo_int_m,4,4)
+MAX77759_BFF(pmic_topsys_int_mask_spr_3_0,3,0)
+static inline const char *
+max77759_pmic_topsys_int_mask_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_MASK_SPR_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " TSHDN_INT_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_MASK_TSHDN_INT_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYSOVLO_INT_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_MASK_SYSOVLO_INT_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYSUVLO_INT_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_MASK_SYSUVLO_INT_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3_0=%x",
+ FIELD2VALUE(MAX77759_PMIC_TOPSYS_INT_MASK_SPR_3_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * I2C_CNFG,0x40,0b00000000,0x00
+ * SPR_7,PAIR[2:0],,,SPR_3_1[2:0],,
+ */
+#define MAX77759_PMIC_I2C_CNFG 0x40
+#define MAX77759_PMIC_I2C_CNFG_SPR_7 (0x1 << 7)
+#define MAX77759_PMIC_I2C_CNFG_HS_EXT_EN (0x1 << 0)
+
+#define MAX77759_PMIC_I2C_CNFG_SPR_7_SHIFT 7
+#define MAX77759_PMIC_I2C_CNFG_SPR_7_MASK (0x1 << 7)
+#define MAX77759_PMIC_I2C_CNFG_SPR_7_CLEAR (~(0x1 << 7))
+#define MAX77759_PMIC_I2C_CNFG_PAIR_SHIFT 4
+#define MAX77759_PMIC_I2C_CNFG_PAIR_MASK (0x7 << 4)
+#define MAX77759_PMIC_I2C_CNFG_PAIR_CLEAR (~(0x7 << 4))
+#define MAX77759_PMIC_I2C_CNFG_SPR_3_1_SHIFT 1
+#define MAX77759_PMIC_I2C_CNFG_SPR_3_1_MASK (0x7 << 1)
+#define MAX77759_PMIC_I2C_CNFG_SPR_3_1_CLEAR (~(0x7 << 1))
+#define MAX77759_PMIC_I2C_CNFG_HS_EXT_EN_SHIFT 0
+#define MAX77759_PMIC_I2C_CNFG_HS_EXT_EN_MASK (0x1 << 0)
+#define MAX77759_PMIC_I2C_CNFG_HS_EXT_EN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(pmic_i2c_cnfg_spr_7,7,7)
+MAX77759_BFF(pmic_i2c_cnfg_pair,6,4)
+MAX77759_BFF(pmic_i2c_cnfg_spr_3_1,3,1)
+MAX77759_BFF(pmic_i2c_cnfg_hs_ext_en,0,0)
+static inline const char *
+max77759_pmic_i2c_cnfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7=%x",
+ FIELD2VALUE(MAX77759_PMIC_I2C_CNFG_SPR_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " PAIR=%x",
+ FIELD2VALUE(MAX77759_PMIC_I2C_CNFG_PAIR, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3_1=%x",
+ FIELD2VALUE(MAX77759_PMIC_I2C_CNFG_SPR_3_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " HS_EXT_EN=%x",
+ FIELD2VALUE(MAX77759_PMIC_I2C_CNFG_HS_EXT_EN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * SWRESET,0x50,0b00000000,0x00
+ * VIO_OK_MASK,IC_RST_MASK,SWR_RST[5:0],,,,
+ */
+#define MAX77759_PMIC_SWRESET 0x50
+#define MAX77759_PMIC_SWRESET_VIO_OK_MASK (0x1 << 7)
+#define MAX77759_PMIC_SWRESET_IC_RST_MASK (0x1 << 6)
+
+#define MAX77759_PMIC_SWRESET_VIO_OK_MASK_SHIFT 7
+#define MAX77759_PMIC_SWRESET_VIO_OK_MASK_MASK (0x1 << 7)
+#define MAX77759_PMIC_SWRESET_VIO_OK_MASK_CLEAR (~(0x1 << 7))
+#define MAX77759_PMIC_SWRESET_IC_RST_MASK_SHIFT 6
+#define MAX77759_PMIC_SWRESET_IC_RST_MASK_MASK (0x1 << 6)
+#define MAX77759_PMIC_SWRESET_IC_RST_MASK_CLEAR (~(0x1 << 6))
+#define MAX77759_PMIC_SWRESET_SWR_RST_SHIFT 0
+#define MAX77759_PMIC_SWRESET_SWR_RST_MASK (0x3f << 0)
+#define MAX77759_PMIC_SWRESET_SWR_RST_CLEAR (~(0x3f << 0))
+
+MAX77759_BFF(pmic_swreset_vio_ok_mask,7,7)
+MAX77759_BFF(pmic_swreset_ic_rst_mask,6,6)
+MAX77759_BFF(pmic_swreset_swr_rst,5,0)
+static inline const char *
+max77759_pmic_swreset_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VIO_OK_MASK=%x",
+ FIELD2VALUE(MAX77759_PMIC_SWRESET_VIO_OK_MASK, val));
+ i += SCNPRINTF(&buff[i], len - i, " IC_RST_MASK=%x",
+ FIELD2VALUE(MAX77759_PMIC_SWRESET_IC_RST_MASK, val));
+ i += SCNPRINTF(&buff[i], len - i, " SWR_RST=%x",
+ FIELD2VALUE(MAX77759_PMIC_SWRESET_SWR_RST, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CONTROL_FG,0x51,0b00010000,0x10
+ * SPR_7_5[2:0],,,TSHDN_DIS,SPR_3_0[1:0],,THMIO_MUX[1:0]
+ */
+#define MAX77759_PMIC_CONTROL_FG 0x51
+#define MAX77759_PMIC_CONTROL_FG_TSHDN_DIS (0x1 << 4)
+
+#define MAX77759_PMIC_CONTROL_FG_SPR_7_5_SHIFT 5
+#define MAX77759_PMIC_CONTROL_FG_SPR_7_5_MASK (0x7 << 5)
+#define MAX77759_PMIC_CONTROL_FG_SPR_7_5_CLEAR (~(0x7 << 5))
+#define MAX77759_PMIC_CONTROL_FG_TSHDN_DIS_SHIFT 4
+#define MAX77759_PMIC_CONTROL_FG_TSHDN_DIS_MASK (0x1 << 4)
+#define MAX77759_PMIC_CONTROL_FG_TSHDN_DIS_CLEAR (~(0x1 << 4))
+#define MAX77759_PMIC_CONTROL_FG_SPR_3_0_SHIFT 2
+#define MAX77759_PMIC_CONTROL_FG_SPR_3_0_MASK (0x3 << 2)
+#define MAX77759_PMIC_CONTROL_FG_SPR_3_0_CLEAR (~(0x3 << 2))
+#define MAX77759_PMIC_CONTROL_FG_THMIO_MUX_SHIFT 0
+#define MAX77759_PMIC_CONTROL_FG_THMIO_MUX_MASK (0x3 << 0)
+#define MAX77759_PMIC_CONTROL_FG_THMIO_MUX_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(pmic_control_fg_spr_7_5,7,5)
+MAX77759_BFF(pmic_control_fg_tshdn_dis,4,4)
+MAX77759_BFF(pmic_control_fg_spr_3_0,3,2)
+MAX77759_BFF(pmic_control_fg_thmio_mux,1,0)
+static inline const char *
+max77759_pmic_control_fg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_5=%x",
+ FIELD2VALUE(MAX77759_PMIC_CONTROL_FG_SPR_7_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " TSHDN_DIS=%x",
+ FIELD2VALUE(MAX77759_PMIC_CONTROL_FG_TSHDN_DIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3_0=%x",
+ FIELD2VALUE(MAX77759_PMIC_CONTROL_FG_SPR_3_0, val));
+ i += SCNPRINTF(&buff[i], len - i, " THMIO_MUX=%x",
+ FIELD2VALUE(MAX77759_PMIC_CONTROL_FG_THMIO_MUX, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * DEVICE_ID,0x60,0b01011001,0x59
+ * DeviceID[7:0],,,,,,
+ */
+#define MAX77759_PMIC_DEVICE_ID 0x60
+
+/*
+ * DEVICE_REV,0x61,0b00000000,0x00
+ * DeviceRev[7:0],,,,,,
+ */
+#define MAX77759_PMIC_DEVICE_REV 0x61
+
+/*
+ * UIC_INT1,0x64,0b00000000,0x00
+ * APCmdResI,UIC_INT1I[6:0],,,,,
+ */
+#define MAX77759_PMIC_UIC_INT1 0x64
+#define MAX77759_PMIC_UIC_INT1_APCMDRESI (0x1 << 7)
+
+#define MAX77759_PMIC_UIC_INT1_APCMDRESI_SHIFT 7
+#define MAX77759_PMIC_UIC_INT1_APCMDRESI_MASK (0x1 << 7)
+#define MAX77759_PMIC_UIC_INT1_APCMDRESI_CLEAR (~(0x1 << 7))
+#define MAX77759_PMIC_UIC_INT1_UIC_INT1I_SHIFT 0
+#define MAX77759_PMIC_UIC_INT1_UIC_INT1I_MASK (0x7f << 0)
+#define MAX77759_PMIC_UIC_INT1_UIC_INT1I_CLEAR (~(0x7f << 0))
+
+MAX77759_BFF(pmic_uic_int1_apcmdresi,7,7)
+MAX77759_BFF(pmic_uic_int1_uic_int1i,6,0)
+static inline const char *
+max77759_pmic_uic_int1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " APCMDRESI=%x",
+ FIELD2VALUE(MAX77759_PMIC_UIC_INT1_APCMDRESI, val));
+ i += SCNPRINTF(&buff[i], len - i, " UIC_INT1I=%x",
+ FIELD2VALUE(MAX77759_PMIC_UIC_INT1_UIC_INT1I, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * UIC_INT2,0x65,0b00000000,0x00
+ * UIC_INT2I[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_INT2 0x65
+
+/*
+ * UIC_INT3,0x66,0b00000000,0x00
+ * UIC_INT3I[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_INT3 0x66
+
+/*
+ * UIC_INT4,0x67,0b00000000,0x00
+ * UIC_INT4I[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_INT4 0x67
+
+/*
+ * UIC_STATUS1,0x68,0b00000000,0x00
+ * UIC_STATUS1[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS1 0x68
+
+/*
+ * UIC_STATUS2,0x69,0b00000000,0x00
+ * UIC_STATUS2[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS2 0x69
+
+/*
+ * UIC_STATUS3,0x6A,0b00000000,0x00
+ * UIC_STATUS3[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS3 0x6A
+
+/*
+ * UIC_STATUS4,0x6B,0b00000000,0x00
+ * UIC_STATUS4[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS4 0x6B
+
+/*
+ * UIC_STATUS5,0x6C,0b00000000,0x00
+ * UIC_STATUS5[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS5 0x6C
+
+/*
+ * UIC_STATUS6,0x6D,0b00000000,0x00
+ * UIC_STATUS6[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS6 0x6D
+
+/*
+ * UIC_STATUS7,0x6E,0b00000000,0x00
+ * UIC_STATUS7[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS7 0x6E
+
+/*
+ * UIC_STATUS8,0x6F,0b00000000,0x00
+ * UIC_STATUS8[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_STATUS8 0x6F
+
+/*
+ * UIC_INT1_M,0x70,0b10111111,0xbf
+ * APCmdRes_M,UIC_INT1_M[6:0],,,,,
+ */
+#define MAX77759_PMIC_UIC_INT1_M 0x70
+#define MAX77759_PMIC_UIC_INT1_M_APCMDRES_M (0x1 << 7)
+
+#define MAX77759_PMIC_UIC_INT1_M_APCMDRES_M_SHIFT 7
+#define MAX77759_PMIC_UIC_INT1_M_APCMDRES_M_MASK (0x1 << 7)
+#define MAX77759_PMIC_UIC_INT1_M_APCMDRES_M_CLEAR (~(0x1 << 7))
+#define MAX77759_PMIC_UIC_INT1_M_UIC_INT1_M_SHIFT 0
+#define MAX77759_PMIC_UIC_INT1_M_UIC_INT1_M_MASK (0x7f << 0)
+#define MAX77759_PMIC_UIC_INT1_M_UIC_INT1_M_CLEAR (~(0x7f << 0))
+
+MAX77759_BFF(pmic_uic_int1_m_apcmdres_m,7,7)
+MAX77759_BFF(pmic_uic_int1_m_uic_int1_m,6,0)
+static inline const char *
+max77759_pmic_uic_int1_m_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " APCMDRES_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_UIC_INT1_M_APCMDRES_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " UIC_INT1_M=%x",
+ FIELD2VALUE(MAX77759_PMIC_UIC_INT1_M_UIC_INT1_M, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * UIC_INT2_M,0x71,0b11111111,0xff
+ * UIC_INT2_M[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_INT2_M 0x71
+
+/*
+ * UIC_INT3_M,0x72,0b11111111,0xff
+ * UIC_INT3_M[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_INT3_M 0x72
+
+/*
+ * UIC_INT4_M,0x73,0b11111111,0xff
+ * UIC_INT4_M[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_INT4_M 0x73
+
+/*
+ * AP_DATAOUT0,0x81,0b00000000,0x00
+ * AP_REQUEST_OPCODE[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT0 0x81
+
+/*
+ * AP_DATAOUT1,0x82,0b00000000,0x00
+ * OPCODE_DATAOUT_01[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT1 0x82
+
+/*
+ * AP_DATAOUT2,0x83,0b00000000,0x00
+ * OPCODE_DATAOUT_02[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT2 0x83
+
+/*
+ * AP_DATAOUT3,0x84,0b00000000,0x00
+ * OPCODE_DATAOUT_03[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT3 0x84
+
+/*
+ * AP_DATAOUT4,0x85,0b00000000,0x00
+ * OPCODE_DATAOUT_04[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT4 0x85
+
+/*
+ * AP_DATAOUT5,0x86,0b00000000,0x00
+ * OPCODE_DATAOUT_05[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT5 0x86
+
+/*
+ * AP_DATAOUT6,0x87,0b00000000,0x00
+ * OPCODE_DATAOUT_06[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT6 0x87
+
+/*
+ * AP_DATAOUT7,0x88,0b00000000,0x00
+ * OPCODE_DATAOUT_07[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT7 0x88
+
+/*
+ * AP_DATAOUT8,0x89,0b00000000,0x00
+ * OPCODE_DATAOUT_08[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT8 0x89
+
+/*
+ * AP_DATAOUT9,0x8A,0b00000000,0x00
+ * OPCODE_DATAOUT_09[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT9 0x8A
+
+/*
+ * AP_DATAOUT10,0x8B,0b00000000,0x00
+ * OPCODE_DATAOUT_10[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT10 0x8B
+
+/*
+ * AP_DATAOUT11,0x8C,0b00000000,0x00
+ * OPCODE_DATAOUT_11[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT11 0x8C
+
+/*
+ * AP_DATAOUT12,0x8D,0b00000000,0x00
+ * OPCODE_DATAOUT_12[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT12 0x8D
+
+/*
+ * AP_DATAOUT13,0x8E,0b00000000,0x00
+ * OPCODE_DATAOUT_13[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT13 0x8E
+
+/*
+ * AP_DATAOUT14,0x8F,0b00000000,0x00
+ * OPCODE_DATAOUT_14[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT14 0x8F
+
+/*
+ * AP_DATAOUT15,0x90,0b00000000,0x00
+ * OPCODE_DATAOUT_15[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT15 0x90
+
+/*
+ * AP_DATAOUT16,0x91,0b00000000,0x00
+ * OPCODE_DATAOUT_16[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT16 0x91
+
+/*
+ * AP_DATAOUT17,0x92,0b00000000,0x00
+ * OPCODE_DATAOUT_17[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT17 0x92
+
+/*
+ * AP_DATAOUT18,0x93,0b00000000,0x00
+ * OPCODE_DATAOUT_18[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT18 0x93
+
+/*
+ * AP_DATAOUT19,0x94,0b00000000,0x00
+ * OPCODE_DATAOUT_19[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT19 0x94
+
+/*
+ * AP_DATAOUT20,0x95,0b00000000,0x00
+ * OPCODE_DATAOUT_20[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT20 0x95
+
+/*
+ * AP_DATAOUT21,0x96,0b00000000,0x00
+ * OPCODE_DATAOUT_21[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT21 0x96
+
+/*
+ * AP_DATAOUT22,0x97,0b00000000,0x00
+ * OPCODE_DATAOUT_22[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT22 0x97
+
+/*
+ * AP_DATAOUT23,0x98,0b00000000,0x00
+ * OPCODE_DATAOUT_23[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT23 0x98
+
+/*
+ * AP_DATAOUT24,0x99,0b00000000,0x00
+ * OPCODE_DATAOUT_24[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT24 0x99
+
+/*
+ * AP_DATAOUT25,0x9A,0b00000000,0x00
+ * OPCODE_DATAOUT_25[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT25 0x9A
+
+/*
+ * AP_DATAOUT26,0x9B,0b00000000,0x00
+ * OPCODE_DATAOUT_26[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT26 0x9B
+
+/*
+ * AP_DATAOUT27,0x9C,0b00000000,0x00
+ * OPCODE_DATAOUT_27[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT27 0x9C
+
+/*
+ * AP_DATAOUT28,0x9D,0b00000000,0x00
+ * OPCODE_DATAOUT_28[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT28 0x9D
+
+/*
+ * AP_DATAOUT29,0x9E,0b00000000,0x00
+ * OPCODE_DATAOUT_29[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT29 0x9E
+
+/*
+ * AP_DATAOUT30,0x9F,0b00000000,0x00
+ * OPCODE_DATAOUT_30[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT30 0x9F
+
+/*
+ * AP_DATAOUT31,0xA0,0b00000000,0x00
+ * OPCODE_DATAOUT_31[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT31 0xA0
+
+/*
+ * AP_DATAOUT32,0xA1,0b00000000,0x00
+ * OPCODE_DATAOUT_32[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAOUT32 0xA1
+
+/*
+ * AP_DATAIN0,0xB1,0b00000000,0x00
+ * MAXQ_RESPONSE_OPCODE[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN0 0xB1
+
+/*
+ * AP_DATAIN1,0xB2,0b00000000,0x00
+ * OPCODE_DATAIN_01[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN1 0xB2
+
+/*
+ * AP_DATAIN2,0xB3,0b00000000,0x00
+ * OPCODE_DATAIN_02[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN2 0xB3
+
+/*
+ * AP_DATAIN3,0xB4,0b00000000,0x00
+ * OPCODE_DATAIN_03[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN3 0xB4
+
+/*
+ * AP_DATAIN4,0xB5,0b00000000,0x00
+ * OPCODE_DATAIN_04[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN4 0xB5
+
+/*
+ * AP_DATAIN5,0xB6,0b00000000,0x00
+ * OPCODE_DATAIN_05[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN5 0xB6
+
+/*
+ * AP_DATAIN6,0xB7,0b00000000,0x00
+ * OPCODE_DATAIN_06[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN6 0xB7
+
+/*
+ * AP_DATAIN7,0xB8,0b00000000,0x00
+ * OPCODE_DATAIN_07[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN7 0xB8
+
+/*
+ * AP_DATAIN8,0xB9,0b00000000,0x00
+ * OPCODE_DATAIN_08[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN8 0xB9
+
+/*
+ * AP_DATAIN9,0xBA,0b00000000,0x00
+ * OPCODE_DATAIN_09[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN9 0xBA
+
+/*
+ * AP_DATAIN10,0xBB,0b00000000,0x00
+ * OPCODE_DATAIN_10[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN10 0xBB
+
+/*
+ * AP_DATAIN11,0xBC,0b00000000,0x00
+ * OPCODE_DATAIN_11[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN11 0xBC
+
+/*
+ * AP_DATAIN12,0xBD,0b00000000,0x00
+ * OPCODE_DATAIN_12[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN12 0xBD
+
+/*
+ * AP_DATAIN13,0xBE,0b00000000,0x00
+ * OPCODE_DATAIN_13[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN13 0xBE
+
+/*
+ * AP_DATAIN14,0xBF,0b00000000,0x00
+ * OPCODE_DATAIN_14[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN14 0xBF
+
+/*
+ * AP_DATAIN15,0xC0,0b00000000,0x00
+ * OPCODE_DATAIN_15[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN15 0xC0
+
+/*
+ * AP_DATAIN16,0xC1,0b00000000,0x00
+ * OPCODE_DATAIN_16[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN16 0xC1
+
+/*
+ * AP_DATAIN17,0xC2,0b00000000,0x00
+ * OPCODE_DATAIN_17[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN17 0xC2
+
+/*
+ * AP_DATAIN18,0xC3,0b00000000,0x00
+ * OPCODE_DATAIN_18[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN18 0xC3
+
+/*
+ * AP_DATAIN19,0xC4,0b00000000,0x00
+ * OPCODE_DATAIN_19[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN19 0xC4
+
+/*
+ * AP_DATAIN20,0xC5,0b00000000,0x00
+ * OPCODE_DATAIN_20[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN20 0xC5
+
+/*
+ * AP_DATAIN21,0xC6,0b00000000,0x00
+ * OPCODE_DATAIN_21[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN21 0xC6
+
+/*
+ * AP_DATAIN22,0xC7,0b00000000,0x00
+ * OPCODE_DATAIN_22[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN22 0xC7
+
+/*
+ * AP_DATAIN23,0xC8,0b00000000,0x00
+ * OPCODE_DATAIN_23[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN23 0xC8
+
+/*
+ * AP_DATAIN24,0xC9,0b00000000,0x00
+ * OPCODE_DATAIN_24[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN24 0xC9
+
+/*
+ * AP_DATAIN25,0xCA,0b00000000,0x00
+ * OPCODE_DATAIN_25[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN25 0xCA
+
+/*
+ * AP_DATAIN26,0xCB,0b00000000,0x00
+ * OPCODE_DATAIN_26[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN26 0xCB
+
+/*
+ * AP_DATAIN27,0xCC,0b00000000,0x00
+ * OPCODE_DATAIN_27[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN27 0xCC
+
+/*
+ * AP_DATAIN28,0xCD,0b00000000,0x00
+ * OPCODE_DATAIN_28[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN28 0xCD
+
+/*
+ * AP_DATAIN29,0xCE,0b00000000,0x00
+ * OPCODE_DATAIN_29[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN29 0xCE
+
+/*
+ * AP_DATAIN30,0xCF,0b00000000,0x00
+ * OPCODE_DATAIN_30[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN30 0xCF
+
+/*
+ * AP_DATAIN31,0xD0,0b00000000,0x00
+ * OPCODE_DATAIN_31[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN31 0xD0
+
+/*
+ * AP_DATAIN32,0xD1,0b00000000,0x00
+ * OPCODE_DATAIN_32[7:0],,,,,,
+ */
+#define MAX77759_PMIC_AP_DATAIN32 0xD1
+
+/*
+ * UIC_SWRST,0xE0,0b00000000,0x00
+ * UIC_SWRST[7:0],,,,,,
+ */
+#define MAX77759_PMIC_UIC_SWRST 0xE0
+
+/* section: Charger */
+
+/*
+ * CHG_INT,0xB0,0b00000000,0x00
+ * AICL_I,CHGIN_I,WCIN_I,CHG_I,BAT_I,INLIM_I,THM2_I
+ */
+#define MAX77759_CHG_INT 0xB0
+#define MAX77759_CHG_INT_AICL_I (0x1 << 7)
+#define MAX77759_CHG_INT_CHGIN_I (0x1 << 6)
+#define MAX77759_CHG_INT_WCIN_I (0x1 << 5)
+#define MAX77759_CHG_INT_CHG_I (0x1 << 4)
+#define MAX77759_CHG_INT_BAT_I (0x1 << 3)
+#define MAX77759_CHG_INT_INLIM_I (0x1 << 2)
+#define MAX77759_CHG_INT_THM2_I (0x1 << 1)
+#define MAX77759_CHG_INT_BYP_I (0x1 << 0)
+
+#define MAX77759_CHG_INT_AICL_I_SHIFT 7
+#define MAX77759_CHG_INT_AICL_I_MASK (0x1 << 7)
+#define MAX77759_CHG_INT_AICL_I_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_INT_CHGIN_I_SHIFT 6
+#define MAX77759_CHG_INT_CHGIN_I_MASK (0x1 << 6)
+#define MAX77759_CHG_INT_CHGIN_I_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_INT_WCIN_I_SHIFT 5
+#define MAX77759_CHG_INT_WCIN_I_MASK (0x1 << 5)
+#define MAX77759_CHG_INT_WCIN_I_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_INT_CHG_I_SHIFT 4
+#define MAX77759_CHG_INT_CHG_I_MASK (0x1 << 4)
+#define MAX77759_CHG_INT_CHG_I_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_INT_BAT_I_SHIFT 3
+#define MAX77759_CHG_INT_BAT_I_MASK (0x1 << 3)
+#define MAX77759_CHG_INT_BAT_I_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_INT_INLIM_I_SHIFT 2
+#define MAX77759_CHG_INT_INLIM_I_MASK (0x1 << 2)
+#define MAX77759_CHG_INT_INLIM_I_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_INT_THM2_I_SHIFT 1
+#define MAX77759_CHG_INT_THM2_I_MASK (0x1 << 1)
+#define MAX77759_CHG_INT_THM2_I_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_INT_BYP_I_SHIFT 0
+#define MAX77759_CHG_INT_BYP_I_MASK (0x1 << 0)
+#define MAX77759_CHG_INT_BYP_I_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_int_aicl_i,7,7)
+MAX77759_BFF(chg_int_chgin_i,6,6)
+MAX77759_BFF(chg_int_wcin_i,5,5)
+MAX77759_BFF(chg_int_chg_i,4,4)
+MAX77759_BFF(chg_int_bat_i,3,3)
+MAX77759_BFF(chg_int_inlim_i,2,2)
+MAX77759_BFF(chg_int_thm2_i,1,1)
+MAX77759_BFF(chg_int_byp_i,0,0)
+static inline const char *
+max77759_chg_int_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " AICL_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_AICL_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGIN_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_CHGIN_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_WCIN_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_CHG_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_BAT_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " INLIM_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_INLIM_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM2_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_THM2_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " BYP_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_BYP_I, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_INT2,0xB1,0b00000000,0x00
+ * INSEL_I,SYS_UVLO1_I,SYS_UVLO2_I,BAT_OILO_I,CHG_STA_CC_I,CHG_STA_CV_I,CHG_STA_TO_I
+ */
+#define MAX77759_CHG_INT2 0xB1
+#define MAX77759_CHG_INT2_INSEL_I (0x1 << 7)
+#define MAX77759_CHG_INT2_SYS_UVLO1_I (0x1 << 6)
+#define MAX77759_CHG_INT2_SYS_UVLO2_I (0x1 << 5)
+#define MAX77759_CHG_INT2_BAT_OILO_I (0x1 << 4)
+#define MAX77759_CHG_INT2_CHG_STA_CC_I (0x1 << 3)
+#define MAX77759_CHG_INT2_CHG_STA_CV_I (0x1 << 2)
+#define MAX77759_CHG_INT2_CHG_STA_TO_I (0x1 << 1)
+#define MAX77759_CHG_INT2_CHG_STA_DONE_I (0x1 << 0)
+
+#define MAX77759_CHG_INT2_INSEL_I_SHIFT 7
+#define MAX77759_CHG_INT2_INSEL_I_MASK (0x1 << 7)
+#define MAX77759_CHG_INT2_INSEL_I_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_INT2_SYS_UVLO1_I_SHIFT 6
+#define MAX77759_CHG_INT2_SYS_UVLO1_I_MASK (0x1 << 6)
+#define MAX77759_CHG_INT2_SYS_UVLO1_I_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_INT2_SYS_UVLO2_I_SHIFT 5
+#define MAX77759_CHG_INT2_SYS_UVLO2_I_MASK (0x1 << 5)
+#define MAX77759_CHG_INT2_SYS_UVLO2_I_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_INT2_BAT_OILO_I_SHIFT 4
+#define MAX77759_CHG_INT2_BAT_OILO_I_MASK (0x1 << 4)
+#define MAX77759_CHG_INT2_BAT_OILO_I_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_INT2_CHG_STA_CC_I_SHIFT 3
+#define MAX77759_CHG_INT2_CHG_STA_CC_I_MASK (0x1 << 3)
+#define MAX77759_CHG_INT2_CHG_STA_CC_I_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_INT2_CHG_STA_CV_I_SHIFT 2
+#define MAX77759_CHG_INT2_CHG_STA_CV_I_MASK (0x1 << 2)
+#define MAX77759_CHG_INT2_CHG_STA_CV_I_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_INT2_CHG_STA_TO_I_SHIFT 1
+#define MAX77759_CHG_INT2_CHG_STA_TO_I_MASK (0x1 << 1)
+#define MAX77759_CHG_INT2_CHG_STA_TO_I_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_INT2_CHG_STA_DONE_I_SHIFT 0
+#define MAX77759_CHG_INT2_CHG_STA_DONE_I_MASK (0x1 << 0)
+#define MAX77759_CHG_INT2_CHG_STA_DONE_I_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_int2_insel_i,7,7)
+MAX77759_BFF(chg_int2_sys_uvlo1_i,6,6)
+MAX77759_BFF(chg_int2_sys_uvlo2_i,5,5)
+MAX77759_BFF(chg_int2_bat_oilo_i,4,4)
+MAX77759_BFF(chg_int2_chg_sta_cc_i,3,3)
+MAX77759_BFF(chg_int2_chg_sta_cv_i,2,2)
+MAX77759_BFF(chg_int2_chg_sta_to_i,1,1)
+MAX77759_BFF(chg_int2_chg_sta_done_i,0,0)
+static inline const char *
+max77759_chg_int2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " INSEL_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_INSEL_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO1_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_SYS_UVLO1_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO2_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_SYS_UVLO2_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OILO_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_BAT_OILO_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_CC_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_CHG_STA_CC_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_CV_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_CHG_STA_CV_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_TO_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_CHG_STA_TO_I, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_DONE_I=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_CHG_STA_DONE_I, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_INT_MASK,0xB2,0b11111111,0xff
+ * AICL_M,CHGIN_M,WCIN_M,CHG_M,BAT_M,INLIM_M,THM2_M
+ */
+#define MAX77759_CHG_INT_MASK 0xB2
+#define MAX77759_CHG_INT_MASK_AICL_M (0x1 << 7)
+#define MAX77759_CHG_INT_MASK_CHGIN_M (0x1 << 6)
+#define MAX77759_CHG_INT_MASK_WCIN_M (0x1 << 5)
+#define MAX77759_CHG_INT_MASK_CHG_M (0x1 << 4)
+#define MAX77759_CHG_INT_MASK_BAT_M (0x1 << 3)
+#define MAX77759_CHG_INT_MASK_INLIM_M (0x1 << 2)
+#define MAX77759_CHG_INT_MASK_THM2_M (0x1 << 1)
+#define MAX77759_CHG_INT_MASK_BYP_M (0x1 << 0)
+
+#define MAX77759_CHG_INT_MASK_AICL_M_SHIFT 7
+#define MAX77759_CHG_INT_MASK_AICL_M_MASK (0x1 << 7)
+#define MAX77759_CHG_INT_MASK_AICL_M_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_INT_MASK_CHGIN_M_SHIFT 6
+#define MAX77759_CHG_INT_MASK_CHGIN_M_MASK (0x1 << 6)
+#define MAX77759_CHG_INT_MASK_CHGIN_M_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_INT_MASK_WCIN_M_SHIFT 5
+#define MAX77759_CHG_INT_MASK_WCIN_M_MASK (0x1 << 5)
+#define MAX77759_CHG_INT_MASK_WCIN_M_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_INT_MASK_CHG_M_SHIFT 4
+#define MAX77759_CHG_INT_MASK_CHG_M_MASK (0x1 << 4)
+#define MAX77759_CHG_INT_MASK_CHG_M_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_INT_MASK_BAT_M_SHIFT 3
+#define MAX77759_CHG_INT_MASK_BAT_M_MASK (0x1 << 3)
+#define MAX77759_CHG_INT_MASK_BAT_M_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_INT_MASK_INLIM_M_SHIFT 2
+#define MAX77759_CHG_INT_MASK_INLIM_M_MASK (0x1 << 2)
+#define MAX77759_CHG_INT_MASK_INLIM_M_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_INT_MASK_THM2_M_SHIFT 1
+#define MAX77759_CHG_INT_MASK_THM2_M_MASK (0x1 << 1)
+#define MAX77759_CHG_INT_MASK_THM2_M_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_INT_MASK_BYP_M_SHIFT 0
+#define MAX77759_CHG_INT_MASK_BYP_M_MASK (0x1 << 0)
+#define MAX77759_CHG_INT_MASK_BYP_M_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_int_mask_aicl_m,7,7)
+MAX77759_BFF(chg_int_mask_chgin_m,6,6)
+MAX77759_BFF(chg_int_mask_wcin_m,5,5)
+MAX77759_BFF(chg_int_mask_chg_m,4,4)
+MAX77759_BFF(chg_int_mask_bat_m,3,3)
+MAX77759_BFF(chg_int_mask_inlim_m,2,2)
+MAX77759_BFF(chg_int_mask_thm2_m,1,1)
+MAX77759_BFF(chg_int_mask_byp_m,0,0)
+static inline const char *
+max77759_chg_int_mask_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " AICL_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_AICL_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGIN_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_CHGIN_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_WCIN_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_CHG_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_BAT_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " INLIM_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_INLIM_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM2_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_THM2_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " BYP_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_MASK_BYP_M, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_INT2_MASK,0xB3,0b11111111,0xff
+ * INSEL_M,SYS_UVLO1_M,SYS_UVLO2_M,BAT_OILO_M,CHG_STA_CC_M,CHG_STA_CV_M,CHG_STA_TO_M
+ */
+#define MAX77759_CHG_INT2_MASK 0xB3
+#define MAX77759_CHG_INT2_MASK_INSEL_M (0x1 << 7)
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO1_M (0x1 << 6)
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO2_M (0x1 << 5)
+#define MAX77759_CHG_INT2_MASK_BAT_OILO_M (0x1 << 4)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CC_M (0x1 << 3)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CV_M (0x1 << 2)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_TO_M (0x1 << 1)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M (0x1 << 0)
+
+#define MAX77759_CHG_INT2_MASK_INSEL_M_SHIFT 7
+#define MAX77759_CHG_INT2_MASK_INSEL_M_MASK (0x1 << 7)
+#define MAX77759_CHG_INT2_MASK_INSEL_M_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO1_M_SHIFT 6
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO1_M_MASK (0x1 << 6)
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO1_M_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO2_M_SHIFT 5
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO2_M_MASK (0x1 << 5)
+#define MAX77759_CHG_INT2_MASK_SYS_UVLO2_M_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_INT2_MASK_BAT_OILO_M_SHIFT 4
+#define MAX77759_CHG_INT2_MASK_BAT_OILO_M_MASK (0x1 << 4)
+#define MAX77759_CHG_INT2_MASK_BAT_OILO_M_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CC_M_SHIFT 3
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CC_M_MASK (0x1 << 3)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CC_M_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CV_M_SHIFT 2
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CV_M_MASK (0x1 << 2)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_CV_M_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_INT2_MASK_CHG_STA_TO_M_SHIFT 1
+#define MAX77759_CHG_INT2_MASK_CHG_STA_TO_M_MASK (0x1 << 1)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_TO_M_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M_SHIFT 0
+#define MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M_MASK (0x1 << 0)
+#define MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_int2_mask_insel_m,7,7)
+MAX77759_BFF(chg_int2_mask_sys_uvlo1_m,6,6)
+MAX77759_BFF(chg_int2_mask_sys_uvlo2_m,5,5)
+MAX77759_BFF(chg_int2_mask_bat_oilo_m,4,4)
+MAX77759_BFF(chg_int2_mask_chg_sta_cc_m,3,3)
+MAX77759_BFF(chg_int2_mask_chg_sta_cv_m,2,2)
+MAX77759_BFF(chg_int2_mask_chg_sta_to_m,1,1)
+MAX77759_BFF(chg_int2_mask_chg_sta_done_m,0,0)
+static inline const char *
+max77759_chg_int2_mask_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " INSEL_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_INSEL_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO1_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_SYS_UVLO1_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO2_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_SYS_UVLO2_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OILO_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_BAT_OILO_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_CC_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_CHG_STA_CC_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_CV_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_CHG_STA_CV_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_TO_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_CHG_STA_TO_M, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_STA_DONE_M=%x",
+ FIELD2VALUE(MAX77759_CHG_INT2_MASK_CHG_STA_DONE_M, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_INT_OK,0xB4,0b10011111,0x9f
+ * AICL_OK,CHGIN_OK,WCIN_OK,CHG_OK,BAT_OK,INLIM_OK,THM2_OK
+ */
+#define MAX77759_CHG_INT_OK 0xB4
+#define MAX77759_CHG_INT_OK_AICL_OK (0x1 << 7)
+#define MAX77759_CHG_INT_OK_CHGIN_OK (0x1 << 6)
+#define MAX77759_CHG_INT_OK_WCIN_OK (0x1 << 5)
+#define MAX77759_CHG_INT_OK_CHG_OK (0x1 << 4)
+#define MAX77759_CHG_INT_OK_BAT_OK (0x1 << 3)
+#define MAX77759_CHG_INT_OK_INLIM_OK (0x1 << 2)
+#define MAX77759_CHG_INT_OK_THM2_OK (0x1 << 1)
+#define MAX77759_CHG_INT_OK_BYP_OK (0x1 << 0)
+
+#define MAX77759_CHG_INT_OK_AICL_OK_SHIFT 7
+#define MAX77759_CHG_INT_OK_AICL_OK_MASK (0x1 << 7)
+#define MAX77759_CHG_INT_OK_AICL_OK_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_INT_OK_CHGIN_OK_SHIFT 6
+#define MAX77759_CHG_INT_OK_CHGIN_OK_MASK (0x1 << 6)
+#define MAX77759_CHG_INT_OK_CHGIN_OK_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_INT_OK_WCIN_OK_SHIFT 5
+#define MAX77759_CHG_INT_OK_WCIN_OK_MASK (0x1 << 5)
+#define MAX77759_CHG_INT_OK_WCIN_OK_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_INT_OK_CHG_OK_SHIFT 4
+#define MAX77759_CHG_INT_OK_CHG_OK_MASK (0x1 << 4)
+#define MAX77759_CHG_INT_OK_CHG_OK_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_INT_OK_BAT_OK_SHIFT 3
+#define MAX77759_CHG_INT_OK_BAT_OK_MASK (0x1 << 3)
+#define MAX77759_CHG_INT_OK_BAT_OK_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_INT_OK_INLIM_OK_SHIFT 2
+#define MAX77759_CHG_INT_OK_INLIM_OK_MASK (0x1 << 2)
+#define MAX77759_CHG_INT_OK_INLIM_OK_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_INT_OK_THM2_OK_SHIFT 1
+#define MAX77759_CHG_INT_OK_THM2_OK_MASK (0x1 << 1)
+#define MAX77759_CHG_INT_OK_THM2_OK_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_INT_OK_BYP_OK_SHIFT 0
+#define MAX77759_CHG_INT_OK_BYP_OK_MASK (0x1 << 0)
+#define MAX77759_CHG_INT_OK_BYP_OK_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_int_ok_aicl_ok,7,7)
+MAX77759_BFF(chg_int_ok_chgin_ok,6,6)
+MAX77759_BFF(chg_int_ok_wcin_ok,5,5)
+MAX77759_BFF(chg_int_ok_chg_ok,4,4)
+MAX77759_BFF(chg_int_ok_bat_ok,3,3)
+MAX77759_BFF(chg_int_ok_inlim_ok,2,2)
+MAX77759_BFF(chg_int_ok_thm2_ok,1,1)
+MAX77759_BFF(chg_int_ok_byp_ok,0,0)
+static inline const char *
+max77759_chg_int_ok_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " AICL_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_AICL_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGIN_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_CHGIN_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_WCIN_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_CHG_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_BAT_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " INLIM_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_INLIM_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM2_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_THM2_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " BYP_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_INT_OK_BYP_OK, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_DETAILS_00,0xB5,0b00000000,0x00
+ * RSVD,CHGIN_DTLS[1:0],,WCIN_DTLS[1:0],,SPSN_DTLS[1:0],
+ */
+#define MAX77759_CHG_DETAILS_00 0xB5
+#define MAX77759_CHG_DETAILS_00_RSVD (0x1 << 7)
+#define MAX77759_CHG_DETAILS_00_TREG (0x1 << 0)
+
+#define MAX77759_CHG_DETAILS_00_RSVD_SHIFT 7
+#define MAX77759_CHG_DETAILS_00_RSVD_MASK (0x1 << 7)
+#define MAX77759_CHG_DETAILS_00_RSVD_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_DETAILS_00_CHGIN_DTLS_SHIFT 5
+#define MAX77759_CHG_DETAILS_00_CHGIN_DTLS_MASK (0x3 << 5)
+#define MAX77759_CHG_DETAILS_00_CHGIN_DTLS_CLEAR (~(0x3 << 5))
+#define MAX77759_CHG_DETAILS_00_WCIN_DTLS_SHIFT 3
+#define MAX77759_CHG_DETAILS_00_WCIN_DTLS_MASK (0x3 << 3)
+#define MAX77759_CHG_DETAILS_00_WCIN_DTLS_CLEAR (~(0x3 << 3))
+#define MAX77759_CHG_DETAILS_00_SPSN_DTLS_SHIFT 1
+#define MAX77759_CHG_DETAILS_00_SPSN_DTLS_MASK (0x3 << 1)
+#define MAX77759_CHG_DETAILS_00_SPSN_DTLS_CLEAR (~(0x3 << 1))
+#define MAX77759_CHG_DETAILS_00_TREG_SHIFT 0
+#define MAX77759_CHG_DETAILS_00_TREG_MASK (0x1 << 0)
+#define MAX77759_CHG_DETAILS_00_TREG_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_details_00_rsvd,7,7)
+MAX77759_BFF(chg_details_00_chgin_dtls,6,5)
+MAX77759_BFF(chg_details_00_wcin_dtls,4,3)
+MAX77759_BFF(chg_details_00_spsn_dtls,2,1)
+MAX77759_BFF(chg_details_00_treg,0,0)
+static inline const char *
+max77759_chg_details_00_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_00_RSVD, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGIN_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_00_CHGIN_DTLS, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_00_WCIN_DTLS, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPSN_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_00_SPSN_DTLS, val));
+ i += SCNPRINTF(&buff[i], len - i, " TREG=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_00_TREG, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_DETAILS_01,0xB6,0b01111000,0x78
+ * VDROOP2_OK,BAT_DTLS[2:0],,,CHG_DTLS[3:0],,
+ */
+#define MAX77759_CHG_DETAILS_01 0xB6
+#define MAX77759_CHG_DETAILS_01_VDROOP2_OK (0x1 << 7)
+
+#define MAX77759_CHG_DETAILS_01_VDROOP2_OK_SHIFT 7
+#define MAX77759_CHG_DETAILS_01_VDROOP2_OK_MASK (0x1 << 7)
+#define MAX77759_CHG_DETAILS_01_VDROOP2_OK_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_DETAILS_01_BAT_DTLS_SHIFT 4
+#define MAX77759_CHG_DETAILS_01_BAT_DTLS_MASK (0x7 << 4)
+#define MAX77759_CHG_DETAILS_01_BAT_DTLS_CLEAR (~(0x7 << 4))
+#define MAX77759_CHG_DETAILS_01_CHG_DTLS_SHIFT 0
+#define MAX77759_CHG_DETAILS_01_CHG_DTLS_MASK (0xf << 0)
+#define MAX77759_CHG_DETAILS_01_CHG_DTLS_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_details_01_vdroop2_ok,7,7)
+MAX77759_BFF(chg_details_01_bat_dtls,6,4)
+MAX77759_BFF(chg_details_01_chg_dtls,3,0)
+static inline const char *
+max77759_chg_details_01_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VDROOP2_OK=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_01_VDROOP2_OK, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_01_BAT_DTLS, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_01_CHG_DTLS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_DETAILS_02,0xB7,0b00000000,0x00
+ * RSVD[1:0],,CHGIN_STS,WCIN_STS,BYP_DTLS[3:0],,
+ */
+#define MAX77759_CHG_DETAILS_02 0xB7
+#define MAX77759_CHG_DETAILS_02_CHGIN_STS (0x1 << 5)
+#define MAX77759_CHG_DETAILS_02_WCIN_STS (0x1 << 4)
+
+#define MAX77759_CHG_DETAILS_02_RSVD_SHIFT 6
+#define MAX77759_CHG_DETAILS_02_RSVD_MASK (0x3 << 6)
+#define MAX77759_CHG_DETAILS_02_RSVD_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_DETAILS_02_CHGIN_STS_SHIFT 5
+#define MAX77759_CHG_DETAILS_02_CHGIN_STS_MASK (0x1 << 5)
+#define MAX77759_CHG_DETAILS_02_CHGIN_STS_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_DETAILS_02_WCIN_STS_SHIFT 4
+#define MAX77759_CHG_DETAILS_02_WCIN_STS_MASK (0x1 << 4)
+#define MAX77759_CHG_DETAILS_02_WCIN_STS_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_DETAILS_02_BYP_DTLS_SHIFT 0
+#define MAX77759_CHG_DETAILS_02_BYP_DTLS_MASK (0xf << 0)
+#define MAX77759_CHG_DETAILS_02_BYP_DTLS_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_details_02_rsvd,7,6)
+MAX77759_BFF(chg_details_02_chgin_sts,5,5)
+MAX77759_BFF(chg_details_02_wcin_sts,4,4)
+MAX77759_BFF(chg_details_02_byp_dtls,3,0)
+static inline const char *
+max77759_chg_details_02_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_02_RSVD, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGIN_STS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_02_CHGIN_STS, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_STS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_02_WCIN_STS, val));
+ i += SCNPRINTF(&buff[i], len - i, " BYP_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_02_BYP_DTLS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_DETAILS_03,0xB8,0b00000000,0x00
+ * FSHIP_EXIT_DTLS[1:0],,MD_DTLS[1:0],,RSVD,THM_DTLS[2:0],
+ */
+#define MAX77759_CHG_DETAILS_03 0xB8
+#define MAX77759_CHG_DETAILS_03_RSVD (0x1 << 3)
+
+#define MAX77759_CHG_DETAILS_03_FSHIP_EXIT_DTLS_SHIFT 6
+#define MAX77759_CHG_DETAILS_03_FSHIP_EXIT_DTLS_MASK (0x3 << 6)
+#define MAX77759_CHG_DETAILS_03_FSHIP_EXIT_DTLS_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_DETAILS_03_MD_DTLS_SHIFT 4
+#define MAX77759_CHG_DETAILS_03_MD_DTLS_MASK (0x3 << 4)
+#define MAX77759_CHG_DETAILS_03_MD_DTLS_CLEAR (~(0x3 << 4))
+#define MAX77759_CHG_DETAILS_03_RSVD_SHIFT 3
+#define MAX77759_CHG_DETAILS_03_RSVD_MASK (0x1 << 3)
+#define MAX77759_CHG_DETAILS_03_RSVD_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_DETAILS_03_THM_DTLS_SHIFT 0
+#define MAX77759_CHG_DETAILS_03_THM_DTLS_MASK (0x7 << 0)
+#define MAX77759_CHG_DETAILS_03_THM_DTLS_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(chg_details_03_fship_exit_dtls,7,6)
+MAX77759_BFF(chg_details_03_md_dtls,5,4)
+MAX77759_BFF(chg_details_03_rsvd,3,3)
+MAX77759_BFF(chg_details_03_thm_dtls,2,0)
+static inline const char *
+max77759_chg_details_03_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " FSHIP_EXIT_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_03_FSHIP_EXIT_DTLS, val));
+ i += SCNPRINTF(&buff[i], len - i, " MD_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_03_MD_DTLS, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_03_RSVD, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM_DTLS=%x",
+ FIELD2VALUE(MAX77759_CHG_DETAILS_03_THM_DTLS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_00,0xB9,0b00000100,0x04
+ * WDTCLR[1:0],,CP_EN,SPR_4,MODE[3:0],,
+ */
+#define MAX77759_CHG_CNFG_00 0xB9
+#define MAX77759_CHG_CNFG_00_CP_EN (0x1 << 5)
+#define MAX77759_CHG_CNFG_00_SPR_4 (0x1 << 4)
+
+#define MAX77759_CHG_CNFG_00_WDTCLR_SHIFT 6
+#define MAX77759_CHG_CNFG_00_WDTCLR_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_00_WDTCLR_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_00_CP_EN_SHIFT 5
+#define MAX77759_CHG_CNFG_00_CP_EN_MASK (0x1 << 5)
+#define MAX77759_CHG_CNFG_00_CP_EN_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_CNFG_00_SPR_4_SHIFT 4
+#define MAX77759_CHG_CNFG_00_SPR_4_MASK (0x1 << 4)
+#define MAX77759_CHG_CNFG_00_SPR_4_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_CNFG_00_MODE_SHIFT 0
+#define MAX77759_CHG_CNFG_00_MODE_MASK (0xf << 0)
+#define MAX77759_CHG_CNFG_00_MODE_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_cnfg_00_wdtclr,7,6)
+MAX77759_BFF(chg_cnfg_00_cp_en,5,5)
+MAX77759_BFF(chg_cnfg_00_spr_4,4,4)
+MAX77759_BFF(chg_cnfg_00_mode,3,0)
+static inline const char *
+max77759_chg_cnfg_00_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " WDTCLR=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_00_WDTCLR, val));
+ i += SCNPRINTF(&buff[i], len - i, " CP_EN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_00_CP_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_4=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_00_SPR_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " MODE=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_00_MODE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_01,0xBA,0b10010001,0x91
+ * PQEN,LSEL,CHG_RSTRT[1:0],,RECYCLE_EN,FCHGTIME[2:0],
+ */
+#define MAX77759_CHG_CNFG_01 0xBA
+#define MAX77759_CHG_CNFG_01_PQEN (0x1 << 7)
+#define MAX77759_CHG_CNFG_01_LSEL (0x1 << 6)
+#define MAX77759_CHG_CNFG_01_RECYCLE_EN (0x1 << 3)
+
+#define MAX77759_CHG_CNFG_01_PQEN_SHIFT 7
+#define MAX77759_CHG_CNFG_01_PQEN_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_01_PQEN_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_01_LSEL_SHIFT 6
+#define MAX77759_CHG_CNFG_01_LSEL_MASK (0x1 << 6)
+#define MAX77759_CHG_CNFG_01_LSEL_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_CNFG_01_CHG_RSTRT_SHIFT 4
+#define MAX77759_CHG_CNFG_01_CHG_RSTRT_MASK (0x3 << 4)
+#define MAX77759_CHG_CNFG_01_CHG_RSTRT_CLEAR (~(0x3 << 4))
+#define MAX77759_CHG_CNFG_01_RECYCLE_EN_SHIFT 3
+#define MAX77759_CHG_CNFG_01_RECYCLE_EN_MASK (0x1 << 3)
+#define MAX77759_CHG_CNFG_01_RECYCLE_EN_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_CNFG_01_FCHGTIME_SHIFT 0
+#define MAX77759_CHG_CNFG_01_FCHGTIME_MASK (0x7 << 0)
+#define MAX77759_CHG_CNFG_01_FCHGTIME_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(chg_cnfg_01_pqen,7,7)
+MAX77759_BFF(chg_cnfg_01_lsel,6,6)
+MAX77759_BFF(chg_cnfg_01_chg_rstrt,5,4)
+MAX77759_BFF(chg_cnfg_01_recycle_en,3,3)
+MAX77759_BFF(chg_cnfg_01_fchgtime,2,0)
+static inline const char *
+max77759_chg_cnfg_01_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " PQEN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_01_PQEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " LSEL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_01_LSEL, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_RSTRT=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_01_CHG_RSTRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " RECYCLE_EN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_01_RECYCLE_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " FCHGTIME=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_01_FCHGTIME, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_02,0xBB,0b00000111,0x07
+ * SPR_7_6[1:0],,CHGCC[5:0],,,,
+ */
+#define MAX77759_CHG_CNFG_02 0xBB
+
+#define MAX77759_CHG_CNFG_02_SPR_7_6_SHIFT 6
+#define MAX77759_CHG_CNFG_02_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_02_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_02_CHGCC_SHIFT 0
+#define MAX77759_CHG_CNFG_02_CHGCC_MASK (0x3f << 0)
+#define MAX77759_CHG_CNFG_02_CHGCC_CLEAR (~(0x3f << 0))
+
+MAX77759_BFF(chg_cnfg_02_spr_7_6,7,6)
+MAX77759_BFF(chg_cnfg_02_chgcc,5,0)
+static inline const char *
+max77759_chg_cnfg_02_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_02_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGCC=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_02_CHGCC, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_03,0xBC,0b11011001,0xd9
+ * SYS_TRACK_DIS,AUTO_FSHIP_MODE_EN,TO_TIME[2:0],,,TO_ITH[2:0],
+ */
+#define MAX77759_CHG_CNFG_03 0xBC
+#define MAX77759_CHG_CNFG_03_SYS_TRACK_DIS (0x1 << 7)
+#define MAX77759_CHG_CNFG_03_AUTO_FSHIP_MODE_EN (0x1 << 6)
+
+#define MAX77759_CHG_CNFG_03_SYS_TRACK_DIS_SHIFT 7
+#define MAX77759_CHG_CNFG_03_SYS_TRACK_DIS_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_03_SYS_TRACK_DIS_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_03_AUTO_FSHIP_MODE_EN_SHIFT 6
+#define MAX77759_CHG_CNFG_03_AUTO_FSHIP_MODE_EN_MASK (0x1 << 6)
+#define MAX77759_CHG_CNFG_03_AUTO_FSHIP_MODE_EN_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_CNFG_03_TO_TIME_SHIFT 3
+#define MAX77759_CHG_CNFG_03_TO_TIME_MASK (0x7 << 3)
+#define MAX77759_CHG_CNFG_03_TO_TIME_CLEAR (~(0x7 << 3))
+#define MAX77759_CHG_CNFG_03_TO_ITH_SHIFT 0
+#define MAX77759_CHG_CNFG_03_TO_ITH_MASK (0x7 << 0)
+#define MAX77759_CHG_CNFG_03_TO_ITH_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(chg_cnfg_03_sys_track_dis,7,7)
+MAX77759_BFF(chg_cnfg_03_auto_fship_mode_en,6,6)
+MAX77759_BFF(chg_cnfg_03_to_time,5,3)
+MAX77759_BFF(chg_cnfg_03_to_ith,2,0)
+static inline const char *
+max77759_chg_cnfg_03_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SYS_TRACK_DIS=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_03_SYS_TRACK_DIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " AUTO_FSHIP_MODE_EN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_03_AUTO_FSHIP_MODE_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " TO_TIME=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_03_TO_TIME, val));
+ i += SCNPRINTF(&buff[i], len - i, " TO_ITH=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_03_TO_ITH, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_04,0xBD,0b00010100,0x14
+ * SPR_7_6[1:0],,CHG_CV_PRM[5:0],,,,
+ */
+#define MAX77759_CHG_CNFG_04 0xBD
+
+#define MAX77759_CHG_CNFG_04_SPR_7_6_SHIFT 6
+#define MAX77759_CHG_CNFG_04_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_04_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_04_CHG_CV_PRM_SHIFT 0
+#define MAX77759_CHG_CNFG_04_CHG_CV_PRM_MASK (0x3f << 0)
+#define MAX77759_CHG_CNFG_04_CHG_CV_PRM_CLEAR (~(0x3f << 0))
+
+MAX77759_BFF(chg_cnfg_04_spr_7_6,7,6)
+MAX77759_BFF(chg_cnfg_04_chg_cv_prm,5,0)
+static inline const char *
+max77759_chg_cnfg_04_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_04_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHG_CV_PRM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_04_CHG_CV_PRM, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_05,0xBE,0b00110110,0x36
+ * UNO_ILIM[3:0],,,,OTG_ILIM[3:0],,
+ */
+#define MAX77759_CHG_CNFG_05 0xBE
+
+#define MAX77759_CHG_CNFG_05_UNO_ILIM_SHIFT 4
+#define MAX77759_CHG_CNFG_05_UNO_ILIM_MASK (0xf << 4)
+#define MAX77759_CHG_CNFG_05_UNO_ILIM_CLEAR (~(0xf << 4))
+#define MAX77759_CHG_CNFG_05_OTG_ILIM_SHIFT 0
+#define MAX77759_CHG_CNFG_05_OTG_ILIM_MASK (0xf << 0)
+#define MAX77759_CHG_CNFG_05_OTG_ILIM_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_cnfg_05_uno_ilim,7,4)
+MAX77759_BFF(chg_cnfg_05_otg_ilim,3,0)
+static inline const char *
+max77759_chg_cnfg_05_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " UNO_ILIM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_05_UNO_ILIM, val));
+ i += SCNPRINTF(&buff[i], len - i, " OTG_ILIM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_05_OTG_ILIM, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_06,0xBF,0b00000000,0x00
+ * SPR_7_4[3:0],,,,CHGPROT[1:0],,SPR_1_0[1:0]
+ */
+#define MAX77759_CHG_CNFG_06 0xBF
+
+#define MAX77759_CHG_CNFG_06_SPR_7_4_SHIFT 4
+#define MAX77759_CHG_CNFG_06_SPR_7_4_MASK (0xf << 4)
+#define MAX77759_CHG_CNFG_06_SPR_7_4_CLEAR (~(0xf << 4))
+#define MAX77759_CHG_CNFG_06_CHGPROT_SHIFT 2
+#define MAX77759_CHG_CNFG_06_CHGPROT_MASK (0x3 << 2)
+#define MAX77759_CHG_CNFG_06_CHGPROT_CLEAR (~(0x3 << 2))
+#define MAX77759_CHG_CNFG_06_SPR_1_0_SHIFT 0
+#define MAX77759_CHG_CNFG_06_SPR_1_0_MASK (0x3 << 0)
+#define MAX77759_CHG_CNFG_06_SPR_1_0_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(chg_cnfg_06_spr_7_4,7,4)
+MAX77759_BFF(chg_cnfg_06_chgprot,3,2)
+MAX77759_BFF(chg_cnfg_06_spr_1_0,1,0)
+static inline const char *
+max77759_chg_cnfg_06_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_4=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_06_SPR_7_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGPROT=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_06_CHGPROT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_1_0=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_06_SPR_1_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_07,0xC0,0b00110000,0x30
+ * WD_QBATOFF,REGTEMP[3:0],,,,SPR_2,FGSRC
+ */
+#define MAX77759_CHG_CNFG_07 0xC0
+#define MAX77759_CHG_CNFG_07_WD_QBATOFF (0x1 << 7)
+#define MAX77759_CHG_CNFG_07_SPR_2 (0x1 << 2)
+#define MAX77759_CHG_CNFG_07_FGSRC (0x1 << 1)
+#define MAX77759_CHG_CNFG_07_FSHIP_MODE (0x1 << 0)
+
+#define MAX77759_CHG_CNFG_07_WD_QBATOFF_SHIFT 7
+#define MAX77759_CHG_CNFG_07_WD_QBATOFF_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_07_WD_QBATOFF_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_07_REGTEMP_SHIFT 3
+#define MAX77759_CHG_CNFG_07_REGTEMP_MASK (0xf << 3)
+#define MAX77759_CHG_CNFG_07_REGTEMP_CLEAR (~(0xf << 3))
+#define MAX77759_CHG_CNFG_07_SPR_2_SHIFT 2
+#define MAX77759_CHG_CNFG_07_SPR_2_MASK (0x1 << 2)
+#define MAX77759_CHG_CNFG_07_SPR_2_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_CNFG_07_FGSRC_SHIFT 1
+#define MAX77759_CHG_CNFG_07_FGSRC_MASK (0x1 << 1)
+#define MAX77759_CHG_CNFG_07_FGSRC_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_CNFG_07_FSHIP_MODE_SHIFT 0
+#define MAX77759_CHG_CNFG_07_FSHIP_MODE_MASK (0x1 << 0)
+#define MAX77759_CHG_CNFG_07_FSHIP_MODE_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_cnfg_07_wd_qbatoff,7,7)
+MAX77759_BFF(chg_cnfg_07_regtemp,6,3)
+MAX77759_BFF(chg_cnfg_07_spr_2,2,2)
+MAX77759_BFF(chg_cnfg_07_fgsrc,1,1)
+MAX77759_BFF(chg_cnfg_07_fship_mode,0,0)
+static inline const char *
+max77759_chg_cnfg_07_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " WD_QBATOFF=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_07_WD_QBATOFF, val));
+ i += SCNPRINTF(&buff[i], len - i, " REGTEMP=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_07_REGTEMP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_2=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_07_SPR_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " FGSRC=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_07_FGSRC, val));
+ i += SCNPRINTF(&buff[i], len - i, " FSHIP_MODE=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_07_FSHIP_MODE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_08,0xC1,0b00000101,0x05
+ * SPR_7,VCHGCV_WARM,ICHGCC_WARM,VCHGCV_COOL,ICHGCC_COOL,JEITA_EN,FSW[1:0]
+ */
+#define MAX77759_CHG_CNFG_08 0xC1
+#define MAX77759_CHG_CNFG_08_SPR_7 (0x1 << 7)
+#define MAX77759_CHG_CNFG_08_VCHGCV_WARM (0x1 << 6)
+#define MAX77759_CHG_CNFG_08_ICHGCC_WARM (0x1 << 5)
+#define MAX77759_CHG_CNFG_08_VCHGCV_COOL (0x1 << 4)
+#define MAX77759_CHG_CNFG_08_ICHGCC_COOL (0x1 << 3)
+#define MAX77759_CHG_CNFG_08_JEITA_EN (0x1 << 2)
+
+#define MAX77759_CHG_CNFG_08_SPR_7_SHIFT 7
+#define MAX77759_CHG_CNFG_08_SPR_7_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_08_SPR_7_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_08_VCHGCV_WARM_SHIFT 6
+#define MAX77759_CHG_CNFG_08_VCHGCV_WARM_MASK (0x1 << 6)
+#define MAX77759_CHG_CNFG_08_VCHGCV_WARM_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_CNFG_08_ICHGCC_WARM_SHIFT 5
+#define MAX77759_CHG_CNFG_08_ICHGCC_WARM_MASK (0x1 << 5)
+#define MAX77759_CHG_CNFG_08_ICHGCC_WARM_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_CNFG_08_VCHGCV_COOL_SHIFT 4
+#define MAX77759_CHG_CNFG_08_VCHGCV_COOL_MASK (0x1 << 4)
+#define MAX77759_CHG_CNFG_08_VCHGCV_COOL_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_CNFG_08_ICHGCC_COOL_SHIFT 3
+#define MAX77759_CHG_CNFG_08_ICHGCC_COOL_MASK (0x1 << 3)
+#define MAX77759_CHG_CNFG_08_ICHGCC_COOL_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_CNFG_08_JEITA_EN_SHIFT 2
+#define MAX77759_CHG_CNFG_08_JEITA_EN_MASK (0x1 << 2)
+#define MAX77759_CHG_CNFG_08_JEITA_EN_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_CNFG_08_FSW_SHIFT 0
+#define MAX77759_CHG_CNFG_08_FSW_MASK (0x3 << 0)
+#define MAX77759_CHG_CNFG_08_FSW_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(chg_cnfg_08_spr_7,7,7)
+MAX77759_BFF(chg_cnfg_08_vchgcv_warm,6,6)
+MAX77759_BFF(chg_cnfg_08_ichgcc_warm,5,5)
+MAX77759_BFF(chg_cnfg_08_vchgcv_cool,4,4)
+MAX77759_BFF(chg_cnfg_08_ichgcc_cool,3,3)
+MAX77759_BFF(chg_cnfg_08_jeita_en,2,2)
+MAX77759_BFF(chg_cnfg_08_fsw,1,0)
+static inline const char *
+max77759_chg_cnfg_08_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_SPR_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCHGCV_WARM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_VCHGCV_WARM, val));
+ i += SCNPRINTF(&buff[i], len - i, " ICHGCC_WARM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_ICHGCC_WARM, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCHGCV_COOL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_VCHGCV_COOL, val));
+ i += SCNPRINTF(&buff[i], len - i, " ICHGCC_COOL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_ICHGCC_COOL, val));
+ i += SCNPRINTF(&buff[i], len - i, " JEITA_EN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_JEITA_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " FSW=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_08_FSW, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_09,0xC2,0b00010011,0x13
+ * NO_AUTOIBUS,CHGIN_ILIM[6:0],,,,,
+ */
+#define MAX77759_CHG_CNFG_09 0xC2
+#define MAX77759_CHG_CNFG_09_NO_AUTOIBUS (0x1 << 7)
+
+#define MAX77759_CHG_CNFG_09_NO_AUTOIBUS_SHIFT 7
+#define MAX77759_CHG_CNFG_09_NO_AUTOIBUS_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_09_NO_AUTOIBUS_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_09_CHGIN_ILIM_SHIFT 0
+#define MAX77759_CHG_CNFG_09_CHGIN_ILIM_MASK (0x7f << 0)
+#define MAX77759_CHG_CNFG_09_CHGIN_ILIM_CLEAR (~(0x7f << 0))
+
+MAX77759_BFF(chg_cnfg_09_no_autoibus,7,7)
+MAX77759_BFF(chg_cnfg_09_chgin_ilim,6,0)
+static inline const char *
+max77759_chg_cnfg_09_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " NO_AUTOIBUS=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_09_NO_AUTOIBUS, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGIN_ILIM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_09_CHGIN_ILIM, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_10,0xC3,0b00001111,0x0f
+ * SPR_7_6[1:0],,WCIN_ILIM[5:0],,,,
+ */
+#define MAX77759_CHG_CNFG_10 0xC3
+
+#define MAX77759_CHG_CNFG_10_SPR_7_6_SHIFT 6
+#define MAX77759_CHG_CNFG_10_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_10_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_10_WCIN_ILIM_SHIFT 0
+#define MAX77759_CHG_CNFG_10_WCIN_ILIM_MASK (0x3f << 0)
+#define MAX77759_CHG_CNFG_10_WCIN_ILIM_CLEAR (~(0x3f << 0))
+
+MAX77759_BFF(chg_cnfg_10_spr_7_6,7,6)
+MAX77759_BFF(chg_cnfg_10_wcin_ilim,5,0)
+static inline const char *
+max77759_chg_cnfg_10_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_10_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_ILIM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_10_WCIN_ILIM, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_11,0xC4,0b00000000,0x00
+ * VBYPSET[7:0],,,,,,
+ */
+#define MAX77759_CHG_CNFG_11 0xC4
+
+/*
+ * CHG_CNFG_12,0xC5,0b01101010,0x6a
+ * CHG_EN,WCINSEL,CHGINSEL,VCHGIN_REG[1:0],,WCIN_REG[1:0],
+ */
+#define MAX77759_CHG_CNFG_12 0xC5
+#define MAX77759_CHG_CNFG_12_CHG_EN (0x1 << 7)
+#define MAX77759_CHG_CNFG_12_WCINSEL (0x1 << 6)
+#define MAX77759_CHG_CNFG_12_CHGINSEL (0x1 << 5)
+#define MAX77759_CHG_CNFG_12_DISKIP (0x1 << 0)
+
+#define MAX77759_CHG_CNFG_12_CHG_EN_SHIFT 7
+#define MAX77759_CHG_CNFG_12_CHG_EN_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_12_CHG_EN_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_12_WCINSEL_SHIFT 6
+#define MAX77759_CHG_CNFG_12_WCINSEL_MASK (0x1 << 6)
+#define MAX77759_CHG_CNFG_12_WCINSEL_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_CNFG_12_CHGINSEL_SHIFT 5
+#define MAX77759_CHG_CNFG_12_CHGINSEL_MASK (0x1 << 5)
+#define MAX77759_CHG_CNFG_12_CHGINSEL_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_CNFG_12_VCHGIN_REG_SHIFT 3
+#define MAX77759_CHG_CNFG_12_VCHGIN_REG_MASK (0x3 << 3)
+#define MAX77759_CHG_CNFG_12_VCHGIN_REG_CLEAR (~(0x3 << 3))
+#define MAX77759_CHG_CNFG_12_WCIN_REG_SHIFT 1
+#define MAX77759_CHG_CNFG_12_WCIN_REG_MASK (0x3 << 1)
+#define MAX77759_CHG_CNFG_12_WCIN_REG_CLEAR (~(0x3 << 1))
+#define MAX77759_CHG_CNFG_12_DISKIP_SHIFT 0
+#define MAX77759_CHG_CNFG_12_DISKIP_MASK (0x1 << 0)
+#define MAX77759_CHG_CNFG_12_DISKIP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_cnfg_12_chg_en,7,7)
+MAX77759_BFF(chg_cnfg_12_wcinsel,6,6)
+MAX77759_BFF(chg_cnfg_12_chginsel,5,5)
+MAX77759_BFF(chg_cnfg_12_vchgin_reg,4,3)
+MAX77759_BFF(chg_cnfg_12_wcin_reg,2,1)
+MAX77759_BFF(chg_cnfg_12_diskip,0,0)
+static inline const char *
+max77759_chg_cnfg_12_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " CHG_EN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_12_CHG_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCINSEL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_12_WCINSEL, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGINSEL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_12_CHGINSEL, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCHGIN_REG=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_12_VCHGIN_REG, val));
+ i += SCNPRINTF(&buff[i], len - i, " WCIN_REG=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_12_WCIN_REG, val));
+ i += SCNPRINTF(&buff[i], len - i, " DISKIP=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_12_DISKIP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_13,0xC6,0b00000011,0x03
+ * EN_FG_ILIM_CTRL,THM_CHR_RSTART,THM_CC_HZ,THM_BUCK_DIS,THM2_HW_CTRL,USB_TEMP[2:0],
+ */
+#define MAX77759_CHG_CNFG_13 0xC6
+#define MAX77759_CHG_CNFG_13_EN_FG_ILIM_CTRL (0x1 << 7)
+#define MAX77759_CHG_CNFG_13_THM_CHR_RSTART (0x1 << 6)
+#define MAX77759_CHG_CNFG_13_THM_CC_HZ (0x1 << 5)
+#define MAX77759_CHG_CNFG_13_THM_BUCK_DIS (0x1 << 4)
+#define MAX77759_CHG_CNFG_13_THM2_HW_CTRL (0x1 << 3)
+
+#define MAX77759_CHG_CNFG_13_EN_FG_ILIM_CTRL_SHIFT 7
+#define MAX77759_CHG_CNFG_13_EN_FG_ILIM_CTRL_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_13_EN_FG_ILIM_CTRL_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_13_THM_CHR_RSTART_SHIFT 6
+#define MAX77759_CHG_CNFG_13_THM_CHR_RSTART_MASK (0x1 << 6)
+#define MAX77759_CHG_CNFG_13_THM_CHR_RSTART_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_CNFG_13_THM_CC_HZ_SHIFT 5
+#define MAX77759_CHG_CNFG_13_THM_CC_HZ_MASK (0x1 << 5)
+#define MAX77759_CHG_CNFG_13_THM_CC_HZ_CLEAR (~(0x1 << 5))
+#define MAX77759_CHG_CNFG_13_THM_BUCK_DIS_SHIFT 4
+#define MAX77759_CHG_CNFG_13_THM_BUCK_DIS_MASK (0x1 << 4)
+#define MAX77759_CHG_CNFG_13_THM_BUCK_DIS_CLEAR (~(0x1 << 4))
+#define MAX77759_CHG_CNFG_13_THM2_HW_CTRL_SHIFT 3
+#define MAX77759_CHG_CNFG_13_THM2_HW_CTRL_MASK (0x1 << 3)
+#define MAX77759_CHG_CNFG_13_THM2_HW_CTRL_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_CNFG_13_USB_TEMP_SHIFT 0
+#define MAX77759_CHG_CNFG_13_USB_TEMP_MASK (0x7 << 0)
+#define MAX77759_CHG_CNFG_13_USB_TEMP_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(chg_cnfg_13_en_fg_ilim_ctrl,7,7)
+MAX77759_BFF(chg_cnfg_13_thm_chr_rstart,6,6)
+MAX77759_BFF(chg_cnfg_13_thm_cc_hz,5,5)
+MAX77759_BFF(chg_cnfg_13_thm_buck_dis,4,4)
+MAX77759_BFF(chg_cnfg_13_thm2_hw_ctrl,3,3)
+MAX77759_BFF(chg_cnfg_13_usb_temp,2,0)
+static inline const char *
+max77759_chg_cnfg_13_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " EN_FG_ILIM_CTRL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_13_EN_FG_ILIM_CTRL, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM_CHR_RSTART=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_13_THM_CHR_RSTART, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM_CC_HZ=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_13_THM_CC_HZ, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM_BUCK_DIS=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_13_THM_BUCK_DIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " THM2_HW_CTRL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_13_THM2_HW_CTRL, val));
+ i += SCNPRINTF(&buff[i], len - i, " USB_TEMP=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_13_USB_TEMP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_14,0xC7,0b00000110,0x06
+ * BAT_OILO_REL[1:0],,BAT_OPEN_TO[1:0],,BAT_OILO[3:0],,
+ */
+#define MAX77759_CHG_CNFG_14 0xC7
+
+#define MAX77759_CHG_CNFG_14_BAT_OILO_REL_SHIFT 6
+#define MAX77759_CHG_CNFG_14_BAT_OILO_REL_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_14_BAT_OILO_REL_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_14_BAT_OPEN_TO_SHIFT 4
+#define MAX77759_CHG_CNFG_14_BAT_OPEN_TO_MASK (0x3 << 4)
+#define MAX77759_CHG_CNFG_14_BAT_OPEN_TO_CLEAR (~(0x3 << 4))
+#define MAX77759_CHG_CNFG_14_BAT_OILO_SHIFT 0
+#define MAX77759_CHG_CNFG_14_BAT_OILO_MASK (0xf << 0)
+#define MAX77759_CHG_CNFG_14_BAT_OILO_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_cnfg_14_bat_oilo_rel,7,6)
+MAX77759_BFF(chg_cnfg_14_bat_open_to,5,4)
+MAX77759_BFF(chg_cnfg_14_bat_oilo,3,0)
+static inline const char *
+max77759_chg_cnfg_14_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OILO_REL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_14_BAT_OILO_REL, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OPEN_TO=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_14_BAT_OPEN_TO, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OILO=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_14_BAT_OILO, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_15,0xC8,0b00001000,0x08
+ * SYS_UVLO1_REL[1:0],,SYS_UVLO1_HYST[1:0],,SYS_UVLO1[3:0],,
+ */
+#define MAX77759_CHG_CNFG_15 0xC8
+
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_REL_SHIFT 6
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_REL_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_REL_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_HYST_SHIFT 4
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_HYST_MASK (0x3 << 4)
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_HYST_CLEAR (~(0x3 << 4))
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_SHIFT 0
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_MASK (0xf << 0)
+#define MAX77759_CHG_CNFG_15_SYS_UVLO1_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_cnfg_15_sys_uvlo1_rel,7,6)
+MAX77759_BFF(chg_cnfg_15_sys_uvlo1_hyst,5,4)
+MAX77759_BFF(chg_cnfg_15_sys_uvlo1,3,0)
+static inline const char *
+max77759_chg_cnfg_15_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO1_REL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_15_SYS_UVLO1_REL, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO1_HYST=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_15_SYS_UVLO1_HYST, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO1=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_15_SYS_UVLO1, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_16,0xC9,0b00000100,0x04
+ * SYS_UVLO2_REL[1:0],,SYS_UVLO2_HYST[1:0],,SYS_UVLO2[3:0],,
+ */
+#define MAX77759_CHG_CNFG_16 0xC9
+
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_REL_SHIFT 6
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_REL_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_REL_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_HYST_SHIFT 4
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_HYST_MASK (0x3 << 4)
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_HYST_CLEAR (~(0x3 << 4))
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_SHIFT 0
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_MASK (0xf << 0)
+#define MAX77759_CHG_CNFG_16_SYS_UVLO2_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(chg_cnfg_16_sys_uvlo2_rel,7,6)
+MAX77759_BFF(chg_cnfg_16_sys_uvlo2_hyst,5,4)
+MAX77759_BFF(chg_cnfg_16_sys_uvlo2,3,0)
+static inline const char *
+max77759_chg_cnfg_16_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO2_REL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_16_SYS_UVLO2_REL, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO2_HYST=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_16_SYS_UVLO2_HYST, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO2=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_16_SYS_UVLO2, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_17,0xCA,0b01000000,0x40
+ * AICL[1:0],,SPR_5_3[2:0],,,BAT_OILO_DET,SYS_UVLO2_DET
+ */
+#define MAX77759_CHG_CNFG_17 0xCA
+#define MAX77759_CHG_CNFG_17_BAT_OILO_DET (0x1 << 2)
+#define MAX77759_CHG_CNFG_17_SYS_UVLO2_DET (0x1 << 1)
+#define MAX77759_CHG_CNFG_17_SYS_UVLO1_DET (0x1 << 0)
+
+#define MAX77759_CHG_CNFG_17_AICL_SHIFT 6
+#define MAX77759_CHG_CNFG_17_AICL_MASK (0x3 << 6)
+#define MAX77759_CHG_CNFG_17_AICL_CLEAR (~(0x3 << 6))
+#define MAX77759_CHG_CNFG_17_SPR_5_3_SHIFT 3
+#define MAX77759_CHG_CNFG_17_SPR_5_3_MASK (0x7 << 3)
+#define MAX77759_CHG_CNFG_17_SPR_5_3_CLEAR (~(0x7 << 3))
+#define MAX77759_CHG_CNFG_17_BAT_OILO_DET_SHIFT 2
+#define MAX77759_CHG_CNFG_17_BAT_OILO_DET_MASK (0x1 << 2)
+#define MAX77759_CHG_CNFG_17_BAT_OILO_DET_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_CNFG_17_SYS_UVLO2_DET_SHIFT 1
+#define MAX77759_CHG_CNFG_17_SYS_UVLO2_DET_MASK (0x1 << 1)
+#define MAX77759_CHG_CNFG_17_SYS_UVLO2_DET_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_CNFG_17_SYS_UVLO1_DET_SHIFT 0
+#define MAX77759_CHG_CNFG_17_SYS_UVLO1_DET_MASK (0x1 << 0)
+#define MAX77759_CHG_CNFG_17_SYS_UVLO1_DET_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_cnfg_17_aicl,7,6)
+MAX77759_BFF(chg_cnfg_17_spr_5_3,5,3)
+MAX77759_BFF(chg_cnfg_17_bat_oilo_det,2,2)
+MAX77759_BFF(chg_cnfg_17_sys_uvlo2_det,1,1)
+MAX77759_BFF(chg_cnfg_17_sys_uvlo1_det,0,0)
+static inline const char *
+max77759_chg_cnfg_17_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " AICL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_17_AICL, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_5_3=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_17_SPR_5_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " BAT_OILO_DET=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_17_BAT_OILO_DET, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO2_DET=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_17_SYS_UVLO2_DET, val));
+ i += SCNPRINTF(&buff[i], len - i, " SYS_UVLO1_DET=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_17_SYS_UVLO1_DET, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_18,0xCB,0b11000000,0xc0
+ * VDP1_STP_BST,VDP2_STP_BST,MINVSYS[1:0],,OTG_V_PGM,SPSN_DET_EN,MASTER_DC
+ */
+#define MAX77759_CHG_CNFG_18 0xCB
+#define MAX77759_CHG_CNFG_18_VDP1_STP_BST (0x1 << 7)
+#define MAX77759_CHG_CNFG_18_VDP2_STP_BST (0x1 << 6)
+#define MAX77759_CHG_CNFG_18_OTG_V_PGM (0x1 << 3)
+#define MAX77759_CHG_CNFG_18_SPSN_DET_EN (0x1 << 2)
+#define MAX77759_CHG_CNFG_18_MASTER_DC (0x1 << 1)
+#define MAX77759_CHG_CNFG_18_WDTEN (0x1 << 0)
+
+#define MAX77759_CHG_CNFG_18_VDP1_STP_BST_SHIFT 7
+#define MAX77759_CHG_CNFG_18_VDP1_STP_BST_MASK (0x1 << 7)
+#define MAX77759_CHG_CNFG_18_VDP1_STP_BST_CLEAR (~(0x1 << 7))
+#define MAX77759_CHG_CNFG_18_VDP2_STP_BST_SHIFT 6
+#define MAX77759_CHG_CNFG_18_VDP2_STP_BST_MASK (0x1 << 6)
+#define MAX77759_CHG_CNFG_18_VDP2_STP_BST_CLEAR (~(0x1 << 6))
+#define MAX77759_CHG_CNFG_18_MINVSYS_SHIFT 4
+#define MAX77759_CHG_CNFG_18_MINVSYS_MASK (0x3 << 4)
+#define MAX77759_CHG_CNFG_18_MINVSYS_CLEAR (~(0x3 << 4))
+#define MAX77759_CHG_CNFG_18_OTG_V_PGM_SHIFT 3
+#define MAX77759_CHG_CNFG_18_OTG_V_PGM_MASK (0x1 << 3)
+#define MAX77759_CHG_CNFG_18_OTG_V_PGM_CLEAR (~(0x1 << 3))
+#define MAX77759_CHG_CNFG_18_SPSN_DET_EN_SHIFT 2
+#define MAX77759_CHG_CNFG_18_SPSN_DET_EN_MASK (0x1 << 2)
+#define MAX77759_CHG_CNFG_18_SPSN_DET_EN_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_CNFG_18_MASTER_DC_SHIFT 1
+#define MAX77759_CHG_CNFG_18_MASTER_DC_MASK (0x1 << 1)
+#define MAX77759_CHG_CNFG_18_MASTER_DC_CLEAR (~(0x1 << 1))
+#define MAX77759_CHG_CNFG_18_WDTEN_SHIFT 0
+#define MAX77759_CHG_CNFG_18_WDTEN_MASK (0x1 << 0)
+#define MAX77759_CHG_CNFG_18_WDTEN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(chg_cnfg_18_vdp1_stp_bst,7,7)
+MAX77759_BFF(chg_cnfg_18_vdp2_stp_bst,6,6)
+MAX77759_BFF(chg_cnfg_18_minvsys,5,4)
+MAX77759_BFF(chg_cnfg_18_otg_v_pgm,3,3)
+MAX77759_BFF(chg_cnfg_18_spsn_det_en,2,2)
+MAX77759_BFF(chg_cnfg_18_master_dc,1,1)
+MAX77759_BFF(chg_cnfg_18_wdten,0,0)
+static inline const char *
+max77759_chg_cnfg_18_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VDP1_STP_BST=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_VDP1_STP_BST, val));
+ i += SCNPRINTF(&buff[i], len - i, " VDP2_STP_BST=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_VDP2_STP_BST, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINVSYS=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_MINVSYS, val));
+ i += SCNPRINTF(&buff[i], len - i, " OTG_V_PGM=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_OTG_V_PGM, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPSN_DET_EN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_SPSN_DET_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " MASTER_DC=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_MASTER_DC, val));
+ i += SCNPRINTF(&buff[i], len - i, " WDTEN=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_18_WDTEN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CHG_CNFG_19,0xCC,0b00010000,0x10
+ * SPR_7_5[2:0],,,INLIM_CLK[1:0],,DIS_IR_CTRL,SLOWLX[1:0]
+ */
+#define MAX77759_CHG_CNFG_19 0xCC
+
+/* section: FuelGauge */
+#define MAX77759_CHG_CNFG_19_DIS_IR_CTRL (0x1 << 2)
+
+#define MAX77759_CHG_CNFG_19_SPR_7_5_SHIFT 5
+#define MAX77759_CHG_CNFG_19_SPR_7_5_MASK (0x7 << 5)
+#define MAX77759_CHG_CNFG_19_SPR_7_5_CLEAR (~(0x7 << 5))
+#define MAX77759_CHG_CNFG_19_INLIM_CLK_SHIFT 3
+#define MAX77759_CHG_CNFG_19_INLIM_CLK_MASK (0x3 << 3)
+#define MAX77759_CHG_CNFG_19_INLIM_CLK_CLEAR (~(0x3 << 3))
+#define MAX77759_CHG_CNFG_19_DIS_IR_CTRL_SHIFT 2
+#define MAX77759_CHG_CNFG_19_DIS_IR_CTRL_MASK (0x1 << 2)
+#define MAX77759_CHG_CNFG_19_DIS_IR_CTRL_CLEAR (~(0x1 << 2))
+#define MAX77759_CHG_CNFG_19_SLOWLX_SHIFT 0
+#define MAX77759_CHG_CNFG_19_SLOWLX_MASK (0x3 << 0)
+#define MAX77759_CHG_CNFG_19_SLOWLX_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(chg_cnfg_19_spr_7_5,7,5)
+MAX77759_BFF(chg_cnfg_19_inlim_clk,4,3)
+MAX77759_BFF(chg_cnfg_19_dis_ir_ctrl,2,2)
+MAX77759_BFF(chg_cnfg_19_slowlx,1,0)
+static inline const char *
+max77759_chg_cnfg_19_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_5=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_19_SPR_7_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " INLIM_CLK=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_19_INLIM_CLK, val));
+ i += SCNPRINTF(&buff[i], len - i, " DIS_IR_CTRL=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_19_DIS_IR_CTRL, val));
+ i += SCNPRINTF(&buff[i], len - i, " SLOWLX=%x",
+ FIELD2VALUE(MAX77759_CHG_CNFG_19_SLOWLX, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * Status,0x0,0b00000010,0x02
+ * Br,Smx,Tmx,Vmx,Bi,Smn,Tmn
+ */
+#define MAX77759_FG_STATUS 0x0
+#define MAX77759_FG_STATUS_BR (0x1 << 15)
+#define MAX77759_FG_STATUS_SMX (0x1 << 14)
+#define MAX77759_FG_STATUS_TMX (0x1 << 13)
+#define MAX77759_FG_STATUS_VMX (0x1 << 12)
+#define MAX77759_FG_STATUS_BI (0x1 << 11)
+#define MAX77759_FG_STATUS_SMN (0x1 << 10)
+#define MAX77759_FG_STATUS_TMN (0x1 << 9)
+#define MAX77759_FG_STATUS_VMN (0x1 << 8)
+#define MAX77759_FG_STATUS_DSOCI (0x1 << 7)
+#define MAX77759_FG_STATUS_THMHOT (0x1 << 6)
+#define MAX77759_FG_STATUS_SPR_5 (0x1 << 5)
+#define MAX77759_FG_STATUS_ISYSMX (0x1 << 4)
+#define MAX77759_FG_STATUS_BST (0x1 << 3)
+#define MAX77759_FG_STATUS_SPR_2 (0x1 << 2)
+#define MAX77759_FG_STATUS_POR (0x1 << 1)
+#define MAX77759_FG_STATUS_IMN (0x1 << 0)
+
+#define MAX77759_FG_STATUS_BR_SHIFT 15
+#define MAX77759_FG_STATUS_BR_MASK (0x1 << 15)
+#define MAX77759_FG_STATUS_BR_CLEAR (~(0x1 << 15))
+#define MAX77759_FG_STATUS_SMX_SHIFT 14
+#define MAX77759_FG_STATUS_SMX_MASK (0x1 << 14)
+#define MAX77759_FG_STATUS_SMX_CLEAR (~(0x1 << 14))
+#define MAX77759_FG_STATUS_TMX_SHIFT 13
+#define MAX77759_FG_STATUS_TMX_MASK (0x1 << 13)
+#define MAX77759_FG_STATUS_TMX_CLEAR (~(0x1 << 13))
+#define MAX77759_FG_STATUS_VMX_SHIFT 12
+#define MAX77759_FG_STATUS_VMX_MASK (0x1 << 12)
+#define MAX77759_FG_STATUS_VMX_CLEAR (~(0x1 << 12))
+#define MAX77759_FG_STATUS_BI_SHIFT 11
+#define MAX77759_FG_STATUS_BI_MASK (0x1 << 11)
+#define MAX77759_FG_STATUS_BI_CLEAR (~(0x1 << 11))
+#define MAX77759_FG_STATUS_SMN_SHIFT 10
+#define MAX77759_FG_STATUS_SMN_MASK (0x1 << 10)
+#define MAX77759_FG_STATUS_SMN_CLEAR (~(0x1 << 10))
+#define MAX77759_FG_STATUS_TMN_SHIFT 9
+#define MAX77759_FG_STATUS_TMN_MASK (0x1 << 9)
+#define MAX77759_FG_STATUS_TMN_CLEAR (~(0x1 << 9))
+#define MAX77759_FG_STATUS_VMN_SHIFT 8
+#define MAX77759_FG_STATUS_VMN_MASK (0x1 << 8)
+#define MAX77759_FG_STATUS_VMN_CLEAR (~(0x1 << 8))
+#define MAX77759_FG_STATUS_DSOCI_SHIFT 7
+#define MAX77759_FG_STATUS_DSOCI_MASK (0x1 << 7)
+#define MAX77759_FG_STATUS_DSOCI_CLEAR (~(0x1 << 7))
+#define MAX77759_FG_STATUS_THMHOT_SHIFT 6
+#define MAX77759_FG_STATUS_THMHOT_MASK (0x1 << 6)
+#define MAX77759_FG_STATUS_THMHOT_CLEAR (~(0x1 << 6))
+#define MAX77759_FG_STATUS_SPR_5_SHIFT 5
+#define MAX77759_FG_STATUS_SPR_5_MASK (0x1 << 5)
+#define MAX77759_FG_STATUS_SPR_5_CLEAR (~(0x1 << 5))
+#define MAX77759_FG_STATUS_ISYSMX_SHIFT 4
+#define MAX77759_FG_STATUS_ISYSMX_MASK (0x1 << 4)
+#define MAX77759_FG_STATUS_ISYSMX_CLEAR (~(0x1 << 4))
+#define MAX77759_FG_STATUS_BST_SHIFT 3
+#define MAX77759_FG_STATUS_BST_MASK (0x1 << 3)
+#define MAX77759_FG_STATUS_BST_CLEAR (~(0x1 << 3))
+#define MAX77759_FG_STATUS_SPR_2_SHIFT 2
+#define MAX77759_FG_STATUS_SPR_2_MASK (0x1 << 2)
+#define MAX77759_FG_STATUS_SPR_2_CLEAR (~(0x1 << 2))
+#define MAX77759_FG_STATUS_POR_SHIFT 1
+#define MAX77759_FG_STATUS_POR_MASK (0x1 << 1)
+#define MAX77759_FG_STATUS_POR_CLEAR (~(0x1 << 1))
+#define MAX77759_FG_STATUS_IMN_SHIFT 0
+#define MAX77759_FG_STATUS_IMN_MASK (0x1 << 0)
+#define MAX77759_FG_STATUS_IMN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(fg_status_br,15,15)
+MAX77759_BFF(fg_status_smx,14,14)
+MAX77759_BFF(fg_status_tmx,13,13)
+MAX77759_BFF(fg_status_vmx,12,12)
+MAX77759_BFF(fg_status_bi,11,11)
+MAX77759_BFF(fg_status_smn,10,10)
+MAX77759_BFF(fg_status_tmn,9,9)
+MAX77759_BFF(fg_status_vmn,8,8)
+MAX77759_BFF(fg_status_dsoci,7,7)
+MAX77759_BFF(fg_status_thmhot,6,6)
+MAX77759_BFF(fg_status_spr_5,5,5)
+MAX77759_BFF(fg_status_isysmx,4,4)
+MAX77759_BFF(fg_status_bst,3,3)
+MAX77759_BFF(fg_status_spr_2,2,2)
+MAX77759_BFF(fg_status_por,1,1)
+MAX77759_BFF(fg_status_imn,0,0)
+static inline const char *
+max77759_fg_status_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " BR=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_BR, val));
+ i += SCNPRINTF(&buff[i], len - i, " SMX=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_SMX, val));
+ i += SCNPRINTF(&buff[i], len - i, " TMX=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_TMX, val));
+ i += SCNPRINTF(&buff[i], len - i, " VMX=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_VMX, val));
+ i += SCNPRINTF(&buff[i], len - i, " BI=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_BI, val));
+ i += SCNPRINTF(&buff[i], len - i, " SMN=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_SMN, val));
+ i += SCNPRINTF(&buff[i], len - i, " TMN=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_TMN, val));
+ i += SCNPRINTF(&buff[i], len - i, " VMN=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_VMN, val));
+ i += SCNPRINTF(&buff[i], len - i, " DSOCI=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_DSOCI, val));
+ i += SCNPRINTF(&buff[i], len - i, " THMHOT=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_THMHOT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_5=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_SPR_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " ISYSMX=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_ISYSMX, val));
+ i += SCNPRINTF(&buff[i], len - i, " BST=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_BST, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_2=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_SPR_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " POR=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_POR, val));
+ i += SCNPRINTF(&buff[i], len - i, " IMN=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS_IMN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VAlrtTh,0x1,0b1111111100000000,0xff00
+ * MaxVoltageAlrt[7:0],,,,,,
+ */
+#define MAX77759_FG_VALRTTH 0x1
+
+#define MAX77759_FG_VALRTTH_MAXVOLTAGEALRT_SHIFT 8
+#define MAX77759_FG_VALRTTH_MAXVOLTAGEALRT_MASK (0xff << 8)
+#define MAX77759_FG_VALRTTH_MAXVOLTAGEALRT_CLEAR (~(0xff << 8))
+#define MAX77759_FG_VALRTTH_MINVOLTAGEALRT_SHIFT 0
+#define MAX77759_FG_VALRTTH_MINVOLTAGEALRT_MASK (0xff << 0)
+#define MAX77759_FG_VALRTTH_MINVOLTAGEALRT_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_valrtth_maxvoltagealrt,15,8)
+MAX77759_BFF(fg_valrtth_minvoltagealrt,7,0)
+static inline const char *
+max77759_fg_valrtth_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MAXVOLTAGEALRT=%x",
+ FIELD2VALUE(MAX77759_FG_VALRTTH_MAXVOLTAGEALRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINVOLTAGEALRT=%x",
+ FIELD2VALUE(MAX77759_FG_VALRTTH_MINVOLTAGEALRT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TAlrtTh,0x2,0b111111110000000,0x7f80
+ * MaxTempAlrt[7:0],,,,,,
+ */
+#define MAX77759_FG_TALRTTH 0x2
+
+#define MAX77759_FG_TALRTTH_MAXTEMPALRT_SHIFT 8
+#define MAX77759_FG_TALRTTH_MAXTEMPALRT_MASK (0xff << 8)
+#define MAX77759_FG_TALRTTH_MAXTEMPALRT_CLEAR (~(0xff << 8))
+#define MAX77759_FG_TALRTTH_MINTEMPALRT_SHIFT 0
+#define MAX77759_FG_TALRTTH_MINTEMPALRT_MASK (0xff << 0)
+#define MAX77759_FG_TALRTTH_MINTEMPALRT_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_talrtth_maxtempalrt,15,8)
+MAX77759_BFF(fg_talrtth_mintempalrt,7,0)
+static inline const char *
+max77759_fg_talrtth_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MAXTEMPALRT=%x",
+ FIELD2VALUE(MAX77759_FG_TALRTTH_MAXTEMPALRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINTEMPALRT=%x",
+ FIELD2VALUE(MAX77759_FG_TALRTTH_MINTEMPALRT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * SAlrtTh,0x3,0b1111111100000000,0xff00
+ * MaxSocAlrt[7:0],,,,,,
+ */
+#define MAX77759_FG_SALRTTH 0x3
+
+#define MAX77759_FG_SALRTTH_MAXSOCALRT_SHIFT 8
+#define MAX77759_FG_SALRTTH_MAXSOCALRT_MASK (0xff << 8)
+#define MAX77759_FG_SALRTTH_MAXSOCALRT_CLEAR (~(0xff << 8))
+#define MAX77759_FG_SALRTTH_MINSOCALRT_SHIFT 0
+#define MAX77759_FG_SALRTTH_MINSOCALRT_MASK (0xff << 0)
+#define MAX77759_FG_SALRTTH_MINSOCALRT_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_salrtth_maxsocalrt,15,8)
+MAX77759_BFF(fg_salrtth_minsocalrt,7,0)
+static inline const char *
+max77759_fg_salrtth_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MAXSOCALRT=%x",
+ FIELD2VALUE(MAX77759_FG_SALRTTH_MAXSOCALRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINSOCALRT=%x",
+ FIELD2VALUE(MAX77759_FG_SALRTTH_MINSOCALRT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * AtRate,0x4,0b00000000,0x00
+ * AtRate[15:8],,,,,,
+ */
+#define MAX77759_FG_ATRATE 0x4
+
+/*
+ * RepCap,0x5,0b10111011100,0x5dc
+ * RepCap[15:8],,,,,,
+ */
+#define MAX77759_FG_REPCAP 0x5
+
+/*
+ * RepSOC,0x6,0b11001000000000,0x3200
+ * RepSOC[15:8],,,,,,
+ */
+#define MAX77759_FG_REPSOC 0x6
+
+/*
+ * Age,0x7,0b110010000000000,0x6400
+ * Age[15:8],,,,,,
+ */
+#define MAX77759_FG_AGE 0x7
+
+/*
+ * Temp,0x8,0b1011000000000,0x1600
+ * TEMP[15:8],,,,,,
+ */
+#define MAX77759_FG_TEMP 0x8
+
+/*
+ * Vcell,0x9,0b1011010000000000,0xb400
+ * VCELL[15:8],,,,,,
+ */
+#define MAX77759_FG_VCELL 0x9
+
+/*
+ * Current,0xA,0b00000000,0x00
+ * Current[15:8],,,,,,
+ */
+#define MAX77759_FG_CURRENT 0xA
+
+/*
+ * AvgCurrent,0xB,0b00000000,0x00
+ * AvgCurrent[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGCURRENT 0xB
+
+/*
+ * QResidual,0xC,0b00000000,0x00
+ * Qresidual[15:8],,,,,,
+ */
+#define MAX77759_FG_QRESIDUAL 0xC
+
+/*
+ * MixSOC,0xD,0b11001000000000,0x3200
+ * MixSOC[15:8],,,,,,
+ */
+#define MAX77759_FG_MIXSOC 0xD
+
+/*
+ * AvSOC,0xE,0b11001000000000,0x3200
+ * AvSOC[15:8],,,,,,
+ */
+#define MAX77759_FG_AVSOC 0xE
+
+/*
+ * MixCap,0xF,0b10111011100,0x5dc
+ * MixCapH[15:8],,,,,,
+ */
+#define MAX77759_FG_MIXCAP 0xF
+
+/*
+ * FullCap,0x10,0b101110111000,0xbb8
+ * FullCAP[15:8],,,,,,
+ */
+#define MAX77759_FG_FULLCAP 0x10
+
+/*
+ * TTE,0x11,0b00000000,0x00
+ * hr[5:0],,,,,,mn[5:4]
+ */
+#define MAX77759_FG_TTE 0x11
+
+#define MAX77759_FG_TTE_HR_SHIFT 10
+#define MAX77759_FG_TTE_HR_MASK (0x3f << 10)
+#define MAX77759_FG_TTE_HR_CLEAR (~(0x3f << 10))
+#define MAX77759_FG_TTE_MN_SHIFT 4
+#define MAX77759_FG_TTE_MN_MASK (0x3f << 4)
+#define MAX77759_FG_TTE_MN_CLEAR (~(0x3f << 4))
+#define MAX77759_FG_TTE_SEC_SHIFT 0
+#define MAX77759_FG_TTE_SEC_MASK (0xf << 0)
+#define MAX77759_FG_TTE_SEC_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(fg_tte_hr,15,10)
+MAX77759_BFF(fg_tte_mn,9,4)
+MAX77759_BFF(fg_tte_sec,3,0)
+static inline const char *
+max77759_fg_tte_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " HR=%x",
+ FIELD2VALUE(MAX77759_FG_TTE_HR, val));
+ i += SCNPRINTF(&buff[i], len - i, " MN=%x",
+ FIELD2VALUE(MAX77759_FG_TTE_MN, val));
+ i += SCNPRINTF(&buff[i], len - i, " SEC=%x",
+ FIELD2VALUE(MAX77759_FG_TTE_SEC, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * QRTable00,0x12,0b11110000000000,0x3c00
+ * QRTable00[15:8],,,,,,
+ */
+#define MAX77759_FG_QRTABLE00 0x12
+
+/*
+ * FullSocThr,0x13,0b101000000000000,0x5000
+ * FullSOCThr[15:8],,,,,,
+ */
+#define MAX77759_FG_FULLSOCTHR 0x13
+
+/*
+ * Rslow,0x14,0b1010010000,0x290
+ * RSLOW[15:8],,,,,,
+ */
+#define MAX77759_FG_RSLOW 0x14
+
+/*
+ * RFast,0x15,0b101001000,0x148
+ * RFAST[15:8],,,,,,
+ */
+#define MAX77759_FG_RFAST 0x15
+
+/*
+ * AvgTA,0x16,0b1011000000000,0x1600
+ * AvgTA[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGTA 0x16
+
+/*
+ * Cycles,0x17,0b00000000,0x00
+ * Cycles[15:8],,,,,,
+ */
+#define MAX77759_FG_CYCLES 0x17
+
+/*
+ * DesignCap,0x18,0b101110111000,0xbb8
+ * DesignCap[15:8],,,,,,
+ */
+#define MAX77759_FG_DESIGNCAP 0x18
+
+/*
+ * AvgVCell,0x19,0b1011010000000000,0xb400
+ * AvgVCELL[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGVCELL 0x19
+
+/*
+ * MaxMinTemp,0x1A,0b1000000001111111,0x807f
+ * MaxTemperature[7:0],,,,,,
+ */
+#define MAX77759_FG_MAXMINTEMP 0x1A
+
+#define MAX77759_FG_MAXMINTEMP_MAXTEMPERATURE_SHIFT 8
+#define MAX77759_FG_MAXMINTEMP_MAXTEMPERATURE_MASK (0xff << 8)
+#define MAX77759_FG_MAXMINTEMP_MAXTEMPERATURE_CLEAR (~(0xff << 8))
+#define MAX77759_FG_MAXMINTEMP_MINTEMPERATURE_SHIFT 0
+#define MAX77759_FG_MAXMINTEMP_MINTEMPERATURE_MASK (0xff << 0)
+#define MAX77759_FG_MAXMINTEMP_MINTEMPERATURE_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_maxmintemp_maxtemperature,15,8)
+MAX77759_BFF(fg_maxmintemp_mintemperature,7,0)
+static inline const char *
+max77759_fg_maxmintemp_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MAXTEMPERATURE=%x",
+ FIELD2VALUE(MAX77759_FG_MAXMINTEMP_MAXTEMPERATURE, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINTEMPERATURE=%x",
+ FIELD2VALUE(MAX77759_FG_MAXMINTEMP_MINTEMPERATURE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * MaxMinVolt,0x1B,0b11111111,0xff
+ * MaxVoltage[7:0],,,,,,
+ */
+#define MAX77759_FG_MAXMINVOLT 0x1B
+
+#define MAX77759_FG_MAXMINVOLT_MAXVOLTAGE_SHIFT 8
+#define MAX77759_FG_MAXMINVOLT_MAXVOLTAGE_MASK (0xff << 8)
+#define MAX77759_FG_MAXMINVOLT_MAXVOLTAGE_CLEAR (~(0xff << 8))
+#define MAX77759_FG_MAXMINVOLT_MINVOLTAGE_SHIFT 0
+#define MAX77759_FG_MAXMINVOLT_MINVOLTAGE_MASK (0xff << 0)
+#define MAX77759_FG_MAXMINVOLT_MINVOLTAGE_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_maxminvolt_maxvoltage,15,8)
+MAX77759_BFF(fg_maxminvolt_minvoltage,7,0)
+static inline const char *
+max77759_fg_maxminvolt_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MAXVOLTAGE=%x",
+ FIELD2VALUE(MAX77759_FG_MAXMINVOLT_MAXVOLTAGE, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINVOLTAGE=%x",
+ FIELD2VALUE(MAX77759_FG_MAXMINVOLT_MINVOLTAGE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * MaxMinCurr,0x1C,0b1000000001111111,0x807f
+ * MaxChargeCurrent[7:0],,,,,,
+ */
+#define MAX77759_FG_MAXMINCURR 0x1C
+
+#define MAX77759_FG_MAXMINCURR_MAXCHARGECURRENT_SHIFT 8
+#define MAX77759_FG_MAXMINCURR_MAXCHARGECURRENT_MASK (0xff << 8)
+#define MAX77759_FG_MAXMINCURR_MAXCHARGECURRENT_CLEAR (~(0xff << 8))
+#define MAX77759_FG_MAXMINCURR_MAXDISCURRENT_SHIFT 0
+#define MAX77759_FG_MAXMINCURR_MAXDISCURRENT_MASK (0xff << 0)
+#define MAX77759_FG_MAXMINCURR_MAXDISCURRENT_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_maxmincurr_maxchargecurrent,15,8)
+MAX77759_BFF(fg_maxmincurr_maxdiscurrent,7,0)
+static inline const char *
+max77759_fg_maxmincurr_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MAXCHARGECURRENT=%x",
+ FIELD2VALUE(MAX77759_FG_MAXMINCURR_MAXCHARGECURRENT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MAXDISCURRENT=%x",
+ FIELD2VALUE(MAX77759_FG_MAXMINCURR_MAXDISCURRENT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * Config,0x1D,0b10001101010000,0x2350
+ * FCFE,Ss,Ts,Vs,FGCC,AINSH,Ten
+ */
+#define MAX77759_FG_CONFIG 0x1D
+#define MAX77759_FG_CONFIG_FCFE (0x1 << 15)
+#define MAX77759_FG_CONFIG_SS (0x1 << 14)
+#define MAX77759_FG_CONFIG_TS (0x1 << 13)
+#define MAX77759_FG_CONFIG_VS (0x1 << 12)
+#define MAX77759_FG_CONFIG_FGCC (0x1 << 11)
+#define MAX77759_FG_CONFIG_AINSH (0x1 << 10)
+#define MAX77759_FG_CONFIG_TEN (0x1 << 9)
+#define MAX77759_FG_CONFIG_TEX (0x1 << 8)
+#define MAX77759_FG_CONFIG_SHDN (0x1 << 7)
+#define MAX77759_FG_CONFIG_I2CSH (0x1 << 6)
+#define MAX77759_FG_CONFIG_ICFE (0x1 << 5)
+#define MAX77759_FG_CONFIG_ETHRM (0x1 << 4)
+#define MAX77759_FG_CONFIG_FTHRM (0x1 << 3)
+#define MAX77759_FG_CONFIG_AEN (0x1 << 2)
+#define MAX77759_FG_CONFIG_BEI (0x1 << 1)
+#define MAX77759_FG_CONFIG_BER (0x1 << 0)
+
+#define MAX77759_FG_CONFIG_FCFE_SHIFT 15
+#define MAX77759_FG_CONFIG_FCFE_MASK (0x1 << 15)
+#define MAX77759_FG_CONFIG_FCFE_CLEAR (~(0x1 << 15))
+#define MAX77759_FG_CONFIG_SS_SHIFT 14
+#define MAX77759_FG_CONFIG_SS_MASK (0x1 << 14)
+#define MAX77759_FG_CONFIG_SS_CLEAR (~(0x1 << 14))
+#define MAX77759_FG_CONFIG_TS_SHIFT 13
+#define MAX77759_FG_CONFIG_TS_MASK (0x1 << 13)
+#define MAX77759_FG_CONFIG_TS_CLEAR (~(0x1 << 13))
+#define MAX77759_FG_CONFIG_VS_SHIFT 12
+#define MAX77759_FG_CONFIG_VS_MASK (0x1 << 12)
+#define MAX77759_FG_CONFIG_VS_CLEAR (~(0x1 << 12))
+#define MAX77759_FG_CONFIG_FGCC_SHIFT 11
+#define MAX77759_FG_CONFIG_FGCC_MASK (0x1 << 11)
+#define MAX77759_FG_CONFIG_FGCC_CLEAR (~(0x1 << 11))
+#define MAX77759_FG_CONFIG_AINSH_SHIFT 10
+#define MAX77759_FG_CONFIG_AINSH_MASK (0x1 << 10)
+#define MAX77759_FG_CONFIG_AINSH_CLEAR (~(0x1 << 10))
+#define MAX77759_FG_CONFIG_TEN_SHIFT 9
+#define MAX77759_FG_CONFIG_TEN_MASK (0x1 << 9)
+#define MAX77759_FG_CONFIG_TEN_CLEAR (~(0x1 << 9))
+#define MAX77759_FG_CONFIG_TEX_SHIFT 8
+#define MAX77759_FG_CONFIG_TEX_MASK (0x1 << 8)
+#define MAX77759_FG_CONFIG_TEX_CLEAR (~(0x1 << 8))
+#define MAX77759_FG_CONFIG_SHDN_SHIFT 7
+#define MAX77759_FG_CONFIG_SHDN_MASK (0x1 << 7)
+#define MAX77759_FG_CONFIG_SHDN_CLEAR (~(0x1 << 7))
+#define MAX77759_FG_CONFIG_I2CSH_SHIFT 6
+#define MAX77759_FG_CONFIG_I2CSH_MASK (0x1 << 6)
+#define MAX77759_FG_CONFIG_I2CSH_CLEAR (~(0x1 << 6))
+#define MAX77759_FG_CONFIG_ICFE_SHIFT 5
+#define MAX77759_FG_CONFIG_ICFE_MASK (0x1 << 5)
+#define MAX77759_FG_CONFIG_ICFE_CLEAR (~(0x1 << 5))
+#define MAX77759_FG_CONFIG_ETHRM_SHIFT 4
+#define MAX77759_FG_CONFIG_ETHRM_MASK (0x1 << 4)
+#define MAX77759_FG_CONFIG_ETHRM_CLEAR (~(0x1 << 4))
+#define MAX77759_FG_CONFIG_FTHRM_SHIFT 3
+#define MAX77759_FG_CONFIG_FTHRM_MASK (0x1 << 3)
+#define MAX77759_FG_CONFIG_FTHRM_CLEAR (~(0x1 << 3))
+#define MAX77759_FG_CONFIG_AEN_SHIFT 2
+#define MAX77759_FG_CONFIG_AEN_MASK (0x1 << 2)
+#define MAX77759_FG_CONFIG_AEN_CLEAR (~(0x1 << 2))
+#define MAX77759_FG_CONFIG_BEI_SHIFT 1
+#define MAX77759_FG_CONFIG_BEI_MASK (0x1 << 1)
+#define MAX77759_FG_CONFIG_BEI_CLEAR (~(0x1 << 1))
+#define MAX77759_FG_CONFIG_BER_SHIFT 0
+#define MAX77759_FG_CONFIG_BER_MASK (0x1 << 0)
+#define MAX77759_FG_CONFIG_BER_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(fg_config_fcfe,15,15)
+MAX77759_BFF(fg_config_ss,14,14)
+MAX77759_BFF(fg_config_ts,13,13)
+MAX77759_BFF(fg_config_vs,12,12)
+MAX77759_BFF(fg_config_fgcc,11,11)
+MAX77759_BFF(fg_config_ainsh,10,10)
+MAX77759_BFF(fg_config_ten,9,9)
+MAX77759_BFF(fg_config_tex,8,8)
+MAX77759_BFF(fg_config_shdn,7,7)
+MAX77759_BFF(fg_config_i2csh,6,6)
+MAX77759_BFF(fg_config_icfe,5,5)
+MAX77759_BFF(fg_config_ethrm,4,4)
+MAX77759_BFF(fg_config_fthrm,3,3)
+MAX77759_BFF(fg_config_aen,2,2)
+MAX77759_BFF(fg_config_bei,1,1)
+MAX77759_BFF(fg_config_ber,0,0)
+static inline const char *
+max77759_fg_config_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " FCFE=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_FCFE, val));
+ i += SCNPRINTF(&buff[i], len - i, " SS=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_SS, val));
+ i += SCNPRINTF(&buff[i], len - i, " TS=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_TS, val));
+ i += SCNPRINTF(&buff[i], len - i, " VS=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_VS, val));
+ i += SCNPRINTF(&buff[i], len - i, " FGCC=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_FGCC, val));
+ i += SCNPRINTF(&buff[i], len - i, " AINSH=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_AINSH, val));
+ i += SCNPRINTF(&buff[i], len - i, " TEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_TEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " TEX=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_TEX, val));
+ i += SCNPRINTF(&buff[i], len - i, " SHDN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_SHDN, val));
+ i += SCNPRINTF(&buff[i], len - i, " I2CSH=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_I2CSH, val));
+ i += SCNPRINTF(&buff[i], len - i, " ICFE=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_ICFE, val));
+ i += SCNPRINTF(&buff[i], len - i, " ETHRM=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_ETHRM, val));
+ i += SCNPRINTF(&buff[i], len - i, " FTHRM=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_FTHRM, val));
+ i += SCNPRINTF(&buff[i], len - i, " AEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_AEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " BEI=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_BEI, val));
+ i += SCNPRINTF(&buff[i], len - i, " BER=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG_BER, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * IChgTerm,0x1E,0b1111000000,0x3c0
+ * ICHGTerm[15:8],,,,,,
+ */
+#define MAX77759_FG_ICHGTERM 0x1E
+
+/*
+ * AvCap,0x1F,0b10111011100,0x5dc
+ * AvCap[15:8],,,,,,
+ */
+#define MAX77759_FG_AVCAP 0x1F
+
+/*
+ * TTF,0x20,0b1111111111111111,0xffff
+ * hr[5:0],,,,,,mn[5:4]
+ */
+#define MAX77759_FG_TTF 0x20
+
+#define MAX77759_FG_TTF_HR_SHIFT 10
+#define MAX77759_FG_TTF_HR_MASK (0x3f << 10)
+#define MAX77759_FG_TTF_HR_CLEAR (~(0x3f << 10))
+#define MAX77759_FG_TTF_MN_SHIFT 4
+#define MAX77759_FG_TTF_MN_MASK (0x3f << 4)
+#define MAX77759_FG_TTF_MN_CLEAR (~(0x3f << 4))
+#define MAX77759_FG_TTF_SEC_SHIFT 0
+#define MAX77759_FG_TTF_SEC_MASK (0xf << 0)
+#define MAX77759_FG_TTF_SEC_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(fg_ttf_hr,15,10)
+MAX77759_BFF(fg_ttf_mn,9,4)
+MAX77759_BFF(fg_ttf_sec,3,0)
+static inline const char *
+max77759_fg_ttf_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " HR=%x",
+ FIELD2VALUE(MAX77759_FG_TTF_HR, val));
+ i += SCNPRINTF(&buff[i], len - i, " MN=%x",
+ FIELD2VALUE(MAX77759_FG_TTF_MN, val));
+ i += SCNPRINTF(&buff[i], len - i, " SEC=%x",
+ FIELD2VALUE(MAX77759_FG_TTF_SEC, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * DevName,0x21,0b110001000000000,0x6200
+ * DevName[15:8],,,,,,
+ */
+#define MAX77759_FG_DEVNAME 0x21
+
+/*
+ * QRTable10,0x22,0b1101110000000,0x1b80
+ * QRTable10[15:8],,,,,,
+ */
+#define MAX77759_FG_QRTABLE10 0x22
+
+/*
+ * FullCapNom,0x23,0b101110111000,0xbb8
+ * FullCapNom[15:8],,,,,,
+ */
+#define MAX77759_FG_FULLCAPNOM 0x23
+
+/*
+ * TempNom,0x24,0b1010000000000,0x1400
+ * TempNom[9:2],,,,,,
+ */
+#define MAX77759_FG_TEMPNOM 0x24
+
+#define MAX77759_FG_TEMPNOM_TEMPNOM_SHIFT 6
+#define MAX77759_FG_TEMPNOM_TEMPNOM_MASK (0x3ff << 6)
+#define MAX77759_FG_TEMPNOM_TEMPNOM_CLEAR (~(0x3ff << 6))
+#define MAX77759_FG_TEMPNOM_SPR_5_0_SHIFT 0
+#define MAX77759_FG_TEMPNOM_SPR_5_0_MASK (0x3f << 0)
+#define MAX77759_FG_TEMPNOM_SPR_5_0_CLEAR (~(0x3f << 0))
+
+MAX77759_BFF(fg_tempnom_tempnom,15,6)
+MAX77759_BFF(fg_tempnom_spr_5_0,5,0)
+static inline const char *
+max77759_fg_tempnom_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " TEMPNOM=%x",
+ FIELD2VALUE(MAX77759_FG_TEMPNOM_TEMPNOM, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_5_0=%x",
+ FIELD2VALUE(MAX77759_FG_TEMPNOM_SPR_5_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TempLim,0x25,0b10001100000101,0x2305
+ * TempHot[7:0],,,,,,
+ */
+#define MAX77759_FG_TEMPLIM 0x25
+
+#define MAX77759_FG_TEMPLIM_TEMPHOT_SHIFT 8
+#define MAX77759_FG_TEMPLIM_TEMPHOT_MASK (0xff << 8)
+#define MAX77759_FG_TEMPLIM_TEMPHOT_CLEAR (~(0xff << 8))
+#define MAX77759_FG_TEMPLIM_TEMPCOLD_SHIFT 0
+#define MAX77759_FG_TEMPLIM_TEMPCOLD_MASK (0xff << 0)
+#define MAX77759_FG_TEMPLIM_TEMPCOLD_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_templim_temphot,15,8)
+MAX77759_BFF(fg_templim_tempcold,7,0)
+static inline const char *
+max77759_fg_templim_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " TEMPHOT=%x",
+ FIELD2VALUE(MAX77759_FG_TEMPLIM_TEMPHOT, val));
+ i += SCNPRINTF(&buff[i], len - i, " TEMPCOLD=%x",
+ FIELD2VALUE(MAX77759_FG_TEMPLIM_TEMPCOLD, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * AvgTA0,0x26,0b1011000000000,0x1600
+ * AvgTA0[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGTA0 0x26
+
+/*
+ * AIN0,0x27,0b1000100011010000,0x88d0
+ * AIN0[15:8],,,,,,
+ */
+#define MAX77759_FG_AIN0 0x27
+
+/*
+ * LearnCfg,0x28,0b10011000000011,0x2603
+ * LearnRCOMP[2:0],,,LearnTCO[2:0],,,FCLm[1:0]
+ */
+#define MAX77759_FG_LEARNCFG 0x28
+#define MAX77759_FG_LEARNCFG_FCX (0x1 << 7)
+#define MAX77759_FG_LEARNCFG_SPR_3 (0x1 << 3)
+#define MAX77759_FG_LEARNCFG_FILLEMPTY (0x1 << 2)
+#define MAX77759_FG_LEARNCFG_MIXEN (0x1 << 1)
+#define MAX77759_FG_LEARNCFG_SPR_0 (0x1 << 0)
+
+#define MAX77759_FG_LEARNCFG_LEARNRCOMP_SHIFT 13
+#define MAX77759_FG_LEARNCFG_LEARNRCOMP_MASK (0x7 << 13)
+#define MAX77759_FG_LEARNCFG_LEARNRCOMP_CLEAR (~(0x7 << 13))
+#define MAX77759_FG_LEARNCFG_LEARNTCO_SHIFT 10
+#define MAX77759_FG_LEARNCFG_LEARNTCO_MASK (0x7 << 10)
+#define MAX77759_FG_LEARNCFG_LEARNTCO_CLEAR (~(0x7 << 10))
+#define MAX77759_FG_LEARNCFG_FCLM_SHIFT 8
+#define MAX77759_FG_LEARNCFG_FCLM_MASK (0x3 << 8)
+#define MAX77759_FG_LEARNCFG_FCLM_CLEAR (~(0x3 << 8))
+#define MAX77759_FG_LEARNCFG_FCX_SHIFT 7
+#define MAX77759_FG_LEARNCFG_FCX_MASK (0x1 << 7)
+#define MAX77759_FG_LEARNCFG_FCX_CLEAR (~(0x1 << 7))
+#define MAX77759_FG_LEARNCFG_FCLRNSTAGE_SHIFT 4
+#define MAX77759_FG_LEARNCFG_FCLRNSTAGE_MASK (0x7 << 4)
+#define MAX77759_FG_LEARNCFG_FCLRNSTAGE_CLEAR (~(0x7 << 4))
+#define MAX77759_FG_LEARNCFG_SPR_3_SHIFT 3
+#define MAX77759_FG_LEARNCFG_SPR_3_MASK (0x1 << 3)
+#define MAX77759_FG_LEARNCFG_SPR_3_CLEAR (~(0x1 << 3))
+#define MAX77759_FG_LEARNCFG_FILLEMPTY_SHIFT 2
+#define MAX77759_FG_LEARNCFG_FILLEMPTY_MASK (0x1 << 2)
+#define MAX77759_FG_LEARNCFG_FILLEMPTY_CLEAR (~(0x1 << 2))
+#define MAX77759_FG_LEARNCFG_MIXEN_SHIFT 1
+#define MAX77759_FG_LEARNCFG_MIXEN_MASK (0x1 << 1)
+#define MAX77759_FG_LEARNCFG_MIXEN_CLEAR (~(0x1 << 1))
+#define MAX77759_FG_LEARNCFG_SPR_0_SHIFT 0
+#define MAX77759_FG_LEARNCFG_SPR_0_MASK (0x1 << 0)
+#define MAX77759_FG_LEARNCFG_SPR_0_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(fg_learncfg_learnrcomp,15,13)
+MAX77759_BFF(fg_learncfg_learntco,12,10)
+MAX77759_BFF(fg_learncfg_fclm,9,8)
+MAX77759_BFF(fg_learncfg_fcx,7,7)
+MAX77759_BFF(fg_learncfg_fclrnstage,6,4)
+MAX77759_BFF(fg_learncfg_spr_3,3,3)
+MAX77759_BFF(fg_learncfg_fillempty,2,2)
+MAX77759_BFF(fg_learncfg_mixen,1,1)
+MAX77759_BFF(fg_learncfg_spr_0,0,0)
+static inline const char *
+max77759_fg_learncfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " LEARNRCOMP=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_LEARNRCOMP, val));
+ i += SCNPRINTF(&buff[i], len - i, " LEARNTCO=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_LEARNTCO, val));
+ i += SCNPRINTF(&buff[i], len - i, " FCLM=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_FCLM, val));
+ i += SCNPRINTF(&buff[i], len - i, " FCX=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_FCX, val));
+ i += SCNPRINTF(&buff[i], len - i, " FCLRNSTAGE=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_FCLRNSTAGE, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_3=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_SPR_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " FILLEMPTY=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_FILLEMPTY, val));
+ i += SCNPRINTF(&buff[i], len - i, " MIXEN=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_MIXEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_0=%x",
+ FIELD2VALUE(MAX77759_FG_LEARNCFG_SPR_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * FilterCfg,0x29,0b1100111010100100,0xcea4
+ * NEMPTY[1:0],,NTEMP[2:0],,,NMIX[3:1],
+ */
+#define MAX77759_FG_FILTERCFG 0x29
+
+#define MAX77759_FG_FILTERCFG_NEMPTY_SHIFT 14
+#define MAX77759_FG_FILTERCFG_NEMPTY_MASK (0x3 << 14)
+#define MAX77759_FG_FILTERCFG_NEMPTY_CLEAR (~(0x3 << 14))
+#define MAX77759_FG_FILTERCFG_NTEMP_SHIFT 11
+#define MAX77759_FG_FILTERCFG_NTEMP_MASK (0x7 << 11)
+#define MAX77759_FG_FILTERCFG_NTEMP_CLEAR (~(0x7 << 11))
+#define MAX77759_FG_FILTERCFG_NMIX_SHIFT 8
+#define MAX77759_FG_FILTERCFG_NMIX_MASK (0x7 << 8)
+#define MAX77759_FG_FILTERCFG_NMIX_CLEAR (~(0x7 << 8))
+#define MAX77759_FG_FILTERCFG_NAVGCELL_SHIFT 5
+#define MAX77759_FG_FILTERCFG_NAVGCELL_MASK (0x7 << 5)
+#define MAX77759_FG_FILTERCFG_NAVGCELL_CLEAR (~(0x7 << 5))
+#define MAX77759_FG_FILTERCFG_NCURR_SHIFT 1
+#define MAX77759_FG_FILTERCFG_NCURR_MASK (0xf << 1)
+#define MAX77759_FG_FILTERCFG_NCURR_CLEAR (~(0xf << 1))
+
+MAX77759_BFF(fg_filtercfg_nempty,15,14)
+MAX77759_BFF(fg_filtercfg_ntemp,13,11)
+MAX77759_BFF(fg_filtercfg_nmix,10,8)
+MAX77759_BFF(fg_filtercfg_navgcell,7,5)
+MAX77759_BFF(fg_filtercfg_ncurr,4,1)
+static inline const char *
+max77759_fg_filtercfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " NEMPTY=%x",
+ FIELD2VALUE(MAX77759_FG_FILTERCFG_NEMPTY, val));
+ i += SCNPRINTF(&buff[i], len - i, " NTEMP=%x",
+ FIELD2VALUE(MAX77759_FG_FILTERCFG_NTEMP, val));
+ i += SCNPRINTF(&buff[i], len - i, " NMIX=%x",
+ FIELD2VALUE(MAX77759_FG_FILTERCFG_NMIX, val));
+ i += SCNPRINTF(&buff[i], len - i, " NAVGCELL=%x",
+ FIELD2VALUE(MAX77759_FG_FILTERCFG_NAVGCELL, val));
+ i += SCNPRINTF(&buff[i], len - i, " NCURR=%x",
+ FIELD2VALUE(MAX77759_FG_FILTERCFG_NCURR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * RelaxCfg,0x2A,0b10000011001001,0x20c9
+ * LoadThr[6:0],,,,,,
+ */
+#define MAX77759_FG_RELAXCFG 0x2A
+
+#define MAX77759_FG_RELAXCFG_LOADTHR_SHIFT 9
+#define MAX77759_FG_RELAXCFG_LOADTHR_MASK (0x7f << 9)
+#define MAX77759_FG_RELAXCFG_LOADTHR_CLEAR (~(0x7f << 9))
+#define MAX77759_FG_RELAXCFG_DVTHR_SHIFT 4
+#define MAX77759_FG_RELAXCFG_DVTHR_MASK (0xf << 4)
+#define MAX77759_FG_RELAXCFG_DVTHR_CLEAR (~(0xf << 4))
+#define MAX77759_FG_RELAXCFG_DTTHR_SHIFT 0
+#define MAX77759_FG_RELAXCFG_DTTHR_MASK (0xf << 0)
+#define MAX77759_FG_RELAXCFG_DTTHR_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(fg_relaxcfg_loadthr,15,9)
+MAX77759_BFF(fg_relaxcfg_dvthr,7,4)
+MAX77759_BFF(fg_relaxcfg_dtthr,3,0)
+static inline const char *
+max77759_fg_relaxcfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " LOADTHR=%x",
+ FIELD2VALUE(MAX77759_FG_RELAXCFG_LOADTHR, val));
+ i += SCNPRINTF(&buff[i], len - i, " DVTHR=%x",
+ FIELD2VALUE(MAX77759_FG_RELAXCFG_DVTHR, val));
+ i += SCNPRINTF(&buff[i], len - i, " DTTHR=%x",
+ FIELD2VALUE(MAX77759_FG_RELAXCFG_DTTHR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * MiscCfg,0x2B,0b100111010000,0x9d0
+ * OopsFilter[3:0],,,,EnBi1,InitVFG,MixRate[4:3]
+ */
+#define MAX77759_FG_MISCCFG 0x2B
+#define MAX77759_FG_MISCCFG_ENBI1 (0x1 << 11)
+#define MAX77759_FG_MISCCFG_INITVFG (0x1 << 10)
+#define MAX77759_FG_MISCCFG_RDFCLRN (0x1 << 4)
+#define MAX77759_FG_MISCCFG_VTTL (0x1 << 3)
+#define MAX77759_FG_MISCCFG_VEX (0x1 << 2)
+
+#define MAX77759_FG_MISCCFG_OOPSFILTER_SHIFT 12
+#define MAX77759_FG_MISCCFG_OOPSFILTER_MASK (0xf << 12)
+#define MAX77759_FG_MISCCFG_OOPSFILTER_CLEAR (~(0xf << 12))
+#define MAX77759_FG_MISCCFG_ENBI1_SHIFT 11
+#define MAX77759_FG_MISCCFG_ENBI1_MASK (0x1 << 11)
+#define MAX77759_FG_MISCCFG_ENBI1_CLEAR (~(0x1 << 11))
+#define MAX77759_FG_MISCCFG_INITVFG_SHIFT 10
+#define MAX77759_FG_MISCCFG_INITVFG_MASK (0x1 << 10)
+#define MAX77759_FG_MISCCFG_INITVFG_CLEAR (~(0x1 << 10))
+#define MAX77759_FG_MISCCFG_MIXRATE_SHIFT 5
+#define MAX77759_FG_MISCCFG_MIXRATE_MASK (0x1f << 5)
+#define MAX77759_FG_MISCCFG_MIXRATE_CLEAR (~(0x1f << 5))
+#define MAX77759_FG_MISCCFG_RDFCLRN_SHIFT 4
+#define MAX77759_FG_MISCCFG_RDFCLRN_MASK (0x1 << 4)
+#define MAX77759_FG_MISCCFG_RDFCLRN_CLEAR (~(0x1 << 4))
+#define MAX77759_FG_MISCCFG_VTTL_SHIFT 3
+#define MAX77759_FG_MISCCFG_VTTL_MASK (0x1 << 3)
+#define MAX77759_FG_MISCCFG_VTTL_CLEAR (~(0x1 << 3))
+#define MAX77759_FG_MISCCFG_VEX_SHIFT 2
+#define MAX77759_FG_MISCCFG_VEX_MASK (0x1 << 2)
+#define MAX77759_FG_MISCCFG_VEX_CLEAR (~(0x1 << 2))
+#define MAX77759_FG_MISCCFG_SACFG_SHIFT 0
+#define MAX77759_FG_MISCCFG_SACFG_MASK (0x3 << 0)
+#define MAX77759_FG_MISCCFG_SACFG_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(fg_misccfg_oopsfilter,15,12)
+MAX77759_BFF(fg_misccfg_enbi1,11,11)
+MAX77759_BFF(fg_misccfg_initvfg,10,10)
+MAX77759_BFF(fg_misccfg_mixrate,9,5)
+MAX77759_BFF(fg_misccfg_rdfclrn,4,4)
+MAX77759_BFF(fg_misccfg_vttl,3,3)
+MAX77759_BFF(fg_misccfg_vex,2,2)
+MAX77759_BFF(fg_misccfg_sacfg,1,0)
+static inline const char *
+max77759_fg_misccfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " OOPSFILTER=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_OOPSFILTER, val));
+ i += SCNPRINTF(&buff[i], len - i, " ENBI1=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_ENBI1, val));
+ i += SCNPRINTF(&buff[i], len - i, " INITVFG=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_INITVFG, val));
+ i += SCNPRINTF(&buff[i], len - i, " MIXRATE=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_MIXRATE, val));
+ i += SCNPRINTF(&buff[i], len - i, " RDFCLRN=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_RDFCLRN, val));
+ i += SCNPRINTF(&buff[i], len - i, " VTTL=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_VTTL, val));
+ i += SCNPRINTF(&buff[i], len - i, " VEX=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_VEX, val));
+ i += SCNPRINTF(&buff[i], len - i, " SACFG=%x",
+ FIELD2VALUE(MAX77759_FG_MISCCFG_SACFG, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TGain,0x2C,0b1110101110001101,0xeb8d
+ * TGAIN[15:8],,,,,,
+ */
+#define MAX77759_FG_TGAIN 0x2C
+
+/*
+ * TOff,0x2D,0b10000010101010,0x20aa
+ * TOFF[15:8],,,,,,
+ */
+#define MAX77759_FG_TOFF 0x2D
+
+/*
+ * CGain,0x2E,0b10000000000,0x400
+ * CGAIN[15:8],,,,,,
+ */
+#define MAX77759_FG_CGAIN 0x2E
+
+/*
+ * COff,0x2F,0b00000000,0x00
+ * COFF[15:8],,,,,,
+ */
+#define MAX77759_FG_COFF 0x2F
+
+/*
+ * dV_acc,0x30,0b10000000000,0x400
+ * dV_acc[15:8],,,,,,
+ */
+#define MAX77759_FG_DV_ACC 0x30
+
+/*
+ * dI_acc,0x31,0b11001000000,0x640
+ * dI_acc[15:8],,,,,,
+ */
+#define MAX77759_FG_DI_ACC 0x31
+
+/*
+ * QRTable20,0x32,0b101100000100,0xb04
+ * QRTable20[15:8],,,,,,
+ */
+#define MAX77759_FG_QRTABLE20 0x32
+
+/*
+ * AtTTF,0x33,0b1111111111111111,0xffff
+ * AtTTF[15:8],,,,,,
+ */
+#define MAX77759_FG_ATTTF 0x33
+
+/*
+ * TConvert,0x34,0b1011000000000,0x1600
+ * TConvert[15:8],,,,,,
+ */
+#define MAX77759_FG_TCONVERT 0x34
+
+/*
+ * FullCapRep,0x35,0b101110111000,0xbb8
+ * FullCapRep[15:8],,,,,,
+ */
+#define MAX77759_FG_FULLCAPREP 0x35
+
+/*
+ * IAvgEmpty,0x36,0b1111010001001000,0xf448
+ * Iavg_empty[15:8],,,,,,
+ */
+#define MAX77759_FG_IAVGEMPTY 0x36
+
+/*
+ * FCTC,0x37,0b10111100000,0x5e0
+ * FCTC[15:8],,,,,,
+ */
+#define MAX77759_FG_FCTC 0x37
+
+/*
+ * RComp0,0x38,0b01110000,0x70
+ * SPR_15_8[7:0],,,,,,
+ */
+#define MAX77759_FG_RCOMP0 0x38
+
+#define MAX77759_FG_RCOMP0_SPR_15_8_SHIFT 8
+#define MAX77759_FG_RCOMP0_SPR_15_8_MASK (0xff << 8)
+#define MAX77759_FG_RCOMP0_SPR_15_8_CLEAR (~(0xff << 8))
+#define MAX77759_FG_RCOMP0_RCOMP0_SHIFT 0
+#define MAX77759_FG_RCOMP0_RCOMP0_MASK (0xff << 0)
+#define MAX77759_FG_RCOMP0_RCOMP0_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_rcomp0_spr_15_8,15,8)
+MAX77759_BFF(fg_rcomp0_rcomp0,7,0)
+static inline const char *
+max77759_fg_rcomp0_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_15_8=%x",
+ FIELD2VALUE(MAX77759_FG_RCOMP0_SPR_15_8, val));
+ i += SCNPRINTF(&buff[i], len - i, " RCOMP0=%x",
+ FIELD2VALUE(MAX77759_FG_RCOMP0_RCOMP0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TempCo,0x39,0b10011000111101,0x263d
+ * TempCoHot[7:0],,,,,,
+ */
+#define MAX77759_FG_TEMPCO 0x39
+
+#define MAX77759_FG_TEMPCO_TEMPCOHOT_SHIFT 8
+#define MAX77759_FG_TEMPCO_TEMPCOHOT_MASK (0xff << 8)
+#define MAX77759_FG_TEMPCO_TEMPCOHOT_CLEAR (~(0xff << 8))
+#define MAX77759_FG_TEMPCO_TEMPCOCOLD_SHIFT 0
+#define MAX77759_FG_TEMPCO_TEMPCOCOLD_MASK (0xff << 0)
+#define MAX77759_FG_TEMPCO_TEMPCOCOLD_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_tempco_tempcohot,15,8)
+MAX77759_BFF(fg_tempco_tempcocold,7,0)
+static inline const char *
+max77759_fg_tempco_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " TEMPCOHOT=%x",
+ FIELD2VALUE(MAX77759_FG_TEMPCO_TEMPCOHOT, val));
+ i += SCNPRINTF(&buff[i], len - i, " TEMPCOCOLD=%x",
+ FIELD2VALUE(MAX77759_FG_TEMPCO_TEMPCOCOLD, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VEmpty,0x3A,0b1010010101100001,0xa561
+ * V_Empty[8:1],,,,,,
+ */
+#define MAX77759_FG_VEMPTY 0x3A
+
+#define MAX77759_FG_VEMPTY_V_EMPTY_SHIFT 8
+#define MAX77759_FG_VEMPTY_V_EMPTY_MASK (0xff << 8)
+#define MAX77759_FG_VEMPTY_V_EMPTY_CLEAR (~(0xff << 8))
+#define MAX77759_FG_VEMPTY_V_RECOVER_SHIFT 1
+#define MAX77759_FG_VEMPTY_V_RECOVER_MASK (0x7f << 1)
+#define MAX77759_FG_VEMPTY_V_RECOVER_CLEAR (~(0x7f << 1))
+
+MAX77759_BFF(fg_vempty_v_empty,15,8)
+MAX77759_BFF(fg_vempty_v_recover,7,1)
+static inline const char *
+max77759_fg_vempty_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " V_EMPTY=%x",
+ FIELD2VALUE(MAX77759_FG_VEMPTY_V_EMPTY, val));
+ i += SCNPRINTF(&buff[i], len - i, " V_RECOVER=%x",
+ FIELD2VALUE(MAX77759_FG_VEMPTY_V_RECOVER, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * AvgCurrent0,0x3B,0b111111111111111,0x7fff
+ * AvgCurrent0[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGCURRENT0 0x3B
+
+/*
+ * TaskPeriod,0x3C,0b1011010000000,0x1680
+ * TaskPeriod[15:8],,,,,,
+ */
+#define MAX77759_FG_TASKPERIOD 0x3C
+
+/*
+ * FStat,0x3D,0b00000001,0x01
+ * xBr,RDF,tmode,DeBn,xBi,Relck,RelDt
+ */
+#define MAX77759_FG_FSTAT 0x3D
+#define MAX77759_FG_FSTAT_XBR (0x1 << 15)
+#define MAX77759_FG_FSTAT_RDF (0x1 << 14)
+#define MAX77759_FG_FSTAT_TMODE (0x1 << 13)
+#define MAX77759_FG_FSTAT_DEBN (0x1 << 12)
+#define MAX77759_FG_FSTAT_XBI (0x1 << 11)
+#define MAX77759_FG_FSTAT_RELCK (0x1 << 10)
+#define MAX77759_FG_FSTAT_RELDT (0x1 << 9)
+#define MAX77759_FG_FSTAT_EDET (0x1 << 8)
+#define MAX77759_FG_FSTAT_FQ (0x1 << 7)
+#define MAX77759_FG_FSTAT_RELDT2 (0x1 << 6)
+#define MAX77759_FG_FSTAT_TIMER_START (0x1 << 5)
+#define MAX77759_FG_FSTAT_XBST (0x1 << 4)
+#define MAX77759_FG_FSTAT_ACCEN (0x1 << 3)
+#define MAX77759_FG_FSTAT_WK (0x1 << 2)
+#define MAX77759_FG_FSTAT_LDMDL (0x1 << 1)
+#define MAX77759_FG_FSTAT_DNR (0x1 << 0)
+
+#define MAX77759_FG_FSTAT_XBR_SHIFT 15
+#define MAX77759_FG_FSTAT_XBR_MASK (0x1 << 15)
+#define MAX77759_FG_FSTAT_XBR_CLEAR (~(0x1 << 15))
+#define MAX77759_FG_FSTAT_RDF_SHIFT 14
+#define MAX77759_FG_FSTAT_RDF_MASK (0x1 << 14)
+#define MAX77759_FG_FSTAT_RDF_CLEAR (~(0x1 << 14))
+#define MAX77759_FG_FSTAT_TMODE_SHIFT 13
+#define MAX77759_FG_FSTAT_TMODE_MASK (0x1 << 13)
+#define MAX77759_FG_FSTAT_TMODE_CLEAR (~(0x1 << 13))
+#define MAX77759_FG_FSTAT_DEBN_SHIFT 12
+#define MAX77759_FG_FSTAT_DEBN_MASK (0x1 << 12)
+#define MAX77759_FG_FSTAT_DEBN_CLEAR (~(0x1 << 12))
+#define MAX77759_FG_FSTAT_XBI_SHIFT 11
+#define MAX77759_FG_FSTAT_XBI_MASK (0x1 << 11)
+#define MAX77759_FG_FSTAT_XBI_CLEAR (~(0x1 << 11))
+#define MAX77759_FG_FSTAT_RELCK_SHIFT 10
+#define MAX77759_FG_FSTAT_RELCK_MASK (0x1 << 10)
+#define MAX77759_FG_FSTAT_RELCK_CLEAR (~(0x1 << 10))
+#define MAX77759_FG_FSTAT_RELDT_SHIFT 9
+#define MAX77759_FG_FSTAT_RELDT_MASK (0x1 << 9)
+#define MAX77759_FG_FSTAT_RELDT_CLEAR (~(0x1 << 9))
+#define MAX77759_FG_FSTAT_EDET_SHIFT 8
+#define MAX77759_FG_FSTAT_EDET_MASK (0x1 << 8)
+#define MAX77759_FG_FSTAT_EDET_CLEAR (~(0x1 << 8))
+#define MAX77759_FG_FSTAT_FQ_SHIFT 7
+#define MAX77759_FG_FSTAT_FQ_MASK (0x1 << 7)
+#define MAX77759_FG_FSTAT_FQ_CLEAR (~(0x1 << 7))
+#define MAX77759_FG_FSTAT_RELDT2_SHIFT 6
+#define MAX77759_FG_FSTAT_RELDT2_MASK (0x1 << 6)
+#define MAX77759_FG_FSTAT_RELDT2_CLEAR (~(0x1 << 6))
+#define MAX77759_FG_FSTAT_TIMER_START_SHIFT 5
+#define MAX77759_FG_FSTAT_TIMER_START_MASK (0x1 << 5)
+#define MAX77759_FG_FSTAT_TIMER_START_CLEAR (~(0x1 << 5))
+#define MAX77759_FG_FSTAT_XBST_SHIFT 4
+#define MAX77759_FG_FSTAT_XBST_MASK (0x1 << 4)
+#define MAX77759_FG_FSTAT_XBST_CLEAR (~(0x1 << 4))
+#define MAX77759_FG_FSTAT_ACCEN_SHIFT 3
+#define MAX77759_FG_FSTAT_ACCEN_MASK (0x1 << 3)
+#define MAX77759_FG_FSTAT_ACCEN_CLEAR (~(0x1 << 3))
+#define MAX77759_FG_FSTAT_WK_SHIFT 2
+#define MAX77759_FG_FSTAT_WK_MASK (0x1 << 2)
+#define MAX77759_FG_FSTAT_WK_CLEAR (~(0x1 << 2))
+#define MAX77759_FG_FSTAT_LDMDL_SHIFT 1
+#define MAX77759_FG_FSTAT_LDMDL_MASK (0x1 << 1)
+#define MAX77759_FG_FSTAT_LDMDL_CLEAR (~(0x1 << 1))
+#define MAX77759_FG_FSTAT_DNR_SHIFT 0
+#define MAX77759_FG_FSTAT_DNR_MASK (0x1 << 0)
+#define MAX77759_FG_FSTAT_DNR_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(fg_fstat_xbr,15,15)
+MAX77759_BFF(fg_fstat_rdf,14,14)
+MAX77759_BFF(fg_fstat_tmode,13,13)
+MAX77759_BFF(fg_fstat_debn,12,12)
+MAX77759_BFF(fg_fstat_xbi,11,11)
+MAX77759_BFF(fg_fstat_relck,10,10)
+MAX77759_BFF(fg_fstat_reldt,9,9)
+MAX77759_BFF(fg_fstat_edet,8,8)
+MAX77759_BFF(fg_fstat_fq,7,7)
+MAX77759_BFF(fg_fstat_reldt2,6,6)
+MAX77759_BFF(fg_fstat_timer_start,5,5)
+MAX77759_BFF(fg_fstat_xbst,4,4)
+MAX77759_BFF(fg_fstat_accen,3,3)
+MAX77759_BFF(fg_fstat_wk,2,2)
+MAX77759_BFF(fg_fstat_ldmdl,1,1)
+MAX77759_BFF(fg_fstat_dnr,0,0)
+static inline const char *
+max77759_fg_fstat_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " XBR=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_XBR, val));
+ i += SCNPRINTF(&buff[i], len - i, " RDF=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_RDF, val));
+ i += SCNPRINTF(&buff[i], len - i, " TMODE=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_TMODE, val));
+ i += SCNPRINTF(&buff[i], len - i, " DEBN=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_DEBN, val));
+ i += SCNPRINTF(&buff[i], len - i, " XBI=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_XBI, val));
+ i += SCNPRINTF(&buff[i], len - i, " RELCK=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_RELCK, val));
+ i += SCNPRINTF(&buff[i], len - i, " RELDT=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_RELDT, val));
+ i += SCNPRINTF(&buff[i], len - i, " EDET=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_EDET, val));
+ i += SCNPRINTF(&buff[i], len - i, " FQ=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_FQ, val));
+ i += SCNPRINTF(&buff[i], len - i, " RELDT2=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_RELDT2, val));
+ i += SCNPRINTF(&buff[i], len - i, " TIMER_START=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_TIMER_START, val));
+ i += SCNPRINTF(&buff[i], len - i, " XBST=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_XBST, val));
+ i += SCNPRINTF(&buff[i], len - i, " ACCEN=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_ACCEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " WK=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_WK, val));
+ i += SCNPRINTF(&buff[i], len - i, " LDMDL=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_LDMDL, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNR=%x",
+ FIELD2VALUE(MAX77759_FG_FSTAT_DNR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * Timer,0x3E,0b00000000,0x00
+ * TIMER[15:8],,,,,,
+ */
+#define MAX77759_FG_TIMER 0x3E
+
+/*
+ * ShdnTimer,0x3F,0b1110000000000000,0xe000
+ * SHDN_THR[2:0],,,SHDNCTR[12:8],,,
+ */
+#define MAX77759_FG_SHDNTIMER 0x3F
+
+#define MAX77759_FG_SHDNTIMER_SHDN_THR_SHIFT 13
+#define MAX77759_FG_SHDNTIMER_SHDN_THR_MASK (0x7 << 13)
+#define MAX77759_FG_SHDNTIMER_SHDN_THR_CLEAR (~(0x7 << 13))
+#define MAX77759_FG_SHDNTIMER_SHDNCTR_SHIFT 0
+#define MAX77759_FG_SHDNTIMER_SHDNCTR_MASK (0x1fff << 0)
+#define MAX77759_FG_SHDNTIMER_SHDNCTR_CLEAR (~(0x1fff << 0))
+
+MAX77759_BFF(fg_shdntimer_shdn_thr,15,13)
+MAX77759_BFF(fg_shdntimer_shdnctr,12,0)
+static inline const char *
+max77759_fg_shdntimer_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SHDN_THR=%x",
+ FIELD2VALUE(MAX77759_FG_SHDNTIMER_SHDN_THR, val));
+ i += SCNPRINTF(&buff[i], len - i, " SHDNCTR=%x",
+ FIELD2VALUE(MAX77759_FG_SHDNTIMER_SHDNCTR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * THMHOT,0x40,0b11111111100,0x7fc
+ * VR[4:0],,,,,Vhys[2:0],
+ */
+#define MAX77759_FG_THMHOT 0x40
+
+#define MAX77759_FG_THMHOT_VR_SHIFT 11
+#define MAX77759_FG_THMHOT_VR_MASK (0x1f << 11)
+#define MAX77759_FG_THMHOT_VR_CLEAR (~(0x1f << 11))
+#define MAX77759_FG_THMHOT_VHYS_SHIFT 8
+#define MAX77759_FG_THMHOT_VHYS_MASK (0x7 << 8)
+#define MAX77759_FG_THMHOT_VHYS_CLEAR (~(0x7 << 8))
+#define MAX77759_FG_THMHOT_TR_SHIFT 3
+#define MAX77759_FG_THMHOT_TR_MASK (0x1f << 3)
+#define MAX77759_FG_THMHOT_TR_CLEAR (~(0x1f << 3))
+#define MAX77759_FG_THMHOT_THYS_SHIFT 0
+#define MAX77759_FG_THMHOT_THYS_MASK (0x7 << 0)
+#define MAX77759_FG_THMHOT_THYS_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(fg_thmhot_vr,15,11)
+MAX77759_BFF(fg_thmhot_vhys,10,8)
+MAX77759_BFF(fg_thmhot_tr,7,3)
+MAX77759_BFF(fg_thmhot_thys,2,0)
+static inline const char *
+max77759_fg_thmhot_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VR=%x",
+ FIELD2VALUE(MAX77759_FG_THMHOT_VR, val));
+ i += SCNPRINTF(&buff[i], len - i, " VHYS=%x",
+ FIELD2VALUE(MAX77759_FG_THMHOT_VHYS, val));
+ i += SCNPRINTF(&buff[i], len - i, " TR=%x",
+ FIELD2VALUE(MAX77759_FG_THMHOT_TR, val));
+ i += SCNPRINTF(&buff[i], len - i, " THYS=%x",
+ FIELD2VALUE(MAX77759_FG_THMHOT_THYS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CTESample,0x41,0b00000000,0x00
+ * CTESample[15:8],,,,,,
+ */
+#define MAX77759_FG_CTESAMPLE 0x41
+
+/*
+ * QRTable30,0x42,0b100010000101,0x885
+ * QRTable30[15:8],,,,,,
+ */
+#define MAX77759_FG_QRTABLE30 0x42
+
+/*
+ * ISys,0x43,0b00000000,0x00
+ * ISYS[15:8],,,,,,
+ */
+#define MAX77759_FG_ISYS 0x43
+
+/*
+ * AvgVCell0,0x44,0b1000000000000000,0x8000
+ * AvgVCELL0[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGVCELL0 0x44
+
+/*
+ * dQAcc,0x45,0b00010111,0x17
+ * dQacc[15:8],,,,,,
+ */
+#define MAX77759_FG_DQACC 0x45
+
+/*
+ * dPAcc,0x46,0b110010000,0x190
+ * dPacc[15:8],,,,,,
+ */
+#define MAX77759_FG_DPACC 0x46
+
+/*
+ * RlxSOC,0x47,0b00000000,0x00
+ * RlxSOC[15:8],,,,,,
+ */
+#define MAX77759_FG_RLXSOC 0x47
+
+/*
+ * VFSOC0,0x48,0b11001000000000,0x3200
+ * VFSOC0[15:8],,,,,,
+ */
+#define MAX77759_FG_VFSOC0 0x48
+
+/*
+ * ConvgCfg,0x49,0b10001001000001,0x2241
+ * RepLow[3:0],,,,VoltLowOff[4:1],,
+ */
+#define MAX77759_FG_CONVGCFG 0x49
+
+#define MAX77759_FG_CONVGCFG_REPLOW_SHIFT 12
+#define MAX77759_FG_CONVGCFG_REPLOW_MASK (0xf << 12)
+#define MAX77759_FG_CONVGCFG_REPLOW_CLEAR (~(0xf << 12))
+#define MAX77759_FG_CONVGCFG_VOLTLOWOFF_SHIFT 8
+#define MAX77759_FG_CONVGCFG_VOLTLOWOFF_MASK (0xf << 8)
+#define MAX77759_FG_CONVGCFG_VOLTLOWOFF_CLEAR (~(0xf << 8))
+#define MAX77759_FG_CONVGCFG_MINSLOPEX_SHIFT 4
+#define MAX77759_FG_CONVGCFG_MINSLOPEX_MASK (0xf << 4)
+#define MAX77759_FG_CONVGCFG_MINSLOPEX_CLEAR (~(0xf << 4))
+#define MAX77759_FG_CONVGCFG_REPL_PER_STAGE_SHIFT 1
+#define MAX77759_FG_CONVGCFG_REPL_PER_STAGE_MASK (0x7 << 1)
+#define MAX77759_FG_CONVGCFG_REPL_PER_STAGE_CLEAR (~(0x7 << 1))
+
+MAX77759_BFF(fg_convgcfg_replow,15,12)
+MAX77759_BFF(fg_convgcfg_voltlowoff,11,8)
+MAX77759_BFF(fg_convgcfg_minslopex,7,4)
+MAX77759_BFF(fg_convgcfg_repl_per_stage,3,1)
+static inline const char *
+max77759_fg_convgcfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " REPLOW=%x",
+ FIELD2VALUE(MAX77759_FG_CONVGCFG_REPLOW, val));
+ i += SCNPRINTF(&buff[i], len - i, " VOLTLOWOFF=%x",
+ FIELD2VALUE(MAX77759_FG_CONVGCFG_VOLTLOWOFF, val));
+ i += SCNPRINTF(&buff[i], len - i, " MINSLOPEX=%x",
+ FIELD2VALUE(MAX77759_FG_CONVGCFG_MINSLOPEX, val));
+ i += SCNPRINTF(&buff[i], len - i, " REPL_PER_STAGE=%x",
+ FIELD2VALUE(MAX77759_FG_CONVGCFG_REPL_PER_STAGE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VFRemCap,0x4A,0b10111011100,0x5dc
+ * VFRemCap[15:8],,,,,,
+ */
+#define MAX77759_FG_VFREMCAP 0x4A
+
+/*
+ * AvgISys,0x4B,0b00000000,0x00
+ * AVGISYS[15:8],,,,,,
+ */
+#define MAX77759_FG_AVGISYS 0x4B
+
+/*
+ * QH0,0x4C,0b00000000,0x00
+ * QH0[15:8],,,,,,
+ */
+#define MAX77759_FG_QH0 0x4C
+
+/*
+ * QH,0x4D,0b00000000,0x00
+ * QH[15:8],,,,,,
+ */
+#define MAX77759_FG_QH 0x4D
+
+/*
+ * QL,0x4E,0b00000000,0x00
+ * QL[15:8],,,,,,
+ */
+#define MAX77759_FG_QL 0x4E
+
+/*
+ * MixAtFull,0x4F,0b101110111000,0xbb8
+ * MixAtFull[15:8],,,,,,
+ */
+#define MAX77759_FG_MIXATFULL 0x4F
+
+/*
+ * Status2,0xB0,0b00000000,0x00
+ * SPR_15_6[9:2],,,,,,
+ */
+#define MAX77759_FG_STATUS2 0xB0
+#define MAX77759_FG_STATUS2_FULLDET (0x1 << 5)
+#define MAX77759_FG_STATUS2_HIB (0x1 << 1)
+#define MAX77759_FG_STATUS2_SPR_0 (0x1 << 0)
+
+#define MAX77759_FG_STATUS2_SPR_15_6_SHIFT 6
+#define MAX77759_FG_STATUS2_SPR_15_6_MASK (0x3ff << 6)
+#define MAX77759_FG_STATUS2_SPR_15_6_CLEAR (~(0x3ff << 6))
+#define MAX77759_FG_STATUS2_FULLDET_SHIFT 5
+#define MAX77759_FG_STATUS2_FULLDET_MASK (0x1 << 5)
+#define MAX77759_FG_STATUS2_FULLDET_CLEAR (~(0x1 << 5))
+#define MAX77759_FG_STATUS2_SPR_4_2_SHIFT 2
+#define MAX77759_FG_STATUS2_SPR_4_2_MASK (0x7 << 2)
+#define MAX77759_FG_STATUS2_SPR_4_2_CLEAR (~(0x7 << 2))
+#define MAX77759_FG_STATUS2_HIB_SHIFT 1
+#define MAX77759_FG_STATUS2_HIB_MASK (0x1 << 1)
+#define MAX77759_FG_STATUS2_HIB_CLEAR (~(0x1 << 1))
+#define MAX77759_FG_STATUS2_SPR_0_SHIFT 0
+#define MAX77759_FG_STATUS2_SPR_0_MASK (0x1 << 0)
+#define MAX77759_FG_STATUS2_SPR_0_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(fg_status2_spr_15_6,15,6)
+MAX77759_BFF(fg_status2_fulldet,5,5)
+MAX77759_BFF(fg_status2_spr_4_2,4,2)
+MAX77759_BFF(fg_status2_hib,1,1)
+MAX77759_BFF(fg_status2_spr_0,0,0)
+static inline const char *
+max77759_fg_status2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_15_6=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS2_SPR_15_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " FULLDET=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS2_FULLDET, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_4_2=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS2_SPR_4_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " HIB=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS2_HIB, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_0=%x",
+ FIELD2VALUE(MAX77759_FG_STATUS2_SPR_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VSys,0xB1,0b00000000,0x00
+ * VSys[15:8],,,,,,
+ */
+#define MAX77759_FG_VSYS 0xB1
+
+/*
+ * TAlrtTh2,0xB2,0b111111110000000,0x7f80
+ * TempWarm[7:0],,,,,,
+ */
+#define MAX77759_FG_TALRTTH2 0xB2
+
+#define MAX77759_FG_TALRTTH2_TEMPWARM_SHIFT 8
+#define MAX77759_FG_TALRTTH2_TEMPWARM_MASK (0xff << 8)
+#define MAX77759_FG_TALRTTH2_TEMPWARM_CLEAR (~(0xff << 8))
+#define MAX77759_FG_TALRTTH2_TEMPCOOL_SHIFT 0
+#define MAX77759_FG_TALRTTH2_TEMPCOOL_MASK (0xff << 0)
+#define MAX77759_FG_TALRTTH2_TEMPCOOL_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_talrtth2_tempwarm,15,8)
+MAX77759_BFF(fg_talrtth2_tempcool,7,0)
+static inline const char *
+max77759_fg_talrtth2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " TEMPWARM=%x",
+ FIELD2VALUE(MAX77759_FG_TALRTTH2_TEMPWARM, val));
+ i += SCNPRINTF(&buff[i], len - i, " TEMPCOOL=%x",
+ FIELD2VALUE(MAX77759_FG_TALRTTH2_TEMPCOOL, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VByp,0xB3,0b00000000,0x00
+ * VByp[15:8],,,,,,
+ */
+#define MAX77759_FG_VBYP 0xB3
+
+/*
+ * IAlrtTh,0xB4,0b111111110000000,0x7f80
+ * ISYSOCP_TH[7:0],,,,,,
+ */
+#define MAX77759_FG_IALRTTH 0xB4
+
+#define MAX77759_FG_IALRTTH_ISYSOCP_TH_SHIFT 8
+#define MAX77759_FG_IALRTTH_ISYSOCP_TH_MASK (0xff << 8)
+#define MAX77759_FG_IALRTTH_ISYSOCP_TH_CLEAR (~(0xff << 8))
+#define MAX77759_FG_IALRTTH_IBATTMIN_TH_SHIFT 0
+#define MAX77759_FG_IALRTTH_IBATTMIN_TH_MASK (0xff << 0)
+#define MAX77759_FG_IALRTTH_IBATTMIN_TH_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_ialrtth_isysocp_th,15,8)
+MAX77759_BFF(fg_ialrtth_ibattmin_th,7,0)
+static inline const char *
+max77759_fg_ialrtth_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " ISYSOCP_TH=%x",
+ FIELD2VALUE(MAX77759_FG_IALRTTH_ISYSOCP_TH, val));
+ i += SCNPRINTF(&buff[i], len - i, " IBATTMIN_TH=%x",
+ FIELD2VALUE(MAX77759_FG_IALRTTH_IBATTMIN_TH, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TTF_CFG,0xB5,0b00000101,0x05
+ * SPR_15_3[12:5],,,,,,
+ */
+#define MAX77759_FG_TTF_CFG 0xB5
+
+#define MAX77759_FG_TTF_CFG_SPR_15_3_SHIFT 3
+#define MAX77759_FG_TTF_CFG_SPR_15_3_MASK (0x1fff << 3)
+#define MAX77759_FG_TTF_CFG_SPR_15_3_CLEAR (~(0x1fff << 3))
+#define MAX77759_FG_TTF_CFG_TTF_CFG_SHIFT 0
+#define MAX77759_FG_TTF_CFG_TTF_CFG_MASK (0x7 << 0)
+#define MAX77759_FG_TTF_CFG_TTF_CFG_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(fg_ttf_cfg_spr_15_3,15,3)
+MAX77759_BFF(fg_ttf_cfg_ttf_cfg,2,0)
+static inline const char *
+max77759_fg_ttf_cfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_15_3=%x",
+ FIELD2VALUE(MAX77759_FG_TTF_CFG_SPR_15_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " TTF_CFG=%x",
+ FIELD2VALUE(MAX77759_FG_TTF_CFG_TTF_CFG, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CV_MixCap,0xB6,0b100011001010,0x8ca
+ * CV_MixCap[15:8],,,,,,
+ */
+#define MAX77759_FG_CV_MIXCAP 0xB6
+
+/*
+ * CV_HalfTime,0xB7,0b101000000000,0xa00
+ * CV_Halftime[15:8],,,,,,
+ */
+#define MAX77759_FG_CV_HALFTIME 0xB7
+
+/*
+ * CGTempCo,0xB8,0b00000000,0x00
+ * CGTempCo[15:8],,,,,,
+ */
+#define MAX77759_FG_CGTEMPCO 0xB8
+
+/*
+ * Curve,0xB9,0b01101011,0x6b
+ * ECURVE[7:0],,,,,,
+ */
+#define MAX77759_FG_CURVE 0xB9
+
+#define MAX77759_FG_CURVE_ECURVE_SHIFT 8
+#define MAX77759_FG_CURVE_ECURVE_MASK (0xff << 8)
+#define MAX77759_FG_CURVE_ECURVE_CLEAR (~(0xff << 8))
+#define MAX77759_FG_CURVE_TCURVE_SHIFT 0
+#define MAX77759_FG_CURVE_TCURVE_MASK (0xff << 0)
+#define MAX77759_FG_CURVE_TCURVE_CLEAR (~(0xff << 0))
+
+MAX77759_BFF(fg_curve_ecurve,15,8)
+MAX77759_BFF(fg_curve_tcurve,7,0)
+static inline const char *
+max77759_fg_curve_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " ECURVE=%x",
+ FIELD2VALUE(MAX77759_FG_CURVE_ECURVE, val));
+ i += SCNPRINTF(&buff[i], len - i, " TCURVE=%x",
+ FIELD2VALUE(MAX77759_FG_CURVE_TCURVE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * HibCfg,0xBA,0b100100001100,0x90c
+ * EnHib,HibEnterTime[2:0],,,HibThreshold[3:0],,
+ */
+#define MAX77759_FG_HIBCFG 0xBA
+#define MAX77759_FG_HIBCFG_ENHIB (0x1 << 15)
+
+#define MAX77759_FG_HIBCFG_ENHIB_SHIFT 15
+#define MAX77759_FG_HIBCFG_ENHIB_MASK (0x1 << 15)
+#define MAX77759_FG_HIBCFG_ENHIB_CLEAR (~(0x1 << 15))
+#define MAX77759_FG_HIBCFG_HIBENTERTIME_SHIFT 12
+#define MAX77759_FG_HIBCFG_HIBENTERTIME_MASK (0x7 << 12)
+#define MAX77759_FG_HIBCFG_HIBENTERTIME_CLEAR (~(0x7 << 12))
+#define MAX77759_FG_HIBCFG_HIBTHRESHOLD_SHIFT 8
+#define MAX77759_FG_HIBCFG_HIBTHRESHOLD_MASK (0xf << 8)
+#define MAX77759_FG_HIBCFG_HIBTHRESHOLD_CLEAR (~(0xf << 8))
+#define MAX77759_FG_HIBCFG_SPR_7_5_SHIFT 5
+#define MAX77759_FG_HIBCFG_SPR_7_5_MASK (0x7 << 5)
+#define MAX77759_FG_HIBCFG_SPR_7_5_CLEAR (~(0x7 << 5))
+#define MAX77759_FG_HIBCFG_HIBEXITTIME_SHIFT 3
+#define MAX77759_FG_HIBCFG_HIBEXITTIME_MASK (0x3 << 3)
+#define MAX77759_FG_HIBCFG_HIBEXITTIME_CLEAR (~(0x3 << 3))
+#define MAX77759_FG_HIBCFG_HIBSCALAR_SHIFT 0
+#define MAX77759_FG_HIBCFG_HIBSCALAR_MASK (0x7 << 0)
+#define MAX77759_FG_HIBCFG_HIBSCALAR_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(fg_hibcfg_enhib,15,15)
+MAX77759_BFF(fg_hibcfg_hibentertime,14,12)
+MAX77759_BFF(fg_hibcfg_hibthreshold,11,8)
+MAX77759_BFF(fg_hibcfg_spr_7_5,7,5)
+MAX77759_BFF(fg_hibcfg_hibexittime,4,3)
+MAX77759_BFF(fg_hibcfg_hibscalar,2,0)
+static inline const char *
+max77759_fg_hibcfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " ENHIB=%x",
+ FIELD2VALUE(MAX77759_FG_HIBCFG_ENHIB, val));
+ i += SCNPRINTF(&buff[i], len - i, " HIBENTERTIME=%x",
+ FIELD2VALUE(MAX77759_FG_HIBCFG_HIBENTERTIME, val));
+ i += SCNPRINTF(&buff[i], len - i, " HIBTHRESHOLD=%x",
+ FIELD2VALUE(MAX77759_FG_HIBCFG_HIBTHRESHOLD, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_5=%x",
+ FIELD2VALUE(MAX77759_FG_HIBCFG_SPR_7_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " HIBEXITTIME=%x",
+ FIELD2VALUE(MAX77759_FG_HIBCFG_HIBEXITTIME, val));
+ i += SCNPRINTF(&buff[i], len - i, " HIBSCALAR=%x",
+ FIELD2VALUE(MAX77759_FG_HIBCFG_HIBSCALAR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * Config2,0xBB,0b01010000,0x50
+ * SPR_15_11[4:0],,,,,FCThmHot,ThmHotEn
+ */
+#define MAX77759_FG_CONFIG2 0xBB
+#define MAX77759_FG_CONFIG2_FCTHMHOT (0x1 << 10)
+#define MAX77759_FG_CONFIG2_THMHOTEN (0x1 << 9)
+#define MAX77759_FG_CONFIG2_THMHOTALRTEN (0x1 << 8)
+#define MAX77759_FG_CONFIG2_DSOCEN (0x1 << 7)
+#define MAX77759_FG_CONFIG2_TALRTEN (0x1 << 6)
+#define MAX77759_FG_CONFIG2_LDMDL (0x1 << 5)
+#define MAX77759_FG_CONFIG2_OCVQEN (0x1 << 4)
+
+#define MAX77759_FG_CONFIG2_SPR_15_11_SHIFT 11
+#define MAX77759_FG_CONFIG2_SPR_15_11_MASK (0x1f << 11)
+#define MAX77759_FG_CONFIG2_SPR_15_11_CLEAR (~(0x1f << 11))
+#define MAX77759_FG_CONFIG2_FCTHMHOT_SHIFT 10
+#define MAX77759_FG_CONFIG2_FCTHMHOT_MASK (0x1 << 10)
+#define MAX77759_FG_CONFIG2_FCTHMHOT_CLEAR (~(0x1 << 10))
+#define MAX77759_FG_CONFIG2_THMHOTEN_SHIFT 9
+#define MAX77759_FG_CONFIG2_THMHOTEN_MASK (0x1 << 9)
+#define MAX77759_FG_CONFIG2_THMHOTEN_CLEAR (~(0x1 << 9))
+#define MAX77759_FG_CONFIG2_THMHOTALRTEN_SHIFT 8
+#define MAX77759_FG_CONFIG2_THMHOTALRTEN_MASK (0x1 << 8)
+#define MAX77759_FG_CONFIG2_THMHOTALRTEN_CLEAR (~(0x1 << 8))
+#define MAX77759_FG_CONFIG2_DSOCEN_SHIFT 7
+#define MAX77759_FG_CONFIG2_DSOCEN_MASK (0x1 << 7)
+#define MAX77759_FG_CONFIG2_DSOCEN_CLEAR (~(0x1 << 7))
+#define MAX77759_FG_CONFIG2_TALRTEN_SHIFT 6
+#define MAX77759_FG_CONFIG2_TALRTEN_MASK (0x1 << 6)
+#define MAX77759_FG_CONFIG2_TALRTEN_CLEAR (~(0x1 << 6))
+#define MAX77759_FG_CONFIG2_LDMDL_SHIFT 5
+#define MAX77759_FG_CONFIG2_LDMDL_MASK (0x1 << 5)
+#define MAX77759_FG_CONFIG2_LDMDL_CLEAR (~(0x1 << 5))
+#define MAX77759_FG_CONFIG2_OCVQEN_SHIFT 4
+#define MAX77759_FG_CONFIG2_OCVQEN_MASK (0x1 << 4)
+#define MAX77759_FG_CONFIG2_OCVQEN_CLEAR (~(0x1 << 4))
+#define MAX77759_FG_CONFIG2_ISYSNCURR_SHIFT 0
+#define MAX77759_FG_CONFIG2_ISYSNCURR_MASK (0xf << 0)
+#define MAX77759_FG_CONFIG2_ISYSNCURR_CLEAR (~(0xf << 0))
+
+MAX77759_BFF(fg_config2_spr_15_11,15,11)
+MAX77759_BFF(fg_config2_fcthmhot,10,10)
+MAX77759_BFF(fg_config2_thmhoten,9,9)
+MAX77759_BFF(fg_config2_thmhotalrten,8,8)
+MAX77759_BFF(fg_config2_dsocen,7,7)
+MAX77759_BFF(fg_config2_talrten,6,6)
+MAX77759_BFF(fg_config2_ldmdl,5,5)
+MAX77759_BFF(fg_config2_ocvqen,4,4)
+MAX77759_BFF(fg_config2_isysncurr,3,0)
+static inline const char *
+max77759_fg_config2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_15_11=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_SPR_15_11, val));
+ i += SCNPRINTF(&buff[i], len - i, " FCTHMHOT=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_FCTHMHOT, val));
+ i += SCNPRINTF(&buff[i], len - i, " THMHOTEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_THMHOTEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " THMHOTALRTEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_THMHOTALRTEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " DSOCEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_DSOCEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " TALRTEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_TALRTEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " LDMDL=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_LDMDL, val));
+ i += SCNPRINTF(&buff[i], len - i, " OCVQEN=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_OCVQEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " ISYSNCURR=%x",
+ FIELD2VALUE(MAX77759_FG_CONFIG2_ISYSNCURR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VRipple,0xBC,0b00000000,0x00
+ * Vripple[15:8],,,,,,
+ */
+#define MAX77759_FG_VRIPPLE 0xBC
+
+/*
+ * RippleCfg,0xBD,0b1000000100,0x204
+ * kDV[12:5],,,,,,
+ */
+#define MAX77759_FG_RIPPLECFG 0xBD
+
+#define MAX77759_FG_RIPPLECFG_KDV_SHIFT 3
+#define MAX77759_FG_RIPPLECFG_KDV_MASK (0x1fff << 3)
+#define MAX77759_FG_RIPPLECFG_KDV_CLEAR (~(0x1fff << 3))
+#define MAX77759_FG_RIPPLECFG_NR_SHIFT 0
+#define MAX77759_FG_RIPPLECFG_NR_MASK (0x7 << 0)
+#define MAX77759_FG_RIPPLECFG_NR_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(fg_ripplecfg_kdv,15,3)
+MAX77759_BFF(fg_ripplecfg_nr,2,0)
+static inline const char *
+max77759_fg_ripplecfg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " KDV=%x",
+ FIELD2VALUE(MAX77759_FG_RIPPLECFG_KDV, val));
+ i += SCNPRINTF(&buff[i], len - i, " NR=%x",
+ FIELD2VALUE(MAX77759_FG_RIPPLECFG_NR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TimerH,0xBE,0b00000000,0x00
+ * TIMERH[15:8],,,,,,
+ */
+#define MAX77759_FG_TIMERH 0xBE
+
+/*
+ * MaxError,0xBF,0b00000000,0x00
+ * MaxError[15:8],,,,,,
+ */
+#define MAX77759_FG_MAXERROR 0xBF
+
+/*
+ * IIn,0xD0,0b00000000,0x00
+ * IIn[15:8],,,,,,
+ */
+#define MAX77759_FG_IIN 0xD0
+
+/*
+ * AtQresidual,0xDC,0b00000000,0x00
+ * AtQresidual[15:8],,,,,,
+ */
+#define MAX77759_FG_ATQRESIDUAL 0xDC
+
+/*
+ * AtTTE,0xDD,0b00000000,0x00
+ * AtTTE[15:8],,,,,,
+ */
+#define MAX77759_FG_ATTTE 0xDD
+
+/*
+ * AtAvSOC,0xDE,0b00000000,0x00
+ * AtAvSOC[15:8],,,,,,
+ */
+#define MAX77759_FG_ATAVSOC 0xDE
+
+/*
+ * AtAvCap,0xDF,0b00000000,0x00
+ * AtAvCap[15:8],,,,,,
+ */
+#define MAX77759_FG_ATAVCAP 0xDF
+
+/* section: USB */
+
+/*
+ * VENDOR_ID_L,0x0,0b01101010,0x6a
+ * VENDOR_ID_L[7:0],,,,,,
+ */
+#define MAX77759_USB_VENDOR_ID_L 0x0
+
+/*
+ * VENDOR_ID_H,0x1,0b00001011,0x0b
+ * VENDOR_ID_H[7:0],,,,,,
+ */
+#define MAX77759_USB_VENDOR_ID_H 0x1
+
+/*
+ * PRODUCT_ID_L,0x2,0b01011001,0x59
+ * PRODUCT_ID_L[7:0],,,,,,
+ */
+#define MAX77759_USB_PRODUCT_ID_L 0x2
+
+/*
+ * PRODUCT_ID_H,0x3,0b01110111,0x77
+ * PRODUCT_ID_H[7:0],,,,,,
+ */
+#define MAX77759_USB_PRODUCT_ID_H 0x3
+
+/*
+ * DEVICE_ID_L,0x4,0b00000001,0x01
+ * DEVICE_ID_L[7:0],,,,,,
+ */
+#define MAX77759_USB_DEVICE_ID_L 0x4
+
+/*
+ * DEVICE_ID_H,0x5,0b00000000,0x00
+ * DEVICE_ID_H[7:0],,,,,,
+ */
+#define MAX77759_USB_DEVICE_ID_H 0x5
+
+/*
+ * USBTYPEC_REV_L,0x6,0b00010011,0x13
+ * TYPEC_REV_L[7:0],,,,,,
+ */
+#define MAX77759_USB_USBTYPEC_REV_L 0x6
+
+/*
+ * USBTYPEC_REV_H,0x7,0b00000000,0x00
+ * TYPEC_REV_H[7:0],,,,,,
+ */
+#define MAX77759_USB_USBTYPEC_REV_H 0x7
+
+/*
+ * USBPD_REV_VER_L,0x8,0b00010010,0x12
+ * PD_VER[7:0],,,,,,
+ */
+#define MAX77759_USB_USBPD_REV_VER_L 0x8
+
+/*
+ * USBPD_REV_VER_H,0x9,0b00110000,0x30
+ * PD_REV[7:0],,,,,,
+ */
+#define MAX77759_USB_USBPD_REV_VER_H 0x9
+
+/*
+ * PD_INTERFACE_REV_L,0xA,0b00010001,0x11
+ * PD_IF_REV_L[7:0],,,,,,
+ */
+#define MAX77759_USB_PD_INTERFACE_REV_L 0xA
+
+/*
+ * PD_INTERFACE_REV_H,0xB,0b00100000,0x20
+ * PD_IF_REV_H[7:0],,,,,,
+ */
+#define MAX77759_USB_PD_INTERFACE_REV_H 0xB
+
+/*
+ * ALERT_L,0x10,0b00000000,0x00
+ * VBUS_ALARM_HI,TX_SOP_MSG_SUCC,TX_SOP_MSG_DISCRD,TX_SOP_MSG_FAIL,RX_HARD_RST,RX_SOP_MSG_STAT,PWR_STAT
+ */
+#define MAX77759_USB_ALERT_L 0x10
+#define MAX77759_USB_ALERT_L_VBUS_ALARM_HI (0x1 << 7)
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_SUCC (0x1 << 6)
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_DISCRD (0x1 << 5)
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_FAIL (0x1 << 4)
+#define MAX77759_USB_ALERT_L_RX_HARD_RST (0x1 << 3)
+#define MAX77759_USB_ALERT_L_RX_SOP_MSG_STAT (0x1 << 2)
+#define MAX77759_USB_ALERT_L_PWR_STAT (0x1 << 1)
+#define MAX77759_USB_ALERT_L_CC_STAT (0x1 << 0)
+
+#define MAX77759_USB_ALERT_L_VBUS_ALARM_HI_SHIFT 7
+#define MAX77759_USB_ALERT_L_VBUS_ALARM_HI_MASK (0x1 << 7)
+#define MAX77759_USB_ALERT_L_VBUS_ALARM_HI_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_SUCC_SHIFT 6
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_SUCC_MASK (0x1 << 6)
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_SUCC_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_DISCRD_SHIFT 5
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_DISCRD_MASK (0x1 << 5)
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_DISCRD_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_FAIL_SHIFT 4
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_FAIL_MASK (0x1 << 4)
+#define MAX77759_USB_ALERT_L_TX_SOP_MSG_FAIL_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_ALERT_L_RX_HARD_RST_SHIFT 3
+#define MAX77759_USB_ALERT_L_RX_HARD_RST_MASK (0x1 << 3)
+#define MAX77759_USB_ALERT_L_RX_HARD_RST_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_ALERT_L_RX_SOP_MSG_STAT_SHIFT 2
+#define MAX77759_USB_ALERT_L_RX_SOP_MSG_STAT_MASK (0x1 << 2)
+#define MAX77759_USB_ALERT_L_RX_SOP_MSG_STAT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_ALERT_L_PWR_STAT_SHIFT 1
+#define MAX77759_USB_ALERT_L_PWR_STAT_MASK (0x1 << 1)
+#define MAX77759_USB_ALERT_L_PWR_STAT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_ALERT_L_CC_STAT_SHIFT 0
+#define MAX77759_USB_ALERT_L_CC_STAT_MASK (0x1 << 0)
+#define MAX77759_USB_ALERT_L_CC_STAT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_alert_l_vbus_alarm_hi,7,7)
+MAX77759_BFF(usb_alert_l_tx_sop_msg_succ,6,6)
+MAX77759_BFF(usb_alert_l_tx_sop_msg_discrd,5,5)
+MAX77759_BFF(usb_alert_l_tx_sop_msg_fail,4,4)
+MAX77759_BFF(usb_alert_l_rx_hard_rst,3,3)
+MAX77759_BFF(usb_alert_l_rx_sop_msg_stat,2,2)
+MAX77759_BFF(usb_alert_l_pwr_stat,1,1)
+MAX77759_BFF(usb_alert_l_cc_stat,0,0)
+static inline const char *
+max77759_usb_alert_l_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_ALARM_HI=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_VBUS_ALARM_HI, val));
+ i += SCNPRINTF(&buff[i], len - i, " TX_SOP_MSG_SUCC=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_TX_SOP_MSG_SUCC, val));
+ i += SCNPRINTF(&buff[i], len - i, " TX_SOP_MSG_DISCRD=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_TX_SOP_MSG_DISCRD, val));
+ i += SCNPRINTF(&buff[i], len - i, " TX_SOP_MSG_FAIL=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_TX_SOP_MSG_FAIL, val));
+ i += SCNPRINTF(&buff[i], len - i, " RX_HARD_RST=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_RX_HARD_RST, val));
+ i += SCNPRINTF(&buff[i], len - i, " RX_SOP_MSG_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_RX_SOP_MSG_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " PWR_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_PWR_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_L_CC_STAT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * ALERT_H,0x11,0b00000000,0x00
+ * VNDR_ALRT,ALERT_EXTND,EXTND_STAT,RSVD_4,VBUS_SNK_DISC_DET,RX_BUFF_OVRFL,FAULT_STAT
+ */
+#define MAX77759_USB_ALERT_H 0x11
+#define MAX77759_USB_ALERT_H_VNDR_ALRT (0x1 << 7)
+#define MAX77759_USB_ALERT_H_ALERT_EXTND (0x1 << 6)
+#define MAX77759_USB_ALERT_H_EXTND_STAT (0x1 << 5)
+#define MAX77759_USB_ALERT_H_RSVD_4 (0x1 << 4)
+#define MAX77759_USB_ALERT_H_VBUS_SNK_DISC_DET (0x1 << 3)
+#define MAX77759_USB_ALERT_H_RX_BUFF_OVRFL (0x1 << 2)
+#define MAX77759_USB_ALERT_H_FAULT_STAT (0x1 << 1)
+#define MAX77759_USB_ALERT_H_VBUS_ALARM_LO (0x1 << 0)
+
+#define MAX77759_USB_ALERT_H_VNDR_ALRT_SHIFT 7
+#define MAX77759_USB_ALERT_H_VNDR_ALRT_MASK (0x1 << 7)
+#define MAX77759_USB_ALERT_H_VNDR_ALRT_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_ALERT_H_ALERT_EXTND_SHIFT 6
+#define MAX77759_USB_ALERT_H_ALERT_EXTND_MASK (0x1 << 6)
+#define MAX77759_USB_ALERT_H_ALERT_EXTND_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_ALERT_H_EXTND_STAT_SHIFT 5
+#define MAX77759_USB_ALERT_H_EXTND_STAT_MASK (0x1 << 5)
+#define MAX77759_USB_ALERT_H_EXTND_STAT_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_ALERT_H_RSVD_4_SHIFT 4
+#define MAX77759_USB_ALERT_H_RSVD_4_MASK (0x1 << 4)
+#define MAX77759_USB_ALERT_H_RSVD_4_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_ALERT_H_VBUS_SNK_DISC_DET_SHIFT 3
+#define MAX77759_USB_ALERT_H_VBUS_SNK_DISC_DET_MASK (0x1 << 3)
+#define MAX77759_USB_ALERT_H_VBUS_SNK_DISC_DET_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_ALERT_H_RX_BUFF_OVRFL_SHIFT 2
+#define MAX77759_USB_ALERT_H_RX_BUFF_OVRFL_MASK (0x1 << 2)
+#define MAX77759_USB_ALERT_H_RX_BUFF_OVRFL_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_ALERT_H_FAULT_STAT_SHIFT 1
+#define MAX77759_USB_ALERT_H_FAULT_STAT_MASK (0x1 << 1)
+#define MAX77759_USB_ALERT_H_FAULT_STAT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_ALERT_H_VBUS_ALARM_LO_SHIFT 0
+#define MAX77759_USB_ALERT_H_VBUS_ALARM_LO_MASK (0x1 << 0)
+#define MAX77759_USB_ALERT_H_VBUS_ALARM_LO_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_alert_h_vndr_alrt,7,7)
+MAX77759_BFF(usb_alert_h_alert_extnd,6,6)
+MAX77759_BFF(usb_alert_h_extnd_stat,5,5)
+MAX77759_BFF(usb_alert_h_rsvd_4,4,4)
+MAX77759_BFF(usb_alert_h_vbus_snk_disc_det,3,3)
+MAX77759_BFF(usb_alert_h_rx_buff_ovrfl,2,2)
+MAX77759_BFF(usb_alert_h_fault_stat,1,1)
+MAX77759_BFF(usb_alert_h_vbus_alarm_lo,0,0)
+static inline const char *
+max77759_usb_alert_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VNDR_ALRT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_VNDR_ALRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " ALERT_EXTND=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_ALERT_EXTND, val));
+ i += SCNPRINTF(&buff[i], len - i, " EXTND_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_EXTND_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_4=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_RSVD_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_SNK_DISC_DET=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_VBUS_SNK_DISC_DET, val));
+ i += SCNPRINTF(&buff[i], len - i, " RX_BUFF_OVRFL=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_RX_BUFF_OVRFL, val));
+ i += SCNPRINTF(&buff[i], len - i, " FAULT_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_FAULT_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_ALARM_LO=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_H_VBUS_ALARM_LO, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * ALERT_MASK_L,0x12,0b11111111,0xff
+ * MSK_VBUS_V_ALRM_HI,MSK_TX_SOP_MSG_SUCC,MSK_TX_SOP_MSG_DISCRD,MSK_TX_SOP_MSG_FAIL,MSK_RX_HARD_RST,MSK_RX_SOP_MSG_STAT,MSK_PWR_STAT
+ */
+#define MAX77759_USB_ALERT_MASK_L 0x12
+#define MAX77759_USB_ALERT_MASK_L_MSK_VBUS_V_ALRM_HI (0x1 << 7)
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_SUCC (0x1 << 6)
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_DISCRD (0x1 << 5)
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_FAIL (0x1 << 4)
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_HARD_RST (0x1 << 3)
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_SOP_MSG_STAT (0x1 << 2)
+#define MAX77759_USB_ALERT_MASK_L_MSK_PWR_STAT (0x1 << 1)
+#define MAX77759_USB_ALERT_MASK_L_MSK_CC_STAT (0x1 << 0)
+
+#define MAX77759_USB_ALERT_MASK_L_MSK_VBUS_V_ALRM_HI_SHIFT 7
+#define MAX77759_USB_ALERT_MASK_L_MSK_VBUS_V_ALRM_HI_MASK (0x1 << 7)
+#define MAX77759_USB_ALERT_MASK_L_MSK_VBUS_V_ALRM_HI_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_SUCC_SHIFT 6
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_SUCC_MASK (0x1 << 6)
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_SUCC_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_DISCRD_SHIFT 5
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_DISCRD_MASK (0x1 << 5)
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_DISCRD_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_FAIL_SHIFT 4
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_FAIL_MASK (0x1 << 4)
+#define MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_FAIL_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_HARD_RST_SHIFT 3
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_HARD_RST_MASK (0x1 << 3)
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_HARD_RST_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_SOP_MSG_STAT_SHIFT 2
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_SOP_MSG_STAT_MASK (0x1 << 2)
+#define MAX77759_USB_ALERT_MASK_L_MSK_RX_SOP_MSG_STAT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_ALERT_MASK_L_MSK_PWR_STAT_SHIFT 1
+#define MAX77759_USB_ALERT_MASK_L_MSK_PWR_STAT_MASK (0x1 << 1)
+#define MAX77759_USB_ALERT_MASK_L_MSK_PWR_STAT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_ALERT_MASK_L_MSK_CC_STAT_SHIFT 0
+#define MAX77759_USB_ALERT_MASK_L_MSK_CC_STAT_MASK (0x1 << 0)
+#define MAX77759_USB_ALERT_MASK_L_MSK_CC_STAT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_alert_mask_l_msk_vbus_v_alrm_hi,7,7)
+MAX77759_BFF(usb_alert_mask_l_msk_tx_sop_msg_succ,6,6)
+MAX77759_BFF(usb_alert_mask_l_msk_tx_sop_msg_discrd,5,5)
+MAX77759_BFF(usb_alert_mask_l_msk_tx_sop_msg_fail,4,4)
+MAX77759_BFF(usb_alert_mask_l_msk_rx_hard_rst,3,3)
+MAX77759_BFF(usb_alert_mask_l_msk_rx_sop_msg_stat,2,2)
+MAX77759_BFF(usb_alert_mask_l_msk_pwr_stat,1,1)
+MAX77759_BFF(usb_alert_mask_l_msk_cc_stat,0,0)
+static inline const char *
+max77759_usb_alert_mask_l_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_V_ALRM_HI=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_VBUS_V_ALRM_HI, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_TX_SOP_MSG_SUCC=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_SUCC, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_TX_SOP_MSG_DISCRD=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_DISCRD, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_TX_SOP_MSG_FAIL=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_TX_SOP_MSG_FAIL, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_RX_HARD_RST=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_RX_HARD_RST, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_RX_SOP_MSG_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_RX_SOP_MSG_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_PWR_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_PWR_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_CC_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_L_MSK_CC_STAT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * ALERT_MASK_H,0x13,0b01101111,0x6f
+ * MSK_VNDR_ALRT,MSK_ALRT_EXTND,MSK_EXTND_STAT,RSVD_4,MSK_VBUS_SNK_DISC_DET,MSK_RX_BUFF_OVRFL,MSK_FAULT_STAT
+ */
+#define MAX77759_USB_ALERT_MASK_H 0x13
+#define MAX77759_USB_ALERT_MASK_H_MSK_VNDR_ALRT (0x1 << 7)
+#define MAX77759_USB_ALERT_MASK_H_MSK_ALRT_EXTND (0x1 << 6)
+#define MAX77759_USB_ALERT_MASK_H_MSK_EXTND_STAT (0x1 << 5)
+#define MAX77759_USB_ALERT_MASK_H_RSVD_4 (0x1 << 4)
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_SNK_DISC_DET (0x1 << 3)
+#define MAX77759_USB_ALERT_MASK_H_MSK_RX_BUFF_OVRFL (0x1 << 2)
+#define MAX77759_USB_ALERT_MASK_H_MSK_FAULT_STAT (0x1 << 1)
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_V_ALRM_LO (0x1 << 0)
+
+#define MAX77759_USB_ALERT_MASK_H_MSK_VNDR_ALRT_SHIFT 7
+#define MAX77759_USB_ALERT_MASK_H_MSK_VNDR_ALRT_MASK (0x1 << 7)
+#define MAX77759_USB_ALERT_MASK_H_MSK_VNDR_ALRT_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_ALERT_MASK_H_MSK_ALRT_EXTND_SHIFT 6
+#define MAX77759_USB_ALERT_MASK_H_MSK_ALRT_EXTND_MASK (0x1 << 6)
+#define MAX77759_USB_ALERT_MASK_H_MSK_ALRT_EXTND_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_ALERT_MASK_H_MSK_EXTND_STAT_SHIFT 5
+#define MAX77759_USB_ALERT_MASK_H_MSK_EXTND_STAT_MASK (0x1 << 5)
+#define MAX77759_USB_ALERT_MASK_H_MSK_EXTND_STAT_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_ALERT_MASK_H_RSVD_4_SHIFT 4
+#define MAX77759_USB_ALERT_MASK_H_RSVD_4_MASK (0x1 << 4)
+#define MAX77759_USB_ALERT_MASK_H_RSVD_4_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_SNK_DISC_DET_SHIFT 3
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_SNK_DISC_DET_MASK (0x1 << 3)
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_SNK_DISC_DET_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_ALERT_MASK_H_MSK_RX_BUFF_OVRFL_SHIFT 2
+#define MAX77759_USB_ALERT_MASK_H_MSK_RX_BUFF_OVRFL_MASK (0x1 << 2)
+#define MAX77759_USB_ALERT_MASK_H_MSK_RX_BUFF_OVRFL_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_ALERT_MASK_H_MSK_FAULT_STAT_SHIFT 1
+#define MAX77759_USB_ALERT_MASK_H_MSK_FAULT_STAT_MASK (0x1 << 1)
+#define MAX77759_USB_ALERT_MASK_H_MSK_FAULT_STAT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_V_ALRM_LO_SHIFT 0
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_V_ALRM_LO_MASK (0x1 << 0)
+#define MAX77759_USB_ALERT_MASK_H_MSK_VBUS_V_ALRM_LO_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_alert_mask_h_msk_vndr_alrt,7,7)
+MAX77759_BFF(usb_alert_mask_h_msk_alrt_extnd,6,6)
+MAX77759_BFF(usb_alert_mask_h_msk_extnd_stat,5,5)
+MAX77759_BFF(usb_alert_mask_h_rsvd_4,4,4)
+MAX77759_BFF(usb_alert_mask_h_msk_vbus_snk_disc_det,3,3)
+MAX77759_BFF(usb_alert_mask_h_msk_rx_buff_ovrfl,2,2)
+MAX77759_BFF(usb_alert_mask_h_msk_fault_stat,1,1)
+MAX77759_BFF(usb_alert_mask_h_msk_vbus_v_alrm_lo,0,0)
+static inline const char *
+max77759_usb_alert_mask_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VNDR_ALRT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_VNDR_ALRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_ALRT_EXTND=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_ALRT_EXTND, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_EXTND_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_EXTND_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_4=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_RSVD_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_SNK_DISC_DET=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_VBUS_SNK_DISC_DET, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_RX_BUFF_OVRFL=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_RX_BUFF_OVRFL, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_FAULT_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_FAULT_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_V_ALRM_LO=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_MASK_H_MSK_VBUS_V_ALRM_LO, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * POWER_STATUS_MASK_REG,0x14,0b11111111,0xff
+ * MSK_DEBG_ACC_CONN_STAT,MSK_TCPC_INIT_STAT,MSK_SRC_HI_V_STAT,MSK_SRC_VBUS_STAT,MSK_VBUS_DET_STAT,MSK_VBUS_PRSN_STAT,MSK_VCONN_PRSN_STAT
+ */
+#define MAX77759_USB_POWER_STATUS_MASK_REG 0x14
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_DEBG_ACC_CONN_STAT (0x1 << 7)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_TCPC_INIT_STAT (0x1 << 6)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_HI_V_STAT (0x1 << 5)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_VBUS_STAT (0x1 << 4)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_DET_STAT (0x1 << 3)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_PRSN_STAT (0x1 << 2)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VCONN_PRSN_STAT (0x1 << 1)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SNK_VBUS_STAT (0x1 << 0)
+
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_DEBG_ACC_CONN_STAT_SHIFT 7
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_DEBG_ACC_CONN_STAT_MASK (0x1 << 7)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_DEBG_ACC_CONN_STAT_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_TCPC_INIT_STAT_SHIFT 6
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_TCPC_INIT_STAT_MASK (0x1 << 6)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_TCPC_INIT_STAT_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_HI_V_STAT_SHIFT 5
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_HI_V_STAT_MASK (0x1 << 5)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_HI_V_STAT_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_VBUS_STAT_SHIFT 4
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_VBUS_STAT_MASK (0x1 << 4)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_VBUS_STAT_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_DET_STAT_SHIFT 3
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_DET_STAT_MASK (0x1 << 3)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_DET_STAT_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_PRSN_STAT_SHIFT 2
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_PRSN_STAT_MASK (0x1 << 2)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_PRSN_STAT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VCONN_PRSN_STAT_SHIFT 1
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VCONN_PRSN_STAT_MASK (0x1 << 1)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VCONN_PRSN_STAT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SNK_VBUS_STAT_SHIFT 0
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SNK_VBUS_STAT_MASK (0x1 << 0)
+#define MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SNK_VBUS_STAT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_power_status_mask_reg_msk_debg_acc_conn_stat,7,7)
+MAX77759_BFF(usb_power_status_mask_reg_msk_tcpc_init_stat,6,6)
+MAX77759_BFF(usb_power_status_mask_reg_msk_src_hi_v_stat,5,5)
+MAX77759_BFF(usb_power_status_mask_reg_msk_src_vbus_stat,4,4)
+MAX77759_BFF(usb_power_status_mask_reg_msk_vbus_det_stat,3,3)
+MAX77759_BFF(usb_power_status_mask_reg_msk_vbus_prsn_stat,2,2)
+MAX77759_BFF(usb_power_status_mask_reg_msk_vconn_prsn_stat,1,1)
+MAX77759_BFF(usb_power_status_mask_reg_msk_snk_vbus_stat,0,0)
+static inline const char *
+max77759_usb_power_status_mask_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MSK_DEBG_ACC_CONN_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_DEBG_ACC_CONN_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_TCPC_INIT_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_TCPC_INIT_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_SRC_HI_V_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_HI_V_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_SRC_VBUS_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SRC_VBUS_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_DET_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_DET_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_PRSN_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VBUS_PRSN_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VCONN_PRSN_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_VCONN_PRSN_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_SNK_VBUS_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_MASK_REG_MSK_SNK_VBUS_STAT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * FAULT_STATUS_MASK_REG,0x15,0b11111111,0xff
+ * MSK_All_REG_RST,MSK_FORC_VBUS,MSK_AUTO_DISCH_FAIL,MSK_FORC_DISCH_FAIL,MSK_VBUS_OCP,MSK_VBUS_OVP,MSK_VCONN_OCP
+ */
+#define MAX77759_USB_FAULT_STATUS_MASK_REG 0x15
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_ALL_REG_RST (0x1 << 7)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_VBUS (0x1 << 6)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_AUTO_DISCH_FAIL (0x1 << 5)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_DISCH_FAIL (0x1 << 4)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OCP (0x1 << 3)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OVP (0x1 << 2)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VCONN_OCP (0x1 << 1)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_I2C_ERR (0x1 << 0)
+
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_ALL_REG_RST_SHIFT 7
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_ALL_REG_RST_MASK (0x1 << 7)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_ALL_REG_RST_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_VBUS_SHIFT 6
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_VBUS_MASK (0x1 << 6)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_VBUS_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_AUTO_DISCH_FAIL_SHIFT 5
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_AUTO_DISCH_FAIL_MASK (0x1 << 5)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_AUTO_DISCH_FAIL_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_DISCH_FAIL_SHIFT 4
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_DISCH_FAIL_MASK (0x1 << 4)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_DISCH_FAIL_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OCP_SHIFT 3
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OCP_MASK (0x1 << 3)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OCP_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OVP_SHIFT 2
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OVP_MASK (0x1 << 2)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OVP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VCONN_OCP_SHIFT 1
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VCONN_OCP_MASK (0x1 << 1)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VCONN_OCP_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_I2C_ERR_SHIFT 0
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_I2C_ERR_MASK (0x1 << 0)
+#define MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_I2C_ERR_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_fault_status_mask_reg_msk_all_reg_rst,7,7)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_forc_vbus,6,6)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_auto_disch_fail,5,5)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_forc_disch_fail,4,4)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_vbus_ocp,3,3)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_vbus_ovp,2,2)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_vconn_ocp,1,1)
+MAX77759_BFF(usb_fault_status_mask_reg_msk_i2c_err,0,0)
+static inline const char *
+max77759_usb_fault_status_mask_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MSK_ALL_REG_RST=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_ALL_REG_RST, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_FORC_VBUS=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_VBUS, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_AUTO_DISCH_FAIL=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_AUTO_DISCH_FAIL, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_FORC_DISCH_FAIL=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_FORC_DISCH_FAIL, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_OCP=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OCP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VBUS_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VBUS_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VCONN_OCP=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_VCONN_OCP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_I2C_ERR=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_MASK_REG_MSK_I2C_ERR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * EXTENDED_STATUS_MASK_REG,0x16,0b00000001,0x01
+ * RSVD_7_1[6:0],,,,,,
+ */
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG 0x16
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_MSK_VSAFE0V (0x1 << 0)
+
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_RSVD_7_1_SHIFT 1
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_RSVD_7_1_MASK (0x7f << 1)
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_RSVD_7_1_CLEAR (~(0x7f << 1))
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_MSK_VSAFE0V_SHIFT 0
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_MSK_VSAFE0V_MASK (0x1 << 0)
+#define MAX77759_USB_EXTENDED_STATUS_MASK_REG_MSK_VSAFE0V_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_extended_status_mask_reg_rsvd_7_1,7,1)
+MAX77759_BFF(usb_extended_status_mask_reg_msk_vsafe0v,0,0)
+static inline const char *
+max77759_usb_extended_status_mask_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_1=%x",
+ FIELD2VALUE(MAX77759_USB_EXTENDED_STATUS_MASK_REG_RSVD_7_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_VSAFE0V=%x",
+ FIELD2VALUE(MAX77759_USB_EXTENDED_STATUS_MASK_REG_MSK_VSAFE0V, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * ALERT_EXTENDED_MASK,0x17,0b00000001,0x01
+ * RSVD_7_1[6:0],,,,,,
+ */
+#define MAX77759_USB_ALERT_EXTENDED_MASK 0x17
+#define MAX77759_USB_ALERT_EXTENDED_MASK_MSK_SNK_FRS (0x1 << 0)
+
+#define MAX77759_USB_ALERT_EXTENDED_MASK_RSVD_7_1_SHIFT 1
+#define MAX77759_USB_ALERT_EXTENDED_MASK_RSVD_7_1_MASK (0x7f << 1)
+#define MAX77759_USB_ALERT_EXTENDED_MASK_RSVD_7_1_CLEAR (~(0x7f << 1))
+#define MAX77759_USB_ALERT_EXTENDED_MASK_MSK_SNK_FRS_SHIFT 0
+#define MAX77759_USB_ALERT_EXTENDED_MASK_MSK_SNK_FRS_MASK (0x1 << 0)
+#define MAX77759_USB_ALERT_EXTENDED_MASK_MSK_SNK_FRS_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_alert_extended_mask_rsvd_7_1,7,1)
+MAX77759_BFF(usb_alert_extended_mask_msk_snk_frs,0,0)
+static inline const char *
+max77759_usb_alert_extended_mask_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_1=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_EXTENDED_MASK_RSVD_7_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_SNK_FRS=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_EXTENDED_MASK_MSK_SNK_FRS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CONFIG_STANDARD_OUTPUT,0x18,0b01000000,0x40
+ * RSVD_7,DbgAccConn,RSVD_5,RSVD_4_3[1:0],,RSVD_2,RSVD_1
+ */
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT 0x18
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_7 (0x1 << 7)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_DBGACCCONN (0x1 << 6)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_5 (0x1 << 5)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_2 (0x1 << 2)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_1 (0x1 << 1)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_0 (0x1 << 0)
+
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_7_SHIFT 7
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_7_MASK (0x1 << 7)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_7_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_DBGACCCONN_SHIFT 6
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_DBGACCCONN_MASK (0x1 << 6)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_DBGACCCONN_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_5_SHIFT 5
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_5_MASK (0x1 << 5)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_5_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_4_3_SHIFT 3
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_4_3_MASK (0x3 << 3)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_4_3_CLEAR (~(0x3 << 3))
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_2_SHIFT 2
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_2_MASK (0x1 << 2)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_2_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_1_SHIFT 1
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_1_MASK (0x1 << 1)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_0_SHIFT 0
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_0_MASK (0x1 << 0)
+#define MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_0_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_config_standard_output_rsvd_7,7,7)
+MAX77759_BFF(usb_config_standard_output_dbgaccconn,6,6)
+MAX77759_BFF(usb_config_standard_output_rsvd_5,5,5)
+MAX77759_BFF(usb_config_standard_output_rsvd_4_3,4,3)
+MAX77759_BFF(usb_config_standard_output_rsvd_2,2,2)
+MAX77759_BFF(usb_config_standard_output_rsvd_1,1,1)
+MAX77759_BFF(usb_config_standard_output_rsvd_0,0,0)
+static inline const char *
+max77759_usb_config_standard_output_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " DBGACCCONN=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_DBGACCCONN, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_5=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_4_3=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_4_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_2=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_1=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_0=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_STANDARD_OUTPUT_RSVD_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TCPC_CONTROL_REG,0x19,0b00000000,0x00
+ * RSVD_7,EN_LK4CONN_ALRT,EN_WD_TMR,DBG_ACC_CNTRL,RSVD_3_2[1:0],,BIST_TM
+ */
+#define MAX77759_USB_TCPC_CONTROL_REG 0x19
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_7 (0x1 << 7)
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_LK4CONN_ALRT (0x1 << 6)
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_WD_TMR (0x1 << 5)
+#define MAX77759_USB_TCPC_CONTROL_REG_DBG_ACC_CNTRL (0x1 << 4)
+#define MAX77759_USB_TCPC_CONTROL_REG_BIST_TM (0x1 << 1)
+#define MAX77759_USB_TCPC_CONTROL_REG_PLUG_ORNT (0x1 << 0)
+
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_7_SHIFT 7
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_7_MASK (0x1 << 7)
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_7_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_LK4CONN_ALRT_SHIFT 6
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_LK4CONN_ALRT_MASK (0x1 << 6)
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_LK4CONN_ALRT_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_WD_TMR_SHIFT 5
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_WD_TMR_MASK (0x1 << 5)
+#define MAX77759_USB_TCPC_CONTROL_REG_EN_WD_TMR_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_TCPC_CONTROL_REG_DBG_ACC_CNTRL_SHIFT 4
+#define MAX77759_USB_TCPC_CONTROL_REG_DBG_ACC_CNTRL_MASK (0x1 << 4)
+#define MAX77759_USB_TCPC_CONTROL_REG_DBG_ACC_CNTRL_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_3_2_SHIFT 2
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_3_2_MASK (0x3 << 2)
+#define MAX77759_USB_TCPC_CONTROL_REG_RSVD_3_2_CLEAR (~(0x3 << 2))
+#define MAX77759_USB_TCPC_CONTROL_REG_BIST_TM_SHIFT 1
+#define MAX77759_USB_TCPC_CONTROL_REG_BIST_TM_MASK (0x1 << 1)
+#define MAX77759_USB_TCPC_CONTROL_REG_BIST_TM_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_TCPC_CONTROL_REG_PLUG_ORNT_SHIFT 0
+#define MAX77759_USB_TCPC_CONTROL_REG_PLUG_ORNT_MASK (0x1 << 0)
+#define MAX77759_USB_TCPC_CONTROL_REG_PLUG_ORNT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_tcpc_control_reg_rsvd_7,7,7)
+MAX77759_BFF(usb_tcpc_control_reg_en_lk4conn_alrt,6,6)
+MAX77759_BFF(usb_tcpc_control_reg_en_wd_tmr,5,5)
+MAX77759_BFF(usb_tcpc_control_reg_dbg_acc_cntrl,4,4)
+MAX77759_BFF(usb_tcpc_control_reg_rsvd_3_2,3,2)
+MAX77759_BFF(usb_tcpc_control_reg_bist_tm,1,1)
+MAX77759_BFF(usb_tcpc_control_reg_plug_ornt,0,0)
+static inline const char *
+max77759_usb_tcpc_control_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_RSVD_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_LK4CONN_ALRT=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_EN_LK4CONN_ALRT, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_WD_TMR=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_EN_WD_TMR, val));
+ i += SCNPRINTF(&buff[i], len - i, " DBG_ACC_CNTRL=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_DBG_ACC_CNTRL, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_3_2=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_RSVD_3_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " BIST_TM=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_BIST_TM, val));
+ i += SCNPRINTF(&buff[i], len - i, " PLUG_ORNT=%x",
+ FIELD2VALUE(MAX77759_USB_TCPC_CONTROL_REG_PLUG_ORNT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * ROLE_CONTROL_REG,0x1A,0b00001010,0x0a
+ * RSVD_7,DRP,RP_VAL[1:0],,CC2[1:0],,CC1[1:0]
+ */
+#define MAX77759_USB_ROLE_CONTROL_REG 0x1A
+#define MAX77759_USB_ROLE_CONTROL_REG_RSVD_7 (0x1 << 7)
+#define MAX77759_USB_ROLE_CONTROL_REG_DRP (0x1 << 6)
+
+#define MAX77759_USB_ROLE_CONTROL_REG_RSVD_7_SHIFT 7
+#define MAX77759_USB_ROLE_CONTROL_REG_RSVD_7_MASK (0x1 << 7)
+#define MAX77759_USB_ROLE_CONTROL_REG_RSVD_7_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_ROLE_CONTROL_REG_DRP_SHIFT 6
+#define MAX77759_USB_ROLE_CONTROL_REG_DRP_MASK (0x1 << 6)
+#define MAX77759_USB_ROLE_CONTROL_REG_DRP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_ROLE_CONTROL_REG_RP_VAL_SHIFT 4
+#define MAX77759_USB_ROLE_CONTROL_REG_RP_VAL_MASK (0x3 << 4)
+#define MAX77759_USB_ROLE_CONTROL_REG_RP_VAL_CLEAR (~(0x3 << 4))
+#define MAX77759_USB_ROLE_CONTROL_REG_CC2_SHIFT 2
+#define MAX77759_USB_ROLE_CONTROL_REG_CC2_MASK (0x3 << 2)
+#define MAX77759_USB_ROLE_CONTROL_REG_CC2_CLEAR (~(0x3 << 2))
+#define MAX77759_USB_ROLE_CONTROL_REG_CC1_SHIFT 0
+#define MAX77759_USB_ROLE_CONTROL_REG_CC1_MASK (0x3 << 0)
+#define MAX77759_USB_ROLE_CONTROL_REG_CC1_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_role_control_reg_rsvd_7,7,7)
+MAX77759_BFF(usb_role_control_reg_drp,6,6)
+MAX77759_BFF(usb_role_control_reg_rp_val,5,4)
+MAX77759_BFF(usb_role_control_reg_cc2,3,2)
+MAX77759_BFF(usb_role_control_reg_cc1,1,0)
+static inline const char *
+max77759_usb_role_control_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7=%x",
+ FIELD2VALUE(MAX77759_USB_ROLE_CONTROL_REG_RSVD_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " DRP=%x",
+ FIELD2VALUE(MAX77759_USB_ROLE_CONTROL_REG_DRP, val));
+ i += SCNPRINTF(&buff[i], len - i, " RP_VAL=%x",
+ FIELD2VALUE(MAX77759_USB_ROLE_CONTROL_REG_RP_VAL, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC2=%x",
+ FIELD2VALUE(MAX77759_USB_ROLE_CONTROL_REG_CC2, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1=%x",
+ FIELD2VALUE(MAX77759_USB_ROLE_CONTROL_REG_CC1, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * FAULT_CONTROL_REG,0x1B,0b00000000,0x00
+ * RSVD_7_5[2:0],,,RSVD_4,VBUS_DISCH_FAIL_DIS,RSVD_2,RSVD_1
+ */
+#define MAX77759_USB_FAULT_CONTROL_REG 0x1B
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_4 (0x1 << 4)
+#define MAX77759_USB_FAULT_CONTROL_REG_VBUS_DISCH_FAIL_DIS (0x1 << 3)
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_2 (0x1 << 2)
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_1 (0x1 << 1)
+#define MAX77759_USB_FAULT_CONTROL_REG_VCONN_OCP_FAULT_DIS (0x1 << 0)
+
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_7_5_SHIFT 5
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_7_5_MASK (0x7 << 5)
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_7_5_CLEAR (~(0x7 << 5))
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_4_SHIFT 4
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_4_MASK (0x1 << 4)
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_4_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_FAULT_CONTROL_REG_VBUS_DISCH_FAIL_DIS_SHIFT 3
+#define MAX77759_USB_FAULT_CONTROL_REG_VBUS_DISCH_FAIL_DIS_MASK (0x1 << 3)
+#define MAX77759_USB_FAULT_CONTROL_REG_VBUS_DISCH_FAIL_DIS_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_2_SHIFT 2
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_2_MASK (0x1 << 2)
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_2_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_1_SHIFT 1
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_1_MASK (0x1 << 1)
+#define MAX77759_USB_FAULT_CONTROL_REG_RSVD_1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_FAULT_CONTROL_REG_VCONN_OCP_FAULT_DIS_SHIFT 0
+#define MAX77759_USB_FAULT_CONTROL_REG_VCONN_OCP_FAULT_DIS_MASK (0x1 << 0)
+#define MAX77759_USB_FAULT_CONTROL_REG_VCONN_OCP_FAULT_DIS_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_fault_control_reg_rsvd_7_5,7,5)
+MAX77759_BFF(usb_fault_control_reg_rsvd_4,4,4)
+MAX77759_BFF(usb_fault_control_reg_vbus_disch_fail_dis,3,3)
+MAX77759_BFF(usb_fault_control_reg_rsvd_2,2,2)
+MAX77759_BFF(usb_fault_control_reg_rsvd_1,1,1)
+MAX77759_BFF(usb_fault_control_reg_vconn_ocp_fault_dis,0,0)
+static inline const char *
+max77759_usb_fault_control_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_5=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_CONTROL_REG_RSVD_7_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_4=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_CONTROL_REG_RSVD_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_DISCH_FAIL_DIS=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_CONTROL_REG_VBUS_DISCH_FAIL_DIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_2=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_CONTROL_REG_RSVD_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_1=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_CONTROL_REG_RSVD_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCONN_OCP_FAULT_DIS=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_CONTROL_REG_VCONN_OCP_FAULT_DIS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * POWER_CONTROL_REG,0x1C,0b01100000,0x60
+ * FAST_RSWP_EN,VBUS_VOLT_MON,DIS_VOLT_ALRM,AUTO_DISCH_DISC,EN_BLEED_DISCH,FORC_DISCH,RSVD_1
+ */
+#define MAX77759_USB_POWER_CONTROL_REG 0x1C
+#define MAX77759_USB_POWER_CONTROL_REG_FAST_RSWP_EN (0x1 << 7)
+#define MAX77759_USB_POWER_CONTROL_REG_VBUS_VOLT_MON (0x1 << 6)
+#define MAX77759_USB_POWER_CONTROL_REG_DIS_VOLT_ALRM (0x1 << 5)
+#define MAX77759_USB_POWER_CONTROL_REG_AUTO_DISCH_DISC (0x1 << 4)
+#define MAX77759_USB_POWER_CONTROL_REG_EN_BLEED_DISCH (0x1 << 3)
+#define MAX77759_USB_POWER_CONTROL_REG_FORC_DISCH (0x1 << 2)
+#define MAX77759_USB_POWER_CONTROL_REG_RSVD_1 (0x1 << 1)
+#define MAX77759_USB_POWER_CONTROL_REG_EN_VCONN (0x1 << 0)
+
+#define MAX77759_USB_POWER_CONTROL_REG_FAST_RSWP_EN_SHIFT 7
+#define MAX77759_USB_POWER_CONTROL_REG_FAST_RSWP_EN_MASK (0x1 << 7)
+#define MAX77759_USB_POWER_CONTROL_REG_FAST_RSWP_EN_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_POWER_CONTROL_REG_VBUS_VOLT_MON_SHIFT 6
+#define MAX77759_USB_POWER_CONTROL_REG_VBUS_VOLT_MON_MASK (0x1 << 6)
+#define MAX77759_USB_POWER_CONTROL_REG_VBUS_VOLT_MON_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_POWER_CONTROL_REG_DIS_VOLT_ALRM_SHIFT 5
+#define MAX77759_USB_POWER_CONTROL_REG_DIS_VOLT_ALRM_MASK (0x1 << 5)
+#define MAX77759_USB_POWER_CONTROL_REG_DIS_VOLT_ALRM_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_POWER_CONTROL_REG_AUTO_DISCH_DISC_SHIFT 4
+#define MAX77759_USB_POWER_CONTROL_REG_AUTO_DISCH_DISC_MASK (0x1 << 4)
+#define MAX77759_USB_POWER_CONTROL_REG_AUTO_DISCH_DISC_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_POWER_CONTROL_REG_EN_BLEED_DISCH_SHIFT 3
+#define MAX77759_USB_POWER_CONTROL_REG_EN_BLEED_DISCH_MASK (0x1 << 3)
+#define MAX77759_USB_POWER_CONTROL_REG_EN_BLEED_DISCH_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_POWER_CONTROL_REG_FORC_DISCH_SHIFT 2
+#define MAX77759_USB_POWER_CONTROL_REG_FORC_DISCH_MASK (0x1 << 2)
+#define MAX77759_USB_POWER_CONTROL_REG_FORC_DISCH_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_POWER_CONTROL_REG_RSVD_1_SHIFT 1
+#define MAX77759_USB_POWER_CONTROL_REG_RSVD_1_MASK (0x1 << 1)
+#define MAX77759_USB_POWER_CONTROL_REG_RSVD_1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_POWER_CONTROL_REG_EN_VCONN_SHIFT 0
+#define MAX77759_USB_POWER_CONTROL_REG_EN_VCONN_MASK (0x1 << 0)
+#define MAX77759_USB_POWER_CONTROL_REG_EN_VCONN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_power_control_reg_fast_rswp_en,7,7)
+MAX77759_BFF(usb_power_control_reg_vbus_volt_mon,6,6)
+MAX77759_BFF(usb_power_control_reg_dis_volt_alrm,5,5)
+MAX77759_BFF(usb_power_control_reg_auto_disch_disc,4,4)
+MAX77759_BFF(usb_power_control_reg_en_bleed_disch,3,3)
+MAX77759_BFF(usb_power_control_reg_forc_disch,2,2)
+MAX77759_BFF(usb_power_control_reg_rsvd_1,1,1)
+MAX77759_BFF(usb_power_control_reg_en_vconn,0,0)
+static inline const char *
+max77759_usb_power_control_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " FAST_RSWP_EN=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_FAST_RSWP_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_VOLT_MON=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_VBUS_VOLT_MON, val));
+ i += SCNPRINTF(&buff[i], len - i, " DIS_VOLT_ALRM=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_DIS_VOLT_ALRM, val));
+ i += SCNPRINTF(&buff[i], len - i, " AUTO_DISCH_DISC=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_AUTO_DISCH_DISC, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_BLEED_DISCH=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_EN_BLEED_DISCH, val));
+ i += SCNPRINTF(&buff[i], len - i, " FORC_DISCH=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_FORC_DISCH, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_1=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_RSVD_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_VCONN=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_CONTROL_REG_EN_VCONN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CC_STATUS_REG,0x1D,0b00000000,0x00
+ * RSVD_7_6[1:0],,LOOKING4CONN,CONN_RSLT,CC2_STATE[1:0],,CC1_STATE[1:0]
+ */
+#define MAX77759_USB_CC_STATUS_REG 0x1D
+#define MAX77759_USB_CC_STATUS_REG_LOOKING4CONN (0x1 << 5)
+#define MAX77759_USB_CC_STATUS_REG_CONN_RSLT (0x1 << 4)
+
+#define MAX77759_USB_CC_STATUS_REG_RSVD_7_6_SHIFT 6
+#define MAX77759_USB_CC_STATUS_REG_RSVD_7_6_MASK (0x3 << 6)
+#define MAX77759_USB_CC_STATUS_REG_RSVD_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_CC_STATUS_REG_LOOKING4CONN_SHIFT 5
+#define MAX77759_USB_CC_STATUS_REG_LOOKING4CONN_MASK (0x1 << 5)
+#define MAX77759_USB_CC_STATUS_REG_LOOKING4CONN_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_CC_STATUS_REG_CONN_RSLT_SHIFT 4
+#define MAX77759_USB_CC_STATUS_REG_CONN_RSLT_MASK (0x1 << 4)
+#define MAX77759_USB_CC_STATUS_REG_CONN_RSLT_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_CC_STATUS_REG_CC2_STATE_SHIFT 2
+#define MAX77759_USB_CC_STATUS_REG_CC2_STATE_MASK (0x3 << 2)
+#define MAX77759_USB_CC_STATUS_REG_CC2_STATE_CLEAR (~(0x3 << 2))
+#define MAX77759_USB_CC_STATUS_REG_CC1_STATE_SHIFT 0
+#define MAX77759_USB_CC_STATUS_REG_CC1_STATE_MASK (0x3 << 0)
+#define MAX77759_USB_CC_STATUS_REG_CC1_STATE_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_cc_status_reg_rsvd_7_6,7,6)
+MAX77759_BFF(usb_cc_status_reg_looking4conn,5,5)
+MAX77759_BFF(usb_cc_status_reg_conn_rslt,4,4)
+MAX77759_BFF(usb_cc_status_reg_cc2_state,3,2)
+MAX77759_BFF(usb_cc_status_reg_cc1_state,1,0)
+static inline const char *
+max77759_usb_cc_status_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_6=%x",
+ FIELD2VALUE(MAX77759_USB_CC_STATUS_REG_RSVD_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " LOOKING4CONN=%x",
+ FIELD2VALUE(MAX77759_USB_CC_STATUS_REG_LOOKING4CONN, val));
+ i += SCNPRINTF(&buff[i], len - i, " CONN_RSLT=%x",
+ FIELD2VALUE(MAX77759_USB_CC_STATUS_REG_CONN_RSLT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC2_STATE=%x",
+ FIELD2VALUE(MAX77759_USB_CC_STATUS_REG_CC2_STATE, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1_STATE=%x",
+ FIELD2VALUE(MAX77759_USB_CC_STATUS_REG_CC1_STATE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * POWER_STATUS_REG,0x1E,0b00001000,0x08
+ * DBG_ACC_CONN,TCPC_INIT_STAT,SRC_HI_VOLT,SRC_VBUS,VBUS_DET_EN,VBUS_PRESENT,VCONN_PRESENT
+ */
+#define MAX77759_USB_POWER_STATUS_REG 0x1E
+#define MAX77759_USB_POWER_STATUS_REG_DBG_ACC_CONN (0x1 << 7)
+#define MAX77759_USB_POWER_STATUS_REG_TCPC_INIT_STAT (0x1 << 6)
+#define MAX77759_USB_POWER_STATUS_REG_SRC_HI_VOLT (0x1 << 5)
+#define MAX77759_USB_POWER_STATUS_REG_SRC_VBUS (0x1 << 4)
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_DET_EN (0x1 << 3)
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_PRESENT (0x1 << 2)
+#define MAX77759_USB_POWER_STATUS_REG_VCONN_PRESENT (0x1 << 1)
+#define MAX77759_USB_POWER_STATUS_REG_SNK_VBUS (0x1 << 0)
+
+#define MAX77759_USB_POWER_STATUS_REG_DBG_ACC_CONN_SHIFT 7
+#define MAX77759_USB_POWER_STATUS_REG_DBG_ACC_CONN_MASK (0x1 << 7)
+#define MAX77759_USB_POWER_STATUS_REG_DBG_ACC_CONN_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_POWER_STATUS_REG_TCPC_INIT_STAT_SHIFT 6
+#define MAX77759_USB_POWER_STATUS_REG_TCPC_INIT_STAT_MASK (0x1 << 6)
+#define MAX77759_USB_POWER_STATUS_REG_TCPC_INIT_STAT_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_POWER_STATUS_REG_SRC_HI_VOLT_SHIFT 5
+#define MAX77759_USB_POWER_STATUS_REG_SRC_HI_VOLT_MASK (0x1 << 5)
+#define MAX77759_USB_POWER_STATUS_REG_SRC_HI_VOLT_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_POWER_STATUS_REG_SRC_VBUS_SHIFT 4
+#define MAX77759_USB_POWER_STATUS_REG_SRC_VBUS_MASK (0x1 << 4)
+#define MAX77759_USB_POWER_STATUS_REG_SRC_VBUS_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_DET_EN_SHIFT 3
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_DET_EN_MASK (0x1 << 3)
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_DET_EN_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_PRESENT_SHIFT 2
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_PRESENT_MASK (0x1 << 2)
+#define MAX77759_USB_POWER_STATUS_REG_VBUS_PRESENT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_POWER_STATUS_REG_VCONN_PRESENT_SHIFT 1
+#define MAX77759_USB_POWER_STATUS_REG_VCONN_PRESENT_MASK (0x1 << 1)
+#define MAX77759_USB_POWER_STATUS_REG_VCONN_PRESENT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_POWER_STATUS_REG_SNK_VBUS_SHIFT 0
+#define MAX77759_USB_POWER_STATUS_REG_SNK_VBUS_MASK (0x1 << 0)
+#define MAX77759_USB_POWER_STATUS_REG_SNK_VBUS_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_power_status_reg_dbg_acc_conn,7,7)
+MAX77759_BFF(usb_power_status_reg_tcpc_init_stat,6,6)
+MAX77759_BFF(usb_power_status_reg_src_hi_volt,5,5)
+MAX77759_BFF(usb_power_status_reg_src_vbus,4,4)
+MAX77759_BFF(usb_power_status_reg_vbus_det_en,3,3)
+MAX77759_BFF(usb_power_status_reg_vbus_present,2,2)
+MAX77759_BFF(usb_power_status_reg_vconn_present,1,1)
+MAX77759_BFF(usb_power_status_reg_snk_vbus,0,0)
+static inline const char *
+max77759_usb_power_status_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " DBG_ACC_CONN=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_DBG_ACC_CONN, val));
+ i += SCNPRINTF(&buff[i], len - i, " TCPC_INIT_STAT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_TCPC_INIT_STAT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_HI_VOLT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_SRC_HI_VOLT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_VBUS=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_SRC_VBUS, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_DET_EN=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_VBUS_DET_EN, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_PRESENT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_VBUS_PRESENT, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCONN_PRESENT=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_VCONN_PRESENT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SNK_VBUS=%x",
+ FIELD2VALUE(MAX77759_USB_POWER_STATUS_REG_SNK_VBUS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * FAULT_STATUS_REG,0x1F,0b10000000,0x80
+ * ALL_REG_RST,RSVD_6,AUTO_DISCH_FAIL,FORCE_DISCH_FAIL,RSVD_3,RSVD_2,VCONN_OCP_FAULT
+ */
+#define MAX77759_USB_FAULT_STATUS_REG 0x1F
+#define MAX77759_USB_FAULT_STATUS_REG_ALL_REG_RST (0x1 << 7)
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_6 (0x1 << 6)
+#define MAX77759_USB_FAULT_STATUS_REG_AUTO_DISCH_FAIL (0x1 << 5)
+#define MAX77759_USB_FAULT_STATUS_REG_FORCE_DISCH_FAIL (0x1 << 4)
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_3 (0x1 << 3)
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_2 (0x1 << 2)
+#define MAX77759_USB_FAULT_STATUS_REG_VCONN_OCP_FAULT (0x1 << 1)
+#define MAX77759_USB_FAULT_STATUS_REG_I2C_ERR (0x1 << 0)
+
+#define MAX77759_USB_FAULT_STATUS_REG_ALL_REG_RST_SHIFT 7
+#define MAX77759_USB_FAULT_STATUS_REG_ALL_REG_RST_MASK (0x1 << 7)
+#define MAX77759_USB_FAULT_STATUS_REG_ALL_REG_RST_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_6_SHIFT 6
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_6_MASK (0x1 << 6)
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_6_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_FAULT_STATUS_REG_AUTO_DISCH_FAIL_SHIFT 5
+#define MAX77759_USB_FAULT_STATUS_REG_AUTO_DISCH_FAIL_MASK (0x1 << 5)
+#define MAX77759_USB_FAULT_STATUS_REG_AUTO_DISCH_FAIL_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_FAULT_STATUS_REG_FORCE_DISCH_FAIL_SHIFT 4
+#define MAX77759_USB_FAULT_STATUS_REG_FORCE_DISCH_FAIL_MASK (0x1 << 4)
+#define MAX77759_USB_FAULT_STATUS_REG_FORCE_DISCH_FAIL_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_3_SHIFT 3
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_3_MASK (0x1 << 3)
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_3_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_2_SHIFT 2
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_2_MASK (0x1 << 2)
+#define MAX77759_USB_FAULT_STATUS_REG_RSVD_2_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_FAULT_STATUS_REG_VCONN_OCP_FAULT_SHIFT 1
+#define MAX77759_USB_FAULT_STATUS_REG_VCONN_OCP_FAULT_MASK (0x1 << 1)
+#define MAX77759_USB_FAULT_STATUS_REG_VCONN_OCP_FAULT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_FAULT_STATUS_REG_I2C_ERR_SHIFT 0
+#define MAX77759_USB_FAULT_STATUS_REG_I2C_ERR_MASK (0x1 << 0)
+#define MAX77759_USB_FAULT_STATUS_REG_I2C_ERR_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_fault_status_reg_all_reg_rst,7,7)
+MAX77759_BFF(usb_fault_status_reg_rsvd_6,6,6)
+MAX77759_BFF(usb_fault_status_reg_auto_disch_fail,5,5)
+MAX77759_BFF(usb_fault_status_reg_force_disch_fail,4,4)
+MAX77759_BFF(usb_fault_status_reg_rsvd_3,3,3)
+MAX77759_BFF(usb_fault_status_reg_rsvd_2,2,2)
+MAX77759_BFF(usb_fault_status_reg_vconn_ocp_fault,1,1)
+MAX77759_BFF(usb_fault_status_reg_i2c_err,0,0)
+static inline const char *
+max77759_usb_fault_status_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " ALL_REG_RST=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_ALL_REG_RST, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_6=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_RSVD_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " AUTO_DISCH_FAIL=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_AUTO_DISCH_FAIL, val));
+ i += SCNPRINTF(&buff[i], len - i, " FORCE_DISCH_FAIL=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_FORCE_DISCH_FAIL, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_3=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_RSVD_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_2=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_RSVD_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCONN_OCP_FAULT=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_VCONN_OCP_FAULT, val));
+ i += SCNPRINTF(&buff[i], len - i, " I2C_ERR=%x",
+ FIELD2VALUE(MAX77759_USB_FAULT_STATUS_REG_I2C_ERR, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * EXTENDED_STATUS_REG,0x20,0b00000000,0x00
+ * RSVD_7_1[6:0],,,,,,
+ */
+#define MAX77759_USB_EXTENDED_STATUS_REG 0x20
+#define MAX77759_USB_EXTENDED_STATUS_REG_VSAFE0V (0x1 << 0)
+
+#define MAX77759_USB_EXTENDED_STATUS_REG_RSVD_7_1_SHIFT 1
+#define MAX77759_USB_EXTENDED_STATUS_REG_RSVD_7_1_MASK (0x7f << 1)
+#define MAX77759_USB_EXTENDED_STATUS_REG_RSVD_7_1_CLEAR (~(0x7f << 1))
+#define MAX77759_USB_EXTENDED_STATUS_REG_VSAFE0V_SHIFT 0
+#define MAX77759_USB_EXTENDED_STATUS_REG_VSAFE0V_MASK (0x1 << 0)
+#define MAX77759_USB_EXTENDED_STATUS_REG_VSAFE0V_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_extended_status_reg_rsvd_7_1,7,1)
+MAX77759_BFF(usb_extended_status_reg_vsafe0v,0,0)
+static inline const char *
+max77759_usb_extended_status_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_1=%x",
+ FIELD2VALUE(MAX77759_USB_EXTENDED_STATUS_REG_RSVD_7_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " VSAFE0V=%x",
+ FIELD2VALUE(MAX77759_USB_EXTENDED_STATUS_REG_VSAFE0V, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * ALERT_EXTENDED,0x21,0b00000000,0x00
+ * RSVD_7_3[4:0],,,,,RSVD_2,RSVD_1
+ */
+#define MAX77759_USB_ALERT_EXTENDED 0x21
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_2 (0x1 << 2)
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_1 (0x1 << 1)
+#define MAX77759_USB_ALERT_EXTENDED_SNK_FRS (0x1 << 0)
+
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_7_3_SHIFT 3
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_7_3_MASK (0x1f << 3)
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_7_3_CLEAR (~(0x1f << 3))
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_2_SHIFT 2
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_2_MASK (0x1 << 2)
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_2_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_1_SHIFT 1
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_1_MASK (0x1 << 1)
+#define MAX77759_USB_ALERT_EXTENDED_RSVD_1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_ALERT_EXTENDED_SNK_FRS_SHIFT 0
+#define MAX77759_USB_ALERT_EXTENDED_SNK_FRS_MASK (0x1 << 0)
+#define MAX77759_USB_ALERT_EXTENDED_SNK_FRS_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_alert_extended_rsvd_7_3,7,3)
+MAX77759_BFF(usb_alert_extended_rsvd_2,2,2)
+MAX77759_BFF(usb_alert_extended_rsvd_1,1,1)
+MAX77759_BFF(usb_alert_extended_snk_frs,0,0)
+static inline const char *
+max77759_usb_alert_extended_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_3=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_EXTENDED_RSVD_7_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_2=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_EXTENDED_RSVD_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_1=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_EXTENDED_RSVD_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " SNK_FRS=%x",
+ FIELD2VALUE(MAX77759_USB_ALERT_EXTENDED_SNK_FRS, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * COMMAND_REG,0x23,0b00000000,0x00
+ * COMMAND[7:0],,,,,,
+ */
+#define MAX77759_USB_COMMAND_REG 0x23
+
+/*
+ * DEVICE_CAPABILITIES_1_L,0x24,0b11011000,0xd8
+ * PWR_ROLE_CAP[2:0],,,SOP_DBG_CAP,SRC_VCONN_CAP,SNK_VBUS_CAP,SRC_HI_VBUS_CAP
+ */
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L 0x24
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SOP_DBG_CAP (0x1 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VCONN_CAP (0x1 << 3)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SNK_VBUS_CAP (0x1 << 2)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_HI_VBUS_CAP (0x1 << 1)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VBUS_CAP (0x1 << 0)
+
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_PWR_ROLE_CAP_SHIFT 5
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_PWR_ROLE_CAP_MASK (0x7 << 5)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_PWR_ROLE_CAP_CLEAR (~(0x7 << 5))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SOP_DBG_CAP_SHIFT 4
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SOP_DBG_CAP_MASK (0x1 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SOP_DBG_CAP_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VCONN_CAP_SHIFT 3
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VCONN_CAP_MASK (0x1 << 3)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VCONN_CAP_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SNK_VBUS_CAP_SHIFT 2
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SNK_VBUS_CAP_MASK (0x1 << 2)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SNK_VBUS_CAP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_HI_VBUS_CAP_SHIFT 1
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_HI_VBUS_CAP_MASK (0x1 << 1)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_HI_VBUS_CAP_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VBUS_CAP_SHIFT 0
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VBUS_CAP_MASK (0x1 << 0)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VBUS_CAP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_device_capabilities_1_l_pwr_role_cap,7,5)
+MAX77759_BFF(usb_device_capabilities_1_l_sop_dbg_cap,4,4)
+MAX77759_BFF(usb_device_capabilities_1_l_src_vconn_cap,3,3)
+MAX77759_BFF(usb_device_capabilities_1_l_snk_vbus_cap,2,2)
+MAX77759_BFF(usb_device_capabilities_1_l_src_hi_vbus_cap,1,1)
+MAX77759_BFF(usb_device_capabilities_1_l_src_vbus_cap,0,0)
+static inline const char *
+max77759_usb_device_capabilities_1_l_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " PWR_ROLE_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_L_PWR_ROLE_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SOP_DBG_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_L_SOP_DBG_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_VCONN_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VCONN_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SNK_VBUS_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_L_SNK_VBUS_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_HI_VBUS_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_HI_VBUS_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_VBUS_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_L_SRC_VBUS_CAP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * DEVICE_CAPABILITIES_1_H,0x25,0b00011110,0x1e
+ * VBUS_HI_VOLT_TRGT_CAP,VBUS_OCP_RPT_CAP,VBUS_OVP_RPT_CAP,BLEED_DISCH_CAP,FORCE_DISCH_CAP,VBUS_MEAS_ALRM_CAP,SRC_RES_SUP_CAP[1:0]
+ */
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H 0x25
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_HI_VOLT_TRGT_CAP (0x1 << 7)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OCP_RPT_CAP (0x1 << 6)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OVP_RPT_CAP (0x1 << 5)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_BLEED_DISCH_CAP (0x1 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_FORCE_DISCH_CAP (0x1 << 3)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_MEAS_ALRM_CAP (0x1 << 2)
+
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_HI_VOLT_TRGT_CAP_SHIFT 7
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_HI_VOLT_TRGT_CAP_MASK (0x1 << 7)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_HI_VOLT_TRGT_CAP_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OCP_RPT_CAP_SHIFT 6
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OCP_RPT_CAP_MASK (0x1 << 6)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OCP_RPT_CAP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OVP_RPT_CAP_SHIFT 5
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OVP_RPT_CAP_MASK (0x1 << 5)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OVP_RPT_CAP_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_BLEED_DISCH_CAP_SHIFT 4
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_BLEED_DISCH_CAP_MASK (0x1 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_BLEED_DISCH_CAP_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_FORCE_DISCH_CAP_SHIFT 3
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_FORCE_DISCH_CAP_MASK (0x1 << 3)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_FORCE_DISCH_CAP_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_MEAS_ALRM_CAP_SHIFT 2
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_MEAS_ALRM_CAP_MASK (0x1 << 2)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_MEAS_ALRM_CAP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_SRC_RES_SUP_CAP_SHIFT 0
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_SRC_RES_SUP_CAP_MASK (0x3 << 0)
+#define MAX77759_USB_DEVICE_CAPABILITIES_1_H_SRC_RES_SUP_CAP_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_device_capabilities_1_h_vbus_hi_volt_trgt_cap,7,7)
+MAX77759_BFF(usb_device_capabilities_1_h_vbus_ocp_rpt_cap,6,6)
+MAX77759_BFF(usb_device_capabilities_1_h_vbus_ovp_rpt_cap,5,5)
+MAX77759_BFF(usb_device_capabilities_1_h_bleed_disch_cap,4,4)
+MAX77759_BFF(usb_device_capabilities_1_h_force_disch_cap,3,3)
+MAX77759_BFF(usb_device_capabilities_1_h_vbus_meas_alrm_cap,2,2)
+MAX77759_BFF(usb_device_capabilities_1_h_src_res_sup_cap,1,0)
+static inline const char *
+max77759_usb_device_capabilities_1_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_HI_VOLT_TRGT_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_HI_VOLT_TRGT_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_OCP_RPT_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OCP_RPT_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_OVP_RPT_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_OVP_RPT_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " BLEED_DISCH_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_BLEED_DISCH_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " FORCE_DISCH_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_FORCE_DISCH_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_MEAS_ALRM_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_VBUS_MEAS_ALRM_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_RES_SUP_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_1_H_SRC_RES_SUP_CAP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * DEVICE_CAPABILITIES_2_L,0x26,0b11000001,0xc1
+ * SNK_DISC_DET_CAP,STP_DISCH_THR_CAP,VBUS_VOLT_ALRM_LSB_CAP[1:0],,VCONN_PWR_CAP[2:0],,
+ */
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L 0x26
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_SNK_DISC_DET_CAP (0x1 << 7)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_STP_DISCH_THR_CAP (0x1 << 6)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_OCP_CAP (0x1 << 0)
+
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_SNK_DISC_DET_CAP_SHIFT 7
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_SNK_DISC_DET_CAP_MASK (0x1 << 7)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_SNK_DISC_DET_CAP_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_STP_DISCH_THR_CAP_SHIFT 6
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_STP_DISCH_THR_CAP_MASK (0x1 << 6)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_STP_DISCH_THR_CAP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VBUS_VOLT_ALRM_LSB_CAP_SHIFT 4
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VBUS_VOLT_ALRM_LSB_CAP_MASK (0x3 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VBUS_VOLT_ALRM_LSB_CAP_CLEAR (~(0x3 << 4))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_PWR_CAP_SHIFT 1
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_PWR_CAP_MASK (0x7 << 1)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_PWR_CAP_CLEAR (~(0x7 << 1))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_OCP_CAP_SHIFT 0
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_OCP_CAP_MASK (0x1 << 0)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_OCP_CAP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_device_capabilities_2_l_snk_disc_det_cap,7,7)
+MAX77759_BFF(usb_device_capabilities_2_l_stp_disch_thr_cap,6,6)
+MAX77759_BFF(usb_device_capabilities_2_l_vbus_volt_alrm_lsb_cap,5,4)
+MAX77759_BFF(usb_device_capabilities_2_l_vconn_pwr_cap,3,1)
+MAX77759_BFF(usb_device_capabilities_2_l_vconn_ocp_cap,0,0)
+static inline const char *
+max77759_usb_device_capabilities_2_l_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SNK_DISC_DET_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_L_SNK_DISC_DET_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " STP_DISCH_THR_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_L_STP_DISCH_THR_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_VOLT_ALRM_LSB_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_L_VBUS_VOLT_ALRM_LSB_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCONN_PWR_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_PWR_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCONN_OCP_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_L_VCONN_OCP_CAP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * DEVICE_CAPABILITIES_2_H,0x27,0b00000011,0x03
+ * RSVD_7,MSGDisDisc,GENERIC_TMR_CAP,LONG_MSG_CAP,SMB_PEC_CAP,SRC_FRS_CAP,SNK_FRS_CAP
+ */
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H 0x27
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_RSVD_7 (0x1 << 7)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_MSGDISDISC (0x1 << 6)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_GENERIC_TMR_CAP (0x1 << 5)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_LONG_MSG_CAP (0x1 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SMB_PEC_CAP (0x1 << 3)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SRC_FRS_CAP (0x1 << 2)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SNK_FRS_CAP (0x1 << 1)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_WDOG_TMR_CAP (0x1 << 0)
+
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_RSVD_7_SHIFT 7
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_RSVD_7_MASK (0x1 << 7)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_RSVD_7_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_MSGDISDISC_SHIFT 6
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_MSGDISDISC_MASK (0x1 << 6)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_MSGDISDISC_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_GENERIC_TMR_CAP_SHIFT 5
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_GENERIC_TMR_CAP_MASK (0x1 << 5)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_GENERIC_TMR_CAP_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_LONG_MSG_CAP_SHIFT 4
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_LONG_MSG_CAP_MASK (0x1 << 4)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_LONG_MSG_CAP_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SMB_PEC_CAP_SHIFT 3
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SMB_PEC_CAP_MASK (0x1 << 3)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SMB_PEC_CAP_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SRC_FRS_CAP_SHIFT 2
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SRC_FRS_CAP_MASK (0x1 << 2)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SRC_FRS_CAP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SNK_FRS_CAP_SHIFT 1
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SNK_FRS_CAP_MASK (0x1 << 1)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_SNK_FRS_CAP_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_WDOG_TMR_CAP_SHIFT 0
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_WDOG_TMR_CAP_MASK (0x1 << 0)
+#define MAX77759_USB_DEVICE_CAPABILITIES_2_H_WDOG_TMR_CAP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_device_capabilities_2_h_rsvd_7,7,7)
+MAX77759_BFF(usb_device_capabilities_2_h_msgdisdisc,6,6)
+MAX77759_BFF(usb_device_capabilities_2_h_generic_tmr_cap,5,5)
+MAX77759_BFF(usb_device_capabilities_2_h_long_msg_cap,4,4)
+MAX77759_BFF(usb_device_capabilities_2_h_smb_pec_cap,3,3)
+MAX77759_BFF(usb_device_capabilities_2_h_src_frs_cap,2,2)
+MAX77759_BFF(usb_device_capabilities_2_h_snk_frs_cap,1,1)
+MAX77759_BFF(usb_device_capabilities_2_h_wdog_tmr_cap,0,0)
+static inline const char *
+max77759_usb_device_capabilities_2_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_RSVD_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSGDISDISC=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_MSGDISDISC, val));
+ i += SCNPRINTF(&buff[i], len - i, " GENERIC_TMR_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_GENERIC_TMR_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " LONG_MSG_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_LONG_MSG_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SMB_PEC_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_SMB_PEC_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_FRS_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_SRC_FRS_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SNK_FRS_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_SNK_FRS_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " WDOG_TMR_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_DEVICE_CAPABILITIES_2_H_WDOG_TMR_CAP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * STANDARD_INPUT_CAPABILITIES_REG,0x28,0b00000000,0x00
+ * RSVD_7_5[2:0],,,SRC_FRS_INP_CAP[1:0],,VBUS_EXT_OVP_CAP,VBUS_EXT_OCP_CAP
+ */
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG 0x28
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OVP_CAP (0x1 << 2)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OCP_CAP (0x1 << 1)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_FRC_OFF_VBUS_CAP (0x1 << 0)
+
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_RSVD_7_5_SHIFT 5
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_RSVD_7_5_MASK (0x7 << 5)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_RSVD_7_5_CLEAR (~(0x7 << 5))
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_SRC_FRS_INP_CAP_SHIFT 3
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_SRC_FRS_INP_CAP_MASK (0x3 << 3)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_SRC_FRS_INP_CAP_CLEAR (~(0x3 << 3))
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OVP_CAP_SHIFT 2
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OVP_CAP_MASK (0x1 << 2)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OVP_CAP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OCP_CAP_SHIFT 1
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OCP_CAP_MASK (0x1 << 1)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OCP_CAP_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_FRC_OFF_VBUS_CAP_SHIFT 0
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_FRC_OFF_VBUS_CAP_MASK (0x1 << 0)
+#define MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_FRC_OFF_VBUS_CAP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_standard_input_capabilities_reg_rsvd_7_5,7,5)
+MAX77759_BFF(usb_standard_input_capabilities_reg_src_frs_inp_cap,4,3)
+MAX77759_BFF(usb_standard_input_capabilities_reg_vbus_ext_ovp_cap,2,2)
+MAX77759_BFF(usb_standard_input_capabilities_reg_vbus_ext_ocp_cap,1,1)
+MAX77759_BFF(usb_standard_input_capabilities_reg_frc_off_vbus_cap,0,0)
+static inline const char *
+max77759_usb_standard_input_capabilities_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_5=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_RSVD_7_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " SRC_FRS_INP_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_SRC_FRS_INP_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_EXT_OVP_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OVP_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_EXT_OCP_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_VBUS_EXT_OCP_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " FRC_OFF_VBUS_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_INPUT_CAPABILITIES_REG_FRC_OFF_VBUS_CAP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * STANDARD_OUTPUT_CAPABILITIES_REG,0x29,0b01000000,0x40
+ * VBUS_SNK_DIS_DET_CAP,DBG_ACC_CAP,VBUS_PRESENT_CAP,AUD_ACC_CAP,ACT_CBL_CAP,MUX_CONFG_CAP,CONN_PRESENT_CAP
+ */
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG 0x29
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_SNK_DIS_DET_CAP (0x1 << 7)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_DBG_ACC_CAP (0x1 << 6)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_PRESENT_CAP (0x1 << 5)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_AUD_ACC_CAP (0x1 << 4)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_ACT_CBL_CAP (0x1 << 3)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_MUX_CONFG_CAP (0x1 << 2)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_PRESENT_CAP (0x1 << 1)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_ORIENT_CAP (0x1 << 0)
+
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_SNK_DIS_DET_CAP_SHIFT 7
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_SNK_DIS_DET_CAP_MASK (0x1 << 7)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_SNK_DIS_DET_CAP_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_DBG_ACC_CAP_SHIFT 6
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_DBG_ACC_CAP_MASK (0x1 << 6)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_DBG_ACC_CAP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_PRESENT_CAP_SHIFT 5
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_PRESENT_CAP_MASK (0x1 << 5)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_PRESENT_CAP_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_AUD_ACC_CAP_SHIFT 4
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_AUD_ACC_CAP_MASK (0x1 << 4)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_AUD_ACC_CAP_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_ACT_CBL_CAP_SHIFT 3
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_ACT_CBL_CAP_MASK (0x1 << 3)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_ACT_CBL_CAP_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_MUX_CONFG_CAP_SHIFT 2
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_MUX_CONFG_CAP_MASK (0x1 << 2)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_MUX_CONFG_CAP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_PRESENT_CAP_SHIFT 1
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_PRESENT_CAP_MASK (0x1 << 1)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_PRESENT_CAP_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_ORIENT_CAP_SHIFT 0
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_ORIENT_CAP_MASK (0x1 << 0)
+#define MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_ORIENT_CAP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_standard_output_capabilities_reg_vbus_snk_dis_det_cap,7,7)
+MAX77759_BFF(usb_standard_output_capabilities_reg_dbg_acc_cap,6,6)
+MAX77759_BFF(usb_standard_output_capabilities_reg_vbus_present_cap,5,5)
+MAX77759_BFF(usb_standard_output_capabilities_reg_aud_acc_cap,4,4)
+MAX77759_BFF(usb_standard_output_capabilities_reg_act_cbl_cap,3,3)
+MAX77759_BFF(usb_standard_output_capabilities_reg_mux_confg_cap,2,2)
+MAX77759_BFF(usb_standard_output_capabilities_reg_conn_present_cap,1,1)
+MAX77759_BFF(usb_standard_output_capabilities_reg_conn_orient_cap,0,0)
+static inline const char *
+max77759_usb_standard_output_capabilities_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_SNK_DIS_DET_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_SNK_DIS_DET_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " DBG_ACC_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_DBG_ACC_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_PRESENT_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_VBUS_PRESENT_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " AUD_ACC_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_AUD_ACC_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " ACT_CBL_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_ACT_CBL_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MUX_CONFG_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_MUX_CONFG_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " CONN_PRESENT_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_PRESENT_CAP, val));
+ i += SCNPRINTF(&buff[i], len - i, " CONN_ORIENT_CAP=%x",
+ FIELD2VALUE(MAX77759_USB_STANDARD_OUTPUT_CAPABILITIES_REG_CONN_ORIENT_CAP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * CONFIG_EXTENDED1_REG,0x2A,0b00000000,0x00
+ * RSVD_7_2[5:0],,,,,,RSVD_1
+ */
+#define MAX77759_USB_CONFIG_EXTENDED1_REG 0x2A
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_1 (0x1 << 1)
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_0 (0x1 << 0)
+
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_7_2_SHIFT 2
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_7_2_MASK (0x3f << 2)
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_7_2_CLEAR (~(0x3f << 2))
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_1_SHIFT 1
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_1_MASK (0x1 << 1)
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_0_SHIFT 0
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_0_MASK (0x1 << 0)
+#define MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_0_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_config_extended1_reg_rsvd_7_2,7,2)
+MAX77759_BFF(usb_config_extended1_reg_rsvd_1,1,1)
+MAX77759_BFF(usb_config_extended1_reg_rsvd_0,0,0)
+static inline const char *
+max77759_usb_config_extended1_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_2=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_7_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_1=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_0=%x",
+ FIELD2VALUE(MAX77759_USB_CONFIG_EXTENDED1_REG_RSVD_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * MESSAGE_HEADER_INFO_REG,0x2E,0b00000100,0x04
+ * RSVD_7_5[2:0],,,CBL_PLG,DATA_ROLE,USB_PD[1:0],
+ */
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG 0x2E
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_CBL_PLG (0x1 << 4)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_DATA_ROLE (0x1 << 3)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_PWR_ROLE (0x1 << 0)
+
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_RSVD_7_5_SHIFT 5
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_RSVD_7_5_MASK (0x7 << 5)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_RSVD_7_5_CLEAR (~(0x7 << 5))
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_CBL_PLG_SHIFT 4
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_CBL_PLG_MASK (0x1 << 4)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_CBL_PLG_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_DATA_ROLE_SHIFT 3
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_DATA_ROLE_MASK (0x1 << 3)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_DATA_ROLE_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_USB_PD_SHIFT 1
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_USB_PD_MASK (0x3 << 1)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_USB_PD_CLEAR (~(0x3 << 1))
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_PWR_ROLE_SHIFT 0
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_PWR_ROLE_MASK (0x1 << 0)
+#define MAX77759_USB_MESSAGE_HEADER_INFO_REG_PWR_ROLE_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_message_header_info_reg_rsvd_7_5,7,5)
+MAX77759_BFF(usb_message_header_info_reg_cbl_plg,4,4)
+MAX77759_BFF(usb_message_header_info_reg_data_role,3,3)
+MAX77759_BFF(usb_message_header_info_reg_usb_pd,2,1)
+MAX77759_BFF(usb_message_header_info_reg_pwr_role,0,0)
+static inline const char *
+max77759_usb_message_header_info_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_5=%x",
+ FIELD2VALUE(MAX77759_USB_MESSAGE_HEADER_INFO_REG_RSVD_7_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " CBL_PLG=%x",
+ FIELD2VALUE(MAX77759_USB_MESSAGE_HEADER_INFO_REG_CBL_PLG, val));
+ i += SCNPRINTF(&buff[i], len - i, " DATA_ROLE=%x",
+ FIELD2VALUE(MAX77759_USB_MESSAGE_HEADER_INFO_REG_DATA_ROLE, val));
+ i += SCNPRINTF(&buff[i], len - i, " USB_PD=%x",
+ FIELD2VALUE(MAX77759_USB_MESSAGE_HEADER_INFO_REG_USB_PD, val));
+ i += SCNPRINTF(&buff[i], len - i, " PWR_ROLE=%x",
+ FIELD2VALUE(MAX77759_USB_MESSAGE_HEADER_INFO_REG_PWR_ROLE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * RECEIVE_DETECT_REG,0x2F,0b00000000,0x00
+ * RSVD_7,EN_CBL_RST,EN_HRD_RST,EN_SOP_DBG2,EN_SOP_DBG1,EN_SOP2,EN_SOP1
+ */
+#define MAX77759_USB_RECEIVE_DETECT_REG 0x2F
+#define MAX77759_USB_RECEIVE_DETECT_REG_RSVD_7 (0x1 << 7)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_CBL_RST (0x1 << 6)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_HRD_RST (0x1 << 5)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG2 (0x1 << 4)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG1 (0x1 << 3)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP2 (0x1 << 2)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP1 (0x1 << 1)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP (0x1 << 0)
+
+#define MAX77759_USB_RECEIVE_DETECT_REG_RSVD_7_SHIFT 7
+#define MAX77759_USB_RECEIVE_DETECT_REG_RSVD_7_MASK (0x1 << 7)
+#define MAX77759_USB_RECEIVE_DETECT_REG_RSVD_7_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_CBL_RST_SHIFT 6
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_CBL_RST_MASK (0x1 << 6)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_CBL_RST_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_HRD_RST_SHIFT 5
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_HRD_RST_MASK (0x1 << 5)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_HRD_RST_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG2_SHIFT 4
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG2_MASK (0x1 << 4)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG2_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG1_SHIFT 3
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG1_MASK (0x1 << 3)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG1_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP2_SHIFT 2
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP2_MASK (0x1 << 2)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP2_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP1_SHIFT 1
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP1_MASK (0x1 << 1)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_SHIFT 0
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_MASK (0x1 << 0)
+#define MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_receive_detect_reg_rsvd_7,7,7)
+MAX77759_BFF(usb_receive_detect_reg_en_cbl_rst,6,6)
+MAX77759_BFF(usb_receive_detect_reg_en_hrd_rst,5,5)
+MAX77759_BFF(usb_receive_detect_reg_en_sop_dbg2,4,4)
+MAX77759_BFF(usb_receive_detect_reg_en_sop_dbg1,3,3)
+MAX77759_BFF(usb_receive_detect_reg_en_sop2,2,2)
+MAX77759_BFF(usb_receive_detect_reg_en_sop1,1,1)
+MAX77759_BFF(usb_receive_detect_reg_en_sop,0,0)
+static inline const char *
+max77759_usb_receive_detect_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_RSVD_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_CBL_RST=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_CBL_RST, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_HRD_RST=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_HRD_RST, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_SOP_DBG2=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG2, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_SOP_DBG1=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP_DBG1, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_SOP2=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP2, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_SOP1=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP1, val));
+ i += SCNPRINTF(&buff[i], len - i, " EN_SOP=%x",
+ FIELD2VALUE(MAX77759_USB_RECEIVE_DETECT_REG_EN_SOP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * RECEIVE_BUFFER_REG,0x30,0b00000000,0x00
+ * RECEIVE_BUFFER[7:0],,,,,,
+ */
+#define MAX77759_USB_RECEIVE_BUFFER_REG 0x30
+
+/*
+ * TRANSMIT_REG,0x50,0b00000000,0x00
+ * RSVD_7_6[1:0],,RETRY_COUNTER[1:0],,RSVD_3,TX_SOP_MESSAGE[2:0],
+ */
+#define MAX77759_USB_TRANSMIT_REG 0x50
+#define MAX77759_USB_TRANSMIT_REG_RSVD_3 (0x1 << 3)
+
+#define MAX77759_USB_TRANSMIT_REG_RSVD_7_6_SHIFT 6
+#define MAX77759_USB_TRANSMIT_REG_RSVD_7_6_MASK (0x3 << 6)
+#define MAX77759_USB_TRANSMIT_REG_RSVD_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_TRANSMIT_REG_RETRY_COUNTER_SHIFT 4
+#define MAX77759_USB_TRANSMIT_REG_RETRY_COUNTER_MASK (0x3 << 4)
+#define MAX77759_USB_TRANSMIT_REG_RETRY_COUNTER_CLEAR (~(0x3 << 4))
+#define MAX77759_USB_TRANSMIT_REG_RSVD_3_SHIFT 3
+#define MAX77759_USB_TRANSMIT_REG_RSVD_3_MASK (0x1 << 3)
+#define MAX77759_USB_TRANSMIT_REG_RSVD_3_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_TRANSMIT_REG_TX_SOP_MESSAGE_SHIFT 0
+#define MAX77759_USB_TRANSMIT_REG_TX_SOP_MESSAGE_MASK (0x7 << 0)
+#define MAX77759_USB_TRANSMIT_REG_TX_SOP_MESSAGE_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(usb_transmit_reg_rsvd_7_6,7,6)
+MAX77759_BFF(usb_transmit_reg_retry_counter,5,4)
+MAX77759_BFF(usb_transmit_reg_rsvd_3,3,3)
+MAX77759_BFF(usb_transmit_reg_tx_sop_message,2,0)
+static inline const char *
+max77759_usb_transmit_reg_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_6=%x",
+ FIELD2VALUE(MAX77759_USB_TRANSMIT_REG_RSVD_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " RETRY_COUNTER=%x",
+ FIELD2VALUE(MAX77759_USB_TRANSMIT_REG_RETRY_COUNTER, val));
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_3=%x",
+ FIELD2VALUE(MAX77759_USB_TRANSMIT_REG_RSVD_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " TX_SOP_MESSAGE=%x",
+ FIELD2VALUE(MAX77759_USB_TRANSMIT_REG_TX_SOP_MESSAGE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * TRANSMIT_BUFFER_REG,0x51,0b00000000,0x00
+ * TRANSMIT_BUFFER[7:0],,,,,,
+ */
+#define MAX77759_USB_TRANSMIT_BUFFER_REG 0x51
+
+/*
+ * VBUS_VOLTAGE_L,0x70,0b00000000,0x00
+ * VBUS_VOLTAGE7_0[7:0],,,,,,
+ */
+#define MAX77759_USB_VBUS_VOLTAGE_L 0x70
+
+/*
+ * VBUS_VOLTAGE_H,0x71,0b00000000,0x00
+ * RSVD_7_4[3:0],,,,SCALE_FACTOR[1:0],,VBUS_VOLTAGE9_8[1:0]
+ */
+#define MAX77759_USB_VBUS_VOLTAGE_H 0x71
+
+#define MAX77759_USB_VBUS_VOLTAGE_H_RSVD_7_4_SHIFT 4
+#define MAX77759_USB_VBUS_VOLTAGE_H_RSVD_7_4_MASK (0xf << 4)
+#define MAX77759_USB_VBUS_VOLTAGE_H_RSVD_7_4_CLEAR (~(0xf << 4))
+#define MAX77759_USB_VBUS_VOLTAGE_H_SCALE_FACTOR_SHIFT 2
+#define MAX77759_USB_VBUS_VOLTAGE_H_SCALE_FACTOR_MASK (0x3 << 2)
+#define MAX77759_USB_VBUS_VOLTAGE_H_SCALE_FACTOR_CLEAR (~(0x3 << 2))
+#define MAX77759_USB_VBUS_VOLTAGE_H_VBUS_VOLTAGE9_8_SHIFT 0
+#define MAX77759_USB_VBUS_VOLTAGE_H_VBUS_VOLTAGE9_8_MASK (0x3 << 0)
+#define MAX77759_USB_VBUS_VOLTAGE_H_VBUS_VOLTAGE9_8_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vbus_voltage_h_rsvd_7_4,7,4)
+MAX77759_BFF(usb_vbus_voltage_h_scale_factor,3,2)
+MAX77759_BFF(usb_vbus_voltage_h_vbus_voltage9_8,1,0)
+static inline const char *
+max77759_usb_vbus_voltage_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_4=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_H_RSVD_7_4, val));
+ i += SCNPRINTF(&buff[i], len - i, " SCALE_FACTOR=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_H_SCALE_FACTOR, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_VOLTAGE9_8=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_H_VBUS_VOLTAGE9_8, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VBUS_SNK_DISC_L,0x72,0b10001100,0x8c
+ * VBUS_SNK_DISC7_0[7:0],,,,,,
+ */
+#define MAX77759_USB_VBUS_SNK_DISC_L 0x72
+
+/*
+ * VBUS_SNK_DISC_H,0x73,0b00000000,0x00
+ * RSVD_7_2[5:0],,,,,,VBUS_SNK_DISC9_8[1:0]
+ */
+#define MAX77759_USB_VBUS_SNK_DISC_H 0x73
+
+#define MAX77759_USB_VBUS_SNK_DISC_H_RSVD_7_2_SHIFT 2
+#define MAX77759_USB_VBUS_SNK_DISC_H_RSVD_7_2_MASK (0x3f << 2)
+#define MAX77759_USB_VBUS_SNK_DISC_H_RSVD_7_2_CLEAR (~(0x3f << 2))
+#define MAX77759_USB_VBUS_SNK_DISC_H_VBUS_SNK_DISC9_8_SHIFT 0
+#define MAX77759_USB_VBUS_SNK_DISC_H_VBUS_SNK_DISC9_8_MASK (0x3 << 0)
+#define MAX77759_USB_VBUS_SNK_DISC_H_VBUS_SNK_DISC9_8_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vbus_snk_disc_h_rsvd_7_2,7,2)
+MAX77759_BFF(usb_vbus_snk_disc_h_vbus_snk_disc9_8,1,0)
+static inline const char *
+max77759_usb_vbus_snk_disc_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_2=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_SNK_DISC_H_RSVD_7_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_SNK_DISC9_8=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_SNK_DISC_H_VBUS_SNK_DISC9_8, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VBUS_STOP_DISCHARGE_THRESHOLD_L,0x74,0b00100000,0x20
+ * VBUS_STOP_DISCH_THRESHOLD7_0[7:0],,,,,,
+ */
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_L 0x74
+
+/*
+ * VBUS_STOP_DISCHARGE_THRESHOLD_H,0x75,0b00000000,0x00
+ * RSVD_7_2[5:0],,,,,,VBUS_STOP_DISCH_THRESHOLD9_8[1:0]
+ */
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H 0x75
+
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_RSVD_7_2_SHIFT 2
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_RSVD_7_2_MASK (0x3f << 2)
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_RSVD_7_2_CLEAR (~(0x3f << 2))
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_VBUS_STOP_DISCH_THRESHOLD9_8_SHIFT 0
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_VBUS_STOP_DISCH_THRESHOLD9_8_MASK (0x3 << 0)
+#define MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_VBUS_STOP_DISCH_THRESHOLD9_8_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vbus_stop_discharge_threshold_h_rsvd_7_2,7,2)
+MAX77759_BFF(usb_vbus_stop_discharge_threshold_h_vbus_stop_disch_threshold9_8,1,0)
+static inline const char *
+max77759_usb_vbus_stop_discharge_threshold_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_2=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_RSVD_7_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_STOP_DISCH_THRESHOLD9_8=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_STOP_DISCHARGE_THRESHOLD_H_VBUS_STOP_DISCH_THRESHOLD9_8, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VBUS_VOLTAGE_ALARM_HI_CFG_L,0x76,0b00000000,0x00
+ * VBUS_ALARM_HI_CFG7_0[7:0],,,,,,
+ */
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_L 0x76
+
+/*
+ * VBUS_VOLTAGE_ALARM_HI_CFG_H,0x77,0b00000000,0x00
+ * RSVD_7_2[5:0],,,,,,VBUS_ALARM_HI_CFG9_8[1:0]
+ */
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H 0x77
+
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_RSVD_7_2_SHIFT 2
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_RSVD_7_2_MASK (0x3f << 2)
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_RSVD_7_2_CLEAR (~(0x3f << 2))
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_VBUS_ALARM_HI_CFG9_8_SHIFT 0
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_VBUS_ALARM_HI_CFG9_8_MASK (0x3 << 0)
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_VBUS_ALARM_HI_CFG9_8_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vbus_voltage_alarm_hi_cfg_h_rsvd_7_2,7,2)
+MAX77759_BFF(usb_vbus_voltage_alarm_hi_cfg_h_vbus_alarm_hi_cfg9_8,1,0)
+static inline const char *
+max77759_usb_vbus_voltage_alarm_hi_cfg_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_2=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_RSVD_7_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_ALARM_HI_CFG9_8=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_ALARM_HI_CFG_H_VBUS_ALARM_HI_CFG9_8, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VBUS_VOLTAGE_ALARM_LO_CFG_L,0x78,0b00000000,0x00
+ * VBUS_ALARM_LO_CFG_7_0[7:0],,,,,,
+ */
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_L 0x78
+
+/*
+ * VBUS_VOLTAGE_ALARM_LO_CFG_H,0x79,0b00000000,0x00
+ * RSVD_7_2[5:0],,,,,,VBUS_ALARM_LO_CFG9_8[1:0]
+ */
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H 0x79
+
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_RSVD_7_2_SHIFT 2
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_RSVD_7_2_MASK (0x3f << 2)
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_RSVD_7_2_CLEAR (~(0x3f << 2))
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_VBUS_ALARM_LO_CFG9_8_SHIFT 0
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_VBUS_ALARM_LO_CFG9_8_MASK (0x3 << 0)
+#define MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_VBUS_ALARM_LO_CFG9_8_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vbus_voltage_alarm_lo_cfg_h_rsvd_7_2,7,2)
+MAX77759_BFF(usb_vbus_voltage_alarm_lo_cfg_h_vbus_alarm_lo_cfg9_8,1,0)
+static inline const char *
+max77759_usb_vbus_voltage_alarm_lo_cfg_h_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_2=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_RSVD_7_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " VBUS_ALARM_LO_CFG9_8=%x",
+ FIELD2VALUE(MAX77759_USB_VBUS_VOLTAGE_ALARM_LO_CFG_H_VBUS_ALARM_LO_CFG9_8, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_ALERT1,0x80,0b00000000,0x00
+ * SPR_7_6[1:0],,dnVdatRefInt,chgTypRunFInt,chgTypRunRInt,prChgTypInt,dcdTmoInt
+ */
+#define MAX77759_USB_VENDOR_ALERT1 0x80
+#define MAX77759_USB_VENDOR_ALERT1_DNVDATREFINT (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNFINT (0x1 << 4)
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNRINT (0x1 << 3)
+#define MAX77759_USB_VENDOR_ALERT1_PRCHGTYPINT (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT1_DCDTMOINT (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPINT (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_ALERT1_SPR_7_6_SHIFT 6
+#define MAX77759_USB_VENDOR_ALERT1_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_USB_VENDOR_ALERT1_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_VENDOR_ALERT1_DNVDATREFINT_SHIFT 5
+#define MAX77759_USB_VENDOR_ALERT1_DNVDATREFINT_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT1_DNVDATREFINT_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNFINT_SHIFT 4
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNFINT_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNFINT_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNRINT_SHIFT 3
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNRINT_MASK (0x1 << 3)
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNRINT_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_VENDOR_ALERT1_PRCHGTYPINT_SHIFT 2
+#define MAX77759_USB_VENDOR_ALERT1_PRCHGTYPINT_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT1_PRCHGTYPINT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_ALERT1_DCDTMOINT_SHIFT 1
+#define MAX77759_USB_VENDOR_ALERT1_DCDTMOINT_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT1_DCDTMOINT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPINT_SHIFT 0
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPINT_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_ALERT1_CHGTYPINT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_alert1_spr_7_6,7,6)
+MAX77759_BFF(usb_vendor_alert1_dnvdatrefint,5,5)
+MAX77759_BFF(usb_vendor_alert1_chgtyprunfint,4,4)
+MAX77759_BFF(usb_vendor_alert1_chgtyprunrint,3,3)
+MAX77759_BFF(usb_vendor_alert1_prchgtypint,2,2)
+MAX77759_BFF(usb_vendor_alert1_dcdtmoint,1,1)
+MAX77759_BFF(usb_vendor_alert1_chgtypint,0,0)
+static inline const char *
+max77759_usb_vendor_alert1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNVDATREFINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_DNVDATREFINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGTYPRUNFINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNFINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGTYPRUNRINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_CHGTYPRUNRINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " PRCHGTYPINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_PRCHGTYPINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " DCDTMOINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_DCDTMOINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGTYPINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT1_CHGTYPINT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_ALERT2,0x81,0b00000000,0x00
+ * SBU_OVPint,USB_OVPint,CC_OVPint,SPR_4_3[1:0],,CCVCNSCInt,FLASH_ADCInt
+ */
+#define MAX77759_USB_VENDOR_ALERT2 0x81
+#define MAX77759_USB_VENDOR_ALERT2_SBU_OVPINT (0x1 << 7)
+#define MAX77759_USB_VENDOR_ALERT2_USB_OVPINT (0x1 << 6)
+#define MAX77759_USB_VENDOR_ALERT2_CC_OVPINT (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT2_CCVCNSCINT (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT2_FLASH_ADCINT (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT2_SPR_0 (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_ALERT2_SBU_OVPINT_SHIFT 7
+#define MAX77759_USB_VENDOR_ALERT2_SBU_OVPINT_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_ALERT2_SBU_OVPINT_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_ALERT2_USB_OVPINT_SHIFT 6
+#define MAX77759_USB_VENDOR_ALERT2_USB_OVPINT_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_ALERT2_USB_OVPINT_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_ALERT2_CC_OVPINT_SHIFT 5
+#define MAX77759_USB_VENDOR_ALERT2_CC_OVPINT_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT2_CC_OVPINT_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_ALERT2_SPR_4_3_SHIFT 3
+#define MAX77759_USB_VENDOR_ALERT2_SPR_4_3_MASK (0x3 << 3)
+#define MAX77759_USB_VENDOR_ALERT2_SPR_4_3_CLEAR (~(0x3 << 3))
+#define MAX77759_USB_VENDOR_ALERT2_CCVCNSCINT_SHIFT 2
+#define MAX77759_USB_VENDOR_ALERT2_CCVCNSCINT_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT2_CCVCNSCINT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_ALERT2_FLASH_ADCINT_SHIFT 1
+#define MAX77759_USB_VENDOR_ALERT2_FLASH_ADCINT_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT2_FLASH_ADCINT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_ALERT2_SPR_0_SHIFT 0
+#define MAX77759_USB_VENDOR_ALERT2_SPR_0_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_ALERT2_SPR_0_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_alert2_sbu_ovpint,7,7)
+MAX77759_BFF(usb_vendor_alert2_usb_ovpint,6,6)
+MAX77759_BFF(usb_vendor_alert2_cc_ovpint,5,5)
+MAX77759_BFF(usb_vendor_alert2_spr_4_3,4,3)
+MAX77759_BFF(usb_vendor_alert2_ccvcnscint,2,2)
+MAX77759_BFF(usb_vendor_alert2_flash_adcint,1,1)
+MAX77759_BFF(usb_vendor_alert2_spr_0,0,0)
+static inline const char *
+max77759_usb_vendor_alert2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SBU_OVPINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_SBU_OVPINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " USB_OVPINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_USB_OVPINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC_OVPINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_CC_OVPINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_4_3=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_SPR_4_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCVCNSCINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_CCVCNSCINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " FLASH_ADCINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_FLASH_ADCINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_0=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT2_SPR_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_ALERT_MASK1,0x82,0b11111111,0xff
+ * SPR_7_6[1:0],,MSK_dnVDatRef,MSK_chgTypRunF,MSK_chgTypRunR,MSK_PrchgTyp,MSK_dcdTmo
+ */
+#define MAX77759_USB_VENDOR_ALERT_MASK1 0x82
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DNVDATREF (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNF (0x1 << 4)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNR (0x1 << 3)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_PRCHGTYP (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DCDTMO (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYP (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_ALERT_MASK1_SPR_7_6_SHIFT 6
+#define MAX77759_USB_VENDOR_ALERT_MASK1_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DNVDATREF_SHIFT 5
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DNVDATREF_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DNVDATREF_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNF_SHIFT 4
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNF_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNF_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNR_SHIFT 3
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNR_MASK (0x1 << 3)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNR_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_PRCHGTYP_SHIFT 2
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_PRCHGTYP_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_PRCHGTYP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DCDTMO_SHIFT 1
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DCDTMO_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DCDTMO_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYP_SHIFT 0
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYP_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYP_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_alert_mask1_spr_7_6,7,6)
+MAX77759_BFF(usb_vendor_alert_mask1_msk_dnvdatref,5,5)
+MAX77759_BFF(usb_vendor_alert_mask1_msk_chgtyprunf,4,4)
+MAX77759_BFF(usb_vendor_alert_mask1_msk_chgtyprunr,3,3)
+MAX77759_BFF(usb_vendor_alert_mask1_msk_prchgtyp,2,2)
+MAX77759_BFF(usb_vendor_alert_mask1_msk_dcdtmo,1,1)
+MAX77759_BFF(usb_vendor_alert_mask1_msk_chgtyp,0,0)
+static inline const char *
+max77759_usb_vendor_alert_mask1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_DNVDATREF=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DNVDATREF, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_CHGTYPRUNF=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNF, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_CHGTYPRUNR=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYPRUNR, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_PRCHGTYP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_MSK_PRCHGTYP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_DCDTMO=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_MSK_DCDTMO, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_CHGTYP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK1_MSK_CHGTYP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_ALERT_MASK2,0x83,0b11111111,0xff
+ * MSK_SBU_OVP,MSK_USB_OVP,MSK_CC_OVP,SPR_4_3[1:0],,MSK_CCVCNSCInt,MSK_FLASH_ADCInt
+ */
+#define MAX77759_USB_VENDOR_ALERT_MASK2 0x83
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_SBU_OVP (0x1 << 7)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_USB_OVP (0x1 << 6)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CC_OVP (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CCVCNSCINT (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_FLASH_ADCINT (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_0 (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_SBU_OVP_SHIFT 7
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_SBU_OVP_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_SBU_OVP_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_USB_OVP_SHIFT 6
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_USB_OVP_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_USB_OVP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CC_OVP_SHIFT 5
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CC_OVP_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CC_OVP_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_4_3_SHIFT 3
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_4_3_MASK (0x3 << 3)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_4_3_CLEAR (~(0x3 << 3))
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CCVCNSCINT_SHIFT 2
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CCVCNSCINT_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CCVCNSCINT_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_FLASH_ADCINT_SHIFT 1
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_FLASH_ADCINT_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_MSK_FLASH_ADCINT_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_0_SHIFT 0
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_0_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_ALERT_MASK2_SPR_0_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_alert_mask2_msk_sbu_ovp,7,7)
+MAX77759_BFF(usb_vendor_alert_mask2_msk_usb_ovp,6,6)
+MAX77759_BFF(usb_vendor_alert_mask2_msk_cc_ovp,5,5)
+MAX77759_BFF(usb_vendor_alert_mask2_spr_4_3,4,3)
+MAX77759_BFF(usb_vendor_alert_mask2_msk_ccvcnscint,2,2)
+MAX77759_BFF(usb_vendor_alert_mask2_msk_flash_adcint,1,1)
+MAX77759_BFF(usb_vendor_alert_mask2_spr_0,0,0)
+static inline const char *
+max77759_usb_vendor_alert_mask2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " MSK_SBU_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_MSK_SBU_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_USB_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_MSK_USB_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_CC_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CC_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_4_3=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_SPR_4_3, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_CCVCNSCINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_MSK_CCVCNSCINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " MSK_FLASH_ADCINT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_MSK_FLASH_ADCINT, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_0=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ALERT_MASK2_SPR_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_CC_STATUS1,0x84,0b00000000,0x00
+ * SBU2_OVP,SBU1_OVP,USBDP_OVP,USBDN_OVP,CC2_OVP,CC1_OVP,SPR_1_0[1:0]
+ */
+#define MAX77759_USB_VENDOR_CC_STATUS1 0x84
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU2_OVP (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU1_OVP (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDP_OVP (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDN_OVP (0x1 << 4)
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC2_OVP (0x1 << 3)
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC1_OVP (0x1 << 2)
+
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU2_OVP_SHIFT 7
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU2_OVP_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU2_OVP_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU1_OVP_SHIFT 6
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU1_OVP_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_STATUS1_SBU1_OVP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDP_OVP_SHIFT 5
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDP_OVP_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDP_OVP_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDN_OVP_SHIFT 4
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDN_OVP_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_CC_STATUS1_USBDN_OVP_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC2_OVP_SHIFT 3
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC2_OVP_MASK (0x1 << 3)
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC2_OVP_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC1_OVP_SHIFT 2
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC1_OVP_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_CC_STATUS1_CC1_OVP_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_CC_STATUS1_SPR_1_0_SHIFT 0
+#define MAX77759_USB_VENDOR_CC_STATUS1_SPR_1_0_MASK (0x3 << 0)
+#define MAX77759_USB_VENDOR_CC_STATUS1_SPR_1_0_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vendor_cc_status1_sbu2_ovp,7,7)
+MAX77759_BFF(usb_vendor_cc_status1_sbu1_ovp,6,6)
+MAX77759_BFF(usb_vendor_cc_status1_usbdp_ovp,5,5)
+MAX77759_BFF(usb_vendor_cc_status1_usbdn_ovp,4,4)
+MAX77759_BFF(usb_vendor_cc_status1_cc2_ovp,3,3)
+MAX77759_BFF(usb_vendor_cc_status1_cc1_ovp,2,2)
+MAX77759_BFF(usb_vendor_cc_status1_spr_1_0,1,0)
+static inline const char *
+max77759_usb_vendor_cc_status1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SBU2_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_SBU2_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SBU1_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_SBU1_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " USBDP_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_USBDP_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " USBDN_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_USBDN_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC2_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_CC2_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1_OVP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_CC1_OVP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_1_0=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS1_SPR_1_0, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_CC_STATUS2,0x85,0b00000000,0x00
+ * CC2_VDFP_OPEN,CC2_VUFP_RD1P5,CC2_VUFP_RD0P5,CC2_VRA_RD0P5,CC1_VDFP_OPEN,CC1_VUFP_RD1P5,CC1_VUFP_RD0P5
+ */
+#define MAX77759_USB_VENDOR_CC_STATUS2 0x85
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VDFP_OPEN (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD1P5 (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD0P5 (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VRA_RD0P5 (0x1 << 4)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VDFP_OPEN (0x1 << 3)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD1P5 (0x1 << 2)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD0P5 (0x1 << 1)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VRA_RD0P5 (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VDFP_OPEN_SHIFT 7
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VDFP_OPEN_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VDFP_OPEN_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD1P5_SHIFT 6
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD1P5_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD1P5_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD0P5_SHIFT 5
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD0P5_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD0P5_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VRA_RD0P5_SHIFT 4
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VRA_RD0P5_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC2_VRA_RD0P5_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VDFP_OPEN_SHIFT 3
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VDFP_OPEN_MASK (0x1 << 3)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VDFP_OPEN_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD1P5_SHIFT 2
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD1P5_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD1P5_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD0P5_SHIFT 1
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD0P5_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD0P5_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VRA_RD0P5_SHIFT 0
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VRA_RD0P5_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_CC_STATUS2_CC1_VRA_RD0P5_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_cc_status2_cc2_vdfp_open,7,7)
+MAX77759_BFF(usb_vendor_cc_status2_cc2_vufp_rd1p5,6,6)
+MAX77759_BFF(usb_vendor_cc_status2_cc2_vufp_rd0p5,5,5)
+MAX77759_BFF(usb_vendor_cc_status2_cc2_vra_rd0p5,4,4)
+MAX77759_BFF(usb_vendor_cc_status2_cc1_vdfp_open,3,3)
+MAX77759_BFF(usb_vendor_cc_status2_cc1_vufp_rd1p5,2,2)
+MAX77759_BFF(usb_vendor_cc_status2_cc1_vufp_rd0p5,1,1)
+MAX77759_BFF(usb_vendor_cc_status2_cc1_vra_rd0p5,0,0)
+static inline const char *
+max77759_usb_vendor_cc_status2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " CC2_VDFP_OPEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC2_VDFP_OPEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC2_VUFP_RD1P5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD1P5, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC2_VUFP_RD0P5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC2_VUFP_RD0P5, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC2_VRA_RD0P5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC2_VRA_RD0P5, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1_VDFP_OPEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC1_VDFP_OPEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1_VUFP_RD1P5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD1P5, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1_VUFP_RD0P5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC1_VUFP_RD0P5, val));
+ i += SCNPRINTF(&buff[i], len - i, " CC1_VRA_RD0P5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS2_CC1_VRA_RD0P5, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_CC_STATUS3,0x86,0b00000000,0x00
+ * ccVcnSc,ccVcnOCP,SPR_5_1[4:0],,,,
+ */
+#define MAX77759_USB_VENDOR_CC_STATUS3 0x86
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSC (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNOCP (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSTAT (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSC_SHIFT 7
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSC_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSC_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNOCP_SHIFT 6
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNOCP_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNOCP_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_CC_STATUS3_SPR_5_1_SHIFT 1
+#define MAX77759_USB_VENDOR_CC_STATUS3_SPR_5_1_MASK (0x1f << 1)
+#define MAX77759_USB_VENDOR_CC_STATUS3_SPR_5_1_CLEAR (~(0x1f << 1))
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSTAT_SHIFT 0
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSTAT_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSTAT_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_cc_status3_ccvcnsc,7,7)
+MAX77759_BFF(usb_vendor_cc_status3_ccvcnocp,6,6)
+MAX77759_BFF(usb_vendor_cc_status3_spr_5_1,5,1)
+MAX77759_BFF(usb_vendor_cc_status3_ccvcnstat,0,0)
+static inline const char *
+max77759_usb_vendor_cc_status3_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " CCVCNSC=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSC, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCVCNOCP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS3_CCVCNOCP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_5_1=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS3_SPR_5_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCVCNSTAT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_STATUS3_CCVCNSTAT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_BC_STATUS1,0x87,0b00000000,0x00
+ * SPR_7,ChgTypRun,PrChgTyp[2:0],,,DCDTmo,ChgTyp[1:0]
+ */
+#define MAX77759_USB_VENDOR_BC_STATUS1 0x87
+#define MAX77759_USB_VENDOR_BC_STATUS1_SPR_7 (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYPRUN (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_STATUS1_DCDTMO (0x1 << 2)
+
+#define MAX77759_USB_VENDOR_BC_STATUS1_SPR_7_SHIFT 7
+#define MAX77759_USB_VENDOR_BC_STATUS1_SPR_7_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_STATUS1_SPR_7_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYPRUN_SHIFT 6
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYPRUN_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYPRUN_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_BC_STATUS1_PRCHGTYP_SHIFT 3
+#define MAX77759_USB_VENDOR_BC_STATUS1_PRCHGTYP_MASK (0x7 << 3)
+#define MAX77759_USB_VENDOR_BC_STATUS1_PRCHGTYP_CLEAR (~(0x7 << 3))
+#define MAX77759_USB_VENDOR_BC_STATUS1_DCDTMO_SHIFT 2
+#define MAX77759_USB_VENDOR_BC_STATUS1_DCDTMO_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_BC_STATUS1_DCDTMO_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYP_SHIFT 0
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYP_MASK (0x3 << 0)
+#define MAX77759_USB_VENDOR_BC_STATUS1_CHGTYP_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vendor_bc_status1_spr_7,7,7)
+MAX77759_BFF(usb_vendor_bc_status1_chgtyprun,6,6)
+MAX77759_BFF(usb_vendor_bc_status1_prchgtyp,5,3)
+MAX77759_BFF(usb_vendor_bc_status1_dcdtmo,2,2)
+MAX77759_BFF(usb_vendor_bc_status1_chgtyp,1,0)
+static inline const char *
+max77759_usb_vendor_bc_status1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS1_SPR_7, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGTYPRUN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS1_CHGTYPRUN, val));
+ i += SCNPRINTF(&buff[i], len - i, " PRCHGTYP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS1_PRCHGTYP, val));
+ i += SCNPRINTF(&buff[i], len - i, " DCDTMO=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS1_DCDTMO, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGTYP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS1_CHGTYP, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_BC_STATUS2,0x88,0b00000000,0x00
+ * dnDebOk,SPR_6,dnVlgc,dnVdatRef,dpDebOk,SPR_2,dpVlgc
+ */
+#define MAX77759_USB_VENDOR_BC_STATUS2 0x88
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNDEBOK (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_6 (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVLGC (0x1 << 5)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVDATREF (0x1 << 4)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPDEBOK (0x1 << 3)
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_2 (0x1 << 2)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVLGC (0x1 << 1)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVDATREF (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNDEBOK_SHIFT 7
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNDEBOK_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNDEBOK_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_6_SHIFT 6
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_6_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_6_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVLGC_SHIFT 5
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVLGC_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVLGC_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVDATREF_SHIFT 4
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVDATREF_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DNVDATREF_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPDEBOK_SHIFT 3
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPDEBOK_MASK (0x1 << 3)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPDEBOK_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_2_SHIFT 2
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_2_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_BC_STATUS2_SPR_2_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVLGC_SHIFT 1
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVLGC_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVLGC_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVDATREF_SHIFT 0
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVDATREF_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_BC_STATUS2_DPVDATREF_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_bc_status2_dndebok,7,7)
+MAX77759_BFF(usb_vendor_bc_status2_spr_6,6,6)
+MAX77759_BFF(usb_vendor_bc_status2_dnvlgc,5,5)
+MAX77759_BFF(usb_vendor_bc_status2_dnvdatref,4,4)
+MAX77759_BFF(usb_vendor_bc_status2_dpdebok,3,3)
+MAX77759_BFF(usb_vendor_bc_status2_spr_2,2,2)
+MAX77759_BFF(usb_vendor_bc_status2_dpvlgc,1,1)
+MAX77759_BFF(usb_vendor_bc_status2_dpvdatref,0,0)
+static inline const char *
+max77759_usb_vendor_bc_status2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " DNDEBOK=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_DNDEBOK, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_SPR_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNVLGC=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_DNVLGC, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNVDATREF=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_DNVDATREF, val));
+ i += SCNPRINTF(&buff[i], len - i, " DPDEBOK=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_DPDEBOK, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_2=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_SPR_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " DPVLGC=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_DPVLGC, val));
+ i += SCNPRINTF(&buff[i], len - i, " DPVDATREF=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_STATUS2_DPVDATREF, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_FLADC_STATUS,0x89,0b00000000,0x00
+ * ADC_VAL[7:0],,,,,,
+ */
+#define MAX77759_USB_VENDOR_FLADC_STATUS 0x89
+
+/*
+ * VENDOR_WDG_CTRL,0x8A,0b00000000,0x00
+ * RSVD_7_2[5:0],,,,,,WD_TIMEOUT[1:0]
+ */
+#define MAX77759_USB_VENDOR_WDG_CTRL 0x8A
+
+#define MAX77759_USB_VENDOR_WDG_CTRL_RSVD_7_2_SHIFT 2
+#define MAX77759_USB_VENDOR_WDG_CTRL_RSVD_7_2_MASK (0x3f << 2)
+#define MAX77759_USB_VENDOR_WDG_CTRL_RSVD_7_2_CLEAR (~(0x3f << 2))
+#define MAX77759_USB_VENDOR_WDG_CTRL_WD_TIMEOUT_SHIFT 0
+#define MAX77759_USB_VENDOR_WDG_CTRL_WD_TIMEOUT_MASK (0x3 << 0)
+#define MAX77759_USB_VENDOR_WDG_CTRL_WD_TIMEOUT_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vendor_wdg_ctrl_rsvd_7_2,7,2)
+MAX77759_BFF(usb_vendor_wdg_ctrl_wd_timeout,1,0)
+static inline const char *
+max77759_usb_vendor_wdg_ctrl_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " RSVD_7_2=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_WDG_CTRL_RSVD_7_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " WD_TIMEOUT=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_WDG_CTRL_WD_TIMEOUT, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_VCON_CTRL,0x8B,0b10000001,0x81
+ * VcnOcpEn,SPR_6_5[1:0],,VcnSoftStartDis,VcnSc_Deb,VcnIlim[2:0],
+ */
+#define MAX77759_USB_VENDOR_VCON_CTRL 0x8B
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNOCPEN (0x1 << 7)
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSOFTSTARTDIS (0x1 << 4)
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSC_DEB (0x1 << 3)
+
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNOCPEN_SHIFT 7
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNOCPEN_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNOCPEN_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_VCON_CTRL_SPR_6_5_SHIFT 5
+#define MAX77759_USB_VENDOR_VCON_CTRL_SPR_6_5_MASK (0x3 << 5)
+#define MAX77759_USB_VENDOR_VCON_CTRL_SPR_6_5_CLEAR (~(0x3 << 5))
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSOFTSTARTDIS_SHIFT 4
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSOFTSTARTDIS_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSOFTSTARTDIS_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSC_DEB_SHIFT 3
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSC_DEB_MASK (0x1 << 3)
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNSC_DEB_CLEAR (~(0x1 << 3))
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNILIM_SHIFT 0
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNILIM_MASK (0x7 << 0)
+#define MAX77759_USB_VENDOR_VCON_CTRL_VCNILIM_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(usb_vendor_vcon_ctrl_vcnocpen,7,7)
+MAX77759_BFF(usb_vendor_vcon_ctrl_spr_6_5,6,5)
+MAX77759_BFF(usb_vendor_vcon_ctrl_vcnsoftstartdis,4,4)
+MAX77759_BFF(usb_vendor_vcon_ctrl_vcnsc_deb,3,3)
+MAX77759_BFF(usb_vendor_vcon_ctrl_vcnilim,2,0)
+static inline const char *
+max77759_usb_vendor_vcon_ctrl_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " VCNOCPEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_VCON_CTRL_VCNOCPEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_6_5=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_VCON_CTRL_SPR_6_5, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCNSOFTSTARTDIS=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_VCON_CTRL_VCNSOFTSTARTDIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCNSC_DEB=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_VCON_CTRL_VCNSC_DEB, val));
+ i += SCNPRINTF(&buff[i], len - i, " VCNILIM=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_VCON_CTRL_VCNILIM, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_CC_CTRL1,0x8C,0b00000010,0x02
+ * ccConnDry,RdOpenDis,ccCompEn,ccSnkExitEn,ccLpDrpCycle[1:0],,ccDrpPhase[1:0]
+ */
+#define MAX77759_USB_VENDOR_CC_CTRL1 0x8C
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCONNDRY (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_CTRL1_RDOPENDIS (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCOMPEN (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCSNKEXITEN (0x1 << 4)
+
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCONNDRY_SHIFT 7
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCONNDRY_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCONNDRY_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_CC_CTRL1_RDOPENDIS_SHIFT 6
+#define MAX77759_USB_VENDOR_CC_CTRL1_RDOPENDIS_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_CTRL1_RDOPENDIS_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCOMPEN_SHIFT 5
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCOMPEN_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCCOMPEN_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCSNKEXITEN_SHIFT 4
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCSNKEXITEN_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCSNKEXITEN_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCLPDRPCYCLE_SHIFT 2
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCLPDRPCYCLE_MASK (0x3 << 2)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCLPDRPCYCLE_CLEAR (~(0x3 << 2))
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCDRPPHASE_SHIFT 0
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCDRPPHASE_MASK (0x3 << 0)
+#define MAX77759_USB_VENDOR_CC_CTRL1_CCDRPPHASE_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vendor_cc_ctrl1_ccconndry,7,7)
+MAX77759_BFF(usb_vendor_cc_ctrl1_rdopendis,6,6)
+MAX77759_BFF(usb_vendor_cc_ctrl1_cccompen,5,5)
+MAX77759_BFF(usb_vendor_cc_ctrl1_ccsnkexiten,4,4)
+MAX77759_BFF(usb_vendor_cc_ctrl1_cclpdrpcycle,3,2)
+MAX77759_BFF(usb_vendor_cc_ctrl1_ccdrpphase,1,0)
+static inline const char *
+max77759_usb_vendor_cc_ctrl1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " CCCONNDRY=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL1_CCCONNDRY, val));
+ i += SCNPRINTF(&buff[i], len - i, " RDOPENDIS=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL1_RDOPENDIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCCOMPEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL1_CCCOMPEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCSNKEXITEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL1_CCSNKEXITEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCLPDRPCYCLE=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL1_CCLPDRPCYCLE, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCDRPPHASE=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL1_CCDRPPHASE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_CC_CTRL2,0x8D,0b00000000,0x00
+ * sbuOvpDis,ccOvpDis,sbuRpCtrl,ccLpModeSel[1:0],,ccRpCtrl[2:0],
+ */
+#define MAX77759_USB_VENDOR_CC_CTRL2 0x8D
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBUOVPDIS (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCOVPDIS (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBURPCTRL (0x1 << 5)
+
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBUOVPDIS_SHIFT 7
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBUOVPDIS_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBUOVPDIS_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCOVPDIS_SHIFT 6
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCOVPDIS_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCOVPDIS_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBURPCTRL_SHIFT 5
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBURPCTRL_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_CC_CTRL2_SBURPCTRL_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCLPMODESEL_SHIFT 3
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCLPMODESEL_MASK (0x3 << 3)
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCLPMODESEL_CLEAR (~(0x3 << 3))
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCRPCTRL_SHIFT 0
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCRPCTRL_MASK (0x7 << 0)
+#define MAX77759_USB_VENDOR_CC_CTRL2_CCRPCTRL_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(usb_vendor_cc_ctrl2_sbuovpdis,7,7)
+MAX77759_BFF(usb_vendor_cc_ctrl2_ccovpdis,6,6)
+MAX77759_BFF(usb_vendor_cc_ctrl2_sburpctrl,5,5)
+MAX77759_BFF(usb_vendor_cc_ctrl2_cclpmodesel,4,3)
+MAX77759_BFF(usb_vendor_cc_ctrl2_ccrpctrl,2,0)
+static inline const char *
+max77759_usb_vendor_cc_ctrl2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SBUOVPDIS=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL2_SBUOVPDIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCOVPDIS=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL2_CCOVPDIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " SBURPCTRL=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL2_SBURPCTRL, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCLPMODESEL=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL2_CCLPMODESEL, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCRPCTRL=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL2_CCRPCTRL, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_CC_CTRL3,0x8E,0b00100000,0x20
+ * ccWtrDeb[1:0],,ccWtrSel[2:0],,,ccLadderDis,SPR_1
+ */
+#define MAX77759_USB_VENDOR_CC_CTRL3 0x8E
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCLADDERDIS (0x1 << 2)
+#define MAX77759_USB_VENDOR_CC_CTRL3_SPR_1 (0x1 << 1)
+#define MAX77759_USB_VENDOR_CC_CTRL3_WTRCYCLE (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCWTRDEB_SHIFT 6
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCWTRDEB_MASK (0x3 << 6)
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCWTRDEB_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCWTRSEL_SHIFT 3
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCWTRSEL_MASK (0x7 << 3)
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCWTRSEL_CLEAR (~(0x7 << 3))
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCLADDERDIS_SHIFT 2
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCLADDERDIS_MASK (0x1 << 2)
+#define MAX77759_USB_VENDOR_CC_CTRL3_CCLADDERDIS_CLEAR (~(0x1 << 2))
+#define MAX77759_USB_VENDOR_CC_CTRL3_SPR_1_SHIFT 1
+#define MAX77759_USB_VENDOR_CC_CTRL3_SPR_1_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_CC_CTRL3_SPR_1_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_CC_CTRL3_WTRCYCLE_SHIFT 0
+#define MAX77759_USB_VENDOR_CC_CTRL3_WTRCYCLE_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_CC_CTRL3_WTRCYCLE_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_cc_ctrl3_ccwtrdeb,7,6)
+MAX77759_BFF(usb_vendor_cc_ctrl3_ccwtrsel,5,3)
+MAX77759_BFF(usb_vendor_cc_ctrl3_ccladderdis,2,2)
+MAX77759_BFF(usb_vendor_cc_ctrl3_spr_1,1,1)
+MAX77759_BFF(usb_vendor_cc_ctrl3_wtrcycle,0,0)
+static inline const char *
+max77759_usb_vendor_cc_ctrl3_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " CCWTRDEB=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL3_CCWTRDEB, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCWTRSEL=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL3_CCWTRSEL, val));
+ i += SCNPRINTF(&buff[i], len - i, " CCLADDERDIS=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL3_CCLADDERDIS, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_1=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL3_SPR_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " WTRCYCLE=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_CC_CTRL3_WTRCYCLE, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_BC_CTRL1,0x8F,0b10100001,0xa1
+ * dcdCpl,SPR_6,noBcComp,SPR_4_2[2:0],,,chgDetMan
+ */
+#define MAX77759_USB_VENDOR_BC_CTRL1 0x8F
+#define MAX77759_USB_VENDOR_BC_CTRL1_DCDCPL (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_6 (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_CTRL1_NOBCCOMP (0x1 << 5)
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETMAN (0x1 << 1)
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETEN (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_BC_CTRL1_DCDCPL_SHIFT 7
+#define MAX77759_USB_VENDOR_BC_CTRL1_DCDCPL_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_CTRL1_DCDCPL_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_6_SHIFT 6
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_6_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_6_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_BC_CTRL1_NOBCCOMP_SHIFT 5
+#define MAX77759_USB_VENDOR_BC_CTRL1_NOBCCOMP_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_BC_CTRL1_NOBCCOMP_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_4_2_SHIFT 2
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_4_2_MASK (0x7 << 2)
+#define MAX77759_USB_VENDOR_BC_CTRL1_SPR_4_2_CLEAR (~(0x7 << 2))
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETMAN_SHIFT 1
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETMAN_MASK (0x1 << 1)
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETMAN_CLEAR (~(0x1 << 1))
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETEN_SHIFT 0
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETEN_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_BC_CTRL1_CHGDETEN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_bc_ctrl1_dcdcpl,7,7)
+MAX77759_BFF(usb_vendor_bc_ctrl1_spr_6,6,6)
+MAX77759_BFF(usb_vendor_bc_ctrl1_nobccomp,5,5)
+MAX77759_BFF(usb_vendor_bc_ctrl1_spr_4_2,4,2)
+MAX77759_BFF(usb_vendor_bc_ctrl1_chgdetman,1,1)
+MAX77759_BFF(usb_vendor_bc_ctrl1_chgdeten,0,0)
+static inline const char *
+max77759_usb_vendor_bc_ctrl1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " DCDCPL=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL1_DCDCPL, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL1_SPR_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " NOBCCOMP=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL1_NOBCCOMP, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_4_2=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL1_SPR_4_2, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGDETMAN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL1_CHGDETMAN, val));
+ i += SCNPRINTF(&buff[i], len - i, " CHGDETEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL1_CHGDETEN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_BC_CTRL2,0x90,0b00000000,0x00
+ * dpMonEn,SPR_6,dnMonEn,dpDnMan,dpDrv[1:0],,dnDrv[1:0]
+ */
+#define MAX77759_USB_VENDOR_BC_CTRL2 0x90
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPMONEN (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_CTRL2_SPR_6 (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNMONEN (0x1 << 5)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDNMAN (0x1 << 4)
+
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPMONEN_SHIFT 7
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPMONEN_MASK (0x1 << 7)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPMONEN_CLEAR (~(0x1 << 7))
+#define MAX77759_USB_VENDOR_BC_CTRL2_SPR_6_SHIFT 6
+#define MAX77759_USB_VENDOR_BC_CTRL2_SPR_6_MASK (0x1 << 6)
+#define MAX77759_USB_VENDOR_BC_CTRL2_SPR_6_CLEAR (~(0x1 << 6))
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNMONEN_SHIFT 5
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNMONEN_MASK (0x1 << 5)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNMONEN_CLEAR (~(0x1 << 5))
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDNMAN_SHIFT 4
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDNMAN_MASK (0x1 << 4)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDNMAN_CLEAR (~(0x1 << 4))
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDRV_SHIFT 2
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDRV_MASK (0x3 << 2)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DPDRV_CLEAR (~(0x3 << 2))
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNDRV_SHIFT 0
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNDRV_MASK (0x3 << 0)
+#define MAX77759_USB_VENDOR_BC_CTRL2_DNDRV_CLEAR (~(0x3 << 0))
+
+MAX77759_BFF(usb_vendor_bc_ctrl2_dpmonen,7,7)
+MAX77759_BFF(usb_vendor_bc_ctrl2_spr_6,6,6)
+MAX77759_BFF(usb_vendor_bc_ctrl2_dnmonen,5,5)
+MAX77759_BFF(usb_vendor_bc_ctrl2_dpdnman,4,4)
+MAX77759_BFF(usb_vendor_bc_ctrl2_dpdrv,3,2)
+MAX77759_BFF(usb_vendor_bc_ctrl2_dndrv,1,0)
+static inline const char *
+max77759_usb_vendor_bc_ctrl2_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " DPMONEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL2_DPMONEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL2_SPR_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNMONEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL2_DNMONEN, val));
+ i += SCNPRINTF(&buff[i], len - i, " DPDNMAN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL2_DPDNMAN, val));
+ i += SCNPRINTF(&buff[i], len - i, " DPDRV=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL2_DPDRV, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNDRV=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_BC_CTRL2_DNDRV, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_ADC_CTRL1,0x91,0b00000000,0x00
+ * adcinSel[2:0],,,SPR_4_1[3:0],,,
+ */
+#define MAX77759_USB_VENDOR_ADC_CTRL1 0x91
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCEN (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCINSEL_SHIFT 5
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCINSEL_MASK (0x7 << 5)
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCINSEL_CLEAR (~(0x7 << 5))
+#define MAX77759_USB_VENDOR_ADC_CTRL1_SPR_4_1_SHIFT 1
+#define MAX77759_USB_VENDOR_ADC_CTRL1_SPR_4_1_MASK (0xf << 1)
+#define MAX77759_USB_VENDOR_ADC_CTRL1_SPR_4_1_CLEAR (~(0xf << 1))
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCEN_SHIFT 0
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCEN_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_ADC_CTRL1_ADCEN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_adc_ctrl1_adcinsel,7,5)
+MAX77759_BFF(usb_vendor_adc_ctrl1_spr_4_1,4,1)
+MAX77759_BFF(usb_vendor_adc_ctrl1_adcen,0,0)
+static inline const char *
+max77759_usb_vendor_adc_ctrl1_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " ADCINSEL=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ADC_CTRL1_ADCINSEL, val));
+ i += SCNPRINTF(&buff[i], len - i, " SPR_4_1=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ADC_CTRL1_SPR_4_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " ADCEN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_ADC_CTRL1_ADCEN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_EXTBST_CTRL,0x92,0b00000000,0x00
+ * SPR_7_1[6:0],,,,,,
+ */
+#define MAX77759_USB_VENDOR_EXTBST_CTRL 0x92
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_EXT_BST_EN (0x1 << 0)
+
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_SPR_7_1_SHIFT 1
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_SPR_7_1_MASK (0x7f << 1)
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_SPR_7_1_CLEAR (~(0x7f << 1))
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_EXT_BST_EN_SHIFT 0
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_EXT_BST_EN_MASK (0x1 << 0)
+#define MAX77759_USB_VENDOR_EXTBST_CTRL_EXT_BST_EN_CLEAR (~(0x1 << 0))
+
+MAX77759_BFF(usb_vendor_extbst_ctrl_spr_7_1,7,1)
+MAX77759_BFF(usb_vendor_extbst_ctrl_ext_bst_en,0,0)
+static inline const char *
+max77759_usb_vendor_extbst_ctrl_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_1=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_EXTBST_CTRL_SPR_7_1, val));
+ i += SCNPRINTF(&buff[i], len - i, " EXT_BST_EN=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_EXTBST_CTRL_EXT_BST_EN, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_USBSW_CTRL,0x93,0b00000000,0x00
+ * SPR_7_6[1:0],,DPSw[2:0],,,DNSw[2:0],
+ */
+#define MAX77759_USB_VENDOR_USBSW_CTRL 0x93
+
+#define MAX77759_USB_VENDOR_USBSW_CTRL_SPR_7_6_SHIFT 6
+#define MAX77759_USB_VENDOR_USBSW_CTRL_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_USB_VENDOR_USBSW_CTRL_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_VENDOR_USBSW_CTRL_DPSW_SHIFT 3
+#define MAX77759_USB_VENDOR_USBSW_CTRL_DPSW_MASK (0x7 << 3)
+#define MAX77759_USB_VENDOR_USBSW_CTRL_DPSW_CLEAR (~(0x7 << 3))
+#define MAX77759_USB_VENDOR_USBSW_CTRL_DNSW_SHIFT 0
+#define MAX77759_USB_VENDOR_USBSW_CTRL_DNSW_MASK (0x7 << 0)
+#define MAX77759_USB_VENDOR_USBSW_CTRL_DNSW_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(usb_vendor_usbsw_ctrl_spr_7_6,7,6)
+MAX77759_BFF(usb_vendor_usbsw_ctrl_dpsw,5,3)
+MAX77759_BFF(usb_vendor_usbsw_ctrl_dnsw,2,0)
+static inline const char *
+max77759_usb_vendor_usbsw_ctrl_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_USBSW_CTRL_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " DPSW=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_USBSW_CTRL_DPSW, val));
+ i += SCNPRINTF(&buff[i], len - i, " DNSW=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_USBSW_CTRL_DNSW, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_SBUSW_CTRL,0x94,0b00000000,0x00
+ * SPR_7_6[1:0],,SBU2Sw[2:0],,,SBU1Sw[2:0],
+ */
+#define MAX77759_USB_VENDOR_SBUSW_CTRL 0x94
+
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SPR_7_6_SHIFT 6
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SPR_7_6_MASK (0x3 << 6)
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SPR_7_6_CLEAR (~(0x3 << 6))
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SBU2SW_SHIFT 3
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SBU2SW_MASK (0x7 << 3)
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SBU2SW_CLEAR (~(0x7 << 3))
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SBU1SW_SHIFT 0
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SBU1SW_MASK (0x7 << 0)
+#define MAX77759_USB_VENDOR_SBUSW_CTRL_SBU1SW_CLEAR (~(0x7 << 0))
+
+MAX77759_BFF(usb_vendor_sbusw_ctrl_spr_7_6,7,6)
+MAX77759_BFF(usb_vendor_sbusw_ctrl_sbu2sw,5,3)
+MAX77759_BFF(usb_vendor_sbusw_ctrl_sbu1sw,2,0)
+static inline const char *
+max77759_usb_vendor_sbusw_ctrl_cstr(char *buff, size_t len, int val)
+{
+#ifdef SCNPRINTF
+ int i = 0;
+
+ i += SCNPRINTF(&buff[i], len - i, " SPR_7_6=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_SBUSW_CTRL_SPR_7_6, val));
+ i += SCNPRINTF(&buff[i], len - i, " SBU2SW=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_SBUSW_CTRL_SBU2SW, val));
+ i += SCNPRINTF(&buff[i], len - i, " SBU1SW=%x",
+ FIELD2VALUE(MAX77759_USB_VENDOR_SBUSW_CTRL_SBU1SW, val));
+#else
+ buff[0] = 0;
+#endif
+ return buff;
+}
+
+/*
+ * VENDOR_SW_CTRL,0x95,0b00000000,0x00
+ * SPR_7_1[6:0],,,,,,
+ */
+#define MAX77759_USB_VENDOR_SW_CTRL 0x95
+
+#endif /* MAX77759_REG_H_ */
diff --git a/max_m5.c b/max_m5.c
new file mode 100644
index 0000000..45699e4
--- /dev/null
+++ b/max_m5.c
@@ -0,0 +1,849 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Fuel gauge driver for Maxim Fuel Gauges with M5 Algo
+ *
+ * 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/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include "google_bms.h"
+#include "google_psy.h"
+#include "logbuffer.h"
+
+#include "max_m5_reg.h"
+#include "max_m5.h"
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#endif
+
+static void dump_model(struct device *dev, u16 *data, int count)
+{
+ int i, j, len;
+ char buff[16 * 5 + 1] = {};
+
+ for (i = 0; i < count; i += 16) {
+
+ for (len = 0, j = 0; j < 16; j++)
+ len += scnprintf(&buff[len], sizeof(buff) - len,
+ "%04x ", data[i + j]);
+
+ dev_info(dev, "%x: %s\n", i + MAX_M5_FG_MODEL_START, buff);
+ }
+}
+
+/* input current is in the fuel gauge */
+int max_m5_read_actual_input_current_ua(struct i2c_client *client, int *iic)
+{
+ struct max_m5_data *m5_data = max1720x_get_model_data(client);
+ unsigned int tmp;
+ int rtn;
+
+ if (!m5_data || !m5_data->regmap)
+ return -ENODEV;
+
+ rtn = regmap_read(m5_data->regmap->regmap, MAX_M5_IIN, &tmp);
+ if (rtn)
+ pr_err("Failed to read %x\n", MAX_M5_IIN);
+ else
+ *iic = tmp * 125;
+
+ return rtn;
+}
+EXPORT_SYMBOL_GPL(max_m5_read_actual_input_current_ua);
+
+int max_m5_reg_read(struct i2c_client *client, unsigned int reg,
+ unsigned int *val)
+{
+ struct max_m5_data *m5_data = max1720x_get_model_data(client);
+
+ if (!m5_data || !m5_data->regmap)
+ return -ENODEV;
+
+ return regmap_read(m5_data->regmap->regmap, reg, val);
+}
+EXPORT_SYMBOL_GPL(max_m5_reg_read);
+
+int max_m5_reg_write(struct i2c_client *client, unsigned int reg,
+ unsigned int val)
+{
+ struct max_m5_data *m5_data = max1720x_get_model_data(client);
+
+ if (!m5_data || !m5_data->regmap)
+ return -ENODEV;
+
+ return regmap_write(m5_data->regmap->regmap, reg, val);
+}
+EXPORT_SYMBOL_GPL(max_m5_reg_write);
+
+
+static int max_m5_read_custom_model(struct regmap *regmap, u16 *model_data,
+ int count)
+{
+ return regmap_raw_read(regmap, MAX_M5_FG_MODEL_START, model_data,
+ count * 2);
+}
+
+static int max_m5_write_custom_model(struct regmap *regmap, u16 *model_data,
+ int count)
+{
+ return regmap_raw_write(regmap, MAX_M5_FG_MODEL_START, model_data,
+ count * 2);
+}
+
+static int max_m5_model_lock(struct regmap *regmap, bool enabled)
+{
+ u16 code[2] = {0x59, 0xC4};
+
+ if (enabled) {
+ code[0] = 0;
+ code[1] = 0;
+ }
+
+ return regmap_raw_write(regmap, MAX_M5_UNLOCK_MODEL_ACCESS, code,
+ sizeof(code));
+}
+
+static int mem16test(u16 *data, u16 code, int count)
+{
+ int same, i;
+
+ for (i = 0, same = 1; same && i < count; i++)
+ same = data[i] == code;
+
+ return same;
+}
+
+/* load custom model b/137037210 */
+static int max_m5_update_custom_model(struct max_m5_data *m5_data)
+{
+ int retries, ret;
+ bool success;
+ u16 *data;
+
+ data = kzalloc(m5_data->custom_model_size * 2, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* un lock, update the model */
+ for (success = false, retries = 3; !success && retries > 0; retries--) {
+
+ ret = max_m5_model_lock(m5_data->regmap->regmap, false);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot unlock model access (%d)\n",
+ ret);
+ continue;
+ }
+
+ ret = max_m5_write_custom_model(m5_data->regmap->regmap,
+ m5_data->custom_model,
+ m5_data->custom_model_size);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot write custom model (%d)\n",
+ ret);
+ continue;
+ }
+
+ ret = max_m5_read_custom_model(m5_data->regmap->regmap,
+ data,
+ m5_data->custom_model_size);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot write custom model (%d)\n",
+ ret);
+ continue;
+ }
+
+ ret = memcmp(m5_data->custom_model, data,
+ m5_data->custom_model_size * 2);
+ success = ret == 0;
+ if (!success) {
+ dump_model(m5_data->dev, m5_data->custom_model,
+ m5_data->custom_model_size);
+ dump_model(m5_data->dev, data,
+ m5_data->custom_model_size);
+ }
+
+ }
+
+ if (!success) {
+ dev_err(m5_data->dev, "cannot write custom model (%d)\n", ret);
+ kfree(data);
+ return -EIO;
+ }
+
+ /* lock and verify lock */
+ for (retries = 3; retries > 0; retries--) {
+ int same;
+
+ ret = max_m5_model_lock(m5_data->regmap->regmap, true);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot lock model access (%d)\n",
+ ret);
+ continue;
+ }
+
+ ret = max_m5_read_custom_model(m5_data->regmap->regmap, data,
+ m5_data->custom_model_size);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot read custom model (%d)\n",
+ ret);
+ continue;
+ }
+
+ /* model is locked when read retuns all 0xffff */
+ same = mem16test(data, 0xffff, m5_data->custom_model_size);
+ if (same)
+ break;
+ }
+
+ kfree(data);
+ return 0;
+}
+
+/* Step 7: Write custom parameters */
+static int max_m5_update_custom_parameters(struct max_m5_data *m5_data)
+{
+ struct max_m5_custom_parameters *cp = &m5_data->parameters;
+ struct max17x0x_regmap *regmap = m5_data->regmap;
+ int tmp, ret;
+ u16 vfsoc;
+
+ ret = REGMAP_WRITE(regmap, MAX_M5_TEMPNOM, 0x1403);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_COFF, 0x1);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_REPCAP, 0x0);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_IAVGEMPTY,
+ cp->iavg_empty);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_RELAXCFG,
+ cp->relaxcfg);
+ if (ret < 0)
+ return -EIO;
+
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_UNLOCK_EXTRA_CONFIG,
+ MAX_M5_UNLOCK_EXTRA_CONFIG_UNLOCK_CODE);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot unlock extra config (%d)\n", ret);
+ return -EIO;
+ }
+
+ ret = REGMAP_READ(regmap, MAX_M5_VFSOC, &vfsoc);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_VFSOC0, vfsoc);
+
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_LEARNCFG, cp->learncfg);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_CONFIG, cp->config);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_CONFIG2, cp->config2);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_FULLSOCTHR, cp->fullsocthr);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_FULLCAPREP,
+ cp->fullcaprep);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_DESIGNCAP,
+ cp->designcap);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_DPACC, cp->dpacc);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_DQACC, cp->dqacc);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_FULLCAPNOM,
+ cp->fullcapnom);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_VEMPTY, cp->v_empty);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_QRTABLE00,
+ cp->qresidual00);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_QRTABLE10,
+ cp->qresidual10);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_QRTABLE20,
+ cp->qresidual20);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_QRTABLE30,
+ cp->qresidual30);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_RCOMP0,
+ cp->rcomp0);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_RCOMP0, cp->tempco);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_ICHGTERM, cp->ichgterm);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_TGAIN, cp->tgain);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_TOFF, cp->toff);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_MISCCFG, cp->misccfg);
+ if (ret < 0)
+ goto exit_done;
+
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_UNLOCK_EXTRA_CONFIG,
+ MAX_M5_UNLOCK_EXTRA_CONFIG_UNLOCK_CODE);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot unlock extra config (%d)\n", ret);
+ goto exit_done;
+ }
+
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_ATRATE, cp->atrate);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_CV_MIXCAP,
+ (cp->fullcapnom * 75) / 100);
+ if (ret == 0)
+ ret = REGMAP_WRITE_VERIFY(regmap, MAX_M5_CV_HALFTIME, 0x600);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_CONVGCFG, cp->convgcfg);
+
+exit_done:
+ tmp = REGMAP_WRITE_VERIFY(regmap, MAX_M5_UNLOCK_EXTRA_CONFIG,
+ MAX_M5_UNLOCK_EXTRA_CONFIG_LOCK_CODE);
+ if (tmp < 0) {
+ dev_err(m5_data->dev, "cannot lock extra config (%d)\n", tmp);
+ return tmp;
+ }
+
+ return ret;
+}
+
+int max_m5_load_gauge_model(struct max_m5_data *m5_data)
+{
+ struct max17x0x_regmap *regmap = m5_data->regmap;
+ int ret, retries;
+ u16 data;
+
+ if (!regmap)
+ return -EIO;
+
+ if (!m5_data || !m5_data->custom_model || !m5_data->custom_model_size)
+ return -ENODATA;
+
+ ret = max_m5_update_custom_model(m5_data);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot update custom model (%d)\n", ret);
+ return ret;
+ }
+
+ ret = max_m5_update_custom_parameters(m5_data);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot update custom parameters (%d)\n",
+ ret);
+ return ret;
+ }
+
+ /* tcurve, filterconfig are not part of model state */
+ ret = REGMAP_WRITE(regmap, MAX_M5_TCURVE, m5_data->parameters.tcurve);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot update tcurve (%d)\n",
+ ret);
+ return ret;
+ }
+
+ ret = REGMAP_WRITE(regmap, MAX_M5_FILTERCFG,
+ m5_data->parameters.filtercfg);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "cannot update filter config (%d)\n",
+ ret);
+ return ret;
+ }
+
+ /* load model now */
+ ret = REGMAP_READ(regmap, MAX_M5_CONFIG2, &data);
+ if (ret == 0)
+ ret = REGMAP_WRITE(regmap, MAX_M5_CONFIG2, data | 0x20);
+ if (ret < 0) {
+ dev_err(m5_data->dev, "failed start model loading (%d)\n", ret);
+ return ret;
+ }
+
+ /* around 400ms for this */
+ for (retries = 10; retries > 0; retries--) {
+
+ mdelay(50);
+
+ ret = REGMAP_READ(regmap, MAX_M5_CONFIG2, &data);
+ if (ret == 0 && !(data & 0x20))
+ return 0;
+ }
+
+ return -ETIMEDOUT;
+}
+
+/* load parameters and model state from permanent storage */
+int max_m5_load_state_data(struct max_m5_data *m5_data)
+{
+ int ret = 0;
+
+ if (!m5_data)
+ return -EINVAL;
+
+ /* might return -EAGAIN during init */
+ ret = gbms_storage_read(GBMS_TAG_GMSR, &m5_data->model_save,
+ sizeof(m5_data->model_save));
+ if (ret < 0) {
+ dev_info(m5_data->dev, "Load Model Data Failed ret=%d\n", ret);
+ return ret;
+ }
+
+ if (m5_data->model_save.rcomp0 == 0xFF) {
+ dev_info(m5_data->dev, "Model Data Empty\n");
+ return -EINVAL;
+ }
+
+ m5_data->parameters.rcomp0 = m5_data->model_save.rcomp0;
+ m5_data->parameters.tempco = m5_data->model_save.tempco;
+ m5_data->parameters.fullcaprep = m5_data->model_save.fullcaprep;
+ m5_data->cycles = m5_data->model_save.cycles;
+ m5_data->parameters.fullcapnom = m5_data->model_save.fullcapnom;
+ m5_data->parameters.qresidual00 = m5_data->model_save.qresidual00;
+ m5_data->parameters.qresidual10 = m5_data->model_save.qresidual10;
+ m5_data->parameters.qresidual20 = m5_data->model_save.qresidual20;
+ m5_data->parameters.qresidual30 = m5_data->model_save.qresidual30;
+ m5_data->mixcap = m5_data->model_save.mixcap;
+ m5_data->halftime = m5_data->model_save.halftime;
+
+ return ret;
+}
+
+/* save/commit parameters and model state to permanent storage */
+int max_m5_save_state_data(struct max_m5_data *m5_data)
+{
+ int ret = 0;
+
+ m5_data->model_save.rcomp0 = m5_data->parameters.rcomp0;
+ m5_data->model_save.tempco = m5_data->parameters.tempco;
+ m5_data->model_save.fullcaprep = m5_data->parameters.fullcaprep;
+ m5_data->model_save.cycles = m5_data->cycles;
+ m5_data->model_save.fullcapnom = m5_data->parameters.fullcapnom;
+ m5_data->model_save.qresidual00 = m5_data->parameters.qresidual00;
+ m5_data->model_save.qresidual10 = m5_data->parameters.qresidual10;
+ m5_data->model_save.qresidual20 = m5_data->parameters.qresidual20;
+ m5_data->model_save.qresidual30 = m5_data->parameters.qresidual30;
+ m5_data->model_save.mixcap = m5_data->mixcap;
+ m5_data->model_save.halftime = m5_data->halftime;
+
+ ret = gbms_storage_write(GBMS_TAG_GMSR,
+ (const void *)&m5_data->model_save,
+ sizeof(m5_data->model_save));
+ if (ret < 0)
+ return ret;
+
+ return ret == sizeof(m5_data->model_save) ? 0 : -ERANGE;
+}
+
+/* read fuel gauge state to parameters/model state */
+int max_m5_model_read_state(struct max_m5_data *m5_data)
+{
+ int rc;
+ struct max17x0x_regmap *regmap = m5_data->regmap;
+
+ rc= REGMAP_READ(regmap, MAX_M5_RCOMP0, &m5_data->parameters.rcomp0);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_TEMPCO,
+ &m5_data->parameters.tempco);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_FULLCAPREP,
+ &m5_data->parameters.fullcaprep);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_CYCLES, &m5_data->cycles);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_FULLCAPNOM,
+ &m5_data->parameters.fullcapnom);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_QRTABLE00,
+ &m5_data->parameters.qresidual00);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_QRTABLE10,
+ &m5_data->parameters.qresidual10);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_QRTABLE20,
+ &m5_data->parameters.qresidual20);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_QRTABLE30,
+ &m5_data->parameters.qresidual30);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_MIXCAP, &m5_data->mixcap);
+ if (rc == 0)
+ rc = REGMAP_READ(regmap, MAX_M5_CV_HALFTIME,
+ &m5_data->halftime);
+
+ return rc;
+}
+
+ssize_t max_m5_model_state_cstr(char *buf, int max,
+ struct max_m5_data *m5_data)
+{
+ int len = 0;
+
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_RCOMP0,
+ m5_data->parameters.rcomp0);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_TEMPCO,
+ m5_data->parameters.tempco);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_FULLCAPREP,
+ m5_data->parameters.fullcaprep);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_CYCLES,
+ m5_data->cycles);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_FULLCAPNOM,
+ m5_data->parameters.fullcapnom);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_QRTABLE00,
+ m5_data->parameters.qresidual00);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_QRTABLE10,
+ m5_data->parameters.qresidual10);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_QRTABLE20,
+ m5_data->parameters.qresidual20);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_QRTABLE30,
+ m5_data->parameters.qresidual30);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_MIXCAP,
+ m5_data->mixcap);
+ len += scnprintf(&buf[len], max - len,"%02x:%02x\n", MAX_M5_CV_HALFTIME,
+ m5_data->halftime);
+
+ return len;
+}
+
+/* can be use to restore parametes and model state after POR */
+int max_m5_model_state_sscan(struct max_m5_data *m5_data, const char *buf,
+ int max)
+{
+ int ret, index, reg, val;
+
+ for (index = 0; index < max ; index += 1) {
+ ret = sscanf(&buf[index], "%x:%x", ®, &val);
+ if (ret != 2) {
+ dev_err(m5_data->dev, "@%d: sscan error %d\n",
+ index, ret);
+ return -EINVAL;
+ }
+
+ dev_info(m5_data->dev, "@%d: reg=%x val=%x\n", index, reg, val);
+
+ switch (reg) {
+ /* fg-params */
+ case MAX_M5_IAVGEMPTY:
+ m5_data->parameters.iavg_empty = val;
+ break;
+ case MAX_M5_RELAXCFG:
+ m5_data->parameters.relaxcfg = val;
+ break;
+ case MAX_M5_LEARNCFG:
+ m5_data->parameters.learncfg = val;
+ break;
+ case MAX_M5_CONFIG:
+ m5_data->parameters.config = val;
+ break;
+ case MAX_M5_CONFIG2:
+ m5_data->parameters.config2 = val;
+ break;
+ case MAX_M5_FULLSOCTHR:
+ m5_data->parameters.fullsocthr = val;
+ break;
+ case MAX_M5_DESIGNCAP:
+ m5_data->parameters.designcap = val;
+ break;
+ case MAX_M5_DPACC:
+ m5_data->parameters.dpacc = val;
+ break;
+ case MAX_M5_DQACC:
+ m5_data->parameters.dqacc = val;
+ break;
+ case MAX_M5_VEMPTY:
+ m5_data->parameters.v_empty = val;
+ break;
+ case MAX_M5_TGAIN:
+ m5_data->parameters.tgain = val;
+ break;
+ case MAX_M5_TOFF:
+ m5_data->parameters.toff = val;
+ break;
+ case MAX_M5_TCURVE:
+ m5_data->parameters.tcurve = val;
+ break;
+ case MAX_M5_MISCCFG:
+ m5_data->parameters.misccfg = val;
+ break;
+ case MAX_M5_ATRATE:
+ m5_data->parameters.atrate = val;
+ break;
+ case MAX_M5_CONVGCFG:
+ m5_data->parameters.convgcfg = val;
+ break;
+ case MAX_M5_FILTERCFG:
+ m5_data->parameters.filtercfg = val;
+ break;
+
+ /* model state */
+ case MAX_M5_RCOMP0:
+ m5_data->parameters.rcomp0 = val;
+ break;
+ case MAX_M5_TEMPCO:
+ m5_data->parameters.tempco = val;
+ break;
+ case MAX_M5_FULLCAPREP:
+ m5_data->parameters.fullcaprep = val;
+ break;
+ case MAX_M5_CYCLES:
+ m5_data->cycles = val;
+ break;
+ case MAX_M5_FULLCAPNOM:
+ m5_data->parameters.fullcapnom = val;
+ break;
+ case MAX_M5_QRTABLE00:
+ m5_data->parameters.qresidual00 = val;
+ break;
+ case MAX_M5_QRTABLE10:
+ m5_data->parameters.qresidual10 = val;
+ break;
+ case MAX_M5_QRTABLE20:
+ m5_data->parameters.qresidual20 = val;
+ break;
+ case MAX_M5_QRTABLE30:
+ m5_data->parameters.qresidual30 = val;
+ break;
+ case MAX_M5_MIXCAP:
+ m5_data->mixcap = val;
+ break;
+ case MAX_M5_CV_HALFTIME:
+ m5_data->halftime = val;
+ break;
+ default:
+ dev_err(m5_data->dev, "@%d: reg=%x out of range\n",
+ index, reg);
+ return -EINVAL;
+ }
+
+ for ( ; index < max && buf[index] != '\n'; index++)
+ ;
+ }
+
+ return 0;
+}
+
+/* custom model parameters */
+int max_m5_fg_model_cstr(char *buf, int max, const struct max_m5_data *m5_data)
+{
+ int i, len;
+
+ if (!m5_data->custom_model || !m5_data->custom_model_size)
+ return -EINVAL;
+
+ for (len = 0, i = 0; i < m5_data->custom_model_size; i += 1)
+ len += scnprintf(&buf[len], max - len, "%x: %04x\n",
+ MAX_M5_FG_MODEL_START + i,
+ m5_data->custom_model[i]);
+
+ return len;
+}
+
+/* custom model parameters */
+int max_m5_fg_model_sscan(struct max_m5_data *m5_data, const char *buf, int max)
+{
+ int ret, index, reg, val, fg_model_end;
+
+ if (!m5_data->custom_model)
+ return -EINVAL;
+
+ /* use the default size */
+ if (!m5_data->custom_model_size)
+ m5_data->custom_model_size = MAX_M5_FG_MODEL_SIZE;
+
+ fg_model_end = MAX_M5_FG_MODEL_START + m5_data->custom_model_size;
+ for (index = 0; index < max ; index += 1) {
+ ret = sscanf(&buf[index], "%x:%x", ®, &val);
+ if (ret != 2) {
+ dev_err(m5_data->dev, "@%d: sscan error %d\n",
+ index, ret);
+ return -EINVAL;
+ }
+
+ dev_info(m5_data->dev, "@%d: reg=%x val=%x\n", index, reg, val);
+
+ if (reg >= MAX_M5_FG_MODEL_START && reg < fg_model_end) {
+ const int offset = reg - MAX_M5_FG_MODEL_START;
+
+ m5_data->custom_model[offset] = val;
+ }
+
+ for ( ; index < max && buf[index] != '\n'; index++)
+ ;
+ }
+
+ return 0;
+}
+
+/* Initial values??? */
+static int m5_init_custom_parameters(struct device *dev,
+ struct max_m5_custom_parameters *cp,
+ struct device_node *node)
+{
+ const char *propname = "maxim,fg-params";
+ int ret, cnt;
+
+ memset(cp, 0, sizeof(*cp));
+
+ cnt = of_property_count_elems_of_size(node, propname, sizeof(u16));
+ if (cnt < 0)
+ return -ENODATA;
+
+ if (cnt != sizeof(*cp) / 2) {
+ dev_err(dev, "fg-params: %s has %d elements, need %d\n",
+ propname, cnt, sizeof(*cp) / 2);
+ return -ERANGE;
+ }
+
+ ret = of_property_read_u16_array(node, propname, (u16 *)cp, cnt);
+ if (ret < 0) {
+ dev_err(dev, "fg-params: failed to read %s %s: %d\n",
+ node->name, propname, ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void max_m5_free_data(void *data)
+{
+
+}
+
+void *max_m5_init_data(struct device *dev, struct device_node *node,
+ struct max17x0x_regmap *regmap)
+{
+ const char *propname = "maxim,fg-model";
+ struct max_m5_data *m5_data;
+ int cnt, ret;
+ u16 *model;
+
+ m5_data = devm_kzalloc(dev, sizeof(*m5_data), GFP_KERNEL);
+ if (!m5_data) {
+ dev_err(dev, "fg-model: %s not found\n", propname);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ model = devm_kmalloc_array(dev, MAX_M5_FG_MODEL_SIZE, sizeof(u16),
+ GFP_KERNEL);
+ if (!model) {
+ dev_err(dev, "fg-model: out of memory\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ cnt = of_property_count_elems_of_size(node, propname, sizeof(u16));
+ if (cnt != MAX_M5_FG_MODEL_SIZE) {
+ dev_err(dev, "fg-model: not found, or invalid %d\n", cnt);
+ } else {
+ ret = of_property_read_u16_array(node, propname, model, cnt);
+ if (ret < 0)
+ dev_err(dev, "fg-model: no data cnt=%d %s %s: %d\n",
+ cnt, node->name, propname, ret);
+ else
+ m5_data->custom_model_size = cnt;
+ }
+
+ /*
+ * Initial values: check max_m5_model_read_state() for the registers
+ * updated from max1720x_model_work()
+ */
+ ret = m5_init_custom_parameters(dev, &m5_data->parameters, node);
+ if (ret < 0)
+ dev_err(dev, "fg-params: %s not found\n", propname);
+
+ m5_data->custom_model = model;
+ m5_data->regmap = regmap;
+ m5_data->dev = dev;
+
+ return m5_data;
+}
+
+static bool max_m5_is_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x00 ... 0x4F:
+ case 0xB0 ... 0xBF:
+ case 0xD0: /* IIC */
+ case 0xDC ... 0xDF:
+ case 0xFB:
+ case 0xFF: /* VFSOC */
+ return true;
+ case 0x60: /* Model unlock */
+ case 0x62: /* Unlock Model Access */
+ case 0x63: /* Unlock Model Access */
+ case 0x80 ... 0xAF: /* FG Model */
+ /* TODO: add a check on unlock */
+ return true;
+ }
+
+ return false;
+}
+
+const struct regmap_config max_m5_regmap_cfg = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+ .max_register = MAX_M5_VFSOC,
+ .readable_reg = max_m5_is_reg,
+ .volatile_reg = max_m5_is_reg,
+};
+const struct max17x0x_reg max_m5[] = {
+ [MAX17X0X_TAG_avgc] = { ATOM_INIT_REG16(MAX_M5_AVGCURRENT)},
+ [MAX17X0X_TAG_cnfg] = { ATOM_INIT_REG16(MAX_M5_CONFIG)},
+ [MAX17X0X_TAG_mmdv] = { ATOM_INIT_REG16(MAX_M5_MAXMINVOLT)},
+ [MAX17X0X_TAG_vcel] = { ATOM_INIT_REG16(MAX_M5_VCELL)},
+ [MAX17X0X_TAG_temp] = { ATOM_INIT_REG16(MAX_M5_TEMP)},
+ [MAX17X0X_TAG_curr] = { ATOM_INIT_REG16(MAX_M5_CURRENT)},
+ [MAX17X0X_TAG_mcap] = { ATOM_INIT_REG16(MAX_M5_MIXCAP)},
+ [MAX17X0X_TAG_vfsoc] = { ATOM_INIT_REG16(MAX_M5_VFSOC)},
+};
+
+int max_m5_regmap_init(struct max17x0x_regmap *regmap, struct i2c_client *clnt)
+{
+ struct regmap *map;
+
+ map = devm_regmap_init_i2c(clnt, &max_m5_regmap_cfg);
+ if (IS_ERR(map))
+ return IS_ERR_VALUE(map);
+
+ regmap->regtags.max = ARRAY_SIZE(max_m5);
+ regmap->regtags.map = max_m5;
+ regmap->regmap = map;
+ return 0;
+}
+
+int batt_hist_data_collect(void *h, int idx, int cycle_cnt,
+ struct power_supply *fg_psy)
+{
+ return -ENODEV;
+}
+void *batt_hist_init_data(struct device *dev)
+{
+ return NULL;
+}
+
+void batt_hist_free_data(void *p)
+{
+
+}
diff --git a/max_m5.h b/max_m5.h
new file mode 100644
index 0000000..e6601ac
--- /dev/null
+++ b/max_m5.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef MAX_M5_H_
+#define MAX_M5_H_
+
+#include "max1720x_battery.h"
+
+#define MAX_M5_I2C_ADDR 0x6C
+
+/** ------------------------------------------------------------------------ */
+
+/* change to 1 or 0 to load FG model with default parameters on startup */
+#define MAX_M5_LOAD_MODEL_DISABLED -1
+#define MAX_M5_LOAD_MODEL_IDLE 0
+#define MAX_M5_LOAD_MODEL_REQUEST 1
+
+#define MAX_M5_FG_MODEL_START 0x80
+#define MAX_M5_FG_MODEL_SIZE 48
+
+#define MAX_M5_UNLOCK_EXTRA_CONFIG 0x60
+#define MAX_M5_UNLOCK_EXTRA_CONFIG_UNLOCK_CODE 0x80
+#define MAX_M5_UNLOCK_EXTRA_CONFIG_LOCK_CODE 0x00
+
+#define MAX_M5_UNLOCK_MODEL_ACCESS 0x62
+#define MAX_M5_MODEL_ACCESS_UNLOCK_CODE 0xc459
+#define MAX_M5_MODEL_ACCESS_LOCK_CODE 0x0000
+#define MAX_M5_MODEL_ACCESS_LOCK_OK 0xFFFF
+
+#define MAX_M5_TCURVE 0xB9
+#define MAX_M5_VFSOC 0xFF
+
+/** ------------------------------------------------------------------------ */
+
+/*
+ * Custom parameters are updated while the device is runnig. These are saved
+ * to permanent storage and restored from userspace (on load).
+ */
+struct max_m5_custom_parameters {
+ u16 iavg_empty; /* WV */
+ u16 relaxcfg;
+ u16 learncfg;
+ u16 config;
+ u16 config2;
+ u16 fullsocthr;
+ u16 fullcaprep; /* WV */
+ u16 designcap;
+ u16 dpacc; /* WV */
+ u16 dqacc; /* WV */
+ u16 fullcapnom; /* WV */
+ u16 v_empty;
+ u16 qresidual00; /* WV */
+ u16 qresidual10; /* WV */
+ u16 qresidual20; /* WV */
+ u16 qresidual30; /* WV */
+ u16 rcomp0; /* WV */
+ u16 tempco; /* WV */
+ u16 ichgterm;
+ u16 tgain;
+ u16 toff;
+ u16 tcurve; /* write to 0x00B9 */
+ u16 misccfg; /* 0x9d0 for internal current sense, 0x8d0 external */
+
+ u16 atrate;
+ u16 convgcfg;
+ u16 filtercfg; /* write to 0x0029 */
+} __attribute__((packed));
+
+struct model_state_save {
+ u16 rcomp0;
+ u16 tempco;
+ u16 fullcaprep;
+ u16 cycles;
+ u16 fullcapnom;
+ u16 qresidual00;
+ u16 qresidual10;
+ u16 qresidual20;
+ u16 qresidual30;
+ u16 mixcap;
+ u16 halftime;
+};
+
+struct max_m5_data {
+ struct device *dev;
+
+ struct max_m5_custom_parameters parameters;
+ u16 cycles;
+ u16 mixcap;
+ u16 halftime;
+
+ struct max17x0x_regmap *regmap;
+
+ int custom_model_size;
+ u16 *custom_model;
+
+ struct model_state_save model_save;
+};
+
+int max_m5_regmap_init(struct max17x0x_regmap *regmap,
+ struct i2c_client *primary);
+
+void *max_m5_init_data(struct device *dev, struct device_node *batt_node,
+ struct max17x0x_regmap *regmap);
+void max_m5_free_data(void *data);
+
+/* load state from storage */
+int max_m5_load_state_data(struct max_m5_data *m5_data);
+/* save state to storage */
+int max_m5_save_state_data(struct max_m5_data *m5_data);
+
+/* read state from the gauge */
+int max_m5_model_read_state(struct max_m5_data *m5_data);
+
+/* load model to gauge */
+int max_m5_load_gauge_model(struct max_m5_data *m5_data);
+
+ssize_t max_m5_model_state_cstr(char *buf, int max,
+ struct max_m5_data *m5_data);
+int max_m5_model_state_sscan(struct max_m5_data *m5_data, const char *buf,
+ int max);
+int max_m5_fg_model_sscan(struct max_m5_data *m5_data, const char *buf,
+ int max);
+int max_m5_fg_model_cstr(char *buf, int max, const struct max_m5_data *m5_data);
+
+int max_m5_read_actual_input_current_ua(struct i2c_client *client, int *iic);
+
+int max_m5_reg_read(struct i2c_client *client, unsigned int reg,
+ unsigned int *val);
+int max_m5_reg_write(struct i2c_client *client, unsigned int reg,
+ unsigned int val);
+
+void *max1720x_get_model_data(struct i2c_client *client);
+
+#endif
\ No newline at end of file
diff --git a/max_m5_reg.h b/max_m5_reg.h
new file mode 100644
index 0000000..979ef62
--- /dev/null
+++ b/max_m5_reg.h
@@ -0,0 +1,1607 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* machine generated DO NOT MODIFY
+ * source MW_regmap_ds_v0p54_091819.csv
+ * 2019-11-04
+ */
+
+#ifndef MAX_M5_REG_H_
+#define MAX_M5_REG_H_
+
+/* Status,0x0,0b00000010,0x02
+ * Br,Smx,Tmx,Vmx,Bi,Smn,Tmn
+ */
+#define MAX_M5_STATUS 0x0
+#define MAX_M5_STATUS_BR (0x1 << 15)
+#define MAX_M5_STATUS_SMX (0x1 << 14)
+#define MAX_M5_STATUS_TMX (0x1 << 13)
+#define MAX_M5_STATUS_VMX (0x1 << 12)
+#define MAX_M5_STATUS_BI (0x1 << 11)
+#define MAX_M5_STATUS_SMN (0x1 << 10)
+#define MAX_M5_STATUS_TMN (0x1 << 9)
+#define MAX_M5_STATUS_VMN (0x1 << 8)
+#define MAX_M5_STATUS_DSOCI (0x1 << 7)
+#define MAX_M5_STATUS_THMHOT (0x1 << 6)
+#define MAX_M5_STATUS_SPR_5 (0x1 << 5)
+#define MAX_M5_STATUS_ISYSMX (0x1 << 4)
+#define MAX_M5_STATUS_BST (0x1 << 3)
+#define MAX_M5_STATUS_SPR_2 (0x1 << 2)
+#define MAX_M5_STATUS_POR (0x1 << 1)
+#define MAX_M5_STATUS_IMN (0x1 << 0)
+
+#define MAX_M5_STATUS_BR_MASK 0x1
+#define MAX_M5_STATUS_BR_SHIFT 15
+#define MAX_M5_STATUS_BR_CLEAR (~(0x1 << 15))
+#define MAX_M5_STATUS_BR_CLR(v) ((v) & MAX_M5_STATUS_BR_CLEAR)
+#define MAX_M5_STATUS_BR_SET(v) \
+ (MAX_M5_STATUS_BR_CLR(v) | MAX_M5_STATUS_BR)
+#define MAX_M5_STATUS_SMX_MASK 0x1
+#define MAX_M5_STATUS_SMX_SHIFT 14
+#define MAX_M5_STATUS_SMX_CLEAR (~(0x1 << 14))
+#define MAX_M5_STATUS_SMX_CLR(v) ((v) & MAX_M5_STATUS_SMX_CLEAR)
+#define MAX_M5_STATUS_SMX_SET(v) \
+ (MAX_M5_STATUS_SMX_CLR(v) | MAX_M5_STATUS_SMX)
+#define MAX_M5_STATUS_TMX_MASK 0x1
+#define MAX_M5_STATUS_TMX_SHIFT 13
+#define MAX_M5_STATUS_TMX_CLEAR (~(0x1 << 13))
+#define MAX_M5_STATUS_TMX_CLR(v) ((v) & MAX_M5_STATUS_TMX_CLEAR)
+#define MAX_M5_STATUS_TMX_SET(v) \
+ (MAX_M5_STATUS_TMX_CLR(v) | MAX_M5_STATUS_TMX)
+#define MAX_M5_STATUS_VMX_MASK 0x1
+#define MAX_M5_STATUS_VMX_SHIFT 12
+#define MAX_M5_STATUS_VMX_CLEAR (~(0x1 << 12))
+#define MAX_M5_STATUS_VMX_CLR(v) ((v) & MAX_M5_STATUS_VMX_CLEAR)
+#define MAX_M5_STATUS_VMX_SET(v) \
+ (MAX_M5_STATUS_VMX_CLR(v) | MAX_M5_STATUS_VMX)
+#define MAX_M5_STATUS_BI_MASK 0x1
+#define MAX_M5_STATUS_BI_SHIFT 11
+#define MAX_M5_STATUS_BI_CLEAR (~(0x1 << 11))
+#define MAX_M5_STATUS_BI_CLR(v) ((v) & MAX_M5_STATUS_BI_CLEAR)
+#define MAX_M5_STATUS_BI_SET(v) \
+ (MAX_M5_STATUS_BI_CLR(v) | MAX_M5_STATUS_BI)
+#define MAX_M5_STATUS_SMN_MASK 0x1
+#define MAX_M5_STATUS_SMN_SHIFT 10
+#define MAX_M5_STATUS_SMN_CLEAR (~(0x1 << 10))
+#define MAX_M5_STATUS_SMN_CLR(v) ((v) & MAX_M5_STATUS_SMN_CLEAR)
+#define MAX_M5_STATUS_SMN_SET(v) \
+ (MAX_M5_STATUS_SMN_CLR(v) | MAX_M5_STATUS_SMN)
+#define MAX_M5_STATUS_TMN_MASK 0x1
+#define MAX_M5_STATUS_TMN_SHIFT 9
+#define MAX_M5_STATUS_TMN_CLEAR (~(0x1 << 9))
+#define MAX_M5_STATUS_TMN_CLR(v) ((v) & MAX_M5_STATUS_TMN_CLEAR)
+#define MAX_M5_STATUS_TMN_SET(v) \
+ (MAX_M5_STATUS_TMN_CLR(v) | MAX_M5_STATUS_TMN)
+#define MAX_M5_STATUS_VMN_MASK 0x1
+#define MAX_M5_STATUS_VMN_SHIFT 8
+#define MAX_M5_STATUS_VMN_CLEAR (~(0x1 << 8))
+#define MAX_M5_STATUS_VMN_CLR(v) ((v) & MAX_M5_STATUS_VMN_CLEAR)
+#define MAX_M5_STATUS_VMN_SET(v) \
+ (MAX_M5_STATUS_VMN_CLR(v) | MAX_M5_STATUS_VMN)
+#define MAX_M5_STATUS_DSOCI_MASK 0x1
+#define MAX_M5_STATUS_DSOCI_SHIFT 7
+#define MAX_M5_STATUS_DSOCI_CLEAR (~(0x1 << 7))
+#define MAX_M5_STATUS_DSOCI_CLR(v) ((v) & MAX_M5_STATUS_DSOCI_CLEAR)
+#define MAX_M5_STATUS_DSOCI_SET(v) \
+ (MAX_M5_STATUS_DSOCI_CLR(v) | MAX_M5_STATUS_DSOCI)
+#define MAX_M5_STATUS_THMHOT_MASK 0x1
+#define MAX_M5_STATUS_THMHOT_SHIFT 6
+#define MAX_M5_STATUS_THMHOT_CLEAR (~(0x1 << 6))
+#define MAX_M5_STATUS_THMHOT_CLR(v) \
+ ((v) & MAX_M5_STATUS_THMHOT_CLEAR)
+#define MAX_M5_STATUS_THMHOT_SET(v) \
+ (MAX_M5_STATUS_THMHOT_CLR(v) | MAX_M5_STATUS_THMHOT)
+#define MAX_M5_STATUS_SPR_5_MASK 0x1
+#define MAX_M5_STATUS_SPR_5_SHIFT 5
+#define MAX_M5_STATUS_SPR_5_CLEAR (~(0x1 << 5))
+#define MAX_M5_STATUS_SPR_5_CLR(v) ((v) & MAX_M5_STATUS_SPR_5_CLEAR)
+#define MAX_M5_STATUS_SPR_5_SET(v) \
+ (MAX_M5_STATUS_SPR_5_CLR(v) | MAX_M5_STATUS_SPR_5)
+#define MAX_M5_STATUS_ISYSMX_MASK 0x1
+#define MAX_M5_STATUS_ISYSMX_SHIFT 4
+#define MAX_M5_STATUS_ISYSMX_CLEAR (~(0x1 << 4))
+#define MAX_M5_STATUS_ISYSMX_CLR(v) \
+ ((v) & MAX_M5_STATUS_ISYSMX_CLEAR)
+#define MAX_M5_STATUS_ISYSMX_SET(v) \
+ (MAX_M5_STATUS_ISYSMX_CLR(v) | MAX_M5_STATUS_ISYSMX)
+#define MAX_M5_STATUS_BST_MASK 0x1
+#define MAX_M5_STATUS_BST_SHIFT 3
+#define MAX_M5_STATUS_BST_CLEAR (~(0x1 << 3))
+#define MAX_M5_STATUS_BST_CLR(v) ((v) & MAX_M5_STATUS_BST_CLEAR)
+#define MAX_M5_STATUS_BST_SET(v) \
+ (MAX_M5_STATUS_BST_CLR(v) | MAX_M5_STATUS_BST)
+#define MAX_M5_STATUS_SPR_2_MASK 0x1
+#define MAX_M5_STATUS_SPR_2_SHIFT 2
+#define MAX_M5_STATUS_SPR_2_CLEAR (~(0x1 << 2))
+#define MAX_M5_STATUS_SPR_2_CLR(v) ((v) & MAX_M5_STATUS_SPR_2_CLEAR)
+#define MAX_M5_STATUS_SPR_2_SET(v) \
+ (MAX_M5_STATUS_SPR_2_CLR(v) | MAX_M5_STATUS_SPR_2)
+#define MAX_M5_STATUS_POR_MASK 0x1
+#define MAX_M5_STATUS_POR_SHIFT 1
+#define MAX_M5_STATUS_POR_CLEAR (~(0x1 << 1))
+#define MAX_M5_STATUS_POR_CLR(v) ((v) & MAX_M5_STATUS_POR_CLEAR)
+#define MAX_M5_STATUS_POR_SET(v) \
+ (MAX_M5_STATUS_POR_CLR(v) | MAX_M5_STATUS_POR)
+#define MAX_M5_STATUS_IMN_MASK 0x1
+#define MAX_M5_STATUS_IMN_SHIFT 0
+#define MAX_M5_STATUS_IMN_CLEAR (~(0x1 << 0))
+#define MAX_M5_STATUS_IMN_CLR(v) ((v) & MAX_M5_STATUS_IMN_CLEAR)
+#define MAX_M5_STATUS_IMN_SET(v) \
+ (MAX_M5_STATUS_IMN_CLR(v) | MAX_M5_STATUS_IMN)
+
+/* VAlrtTh,0x1,0b1111111100000000,0xff00
+ * MaxVoltageAlrt[7:0],,,,,,
+ */
+#define MAX_M5_VALRTTH 0x1
+#define MAX_M5_VALRTTH_MAXVOLTAGEALRT (0xff << 8)
+#define MAX_M5_VALRTTH_MINVOLTAGEALRT (0xff << 0)
+
+#define MAX_M5_VALRTTH_MAXVOLTAGEALRT_MASK 0xff
+#define MAX_M5_VALRTTH_MAXVOLTAGEALRT_SHIFT 8
+#define MAX_M5_VALRTTH_MAXVOLTAGEALRT_CLEAR (~(0xff << 8))
+#define MAX_M5_VALRTTH_MAXVOLTAGEALRT_CLR(v) \
+ ((v) & MAX_M5_VALRTTH_MAXVOLTAGEALRT_CLEAR)
+#define MAX_M5_VALRTTH_MAXVOLTAGEALRT_SET(v) \
+ (MAX_M5_VALRTTH_MAXVOLTAGEALRT_CLR(v) | MAX_M5_VALRTTH_MAXVOLTAGEALRT)
+#define MAX_M5_VALRTTH_MINVOLTAGEALRT_MASK 0xff
+#define MAX_M5_VALRTTH_MINVOLTAGEALRT_SHIFT 0
+#define MAX_M5_VALRTTH_MINVOLTAGEALRT_CLEAR (~(0xff << 0))
+#define MAX_M5_VALRTTH_MINVOLTAGEALRT_CLR(v) \
+ ((v) & MAX_M5_VALRTTH_MINVOLTAGEALRT_CLEAR)
+#define MAX_M5_VALRTTH_MINVOLTAGEALRT_SET(v) \
+ (MAX_M5_VALRTTH_MINVOLTAGEALRT_CLR(v) | MAX_M5_VALRTTH_MINVOLTAGEALRT)
+
+/* TAlrtTh,0x2,0b111111110000000,0x7f80
+ * MaxTempAlrt[7:0],,,,,,
+ */
+#define MAX_M5_TALRTTH 0x2
+#define MAX_M5_TALRTTH_MAXTEMPALRT (0xff << 8)
+#define MAX_M5_TALRTTH_MINTEMPALRT (0xff << 0)
+
+#define MAX_M5_TALRTTH_MAXTEMPALRT_MASK 0xff
+#define MAX_M5_TALRTTH_MAXTEMPALRT_SHIFT 8
+#define MAX_M5_TALRTTH_MAXTEMPALRT_CLEAR (~(0xff << 8))
+#define MAX_M5_TALRTTH_MAXTEMPALRT_CLR(v) \
+ ((v) & MAX_M5_TALRTTH_MAXTEMPALRT_CLEAR)
+#define MAX_M5_TALRTTH_MAXTEMPALRT_SET(v) \
+ (MAX_M5_TALRTTH_MAXTEMPALRT_CLR(v) | MAX_M5_TALRTTH_MAXTEMPALRT)
+#define MAX_M5_TALRTTH_MINTEMPALRT_MASK 0xff
+#define MAX_M5_TALRTTH_MINTEMPALRT_SHIFT 0
+#define MAX_M5_TALRTTH_MINTEMPALRT_CLEAR (~(0xff << 0))
+#define MAX_M5_TALRTTH_MINTEMPALRT_CLR(v) \
+ ((v) & MAX_M5_TALRTTH_MINTEMPALRT_CLEAR)
+#define MAX_M5_TALRTTH_MINTEMPALRT_SET(v) \
+ (MAX_M5_TALRTTH_MINTEMPALRT_CLR(v) | MAX_M5_TALRTTH_MINTEMPALRT)
+
+/* SAlrtTh,0x3,0b1111111100000000,0xff00
+ * MaxSocAlrt[7:0],,,,,,
+ */
+#define MAX_M5_SALRTTH 0x3
+#define MAX_M5_SALRTTH_MAXSOCALRT (0xff << 8)
+#define MAX_M5_SALRTTH_MINSOCALRT (0xff << 0)
+
+#define MAX_M5_SALRTTH_MAXSOCALRT_MASK 0xff
+#define MAX_M5_SALRTTH_MAXSOCALRT_SHIFT 8
+#define MAX_M5_SALRTTH_MAXSOCALRT_CLEAR (~(0xff << 8))
+#define MAX_M5_SALRTTH_MAXSOCALRT_CLR(v) \
+ ((v) & MAX_M5_SALRTTH_MAXSOCALRT_CLEAR)
+#define MAX_M5_SALRTTH_MAXSOCALRT_SET(v) \
+ (MAX_M5_SALRTTH_MAXSOCALRT_CLR(v) | MAX_M5_SALRTTH_MAXSOCALRT)
+#define MAX_M5_SALRTTH_MINSOCALRT_MASK 0xff
+#define MAX_M5_SALRTTH_MINSOCALRT_SHIFT 0
+#define MAX_M5_SALRTTH_MINSOCALRT_CLEAR (~(0xff << 0))
+#define MAX_M5_SALRTTH_MINSOCALRT_CLR(v) \
+ ((v) & MAX_M5_SALRTTH_MINSOCALRT_CLEAR)
+#define MAX_M5_SALRTTH_MINSOCALRT_SET(v) \
+ (MAX_M5_SALRTTH_MINSOCALRT_CLR(v) | MAX_M5_SALRTTH_MINSOCALRT)
+
+/* AtRate,0x4,0b00000000,0x00
+ * AtRate[15:8],,,,,,
+ */
+#define MAX_M5_ATRATE 0x4
+
+/* RepCap,0x5,0b10111011100,0x5dc
+ * RepCap[15:8],,,,,,
+ */
+#define MAX_M5_REPCAP 0x5
+
+/* RepSOC,0x6,0b11001000000000,0x3200
+ * RepSOC[15:8],,,,,,
+ */
+#define MAX_M5_REPSOC 0x6
+
+/* Age,0x7,0b110010000000000,0x6400
+ * Age[15:8],,,,,,
+ */
+#define MAX_M5_AGE 0x7
+
+/* Temp,0x8,0b1011000000000,0x1600
+ * TEMP[15:8],,,,,,
+ */
+#define MAX_M5_TEMP 0x8
+
+/* Vcell,0x9,0b1011010000000000,0xb400
+ * VCELL[15:8],,,,,,
+ */
+#define MAX_M5_VCELL 0x9
+
+/* Current,0xA,0b00000000,0x00
+ * Current[15:8],,,,,,
+ */
+#define MAX_M5_CURRENT 0xA
+
+/* AvgCurrent,0xB,0b00000000,0x00
+ * AvgCurrent[15:8],,,,,,
+ */
+#define MAX_M5_AVGCURRENT 0xB
+
+/* QResidual,0xC,0b00000000,0x00
+ * Qresidual[15:8],,,,,,
+ */
+#define MAX_M5_QRESIDUAL 0xC
+
+/* MixSOC,0xD,0b11001000000000,0x3200
+ * MixSOC[15:8],,,,,,
+ */
+#define MAX_M5_MIXSOC 0xD
+
+/* AvSOC,0xE,0b11001000000000,0x3200
+ * AvSOC[15:8],,,,,,
+ */
+#define MAX_M5_AVSOC 0xE
+
+/* MixCap,0xF,0b10111011100,0x5dc
+ * MixCapH[15:8],,,,,,
+ */
+#define MAX_M5_MIXCAP 0xF
+
+/* FullCap,0x10,0b101110111000,0xbb8
+ * FullCAP[15:8],,,,,,
+ */
+#define MAX_M5_FULLCAP 0x10
+
+/* TTE,0x11,0b00000000,0x00
+ * hr[5:0],,,,,,mn[5:4]
+ */
+#define MAX_M5_TTE 0x11
+#define MAX_M5_TTE_HR (0x3f << 10)
+#define MAX_M5_TTE_MN (0x3f << 4)
+#define MAX_M5_TTE_SEC (0xf << 0)
+
+#define MAX_M5_TTE_HR_MASK 0x3f
+#define MAX_M5_TTE_HR_SHIFT 10
+#define MAX_M5_TTE_HR_CLEAR (~(0x3f << 10))
+#define MAX_M5_TTE_HR_CLR(v) ((v) & MAX_M5_TTE_HR_CLEAR)
+#define MAX_M5_TTE_HR_SET(v) (MAX_M5_TTE_HR_CLR(v) | MAX_M5_TTE_HR)
+#define MAX_M5_TTE_MN_MASK 0x3f
+#define MAX_M5_TTE_MN_SHIFT 4
+#define MAX_M5_TTE_MN_CLEAR (~(0x3f << 4))
+#define MAX_M5_TTE_MN_CLR(v) ((v) & MAX_M5_TTE_MN_CLEAR)
+#define MAX_M5_TTE_MN_SET(v) (MAX_M5_TTE_MN_CLR(v) | MAX_M5_TTE_MN)
+#define MAX_M5_TTE_SEC_MASK 0xf
+#define MAX_M5_TTE_SEC_SHIFT 0
+#define MAX_M5_TTE_SEC_CLEAR (~(0xf << 0))
+#define MAX_M5_TTE_SEC_CLR(v) ((v) & MAX_M5_TTE_SEC_CLEAR)
+#define MAX_M5_TTE_SEC_SET(v) \
+ (MAX_M5_TTE_SEC_CLR(v) | MAX_M5_TTE_SEC)
+
+/* QRTable00,0x12,0b11110000000000,0x3c00
+ * QRTable00[15:8],,,,,,
+ */
+#define MAX_M5_QRTABLE00 0x12
+
+/* FullSocThr,0x13,0b101000000000000,0x5000
+ * FullSOCThr[15:8],,,,,,
+ */
+#define MAX_M5_FULLSOCTHR 0x13
+
+/* Rslow,0x14,0b1010010000,0x290
+ * RSLOW[15:8],,,,,,
+ */
+#define MAX_M5_RSLOW 0x14
+
+/* RFast,0x15,0b101001000,0x148
+ * RFAST[15:8],,,,,,
+ */
+#define MAX_M5_RFAST 0x15
+
+/* AvgTA,0x16,0b1011000000000,0x1600
+ * AvgTA[15:8],,,,,,
+ */
+#define MAX_M5_AVGTA 0x16
+
+/* Cycles,0x17,0b00000000,0x00
+ * Cycles[15:8],,,,,,
+ */
+#define MAX_M5_CYCLES 0x17
+
+/* DesignCap,0x18,0b101110111000,0xbb8
+ * DesignCap[15:8],,,,,,
+ */
+#define MAX_M5_DESIGNCAP 0x18
+
+/* AvgVCell,0x19,0b1011010000000000,0xb400
+ * AvgVCELL[15:8],,,,,,
+ */
+#define MAX_M5_AVGVCELL 0x19
+
+/* MaxMinTemp,0x1A,0b1000000001111111,0x807f
+ * MaxTemperature[7:0],,,,,,
+ */
+#define MAX_M5_MAXMINTEMP 0x1A
+#define MAX_M5_MAXMINTEMP_MAXTEMPERATURE (0xff << 8)
+#define MAX_M5_MAXMINTEMP_MINTEMPERATURE (0xff << 0)
+
+#define MAX_M5_MAXMINTEMP_MAXTEMPERATURE_MASK 0xff
+#define MAX_M5_MAXMINTEMP_MAXTEMPERATURE_SHIFT 8
+#define MAX_M5_MAXMINTEMP_MAXTEMPERATURE_CLEAR (~(0xff << 8))
+#define MAX_M5_MAXMINTEMP_MAXTEMPERATURE_CLR(v) \
+ ((v) & MAX_M5_MAXMINTEMP_MAXTEMPERATURE_CLEAR)
+#define MAX_M5_MAXMINTEMP_MAXTEMPERATURE_SET(v) \
+(MAX_M5_MAXMINTEMP_MAXTEMPERATURE_CLR(v) | MAX_M5_MAXMINTEMP_MAXTEMPERATURE)
+#define MAX_M5_MAXMINTEMP_MINTEMPERATURE_MASK 0xff
+#define MAX_M5_MAXMINTEMP_MINTEMPERATURE_SHIFT 0
+#define MAX_M5_MAXMINTEMP_MINTEMPERATURE_CLEAR (~(0xff << 0))
+#define MAX_M5_MAXMINTEMP_MINTEMPERATURE_CLR(v) \
+ ((v) & MAX_M5_MAXMINTEMP_MINTEMPERATURE_CLEAR)
+#define MAX_M5_MAXMINTEMP_MINTEMPERATURE_SET(v) \
+(MAX_M5_MAXMINTEMP_MINTEMPERATURE_CLR(v) | MAX_M5_MAXMINTEMP_MINTEMPERATURE)
+
+/* MaxMinVolt,0x1B,0b11111111,0xff
+ * MaxVoltage[7:0],,,,,,
+ */
+#define MAX_M5_MAXMINVOLT 0x1B
+#define MAX_M5_MAXMINVOLT_MAXVOLTAGE (0xff << 8)
+#define MAX_M5_MAXMINVOLT_MINVOLTAGE (0xff << 0)
+
+#define MAX_M5_MAXMINVOLT_MAXVOLTAGE_MASK 0xff
+#define MAX_M5_MAXMINVOLT_MAXVOLTAGE_SHIFT 8
+#define MAX_M5_MAXMINVOLT_MAXVOLTAGE_CLEAR (~(0xff << 8))
+#define MAX_M5_MAXMINVOLT_MAXVOLTAGE_CLR(v) \
+ ((v) & MAX_M5_MAXMINVOLT_MAXVOLTAGE_CLEAR)
+#define MAX_M5_MAXMINVOLT_MAXVOLTAGE_SET(v) \
+ (MAX_M5_MAXMINVOLT_MAXVOLTAGE_CLR(v) | MAX_M5_MAXMINVOLT_MAXVOLTAGE)
+#define MAX_M5_MAXMINVOLT_MINVOLTAGE_MASK 0xff
+#define MAX_M5_MAXMINVOLT_MINVOLTAGE_SHIFT 0
+#define MAX_M5_MAXMINVOLT_MINVOLTAGE_CLEAR (~(0xff << 0))
+#define MAX_M5_MAXMINVOLT_MINVOLTAGE_CLR(v) \
+ ((v) & MAX_M5_MAXMINVOLT_MINVOLTAGE_CLEAR)
+#define MAX_M5_MAXMINVOLT_MINVOLTAGE_SET(v) \
+ (MAX_M5_MAXMINVOLT_MINVOLTAGE_CLR(v) | MAX_M5_MAXMINVOLT_MINVOLTAGE)
+
+/* MaxMinCurr,0x1C,0b1000000001111111,0x807f
+ * MaxChargeCurrent[7:0],,,,,,
+ */
+#define MAX_M5_MAXMINCURR 0x1C
+#define MAX_M5_MAXMINCURR_MAXCHARGECURRENT (0xff << 8)
+#define MAX_M5_MAXMINCURR_MAXDISCURRENT (0xff << 0)
+
+#define MAX_M5_MAXMINCURR_MAXCHARGECURRENT_MASK 0xff
+#define MAX_M5_MAXMINCURR_MAXCHARGECURRENT_SHIFT 8
+#define MAX_M5_MAXMINCURR_MAXCHARGECURRENT_CLEAR (~(0xff << 8))
+#define MAX_M5_MAXMINCURR_MAXCHARGECURRENT_CLR(v) \
+ ((v) & MAX_M5_MAXMINCURR_MAXCHARGECURRENT_CLEAR)
+#define MAX_M5_MAXMINCURR_MAXCHARGECURRENT_SET(v) \
+(MAX_M5_MAXMINCURR_MAXCHARGECURRENT_CLR(v) | MAX_M5_MAXMINCURR_MAXCHARGECURRENT)
+#define MAX_M5_MAXMINCURR_MAXDISCURRENT_MASK 0xff
+#define MAX_M5_MAXMINCURR_MAXDISCURRENT_SHIFT 0
+#define MAX_M5_MAXMINCURR_MAXDISCURRENT_CLEAR (~(0xff << 0))
+#define MAX_M5_MAXMINCURR_MAXDISCURRENT_CLR(v) \
+ ((v) & MAX_M5_MAXMINCURR_MAXDISCURRENT_CLEAR)
+#define MAX_M5_MAXMINCURR_MAXDISCURRENT_SET(v) \
+(MAX_M5_MAXMINCURR_MAXDISCURRENT_CLR(v) | MAX_M5_MAXMINCURR_MAXDISCURRENT)
+
+/* Config,0x1D,0b10001101010000,0x2350
+ * FCFE,Ss,Ts,Vs,FGCC,AINSH,Ten
+ */
+#define MAX_M5_CONFIG 0x1D
+#define MAX_M5_CONFIG_FCFE (0x1 << 15)
+#define MAX_M5_CONFIG_SS (0x1 << 14)
+#define MAX_M5_CONFIG_TS (0x1 << 13)
+#define MAX_M5_CONFIG_VS (0x1 << 12)
+#define MAX_M5_CONFIG_FGCC (0x1 << 11)
+#define MAX_M5_CONFIG_AINSH (0x1 << 10)
+#define MAX_M5_CONFIG_TEN (0x1 << 9)
+#define MAX_M5_CONFIG_TEX (0x1 << 8)
+#define MAX_M5_CONFIG_SHDN (0x1 << 7)
+#define MAX_M5_CONFIG_I2CSH (0x1 << 6)
+#define MAX_M5_CONFIG_ICFE (0x1 << 5)
+#define MAX_M5_CONFIG_ETHRM (0x1 << 4)
+#define MAX_M5_CONFIG_FTHRM (0x1 << 3)
+#define MAX_M5_CONFIG_AEN (0x1 << 2)
+#define MAX_M5_CONFIG_BEI (0x1 << 1)
+#define MAX_M5_CONFIG_BER (0x1 << 0)
+
+#define MAX_M5_CONFIG_FCFE_MASK 0x1
+#define MAX_M5_CONFIG_FCFE_SHIFT 15
+#define MAX_M5_CONFIG_FCFE_CLEAR (~(0x1 << 15))
+#define MAX_M5_CONFIG_FCFE_CLR(v) ((v) & MAX_M5_CONFIG_FCFE_CLEAR)
+#define MAX_M5_CONFIG_FCFE_SET(v) \
+ (MAX_M5_CONFIG_FCFE_CLR(v) | MAX_M5_CONFIG_FCFE)
+#define MAX_M5_CONFIG_SS_MASK 0x1
+#define MAX_M5_CONFIG_SS_SHIFT 14
+#define MAX_M5_CONFIG_SS_CLEAR (~(0x1 << 14))
+#define MAX_M5_CONFIG_SS_CLR(v) ((v) & MAX_M5_CONFIG_SS_CLEAR)
+#define MAX_M5_CONFIG_SS_SET(v) \
+ (MAX_M5_CONFIG_SS_CLR(v) | MAX_M5_CONFIG_SS)
+#define MAX_M5_CONFIG_TS_MASK 0x1
+#define MAX_M5_CONFIG_TS_SHIFT 13
+#define MAX_M5_CONFIG_TS_CLEAR (~(0x1 << 13))
+#define MAX_M5_CONFIG_TS_CLR(v) ((v) & MAX_M5_CONFIG_TS_CLEAR)
+#define MAX_M5_CONFIG_TS_SET(v) \
+ (MAX_M5_CONFIG_TS_CLR(v) | MAX_M5_CONFIG_TS)
+#define MAX_M5_CONFIG_VS_MASK 0x1
+#define MAX_M5_CONFIG_VS_SHIFT 12
+#define MAX_M5_CONFIG_VS_CLEAR (~(0x1 << 12))
+#define MAX_M5_CONFIG_VS_CLR(v) ((v) & MAX_M5_CONFIG_VS_CLEAR)
+#define MAX_M5_CONFIG_VS_SET(v) \
+ (MAX_M5_CONFIG_VS_CLR(v) | MAX_M5_CONFIG_VS)
+#define MAX_M5_CONFIG_FGCC_MASK 0x1
+#define MAX_M5_CONFIG_FGCC_SHIFT 11
+#define MAX_M5_CONFIG_FGCC_CLEAR (~(0x1 << 11))
+#define MAX_M5_CONFIG_FGCC_CLR(v) ((v) & MAX_M5_CONFIG_FGCC_CLEAR)
+#define MAX_M5_CONFIG_FGCC_SET(v) \
+ (MAX_M5_CONFIG_FGCC_CLR(v) | MAX_M5_CONFIG_FGCC)
+#define MAX_M5_CONFIG_AINSH_MASK 0x1
+#define MAX_M5_CONFIG_AINSH_SHIFT 10
+#define MAX_M5_CONFIG_AINSH_CLEAR (~(0x1 << 10))
+#define MAX_M5_CONFIG_AINSH_CLR(v) ((v) & MAX_M5_CONFIG_AINSH_CLEAR)
+#define MAX_M5_CONFIG_AINSH_SET(v) \
+ (MAX_M5_CONFIG_AINSH_CLR(v) | MAX_M5_CONFIG_AINSH)
+#define MAX_M5_CONFIG_TEN_MASK 0x1
+#define MAX_M5_CONFIG_TEN_SHIFT 9
+#define MAX_M5_CONFIG_TEN_CLEAR (~(0x1 << 9))
+#define MAX_M5_CONFIG_TEN_CLR(v) ((v) & MAX_M5_CONFIG_TEN_CLEAR)
+#define MAX_M5_CONFIG_TEN_SET(v) \
+ (MAX_M5_CONFIG_TEN_CLR(v) | MAX_M5_CONFIG_TEN)
+#define MAX_M5_CONFIG_TEX_MASK 0x1
+#define MAX_M5_CONFIG_TEX_SHIFT 8
+#define MAX_M5_CONFIG_TEX_CLEAR (~(0x1 << 8))
+#define MAX_M5_CONFIG_TEX_CLR(v) ((v) & MAX_M5_CONFIG_TEX_CLEAR)
+#define MAX_M5_CONFIG_TEX_SET(v) \
+ (MAX_M5_CONFIG_TEX_CLR(v) | MAX_M5_CONFIG_TEX)
+#define MAX_M5_CONFIG_SHDN_MASK 0x1
+#define MAX_M5_CONFIG_SHDN_SHIFT 7
+#define MAX_M5_CONFIG_SHDN_CLEAR (~(0x1 << 7))
+#define MAX_M5_CONFIG_SHDN_CLR(v) ((v) & MAX_M5_CONFIG_SHDN_CLEAR)
+#define MAX_M5_CONFIG_SHDN_SET(v) \
+ (MAX_M5_CONFIG_SHDN_CLR(v) | MAX_M5_CONFIG_SHDN)
+#define MAX_M5_CONFIG_I2CSH_MASK 0x1
+#define MAX_M5_CONFIG_I2CSH_SHIFT 6
+#define MAX_M5_CONFIG_I2CSH_CLEAR (~(0x1 << 6))
+#define MAX_M5_CONFIG_I2CSH_CLR(v) ((v) & MAX_M5_CONFIG_I2CSH_CLEAR)
+#define MAX_M5_CONFIG_I2CSH_SET(v) \
+ (MAX_M5_CONFIG_I2CSH_CLR(v) | MAX_M5_CONFIG_I2CSH)
+#define MAX_M5_CONFIG_ICFE_MASK 0x1
+#define MAX_M5_CONFIG_ICFE_SHIFT 5
+#define MAX_M5_CONFIG_ICFE_CLEAR (~(0x1 << 5))
+#define MAX_M5_CONFIG_ICFE_CLR(v) ((v) & MAX_M5_CONFIG_ICFE_CLEAR)
+#define MAX_M5_CONFIG_ICFE_SET(v) \
+ (MAX_M5_CONFIG_ICFE_CLR(v) | MAX_M5_CONFIG_ICFE)
+#define MAX_M5_CONFIG_ETHRM_MASK 0x1
+#define MAX_M5_CONFIG_ETHRM_SHIFT 4
+#define MAX_M5_CONFIG_ETHRM_CLEAR (~(0x1 << 4))
+#define MAX_M5_CONFIG_ETHRM_CLR(v) ((v) & MAX_M5_CONFIG_ETHRM_CLEAR)
+#define MAX_M5_CONFIG_ETHRM_SET(v) \
+ (MAX_M5_CONFIG_ETHRM_CLR(v) | MAX_M5_CONFIG_ETHRM)
+#define MAX_M5_CONFIG_FTHRM_MASK 0x1
+#define MAX_M5_CONFIG_FTHRM_SHIFT 3
+#define MAX_M5_CONFIG_FTHRM_CLEAR (~(0x1 << 3))
+#define MAX_M5_CONFIG_FTHRM_CLR(v) ((v) & MAX_M5_CONFIG_FTHRM_CLEAR)
+#define MAX_M5_CONFIG_FTHRM_SET(v) \
+ (MAX_M5_CONFIG_FTHRM_CLR(v) | MAX_M5_CONFIG_FTHRM)
+#define MAX_M5_CONFIG_AEN_MASK 0x1
+#define MAX_M5_CONFIG_AEN_SHIFT 2
+#define MAX_M5_CONFIG_AEN_CLEAR (~(0x1 << 2))
+#define MAX_M5_CONFIG_AEN_CLR(v) ((v) & MAX_M5_CONFIG_AEN_CLEAR)
+#define MAX_M5_CONFIG_AEN_SET(v) \
+ (MAX_M5_CONFIG_AEN_CLR(v) | MAX_M5_CONFIG_AEN)
+#define MAX_M5_CONFIG_BEI_MASK 0x1
+#define MAX_M5_CONFIG_BEI_SHIFT 1
+#define MAX_M5_CONFIG_BEI_CLEAR (~(0x1 << 1))
+#define MAX_M5_CONFIG_BEI_CLR(v) ((v) & MAX_M5_CONFIG_BEI_CLEAR)
+#define MAX_M5_CONFIG_BEI_SET(v) \
+ (MAX_M5_CONFIG_BEI_CLR(v) | MAX_M5_CONFIG_BEI)
+#define MAX_M5_CONFIG_BER_MASK 0x1
+#define MAX_M5_CONFIG_BER_SHIFT 0
+#define MAX_M5_CONFIG_BER_CLEAR (~(0x1 << 0))
+#define MAX_M5_CONFIG_BER_CLR(v) ((v) & MAX_M5_CONFIG_BER_CLEAR)
+#define MAX_M5_CONFIG_BER_SET(v) \
+ (MAX_M5_CONFIG_BER_CLR(v) | MAX_M5_CONFIG_BER)
+
+/* IChgTerm,0x1E,0b1111000000,0x3c0
+ * ICHGTerm[15:8],,,,,,
+ */
+#define MAX_M5_ICHGTERM 0x1E
+
+/* AvCap,0x1F,0b10111011100,0x5dc
+ * AvCap[15:8],,,,,,
+ */
+#define MAX_M5_AVCAP 0x1F
+
+/* TTF,0x20,0b1111111111111111,0xffff
+ * hr[5:0],,,,,,mn[5:4]
+ */
+#define MAX_M5_TTF 0x20
+#define MAX_M5_TTF_HR (0x3f << 10)
+#define MAX_M5_TTF_MN (0x3f << 4)
+#define MAX_M5_TTF_SEC (0xf << 0)
+
+#define MAX_M5_TTF_HR_MASK 0x3f
+#define MAX_M5_TTF_HR_SHIFT 10
+#define MAX_M5_TTF_HR_CLEAR (~(0x3f << 10))
+#define MAX_M5_TTF_HR_CLR(v) ((v) & MAX_M5_TTF_HR_CLEAR)
+#define MAX_M5_TTF_HR_SET(v) (MAX_M5_TTF_HR_CLR(v) | MAX_M5_TTF_HR)
+#define MAX_M5_TTF_MN_MASK 0x3f
+#define MAX_M5_TTF_MN_SHIFT 4
+#define MAX_M5_TTF_MN_CLEAR (~(0x3f << 4))
+#define MAX_M5_TTF_MN_CLR(v) ((v) & MAX_M5_TTF_MN_CLEAR)
+#define MAX_M5_TTF_MN_SET(v) (MAX_M5_TTF_MN_CLR(v) | MAX_M5_TTF_MN)
+#define MAX_M5_TTF_SEC_MASK 0xf
+#define MAX_M5_TTF_SEC_SHIFT 0
+#define MAX_M5_TTF_SEC_CLEAR (~(0xf << 0))
+#define MAX_M5_TTF_SEC_CLR(v) ((v) & MAX_M5_TTF_SEC_CLEAR)
+#define MAX_M5_TTF_SEC_SET(v) \
+ (MAX_M5_TTF_SEC_CLR(v) | MAX_M5_TTF_SEC)
+
+/* DevName,0x21,0b110001000000000,0x6200
+ * DevName[15:8],,,,,,
+ */
+#define MAX_M5_DEVNAME 0x21
+
+/* QRTable10,0x22,0b1101110000000,0x1b80
+ * QRTable10[15:8],,,,,,
+ */
+#define MAX_M5_QRTABLE10 0x22
+
+/* FullCapNom,0x23,0b101110111000,0xbb8
+ * FullCapNom[15:8],,,,,,
+ */
+#define MAX_M5_FULLCAPNOM 0x23
+
+/* TempNom,0x24,0b1010000000000,0x1400
+ * TempNom[9:2],,,,,,
+ */
+#define MAX_M5_TEMPNOM 0x24
+#define MAX_M5_TEMPNOM_TEMPNOM (0x3ff << 6)
+#define MAX_M5_TEMPNOM_SPR_5_0 (0x3f << 0)
+
+#define MAX_M5_TEMPNOM_TEMPNOM_MASK 0x3ff
+#define MAX_M5_TEMPNOM_TEMPNOM_SHIFT 6
+#define MAX_M5_TEMPNOM_TEMPNOM_CLEAR (~(0x3ff << 6))
+#define MAX_M5_TEMPNOM_TEMPNOM_CLR(v) \
+ ((v) & MAX_M5_TEMPNOM_TEMPNOM_CLEAR)
+#define MAX_M5_TEMPNOM_TEMPNOM_SET(v) \
+ (MAX_M5_TEMPNOM_TEMPNOM_CLR(v) | MAX_M5_TEMPNOM_TEMPNOM)
+#define MAX_M5_TEMPNOM_SPR_5_0_MASK 0x3f
+#define MAX_M5_TEMPNOM_SPR_5_0_SHIFT 0
+#define MAX_M5_TEMPNOM_SPR_5_0_CLEAR (~(0x3f << 0))
+#define MAX_M5_TEMPNOM_SPR_5_0_CLR(v) \
+ ((v) & MAX_M5_TEMPNOM_SPR_5_0_CLEAR)
+#define MAX_M5_TEMPNOM_SPR_5_0_SET(v) \
+ (MAX_M5_TEMPNOM_SPR_5_0_CLR(v) | MAX_M5_TEMPNOM_SPR_5_0)
+
+/* TempLim,0x25,0b10001100000101,0x2305
+ * TempHot[7:0],,,,,,
+ */
+#define MAX_M5_TEMPLIM 0x25
+#define MAX_M5_TEMPLIM_TEMPHOT (0xff << 8)
+#define MAX_M5_TEMPLIM_TEMPCOLD (0xff << 0)
+
+#define MAX_M5_TEMPLIM_TEMPHOT_MASK 0xff
+#define MAX_M5_TEMPLIM_TEMPHOT_SHIFT 8
+#define MAX_M5_TEMPLIM_TEMPHOT_CLEAR (~(0xff << 8))
+#define MAX_M5_TEMPLIM_TEMPHOT_CLR(v) \
+ ((v) & MAX_M5_TEMPLIM_TEMPHOT_CLEAR)
+#define MAX_M5_TEMPLIM_TEMPHOT_SET(v) \
+ (MAX_M5_TEMPLIM_TEMPHOT_CLR(v) | MAX_M5_TEMPLIM_TEMPHOT)
+#define MAX_M5_TEMPLIM_TEMPCOLD_MASK 0xff
+#define MAX_M5_TEMPLIM_TEMPCOLD_SHIFT 0
+#define MAX_M5_TEMPLIM_TEMPCOLD_CLEAR (~(0xff << 0))
+#define MAX_M5_TEMPLIM_TEMPCOLD_CLR(v) \
+ ((v) & MAX_M5_TEMPLIM_TEMPCOLD_CLEAR)
+#define MAX_M5_TEMPLIM_TEMPCOLD_SET(v) \
+ (MAX_M5_TEMPLIM_TEMPCOLD_CLR(v) | MAX_M5_TEMPLIM_TEMPCOLD)
+
+/* AvgTA0,0x26,0b1011000000000,0x1600
+ * AvgTA0[15:8],,,,,,
+ */
+#define MAX_M5_AVGTA0 0x26
+
+/* AIN0,0x27,0b1000100011010000,0x88d0
+ * AIN0[15:8],,,,,,
+ */
+#define MAX_M5_AIN0 0x27
+
+/* LearnCfg,0x28,0b10011000000011,0x2603
+ * LearnRCOMP[2:0],,,LearnTCO[2:0],,,FCLm[1:0]
+ */
+#define MAX_M5_LEARNCFG 0x28
+#define MAX_M5_LEARNCFG_LEARNRCOMP (0x7 << 13)
+#define MAX_M5_LEARNCFG_LEARNTCO (0x7 << 10)
+#define MAX_M5_LEARNCFG_FCLM (0x3 << 8)
+#define MAX_M5_LEARNCFG_FCX (0x1 << 7)
+#define MAX_M5_LEARNCFG_FCLRNSTAGE (0x7 << 4)
+#define MAX_M5_LEARNCFG_SPR_3 (0x1 << 3)
+#define MAX_M5_LEARNCFG_FILLEMPTY (0x1 << 2)
+#define MAX_M5_LEARNCFG_MIXEN (0x1 << 1)
+#define MAX_M5_LEARNCFG_SPR_0 (0x1 << 0)
+
+#define MAX_M5_LEARNCFG_LEARNRCOMP_MASK 0x7
+#define MAX_M5_LEARNCFG_LEARNRCOMP_SHIFT 13
+#define MAX_M5_LEARNCFG_LEARNRCOMP_CLEAR (~(0x7 << 13))
+#define MAX_M5_LEARNCFG_LEARNRCOMP_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_LEARNRCOMP_CLEAR)
+#define MAX_M5_LEARNCFG_LEARNRCOMP_SET(v) \
+ (MAX_M5_LEARNCFG_LEARNRCOMP_CLR(v) | MAX_M5_LEARNCFG_LEARNRCOMP)
+#define MAX_M5_LEARNCFG_LEARNTCO_MASK 0x7
+#define MAX_M5_LEARNCFG_LEARNTCO_SHIFT 10
+#define MAX_M5_LEARNCFG_LEARNTCO_CLEAR (~(0x7 << 10))
+#define MAX_M5_LEARNCFG_LEARNTCO_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_LEARNTCO_CLEAR)
+#define MAX_M5_LEARNCFG_LEARNTCO_SET(v) \
+ (MAX_M5_LEARNCFG_LEARNTCO_CLR(v) | MAX_M5_LEARNCFG_LEARNTCO)
+#define MAX_M5_LEARNCFG_FCLM_MASK 0x3
+#define MAX_M5_LEARNCFG_FCLM_SHIFT 8
+#define MAX_M5_LEARNCFG_FCLM_CLEAR (~(0x3 << 8))
+#define MAX_M5_LEARNCFG_FCLM_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_FCLM_CLEAR)
+#define MAX_M5_LEARNCFG_FCLM_SET(v) \
+ (MAX_M5_LEARNCFG_FCLM_CLR(v) | MAX_M5_LEARNCFG_FCLM)
+#define MAX_M5_LEARNCFG_FCX_MASK 0x1
+#define MAX_M5_LEARNCFG_FCX_SHIFT 7
+#define MAX_M5_LEARNCFG_FCX_CLEAR (~(0x1 << 7))
+#define MAX_M5_LEARNCFG_FCX_CLR(v) ((v) & MAX_M5_LEARNCFG_FCX_CLEAR)
+#define MAX_M5_LEARNCFG_FCX_SET(v) \
+ (MAX_M5_LEARNCFG_FCX_CLR(v) | MAX_M5_LEARNCFG_FCX)
+#define MAX_M5_LEARNCFG_FCLRNSTAGE_MASK 0x7
+#define MAX_M5_LEARNCFG_FCLRNSTAGE_SHIFT 4
+#define MAX_M5_LEARNCFG_FCLRNSTAGE_CLEAR (~(0x7 << 4))
+#define MAX_M5_LEARNCFG_FCLRNSTAGE_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_FCLRNSTAGE_CLEAR)
+#define MAX_M5_LEARNCFG_FCLRNSTAGE_SET(v) \
+ (MAX_M5_LEARNCFG_FCLRNSTAGE_CLR(v) | MAX_M5_LEARNCFG_FCLRNSTAGE)
+#define MAX_M5_LEARNCFG_SPR_3_MASK 0x1
+#define MAX_M5_LEARNCFG_SPR_3_SHIFT 3
+#define MAX_M5_LEARNCFG_SPR_3_CLEAR (~(0x1 << 3))
+#define MAX_M5_LEARNCFG_SPR_3_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_SPR_3_CLEAR)
+#define MAX_M5_LEARNCFG_SPR_3_SET(v) \
+ (MAX_M5_LEARNCFG_SPR_3_CLR(v) | MAX_M5_LEARNCFG_SPR_3)
+#define MAX_M5_LEARNCFG_FILLEMPTY_MASK 0x1
+#define MAX_M5_LEARNCFG_FILLEMPTY_SHIFT 2
+#define MAX_M5_LEARNCFG_FILLEMPTY_CLEAR (~(0x1 << 2))
+#define MAX_M5_LEARNCFG_FILLEMPTY_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_FILLEMPTY_CLEAR)
+#define MAX_M5_LEARNCFG_FILLEMPTY_SET(v) \
+ (MAX_M5_LEARNCFG_FILLEMPTY_CLR(v) | MAX_M5_LEARNCFG_FILLEMPTY)
+#define MAX_M5_LEARNCFG_MIXEN_MASK 0x1
+#define MAX_M5_LEARNCFG_MIXEN_SHIFT 1
+#define MAX_M5_LEARNCFG_MIXEN_CLEAR (~(0x1 << 1))
+#define MAX_M5_LEARNCFG_MIXEN_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_MIXEN_CLEAR)
+#define MAX_M5_LEARNCFG_MIXEN_SET(v) \
+ (MAX_M5_LEARNCFG_MIXEN_CLR(v) | MAX_M5_LEARNCFG_MIXEN)
+#define MAX_M5_LEARNCFG_SPR_0_MASK 0x1
+#define MAX_M5_LEARNCFG_SPR_0_SHIFT 0
+#define MAX_M5_LEARNCFG_SPR_0_CLEAR (~(0x1 << 0))
+#define MAX_M5_LEARNCFG_SPR_0_CLR(v) \
+ ((v) & MAX_M5_LEARNCFG_SPR_0_CLEAR)
+#define MAX_M5_LEARNCFG_SPR_0_SET(v) \
+ (MAX_M5_LEARNCFG_SPR_0_CLR(v) | MAX_M5_LEARNCFG_SPR_0)
+
+/* FilterCfg,0x29,0b1100111010100100,0xcea4
+ * NEMPTY[1:0],,NTEMP[2:0],,,NMIX[3:1],
+ */
+#define MAX_M5_FILTERCFG 0x29
+#define MAX_M5_FILTERCFG_NEMPTY (0x3 << 14)
+#define MAX_M5_FILTERCFG_NTEMP (0x7 << 11)
+#define MAX_M5_FILTERCFG_NMIX (0x7 << 8)
+#define MAX_M5_FILTERCFG_NAVGCELL (0x7 << 5)
+#define MAX_M5_FILTERCFG_NCURR (0xf << 1)
+
+#define MAX_M5_FILTERCFG_NEMPTY_MASK 0x3
+#define MAX_M5_FILTERCFG_NEMPTY_SHIFT 14
+#define MAX_M5_FILTERCFG_NEMPTY_CLEAR (~(0x3 << 14))
+#define MAX_M5_FILTERCFG_NEMPTY_CLR(v) \
+ ((v) & MAX_M5_FILTERCFG_NEMPTY_CLEAR)
+#define MAX_M5_FILTERCFG_NEMPTY_SET(v) \
+ (MAX_M5_FILTERCFG_NEMPTY_CLR(v) | MAX_M5_FILTERCFG_NEMPTY)
+#define MAX_M5_FILTERCFG_NTEMP_MASK 0x7
+#define MAX_M5_FILTERCFG_NTEMP_SHIFT 11
+#define MAX_M5_FILTERCFG_NTEMP_CLEAR (~(0x7 << 11))
+#define MAX_M5_FILTERCFG_NTEMP_CLR(v) \
+ ((v) & MAX_M5_FILTERCFG_NTEMP_CLEAR)
+#define MAX_M5_FILTERCFG_NTEMP_SET(v) \
+ (MAX_M5_FILTERCFG_NTEMP_CLR(v) | MAX_M5_FILTERCFG_NTEMP)
+#define MAX_M5_FILTERCFG_NMIX_MASK 0x7
+#define MAX_M5_FILTERCFG_NMIX_SHIFT 8
+#define MAX_M5_FILTERCFG_NMIX_CLEAR (~(0x7 << 8))
+#define MAX_M5_FILTERCFG_NMIX_CLR(v) \
+ ((v) & MAX_M5_FILTERCFG_NMIX_CLEAR)
+#define MAX_M5_FILTERCFG_NMIX_SET(v) \
+ (MAX_M5_FILTERCFG_NMIX_CLR(v) | MAX_M5_FILTERCFG_NMIX)
+#define MAX_M5_FILTERCFG_NAVGCELL_MASK 0x7
+#define MAX_M5_FILTERCFG_NAVGCELL_SHIFT 5
+#define MAX_M5_FILTERCFG_NAVGCELL_CLEAR (~(0x7 << 5))
+#define MAX_M5_FILTERCFG_NAVGCELL_CLR(v) \
+ ((v) & MAX_M5_FILTERCFG_NAVGCELL_CLEAR)
+#define MAX_M5_FILTERCFG_NAVGCELL_SET(v) \
+ (MAX_M5_FILTERCFG_NAVGCELL_CLR(v) | MAX_M5_FILTERCFG_NAVGCELL)
+#define MAX_M5_FILTERCFG_NCURR_MASK 0xf
+#define MAX_M5_FILTERCFG_NCURR_SHIFT 1
+#define MAX_M5_FILTERCFG_NCURR_CLEAR (~(0xf << 1))
+#define MAX_M5_FILTERCFG_NCURR_CLR(v) \
+ ((v) & MAX_M5_FILTERCFG_NCURR_CLEAR)
+#define MAX_M5_FILTERCFG_NCURR_SET(v) \
+ (MAX_M5_FILTERCFG_NCURR_CLR(v) | MAX_M5_FILTERCFG_NCURR)
+
+/* RelaxCfg,0x2A,0b10000011001001,0x20c9
+ * LoadThr[6:0],,,,,,
+ */
+#define MAX_M5_RELAXCFG 0x2A
+#define MAX_M5_RELAXCFG_LOADTHR (0x7f << 9)
+#define MAX_M5_RELAXCFG_DVTHR (0xf << 4)
+#define MAX_M5_RELAXCFG_DTTHR (0xf << 0)
+
+#define MAX_M5_RELAXCFG_LOADTHR_MASK 0x7f
+#define MAX_M5_RELAXCFG_LOADTHR_SHIFT 9
+#define MAX_M5_RELAXCFG_LOADTHR_CLEAR (~(0x7f << 9))
+#define MAX_M5_RELAXCFG_LOADTHR_CLR(v) \
+ ((v) & MAX_M5_RELAXCFG_LOADTHR_CLEAR)
+#define MAX_M5_RELAXCFG_LOADTHR_SET(v) \
+ (MAX_M5_RELAXCFG_LOADTHR_CLR(v) | MAX_M5_RELAXCFG_LOADTHR)
+#define MAX_M5_RELAXCFG_DVTHR_MASK 0xf
+#define MAX_M5_RELAXCFG_DVTHR_SHIFT 4
+#define MAX_M5_RELAXCFG_DVTHR_CLEAR (~(0xf << 4))
+#define MAX_M5_RELAXCFG_DVTHR_CLR(v) \
+ ((v) & MAX_M5_RELAXCFG_DVTHR_CLEAR)
+#define MAX_M5_RELAXCFG_DVTHR_SET(v) \
+ (MAX_M5_RELAXCFG_DVTHR_CLR(v) | MAX_M5_RELAXCFG_DVTHR)
+#define MAX_M5_RELAXCFG_DTTHR_MASK 0xf
+#define MAX_M5_RELAXCFG_DTTHR_SHIFT 0
+#define MAX_M5_RELAXCFG_DTTHR_CLEAR (~(0xf << 0))
+#define MAX_M5_RELAXCFG_DTTHR_CLR(v) \
+ ((v) & MAX_M5_RELAXCFG_DTTHR_CLEAR)
+#define MAX_M5_RELAXCFG_DTTHR_SET(v) \
+ (MAX_M5_RELAXCFG_DTTHR_CLR(v) | MAX_M5_RELAXCFG_DTTHR)
+
+/* MiscCfg,0x2B,0b100111010000,0x9d0
+ * OopsFilter[3:0],,,,EnBi1,InitVFG,MixRate[4:3]
+ */
+#define MAX_M5_MISCCFG 0x2B
+#define MAX_M5_MISCCFG_OOPSFILTER (0xf << 12)
+#define MAX_M5_MISCCFG_ENBI1 (0x1 << 11)
+#define MAX_M5_MISCCFG_INITVFG (0x1 << 10)
+#define MAX_M5_MISCCFG_MIXRATE (0x1f << 5)
+#define MAX_M5_MISCCFG_RDFCLRN (0x1 << 4)
+#define MAX_M5_MISCCFG_VTTL (0x1 << 3)
+#define MAX_M5_MISCCFG_VEX (0x1 << 2)
+#define MAX_M5_MISCCFG_SACFG (0x3 << 0)
+
+#define MAX_M5_MISCCFG_OOPSFILTER_MASK 0xf
+#define MAX_M5_MISCCFG_OOPSFILTER_SHIFT 12
+#define MAX_M5_MISCCFG_OOPSFILTER_CLEAR (~(0xf << 12))
+#define MAX_M5_MISCCFG_OOPSFILTER_CLR(v) \
+ ((v) & MAX_M5_MISCCFG_OOPSFILTER_CLEAR)
+#define MAX_M5_MISCCFG_OOPSFILTER_SET(v) \
+ (MAX_M5_MISCCFG_OOPSFILTER_CLR(v) | MAX_M5_MISCCFG_OOPSFILTER)
+#define MAX_M5_MISCCFG_ENBI1_MASK 0x1
+#define MAX_M5_MISCCFG_ENBI1_SHIFT 11
+#define MAX_M5_MISCCFG_ENBI1_CLEAR (~(0x1 << 11))
+#define MAX_M5_MISCCFG_ENBI1_CLR(v) \
+ ((v) & MAX_M5_MISCCFG_ENBI1_CLEAR)
+#define MAX_M5_MISCCFG_ENBI1_SET(v) \
+ (MAX_M5_MISCCFG_ENBI1_CLR(v) | MAX_M5_MISCCFG_ENBI1)
+#define MAX_M5_MISCCFG_INITVFG_MASK 0x1
+#define MAX_M5_MISCCFG_INITVFG_SHIFT 10
+#define MAX_M5_MISCCFG_INITVFG_CLEAR (~(0x1 << 10))
+#define MAX_M5_MISCCFG_INITVFG_CLR(v) \
+ ((v) & MAX_M5_MISCCFG_INITVFG_CLEAR)
+#define MAX_M5_MISCCFG_INITVFG_SET(v) \
+ (MAX_M5_MISCCFG_INITVFG_CLR(v) | MAX_M5_MISCCFG_INITVFG)
+#define MAX_M5_MISCCFG_MIXRATE_MASK 0x1f
+#define MAX_M5_MISCCFG_MIXRATE_SHIFT 5
+#define MAX_M5_MISCCFG_MIXRATE_CLEAR (~(0x1f << 5))
+#define MAX_M5_MISCCFG_MIXRATE_CLR(v) \
+ ((v) & MAX_M5_MISCCFG_MIXRATE_CLEAR)
+#define MAX_M5_MISCCFG_MIXRATE_SET(v) \
+ (MAX_M5_MISCCFG_MIXRATE_CLR(v) | MAX_M5_MISCCFG_MIXRATE)
+#define MAX_M5_MISCCFG_RDFCLRN_MASK 0x1
+#define MAX_M5_MISCCFG_RDFCLRN_SHIFT 4
+#define MAX_M5_MISCCFG_RDFCLRN_CLEAR (~(0x1 << 4))
+#define MAX_M5_MISCCFG_RDFCLRN_CLR(v) \
+ ((v) & MAX_M5_MISCCFG_RDFCLRN_CLEAR)
+#define MAX_M5_MISCCFG_RDFCLRN_SET(v) \
+ (MAX_M5_MISCCFG_RDFCLRN_CLR(v) | MAX_M5_MISCCFG_RDFCLRN)
+#define MAX_M5_MISCCFG_VTTL_MASK 0x1
+#define MAX_M5_MISCCFG_VTTL_SHIFT 3
+#define MAX_M5_MISCCFG_VTTL_CLEAR (~(0x1 << 3))
+#define MAX_M5_MISCCFG_VTTL_CLR(v) ((v) & MAX_M5_MISCCFG_VTTL_CLEAR)
+#define MAX_M5_MISCCFG_VTTL_SET(v) \
+ (MAX_M5_MISCCFG_VTTL_CLR(v) | MAX_M5_MISCCFG_VTTL)
+#define MAX_M5_MISCCFG_VEX_MASK 0x1
+#define MAX_M5_MISCCFG_VEX_SHIFT 2
+#define MAX_M5_MISCCFG_VEX_CLEAR (~(0x1 << 2))
+#define MAX_M5_MISCCFG_VEX_CLR(v) ((v) & MAX_M5_MISCCFG_VEX_CLEAR)
+#define MAX_M5_MISCCFG_VEX_SET(v) \
+ (MAX_M5_MISCCFG_VEX_CLR(v) | MAX_M5_MISCCFG_VEX)
+#define MAX_M5_MISCCFG_SACFG_MASK 0x3
+#define MAX_M5_MISCCFG_SACFG_SHIFT 0
+#define MAX_M5_MISCCFG_SACFG_CLEAR (~(0x3 << 0))
+#define MAX_M5_MISCCFG_SACFG_CLR(v) \
+ ((v) & MAX_M5_MISCCFG_SACFG_CLEAR)
+#define MAX_M5_MISCCFG_SACFG_SET(v) \
+ (MAX_M5_MISCCFG_SACFG_CLR(v) | MAX_M5_MISCCFG_SACFG)
+
+/* TGain,0x2C,0b1110101110001101,0xeb8d
+ * TGAIN[15:8],,,,,,
+ */
+#define MAX_M5_TGAIN 0x2C
+
+/* TOff,0x2D,0b10000010101010,0x20aa
+ * TOFF[15:8],,,,,,
+ */
+#define MAX_M5_TOFF 0x2D
+
+/* CGain,0x2E,0b10000000000,0x400
+ * CGAIN[15:8],,,,,,
+ */
+#define MAX_M5_CGAIN 0x2E
+
+/* COff,0x2F,0b00000000,0x00
+ * COFF[15:8],,,,,,
+ */
+#define MAX_M5_COFF 0x2F
+
+/* dV_acc,0x30,0b10000000000,0x400
+ * dV_acc[15:8],,,,,,
+ */
+#define MAX_M5_DV_ACC 0x30
+
+/* dI_acc,0x31,0b11001000000,0x640
+ * dI_acc[15:8],,,,,,
+ */
+#define MAX_M5_DI_ACC 0x31
+
+/* QRTable20,0x32,0b101100000100,0xb04
+ * QRTable20[15:8],,,,,,
+ */
+#define MAX_M5_QRTABLE20 0x32
+
+/* AtTTF,0x33,0b1111111111111111,0xffff
+ * AtTTF[15:8],,,,,,
+ */
+#define MAX_M5_ATTTF 0x33
+
+/* TConvert,0x34,0b1011000000000,0x1600
+ * TConvert[15:8],,,,,,
+ */
+#define MAX_M5_TCONVERT 0x34
+
+/* FullCapRep,0x35,0b101110111000,0xbb8
+ * FullCapRep[15:8],,,,,,
+ */
+#define MAX_M5_FULLCAPREP 0x35
+
+/* IAvgEmpty,0x36,0b1111010001001000,0xf448
+ * Iavg_empty[15:8],,,,,,
+ */
+#define MAX_M5_IAVGEMPTY 0x36
+
+/* FCTC,0x37,0b10111100000,0x5e0
+ * FCTC[15:8],,,,,,
+ */
+#define MAX_M5_FCTC 0x37
+
+/* RComp0,0x38,0b01110000,0x70
+ * SPR_15_8[7:0],,,,,,
+ */
+#define MAX_M5_RCOMP0 0x38
+#define MAX_M5_RCOMP0_SPR_15_8 (0xff << 8)
+#define MAX_M5_RCOMP0_RCOMP0 (0xff << 0)
+
+#define MAX_M5_RCOMP0_SPR_15_8_MASK 0xff
+#define MAX_M5_RCOMP0_SPR_15_8_SHIFT 8
+#define MAX_M5_RCOMP0_SPR_15_8_CLEAR (~(0xff << 8))
+#define MAX_M5_RCOMP0_SPR_15_8_CLR(v) \
+ ((v) & MAX_M5_RCOMP0_SPR_15_8_CLEAR)
+#define MAX_M5_RCOMP0_SPR_15_8_SET(v) \
+ (MAX_M5_RCOMP0_SPR_15_8_CLR(v) | MAX_M5_RCOMP0_SPR_15_8)
+#define MAX_M5_RCOMP0_RCOMP0_MASK 0xff
+#define MAX_M5_RCOMP0_RCOMP0_SHIFT 0
+#define MAX_M5_RCOMP0_RCOMP0_CLEAR (~(0xff << 0))
+#define MAX_M5_RCOMP0_RCOMP0_CLR(v) \
+ ((v) & MAX_M5_RCOMP0_RCOMP0_CLEAR)
+#define MAX_M5_RCOMP0_RCOMP0_SET(v) \
+ (MAX_M5_RCOMP0_RCOMP0_CLR(v) | MAX_M5_RCOMP0_RCOMP0)
+
+/* TempCo,0x39,0b10011000111101,0x263d
+ * TempCoHot[7:0],,,,,,
+ */
+#define MAX_M5_TEMPCO 0x39
+#define MAX_M5_TEMPCO_TEMPCOHOT (0xff << 8)
+#define MAX_M5_TEMPCO_TEMPCOCOLD (0xff << 0)
+
+#define MAX_M5_TEMPCO_TEMPCOHOT_MASK 0xff
+#define MAX_M5_TEMPCO_TEMPCOHOT_SHIFT 8
+#define MAX_M5_TEMPCO_TEMPCOHOT_CLEAR (~(0xff << 8))
+#define MAX_M5_TEMPCO_TEMPCOHOT_CLR(v) \
+ ((v) & MAX_M5_TEMPCO_TEMPCOHOT_CLEAR)
+#define MAX_M5_TEMPCO_TEMPCOHOT_SET(v) \
+ (MAX_M5_TEMPCO_TEMPCOHOT_CLR(v) | MAX_M5_TEMPCO_TEMPCOHOT)
+#define MAX_M5_TEMPCO_TEMPCOCOLD_MASK 0xff
+#define MAX_M5_TEMPCO_TEMPCOCOLD_SHIFT 0
+#define MAX_M5_TEMPCO_TEMPCOCOLD_CLEAR (~(0xff << 0))
+#define MAX_M5_TEMPCO_TEMPCOCOLD_CLR(v) \
+ ((v) & MAX_M5_TEMPCO_TEMPCOCOLD_CLEAR)
+#define MAX_M5_TEMPCO_TEMPCOCOLD_SET(v) \
+ (MAX_M5_TEMPCO_TEMPCOCOLD_CLR(v) | MAX_M5_TEMPCO_TEMPCOCOLD)
+
+/* VEmpty,0x3A,0b1010010101100001,0xa561
+ * V_Empty[8:1],,,,,,
+ */
+#define MAX_M5_VEMPTY 0x3A
+#define MAX_M5_VEMPTY_V_EMPTY (0xff << 8)
+#define MAX_M5_VEMPTY_V_RECOVER (0x7f << 1)
+
+#define MAX_M5_VEMPTY_V_EMPTY_MASK 0xff
+#define MAX_M5_VEMPTY_V_EMPTY_SHIFT 8
+#define MAX_M5_VEMPTY_V_EMPTY_CLEAR (~(0xff << 8))
+#define MAX_M5_VEMPTY_V_EMPTY_CLR(v) \
+ ((v) & MAX_M5_VEMPTY_V_EMPTY_CLEAR)
+#define MAX_M5_VEMPTY_V_EMPTY_SET(v) \
+ (MAX_M5_VEMPTY_V_EMPTY_CLR(v) | MAX_M5_VEMPTY_V_EMPTY)
+#define MAX_M5_VEMPTY_V_RECOVER_MASK 0x7f
+#define MAX_M5_VEMPTY_V_RECOVER_SHIFT 1
+#define MAX_M5_VEMPTY_V_RECOVER_CLEAR (~(0x7f << 1))
+#define MAX_M5_VEMPTY_V_RECOVER_CLR(v) \
+ ((v) & MAX_M5_VEMPTY_V_RECOVER_CLEAR)
+#define MAX_M5_VEMPTY_V_RECOVER_SET(v) \
+ (MAX_M5_VEMPTY_V_RECOVER_CLR(v) | MAX_M5_VEMPTY_V_RECOVER)
+
+/* AvgCurrent0,0x3B,0b111111111111111,0x7fff
+ * AvgCurrent0[15:8],,,,,,
+ */
+#define MAX_M5_AVGCURRENT0 0x3B
+
+/* TaskPeriod,0x3C,0b1011010000000,0x1680
+ * TaskPeriod[15:8],,,,,,
+ */
+#define MAX_M5_TASKPERIOD 0x3C
+
+/* FStat,0x3D,0b00000001,0x01
+ * xBr,RDF,tmode,DeBn,xBi,Relck,RelDt
+ */
+#define MAX_M5_FSTAT 0x3D
+#define MAX_M5_FSTAT_XBR (0x1 << 15)
+#define MAX_M5_FSTAT_RDF (0x1 << 14)
+#define MAX_M5_FSTAT_TMODE (0x1 << 13)
+#define MAX_M5_FSTAT_DEBN (0x1 << 12)
+#define MAX_M5_FSTAT_XBI (0x1 << 11)
+#define MAX_M5_FSTAT_RELCK (0x1 << 10)
+#define MAX_M5_FSTAT_RELDT (0x1 << 9)
+#define MAX_M5_FSTAT_EDET (0x1 << 8)
+#define MAX_M5_FSTAT_FQ (0x1 << 7)
+#define MAX_M5_FSTAT_RELDT2 (0x1 << 6)
+#define MAX_M5_FSTAT_TIMER_START (0x1 << 5)
+#define MAX_M5_FSTAT_XBST (0x1 << 4)
+#define MAX_M5_FSTAT_ACCEN (0x1 << 3)
+#define MAX_M5_FSTAT_WK (0x1 << 2)
+#define MAX_M5_FSTAT_LDMDL (0x1 << 1)
+#define MAX_M5_FSTAT_DNR (0x1 << 0)
+
+#define MAX_M5_FSTAT_XBR_MASK 0x1
+#define MAX_M5_FSTAT_XBR_SHIFT 15
+#define MAX_M5_FSTAT_XBR_CLEAR (~(0x1 << 15))
+#define MAX_M5_FSTAT_XBR_CLR(v) ((v) & MAX_M5_FSTAT_XBR_CLEAR)
+#define MAX_M5_FSTAT_XBR_SET(v) \
+ (MAX_M5_FSTAT_XBR_CLR(v) | MAX_M5_FSTAT_XBR)
+#define MAX_M5_FSTAT_RDF_MASK 0x1
+#define MAX_M5_FSTAT_RDF_SHIFT 14
+#define MAX_M5_FSTAT_RDF_CLEAR (~(0x1 << 14))
+#define MAX_M5_FSTAT_RDF_CLR(v) ((v) & MAX_M5_FSTAT_RDF_CLEAR)
+#define MAX_M5_FSTAT_RDF_SET(v) \
+ (MAX_M5_FSTAT_RDF_CLR(v) | MAX_M5_FSTAT_RDF)
+#define MAX_M5_FSTAT_TMODE_MASK 0x1
+#define MAX_M5_FSTAT_TMODE_SHIFT 13
+#define MAX_M5_FSTAT_TMODE_CLEAR (~(0x1 << 13))
+#define MAX_M5_FSTAT_TMODE_CLR(v) ((v) & MAX_M5_FSTAT_TMODE_CLEAR)
+#define MAX_M5_FSTAT_TMODE_SET(v) \
+ (MAX_M5_FSTAT_TMODE_CLR(v) | MAX_M5_FSTAT_TMODE)
+#define MAX_M5_FSTAT_DEBN_MASK 0x1
+#define MAX_M5_FSTAT_DEBN_SHIFT 12
+#define MAX_M5_FSTAT_DEBN_CLEAR (~(0x1 << 12))
+#define MAX_M5_FSTAT_DEBN_CLR(v) ((v) & MAX_M5_FSTAT_DEBN_CLEAR)
+#define MAX_M5_FSTAT_DEBN_SET(v) \
+ (MAX_M5_FSTAT_DEBN_CLR(v) | MAX_M5_FSTAT_DEBN)
+#define MAX_M5_FSTAT_XBI_MASK 0x1
+#define MAX_M5_FSTAT_XBI_SHIFT 11
+#define MAX_M5_FSTAT_XBI_CLEAR (~(0x1 << 11))
+#define MAX_M5_FSTAT_XBI_CLR(v) ((v) & MAX_M5_FSTAT_XBI_CLEAR)
+#define MAX_M5_FSTAT_XBI_SET(v) \
+ (MAX_M5_FSTAT_XBI_CLR(v) | MAX_M5_FSTAT_XBI)
+#define MAX_M5_FSTAT_RELCK_MASK 0x1
+#define MAX_M5_FSTAT_RELCK_SHIFT 10
+#define MAX_M5_FSTAT_RELCK_CLEAR (~(0x1 << 10))
+#define MAX_M5_FSTAT_RELCK_CLR(v) ((v) & MAX_M5_FSTAT_RELCK_CLEAR)
+#define MAX_M5_FSTAT_RELCK_SET(v) \
+ (MAX_M5_FSTAT_RELCK_CLR(v) | MAX_M5_FSTAT_RELCK)
+#define MAX_M5_FSTAT_RELDT_MASK 0x1
+#define MAX_M5_FSTAT_RELDT_SHIFT 9
+#define MAX_M5_FSTAT_RELDT_CLEAR (~(0x1 << 9))
+#define MAX_M5_FSTAT_RELDT_CLR(v) ((v) & MAX_M5_FSTAT_RELDT_CLEAR)
+#define MAX_M5_FSTAT_RELDT_SET(v) \
+ (MAX_M5_FSTAT_RELDT_CLR(v) | MAX_M5_FSTAT_RELDT)
+#define MAX_M5_FSTAT_EDET_MASK 0x1
+#define MAX_M5_FSTAT_EDET_SHIFT 8
+#define MAX_M5_FSTAT_EDET_CLEAR (~(0x1 << 8))
+#define MAX_M5_FSTAT_EDET_CLR(v) ((v) & MAX_M5_FSTAT_EDET_CLEAR)
+#define MAX_M5_FSTAT_EDET_SET(v) \
+ (MAX_M5_FSTAT_EDET_CLR(v) | MAX_M5_FSTAT_EDET)
+#define MAX_M5_FSTAT_FQ_MASK 0x1
+#define MAX_M5_FSTAT_FQ_SHIFT 7
+#define MAX_M5_FSTAT_FQ_CLEAR (~(0x1 << 7))
+#define MAX_M5_FSTAT_FQ_CLR(v) ((v) & MAX_M5_FSTAT_FQ_CLEAR)
+#define MAX_M5_FSTAT_FQ_SET(v) \
+ (MAX_M5_FSTAT_FQ_CLR(v) | MAX_M5_FSTAT_FQ)
+#define MAX_M5_FSTAT_RELDT2_MASK 0x1
+#define MAX_M5_FSTAT_RELDT2_SHIFT 6
+#define MAX_M5_FSTAT_RELDT2_CLEAR (~(0x1 << 6))
+#define MAX_M5_FSTAT_RELDT2_CLR(v) ((v) & MAX_M5_FSTAT_RELDT2_CLEAR)
+#define MAX_M5_FSTAT_RELDT2_SET(v) \
+ (MAX_M5_FSTAT_RELDT2_CLR(v) | MAX_M5_FSTAT_RELDT2)
+#define MAX_M5_FSTAT_TIMER_START_MASK 0x1
+#define MAX_M5_FSTAT_TIMER_START_SHIFT 5
+#define MAX_M5_FSTAT_TIMER_START_CLEAR (~(0x1 << 5))
+#define MAX_M5_FSTAT_TIMER_START_CLR(v) \
+ ((v) & MAX_M5_FSTAT_TIMER_START_CLEAR)
+#define MAX_M5_FSTAT_TIMER_START_SET(v) \
+ (MAX_M5_FSTAT_TIMER_START_CLR(v) | MAX_M5_FSTAT_TIMER_START)
+#define MAX_M5_FSTAT_XBST_MASK 0x1
+#define MAX_M5_FSTAT_XBST_SHIFT 4
+#define MAX_M5_FSTAT_XBST_CLEAR (~(0x1 << 4))
+#define MAX_M5_FSTAT_XBST_CLR(v) ((v) & MAX_M5_FSTAT_XBST_CLEAR)
+#define MAX_M5_FSTAT_XBST_SET(v) \
+ (MAX_M5_FSTAT_XBST_CLR(v) | MAX_M5_FSTAT_XBST)
+#define MAX_M5_FSTAT_ACCEN_MASK 0x1
+#define MAX_M5_FSTAT_ACCEN_SHIFT 3
+#define MAX_M5_FSTAT_ACCEN_CLEAR (~(0x1 << 3))
+#define MAX_M5_FSTAT_ACCEN_CLR(v) ((v) & MAX_M5_FSTAT_ACCEN_CLEAR)
+#define MAX_M5_FSTAT_ACCEN_SET(v) \
+ (MAX_M5_FSTAT_ACCEN_CLR(v) | MAX_M5_FSTAT_ACCEN)
+#define MAX_M5_FSTAT_WK_MASK 0x1
+#define MAX_M5_FSTAT_WK_SHIFT 2
+#define MAX_M5_FSTAT_WK_CLEAR (~(0x1 << 2))
+#define MAX_M5_FSTAT_WK_CLR(v) ((v) & MAX_M5_FSTAT_WK_CLEAR)
+#define MAX_M5_FSTAT_WK_SET(v) \
+ (MAX_M5_FSTAT_WK_CLR(v) | MAX_M5_FSTAT_WK)
+#define MAX_M5_FSTAT_LDMDL_MASK 0x1
+#define MAX_M5_FSTAT_LDMDL_SHIFT 1
+#define MAX_M5_FSTAT_LDMDL_CLEAR (~(0x1 << 1))
+#define MAX_M5_FSTAT_LDMDL_CLR(v) ((v) & MAX_M5_FSTAT_LDMDL_CLEAR)
+#define MAX_M5_FSTAT_LDMDL_SET(v) \
+ (MAX_M5_FSTAT_LDMDL_CLR(v) | MAX_M5_FSTAT_LDMDL)
+#define MAX_M5_FSTAT_DNR_MASK 0x1
+#define MAX_M5_FSTAT_DNR_SHIFT 0
+#define MAX_M5_FSTAT_DNR_CLEAR (~(0x1 << 0))
+#define MAX_M5_FSTAT_DNR_CLR(v) ((v) & MAX_M5_FSTAT_DNR_CLEAR)
+#define MAX_M5_FSTAT_DNR_SET(v) \
+ (MAX_M5_FSTAT_DNR_CLR(v) | MAX_M5_FSTAT_DNR)
+
+/* Timer,0x3E,0b00000000,0x00
+ * TIMER[15:8],,,,,,
+ */
+#define MAX_M5_TIMER 0x3E
+
+/* ShdnTimer,0x3F,0b1110000000000000,0xe000
+ * SHDN_THR[2:0],,,SHDNCTR[12:8],,,
+ */
+#define MAX_M5_SHDNTIMER 0x3F
+#define MAX_M5_SHDNTIMER_SHDN_THR (0x7 << 13)
+#define MAX_M5_SHDNTIMER_SHDNCTR (0x1fff << 0)
+
+#define MAX_M5_SHDNTIMER_SHDN_THR_MASK 0x7
+#define MAX_M5_SHDNTIMER_SHDN_THR_SHIFT 13
+#define MAX_M5_SHDNTIMER_SHDN_THR_CLEAR (~(0x7 << 13))
+#define MAX_M5_SHDNTIMER_SHDN_THR_CLR(v) \
+ ((v) & MAX_M5_SHDNTIMER_SHDN_THR_CLEAR)
+#define MAX_M5_SHDNTIMER_SHDN_THR_SET(v) \
+ (MAX_M5_SHDNTIMER_SHDN_THR_CLR(v) | MAX_M5_SHDNTIMER_SHDN_THR)
+#define MAX_M5_SHDNTIMER_SHDNCTR_MASK 0x1fff
+#define MAX_M5_SHDNTIMER_SHDNCTR_SHIFT 0
+#define MAX_M5_SHDNTIMER_SHDNCTR_CLEAR (~(0x1fff << 0))
+#define MAX_M5_SHDNTIMER_SHDNCTR_CLR(v) \
+ ((v) & MAX_M5_SHDNTIMER_SHDNCTR_CLEAR)
+#define MAX_M5_SHDNTIMER_SHDNCTR_SET(v) \
+ (MAX_M5_SHDNTIMER_SHDNCTR_CLR(v) | MAX_M5_SHDNTIMER_SHDNCTR)
+
+/* THMHOT,0x40,0b11111111100,0x7fc
+ * VR[4:0],,,,,Vhys[2:0],
+ */
+#define MAX_M5_THMHOT 0x40
+#define MAX_M5_THMHOT_VR (0x1f << 11)
+#define MAX_M5_THMHOT_VHYS (0x7 << 8)
+#define MAX_M5_THMHOT_TR (0x1f << 3)
+#define MAX_M5_THMHOT_THYS (0x7 << 0)
+
+#define MAX_M5_THMHOT_VR_MASK 0x1f
+#define MAX_M5_THMHOT_VR_SHIFT 11
+#define MAX_M5_THMHOT_VR_CLEAR (~(0x1f << 11))
+#define MAX_M5_THMHOT_VR_CLR(v) ((v) & MAX_M5_THMHOT_VR_CLEAR)
+#define MAX_M5_THMHOT_VR_SET(v) \
+ (MAX_M5_THMHOT_VR_CLR(v) | MAX_M5_THMHOT_VR)
+#define MAX_M5_THMHOT_VHYS_MASK 0x7
+#define MAX_M5_THMHOT_VHYS_SHIFT 8
+#define MAX_M5_THMHOT_VHYS_CLEAR (~(0x7 << 8))
+#define MAX_M5_THMHOT_VHYS_CLR(v) ((v) & MAX_M5_THMHOT_VHYS_CLEAR)
+#define MAX_M5_THMHOT_VHYS_SET(v) \
+ (MAX_M5_THMHOT_VHYS_CLR(v) | MAX_M5_THMHOT_VHYS)
+#define MAX_M5_THMHOT_TR_MASK 0x1f
+#define MAX_M5_THMHOT_TR_SHIFT 3
+#define MAX_M5_THMHOT_TR_CLEAR (~(0x1f << 3))
+#define MAX_M5_THMHOT_TR_CLR(v) ((v) & MAX_M5_THMHOT_TR_CLEAR)
+#define MAX_M5_THMHOT_TR_SET(v) \
+ (MAX_M5_THMHOT_TR_CLR(v) | MAX_M5_THMHOT_TR)
+#define MAX_M5_THMHOT_THYS_MASK 0x7
+#define MAX_M5_THMHOT_THYS_SHIFT 0
+#define MAX_M5_THMHOT_THYS_CLEAR (~(0x7 << 0))
+#define MAX_M5_THMHOT_THYS_CLR(v) ((v) & MAX_M5_THMHOT_THYS_CLEAR)
+#define MAX_M5_THMHOT_THYS_SET(v) \
+ (MAX_M5_THMHOT_THYS_CLR(v) | MAX_M5_THMHOT_THYS)
+
+/* CTESample,0x41,0b00000000,0x00
+ * CTESample[15:8],,,,,,
+ */
+#define MAX_M5_CTESAMPLE 0x41
+
+/* QRTable30,0x42,0b100010000101,0x885
+ * QRTable30[15:8],,,,,,
+ */
+#define MAX_M5_QRTABLE30 0x42
+
+/* ISys,0x43,0b00000000,0x00
+ * ISYS[15:8],,,,,,
+ */
+#define MAX_M5_ISYS 0x43
+
+/* AvgVCell0,0x44,0b1000000000000000,0x8000
+ * AvgVCELL0[15:8],,,,,,
+ */
+#define MAX_M5_AVGVCELL0 0x44
+
+/* dQAcc,0x45,0b00010111,0x17
+ * dQacc[15:8],,,,,,
+ */
+#define MAX_M5_DQACC 0x45
+
+/* dPAcc,0x46,0b110010000,0x190
+ * dPacc[15:8],,,,,,
+ */
+#define MAX_M5_DPACC 0x46
+
+/* RlxSOC,0x47,0b00000000,0x00
+ * RlxSOC[15:8],,,,,,
+ */
+#define MAX_M5_RLXSOC 0x47
+
+/* VFSOC0,0x48,0b11001000000000,0x3200
+ * VFSOC0[15:8],,,,,,
+ */
+#define MAX_M5_VFSOC0 0x48
+
+/* ConvgCfg,0x49,0b10001001000001,0x2241
+ * RepLow[3:0],,,,VoltLowOff[4:1],,
+ */
+#define MAX_M5_CONVGCFG 0x49
+#define MAX_M5_CONVGCFG_REPLOW (0xf << 12)
+#define MAX_M5_CONVGCFG_VOLTLOWOFF (0xf << 8)
+#define MAX_M5_CONVGCFG_MINSLOPEX (0xf << 4)
+#define MAX_M5_CONVGCFG_REPL_PER_STAGE (0x7 << 1)
+
+#define MAX_M5_CONVGCFG_REPLOW_MASK 0xf
+#define MAX_M5_CONVGCFG_REPLOW_SHIFT 12
+#define MAX_M5_CONVGCFG_REPLOW_CLEAR (~(0xf << 12))
+#define MAX_M5_CONVGCFG_REPLOW_CLR(v) \
+ ((v) & MAX_M5_CONVGCFG_REPLOW_CLEAR)
+#define MAX_M5_CONVGCFG_REPLOW_SET(v) \
+ (MAX_M5_CONVGCFG_REPLOW_CLR(v) | MAX_M5_CONVGCFG_REPLOW)
+#define MAX_M5_CONVGCFG_VOLTLOWOFF_MASK 0xf
+#define MAX_M5_CONVGCFG_VOLTLOWOFF_SHIFT 8
+#define MAX_M5_CONVGCFG_VOLTLOWOFF_CLEAR (~(0xf << 8))
+#define MAX_M5_CONVGCFG_VOLTLOWOFF_CLR(v) \
+ ((v) & MAX_M5_CONVGCFG_VOLTLOWOFF_CLEAR)
+#define MAX_M5_CONVGCFG_VOLTLOWOFF_SET(v) \
+ (MAX_M5_CONVGCFG_VOLTLOWOFF_CLR(v) | MAX_M5_CONVGCFG_VOLTLOWOFF)
+#define MAX_M5_CONVGCFG_MINSLOPEX_MASK 0xf
+#define MAX_M5_CONVGCFG_MINSLOPEX_SHIFT 4
+#define MAX_M5_CONVGCFG_MINSLOPEX_CLEAR (~(0xf << 4))
+#define MAX_M5_CONVGCFG_MINSLOPEX_CLR(v) \
+ ((v) & MAX_M5_CONVGCFG_MINSLOPEX_CLEAR)
+#define MAX_M5_CONVGCFG_MINSLOPEX_SET(v) \
+ (MAX_M5_CONVGCFG_MINSLOPEX_CLR(v) | MAX_M5_CONVGCFG_MINSLOPEX)
+#define MAX_M5_CONVGCFG_REPL_PER_STAGE_MASK 0x7
+#define MAX_M5_CONVGCFG_REPL_PER_STAGE_SHIFT 1
+#define MAX_M5_CONVGCFG_REPL_PER_STAGE_CLEAR (~(0x7 << 1))
+#define MAX_M5_CONVGCFG_REPL_PER_STAGE_CLR(v) \
+ ((v) & MAX_M5_CONVGCFG_REPL_PER_STAGE_CLEAR)
+#define MAX_M5_CONVGCFG_REPL_PER_STAGE_SET(v) \
+ (MAX_M5_CONVGCFG_REPL_PER_STAGE_CLR(v) | MAX_M5_CONVGCFG_REPL_PER_STAGE)
+
+/* VFRemCap,0x4A,0b10111011100,0x5dc
+ * VFRemCap[15:8],,,,,,
+ */
+#define MAX_M5_VFREMCAP 0x4A
+
+/* AvgISys,0x4B,0b00000000,0x00
+ * AVGISYS[15:8],,,,,,
+ */
+#define MAX_M5_AVGISYS 0x4B
+
+/* QH0,0x4C,0b00000000,0x00
+ * QH0[15:8],,,,,,
+ */
+#define MAX_M5_QH0 0x4C
+
+/* QH,0x4D,0b00000000,0x00
+ * QH[15:8],,,,,,
+ */
+#define MAX_M5_QH 0x4D
+
+/* QL,0x4E,0b00000000,0x00
+ * QL[15:8],,,,,,
+ */
+#define MAX_M5_QL 0x4E
+
+/* MixAtFull,0x4F,0b101110111000,0xbb8
+ * MixAtFull[15:8],,,,,,
+ */
+#define MAX_M5_MIXATFULL 0x4F
+
+/* Status2,0xB0,0b00000000,0x00
+ * SPR_15_6[9:2],,,,,,
+ */
+#define MAX_M5_STATUS2 0xB0
+#define MAX_M5_STATUS2_SPR_15_6 (0x3ff << 6)
+#define MAX_M5_STATUS2_FULLDET (0x1 << 5)
+#define MAX_M5_STATUS2_SPR_4_2 (0x7 << 2)
+#define MAX_M5_STATUS2_HIB (0x1 << 1)
+#define MAX_M5_STATUS2_SPR_0 (0x1 << 0)
+
+#define MAX_M5_STATUS2_SPR_15_6_MASK 0x3ff
+#define MAX_M5_STATUS2_SPR_15_6_SHIFT 6
+#define MAX_M5_STATUS2_SPR_15_6_CLEAR (~(0x3ff << 6))
+#define MAX_M5_STATUS2_SPR_15_6_CLR(v) \
+ ((v) & MAX_M5_STATUS2_SPR_15_6_CLEAR)
+#define MAX_M5_STATUS2_SPR_15_6_SET(v) \
+ (MAX_M5_STATUS2_SPR_15_6_CLR(v) | MAX_M5_STATUS2_SPR_15_6)
+#define MAX_M5_STATUS2_FULLDET_MASK 0x1
+#define MAX_M5_STATUS2_FULLDET_SHIFT 5
+#define MAX_M5_STATUS2_FULLDET_CLEAR (~(0x1 << 5))
+#define MAX_M5_STATUS2_FULLDET_CLR(v) \
+ ((v) & MAX_M5_STATUS2_FULLDET_CLEAR)
+#define MAX_M5_STATUS2_FULLDET_SET(v) \
+ (MAX_M5_STATUS2_FULLDET_CLR(v) | MAX_M5_STATUS2_FULLDET)
+#define MAX_M5_STATUS2_SPR_4_2_MASK 0x7
+#define MAX_M5_STATUS2_SPR_4_2_SHIFT 2
+#define MAX_M5_STATUS2_SPR_4_2_CLEAR (~(0x7 << 2))
+#define MAX_M5_STATUS2_SPR_4_2_CLR(v) \
+ ((v) & MAX_M5_STATUS2_SPR_4_2_CLEAR)
+#define MAX_M5_STATUS2_SPR_4_2_SET(v) \
+ (MAX_M5_STATUS2_SPR_4_2_CLR(v) | MAX_M5_STATUS2_SPR_4_2)
+#define MAX_M5_STATUS2_HIB_MASK 0x1
+#define MAX_M5_STATUS2_HIB_SHIFT 1
+#define MAX_M5_STATUS2_HIB_CLEAR (~(0x1 << 1))
+#define MAX_M5_STATUS2_HIB_CLR(v) ((v) & MAX_M5_STATUS2_HIB_CLEAR)
+#define MAX_M5_STATUS2_HIB_SET(v) \
+ (MAX_M5_STATUS2_HIB_CLR(v) | MAX_M5_STATUS2_HIB)
+#define MAX_M5_STATUS2_SPR_0_MASK 0x1
+#define MAX_M5_STATUS2_SPR_0_SHIFT 0
+#define MAX_M5_STATUS2_SPR_0_CLEAR (~(0x1 << 0))
+#define MAX_M5_STATUS2_SPR_0_CLR(v) \
+ ((v) & MAX_M5_STATUS2_SPR_0_CLEAR)
+#define MAX_M5_STATUS2_SPR_0_SET(v) \
+ (MAX_M5_STATUS2_SPR_0_CLR(v) | MAX_M5_STATUS2_SPR_0)
+
+/* VSys,0xB1,0b00000000,0x00
+ * VSys[15:8],,,,,,
+ */
+#define MAX_M5_VSYS 0xB1
+
+/* TAlrtTh2,0xB2,0b111111110000000,0x7f80
+ * TempWarm[7:0],,,,,,
+ */
+#define MAX_M5_TALRTTH2 0xB2
+#define MAX_M5_TALRTTH2_TEMPWARM (0xff << 8)
+#define MAX_M5_TALRTTH2_TEMPCOOL (0xff << 0)
+
+#define MAX_M5_TALRTTH2_TEMPWARM_MASK 0xff
+#define MAX_M5_TALRTTH2_TEMPWARM_SHIFT 8
+#define MAX_M5_TALRTTH2_TEMPWARM_CLEAR (~(0xff << 8))
+#define MAX_M5_TALRTTH2_TEMPWARM_CLR(v) \
+ ((v) & MAX_M5_TALRTTH2_TEMPWARM_CLEAR)
+#define MAX_M5_TALRTTH2_TEMPWARM_SET(v) \
+ (MAX_M5_TALRTTH2_TEMPWARM_CLR(v) | MAX_M5_TALRTTH2_TEMPWARM)
+#define MAX_M5_TALRTTH2_TEMPCOOL_MASK 0xff
+#define MAX_M5_TALRTTH2_TEMPCOOL_SHIFT 0
+#define MAX_M5_TALRTTH2_TEMPCOOL_CLEAR (~(0xff << 0))
+#define MAX_M5_TALRTTH2_TEMPCOOL_CLR(v) \
+ ((v) & MAX_M5_TALRTTH2_TEMPCOOL_CLEAR)
+#define MAX_M5_TALRTTH2_TEMPCOOL_SET(v) \
+ (MAX_M5_TALRTTH2_TEMPCOOL_CLR(v) | MAX_M5_TALRTTH2_TEMPCOOL)
+
+/* VByp,0xB3,0b00000000,0x00
+ * VByp[15:8],,,,,,
+ */
+#define MAX_M5_VBYP 0xB3
+
+/* IAlrtTh,0xB4,0b111111110000000,0x7f80
+ * ISYSOCP_TH[7:0],,,,,,
+ */
+#define MAX_M5_IALRTTH 0xB4
+#define MAX_M5_IALRTTH_ISYSOCP_TH (0xff << 8)
+#define MAX_M5_IALRTTH_IBATTMIN_TH (0xff << 0)
+
+#define MAX_M5_IALRTTH_ISYSOCP_TH_MASK 0xff
+#define MAX_M5_IALRTTH_ISYSOCP_TH_SHIFT 8
+#define MAX_M5_IALRTTH_ISYSOCP_TH_CLEAR (~(0xff << 8))
+#define MAX_M5_IALRTTH_ISYSOCP_TH_CLR(v) \
+ ((v) & MAX_M5_IALRTTH_ISYSOCP_TH_CLEAR)
+#define MAX_M5_IALRTTH_ISYSOCP_TH_SET(v) \
+ (MAX_M5_IALRTTH_ISYSOCP_TH_CLR(v) | MAX_M5_IALRTTH_ISYSOCP_TH)
+#define MAX_M5_IALRTTH_IBATTMIN_TH_MASK 0xff
+#define MAX_M5_IALRTTH_IBATTMIN_TH_SHIFT 0
+#define MAX_M5_IALRTTH_IBATTMIN_TH_CLEAR (~(0xff << 0))
+#define MAX_M5_IALRTTH_IBATTMIN_TH_CLR(v) \
+ ((v) & MAX_M5_IALRTTH_IBATTMIN_TH_CLEAR)
+#define MAX_M5_IALRTTH_IBATTMIN_TH_SET(v) \
+ (MAX_M5_IALRTTH_IBATTMIN_TH_CLR(v) | MAX_M5_IALRTTH_IBATTMIN_TH)
+
+/* TTF_CFG,0xB5,0b00000101,0x05
+ * SPR_15_3[12:5],,,,,,
+ */
+#define MAX_M5_TTF_CFG 0xB5
+#define MAX_M5_TTF_CFG_SPR_15_3 (0x1fff << 3)
+#define MAX_M5_TTF_CFG_TTF_CFG (0x7 << 0)
+
+#define MAX_M5_TTF_CFG_SPR_15_3_MASK 0x1fff
+#define MAX_M5_TTF_CFG_SPR_15_3_SHIFT 3
+#define MAX_M5_TTF_CFG_SPR_15_3_CLEAR (~(0x1fff << 3))
+#define MAX_M5_TTF_CFG_SPR_15_3_CLR(v) \
+ ((v) & MAX_M5_TTF_CFG_SPR_15_3_CLEAR)
+#define MAX_M5_TTF_CFG_SPR_15_3_SET(v) \
+ (MAX_M5_TTF_CFG_SPR_15_3_CLR(v) | MAX_M5_TTF_CFG_SPR_15_3)
+#define MAX_M5_TTF_CFG_TTF_CFG_MASK 0x7
+#define MAX_M5_TTF_CFG_TTF_CFG_SHIFT 0
+#define MAX_M5_TTF_CFG_TTF_CFG_CLEAR (~(0x7 << 0))
+#define MAX_M5_TTF_CFG_TTF_CFG_CLR(v) \
+ ((v) & MAX_M5_TTF_CFG_TTF_CFG_CLEAR)
+#define MAX_M5_TTF_CFG_TTF_CFG_SET(v) \
+ (MAX_M5_TTF_CFG_TTF_CFG_CLR(v) | MAX_M5_TTF_CFG_TTF_CFG)
+
+/* CV_MixCap,0xB6,0b100011001010,0x8ca
+ * CV_MixCap[15:8],,,,,,
+ */
+#define MAX_M5_CV_MIXCAP 0xB6
+
+/* CV_HalfTime,0xB7,0b101000000000,0xa00
+ * CV_Halftime[15:8],,,,,,
+ */
+#define MAX_M5_CV_HALFTIME 0xB7
+
+/* CGTempCo,0xB8,0b00000000,0x00
+ * CGTempCo[15:8],,,,,,
+ */
+#define MAX_M5_CGTEMPCO 0xB8
+
+/* Curve,0xB9,0b01101011,0x6b
+ * ECURVE[7:0],,,,,,
+ */
+#define MAX_M5_CURVE 0xB9
+#define MAX_M5_CURVE_ECURVE (0xff << 8)
+#define MAX_M5_CURVE_TCURVE (0xff << 0)
+
+#define MAX_M5_CURVE_ECURVE_MASK 0xff
+#define MAX_M5_CURVE_ECURVE_SHIFT 8
+#define MAX_M5_CURVE_ECURVE_CLEAR (~(0xff << 8))
+#define MAX_M5_CURVE_ECURVE_CLR(v) ((v) & MAX_M5_CURVE_ECURVE_CLEAR)
+#define MAX_M5_CURVE_ECURVE_SET(v) \
+ (MAX_M5_CURVE_ECURVE_CLR(v) | MAX_M5_CURVE_ECURVE)
+#define MAX_M5_CURVE_TCURVE_MASK 0xff
+#define MAX_M5_CURVE_TCURVE_SHIFT 0
+#define MAX_M5_CURVE_TCURVE_CLEAR (~(0xff << 0))
+#define MAX_M5_CURVE_TCURVE_CLR(v) ((v) & MAX_M5_CURVE_TCURVE_CLEAR)
+#define MAX_M5_CURVE_TCURVE_SET(v) \
+ (MAX_M5_CURVE_TCURVE_CLR(v) | MAX_M5_CURVE_TCURVE)
+
+/* HibCfg,0xBA,0b100100001100,0x90c
+ * EnHib,HibEnterTime[2:0],,,HibThreshold[3:0],,
+ */
+#define MAX_M5_HIBCFG 0xBA
+#define MAX_M5_HIBCFG_ENHIB (0x1 << 15)
+#define MAX_M5_HIBCFG_HIBENTERTIME (0x7 << 12)
+#define MAX_M5_HIBCFG_HIBTHRESHOLD (0xf << 8)
+#define MAX_M5_HIBCFG_SPR_7_5 (0x7 << 5)
+#define MAX_M5_HIBCFG_HIBEXITTIME (0x3 << 3)
+#define MAX_M5_HIBCFG_HIBSCALAR (0x7 << 0)
+
+#define MAX_M5_HIBCFG_ENHIB_MASK 0x1
+#define MAX_M5_HIBCFG_ENHIB_SHIFT 15
+#define MAX_M5_HIBCFG_ENHIB_CLEAR (~(0x1 << 15))
+#define MAX_M5_HIBCFG_ENHIB_CLR(v) ((v) & MAX_M5_HIBCFG_ENHIB_CLEAR)
+#define MAX_M5_HIBCFG_ENHIB_SET(v) \
+ (MAX_M5_HIBCFG_ENHIB_CLR(v) | MAX_M5_HIBCFG_ENHIB)
+#define MAX_M5_HIBCFG_HIBENTERTIME_MASK 0x7
+#define MAX_M5_HIBCFG_HIBENTERTIME_SHIFT 12
+#define MAX_M5_HIBCFG_HIBENTERTIME_CLEAR (~(0x7 << 12))
+#define MAX_M5_HIBCFG_HIBENTERTIME_CLR(v) \
+ ((v) & MAX_M5_HIBCFG_HIBENTERTIME_CLEAR)
+#define MAX_M5_HIBCFG_HIBENTERTIME_SET(v) \
+ (MAX_M5_HIBCFG_HIBENTERTIME_CLR(v) | MAX_M5_HIBCFG_HIBENTERTIME)
+#define MAX_M5_HIBCFG_HIBTHRESHOLD_MASK 0xf
+#define MAX_M5_HIBCFG_HIBTHRESHOLD_SHIFT 8
+#define MAX_M5_HIBCFG_HIBTHRESHOLD_CLEAR (~(0xf << 8))
+#define MAX_M5_HIBCFG_HIBTHRESHOLD_CLR(v) \
+ ((v) & MAX_M5_HIBCFG_HIBTHRESHOLD_CLEAR)
+#define MAX_M5_HIBCFG_HIBTHRESHOLD_SET(v) \
+ (MAX_M5_HIBCFG_HIBTHRESHOLD_CLR(v) | MAX_M5_HIBCFG_HIBTHRESHOLD)
+#define MAX_M5_HIBCFG_SPR_7_5_MASK 0x7
+#define MAX_M5_HIBCFG_SPR_7_5_SHIFT 5
+#define MAX_M5_HIBCFG_SPR_7_5_CLEAR (~(0x7 << 5))
+#define MAX_M5_HIBCFG_SPR_7_5_CLR(v) \
+ ((v) & MAX_M5_HIBCFG_SPR_7_5_CLEAR)
+#define MAX_M5_HIBCFG_SPR_7_5_SET(v) \
+ (MAX_M5_HIBCFG_SPR_7_5_CLR(v) | MAX_M5_HIBCFG_SPR_7_5)
+#define MAX_M5_HIBCFG_HIBEXITTIME_MASK 0x3
+#define MAX_M5_HIBCFG_HIBEXITTIME_SHIFT 3
+#define MAX_M5_HIBCFG_HIBEXITTIME_CLEAR (~(0x3 << 3))
+#define MAX_M5_HIBCFG_HIBEXITTIME_CLR(v) \
+ ((v) & MAX_M5_HIBCFG_HIBEXITTIME_CLEAR)
+#define MAX_M5_HIBCFG_HIBEXITTIME_SET(v) \
+ (MAX_M5_HIBCFG_HIBEXITTIME_CLR(v) | MAX_M5_HIBCFG_HIBEXITTIME)
+#define MAX_M5_HIBCFG_HIBSCALAR_MASK 0x7
+#define MAX_M5_HIBCFG_HIBSCALAR_SHIFT 0
+#define MAX_M5_HIBCFG_HIBSCALAR_CLEAR (~(0x7 << 0))
+#define MAX_M5_HIBCFG_HIBSCALAR_CLR(v) \
+ ((v) & MAX_M5_HIBCFG_HIBSCALAR_CLEAR)
+#define MAX_M5_HIBCFG_HIBSCALAR_SET(v) \
+ (MAX_M5_HIBCFG_HIBSCALAR_CLR(v) | MAX_M5_HIBCFG_HIBSCALAR)
+
+/* Config2,0xBB,0b01010000,0x50
+ * SPR_15_11[4:0],,,,,FCThmHot,ThmHotEn
+ */
+#define MAX_M5_CONFIG2 0xBB
+#define MAX_M5_CONFIG2_SPR_15_11 (0x1f << 11)
+#define MAX_M5_CONFIG2_FCTHMHOT (0x1 << 10)
+#define MAX_M5_CONFIG2_THMHOTEN (0x1 << 9)
+#define MAX_M5_CONFIG2_THMHOTALRTEN (0x1 << 8)
+#define MAX_M5_CONFIG2_DSOCEN (0x1 << 7)
+#define MAX_M5_CONFIG2_TALRTEN (0x1 << 6)
+#define MAX_M5_CONFIG2_LDMDL (0x1 << 5)
+#define MAX_M5_CONFIG2_OCVQEN (0x1 << 4)
+#define MAX_M5_CONFIG2_ISYSNCURR (0xf << 0)
+
+#define MAX_M5_CONFIG2_SPR_15_11_MASK 0x1f
+#define MAX_M5_CONFIG2_SPR_15_11_SHIFT 11
+#define MAX_M5_CONFIG2_SPR_15_11_CLEAR (~(0x1f << 11))
+#define MAX_M5_CONFIG2_SPR_15_11_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_SPR_15_11_CLEAR)
+#define MAX_M5_CONFIG2_SPR_15_11_SET(v) \
+ (MAX_M5_CONFIG2_SPR_15_11_CLR(v) | MAX_M5_CONFIG2_SPR_15_11)
+#define MAX_M5_CONFIG2_FCTHMHOT_MASK 0x1
+#define MAX_M5_CONFIG2_FCTHMHOT_SHIFT 10
+#define MAX_M5_CONFIG2_FCTHMHOT_CLEAR (~(0x1 << 10))
+#define MAX_M5_CONFIG2_FCTHMHOT_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_FCTHMHOT_CLEAR)
+#define MAX_M5_CONFIG2_FCTHMHOT_SET(v) \
+ (MAX_M5_CONFIG2_FCTHMHOT_CLR(v) | MAX_M5_CONFIG2_FCTHMHOT)
+#define MAX_M5_CONFIG2_THMHOTEN_MASK 0x1
+#define MAX_M5_CONFIG2_THMHOTEN_SHIFT 9
+#define MAX_M5_CONFIG2_THMHOTEN_CLEAR (~(0x1 << 9))
+#define MAX_M5_CONFIG2_THMHOTEN_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_THMHOTEN_CLEAR)
+#define MAX_M5_CONFIG2_THMHOTEN_SET(v) \
+ (MAX_M5_CONFIG2_THMHOTEN_CLR(v) | MAX_M5_CONFIG2_THMHOTEN)
+#define MAX_M5_CONFIG2_THMHOTALRTEN_MASK 0x1
+#define MAX_M5_CONFIG2_THMHOTALRTEN_SHIFT 8
+#define MAX_M5_CONFIG2_THMHOTALRTEN_CLEAR (~(0x1 << 8))
+#define MAX_M5_CONFIG2_THMHOTALRTEN_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_THMHOTALRTEN_CLEAR)
+#define MAX_M5_CONFIG2_THMHOTALRTEN_SET(v) \
+ (MAX_M5_CONFIG2_THMHOTALRTEN_CLR(v) | MAX_M5_CONFIG2_THMHOTALRTEN)
+#define MAX_M5_CONFIG2_DSOCEN_MASK 0x1
+#define MAX_M5_CONFIG2_DSOCEN_SHIFT 7
+#define MAX_M5_CONFIG2_DSOCEN_CLEAR (~(0x1 << 7))
+#define MAX_M5_CONFIG2_DSOCEN_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_DSOCEN_CLEAR)
+#define MAX_M5_CONFIG2_DSOCEN_SET(v) \
+ (MAX_M5_CONFIG2_DSOCEN_CLR(v) | MAX_M5_CONFIG2_DSOCEN)
+#define MAX_M5_CONFIG2_TALRTEN_MASK 0x1
+#define MAX_M5_CONFIG2_TALRTEN_SHIFT 6
+#define MAX_M5_CONFIG2_TALRTEN_CLEAR (~(0x1 << 6))
+#define MAX_M5_CONFIG2_TALRTEN_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_TALRTEN_CLEAR)
+#define MAX_M5_CONFIG2_TALRTEN_SET(v) \
+ (MAX_M5_CONFIG2_TALRTEN_CLR(v) | MAX_M5_CONFIG2_TALRTEN)
+#define MAX_M5_CONFIG2_LDMDL_MASK 0x1
+#define MAX_M5_CONFIG2_LDMDL_SHIFT 5
+#define MAX_M5_CONFIG2_LDMDL_CLEAR (~(0x1 << 5))
+#define MAX_M5_CONFIG2_LDMDL_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_LDMDL_CLEAR)
+#define MAX_M5_CONFIG2_LDMDL_SET(v) \
+ (MAX_M5_CONFIG2_LDMDL_CLR(v) | MAX_M5_CONFIG2_LDMDL)
+#define MAX_M5_CONFIG2_OCVQEN_MASK 0x1
+#define MAX_M5_CONFIG2_OCVQEN_SHIFT 4
+#define MAX_M5_CONFIG2_OCVQEN_CLEAR (~(0x1 << 4))
+#define MAX_M5_CONFIG2_OCVQEN_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_OCVQEN_CLEAR)
+#define MAX_M5_CONFIG2_OCVQEN_SET(v) \
+ (MAX_M5_CONFIG2_OCVQEN_CLR(v) | MAX_M5_CONFIG2_OCVQEN)
+#define MAX_M5_CONFIG2_ISYSNCURR_MASK 0xf
+#define MAX_M5_CONFIG2_ISYSNCURR_SHIFT 0
+#define MAX_M5_CONFIG2_ISYSNCURR_CLEAR (~(0xf << 0))
+#define MAX_M5_CONFIG2_ISYSNCURR_CLR(v) \
+ ((v) & MAX_M5_CONFIG2_ISYSNCURR_CLEAR)
+#define MAX_M5_CONFIG2_ISYSNCURR_SET(v) \
+ (MAX_M5_CONFIG2_ISYSNCURR_CLR(v) | MAX_M5_CONFIG2_ISYSNCURR)
+
+/* VRipple,0xBC,0b00000000,0x00
+ * Vripple[15:8],,,,,,
+ */
+#define MAX_M5_VRIPPLE 0xBC
+
+/* RippleCfg,0xBD,0b1000000100,0x204
+ * kDV[12:5],,,,,,
+ */
+#define MAX_M5_RIPPLECFG 0xBD
+#define MAX_M5_RIPPLECFG_KDV (0x1fff << 3)
+#define MAX_M5_RIPPLECFG_NR (0x7 << 0)
+
+#define MAX_M5_RIPPLECFG_KDV_MASK 0x1fff
+#define MAX_M5_RIPPLECFG_KDV_SHIFT 3
+#define MAX_M5_RIPPLECFG_KDV_CLEAR (~(0x1fff << 3))
+#define MAX_M5_RIPPLECFG_KDV_CLR(v) \
+ ((v) & MAX_M5_RIPPLECFG_KDV_CLEAR)
+#define MAX_M5_RIPPLECFG_KDV_SET(v) \
+ (MAX_M5_RIPPLECFG_KDV_CLR(v) | MAX_M5_RIPPLECFG_KDV)
+#define MAX_M5_RIPPLECFG_NR_MASK 0x7
+#define MAX_M5_RIPPLECFG_NR_SHIFT 0
+#define MAX_M5_RIPPLECFG_NR_CLEAR (~(0x7 << 0))
+#define MAX_M5_RIPPLECFG_NR_CLR(v) ((v) & MAX_M5_RIPPLECFG_NR_CLEAR)
+#define MAX_M5_RIPPLECFG_NR_SET(v) \
+ (MAX_M5_RIPPLECFG_NR_CLR(v) | MAX_M5_RIPPLECFG_NR)
+
+/* TimerH,0xBE,0b00000000,0x00
+ * TIMERH[15:8],,,,,,
+ */
+#define MAX_M5_TIMERH 0xBE
+
+/* MaxError,0xBF,0b00000000,0x00
+ * MaxError[15:8],,,,,,
+ */
+#define MAX_M5_MAXERROR 0xBF
+
+/* IIn,0xD0,0b00000000,0x00
+ * IIn[15:8],,,,,,
+ */
+#define MAX_M5_IIN 0xD0
+
+/* AtQresidual,0xDC,0b00000000,0x00
+ * AtQresidual[15:8],,,,,,
+ */
+#define MAX_M5_ATQRESIDUAL 0xDC
+
+/* AtTTE,0xDD,0b00000000,0x00
+ * AtTTE[15:8],,,,,,
+ */
+#define MAX_M5_ATTTE 0xDD
+
+/* AtAvSOC,0xDE,0b00000000,0x00
+ * AtAvSOC[15:8],,,,,,
+ */
+#define MAX_M5_ATAVSOC 0xDE
+
+/* AtAvCap,0xDF,0b00000000,0x00
+ * AtAvCap[15:8],,,,,,
+ */
+#define MAX_M5_ATAVCAP 0xDF
+
+#endif /* MAX_M5_REG_H_ */
diff --git a/overheat_mitigation.c b/overheat_mitigation.c
new file mode 100644
index 0000000..43bcb7f
--- /dev/null
+++ b/overheat_mitigation.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright 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.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/math64.h>
+#include <linux/platform_device.h>
+#include <linux/pmic-voter.h>
+#include <linux/power_supply.h>
+#include <linux/printk.h>
+#include <linux/thermal.h>
+#include <linux/pm_wakeup.h>
+#include "google_psy.h"
+
+#define USB_OVERHEAT_MITIGATION_VOTER "USB_OVERHEAT_MITIGATION_VOTER"
+
+enum {
+ USB_NO_LIMIT = 0,
+ USB_OVERHEAT_THROTTLE = 1,
+ USB_MAX_THROTTLE_STATE = USB_OVERHEAT_THROTTLE,
+};
+
+static bool mitigation_enabled = true;
+module_param_named(
+ enable, mitigation_enabled, bool, 0600
+);
+
+struct overheat_event_stats {
+ int plug_temp;
+ int max_temp;
+ int trip_time;
+ int hysteresis_time;
+ int cleared_time;
+};
+
+struct overheat_info {
+ struct device *dev;
+ struct power_supply *usb_psy;
+ struct votable *usb_icl_votable;
+ struct votable *disable_power_role_switch;
+ struct notifier_block psy_nb;
+ struct delayed_work port_overheat_work;
+ struct wakeup_source overheat_ws;
+ struct overheat_event_stats stats;
+ struct thermal_cooling_device *cooling_dev;
+
+ bool usb_connected;
+ bool accessory_connected;
+ bool usb_replug;
+ bool overheat_mitigation;
+ bool overheat_work_running;
+
+ int temp;
+ int plug_temp;
+ int max_temp;
+ time_t plug_time;
+ time_t trip_time;
+ time_t hysteresis_time;
+
+ int begin_temp;
+ int clear_temp;
+ int overheat_work_delay_ms;
+ int polling_freq;
+ int check_status;
+ unsigned long throttle_state;
+};
+
+#define OVH_ATTR(_name) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct overheat_info *ovh_info = dev_get_drvdata(dev); \
+ \
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ovh_info->stats._name);\
+} \
+static DEVICE_ATTR_RO(_name);
+
+OVH_ATTR(max_temp);
+OVH_ATTR(plug_temp);
+OVH_ATTR(trip_time);
+OVH_ATTR(hysteresis_time);
+OVH_ATTR(cleared_time);
+
+static struct attribute *ovh_attr[] = {
+ &dev_attr_max_temp.attr,
+ &dev_attr_plug_temp.attr,
+ &dev_attr_hysteresis_time.attr,
+ &dev_attr_trip_time.attr,
+ &dev_attr_cleared_time.attr,
+ NULL,
+};
+
+static const struct attribute_group ovh_attr_group = {
+ .attrs = ovh_attr,
+};
+
+static inline time_t get_seconds_since_boot(void)
+{
+ return div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC);
+}
+
+static inline int get_dts_vars(struct overheat_info *ovh_info)
+{
+ struct device *dev = ovh_info->dev;
+ struct device_node *node = dev->of_node;
+ int ret;
+
+ ret = of_property_read_u32(node, "google,begin-mitigation-temp",
+ &ovh_info->begin_temp);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "cannot read begin-mitigation-temp, ret=%d\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "google,end-mitigation-temp",
+ &ovh_info->clear_temp);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "cannot read end-mitigation-temp, ret=%d\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "google,port-overheat-work-interval",
+ &ovh_info->overheat_work_delay_ms);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "cannot read port-overheat-work-interval, ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "google,polling-freq",
+ &ovh_info->polling_freq);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "cannot read polling-freq, ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int suspend_usb(struct overheat_info *ovh_info)
+{
+ int ret;
+
+ ovh_info->usb_replug = false;
+
+ /* disable USB */
+ ret = vote(ovh_info->disable_power_role_switch,
+ USB_OVERHEAT_MITIGATION_VOTER, true, 0);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Couldn't vote for disable_power_role_switch ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ /* suspend charging */
+ ret = vote(ovh_info->usb_icl_votable,
+ USB_OVERHEAT_MITIGATION_VOTER, true, 0);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Couldn't vote for USB ICL ret=%d\n", ret);
+ return ret;
+ }
+
+ ovh_info->trip_time = get_seconds_since_boot();
+ ovh_info->overheat_mitigation = true;
+ return ret;
+}
+
+static int resume_usb(struct overheat_info *ovh_info)
+{
+ int ret;
+
+ /* Fill out stats so userspace can read them. */
+ ovh_info->stats.max_temp = ovh_info->max_temp;
+ ovh_info->stats.plug_temp = ovh_info->plug_temp;
+ ovh_info->stats.trip_time =
+ (int) (ovh_info->trip_time - ovh_info->plug_time);
+ ovh_info->stats.hysteresis_time =
+ (int) (ovh_info->hysteresis_time - ovh_info->trip_time);
+ ovh_info->stats.cleared_time =
+ (int) (get_seconds_since_boot() - ovh_info->hysteresis_time);
+
+ /* enable charging */
+ ret = vote(ovh_info->usb_icl_votable,
+ USB_OVERHEAT_MITIGATION_VOTER, false, 0);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Couldn't un-vote for USB ICL ret=%d\n", ret);
+ return ret;
+ }
+
+ /* enable USB */
+ ret = vote(ovh_info->disable_power_role_switch,
+ USB_OVERHEAT_MITIGATION_VOTER, false, 0);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Couldn't un-vote for disable_power_role_switch ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ /* Notify userspace to read the stats. */
+ kobject_uevent(&ovh_info->dev->kobj, KOBJ_CHANGE);
+
+ ovh_info->max_temp = INT_MIN;
+ ovh_info->plug_temp = INT_MIN;
+ ovh_info->plug_time = 0;
+ ovh_info->trip_time = 0;
+ ovh_info->hysteresis_time = 0;
+ ovh_info->overheat_mitigation = false;
+ ovh_info->usb_replug = false;
+ return ret;
+}
+
+/*
+ * Update usb_connected, accessory_connected, usb_replug, and plug_temp
+ * status in overheat_info struct.
+ */
+static int update_usb_status(struct overheat_info *ovh_info)
+{
+ int ret;
+ bool prev_state = ovh_info->usb_connected ||
+ ovh_info->accessory_connected;
+ bool curr_state;
+ int *check_status = &ovh_info->check_status;
+
+ /* Port is too hot to safely check the connected status. */
+ if (ovh_info->overheat_mitigation &&
+ ovh_info->temp > ovh_info->clear_temp)
+ return -EBUSY;
+
+ if (ovh_info->overheat_mitigation) {
+ if (!ovh_info->hysteresis_time)
+ ovh_info->hysteresis_time = get_seconds_since_boot();
+ // Only check USB status every polling_freq instances
+ *check_status = (*check_status + 1) % ovh_info->polling_freq;
+ if (*check_status > 0)
+ return 0;
+ ret = vote(ovh_info->disable_power_role_switch,
+ USB_OVERHEAT_MITIGATION_VOTER, false, 0);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Couldn't un-vote for disable_power_role_switch ret=%d\n",
+ ret);
+ return ret;
+ }
+ msleep(200);
+ }
+
+ dev_dbg(ovh_info->dev, "Updating USB connected status\n");
+
+ /*
+ * Update USB present status to determine if USB has been disconnected.
+ * If we use USB online status to determine replug, we will need to
+ * extend the delay between re-enabling CC detection and checking the
+ * USB online status.
+ */
+ ret = GPSY_GET_PROP(ovh_info->usb_psy, POWER_SUPPLY_PROP_PRESENT);
+ if (ret < 0)
+ return ret;
+ ovh_info->usb_connected = ret;
+
+ ret = GPSY_GET_PROP(ovh_info->usb_psy, POWER_SUPPLY_PROP_TYPEC_MODE);
+ if (ret < 0)
+ return ret;
+ ovh_info->accessory_connected = (ret == POWER_SUPPLY_TYPEC_SINK) ||
+ (ret == POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE);
+
+ curr_state = ovh_info->usb_connected || ovh_info->accessory_connected;
+ if (curr_state && !prev_state) {
+ ovh_info->plug_time = get_seconds_since_boot();
+ ovh_info->plug_temp = ovh_info->temp;
+ }
+
+ if (ovh_info->overheat_mitigation) {
+ ret = vote(ovh_info->disable_power_role_switch,
+ USB_OVERHEAT_MITIGATION_VOTER, true, 0);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Couldn't vote for disable_power_role_switch ret=%d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (curr_state != prev_state)
+ dev_info(ovh_info->dev,
+ "USB is %sconnected",
+ curr_state ? "" : "dis");
+
+ // USB should be disconnected for two cycles before replug is acked
+ if (ovh_info->overheat_mitigation && !curr_state && !prev_state)
+ ovh_info->usb_replug = true;
+
+ return 0;
+}
+
+static inline int get_usb_port_temp(struct overheat_info *ovh_info)
+{
+ int temp;
+
+ temp = GPSY_GET_PROP(ovh_info->usb_psy, POWER_SUPPLY_PROP_TEMP);
+
+ if (temp == -EINVAL || temp == -ENODATA)
+ return temp;
+
+ dev_info(ovh_info->dev, "Update USB port temp:%d\n", temp);
+ if (temp > ovh_info->max_temp)
+ ovh_info->max_temp = temp;
+
+ ovh_info->temp = temp;
+ return 0;
+}
+
+static int psy_changed(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct power_supply *psy = data;
+ struct overheat_info *ovh_info =
+ container_of(nb, struct overheat_info, psy_nb);
+
+ if ((action != PSY_EVENT_PROP_CHANGED) || (psy == NULL) ||
+ (psy->desc == NULL) || (psy->desc->name == NULL))
+ return NOTIFY_OK;
+
+ if (action == PSY_EVENT_PROP_CHANGED &&
+ !strcmp(psy->desc->name, "usb")) {
+ dev_dbg(ovh_info->dev, "name=usb evt=%lu\n", action);
+ if (!ovh_info->overheat_work_running)
+ schedule_delayed_work(&ovh_info->port_overheat_work, 0);
+ }
+ return NOTIFY_OK;
+}
+
+static void port_overheat_work(struct work_struct *work)
+{
+ struct overheat_info *ovh_info =
+ container_of(work, struct overheat_info,
+ port_overheat_work.work);
+ int ret = 0;
+
+ // Take a wake lock to ensure we poll the temp regularly
+ if (!ovh_info->overheat_work_running)
+ __pm_stay_awake(&ovh_info->overheat_ws);
+ ovh_info->overheat_work_running = true;
+
+ if (get_usb_port_temp(ovh_info) < 0)
+ goto rerun;
+
+ ret = update_usb_status(ovh_info);
+ if (ret < 0)
+ goto rerun;
+
+ if (ovh_info->overheat_mitigation && (!mitigation_enabled ||
+ (ovh_info->temp < ovh_info->clear_temp && ovh_info->usb_replug))) {
+ dev_err(ovh_info->dev, "Port overheat mitigated\n");
+ resume_usb(ovh_info);
+ } else if (!ovh_info->overheat_mitigation &&
+ mitigation_enabled && ovh_info->temp > ovh_info->begin_temp) {
+ dev_err(ovh_info->dev, "Port overheat triggered\n");
+ suspend_usb(ovh_info);
+ goto rerun;
+ }
+
+ if (ovh_info->overheat_mitigation || ovh_info->throttle_state)
+ goto rerun;
+ // Do not run again, USB port isn't overheated
+ ovh_info->overheat_work_running = false;
+ __pm_relax(&ovh_info->overheat_ws);
+ return;
+
+rerun:
+ schedule_delayed_work(
+ &ovh_info->port_overheat_work,
+ msecs_to_jiffies(ovh_info->overheat_work_delay_ms));
+}
+
+static int usb_get_cur_state(struct thermal_cooling_device *cooling_dev,
+ unsigned long *state)
+{
+ struct overheat_info *ovh_info = cooling_dev->devdata;
+
+ if (!ovh_info)
+ return -EINVAL;
+
+ *state = ovh_info->throttle_state;
+
+ return 0;
+}
+
+static int usb_get_max_state(struct thermal_cooling_device *cooling_dev,
+ unsigned long *state)
+{
+ *state = USB_MAX_THROTTLE_STATE;
+
+ return 0;
+}
+
+static int usb_set_cur_state(struct thermal_cooling_device *cooling_dev,
+ unsigned long state)
+{
+ struct overheat_info *ovh_info = cooling_dev->devdata;
+ unsigned long current_state;
+
+ if (!ovh_info)
+ return -EINVAL;
+
+ if (state > USB_MAX_THROTTLE_STATE)
+ return -EINVAL;
+
+ current_state = ovh_info->throttle_state;
+ ovh_info->throttle_state = state;
+
+ if (current_state != state) {
+ dev_info(ovh_info->dev, "usb overheat throttle state=%lu\n",
+ state);
+ mod_delayed_work(system_wq, &ovh_info->port_overheat_work, 0);
+ }
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops usb_cooling_ops = {
+ .get_max_state = usb_get_max_state,
+ .get_cur_state = usb_get_cur_state,
+ .set_cur_state = usb_set_cur_state,
+};
+
+static int ovh_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct overheat_info *ovh_info;
+ struct power_supply *usb_psy;
+ struct votable *usb_icl_votable;
+ struct votable *disable_power_role_switch;
+
+ usb_psy = power_supply_get_by_name("usb");
+ if (!usb_psy)
+ return -EPROBE_DEFER;
+
+ usb_icl_votable = find_votable("USB_ICL");
+ if (usb_icl_votable == NULL) {
+ pr_err("Couldn't find USB_ICL votable\n");
+ return -EPROBE_DEFER;
+ }
+
+ disable_power_role_switch = find_votable("DISABLE_POWER_ROLE_SWITCH");
+ if (disable_power_role_switch == NULL) {
+ pr_err("Couldn't find DISABLE_POWER_ROLE_SWITCH votable\n");
+ return -EPROBE_DEFER;
+ }
+
+ ovh_info = devm_kzalloc(&pdev->dev, sizeof(*ovh_info), GFP_KERNEL);
+ if (!ovh_info)
+ return -ENOMEM;
+
+ ovh_info->dev = &pdev->dev;
+ ovh_info->usb_icl_votable = usb_icl_votable;
+ ovh_info->disable_power_role_switch = disable_power_role_switch;
+ ovh_info->usb_psy = usb_psy;
+ ovh_info->max_temp = INT_MIN;
+ ovh_info->plug_temp = INT_MIN;
+
+ ret = get_dts_vars(ovh_info);
+ if (ret < 0)
+ return -ENODEV;
+
+ // initialize votables
+ vote(ovh_info->usb_icl_votable,
+ USB_OVERHEAT_MITIGATION_VOTER, false, 0);
+ vote(ovh_info->disable_power_role_switch,
+ USB_OVERHEAT_MITIGATION_VOTER, false, 0);
+
+ wakeup_source_init(&ovh_info->overheat_ws, "overheat_mitigation");
+ INIT_DELAYED_WORK(&ovh_info->port_overheat_work, port_overheat_work);
+
+ // register power supply change notifier to update usb metric data
+ ovh_info->psy_nb.notifier_call = psy_changed;
+ ret = power_supply_reg_notifier(&ovh_info->psy_nb);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Cannot register power supply notifer, ret=%d\n", ret);
+ return ret;
+ }
+
+ /* Register cooling device */
+ ovh_info->cooling_dev = thermal_of_cooling_device_register(
+ dev_of_node(ovh_info->dev), "usb-port",
+ ovh_info, &usb_cooling_ops);
+
+ if (IS_ERR(ovh_info->cooling_dev)) {
+ ret = PTR_ERR(ovh_info->cooling_dev);
+ dev_err(ovh_info->dev, "%s: failed to register cooling device: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, ovh_info);
+ ret = sysfs_create_group(&ovh_info->dev->kobj, &ovh_attr_group);
+ if (ret < 0) {
+ dev_err(ovh_info->dev,
+ "Cannot create sysfs group, ret=%d\n", ret);
+ }
+
+ return 0;
+}
+
+static int ovh_remove(struct platform_device *pdev)
+{
+ struct overheat_info *ovh_info = platform_get_drvdata(pdev);
+ if (ovh_info) {
+ power_supply_unreg_notifier(&ovh_info->psy_nb);
+ sysfs_remove_group(&ovh_info->dev->kobj, &ovh_attr_group);
+ wakeup_source_trash(&ovh_info->overheat_ws);
+ }
+ return 0;
+}
+
+static const struct of_device_id match_table[] = {
+ {
+ .compatible = "google,overheat_mitigation",
+ },
+ {},
+};
+
+static struct platform_driver ovh_driver = {
+ .driver = {
+ .name = "google,overheat_mitigation",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ },
+ .probe = ovh_probe,
+ .remove = ovh_remove,
+};
+
+module_platform_driver(ovh_driver);
+MODULE_DESCRIPTION("USB port overheat mitigation driver");
+MODULE_AUTHOR("Maggie White <[email protected]>");
+MODULE_LICENSE("GPL v2");
diff --git a/p9221_charger.c b/p9221_charger.c
new file mode 100644
index 0000000..0581f11
--- /dev/null
+++ b/p9221_charger.c
@@ -0,0 +1,4357 @@
+/*
+ * P9221 Wireless Charger Driver
+ *
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <dt-bindings/wc/p9221-wc.h>
+#include <linux/device.h>
+#include <linux/pm.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/pmic-voter.h>
+#include <linux/alarmtimer.h>
+#include "p9221_charger.h"
+#include "../google/logbuffer.h"
+
+#define P9221_TX_TIMEOUT_MS (20 * 1000)
+#define P9221_DCIN_TIMEOUT_MS (1 * 1000)
+#define P9221_VRECT_TIMEOUT_MS (2 * 1000)
+#define P9221_ALIGN_TIMEOUT_MS (2 * 1000)
+#define P9221_ALIGN_DELAY_MS 100
+#define P9221_NOTIFIER_DELAY_MS 100
+#define P9221_DCIN_PON_DELAY_MS 250
+#define P9221R5_ILIM_MAX_UA (1600 * 1000)
+#define P9221R5_OVER_CHECK_NUM 3
+
+#define OVC_LIMIT 1
+#define OVC_THRESHOLD 1400000
+#define OVC_BACKOFF_LIMIT 900000
+#define OVC_BACKOFF_AMOUNT 100000
+
+#define P9221_CHECK_NP_DELAY_MS 50
+#define P9221_NEG_POWER_5W (5 / 0.5)
+#define P9221_NEG_POWER_10W (10 / 0.5)
+#define P9221_PTMC_EPP_TX_1912 0x32
+
+#define P9382A_DC_ICL_EPP_1200 1200000
+#define P9382A_DC_ICL_EPP_1000 1000000
+#define P9382A_NEG_POWER_10W (10 / 0.5)
+#define P9382A_NEG_POWER_11W (11 / 0.5)
+
+#define WLC_ALIGNMENT_MAX 100
+#define WLC_MFG_GOOGLE 0x72
+#define WLC_CURRENT_FILTER_LENGTH 10
+#define WLC_ALIGN_DEFAULT_SCALAR 4
+#define WLC_ALIGN_IRQ_THRESHOLD 10
+#define WLC_ALIGN_DEFAULT_HYSTERESIS 5000
+
+#define RTX_BEN_DISABLED 0
+#define RTX_BEN_ON 1
+#define RTX_BEN_ENABLED 2
+
+static void p9221_icl_ramp_reset(struct p9221_charger_data *charger);
+static void p9221_icl_ramp_start(struct p9221_charger_data *charger);
+
+static const u32 p9221_ov_set_lut[] = {
+ 17000000, 20000000, 15000000, 13000000,
+ 11000000, 11000000, 11000000, 11000000};
+
+static char *align_status_str[] = {
+ "...", "M2C", "OK", "-1"
+};
+
+static size_t p9221_hex_str(u8 *data, size_t len, char *buf, size_t max_buf,
+ bool msbfirst)
+{
+ int i;
+ int blen = 0;
+ u8 val;
+
+ for (i = 0; i < len; i++) {
+ if (msbfirst)
+ val = data[len - 1 - i];
+ else
+ val = data[i];
+ blen += scnprintf(buf + (i * 3), max_buf - (i * 3),
+ "%02x ", val);
+ }
+ return blen;
+}
+
+static int p9221_reg_read_n(struct p9221_charger_data *charger, u16 reg,
+ void *buf, size_t n)
+{
+ int ret;
+ struct i2c_msg msg[2];
+ u8 wbuf[2];
+
+ msg[0].addr = charger->client->addr;
+ msg[0].flags = charger->client->flags & I2C_M_TEN;
+ msg[0].len = 2;
+ msg[0].buf = wbuf;
+
+ wbuf[0] = (reg & 0xFF00) >> 8;
+ wbuf[1] = (reg & 0xFF);
+
+ msg[1].addr = charger->client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = n;
+ msg[1].buf = buf;
+
+ mutex_lock(&charger->io_lock);
+ ret = i2c_transfer(charger->client->adapter, msg, 2);
+ mutex_unlock(&charger->io_lock);
+
+ if (ret < 0) {
+ /*
+ * Treat -ENOTCONN as -ENODEV to suppress the get/set
+ * prop warnings.
+ */
+ int nret = (ret == -ENOTCONN) ? -ENODEV : ret;
+
+ dev_err(&charger->client->dev,
+ "i2c read error, reg:%x, ret:%d (%d)\n",
+ reg, ret, nret);
+ return nret;
+ }
+
+ return (ret == 2) ? 0 : -EIO;
+}
+
+static int p9221_reg_read_16(struct p9221_charger_data *charger, u16 reg,
+ u16 *val)
+{
+ u8 buf[2];
+ int ret;
+
+ ret = p9221_reg_read_n(charger, reg, buf, 2);
+ if (ret == 0)
+ *val = (buf[1] << 8) | buf[0];
+ return ret;
+}
+
+static int p9221_reg_read_8(struct p9221_charger_data *charger,
+ u16 reg, u8 *val)
+{
+ return p9221_reg_read_n(charger, reg, val, 1);
+}
+
+static bool p9221_reg_is_8_bit(struct p9221_charger_data *charger, u16 reg)
+{
+ switch (reg) {
+ case P9221_CHIP_REVISION_REG:
+ case P9221_CUSTOMER_ID_REG:
+ case P9221_COM_REG:
+ case P9221R5_VOUT_SET_REG:
+ case P9221R5_ILIM_SET_REG:
+ case P9221R5_CHARGE_STAT_REG:
+ case P9221R5_EPT_REG:
+ case P9221R5_SYSTEM_MODE_REG:
+ case P9221R5_COM_CHAN_RESET_REG:
+ case P9221R5_COM_CHAN_SEND_SIZE_REG:
+ case P9221R5_COM_CHAN_SEND_IDX_REG:
+ case P9221R5_COM_CHAN_RECV_SIZE_REG:
+ case P9221R5_COM_CHAN_RECV_IDX_REG:
+ case P9221R5_DEBUG_REG:
+ case P9221R5_EPP_Q_FACTOR_REG:
+ case P9221R5_EPP_TX_GUARANTEED_POWER_REG:
+ case P9221R5_EPP_TX_POTENTIAL_POWER_REG:
+ case P9221R5_EPP_TX_CAPABILITY_FLAGS_REG:
+ case P9221R5_EPP_RENEGOTIATION_REG:
+ case P9221R5_EPP_CUR_RPP_HEADER_REG:
+ case P9221R5_EPP_CUR_NEGOTIATED_POWER_REG:
+ case P9221R5_EPP_CUR_MAXIMUM_POWER_REG:
+ case P9221R5_EPP_CUR_FSK_MODULATION_REG:
+ case P9221R5_EPP_REQ_RPP_HEADER_REG:
+ case P9221R5_EPP_REQ_NEGOTIATED_POWER_REG:
+ case P9221R5_EPP_REQ_MAXIMUM_POWER_REG:
+ case P9221R5_EPP_REQ_FSK_MODULATION_REG:
+ case P9221R5_VRECT_TARGET_REG:
+ case P9221R5_VRECT_KNEE_REG:
+ case P9221R5_FOD_SECTION_REG:
+ case P9221R5_VRECT_ADJ_REG:
+ case P9221R5_ALIGN_X_ADC_REG:
+ case P9221R5_ALIGN_Y_ADC_REG:
+ case P9221R5_ASK_MODULATION_DEPTH_REG:
+ case P9221R5_OVSET_REG:
+ case P9221R5_EPP_TX_SPEC_REV_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * Cook the value according to the register (R5+)
+ */
+static int p9221_cook_reg(struct p9221_charger_data *charger, u16 reg,
+ u16 raw_data, u32 *val)
+{
+ /* Do the appropriate conversion */
+ switch (reg) {
+ /* The following raw values */
+ case P9221R5_ALIGN_X_ADC_REG:
+ case P9221R5_ALIGN_Y_ADC_REG:
+ *val = raw_data;
+ break;
+
+ /* The following are 12-bit ADC raw values */
+ case P9221R5_VOUT_ADC_REG:
+ case P9221R5_IOUT_ADC_REG:
+ case P9221R5_EXT_TEMP_REG:
+ *val = raw_data & 0xFFF;
+ break;
+
+ /* the formula to translate it into milli-degreeC */
+ case P9221R5_DIE_TEMP_ADC_REG:
+ *val = (raw_data * 10 / 107 - 247) * 10;
+ break;
+
+ /* The following are in 0.1 mill- and need to go to micro- */
+ case P9221R5_VOUT_SET_REG: /* 100mV -> uV */
+ raw_data *= 100;
+ /* Fall through */
+
+ /* The following are in milli- and need to go to micro- */
+ case P9221R5_IOUT_REG: /* mA -> uA */
+ case P9221R5_VRECT_REG: /* mV -> uV */
+ case P9221R5_VOUT_REG: /* mV -> uV */
+ case P9382A_ILIM_SET_REG: /* mA -> uA */
+ /* Fall through */
+
+ /* The following are in kilo- and need to go to their base */
+ case P9221R5_OP_FREQ_REG: /* kHz -> Hz */
+ case P9221R5_TX_PINGFREQ_REG: /* kHz -> Hz */
+ *val = raw_data * 1000;
+ break;
+
+ case P9221R5_ILIM_SET_REG:
+ /* 100mA -> uA, 200mA offset */
+ *val = ((raw_data * 100) + 200) * 1000;
+ break;
+
+ case P9221R5_OVSET_REG:
+ /* uV */
+ raw_data &= P9221R5_OVSET_MASK;
+ *val = p9221_ov_set_lut[raw_data];
+ break;
+
+ default:
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+/*
+ * Read the reg and return the cooked value.
+ */
+static int p9221_reg_read_cooked(struct p9221_charger_data *charger,
+ u16 reg, u32 *val)
+{
+ int ret = 0;
+ u16 data = 0;
+
+ if (p9221_reg_is_8_bit(charger, reg)) {
+ u8 data8 = 0;
+
+ ret = p9221_reg_read_8(charger, reg, &data8);
+ data = data8;
+ } else {
+ ret = p9221_reg_read_16(charger, reg, &data);
+ }
+ if (ret)
+ return ret;
+
+ return p9221_cook_reg(charger, reg, data, val);
+}
+
+static int p9221_reg_write_n(struct p9221_charger_data *charger, u16 reg,
+ void *buf, size_t n)
+{
+ int ret;
+ u8 *data;
+ int datalen = 2 + n;
+
+ data = kmalloc(datalen, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data[0] = reg >> 8;
+ data[1] = reg & 0xFF;
+ memcpy(&data[2], buf, n);
+
+ mutex_lock(&charger->io_lock);
+ ret = i2c_master_send(charger->client, data, datalen);
+ mutex_unlock(&charger->io_lock);
+ kfree(data);
+
+ if (ret < datalen) {
+ /*
+ * Treat -ENOTCONN as -ENODEV to suppress the get/set
+ * prop warnings.
+ */
+ int nret = (ret == -ENOTCONN) ? -ENODEV : -EIO;
+
+ dev_err(&charger->client->dev,
+ "%s: i2c write error, reg: 0x%x, n: %zd ret: %d (%d)\n",
+ __func__, reg, n, ret, nret);
+ return nret;
+ }
+
+ return 0;
+}
+
+static int p9221_reg_write_16(struct p9221_charger_data *charger, u16 reg,
+ u16 val)
+{
+ return p9221_reg_write_n(charger, reg, &val, 2);
+}
+
+static int p9221_reg_write_8(struct p9221_charger_data *charger, u16 reg,
+ u8 val)
+{
+ return p9221_reg_write_n(charger, reg, &val, 1);
+}
+
+static int p9221_chip_get_tx_id(struct p9221_charger_data *charger, u32 *id)
+{
+ return p9221_reg_read_n(charger, P9221R5_PROP_TX_ID_REG, id, sizeof(*id));
+}
+
+static int p9382_chip_get_tx_id(struct p9221_charger_data *charger, u32 *id)
+{
+ return p9221_reg_read_n(charger, P9382_PROP_TX_ID_REG, id, sizeof(*id));
+}
+
+/* Manufacturer Code shows the WPC ID of the other side of the communication. */
+static int p9221_chip_get_tx_mfg_code(struct p9221_charger_data *charger,
+ u16 *code)
+{
+ return p9221_reg_read_16(charger, P9221R5_EPP_TX_MFG_CODE_REG, code);
+}
+
+static int p9382_chip_get_tx_mfg_code(struct p9221_charger_data *charger,
+ u16 *code)
+{
+ return p9221_reg_read_16(charger, P9382_EPP_TX_MFG_CODE_REG, code);
+}
+
+/*
+ * Uncook the values and write to register
+ */
+static int p9221_reg_write_cooked(struct p9221_charger_data *charger,
+ u16 reg, u32 val)
+{
+ int ret = 0;
+ u16 data;
+ int i;
+
+ /* Do the appropriate conversion */
+ switch (reg) {
+ case P9221R5_ILIM_SET_REG:
+ /* uA -> 0.1A, offset 0.2A */
+ if ((val < 200000) || (val > 1600000))
+ return -EINVAL;
+ data = (val / (100 * 1000)) - 2;
+ break;
+
+ case P9221R5_VOUT_SET_REG:
+ /* uV -> 0.1V */
+ val /= 1000;
+ if (val < 3500 || val > charger->pdata->max_vout_mv)
+ return -EINVAL;
+ data = val / 100;
+ break;
+
+ case P9221R5_OVSET_REG:
+ /* uV */
+ for (i = 0; i < ARRAY_SIZE(p9221_ov_set_lut); i++) {
+ if (val == p9221_ov_set_lut[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(p9221_ov_set_lut))
+ return -EINVAL;
+ data = i;
+ break;
+ case P9382A_ILIM_SET_REG:
+ /* mA */
+ if ((val < 0) || (val > P9382A_RTX_ICL_MAX_MA))
+ return -EINVAL;
+ data = val;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (p9221_reg_is_8_bit(charger, reg))
+ ret = p9221_reg_write_8(charger, reg, data);
+ else
+ ret = p9221_reg_write_16(charger, reg, data);
+
+ return ret;
+}
+
+static bool p9221_is_epp(struct p9221_charger_data *charger)
+{
+ int ret;
+ u32 vout_uv;
+ uint8_t reg;
+
+ if (charger->fake_force_epp > 0)
+ return true;
+ if (charger->force_bpp)
+ return false;
+
+ /*
+ * NOTE: mfg may be zero due to race condition during bringup. will
+ * check once more if mfg == 0.
+ */
+ if (charger->mfg == 0) {
+ ret = charger->chip_get_tx_mfg_code(charger, &charger->mfg);
+ if (ret < 0)
+ dev_err(&charger->client->dev,
+ "cannot read MFG_CODE (%d)\n", ret);
+ }
+
+ charger->is_mfg_google = charger->mfg == WLC_MFG_GOOGLE;
+
+ ret = p9221_reg_read_8(charger, P9221R5_SYSTEM_MODE_REG, ®);
+ if (ret == 0)
+ return (reg & P9221R5_SYSTEM_MODE_EXTENDED_MASK) > 0;
+
+ dev_err(&charger->client->dev, "Could not read mode: %d\n",
+ ret);
+
+ /* Check based on power supply voltage */
+ ret = p9221_reg_read_cooked(charger, P9221R5_VOUT_ADC_REG, &vout_uv);
+ if (ret) {
+ dev_err(&charger->client->dev, "Could read VOUT_ADC, %d\n",
+ ret);
+ goto out;
+ }
+
+ dev_info(&charger->client->dev, "Voltage is %duV\n", vout_uv);
+ if (vout_uv > P9221_EPP_THRESHOLD_UV)
+ return true;
+
+out:
+ /* Default to BPP otherwise */
+ return false;
+}
+
+static void p9221_write_fod(struct p9221_charger_data *charger)
+{
+ bool epp = false;
+ u8 *fod = NULL;
+ int fod_count = charger->pdata->fod_num;
+ int ret;
+ int retries = 3;
+
+ if (!charger->pdata->fod_num && !charger->pdata->fod_epp_num)
+ goto no_fod;
+
+ /* Default to BPP FOD */
+ if (charger->pdata->fod_num)
+ fod = charger->pdata->fod;
+
+ if (p9221_is_epp(charger) && charger->pdata->fod_epp_num) {
+ fod = charger->pdata->fod_epp;
+ fod_count = charger->pdata->fod_epp_num;
+ epp = true;
+ }
+
+ if (!fod)
+ goto no_fod;
+
+ while (retries) {
+ char s[P9221R5_NUM_FOD * 3 + 1];
+ u8 fod_read[P9221R5_NUM_FOD];
+
+ dev_info(&charger->client->dev, "Writing %s FOD (n=%d reg=%02x try=%d)\n",
+ epp ? "EPP" : "BPP", fod_count, P9221R5_FOD_REG,
+ retries);
+
+ ret = p9221_reg_write_n(charger, P9221R5_FOD_REG, fod,
+ fod_count);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Could not write FOD: %d\n", ret);
+ return;
+ }
+
+ /* Verify the FOD has been written properly */
+ ret = p9221_reg_read_n(charger, P9221R5_FOD_REG, fod_read,
+ fod_count);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Could not read back FOD: %d\n", ret);
+ return;
+ }
+
+ if (memcmp(fod, fod_read, fod_count) == 0)
+ return;
+
+ p9221_hex_str(fod_read, fod_count, s, sizeof(s), 0);
+ dev_err(&charger->client->dev,
+ "FOD verify error, read: %s\n", s);
+
+ retries--;
+ msleep(100);
+ }
+
+no_fod:
+ dev_warn(&charger->client->dev, "FOD not set! bpp:%d epp:%d r:%d\n",
+ charger->pdata->fod_num, charger->pdata->fod_epp_num, retries);
+}
+
+static int p9221_set_cmd_reg(struct p9221_charger_data *charger, u8 cmd)
+{
+ u8 cur_cmd = 0;
+ int retry;
+ int ret;
+
+ for (retry = 0; retry < P9221_COM_CHAN_RETRIES; retry++) {
+ ret = p9221_reg_read_8(charger, P9221_COM_REG, &cur_cmd);
+ if (ret == 0 && cur_cmd == 0)
+ break;
+ msleep(25);
+ }
+
+ if (retry >= P9221_COM_CHAN_RETRIES) {
+ dev_err(&charger->client->dev,
+ "Failed to wait for cmd free %02x\n", cur_cmd);
+ return -EBUSY;
+ }
+
+ ret = p9221_reg_write_8(charger, P9221_COM_REG, cmd);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Failed to set cmd reg %02x: %d\n", cmd, ret);
+
+ return ret;
+}
+
+static int p9221_send_data(struct p9221_charger_data *charger)
+{
+ int ret;
+
+ if (charger->tx_busy)
+ return -EBUSY;
+
+ if (!charger->tx_len || charger->tx_len > P9221R5_DATA_SEND_BUF_SIZE)
+ return -EINVAL;
+
+ charger->tx_busy = true;
+
+ mutex_lock(&charger->cmd_lock);
+
+ ret = p9221_reg_write_n(charger, charger->addr_data_send_buf_start,
+ charger->tx_buf, charger->tx_len);
+ if (ret) {
+ dev_err(&charger->client->dev, "Failed to load tx %d\n", ret);
+ goto error;
+ }
+
+ ret = p9221_reg_write_8(charger, P9221R5_COM_CHAN_SEND_SIZE_REG,
+ charger->tx_len);
+ if (ret) {
+ dev_err(&charger->client->dev, "Failed to load txsz %d\n", ret);
+ goto error;
+ }
+
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_CCACTIVATE);
+ if (ret)
+ goto error;
+
+ mutex_unlock(&charger->cmd_lock);
+ return ret;
+
+error:
+ mutex_unlock(&charger->cmd_lock);
+ charger->tx_busy = false;
+ return ret;
+}
+
+static int p9221_send_csp(struct p9221_charger_data *charger, u8 stat)
+{
+ int ret;
+
+ dev_info(&charger->client->dev, "Send CSP status=%d\n", stat);
+
+ mutex_lock(&charger->cmd_lock);
+
+ ret = p9221_reg_write_8(charger, P9221R5_CHARGE_STAT_REG, stat);
+ if (ret == 0)
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_SENDCSP);
+
+ mutex_unlock(&charger->cmd_lock);
+ return ret;
+}
+
+static int p9221_send_eop(struct p9221_charger_data *charger, u8 reason)
+{
+ int ret;
+
+ dev_info(&charger->client->dev, "Send EOP reason=%d\n", reason);
+
+ mutex_lock(&charger->cmd_lock);
+
+ ret = p9221_reg_write_8(charger, P9221R5_EPT_REG, reason);
+ if (ret == 0)
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_SENDEPT);
+
+ mutex_unlock(&charger->cmd_lock);
+ return ret;
+}
+
+static int p9221_send_ccreset(struct p9221_charger_data *charger)
+{
+ int ret;
+
+ dev_info(&charger->client->dev, "Send CC reset\n");
+
+ mutex_lock(&charger->cmd_lock);
+
+ ret = p9221_reg_write_8(charger, P9221R5_COM_CHAN_RESET_REG,
+ P9221R5_COM_CHAN_CCRESET);
+ if (ret == 0)
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_CCACTIVATE);
+
+ mutex_unlock(&charger->cmd_lock);
+ return ret;
+}
+
+struct p9221_prop_reg_map_entry p9221_prop_reg_map[] = {
+ /* property register g, s */
+ {POWER_SUPPLY_PROP_CURRENT_NOW, P9221R5_IOUT_REG, 1, 0},
+ {POWER_SUPPLY_PROP_VOLTAGE_NOW, P9221R5_VOUT_REG, 1, 0},
+ {POWER_SUPPLY_PROP_VOLTAGE_MAX, P9221R5_VOUT_SET_REG, 1, 1},
+ {POWER_SUPPLY_PROP_TEMP, P9221R5_DIE_TEMP_ADC_REG, 1, 0},
+ {POWER_SUPPLY_PROP_CAPACITY, 0, 1, 1},
+ {POWER_SUPPLY_PROP_ONLINE, 0, 1, 1},
+ {POWER_SUPPLY_PROP_OPERATING_FREQ,
+ P9221R5_OP_FREQ_REG, 1, 0},
+};
+
+static struct p9221_prop_reg_map_entry *p9221_get_map_entry(
+ struct p9221_charger_data *charger,
+ enum power_supply_property prop, bool set)
+{
+ int i;
+ struct p9221_prop_reg_map_entry *p;
+ int map_size;
+
+ p = p9221_prop_reg_map;
+ map_size = ARRAY_SIZE(p9221_prop_reg_map);
+
+ for (i = 0; i < map_size; i++) {
+ if (p->prop == prop) {
+ if ((set && p->set) || (!set && p->get))
+ return p;
+ }
+ p++;
+ }
+ return NULL;
+}
+
+static bool p9221_is_online(const struct p9221_charger_data *charger)
+{
+ return charger->online || charger->ben_state;
+}
+
+static int p9221_get_property_reg(struct p9221_charger_data *charger,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ int ret;
+ struct p9221_prop_reg_map_entry *p;
+ u32 data;
+
+ pm_runtime_get_sync(charger->dev);
+ if (!charger->resume_complete) {
+ pm_runtime_put_sync(charger->dev);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(charger->dev);
+
+ p = p9221_get_map_entry(charger, prop, false);
+ if (p == NULL)
+ return -EINVAL;
+
+ if (!p9221_is_online(charger))
+ return -ENODEV;
+
+ ret = p9221_reg_read_cooked(charger, p->reg, &data);
+ if (ret)
+ return ret;
+
+ val->intval = data;
+ return 0;
+}
+
+static int p9221_set_property_reg(struct p9221_charger_data *charger,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct p9221_prop_reg_map_entry *p;
+
+ p = p9221_get_map_entry(charger, prop, true);
+ if (p == NULL)
+ return -EINVAL;
+
+ if (!p9221_is_online(charger))
+ return -ENODEV;
+
+ return p9221_reg_write_cooked(charger, p->reg, val->intval);
+}
+
+static void p9221_abort_transfers(struct p9221_charger_data *charger)
+{
+ /* Abort all transfers */
+ cancel_delayed_work(&charger->tx_work);
+ charger->tx_busy = false;
+ charger->tx_done = true;
+ charger->rx_done = true;
+ charger->rx_len = 0;
+ sysfs_notify(&charger->dev->kobj, NULL, "txbusy");
+ sysfs_notify(&charger->dev->kobj, NULL, "txdone");
+ sysfs_notify(&charger->dev->kobj, NULL, "rxdone");
+}
+
+/*
+ * Put the default ICL back to BPP, reset OCP voter
+ * @pre charger && charger->dc_icl_votable && charger->client->dev
+ */
+static void p9221_vote_defaults(struct p9221_charger_data *charger)
+{
+ int ret, ocp_icl;
+
+ if (!charger->dc_icl_votable) {
+ dev_err(&charger->client->dev,
+ "Could not vote DC_ICL - no votable\n");
+ return;
+ }
+
+ ret = vote(charger->dc_icl_votable, P9221_WLC_VOTER, true,
+ P9221_DC_ICL_BPP_UA);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not vote DC_ICL %d\n", ret);
+
+ if (charger->dc_icl_epp > charger->ocp_icl_lmt)
+ charger->dc_icl_epp = charger->ocp_icl_val;
+
+ ret = vote(charger->dc_icl_votable, P9221_OCP_VOTER, true,
+ ocp_icl);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not reset OCP DC_ICL voter %d\n", ret);
+}
+
+static void p9221_set_offline(struct p9221_charger_data *charger)
+{
+ dev_info(&charger->client->dev, "Set offline\n");
+ logbuffer_log(charger->log, "offline\n");
+
+ charger->online = false;
+ charger->force_bpp = false;
+
+ /* Reset PP buf so we can get a new serial number next time around */
+ charger->pp_buf_valid = false;
+
+ p9221_abort_transfers(charger);
+ cancel_delayed_work(&charger->dcin_work);
+
+ /* Reset alignment value when charger goes offline */
+ cancel_delayed_work(&charger->align_work);
+ charger->align = POWER_SUPPLY_ALIGN_ERROR;
+ charger->align_count = 0;
+ charger->alignment = -1;
+ charger->alignment_capable = ALIGN_MFG_FAILED;
+ charger->mfg = 0;
+ schedule_work(&charger->uevent_work);
+
+ p9221_icl_ramp_reset(charger);
+ del_timer(&charger->vrect_timer);
+
+ p9221_vote_defaults(charger);
+ if (charger->enabled &&
+ charger->pdata->qien_gpio >= 0) {
+ if (charger->is_mfg_google == false)
+ gpio_set_value(charger->pdata->qien_gpio, 1);
+
+ mod_delayed_work(system_wq, &charger->dcin_pon_work,
+ msecs_to_jiffies(P9221_DCIN_PON_DELAY_MS));
+ }
+ charger->is_mfg_google = false;
+}
+
+static void p9221_tx_work(struct work_struct *work)
+{
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, tx_work.work);
+
+ dev_info(&charger->client->dev, "timeout waiting for tx complete\n");
+
+ charger->tx_busy = false;
+ charger->tx_done = true;
+ sysfs_notify(&charger->dev->kobj, NULL, "txbusy");
+ sysfs_notify(&charger->dev->kobj, NULL, "txdone");
+}
+
+static void p9221_vrect_timer_handler(struct timer_list *t)
+{
+ struct p9221_charger_data *charger = from_timer(charger,
+ t, vrect_timer);
+
+ if (charger->align == POWER_SUPPLY_ALIGN_CHECKING) {
+ schedule_work(&charger->uevent_work);
+ charger->align = POWER_SUPPLY_ALIGN_MOVE;
+ logbuffer_log(charger->log, "align: state: %s",
+ align_status_str[charger->align]);
+ }
+ dev_info(&charger->client->dev,
+ "timeout waiting for VRECT, online=%d\n", charger->online);
+ logbuffer_log(charger->log,
+ "vrect: timeout online=%d", charger->online);
+
+ mod_timer(&charger->align_timer,
+ jiffies + msecs_to_jiffies(P9221_ALIGN_TIMEOUT_MS));
+
+ pm_relax(charger->dev);
+}
+
+static void p9221_align_timer_handler(struct timer_list *t)
+{
+ struct p9221_charger_data *charger = from_timer(charger,
+ t, align_timer);
+
+ schedule_work(&charger->uevent_work);
+ charger->align = POWER_SUPPLY_ALIGN_ERROR;
+ logbuffer_log(charger->log, "align: timeout no IRQ");
+}
+
+static void p9221_dcin_pon_work(struct work_struct *work)
+{
+ int ret;
+ union power_supply_propval prop;
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, dcin_pon_work.work);
+
+ if (!charger->dc_psy)
+ return;
+
+ ret = power_supply_get_property(charger->dc_psy,
+ POWER_SUPPLY_PROP_DC_RESET, &prop);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Error getting charging status: %d\n", ret);
+ return;
+ }
+
+ if (prop.intval == 0) {
+ gpio_set_value(charger->pdata->qien_gpio, 0);
+ } else {
+ /* Signal DC_RESET when vout keeps on 1. */
+ ret = power_supply_set_property(charger->dc_psy,
+ POWER_SUPPLY_PROP_DC_RESET,
+ &prop);
+ if (ret < 0)
+ dev_err(&charger->client->dev,
+ "unable to set DC_RESET, ret=%d", ret);
+
+ schedule_delayed_work(&charger->dcin_pon_work,
+ msecs_to_jiffies(P9221_DCIN_TIMEOUT_MS));
+ }
+}
+
+static void p9221_dcin_work(struct work_struct *work)
+{
+ int res;
+ u16 status_reg = 0;
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, dcin_work.work);
+
+ res = p9221_reg_read_16(charger, P9221_STATUS_REG, &status_reg);
+ if (res != 0) {
+ dev_info(&charger->client->dev,
+ "timeout waiting for dc-in, online=%d\n",
+ charger->online);
+ logbuffer_log(charger->log,
+ "dc_in: timeout online=%d", charger->online);
+
+ if (charger->online)
+ p9221_set_offline(charger);
+
+ power_supply_changed(charger->wc_psy);
+ pm_relax(charger->dev);
+
+ return;
+ }
+
+ schedule_delayed_work(&charger->dcin_work,
+ msecs_to_jiffies(P9221_DCIN_TIMEOUT_MS));
+ logbuffer_log(charger->log, "dc_in: check online=%d status=%x",
+ charger->online, status_reg);
+}
+
+static void p9221_init_align(struct p9221_charger_data *charger)
+{
+ /* Reset values used for alignment */
+ charger->alignment_last = -1;
+ charger->current_filtered = 0;
+ charger->current_sample_cnt = 0;
+ charger->mfg_check_count = 0;
+ schedule_delayed_work(&charger->align_work,
+ msecs_to_jiffies(P9221_ALIGN_DELAY_MS));
+}
+
+static void p9221_align_work(struct work_struct *work)
+{
+ int res, align_buckets, i, wlc_freq_threshold, wlc_adj_freq;
+ u16 current_now, current_filter_sample;
+ u32 wlc_freq, current_scaling = 0;
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, align_work.work);
+
+ if (charger->pdata->alignment_freq == NULL)
+ return;
+
+ charger->alignment = -1;
+
+ if (!charger->online)
+ return;
+
+ /*
+ * NOTE: mfg may be zero due to race condition during bringup. If the
+ * mfg check continues to fail then mfg is not correct and we do not
+ * reschedule align_work. Always reschedule if alignment_capable is 1.
+ * Check 10 times if alignment_capble is still 0.
+ */
+ if ((charger->mfg_check_count < 10) ||
+ (charger->alignment_capable == ALIGN_MFG_PASSED))
+ schedule_delayed_work(&charger->align_work,
+ msecs_to_jiffies(P9221_ALIGN_DELAY_MS));
+
+ if (charger->alignment_capable == ALIGN_MFG_CHECKING) {
+ charger->mfg_check_count += 1;
+
+ res = charger->chip_get_tx_mfg_code(charger, &charger->mfg);
+ if (res < 0) {
+ dev_err(&charger->client->dev,
+ "cannot read MFG_CODE (%d)\n", res);
+ return;
+ }
+
+ /* No mfg update. Will check again on next schedule */
+ if (charger->mfg == 0)
+ return;
+
+ if ((charger->mfg != WLC_MFG_GOOGLE) ||
+ !p9221_is_epp(charger)) {
+ logbuffer_log(charger->log,
+ "align: not align capable mfg: 0x%x",
+ charger->mfg);
+ cancel_delayed_work(&charger->align_work);
+ charger->alignment_capable = ALIGN_MFG_FAILED;
+ return;
+ }
+ charger->alignment_capable = ALIGN_MFG_PASSED;
+ }
+
+ if (charger->pdata->alignment_scalar == 0)
+ goto no_scaling;
+
+ res = p9221_reg_read_16(charger, P9221R5_IOUT_REG, ¤t_now);
+
+ if (res != 0) {
+ logbuffer_log(charger->log, "align: failed to read IOUT");
+ current_now = 0;
+ }
+
+ current_filter_sample =
+ charger->current_filtered / WLC_CURRENT_FILTER_LENGTH;
+
+ if (charger->current_sample_cnt < WLC_CURRENT_FILTER_LENGTH)
+ charger->current_sample_cnt++;
+ else
+ charger->current_filtered -= current_filter_sample;
+
+ charger->current_filtered += (current_now / WLC_CURRENT_FILTER_LENGTH);
+ dev_dbg(&charger->client->dev, "current = %umA, avg_current = %umA\n",
+ current_now, charger->current_filtered);
+
+ current_scaling = charger->pdata->alignment_scalar *
+ charger->current_filtered;
+
+no_scaling:
+ res = p9221_reg_read_cooked(charger, P9221R5_OP_FREQ_REG, &wlc_freq);
+
+ if (res != 0) {
+ logbuffer_log(charger->log, "align: failed to read op_freq");
+ return;
+ }
+
+ align_buckets = charger->pdata->nb_alignment_freq - 1;
+
+ charger->alignment = -1;
+ wlc_adj_freq = wlc_freq + current_scaling;
+
+ if (wlc_adj_freq < charger->pdata->alignment_freq[0]) {
+ logbuffer_log(charger->log, "align: freq below range");
+ return;
+ }
+
+ for (i = 0; i < align_buckets; i += 1) {
+ if ((wlc_adj_freq > charger->pdata->alignment_freq[i]) &&
+ (wlc_adj_freq <= charger->pdata->alignment_freq[i + 1])) {
+ charger->alignment = (WLC_ALIGNMENT_MAX * i) /
+ (align_buckets - 1);
+ break;
+ }
+ }
+
+ if (i >= align_buckets) {
+ logbuffer_log(charger->log, "align: freq above range");
+ return;
+ }
+
+ if (charger->alignment == charger->alignment_last)
+ return;
+
+ /*
+ * Frequency needs to be higher than frequency + hysteresis before
+ * increasing alignment score.
+ */
+ wlc_freq_threshold = charger->pdata->alignment_freq[i] +
+ charger->pdata->alignment_hysteresis;
+
+ if ((charger->alignment < charger->alignment_last) ||
+ (wlc_adj_freq >= wlc_freq_threshold)) {
+ schedule_work(&charger->uevent_work);
+ logbuffer_log(charger->log,
+ "align: alignment=%i. op_freq=%u. current_avg=%u",
+ charger->alignment, wlc_freq,
+ charger->current_filtered);
+ charger->alignment_last = charger->alignment;
+ }
+}
+
+static const char *p9221_get_tx_id_str(struct p9221_charger_data *charger)
+{
+ int ret;
+ uint32_t tx_id = 0;
+
+ if (!p9221_is_online(charger))
+ return NULL;
+
+ pm_runtime_get_sync(charger->dev);
+ if (!charger->resume_complete) {
+ pm_runtime_put_sync(charger->dev);
+ return NULL;
+ }
+ pm_runtime_put_sync(charger->dev);
+
+ if (p9221_is_epp(charger)) {
+ ret = charger->chip_get_tx_id(charger, &tx_id);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Failed to read txid %d\n", ret);
+ } else {
+ /*
+ * If pp_buf_valid is true, we have received a serial
+ * number from the Tx, copy it to tx_id. (pp_buf_valid
+ * is left true here until we go offline as we may
+ * read this multiple times.)
+ */
+ if (charger->pp_buf_valid &&
+ sizeof(tx_id) <= P9221R5_MAX_PP_BUF_SIZE)
+ memcpy(&tx_id, &charger->pp_buf[1],
+ sizeof(tx_id));
+ }
+ scnprintf(charger->tx_id_str, sizeof(charger->tx_id_str),
+ "%08x", tx_id);
+ return charger->tx_id_str;
+}
+static const char *p9382_get_ptmc_id_str(struct p9221_charger_data *charger)
+{
+ int ret;
+ uint16_t ptmc_id;
+
+ if (!charger->ben_state || (charger->chip_id < P9382A_CHIP_ID))
+ return NULL;
+
+ pm_runtime_get_sync(charger->dev);
+ if (!charger->resume_complete) {
+ pm_runtime_put_sync(charger->dev);
+ return NULL;
+ }
+ pm_runtime_put_sync(charger->dev);
+
+ ret = charger->chip_get_tx_mfg_code(charger, &ptmc_id);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to read device prmc %d\n", ret);
+ return NULL;
+ }
+
+ scnprintf(charger->ptmc_id_str,
+ sizeof(charger->ptmc_id_str), "%04x", ptmc_id);
+
+ return charger->ptmc_id_str;
+}
+
+/*
+ * DC_SUSPEND is used to prevent inflow from wireless charging. When present
+ * will return 1 if the user has disabled the source (override online).
+ */
+static int p9221_get_dc_enable(struct p9221_charger_data *charger)
+{
+ int suspend = -EINVAL;
+
+ if (!charger->dc_suspend_votable)
+ charger->dc_suspend_votable = find_votable("DC_SUSPEND");
+ if (charger->dc_suspend_votable)
+ suspend = get_effective_result(charger->dc_suspend_votable);
+
+ pr_debug("%s: suspend=%d\n", __func__, suspend);
+ return suspend < 0 ? suspend : !suspend;
+}
+
+
+static int p9221_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct p9221_charger_data *charger = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (prop) {
+ /* check for field */
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = p9221_get_dc_enable(charger);
+ pr_debug("%s: dc_enable=%d, online=%d, enabled=%d\n",
+ __func__, val->intval, charger->online,
+ charger->enabled);
+ if (val->intval != 0)
+ val->intval = charger->online && charger->enabled;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = p9221_get_tx_id_str(charger);
+ if (val->strval == NULL)
+ return -ENODATA;
+ break;
+ case POWER_SUPPLY_PROP_PTMC_ID:
+ val->strval = p9382_get_ptmc_id_str(charger);
+ if (val->strval == NULL)
+ return -ENODATA;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (charger->last_capacity > 0)
+ val->intval = charger->last_capacity;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (!charger->dc_icl_votable)
+ return -EAGAIN;
+
+ ret = get_effective_result(charger->dc_icl_votable);
+ if (ret < 0)
+ break;
+
+ val->intval = ret;
+
+ /* success */
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_AICL_DELAY:
+ val->intval = charger->aicl_delay_ms;
+ break;
+ case POWER_SUPPLY_PROP_AICL_ICL:
+ val->intval = charger->aicl_icl_ua;
+ break;
+ default:
+ ret = p9221_get_property_reg(charger, prop, val);
+ break;
+ }
+
+ if (ret)
+ dev_dbg(&charger->client->dev,
+ "Couldn't get prop %d, ret=%d\n", prop, ret);
+ return ret;
+}
+
+static int p9221_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct p9221_charger_data *charger = power_supply_get_drvdata(psy);
+ int ret = 0;
+ bool changed = false;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if ((val->intval < 0) || (val->intval > 1)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (charger->enabled == val->intval)
+ break;
+ /*
+ * Asserting the enable line will automatically take bring
+ * us online if we are in field. De-asserting the enable
+ * line will automatically take us offline if we are in field.
+ * This is due to the fact that DC in will change state
+ * appropriately when we change the state of this line.
+ */
+ charger->enabled = val->intval;
+
+ dev_warn(&charger->client->dev, "Set enable %d\n",
+ charger->enabled);
+
+ if (charger->pdata->qien_gpio >= 0)
+ gpio_set_value(charger->pdata->qien_gpio,
+ charger->enabled ? 0 : 1);
+
+ changed = true;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (charger->last_capacity == val->intval)
+ break;
+
+ charger->last_capacity = val->intval;
+
+ if (!charger->online)
+ break;
+
+ ret = p9221_send_csp(charger, charger->last_capacity);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could send csp: %d\n", ret);
+ changed = true;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (val->intval < 0) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (!charger->dc_icl_votable) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ ret = vote(charger->dc_icl_votable, P9221_USER_VOTER, true,
+ val->intval);
+
+ changed = true;
+ break;
+ default:
+ ret = p9221_set_property_reg(charger, prop, val);
+ if (ret == 0)
+ changed = true;
+ break;
+ }
+
+ if (ret)
+ dev_dbg(&charger->client->dev,
+ "Couldn't set prop %d, ret=%d\n", prop, ret);
+
+ if (changed)
+ power_supply_changed(psy);
+
+ return ret;
+}
+
+static int p9221_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ struct p9221_charger_data *charger = power_supply_get_drvdata(psy);
+
+ if (p9221_get_map_entry(charger, prop, true))
+ return 1;
+
+ return 0;
+}
+
+static int p9221_notifier_cb(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+ struct power_supply *psy = data;
+ struct p9221_charger_data *charger =
+ container_of(nb, struct p9221_charger_data, nb);
+
+ if (charger->ben_state)
+ goto out;
+
+ if (event != PSY_EVENT_PROP_CHANGED)
+ goto out;
+
+ pr_debug("%s: psy_changed: from=%s evt=%d\n", __func__,
+ psy->desc->name, event);
+
+ if (strcmp(psy->desc->name, "dc") == 0) {
+ charger->dc_psy = psy;
+ charger->check_dc = true;
+ }
+
+ if (!charger->check_dc)
+ goto out;
+
+ pm_stay_awake(charger->dev);
+
+ if (!schedule_delayed_work(&charger->notifier_work,
+ msecs_to_jiffies(P9221_NOTIFIER_DELAY_MS)))
+ pm_relax(charger->dev);
+
+out:
+ return NOTIFY_OK;
+}
+
+static int p9221_clear_interrupts(struct p9221_charger_data *charger, u16 mask)
+{
+ int ret;
+
+ mutex_lock(&charger->cmd_lock);
+
+ ret = p9221_reg_write_16(charger, P9221R5_INT_CLEAR_REG, mask);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to clear INT reg: %d\n", ret);
+ goto out;
+ }
+
+ ret = p9221_set_cmd_reg(charger, P9221_COM_CLEAR_INT_MASK);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to reset INT: %d\n", ret);
+ }
+out:
+ mutex_unlock(&charger->cmd_lock);
+ return ret;
+}
+
+/*
+ * Enable interrupts on the P9221, note we don't really need to disable
+ * interrupts since when the device goes out of field, the P9221 is reset.
+ */
+static int p9221_enable_interrupts(struct p9221_charger_data *charger)
+{
+ u16 mask;
+ int ret;
+
+ dev_dbg(&charger->client->dev, "Enable interrupts\n");
+
+ if (charger->ben_state) {
+ /* enable necessary INT for RTx mode */
+ mask = P9382_STAT_RXCONNECTED | P9221R5_STAT_MODECHANGED |
+ P9382_STAT_CSP | P9382_STAT_TXCONFLICT;
+ } else {
+ mask = P9221R5_STAT_LIMIT_MASK | P9221R5_STAT_CC_MASK |
+ P9221_STAT_VRECT;
+
+ if (charger->pdata->needs_dcin_reset ==
+ P9221_WC_DC_RESET_VOUTCHANGED)
+ mask |= P9221R5_STAT_VOUTCHANGED;
+ if (charger->pdata->needs_dcin_reset ==
+ P9221_WC_DC_RESET_MODECHANGED)
+ mask |= P9221R5_STAT_MODECHANGED;
+ }
+ ret = p9221_clear_interrupts(charger, mask);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not clear interrupts: %d\n", ret);
+
+ ret = p9221_reg_write_8(charger, P9221_INT_ENABLE_REG, mask);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not enable interrupts: %d\n", ret);
+
+ return ret;
+}
+
+static void p9382_check_neg_power(struct p9221_charger_data *charger)
+{
+ int ret;
+ u8 np8;
+
+ charger->dc_icl_epp_neg = P9221_DC_ICL_EPP_UA;
+
+ if ((charger->chip_id < P9382A_CHIP_ID) || !p9221_is_epp(charger))
+ return;
+
+ if (charger->is_mfg_google) {
+ charger->dc_icl_epp_neg = P9382A_DC_ICL_EPP_1000;
+ dev_info(&charger->client->dev,
+ "mfg code=%02x, use dc_icl=%dmA\n",
+ WLC_MFG_GOOGLE, P9382A_DC_ICL_EPP_1000);
+ return;
+ }
+
+ ret = p9221_reg_read_8(charger, P9221R5_EPP_CUR_NEGOTIATED_POWER_REG,
+ &np8);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not read Tx neg power: %d\n", ret);
+ else if (np8 < P9382A_NEG_POWER_10W) {
+ /*
+ * base on firmware 17
+ * Vout is 5V when Tx<10W, use BPP ICL
+ */
+ charger->dc_icl_epp_neg = P9221_DC_ICL_BPP_UA;
+ dev_info(&charger->client->dev,
+ "EPP less than 10W,use dc_icl=%dmA,np=%02x\n",
+ P9221_DC_ICL_BPP_UA/1000, np8);
+ } else {
+ if (np8 > P9382A_NEG_POWER_11W)
+ charger->dc_icl_epp_neg = P9382A_DC_ICL_EPP_1200;
+ else if (np8 < P9382A_NEG_POWER_11W)
+ charger->dc_icl_epp_neg = P9382A_DC_ICL_EPP_1000;
+
+ dev_info(&charger->client->dev,
+ "Use dc_icl=%dmA,np=%02x\n",
+ charger->dc_icl_epp_neg/1000, np8);
+ }
+}
+
+static int p9221_set_dc_icl(struct p9221_charger_data *charger)
+{
+ int icl;
+ int ret;
+
+ if (!charger->dc_icl_votable) {
+ charger->dc_icl_votable = find_votable("DC_ICL");
+ if (!charger->dc_icl_votable) {
+ dev_err(&charger->client->dev,
+ "Could not get votable: DC_ICL\n");
+ return -ENODEV;
+ }
+ }
+
+ /* Default to BPP ICL */
+ icl = P9221_DC_ICL_BPP_UA;
+
+ if (charger->icl_ramp)
+ icl = charger->icl_ramp_ua;
+
+ if (charger->dc_icl_bpp)
+ icl = charger->dc_icl_bpp;
+
+ if (p9221_is_epp(charger))
+ icl = charger->dc_icl_epp_neg;
+
+ if (p9221_is_epp(charger) && charger->dc_icl_epp)
+ icl = charger->dc_icl_epp;
+
+ dev_info(&charger->client->dev, "Setting ICL %duA ramp=%d\n", icl,
+ charger->icl_ramp);
+
+ if (charger->icl_ramp)
+ vote(charger->dc_icl_votable, DCIN_AICL_VOTER, true, icl);
+
+ ret = vote(charger->dc_icl_votable, P9221_WLC_VOTER, true, icl);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not vote DC_ICL %d\n", ret);
+
+ /* Increase the IOUT limit */
+ ret = p9221_reg_write_cooked(charger, P9221R5_ILIM_SET_REG,
+ P9221R5_ILIM_MAX_UA);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not set rx_iout limit reg: %d\n", ret);
+
+ return ret;
+}
+
+static enum alarmtimer_restart p9221_icl_ramp_alarm_cb(struct alarm *alarm,
+ ktime_t now)
+{
+ struct p9221_charger_data *charger =
+ container_of(alarm, struct p9221_charger_data,
+ icl_ramp_alarm);
+
+ dev_info(&charger->client->dev, "ICL ramp alarm, ramp=%d\n",
+ charger->icl_ramp);
+
+ /* Alarm is in atomic context, schedule work to complete the task */
+ pm_stay_awake(charger->dev);
+ schedule_delayed_work(&charger->icl_ramp_work, msecs_to_jiffies(100));
+
+ return ALARMTIMER_NORESTART;
+}
+
+static void p9221_icl_ramp_work(struct work_struct *work)
+{
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, icl_ramp_work.work);
+
+ pm_runtime_get_sync(charger->dev);
+ if (!charger->resume_complete) {
+ pm_runtime_put_sync(charger->dev);
+ schedule_delayed_work(&charger->icl_ramp_work,
+ msecs_to_jiffies(100));
+ dev_dbg(&charger->client->dev, "Ramp reschedule\n");
+ return;
+ }
+ pm_runtime_put_sync(charger->dev);
+
+ dev_info(&charger->client->dev, "ICL ramp work, ramp=%d\n",
+ charger->icl_ramp);
+
+ charger->icl_ramp = true;
+ p9221_set_dc_icl(charger);
+
+ pm_relax(charger->dev);
+}
+
+static void p9221_icl_ramp_reset(struct p9221_charger_data *charger)
+{
+ dev_info(&charger->client->dev, "ICL ramp reset, ramp=%d\n",
+ charger->icl_ramp);
+
+ charger->icl_ramp = false;
+
+ if (alarm_try_to_cancel(&charger->icl_ramp_alarm) < 0)
+ dev_warn(&charger->client->dev, "Couldn't cancel icl_ramp_alarm\n");
+ cancel_delayed_work(&charger->icl_ramp_work);
+}
+
+static void p9221_icl_ramp_start(struct p9221_charger_data *charger)
+{
+ const bool no_ramp = charger->pdata->icl_ramp_delay_ms == -1 ||
+ !charger->icl_ramp_ua;
+
+ /* Only ramp on BPP at this time */
+ if (p9221_is_epp(charger) || no_ramp)
+ return;
+
+ p9221_icl_ramp_reset(charger);
+
+ dev_info(&charger->client->dev, "ICL ramp set alarm %dms, %dua, ramp=%d\n",
+ charger->pdata->icl_ramp_delay_ms, charger->icl_ramp_ua,
+ charger->icl_ramp);
+
+ alarm_start_relative(&charger->icl_ramp_alarm,
+ ms_to_ktime(charger->pdata->icl_ramp_delay_ms));
+}
+
+static void p9221_set_online(struct p9221_charger_data *charger)
+{
+ int ret;
+ u8 cid = 5;
+
+ dev_info(&charger->client->dev, "Set online\n");
+
+ charger->online = true;
+ charger->tx_busy = false;
+ charger->tx_done = true;
+ charger->rx_done = false;
+ charger->last_capacity = -1;
+
+ ret = p9221_reg_read_8(charger, P9221_CUSTOMER_ID_REG, &cid);
+ if (ret)
+ dev_err(&charger->client->dev, "Could not get ID: %d\n", ret);
+ else
+ charger->cust_id = cid;
+
+ dev_info(&charger->client->dev, "P9221 cid: %02x\n", charger->cust_id);
+
+ ret = p9221_enable_interrupts(charger);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not enable interrupts: %d\n", ret);
+
+ /* NOTE: depends on _is_epp() which is not valid until DC_IN */
+ p9221_write_fod(charger);
+
+ cancel_delayed_work(&charger->dcin_pon_work);
+
+ charger->alignment_capable = ALIGN_MFG_CHECKING;
+ charger->align = POWER_SUPPLY_ALIGN_CENTERED;
+ charger->alignment = -1;
+ logbuffer_log(charger->log, "align: state: %s",
+ align_status_str[charger->align]);
+ schedule_work(&charger->uevent_work);
+}
+
+static int p9221_has_dc_in(struct p9221_charger_data *charger)
+{
+ union power_supply_propval prop;
+ int ret;
+
+ if (!charger->dc_psy)
+ return -EINVAL;
+
+ ret = power_supply_get_property(charger->dc_psy,
+ POWER_SUPPLY_PROP_PRESENT, &prop);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Error getting charging status: %d\n", ret);
+ return -EINVAL;
+ }
+
+ return prop.intval != 0;
+}
+
+static int p9221_set_bpp_vout(struct p9221_charger_data *charger)
+{
+ uint8_t val8;
+ int ret, loops;
+ const uint8_t vout_5000mv = 5 * 10; /* in 0.1V units */
+
+ for (loops = 0; loops < 10; loops++) {
+ ret = p9221_reg_write_8(charger,
+ P9221R5_VOUT_SET_REG, vout_5000mv);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "cannot set VOUT (%d)\n", ret);
+ return ret;
+ }
+
+ ret = p9221_reg_read_8(charger, P9221R5_VOUT_SET_REG, &val8);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "cannot read VOUT (%d)\n", ret);
+ return ret;
+ }
+
+ if (val8 == vout_5000mv)
+ return 0;
+
+ msleep(10);
+ }
+
+ return -ETIMEDOUT;
+}
+
+/* return <0 on error, 0 on done, 1 on keep trying */
+static int p9221_notifier_check_neg_power(struct p9221_charger_data *charger)
+{
+ u8 np8;
+ int ret;
+ u16 status_reg;
+
+ ret = p9221_reg_read_8(charger, P9221R5_EPP_CUR_NEGOTIATED_POWER_REG,
+ &np8);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "cannot read EPP_NEG_POWER (%d)\n", ret);
+ return -EIO;
+ }
+
+ if (np8 >= P9221_NEG_POWER_10W) {
+ u16 mfg;
+
+ ret = charger->chip_get_tx_mfg_code(charger, &mfg);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "cannot read MFG_CODE (%d)\n", ret);
+ return -EIO;
+ }
+
+
+ /* EPP unless dealing with P9221_PTMC_EPP_TX_1912 */
+ charger->force_bpp = (mfg == P9221_PTMC_EPP_TX_1912);
+ dev_info(&charger->client->dev, "np=%x mfg=%x fb=%d\n",
+ np8, mfg, charger->force_bpp);
+ goto done;
+ }
+
+ ret = p9221_reg_read_16(charger, P9221_STATUS_REG, &status_reg);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "failed to read P9221_STATUS_REG reg: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* VOUT for standard BPP comes much earlier that VOUT for EPP */
+ if (!(status_reg & P9221_STAT_VOUT))
+ return 1;
+
+ /* normal BPP TX or EPP at less than 10W */
+ charger->force_bpp = true;
+ dev_info(&charger->client->dev,
+ "np=%x normal BPP or EPP less than 10W (%d)\n",
+ np8, ret);
+
+done:
+ if (charger->force_bpp) {
+ ret = p9221_set_bpp_vout(charger);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "cannot change VOUT (%d)\n", ret);
+ }
+
+ return 0;
+}
+
+/* 2 P9221_NOTIFIER_DELAY_MS from VRECTON */
+static void p9221_notifier_check_dc(struct p9221_charger_data *charger)
+{
+ int ret, dc_in;
+
+ charger->check_dc = false;
+
+ if ((charger->chip_id < P9382A_CHIP_ID) && charger->check_np) {
+
+ ret = p9221_notifier_check_neg_power(charger);
+ if (ret > 0) {
+ ret = schedule_delayed_work(&charger->notifier_work,
+ msecs_to_jiffies(P9221_CHECK_NP_DELAY_MS));
+ if (ret)
+ return;
+
+ dev_err(&charger->client->dev,
+ "cannot reschedule check_np (%d)\n", ret);
+ }
+
+ /* done */
+ charger->check_np = false;
+ }
+
+ dc_in = p9221_has_dc_in(charger);
+ if (dc_in < 0)
+ return;
+
+ dev_info(&charger->client->dev, "dc status is %d\n", dc_in);
+
+ /*
+ * We now have confirmation from DC_IN, kill the timer, charger->online
+ * will be set by this function.
+ */
+ cancel_delayed_work(&charger->dcin_work);
+ del_timer(&charger->vrect_timer);
+
+ if (charger->log) {
+ u32 vout_uv;
+
+ ret = p9221_reg_read_cooked(charger, P9221R5_VOUT_REG,
+ &vout_uv);
+ logbuffer_log(charger->log,
+ "check_dc: online=%d present=%d VOUT=%uuV (%d)",
+ charger->online, dc_in,
+ (ret == 0) ? vout_uv : 0, ret);
+ }
+
+ /*
+ * Always write FOD, check dc_icl, send CSP
+ */
+ if (dc_in) {
+ p9382_check_neg_power(charger);
+ p9221_set_dc_icl(charger);
+ p9221_write_fod(charger);
+ if (charger->last_capacity > 0)
+ p9221_send_csp(charger, charger->last_capacity);
+ if (!charger->dc_icl_bpp)
+ p9221_icl_ramp_start(charger);
+ }
+
+ /* We may have already gone online during check_det */
+ if (charger->online == dc_in)
+ goto out;
+
+ if (dc_in)
+ p9221_set_online(charger);
+ else
+ p9221_set_offline(charger);
+
+out:
+ dev_info(&charger->client->dev, "trigger wc changed on:%d in:%d\n",
+ charger->online, dc_in);
+ power_supply_changed(charger->wc_psy);
+}
+
+/* P9221_NOTIFIER_DELAY_MS from VRECTON */
+bool p9221_notifier_check_det(struct p9221_charger_data *charger)
+{
+ bool relax = true;
+
+ del_timer(&charger->vrect_timer);
+
+ if (charger->online && !charger->ben_state)
+ goto done;
+
+ dev_info(&charger->client->dev, "detected wlc, trigger wc changed\n");
+
+ /* b/130637382 workaround for 2622,2225,2574,1912 */
+ charger->check_np = true;
+ /* will send out a FOD but is_epp() is still invalid */
+ p9221_set_online(charger);
+ power_supply_changed(charger->wc_psy);
+
+ /* Check dc-in every seconds as long as we are in field. */
+ dev_info(&charger->client->dev, "start dc-in timer\n");
+ cancel_delayed_work_sync(&charger->dcin_work);
+ schedule_delayed_work(&charger->dcin_work,
+ msecs_to_jiffies(P9221_DCIN_TIMEOUT_MS));
+ relax = false;
+
+done:
+ charger->check_det = false;
+
+ return relax;
+}
+
+static void p9221_notifier_work(struct work_struct *work)
+{
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, notifier_work.work);
+ bool relax = true;
+ int ret;
+
+ dev_info(&charger->client->dev, "Notifier work: on:%d ben:%d dc:%d np:%d det:%d\n",
+ charger->online,
+ charger->ben_state,
+ charger->check_dc, charger->check_np,
+ charger->check_det);
+
+ if (charger->pdata->q_value != -1) {
+
+ ret = p9221_reg_write_8(charger,
+ P9221R5_EPP_Q_FACTOR_REG,
+ charger->pdata->q_value);
+ if (ret < 0)
+ dev_err(&charger->client->dev,
+ "cannot write Q=%d (%d)\n",
+ charger->pdata->q_value, ret);
+ }
+
+ if (charger->pdata->epp_rp_value != -1) {
+
+ ret = p9221_reg_write_8(charger,
+ P9221R5_EPP_REQ_NEGOTIATED_POWER_REG,
+ charger->pdata->epp_rp_value);
+ if (ret < 0)
+ dev_err(&charger->client->dev,
+ "cannot write to EPP_NEG_POWER=%d (%d)\n",
+ charger->pdata->epp_rp_value, ret);
+ }
+
+ if (charger->log) {
+ u32 vrect_uv;
+
+ ret = p9221_reg_read_cooked(charger, P9221R5_VRECT_REG,
+ &vrect_uv);
+ logbuffer_log(charger->log,
+ "notifier: on:%d ben:%d dc:%d det:%d VRECT=%uuV (%d)",
+ charger->online,
+ charger->ben_state,
+ charger->check_dc, charger->check_det,
+ (ret == 0) ? vrect_uv : 0, ret);
+ }
+
+ if (charger->check_det)
+ relax = p9221_notifier_check_det(charger);
+
+ if (charger->check_dc)
+ p9221_notifier_check_dc(charger);
+
+ if (relax)
+ pm_relax(charger->dev);
+}
+
+static ssize_t p9221_add_reg_buffer(struct p9221_charger_data *charger,
+ char *buf, size_t count, u16 reg, int width,
+ bool cooked, const char *name, char *fmt)
+{
+ u32 val;
+ int ret;
+ int added = 0;
+
+ if (cooked)
+ ret = p9221_reg_read_cooked(charger, reg, &val);
+ else if (width == 16) {
+ u16 val16 = 0;
+
+ ret = p9221_reg_read_16(charger, reg, &val16);
+ val = val16;
+ } else {
+ u8 val8 = 0;
+
+ ret = p9221_reg_read_8(charger, reg, &val8);
+ val = val8;
+ }
+
+ added += scnprintf(buf + count, PAGE_SIZE - count, "%s", name);
+ count += added;
+ if (ret)
+ added += scnprintf(buf + count, PAGE_SIZE - count,
+ "err %d\n", ret);
+ else
+ added += scnprintf(buf + count, PAGE_SIZE - count, fmt, val);
+
+ return added;
+}
+
+static ssize_t p9221_show_version(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int count = 0;
+ int i;
+ int ret;
+ u8 val8 = 0;
+
+ if (!p9221_is_online(charger))
+ return -ENODEV;
+
+ count += p9221_add_reg_buffer(charger, buf, count, P9221_CHIP_ID_REG,
+ 16, 0, "chip id : ", "%04x\n");
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_CHIP_REVISION_REG, 8, 0,
+ "chip rev : ", "%02x\n");
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_CUSTOMER_ID_REG, 8, 0,
+ "cust id : ", "%02x\n");
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_OTP_FW_MAJOR_REV_REG, 16, 0,
+ "otp fw maj : ", "%04x\n");
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_OTP_FW_MINOR_REV_REG, 16, 0,
+ "otp fw min : ", "%04x\n");
+
+ count += scnprintf(buf + count, PAGE_SIZE - count, "otp fw date: ");
+ for (i = 0; i < P9221_OTP_FW_DATE_SIZE; i++) {
+ ret = p9221_reg_read_8(charger,
+ P9221_OTP_FW_DATE_REG + i, &val8);
+ if (val8)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "%c", val8);
+ }
+
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\notp fw time: ");
+ for (i = 0; i < P9221_OTP_FW_TIME_SIZE; i++) {
+ ret = p9221_reg_read_8(charger,
+ P9221_OTP_FW_TIME_REG + i, &val8);
+ if (val8)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "%c", val8);
+ }
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_SRAM_FW_MAJOR_REV_REG, 16, 0,
+ "\nram fw maj : ", "%04x\n");
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_SRAM_FW_MINOR_REV_REG, 16, 0,
+ "ram fw min : ", "%04x\n");
+
+ count += scnprintf(buf + count, PAGE_SIZE - count, "ram fw date: ");
+ for (i = 0; i < P9221_SRAM_FW_DATE_SIZE; i++) {
+ ret = p9221_reg_read_8(charger,
+ P9221_SRAM_FW_DATE_REG + i, &val8);
+ if (val8)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "%c", val8);
+ }
+
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\nram fw time: ");
+ for (i = 0; i < P9221_SRAM_FW_TIME_SIZE; i++) {
+ ret = p9221_reg_read_8(charger,
+ P9221_SRAM_FW_TIME_REG + i, &val8);
+ if (val8)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "%c", val8);
+ }
+
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+ return count;
+}
+
+static DEVICE_ATTR(version, 0444, p9221_show_version, NULL);
+
+static ssize_t p9221_show_status(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int count = 0;
+ int ret;
+ u8 tmp[P9221R5_NUM_FOD];
+ uint32_t tx_id = 0;
+ u16 ilim_set_reg;
+
+ if (!p9221_is_online(charger))
+ return -ENODEV;
+
+ ilim_set_reg = (charger->ben_state == 1) ?
+ P9382A_ILIM_SET_REG : P9221R5_ILIM_SET_REG;
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_STATUS_REG, 16, 0,
+ "status : ", "%04x\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_INT_REG, 16, 0,
+ "int : ", "%04x\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221_INT_ENABLE_REG, 16, 0,
+ "int_enable : ", "%04x\n");
+
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_SYSTEM_MODE_REG, 8, 0,
+ "mode : ", "%02x\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_VOUT_REG, 16, 1,
+ "vout : ", "%d uV\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_VRECT_REG, 16, 1,
+ "vrect : ", "%d uV\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_IOUT_REG, 16, 1,
+ "iout : ", "%d uA\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ ilim_set_reg, 16, 1,
+ "ilim : ", "%d uA\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_OP_FREQ_REG, 16, 1,
+ "freq : ", "%d hz\n");
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "tx_busy : %d\n", charger->tx_busy);
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "tx_done : %d\n", charger->tx_done);
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "rx_done : %d\n", charger->rx_done);
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "tx_len : %d\n", charger->tx_len);
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "rx_len : %d\n", charger->rx_len);
+ charger->chip_get_tx_id(charger, &tx_id);
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "tx_id : %08x (%s)\n", tx_id,
+ p9221_get_tx_id_str(charger));
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_ALIGN_X_ADC_REG, 8, 1,
+ "align_x : ", "%d\n");
+
+ count += p9221_add_reg_buffer(charger, buf, count,
+ P9221R5_ALIGN_Y_ADC_REG, 8, 1,
+ "align_y : ", "%d\n");
+
+ /* FOD Register */
+ ret = p9221_reg_read_n(charger, P9221R5_FOD_REG, tmp, P9221R5_NUM_FOD);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "fod : ");
+ if (ret)
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "err %d\n", ret);
+ else {
+ count += p9221_hex_str(tmp, P9221R5_NUM_FOD, buf + count, count,
+ false);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+ }
+
+ /* Device tree FOD entries */
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "dt fod : (n=%d) ", charger->pdata->fod_num);
+ count += p9221_hex_str(charger->pdata->fod, charger->pdata->fod_num,
+ buf + count, PAGE_SIZE - count, false);
+
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "\ndt fod-epp : (n=%d) ",
+ charger->pdata->fod_epp_num);
+ count += p9221_hex_str(charger->pdata->fod_epp,
+ charger->pdata->fod_epp_num,
+ buf + count, PAGE_SIZE - count, false);
+
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "\npp buf : (v=%d) ", charger->pp_buf_valid);
+ count += p9221_hex_str(charger->pp_buf, sizeof(charger->pp_buf),
+ buf + count, PAGE_SIZE - count, false);
+
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+ return count;
+}
+
+static DEVICE_ATTR(status, 0444, p9221_show_status, NULL);
+
+static ssize_t p9221_show_count(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", charger->count);
+}
+
+static ssize_t p9221_store_count(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+ u8 cnt;
+
+ ret = kstrtou8(buf, 0, &cnt);
+ if (ret < 0)
+ return ret;
+ charger->count = cnt;
+ return count;
+}
+
+static DEVICE_ATTR(count, 0644, p9221_show_count, p9221_store_count);
+
+static ssize_t p9221_show_icl_ramp_delay_ms(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n",
+ charger->pdata->icl_ramp_delay_ms);
+}
+
+static ssize_t p9221_store_icl_ramp_delay_ms(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+ u32 ms;
+
+ ret = kstrtou32(buf, 10, &ms);
+ if (ret < 0)
+ return ret;
+ charger->pdata->icl_ramp_delay_ms = ms;
+ return count;
+}
+
+static DEVICE_ATTR(icl_ramp_delay_ms, 0644,
+ p9221_show_icl_ramp_delay_ms,
+ p9221_store_icl_ramp_delay_ms);
+
+static ssize_t p9221_show_icl_ramp_ua(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->icl_ramp_ua);
+}
+
+static ssize_t p9221_store_icl_ramp_ua(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+ u32 ua;
+
+ ret = kstrtou32(buf, 10, &ua);
+ if (ret < 0)
+ return ret;
+ charger->icl_ramp_ua = ua;
+ return count;
+}
+
+static DEVICE_ATTR(icl_ramp_ua, 0644,
+ p9221_show_icl_ramp_ua, p9221_store_icl_ramp_ua);
+
+static ssize_t p9221_show_addr(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%04x\n", charger->addr);
+}
+
+static ssize_t p9221_store_addr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+ u16 addr;
+
+ ret = kstrtou16(buf, 16, &addr);
+ if (ret < 0)
+ return ret;
+ charger->addr = addr;
+ return count;
+}
+
+static DEVICE_ATTR(addr, 0644, p9221_show_addr, p9221_store_addr);
+
+static ssize_t p9221_show_data(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ u8 reg[256];
+ int ret;
+ int i;
+ ssize_t len = 0;
+
+ if (!charger->count || (charger->addr > (0xFFFF - charger->count)))
+ return -EINVAL;
+
+ if (!p9221_is_online(charger))
+ return -ENODEV;
+
+ ret = p9221_reg_read_n(charger, charger->addr, reg, charger->count);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < charger->count; i++) {
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%02x: %02x\n",
+ charger->addr + i, reg[i]);
+ }
+ return len;
+}
+
+static ssize_t p9221_store_data(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ u8 reg[256];
+ int i = 0;
+ int ret = 0;
+ char *data;
+ char *tmp_buf;
+
+ if (!charger->count || (charger->addr > (0xFFFF - charger->count)))
+ return -EINVAL;
+
+ if (!p9221_is_online(charger))
+ return -ENODEV;
+
+ tmp_buf = kstrdup(buf, GFP_KERNEL);
+ data = tmp_buf;
+ if (!data)
+ return -ENOMEM;
+
+ while (data && i < charger->count) {
+ char *d = strsep(&data, " ");
+
+ if (*d) {
+ ret = kstrtou8(d, 16, ®[i]);
+ if (ret)
+ break;
+ i++;
+ }
+ }
+ if ((i != charger->count) || ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = p9221_reg_write_n(charger, charger->addr, reg, charger->count);
+ if (ret)
+ goto out;
+ ret = count;
+
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
+static DEVICE_ATTR(data, 0644, p9221_show_data, p9221_store_data);
+
+static ssize_t p9221_store_ccreset(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+
+ ret = p9221_send_ccreset(charger);
+ if (ret)
+ return ret;
+ return count;
+}
+
+static DEVICE_ATTR(ccreset, 0200, NULL, p9221_store_ccreset);
+
+static ssize_t p9221_show_rxdone(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ buf[0] = charger->rx_done ? '1' : '0';
+ buf[1] = 0;
+ return 1;
+}
+
+static DEVICE_ATTR(rxdone, 0444, p9221_show_rxdone, NULL);
+
+static ssize_t p9221_show_rxlen(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%hu\n", charger->rx_len);
+}
+
+static DEVICE_ATTR(rxlen, 0444, p9221_show_rxlen, NULL);
+
+static ssize_t p9221_show_txdone(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ buf[0] = charger->tx_done ? '1' : '0';
+ buf[1] = 0;
+ return 1;
+}
+
+static DEVICE_ATTR(txdone, 0444, p9221_show_txdone, NULL);
+
+static ssize_t p9221_show_txbusy(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ buf[0] = charger->tx_busy ? '1' : '0';
+ buf[1] = 0;
+ return 1;
+}
+
+static DEVICE_ATTR(txbusy, 0444, p9221_show_txbusy, NULL);
+
+static ssize_t p9221_store_txlen(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+ u16 len;
+
+ ret = kstrtou16(buf, 16, &len);
+ if (ret < 0)
+ return ret;
+ if (ret > P9221R5_COM_CHAN_SEND_SIZE_REG)
+ return -EINVAL;
+
+ cancel_delayed_work_sync(&charger->tx_work);
+
+ charger->tx_len = len;
+ charger->tx_done = false;
+ ret = p9221_send_data(charger);
+ if (ret) {
+ charger->tx_done = true;
+ return ret;
+ }
+
+ schedule_delayed_work(&charger->tx_work,
+ msecs_to_jiffies(P9221_TX_TIMEOUT_MS));
+
+ return count;
+}
+
+static DEVICE_ATTR(txlen, 0200, NULL, p9221_store_txlen);
+
+static ssize_t p9221_show_force_epp(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ buf[0] = charger->fake_force_epp ? '1' : '0';
+ buf[1] = 0;
+ return 1;
+}
+
+static ssize_t p9221_force_epp(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+ u16 val;
+
+ ret = kstrtou16(buf, 16, &val);
+ if (ret < 0)
+ return ret;
+
+ charger->fake_force_epp = (val != 0);
+
+ if (charger->pdata->slct_gpio >= 0)
+ gpio_set_value(charger->pdata->slct_gpio,
+ charger->fake_force_epp ? 1 : 0);
+ return count;
+}
+
+static DEVICE_ATTR(force_epp, 0600, p9221_show_force_epp, p9221_force_epp);
+
+static ssize_t dc_icl_epp_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->dc_icl_epp);
+}
+
+static ssize_t dc_icl_epp_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret = 0;
+ u32 ua;
+
+ ret = kstrtou32(buf, 10, &ua);
+ if (ret < 0)
+ return ret;
+
+ charger->dc_icl_epp = ua;
+
+ if (charger->dc_icl_votable && p9221_is_epp(charger)) {
+ vote(charger->dc_icl_votable,
+ P9221_WLC_VOTER, true, charger->dc_icl_epp);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(dc_icl_epp);
+
+static ssize_t p9221_show_dc_icl_bpp(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->dc_icl_bpp);
+}
+
+static ssize_t p9221_set_dc_icl_bpp(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret = 0;
+ u32 ua;
+
+ ret = kstrtou32(buf, 10, &ua);
+ if (ret < 0)
+ return ret;
+
+ charger->dc_icl_bpp = ua;
+
+ if (charger->dc_icl_votable && !p9221_is_epp(charger))
+ vote(charger->dc_icl_votable,
+ P9221_WLC_VOTER, true, charger->dc_icl_bpp);
+
+ return count;
+}
+
+static DEVICE_ATTR(dc_icl_bpp, 0644,
+ p9221_show_dc_icl_bpp, p9221_set_dc_icl_bpp);
+
+static ssize_t p9221_show_alignment(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ if (charger->alignment == -1)
+ p9221_init_align(charger);
+
+ if ((charger->align != POWER_SUPPLY_ALIGN_CENTERED) ||
+ (charger->alignment == -1))
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ align_status_str[charger->align]);
+ else
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->alignment);
+}
+
+static DEVICE_ATTR(alignment, 0444, p9221_show_alignment, NULL);
+
+static ssize_t aicl_delay_ms_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->aicl_delay_ms);
+}
+
+static ssize_t aicl_delay_ms_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret = 0;
+ u32 t;
+
+ ret = kstrtou32(buf, 10, &t);
+ if (ret < 0)
+ return ret;
+
+ charger->aicl_delay_ms = t;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(aicl_delay_ms);
+
+static ssize_t aicl_icl_ua_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->aicl_icl_ua);
+}
+
+static ssize_t aicl_icl_ua_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret = 0;
+ u32 ua;
+
+ ret = kstrtou32(buf, 10, &ua);
+ if (ret < 0)
+ return ret;
+
+ charger->aicl_icl_ua = ua;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(aicl_icl_ua);
+
+/* ------------------------------------------------------------------------ */
+static ssize_t rx_lvl_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ if (charger->pdata->switch_gpio < 0)
+ return -ENODEV;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->rtx_csp);
+}
+
+static DEVICE_ATTR_RO(rx_lvl);
+
+static ssize_t rtx_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ static const char * const rtx_state_text[] = {
+ "not support", "available", "active", "disabled" };
+
+ u8 reg;
+ int ret;
+
+ if (charger->pdata->switch_gpio < 0)
+ charger->rtx_state = RTX_NOTSUPPORTED;
+
+ ret = p9221_reg_read_8(charger, P9221R5_SYSTEM_MODE_REG, ®);
+ if (ret == 0) {
+ if (reg & P9382A_MODE_TXMODE)
+ charger->rtx_state = RTX_ACTIVE;
+ else
+ charger->rtx_state = RTX_DISABLED;
+ } else {
+ /* FIXME: b/147213330
+ * if otg enabled, rtx disabled.
+ * if otg disabled, rtx available.
+ */
+ charger->rtx_state = RTX_AVAILABLE;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ rtx_state_text[charger->rtx_state]);
+}
+
+static DEVICE_ATTR_RO(rtx_status);
+
+static ssize_t is_rtx_connected_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ u16 status_reg = 0;
+ bool attached = 0;
+
+ if (charger->pdata->switch_gpio < 0)
+ return -ENODEV;
+
+ if (charger->ben_state)
+ p9221_reg_read_16(charger, P9221_STATUS_REG, &status_reg);
+
+ attached = status_reg & P9382_STAT_RXCONNECTED;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ attached ? "connected" : "disconnect");
+}
+
+static DEVICE_ATTR_RO(is_rtx_connected);
+
+static ssize_t rtx_err_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->rtx_err);
+}
+
+static DEVICE_ATTR_RO(rtx_err);
+
+static ssize_t rtx_sw_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int value;
+
+ if (charger->pdata->switch_gpio < 0)
+ return -ENODEV;
+
+ value = gpio_get_value(charger->pdata->switch_gpio);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", value != 0);
+}
+
+static ssize_t rtx_sw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ if (charger->pdata->switch_gpio < 0)
+ return -ENODEV;
+
+ /* TODO: better test on rX mode */
+ if (charger->online) {
+ dev_err(&charger->client->dev, "invalid rX state");
+ return -EINVAL;
+ }
+
+ gpio_set_value(charger->pdata->switch_gpio, buf[0] != '0');
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(rtx_sw);
+
+
+static ssize_t p9382_show_rtx_boost(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->ben_state);
+}
+
+/* assume that we have 2 GPIO to turn on the boost */
+static int p9382_rtx_enable(struct p9221_charger_data *charger, bool enable)
+{
+ if (charger->pdata->ben_gpio >= 0)
+ gpio_set_value(charger->pdata->ben_gpio, enable);
+ if (charger->pdata->switch_gpio >= 0)
+ gpio_set_value(charger->pdata->switch_gpio, enable);
+ /* some systems provide additional boost_gpio for charging level */
+ if (charger->pdata->boost_gpio >= 0)
+ gpio_set_value(charger->pdata->boost_gpio, enable);
+
+ return (charger->pdata->ben_gpio < 0 &&
+ charger->pdata->switch_gpio < 0) ? -ENODEV : 0;
+}
+
+static int p9382_ben_cfg(struct p9221_charger_data *charger, int cfg)
+{
+ const int ben_gpio = charger->pdata->ben_gpio;
+ const int switch_gpio = charger->pdata->switch_gpio;
+
+ dev_info(&charger->client->dev, "ben_cfg: %d->%d (ben=%d, switch=%d)",
+ charger->ben_state, cfg, ben_gpio, switch_gpio);
+
+ switch (cfg) {
+ case RTX_BEN_DISABLED:
+ if (charger->ben_state == RTX_BEN_ON)
+ p9382_rtx_enable(charger, false);
+ else if (ben_gpio == RTX_BEN_ENABLED)
+ gpio_set_value(ben_gpio, 0);
+ charger->ben_state = cfg;
+ break;
+ case RTX_BEN_ENABLED:
+ charger->ben_state = cfg;
+ if (ben_gpio >= 0)
+ gpio_set_value(ben_gpio, 1);
+ break;
+ case RTX_BEN_ON:
+ charger->ben_state = cfg;
+ p9382_rtx_enable(charger, true);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t p9382_set_rtx_boost(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ const int state = buf[0] - '0';
+ int ret;
+
+ /* always ok to disable */
+ if (state && charger->online && !charger->ben_state) {
+ dev_err(&charger->client->dev, "invalid rX state");
+ return -ENODEV;
+ }
+
+ /* 0 -> BEN_DISABLED, 1 -> BEN_ON */
+ ret = p9382_ben_cfg(charger, state);
+ if (ret < 0)
+ count = ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(rtx_boost, 0644, p9382_show_rtx_boost, p9382_set_rtx_boost);
+
+static int p9382_wait_for_mode(struct p9221_charger_data *charger, int mode)
+{
+ int loops, ret;
+ uint8_t sys_mode;
+
+ /* 30 * 100 = 3 sec */
+ for (loops = 30 ; loops ; loops--) {
+ ret = p9221_reg_read_8(charger, P9221R5_SYSTEM_MODE_REG,
+ &sys_mode);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "cannot read system_mode (%d)", ret);
+ return -EIO;
+ }
+
+ if (sys_mode == mode)
+ return 0;
+
+ msleep(100);
+ }
+
+ return -ETIMEDOUT;
+}
+
+/* chip only steps needed to enable/disable tx mode */
+static int p9221_chip_tx_mode(struct p9221_charger_data *charger, bool enable)
+{
+ logbuffer_log(charger->rtx_log, "%s(%d)", __func__, enable);
+ return -ENOTSUPP;
+}
+
+static int p9382_chip_tx_mode(struct p9221_charger_data *charger, bool enable)
+{
+ int ret;
+ u16 rev;
+
+ logbuffer_log(charger->rtx_log, "%s(%d)", __func__, enable);
+ if (enable) {
+ /* check FW revision */
+ ret = p9221_reg_read_16(charger,
+ P9221_OTP_FW_MINOR_REV_REG, &rev);
+ if (ret)
+ return ret;
+
+ if (rev >= P9382A_FW_REV_25) {
+ /* write 0x0003 to 0x69 after rev 25 */
+ ret = p9221_reg_write_16(charger,
+ P9382A_TRX_ENABLE_REG,
+ P9382A_TX_INHIBIT);
+ } else {
+ /* write 0x0000 to 0x34 */
+ ret = p9221_reg_write_16(charger,
+ P9382A_STATUS_REG, 0);
+ }
+
+ /* check 0x4C reads back as 0x04 */
+ ret = p9382_wait_for_mode(charger, P9382A_MODE_TXMODE);
+ } else {
+ /* Write 0x80 to 0x4E, check 0x4C reads back as 0x0000 */
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_RENEGOTIATE);
+ if (ret == 0) {
+ ret = p9382_wait_for_mode(charger, 0);
+ if (ret < 0)
+ pr_err("cannot exit rTX mode (%d)\n", ret);
+ }
+ }
+
+ return ret;
+}
+
+static int p9412_chip_tx_mode(struct p9221_charger_data *charger, bool enable)
+{
+ int ret;
+
+ logbuffer_log(charger->rtx_log, "%s(%d)", __func__, enable);
+ if (enable) {
+ ret = p9221_reg_write_8(charger, P9412_TX_CMD_REG,
+ P9412_TX_CMD_TX_MODE_EN);
+ if (ret) {
+ logbuffer_log(charger->rtx_log,
+ "tx_cmd_reg write failed (%d)\n", ret);
+ return ret;
+ }
+
+ ret = p9382_wait_for_mode(charger, P9412_SYS_OP_MODE_TX_MODE);
+ if (ret)
+ logbuffer_log(charger->rtx_log,
+ "error waiting for tx_mode (%d)", ret);
+ } else {
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_RENEGOTIATE);
+ if (ret == 0) {
+ ret = p9382_wait_for_mode(charger, 0);
+ if (ret < 0)
+ pr_err("cannot exit rTX mode (%d)\n", ret);
+ }
+ }
+ return ret;
+}
+
+static int p9382_set_rtx(struct p9221_charger_data *charger, bool enable)
+{
+ int ret, tx_icl = -1;
+
+ if (enable == 0) {
+ logbuffer_log(charger->rtx_log, "disable rtx");
+
+ ret = charger->chip_tx_mode_en(charger, false);
+ ret = p9382_ben_cfg(charger, RTX_BEN_DISABLED);
+ if (ret < 0)
+ goto exit;
+
+ ret = vote(charger->disable_dcin_en_votable,
+ P9221_WLC_VOTER, false, 0);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "fail to enable dcin, ret=%d\n", ret);
+ } else {
+ logbuffer_log(charger->rtx_log, "enable rtx");
+ /* Check if there is any one vote disabled */
+ if (charger->tx_icl_votable)
+ tx_icl = get_effective_result(charger->tx_icl_votable);
+ if (tx_icl == 0) {
+ dev_err(&charger->client->dev, "rtx be disabled\n");
+ logbuffer_log(charger->rtx_log, "rtx be disabled\n");
+ goto exit;
+ }
+ /* Check if WLC online */
+ if (charger->online) {
+ dev_err(&charger->client->dev,
+ "rTX is not allowed during WLC\n");
+ logbuffer_log(charger->rtx_log,
+ "rTX is not allowed during WLC\n");
+ goto exit;
+ }
+
+ /*
+ * DCIN_EN votable will not be available on all systems.
+ * if it is there, it is needed.
+ */
+ if (!charger->disable_dcin_en_votable) {
+ charger->disable_dcin_en_votable =
+ find_votable("DISABLE_DCIN_EN");
+ }
+
+ if (charger->disable_dcin_en_votable) {
+ ret = vote(charger->disable_dcin_en_votable,
+ P9221_WLC_VOTER, true, 0);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Could not vote DISABLE_DCIN_EN, skip enable rTX mode %d\n",
+ ret);
+ goto exit;
+ }
+ }
+
+ charger->rtx_csp = 0;
+ charger->rtx_err = RTX_NO_ERROR;
+
+ ret = p9382_ben_cfg(charger, RTX_BEN_ON);
+ if (ret < 0)
+ goto exit;
+
+ msleep(10);
+
+ ret = charger->chip_tx_mode_en(charger, true);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "cannot enter rTX mode (%d)\n", ret);
+ logbuffer_log(charger->rtx_log,
+ "cannot enter rTX mode (%d)\n", ret);
+ p9382_ben_cfg(charger, RTX_BEN_DISABLED);
+ goto exit;
+ }
+
+ ret = p9221_enable_interrupts(charger);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Could not enable interrupts: %d\n", ret);
+
+ /* configure TX_ICL */
+ if (charger->tx_icl_votable)
+ tx_icl = get_effective_result(charger->tx_icl_votable);
+ if ((tx_icl > 0) && (tx_icl != P9382A_RTX_ICL_MAX_MA)) {
+ ret = p9221_reg_write_cooked(charger,
+ P9382A_ILIM_SET_REG,
+ tx_icl);
+ if (ret == 0)
+ logbuffer_log(charger->rtx_log,
+ "set Tx current limit: %dmA",
+ tx_icl);
+ else
+ dev_err(&charger->client->dev,
+ "Could not set Tx current limit: %d\n",
+ ret);
+ }
+ }
+exit:
+ schedule_work(&charger->uevent_work);
+ return ret;
+}
+
+static ssize_t rtx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", charger->ben_state);
+}
+
+/* write 1 to enable boost & switch, write 0 to 0x34, wait for 0x4c==0x4
+ * write 0 to write 0x80 to 0x4E, wait for 0x4c==0, disable boost & switch
+ */
+static ssize_t rtx_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+ int ret;
+
+ if (buf[0] == '0')
+ ret = p9382_set_rtx(charger, false);
+ else if (buf[0] == '1')
+ ret = p9382_set_rtx(charger, true);
+ else
+ return -EINVAL;
+
+ if (ret == 0)
+ return count;
+ else
+ return ret;
+}
+
+static DEVICE_ATTR_RW(rtx);
+
+static struct attribute *rtx_attributes[] = {
+ &dev_attr_rtx_sw.attr,
+ &dev_attr_rtx_boost.attr,
+ &dev_attr_rtx.attr,
+ &dev_attr_rtx_status.attr,
+ &dev_attr_is_rtx_connected.attr,
+ &dev_attr_rx_lvl.attr,
+ &dev_attr_rtx_err.attr,
+ NULL
+};
+
+static const struct attribute_group rtx_attr_group = {
+ .attrs = rtx_attributes,
+};
+
+static struct attribute *p9221_attributes[] = {
+ &dev_attr_version.attr,
+ &dev_attr_status.attr,
+ &dev_attr_addr.attr,
+ &dev_attr_count.attr,
+ &dev_attr_data.attr,
+ &dev_attr_ccreset.attr,
+ &dev_attr_txbusy.attr,
+ &dev_attr_txdone.attr,
+ &dev_attr_txlen.attr,
+ &dev_attr_rxlen.attr,
+ &dev_attr_rxdone.attr,
+ &dev_attr_icl_ramp_ua.attr,
+ &dev_attr_icl_ramp_delay_ms.attr,
+ &dev_attr_force_epp.attr,
+ &dev_attr_dc_icl_bpp.attr,
+ &dev_attr_dc_icl_epp.attr,
+ &dev_attr_alignment.attr,
+ &dev_attr_aicl_delay_ms.attr,
+ &dev_attr_aicl_icl_ua.attr,
+ NULL
+};
+
+static ssize_t p9221_rxdata_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t pos, size_t size)
+{
+ struct p9221_charger_data *charger;
+ charger = dev_get_drvdata(container_of(kobj, struct device, kobj));
+
+ memcpy(buf, &charger->rx_buf[pos], size);
+ charger->rx_done = false;
+ return size;
+}
+
+static struct bin_attribute bin_attr_rxdata = {
+ .attr = {
+ .name = "rxdata",
+ .mode = 0400,
+ },
+ .read = p9221_rxdata_read,
+ .size = P9221R5_DATA_RECV_BUF_SIZE,
+};
+
+static ssize_t p9221_txdata_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t pos, size_t size)
+{
+ struct p9221_charger_data *charger;
+ charger = dev_get_drvdata(container_of(kobj, struct device, kobj));
+
+ memcpy(buf, &charger->tx_buf[pos], size);
+ return size;
+}
+
+static ssize_t p9221_txdata_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t pos, size_t size)
+{
+ struct p9221_charger_data *charger;
+ charger = dev_get_drvdata(container_of(kobj, struct device, kobj));
+
+ memcpy(&charger->tx_buf[pos], buf, size);
+ return size;
+}
+
+static struct bin_attribute bin_attr_txdata = {
+ .attr = {
+ .name = "txdata",
+ .mode = 0600,
+ },
+ .read = p9221_txdata_read,
+ .write = p9221_txdata_write,
+ .size = P9221R5_DATA_SEND_BUF_SIZE,
+};
+
+static struct bin_attribute *p9221_bin_attributes[] = {
+ &bin_attr_txdata,
+ &bin_attr_rxdata,
+ NULL,
+};
+
+static const struct attribute_group p9221_attr_group = {
+ .attrs = p9221_attributes,
+ .bin_attrs = p9221_bin_attributes,
+};
+
+static void print_current_samples(struct p9221_charger_data *charger,
+ u32 *iout_val, int count)
+{
+ int i;
+ char temp[P9221R5_OVER_CHECK_NUM * 9 + 1] = { 0 };
+
+ for (i = 0; i < count ; i++)
+ scnprintf(temp + i * 9, sizeof(temp) - i * 9,
+ "%08x ", iout_val[i]);
+
+ dev_info(&charger->client->dev, "OVER IOUT_SAMPLES: %s\n", temp);
+}
+
+/*
+ * Number of times to poll the status to see if the current limit condition
+ * was transient or not.
+ */
+static void p9221_over_handle(struct p9221_charger_data *charger,
+ u16 irq_src)
+{
+ u8 reason = 0;
+ int i;
+ int ret;
+ int ovc_count = 0;
+ u32 iout_val[P9221R5_OVER_CHECK_NUM] = { 0 };
+
+ dev_err(&charger->client->dev, "Received OVER INT: %02x\n", irq_src);
+
+ if (irq_src & P9221R5_STAT_OVV) {
+ reason = P9221_EOP_OVER_VOLT;
+ goto send_eop;
+ }
+
+ if (irq_src & P9221R5_STAT_OVT) {
+ reason = P9221_EOP_OVER_TEMP;
+ goto send_eop;
+ }
+
+ if ((irq_src & P9221R5_STAT_UV) && !(irq_src & P9221R5_STAT_OVC))
+ return;
+
+ /* Overcurrent, reduce ICL and poll to absorb any transients */
+
+ if (charger->dc_icl_votable) {
+ int icl;
+
+ icl = get_effective_result_locked(charger->dc_icl_votable);
+ if (icl < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to read ICL (%d)\n", icl);
+ } else if (icl > OVC_BACKOFF_LIMIT) {
+ icl -= OVC_BACKOFF_AMOUNT;
+
+ ret = vote(charger->dc_icl_votable,
+ P9221_OCP_VOTER, true,
+ icl);
+ dev_err(&charger->client->dev,
+ "Reduced ICL to %d (%d)\n", icl, ret);
+ }
+ }
+
+ reason = P9221_EOP_OVER_CURRENT;
+ for (i = 0; i < P9221R5_OVER_CHECK_NUM; i++) {
+ ret = p9221_clear_interrupts(charger,
+ irq_src & P9221R5_STAT_LIMIT_MASK);
+ msleep(50);
+ if (ret)
+ continue;
+
+ ret = p9221_reg_read_cooked(charger, P9221R5_IOUT_REG,
+ &iout_val[i]);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to read IOUT[%d]: %d\n", i, ret);
+ continue;
+ } else if (iout_val[i] > OVC_THRESHOLD) {
+ ovc_count++;
+ }
+
+ ret = p9221_reg_read_16(charger, P9221_STATUS_REG, &irq_src);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to read status: %d\n", ret);
+ continue;
+ }
+
+ if ((irq_src & P9221R5_STAT_OVC) == 0) {
+ print_current_samples(charger, iout_val, i + 1);
+ dev_info(&charger->client->dev,
+ "OVER condition %04x cleared after %d tries\n",
+ irq_src, i);
+ return;
+ }
+
+ dev_err(&charger->client->dev,
+ "OVER status is still %04x, retry\n", irq_src);
+ }
+
+ if (ovc_count < OVC_LIMIT) {
+ print_current_samples(charger, iout_val,
+ P9221R5_OVER_CHECK_NUM);
+ dev_info(&charger->client->dev,
+ "ovc_threshold=%d, ovc_count=%d, ovc_limit=%d\n",
+ OVC_THRESHOLD, ovc_count, OVC_LIMIT);
+ return;
+ }
+
+send_eop:
+ dev_err(&charger->client->dev,
+ "OVER is %04x, sending EOP %d\n", irq_src, reason);
+
+ ret = p9221_send_eop(charger, reason);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "Failed to send EOP %d: %d\n", reason, ret);
+}
+
+static bool p9221_dc_reset_needed(struct p9221_charger_data *charger,
+ u16 irq_src)
+{
+
+ /*
+ * It is suspected that p9221 misses to set the interrupt status
+ * register occasionally. Evaluate spurious interrupt case for
+ * dc reset as well.
+ */
+ if (charger->pdata->needs_dcin_reset == P9221_WC_DC_RESET_MODECHANGED &&
+ (irq_src & P9221R5_STAT_MODECHANGED || !irq_src)) {
+ u8 mode_reg = 0;
+ int res;
+
+ res = p9221_reg_read_8(charger, P9221R5_SYSTEM_MODE_REG,
+ &mode_reg);
+ if (res < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to read P9221_SYSTEM_MODE_REG: %d\n",
+ res);
+ /*
+ * p9221_reg_read_n returns ENODEV for ENOTCONN as well.
+ * Signal dc_reset when register read fails with the
+ * above reasons.
+ */
+ return res == -ENODEV;
+ }
+
+ dev_info(&charger->client->dev,
+ "P9221_SYSTEM_MODE_REG reg: %02x\n", mode_reg);
+ return !(mode_reg & (P9221R5_MODE_EXTENDED |
+ P9221R5_MODE_WPCMODE));
+ }
+
+ if (charger->pdata->needs_dcin_reset == P9221_WC_DC_RESET_VOUTCHANGED &&
+ irq_src & P9221R5_STAT_VOUTCHANGED) {
+ u16 status_reg = 0;
+ int res;
+
+ res = p9221_reg_read_16(charger, P9221_STATUS_REG, &status_reg);
+ if (res < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to read P9221_STATUS_REG: %d\n", res);
+ return res == -ENODEV ? true : false;
+ }
+
+ dev_info(&charger->client->dev,
+ "P9221_STATUS_REG reg: %04x\n", status_reg);
+ return !(status_reg & P9221_STAT_VOUT);
+ }
+
+ return false;
+}
+
+static void p9382_txid_work(struct work_struct *work)
+{
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, txid_work.work);
+ int ret;
+ char s[FAST_SERIAL_ID_SIZE * 3 + 1];
+
+ mutex_lock(&charger->cmd_lock);
+
+ // write packet type to 0x100
+ ret = p9221_reg_write_8(charger,
+ PROPRIETARY_PACKET_TYPE_ADDR,
+ PROPRIETARY_PACKET_TYPE);
+
+ memset(charger->tx_buf, 0, P9221R5_DATA_SEND_BUF_SIZE);
+
+ // write 0x4F as header to 0x104
+ charger->tx_buf[0] = FAST_SERIAL_ID_HEADER;
+ charger->tx_len = FAST_SERIAL_ID_SIZE;
+
+ // TODO: write txid to bit(23, 0)
+ memset(&charger->tx_buf[1], 0x12, FAST_SERIAL_ID_SIZE - 1);
+
+ // write accessory type to bit(31, 24)
+ charger->tx_buf[4] = TX_ACCESSORY_TYPE;
+
+ ret |= p9221_reg_write_n(charger, charger->addr_data_send_buf_start,
+ charger->tx_buf, charger->tx_len + 1);
+ if (ret) {
+ dev_err(&charger->client->dev, "Failed to load tx %d\n", ret);
+ goto error;
+ }
+
+ // send packet
+ ret = p9221_set_cmd_reg(charger, P9221R5_COM_CCACTIVATE);
+ if (ret) {
+ dev_err(&charger->client->dev, "Failed to send txid %d\n", ret);
+ goto error;
+ }
+
+ p9221_hex_str(&charger->tx_buf[1], FAST_SERIAL_ID_SIZE,
+ s, FAST_SERIAL_ID_SIZE * 3 + 1, false);
+ dev_info(&charger->client->dev, "Fast serial ID send(%s)\n", s);
+
+error:
+ mutex_unlock(&charger->cmd_lock);
+}
+
+/* Handler for rtx mode */
+static void rtx_irq_handler(struct p9221_charger_data *charger, u16 irq_src)
+{
+ int ret;
+ u8 mode_reg, csp_reg;
+ u16 status_reg;
+ bool attached = 0;
+
+ if (irq_src & P9221R5_STAT_MODECHANGED) {
+ ret = p9221_reg_read_8(charger, P9221R5_SYSTEM_MODE_REG,
+ &mode_reg);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to read P9221_SYSTEM_MODE_REG: %d\n",
+ ret);
+ return;
+ }
+ dev_info(&charger->client->dev,
+ "P9221_SYSTEM_MODE_REG reg: %02x\n",
+ mode_reg);
+ logbuffer_log(charger->rtx_log,
+ "SYSTEM_MODE_REG=%02x", mode_reg);
+ }
+
+ ret = p9221_reg_read_16(charger, P9221_STATUS_REG, &status_reg);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "failed to read P9221_STATUS_REG reg: %d\n",
+ ret);
+ return;
+ }
+
+ if (irq_src & P9382_STAT_TXCONFLICT) {
+ charger->rtx_err = RTX_TX_CONFLICT;
+ dev_info(&charger->client->dev,
+ "TX conflict, disable RTx. STATUS_REG=%04x",
+ status_reg);
+ logbuffer_log(charger->rtx_log,
+ "TX conflict, disable RTx. STATUS_REG=%04x",
+ status_reg);
+ p9382_set_rtx(charger, false);
+ }
+
+ if (irq_src & P9382_STAT_RXCONNECTED) {
+ attached = status_reg & P9382_STAT_RXCONNECTED;
+ logbuffer_log(charger->rtx_log,
+ "Rx is %s. STATUS_REG=%04x",
+ attached ? "connected" : "disconnect",
+ status_reg);
+ schedule_work(&charger->uevent_work);
+ if (attached)
+ schedule_delayed_work(&charger->txid_work,
+ msecs_to_jiffies(TXID_SEND_DELAY_MS));
+ else
+ charger->rtx_csp = 0;
+ }
+
+ if (irq_src & P9382_STAT_CSP) {
+ if (status_reg & P9382_STAT_CSP) {
+ ret = p9221_reg_read_8(charger, P9382A_CHARGE_STAT_REG,
+ &csp_reg);
+ if (ret) {
+ logbuffer_log(charger->rtx_log,
+ "failed to read CSP_REG reg: %d",
+ ret);
+ } else {
+ charger->rtx_csp = csp_reg;
+ schedule_work(&charger->uevent_work);
+ }
+ }
+ }
+}
+
+/* Handler for R5 and R7 chips */
+static void p9221_irq_handler(struct p9221_charger_data *charger, u16 irq_src)
+{
+ int res;
+
+ if (p9221_dc_reset_needed(charger, irq_src)) {
+ union power_supply_propval val = {.intval = 1};
+
+ if (!charger->dc_psy)
+ charger->dc_psy = power_supply_get_by_name("dc");
+ if (charger->dc_psy) {
+ /* Signal DC_RESET when wireless removal is sensed. */
+ res = power_supply_set_property(charger->dc_psy,
+ POWER_SUPPLY_PROP_DC_RESET,
+ &val);
+ } else {
+ res = -ENODEV;
+ }
+
+ if (res < 0)
+ dev_err(&charger->client->dev,
+ "unable to set DC_RESET, ret=%d",
+ res);
+ }
+
+ if (irq_src & P9221R5_STAT_LIMIT_MASK)
+ p9221_over_handle(charger, irq_src);
+
+ /* Receive complete */
+ if (irq_src & P9221R5_STAT_CCDATARCVD) {
+ uint8_t rxlen = 0;
+
+ res = p9221_reg_read_8(charger, P9221R5_COM_CHAN_RECV_SIZE_REG,
+ &rxlen);
+ if (res) {
+ dev_err(&charger->client->dev,
+ "Failed to read len: %d\n", res);
+ rxlen = 0;
+ }
+ if (rxlen) {
+ res = p9221_reg_read_n(charger,
+ charger->addr_data_recv_buf_start,
+ charger->rx_buf, rxlen);
+ if (res)
+ dev_err(&charger->client->dev,
+ "Failed to read len: %d\n", res);
+
+ charger->rx_len = rxlen;
+ charger->rx_done = true;
+ sysfs_notify(&charger->dev->kobj, NULL, "rxdone");
+ }
+ }
+
+ /* Send complete */
+ if (irq_src & P9221R5_STAT_CCSENDBUSY) {
+ charger->tx_busy = false;
+ charger->tx_done = true;
+ cancel_delayed_work(&charger->tx_work);
+ sysfs_notify(&charger->dev->kobj, NULL, "txbusy");
+ sysfs_notify(&charger->dev->kobj, NULL, "txdone");
+ }
+
+ /* Proprietary packet */
+ if (irq_src & P9221R5_STAT_PPRCVD) {
+ const size_t maxsz = sizeof(charger->pp_buf) * 3 + 1;
+ char s[maxsz];
+
+ res = p9221_reg_read_n(charger,
+ charger->addr_data_recv_buf_start,
+ charger->pp_buf,
+ sizeof(charger->pp_buf));
+ if (res)
+ dev_err(&charger->client->dev,
+ "Failed to read PP len: %d\n", res);
+
+ /* We only care about PP which come with 0x4F header */
+ charger->pp_buf_valid = (charger->pp_buf[0] == 0x4F);
+
+ p9221_hex_str(charger->pp_buf, sizeof(charger->pp_buf),
+ s, maxsz, false);
+ dev_info(&charger->client->dev, "Received PP: %s\n", s);
+ }
+
+ /* CC Reset complete */
+ if (irq_src & P9221R5_STAT_CCRESET)
+ p9221_abort_transfers(charger);
+}
+
+static irqreturn_t p9221_irq_thread(int irq, void *irq_data)
+{
+ struct p9221_charger_data *charger = irq_data;
+ int ret;
+ u16 irq_src = 0;
+
+ pm_runtime_get_sync(charger->dev);
+ if (!charger->resume_complete) {
+ pm_runtime_put_sync(charger->dev);
+ return -EAGAIN;
+ }
+ pm_runtime_put_sync(charger->dev);
+
+ ret = p9221_reg_read_16(charger, P9221_INT_REG, &irq_src);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to read INT reg: %d\n", ret);
+ goto out;
+ }
+
+ /* TODO: interrupt storm with irq_src = when in rTX mode */
+ if (!charger->ben_state) {
+ dev_info(&charger->client->dev, "INT: %04x\n", irq_src);
+ logbuffer_log(charger->log, "INT=%04x on:%d",
+ irq_src, charger->online);
+ }
+
+ if (!irq_src)
+ goto out;
+
+ ret = p9221_clear_interrupts(charger, irq_src);
+ if (ret) {
+ dev_err(&charger->client->dev,
+ "Failed to clear INT reg: %d\n", ret);
+ goto out;
+ }
+
+ /* todo interrupt handling for rx */
+ if (charger->ben_state) {
+ logbuffer_log(charger->rtx_log, "INT=%04x", irq_src);
+ rtx_irq_handler(charger, irq_src);
+ goto out;
+ }
+
+ if (irq_src & P9221_STAT_VRECT) {
+ dev_info(&charger->client->dev,
+ "Received VRECTON, online=%d\n", charger->online);
+ if (!charger->online) {
+ charger->check_det = true;
+ pm_stay_awake(charger->dev);
+
+ if (!schedule_delayed_work(&charger->notifier_work,
+ msecs_to_jiffies(P9221_NOTIFIER_DELAY_MS))) {
+ pm_relax(charger->dev);
+ }
+ }
+ }
+
+ p9221_irq_handler(charger, irq_src);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t p9221_irq_det_thread(int irq, void *irq_data)
+{
+ struct p9221_charger_data *charger = irq_data;
+
+ logbuffer_log(charger->log, "irq_det: online=%d ben=%d",
+ charger->online, charger->ben_state);
+
+ /* If we are already online, just ignore the interrupt. */
+ if (p9221_is_online(charger))
+ return IRQ_HANDLED;
+
+ if (charger->align != POWER_SUPPLY_ALIGN_MOVE) {
+ if (charger->align != POWER_SUPPLY_ALIGN_CHECKING)
+ schedule_work(&charger->uevent_work);
+ charger->align = POWER_SUPPLY_ALIGN_CHECKING;
+ charger->align_count++;
+
+ if (charger->align_count > WLC_ALIGN_IRQ_THRESHOLD) {
+ schedule_work(&charger->uevent_work);
+ charger->align = POWER_SUPPLY_ALIGN_MOVE;
+ }
+ logbuffer_log(charger->log, "align: state: %s",
+ align_status_str[charger->align]);
+ }
+
+ del_timer(&charger->align_timer);
+
+ /*
+ * This interrupt will wake the device if it's suspended,
+ * but it is not reliable enough to trigger the charging indicator.
+ * Give ourselves 2 seconds for the VRECTON interrupt to appear
+ * before we put up the charging indicator.
+ */
+ mod_timer(&charger->vrect_timer,
+ jiffies + msecs_to_jiffies(P9221_VRECT_TIMEOUT_MS));
+ pm_stay_awake(charger->dev);
+
+ return IRQ_HANDLED;
+}
+
+static void p9382_rtx_disable_work(struct work_struct *work)
+{
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, rtx_disable_work);
+ int tx_icl, ret = 0;
+
+ /* Set error reason if THERMAL_DAEMON_VOTER want to disable rtx */
+ tx_icl = get_client_vote(charger->tx_icl_votable,
+ THERMAL_DAEMON_VOTER);
+ if (tx_icl == 0) {
+ charger->rtx_err = RTX_OVER_TEMP;
+ logbuffer_log(charger->rtx_log,
+ "tdv vote %d to tx_icl",
+ tx_icl);
+ }
+
+ /* Disable rtx mode */
+ ret = p9382_set_rtx(charger, false);
+ if (ret)
+ dev_err(&charger->client->dev,
+ "unable to disable rtx: %d\n", ret);
+}
+
+static void p9221_uevent_work(struct work_struct *work)
+{
+ struct p9221_charger_data *charger = container_of(work,
+ struct p9221_charger_data, uevent_work);
+ int ret;
+ union power_supply_propval vout, iout;
+
+ kobject_uevent(&charger->dev->kobj, KOBJ_CHANGE);
+
+ if (!charger->ben_state)
+ return;
+
+ ret = p9221_get_property_reg(charger, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &iout);
+ ret |= p9221_get_property_reg(charger, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ &vout);
+ if (ret == 0) {
+ logbuffer_log(charger->rtx_log,
+ "Vout=%dmV, Iout=%dmA, rx_lvl=%d",
+ vout.intval/1000, iout.intval/1000,
+ charger->rtx_csp);
+ } else {
+ logbuffer_log(charger->rtx_log, "failed to read rtx info.");
+ }
+}
+
+static int p9221_parse_dt(struct device *dev,
+ struct p9221_charger_platform_data *pdata)
+{
+ int ret = 0;
+ u32 data;
+ struct device_node *node = dev->of_node;
+
+ if (of_device_is_compatible(node, "idt,p9412")) {
+ dev_info(dev, "selecting p9412\n");
+ pdata->chip_id = P9412_CHIP_ID;
+ } else if (of_device_is_compatible(node, "idt,p9382")) {
+ dev_info(dev, "selecting p9382\n");
+ pdata->chip_id = P9382A_CHIP_ID;
+ } else if (of_device_is_compatible(node, "idt,p9221")) {
+ dev_info(dev, "selecting p9211\n");
+ pdata->chip_id = P9221_CHIP_ID;
+ }
+
+ /* Enable */
+ ret = of_get_named_gpio(node, "idt,gpio_qien", 0);
+ pdata->qien_gpio = ret;
+ if (ret < 0)
+ dev_warn(dev, "unable to read idt,gpio_qien from dt: %d\n",
+ ret);
+ else
+ dev_info(dev, "enable gpio:%d", pdata->qien_gpio);
+
+ /* WLC_BPP_EPP_SLCT */
+ ret = of_get_named_gpio(node, "idt,gpio_slct", 0);
+ pdata->slct_gpio = ret;
+ if (ret < 0) {
+ dev_warn(dev, "unable to read idt,gpio_slct from dt: %d\n",
+ ret);
+ } else {
+ ret = of_property_read_u32(node, "idt,gpio_slct_value", &data);
+ if (ret == 0)
+ pdata->slct_value = (data != 0);
+
+ dev_info(dev, "WLC_BPP_EPP_SLCT gpio:%d value=%d",
+ pdata->slct_gpio, pdata->slct_value);
+ }
+
+ /* boost enable, power WLC IC from device */
+ ret = of_get_named_gpio(node, "idt,gpio_ben", 0);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ pdata->ben_gpio = ret;
+ if (ret >= 0)
+ dev_info(dev, "ben gpio:%d\n", pdata->ben_gpio);
+
+ ret = of_get_named_gpio(node, "idt,gpio_switch", 0);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ pdata->switch_gpio = ret;
+ if (ret >= 0)
+ dev_info(dev, "switch gpio:%d\n", pdata->switch_gpio);
+
+ /* boost gpio sets rtx at charging voltage level */
+ ret = of_get_named_gpio(node, "idt,gpio_boost", 0);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ pdata->boost_gpio = ret;
+ if (ret >= 0)
+ dev_info(dev, "boost gpio:%d\n", pdata->boost_gpio);
+
+ /* Main IRQ */
+ ret = of_get_named_gpio(node, "idt,irq_gpio", 0);
+ if (ret < 0) {
+ dev_err(dev, "unable to read idt,irq_gpio from dt: %d\n", ret);
+ return ret;
+ }
+ pdata->irq_gpio = ret;
+ pdata->irq_int = gpio_to_irq(pdata->irq_gpio);
+ dev_info(dev, "gpio:%d, gpio_irq:%d\n", pdata->irq_gpio,
+ pdata->irq_int);
+
+ /* Optional Detect IRQ */
+ ret = of_get_named_gpio(node, "idt,irq_det_gpio", 0);
+ pdata->irq_det_gpio = ret;
+ if (ret < 0) {
+ dev_warn(dev, "unable to read idt,irq_det_gpio from dt: %d\n",
+ ret);
+ } else {
+ pdata->irq_det_int = gpio_to_irq(pdata->irq_det_gpio);
+ dev_info(dev, "det gpio:%d, det gpio_irq:%d\n",
+ pdata->irq_det_gpio, pdata->irq_det_int);
+ }
+
+ /* Optional VOUT max */
+ pdata->max_vout_mv = P9221_MAX_VOUT_SET_MV_DEFAULT;
+ ret = of_property_read_u32(node, "max_vout_mv", &data);
+ if (ret == 0) {
+ if (data < 3500 || data > 29000)
+ dev_err(dev, "max_vout_mv out of range %d\n", data);
+ else
+ pdata->max_vout_mv = data;
+ }
+
+ /* Optional FOD data */
+ pdata->fod_num =
+ of_property_count_elems_of_size(node, "fod", sizeof(u8));
+ if (pdata->fod_num <= 0) {
+ dev_err(dev, "No dt fod provided (%d)\n", pdata->fod_num);
+ pdata->fod_num = 0;
+ } else {
+ if (pdata->fod_num > P9221R5_NUM_FOD) {
+ dev_err(dev,
+ "Incorrect num of FOD %d, using first %d\n",
+ pdata->fod_num, P9221R5_NUM_FOD);
+ pdata->fod_num = P9221R5_NUM_FOD;
+ }
+ ret = of_property_read_u8_array(node, "fod", pdata->fod,
+ pdata->fod_num);
+ if (ret == 0) {
+ char buf[P9221R5_NUM_FOD * 3 + 1];
+
+ p9221_hex_str(pdata->fod, pdata->fod_num, buf,
+ pdata->fod_num * 3 + 1, false);
+ dev_info(dev, "dt fod: %s (%d)\n", buf, pdata->fod_num);
+ }
+ }
+
+ pdata->fod_epp_num =
+ of_property_count_elems_of_size(node, "fod_epp", sizeof(u8));
+ if (pdata->fod_epp_num <= 0) {
+ dev_err(dev, "No dt fod epp provided (%d)\n",
+ pdata->fod_epp_num);
+ pdata->fod_epp_num = 0;
+ } else {
+ if (pdata->fod_epp_num > P9221R5_NUM_FOD) {
+ dev_err(dev,
+ "Incorrect num of EPP FOD %d, using first %d\n",
+ pdata->fod_epp_num, P9221R5_NUM_FOD);
+ pdata->fod_epp_num = P9221R5_NUM_FOD;
+ }
+ ret = of_property_read_u8_array(node, "fod_epp", pdata->fod_epp,
+ pdata->fod_epp_num);
+ if (ret == 0) {
+ char buf[P9221R5_NUM_FOD * 3 + 1];
+
+ p9221_hex_str(pdata->fod_epp, pdata->fod_epp_num, buf,
+ pdata->fod_epp_num * 3 + 1, false);
+ dev_info(dev, "dt fod_epp: %s (%d)\n", buf,
+ pdata->fod_epp_num);
+ }
+ }
+
+ ret = of_property_read_u32(node, "google,q_value", &data);
+ if (ret < 0) {
+ pdata->q_value = -1;
+ } else {
+ pdata->q_value = data;
+ dev_info(dev, "dt q_value:%d\n", pdata->q_value);
+ }
+
+ ret = of_property_read_u32(node, "google,epp_rp_value", &data);
+ if (ret < 0) {
+ pdata->epp_rp_value = -1;
+ } else {
+ pdata->epp_rp_value = data;
+ dev_info(dev, "dt epp_rp_value: %d\n", pdata->epp_rp_value);
+ }
+
+ ret = of_property_read_u32(node, "google,needs_dcin_reset", &data);
+ if (ret < 0) {
+ pdata->needs_dcin_reset = -1;
+ } else {
+ pdata->needs_dcin_reset = data;
+ dev_info(dev, "dt needs_dcin_reset: %d\n",
+ pdata->needs_dcin_reset);
+ }
+
+ pdata->nb_alignment_freq =
+ of_property_count_elems_of_size(node,
+ "google,alignment_frequencies",
+ sizeof(u32));
+ dev_info(dev, "dt google,alignment_frequencies size = %d\n",
+ pdata->nb_alignment_freq);
+
+ if (pdata->nb_alignment_freq > 0) {
+ pdata->alignment_freq =
+ devm_kmalloc_array(dev,
+ pdata->nb_alignment_freq,
+ sizeof(u32),
+ GFP_KERNEL);
+ if (!pdata->alignment_freq) {
+ dev_warn(dev,
+ "dt google,alignment_frequencies array not created");
+ } else {
+ ret = of_property_read_u32_array(node,
+ "google,alignment_frequencies",
+ pdata->alignment_freq,
+ pdata->nb_alignment_freq);
+ if (ret) {
+ dev_warn(dev,
+ "failed to read google,alignment_frequencies: %d\n",
+ ret);
+ devm_kfree(dev, pdata->alignment_freq);
+ }
+ }
+ }
+
+ ret = of_property_read_u32(node, "google,alignment_scalar", &data);
+ if (ret < 0)
+ pdata->alignment_scalar = WLC_ALIGN_DEFAULT_SCALAR;
+ else {
+ pdata->alignment_scalar = data;
+ if (pdata->alignment_scalar != WLC_ALIGN_DEFAULT_SCALAR)
+ dev_info(dev, "google,alignment_scalar updated to: %d\n",
+ pdata->alignment_scalar);
+ }
+
+ ret = of_property_read_u32(node, "google,alignment_hysteresis", &data);
+ if (ret < 0)
+ pdata->alignment_hysteresis = WLC_ALIGN_DEFAULT_HYSTERESIS;
+ else
+ pdata->alignment_hysteresis = data;
+
+ dev_info(dev, "google,alignment_hysteresis set to: %d\n",
+ pdata->alignment_hysteresis);
+
+ ret = of_property_read_bool(node, "idt,ramp-disable");
+ if (ret)
+ pdata->icl_ramp_delay_ms = -1;
+
+ return 0;
+}
+
+static enum power_supply_property p9221_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_AICL_DELAY,
+ POWER_SUPPLY_PROP_AICL_ICL,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_PTMC_ID,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_OPERATING_FREQ,
+};
+
+static const struct power_supply_desc p9221_psy_desc = {
+ .name = "wireless",
+ .type = POWER_SUPPLY_TYPE_WIRELESS,
+ .properties = p9221_props,
+ .num_properties = ARRAY_SIZE(p9221_props),
+ .get_property = p9221_get_property,
+ .set_property = p9221_set_property,
+ .property_is_writeable = p9221_prop_is_writeable,
+ .no_thermal = true,
+};
+
+static int p9382a_tx_icl_vote_callback(struct votable *votable, void *data,
+ int icl_ua, const char *client)
+{
+ struct p9221_charger_data *charger = data;
+ int ret = 0;
+
+ if (!charger->ben_state)
+ return ret;
+
+ if (icl_ua == 0) {
+ schedule_work(&charger->rtx_disable_work);
+ } else {
+ ret = p9221_reg_write_cooked(charger,
+ P9382A_ILIM_SET_REG, icl_ua);
+ if (ret == 0)
+ logbuffer_log(charger->rtx_log, "set TX_ICL to %dmA",
+ icl_ua);
+ else
+ dev_err(&charger->client->dev,
+ "Couldn't set Tx current limit rc=%d\n", ret);
+ }
+
+ return ret;
+}
+
+/*
+ * If able to read the chip_id register, sets chip_id to value read
+ * otherwise sets value to default passed in.
+ *
+ * Returns true when online.
+ */
+static bool p9221_get_chip_id(struct p9221_charger_data *charger,
+ u16 *chip_id, u16 default_chip_id)
+{
+ int ret;
+
+ /* Test to see if the charger is online */
+ ret = p9221_reg_read_16(charger, P9221_CHIP_ID_REG, chip_id);
+ if (ret == 0) {
+ dev_info(charger->dev, "Charger online id:%04x\n", *chip_id);
+ return true;
+ }
+
+ /* off, try to power on the WLC chip */
+ ret = p9382_rtx_enable(charger, true);
+ if (ret == 0) {
+ /* FIXME: b/146316852 */
+ ret = p9221_reg_read_16(charger, P9221_CHIP_ID_REG, chip_id);
+ p9382_rtx_enable(charger, false);
+
+ if (ret == 0) {
+ dev_info(charger->dev, "Charger rTX id:%04x\n",
+ *chip_id);
+ return false;
+ }
+ }
+
+ *chip_id = default_chip_id;
+
+ return false;
+}
+
+static int p9221_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device_node *of_node = client->dev.of_node;
+ struct p9221_charger_data *charger;
+ struct p9221_charger_platform_data *pdata = client->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ u16 chip_id = 0;
+ bool online;
+ int ret;
+
+ ret = i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK);
+ if (!ret) {
+ ret = i2c_get_functionality(client->adapter);
+ dev_err(&client->dev, "I2C adapter not compatible %x\n", ret);
+ return -ENOSYS;
+ }
+
+ if (of_node) {
+ pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&client->dev, "Failed to allocate pdata\n");
+ return -ENOMEM;
+ }
+ ret = p9221_parse_dt(&client->dev, pdata);
+ if (ret) {
+ dev_err(&client->dev, "Failed to parse dt\n");
+ return ret;
+ }
+ }
+
+ charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL);
+ if (charger == NULL) {
+ dev_err(&client->dev, "Failed to allocate charger\n");
+ return -ENOMEM;
+ }
+ i2c_set_clientdata(client, charger);
+ charger->dev = &client->dev;
+ charger->client = client;
+ charger->pdata = pdata;
+ charger->resume_complete = true;
+ charger->align = POWER_SUPPLY_ALIGN_ERROR;
+ charger->align_count = 0;
+ charger->is_mfg_google = false;
+ mutex_init(&charger->io_lock);
+ mutex_init(&charger->cmd_lock);
+ timer_setup(&charger->vrect_timer, p9221_vrect_timer_handler, 0);
+ timer_setup(&charger->align_timer, p9221_align_timer_handler, 0);
+ INIT_DELAYED_WORK(&charger->dcin_work, p9221_dcin_work);
+ INIT_DELAYED_WORK(&charger->tx_work, p9221_tx_work);
+ INIT_DELAYED_WORK(&charger->txid_work, p9382_txid_work);
+ INIT_DELAYED_WORK(&charger->icl_ramp_work, p9221_icl_ramp_work);
+ INIT_DELAYED_WORK(&charger->align_work, p9221_align_work);
+ INIT_DELAYED_WORK(&charger->dcin_pon_work, p9221_dcin_pon_work);
+ INIT_WORK(&charger->uevent_work, p9221_uevent_work);
+ INIT_WORK(&charger->rtx_disable_work, p9382_rtx_disable_work);
+ alarm_init(&charger->icl_ramp_alarm, ALARM_BOOTTIME,
+ p9221_icl_ramp_alarm_cb);
+
+ /* Default enable */
+ charger->enabled = true;
+ if (charger->pdata->qien_gpio >= 0)
+ gpio_direction_output(charger->pdata->qien_gpio, 0);
+
+ if (charger->pdata->slct_gpio >= 0)
+ gpio_direction_output(charger->pdata->slct_gpio,
+ charger->pdata->slct_value);
+
+ if (charger->pdata->ben_gpio >= 0)
+ gpio_direction_output(charger->pdata->ben_gpio, 0);
+
+ if (charger->pdata->switch_gpio >= 0)
+ gpio_direction_output(charger->pdata->switch_gpio, 0);
+
+ /* Default to R5+ */
+ charger->cust_id = 5;
+
+ psy_cfg.drv_data = charger;
+ psy_cfg.of_node = charger->dev->of_node;
+ charger->wc_psy = devm_power_supply_register(charger->dev,
+ &p9221_psy_desc,
+ &psy_cfg);
+ if (IS_ERR(charger->wc_psy)) {
+ dev_err(&client->dev, "Fail to register supply: %d\n", ret);
+ return PTR_ERR(charger->wc_psy);
+ }
+
+
+ /*
+ * Create the RTX_ICL votable, we use this to limit the current that
+ * is taken for RTx mode
+ */
+ if (charger->pdata->switch_gpio >= 0) {
+ charger->tx_icl_votable = create_votable("TX_ICL", VOTE_MIN,
+ p9382a_tx_icl_vote_callback, charger);
+ if (IS_ERR(charger->tx_icl_votable)) {
+ ret = PTR_ERR(charger->tx_icl_votable);
+ dev_err(&client->dev,
+ "Couldn't create TX_ICL rc=%d\n", ret);
+ charger->tx_icl_votable = NULL;
+ }
+ }
+
+ /* vote default TX_ICL for rtx mode */
+ if (charger->tx_icl_votable)
+ vote(charger->tx_icl_votable, P9382A_RTX_VOTER, true,
+ P9382A_RTX_ICL_MAX_MA);
+ /*
+ * Find the DC_ICL votable, we use this to limit the current that
+ * is taken from the wireless charger.
+ */
+ charger->dc_icl_votable = find_votable("DC_ICL");
+ if (!charger->dc_icl_votable)
+ dev_warn(&charger->client->dev, "Could not find DC_ICL votable\n");
+
+ /*
+ * Find the DC_SUSPEND, we use this to disable DCIN before
+ * enter RTx mode
+ */
+ charger->dc_suspend_votable = find_votable("DC_SUSPEND");
+ if (!charger->dc_suspend_votable)
+ dev_warn(&charger->client->dev, "Could not find DC_SUSPEND votable\n");
+
+ /* Ramping on BPP is optional */
+ if (charger->pdata->icl_ramp_delay_ms != -1) {
+ charger->icl_ramp_ua = P9221_DC_ICL_BPP_RAMP_DEFAULT_UA;
+ charger->pdata->icl_ramp_delay_ms =
+ P9221_DC_ICL_BPP_RAMP_DELAY_DEFAULT_MS;
+ }
+
+ charger->dc_icl_bpp = 0;
+ charger->dc_icl_epp = 0;
+ charger->dc_icl_epp_neg = P9221_DC_ICL_EPP_UA;
+ charger->aicl_icl_ua = 0;
+ charger->aicl_delay_ms = 0;
+
+ online = p9221_get_chip_id(charger, &chip_id, charger->pdata->chip_id);
+ switch (chip_id) {
+ case P9412_CHIP_ID:
+ charger->addr_data_send_buf_start = P9382A_DATA_SEND_BUF_START;
+ charger->addr_data_recv_buf_start = P9382A_DATA_RECV_BUF_START;
+ charger->chip_get_tx_id = p9382_chip_get_tx_id;
+ charger->chip_get_tx_mfg_code = p9382_chip_get_tx_mfg_code;
+ charger->ocp_icl_lmt = P9382A_DC_ICL_EPP_1200;
+ charger->ocp_icl_val = P9382A_DC_ICL_EPP_1200;
+ charger->rtx_state = RTX_AVAILABLE;
+
+ charger->chip_tx_mode_en = p9412_chip_tx_mode;
+ break;
+ case P9382A_CHIP_ID:
+ charger->addr_data_send_buf_start = P9382A_DATA_SEND_BUF_START;
+ charger->addr_data_recv_buf_start = P9382A_DATA_RECV_BUF_START;
+ charger->chip_get_tx_id = p9382_chip_get_tx_id;
+ charger->chip_get_tx_mfg_code = p9382_chip_get_tx_mfg_code;
+ charger->ocp_icl_lmt = P9382A_DC_ICL_EPP_1200;
+ charger->ocp_icl_val = P9382A_DC_ICL_EPP_1200;
+ charger->rtx_state = RTX_AVAILABLE;
+
+ charger->chip_tx_mode_en = p9382_chip_tx_mode;
+ break;
+ default:
+ charger->addr_data_send_buf_start = P9221R5_DATA_SEND_BUF_START;
+ charger->addr_data_recv_buf_start = P9221R5_DATA_RECV_BUF_START;
+ charger->chip_get_tx_id = p9221_chip_get_tx_id;
+ charger->chip_get_tx_mfg_code = p9221_chip_get_tx_mfg_code;
+ charger->ocp_icl_lmt = 0;
+ charger->ocp_icl_val = P9382A_DC_ICL_EPP_1200;
+ charger->rtx_state = RTX_NOTSUPPORTED;
+
+ charger->chip_tx_mode_en = p9221_chip_tx_mode;
+ break;
+ }
+ charger->chip_id = chip_id;
+ dev_info(&client->dev, "online = %d CHIP_ID = 0x%x\n", online, chip_id);
+
+ if (online) {
+ /* set charger->online=true, will ignore first VRECTON IRQ */
+ p9221_set_online(charger);
+ } else {
+ /* disconnected, (likely err!=0) vote for BPP */
+ p9221_vote_defaults(charger);
+ }
+
+ ret = devm_request_threaded_irq(
+ &client->dev, charger->pdata->irq_int, NULL,
+ p9221_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "p9221-irq", charger);
+ if (ret) {
+ dev_err(&client->dev, "Failed to request IRQ\n");
+ return ret;
+ }
+ device_init_wakeup(charger->dev, true);
+
+ /*
+ * We will receive a VRECTON after enabling IRQ if the device is
+ * if the device is already in-field when the driver is probed.
+ */
+ enable_irq_wake(charger->pdata->irq_int);
+
+ if (gpio_is_valid(charger->pdata->irq_det_gpio)) {
+ ret = devm_request_threaded_irq(
+ &client->dev, charger->pdata->irq_det_int, NULL,
+ p9221_irq_det_thread,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "p9221-irq-det",
+ charger);
+ if (ret) {
+ dev_err(&client->dev, "Failed to request IRQ_DET\n");
+ return ret;
+ }
+
+ ret = devm_gpio_request_one(&client->dev,
+ charger->pdata->irq_det_gpio,
+ GPIOF_DIR_IN, "p9221-det-gpio");
+ if (ret) {
+ dev_err(&client->dev, "Failed to request GPIO_DET\n");
+ return ret;
+ }
+ enable_irq_wake(charger->pdata->irq_det_int);
+ }
+
+ charger->last_capacity = -1;
+ charger->count = 1;
+ ret = sysfs_create_group(&charger->dev->kobj, &p9221_attr_group);
+ if (ret) {
+ dev_info(&client->dev, "sysfs_create_group failed\n");
+ }
+ if (charger->pdata->switch_gpio >= 0) {
+ ret = sysfs_create_group(&charger->dev->kobj, &rtx_attr_group);
+ if (ret)
+ dev_info(&client->dev, "rtx sysfs_create_group failed\n");
+ }
+
+ /*
+ * Register notifier so we can detect changes on DC_IN
+ */
+ INIT_DELAYED_WORK(&charger->notifier_work, p9221_notifier_work);
+ charger->nb.notifier_call = p9221_notifier_cb;
+ ret = power_supply_reg_notifier(&charger->nb);
+ if (ret) {
+ dev_err(&client->dev, "Fail to register notifier: %d\n", ret);
+ return ret;
+ }
+
+ charger->log = debugfs_logbuffer_register("wireless");
+ if (IS_ERR(charger->log)) {
+ ret = PTR_ERR(charger->log);
+ dev_err(charger->dev,
+ "failed to obtain logbuffer instance, ret=%d\n", ret);
+ charger->log = NULL;
+ }
+
+ charger->rtx_log = debugfs_logbuffer_register("rtx");
+ if (IS_ERR(charger->rtx_log)) {
+ ret = PTR_ERR(charger->rtx_log);
+ dev_err(charger->dev,
+ "failed to obtain rtx logbuffer instance, ret=%d\n",
+ ret);
+ charger->rtx_log = NULL;
+ }
+
+ dev_info(&client->dev, "p9221 Charger Driver Loaded\n");
+
+ if (online) {
+ charger->dc_psy = power_supply_get_by_name("dc");
+ if (charger->dc_psy)
+ power_supply_changed(charger->dc_psy);
+ }
+
+ return 0;
+}
+
+static int p9221_charger_remove(struct i2c_client *client)
+{
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&charger->dcin_work);
+ cancel_delayed_work_sync(&charger->tx_work);
+ cancel_delayed_work_sync(&charger->txid_work);
+ cancel_delayed_work_sync(&charger->icl_ramp_work);
+ cancel_delayed_work_sync(&charger->dcin_pon_work);
+ cancel_delayed_work_sync(&charger->align_work);
+ cancel_work_sync(&charger->uevent_work);
+ cancel_work_sync(&charger->rtx_disable_work);
+ alarm_try_to_cancel(&charger->icl_ramp_alarm);
+ del_timer_sync(&charger->vrect_timer);
+ del_timer_sync(&charger->align_timer);
+ device_init_wakeup(charger->dev, false);
+ cancel_delayed_work_sync(&charger->notifier_work);
+ power_supply_unreg_notifier(&charger->nb);
+ mutex_destroy(&charger->io_lock);
+ if (charger->log)
+ debugfs_logbuffer_unregister(charger->log);
+ if (charger->rtx_log)
+ debugfs_logbuffer_unregister(charger->rtx_log);
+ return 0;
+}
+
+static const struct i2c_device_id p9221_charger_id_table[] = {
+ { "p9221", 0 },
+ { "p9382", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, p9221_charger_id_table);
+
+#ifdef CONFIG_OF
+static struct of_device_id p9221_charger_match_table[] = {
+ { .compatible = "idt,p9221",},
+ { .compatible = "idt,p9382",},
+ { .compatible = "idt,p9412",},
+ {},
+};
+#else
+#define p9221_charger_match_table NULL
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int p9221_pm_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ pm_runtime_get_sync(charger->dev);
+ charger->resume_complete = false;
+ pm_runtime_put_sync(charger->dev);
+
+ return 0;
+}
+
+static int p9221_pm_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct p9221_charger_data *charger = i2c_get_clientdata(client);
+
+ pm_runtime_get_sync(charger->dev);
+ charger->resume_complete = true;
+ pm_runtime_put_sync(charger->dev);
+
+ return 0;
+}
+#endif
+static const struct dev_pm_ops p9221_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(p9221_pm_suspend, p9221_pm_resume)
+};
+
+static struct i2c_driver p9221_charger_driver = {
+ .driver = {
+ .name = "p9221",
+ .owner = THIS_MODULE,
+ .of_match_table = p9221_charger_match_table,
+ .pm = &p9221_pm_ops,
+ },
+ .probe = p9221_charger_probe,
+ .remove = p9221_charger_remove,
+ .id_table = p9221_charger_id_table,
+};
+
+static int __init p9221_charger_init(void)
+{
+ return i2c_add_driver(&p9221_charger_driver);
+}
+
+static void __exit p9221_charger_exit(void)
+{
+ i2c_del_driver(&p9221_charger_driver);
+}
+
+module_init(p9221_charger_init);
+module_exit(p9221_charger_exit);
+
+MODULE_DESCRIPTION("IDT P9221 Wireless Power Receiver Driver");
+MODULE_AUTHOR("Patrick Tjin <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/p9221_charger.h b/p9221_charger.h
new file mode 100644
index 0000000..434966f
--- /dev/null
+++ b/p9221_charger.h
@@ -0,0 +1,442 @@
+/*
+ * P9221 Wireless Charger Driver
+ *
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+#ifndef __P9221_CHARGER_H__
+#define __P9221_CHARGER_H__
+
+#define P9221_WLC_VOTER "WLC_VOTER"
+#define P9221_USER_VOTER "WLC_USER_VOTER"
+#define P9221_OCP_VOTER "OCP_VOTER"
+#define DCIN_AICL_VOTER "DCIN_AICL_VOTER"
+#define P9382A_RTX_VOTER "RTX_VOTER"
+#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER"
+#define P9221_DC_ICL_BPP_UA 700000
+#define P9221_DC_ICL_BPP_RAMP_DEFAULT_UA 900000
+#define P9221_DC_ICL_BPP_RAMP_DELAY_DEFAULT_MS (7 * 60 * 1000) /* 7 mins */
+#define P9221_DC_ICL_EPP_UA 1100000
+#define P9221_EPP_THRESHOLD_UV 7000000
+#define P9221_MAX_VOUT_SET_MV_DEFAULT 9000
+#define P9382A_RTX_ICL_MAX_MA 1350
+
+/*
+ * P9221 common registers
+ */
+#define P9221_CHIP_ID_REG 0x00
+#define P9221_CHIP_ID 0x9220
+#define P9221_CHIP_REVISION_REG 0x02
+#define P9221_CUSTOMER_ID_REG 0x03
+#define P9221R5_CUSTOMER_ID_VAL 0x05
+#define P9221_OTP_FW_MAJOR_REV_REG 0x04
+#define P9221_OTP_FW_MINOR_REV_REG 0x06
+#define P9221_OTP_FW_DATE_REG 0x08
+#define P9221_OTP_FW_DATE_SIZE 12
+#define P9221_OTP_FW_TIME_REG 0x14
+#define P9221_OTP_FW_TIME_SIZE 8
+#define P9221_SRAM_FW_MAJOR_REV_REG 0x1C
+#define P9221_SRAM_FW_MINOR_REV_REG 0x1E
+#define P9221_SRAM_FW_DATE_REG 0x20
+#define P9221_SRAM_FW_DATE_SIZE 12
+#define P9221_SRAM_FW_TIME_REG 0x2C
+#define P9221_SRAM_FW_TIME_SIZE 8
+#define P9221_STATUS_REG 0x34
+#define P9221_INT_REG 0x36
+#define P9221_INT_MASK 0xF7
+#define P9221_INT_ENABLE_REG 0x38
+#define P9221_COM_REG 0x4E
+
+
+/*
+ * P9221R5 unique registers
+ */
+#define P9221R5_INT_CLEAR_REG 0x3A
+#define P9221R5_VOUT_SET_REG 0x3C
+#define P9221R5_ILIM_SET_REG 0x3D
+#define P9221R5_ILIM_SET_MAX 0x0E /* 0x0E = 1.6A */
+#define P9221R5_CHARGE_STAT_REG 0x3E
+#define P9221R5_EPT_REG 0x3F
+#define P9221R5_VRECT_REG 0x40
+#define P9221R5_VOUT_REG 0x42
+#define P9221R5_IOUT_REG 0x44
+#define P9221R5_OP_FREQ_REG 0x48
+#define P9221R5_SYSTEM_MODE_REG 0x4C
+#define P9221R5_COM_CHAN_RESET_REG 0x50
+#define P9221R5_COM_CHAN_SEND_SIZE_REG 0x58
+#define P9221R5_COM_CHAN_SEND_IDX_REG 0x59
+#define P9221R5_COM_CHAN_RECV_SIZE_REG 0x5A
+#define P9221R5_COM_CHAN_RECV_IDX_REG 0x5B
+#define P9221R5_VRECT_ADC_REG 0x60
+#define P9221R5_VOUT_ADC_REG 0x62
+#define P9221R5_VOUT_ADC_MASK 0xFFF
+#define P9221R5_IOUT_ADC_REG 0x64
+#define P9221R5_IOUT_ADC_MASK 0xFFF
+#define P9221R5_DIE_TEMP_ADC_REG 0x66
+#define P9221R5_DIE_TEMP_ADC_MASK 0xFFF
+#define P9221R5_AC_PERIOD_REG 0x68
+#define P9221R5_TX_PINGFREQ_REG 0x6A
+#define P9221R5_EXT_TEMP_REG 0x6C
+#define P9221R5_EXT_TEMP_MASK 0xFFF
+#define P9221R5_FOD_REG 0x70
+#define P9221R5_NUM_FOD 16
+#define P9221R5_DEBUG_REG 0x80
+#define P9221R5_EPP_Q_FACTOR_REG 0x83
+#define P9221R5_EPP_TX_GUARANTEED_POWER_REG 0x84
+#define P9221R5_EPP_TX_POTENTIAL_POWER_REG 0x85
+#define P9221R5_EPP_TX_CAPABILITY_FLAGS_REG 0x86
+#define P9221R5_EPP_RENEGOTIATION_REG 0x87
+#define P9221R5_EPP_CUR_RPP_HEADER_REG 0x88
+#define P9221R5_EPP_CUR_NEGOTIATED_POWER_REG 0x89
+#define P9221R5_EPP_CUR_MAXIMUM_POWER_REG 0x8A
+#define P9221R5_EPP_CUR_FSK_MODULATION_REG 0x8B
+#define P9221R5_EPP_REQ_RPP_HEADER_REG 0x8C
+#define P9221R5_EPP_REQ_NEGOTIATED_POWER_REG 0x8D
+#define P9221R5_EPP_REQ_MAXIMUM_POWER_REG 0x8E
+#define P9221R5_EPP_REQ_FSK_MODULATION_REG 0x8F
+#define P9221R5_VRECT_TARGET_REG 0x90
+#define P9221R5_VRECT_KNEE_REG 0x92
+#define P9221R5_VRECT_CORRECTION_FACTOR_REG 0x93
+#define P9221R5_VRECT_MAX_CORRECTION_FACTOR_REG 0x94
+#define P9221R5_VRECT_MIN_CORRECTION_FACTOR_REG 0x96
+#define P9221R5_FOD_SECTION_REG 0x99
+#define P9221R5_VRECT_ADJ_REG 0x9E
+#define P9221R5_ALIGN_X_ADC_REG 0xA0
+#define P9221R5_ALIGN_Y_ADC_REG 0xA1
+#define P9221R5_ASK_MODULATION_DEPTH_REG 0xA2
+#define P9221R5_OVSET_REG 0xA3
+#define P9221R5_OVSET_MASK 0x7
+#define P9221R5_EPP_TX_SPEC_REV_REG 0xA9
+#define P9221R5_EPP_TX_MFG_CODE_REG 0xAA
+#define P9221R5_GP0_RESET_VOLT_REG 0xAC
+#define P9221R5_GP1_RESET_VOLT_REG 0xAE
+#define P9221R5_GP2_RESET_VOLT_REG 0xB0
+#define P9221R5_GP3_RESET_VOLT_REG 0xB2
+#define P9221R5_PROP_TX_ID_REG 0xB4
+#define P9221R5_PROP_TX_ID_SIZE 4
+#define P9221R5_DATA_SEND_BUF_START 0x100
+#define P9221R5_DATA_SEND_BUF_SIZE 0x80
+#define P9221R5_DATA_RECV_BUF_START 0x180
+#define P9221R5_DATA_RECV_BUF_SIZE 0x80
+#define P9221R5_MAX_PP_BUF_SIZE 16
+#define P9221R5_LAST_REG 0x1FF
+
+/*
+ * System Mode Mask (R5+/0x4C)
+ */
+#define P9221R5_SYSTEM_MODE_EXTENDED_MASK (1 << 3)
+
+/*
+ * Com Channel Commands
+ */
+#define P9221R5_COM_CHAN_CCRESET BIT(7)
+#define P9221_COM_CHAN_RETRIES 5
+
+/*
+ * End of Power packet types
+ */
+#define P9221_EOP_UNKNOWN 0x00
+#define P9221_EOP_EOC 0x01
+#define P9221_EOP_INTERNAL_FAULT 0x02
+#define P9221_EOP_OVER_TEMP 0x03
+#define P9221_EOP_OVER_VOLT 0x04
+#define P9221_EOP_OVER_CURRENT 0x05
+#define P9221_EOP_BATT_FAIL 0x06
+#define P9221_EOP_RECONFIG 0x07
+#define P9221_EOP_NO_RESPONSE 0x08
+#define P9221_EOP_NEGOTIATION_FAIL 0x0A
+#define P9221_EOP_RESTART_POWER 0x0B
+
+/*
+ * Command flags
+ */
+#define P9221R5_COM_RENEGOTIATE P9221_COM_RENEGOTIATE
+#define P9221R5_COM_SWITCH2RAM P9221_COM_SWITCH_TO_RAM_MASK
+#define P9221R5_COM_CLRINT P9221_COM_CLEAR_INT_MASK
+#define P9221R5_COM_SENDCSP P9221_COM_SEND_CHG_STAT_MASK
+#define P9221R5_COM_SENDEPT P9221_COM_SEND_EOP_MASK
+#define P9221R5_COM_LDOTGL P9221_COM_LDO_TOGGLE
+#define P9221R5_COM_CCACTIVATE BIT(0)
+
+#define P9221_COM_RENEGOTIATE BIT(7)
+#define P9221_COM_SWITCH_TO_RAM_MASK BIT(6)
+#define P9221_COM_CLEAR_INT_MASK BIT(5)
+#define P9221_COM_SEND_CHG_STAT_MASK BIT(4)
+#define P9221_COM_SEND_EOP_MASK BIT(3)
+#define P9221_COM_LDO_TOGGLE BIT(1)
+
+/*
+ * Interrupt/Status flags for P9221
+ */
+#define P9221_STAT_VOUT BIT(7)
+#define P9221_STAT_VRECT BIT(6)
+#define P9221_STAT_ACMISSING BIT(5)
+#define P9221_STAT_OV_TEMP BIT(2)
+#define P9221_STAT_OV_VOLT BIT(1)
+#define P9221_STAT_OV_CURRENT BIT(0)
+#define P9221_STAT_LIMIT_MASK (P9221_STAT_OV_TEMP | \
+ P9221_STAT_OV_VOLT | \
+ P9221_STAT_OV_CURRENT)
+/*
+ * Interrupt/Status flags for P9221R5
+ */
+#define P9221R5_STAT_CCRESET BIT(12)
+#define P9221R5_STAT_CCERROR BIT(11)
+#define P9221R5_STAT_PPRCVD BIT(10)
+#define P9221R5_STAT_CCDATARCVD BIT(9)
+#define P9221R5_STAT_CCSENDBUSY BIT(8)
+#define P9221R5_STAT_VOUTCHANGED BIT(7)
+#define P9221R5_STAT_VRECTON BIT(6)
+#define P9221R5_STAT_MODECHANGED BIT(5)
+#define P9221R5_STAT_UV BIT(3)
+#define P9221R5_STAT_OVT BIT(2)
+#define P9221R5_STAT_OVV BIT(1)
+#define P9221R5_STAT_OVC BIT(0)
+#define P9221R5_STAT_MASK 0x1FFF
+#define P9221R5_STAT_CC_MASK (P9221R5_STAT_CCRESET | \
+ P9221R5_STAT_PPRCVD | \
+ P9221R5_STAT_CCERROR | \
+ P9221R5_STAT_CCDATARCVD | \
+ P9221R5_STAT_CCSENDBUSY)
+#define P9221R5_STAT_LIMIT_MASK (P9221R5_STAT_UV | \
+ P9221R5_STAT_OVV | \
+ P9221R5_STAT_OVT | \
+ P9221R5_STAT_OVC)
+
+/*
+ * P9221R5_SYSTEM_MODE_REG bits.
+ */
+#define P9221R5_MODE_RAMCODE BIT(6)
+#define P9221R5_MODE_EEPROMCODE BIT(5)
+#define P9221R5_MODE_EXTENDED BIT(3)
+#define P9221R5_MODE_WPCMODE BIT(0)
+
+/*
+ * P9382 unique registers
+ */
+#define P9382A_I2C_ADDRESS 0x3b
+
+#define P9382A_CHIP_ID 0x9381 /* FIXME: b/146316852 */
+#define P9382A_DATA_SEND_BUF_START 0x130
+#define P9382A_DATA_RECV_BUF_START 0x1B0
+
+#define P9382A_STATUS_REG 0x34
+#define P9382A_CHARGE_STAT_REG 0x3E
+#define P9382A_ILIM_SET_REG 0x4A
+#define P9382A_TRX_ENABLE_REG 0x69
+#define P9382A_TX_INHIBIT 0x3
+
+#define P9382A_MODE_TXMODE BIT(2)
+
+#define P9382_PROP_TX_ID_REG 0xC4
+#define P9382_EPP_TX_MFG_CODE_REG 0xBA
+#define P9382A_FW_REV_25 0x25
+
+/*
+ * Interrupt/Status flags for P9382
+ */
+#define P9382_STAT_TXCONFLICT BIT(3)
+#define P9382_STAT_CSP BIT(4)
+#define P9382_STAT_RXCONNECTED BIT(10)
+
+/*
+ * Send PPP in Tx mode
+ */
+#define PROPRIETARY_PACKET_TYPE_ADDR 0x100
+#define PROPRIETARY_PACKET_TYPE 0x80
+#define FAST_SERIAL_ID_HEADER 0x4F
+#define FAST_SERIAL_ID_SIZE 4
+#define ACCESSORY_TYPE_PHONE BIT(2)
+#define AICL_ENABLED BIT(7)
+#define TX_ACCESSORY_TYPE (ACCESSORY_TYPE_PHONE | \
+ AICL_ENABLED)
+#define TXID_SEND_DELAY_MS (1 * 1000)
+
+/*
+ * P9412 unique registers
+ */
+#define P9412_CHIP_ID 0x9412
+
+/* P9221R5_SYSTEM_MODE_REG(0x4C) values for P9412 */
+#define P9412_SYS_OP_MODE_AC_MISSING 0x00 /* AC Missing */
+#define P9412_SYS_OP_MODE_WPC_BASIC 0x01 /* WPC Basic Protocol */
+#define P9412_SYS_OP_MODE_WPC_EXTD 0x02 /* WPC Extended Protocol */
+#define P9412_SYS_OP_MODE_PROPRIETARY 0x03 /* Renesas Prop. Protocol */
+#define P9412_SYS_OP_MODE_TX_MODE 0x08 /* TX Mode */
+#define P9412_SYS_OP_MODE_TX_FOD 0x09 /* TX FOD (Stop) */
+
+#define P9412_TX_CMD_REG 0x4D
+
+#define P9412_TX_CMD_TX_MODE_EN BIT(7)
+
+enum p9221_align_mfg_chk_state {
+ ALIGN_MFG_FAILED = -1,
+ ALIGN_MFG_CHECKING,
+ ALIGN_MFG_PASSED,
+};
+
+struct p9221_charger_platform_data {
+ int irq_gpio;
+ int irq_int;
+ int irq_det_gpio;
+ int irq_det_int;
+ int qien_gpio;
+ int slct_gpio;
+ int slct_value;
+ int ben_gpio;
+ int switch_gpio;
+ int boost_gpio;
+ int max_vout_mv;
+ u8 fod[P9221R5_NUM_FOD];
+ u8 fod_epp[P9221R5_NUM_FOD];
+ int fod_num;
+ int fod_epp_num;
+ int q_value;
+ int epp_rp_value;
+ int needs_dcin_reset;
+ int nb_alignment_freq;
+ int *alignment_freq;
+ u32 alignment_scalar;
+ u32 alignment_hysteresis;
+ u32 icl_ramp_delay_ms;
+ u16 chip_id;
+};
+
+struct p9221_charger_data {
+ struct i2c_client *client;
+ struct p9221_charger_platform_data *pdata;
+ struct power_supply *wc_psy;
+ struct power_supply *dc_psy;
+ struct votable *dc_icl_votable;
+ struct votable *dc_suspend_votable;
+ struct votable *tx_icl_votable;
+ struct votable *disable_dcin_en_votable;
+ struct notifier_block nb;
+ struct mutex io_lock;
+ struct mutex cmd_lock;
+ struct device *dev;
+ struct delayed_work notifier_work;
+ struct delayed_work dcin_work;
+ struct delayed_work align_work;
+ struct delayed_work tx_work;
+ struct delayed_work icl_ramp_work;
+ struct delayed_work txid_work;
+ struct work_struct uevent_work;
+ struct work_struct rtx_disable_work;
+ struct alarm icl_ramp_alarm;
+ struct timer_list vrect_timer;
+ struct timer_list align_timer;
+ struct bin_attribute bin;
+ struct logbuffer *log;
+ struct logbuffer *rtx_log;
+ u16 chip_id;
+ int online;
+ bool enabled;
+ u16 addr;
+ u8 count;
+ u8 cust_id;
+ int ben_state;
+ u8 pp_buf[P9221R5_MAX_PP_BUF_SIZE];
+ bool pp_buf_valid;
+ u8 rx_buf[P9221R5_DATA_RECV_BUF_SIZE];
+ u16 rx_len;
+ bool rx_done;
+ u8 tx_buf[P9221R5_DATA_SEND_BUF_SIZE];
+ u32 tx_id;
+ u8 tx_id_str[(sizeof(u32) * 2) + 1];
+ u16 tx_len;
+ bool tx_done;
+ bool tx_busy;
+ bool check_np;
+ bool check_dc;
+ bool check_det;
+ int last_capacity;
+ bool resume_complete;
+ bool icl_ramp;
+ u32 icl_ramp_ua;
+ bool fake_force_epp;
+ bool force_bpp;
+ u32 dc_icl_epp_neg;
+ u32 dc_icl_bpp;
+ int align;
+ int align_count;
+ int alignment;
+ u8 alignment_str[(sizeof(u32) * 3) + 1];
+ int alignment_last;
+ enum p9221_align_mfg_chk_state alignment_capable;
+ int mfg_check_count;
+ u16 mfg;
+ int alignment_time;
+ u32 dc_icl_epp;
+ u32 current_filtered;
+ u32 current_sample_cnt;
+ struct delayed_work dcin_pon_work;
+ bool is_mfg_google;
+ u8 ptmc_id_str[(sizeof(u16) * 2) + 1];
+ u32 aicl_delay_ms;
+ u32 aicl_icl_ua;
+ int rtx_state;
+ u32 rtx_csp;
+ int rtx_err;
+
+ /* chip_id dependent values */
+ int addr_data_send_buf_start;
+ int addr_data_recv_buf_start;
+ int ocp_icl_lmt;
+ int ocp_icl_val;
+
+ int (*chip_get_tx_id)(struct p9221_charger_data *chgr, u32 *id);
+ int (*chip_get_tx_mfg_code)(struct p9221_charger_data *chgr, u16 *code);
+ int (*chip_tx_mode_en)(struct p9221_charger_data *chgr, bool en);
+
+};
+
+struct p9221_prop_reg_map_entry {
+ enum power_supply_property prop;
+ u16 reg;
+ bool get;
+ bool set;
+};
+
+enum p9382_rtx_state {
+ RTX_NOTSUPPORTED = 0,
+ RTX_AVAILABLE,
+ RTX_ACTIVE,
+ RTX_DISABLED,
+};
+
+enum p9382_rtx_err {
+ RTX_NO_ERROR = 0,
+ RTX_BATT_LOW,
+ RTX_OVER_TEMP,
+ RTX_TX_CONFLICT,
+};
+
+
+#define P9221_SHOW(name, reg, width, mask, format) \
+ static ssize_t p9221_show_##name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ struct i2c_client *client = to_i2c_client(dev); \
+ struct p9221_charger_data *charger = \
+ i2c_get_clientdata(client); \
+ u##width val; \
+ int ret; \
+ \
+ ret = p9221_reg_read_##width(charger, reg, &val); \
+ if (ret) \
+ return ret; \
+ val &= mask; \
+ return snprintf(buf, PAGE_SIZE, format, val); \
+ }
+
+#endif /* __P9221_CHARGER_H__ */
diff --git a/pca9468_charger.c b/pca9468_charger.c
new file mode 100644
index 0000000..358d84e
--- /dev/null
+++ b/pca9468_charger.c
@@ -0,0 +1,5412 @@
+/*
+ * Driver for the NXP PCA9468 battery charger.
+ *
+ * Copyright (C) 2018 NXP Semiconductor.
+ * Copyright 2020 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/debugfs.h>
+#include "pca9468_charger.h"
+
+#if defined (CONFIG_OF)
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#endif /* CONFIG_OF */
+
+#include <linux/pm_wakeup.h>
+
+/* Google integration */
+#include "google_bms.h"
+#include "google_dc_pps.h"
+
+#define BITS(_end, _start) ((BIT(_end) - BIT(_start)) + BIT(_end))
+#define MASK2SHIFT(_mask) __ffs(_mask)
+
+/*
+ * Register Map
+ */
+#define PCA9468_REG_DEVICE_INFO 0x00 /* Device ID, revision */
+#define PCA9468_BIT_DEV_REV BITS(7, 4)
+#define PCA9468_BIT_DEV_ID BITS(3, 0)
+#define PCA9468_DEVICE_ID 0x18 /* Default ID */
+
+#define PCA9468_REG_INT1 0x01
+#define PCA9468_BIT_V_OK_INT BIT(7)
+#define PCA9468_BIT_NTC_TEMP_INT BIT(6)
+#define PCA9468_BIT_CHG_PHASE_INT BIT(5)
+#define PCA9468_BIT_CTRL_LIMIT_INT BIT(3)
+#define PCA9468_BIT_TEMP_REG_INT BIT(2)
+#define PCA9468_BIT_ADC_DONE_INT BIT(1)
+#define PCA9468_BIT_TIMER_INT BIT(0)
+
+#define PCA9468_REG_INT1_MSK 0x02
+#define PCA9468_BIT_V_OK_M BIT(7)
+#define PCA9468_BIT_NTC_TEMP_M BIT(6)
+#define PCA9468_BIT_CHG_PHASE_M BIT(5)
+#define PCA9468_BIT_RESERVED_M BIT(4)
+#define PCA9468_BIT_CTRL_LIMIT_M BIT(3)
+#define PCA9468_BIT_TEMP_REG_M BIT(2)
+#define PCA9468_BIT_ADC_DONE_M BIT(1)
+#define PCA9468_BIT_TIMER_M BIT(0)
+
+#define PCA9468_REG_INT1_STS 0x03
+#define PCA9468_BIT_V_OK_STS BIT(7)
+#define PCA9468_BIT_NTC_TEMP_STS BIT(6)
+#define PCA9468_BIT_CHG_PHASE_STS BIT(5)
+#define PCA9468_BIT_CTRL_LIMIT_STS BIT(3)
+#define PCA9468_BIT_TEMP_REG_STS BIT(2)
+#define PCA9468_BIT_ADC_DONE_STS BIT(1)
+#define PCA9468_BIT_TIMER_STS BIT(0)
+
+#define PCA9468_REG_STS_A 0x04
+#define PCA9468_BIT_IIN_LOOP_STS BIT(7)
+#define PCA9468_BIT_CHG_LOOP_STS BIT(6) /* not in pca9468 */
+#define PCA9468_BIT_VFLT_LOOP_STS BIT(5)
+#define PCA9468_BIT_CFLY_SHORT_STS BIT(4)
+#define PCA9468_BIT_VOUT_UV_STS BIT(3)
+#define PCA9468_BIT_VBAT_OV_STS BIT(2)
+#define PCA9468_BIT_VIN_OV_STS BIT(1)
+#define PCA9468_BIT_VIN_UV_STS BIT(0)
+
+#define PCA9468_REG_STS_B 0x05
+#define PCA9468_BIT_BATT_MISS_STS BIT(7)
+#define PCA9468_BIT_OCP_FAST_STS BIT(6)
+#define PCA9468_BIT_OCP_AVG_STS BIT(5)
+#define PCA9468_BIT_ACTIVE_STATE_STS BIT(4)
+#define PCA9468_BIT_SHUTDOWN_STATE_STS BIT(3)
+#define PCA9468_BIT_STANDBY_STATE_STS BIT(2)
+#define PCA9468_BIT_CHARGE_TIMER_STS BIT(1)
+#define PCA9468_BIT_WATCHDOG_TIMER_STS BIT(0)
+
+#define PCA9468_REG_STS_C 0x06 /* IIN status */
+#define PCA9468_BIT_IIN_STS BITS(7, 2)
+
+#define PCA9468_REG_STS_D 0x07 /* ICHG status */
+#define PCA9468_BIT_ICHG_STS BITS(7, 1)
+
+#define PCA9468_REG_STS_ADC_1 0x08
+#define PCA9468_BIT_ADC_IIN7_0 BITS(7, 0)
+
+#define PCA9468_REG_STS_ADC_2 0x09
+#define PCA9468_BIT_ADC_IOUT5_0 BITS(7, 2)
+#define PCA9468_BIT_ADC_IIN9_8 BITS(1, 0)
+
+#define PCA9468_REG_STS_ADC_3 0x0A
+#define PCA9468_BIT_ADC_VIN3_0 BITS(7, 4)
+#define PCA9468_BIT_ADC_IOUT9_6 BITS(3, 0)
+
+#define PCA9468_REG_STS_ADC_4 0x0B
+#define PCA9468_BIT_ADC_VOUT1_0 BITS(7, 6)
+#define PCA9468_BIT_ADC_VIN9_4 BITS(5, 0)
+
+#define PCA9468_REG_STS_ADC_5 0x0C
+#define PCA9468_BIT_ADC_VOUT9_2 BITS(7, 0)
+
+#define PCA9468_REG_STS_ADC_6 0x0D
+#define PCA9468_BIT_ADC_VBAT7_0 BITS(7, 0)
+
+#define PCA9468_REG_STS_ADC_7 0x0E
+#define PCA9468_BIT_ADC_DIETEMP5_0 BITS(7, 2)
+#define PCA9468_BIT_ADC_VBAT9_8 BITS(1, 0)
+
+#define PCA9468_REG_STS_ADC_8 0x0F
+#define PCA9468_BIT_ADC_NTCV3_0 BITS(7, 4)
+#define PCA9468_BIT_ADC_DIETEMP9_6 BITS(3, 0)
+
+#define PCA9468_REG_STS_ADC_9 0x10
+#define PCA9468_BIT_ADC_NTCV9_4 BITS(5, 0)
+
+/*
+ * Charge current cannot be in PCA9468.
+#define PCA9468_REG_ICHG_CTRL 0x20
+#define PCA9468_BIT_ICHG_SS BIT(7)
+#define PCA9468_BIT_ICHG_CFG BITS(6, 0)
+ */
+
+#define PCA9468_REG_IIN_CTRL 0x21 /* Input current */
+#define PCA9468_BIT_LIMIT_INCREMENT_EN BIT(7)
+#define PCA9468_BIT_IIN_SS BIT(6)
+#define PCA9468_BIT_IIN_CFG BITS(5, 0)
+
+#define PCA9468_REG_START_CTRL 0x22 /* device init and config */
+#define PCA9468_BIT_SNSRES BIT(7)
+#define PCA9468_BIT_EN_CFG BIT(6)
+#define PCA9468_BIT_STANDBY_EN BIT(5)
+#define PCA9468_BIT_REV_IIN_DET BIT(4)
+#define PCA9468_BIT_FSW_CFG BITS(3, 0)
+
+#define PCA9468_REG_ADC_CTRL 0x23 /* ADC configuration */
+#define PCA9468_BIT_FORCE_ADC_MODE BITS(7, 6)
+#define PCA9468_BIT_ADC_SHUTDOWN_CFG BIT(5)
+#define PCA9468_BIT_HIBERNATE_DELAY BITS(4, 3)
+
+#define PCA9468_REG_ADC_CFG 0x24 /* ADC channel configuration */
+#define PCA9468_BIT_CH7_EN BIT(7)
+#define PCA9468_BIT_CH6_EN BIT(6)
+#define PCA9468_BIT_CH5_EN BIT(5)
+#define PCA9468_BIT_CH4_EN BIT(4)
+#define PCA9468_BIT_CH3_EN BIT(3)
+#define PCA9468_BIT_CH2_EN BIT(2)
+#define PCA9468_BIT_CH1_EN BIT(1)
+
+#define PCA9468_REG_TEMP_CTRL 0x25 /* Temperature configuration */
+#define PCA9468_BIT_TEMP_REG BITS(7, 6)
+#define PCA9468_BIT_TEMP_DELTA BITS(5, 4)
+#define PCA9468_BIT_TEMP_REG_EN BIT(3)
+#define PCA9468_BIT_NTC_PROTECTION_EN BIT(2)
+#define PCA9468_BIT_TEMP_MAX_EN BIT(1)
+
+#define PCA9468_REG_PWR_COLLAPSE 0x26 /* Power collapse cfg */
+#define PCA9468_BIT_UV_DELTA BITS(7, 6)
+#define PCA9468_BIT_IIN_FORCE_COUNT BIT(4)
+#define PCA9468_BIT_BAT_MISS_DET_EN BIT(3)
+
+#define PCA9468_REG_V_FLOAT 0x27 /* Voltage regulation */
+#define PCA9468_BIT_V_FLOAT BITS(7, 0)
+
+#define PCA9468_REG_SAFETY_CTRL 0x28 /* Safety configuration */
+#define PCA9468_BIT_WATCHDOG_EN BIT(7)
+#define PCA9468_BIT_WATCHDOG_CFG BITS(6, 5)
+#define PCA9468_BIT_CHG_TIMER_EN BIT(4)
+#define PCA9468_BIT_CHG_TIMER_CFG BITS(3, 2)
+#define PCA9468_BIT_OV_DELTA BITS(1, 0)
+
+#define PCA9468_REG_NTC_TH_1 0x29 /* Thermistor threshold */
+#define PCA9468_BIT_NTC_THRESHOLD7_0 BITS(7, 0)
+
+#define PCA9468_REG_NTC_TH_2 0x2A /* Thermistor threshold */
+#define PCA9468_BIT_NTC_THRESHOLD9_8 BITS(1, 0)
+
+#define PCA9468_REG_ADC_ACCESS 0x30
+
+#define PCA9468_REG_ADC_ADJUST 0x31
+#define PCA9468_BIT_ADC_GAIN BITS(7, 4)
+
+#define PCA9468_REG_ADC_IMPROVE 0x3D
+#define PCA9468_BIT_ADC_IIN_IMP BIT(3)
+
+#define PCA9468_REG_ADC_MODE 0x3F
+#define PCA9468_BIT_ADC_MODE BIT(4)
+
+#define PCA9468_MAX_REGISTER 0x4F
+
+
+/* input current step, unit - uA */
+#define PCA9468_IIN_CFG_STEP 100000
+/* input current, unit - uA */
+#define PCA9468_IIN_CFG(input_curr) ((input_curr) / PCA9468_IIN_CFG_STEP)
+/* charging current, uint - uA */
+#define PCA9468_ICHG_CFG(_chg_current) ((_chg_current) / 100000)
+/* v_float voltage, unit - uV */
+#define PCA9468_V_FLOAT(_v_float) (((_v_float) / 1000 - 3725) / 5)
+
+#define PCA9468_SNSRES_5mOhm 0x00
+#define PCA9468_SNSRES_10mOhm PCA9468_BIT_SNSRES
+
+#define PCA9468_NTC_TH_STEP 2346 /* 2.346mV, unit - uV */
+
+/* VIN over voltage setting from 2*VOUT */
+enum {
+ OV_DELTA_10P,
+ OV_DELTA_30P,
+ OV_DELTA_20P,
+ OV_DELTA_40P,
+};
+
+/* Switching frequency */
+enum {
+ FSW_CFG_833KHZ,
+ FSW_CFG_893KHZ,
+ FSW_CFG_935KHZ,
+ FSW_CFG_980KHZ,
+ FSW_CFG_1020KHZ,
+ FSW_CFG_1080KHZ,
+ FSW_CFG_1120KHZ,
+ FSW_CFG_1160KHZ,
+ FSW_CFG_440KHZ,
+ FSW_CFG_490KHZ,
+ FSW_CFG_540KHZ,
+ FSW_CFG_590KHZ,
+ FSW_CFG_630KHZ,
+ FSW_CFG_680KHZ,
+ FSW_CFG_730KHZ,
+ FSW_CFG_780KHZ
+};
+
+/* Enable pin polarity selection */
+#define PCA9468_EN_ACTIVE_H 0x00
+#define PCA9468_EN_ACTIVE_L PCA9468_BIT_EN_CFG
+#define PCA9468_STANDBY_FORCED PCA9468_BIT_STANDBY_EN
+#define PCA9468_STANDBY_DONOT 0
+
+/* ADC Channel */
+enum {
+ ADCCH_VOUT = 1,
+ ADCCH_VIN,
+ ADCCH_VBAT,
+ ADCCH_ICHG,
+ ADCCH_IIN,
+ ADCCH_DIETEMP,
+ ADCCH_NTC,
+ ADCCH_MAX
+};
+
+/* ADC step */
+#define VIN_STEP 16000 /* 16mV(16000uV) LSB, Range(0V ~ 16.368V) */
+#define VBAT_STEP 5000 /* 5mV(5000uV) LSB, Range(0V ~ 5.115V) */
+#define IIN_STEP 4890 /* 4.89mA(4890uA) LSB, Range(0A ~ 5A) */
+#define ICHG_STEP 9780 /* 9.78mA(9780uA) LSB, Range(0A ~ 10A) */
+#define DIETEMP_STEP 435 /* 0.435C LSB, Range(-25C ~ 160C) */
+#define DIETEMP_DENOM 1000 /* 1000, denominator */
+#define DIETEMP_MIN -25 /* -25C */
+#define DIETEMP_MAX 160 /* 160C */
+#define VOUT_STEP 5000 /* 5mV(5000uV) LSB, Range(0V ~ 5.115V) */
+#define NTCV_STEP 2346 /* 2.346mV(2346uV) LSB, Range(0V ~ 2.4V) */
+#define ADC_IIN_OFFSET 900000 /* 900mA */
+
+/* adc_gain bit[7:4] of reg 0x31 - 2's complement */
+static int adc_gain[16] = { 0, 1, 2, 3, 4, 5, 6, 7,
+ -8, -7, -6, -5, -4, -3, -2, -1};
+
+/* Timer definition */
+#define PCA9468_VBATMIN_CHECK_T 1000 /* 1000ms */
+#define PCA9468_CCMODE_CHECK1_T 5000 /* 10000ms -> 500ms */
+#define PCA9468_CCMODE_CHECK2_T 5000 /* 5000ms */
+#define PCA9468_CVMODE_CHECK_T 10000 /* 10000ms */
+#define PCA9468_PDMSG_WAIT_T 200 /* 200ms */
+#define PCA4968_ENABLE_DELAY_T 150 /* 150ms */
+#define PCA9468_PPS_PERIODIC_T 10000 /* 10000ms */
+#define PCA9468_CVMODE_CHECK2_T 1000 /* 1000ms */
+
+/* Battery Threshold */
+#define PCA9468_DC_VBAT_MIN 3500000 /* uV */
+/* Input Current Limit default value */
+#define PCA9468_IIN_CFG_DFT 2500000 /* uA*/
+/* Charging Current default value */
+#define PCA9468_ICHG_CFG_DFT 6000000 /* uA*/
+/* Charging Float Voltage default value */
+#define PCA9468_VFLOAT_DFT 4350000 /* uV */
+/* Charging Sub Float Voltage default value */
+#define PCA9468_VFLOAT_SUB_DFT 5000000 /* 5000000uV */
+
+/* Sense Resistance default value */
+#define PCA9468_SENSE_R_DFT 1 /* 10mOhm */
+/* Switching Frequency default value */
+#define PCA9468_FSW_CFG_DFT 3 /* 980KHz */
+/* NTC threshold voltage default value */
+#define PCA9468_NTC_TH_DFT 0 /* uV*/
+
+/* Charging Done Condition */
+#define PCA9468_ICHG_DONE_DFT 1000000 /* uA */
+#define PCA9468_IIN_DONE_DFT 500000 /* uA */
+/* parallel charging done conditoin */
+#define PCA9468_IIN_P_DONE 1000000 /* uA */
+/* Parallel charging default threshold */
+#define PCA9468_IIN_P_TH_DFT 4000000 /* uA */
+/* Single charging default threshold */
+#define PCA9468_IIN_S_TH_DFT 10000000 /* uA */
+
+/* Maximum TA voltage threshold */
+#define PCA9468_TA_MAX_VOL 9800000 /* uV */
+/* Maximum TA current threshold */
+#define PCA9468_TA_MAX_CUR 2500000 /* uA */
+/* Minimum TA current threshold */
+#define PCA9468_TA_MIN_CUR 1000000 /* uA - PPS minimum current */
+
+/* Minimum TA voltage threshold in Preset mode */
+#define PCA9468_TA_MIN_VOL_PRESET 8000000 /* uV */
+/* TA voltage threshold starting Adjust CC mode */
+#define PCA9468_TA_MIN_VOL_CCADJ 8500000 /* 8000000uV --> 8500000uV */
+
+#define PCA9468_TA_VOL_PRE_OFFSET 600000 /* uV */
+/* Adjust CC mode TA voltage step */
+#define PCA9468_TA_VOL_STEP_ADJ_CC 40000 /* uV */
+/* Pre CV mode TA voltage step */
+#define PCA9468_TA_VOL_STEP_PRE_CV 20000 /* uV */
+
+/* IIN_CC adc offset for accuracy */
+#define PCA9468_IIN_ADC_OFFSET 20000 /* uA */
+/* IIN_CC compensation offset */
+#define PCA9468_IIN_CC_COMP_OFFSET 50000 /* uA */
+/* IIN_CC compensation offset in Power Limit Mode(Constant Power) TA */
+#define PCA9468_IIN_CC_COMP_OFFSET_CP 20000 /* uA */
+/* TA maximum voltage that can support CC in Constant Power Mode */
+#define PCA9468_TA_MAX_VOL_CP 9800000 /* 9760000uV --> 9800000uV */
+
+/* maximum retry counter for restarting charging */
+#define PCA9468_MAX_RETRY_CNT 3 /* retries */
+/* TA IIN tolerance */
+#define PCA9468_TA_IIN_OFFSET 100000 /* uA */
+/* IIN_CC upper protection offset in Power Limit Mode TA */
+#define PCA9468_IIN_CC_UPPER_OFFSET 50000 /* 50mA */
+
+/* PD Message Voltage and Current Step */
+#define PD_MSG_TA_VOL_STEP 20000 /* uV */
+#define PD_MSG_TA_CUR_STEP 50000 /* uA */
+
+/* Maximum WCRX voltage threshold */
+#define PCA9468_WCRX_MAX_VOL 10000000 /* uV */
+/* WCRX voltage Step */
+#define WCRX_VOL_STEP 100000 /* uV */
+
+/* Switching charger minimum current */
+#define SWCHG_ICL_MIN 100000 /* uA */
+#define SWCHG_ICL_NORMAL 3000000 /* uA */
+
+/* INT1 Register Buffer */
+enum {
+ REG_INT1,
+ REG_INT1_MSK,
+ REG_INT1_STS,
+ REG_INT1_MAX
+};
+
+/* STS Register Buffer */
+enum {
+ REG_STS_A,
+ REG_STS_B,
+ REG_STS_C,
+ REG_STS_D,
+ REG_STS_MAX
+};
+
+/* Direct Charging State */
+enum {
+ DC_STATE_NO_CHARGING, /* No charging */
+ DC_STATE_CHECK_VBAT, /* Check min battery level */
+ DC_STATE_PRESET_DC, /* Preset TA voltage/current for DC */
+ DC_STATE_CHECK_ACTIVE, /* Check active status before Adjust CC mode */
+ DC_STATE_ADJUST_CC, /* Adjust CC mode */
+ DC_STATE_CC_MODE, /* Check CC mode status */
+ DC_STATE_START_CV, /* Start CV mode */
+ DC_STATE_CV_MODE, /* Check CV mode status */
+ DC_STATE_CHARGING_DONE, /* Charging Done */
+ DC_STATE_ADJUST_TAVOL, /* Adjust TA voltage, new TA current < 1000mA */
+ DC_STATE_ADJUST_TACUR, /* Adjust TA current, new TA current < 1000mA */
+ DC_STATE_MAX,
+};
+
+
+/* CC Mode Status */
+enum {
+ CCMODE_CHG_LOOP, /* TODO: There is no such thing */
+ CCMODE_VFLT_LOOP,
+ CCMODE_IIN_LOOP,
+ CCMODE_LOOP_INACTIVE,
+ CCMODE_VIN_UVLO,
+};
+
+/* CV Mode Status */
+enum {
+ CVMODE_CHG_LOOP, /* TODO: There is no such thing */
+ CVMODE_VFLT_LOOP,
+ CVMODE_IIN_LOOP,
+ CVMODE_LOOP_INACTIVE,
+ CVMODE_CHG_DONE,
+ CVMODE_VIN_UVLO,
+};
+
+/* Timer ID */
+enum {
+ TIMER_ID_NONE,
+ TIMER_VBATMIN_CHECK,
+ TIMER_PRESET_DC,
+ TIMER_PRESET_CONFIG,
+ TIMER_CHECK_ACTIVE,
+ TIMER_ADJUST_CCMODE,
+ TIMER_CHECK_CCMODE,
+ TIMER_ENTER_CVMODE,
+ TIMER_CHECK_CVMODE,
+ TIMER_PDMSG_SEND,
+ TIMER_ADJUST_TAVOL,
+ TIMER_ADJUST_TACUR,
+};
+
+/* PD Message Type */
+enum {
+ PD_MSG_REQUEST_APDO,
+ PD_MSG_REQUEST_FIXED_PDO,
+ WCRX_REQUEST_VOLTAGE,
+};
+
+/* TA increment Type */
+enum {
+ INC_NONE, /* No increment */
+ INC_TA_VOL, /* TA voltage increment */
+ INC_TA_CUR, /* TA current increment */
+};
+
+
+/* TA Type for the direct charging */
+enum {
+ TA_TYPE_UNKNOWN,
+ TA_TYPE_USBPD,
+ TA_TYPE_WIRELESS,
+};
+
+/* Direct Charging Mode for the direct charging */
+enum {
+ CHG_NO_DC_MODE,
+ CHG_2TO1_DC_MODE,
+ CHG_4TO1_DC_MODE,
+};
+
+/* IIN offset as the switching frequency in uA*/
+static int iin_fsw_cfg[16] = { 9990, 10540, 11010, 11520, 12000, 12520, 12990,
+ 13470, 5460, 6050, 6580, 7150, 7670, 8230, 8720,
+ 9260};
+
+/**
+ * struct pca9468_charger - pca9468 charger instance
+ * @monitor_wake_lock: lock to enter the suspend mode
+ * @lock: protects concurrent access to online variables
+ * @dev: pointer to device
+ * @regmap: pointer to driver regmap
+ * @mains: power_supply instance for AC/DC power
+ * @dc_wq: work queue for the algorithm and monitor timer
+ * @timer_work: timer work for charging
+ * @timer_id: timer id for timer_work
+ * @timer_period: timer period for timer_work
+ * @last_update_time: last update time after sleep
+ * @pps_work: pps work for PPS periodic time
+ * @pd: phandle for qualcomm PMI usbpd-phy
+ * @mains_online: is AC/DC input connected
+ * @charging_state: direct charging state
+ * @ret_state: return direct charging state after DC_STATE_ADJUST_TAVOL is done
+ * @iin_cc: input current for the direct charging in cc mode, uA
+ * @ta_cur: AC/DC(TA) current, uA
+ * @ta_vol: AC/DC(TA) voltage, uV
+ * @ta_objpos: AC/DC(TA) PDO object position
+ * @ta_max_cur: TA maximum current of APDO, uA
+ * @ta_max_vol: TA maximum voltage for the direct charging, uV
+ * @ta_max_pwr: TA maximum power, uW
+ * @prev_iin: Previous IIN ADC of PCA9468, uA
+ * @prev_inc: Previous TA voltage or current increment factor
+ * @req_new_iin: Request for new input current limit, true or false
+ * @req_new_vfloat: Request for new vfloat, true or false
+ * @new_iin: New request input current limit, uA
+ * @new_vfloat: New request vfloat, uV
+ * @adc_comp_gain: adc gain for compensation
+ * @retry_cnt: retry counter for re-starting charging if charging stop happens
+ * @ta_type: TA type for the direct charging, USBPD TA or Wireless Charger.
+ * @chg_mode: supported DC charging mode 2:1 or 4:1 mode
+ * @pdata: pointer to platform data
+ * @debug_root: debug entry
+ * @debug_address: debug register address
+ */
+struct pca9468_charger {
+ struct wakeup_source monitor_wake_lock;
+ struct mutex lock;
+ struct device *dev;
+ struct regmap *regmap;
+ struct power_supply *mains;
+
+ struct workqueue_struct *dc_wq;
+ struct delayed_work timer_work;
+ unsigned int timer_id;
+ unsigned long timer_period;
+ unsigned long last_update_time;
+
+ /* PPS implementation */
+ struct delayed_work pps_work;
+ struct power_supply *pd;
+ const char *tcpm_psy_name;
+ u32 tcpm_phandle;
+ struct pd_pps_data pps_data;
+
+ bool mains_online;
+ unsigned int charging_state;
+ unsigned int ret_state;
+
+ unsigned int iin_cc;
+
+ unsigned int ta_cur;
+ unsigned int ta_vol;
+ unsigned int ta_objpos;
+
+ /* same as pps_data */
+ unsigned int ta_max_cur;
+ unsigned int ta_max_vol;
+ unsigned long ta_max_pwr;
+
+ unsigned int prev_iin;
+ unsigned int prev_inc;
+
+ bool req_new_iin;
+ bool req_new_vfloat;
+
+ /* requested charging current and voltage */
+ int fv_uv;
+ int cc_max;
+
+ unsigned int new_iin;
+ unsigned int new_vfloat;
+
+ int adc_comp_gain;
+
+ int retry_cnt;
+
+ int ta_type;
+ unsigned int chg_mode;
+
+ struct pca9468_platform_data *pdata;
+
+ /* debug */
+ struct dentry *debug_root;
+ u32 debug_address;
+ int debug_adc_channel;
+};
+
+/* ------------------------------------------------------------------------- */
+
+/* Configure GCPM not needed */
+static int pca9468_set_switching_charger(bool enable,
+ unsigned int input_current,
+ unsigned int charging_current,
+ unsigned int vfloat)
+{
+ int ret = 0;
+
+ /* handled in GCPM */
+
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* force OFF, handled in GCPM */
+static int pca9468_get_swc_property(enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = 0;
+ break;
+ default:
+ pr_err("%s: cannot set prop %d\n", __func__, prop);
+ break;
+ }
+
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* switch PDO if needed */
+static int pca9468_request_pdo(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+
+ pr_debug("%s: ta_objpos=%u, ta_vol=%u, ta_cur=%u\n", __func__,
+ pca9468->ta_objpos, pca9468->ta_vol, pca9468->ta_cur);
+
+ /*
+ * the reference implementation call pps_request_pdo() twice with a
+ * 100 ms delay between the calls when the function returns -EBUSY:
+ *
+ * ret = pps_request_pdo(&pca9468->pps_data, pca9468->ta_objpos,
+ * pca9468->ta_vol, pca9468->ta_cur,
+ * pca9468->pd);
+ *
+ * The wrapper in google_dc_pps route the calls to the tcpm engine
+ * via tcpm_update_sink_capabilities(). The sync capabilities are
+ * in pps_data, ->ta_objpos select the (A)PDO index, ->ta_vol and
+ * ->ta_cur are the desired TA voltage and current.
+ *
+ * this is now handled by pps_update_adapter()
+ *
+ * TODO: verify the timing and make sure that there are no races that
+ * cause the targets
+ */
+
+ return ret;
+}
+
+static int pca9468_usbpd_setup(struct pca9468_charger *pca9468)
+{
+ struct power_supply *tcpm_psy;
+ bool online;
+ int ret = 0;
+
+ if (pca9468->pd != NULL)
+ goto check_online;
+
+ if (!pca9468->tcpm_phandle)
+ return -ENOENT;
+
+ tcpm_psy = pps_get_tcpm_psy(pca9468->dev->of_node, 2);
+ if (IS_ERR(tcpm_psy))
+ return PTR_ERR(tcpm_psy);
+ if (!tcpm_psy) {
+ pca9468->tcpm_phandle = 0;
+ return -ENODEV;
+ }
+
+ pca9468->tcpm_psy_name = tcpm_psy->desc->name;
+ pca9468->pd = tcpm_psy;
+
+check_online:
+ online = pps_prog_check_online(&pca9468->pps_data, pca9468->pd);
+ if (!online)
+ return -ENODEV;
+
+ return ret;
+}
+
+
+/* Send Request message to the source */
+/* call holding mutex_lock(&pca9468->lock); */
+static int pca9468_send_pd_message(struct pca9468_charger *pca9468,
+ unsigned int msg_type)
+{
+ struct pd_pps_data *pps_data = &pca9468->pps_data;
+ struct power_supply *tcpm_psy = pca9468->pd;
+ bool online;
+ int pps_ui;
+ int ret;
+
+ mutex_lock(&pca9468->lock);
+
+ pr_info("%s: tcpm_psy_ok=%d charging_state=%u",
+ __func__, tcpm_psy != 0, pca9468->charging_state);
+
+ if (!tcpm_psy || (pca9468->charging_state == DC_STATE_NO_CHARGING &&
+ msg_type == PD_MSG_REQUEST_APDO) || !pca9468->mains_online) {
+ mutex_unlock(&pca9468->lock);
+ return -EINVAL;
+ }
+
+ /* false when offline (0) or not in prog (1) mode */
+ online = pps_prog_check_online(&pca9468->pps_data, tcpm_psy);
+ if (!online) {
+ mutex_unlock(&pca9468->lock);
+ pr_debug("%s: offline ret=%d\n", __func__, ret);
+ return -EINVAL;
+ }
+
+ /* request offline */
+ if (msg_type == PD_MSG_REQUEST_FIXED_PDO) {
+ ret = pps_prog_offline(&pca9468->pps_data, tcpm_psy);
+ pr_debug("%s: request offline ret=%d\n", __func__, ret);
+ /* TODO: reset state? */
+ mutex_unlock(&pca9468->lock);
+ return ret;
+ }
+
+ pr_info("%s: tcpm_psy_ok=%d pd_online=%d pps_stage=%d charging_state=%u",
+ __func__, tcpm_psy != 0, pps_data->pd_online,
+ pps_data->stage, pca9468->charging_state);
+
+ if (pca9468->pps_data.stage == PPS_ACTIVE) {
+
+ /* not sure I need to do this */
+ ret = pca9468_request_pdo(pca9468);
+ if (ret == 0) {
+ const int pre_out_uv = pps_data->out_uv;
+ const int pre_out_ua = pps_data->op_ua;
+
+ pr_debug("%s: ta_vol=%u, ta_cur=%u, ta_objpos=%u\n",
+ __func__, pca9468->ta_vol, pca9468->ta_cur,
+ pca9468->ta_objpos);
+
+ pps_ui = pps_update_adapter(&pca9468->pps_data,
+ pca9468->ta_vol,
+ pca9468->ta_cur,
+ tcpm_psy);
+
+ pr_info("%s: out_uv=%d %d->%d, out_ua=%d %d->%d (%d)\n",
+ __func__,
+ pps_data->out_uv, pre_out_uv, pca9468->ta_vol,
+ pps_data->op_ua, pre_out_ua, pca9468->ta_cur,
+ pps_ui);
+
+ if (pps_ui < 0)
+ pps_ui = 1000;
+ } else {
+ pr_debug("%s: request_pdo failed ret=%d\n",
+ __func__, ret);
+ pps_ui = 1000;
+ }
+
+ } else {
+ ret = pps_keep_alive(pps_data, tcpm_psy);
+ if (ret == 0)
+ pps_ui = PD_T_PPS_TIMEOUT;
+
+ pr_debug("%s: dkeep alive ret=%d\n", __func__, ret);
+ }
+
+ if (((pca9468->charging_state == DC_STATE_NO_CHARGING) &&
+ (msg_type == PD_MSG_REQUEST_APDO)) ||
+ (pca9468->mains_online == false)) {
+
+ /*
+ * Vbus reset might occour even when PD comms is successful.
+ * Check again.
+ */
+ pps_ui = -EINVAL;
+ }
+
+ pr_debug("%s: pps_ui = %d\n", __func__, pps_ui);
+ if (pps_ui > 0)
+ schedule_delayed_work(&pca9468->pps_work, msecs_to_jiffies(pps_ui));
+
+ mutex_unlock(&pca9468->lock);
+ return pps_ui;
+}
+
+/* Get the max current/voltage/power of APDO from the CC/PD driver */
+/* This function needs some modification by a customer */
+static int pca9468_get_apdo_max_power(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+
+ /* check the phandle */
+ ret = pca9468_usbpd_setup(pca9468);
+ if (ret != 0) {
+ dev_err(pca9468->dev, "cannot find TCPM %d\n", ret);
+ pca9468->pd = NULL;
+ return ret;
+ }
+
+ ret = pps_get_src_cap(&pca9468->pps_data, pca9468->pd);
+ if (ret < 0)
+ return ret;
+
+ ret = pps_get_apdo_max_power(&pca9468->pps_data, &pca9468->ta_objpos,
+ &pca9468->ta_max_vol, &pca9468->ta_max_cur,
+ &pca9468->ta_max_pwr);
+ if (ret < 0) {
+ pr_err("cannot determine the apdo max power ret = %d\n", ret);
+ return ret;
+ }
+
+ pr_info("%s APDO pos=%u max_v=%u max_c=%u max_pwr=%lu\n", __func__,
+ pca9468->ta_objpos, pca9468->ta_max_vol, pca9468->ta_max_cur,
+ pca9468->ta_max_pwr);
+
+ return 0;
+}
+
+/******************/
+/* Set RX voltage */
+/******************/
+/* Send RX voltage to RX IC */
+/* This function needs some modification by a customer */
+static int pca9468_send_rx_voltage(struct pca9468_charger *pca9468,
+ unsigned int msg_type)
+{
+ struct power_supply *psy;
+ union power_supply_propval pro_val;
+ int ret = 0;
+
+ mutex_lock(&pca9468->lock);
+
+ if (pca9468->mains_online == false) {
+ /* Vbus reset happened in the previous PD communication */
+ goto out;
+ }
+
+ pr_info("%s: rx_vol=%d\n", __func__, pca9468->ta_vol);
+
+ /* Need to implement send RX voltage to wireless RX IC */
+
+ /* The below code should be modified by the customer */
+ /* Get power supply name */
+ psy = power_supply_get_by_name("wireless");
+ if (!psy) {
+ dev_err(pca9468->dev, "Cannot find wireless power supply\n");
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /* Set the RX voltage */
+ pro_val.intval = pca9468->ta_vol;
+
+ /* Set the property */
+ ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ &pro_val);
+ power_supply_put(psy);
+ if (ret < 0) {
+ dev_err(pca9468->dev, "Cannot set RX voltage\n");
+ return ret;
+ }
+
+out:
+ if (pca9468->mains_online == false) {
+ /* Vbus reset might happen, check the charging state again */
+ ret = -EINVAL;
+ }
+
+ pr_info("%s: ret=%d\n", __func__, ret);
+ mutex_unlock(&pca9468->lock);
+ return ret;
+}
+
+
+/************************/
+/* Get RX max power */
+/************************/
+/* Get the max current/voltage/power of RXIC from the WCRX driver */
+/* This function needs some modification by a customer */
+static int pca9468_get_rx_max_power(struct pca9468_charger *pca9468)
+{
+ struct power_supply *psy;
+ union power_supply_propval pro_val;
+ int ret = 0;
+
+ /* insert code */
+
+ /* Get power supply name */
+ psy = power_supply_get_by_name("wireless");
+ if (!psy) {
+ dev_err(pca9468->dev, "Cannot find wireless power supply\n");
+ ret = -ENODEV;
+ goto error;
+ }
+
+ /* Get the maximum voltage */
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ &pro_val);
+ if (ret < 0) {
+ dev_err(pca9468->dev, "Cannot get the maximum RX voltage\n");
+ goto error;
+ }
+
+ if (pca9468->ta_max_vol > pro_val.intval) {
+ /* RX IC cannot support the request maximum voltage */
+ ret = -EINVAL;
+ goto error;
+ } else {
+ pca9468->ta_max_vol = pro_val.intval;
+ }
+
+ /* Get the maximum current */
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ &pro_val);
+ if (ret < 0) {
+ dev_err(pca9468->dev, "Cannot get the maximum RX current\n");
+ goto error;
+ }
+
+ pca9468->ta_max_cur = pro_val.intval;
+ pca9468->ta_max_pwr = (pca9468->ta_max_vol / 1000) *
+ (pca9468->ta_max_cur / 1000);
+
+error:
+ power_supply_put(psy);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* ADC Read function, return uV or uA */
+static int pca9468_read_adc(struct pca9468_charger *pca9468, u8 adc_ch)
+{
+ u8 reg_data[2];
+ u16 raw_adc;
+ int conv_adc;
+ int ret;
+
+
+ switch (adc_ch) {
+ case ADCCH_VOUT:
+ /* ~PCA9468_BIT_CH1_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_4,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_VOUT9_2) << 2) |
+ ((reg_data[0] & PCA9468_BIT_ADC_VOUT1_0) >> 6);
+ conv_adc = raw_adc * VOUT_STEP; /* unit - uV */
+ break;
+
+ case ADCCH_VIN:
+ /* ~PCA9468_BIT_CH2_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_3,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_VIN9_4) << 4) |
+ ((reg_data[0] & PCA9468_BIT_ADC_VIN3_0) >> 4);
+ conv_adc = raw_adc * VIN_STEP; /* unit - uV */
+ break;
+
+ case ADCCH_VBAT:
+ /* ~PCA9468_BIT_CH3_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_6,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_VBAT9_8) << 8) |
+ ((reg_data[0] & PCA9468_BIT_ADC_VBAT7_0) >> 0);
+ conv_adc = raw_adc * VBAT_STEP; /* unit - uV */
+ break;
+
+ /* this doesn't seems right */
+ case ADCCH_ICHG:
+ /* ~PCA9468_BIT_CH4_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_2,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_IOUT9_6) << 6) |
+ ((reg_data[0] & PCA9468_BIT_ADC_IOUT5_0) >> 2);
+ conv_adc = raw_adc * ICHG_STEP; /* unit - uA */
+ break;
+
+ case ADCCH_IIN:
+ /* ~PCA9468_BIT_CH5_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_1,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_IIN9_8) << 8) |
+ ((reg_data[0] & PCA9468_BIT_ADC_IIN7_0) >> 0);
+
+ /*
+ * iin = rawadc*4.89 + (rawadc*4.89 - 900) *
+ * adc_comp_gain/100
+ */
+ conv_adc = raw_adc * IIN_STEP + (raw_adc * IIN_STEP -
+ ADC_IIN_OFFSET) * pca9468->adc_comp_gain /
+ 100; /* unit - uA */
+ /*
+ * If ADC raw value is 0, convert value will be minus value
+ * because of compensation gain, so in this case conv_adc
+ * is 0
+ */
+ if (conv_adc < 0)
+ conv_adc = 0;
+ break;
+
+ case ADCCH_DIETEMP:
+ /* ~PCA9468_BIT_CH6_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_7,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_DIETEMP9_6) << 6) |
+ ((reg_data[0] & PCA9468_BIT_ADC_DIETEMP5_0) >> 2);
+
+ /* Temp = (935-rawadc)*0.435, unit - C */
+ conv_adc = (935 - raw_adc) * DIETEMP_STEP / DIETEMP_DENOM;
+ if (conv_adc > DIETEMP_MAX)
+ conv_adc = DIETEMP_MAX;
+ else if (conv_adc < DIETEMP_MIN)
+ conv_adc = DIETEMP_MIN;
+ break;
+
+ case ADCCH_NTC:
+ /* ~PCA9468_BIT_CH7_EN, PCA9468_REG_ADC_CFG, udelay(120) us */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_ADC_8,
+ reg_data, 2);
+ if (ret < 0) {
+ conv_adc = ret;
+ goto error;
+ }
+
+ raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_NTCV9_4) << 4) |
+ ((reg_data[0] & PCA9468_BIT_ADC_NTCV3_0) >> 4);
+ conv_adc = raw_adc * NTCV_STEP; /* unit - uV */
+ break;
+
+ default:
+ conv_adc = -EINVAL;
+ break;
+ }
+
+error:
+ /* if disabled a channel, re-enable it in -> PCA9468_REG_ADC_CFG */
+
+ pr_debug("%s: adc_ch=%u, raw_adc=%x convert_val=%d\n", __func__,
+ adc_ch, raw_adc, conv_adc);
+
+ return conv_adc;
+}
+
+
+static int pca9468_set_vfloat(struct pca9468_charger *pca9468,
+ unsigned int v_float)
+{
+ int ret, val;
+
+ pr_debug("%s: vfloat=%u\n", __func__, v_float);
+
+ /* v float voltage */
+ val = PCA9468_V_FLOAT(v_float);
+
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_V_FLOAT, val);
+ return ret;
+}
+
+static int pca9468_set_charging_current(struct pca9468_charger *pca9468,
+ unsigned int ichg)
+{
+ /*
+ * charging current cannot be controlled directly: SW needs to
+ * calculate the combination of TA_VOUT/IOUT to achieve no more
+ * than cc_max to the battery.
+ *
+ * This should be done setting new_iin and/or adjusting ta_vout
+ pca9468->new_iin = val->intval;
+ ret = pca9468_set_new_iin(pca9468);
+ */
+
+ pr_debug("%s: ichg=%u\n", __func__, ichg);
+ pca9468->cc_max = ichg;
+
+ return 0;
+}
+
+static int pca9468_set_input_current(struct pca9468_charger *pca9468,
+ unsigned int iin)
+{
+ int ret, val;
+
+ pr_debug("%s: iin=%u\n", __func__, iin);
+
+
+ /* round-up and increase one step */
+ iin = iin + PD_MSG_TA_CUR_STEP;
+ val = PCA9468_IIN_CFG(iin);
+
+ /* Set IIN_CFG to one step higher */
+ val = val + 1;
+ if (val > 0x32)
+ val = 0x32; /* maximum value is 5A */
+
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_IIN_CTRL,
+ PCA9468_BIT_IIN_CFG, val);
+
+ pr_debug("%s: real iin_cfg=%d\n", __func__,
+ val * PCA9468_IIN_CFG_STEP);
+ return ret;
+}
+
+static int pca9468_set_charging(struct pca9468_charger *pca9468, bool enable)
+{
+ int ret, val;
+
+ pr_debug("%s: enable=%d\n", __func__, enable);
+
+ if (enable) {
+ /* Improve adc */
+ val = 0x5B;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_ADC_ACCESS,
+ val);
+ if (ret < 0)
+ goto error;
+ ret = regmap_update_bits(pca9468->regmap,
+ PCA9468_REG_ADC_IMPROVE,
+ PCA9468_BIT_ADC_IIN_IMP, 0);
+ if (ret < 0)
+ goto error;
+
+ /* For fixing input current error */
+ /* Overwrite 0x00 in 0x41 register */
+ val = 0x00;
+ ret = regmap_write(pca9468->regmap, 0x41, val);
+ if (ret < 0)
+ goto error;
+ /* Overwrite 0x01 in 0x43 register */
+ val = 0x01;
+ ret = regmap_write(pca9468->regmap, 0x43, val);
+ if (ret < 0)
+ goto error;
+ /* Overwrite 0x00 in 0x4B register */
+ val = 0x00;
+ ret = regmap_write(pca9468->regmap, 0x4B, val);
+ if (ret < 0)
+ goto error;
+ /* End for fixing input current error */
+
+ } else {
+ /* Disable NTC_PROTECTION_EN */
+ ret = regmap_update_bits(pca9468->regmap,
+ PCA9468_REG_TEMP_CTRL,
+ PCA9468_BIT_NTC_PROTECTION_EN, 0);
+ }
+
+ /* Enable PCA9468 */
+ val = enable ? PCA9468_STANDBY_DONOT : PCA9468_STANDBY_FORCED;
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_START_CTRL,
+ PCA9468_BIT_STANDBY_EN, val);
+ if (ret < 0)
+ goto error;
+
+ if (enable) {
+ /* Wait 50ms, first to keep the start-up sequence */
+ mdelay(50);
+ /* Wait 150ms */
+ msleep(150);
+
+ /* Improve ADC */
+ ret = regmap_update_bits(pca9468->regmap,
+ PCA9468_REG_ADC_IMPROVE,
+ PCA9468_BIT_ADC_IIN_IMP,
+ PCA9468_BIT_ADC_IIN_IMP);
+ if (ret < 0)
+ goto error;
+
+ val = 0x00;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_ADC_ACCESS,
+ val);
+
+ /* Enable NTC_PROTECTION_EN TODO: disable */
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_TEMP_CTRL,
+ PCA9468_BIT_NTC_PROTECTION_EN,
+ PCA9468_BIT_NTC_PROTECTION_EN);
+ } else {
+ /* Wait 5ms to keep the shutdown sequence */
+ mdelay(5);
+ }
+
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int pca9468_check_state(u8 val[8], struct pca9468_charger *pca9468)
+{
+ int ret;
+
+ /* Dump register */
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_INT1,
+ &val[PCA9468_REG_INT1], 7);
+ if (ret < 0)
+ return ret;
+
+ pr_err("%s: Error reg[1]=0x%x,[2]=0x%x,[3]=0x%x,[4]=0x%x,[5]=0x%x,[6]=0x%x,[7]=0x%x\n",
+ __func__, val[1], val[2], val[3], val[4], val[5], val[6],
+ val[7]);
+
+ return 0;
+}
+
+static void pca9468_dump_test_debug(struct pca9468_charger *pca9468)
+{
+ u8 test_val[16];
+ int ret;
+
+ /* Read test register for debugging */
+ ret = regmap_bulk_read(pca9468->regmap, 0x40, test_val, 16);
+ if (ret < 0) {
+ pr_err("%s: cannot read test registers (%d)\n", __func__, ret);
+ } else {
+
+ pr_err("%s: Error reg[0x40]=0x%x,[0x41]=0x%x,[0x42]=0x%x,[0x43]=0x%x,[0x44]=0x%x,[0x45]=0x%x,[0x46]=0x%x,[0x47]=0x%x\n",
+ __func__, test_val[0], test_val[1], test_val[2], test_val[3],
+ test_val[4], test_val[5], test_val[6], test_val[7]);
+ pr_err("%s: Error reg[0x48]=0x%x,[0x49]=0x%x,[0x4A]=0x%x,[0x4B]=0x%x,[0x4C]=0x%x,[0x4D]=0x%x,[0x4E]=0x%x,[0x4F]=0x%x\n",
+ __func__, test_val[8], test_val[9], test_val[10], test_val[11],
+ test_val[12], test_val[13], test_val[14], test_val[15]);
+ }
+}
+
+/* PCA9468 is not active state - standby or shutdown */
+/* Stop charging in timer_work */
+/* return 0 when no error is detected */
+static int pca9468_check_not_active(struct pca9468_charger *pca9468)
+{
+ u8 val[8];
+ int ret;
+
+ ret = pca9468_check_state(val, pca9468);
+ if (ret < 0) {
+ pr_err("%s: cannot read state\n", __func__);
+ return ret;
+ }
+
+ pca9468_dump_test_debug(pca9468);
+
+ /* Check INT1_STS first */
+ if ((val[PCA9468_REG_INT1_STS] & PCA9468_BIT_V_OK_STS) != PCA9468_BIT_V_OK_STS) {
+ /* VBUS is invalid */
+ pr_err("%s: VOK is invalid", __func__);
+
+ /* Check STS_A. NOTE: V_OV_TRACKING is with VIN OV */
+ if (val[PCA9468_REG_STS_A] & PCA9468_BIT_CFLY_SHORT_STS)
+ pr_err("%s: Flying Cap is shorted to GND", __func__);
+ else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VOUT_UV_STS)
+ pr_err("%s: VOUT UV", __func__); /* VOUT < VOUT_OK */
+ else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VBAT_OV_STS)
+ pr_err("%s: VBAT OV", __func__); /* VBAT > VBAT_OV */
+ else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VIN_OV_STS)
+ pr_err("%s: VIN OV", __func__); /* VIN > V_OV_FIXED */
+ else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VIN_UV_STS)
+ pr_err("%s: VIN UV", __func__); /* VIN < V_UVTH */
+ else
+ pr_err("%s: Invalid VIN or VOUT", __func__);
+
+ return -EINVAL;
+ }
+
+ if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_NTC_TEMP_STS) {
+ int ntc_adc, ntc_th; /* NTC protection */
+ u8 reg_data[2]; /* NTC threshold */
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_NTC_TH_1,
+ reg_data, sizeof(reg_data));
+ if (ret < 0)
+ return -EIO;
+
+ ntc_th = ((reg_data[1] & PCA9468_BIT_NTC_THRESHOLD9_8) << 8) |
+ reg_data[0]; /* uV unit */
+
+ /* Read NTC ADC */
+ ntc_adc = pca9468_read_adc(pca9468, ADCCH_NTC); /* uV unit */
+ pr_err("%s: NTC Protection, NTC_TH=%d(uV), NTC_ADC=%d(uV)",
+ __func__, ntc_th, ntc_adc);
+
+ return -EINVAL;
+ }
+
+ if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_CTRL_LIMIT_STS) {
+ /* OCP event happens */
+
+ if (val[PCA9468_REG_STS_B] & PCA9468_BIT_OCP_FAST_STS)
+ pr_err("%s: IIN is over OCP_FAST", __func__);
+ else if (val[PCA9468_REG_STS_B] & PCA9468_BIT_OCP_AVG_STS)
+ pr_err("%s: IIN is over OCP_AVG", __func__);
+ else
+ pr_err("%s: No Loop active", __func__);
+
+ return -EINVAL;
+ }
+
+ if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_TEMP_REG_STS) {
+ /* Over temperature protection */
+ pr_err("%s: Device is in temperature regulation", __func__);
+ return -EINVAL;
+ }
+
+ if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_TIMER_STS) {
+ const u8 sts_b = val[PCA9468_REG_STS_B];
+
+ if (sts_b & PCA9468_BIT_CHARGE_TIMER_STS)
+ pr_err("%s: Charger timer is expired", __func__);
+ else if (sts_b & PCA9468_BIT_WATCHDOG_TIMER_STS)
+ pr_err("%s: Watchdog timer is expired", __func__);
+ else
+ pr_err("%s: Timer INT, but no timer STS", __func__);
+
+ return -EINVAL;
+ }
+
+ if (val[PCA9468_REG_STS_A] & PCA9468_BIT_CFLY_SHORT_STS) {
+ /* Flying cap is short to GND */
+ pr_err("%s: Flying Cap is shorted to GND", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Keep the current charging state, check STS_B again */
+/* return 0 if VIN is still present, -EAGAIN if needs to retry, -EINVAL oth */
+static int pca9468_check_standby(struct pca9468_charger *pca9468)
+{
+ unsigned int reg_val;
+ int ret;
+ u8 val[8];
+
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_STS_B, ®_val);
+ if (ret < 0)
+ return -EIO;
+
+ pr_info("%s: RCP check, STS_B=0x%x\n", __func__, reg_val);
+
+ /* Check Active status */
+ if (reg_val & PCA9468_BIT_ACTIVE_STATE_STS) {
+ /* RCP condition happened, but VIN is still valid */
+
+ /* If VIN is increased, input current will increased over
+ * IIN_LOW level
+ */
+ /* Normal charging */
+ return 0;
+ }
+
+ /* It is not RCP condition */
+ if (reg_val & PCA9468_BIT_STANDBY_STATE_STS) {
+ /* Need to retry if DC is in starting state */
+ pr_err("%s: Any abnormal condition, retry\n", __func__);
+ ret = -EAGAIN;
+ } else {
+ pr_err("%s: Shutdown state\n", __func__);
+ ret = -EINVAL;
+ }
+
+ /* Dump registers again */
+ pca9468_check_state(val, pca9468);
+ ret = regmap_bulk_read(pca9468->regmap, 0x48, val, 3);
+ pr_err("%s: Error reg[0x48]=0x%x,[0x49]=0x%x,[0x4a]=0x%x\n",
+ __func__, val[0], val[1], val[2]);
+
+ return ret;
+}
+
+/* Check Active status */
+static int pca9468_check_error(struct pca9468_charger *pca9468)
+{
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_STS_B, ®_val);
+ if (ret < 0)
+ goto error;
+
+ /* PCA9468 is active state */
+ if (reg_val & PCA9468_BIT_ACTIVE_STATE_STS) {
+ int vbatt;
+
+ /* PCA9468 is charging */
+
+ /* Check whether the battery voltage is over the minimum */
+ vbatt = pca9468_read_adc(pca9468, ADCCH_VBAT);
+ if (vbatt > PCA9468_DC_VBAT_MIN) {
+ /* Normal charging battery level */
+ /* Check temperature regulation loop */
+ /* Read INT1_STS register */
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_INT1_STS,
+ ®_val);
+ if (ret < 0) {
+ pr_err("%s: cannot read status (%d)\n",
+ __func__, ret);
+ } else if (reg_val & PCA9468_BIT_TEMP_REG_STS) {
+ /* Over temperature protection */
+ pr_err("%s: Device is in temperature regulation",
+ __func__);
+ ret = -EINVAL;
+ }
+ } else {
+ /* Abnormal battery level */
+ pr_err("%s: Error abnormal battery voltage=%d\n",
+ __func__, vbatt);
+ ret = -EINVAL;
+ }
+
+ pr_info("%s: Active Status=%d\n", __func__, ret);
+ return ret;
+ }
+
+ /* not in error but in standby or shutdown */
+ /* will stop charging in timer_work */
+
+ ret = pca9468_check_not_active(pca9468);
+ if (ret < 0) {
+ /* There was an error, done... */
+ } else if ((reg_val & PCA9468_BIT_STANDBY_STATE_STS) == 0) {
+ /* PCA9468 is in shutdown state */
+ pr_err("%s: PCA9468 is in shutdown\n", __func__);
+ ret = -EINVAL;
+ } else {
+ /* Standby? state */
+
+ /* Check the RCP condition, T_REVI_DET is 300ms */
+ /* Wait 200ms */
+ msleep(200);
+
+ /*
+ * Sometimes battery driver might call set_property function
+ * to stop charging during msleep. At this case, charging
+ * state would change DC_STATE_NO_CHARGING. PCA9468 should
+ * stop checking RCP condition and exit timer_work
+ */
+ if (pca9468->charging_state == DC_STATE_NO_CHARGING) {
+ pr_err("%s: other driver forced to stop direct charging\n",
+ __func__);
+ ret = -EINVAL;
+ } else {
+
+ ret = pca9468_check_standby(pca9468);
+ if (ret == 0) {
+ pr_info("%s: RCP happened, but VIN is valid\n",
+ __func__);
+ }
+ }
+ }
+
+error:
+ pr_info("%s: Not Active Status=%d\n", __func__, ret);
+ return -EINVAL;
+}
+
+/* Check CC Mode status */
+static int pca9468_check_ccmode_status(struct pca9468_charger *pca9468)
+{
+ unsigned int reg_val;
+ int ret;
+
+ /* Read STS_A */
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_STS_A, ®_val);
+ if (ret < 0)
+ goto error;
+
+ /* Check STS_A */
+ if (reg_val & PCA9468_BIT_VIN_UV_STS) {
+ ret = CCMODE_VIN_UVLO;
+ } else if (reg_val & PCA9468_BIT_CHG_LOOP_STS) {
+ ret = CCMODE_CHG_LOOP;
+ } else if (reg_val & PCA9468_BIT_VFLT_LOOP_STS) {
+ ret = CCMODE_VFLT_LOOP;
+ } else if (reg_val & PCA9468_BIT_IIN_LOOP_STS) {
+ ret = CCMODE_IIN_LOOP;
+ } else {
+ ret = CCMODE_LOOP_INACTIVE;
+ }
+
+error:
+ pr_info("%s: CCMODE Status=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* Check CVMode Status */
+static int pca9468_check_cvmode_status(struct pca9468_charger *pca9468)
+{
+ unsigned int val;
+ int ret;
+
+ /* Read STS_A */
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_STS_A, &val);
+ if (ret < 0)
+ goto error;
+
+ /* Check STS_A */
+ if (val & PCA9468_BIT_CHG_LOOP_STS) {
+ ret = CVMODE_CHG_LOOP;
+ } else if (val & PCA9468_BIT_VFLT_LOOP_STS) {
+ ret = CVMODE_VFLT_LOOP;
+ } else if (val & PCA9468_BIT_IIN_LOOP_STS) {
+ ret = CVMODE_IIN_LOOP;
+ } else if (val & PCA9468_BIT_VIN_UV_STS) {
+ ret = CVMODE_VIN_UVLO;
+ } else {
+ /* Any LOOP is inactive */
+ ret = CVMODE_LOOP_INACTIVE;
+ }
+
+error:
+ pr_info("%s: CVMODE Status=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* Stop Charging */
+static int pca9468_stop_charging(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+
+ /* Check the current state */
+ if (pca9468->charging_state == DC_STATE_NO_CHARGING)
+ goto done;
+
+ /* Recover switching charger ICL */
+ ret = pca9468_set_switching_charger(true, SWCHG_ICL_NORMAL,
+ pca9468->pdata->ichg_cfg,
+ pca9468->pdata->v_float);
+ if (ret < 0) {
+ pr_err("%s: Error-set_switching charger ICL\n", __func__);
+ goto error;
+ }
+
+ /* Stop Direct charging */
+ cancel_delayed_work(&pca9468->timer_work);
+ cancel_delayed_work(&pca9468->pps_work);
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ID_NONE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Clear parameter */
+ pca9468->charging_state = DC_STATE_NO_CHARGING;
+ pca9468->ret_state = DC_STATE_NO_CHARGING;
+ pca9468->prev_iin = 0;
+ pca9468->prev_inc = INC_NONE;
+ pca9468->req_new_iin = false;
+ pca9468->req_new_vfloat = false;
+ pca9468->chg_mode = CHG_NO_DC_MODE;
+
+ /* Set IIN_CFG and VFLOAT to the default value */
+ pca9468->pdata->iin_cfg = PCA9468_IIN_CFG_DFT;
+ pca9468->pdata->v_float = PCA9468_VFLOAT_DFT;
+
+ /* Clear new Vfloat and new IIN */
+ pca9468->new_vfloat = pca9468->pdata->v_float;
+ pca9468->new_iin = pca9468->pdata->iin_cfg;
+
+ /* Clear retry counter */
+ pca9468->retry_cnt = 0;
+
+ ret = pca9468_set_charging(pca9468, false);
+ if (ret < 0) {
+ pr_err("%s: Error-set_charging(main)\n", __func__);
+ goto error;
+ }
+
+ if (pca9468->mains_online == true) {
+ /* Recover TA voltage */
+
+ /* It needs some modification by a customer */
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Set RX voltage to 9V */
+ pca9468->ta_vol = 9000000;
+ /* Send RX voltage */
+ ret = pca9468_send_rx_voltage(pca9468,
+ WCRX_REQUEST_VOLTAGE);
+ } else {
+ /* Set TA voltage to fixed 5V */
+ pca9468->ta_vol = 5000000;
+ /* Set TA current to maximum 3A */
+ pca9468->ta_cur = 3000000;
+
+ /* Send PD Message */
+ pca9468->ta_objpos = 1; /* PDO1 - fixed 5V */
+ ret = pca9468_send_pd_message(pca9468,
+ PD_MSG_REQUEST_FIXED_PDO);
+ }
+
+ if (ret < 0)
+ pr_err("%s: Error-send_pd_message\n", __func__);
+
+ }
+
+ power_supply_changed(pca9468->mains);
+
+done:
+error:
+ __pm_relax(&pca9468->monitor_wake_lock);
+ pr_debug("%s: END, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* Compensate TA current for the target input current */
+static int pca9468_set_ta_current_comp(struct pca9468_charger *pca9468)
+{
+ int iin;
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ pr_debug("%s: iin=%d\n", __func__, iin);
+
+ /* Compare IIN ADC with target input current */
+ if (iin > (pca9468->iin_cc + PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* TA current is higher than the target input current */
+ /* Compare TA current with IIN_CC */
+ if (pca9468->ta_cur > pca9468->iin_cc) {
+ /* TA current is over than IIN_CC */
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: Comp. Cont1: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+
+ /* TA current is already less than IIN_CC */
+ /* Compara IIN_ADC with the previous IIN_ADC */
+ } else if (iin < (pca9468->prev_iin - PCA9468_IIN_ADC_OFFSET)) {
+ /* Assume that TA operation mode is CV mode */
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: Comp. Cont2-1: ta_vol=%u\n", __func__,
+ pca9468->ta_vol);
+ } else {
+ /* Assume TA operation mode is CL mode */
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur -
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: Comp. Cont2-2: ta_cur=%u\n", __func__,
+ pca9468->ta_cur);
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ } else if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) {
+
+ /* compare IIN ADC with previous IIN ADC + 20mA */
+ if (iin > (pca9468->prev_iin + PCA9468_IIN_ADC_OFFSET)) {
+ /*
+ * TA voltage is not enough to supply the operating
+ * current of RDO: increase TA voltage
+ */
+
+ /* Compare TA max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* TA voltage is already the maximum voltage */
+ /* Compare TA max current */
+ if (pca9468->ta_cur == pca9468->ta_max_cur) {
+ /* TA voltage and current are at max */
+ pr_debug("%s: Comp. End1: ta_vol=%u, ta_cur=%u\n",
+ __func__, pca9468->ta_vol,
+ pca9468->ta_cur);
+
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period =
+ PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Increase TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur +
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: Comp. Cont3: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_CUR;
+ }
+ } else {
+ /* Increase TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: Comp. Cont4: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_VOL;
+ }
+
+ /* TA current is lower than the target input current */
+ /* Check the previous TA increment */
+ } else if (pca9468->prev_inc == INC_TA_VOL) {
+ /*
+ * The previous increment is TA voltage, but
+ * input current does not increase.
+ */
+
+ /* Try to increase TA current */
+ /* Compare TA max current */
+ if (pca9468->ta_cur == pca9468->ta_max_cur) {
+
+ /* TA current is already the maximum current */
+ /* Compare TA max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /*
+ * TA voltage and current are already
+ * the maximum values
+ */
+ pr_debug("%s: Comp. End2: ta_vol=%u, ta_cur=%u\n",
+ __func__, pca9468->ta_vol,
+ pca9468->ta_cur);
+
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period =
+ PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Increase TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: Comp. Cont5: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_VOL;
+ }
+ } else {
+ /* Increase TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur +
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: Comp. Cont6: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_CUR;
+ }
+ } else {
+ /*
+ * The previous increment is TA current,
+ * but input current does not increase.
+ */
+
+ /* Try to increase TA voltage */
+ /* Compare TA max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* TA voltage is already the maximum voltage */
+ /* Compare TA maximum current */
+ if (pca9468->ta_cur == pca9468->ta_max_cur) {
+ /* TA voltage and current are already
+ * the maximum values
+ */
+ pr_debug("%s: Comp. End3: ta_vol=%u, ta_cur=%u\n",
+ __func__, pca9468->ta_vol,
+ pca9468->ta_cur);
+
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period =
+ PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Increase TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur +
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: Comp. Cont7: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_CUR;
+ }
+ } else {
+ /* Increase TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: Comp. Cont8: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_VOL;
+ }
+ }
+ } else {
+ /* IIN ADC is in valid range */
+ /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */
+ pr_debug("%s: Comp. End4(valid): ta_vol=%u, ta_cur=%u\n",
+ __func__, pca9468->ta_vol, pca9468->ta_cur);
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ }
+
+ /* Save previous iin adc */
+ pca9468->prev_iin = iin;
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+ return 0;
+}
+
+/* Compensate TA current for constant power mode */
+static int pca9468_set_ta_current_comp2(struct pca9468_charger *pca9468)
+{
+ int iin;
+ unsigned int val;
+ unsigned int iin_apdo;
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ pr_debug("%s: iin=%d\n", __func__, iin);
+
+ /* Compare IIN ADC with target input current */
+ if (iin > (pca9468->pdata->iin_cfg + PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* TA current is higher than the target input current limit */
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP;
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET_CP)) {
+
+ /* TA current is lower than the target input current */
+ /* IIN_ADC < IIN_CC -20mA */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ const int iin_cc_lb = pca9468->iin_cc -
+ PCA9468_IIN_CC_COMP_OFFSET;
+
+ /* Check IIN_ADC < IIN_CC - 50mA */
+ if (iin < iin_cc_lb) {
+ /* Set new IIN_CC to IIN_CC - 50mA */
+ pca9468->iin_cc = pca9468->iin_cc -
+ PCA9468_IIN_CC_COMP_OFFSET;
+
+ /* Set new TA_MAX_VOL to TA_MAX_PWR/IIN_CC */
+ /* Adjust new IIN_CC with APDO resolution */
+ iin_apdo = pca9468->iin_cc / PD_MSG_TA_CUR_STEP;
+ iin_apdo = iin_apdo * PD_MSG_TA_CUR_STEP;
+ /* in mV */
+ val = pca9468->ta_max_pwr /
+ (iin_apdo / pca9468->chg_mode / 1000);
+ /* Adjust values with APDO resolution(20mV) */
+ val = val * 1000 / PD_MSG_TA_VOL_STEP;
+ val = val * PD_MSG_TA_VOL_STEP; /* uV */
+
+ /* Set new TA_MAX_VOL */
+ pca9468->ta_max_vol = min(val,
+ PCA9468_TA_MAX_VOL * pca9468->chg_mode);
+ /* Increase TA voltage(40mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PD_MSG_TA_VOL_STEP*2;
+
+ pr_debug("%s: Comp. Cont1: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Wait for next current step compensation */
+ /* IIN_CC - 50mA < IIN ADC < IIN_CC - 20mA */
+ pr_debug("%s: Comp.(wait): ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK2_T;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* Increase TA voltage(40mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PD_MSG_TA_VOL_STEP * 2;
+ if (pca9468->ta_vol > pca9468->ta_max_vol)
+ pca9468->ta_vol = pca9468->ta_max_vol;
+
+ pr_debug("%s: Comp. Cont2: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* IIN ADC is in valid range */
+ /* IIN_CC - 50mA < IIN ADC < IIN_CFG + 50mA */
+ pr_debug("%s: Comp. End(valid): ta_vol=%u\n", __func__,
+ pca9468->ta_vol);
+
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK2_T;
+ mutex_unlock(&pca9468->lock);
+ }
+
+ /* Save previous iin adc */
+ pca9468->prev_iin = iin;
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+ return 0;
+}
+
+/* Compensate TA voltage for the target input current */
+static int pca9468_set_ta_voltage_comp(struct pca9468_charger *pca9468)
+{
+ int iin;
+
+ pr_debug("%s: ======START=======\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ pr_debug("%s: iin=%d\n", __func__, iin);
+
+ /* Compare IIN ADC with target input current */
+ if (iin > (pca9468->iin_cc + PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* TA current is higher than the target input current */
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: Comp. Cont1: ta_vol=%u\n", __func__,
+ pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* TA current is lower than the target input current */
+ /* Compare TA max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* TA is already at maximum voltage */
+ pr_debug("%s: Comp. End1(max TA vol): ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Set timer */
+ /* Check the current charging state */
+ if (pca9468->charging_state == DC_STATE_CC_MODE) {
+ /* CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* CV mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = PCA9468_CVMODE_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* Increase TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: Comp. Cont2: ta_vol=%u\n", __func__,
+ pca9468->ta_vol);
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* IIN ADC is in valid range */
+ /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */
+ pr_debug("%s: Comp. End(valid): ta_vol=%u\n", __func__,
+ pca9468->ta_vol);
+ /* Set timer */
+ /* Check the current charging state */
+ mutex_lock(&pca9468->lock);
+ if (pca9468->charging_state == DC_STATE_CC_MODE) {
+ /* CC mode */
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK1_T;
+ } else {
+ /* CV mode */
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = PCA9468_CVMODE_CHECK_T;
+ }
+ mutex_unlock(&pca9468->lock);
+ }
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+ return 0;
+}
+
+/* Compensate RX voltage for the target input current */
+static int pca9468_set_rx_voltage_comp(struct pca9468_charger *pca9468)
+{
+ int iin;
+
+ pr_debug("%s: ======START=======\n", __func__);
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ pr_debug("%s: iin=%d\n", __func__, iin);
+
+ /* Compare IIN ADC with target input current */
+ if (iin > (pca9468->iin_cc + PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* RX current is higher than the target input current */
+ /* Decrease RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol - WCRX_VOL_STEP;
+ pr_debug("%s: Comp. Cont1: rx_vol=%u\n", __func__,
+ pca9468->ta_vol);
+
+ /* Set RX Voltage */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* RX current is lower than the target input current */
+ /* Compare RX max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+
+ /* TA current is already the maximum voltage */
+ pr_debug("%s: Comp. End1(max RX vol): rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ /* Check the current charging state */
+ if (pca9468->charging_state == DC_STATE_CC_MODE) {
+ /* CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* CV mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = PCA9468_CVMODE_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* Increase RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol + WCRX_VOL_STEP;
+ pr_debug("%s: Comp. Cont2: rx_vol=%u\n", __func__,
+ pca9468->ta_vol);
+
+ /* Set RX Voltage */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* IIN ADC is in valid range */
+ /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */
+ pr_debug("%s: Comp. End(valid): rx_vol=%u\n", __func__,
+ pca9468->ta_vol);
+
+ /* Set timer */
+ /* Check the current charging state */
+ if (pca9468->charging_state == DC_STATE_CC_MODE) {
+ /* CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = PCA9468_CCMODE_CHECK1_T;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* CV mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = PCA9468_CVMODE_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ }
+ }
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+ return 0;
+}
+
+/* Set TA current for target current */
+static int pca9468_adjust_ta_current(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+ int vbat;
+ unsigned int val;
+
+ /* Set charging state to ADJUST_TACUR */
+ pca9468->charging_state = DC_STATE_ADJUST_TACUR;
+
+ if (pca9468->ta_cur == pca9468->iin_cc / pca9468->chg_mode) {
+ /* finish sending PD message */
+ /* Recover IIN_CC to the original value(new_iin) */
+ pca9468->iin_cc = pca9468->new_iin;
+
+ /* Clear req_new_iin */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ pr_debug("%s: adj. End, ta_cur=%u, ta_vol=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_cur, pca9468->ta_vol,
+ pca9468->iin_cc, pca9468->chg_mode);
+
+ /* Go to return state */
+ pca9468->charging_state = pca9468->ret_state;
+ /* Set timer */
+ if (pca9468->charging_state == DC_STATE_CC_MODE)
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ else
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 1000; /* Wait 1000ms */
+ mutex_unlock(&pca9468->lock);
+
+ /* Compare new IIN with IIN_CFG */
+ } else if (pca9468->iin_cc > pca9468->pdata->iin_cfg) {
+ /* New iin is higher than the current iin_cfg */
+
+ /* Update iin_cfg */
+ pca9468->pdata->iin_cfg = pca9468->iin_cc;
+ /* Set IIN_CFG to new IIN */
+ ret = pca9468_set_input_current(pca9468, pca9468->iin_cc);
+ if (ret < 0)
+ goto error;
+
+ /* Clear Request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+
+ /* Set new TA voltage and current */
+ /* Read VBAT ADC */
+ vbat = pca9468_read_adc(pca9468, ADCCH_VBAT);
+
+ /*
+ * Calculate new TA maximum current and voltage that
+ * used in the direct charging
+ */
+
+ /* Set IIN_CC to MIN[IIN, TA_MAX_CUR * chg_mode]*/
+ pca9468->iin_cc = min(pca9468->pdata->iin_cfg,
+ pca9468->ta_max_cur * pca9468->chg_mode);
+ /*
+ * Set the current IIN_CC to iin_cfg for recovering
+ * it after resolution adjustment
+ */
+ pca9468->pdata->iin_cfg = pca9468->iin_cc;
+
+ /* Calculate new TA max voltage */
+ /*
+ * Adjust IIN_CC with APDO resolution(50mA) - It will
+ * recover to the original value after max voltage
+ * calculation
+ */
+ val = pca9468->iin_cc / (PD_MSG_TA_CUR_STEP *
+ pca9468->chg_mode);
+ pca9468->iin_cc = val * (PD_MSG_TA_CUR_STEP *
+ pca9468->chg_mode);
+
+ /* TA_MAX_VOL = MIN[PCA9468_TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */
+ val = pca9468->ta_max_pwr /
+ (pca9468->iin_cc / pca9468->chg_mode / 1000); /* mV */
+ /* Adjust values with APDO resolution(20mV) */
+ val = val * 1000 / PD_MSG_TA_VOL_STEP;
+ val = val * PD_MSG_TA_VOL_STEP; /* uV */
+ pca9468->ta_max_vol = min(val, PCA9468_TA_MAX_VOL *
+ pca9468->chg_mode);
+
+ /* TA voltage = MAX[8000mV, (2*VBAT_ADC + 500 mV)] */
+ pca9468->ta_vol = max(PCA9468_TA_MIN_VOL_PRESET *
+ pca9468->chg_mode, (2 * vbat *
+ pca9468->chg_mode +
+ PCA9468_TA_VOL_PRE_OFFSET));
+ /* PPS voltage resolution is 20mV */
+ val = pca9468->ta_vol / PD_MSG_TA_VOL_STEP;
+ pca9468->ta_vol = val * PD_MSG_TA_VOL_STEP;
+ /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL] */
+ pca9468->ta_vol = min(pca9468->ta_vol, pca9468->ta_max_vol);
+ /* Set TA current to IIN_CC */
+ pca9468->ta_cur = pca9468->iin_cc / pca9468->chg_mode;
+ /* Recover IIN_CC to the original value(iin_cfg) */
+ pca9468->iin_cc = pca9468->pdata->iin_cfg;
+
+ pr_debug("%s: New IIN, ta_max_vol=%u, ta_max_cur=%u, ta_max_pwr=%lu, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_max_vol, pca9468->ta_max_cur,
+ pca9468->ta_max_pwr, pca9468->iin_cc,
+ pca9468->chg_mode);
+
+ /* Clear previous IIN ADC */
+ pca9468->prev_iin = 0;
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+
+ /* Send PD Message and go to Adjust CC mode */
+ pca9468->charging_state = DC_STATE_ADJUST_CC;
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /*
+ * Adjust IIN_CC with APDO resolution(50mA) - It will
+ * recover to the original value after sending PD message
+ */
+ val = pca9468->iin_cc / PD_MSG_TA_CUR_STEP;
+ pca9468->iin_cc = val * PD_MSG_TA_CUR_STEP;
+ /* Set TA current to IIN_CC */
+ pca9468->ta_cur = pca9468->iin_cc / pca9468->chg_mode;
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+error:
+ return ret;
+}
+
+
+/* Set TA voltage for target current */
+static int pca9468_adjust_ta_voltage(struct pca9468_charger *pca9468)
+{
+ int iin;
+
+ pca9468->charging_state = DC_STATE_ADJUST_TAVOL;
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ /* Compare IIN ADC with targer input current */
+ if (iin > (pca9468->iin_cc + PD_MSG_TA_CUR_STEP)) {
+ /* TA current is higher than the target input current */
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP;
+
+ pr_debug("%s: adj. Cont1, ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else if (iin < (pca9468->iin_cc - PD_MSG_TA_CUR_STEP)) {
+ /* TA current is lower than the target input current */
+ /* Compare TA max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* TA current is already the maximum voltage */
+ /* Clear req_new_iin */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+ /* Return charging state to the previous state */
+ pca9468->charging_state = pca9468->ret_state;
+
+ pr_debug("%s: adj. End1, ta_cur=%u, ta_vol=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_cur, pca9468->ta_vol,
+ pca9468->iin_cc, pca9468->chg_mode);
+
+ /* Go to return state */
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ if (pca9468->charging_state == DC_STATE_CC_MODE)
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ else
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 1000; /* Wait 1000ms */
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Increase TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP;
+
+ pr_debug("%s: adj. Cont2, ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* IIN ADC is in valid range */
+ /* Clear req_new_iin */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+
+ /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */
+ /* Return charging state to the previous state */
+ pca9468->charging_state = pca9468->ret_state;
+
+ pr_debug("%s: adj. End2, ta_cur=%u, ta_vol=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_cur, pca9468->ta_vol,
+ pca9468->iin_cc, pca9468->chg_mode);
+
+ /* Go to return state */
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ if (pca9468->charging_state == DC_STATE_CC_MODE)
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ else
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 1000; /* Wait 1000ms */
+ mutex_unlock(&pca9468->lock);
+ }
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ return 0;
+}
+
+
+/* Set RX voltage for target current */
+static int pca9468_adjust_rx_voltage(struct pca9468_charger *pca9468)
+{
+ int iin;
+
+ pca9468->charging_state = DC_STATE_ADJUST_TAVOL;
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ /* Compare IIN ADC with targer input current */
+ if (iin > (pca9468->iin_cc + PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* RX current is higher than the target input current */
+ /* Decrease RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol - WCRX_VOL_STEP;
+
+ pr_debug("%s: adj. Cont1, rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Set RX voltage */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) {
+ /* RX current is lower than the target input current */
+ /* Compare RX max voltage */
+ if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* RX current is already the maximum voltage */
+ /* Clear req_new_iin */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+
+ pr_debug("%s: adj. End1, rx_vol=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_vol, pca9468->iin_cc,
+ pca9468->chg_mode);
+
+ /* Return charging state to the previous state */
+ pca9468->charging_state = pca9468->ret_state;
+
+ /* Go to return state */
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ if (pca9468->charging_state == DC_STATE_CC_MODE)
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ else
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 1000; /* Wait 1000ms */
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Increase RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol + WCRX_VOL_STEP;
+
+ pr_debug("%s: adj. Cont2, rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Set RX voltage */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+ } else {
+ /* IIN ADC is in valid range */
+ /* Clear req_new_iin */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+
+ /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */
+ pr_debug("%s: adj. End2, rx_vol=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_vol, pca9468->iin_cc,
+ pca9468->chg_mode);
+
+ /* Return charging state to the previous state */
+ pca9468->charging_state = pca9468->ret_state;
+
+ /* Go to return state */
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ if (pca9468->charging_state == DC_STATE_CC_MODE)
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ else
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 1000; /* Wait 1000ms */
+ mutex_unlock(&pca9468->lock);
+ }
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+ return 0;
+}
+
+
+/* Set new input current */
+static int pca9468_set_new_iin(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+
+ pr_debug("%s: new_iin=%d\n", __func__, pca9468->new_iin);
+
+ /* Check the charging state */
+ if ((pca9468->charging_state == DC_STATE_NO_CHARGING) ||
+ (pca9468->charging_state == DC_STATE_CHECK_VBAT)) {
+ /* Apply new iin when the direct charging is started */
+ pca9468->pdata->iin_cfg = pca9468->new_iin;
+
+ /* Check whether the previous request is done */
+ } else if (pca9468->req_new_iin) {
+ /* The previous request is not done yet */
+ pr_err("%s: There is the previous request for New iin\n",
+ __func__);
+ return -EBUSY;
+ }
+
+ /* Set request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = true;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check the charging state */
+ if ((pca9468->charging_state == DC_STATE_CC_MODE) ||
+ (pca9468->charging_state == DC_STATE_CV_MODE) ||
+ (pca9468->charging_state == DC_STATE_CHARGING_DONE)) {
+ /* cancel delayed_work */
+ cancel_delayed_work(&pca9468->timer_work);
+
+ /* Set new IIN to IIN_CC */
+ pca9468->iin_cc = pca9468->new_iin;
+ /* Save return state */
+ pca9468->ret_state = pca9468->charging_state;
+
+ /* Check the TA type first */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Wireless Charger is connected */
+ ret = pca9468_adjust_rx_voltage(pca9468);
+ } else {
+ /* USBPD TA is connected */
+
+ /* Check new IIN with the minimum TA current */
+ if (pca9468->iin_cc < (PCA9468_TA_MIN_CUR *
+ pca9468->chg_mode)) {
+ /* TA current = PCA9468_TA_MIN_CUR(1.0A) */
+ pca9468->ta_cur = PCA9468_TA_MIN_CUR;
+
+ /* control TA voltage for request current */
+ ret = pca9468_adjust_ta_voltage(pca9468);
+ } else {
+ /* control TA current for request current */
+ ret = pca9468_adjust_ta_current(pca9468);
+ }
+ }
+ } else {
+ /* Wait for next valid state */
+ pr_debug("%s: Not support new iin yet in charging state=%d\n",
+ __func__, pca9468->charging_state);
+ }
+
+ pr_debug("%s: ret=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* Set new float voltage */
+static int pca9468_set_new_vfloat(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+ int vbat;
+ unsigned int val;
+
+ /* Check the charging state */
+ if ((pca9468->charging_state == DC_STATE_NO_CHARGING) ||
+ (pca9468->charging_state == DC_STATE_CHECK_VBAT)) {
+ /* Apply new vfloat when the direct charging is started */
+ pca9468->pdata->v_float = pca9468->new_vfloat;
+
+ /* Check whether the previous request is done */
+ } else if (pca9468->req_new_vfloat) {
+ /* The previous request is not done yet */
+ pr_err("%s: There is the previous request for New vfloat\n",
+ __func__);
+ return -EBUSY;
+ }
+
+ /* Set request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_vfloat = true;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check the charging state */
+ if ((pca9468->charging_state == DC_STATE_CC_MODE) ||
+ (pca9468->charging_state == DC_STATE_CV_MODE) ||
+ (pca9468->charging_state == DC_STATE_CHARGING_DONE)) {
+ unsigned int vmin = PCA9468_TA_MIN_VOL_PRESET * pca9468->chg_mode;
+ unsigned int cc_max = pca9468->ta_max_cur * pca9468->chg_mode;
+
+ /* Read VBAT ADC */
+ vbat = pca9468_read_adc(pca9468, ADCCH_VBAT);
+
+ /* Compare the new VBAT with the current VBAT */
+ if (pca9468->new_vfloat <= vbat) {
+ /* The new VBAT is lower than the current VBAT */
+ /* return invalid error */
+ pr_err("%s: New vfloat is lower than VBAT ADC\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ /* cancel delayed_work */
+ cancel_delayed_work(&pca9468->timer_work);
+
+ /* Set VFLOAT to new vfloat */
+ pca9468->pdata->v_float = pca9468->new_vfloat;
+ ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float);
+ if (ret < 0)
+ goto error;
+
+ /* Set IIN_CFG to the current IIN_CC */
+ /* save the current iin_cc in iin_cfg */
+ pca9468->pdata->iin_cfg = pca9468->iin_cc;
+ pca9468->pdata->iin_cfg = min(pca9468->pdata->iin_cfg, cc_max);
+ ret = pca9468_set_input_current(pca9468,
+ pca9468->pdata->iin_cfg);
+ if (ret < 0)
+ goto error;
+
+ pca9468->iin_cc = pca9468->pdata->iin_cfg;
+
+ /* Clear req_new_vfloat */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_vfloat = false;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+
+ /*
+ * Wireless Charger is connected
+ * Set RX voltage to MAX[8000mV*chg_mode,
+ * (2 * VBAT_ADC * chg_mode + 500mV)]
+ */
+ pca9468->ta_vol = max(vmin, (2 * vbat *
+ pca9468->chg_mode +
+ PCA9468_TA_VOL_PRE_OFFSET));
+
+ /* RX voltage resolution is 100mV */
+ val = pca9468->ta_vol / WCRX_VOL_STEP;
+ pca9468->ta_vol = val * WCRX_VOL_STEP;
+ /* RX voltage = MIN[RX voltage, RX_MAX_VOL] */
+ pca9468->ta_vol = min(pca9468->ta_vol,
+ pca9468->ta_max_vol);
+
+ pr_debug("%s: New VFLOAT, rx_max_vol=%u, rx_vol=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_max_vol,
+ pca9468->ta_vol, pca9468->iin_cc,
+ pca9468->chg_mode);
+ } else {
+
+ /*
+ * USBPD TA is connected
+ * Adjust IIN_CC with APDO resoultion(50mA)
+ * It will recover to the original value after
+ * max voltage calculation
+ */
+
+ /* Calculate new TA max voltage */
+ val = pca9468->iin_cc / (PD_MSG_TA_CUR_STEP *
+ pca9468->chg_mode);
+ pca9468->iin_cc = val * (PD_MSG_TA_CUR_STEP *
+ pca9468->chg_mode);
+ /*
+ * TA_MAX_VOL = MIN[PCA9468_TA_MAX_VOL,
+ * (TA_MAX_PWR/IIN_CC)]
+ */
+ val = pca9468->ta_max_pwr / (pca9468->iin_cc /
+ pca9468->chg_mode / 1000); /* mV */
+ val = val * 1000 / PD_MSG_TA_VOL_STEP; /* uV */
+ /* Adjust values with APDO resolution(20mV) */
+ val = val * PD_MSG_TA_VOL_STEP;
+ pca9468->ta_max_vol = min(val, PCA9468_TA_MAX_VOL *
+ pca9468->chg_mode);
+
+ /*
+ * TA voltage = MAX[8000mV*chg_mode,
+ * (2*VBAT_ADC*chg_mode + 500 mV)]
+ */
+ pca9468->ta_vol = max(vmin, (2 * vbat *
+ pca9468->chg_mode +
+ PCA9468_TA_VOL_PRE_OFFSET));
+ /* PPS voltage resolution is 20mV */
+ val = pca9468->ta_vol / PD_MSG_TA_VOL_STEP;
+ pca9468->ta_vol = val * PD_MSG_TA_VOL_STEP;
+ /* TA voltage = MIN[TA voltage, TA_MAX_VOL] */
+ pca9468->ta_vol = min(pca9468->ta_vol,
+ pca9468->ta_max_vol);
+ /* Set TA current to IIN_CC */
+ pca9468->ta_cur = pca9468->iin_cc /
+ pca9468->chg_mode;
+ /* Recover IIN_CC to the original (iin_cfg) */
+ pca9468->iin_cc = pca9468->pdata->iin_cfg;
+
+ pr_debug("%s: New VFLOAT, ta_max_vol=%u, ta_max_cur=%u, ta_max_pwr=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_max_vol,
+ pca9468->ta_max_cur, pca9468->ta_max_pwr,
+ pca9468->iin_cc, pca9468->chg_mode);
+ }
+
+ /* Clear previous IIN ADC, TA increment flag */
+ pca9468->prev_iin = 0;
+ pca9468->prev_inc = INC_NONE;
+
+ /* Send PD Message and go to Adjust CC mode */
+ pca9468->charging_state = DC_STATE_ADJUST_CC;
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ } else {
+ /* Wait for next valid state */
+ pr_debug("%s: Not support new vfloat yet in charging state=%d\n",
+ __func__, pca9468->charging_state);
+ }
+
+error:
+ pr_debug("%s: ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* 2:1 Direct Charging Adjust CC MODE control */
+static int pca9468_charge_adjust_ccmode(struct pca9468_charger *pca9468)
+{
+ int iin, ccmode;
+ int vbatt;
+ int vin_vol;
+ int ret = 0;
+
+ pr_debug("%s: ======START=======\n", __func__);
+
+ pca9468->charging_state = DC_STATE_ADJUST_CC;
+
+ ret = pca9468_check_error(pca9468);
+ if (ret != 0)
+ goto error; // This is not active mode.
+ /* Check the status */
+ ccmode = pca9468_check_ccmode_status(pca9468);
+ if (ccmode < 0) {
+ ret = ccmode;
+ goto error;
+ }
+
+ switch(ccmode) {
+ case CCMODE_IIN_LOOP:
+ case CCMODE_CHG_LOOP:
+ /* Check the TA type first */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Decrease RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol - WCRX_VOL_STEP;
+ pr_debug("%s: CC adjust End(LOOP): rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ } else {
+ /* Check TA current */
+ if (pca9468->ta_cur > PCA9468_TA_MIN_CUR) {
+ /* TA current is higher than 1.0A */
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur -
+ PD_MSG_TA_CUR_STEP;
+ } else {
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ PD_MSG_TA_VOL_STEP;
+ }
+
+ pr_debug("%s: CC adjust End(LOOP): ta_cur=%u, ta_vol=%u\n",
+ __func__, pca9468->ta_cur, pca9468->ta_vol);
+ }
+
+ /* Read IIN ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Send PD Message and then go to CC mode */
+ pca9468->charging_state = DC_STATE_CC_MODE;
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ break;
+
+ case CCMODE_VFLT_LOOP:
+ /* Read VBAT ADC */
+ vbatt = pca9468_read_adc(pca9468, ADCCH_VBAT);
+ pr_debug("%s: CC adjust End(VFLOAT): vbatt=%d, ta_vol=%u\n",
+ __func__, vbatt, pca9468->ta_vol);
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Go to Pre-CV mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ENTER_CVMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ break;
+
+ case CCMODE_LOOP_INACTIVE:
+ /* Check IIN ADC with IIN */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ /* Check the TA type first */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+
+ /* IIN_ADC > IIN_CC -20mA ? */
+ if (iin > (pca9468->iin_cc - PCA9468_IIN_ADC_OFFSET)) {
+ /* Input current is already over IIN_CC */
+ /* End RX voltage adjustment */
+ /* change charging state to CC mode */
+ pca9468->charging_state = DC_STATE_CC_MODE;
+
+ pr_debug("%s: CC adjust End: IIN_ADC=%d, rx_vol=%u\n",
+ __func__, iin, pca9468->ta_vol);
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Go to CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check RX voltage */
+ } else if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* RX voltage is already max value */
+ pr_debug("%s: CC adjust End: MAX value, rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Go to CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* Try to increase RX voltage(100mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ WCRX_VOL_STEP;
+ if (pca9468->ta_vol > pca9468->ta_max_vol)
+ pca9468->ta_vol = pca9468->ta_max_vol;
+
+ pr_debug("%s: CC adjust. Cont: rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ /* Set RX voltage */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+
+ /* USBPD TA is connected */
+ } else if (iin > (pca9468->iin_cc - PCA9468_IIN_ADC_OFFSET)) {
+ /* IIN_ADC > IIN_CC -20mA ? */
+ /* Input current is already over IIN_CC */
+ /* End TA voltage and current adjustment */
+ /* change charging state to CC mode */
+ pca9468->charging_state = DC_STATE_CC_MODE;
+
+ pr_debug("%s: CC adjust End: IIN_ADC=%d, ta_vol=%u, ta_cur=%u\n",
+ __func__, iin, pca9468->ta_vol,
+ pca9468->ta_cur);
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Go to CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check TA voltage */
+ } else if (pca9468->ta_vol == pca9468->ta_max_vol) {
+ /* TA voltage is already max value */
+ pr_debug("%s: CC adjust End: MAX value, ta_vol=%u, ta_cur=%u\n",
+ __func__, pca9468->ta_vol, pca9468->ta_cur);
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Go to CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check TA tolerance
+ * The current input current compares the final input
+ * current(IIN_CC) with 100mA offset PPS current tolerance
+ * has +/-150mA, so offset defined 100mA(tolerance +50mA)
+ */
+ } else if (iin < (pca9468->iin_cc - PCA9468_TA_IIN_OFFSET)) {
+ /*
+ * TA voltage too low to enter TA CC mode, so we
+ * should increase TA voltage
+ */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PCA9468_TA_VOL_STEP_ADJ_CC *
+ pca9468->chg_mode;
+
+ if (pca9468->ta_vol > pca9468->ta_max_vol)
+ pca9468->ta_vol = pca9468->ta_max_vol;
+
+ pr_debug("%s: CC adjust Cont: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_VOL;
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* compare IIN ADC with previous IIN ADC + 20mA */
+ } else if (iin > (pca9468->prev_iin + PCA9468_IIN_ADC_OFFSET)) {
+ /* TA can supply more current if TA voltage is high */
+ /* TA voltage too low for TA CC mode: increase it */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PCA9468_TA_VOL_STEP_ADJ_CC *
+ pca9468->chg_mode;
+ if (pca9468->ta_vol > pca9468->ta_max_vol)
+ pca9468->ta_vol = pca9468->ta_max_vol;
+
+ pr_debug("%s: CC adjust Cont: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_VOL;
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* Check the previous increment */
+ } else if (pca9468->prev_inc == INC_TA_CUR) {
+ /*
+ * The previous increment is TA current, but input
+ * current does not increase
+ */
+
+ /* Try to increase TA voltage(40mV) */
+ pca9468->ta_vol = pca9468->ta_vol +
+ PCA9468_TA_VOL_STEP_ADJ_CC *
+ pca9468->chg_mode;
+ if (pca9468->ta_vol > pca9468->ta_max_vol)
+ pca9468->ta_vol = pca9468->ta_max_vol;
+
+ pr_debug("%s: CC adjust(flag) Cont: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_VOL;
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ /* The previous increment is TA voltage, but input
+ * current does not increase
+ */
+ /* Try to increase TA current */
+ /* Check APDO max current */
+ } else if (pca9468->ta_cur == pca9468->ta_max_cur) {
+ /* TA current is maximum current */
+
+ pr_debug("%s: CC adjust End(MAX_CUR): IIN_ADC=%d, ta_vol=%u, ta_cur=%u\n",
+ __func__, iin, pca9468->ta_vol,
+ pca9468->ta_cur);
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+ /* Go to CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ } else {
+ /* TA has tolerance and compensate it as real current */
+ /* Increase TA current(50mA) */
+ pca9468->ta_cur = pca9468->ta_cur + PD_MSG_TA_CUR_STEP;
+ if (pca9468->ta_cur > pca9468->ta_max_cur)
+ pca9468->ta_cur = pca9468->ta_max_cur;
+
+ pr_debug("%s: CC adjust Cont: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+
+ /* Set TA increment flag */
+ pca9468->prev_inc = INC_TA_CUR;
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ }
+
+ /* Save previous iin adc */
+ pca9468->prev_iin = iin;
+ break;
+
+ case CCMODE_VIN_UVLO:
+ /* VIN UVLO - just notification , it works by hardware */
+ vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN);
+
+ pr_debug("%s: CC adjust VIN_UVLO: ta_vol=%u, vin_vol=%d\n",
+ __func__, pca9468->ta_cur, vin_vol);
+ /* Check VIN after 1sec */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ADJUST_CCMODE;
+ pca9468->timer_period = 1000;
+ mutex_unlock(&pca9468->lock);
+ break;
+
+ default:
+ goto error;
+ }
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* 2:1 Direct Charging CC MODE control */
+static int pca9468_charge_ccmode(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+ int ccmode;
+ int vin_vol, iin;
+
+ pr_debug("%s: ======START======= \n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_CC_MODE;
+
+ ret = pca9468_check_error(pca9468);
+ if (ret != 0)
+ goto error; /* This is not active mode. */
+
+ /* Check new vfloat request and new iin request */
+ if (pca9468->req_new_vfloat) {
+ /* Clear request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_vfloat = false;
+ mutex_unlock(&pca9468->lock);
+ ret = pca9468_set_new_vfloat(pca9468);
+ } else if (pca9468->req_new_iin) {
+ /* Clear request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+ ret = pca9468_set_new_iin(pca9468);
+ } else {
+ /* Check the charging type */
+ ccmode = pca9468_check_ccmode_status(pca9468);
+ if (ccmode < 0) {
+ ret = ccmode;
+ goto error;
+ }
+
+ switch(ccmode) {
+ case CCMODE_LOOP_INACTIVE:
+ /* Set input current compensation */
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Need RX voltage compensation */
+ ret = pca9468_set_rx_voltage_comp(pca9468);
+ pr_debug("%s: CC INACTIVE: rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ } else {
+ const int ta_max_vol = pca9468->ta_max_vol;
+
+ /* Check TA current with TA_MIN_CUR */
+ if (pca9468->ta_cur <= PCA9468_TA_MIN_CUR) {
+ /* TA current = 1.0A */
+ pca9468->ta_cur = PCA9468_TA_MIN_CUR;
+ /* Need input voltage compensation */
+ ret = pca9468_set_ta_voltage_comp(pca9468);
+ } else if (ta_max_vol >= PCA9468_TA_MAX_VOL_CP) {
+ /* Need input current compensation */
+ ret = pca9468_set_ta_current_comp(pca9468);
+ } else {
+ /* Need input current compensation in
+ * constant power mode
+ */
+ ret = pca9468_set_ta_current_comp2(pca9468);
+ }
+
+ pr_debug("%s: CC INACTIVE: ta_cur=%u, ta_vol=%u\n",
+ __func__, pca9468->ta_cur,
+ pca9468->ta_vol);
+ }
+ break;
+
+ case CCMODE_VFLT_LOOP:
+ /* Read IIN_ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ pr_debug("%s: CC VFLOAT: iin=%d\n", __func__, iin);
+ /* go to Pre-CV mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ENTER_CVMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CCMODE_IIN_LOOP:
+ case CCMODE_CHG_LOOP:
+ /* Read IIN_ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Decrease RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ WCRX_VOL_STEP;
+ pr_debug("%s: CC LOOP:iin=%d, next_rx_vol=%u\n",
+ __func__, iin, pca9468->ta_vol);
+ } else {
+ /* USBPD TA is connected */
+
+ /* Check TA current with TA_MIN_CUR */
+ if (pca9468->ta_cur <= PCA9468_TA_MIN_CUR) {
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: CC LOOP:iin=%d, next_ta_vol=%u\n",
+ __func__, iin,
+ pca9468->ta_vol);
+ } else {
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur -
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: CC LOOP:iin=%d, next_ta_cur=%u\n",
+ __func__, iin,
+ pca9468->ta_cur);
+ }
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CCMODE_VIN_UVLO:
+ /* VIN UVLO - just notification, it works by hardware */
+ vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN);
+ pr_debug("%s: CC VIN_UVLO: ta_cur=%u ta_vol=%u, vin_vol=%d\n",
+ __func__, pca9468->ta_cur, pca9468->ta_vol,
+ vin_vol);
+
+ /* Check VIN after 1sec */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ pca9468->timer_period = 1000;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ default:
+ break;
+ }
+ }
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* 2:1 Direct Charging Start CV MODE control - Pre CV MODE */
+static int pca9468_charge_start_cvmode(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+ int cvmode;
+ int vin_vol;
+
+ pr_debug("%s: ======START=======\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_START_CV;
+ /* Check the charging type */
+ ret = pca9468_check_error(pca9468);
+ if (ret != 0)
+ goto error; // This is not active mode.
+ /* Check the status */
+ cvmode = pca9468_check_cvmode_status(pca9468);
+ if (cvmode < 0) {
+ ret = cvmode;
+ goto error;
+ }
+
+ switch(cvmode) {
+ case CVMODE_CHG_LOOP:
+ case CVMODE_IIN_LOOP:
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Decrease RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol - WCRX_VOL_STEP;
+ pr_debug("%s: PreCV Cont(IIN_LOOP): rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ } else {
+ /* Check TA current */
+ if (pca9468->ta_cur > PCA9468_TA_MIN_CUR) {
+ /* TA current is higher than 1.0A */
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur -
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: PreCV Cont: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+ } else {
+ /* TA current is less than 1.0A */
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: PreCV Cont(IIN_LOOP): ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ }
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_VFLT_LOOP:
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Decrease RX voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol - WCRX_VOL_STEP;
+ pr_debug("%s: PreCV Cont: rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ } else {
+ /* Decrease TA voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ PCA9468_TA_VOL_STEP_PRE_CV *
+ pca9468->chg_mode;
+ pr_debug("%s: PreCV Cont: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_LOOP_INACTIVE:
+ /* Exit Pre CV mode */
+ pr_debug("%s: PreCV End: ta_vol=%u, ta_cur=%u\n", __func__,
+ pca9468->ta_vol, pca9468->ta_cur);
+
+ /* Need to implement notification to other driver */
+ /* To do here */
+
+ /* Go to CV mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_VIN_UVLO:
+ /* VIN UVLO - just notification , it works by hardware */
+ vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN);
+ pr_debug("%s: PreCV VIN_UVLO: ta_vol=%u, vin_vol=%u\n",
+ __func__, pca9468->ta_cur, vin_vol);
+ /* Check VIN after 1sec */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ENTER_CVMODE;
+ pca9468->timer_period = 1000;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ default:
+ break;
+ }
+
+error:
+ pr_info("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* 2:1 Direct Charging CV MODE control */
+static int pca9468_charge_cvmode(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+ int cvmode;
+ int vin_vol;
+ int iin = 0;
+
+ pr_debug("%s: ======START=======\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_CV_MODE;
+
+ ret = pca9468_check_error(pca9468);
+ if (ret != 0)
+ goto error; /* This is not active mode. */
+
+ /* Check new vfloat request and new iin request */
+ if (pca9468->req_new_vfloat) {
+ /* Clear request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_vfloat = false;
+ mutex_unlock(&pca9468->lock);
+ ret = pca9468_set_new_vfloat(pca9468);
+ } else if (pca9468->req_new_iin) {
+ /* Clear request flag */
+ mutex_lock(&pca9468->lock);
+ pca9468->req_new_iin = false;
+ mutex_unlock(&pca9468->lock);
+ ret = pca9468_set_new_iin(pca9468);
+ } else {
+ cvmode = pca9468_check_cvmode_status(pca9468);
+ if (cvmode < 0) {
+ ret = cvmode;
+ goto error;
+ }
+
+ /* Check charging done state */
+ if (cvmode == CVMODE_LOOP_INACTIVE) {
+ /* Read IIN_ADC */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+
+ /* Compare iin with input topoff current */
+ pr_debug("%s: iin=%d, iin_topoff=%u\n",
+ __func__, iin, pca9468->pdata->iin_topoff);
+ if (iin < pca9468->pdata->iin_topoff) {
+ /* Change cvmode status to charging done */
+ cvmode = CVMODE_CHG_DONE;
+ pr_debug("%s: CVMODE Status=%d\n",
+ __func__, cvmode);
+ }
+ }
+
+ switch(cvmode) {
+ case CVMODE_CHG_DONE:
+ /* Charging Done */
+ /* Keep CV mode until driver send stop charging */
+ pca9468->charging_state = DC_STATE_CHARGING_DONE;
+ power_supply_changed(pca9468->mains);
+
+ /* Need to implement notification function */
+ /* TODO: notify DONE charger here */
+
+ pr_debug("%s: CV Done\n", __func__);
+
+ if (pca9468->charging_state == DC_STATE_NO_CHARGING) {
+ /* notification function called stop */
+ pr_debug("%s: Already stop DC\n", __func__);
+ break;
+ }
+
+ /* Notification function does not stop timer work yet */
+ /* Keep the charging done state */
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = PCA9468_CVMODE_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_CHG_LOOP:
+ case CVMODE_IIN_LOOP:
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Decrease RX Voltage (100mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ WCRX_VOL_STEP;
+ pr_debug("%s: CV LOOP, Cont: rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+
+ /* Check TA current */
+ } else if (pca9468->ta_cur > PCA9468_TA_MIN_CUR) {
+ /* TA current is higher than (1.0A*chg_mode) */
+ /* Decrease TA current (50mA) */
+ pca9468->ta_cur = pca9468->ta_cur -
+ PD_MSG_TA_CUR_STEP;
+ pr_debug("%s: CV LOOP, Cont: ta_cur=%u\n",
+ __func__, pca9468->ta_cur);
+ } else {
+ /* TA current is less than (1.0A*chg_mode) */
+ /* Decrease TA Voltage (20mV) */
+ pca9468->ta_vol = pca9468->ta_vol -
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: CV LOOP, Cont: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_VFLT_LOOP:
+ /* Check the TA type */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* Decrease RX voltage */
+ pca9468->ta_vol = pca9468->ta_vol -
+ WCRX_VOL_STEP;
+ pr_debug("%s: CV VFLOAT, Cont: rx_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ } else {
+ /* Decrease TA voltage */
+ pca9468->ta_vol = pca9468->ta_vol -
+ PD_MSG_TA_VOL_STEP;
+ pr_debug("%s: CV VFLOAT, Cont: ta_vol=%u\n",
+ __func__, pca9468->ta_vol);
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_LOOP_INACTIVE:
+ /* Set timer */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = PCA9468_CVMODE_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case CVMODE_VIN_UVLO:
+ /* VIN UVLO - just notification, it works by hardware */
+ vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN);
+ pr_debug("%s: CC VIN_UVLO: ta_cur=%u ta_vol=%u, vin_vol=%d\n",
+ __func__, pca9468->ta_cur, pca9468->ta_vol,
+ vin_vol);
+ /* Check VIN after 1sec */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ pca9468->timer_period = 1000;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ default:
+ break;
+ }
+ }
+
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* Preset TA voltage and current for Direct Charging Mode */
+static int pca9468_preset_dcmode(struct pca9468_charger *pca9468)
+{
+ int vbat;
+ unsigned long val;
+ int ret = 0;
+ int chg_mode;
+
+ pr_debug("%s: ======START=======\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_PRESET_DC;
+
+ /* Read VBAT ADC */
+ vbat = pca9468_read_adc(pca9468, ADCCH_VBAT);
+ if (vbat < 0) {
+ ret = vbat;
+ goto error;
+ }
+
+ /* Compare VBAT with VBAT ADC */
+ if (vbat > pca9468->pdata->v_float) {
+ /* Invalid battery voltage to start direct charging */
+ pr_err("%s: vbat adc is higher than VFLOAT\n", __func__);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Check the TA type and set the charging mode */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /*
+ * Set the RX max current to input request current(iin_cfg)
+ * initially to get RX maximum current from RX IC
+ */
+ pca9468->ta_max_cur = pca9468->pdata->iin_cfg;
+ /*
+ * Set the RX max voltage to enough high value to find RX
+ * maximum voltage initially
+ */
+ pca9468->ta_max_vol = PCA9468_WCRX_MAX_VOL *
+ pca9468->pdata->chg_mode;
+
+ /* Get the RX max current/voltage(RX_MAX_CUR/VOL) */
+ ret = pca9468_get_rx_max_power(pca9468);
+ if (ret < 0) {
+ /* RX IC does not have the desired maximum voltage */
+ /* Check the desired mode */
+ if (pca9468->pdata->chg_mode == CHG_4TO1_DC_MODE) {
+ /*
+ * RX IC doesn't have any maximum voltage to
+ * support 4:1 mode
+ */
+
+ /* RX max current/voltage with 2:1 mode */
+ pca9468->ta_max_vol = PCA9468_WCRX_MAX_VOL;
+ ret = pca9468_get_rx_max_power(pca9468);
+ if (ret < 0) {
+ pr_err("%s: RX IC doesn't have any RX voltage to support 2:1 or 4:1\n",
+ __func__);
+ pca9468->chg_mode = CHG_NO_DC_MODE;
+ goto error;
+ } else {
+ /*
+ * RX IC has the maximum RX voltage to
+ * support 2:1 mode
+ */
+ pca9468->chg_mode = CHG_2TO1_DC_MODE;
+ }
+ } else {
+ /*
+ * The desired CHG mode is 2:1 mode RX IC
+ * doesn't have any RX voltage to
+ * support 2:1 mode
+ */
+ pr_err("%s: RX IC doesn't have any RX voltage to support 2:1\n",
+ __func__);
+ pca9468->chg_mode = CHG_NO_DC_MODE;
+ goto error;
+ }
+ } else {
+ /* RX IC has the desired RX voltage */
+ pca9468->chg_mode = pca9468->pdata->chg_mode;
+ }
+
+ chg_mode = pca9468->chg_mode;
+
+ /* Set IIN_CC to MIN[IIN, (RX_MAX_CUR by RX IC)*chg_mode]*/
+ pca9468->iin_cc = min(pca9468->pdata->iin_cfg,
+ pca9468->ta_max_cur * chg_mode);
+ /* Set the current IIN_CC to iin_cfg */
+ pca9468->pdata->iin_cfg = pca9468->iin_cc;
+
+ /* RX_vol = MAX[(2*VBAT_ADC*CHG_mode + 500mV), 8.0V*CHG_mode] */
+ pca9468->ta_vol = max(PCA9468_TA_MIN_VOL_PRESET * chg_mode,
+ (2 * vbat * chg_mode +
+ PCA9468_TA_VOL_PRE_OFFSET));
+ /* RX voltage resolution is 100mV */
+ val = pca9468->ta_vol / WCRX_VOL_STEP;
+ pca9468->ta_vol = val * WCRX_VOL_STEP;
+ /* Set RX voltage to MIN[RX voltage, RX_MAX_VOL*chg_mode] */
+ pca9468->ta_vol = min(pca9468->ta_vol, pca9468->ta_max_vol);
+
+ pr_debug("%s: Preset DC, rx_max_vol=%u, rx_max_cur=%u, rx_max_pwr=%u, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_max_vol, pca9468->ta_max_cur,
+ pca9468->ta_max_pwr, pca9468->iin_cc,
+ pca9468->chg_mode);
+
+ pr_debug("%s: Preset DC, rx_vol=%u\n", __func__,
+ pca9468->ta_vol);
+
+ } else {
+ /* Set the TA max current to input request current(iin_cfg)
+ * initially to get TA maximum current from PD IC
+ */
+ pca9468->ta_max_cur = pca9468->pdata->iin_cfg;
+ /* high enough to to find initial TA maximum voltage */
+ pca9468->ta_max_vol = PCA9468_TA_MAX_VOL *
+ pca9468->pdata->chg_mode;
+
+ /* Search the proper object position of PDO */
+ pca9468->ta_objpos = 0;
+ /* Get the APDO max current/voltage(TA_MAX_CUR/VOL) */
+ ret = pca9468_get_apdo_max_power(pca9468);
+ if (ret < 0) {
+ /* TA does not have the desired APDO */
+ /* Check the desired mode */
+ if (pca9468->pdata->chg_mode == CHG_4TO1_DC_MODE) {
+ /* NO APDO to support 4:1 mode */
+ /* Use APDO max current/voltage for 2:1 mode */
+ pca9468->ta_max_vol = PCA9468_TA_MAX_VOL;
+ pca9468->ta_objpos = 0;
+ ret = pca9468_get_apdo_max_power(pca9468);
+ if (ret < 0) {
+ pr_err("%s: TA doesn't have any APDO to support 2:1 or 4:1\n",
+ __func__);
+ pca9468->chg_mode = CHG_NO_DC_MODE;
+ goto error;
+ } else {
+ /* TA has APDO to support 2:1 mode */
+ pca9468->chg_mode = CHG_2TO1_DC_MODE;
+ }
+ } else {
+ /* The desired TA mode is 2:1 mode */
+ /* No APDO to support 2:1 mode*/
+ pr_err("%s: TA doesn't have any APDO to support 2:1\n",
+ __func__);
+ pca9468->chg_mode = CHG_NO_DC_MODE;
+ goto error;
+ }
+ } else {
+ /* TA has the desired APDO */
+ pca9468->chg_mode = pca9468->pdata->chg_mode;
+ }
+
+ chg_mode = pca9468->chg_mode;
+
+ /* Calculate new TA maximum current and voltage */
+ /* Set IIN_CC to MIN[IIN, (TA_MAX_CUR by APDO)*chg_mode] */
+ pca9468->iin_cc = min(pca9468->pdata->iin_cfg,
+ pca9468->ta_max_cur * chg_mode);
+ /*
+ * Set the current IIN_CC to iin_cfg for recovering it after
+ * resolution adjustment
+ */
+ pca9468->pdata->iin_cfg = pca9468->iin_cc;
+
+ /*
+ * Calculate new TA max voltage
+ * Adjust IIN_CC with APDO resoultion(50mA) - It will recover
+ * to the original value after max voltage calculation
+ */
+ val = pca9468->iin_cc / PD_MSG_TA_CUR_STEP;
+ pca9468->iin_cc = val * PD_MSG_TA_CUR_STEP;
+
+ /* val= TA_MAX_PWR/(IIN_CC/chg_mode) mV */
+ val = pca9468->ta_max_pwr / (pca9468->iin_cc / chg_mode / 1000);
+ /* Adjust values with APDO resolution(20mV) */
+ val = val * 1000 / PD_MSG_TA_VOL_STEP;
+ val = val*PD_MSG_TA_VOL_STEP; /* uV */
+ pca9468->ta_max_vol = min(val, (unsigned long)PCA9468_TA_MAX_VOL * chg_mode);
+
+ /* MAX[8000mV*chg_mode, 2*VBAT_ADC*chg_mode+500 mV] */
+ pca9468->ta_vol = max(PCA9468_TA_MIN_VOL_PRESET * chg_mode,
+ 2 * vbat * chg_mode +
+ PCA9468_TA_VOL_PRE_OFFSET);
+ /* PPS voltage resolution is 20mV */
+ val = pca9468->ta_vol / PD_MSG_TA_VOL_STEP;
+ pca9468->ta_vol = val * PD_MSG_TA_VOL_STEP;
+ /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL*chg_mode] */
+ pca9468->ta_vol = min(pca9468->ta_vol, pca9468->ta_max_vol);
+ /* Set the initial TA current to IIN_CC/chg_mode */
+ pca9468->ta_cur = pca9468->iin_cc/chg_mode;
+ /* Recover IIN_CC to the original value(iin_cfg) */
+ pca9468->iin_cc = pca9468->pdata->iin_cfg;
+
+ pr_debug("%s: Preset DC, ta_max_vol=%u, ta_max_cur=%u, ta_max_pwr=%lu, iin_cc=%u, chg_mode=%u\n",
+ __func__, pca9468->ta_max_vol, pca9468->ta_max_cur,
+ pca9468->ta_max_pwr, pca9468->iin_cc,
+ pca9468->chg_mode);
+ pr_debug("%s: Preset DC, ta_vol=%u, ta_cur=%u\n", __func__,
+ pca9468->ta_vol, pca9468->ta_cur);
+ }
+
+ /* Send PD Message */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PDMSG_SEND;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* Preset direct charging configuration */
+static int pca9468_preset_config(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+
+ pr_debug("%s: ======START=======\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_PRESET_DC;
+
+ /* Set IIN_CFG to IIN_CC */
+ ret = pca9468_set_input_current(pca9468, pca9468->iin_cc);
+ if (ret < 0)
+ goto error;
+
+ /* Set ICHG_CFG to enough high value */
+ ret = pca9468_set_charging_current(pca9468, pca9468->pdata->ichg_cfg);
+ if (ret < 0)
+ goto error;
+
+ /* Set VFLOAT */
+ ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float);
+ if (ret < 0)
+ goto error;
+
+ /* Enable PCA9468 */
+ ret = pca9468_set_charging(pca9468, true);
+ if (ret < 0)
+ goto error;
+
+ /* Clear previous iin adc */
+ pca9468->prev_iin = 0;
+
+ /* Clear TA increment flag */
+ pca9468->prev_inc = INC_NONE;
+
+ /* Go to CHECK_ACTIVE state after 150ms*/
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_CHECK_ACTIVE;
+ pca9468->timer_period = PCA4968_ENABLE_DELAY_T;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* Check the charging status before entering the adjust cc mode */
+static int pca9468_check_active_state(struct pca9468_charger *pca9468)
+{
+ int ret = 0;
+
+ pr_debug("%s: ======START=======\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_CHECK_ACTIVE;
+
+ ret = pca9468_check_error(pca9468);
+
+ if (ret == 0) {
+ /* PCA9468 is active state */
+ /* Clear retry counter */
+ pca9468->retry_cnt = 0;
+ /* Go to Adjust CC mode */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ADJUST_CCMODE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ } else if (ret == -EAGAIN) {
+ /* It is the retry condition */
+ /* Check the retry counter */
+ if (pca9468->retry_cnt < PCA9468_MAX_RETRY_CNT) {
+ /* Disable charging */
+ ret = pca9468_set_charging(pca9468, false);
+ /* Increase retry counter */
+ pca9468->retry_cnt++;
+ pr_err("%s: retry charging start - retry_cnt=%d\n",
+ __func__, pca9468->retry_cnt);
+ /* Go to DC_STATE_PRESET_DC */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PRESET_DC;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ ret = 0;
+ } else {
+ pr_err("%s: retry fail\n", __func__);
+ /* Notify maximum retry error */
+ ret = -EINVAL;
+ }
+ } else {
+ /* Implement error handler function if it is needed */
+ /* Stop charging in timer_work */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ID_NONE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ }
+
+ return ret;
+}
+
+
+/* Enter direct charging algorithm */
+static int pca9468_start_direct_charging(struct pca9468_charger *pca9468)
+{
+ int ret;
+ unsigned int val;
+
+ pr_debug("%s: =========START=========\n", __func__);
+
+ /* Set OV_DELTA to 40% */
+ val = OV_DELTA_40P << MASK2SHIFT(PCA9468_BIT_OV_DELTA);
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_SAFETY_CTRL,
+ PCA9468_BIT_OV_DELTA, val);
+ if (ret < 0)
+ return ret;
+
+ /* Set Switching Frequency */
+ val = pca9468->pdata->fsw_cfg;
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_START_CTRL,
+ PCA9468_BIT_FSW_CFG, val);
+ if (ret < 0)
+ return ret;
+
+ /* Set EN_CFG to active HIGH */
+ val = PCA9468_EN_ACTIVE_H;
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_START_CTRL,
+ PCA9468_BIT_EN_CFG, val);
+ if (ret < 0)
+ return ret;
+
+ /* Set NTC voltage threshold */
+ val = pca9468->pdata->ntc_th / PCA9468_NTC_TH_STEP;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_NTC_TH_1, (val & 0xFF));
+ if (ret < 0)
+ return ret;
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_NTC_TH_2,
+ PCA9468_BIT_NTC_THRESHOLD9_8, (val >> 8));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * TODO: determine if we need to use the wireless power supply
+ * if (pro_val.intval == POWER_SUPPLY_TYPE_WIRELESS) {
+ * pca9468->ta_type = TA_TYPE_WIRELESS;
+ * pr_info("%s: The current power supply type is WC,
+ * ta_type=%d\n", __func__, pca9468->ta_type);
+ * } else {
+ * pca9468->ta_type = TA_TYPE_USBPD;
+ * }
+ */
+ pca9468->ta_type = TA_TYPE_USBPD;
+ pr_info("%s: The current power supply type is USBPD, ta_type=%d\n",
+ __func__, pca9468->ta_type);
+
+ /* wake lock */
+ __pm_stay_awake(&pca9468->monitor_wake_lock);
+
+ /* Preset charging configuration and TA condition */
+ ret = pca9468_preset_dcmode(pca9468);
+
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+
+/* Check Vbat minimum level to start direct charging */
+static int pca9468_check_vbatmin(struct pca9468_charger *pca9468)
+{
+ unsigned int vbat;
+ int ret;
+ union power_supply_propval val;
+
+ pr_debug("%s: =========START=========\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ pca9468->charging_state = DC_STATE_CHECK_VBAT;
+
+ /* Check Vbat */
+ vbat = pca9468_read_adc(pca9468, ADCCH_VBAT);
+ if (vbat < 0) {
+ ret = vbat;
+ goto error;
+ }
+
+ /* Read switching charger status */
+ ret = pca9468_get_swc_property(POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ &val);
+ if (ret < 0) {
+ /* Start Direct Charging again after 1sec */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_VBATMIN_CHECK;
+ pca9468->timer_period = PCA9468_VBATMIN_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ } else if (val.intval == 0) {
+ /* already disabled switching charger */
+
+ /* Clear retry counter */
+ pca9468->retry_cnt = 0;
+ /* Preset TA voltage and PCA9468 parameters */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_PRESET_DC;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ } else {
+ /* Switching charger is enabled */
+ if (vbat > PCA9468_DC_VBAT_MIN) {
+ /* Start Direct Charging */
+ /* now switching charger is enabled */
+ /* disable switching charger first */
+ ret = pca9468_set_switching_charger(false, 0, 0, 0);
+ }
+
+ /*
+ * Wait 1sec for stopping switching charger or
+ * Start 1sec timer for battery check
+ */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_VBATMIN_CHECK;
+ pca9468->timer_period = PCA9468_VBATMIN_CHECK_T;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ }
+
+error:
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+/* delayed work function for charging timer */
+static void pca9468_timer_work(struct work_struct *work)
+{
+ struct pca9468_charger *pca9468 =
+ container_of(work, struct pca9468_charger, timer_work.work);
+ int ret = 0;
+ unsigned int val;
+
+
+ pr_debug("%s: timer id=%d, charging_state=%u\n", __func__,
+ pca9468->timer_id, pca9468->charging_state);
+
+ switch (pca9468->timer_id) {
+
+ /* charging_state <- DC_STATE_CHECK_VBAT */
+ case TIMER_VBATMIN_CHECK:
+ ret = pca9468_check_vbatmin(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ /* charging_state <- DC_STATE_PRESET_DC */
+ case TIMER_PRESET_DC:
+ ret = pca9468_start_direct_charging(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ /*
+ * charging_state <- DC_STATE_PRESET_DC
+ * preset configuration, start charging
+ */
+ case TIMER_PRESET_CONFIG:
+ ret = pca9468_preset_config(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ /*
+ * charging_state <- DC_STATE_PRESET_DC
+ * 150 ms after preset_config
+ */
+ case TIMER_CHECK_ACTIVE:
+ ret = pca9468_check_active_state(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case TIMER_ADJUST_CCMODE:
+ ret = pca9468_charge_adjust_ccmode(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case TIMER_CHECK_CCMODE:
+ ret = pca9468_charge_ccmode(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case TIMER_ENTER_CVMODE:
+ /* Enter Pre-CV mode */
+ ret = pca9468_charge_start_cvmode(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case TIMER_CHECK_CVMODE:
+ ret = pca9468_charge_cvmode(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case TIMER_PDMSG_SEND:
+ /* Adjust TA current and voltage step */
+ if (pca9468->ta_type == TA_TYPE_WIRELESS) {
+ /* RX voltage resolution is 100mV */
+ val = pca9468->ta_vol / WCRX_VOL_STEP;
+ pca9468->ta_vol = val * WCRX_VOL_STEP;
+
+ /* Set RX voltage */
+ ret = pca9468_send_rx_voltage(pca9468,
+ WCRX_REQUEST_VOLTAGE);
+ } else {
+ /* PPS voltage resolution is 20mV */
+ val = pca9468->ta_vol / PD_MSG_TA_VOL_STEP;
+ pca9468->ta_vol = val * PD_MSG_TA_VOL_STEP;
+ /* PPS current resolution is 50mA */
+ val = pca9468->ta_cur / PD_MSG_TA_CUR_STEP;
+ pca9468->ta_cur = val * PD_MSG_TA_CUR_STEP;
+ /* PPS minimum current is 1000mA */
+ if (pca9468->ta_cur < PCA9468_TA_MIN_CUR)
+ pca9468->ta_cur = PCA9468_TA_MIN_CUR;
+
+ /* Send PD Message */
+ ret = pca9468_send_pd_message(pca9468,
+ PD_MSG_REQUEST_APDO);
+ }
+
+ if (ret < 0)
+ pr_err("%s: Error-send_pd_message to %d (%d)\n",
+ __func__, pca9468->ta_type, ret);
+
+ /* Go to the next state */
+ mutex_lock(&pca9468->lock);
+ switch (pca9468->charging_state) {
+ case DC_STATE_PRESET_DC:
+ pca9468->timer_id = TIMER_PRESET_CONFIG;
+ break;
+ case DC_STATE_ADJUST_CC:
+ pca9468->timer_id = TIMER_ADJUST_CCMODE;
+ break;
+ case DC_STATE_CC_MODE:
+ pca9468->timer_id = TIMER_CHECK_CCMODE;
+ break;
+ case DC_STATE_START_CV:
+ pca9468->timer_id = TIMER_ENTER_CVMODE;
+ break;
+ case DC_STATE_CV_MODE:
+ pca9468->timer_id = TIMER_CHECK_CVMODE;
+ break;
+ case DC_STATE_ADJUST_TAVOL:
+ pca9468->timer_id = TIMER_ADJUST_TAVOL;
+ break;
+ case DC_STATE_ADJUST_TACUR:
+ pca9468->timer_id = TIMER_ADJUST_TACUR;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ pca9468->timer_period = PCA9468_PDMSG_WAIT_T;
+ mutex_unlock(&pca9468->lock);
+
+ pr_debug("%s: charging_state=%u next_time_id => pca9468->timer_id=%d\n",
+ __func__, pca9468->charging_state, pca9468->ta_type,
+ ret);
+
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ break;
+
+ case TIMER_ADJUST_TAVOL:
+ ret = pca9468_adjust_ta_voltage(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case TIMER_ADJUST_TACUR:
+ ret = pca9468_adjust_ta_current(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+ case TIMER_ID_NONE:
+ ret = pca9468_stop_charging(pca9468);
+ if (ret < 0)
+ goto error;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Check the charging state again */
+ if (pca9468->charging_state == DC_STATE_NO_CHARGING) {
+ /* Cancel work queue again */
+ cancel_delayed_work(&pca9468->timer_work);
+ cancel_delayed_work(&pca9468->pps_work);
+ }
+
+ return;
+
+error:
+ pca9468_stop_charging(pca9468);
+ return;
+}
+
+
+/* delayed work function for pps periodic timer */
+static void pca9468_pps_request_work(struct work_struct *work)
+{
+ struct pca9468_charger *pca9468 = container_of(work,
+ struct pca9468_charger, pps_work.work);
+ int ret;
+
+ pr_debug("%s: =========START=========\n", __func__);
+ pr_debug("%s: = charging_state=%u == \n", __func__,
+ pca9468->charging_state);
+
+ ret = pca9468_send_pd_message(pca9468, PD_MSG_REQUEST_APDO);
+ if (ret < 0)
+ pr_err("%s: Error-send_pd_message\n", __func__);
+
+ /* TODO: background stuff */
+
+ pr_debug("%s: ret=%d\n", __func__, ret);
+}
+
+static int pca9468_hw_init(struct pca9468_charger *pca9468)
+{
+ unsigned int val;
+ int ret;
+
+ pr_debug("%s: =========START=========\n", __func__);
+
+ /* Read Device info register to check the incomplete I2C operation */
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_DEVICE_INFO, &val);
+ if ((ret < 0) || (val != PCA9468_DEVICE_ID)) {
+ /* incomplete I2C operation or I2C communication error */
+ /* Read Device info register again */
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_DEVICE_INFO,
+ &val);
+ if ((ret < 0) || (val != PCA9468_DEVICE_ID)) {
+ dev_err(pca9468->dev,
+ "reading DEVICE_INFO failed, val=0x%x\n",
+ val);
+ ret = -EINVAL;
+ return ret;
+ }
+ }
+
+ /* Program the platform specific configuration values */
+
+ /* Set OV_DELTA to 40% */
+ val = OV_DELTA_40P << MASK2SHIFT(PCA9468_BIT_OV_DELTA);
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_SAFETY_CTRL,
+ PCA9468_BIT_OV_DELTA, val);
+ if (ret < 0)
+ return ret;
+
+ /* Set Switching Frequency */
+ val = pca9468->pdata->fsw_cfg << MASK2SHIFT(PCA9468_BIT_FSW_CFG);
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_START_CTRL,
+ PCA9468_BIT_FSW_CFG, val);
+ if (ret < 0)
+ return ret;
+
+ /* Set Reverse Current Detection and standby mode*/
+ val = PCA9468_BIT_REV_IIN_DET | PCA9468_EN_ACTIVE_L |
+ PCA9468_STANDBY_FORCED;
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_START_CTRL,
+ (PCA9468_BIT_REV_IIN_DET |
+ PCA9468_BIT_EN_CFG |
+ PCA9468_BIT_STANDBY_EN),
+ val);
+ if (ret < 0)
+ return ret;
+
+ /* clear LIMIT_INCREMENT_EN */
+ val = 0;
+ ret = regmap_update_bits(pca9468->regmap, PCA9468_REG_IIN_CTRL,
+ PCA9468_BIT_LIMIT_INCREMENT_EN, val);
+ if (ret < 0)
+ return ret;
+ /* Set the ADC channels, NTC is invalid if Bias is not enabled */
+ val = PCA9468_BIT_CH7_EN | /* NTC voltage ADC */
+ PCA9468_BIT_CH6_EN | /* DIETEMP ADC */
+ PCA9468_BIT_CH5_EN | /* IIN ADC */
+ PCA9468_BIT_CH3_EN | /* VBAT ADC */
+ PCA9468_BIT_CH2_EN | /* VIN ADC */
+ PCA9468_BIT_CH1_EN; /* VOUT ADC */
+
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_ADC_CFG, val);
+ if (ret < 0)
+ return ret;
+
+ /* ADC Mode change */
+ val = 0x5B;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_ADC_ACCESS, val);
+ if (ret < 0)
+ return ret;
+ val = 0x10;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_ADC_MODE, val);
+ if (ret < 0)
+ return ret;
+ val = 0x00;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_ADC_ACCESS, val);
+ if (ret < 0)
+ return ret;
+
+ /* Read ADC compensation gain */
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_ADC_ADJUST, &val);
+ if (ret < 0)
+ return ret;
+ pca9468->adc_comp_gain =
+ adc_gain[val >> MASK2SHIFT(PCA9468_BIT_ADC_GAIN)];
+
+ /* input current - uA*/
+ ret = pca9468_set_input_current(pca9468, pca9468->pdata->iin_cfg);
+ if (ret < 0)
+ return ret;
+
+ /* charging current */
+ ret = pca9468_set_charging_current(pca9468, pca9468->pdata->ichg_cfg);
+ if (ret < 0)
+ return ret;
+
+ /* v float voltage */
+ ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+
+static irqreturn_t pca9468_interrupt_handler(int irq, void *data)
+{
+ struct pca9468_charger *pca9468 = data;
+ /* INT1, INT1_MSK, INT1_STS, STS_A, B, C, D */
+ u8 int1[REG_INT1_MAX], sts[REG_STS_MAX];
+ u8 masked_int; /* masked int */
+ bool handled = false;
+ int ret;
+
+ /* Read INT1, INT1_MSK, INT1_STS */
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_INT1, int1, 3);
+ if (ret < 0) {
+ dev_warn(pca9468->dev, "reading INT1_X failed\n");
+ return IRQ_NONE;
+ }
+
+ /* Read STS_A, B, C, D */
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_STS_A, sts, 4);
+ if (ret < 0) {
+ dev_warn(pca9468->dev, "reading STS_X failed\n");
+ return IRQ_NONE;
+ }
+
+ pr_debug("%s: int1=0x%2x, int1_sts=0x%2x, sts_a=0x%2x\n", __func__,
+ int1[REG_INT1], int1[REG_INT1_STS], sts[REG_STS_A]);
+
+ /* Check Interrupt */
+ masked_int = int1[REG_INT1] & !int1[REG_INT1_MSK];
+ if (masked_int & PCA9468_BIT_V_OK_INT) {
+ /* V_OK interrupt happened */
+ mutex_lock(&pca9468->lock);
+ pca9468->mains_online = !!(int1[REG_INT1_STS] &
+ PCA9468_BIT_V_OK_STS);
+
+ /* TODO: alex perform a clean shutdown */
+ mutex_unlock(&pca9468->lock);
+ power_supply_changed(pca9468->mains);
+ handled = true;
+ }
+
+ if (masked_int & PCA9468_BIT_NTC_TEMP_INT) {
+ /* NTC_TEMP interrupt happened */
+ if (int1[REG_INT1_STS] & PCA9468_BIT_NTC_TEMP_STS) {
+ /* above NTC_THRESHOLD */
+ dev_err(pca9468->dev, "charging stopped due to NTC threshold voltage\n");
+ }
+ handled = true;
+ }
+
+ if (masked_int & PCA9468_BIT_CHG_PHASE_INT) {
+ /* CHG_PHASE interrupt happened */
+ if (int1[REG_INT1_STS] & PCA9468_BIT_CHG_PHASE_STS) {
+ /* Any of loops is active*/
+ if (sts[REG_STS_A] & PCA9468_BIT_VFLT_LOOP_STS) {
+ /* V_FLOAT loop is in regulation */
+ pr_debug("%s: V_FLOAT loop interrupt\n",
+ __func__);
+ /* Disable CHG_PHASE_M */
+ ret = regmap_update_bits(pca9468->regmap,
+ PCA9468_REG_INT1_MSK,
+ PCA9468_BIT_CHG_PHASE_M,
+ PCA9468_BIT_CHG_PHASE_M);
+ if (ret < 0) {
+ handled = false;
+ return handled;
+ }
+
+ /* Go to Pre CV Mode */
+ pca9468->timer_id = TIMER_ENTER_CVMODE;
+ pca9468->timer_period = 10;
+ queue_delayed_work(pca9468->dc_wq,
+ &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+
+ } else if (sts[REG_STS_A] & PCA9468_BIT_IIN_LOOP_STS) {
+ /* IIN loop or ICHG loop is in regulation */
+ pr_debug("%s: IIN loop interrupt\n", __func__);
+ } else if (sts[REG_STS_A] & PCA9468_BIT_CHG_LOOP_STS) {
+ /* ICHG loop is in regulation */
+ pr_debug("%s: ICHG loop interrupt\n", __func__);
+ }
+ }
+ handled = true;
+ }
+
+ if (masked_int & PCA9468_BIT_CTRL_LIMIT_INT) {
+ /* CTRL_LIMIT interrupt happened */
+ if (int1[REG_INT1_STS] & PCA9468_BIT_CTRL_LIMIT_STS) {
+ /* No Loop is active or OCP */
+ if (sts[REG_STS_B] & PCA9468_BIT_OCP_FAST_STS) {
+ /* Input fast over current */
+ dev_err(pca9468->dev, "IIN > 50A instantaneously\n");
+ }
+ if (sts[REG_STS_B] & PCA9468_BIT_OCP_AVG_STS) {
+ /* Input average over current */
+ dev_err(pca9468->dev, "IIN > IIN_CFG*150percent\n");
+ }
+ }
+ handled = true;
+ }
+
+ if (masked_int & PCA9468_BIT_TEMP_REG_INT) {
+ /* TEMP_REG interrupt happened */
+ if (int1[REG_INT1_STS] & PCA9468_BIT_TEMP_REG_STS) {
+ /* Device is in temperature regulation */
+ dev_err(pca9468->dev, "Device is in temperature regulation\n");
+ }
+ handled = true;
+ }
+
+ if (masked_int & PCA9468_BIT_ADC_DONE_INT) {
+ /* ADC complete interrupt happened */
+ dev_dbg(pca9468->dev, "ADC has been completed\n");
+ handled = true;
+ }
+
+ if (masked_int & PCA9468_BIT_TIMER_INT) {
+ /* Timer falut interrupt happened */
+ if (int1[REG_INT1_STS] & PCA9468_BIT_TIMER_STS) {
+ if (sts[REG_STS_B] & PCA9468_BIT_CHARGE_TIMER_STS) {
+ /* Charger timer is expired */
+ dev_err(pca9468->dev, "Charger timer is expired\n");
+ }
+ if (sts[REG_STS_B] & PCA9468_BIT_WATCHDOG_TIMER_STS) {
+ /* Watchdog timer is expired */
+ dev_err(pca9468->dev, "Watchdog timer is expired\n");
+ }
+ }
+ handled = true;
+ }
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int pca9468_irq_init(struct pca9468_charger *pca9468,
+ struct i2c_client *client)
+{
+ const struct pca9468_platform_data *pdata = pca9468->pdata;
+ int ret, msk, irq;
+
+ pr_debug("%s: =========START=========\n", __func__);
+
+ irq = gpio_to_irq(pdata->irq_gpio);
+
+ ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name);
+ if (ret < 0)
+ goto fail;
+
+ ret = request_threaded_irq(irq, NULL, pca9468_interrupt_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ client->name, pca9468);
+ if (ret < 0)
+ goto fail_gpio;
+
+ /* disable all interrupts by default. */
+ msk = PCA9468_BIT_V_OK_M |
+ PCA9468_BIT_NTC_TEMP_M |
+ PCA9468_BIT_CHG_PHASE_M |
+ PCA9468_BIT_RESERVED_M |
+ PCA9468_BIT_CTRL_LIMIT_M |
+ PCA9468_BIT_TEMP_REG_M |
+ PCA9468_BIT_ADC_DONE_M |
+ PCA9468_BIT_TIMER_M;
+ ret = regmap_write(pca9468->regmap, PCA9468_REG_INT1_MSK, msk);
+ if (ret < 0)
+ goto fail_wirte;
+
+ client->irq = irq;
+ return 0;
+
+fail_wirte:
+ free_irq(irq, pca9468);
+fail_gpio:
+ gpio_free(pdata->irq_gpio);
+fail:
+ client->irq = 0;
+ return ret;
+}
+
+
+/*
+ * Returns the input current limit programmed
+ * into the charger in uA.
+ */
+static int get_input_current_limit(struct pca9468_charger *pca9468)
+{
+ int ret, intval;
+ unsigned int val;
+
+ if (!pca9468->mains_online)
+ return -ENODATA;
+
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_IIN_CTRL, &val);
+ if (ret < 0)
+ return ret;
+
+ intval = (val & PCA9468_BIT_IIN_CFG) * 100000;
+
+ if (intval < 500000)
+ intval = 500000;
+
+ return intval;
+}
+
+/*
+ * Returns the constant charge current programmed
+ * into the charger in uA.
+ */
+static int get_const_charge_current(struct pca9468_charger *pca9468)
+{
+ /* Charging current cannot be controlled directly */
+ return pca9468->cc_max;
+}
+
+/*
+ * Returns the constant charge voltage programmed
+ * into the charger in uV.
+ */
+static int get_const_charge_voltage(struct pca9468_charger *pca9468)
+{
+ int ret, intval;
+ unsigned int val;
+
+ if (!pca9468->mains_online)
+ return -ENODATA;
+
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_V_FLOAT, &val);
+ if (ret < 0)
+ return ret;
+
+ intval = (val * 5 + 3725) * 1000;
+
+ return intval;
+}
+
+/* Returns the enable or disable value. into 1 or 0. */
+static int get_charging_enabled(struct pca9468_charger *pca9468)
+{
+ int ret, intval;
+ unsigned int val;
+
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_START_CTRL, &val);
+ if (ret < 0)
+ return ret;
+
+ intval = (val & PCA9468_BIT_STANDBY_EN) ? 0 : 1;
+
+ return intval;
+}
+
+static int pca9468_mains_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct pca9468_charger *pca9468 = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ pr_debug("%s: =========START=========\n", __func__);
+ pr_debug("%s: prop=%d, val=%d\n", __func__, prop, val->intval);
+
+ switch (prop) {
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ pca9468->mains_online = val->intval;
+ break;
+
+ /* TODO: locking is wrong */
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ if (val->intval == 0) {
+ /* Cancel delayed work */
+ cancel_delayed_work(&pca9468->timer_work);
+ cancel_delayed_work(&pca9468->pps_work);
+
+ /* Stop Direct Charging */
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_id = TIMER_ID_NONE;
+ pca9468->timer_period = 0;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ } else if (pca9468->charging_state == DC_STATE_NO_CHARGING) {
+ /* Start Direct Charging */
+
+ /* Start 1sec timer for battery check */
+ mutex_lock(&pca9468->lock);
+ pca9468->charging_state = DC_STATE_CHECK_VBAT;
+ pca9468->timer_id = TIMER_VBATMIN_CHECK;
+
+ /* The delay time for PD state goes to PE_SNK_STATE */
+ pca9468->timer_period = 1000;
+ mutex_unlock(&pca9468->lock);
+ queue_delayed_work(pca9468->dc_wq, &pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ /* Set the initial charging step */
+ power_supply_changed(pca9468->mains);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ pr_debug("%s: new_vfloat=%u->%d\n", __func__,
+ pca9468->new_vfloat, val->intval);
+
+ pca9468->fv_uv = val->intval;
+ if (val->intval < 0) {
+ pr_debug("%s: ignore negative vfloat %d\n",
+ __func__, val->intval);
+ } else if (val->intval != pca9468->new_vfloat) {
+ /* race with pca9468_set_new_vfloat(pca9468) */
+ pca9468->new_vfloat = val->intval;
+ ret = pca9468_set_new_vfloat(pca9468);
+ }
+ break;
+
+ /* pcaA9468 cannot control charging current directly. */
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ pr_debug("%s: new_iin=%u->%d\n", __func__,
+ pca9468->new_iin, val->intval);
+ pca9468->cc_max = val->intval;
+
+ if (val->intval < 0) {
+ pr_debug("%s: ignore negative iin %d\n",
+ __func__, val->intval);
+ } else if (val->intval != pca9468->new_iin) {
+ /* request new input current */
+ pca9468->new_iin = val->intval;
+ ret = pca9468_set_new_iin(pca9468);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ pr_debug("%s: End, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int pca9468_get_charge_type(struct pca9468_charger *pca9468)
+{
+ int ret, sts;
+
+ if (!pca9468->mains_online)
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ ret = regmap_read(pca9468->regmap, PCA9468_REG_STS_A, &sts);
+ if (ret < 0)
+ return ret;
+
+ pr_debug("%s: sts_a=%0x2\n", __func__, sts);
+
+ /* Use SW state for now */
+ switch (pca9468->charging_state) {
+ case DC_STATE_ADJUST_CC:
+ case DC_STATE_CC_MODE:
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ case DC_STATE_START_CV:
+ case DC_STATE_CV_MODE:
+ return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ case DC_STATE_CHARGING_DONE:
+ break;
+ }
+
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+#define PCA9468_NOT_CHARGING \
+ (PCA9468_BIT_SHUTDOWN_STATE_STS | PCA9468_BIT_STANDBY_STATE_STS)
+#define PCA9468_ANY_CHARGING_LOOP \
+ (PCA9468_BIT_CHG_LOOP_STS | PCA9468_BIT_IIN_LOOP_STS | \
+ PCA9468_BIT_VFLT_LOOP_STS)
+
+static int pca9468_get_status(struct pca9468_charger *pca9468)
+{
+ u8 val[8];
+ int ret;
+
+ ret = regmap_bulk_read(pca9468->regmap, PCA9468_REG_INT1_STS,
+ &val[PCA9468_REG_INT1_STS], 5);
+ if (ret < 0) {
+ pr_debug("%s: ioerr=%d", __func__, ret);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ pr_debug("%s: int1_sts=0x%x,sts_a=0x%x,sts_b=0x%x,sts_c=0x%x,sts_d=0x%x\n",
+ __func__, val[3], val[4], val[5], val[6], val[7]);
+
+ if ((val[PCA9468_REG_STS_B] & PCA9468_BIT_ACTIVE_STATE_STS) == 0 ||
+ (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_V_OK_STS) == 0) {
+ const bool online = pca9468->mains_online;
+
+ /* no disconnect during charger transition */
+ return online ? POWER_SUPPLY_STATUS_NOT_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ /* Use SW state (for now) */
+ switch (pca9468->charging_state) {
+ case DC_STATE_NO_CHARGING:
+ case DC_STATE_CHECK_VBAT:
+ case DC_STATE_PRESET_DC:
+ case DC_STATE_CHECK_ACTIVE: /* last state really */
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ case DC_STATE_ADJUST_CC:
+ case DC_STATE_CC_MODE:
+ case DC_STATE_START_CV:
+ case DC_STATE_CV_MODE:
+ return POWER_SUPPLY_STATUS_CHARGING;
+ case DC_STATE_CHARGING_DONE:
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+}
+
+#define PCA9468_PRESENT_MASK \
+ (PCA9468_BIT_ACTIVE_STATE_STS | PCA9468_BIT_STANDBY_STATE_STS)
+
+static int pca9468_is_present(struct pca9468_charger *pca9468)
+{
+ int sts = 0;
+
+ regmap_read(pca9468->regmap, PCA9468_REG_STS_B, &sts);
+ return !!(sts & PCA9468_PRESENT_MASK);
+}
+
+static int pca9468_is_done(struct pca9468_charger *pca9468)
+{
+ /* Check the charging state */
+ return pca9468->charging_state == DC_STATE_CHARGING_DONE;
+}
+
+static int pca9468_get_chg_chgr_state(struct pca9468_charger *pca9468,
+ union gbms_charger_state *chg_state)
+{
+ chg_state->v = 0;
+ chg_state->f.chg_status = pca9468_get_status(pca9468);
+ chg_state->f.chg_type = pca9468_get_charge_type(pca9468);
+ chg_state->f.flags = gbms_gen_chg_flags(chg_state->f.chg_status,
+ chg_state->f.chg_type);
+
+ /* chg_state->f.vchrg == 0, disable tier matching */
+
+ if (chg_state->f.chg_status != POWER_SUPPLY_STATUS_DISCHARGING) {
+ int rc;
+
+ rc = get_input_current_limit(pca9468);
+ if (rc > 0)
+ chg_state->f.icl = rc / 1000;
+ }
+
+ return 0;
+}
+
+static int pca9468_mains_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct pca9468_charger *pca9468 = power_supply_get_drvdata(psy);
+ union gbms_charger_state chg_state;
+ int intval, ret = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = pca9468->mains_online;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pca9468_is_present(pca9468);
+ if (val->intval < 0)
+ val->intval = 0;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_DONE:
+ val->intval = pca9468_is_done(pca9468);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ ret = get_charging_enabled(pca9468);
+ if (ret < 0)
+ return ret;
+ val->intval = !ret;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ ret = get_charging_enabled(pca9468);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = get_const_charge_voltage(pca9468);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = get_const_charge_current(pca9468);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = get_input_current_limit(pca9468);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+
+ /* ADCCH_ICHG is dead, this is input current */
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ /* return the output current - uA unit */
+ /* check charging status */
+ if (pca9468->charging_state == DC_STATE_NO_CHARGING) {
+ /* return invalid */
+ val->intval = 0;
+ } else {
+ int iin;
+ const int offset = iin_fsw_cfg[pca9468->pdata->fsw_cfg];
+
+ /* get input current */
+ iin = pca9468_read_adc(pca9468, ADCCH_IIN);
+ if (ret < 0) {
+ dev_err(pca9468->dev, "Invalid IIN ADC\n");
+ return ret;
+ }
+
+ /* calculate the output current */
+ /* Iout = (Iin - Ifsw_cfg)*2 */
+ val->intval = (iin - offset) * 2;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_CHARGER_STATE:
+ ret = pca9468_get_chg_chgr_state(pca9468, &chg_state);
+ if (ret == 0)
+ val->int64val = chg_state.v;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ intval = pca9468_read_adc(pca9468, ADCCH_VOUT);
+ if (intval < 0)
+ return intval;
+ val->intval = intval;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ intval = pca9468_read_adc(pca9468, ADCCH_VBAT);
+ if (intval < 0)
+ return intval;
+ val->intval = intval;
+ break;
+
+ /* TODO: read NTC temperature? */
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = pca9468_read_adc(pca9468, ADCCH_DIETEMP);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = pca9468_get_charge_type(pca9468);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = pca9468_get_status(pca9468);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = get_input_current_limit(pca9468);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * GBMS not visible
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ * POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ */
+static enum power_supply_property pca9468_mains_properties[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGE_DISABLE,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ /* same as POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT */
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+};
+
+static int pca9468_mains_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CHARGE_DISABLE:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static bool pca9468_is_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case PCA9468_REG_DEVICE_INFO ... PCA9468_REG_STS_ADC_9:
+ case PCA9468_REG_IIN_CTRL ... PCA9468_REG_NTC_TH_2:
+ case PCA9468_REG_ADC_ACCESS:
+ case PCA9468_REG_ADC_ADJUST:
+ case PCA9468_REG_ADC_IMPROVE:
+ case PCA9468_REG_ADC_MODE:
+ case 0x40 ... 0x4f: /* debug */
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static const struct regmap_config pca9468_regmap = {
+ .name = "pca9468-mains",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = PCA9468_MAX_REGISTER,
+ .readable_reg = pca9468_is_reg,
+ .volatile_reg = pca9468_is_reg,
+};
+
+static const struct power_supply_desc pca9468_mains_desc = {
+ .name = "pca9468-mains",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = pca9468_mains_get_property,
+ .set_property = pca9468_mains_set_property,
+ .properties = pca9468_mains_properties,
+ .property_is_writeable = pca9468_mains_is_writeable,
+ .num_properties = ARRAY_SIZE(pca9468_mains_properties),
+};
+
+#if defined(CONFIG_OF)
+static int of_pca9468_dt(struct device *dev,
+ struct pca9468_platform_data *pdata)
+{
+ struct device_node *np_pca9468 = dev->of_node;
+ int ret;
+ if(!np_pca9468)
+ return -EINVAL;
+
+ /* irq gpio */
+ pdata->irq_gpio = of_get_named_gpio(np_pca9468, "pca9468,irq-gpio", 0);
+ pr_info("%s: irq-gpio: %d \n", __func__, pdata->irq_gpio);
+
+ /* input current limit */
+ ret = of_property_read_u32(np_pca9468, "pca9468,input-current-limit",
+ &pdata->iin_cfg);
+ if (ret) {
+ pr_warn("%s: pca9468,input-current-limit is Empty\n", __func__);
+ pdata->iin_cfg = PCA9468_IIN_CFG_DFT;
+ }
+ pr_info("%s: pca9468,iin_cfg is %u\n", __func__, pdata->iin_cfg);
+
+ /* charging current */
+ ret = of_property_read_u32(np_pca9468, "pca9468,charging-current",
+ &pdata->ichg_cfg);
+ if (ret) {
+ pr_warn("%s: pca9468,charging-current is Empty\n", __func__);
+ pdata->ichg_cfg = PCA9468_ICHG_CFG_DFT;
+ }
+ pr_info("%s: pca9468,ichg_cfg is %u\n", __func__, pdata->ichg_cfg);
+
+ /* charging float voltage */
+ ret = of_property_read_u32(np_pca9468, "pca9468,float-voltage",
+ &pdata->v_float);
+ if (ret) {
+ pr_warn("%s: pca9468,float-voltage is Empty\n", __func__);
+ pdata->v_float = PCA9468_VFLOAT_DFT;
+ }
+ pr_info("%s: pca9468,v_float is %u\n", __func__, pdata->v_float);
+
+ /* input topoff current */
+ ret = of_property_read_u32(np_pca9468, "pca9468,input-itopoff",
+ &pdata->iin_topoff);
+ if (ret) {
+ pr_warn("%s: pca9468,input-itopoff is Empty\n", __func__);
+ pdata->iin_topoff = PCA9468_IIN_DONE_DFT;
+ }
+ pr_info("%s: pca9468,iin_topoff is %u\n", __func__, pdata->iin_topoff);
+
+ /* switching frequency */
+ ret = of_property_read_u32(np_pca9468, "pca9468,switching-frequency",
+ &pdata->fsw_cfg);
+ if (ret) {
+ pr_warn("%s: pca9468,switching frequency is Empty\n", __func__);
+ pdata->fsw_cfg = PCA9468_FSW_CFG_DFT;
+ }
+ pr_info("%s: pca9468,fsw_cfg is %u\n", __func__, pdata->fsw_cfg);
+
+ /* NTC threshold voltage */
+ ret = of_property_read_u32(np_pca9468, "pca9468,ntc-threshold",
+ &pdata->ntc_th);
+ if (ret) {
+ pr_warn("%s: pca9468,ntc threshold voltage is Empty\n",
+ __func__);
+ pdata->ntc_th = PCA9468_NTC_TH_DFT;
+ }
+ pr_info("%s: pca9468,ntc_th is %u\n", __func__, pdata->ntc_th);
+
+ /* Charging mode */
+ ret = of_property_read_u32(np_pca9468, "pca9468,chg-mode",
+ &pdata->chg_mode);
+ if (ret) {
+ pr_info("%s: pca9468,charging mode is Empty\n", __func__);
+ pdata->chg_mode = CHG_2TO1_DC_MODE;
+ }
+ pr_info("%s: pca9468,chg_mode is %u\n", __func__, pdata->chg_mode);
+
+ return 0;
+}
+#else
+static int of_pca9468_dt(struct device *dev,
+ struct pca9468_platform_data *pdata)
+{
+ return 0;
+}
+#endif /* CONFIG_OF */
+
+static int read_reg(void *data, u64 *val)
+{
+ struct pca9468_charger *chip = data;
+ int rc;
+ unsigned int temp;
+
+ rc = regmap_read(chip->regmap, chip->debug_address, &temp);
+ if (rc) {
+ pr_err("Couldn't read reg %x rc = %d\n",
+ chip->debug_address, rc);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int write_reg(void *data, u64 val)
+{
+ struct pca9468_charger *chip = data;
+ int rc;
+ u8 temp;
+
+ temp = (u8) val;
+ rc = regmap_write(chip->regmap, chip->debug_address, temp);
+ if (rc) {
+ pr_err("Couldn't write 0x%02x to 0x%02x rc = %d\n",
+ temp, chip->debug_address, rc);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(register_debug_ops, read_reg, write_reg, "0x%02llx\n");
+
+static int debug_adc_chan_get(void *data, u64 *val)
+{
+ struct pca9468_charger *pca9468 = data;
+
+ *val = pca9468_read_adc(data, pca9468->debug_adc_channel);
+ return 0;
+}
+
+static int debug_adc_chan_set(void *data, u64 val)
+{
+ struct pca9468_charger *pca9468 = data;
+
+ if (val < ADCCH_VOUT || val >= ADCCH_MAX)
+ return -EINVAL;
+ pca9468->debug_adc_channel = val;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_adc_chan_ops, debug_adc_chan_get,
+ debug_adc_chan_set, "%llu\n");
+
+
+
+static int pca9468_create_debugfs_entries(struct pca9468_charger *chip)
+{
+ struct dentry *ent;
+
+ chip->debug_root = debugfs_create_dir("charger-pca9468", NULL);
+ if (IS_ERR_OR_NULL(chip->debug_root)) {
+ dev_err(chip->dev, "Couldn't create debug dir\n");
+ return -ENOENT;
+ }
+
+ ent = debugfs_create_x32("address", 0644, chip->debug_root,
+ &chip->debug_address);
+ if (!ent)
+ dev_err(chip->dev, "Couldn't create address debug file\n");
+
+ ent = debugfs_create_file("data", 0644, chip->debug_root, chip,
+ ®ister_debug_ops);
+ if (!ent)
+ dev_err(chip->dev, "Couldn't create data debug file\n");
+
+ chip->debug_adc_channel = ADCCH_VOUT;
+ ent = debugfs_create_file("adc_chan", 0644, chip->debug_root, chip,
+ &debug_adc_chan_ops);
+
+ if (!ent)
+ dev_err(chip->dev, "Couldn't create data debug file\n");
+
+ chip->debug_adc_channel = ADCCH_VOUT;
+ ent = debugfs_create_file("adc_chan", 0644, chip->debug_root, chip,
+ &debug_adc_chan_ops);
+ if (!ent)
+ dev_err(chip->dev, "Couldn't create data debug file\n");
+
+ return 0;
+}
+
+static int pca9468_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ static char *battery[] = { "pca9468-battery" };
+ struct power_supply_config mains_cfg = {};
+ struct pca9468_platform_data *pdata;
+ struct device *dev = &client->dev;
+ struct pca9468_charger *pca9468_chg;
+ int ret;
+
+ pr_debug("%s: =========START=========\n", __func__);
+
+ pca9468_chg = devm_kzalloc(dev, sizeof(*pca9468_chg), GFP_KERNEL);
+ if (!pca9468_chg)
+ return -ENOMEM;
+
+#if defined(CONFIG_OF)
+ if (client->dev.of_node) {
+ pdata = devm_kzalloc(&client->dev,
+ sizeof(struct pca9468_platform_data),
+ GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&client->dev, "Failed to allocate memory \n");
+ return -ENOMEM;
+ }
+
+ ret = of_pca9468_dt(&client->dev, pdata);
+ if (ret < 0){
+ dev_err(&client->dev, "Failed to get device of_node \n");
+ return -ENOMEM;
+ }
+
+ client->dev.platform_data = pdata;
+ } else {
+ pdata = client->dev.platform_data;
+ }
+#else
+ pdata = dev->platform_data;
+#endif
+ if (!pdata)
+ return -EINVAL;
+
+ i2c_set_clientdata(client, pca9468_chg);
+
+ mutex_init(&pca9468_chg->lock);
+ pca9468_chg->dev = &client->dev;
+ pca9468_chg->pdata = pdata;
+ pca9468_chg->charging_state = DC_STATE_NO_CHARGING;
+
+ /* Create a work queue for the direct charger */
+ pca9468_chg->dc_wq = alloc_ordered_workqueue("pca9468_dc_wq",
+ WQ_MEM_RECLAIM);
+ if (pca9468_chg->dc_wq == NULL) {
+ dev_err(pca9468_chg->dev, "failed to create work queue\n");
+ return -ENOMEM;
+ }
+
+ wakeup_source_init(&pca9468_chg->monitor_wake_lock,
+ "pca9468-charger-monitor");
+
+ /* initialize work */
+ INIT_DELAYED_WORK(&pca9468_chg->timer_work, pca9468_timer_work);
+ pca9468_chg->timer_id = TIMER_ID_NONE;
+ pca9468_chg->timer_period = 0;
+
+ INIT_DELAYED_WORK(&pca9468_chg->pps_work, pca9468_pps_request_work);
+
+ pca9468_chg->regmap = devm_regmap_init_i2c(client, &pca9468_regmap);
+ if (IS_ERR(pca9468_chg->regmap)) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ ret = of_property_read_u32(pca9468_chg->dev->of_node,
+ "google,tcpm-power-supply",
+ &pca9468_chg->tcpm_phandle);
+ if (ret < 0)
+ pr_warn("pca6468: pca,tcpm-power-supply not defined\n");
+
+ ret = pca9468_hw_init(pca9468_chg);
+ if (ret < 0)
+ goto error;
+
+ mains_cfg.supplied_to = battery;
+ mains_cfg.num_supplicants = ARRAY_SIZE(battery);
+ mains_cfg.drv_data = pca9468_chg;
+ pca9468_chg->mains = devm_power_supply_register(dev,
+ &pca9468_mains_desc,
+ &mains_cfg);
+ if (IS_ERR(pca9468_chg->mains)) {
+ ret = -ENODEV;
+ goto error;
+ }
+
+ /* Interrupt pin is optional. */
+ if (pdata->irq_gpio >= 0) {
+ ret = pca9468_irq_init(pca9468_chg, client);
+ if (ret < 0) {
+ dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
+ dev_warn(dev, "disabling IRQ support\n");
+ }
+
+ /* disable interrupt */
+ disable_irq(client->irq);
+ }
+
+ ret = pca9468_create_debugfs_entries(pca9468_chg);
+ if (ret < 0)
+ dev_err(dev, "error while registering debugfs %d\n", ret);
+
+ /* setup PPS, not needed if tcpm-power-supply is not there */
+ ret = pps_init(&pca9468_chg->pps_data, pca9468_chg->dev);
+ if (ret == 0 && pca9468_chg->debug_root)
+ pps_init_fs(&pca9468_chg->pps_data,
+ pca9468_chg->debug_root);
+ if (ret == 0) {
+ pps_init_state(&pca9468_chg->pps_data);
+ pr_info("pca9468: PPS direct available\n");
+ }
+
+#ifdef CONFIG_DC_STEP_CHARGING
+ ret = pca9468_step_chg_init(pca9468_chg->dev);
+ if (ret < 0) {
+ dev_err(dev, "Couldn't init pca9468_step_chg_init ret=%d\n",
+ ret);
+ goto error;
+ }
+#endif
+
+ pr_info("pca9468: probe_done\n", __func__);
+ pr_debug("%s: =========END=========\n", __func__);
+ return 0;
+
+error:
+ destroy_workqueue(pca9468_chg->dc_wq);
+ mutex_destroy(&pca9468_chg->lock);
+ wakeup_source_trash(&pca9468_chg->monitor_wake_lock);
+ return ret;
+}
+
+static int pca9468_remove(struct i2c_client *client)
+{
+ struct pca9468_charger *pca9468_chg = i2c_get_clientdata(client);
+
+ /* stop charging if it is active */
+ pca9468_stop_charging(pca9468_chg);
+
+ if (client->irq) {
+ free_irq(client->irq, pca9468_chg);
+ gpio_free(pca9468_chg->pdata->irq_gpio);
+ }
+
+ /* Delete the work queue */
+ destroy_workqueue(pca9468_chg->dc_wq);
+
+ wakeup_source_trash(&pca9468_chg->monitor_wake_lock);
+#ifdef CONFIG_DC_STEP_CHARGING
+ pca9468_step_chg_deinit();
+#endif
+ return 0;
+}
+
+static const struct i2c_device_id pca9468_id[] = {
+ { "pca9468", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pca9468_id);
+
+#if defined(CONFIG_OF)
+static struct of_device_id pca9468_i2c_dt_ids[] = {
+ { .compatible = "nxp,pca9468" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pca9468_i2c_dt_ids);
+#endif /* CONFIG_OF */
+
+#if defined(CONFIG_PM)
+#ifdef CONFIG_RTC_HCTOSYS
+static int get_current_time(unsigned long *now_tm_sec)
+{
+ struct rtc_time tm;
+ struct rtc_device *rtc;
+ int rc;
+
+ rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
+ if (rtc == NULL) {
+ pr_err("%s: unable to open rtc device (%s)\n",
+ __FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
+ return -EINVAL;
+ }
+
+ rc = rtc_read_time(rtc, &tm);
+ if (rc) {
+ pr_err("Error reading rtc device (%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, now_tm_sec);
+
+close_time:
+ rtc_class_close(rtc);
+ return rc;
+}
+
+static void
+pca9468_check_and_update_charging_timer(struct pca9468_charger *pca9468)
+{
+ unsigned long current_time = 0, next_update_time, time_left;
+
+ get_current_time(¤t_time);
+
+ if (pca9468->timer_id != TIMER_ID_NONE) {
+ next_update_time = pca9468->last_update_time +
+ (pca9468->timer_period / 1000); /* seconds */
+
+ pr_debug("%s: current_time=%ld, next_update_time=%ld\n",
+ __func__, current_time, next_update_time);
+
+ if (next_update_time > current_time)
+ time_left = next_update_time - current_time;
+ else
+ time_left = 0;
+
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_period = time_left * 1000; /* ms unit */
+ mutex_unlock(&pca9468->lock);
+ schedule_delayed_work(&pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ pr_debug("%s: timer_id=%d, time_period=%ld\n", __func__,
+ pca9468->timer_id, pca9468->timer_period);
+ }
+ pca9468->last_update_time = current_time;
+}
+#endif
+
+static int pca9468_suspend(struct device *dev)
+{
+ struct pca9468_charger *pca9468 = dev_get_drvdata(dev);
+
+ pr_debug("%s: cancel delayed work\n", __func__);
+
+ /* cancel delayed_work */
+ cancel_delayed_work(&pca9468->timer_work);
+ return 0;
+}
+
+static int pca9468_resume(struct device *dev)
+{
+ struct pca9468_charger *pca9468 = dev_get_drvdata(dev);
+
+ pr_debug("%s: update_timer\n", __func__);
+
+ /* Update the current timer */
+#ifdef CONFIG_RTC_HCTOSYS
+ pca9468_check_and_update_charging_timer(pca9468);
+#else
+ if (pca9468->timer_id != TIMER_ID_NONE) {
+ mutex_lock(&pca9468->lock);
+ pca9468->timer_period = 0; /* ms unit */
+ mutex_unlock(&pca9468->lock);
+ schedule_delayed_work(&pca9468->timer_work,
+ msecs_to_jiffies(pca9468->timer_period));
+ }
+#endif
+ return 0;
+}
+#else
+#define pca9468_suspend NULL
+#define pca9468_resume NULL
+#endif
+
+const struct dev_pm_ops pca9468_pm_ops = {
+ .suspend = pca9468_suspend,
+ .resume = pca9468_resume,
+};
+
+static struct i2c_driver pca9468_driver = {
+ .driver = {
+ .name = "pca9468",
+#if defined(CONFIG_OF)
+ .of_match_table = pca9468_i2c_dt_ids,
+#endif /* CONFIG_OF */
+#if defined(CONFIG_PM)
+ .pm = &pca9468_pm_ops,
+#endif
+ },
+ .probe = pca9468_probe,
+ .remove = pca9468_remove,
+ .id_table = pca9468_id,
+};
+
+module_i2c_driver(pca9468_driver);
+
+MODULE_AUTHOR("Clark Kim <[email protected]>");
+MODULE_AUTHOR("AleX Pelosi <[email protected]>");
+MODULE_DESCRIPTION("PCA9468 charger driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("3.7.0");
diff --git a/pca9468_charger.h b/pca9468_charger.h
new file mode 100644
index 0000000..3fbb9da
--- /dev/null
+++ b/pca9468_charger.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Platform data for the NXP PCA9468 battery charger driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PCA9468_CHARGER_H_
+#define _PCA9468_CHARGER_H_
+
+struct pca9468_platform_data {
+ int irq_gpio; /* GPIO pin that's connected to INT# */
+ unsigned int iin_cfg; /* Input Current Limit - uA unit */
+ unsigned int ichg_cfg; /* Charging Current - uA unit */
+ unsigned int v_float; /* V_Float Voltage - uV unit */
+ unsigned int iin_topoff; /* Input Topoff current -uV unit */
+ /* Switching frequency: 0 - 833kHz, ... , 3 - 980kHz */
+ unsigned int fsw_cfg;
+ /* NTC voltage threshold : 0~2.4V - uV unit */
+ unsigned int ntc_th;
+ /*
+ * Default charging mode:
+ * 0 - No direct charging
+ * 1 - 2:1 charging mode
+ * 2 - 4:1 charging mode
+ */
+ unsigned int chg_mode;
+};
+
+#endif
diff --git a/pmic-voter-compat.c b/pmic-voter-compat.c
new file mode 100644
index 0000000..1ff2e1d
--- /dev/null
+++ b/pmic-voter-compat.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2019 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.
+ */
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/pmic-voter.h>
+#include "linux/slab.h"
+
+#include "gvotable.h"
+
+#define V2EL(x) ((struct gvotable_election *)(v))
+
+struct votable_data {
+ const char *name;
+ void *callback_data; /* data passed to create_votable */
+ int (*callback)(struct votable *votable,
+ void *data,
+ int effective_result,
+ const char *effective_client);
+};
+
+
+bool is_client_vote_enabled_locked(struct votable *v, const char *client_str)
+{
+ int ret;
+ bool enabled = false;
+
+ ret = gvotable_is_enabled(V2EL(v), client_str, &enabled);
+ if (ret)
+ pr_err("Error gvotable_is_enabled returned %d\n", ret);
+
+ return enabled;
+}
+
+bool is_client_vote_enabled(struct votable *votable, const char *client_str)
+{
+ return is_client_vote_enabled_locked(votable, client_str);
+}
+
+int get_client_vote_locked(struct votable *v, const char *client_str)
+{
+ void *ptr;
+ int ret;
+
+ ret = gvotable_get_vote(V2EL(votable), client_str, &ptr);
+ return ret ? : (uintptr_t)ptr;
+}
+
+int get_client_vote(struct votable *votable, const char *client_str)
+{
+ return get_client_vote_locked(votable, client_str);
+}
+
+int get_effective_result_locked(struct votable *v)
+{
+ const void *ptr;
+ int ret;
+
+ ret = gvotable_get_current_vote(V2EL(v), &ptr);
+ return ret ? : (uintptr_t)ptr;
+}
+
+int get_effective_result(struct votable *votable)
+{
+ return get_effective_result_locked(votable);
+}
+
+int vote(struct votable *v, const char *client_str, bool state, int val)
+{
+ return gvotable_cast_vote(V2EL(v), client_str, (void *)(long)val,
+ state);
+}
+
+/* It needs to work for new and instances so we can mix the code
+ */
+struct votable *find_votable(const char *name)
+{
+ return (struct votable *)gvotable_election_get_handle(name);
+}
+
+static void pmic_voter_compat_cb(struct gvotable_election *el,
+ const char *cb_reason, void *cb_result)
+{
+ struct votable_data *vd = (struct votable_data *)gvotable_get_data(el);
+ char reason[GVOTABLE_MAX_REASON_LEN] = { 0 };
+ char *effective_reason = NULL;
+ int effective_result = -EINVAL;
+ const void *ptr;
+ int ret;
+
+ if (!vd->callback)
+ return;
+
+ ret = gvotable_get_current_vote(el, &ptr);
+ if (ret == 0)
+ effective_result = (uintptr_t)ptr;
+
+ ret = gvotable_get_current_reason(el, reason, sizeof(reason));
+ if (ret == 0)
+ effective_reason = reason;
+
+ /* for SET_ANY voter, the value is always same as enabled. */
+ pr_debug("%s: name=%s result=%d reason=%s\n", __func__, vd->name,
+ effective_result, effective_reason ? effective_reason : "<>");
+
+ /* call with NULL reason and -EINVAL if votes no enabled */
+ vd->callback((struct votable *)el, vd->callback_data,
+ effective_result, effective_reason);
+}
+
+/* Allow redefining the allocator: required for testing */
+#ifndef kzalloc_votable
+#define kzalloc_votable(p, f) kzalloc(sizeof(*p), f)
+#endif
+
+struct votable *create_votable(const char *name,
+ int votable_type,
+ int (*callback)(struct votable *votable,
+ void *data,
+ int effective_result,
+ const char *effective_client),
+ void *callback_data)
+{
+ int (*comp_fn)(void * , void *) = NULL;
+ struct gvotable_election *el;
+ struct votable_data *vd;
+ int ret;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ /* create extra votable data */
+ vd = kzalloc_votable(vd, GFP_KERNEL);
+ if (!vd)
+ return ERR_PTR(-ENOMEM);
+ vd->callback_data = callback_data;
+ vd->callback = callback;
+ vd->name = name;
+
+ switch (votable_type) {
+ case VOTE_MIN:
+ comp_fn = gvotable_comparator_int_min;
+ break;
+ case VOTE_MAX:
+ comp_fn = gvotable_comparator_int_max;
+ break;
+ case VOTE_SET_ANY:
+ break;
+ default:
+ kfree(vd);
+ return ERR_PTR(-EINVAL);
+ break;
+ }
+
+ if (votable_type == VOTE_SET_ANY) {
+ el = gvotable_create_bool_election(NULL, pmic_voter_compat_cb,
+ vd);
+ } else {
+ el = gvotable_create_int_election(NULL, comp_fn,
+ pmic_voter_compat_cb,
+ vd);
+ }
+
+ if (IS_ERR_OR_NULL(el)) {
+ kfree(vd);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ gvotable_set_vote2str(el, gvotable_v2s_int);
+ /* votables of type ANY have a default but they don't use it */
+ if (votable_type == VOTE_SET_ANY) {
+ gvotable_set_default(el, 0);
+ gvotable_use_default(el, false);
+ }
+
+ ret = gvotable_election_set_name(el, vd->name);
+ if (ret < 0) {
+ gvotable_destroy_election(el);
+ kfree(vd);
+ return ERR_PTR(-EEXIST);
+ }
+
+
+ return (struct votable *)el;
+}
+
+void destroy_votable(struct votable *v)
+{
+ if (!v)
+ return;
+
+ kfree(gvotable_get_data(V2EL(v)));
+ gvotable_destroy_election(V2EL(v));
+}
diff --git a/qmath.h b/qmath.h
new file mode 100644
index 0000000..ed8f937
--- /dev/null
+++ b/qmath.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef QMATH_H_
+#define QMATH_H_
+
+#include <linux/types.h>
+
+typedef s32 qnum_t;
+typedef s64 qnumd_t;
+typedef u32 qnumu_t;
+typedef s64 qnumud_t;
+
+#define QNUM_BITS (sizeof(qnum_t)*8)
+/* integer part */
+#define QNUM_IBITS 8
+/* fractional part and mask */
+#define QNUM_FBITS (QNUM_BITS - QNUM_IBITS)
+#define QNUM_FMASK (((qnum_t)1 << QNUM_FBITS) - 1)
+
+#define qnum_rconst(R) \
+ ((qnum_t)((R) * (((qnumd_t)1 << QNUM_FBITS)\
+ + ((R) >= 0 ? 0.5 : -0.5))))
+
+#define qnum_fromint(I) ((qnumd_t)(I) << QNUM_FBITS)
+/* battery raw capacity is in Q8_8 */
+#define qnum_from_q8_8(Q8_8) ((qnumd_t)(Q8_8) << (QNUM_FBITS-8))
+
+/* truncate */
+#define qnum_toint(F) ((int)((F) >> QNUM_FBITS))
+/* round the number */
+#define qnum_roundint(F, P) \
+ qnum_toint(F + qnum_rconst(P))
+
+
+static inline qnum_t qnum_mul(qnum_t A, qnum_t B)
+{
+ return (((qnumd_t)A * (qnumd_t)B) >> QNUM_FBITS);
+}
+
+static inline qnum_t qnum_div(qnum_t A, qnum_t B)
+{
+ return (((qnumd_t)A << QNUM_FBITS) / (qnumd_t)B);
+}
+
+
+/* 1, 2, 3, and 4 digits */
+#define qnum_fracpart(A) ((qnum_t)(A) & QNUM_FMASK)
+
+#define QNUM_FRACBITS(A) \
+ (((qnum_fracpart(A) << QNUM_IBITS)) & (((qnumud_t)1 << QNUM_BITS)-1))
+
+#define QNUM_FRACn(A, n) \
+ (((QNUM_FRACBITS(A) * n) >> QNUM_BITS) % n)
+
+#define QNUM_FRAC1(A) QNUM_FRACn(A, 10)
+#define QNUM_FRAC2(A) QNUM_FRACn(A, 100)
+#define QNUM_FRAC3(A) QNUM_FRACn(A, 1000)
+#define QNUM_FRAC4(A) QNUM_FRACn(A, 10000)
+
+
+#define _PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
+
+/* print as %d.%0<num>d */
+#define _STR_(x) # x
+#define QNUM_FDGT_NUM 2
+#define QNUM_CSTR_FMT "%d.%02d"
+#define QNUM_CSTR_SZ (4 + 1 + QNUM_FDGT_NUM + 1)
+#define qnum_nfracdgt(A, n) _PRIMITIVE_CAT(QNUM_FRAC, n)(A)
+#define qnum_fracdgt(A) ((int)qnum_nfracdgt(A, QNUM_FDGT_NUM))
+#endif /* QMATH_H_ */