From 4879c1b867b6510add03e7c736f12cd4e442f13d Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Sun, 7 Feb 2021 09:44:01 -0800 Subject: [PATCH] rM2: Copy the rM2 max77818 driver Copy the rM2 zero-sugar branch (https://github.com/reMarkable/linux/tree/zero-sugar) to the new kernel. Signed-off-by: Alistair Francis --- drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 1 + drivers/mfd/max77818.c | 376 ++++ drivers/power/supply/Kconfig | 21 + drivers/power/supply/Makefile | 3 + drivers/power/supply/max77818-charger.c | 1080 +++++++++ drivers/power/supply/max77818_battery.c | 1984 +++++++++++++++++ drivers/power/supply/max77818_battery_utils.c | 74 + include/linux/mfd/max77818/max77818.h | 108 + include/linux/power/max17042_battery.h | 21 + include/linux/power/max77818_battery_utils.h | 168 ++ include/linux/power_supply.h | 16 + 12 files changed, 3863 insertions(+) create mode 100644 drivers/mfd/max77818.c create mode 100644 drivers/power/supply/max77818-charger.c create mode 100644 drivers/power/supply/max77818_battery.c create mode 100644 drivers/power/supply/max77818_battery_utils.c create mode 100644 include/linux/mfd/max77818/max77818.h create mode 100644 include/linux/power/max77818_battery_utils.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index e2e88fde69bc..3df0e4f88a36 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -878,6 +878,17 @@ config MFD_MAX8998 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX77818 + bool "Maxim Semiconductor MAX77818 REGULATOR/CHARGER/FUEL-GAUGE Support" + depends on I2C=y + select BATTERY_MAX77818_UTILS + select MFD_CORE + select IRW_DOMAIN + help + Say yes here to add support for Maxime Semiconductor MAX77818 device. + The device has a regulator part, a charger part and a fuel-gauge part + which may be enabled as required (power management devices). + config MFD_MT6397 tristate "MediaTek MT6397 PMIC Support" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 622be1a92c1c..836520f4154e 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -171,6 +171,7 @@ max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o +obj-$(CONFIG_MFD_MAX77818) += max77818.o pcf50633-objs := pcf50633-core.o pcf50633-irq.o obj-$(CONFIG_MFD_PCF50633) += pcf50633.o diff --git a/drivers/mfd/max77818.c b/drivers/mfd/max77818.c new file mode 100644 index 000000000000..ed5220a41aa5 --- /dev/null +++ b/drivers/mfd/max77818.c @@ -0,0 +1,376 @@ +/* + * Maxim MAX77818 MFD Core + * + * Copyright (C) 2014 Maxim Integrated Product + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * Author: Shawn Guo + * Author: Lars Ivar Miljeteig + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver is based on max77843.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define I2C_ADDR_PMIC (0xcc >> 1) /* PMIC (CLOGIC/SAFELDOs) */ +#define I2C_ADDR_CHARGER (0xd2 >> 1) /* Charger */ +#define I2C_ADDR_FUEL_GAUGE (0x6c >> 1) /* Fuel Gauge */ + +#define REG_PMICID 0x20 +#define REG_PMICREV 0x21 + +static int max77818_chg_handle_pre_irq(void *irq_drv_data) +{ + struct max77818_dev *max77818_dev = (struct max77818_dev*) irq_drv_data; + bool restore_state = 0; + int ret = 0; + + if (!max77818_dev) { + printk("%s: No driver data, unable to disable FGCC\n", __func__); + return -EINVAL; + } + + ret = MAX77818_START_NON_FGCC_OP( + max77818_dev, + restore_state, + "Disabling FGCC before handling charger interrupt"); + if (ret) + dev_err(max77818_dev->dev, + "Failed to disable FGCC\n"); + + return ret; +} + +static int max77818_chg_handle_post_irq(void *irq_drv_data) +{ + struct max77818_dev *max77818_dev = (struct max77818_dev*) irq_drv_data; + bool restore_state = 1; + int ret = 0; + + if (!max77818_dev) { + printk("%s: No driver data, unable to disable FGCC\n", __func__); + return -EINVAL; + } + + ret = MAX77818_FINISH_NON_FGCC_OP( + max77818_dev, + restore_state, + "Enabling FGCC after handling charger interrupt"); + if (ret) + dev_err(max77818_dev->dev, + "Failed to enable FGCC\n"); + + return ret; +} + +static const struct regmap_config max77818_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_config max77818_regmap_config_fg = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_NONE, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +/* Declare Interrupt */ +static const struct regmap_irq max77818_intsrc_irqs[] = { + { .reg_offset = 0, .mask = BIT_CHGR_INT, }, + { .reg_offset = 0, .mask = BIT_FG_INT, }, + { .reg_offset = 0, .mask = BIT_SYS_INT, }, +}; + +static const struct regmap_irq_chip max77818_intsrc_irq_chip = { + .name = "max77818 intsrc", + .status_base = REG_INTSRC, + .mask_base = REG_INTSRCMASK, + .num_regs = 1, + .irqs = max77818_intsrc_irqs, + .num_irqs = ARRAY_SIZE(max77818_intsrc_irqs), +}; + +static const struct regmap_irq max77818_sys_irqs[] = { + { .reg_offset = 0, .mask = BIT_SYSUVLO_INT, }, + { .reg_offset = 0, .mask = BIT_SYSOVLO_INT, }, + { .reg_offset = 0, .mask = BIT_TSHDN_INT, }, + { .reg_offset = 0, .mask = BIT_TM_INT, }, +}; + +static const struct regmap_irq_chip max77818_sys_irq_chip = { + .name = "max77818 system", + .status_base = REG_SYSINTSRC, + .mask_base = REG_SYSINTMASK, + .num_regs = 1, + .irqs = max77818_sys_irqs, + .num_irqs = ARRAY_SIZE(max77818_sys_irqs), +}; + +static const struct regmap_irq max77818_chg_irqs[] = { + { .reg_offset = 0, .mask = BIT_CHG_BYP_I, }, + { .reg_offset = 0, .mask = BIT_CHG_BATP_I, }, + { .reg_offset = 0, .mask = BIT_CHG_BAT_I, }, + { .reg_offset = 0, .mask = BIT_CHG_CHG_I, }, + { .reg_offset = 0, .mask = BIT_CHG_WCIN_I, }, + { .reg_offset = 0, .mask = BIT_CHG_CHGIN_I, }, + { .reg_offset = 0, .mask = BIT_CHG_AICL_I, }, +}; + +static struct regmap_irq_chip max77818_chg_irq_chip = { + .name = "max77818 chg", + .status_base = REG_CHARGER_INT, + .mask_base = REG_CHARGER_INT_MASK, + .num_regs = 1, + .irqs = max77818_chg_irqs, + .num_irqs = ARRAY_SIZE(max77818_chg_irqs), + .handle_pre_irq = max77818_chg_handle_pre_irq, + .handle_post_irq = max77818_chg_handle_post_irq, +}; + +static struct mfd_cell max77818_devices[] = { + { + .name = MAX77818_REGULATOR_NAME, + .of_compatible = "maxim,"MAX77818_REGULATOR_NAME, + }, { + .name = MAX77818_CHARGER_NAME, + .of_compatible = "maxim,"MAX77818_CHARGER_NAME, + }, { + .name = MAX77818_FUELGAUGE_NAME, + .of_compatible = "maxim,"MAX77818_FUELGAUGE_NAME, + }, +}; + +static int max77818_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max77818_dev *me; + u32 chip_id, chip_rev; + int ret; + + me = devm_kzalloc(&client->dev, sizeof(*me), GFP_KERNEL); + if (!me) + return -ENOMEM; + + i2c_set_clientdata(client, me); + + mutex_init(&me->lock); + me->dev = &client->dev; + me->irq = client->irq; + me->pmic = client; + + me->regmap_pmic = devm_regmap_init_i2c(client, &max77818_regmap_config); + if (IS_ERR(me->regmap_pmic)) { + ret = PTR_ERR(me->regmap_pmic); + dev_err(me->dev, "failed to initialize PMIC regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(me->regmap_pmic, REG_PMICID, &chip_id); + if (ret < 0) { + dev_err(me->dev, "failed to read chip id: %d\n", ret); + return ret; + } else { + regmap_read(me->regmap_pmic, REG_PMICREV, &chip_rev); + dev_info(me->dev, "device ID: 0x%x, REV: 0x%x\n", + chip_id, chip_rev); + } + + me->chg = i2c_new_dummy(client->adapter, I2C_ADDR_CHARGER); + if (!me->chg) { + dev_err(me->dev, "failed to allocate I2C device for CHG\n"); + return ret; + } + i2c_set_clientdata(me->chg, me); + + me->fg = i2c_new_dummy(client->adapter, I2C_ADDR_FUEL_GAUGE); + if (!me->fg) { + dev_err(me->dev, "failed to allocate I2C device for FG\n"); + goto unreg_chg; + } + i2c_set_clientdata(me->fg, me); + + me->regmap_chg = devm_regmap_init_i2c(me->chg, &max77818_regmap_config); + if (IS_ERR_OR_NULL(me->regmap_chg)) { + ret = PTR_ERR(me->regmap_chg); + dev_warn(me->dev, "failed to initialize CHG regmap: %d\n", ret); + goto unreg_fg; + } + + me->regmap_fg= devm_regmap_init_i2c(me->fg, &max77818_regmap_config_fg); + if (IS_ERR_OR_NULL(me->regmap_fg)) { + ret = PTR_ERR(me->regmap_fg); + dev_err(me->dev, "failed to initialize FG regmap: %d\n", ret); + goto unreg_fg; + } + + /* Disable all interrupt source */ + regmap_write(me->regmap_pmic, REG_INTSRCMASK, 0xff); + + /* Register overall interrupt source (sys, fg, chg) */ + ret = regmap_add_irq_chip(me->regmap_pmic, me->irq, + IRQF_ONESHOT | IRQF_SHARED, 0, + &max77818_intsrc_irq_chip, &me->irqc_intsrc); + if (ret) { + dev_err(me->dev, "failed to add intsrc irq chip: %d\n", ret); + goto unreg_fg; + } + + /* Register system chip irq */ + ret = regmap_add_irq_chip(me->regmap_pmic, me->irq, + IRQF_ONESHOT | IRQF_SHARED, 0, + &max77818_sys_irq_chip, &me->irqc_sys); + if (ret) { + dev_err(me->dev, "failed to add system irq chip: %d\n", ret); + goto del_irqc_intsrc; + } + + /* Register charger chip irq */ + max77818_chg_irq_chip.irq_drv_data = me; + ret = MAX77818_DO_NON_FGCC_OP( + me, + regmap_add_irq_chip(me->regmap_chg, + me->irq, + IRQF_ONESHOT | IRQF_SHARED, + 0, + &max77818_chg_irq_chip, + &me->irqc_chg), + "adding charger chip irq\n"); + + if (ret) { + dev_warn(me->dev, "failed to add chg irq chip: %d\n", ret); + goto del_irqc_sys; + } + + pm_runtime_set_active(me->dev); + + ret = mfd_add_devices(me->dev, -1, max77818_devices, + ARRAY_SIZE(max77818_devices), NULL, 0, NULL); + if (ret < 0) { + dev_err(me->dev, "failed to add mfd devices: %d\n", ret); + goto del_irqc_chg; + } + + device_init_wakeup(me->dev, true); + + return 0; + +del_irqc_chg: + regmap_del_irq_chip(me->irq, me->irqc_chg); +del_irqc_sys: + regmap_del_irq_chip(me->irq, me->irqc_sys); +del_irqc_intsrc: + regmap_del_irq_chip(me->irq, me->irqc_intsrc); +unreg_fg: + i2c_unregister_device(me->fg); +unreg_chg: + i2c_unregister_device(me->chg); + + return ret; +} + +static int max77818_i2c_remove(struct i2c_client *client) +{ + struct max77818_dev *me = i2c_get_clientdata(client); + + mfd_remove_devices(me->dev); + + regmap_del_irq_chip(me->irq, me->irqc_chg); + regmap_del_irq_chip(me->irq, me->irqc_sys); + regmap_del_irq_chip(me->irq, me->irqc_intsrc); + + i2c_unregister_device(me->fg); + + if (me->chg) { + i2c_unregister_device(me->chg); + } + + return 0; +} + +static int max77818_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct max77818_dev *me = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) { + enable_irq_wake(me->irq); + disable_irq(me->irq); + } + + return 0; +} + +static int max77818_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct max77818_dev *me = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) { + disable_irq_wake(me->irq); + enable_irq(me->irq); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(max77818_pm, max77818_suspend, max77818_resume); + +static struct of_device_id max77818_of_id[] = { + { .compatible = "maxim,max77818" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max77818_of_id); + +static const struct i2c_device_id max77818_i2c_id[] = { + { "max77818" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max77818_i2c_id); + +static struct i2c_driver max77818_i2c_driver = { + .driver = { + .name = "max77818", + .pm = &max77818_pm, + .of_match_table = max77818_of_id, + }, + .probe = max77818_i2c_probe, + .remove = max77818_i2c_remove, + .id_table = max77818_i2c_id, +}; +module_i2c_driver(max77818_i2c_driver); + +MODULE_DESCRIPTION("MAX77818 MFD Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index a074fc75c14e..c54c67e27483 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -386,6 +386,20 @@ config BATTERY_MAX1721X Say Y here to enable support for the MAX17211/MAX17215 standalone battery gas-gauge. +config BATTERY_MAX77818_UTILS + tristate "Maxime MAX77818 Fuel Gauge common/external functions" + help + This is automatically selected when the MAX77818 MFD driver is + selected, due to required Max77818 Fuel Gauge device access during + initiation of the MFD device. + . +config BATTERY_MAX77818 + tristate "Maxime MAX77818 Fuel Gauge" + depends on I2C + depends on MFD_MAX77818 + help + Say Y yo enable support for Maxime MAX77818 battery fuel-gauge device (part of MAX77818 multi-function reg/chrg/fg device) + config BATTERY_Z2 tristate "Z2 battery driver" depends on I2C && MACH_ZIPIT2 @@ -549,6 +563,13 @@ config CHARGER_MAX8998 Say Y to enable support for the battery charger control sysfs and platform data of MAX8998/LP3974 PMICs. +config CHARGER_MAX77818 + tristate "Maxime MAX77818 battery charger driver" + depends on I2C + depends on MFD_MAX77818 + help + Say Y yo enable support for Maxime MAX77818 charger device (part of MAX77818 multi-function reg/chrg/fg device) + config CHARGER_QCOM_SMBB tristate "Qualcomm Switch-Mode Battery Charger and Boost" depends on MFD_SPMI_PMIC || COMPILE_TEST diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 9e41cdb2eaff..a90f1ab5e4e8 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_BATTERY_DA9150) += da9150-fg.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o obj-$(CONFIG_BATTERY_MAX1721X) += max1721x_battery.o +obj-$(CONFIG_BATTERY_MAX77818) += max77818_battery.o obj-$(CONFIG_BATTERY_Z2) += z2_battery.o obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o @@ -76,6 +77,7 @@ obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o +obj-$(CONFIG_CHARGER_MAX77818) += max77818-charger.o obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o @@ -93,3 +95,4 @@ obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o +obj-$(CONFIG_BATTERY_MAX77818_UTILS) += max77818_battery_utils.o diff --git a/drivers/power/supply/max77818-charger.c b/drivers/power/supply/max77818-charger.c new file mode 100644 index 000000000000..cd2b974e8406 --- /dev/null +++ b/drivers/power/supply/max77818-charger.c @@ -0,0 +1,1080 @@ +/* + * Maxim MAX77818 Charger Driver + * + * Copyright (C) 2014 Maxim Integrated Product + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * Author: Shawn Guo + * Author: Lars Ivar Miljeteig + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver is based on max77843.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register map */ +#define REG_CHG_INT 0xB0 +#define REG_CHG_INT_MASK 0xB1 +#define BIT_AICL BIT(7) +#define BIT_CHGIN BIT(6) +#define BIT_WCIN BIT(5) +#define BIT_CHG BIT(4) +#define BIT_BAT BIT(3) +#define BIT_BATP BIT(2) +#define BIT_BYP BIT(0) + +#define REG_CHG_INT_OK 0xB2 +#define BIT_AICL_OK BIT(7) +#define BIT_CHGIN_OK BIT(6) +#define BIT_WCIN_OK BIT(5) +#define BIT_CHG_OK BIT(4) +#define BIT_BAT_OK BIT(3) +#define BIT_BATP_OK BIT(2) +#define BIT_BYP_OK BIT(0) + +#define REG_CHG_DTLS_00 0xB3 +#define BIT_CHGIN_DTLS GENMASK(6, 5) +#define BIT_WCIN_DTLS GENMASK(4, 3) +#define BIT_BATP_DTLS BIT(0) + +#define REG_CHG_DTLS_01 0xB4 +#define BIT_TREG BIT(7) +#define BIT_BAT_DTLS GENMASK(6, 4) +#define BIT_CHG_DTLS GENMASK(3, 0) + +#define REG_CHG_DTLS_02 0xB5 +#define BIT_BYP_DTLS GENMASK(3, 0) +#define BIT_BCKNegILIM BIT(2) +#define BIT_BSTILIM BIT(1) +#define BIT_OTGILIM BIT(0) + +#define REG_CHG_CNFG_00 0xB7 +#define BIT_OTG_CTRL BIT(7) +#define BIT_DISIBS BIT(6) +#define BIT_SPREAD BIT(5) +#define BIT_WDTEN BIT(4) +#define BIT_MODE GENMASK(3, 0) +#define BIT_MODE_BOOST BIT(3) +#define BIT_MODE_BUCK BIT(2) +#define BIT_MODE_OTG BIT(1) +#define BIT_MODE_CHARGER BIT(0) +#define MODE_ALL_OFF 0x00 +#define MODE_OTG_BUCK_BOOST 0x0F +#define MODE_CHARGER_BUCK 0x05 + +/* Read back charger status ranges */ +#define MODE_ALL_OFF_MIN 0x00 +#define MODE_ALL_OFF_MAX 0x03 +#define MODE_CHARGER_MIN 0x04 +#define MODE_CHARGER_MAX 0x07 +#define MODE_OTG_MIN 0x0e +#define MODE_OTG_MAX 0x0f + +#define REG_CHG_CNFG_01 0xB8 +#define BIT_PQEN BIT(7) +#define BIT_LSEL BIT(6) +#define BIT_CHG_RSTRT GENMASK(5, 4) +#define SHIFT_CHG_RSTRT 4 +#define BIT_FSW BIT(3) +#define BIT_FCHGTIME GENMASK(2, 0) + +#define REG_CHG_CNFG_02 0xB9 +#define BIT_OTG_ILIM GENMASK(7, 6) +#define BIT_CHG_CC GENMASK(5, 0) + +#define REG_CHG_CNFG_03 0xBA +#define BIT_ILIM GENMASK(7, 6) +#define BIT_TO_TIME GENMASK(5, 3) +#define SHIFT_TO_TIME 3 +#define BIT_TO_ITH GENMASK(2, 0) + +#define REG_CHG_CNFG_04 0xBB +#define SHIFT_MINVSYS 6 + +#define REG_CHG_CNFG_06 0xBD +#define BIT_CHGPROT GENMASK(3, 2) +#define BIT_WDTCLR GENMASK(1, 0) + +#define REG_CHG_CNFG_07 0xBE +#define BIT_REGTEMP GENMASK(6, 5) + +#define REG_CHG_CNFG_09 0xC0 +#define BIT_CHGIN_ILIM GENMASK(6, 0) + +#define REG_CHG_CNFG_10 0xC1 +#define BIT_WCIN_ILIM GENMASK(5, 0) + +#define REG_CHG_CNFG_11 0xC2 +#define BIT_VBYPSET GENMASK(6, 0) + +#define REG_CHG_CNFG_12 0xC3 +#define BIT_WCINSEL BIT(6) +#define BIT_CHGINSEL BIT(5) +#define BIT_VCHGIN_REG GENMASK(4, 3) +#define BIT_B2SOVRC GENMASK(2, 0) + +enum { + WCIN_DTLS_UVLO, + WCIN_DTLS_INVALID_01, + WCIN_DTLS_OVLO, + WCIN_DTLS_VALID, +}; + +enum { + CHGIN_DTLS_UVLO, + CHGIN_DTLS_INVALID_01, + CHGIN_DTLS_OVLO, + CHGIN_DTLS_VALID, +}; + +enum { + CHG_DTLS_PREQUAL, + CHG_DTLS_FASTCHARGE_CC, + CHG_DTLS_FASTCHARGE_CV, + CHG_DTLS_TOPOFF, + CHG_DTLS_DONE, + CHG_DTLS_RESEVRED_05, + CHG_DTLS_OFF_TIMER_FAULT, + CHG_DTLS_OFF_SUSPEND, + CHG_DTLS_OFF_INPUT_INVALID, + CHG_DTLS_RESERVED_09, + CHG_DTLS_OFF_JUCTION_TEMP, + CHG_DTLS_OFF_WDT_EXPIRED, +}; + +enum { + BAT_DTLS_NO_BATTERY, + BAT_DTLS_RESERVED_01, + BAT_DTLS_TIMER_FAULT, + BAT_DTLS_OKAY, + BAT_DTLS_OKAY_LOW, + BAT_DTLS_OVERVOLTAGE, + BAT_DTLS_OVERCURRENT, + BAT_DTLS_RESERVED_07, +}; + +enum { + CHG_INT_BYP_I, + CHG_INT_BATP_I, + CHG_INT_BAT_I, + CHG_INT_CHG_I, + CHG_INT_WCIN_I, + CHG_INT_CHGIN_I, + CHG_INT_AICL_I, +}; + +struct max77818_charger { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy_chg; + struct mutex lock; + struct work_struct irq_work; + + int irq; + int chgin_irq; + int wcin_irq; + int chg_int; + + u8 dtls[3]; + + int health; + int status; + int charge_type; + int charger_mode; /* OTG SUPPLY/CHARGER */ + + int fast_charge_timer; + int fast_charge_current; + int topoff_current; + int topoff_timer; + int restart_threshold; + int termination_voltage; + int vsys_min; + int default_current_limit; + int input_current_limit_chgin; + int input_current_limit_wcin; + + struct gpio_desc *chgin_stat_gpio; + struct gpio_desc *wcin_stat_gpio; +}; + +static enum power_supply_property max77818_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX2, + POWER_SUPPLY_PROP_CHARGER_MODE, + POWER_SUPPLY_PROP_STATUS_EX, +}; + +static bool max77818_charger_chgin_present(struct max77818_charger *chg) +{ + u32 chg_int_ok = 0; + int ret; + + return 0; + + /* Try to read from the device first, but if the charger device + * is offline, try to read the chg status gpios */ + if(!IS_ERR_OR_NULL(chg->regmap)) { + dev_dbg(chg->dev, "Trying to read REG_CHG_INT_OK\n"); + + ret = regmap_read(chg->regmap, REG_CHG_INT_OK, &chg_int_ok); + if (!ret) { + if (chg_int_ok & BIT_CHGIN_OK) { + return true; + } else { + /* check whether charging or not in the UVLO condition */ + if (((chg->dtls[0] & BIT_CHGIN_DTLS) == 0) && + ((chg_int_ok & BIT_WCIN_OK) == 0) && + (((chg->dtls[1] & BIT_CHG_DTLS) == CHG_DTLS_FASTCHARGE_CC) || + ((chg->dtls[1] & BIT_CHG_DTLS) == CHG_DTLS_FASTCHARGE_CV))) { + return true; + } else { + return false; + } + } + } + } + + /* chg->regmap is not initialized, or read from device failed + * Use GPIO + */ + if (chg->chgin_stat_gpio) { + dev_dbg(chg->dev, "Trying to read chgin_stat_gpio\n"); + ret = gpiod_get_value_cansleep(chg->chgin_stat_gpio); + dev_dbg(chg->dev, "ret: %d\n", ret); + if (ret < 0) + dev_err(chg->dev, + "failed to read chgin-stat-gpio: %d\n", + ret); + return (ret > 0); + + } + else { + dev_warn(chg->dev, + "chgin-stat-gpio not configured, chgin connection " + "status not available\n"); + return false; + } +} + +static bool max77818_charger_wcin_present(struct max77818_charger *chg) +{ + u32 chg_int_ok = 0; + int ret; + + /* Try to read from the device first, but if the charger device + * is offline, try to read the chg status gpios */ + if(!IS_ERR_OR_NULL(chg->regmap)) { + dev_dbg(chg->dev, "Trying to read REG_CHG_INT_OK\n"); + + ret = regmap_read(chg->regmap, REG_CHG_INT_OK, &chg_int_ok); + if (!ret) { + if (chg_int_ok & BIT_WCIN_OK) { + return true; + } else { + /* check whether charging or not in the UVLO condition */ + if (((chg->dtls[0] & BIT_WCIN_DTLS) == 0) && + ((chg_int_ok & BIT_CHGIN_OK) == 0) && + (((chg->dtls[1] & BIT_CHG_DTLS) == CHG_DTLS_FASTCHARGE_CC) || + ((chg->dtls[1] & BIT_CHG_DTLS) == CHG_DTLS_FASTCHARGE_CV))) { + return true; + } else { + return false; + } + } + } + } + + /* chg->regmap is not initialized, or read from device failed + * Use GPIO + */ + if (chg->wcin_stat_gpio) { + dev_dbg(chg->dev, "Trying to read wcin_stat_gpio\n"); + ret = gpiod_get_value_cansleep(chg->wcin_stat_gpio); + dev_dbg(chg->dev, "ret: %d\n", ret); + + if (ret < 0) + dev_err(chg->dev, + "failed to read wcin-stat-gpio: %d\n", + ret); + return (ret > 0); + } + else { + dev_warn(chg->dev, + "wcin-stat-gpio not configured, wcin connection " + "status not available\n"); + return false; + } +} + +static int max77818_charger_get_input_current(struct max77818_charger *chg, + int *input_current) +{ + int quotient, remainder; + int steps[3] = { 0, 33, 67 }; + u32 val = 0; + int ret; + + if(IS_ERR_OR_NULL(chg->regmap)) { + dev_warn(chg->dev, "unable to read from charger device\n"); + return -ENODEV; + } + + if (!max77818_charger_chgin_present(chg) && + max77818_charger_wcin_present(chg)) { + /* + * When the wireless charging input is the only one currently + * active, the configured max input current for the wireless + * charging input is returned + */ + ret = regmap_read(chg->regmap, REG_CHG_CNFG_10, &val); + if (ret) { + return ret; + } + + if (val <= 3) + *input_current = 60; + else + *input_current = 60 + (val - 3) * 20; + } else { + /* + * Just return the max wired charging input current in all + * cases where the wireless charging input is not the only + * current active charging input + */ + ret = regmap_read(chg->regmap, REG_CHG_CNFG_09, &val); + if (ret) { + return ret; + } + + quotient = val / 3; + remainder = val % 3; + + if ((val & BIT_CHGIN_ILIM) < 3) + *input_current = 100; + else if ((val & BIT_CHGIN_ILIM) > 0x78) + *input_current = 4000; + else + *input_current = quotient * 100 + steps[remainder]; + } + + return 0; +} + +static int +max77818_charger_set_chgin_current_limit(struct max77818_charger *chg, + int input_current) +{ + int quotient, remainder; + u8 val = 0; + int ret; + + if(IS_ERR_OR_NULL(chg->regmap)) { + dev_warn(chg->dev, "unable to read from charger device\n"); + return -ENODEV; + } + + if (input_current == 0) { + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_12, + BIT_CHGINSEL, 0); + if (ret) + dev_warn(chg->dev, "Failed to clear CHGINSEL\n"); + } + else { + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_12, + BIT_CHGINSEL, BIT_CHGINSEL); + if (ret) + dev_warn(chg->dev, "Failed to set CHGINSEL\n"); + + quotient = input_current / 100; + remainder = input_current % 100; + + if (remainder >= 67) + val |= (quotient * 3) + 2; + else if (remainder >= 33) + val |= (quotient * 3) + 1; + else if (remainder < 33) + val |= quotient * 3; + } + + dev_dbg(chg->dev, "Setting CHGIN_ILIM=0x%02x\n", val); + return regmap_update_bits(chg->regmap, REG_CHG_CNFG_09, + BIT_CHGIN_ILIM, val); +} + +static int +max77818_charger_set_wcin_current_limit(struct max77818_charger *chg, + int input_current) +{ + u8 val = 0; + + if(IS_ERR_OR_NULL(chg->regmap)) { + dev_warn(chg->dev, "unable to read from charger device\n"); + return -ENODEV; + } + + if (input_current) { + if (input_current < 60) + input_current = 60; + else if (input_current > chg->input_current_limit_wcin) + input_current = chg->input_current_limit_wcin; + + val = DIV_ROUND_UP(input_current - 60, 20) + 3; + } + + dev_dbg(chg->dev, "Setting WCIN_ILIM=0x%02x\n", val); + return regmap_update_bits(chg->regmap, REG_CHG_CNFG_10, + BIT_WCIN_ILIM, val); +} + +static int max77818_charger_set_enable(struct max77818_charger *chg, int en) +{ + u8 mode; + int ret; + + if(IS_ERR_OR_NULL(chg->regmap)) { + dev_warn(chg->dev, "unable to read from charger device\n"); + return -ENODEV; + } + + /* If enable = 0, just shut off charging/otg power */ + if (!en) { + mode = MODE_ALL_OFF; + goto update_mode; + } + + /* + * Depending on configured mode, turn on charging or OTG + * power. + */ + if (chg->charger_mode == POWER_SUPPLY_MODE_CHARGER) { + mode = MODE_CHARGER_BUCK; + } else if (chg->charger_mode == POWER_SUPPLY_MODE_OTG_SUPPLY) { + mode = MODE_OTG_BUCK_BOOST; + } else { + dev_err(chg->dev, "invalid charger_mode %d\n", + chg->charger_mode); + return -EINVAL; + } + +update_mode: + dev_info(chg->dev, "set MODE bits to 0x%x", mode); + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_00, BIT_MODE, mode); + if (ret) { + dev_err(chg->dev, "failed to update MODE bits: %d\n", ret); + return ret; + } + + return 0; +} + +static int max77818_charger_get_charger_mode(struct max77818_charger *chg) +{ + int ret; + u32 read_val; + + dev_dbg(chg->dev, "Trying to read current charger mode from device\n"); + + ret = regmap_read(chg->regmap, REG_CHG_CNFG_00, &read_val); + if (ret) { + return ret; + } + dev_dbg(chg->dev, "Read raw charger_mode register: 0x%02x\n", read_val); + dev_dbg(chg->dev, "Read charger_mode 0x%02lx\n", (read_val & BIT_MODE)); + + switch(read_val & BIT_MODE) + { + case MODE_ALL_OFF_MIN ... MODE_ALL_OFF_MAX: + chg->charger_mode = POWER_SUPPLY_MODE_ALL_OFF; + break; + case MODE_CHARGER_MIN ... MODE_CHARGER_MAX: + chg->charger_mode = POWER_SUPPLY_MODE_CHARGER; + break; + case MODE_OTG_MIN ... MODE_OTG_MAX: + chg->charger_mode = POWER_SUPPLY_MODE_OTG_SUPPLY; + break; + default: + dev_warn(chg->dev, + "Unknown charger_mode read from device: 0x%02x\n", + (read_val & BIT_MODE)); + } + + return 0; +} + +static int max77818_charger_initialize(struct max77818_charger *chg) +{ + struct device *dev = chg->dev; + u8 val, tmpval; + int ret; + + if(IS_ERR_OR_NULL(chg->regmap)) { + dev_warn(chg->dev, "unable to read from charger device\n"); + return -ENODEV; + } + + /* unlock charger register */ + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_06, + BIT_CHGPROT, BIT_CHGPROT); + if (ret) { + dev_err(dev, "failed to unlock: %d\n", ret); + return ret; + } + + /* charge current (mA) */ + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_02, BIT_CHG_CC, + chg->fast_charge_current / 50); + if (ret) { + dev_err(dev, "failed to set charge current: %d\n", ret); + return ret; + } + + /* default max input current limited to safe value */ + ret = max77818_charger_set_chgin_current_limit( + chg, + chg->default_current_limit); + if (ret) { + dev_err(dev, "failed to set chgin input current: %d\n", ret); + return ret; + } + + ret = max77818_charger_set_wcin_current_limit( + chg, + chg->default_current_limit); + if (ret) { + dev_err(dev, "failed to set wcin input current: %d\n", ret); + return ret; + } + + /* topoff current (mA) */ + if (chg->topoff_current < 100) + val = 0x00; + else if (chg->topoff_current < 200) + val = (chg->topoff_current - 100) / 25; + else if (chg->topoff_current < 350) + val = chg->topoff_current / 50; + else + val = 0x07; + + /* topoff timer (min) */ + val |= (chg->topoff_timer / 10) << SHIFT_TO_TIME; + + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_03, + BIT_TO_ITH | BIT_TO_TIME, val); + if (ret) { + dev_err(dev, "failed to update topoff bits: %d\n", ret); + return ret; + } + + /* charge restart threshold(mV) */ + if (chg->restart_threshold <= 0) + val = 0x03; /* disable */ + else if (chg->restart_threshold > 200) + val = 0x02; /* 200 mV */ + else + val = (chg->restart_threshold - 100) / 50; + + /* fast-charge timer(hr) */ + if (chg->fast_charge_timer <= 0 || chg->fast_charge_timer > 16) + tmpval = 0x00; /* disable */ + else if (chg->fast_charge_timer > 16) + tmpval = 0x07; /* 16 hours */ + else + tmpval = chg->fast_charge_timer / 2 - 1; + + val = val << SHIFT_CHG_RSTRT | tmpval; + + /* Enable Low Battery Prequalification Mode */ + val |= BIT_PQEN; + + ret = regmap_update_bits(chg->regmap, REG_CHG_CNFG_01, + BIT_CHG_RSTRT | BIT_FCHGTIME, val); + if (ret) { + dev_err(dev, "failed to update CNFG_01: %d\n", ret); + return ret; + } + + /* charge termination voltage (mV) */ + if (chg->termination_voltage < 3650) + val = 0x00; + else if (chg->termination_voltage <= 4325) + val = DIV_ROUND_UP(chg->termination_voltage - 3650, 25); + else if (chg->termination_voltage <= 4340) + val = 0x1C; + else if (chg->termination_voltage <= 4700) + val = DIV_ROUND_UP(chg->termination_voltage - 3650, 25) + 1; + else + val = 0x2B; + + /* vsys min voltage */ + if (chg->vsys_min < 3400) + tmpval = 0x00; + else if (chg->vsys_min <= 3700) + tmpval = DIV_ROUND_UP(chg->vsys_min - 3400, 100); + else + tmpval = 0x03; + + val = val | tmpval << SHIFT_MINVSYS; + + ret = regmap_write(chg->regmap, REG_CHG_CNFG_04, val); + if (ret) { + dev_err(dev, "failed to update CNFG_04: %d\n", ret); + return ret;; + } + + + /* do initial read from device, to initialize shadow value + * with real value and not assume charger_mode = 0 + */ + ret = max77818_charger_get_charger_mode(chg); + if (ret) { + dev_err(dev, "failed to read charger_mode from device: %d\n", + ret); + return ret; + } + + return 0; +} + +struct max77818_charger_status_map { + int health; + int status; + int charge_type; +}; + +#define STATUS_MAP(_chg_dtls, _health, _status, _charge_type) \ + [CHG_DTLS_##_chg_dtls] = { \ + .health = POWER_SUPPLY_HEALTH_##_health, \ + .status = POWER_SUPPLY_STATUS_##_status, \ + .charge_type = POWER_SUPPLY_CHARGE_TYPE_##_charge_type, \ + } + +static struct max77818_charger_status_map max77818_charger_status_map[] = { + // chg_details_xx health status charge_type + STATUS_MAP(PREQUAL, GOOD, CHARGING, TRICKLE), + STATUS_MAP(FASTCHARGE_CC, GOOD, CHARGING, FAST), + STATUS_MAP(FASTCHARGE_CV, GOOD, CHARGING, FAST), + STATUS_MAP(TOPOFF, GOOD, CHARGING, FAST), + STATUS_MAP(DONE, GOOD, FULL, NONE), + STATUS_MAP(OFF_TIMER_FAULT, SAFETY_TIMER_EXPIRE, NOT_CHARGING, NONE), + STATUS_MAP(OFF_SUSPEND, UNKNOWN, NOT_CHARGING, NONE), + STATUS_MAP(OFF_INPUT_INVALID, UNKNOWN, NOT_CHARGING, NONE), + STATUS_MAP(OFF_JUCTION_TEMP, UNKNOWN, NOT_CHARGING, UNKNOWN), + STATUS_MAP(OFF_WDT_EXPIRED, WATCHDOG_TIMER_EXPIRE, NOT_CHARGING, UNKNOWN), +}; + +static void max77818_charger_update(struct max77818_charger *chg) +{ + u32 dtls_01; + u8 chg_dtls; + int ret; + + chg->health = POWER_SUPPLY_HEALTH_UNKNOWN; + chg->status = POWER_SUPPLY_STATUS_UNKNOWN; + chg->charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + + if (!max77818_charger_chgin_present(chg) && + !max77818_charger_wcin_present(chg)) { + /* no charger present */ + return; + } + + if(!IS_ERR_OR_NULL(chg->regmap)) { + dev_dbg(chg->dev, "Trying to read CHG_DTLS_01\n"); + + ret = regmap_read(chg->regmap, REG_CHG_DTLS_01, &dtls_01); + if (!ret) { + chg_dtls = dtls_01 & BIT_CHG_DTLS; + + chg->health = max77818_charger_status_map[chg_dtls].health; + chg->status = max77818_charger_status_map[chg_dtls].status; + chg->charge_type = max77818_charger_status_map[chg_dtls].charge_type; + + if (chg->health != POWER_SUPPLY_HEALTH_UNKNOWN) + return; + + /* override health by TREG */ + if ((dtls_01 & BIT_TREG) != 0) + chg->health = POWER_SUPPLY_HEALTH_OVERHEAT; + + return; + } + } + + /* chg->regmap is not initialized, or read from device failed + * Just do simple GPIO based check when device is not present on + * the I2C bus, and set the status being used by ONLINE property, + * being used by ...am_i_supplied() call which determines the + * status of other supplies dependent on this supply + */ + if (chg->charger_mode == POWER_SUPPLY_MODE_ALL_OFF) + chg->status = 0; + else if(chg->charger_mode == POWER_SUPPLY_MODE_OTG_SUPPLY) + chg->status = max77818_charger_wcin_present(chg); + else + chg->status = max77818_charger_chgin_present(chg) || + max77818_charger_wcin_present(chg); +} + +static int max77818_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77818_charger *chg = (struct max77818_charger*) psy->drv_data; + int ret = 0; + bool chgin_connected, wcin_connected; + + mutex_lock(&chg->lock); + + max77818_charger_update(chg); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (chg->status == POWER_SUPPLY_STATUS_CHARGING || + chg->status == POWER_SUPPLY_STATUS_FULL) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chg->health; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = chg->status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = chg->charge_type; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX2: + ret = max77818_charger_get_input_current(chg, &val->intval); + if (ret) { + ret = -ENODEV; + goto out; + } + break; + case POWER_SUPPLY_PROP_CHARGER_MODE: + ret = max77818_charger_get_charger_mode(chg); + if (ret) { + ret = -ENODEV; + goto out; + } + val->intval = chg->charger_mode; + break; + case POWER_SUPPLY_PROP_STATUS_EX: + if (chg->charger_mode == POWER_SUPPLY_MODE_OTG_SUPPLY) + /* Charger device reports CHGIN OK in OTG mode anyway, + * so just ignore this and report WCIN status only when + * in OTG mode (charging is off anyway) + */ + val->intval = (max77818_charger_wcin_present(chg) << 1); + else { + chgin_connected = max77818_charger_chgin_present(chg); + wcin_connected = max77818_charger_wcin_present(chg); + val->intval = chgin_connected | (wcin_connected << 1); + } + break; + default: + ret = -EINVAL; + goto out; + } + +out: + mutex_unlock(&chg->lock); + return ret; +} + +static int max77818_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77818_charger *chg = (struct max77818_charger *) psy->drv_data; + int current_max = 0; + int ret = 0; + + mutex_lock(&chg->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGER_MODE: + if (val->intval == POWER_SUPPLY_MODE_CHARGER || + val->intval == POWER_SUPPLY_MODE_ALL_OFF || + val->intval == POWER_SUPPLY_MODE_OTG_SUPPLY) { + chg->charger_mode = val->intval; + } else { + ret = -EINVAL; + goto out; + } + + /* + * Disable charger, but only if it's requested. + */ + if (val->intval == POWER_SUPPLY_MODE_ALL_OFF) + max77818_charger_set_enable(chg, 0); + else + max77818_charger_set_enable(chg, 1); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + current_max = min(val->intval, chg->input_current_limit_chgin); + dev_dbg(chg->dev, + "Setting new current max for chgin interface: %d\n", + current_max); + + ret = max77818_charger_set_chgin_current_limit(chg, current_max); + if (ret) { + dev_err(chg->dev, + "failed to set chgin input current: %d\n", + ret); + + goto out; + } + break; + case POWER_SUPPLY_PROP_CURRENT_MAX2: + current_max = min(val->intval, chg->input_current_limit_wcin); + dev_dbg(chg->dev, + "Setting new current max for wcin interface: %d\n", + current_max); + + ret = max77818_charger_set_wcin_current_limit(chg, current_max); + if (ret) { + dev_err(chg->dev, + "failed to set wcin input current: %d\n", + ret); + + goto out; + } + break; + default: + ret = -EINVAL; + goto out; + } + +out: + mutex_unlock(&chg->lock); + return ret; +} + +static int max77818_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGER_MODE: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX2: + return 1; + default: + return 0; + } +} + +static int max77818_charger_parse_dt(struct max77818_charger *chg) +{ + struct device_node *np = chg->dev->of_node; + struct gpio_desc *gdp; + + if (!np) { + dev_err(chg->dev, "no charger OF node\n"); + return -ENODEV; + } + + if (of_property_read_u32(np, "fast_charge_timer", + &chg->fast_charge_timer)) + chg->fast_charge_timer = 4; // 4 hours + + if (of_property_read_u32(np, "fast_charge_current", + &chg->fast_charge_current)) + chg->fast_charge_current = 450; // 450mA + + if (of_property_read_u32(np, "charge_termination_voltage", + &chg->termination_voltage)) + chg->termination_voltage = 4200; // 4200mV + + if (of_property_read_u32(np, "topoff_timer", &chg->topoff_timer)) + chg->topoff_timer = 30; // 30 min + + if (of_property_read_u32(np, "topoff_current", + &chg->topoff_current)) + chg->topoff_current = 150; // 150mA + + if (of_property_read_u32(np, "restart_threshold", + &chg->restart_threshold)) + chg->restart_threshold = 150; // 150mV + + if (of_property_read_u32(np, "default_current_limit", + &chg->default_current_limit)) + chg->default_current_limit = 500; // 500mA + + if (of_property_read_u32(np, "input_current_limit_chgin", + &chg->input_current_limit_chgin)) + chg->input_current_limit_chgin = chg->default_current_limit; + + if (of_property_read_u32(np, "input_current_limit_wcin", + &chg->input_current_limit_wcin)) + chg->input_current_limit_wcin = chg->default_current_limit; + + gdp = devm_gpiod_get(chg->dev, "chgin-stat", GPIOD_IN); + if (IS_ERR(gdp)) { + if (PTR_ERR(gdp) != -ENOENT) + dev_warn(chg->dev, + "chgin-stat GPIO not given in DT, " + "chgin connection status not available\n"); + + if (PTR_ERR(gdp) != -ENOSYS) + dev_warn(chg->dev, + "chgin-stat GPIO given is not valid, " + "chgin connection status not available\n"); + } + else { + chg->chgin_stat_gpio = gdp; + dev_dbg(chg->dev, + "chgin connection status gpio registered (gpio %d)\n", + desc_to_gpio(chg->chgin_stat_gpio)); + } + + gdp = devm_gpiod_get(chg->dev, "wcin-stat", GPIOD_IN); + if (IS_ERR(gdp)) { + if (PTR_ERR(gdp) != -ENOENT) + dev_warn(chg->dev, + "wcin-stat GPIO not given in DT, " + "wcin connection status not available\n"); + + if (PTR_ERR(gdp) != -ENOSYS) + dev_warn(chg->dev, + "wcin-stat GPIO given is not valid, " + "wcin connection status not available\n"); + } + else { + chg->wcin_stat_gpio = gdp; + dev_dbg(chg->dev, + "wcin connection status gpio registered (gpio %d)\n", + desc_to_gpio(chg->wcin_stat_gpio)); + } + return 0; +} + +static const struct power_supply_desc psy_chg_desc = { + .name = "max77818-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = max77818_charger_props, + .num_properties = ARRAY_SIZE(max77818_charger_props), + .get_property = max77818_charger_get_property, + .set_property = max77818_charger_set_property, + .property_is_writeable = max77818_charger_property_is_writeable, +}; + +static int max77818_charger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct max77818_dev *max77818 = dev_get_drvdata(dev->parent); + struct power_supply_config psy_cfg = {}; + struct max77818_charger *chg; + int ret; + bool init_ok; + + if (IS_ERR_OR_NULL(max77818->regmap_chg)) { + dev_warn(dev, + "charge device regmap not initialized by MFD parent\n"); + } + + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = dev; + chg->regmap = max77818->regmap_chg; + + mutex_init(&chg->lock); + + ret = max77818_charger_parse_dt(chg); + if (ret < 0) + dev_warn(dev, "failed to parse charger dt: %d\n", ret); + + psy_cfg.drv_data = chg; + psy_cfg.of_node = dev->of_node; + + init_ok = !max77818_charger_initialize(chg); + if (!init_ok) { + dev_warn(dev, "failed to init charger: %d\n", init_ok); + } + + chg->psy_chg = devm_power_supply_register(dev, &psy_chg_desc, &psy_cfg); + if (IS_ERR(chg->psy_chg)) { + ret = PTR_ERR(chg->psy_chg); + dev_err(dev, "failed to register supply: %d\n", ret); + return ret; + } + + return 0; +} + +static int max77818_charger_suspend(struct device *dev) +{ + if (pm_suspend_target_state == PM_SUSPEND_MEM) { + dev_dbg(dev->parent, "Selecting sleep pinctrl state\n"); + pinctrl_pm_select_sleep_state(dev->parent); + } + + return 0; +} + +static int max77818_charger_resume(struct device *dev) +{ + if (pm_suspend_target_state == PM_SUSPEND_MEM) { + dev_dbg(dev->parent, "Selecting default pinctrl state\n"); + pinctrl_pm_select_default_state(dev->parent); + } + + return 0; +} + +static struct dev_pm_ops max77818_pm_ops = { + .suspend = max77818_charger_suspend, + .resume = max77818_charger_resume, +}; + +static struct platform_driver max77818_charger_driver = { + .driver = { + .name = MAX77818_CHARGER_NAME, + .owner = THIS_MODULE, + .pm = &max77818_pm_ops, + }, + .probe = max77818_charger_probe, +}; +module_platform_driver(max77818_charger_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MAX77818 Charger Driver"); +MODULE_AUTHOR("TaiEup Kim "); diff --git a/drivers/power/supply/max77818_battery.c b/drivers/power/supply/max77818_battery.c new file mode 100644 index 000000000000..10927e751588 --- /dev/null +++ b/drivers/power/supply/max77818_battery.c @@ -0,0 +1,1984 @@ +/* + * Fuel gauge driver for Maxim 77818 + * Note that Maxim 77818 is mfd and this is its subdevice. + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * Author: Shawn Guo + * Author: Lars Ivar Miljeteig + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * It is based on max17042_battery driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Status register bits */ +#define STATUS_POR_BIT (1 << 1) +#define STATUS_BST_BIT (1 << 3) +#define STATUS_VMN_BIT (1 << 8) +#define STATUS_TMN_BIT (1 << 9) +#define STATUS_SMN_BIT (1 << 10) +#define STATUS_BI_BIT (1 << 11) +#define STATUS_VMX_BIT (1 << 12) +#define STATUS_TMX_BIT (1 << 13) +#define STATUS_SMX_BIT (1 << 14) +#define STATUS_BR_BIT (1 << 15) + +/* Interrupt mask bits */ +#define CONFIG_ALRT_BIT_ENBL (1 << 2) +#define STATUS_INTR_SOCMIN_BIT (1 << 10) +#define STATUS_INTR_SOCMAX_BIT (1 << 14) + +/* Config2 register bits */ +#define CONFIG2_LDMDL (1 << 5) + +#define VFSOC0_LOCK 0x0000 +#define VFSOC0_UNLOCK 0x0080 +#define MODEL_UNLOCK1 0X0059 +#define MODEL_UNLOCK2 0X00C4 +#define MODEL_LOCK1 0X0000 +#define MODEL_LOCK2 0X0000 + +#define dQ_ACC_DIV 0x4 +#define dP_ACC_100 0x1900 +#define dP_ACC_200 0x3200 + +#define MAX77818_VMAX_TOLERANCE 50 /* 50 mV */ + +#define SYNC_SET_FLAG(flag, lock) ( \ + { \ + mutex_lock(lock); \ + flag = true; \ + mutex_unlock(lock); \ + } \ +) + +#define SYNC_CLEAR_FLAG(flag, lock) ( \ + { \ + mutex_lock(lock); \ + flag = false; \ + mutex_unlock(lock); \ + } \ +) + +#define SYNC_GET_FLAG(flag, lock) ( \ + { \ + bool state; \ + mutex_lock(lock); \ + state = flag; \ + mutex_unlock(lock); \ + state; \ + } \ +) + +/* Parameter to be given from u-boot after doing update + * in order to verify that all custom FG parameters + * are configured according to DT */ +static char config_update_param[255] = "verify"; +module_param_string(config_update, + config_update_param, + sizeof(config_update_param), + 0644); +MODULE_PARM_DESC(config_update, + "Optional parameter indicating the following config update mode:" + "- complete FG update (config_update=\"complete\")" + "- partial update (config_update=\"partial\")" + "- verification against current DT (config_update=\"verify\")" + "\n" + "By default, the verify mode is set." + "\n" + "Scripts may set the config_update param in order to re-config " + "the device if required, depending on versioning scheme or other " + "means of determining if a complete or partial update is required."); + +struct max77818_chip { + struct device *dev; + struct max77818_dev *max77818_dev; + + int fg_irq; + int chg_irq; + int chg_chgin_irq; + int chg_wcin_irq; + struct regmap *regmap; + struct power_supply *battery; + struct max17042_platform_data *pdata; + struct work_struct init_work; + struct work_struct chg_isr_work; + bool init_complete; + struct power_supply *charger; + struct usb_phy *usb_phy[2]; + struct notifier_block charger_detection_nb[2]; + struct work_struct charger_detection_work[2]; + int status_ex; + int usb_safe_max_current; + struct work_struct initial_charger_sync_work; + struct completion init_completion; + struct mutex lock; +}; + +static enum power_supply_property max77818_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_STATUS_EX, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, + POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_CHARGER_MODE, +}; + +static const char * const max77818_status_ex_text[] = { + "Charger not connected", "POGO connected", "USB-C connected", + "POGO/USB-C connected", "Changing", "Unknown" +}; + +struct max77818_of_property { + const char *property_name; + u8 register_addr; + int (*reg_write_op)(struct regmap *map, + unsigned int reg, + unsigned int value); + bool skip_verify; + bool require_lock; +}; + +static void max77818_do_init_completion(struct max77818_chip *chip) +{ + /* Clear flag used by power supply callbacks to check + * if it is safe to do property read/write */ + SYNC_SET_FLAG(chip->init_complete, &chip->lock); + + /* Signal to worker(s) waiting for initiation to be complete before + * doing final steps */ + complete(&chip->init_completion); +} + +static bool max77818_do_complete_update(struct max77818_chip *chip) +{ + if (strncmp(config_update_param, "complete", 8) == 0) { + dev_dbg(chip->dev, "config_update='complete'\n"); + return true; + } + else { + return false; + } +} + +static bool max77818_do_partial_update(struct max77818_chip *chip) +{ + if (strncmp(config_update_param, "partial", 7) == 0) { + dev_dbg(chip->dev, "config_update='partial'\n"); + return true; + } + else { + return false; + } +} + +static bool max77818_do_param_verification(struct max77818_chip *chip) +{ + if (strncmp(config_update_param, "verify", 6) == 0) { + dev_dbg(chip->dev, "config_update='verify'\n"); + return true; + } + else { + return false; + } +} + +static int max77818_get_temperature(struct max77818_chip *chip, int *temp) +{ + struct regmap *map = chip->regmap; + u32 data; + int ret; + + ret = regmap_read(map, MAX17042_TEMP, &data); + if (ret < 0) + return ret; + + *temp = sign_extend32(data, 15); + /* The value is converted into deci-centigrade scale */ + /* Units of LSB = 1 / 256 degree Celsius */ + *temp = *temp * 10 / 256; + return 0; +} + +static int max77818_get_status(struct max77818_chip *chip, int *status) +{ + int ret, charge_full, charge_now; + + ret = power_supply_am_i_supplied(chip->battery); + if (ret < 0) { + *status = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + if (ret == 0) { + *status = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + /* + * The MAX170xx has builtin end-of-charge detection and will update + * FullCAP to match RepCap when it detects end of charging. + * + * When this cycle the battery gets charged to a higher (calculated) + * capacity then the previous cycle then FullCAP will get updated + * contineously once end-of-charge detection kicks in, so allow the + * 2 to differ a bit. + */ + + ret = regmap_read(chip->regmap, MAX17042_FullCAP, &charge_full); + if (ret < 0) + return ret; + + ret = regmap_read(chip->regmap, MAX17042_RepCap, &charge_now); + if (ret < 0) + return ret; + + if ((charge_full - charge_now) <= MAX17042_FULL_THRESHOLD) + *status = POWER_SUPPLY_STATUS_FULL; + else + *status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; +} + +static void max77818_update_status_ex(struct max77818_chip *chip) +{ + union power_supply_propval val; + int ret = 0; + + ret = MAX77818_DO_NON_FGCC_OP( + chip->max77818_dev, + power_supply_get_property(chip->charger, + POWER_SUPPLY_PROP_STATUS_EX, + &val), + "Requesting status_ex from charger"); + + if (ret) { + dev_err(chip->dev, + "Failed to read status_ex from charger, setting " + "status_ex = UNKNOWN\n"); + chip->status_ex = POWER_SUPPLY_STATUS_EX_UNKNOWN; + } + else { + if (val.intval < (sizeof(max77818_status_ex_text)/ + sizeof(max77818_status_ex_text[0]))) + dev_dbg(chip->dev, + "Setting status_ex = %s\n", + max77818_status_ex_text[val.intval]); + else + dev_dbg(chip->dev, + "Unknown status_ex (%d) returned\n", + val.intval); + + chip->status_ex = val.intval; + } + + dev_dbg(chip->dev, "Sending status_ex change notification\n"); + sysfs_notify(&chip->battery->dev.kobj, NULL, "status_ex"); +} + +static int max77818_get_battery_health(struct max77818_chip *chip, int *health) +{ + int temp, vavg, vbatt, ret; + u32 val; + + ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val); + if (ret < 0) + goto health_error; + + /* bits [0-3] unused */ + vavg = val * 625 / 8; + /* Convert to millivolts */ + vavg /= 1000; + + ret = regmap_read(chip->regmap, MAX17042_VCELL, &val); + if (ret < 0) + goto health_error; + + /* bits [0-3] unused */ + vbatt = val * 625 / 8; + /* Convert to millivolts */ + vbatt /= 1000; + + if (vavg < chip->pdata->vmin) { + *health = POWER_SUPPLY_HEALTH_DEAD; + goto out; + } + + if (vbatt > chip->pdata->vmax + MAX77818_VMAX_TOLERANCE) { + *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto out; + } + + ret = max77818_get_temperature(chip, &temp); + if (ret < 0) + goto health_error; + + if (temp < chip->pdata->temp_min) { + *health = POWER_SUPPLY_HEALTH_COLD; + goto out; + } + + if (temp > chip->pdata->temp_max) { + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + goto out; + } + + *health = POWER_SUPPLY_HEALTH_GOOD; + +out: + return 0; + +health_error: + return ret; +} + +static int max77818_set_charger_mode(struct max77818_chip *chip, + const union power_supply_propval *val) +{ + int ret; + + if (!chip->charger) + return -ENODEV; + + ret = MAX77818_DO_NON_FGCC_OP( + chip->max77818_dev, + power_supply_set_property(chip->charger, + POWER_SUPPLY_PROP_CHARGER_MODE, + val), + "Setting charger mode through charger driver"); + if (ret) + dev_err(chip->dev, + "Failed to forward charger mode to charger driver\n"); + + return ret; +} + +static int max77818_get_charger_mode(struct max77818_chip *chip, + int *charger_mode) +{ + union power_supply_propval val; + int ret; + + if (!chip->charger) + return -ENODEV; + + + ret = MAX77818_DO_NON_FGCC_OP( + chip->max77818_dev, + power_supply_get_property(chip->charger, + POWER_SUPPLY_PROP_CHARGER_MODE, + &val), + "Reading charger mode from charger driver"); + if (ret) + dev_err(chip->dev, + "Failed to read charger mode from charger driver\n"); + + else + *charger_mode = val.intval; + + return ret; +} + +static int max77818_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77818_chip *chip = power_supply_get_drvdata(psy); + struct regmap *map = chip->regmap; + int temp; + int ret; + u32 data; + u64 data64; + + if (!SYNC_GET_FLAG(chip->init_complete, &chip->lock)) + return -EAGAIN; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = max77818_get_status(chip, &val->intval); + if (ret < 0) + return ret; + break; + case POWER_SUPPLY_PROP_STATUS_EX: + /* status_ex is just a shadow value of the status_ex prop + * reported by the charger driver, updated upon receiving + * a connection change interrupt from the charger */ + val->intval = chip->status_ex; + break; + + case POWER_SUPPLY_PROP_PRESENT: + /* + * MAX17042_STATUS_BattAbsent bit is not working for some + * reason unknown yet. We are working around the issue here by + * reading temperature register, in which a negative value + * indicates absence of battery. + */ + ret = max77818_get_temperature(chip, &temp); + if (ret < 0) + return ret; + + if (temp < 0) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = regmap_read(map, MAX17042_Cycles, &data); + if (ret < 0) + return ret; + + val->intval = data; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + ret = regmap_read(map, MAX17042_MinMaxVolt, &data); + if (ret < 0) + return ret; + + val->intval = data >> 8; + val->intval *= 20000; /* Units of LSB = 20mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = regmap_read(map, MAX17042_MinMaxVolt, &data); + if (ret < 0) + return ret; + + val->intval = (data & 0xff) * 20000; /* Units of 20mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = regmap_read(map, MAX17047_V_empty, &data); + if (ret < 0) + return ret; + + val->intval = data >> 7; + val->intval *= 10000; /* Units of LSB = 10mV */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(map, MAX17042_VCELL, &data); + if (ret < 0) + return ret; + + val->intval = data * 625 / 8; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = regmap_read(map, MAX17042_AvgVCELL, &data); + if (ret < 0) + return ret; + + val->intval = data * 625 / 8; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + ret = regmap_read(map, MAX17042_OCVInternal, &data); + if (ret < 0) + return ret; + + val->intval = data * 625 / 8; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(map, MAX17042_RepSOC, &data); + if (ret < 0) + return ret; + + val->intval = data >> 8; + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + ret = regmap_read(map, MAX17042_SALRT_Th, &data); + if (ret < 0) + return ret; + + val->intval = data & 0xff; + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX: + ret = regmap_read(map, MAX17042_SALRT_Th, &data); + if (ret < 0) + return ret; + + val->intval = data >> 8; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = regmap_read(map, MAX17042_DesignCap, &data); + if (ret < 0) + return ret; + + data64 = data * 5000000ll; + do_div(data64, chip->pdata->r_sns); + val->intval = data64; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = regmap_read(map, MAX17042_FullCAP, &data); + if (ret < 0) + return ret; + + data64 = data * 5000000ll; + do_div(data64, chip->pdata->r_sns); + val->intval = data64; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = regmap_read(map, MAX17042_RepCap, &data); + if (ret < 0) + return ret; + + data64 = data * 5000000ll; + do_div(data64, chip->pdata->r_sns); + val->intval = data64; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = regmap_read(map, MAX17042_QH, &data); + if (ret < 0) + return ret; + + val->intval = data * 1000 / 2; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = max77818_get_temperature(chip, &val->intval); + if (ret < 0) + return ret; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + /* LSB is Alert Minimum. In deci-centigrade */ + val->intval = sign_extend32(data & 0xff, 7) * 10; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + /* MSB is Alert Maximum. In deci-centigrade */ + val->intval = sign_extend32(data >> 8, 7) * 10; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = max77818_get_battery_health(chip, &val->intval); + if (ret < 0) + return ret; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = regmap_read(map, MAX17042_Current, &data); + if (ret < 0) + return ret; + + val->intval = sign_extend32(data, 15); + val->intval *= 1562500 / chip->pdata->r_sns; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = regmap_read(map, MAX17042_AvgCurrent, &data); + if (ret < 0) + return ret; + + val->intval = sign_extend32(data, 15); + val->intval *= 1562500 / chip->pdata->r_sns; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = regmap_read(map, MAX17042_TTE, &data); + val->intval = data * 5625 / 1000; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = regmap_read(map, MAX77818_TTF, &data); + val->intval = data * 5625 / 1000; + break; + case POWER_SUPPLY_PROP_CHARGER_MODE: + ret = max77818_get_charger_mode(chip, &val->intval); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77818_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77818_chip *chip = power_supply_get_drvdata(psy); + struct regmap *map = chip->regmap; + int ret = 0; + u32 data; + int8_t temp; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + + /* Input in deci-centigrade, convert to centigrade */ + temp = val->intval / 10; + /* force min < max */ + if (temp >= (int8_t)(data >> 8)) + temp = (int8_t)(data >> 8) - 1; + /* Write both MAX and MIN ALERT */ + data = (data & 0xff00) + temp; + ret = regmap_write(map, MAX17042_TALRT_Th, data); + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + + /* Input in Deci-Centigrade, convert to centigrade */ + temp = val->intval / 10; + /* force max > min */ + if (temp <= (int8_t)(data & 0xff)) + temp = (int8_t)(data & 0xff) + 1; + /* Write both MAX and MIN ALERT */ + data = (data & 0xff) + (temp << 8); + ret = regmap_write(map, MAX17042_TALRT_Th, data); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + if (val->intval < 0 || val->intval > 100) { + ret = -EINVAL; + break; + } + ret = regmap_update_bits(map, MAX17042_SALRT_Th, + 0xff, val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX: + if ((val->intval < 0 || val->intval > 100) && + val->intval != 255) { + ret = -EINVAL; + break; + } + ret = regmap_update_bits(map, MAX17042_SALRT_Th, + 0xff00, val->intval << 8); + break; + case POWER_SUPPLY_PROP_CHARGER_MODE: + if ((val->intval < 0) || (val->intval > 2)) { + ret = -EINVAL; + break; + } + + ret = max77818_set_charger_mode(chip, val); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int max77818_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX: + case POWER_SUPPLY_PROP_CHARGER_MODE: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static void max77818_external_power_changed(struct power_supply *psy) +{ + power_supply_changed(psy); +} + +static int max77818_write_and_verify_reg(struct regmap *map, unsigned int reg, unsigned int value) +{ + int retries = 8; + u32 read_value; + int ret; + + do { + retries--; + ret = regmap_write(map, reg, value); + if (ret) { + continue; + } + + ret = regmap_read(map, reg, &read_value); + if (ret) { + continue; + } + + if (read_value != value) { + ret = -EIO; + } + } while (retries && read_value != value); + + if (ret < 0) + pr_err("%s: err %d\n", __func__, ret); + + return ret; +} + +static inline int max77818_unlock_model(struct max77818_chip *chip) +{ + struct regmap *map = chip->regmap; + int ret; + + ret = regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1); + if (ret) + return ret; + + return regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2); +} + +static inline int max77818_lock_model(struct max77818_chip *chip) +{ + struct regmap *map = chip->regmap; + int ret; + + ret = regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1); + if (ret) + return ret; + + return regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2); +} + +static inline int max77818_write_model_data(struct max77818_chip *chip, + u16 *data, int count) +{ + struct regmap *map = chip->regmap; + int i, ret; + + for (i = 0; i < count; i++) { + ret = regmap_write(map, MAX17042_MODELChrTbl + i, data[i]); + if (ret) + return ret; + } + return 0; +} + +static inline int max77818_read_model_data(struct max77818_chip *chip, + u16 *data, int count) +{ + struct regmap *map = chip->regmap; + u32 tmp; + int ret, i; + + for (i = 0; i < count; i++) { + ret = regmap_read(map, MAX17042_MODELChrTbl + i, &tmp); + if (ret) + return ret; + data[i] = (u16)tmp; + } + return 0; +} + +static inline int max77818_model_data_compare(struct max77818_chip *chip, + u16 *expected, u16 *compareto, int size) +{ + int i; + + if (memcmp(expected, compareto, size)) { + dev_err(chip->dev, "%s compare failed\n", __func__); + for (i = 0; i < size; i++) + dev_info(chip->dev, + "%03d: Expected: 0x%x, read: 0x%x\n", + i, + expected[i], + compareto[i]); + return -EINVAL; + } + return 0; +} + +static int max77818_init_model(struct max77818_chip *chip) +{ + struct device_node *np = chip->dev->of_node; + u16 *data, *rdata; + int count; + int ret; + + count = of_property_count_u16_elems(np, "maxim,cell-model-data"); + if (count < 0 || count != MAX17042_CHARACTERIZATION_DATA_SIZE) { + dev_err(chip->dev, "invalid or missing maxim,cell-model-data: %d\n", + count); + return -EINVAL; + } + + data = kcalloc(count, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(chip->dev, "failed to kcalloc for data\n"); + return -ENOMEM; + } + + /* Read cell model data */ + of_property_read_u16_array(np, "maxim,cell-model-data", data, count); + + rdata = kcalloc(count, sizeof(*rdata), GFP_KERNEL); + if (!rdata) { + dev_err(chip->dev, "failed to kcalloc for rdata\n"); + ret = -ENOMEM; + goto free_data; + } + + ret = max77818_unlock_model(chip); + if (ret) { + dev_err(chip->dev, "unable to unlock model data: %d\n", ret); + goto free_rdata; + } + + ret = max77818_write_model_data(chip, data, count); + if (ret) { + dev_err(chip->dev, "unable to write model data: %d\n", ret); + goto lock_model; + } + + ret = max77818_read_model_data(chip, rdata, count); + if (ret) { + dev_err(chip->dev, "unable to read model data: %d\n", ret); + goto lock_model; + } + + ret = max77818_model_data_compare(chip, data, rdata, count); + if (ret) + dev_err(chip->dev, "model data compare failed: %d\n", ret); + +lock_model: + max77818_lock_model(chip); +free_rdata: + kfree(rdata); +free_data: + kfree(data); + return ret; +} + +static int max77818_verify_model_lock(struct max77818_chip *chip) +{ + int count = MAX17042_CHARACTERIZATION_DATA_SIZE; + u16 *data; + int ret = 0; + int i; + + data = kcalloc(count, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = max77818_read_model_data(chip, data, count); + if (ret) + goto done; + + for (i = 0; i < count; i++) { + if (data[i]) { + ret = -EINVAL; + goto done; + } + } + +done: + kfree(data); + return ret; +} + +static int max77818_model_loading(struct max77818_chip *chip) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(400); + struct regmap *map = chip->regmap; + u32 config2; + int ret; + + /* Setting LdMdl bit */ + ret = regmap_read(map, MAX77818_Config2, &config2); + if (ret) + return ret; + + ret = regmap_write(map, MAX77818_Config2, config2 | CONFIG2_LDMDL); + if (ret) + return ret; + + /* Poll LdMdl bit to be 0 under 400ms */ + do { + ret = regmap_read(map, MAX77818_Config2, &config2); + if (ret) + break; + if ((config2 & CONFIG2_LDMDL) == 0) + break; + if (time_after(jiffies, timeout)) + break; + } while (1); + + ret = regmap_read(map, MAX77818_Config2, &config2); + if (ret) + return ret; + + return (config2 & CONFIG2_LDMDL) ? -ETIMEDOUT : 0; +} + +static inline int max77818_read_of_property(struct max77818_chip *chip, + const char *param, + u16 *value) +{ + struct device_node *np = chip->dev->of_node; + int ret; + + u16 read_value = 0; + + ret = of_property_read_u16(np, param, &read_value); + if (!ret) + *value = read_value; + + return ret; +} + +static int max77818_write_of_param_if_mismatch(struct max77818_chip *chip, + struct max77818_of_property *prop) +{ + u16 read_param = 0; + int read_cur_value = 0; + bool param_mismatch = false; + int ret; + + ret = max77818_read_of_property(chip, prop->property_name, &read_param); + if(!ret) { + dev_dbg(chip->dev, "Verifying '%s' (reg 0x%02x) = 0x%04x\n", + prop->property_name, + prop->register_addr, + read_param); + + if (prop->require_lock) { + dev_dbg(chip->dev, "Applying lock\n"); + mutex_lock(&chip->max77818_dev->lock); + } + + ret = regmap_read(chip->regmap, + prop->register_addr, + &read_cur_value); + if(ret) { + dev_warn(chip->dev, + "Failed to read '%s' from reg 0x%02x)\n", + prop->property_name, + prop->register_addr); + param_mismatch = true; + } + + if ((read_param != read_cur_value) || param_mismatch) { + dev_dbg(chip->dev, + "Read '%s' (reg 0x%02x) = 0x%04x from device, " + "expected 0x%04x\n", + prop->property_name, + prop->register_addr, + read_cur_value, + read_param); + param_mismatch = true; + } + + if (param_mismatch) { + dev_dbg(chip->dev, "Writing '%s' (reg 0x%02x): 0x%04x\n", + prop->property_name, + prop->register_addr, + read_param); + ret = prop->reg_write_op(chip->regmap, + prop->register_addr, + read_param); + if (ret) { + dev_err(chip->dev, + "Failed to write '%s' (reg 0x%02x): 0x%04x\n", + prop->property_name, + prop->register_addr, + read_param); + } + } + + if (prop->require_lock) { + dev_dbg(chip->dev, "Releasing lock\n"); + mutex_unlock(&chip->max77818_dev->lock); + } + } + else if (ret == -EINVAL) + dev_warn(chip->dev, + "'%s' property not given in DT, using default value\n", + prop->property_name); + else + dev_warn(chip->dev, + "Failed to read '%s' param from DT, check value\n", + prop->property_name); + + return ret; +} + +static int max77818_read_of_param_and_write(struct max77818_chip *chip, + struct max77818_of_property *prop) +{ + u16 read_param; + int ret; + + ret = max77818_read_of_property(chip, prop->property_name, &read_param); + if (!ret) { + dev_dbg(chip->dev, "Writing '%s' (reg 0x%02x): 0x%04x\n", + prop->property_name, + prop->register_addr, + read_param); + + if (prop->require_lock) { + dev_dbg(chip->dev, "Applying lock\n"); + mutex_lock(&chip->max77818_dev->lock); + } + + ret = prop->reg_write_op(chip->regmap, + prop->register_addr, + read_param); + if (ret) { + dev_warn(chip->dev, + "Failed to write '%s' property read from DT\n", + prop->property_name); + } + + if (prop->require_lock) { + dev_dbg(chip->dev, "Releasing lock\n"); + mutex_unlock(&chip->max77818_dev->lock); + } + } + else if (ret == -EINVAL) + dev_warn(chip->dev, + "'%s' property not given in DT, using default value\n", + prop->property_name); + else + dev_warn(chip->dev, + "Failed to read '%s' param from DT, check value\n", + prop->property_name); + + return ret; +} + +static int max77818_unlock_extra_config_registers(struct max77818_chip *chip) +{ + struct regmap *map = chip->regmap; + u32 value; + int ret; + + ret = regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); + if (ret) { + dev_warn(chip->dev, + "Failed to write VFSOC0Enable to unlock: %d\n", ret); + return ret; + } + + ret = regmap_read(map, MAX17042_VFSOC, &value); + if (ret) { + dev_warn(chip->dev, "Failed to read VFSOC: %d\n", ret); + return ret; + } + + return max77818_write_and_verify_reg(map, MAX17042_VFSOC0, value); +} + +static int max77818_lock_extra_config_registers(struct max77818_chip *chip) +{ + struct regmap *map = chip->regmap; + int ret; + + ret = regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK); + if (ret) + dev_warn(chip->dev, + "Failed to write VFSOC0Enable to lock: %d\n", ret); + + return ret; +} + +static struct max77818_of_property max77818_relax_cfg = + {"maxim,relax-cfg", MAX17042_RelaxCFG, regmap_write, true, false }; + +static struct max77818_of_property max77818_custom_param_list [] = { + { "maxim,learn-cfg", MAX17042_LearnCFG, regmap_write, true, false }, + + /* Verified and restored if required after reboot to ensure FGCC=1 */ + /* Also marked to require lock */ + { "maxim,config", MAX17042_CONFIG, regmap_write, false, true}, + + { "maxim,config2", MAX77818_Config2, regmap_write, true }, + { "maxim,full-soc-threshold", MAX17047_FullSOCThr, regmap_write, true, false }, + + /* learned value, skipped during verify/write operation at boot */ + { "maxim,fullcaprep", MAX17042_FullCAP0, max77818_write_and_verify_reg, true, false }, + + { "maxim,design-cap", MAX17042_DesignCap, regmap_write, true, false }, + + /* learned values, skipped during verify/write operation at boot */ + { "maxim,dpacc", MAX17042_dPacc, max77818_write_and_verify_reg, true, false }, + { "maxim,dqacc", MAX17042_dQacc, max77818_write_and_verify_reg, true, false }, + { "maxim,fullcapnom", MAX17042_FullCAPNom, max77818_write_and_verify_reg, true, false }, + + { "maxim,misc-cfg", MAX17042_MiscCFG, regmap_write, true, false }, + { "maxim,v-empty", MAX17047_V_empty, regmap_write, true, false }, + { "maxim,qresidual00", MAX17047_QRTbl00, max77818_write_and_verify_reg, true, false }, + { "maxim,qresidual10", MAX17047_QRTbl10, max77818_write_and_verify_reg, true, false }, + { "maxim,qresidual20", MAX17047_QRTbl20, max77818_write_and_verify_reg, true, false }, + { "maxim,qresidual30", MAX17047_QRTbl30, max77818_write_and_verify_reg, true, false }, + + /* learned value, skipped during verify/write operation at boot */ + { "maxim,rcomp0", MAX17042_RCOMP0, max77818_write_and_verify_reg, true, false }, + + { "maxim,tempco", MAX17042_TempCo, max77818_write_and_verify_reg, true, false }, + { "maxim,ichg-term", MAX17042_ICHGTerm, regmap_write, true, false }, + { "maxim,filter-cfg", MAX17042_FilterCFG, regmap_write, true, false }, + + /* learned value, skipped during verify/write operation at boot */ + { "maxim,iavg-empty", MAX17042_LAvg_empty, regmap_write, true, false }, + + { "maxim,tgain", MAX17042_TGAIN, regmap_write, false, false }, + { "maxim,toff", MAx17042_TOFF, regmap_write, false, false }, + { "maxim,tcurve", MAX77818_TCURVE, regmap_write, false, false }, + { "maxim,talrt-th", MAX17042_TALRT_Th, regmap_write, false, false }, + { "maxim,talrt-th2", MAX77818_TALRT_Th2, regmap_write, false, false }, + { "maxim,jeita-curr", MAX77818_JEITA_Curr, regmap_write, false, false }, + { "maxim,jeita-volt", MAX77818_JEITA_Volt, regmap_write, false, false }, + { "maxim,chargestate0", MAX77818_ChargeState0, regmap_write, false, false }, + { "maxim,chargestate1", MAX77818_ChargeState1, regmap_write, false, false }, + { "maxim,chargestate2", MAX77818_ChargeState2, regmap_write, false, false }, + { "maxim,chargestate3", MAX77818_ChargeState3, regmap_write, false, false }, + { "maxim,chargestate4", MAX77818_ChargeState4, regmap_write, false, false }, + { "maxim,chargestate5", MAX77818_ChargeState5, regmap_write, false, false }, + { "maxim,chargestate6", MAX77818_ChargeState6, regmap_write, false, false }, + { "maxim,chargestate7", MAX77818_ChargeState7, regmap_write, false, false }, + + /* The order of the following ones should be respected */ + { "maxim,at-rate", MAX17042_AtRate, regmap_write, true, false }, + { "maxim,smart-chg-cfg", MAX77818_SmartChgCfg, regmap_write, false, false }, + { "maxim,convgcfg", MAX77818_ConvgCfg, regmap_write, true, false }, +}; + +static bool max77818_write_mismatched_custom_params(struct max77818_chip *chip) +{ + int i; + int ret; + bool is_success = true; + + dev_dbg(chip->dev, "Verifying custom params\n"); + + if (max77818_relax_cfg.skip_verify) { + dev_dbg(chip->dev, + "Skipping verify/write for register '%s'\n", + max77818_relax_cfg.property_name); + } + else { + ret = max77818_write_of_param_if_mismatch(chip, &max77818_relax_cfg); + if (ret) + is_success = false; + } + + ret = max77818_unlock_extra_config_registers(chip); + if (ret) + is_success = false; + + for(i = 0; i < ARRAY_SIZE(max77818_custom_param_list); i++) { + if (max77818_custom_param_list[i].skip_verify) { + dev_dbg(chip->dev, + "Skipping verify/write for register '%s'\n", + max77818_custom_param_list[i].property_name); + continue; + } + + ret = max77818_write_of_param_if_mismatch(chip, + &max77818_custom_param_list[i]); + if (ret) + is_success = false; + } + + max77818_lock_extra_config_registers(chip); + + return is_success; +} + +static bool max77818_write_all_custom_params(struct max77818_chip *chip) +{ + struct regmap *map = chip->regmap; + int ret, i; + bool is_success = true; + + dev_dbg(chip->dev, "Writing custom params\n"); + + ret = regmap_write(map, MAX17042_RepCap, 0); + if (ret) { + dev_warn(chip->dev, "Failed to write RepCap: %d\n", ret); + is_success = false; + } + + ret = max77818_read_of_param_and_write(chip, &max77818_relax_cfg); + if (ret) + is_success = false; + + ret = max77818_unlock_extra_config_registers(chip); + if (ret) + is_success = false; + + for(i = 0; i < ARRAY_SIZE(max77818_custom_param_list); i++) { + ret = max77818_read_of_param_and_write(chip, + &max77818_custom_param_list[i]); + if (ret) + is_success = false; + } + + max77818_lock_extra_config_registers(chip); + + return is_success; +} + +static int max77818_init_chip(struct max77818_chip *chip) +{ + struct regmap *map = chip->regmap; + int ret; + bool is_success; + + /* Write cell characterization data */ + ret = max77818_init_model(chip); + if (ret) { + dev_err(chip->dev, "init model failed: %d\n", ret); + return ret; + } + + ret = max77818_verify_model_lock(chip); + if (ret) { + dev_err(chip->dev, "lock verify failed: %d\n", ret); + return ret; + } + + /* Write custom parameters from device tree */ + is_success = max77818_write_all_custom_params(chip); + + /* Initiate model loading for MAX77818 */ + ret = max77818_model_loading(chip); + if (ret) { + dev_err(chip->dev, "initiate model loading failed: %d\n", ret); + return ret; + } + + /* Wait 500 ms for SOC to be calculated from the new parameters */ + msleep(500); + + /* Init complete, Clear the POR bit if successful */ + if (is_success) + regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0); + + return 0; +} + +static void max77818_do_initial_charger_sync_worker(struct work_struct *work) +{ + struct max77818_chip *chip = + container_of(work, struct max77818_chip, initial_charger_sync_work); + struct max77818_dev *max77818 = chip->max77818_dev; + struct device *dev = max77818->dev; + union power_supply_propval val; + int ret; + + /* Wait for other initiation to be completed before running these + * final steps */ + dev_dbg(dev, "Waiting for other initiation to complete before doing " + "final charger driver sync. steps \n"); + ret = wait_for_completion_interruptible_timeout(&chip->init_completion, + 10 * HZ); + if (ret == 0) { + dev_err(dev, "Timeout while waiting for other init to complete, " + "trying to do final charger driver sync anyway\n"); + } + else { + dev_dbg(dev, "Other init completed, starting final charger " + "driver sync stepd\n"); + } + + val.intval = 0; + ret = MAX77818_DO_NON_FGCC_OP( + max77818, + power_supply_set_property(chip->charger, + POWER_SUPPLY_PROP_CURRENT_MAX, + &val), + "Setting chgin max current\n"); + if (ret) { + dev_err(dev, + "Failed to set max current in charger driver\n"); + } + + ret = MAX77818_DO_NON_FGCC_OP( + max77818, + power_supply_get_property(chip->charger, + POWER_SUPPLY_PROP_STATUS_EX, + &val), + "Reading initial status_ex from charger\n"); + if (ret) { + dev_err(chip->dev, + "Failed to read status_ex from charger driver while" + "doing initial charger driver sync\n"); + } + + /* Do an initial max current adjustment according to max current + * currently configured in USB PHY, in case this was updated before + * this driver was loaded. + * + * As the charger detection handling is normally done i a worker, + * the initial calls done here are queued on the same work queue as the + * async event workers, in order to prevent conflict with possible + * ongoing event handling if called directly from here */ + dev_dbg(chip->dev, + "Scheduling initial max current adjustment for chgin interface "); + schedule_work(&chip->charger_detection_work[0]); + + dev_dbg(chip->dev, + "Scheduling initial max current adjustment for wcin interface "); + schedule_work(&chip->charger_detection_work[1]); +} + +static irqreturn_t max77818_fg_isr(int id, void *dev) +{ + struct max77818_chip *chip = dev; + u32 val; + + regmap_read(chip->regmap, MAX17042_STATUS, &val); + + if (val & STATUS_INTR_SOCMIN_BIT) { + dev_info(chip->dev, "MIN SOC alert\n"); + sysfs_notify(&chip->battery->dev.kobj, NULL, + "capacity_alert_min"); + } + + if (val & STATUS_INTR_SOCMAX_BIT) { + dev_info(chip->dev, "MAX SOC alert\n"); + sysfs_notify(&chip->battery->dev.kobj, NULL, + "capacity_alert_max"); + } + + power_supply_changed(chip->battery); + + return IRQ_HANDLED; +} + +static void max77818_charger_isr_work(struct work_struct *work) +{ + struct max77818_chip *chip = + container_of(work, struct max77818_chip, chg_isr_work); + + dev_dbg(chip->dev, "Changing status_ex -> CHANGING\n"); + chip->status_ex = POWER_SUPPLY_STATUS_EX_CHANGING; + + dev_dbg(chip->dev, "Sending status_ex change notification\n"); + sysfs_notify(&chip->battery->dev.kobj, NULL, "status_ex"); + + dev_dbg(chip->dev, "Reading updated connection state from charger\n"); + max77818_update_status_ex(chip); +} + +static irqreturn_t max77818_charger_connection_change_isr(int irq, void *data) +{ + struct max77818_chip *chip = data; + + schedule_work(&chip->chg_isr_work); + + return IRQ_HANDLED; +} + +static irqreturn_t max77818_charger_isr(int irq, void *data) +{ + /* + * This IRQ handler needs to do nothing, as it's here only for + * manipulate top max77818 mfd irq_chip to handle BIT_CHGR_INT. + */ + return IRQ_HANDLED; +} + +static void max77818_init_worker(struct work_struct *work) +{ + struct max77818_chip *chip = container_of(work, struct max77818_chip, + init_work); + int ret; + + dev_dbg(chip->dev, "Doing complete re-config\n"); + ret = max77818_init_chip(chip); + if (ret) { + dev_err(chip->dev, "failed to init chip: %d\n", ret); + } + + max77818_do_init_completion(chip); +} + +static struct max17042_platform_data * +max77818_get_pdata(struct max77818_chip *chip) +{ + struct max17042_platform_data *pdata; + struct device *dev = chip->dev; + struct device_node *np = dev->of_node; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + if (of_property_read_u32(np, "maxim,rsns-microohm", &pdata->r_sns)) + pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; + + if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min)) + pdata->temp_min = INT_MIN; + if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max)) + pdata->temp_max = INT_MAX; + + if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin)) + pdata->vmin = INT_MIN; + if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax)) + pdata->vmax = INT_MAX; + + return pdata; +} + +static void max77818_get_driver_config(struct max77818_chip *chip) +{ + struct device_node *np = chip->dev->of_node; + int read_value; + int ret; + + ret = of_property_read_u32(np, "usb_safe_max_current", &read_value); + if (ret) { + dev_err(chip->dev, + "Failed to read usb_safe_max_current from DT\n"); + chip->usb_safe_max_current = 100; + } + else { + dev_dbg(chip->dev, + "Read usb_safe_max_current: %d\n", + read_value); + chip->usb_safe_max_current = read_value; + } +} + +static const struct regmap_config max77818_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static const struct power_supply_desc max77818_psy_desc = { + .name = "max77818_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = max77818_get_property, + .set_property = max77818_set_property, + .property_is_writeable = max77818_property_is_writeable, + .external_power_changed = max77818_external_power_changed, + .properties = max77818_battery_props, + .num_properties = ARRAY_SIZE(max77818_battery_props), +}; + +static void max77818_charger_detection_worker_chgin(struct work_struct *work) +{ + struct max77818_chip *chip = container_of(work, + struct max77818_chip, + charger_detection_work[0]); + /* + unsigned int min_current, max_current; + */ + + union power_supply_propval val; + int ret; + + mutex_lock(&chip->lock); + + dev_dbg(chip->dev, "Doing charger detection work for chgin interface..\n"); + + if (!chip->charger) { + dev_err(chip->dev, + "Cannot access charger device, unable to set max current for chgin interface\n"); + goto done; + } + + /* + dev_dbg(chip->dev, "Getting max/min current configured for given USB PHY\n"); + usb_phy_get_charger_current(chip->usb_phy[0], &min_current, &max_current); + if (max_current == 0) + val.intval = chip->usb_safe_max_current; + else + val.intval = max_current; + */ + + val.intval = 0; + + ret = MAX77818_DO_NON_FGCC_OP( + chip->max77818_dev, + power_supply_set_property(chip->charger, + POWER_SUPPLY_PROP_CURRENT_MAX, + &val), + "Setting max chgin current through charger driver"); + if (ret) + dev_err(chip->dev, + "Failed to set max chgin current in charger driver\n"); + + done: + mutex_unlock(&chip->lock); +} + +static void max77818_charger_detection_worker_wcin(struct work_struct *work) +{ + struct max77818_chip *chip = container_of(work, + struct max77818_chip, + charger_detection_work[1]); + unsigned int min_current, max_current; + union power_supply_propval val; + int ret; + + mutex_lock(&chip->lock); + + dev_dbg(chip->dev, "Doing charger detection work for wcin interface..\n"); + + if (!chip->charger) { + dev_err(chip->dev, + "Cannot access charger device, unable to set max current for wcin interface\n"); + goto done; + } + + dev_dbg(chip->dev, "Getting max/min current configured for given USB PHY (wcin)\n"); + usb_phy_get_charger_current(chip->usb_phy[1], &min_current, &max_current); + if (max_current == 0) + val.intval = 500; + else + val.intval = max_current; + + ret = MAX77818_DO_NON_FGCC_OP( + chip->max77818_dev, + power_supply_set_property(chip->charger, + POWER_SUPPLY_PROP_CURRENT_MAX2, + &val), + "Setting max wcin current through charger driver"); + if (ret) + dev_err(chip->dev, + "Failed to set max wcin current in charger driver\n"); + + done: + mutex_unlock(&chip->lock); +} + +static int max77818_charger_detection_notifier_call_chgin(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct max77818_chip *chip = container_of(nb, + struct max77818_chip, + charger_detection_nb[0]); + + dev_dbg(chip->dev, + "Handling charger detection notification from chgin interface " + "(max current: %lu)\n", val); + + schedule_work(&chip->charger_detection_work[0]); + + return NOTIFY_OK; +} + +static int max77818_charger_detection_notifier_call_wcin(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct max77818_chip *chip = container_of(nb, + struct max77818_chip, + charger_detection_nb[1]); + + dev_dbg(chip->dev, + "Handling charger detection notification from wcin interface " + "(max current: %lu)\n", val); + + schedule_work(&chip->charger_detection_work[1]); + + return NOTIFY_OK; +} + +static int max77818_init_otg_supply(struct max77818_chip *chip) +{ + struct device *dev = chip->dev; + + dev_dbg(dev, "Trying to reference charger (expecting charger as" + "first supply in given list of supplies)\n"); + if (chip->battery->num_supplies > 0) { + chip->charger = power_supply_get_by_name( + chip->battery->supplied_from[0]); + if (!chip->charger) { + dev_warn(dev, "Failed to reference charger, " + "OTG/charger IRQ handling will not" + "be available - verify DT config\n"); + } + } + + return 0; +} + +static int max77818_init_charger_detection(struct max77818_chip *chip) +{ + struct device *dev = chip->dev; + int ret; + + dev_dbg(dev, + "Trying to reference usb-phy1 (for receiving charger " + "detection notifications for chgin interface\n"); + chip->usb_phy[0]= devm_usb_get_phy_by_phandle(dev, "usb-phy1", 0); + if (IS_ERR(chip->usb_phy[0])) { + ret = PTR_ERR(chip->usb_phy[0]); + dev_err(dev, "usb_get_phy failed: %d\n", ret); + return ret; + } + + dev_dbg(dev, + "Trying to reference usb-phy2 (for receiving charger " + "detection notifications for wcin interface\n"); + chip->usb_phy[1]= devm_usb_get_phy_by_phandle(dev, "usb-phy2", 0); + if (IS_ERR(chip->usb_phy[1])) { + ret = PTR_ERR(chip->usb_phy[1]); + dev_err(dev, "usb_get_phy failed: %d\n", ret); + return ret; + } + + dev_dbg(dev, + "Trying to register notification handler (worker) for " + "chgin interface charger detection notifications \n"); + INIT_WORK(&chip->charger_detection_work[0], + max77818_charger_detection_worker_chgin); + chip->charger_detection_nb[0].notifier_call = + max77818_charger_detection_notifier_call_chgin; + ret = usb_register_notifier(chip->usb_phy[0], + &chip->charger_detection_nb[0]); + if (ret) { + dev_err(dev, "usb_register_notifier failed: %d\n", ret); + return ret; + } + + dev_dbg(dev, + "Trying to register notification handler (worker) for " + "wcin interface charger detection notifications \n"); + INIT_WORK(&chip->charger_detection_work[1], + max77818_charger_detection_worker_wcin); + chip->charger_detection_nb[1].notifier_call = + max77818_charger_detection_notifier_call_wcin; + ret = usb_register_notifier(chip->usb_phy[1], + &chip->charger_detection_nb[1]); + if (ret) { + dev_err(dev, "usb_register_notifier failed: %d\n", ret); + goto unreg_usb_phy0_notifier; + } + + return 0; + +unreg_usb_phy0_notifier: + usb_unregister_notifier(chip->usb_phy[0], + &chip->charger_detection_nb[0]); + + return ret; +} + +static int max77818_init_fg_interrupt_handling(struct max77818_dev *max77818, + struct max77818_chip *chip) +{ + struct device *dev = chip->dev; + int ret; + + /* Disable max SOC alert and set min SOC alert as 10% by default */ + regmap_write(chip->regmap, MAX17042_SALRT_Th, (0xff << 8) | 0x0a ); + + /* Register irq handler for the FG interrupt */ + chip->fg_irq = regmap_irq_get_virq(max77818->irqc_intsrc, + MAX77818_FG_INT); + if (chip->fg_irq <= 0) { + dev_err(dev, "failed to get virq: %d\n", chip->fg_irq); + return -ENODEV; + } + + ret = devm_request_threaded_irq(dev, chip->fg_irq, NULL, + max77818_fg_isr, 0, + chip->battery->desc->name, + chip); + if (ret) { + dev_err(dev, "failed to request irq: %d\n", ret); + return ret; + } + + return 0; +} + +static int max77818_init_chg_interrupt_handling(struct max77818_dev *max77818, + struct max77818_chip *chip) +{ + struct device *dev = chip->dev; + bool fgcc_restore_state; + bool skip_final_fgcc_op_finish = false; + int ret; + + /* Init worker to be used by the charger interrupt handler */ + INIT_WORK(&chip->chg_isr_work, max77818_charger_isr_work); + + /* Disable FGCC during charger device irq handler registration */ + ret = MAX77818_START_NON_FGCC_OP( + max77818, + fgcc_restore_state, + "Starting registration of irq handlers for the charger " + "interrupts\n"); + if (ret) { + dev_err(dev, + "Failed to disable FGCC before charger device irq " + "handler registration\n"); + return ret; + } + + /* Register irq handler for the charger interrupt */ + chip->chg_irq = regmap_irq_get_virq(max77818->irqc_intsrc, + MAX77818_CHGR_INT); + if (chip->chg_irq <= 0) { + dev_err(dev, "failed to get virq: %d\n", chip->chg_irq); + ret = -ENODEV; + goto finish_fgcc_op; + } + + ret = devm_request_threaded_irq(dev, chip->chg_irq, NULL, + max77818_charger_isr, 0, + "charger", chip); + if (ret) { + dev_err(dev, "failed to request charger irq: %d\n", ret); + goto finish_fgcc_op; + } + + /* Register irq handler for the CHGIN interrupt */ + chip->chg_chgin_irq = regmap_irq_get_virq(max77818->irqc_chg, + CHG_IRQ_CHGIN_I); + if (chip->chg_chgin_irq <= 0) { + dev_err(dev, "failed to get chgin virq: %d\n", + chip->chg_chgin_irq); + ret = -ENODEV; + goto free_chg_irq; + } + dev_dbg(dev, "chgin irq: %d\n", chip->chg_chgin_irq); + + ret = devm_request_threaded_irq(dev, chip->chg_chgin_irq, NULL, + max77818_charger_connection_change_isr, + 0, "charger-chgin", chip); + if (ret) { + dev_err(dev, "failed to reqeust chgin irq: %d\n", ret); + goto free_chg_irq; + } + + /* Register irq handler for the WCIN interrupt */ + chip->chg_wcin_irq = regmap_irq_get_virq(max77818->irqc_chg, + CHG_IRQ_WCIN_I); + if (chip->chg_wcin_irq <= 0) { + dev_err(dev, "failed to get wcin virq: %d\n", chip->chg_wcin_irq); + ret = -ENODEV; + goto free_chgin_irq; + } + dev_dbg(dev, "wcin irq: %d\n", chip->chg_wcin_irq); + + ret = devm_request_threaded_irq(dev, chip->chg_wcin_irq, NULL, + max77818_charger_connection_change_isr, + 0, "charger-wcin", chip); + if (ret) { + dev_err(dev, "failed to reqeust wcin irq: %d\n", ret); + goto free_chgin_irq; + } + + ret = MAX77818_FINISH_NON_FGCC_OP( + max77818, + fgcc_restore_state, + "Finishing registration of irq handlers for the charger " + "interrupts\n"); + if (ret) { + dev_err(dev, + "Failed to re-enable FGCC after charger device irq " + "handler registration\n"); + skip_final_fgcc_op_finish = true; + goto free_wcin_irq; + } + + return 0; + +free_wcin_irq: + free_irq(chip->chg_wcin_irq, NULL); +free_chgin_irq: + free_irq(chip->chg_chgin_irq, NULL); +free_chg_irq: + free_irq(chip->chg_irq, NULL); +finish_fgcc_op: + if (!skip_final_fgcc_op_finish) + if (MAX77818_FINISH_NON_FGCC_OP( + max77818, + fgcc_restore_state, + "Finishing (failed) registration of irq handlers " + "for the charger interrupts\n")) + dev_err(dev, + "Failed to re-enable FGCC after failed charger device " + "irq handler registration\n"); + + return ret; +} + +static int max77818_probe(struct platform_device *pdev) +{ + struct max77818_dev *max77818 = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct device *dev = &pdev->dev; + struct max77818_chip *chip; + u32 val; + int ret; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + mutex_init(&chip->lock); + + chip->max77818_dev = max77818; + chip->dev = dev; + chip->regmap = max77818->regmap_fg; + + chip->pdata = max77818_get_pdata(chip); + if (!chip->pdata) { + dev_err(dev, "no platform data provided\n"); + return -EINVAL; + } + + /* Get non device related driver config from DT */ + max77818_get_driver_config(chip); + + platform_set_drvdata(pdev, chip); + psy_cfg.drv_data = chip; + psy_cfg.of_node = dev->of_node; + + SYNC_CLEAR_FLAG(chip->init_complete, &chip->lock); + chip->battery = devm_power_supply_register(dev, &max77818_psy_desc, + &psy_cfg); + + if (IS_ERR(chip->battery)) { + ret = PTR_ERR(chip->battery); + dev_err(dev, "failed to register supply: %d \n", ret); + return ret; + } + + max77818_init_otg_supply(chip); + + ret = max77818_init_charger_detection(chip); + if (ret) { + dev_err(dev, "Failed to init charger detection\n"); + goto unreg_supply; + } + + ret = max77818_init_fg_interrupt_handling(max77818, chip); + if (ret) { + dev_err(dev, "Failed to init FG interrupt handling\n"); + goto unreg_chg_det; + } + + ret = max77818_init_chg_interrupt_handling(max77818, chip); + if (ret) { + dev_err(dev, "Failed to init charger interrupt handling\n"); + goto unreg_fg_irq; + } + + /* A completion object is initiated in order for init to be run after + * completed initiation of this driver is kept waiting until all is done + * here. + * + * Also a worker is scheduled to run which will wait for this until + * performing final charger sync. */ + init_completion(&chip->init_completion); + INIT_WORK(&chip->initial_charger_sync_work, + max77818_do_initial_charger_sync_worker); + schedule_work(&chip->initial_charger_sync_work); + + /* Read the POR bit set when the device boots from a total power loss. + * If this bit is set, the device is given its initial config read from + * DT. + * + * If the POR is not set, but the module parameter 'config_update' is + * set to 'complete' when booting (by image update scripts typically), + * a complete re-config of the FG device is performed in order to apply + * battery model parameters in addition to custom params read + * from updated DT. + * + * If the POR is not set, but the module parameter 'config_update' is + * set to 'partial' when booting (by image update scripts typically), + * a partial re-config of the custom params is performed in order to apply + * only custom parameters read from updated DT. */ + regmap_read(chip->regmap, MAX17042_STATUS, &val); + if ((val & STATUS_POR_BIT) || max77818_do_complete_update(chip)) { + INIT_WORK(&chip->init_work, max77818_init_worker); + schedule_work(&chip->init_work); + } else if (max77818_do_partial_update(chip)) { + max77818_write_all_custom_params(chip); + max77818_do_init_completion(chip); + } else if (max77818_do_param_verification(chip)) { + max77818_write_mismatched_custom_params(chip); + max77818_do_init_completion(chip); + } + else { + dev_dbg(chip->dev, "No config change\n"); + max77818_do_init_completion(chip); + } + + return 0; + +unreg_fg_irq: + free_irq(chip->fg_irq, NULL); +unreg_chg_det: + usb_unregister_notifier(chip->usb_phy[0], + &chip->charger_detection_nb[0]); + + usb_unregister_notifier(chip->usb_phy[1], + &chip->charger_detection_nb[1]); +unreg_supply: + power_supply_unregister(chip->battery); + + return ret; +} + +static struct platform_driver max77818_fg_driver = { + .driver = { + .name = MAX77818_FUELGAUGE_NAME, + .owner = THIS_MODULE, + }, + .probe = max77818_probe, +}; +module_platform_driver(max77818_fg_driver); + +MODULE_DESCRIPTION("MAX77818 Fuel Gauge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max77818_battery_utils.c b/drivers/power/supply/max77818_battery_utils.c new file mode 100644 index 000000000000..17708f0e9b08 --- /dev/null +++ b/drivers/power/supply/max77818_battery_utils.c @@ -0,0 +1,74 @@ +/* + * Maxim MAX77818 Battery Utils + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * Author: Shawn Guo + * Author: Lars Ivar Miljeteig + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#include +#include + +/* Config register bits */ +#define CONFIG_FGCC_BIT (1 << 11) + +/* Parameter to be given from command line in order to tune the delay introduced after + * clearing the FGCC bit before forwarding requests to the charger driver */ +static int post_fgcc_change_delay_us = 100000; + +/* DO NOT CALL DIRECTLY !! + * + * ONLY TO _BE CALLED FROM MAX77818_DO_NON_FGCC_OP macro */ +int max77818_utils_set_fgcc_mode(struct max77818_dev *max77818_dev, + bool enabled, + bool *cur_mode) +{ + unsigned int read_data; + int ret; + + if (cur_mode) { + ret = regmap_read(max77818_dev->regmap_fg, + MAX17042_CONFIG, &read_data); + if (ret) { + dev_err(max77818_dev->dev, + "Failed to read CONFIG register\n"); + return ret; + } + *cur_mode = (read_data & CONFIG_FGCC_BIT); + } + + dev_dbg(max77818_dev->dev, "Turning %s FGCC\n", enabled ? "on" : "off"); + ret = regmap_update_bits(max77818_dev->regmap_fg, + MAX17042_CONFIG, + CONFIG_FGCC_BIT, + enabled ? CONFIG_FGCC_BIT : 0x0000); + + if (ret) { + dev_err(max77818_dev->dev, + "Failed to %s FGCC bit in CONFIG register\n", + enabled ? "set" : "clear"); + return ret; + } + + dev_dbg(max77818_dev->dev, + "Waiting %d us after FGCC mode change..\n", + post_fgcc_change_delay_us); + usleep_range(post_fgcc_change_delay_us, post_fgcc_change_delay_us + 100000); + + return 0; +} diff --git a/include/linux/mfd/max77818/max77818.h b/include/linux/mfd/max77818/max77818.h new file mode 100644 index 000000000000..67ecae8450e3 --- /dev/null +++ b/include/linux/mfd/max77818/max77818.h @@ -0,0 +1,108 @@ +/* + * MAX77818 Driver Core + * + * Copyright (C) 2014 Maxim Integrated + * TaiEup Kim + * + * Copyright and License statement to be determined with Customer. + * GNU Public License version 2 requires software code to be + * publically open source if the code is to be statically linked with + * the Linux kernel binary object. + */ + +#include + +#ifndef __MAX77818_MFD_H__ +#define __MAX77818_MFD_H__ + +#define MAX77818_REGULATOR_NAME "max77818-regulator" +#define MAX77818_CHARGER_NAME "max77818-charger" +#define MAX77818_FUELGAUGE_NAME "max77818-fuelgauge" + +#define REG_INTSRC 0x22 +#define REG_INTSRCMASK 0x23 +#define BIT_CHGR_INT BIT (0) +#define BIT_FG_INT BIT (1) +#define BIT_SYS_INT BIT (2) + +#define REG_SYSINTSRC 0x24 +#define REG_SYSINTMASK 0x26 +#define BIT_SYSUVLO_INT BIT (0) +#define BIT_SYSOVLO_INT BIT (1) +#define BIT_TSHDN_INT BIT (2) +#define BIT_TM_INT BIT (7) + +#define REG_CHARGER_INT 0xB0 +#define REG_CHARGER_INT_MASK 0xB1 + +#define BIT_CHG_BYP_I BIT (0) +#define BIT_CHG_BATP_I BIT (2) +#define BIT_CHG_BAT_I BIT (3) +#define BIT_CHG_CHG_I BIT (4) +#define BIT_CHG_WCIN_I BIT (5) +#define BIT_CHG_CHGIN_I BIT (6) +#define BIT_CHG_AICL_I BIT (7) + +/* Chip Interrupts */ +enum { + MAX77818_CHGR_INT = 0, + MAX77818_FG_INT, + MAX77818_SYS_INT, + + MAX77818_SYS_IRQ_START, + MAX77818_SYS_IRQ_UVLO = MAX77818_SYS_IRQ_START, + MAX77818_SYS_IRQ_OVLO, + MAX77818_SYS_IRQ_TSHDN, + MAX77818_SYS_IRQ_TM, + + MAX77818_CHG_IRQ_START, + MAX77818_CHG_IRQ_BYP_I = MAX77818_CHG_IRQ_START, + MAX77818_CHG_IRQ_BATP_I, + MAX77818_CHG_IRQ_BAT_I, + MAX77818_CHG_IRQ_CHG_I, + MAX77818_CHG_IRQ_WCIN_I, + MAX77818_CHG_IRQ_CHGIN_I, + MAX77818_CHG_IRQ_AICL_I, + + MAX77818_NUM_OF_INTS, +}; + +enum { + SYS_IRQ_UVLO = 0, + SYS_IRQ_OVLO, + SYS_IRQ_TSHDN, + SYS_IRQ_TM, + + CHG_IRQ_BYP_I = 0, + CHG_IRQ_BATP_I, + CHG_IRQ_BAT_I, + CHG_IRQ_CHG_I, + CHG_IRQ_WCIN_I, + CHG_IRQ_CHGIN_I, + CHG_IRQ_AICL_I, + + FG_IRQ_ALERT = 0, +}; + + +struct max77818_dev { + struct device *dev; + int irq; + + struct regmap_irq_chip_data *irqc_intsrc; + struct regmap_irq_chip_data *irqc_sys; + struct regmap_irq_chip_data *irqc_chg; + + struct i2c_client *pmic; + struct i2c_client *chg; + struct i2c_client *fg; + + struct regmap *regmap_pmic; + struct regmap *regmap_chg; + struct regmap *regmap_fg; + + struct mutex lock; +}; + +#endif /* !__MAX77818_MFD_H__ */ + diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index 4badd5322949..aa59fe0d417e 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -120,11 +120,32 @@ enum max17047_register { MAX17047_QRTbl30 = 0x42, }; +/* Registers specific to max77818 */ +enum max77818_register { + MAX77818_TTF = 0x20, + MAX77818_ConvgCfg = 0x49, + MAX77818_TALRT_Th2 = 0xB2, + MAX77818_TCURVE = 0xB9, + MAX77818_Config2 = 0xBB, + MAX77818_ChargeState0 = 0xD1, + MAX77818_ChargeState1 = 0xD2, + MAX77818_ChargeState2 = 0xD3, + MAX77818_ChargeState3 = 0xD4, + MAX77818_ChargeState4 = 0xD5, + MAX77818_ChargeState5 = 0xD6, + MAX77818_ChargeState6 = 0xD7, + MAX77818_ChargeState7 = 0xD8, + MAX77818_JEITA_Volt = 0xD9, + MAX77818_JEITA_Curr = 0xDA, + MAX77818_SmartChgCfg = 0xDB, +}; + enum max170xx_chip_type { MAXIM_DEVICE_TYPE_UNKNOWN = 0, MAXIM_DEVICE_TYPE_MAX17042, MAXIM_DEVICE_TYPE_MAX17047, MAXIM_DEVICE_TYPE_MAX17050, + MAXIM_DEVICE_TYPE_MAX77818, MAXIM_DEVICE_TYPE_NUM }; diff --git a/include/linux/power/max77818_battery_utils.h b/include/linux/power/max77818_battery_utils.h new file mode 100644 index 000000000000..3e7138e777f8 --- /dev/null +++ b/include/linux/power/max77818_battery_utils.h @@ -0,0 +1,168 @@ +/* + * Maxim MAX77818 Battery Utils + * + * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ + * + * Author: Steinar Bakkemo + * Author: Shawn Guo + * Author: Lars Ivar Miljeteig + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MAX17818_BATTERY_UTILS_H_ +#define __MAX17818_BATTERY_UTILS_H_ + +#include + +/* Exported function required for modules external to the max77818_battery + * module to be able to use the MAX77818_DO_NON_FGCC_OP macrov*/ +int max77818_utils_set_fgcc_mode(struct max77818_dev *max77818_dev, + bool enabled, + bool *cur_mode); + +/* Magic to enable optional macro param */ +#define VARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N +#define VARGS(...) VARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +#define CONCAT_(a, b) a##b +#define CONCAT(a, b) CONCAT_(a, b) + +/* Common macro to be user from any context having access to the common + * max77818 struct defined in the max77818 MDF driver */ +#define MAX77818_START_NON_FGCC_OP_3(max77818_dev, fgcc_restore_state, op_description) ( \ +{ \ + int ret = 0; \ + bool restore_state = 0; \ +\ + if (!max77818_dev) { \ + printk("%s: max77818_dev is NULL in MAX77818_DO_NON_FGCC_OP\n", __func__); \ + ret = -EINVAL; \ + } \ + else { \ + dev_dbg(max77818_dev->dev, op_description); \ +\ + dev_dbg(max77818_dev->dev, "Applying lock\n"); \ + mutex_lock(&max77818_dev->lock); \ +\ + dev_dbg(max77818_dev->dev, "Clearing FGCC mode\n"); \ + ret = max77818_utils_set_fgcc_mode(max77818_dev, \ + false, \ + &restore_state); \ + if (ret) { \ + dev_err(max77818_dev->dev, \ + "Failed to clear FGCC bit in CONFIG register\n"); \ + } \ + else { \ + fgcc_restore_state = restore_state; \ + } \ +\ + /* UNLOCKING IS DONE IN MAX77818_FINISH_NON_FGCC_OP */ \ + } \ + ret; \ +}) +#define MAX77818_START_NON_FGCC_OP_2(max77818_dev, fgcc_restore_state) MAX77818_START_NON_FGCC_OP_3(max77818_dev, fgcc_restore_state, "") +#define MAX77818_START_NON_FGCC_OP(...) ( CONCAT(MAX77818_START_NON_FGCC_OP_, VARGS(__VA_ARGS__))(__VA_ARGS__) ) + +/* Common macro to be user from any context having access to the common + * max77818 struct defined in the max77818 MDF driver */ +#define MAX77818_FINISH_NON_FGCC_OP_3(max77818_dev, fgcc_restore_state, op_description) ( \ +{ \ + int ret = 0; \ +\ + if (!max77818_dev) { \ + printk("%s: max77818_dev is NULL in MAX77818_DO_NON_FGCC_OP\n", __func__); \ + ret = -EINVAL; \ + } \ + else { \ + dev_dbg(max77818_dev->dev, op_description); \ +\ + if (fgcc_restore_state) { \ + dev_dbg(max77818_dev->dev, "Restoring FGCC mode\n"); \ +\ + ret = max77818_utils_set_fgcc_mode(max77818_dev, \ + true, \ + NULL); \ + if (ret) { \ + dev_err(max77818_dev->dev, \ + "Failed to set FGCC bit in CONFIG register\n"); \ + } \ + } \ + else { \ + dev_dbg(max77818_dev->dev, \ + "Leaving FGCC bit as it were (OFF)\n"); \ + } \ + dev_dbg(max77818_dev->dev, "Releasing lock\n"); \ + mutex_unlock(&max77818_dev->lock); \ + } \ + ret; \ +}) +#define MAX77818_FINISH_NON_FGCC_OP_2(max77818_dev, fgcc_restore_state) MAX77818_FINISH_NON_FGCC_OP_3(max77818_dev, fgcc_restore_state, "") +#define MAX77818_FINISH_NON_FGCC_OP(...) ( CONCAT(MAX77818_FINISH_NON_FGCC_OP_, VARGS(__VA_ARGS__))(__VA_ARGS__) ) + +/* Common macro to be used from any context having access to the common + * max77818 struct defined in the max77818 MFD driver */ +#define MAX77818_DO_NON_FGCC_OP_3(max77818_dev, op, op_description) ( \ +{ \ + int ret = 0; \ + bool restore_state = 0; \ +\ + if (!max77818_dev) { \ + printk("%s: max77818_dev is NULL in MAX77818_DO_NON_FGCC_OP\n", __func__); \ + ret = -EINVAL; \ + } \ + else { \ + dev_dbg(max77818_dev->dev, "Applying lock\n"); \ + mutex_lock(&max77818_dev->lock); \ +\ + dev_dbg(max77818_dev->dev, "Clearing FGCC mode\n"); \ +\ + ret = max77818_utils_set_fgcc_mode(max77818_dev, \ + false, \ + &restore_state); \ + if (ret) { \ + dev_err(max77818_dev->dev, \ + "Failed to clear FGCC bit in CONFIG register\n"); \ + } \ + else { \ + dev_dbg(max77818_dev->dev, op_description); \ + ret = op; \ +\ + if (ret) { \ + dev_err(max77818_dev->dev, \ + "Failed to read charger mode from charger driver\n"); \ + } \ + else { \ + if (restore_state) { \ + dev_dbg(max77818_dev->dev, "Restoring FGCC mode\n"); \ +\ + ret = max77818_utils_set_fgcc_mode( \ + max77818_dev, true, NULL); \ + if (ret) { \ + dev_err(max77818_dev->dev, \ + "Failed to set FGCC bit in CONFIG register\n"); \ + } \ + } \ + else { \ + dev_dbg(max77818_dev->dev, \ + "Leaving FGCC bit as it were (OFF)\n"); \ + } \ + } \ + } \ + dev_dbg(max77818_dev->dev, "Releasing lock\n"); \ + mutex_unlock(&max77818_dev->lock); \ + } \ + ret; \ +}) +#define MAX77818_DO_NON_FGCC_OP_2(max77818_dev, op) MAX77818_DO_NON_FGCC_OP_3(max77818_dev, op, "") +#define MAX77818_DO_NON_FGCC_OP(...) ( CONCAT(MAX77818_DO_NON_FGCC_OP_, VARGS(__VA_ARGS__))(__VA_ARGS__) ) + +#endif /* __MAX17818_BATTERY_UTILS_H_ */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index b7b3a05d6c90..9775ac43c499 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -94,6 +94,15 @@ enum { POWER_SUPPLY_MODE_ALL_OFF, }; +enum { + POWER_SUPPLY_STATUS_EX_NOT_CONNECTED = 0, + POWER_SUPPLY_STATUS_EX_POGO_CONNECTED, + POWER_SUPPLY_STATUS_EX_USB_C_CONNECTED, + POWER_SUPPLY_STATUS_EX_BOTH_CONNECTED, + POWER_SUPPLY_STATUS_EX_CHANGING, + POWER_SUPPLY_STATUS_EX_UNKNOWN, +}; + enum power_supply_property { /* Properties of type `int' */ POWER_SUPPLY_PROP_STATUS = 0, @@ -113,6 +122,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_VOLTAGE_BOOT, POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX2, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_BOOT, @@ -171,6 +181,11 @@ enum power_supply_property { /* MAX77818 specific mode of operation (OTG supply/charger) */ POWER_SUPPLY_PROP_CHARGER_MODE, + + /* MAX77818-charger specific property to get extended charger status indicating + * which of the two charger inputs are connected + */ + POWER_SUPPLY_PROP_STATUS_EX, }; enum power_supply_type { @@ -452,6 +467,7 @@ static inline bool power_supply_is_amp_property(enum power_supply_property psp) case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX2: case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_CURRENT_BOOT: