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 = &reg;
+
+	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, &reg) == 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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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,
+					&reg);
+		if (ret < 0 || reg)
+			dev_info(data->dev, "INTSRC :%x (%d)\n", reg, ret);
+		ret = max77729_pmic_rd8(data, MAX77759_PMIC_TOPSYS_INT,
+					&reg);
+		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 = &reg;
+
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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, &reg);
+	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", &reg, &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", &reg, &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, &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, &current_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, &reg[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, &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, &reg_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, &reg_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,
+					  &reg_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, &reg_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,
+				  &register_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(&current_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_ */