From 9b12899635017a576641d0de4202fe075624f849 Mon Sep 17 00:00:00 2001 From: shengfei Xu Date: Sun, 12 Jun 2022 03:17:34 +0000 Subject: [PATCH] power: supply: charger: add sc8551 charger pump driver Signed-off-by: shengfei Xu Change-Id: I9580e0af62cb652c0e90832455d025049edb374a --- .../bindings/power/supply/sc8551_charger.txt | 46 + drivers/power/supply/Kconfig | 7 + drivers/power/supply/Makefile | 1 + drivers/power/supply/sc8551_charger.c | 2224 +++++++++++++++++ 4 files changed, 2278 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/sc8551_charger.txt create mode 100644 drivers/power/supply/sc8551_charger.c diff --git a/Documentation/devicetree/bindings/power/supply/sc8551_charger.txt b/Documentation/devicetree/bindings/power/supply/sc8551_charger.txt new file mode 100644 index 000000000000..1d0157ce4c73 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/sc8551_charger.txt @@ -0,0 +1,46 @@ +Binding for sc8551 battery charger + +Required properties: +- compatible: "sc,sc8551-standalone" for sc8551 Charger Power Supply +- monitored-battery: phandle of battery characteristics devicetree node +- interrupts: Interrupt mapping for GPIO IRQ. Use in conjunction with + "interrupt-parent". If an interrupt is not provided the driver will switch + automatically to polling. + +- input-voltage-limit-microvolt: integer, input voltage level in uV, used to + decrease voltage level when the over current + of the input power source occurs. +- input-current-limit-microamp: integer, input current value in uA drained by the + charger from the power source.Default: 500000 uA (500mA) + +- sc,sc8551,bat-ovp-disable: enable the battery over voltage protection +- sc,sc8551,bat-ocp-disable: enable the battery over current protection +- sc,sc8551,bus-ocp-disable: enable the bus over voltage protection +- sc,sc8551,bat-ovp-threshold: Configures the battery over voltage protection voltage (in uV). +- sc,sc8551,bat-ocp-threshold: Configures the battery over current protection voltage (in uA). +- sc,sc8551,bus-ovp-threshold: Configures the bus over voltage protection voltage (in uV). +- sc,sc8551,bus-ocp-threshold: Configures the bus over current protection voltage (in uA). +- sc,sc8551,ac-ovp-threshold: Configures the AC voltage protection voltage (in uV). +- sc,sc8551,sense-resistor-mohm: integer, value of a resistor in series with the battery; + +Example: +c8551: sc8551@66 { + compatible = "sc,sc8551-standalone"; + reg = <0x66>; + interrupt-parent = <&gpio4>; + interrupts = ; + + sc,sc8551,bat-ovp-disable = <0>; + sc,sc8551,bat-ocp-disable = <0>; + sc,sc8551,bus-ocp-disable = <0>; + + sc,sc8551,bat-ovp-threshold = <4500>; + sc,sc8551,bat-ocp-threshold = <4500>; + + sc,sc8551,bus-ovp-threshold = <12000>; + sc,sc8551,bus-ocp-threshold = <4000>; + + sc,sc8551,ac-ovp-threshold = <17>; + sc,sc8551,sense-resistor-mohm = <2>; +}; + diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 79853e1d7640..f8936eec3ee5 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -535,6 +535,13 @@ config CHARGER_LT3651 Say Y to include support for the Analog Devices (Linear Technology) LT3651 battery charger which reports its status via GPIO lines. +config CHARGER_SC8551 + tristate "SC8551 battery charger pump driver" + depends on I2C + select REGMAP_I2C + help + Say Y to enable support for the SC8551 battery pump charger. + config CHARGER_MAX14577 tristate "Maxim MAX14577/77836 battery charger driver" depends on MFD_MAX14577 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index b38dd132f99f..7c6158473764 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_CHARGER_BQ25700) += bq25700_charger.o obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o +obj-$(CONFIG_CHARGER_SC8551) += sc8551_charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o diff --git a/drivers/power/supply/sc8551_charger.c b/drivers/power/supply/sc8551_charger.c new file mode 100644 index 000000000000..e3f7d578f061 --- /dev/null +++ b/drivers/power/supply/sc8551_charger.c @@ -0,0 +1,2224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Chrager driver for Sc8551 + * + * Copyright (c) 2022 Rockchip Electronics Co., Ltd. + * + * Author: Xu Shengfei + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register 00h */ +#define SC8551_REG_00 0x00 +#define SC8551_BAT_OVP_DIS_MASK 0x80 +#define SC8551_BAT_OVP_DIS_SHIFT 7 +#define SC8551_BAT_OVP_ENABLE 0 +#define SC8551_BAT_OVP_DISABLE 1 + +#define SC8551_BAT_OVP_MASK 0x3F +#define SC8551_BAT_OVP_SHIFT 0 +#define SC8551_BAT_OVP_BASE 3500 +#define SC8551_BAT_OVP_LSB 25 + +/* Register 02h */ +#define SC8551_REG_02 0x02 +#define SC8551_BAT_OCP_DIS_MASK 0x80 +#define SC8551_BAT_OCP_DIS_SHIFT 7 +#define SC8551_BAT_OCP_ENABLE 0 +#define SC8551_BAT_OCP_DISABLE 1 + +#define SC8551_BAT_OCP_MASK 0x7F +#define SC8551_BAT_OCP_SHIFT 0 +#define SC8551_BAT_OCP_BASE 2000 +#define SC8551_BAT_OCP_LSB 100 + +/* Register 05h */ +#define SC8551_REG_05 0x05 +#define SC8551_AC_OVP_STAT_MASK 0x80 +#define SC8551_AC_OVP_STAT_SHIFT 7 + +#define SC8551_AC_OVP_FLAG_MASK 0x40 +#define SC8551_AC_OVP_FLAG_SHIFT 6 + +#define SC8551_AC_OVP_MASK_MASK 0x20 +#define SC8551_AC_OVP_MASK_SHIFT 5 + +#define SC8551_VDROP_THRESHOLD_SET_MASK 0x10 +#define SC8551_VDROP_THRESHOLD_SET_SHIFT 4 +#define SC8551_VDROP_THRESHOLD_300MV 0 +#define SC8551_VDROP_THRESHOLD_400MV 1 + +#define SC8551_VDROP_DEGLITCH_SET_MASK 0x08 +#define SC8551_VDROP_DEGLITCH_SET_SHIFT 3 +#define SC8551_VDROP_DEGLITCH_8US 0 +#define SC8551_VDROP_DEGLITCH_5MS 1 + +#define SC8551_AC_OVP_MASK 0x07 +#define SC8551_AC_OVP_SHIFT 0 +#define SC8551_AC_OVP_BASE 11 +#define SC8551_AC_OVP_LSB 1 +#define SC8551_AC_OVP_6P5V 65 + +/* Register 06h */ +#define SC8551_REG_06 0x06 +#define SC8551_VBUS_PD_EN_MASK 0x80 +#define SC8551_VBUS_PD_EN_SHIFT 7 +#define SC8551_VBUS_PD_ENABLE 1 +#define SC8551_VBUS_PD_DISABLE 0 + +#define SC8551_BUS_OVP_MASK 0x7F +#define SC8551_BUS_OVP_SHIFT 0 +#define SC8551_BUS_OVP_BASE 6000 +#define SC8551_BUS_OVP_LSB 50 + +/* Register 08h */ +#define SC8551_REG_08 0x08 +#define SC8551_BUS_OCP_DIS_MASK 0x80 +#define SC8551_BUS_OCP_DIS_SHIFT 7 +#define SC8551_BUS_OCP_ENABLE 0 +#define SC8551_BUS_OCP_DISABLE 1 + +#define SC8551_IBUS_UCP_RISE_FLAG_MASK 0x40 +#define SC8551_IBUS_UCP_RISE_FLAG_SHIFT 6 + +#define SC8551_IBUS_UCP_RISE_MASK_MASK 0x20 +#define SC8551_IBUS_UCP_RISE_MASK_SHIFT 5 +#define SC8551_IBUS_UCP_RISE_MASK_ENABLE 1 +#define SC8551_IBUS_UCP_RISE_MASK_DISABLE 0 + +#define SC8551_IBUS_UCP_FALL_FLAG_MASK 0x10 +#define SC8551_IBUS_UCP_FALL_FLAG_SHIFT 4 + +#define SC8551_BUS_OCP_MASK 0x0F +#define SC8551_BUS_OCP_SHIFT 0 +#define SC8551_BUS_OCP_BASE 1000 +#define SC8551_BUS_OCP_LSB 250 + +/* Register 0Ah */ +#define SC8551_REG_0A 0x0A +#define SC8551_TSHUT_FLAG_MASK 0x80 +#define SC8551_TSHUT_FLAG_SHIFT 7 + +#define SC8551_TSHUT_STAT_MASK 0x40 +#define SC8551_TSHUT_STAT_SHIFT 6 + +#define SC8551_VBUS_ERRORLO_STAT_MASK 0x20 +#define SC8551_VBUS_ERRORLO_STAT_SHIFT 5 + +#define SC8551_VBUS_ERRORHI_STAT_MASK 0x10 +#define SC8551_VBUS_ERRORHI_STAT_SHIFT 4 + +#define SC8551_SS_TIMEOUT_FLAG_MASK 0x08 +#define SC8551_SS_TIMEOUT_FLAG_SHIFT 3 + +#define SC8551_CONV_SWITCHING_STAT_MASK 0x04 +#define SC8551_CONV_SWITCHING_STAT_SHIFT 2 + +#define SC8551_CONV_OCP_FLAG_MASK 0x02 +#define SC8551_CONV_OCP_FLAG_SHIFT 1 + +#define SC8551_PIN_DIAG_FALL_FLAG_MASK 0x01 +#define SC8551_PIN_DIAG_FALL_FLAG_SHIFT 0 + +/* Register 0Bh */ +#define SC8551_REG_0B 0x0B +#define SC8551_REG_RST_MASK 0x80 +#define SC8551_REG_RST_SHIFT 7 +#define SC8551_REG_RST_ENABLE 1 +#define SC8551_REG_RST_DISABLE 0 + +#define SC8551_FSW_SET_MASK 0x70 +#define SC8551_FSW_SET_SHIFT 4 +#define SC8551_FSW_SET_300KHZ 0 +#define SC8551_FSW_SET_350KHZ 1 +#define SC8551_FSW_SET_400KHZ 2 +#define SC8551_FSW_SET_450KHZ 3 +#define SC8551_FSW_SET_500KHZ 4 +#define SC8551_FSW_SET_550KHZ 5 +#define SC8551_FSW_SET_600KHZ 6 +#define SC8551_FSW_SET_750KHZ 7 + +#define SC8551_WD_TIMEOUT_FLAG_MASK 0x08 +#define SC8551_WD_TIMEOUT_SHIFT 3 + +#define SC8551_WATCHDOG_DIS_MASK 0x04 +#define SC8551_WATCHDOG_DIS_SHIFT 2 +#define SC8551_WATCHDOG_ENABLE 0 +#define SC8551_WATCHDOG_DISABLE 1 + +#define SC8551_WATCHDOG_MASK 0x03 +#define SC8551_WATCHDOG_SHIFT 0 +#define SC8551_WATCHDOG_0P5S 0 +#define SC8551_WATCHDOG_1S 1 +#define SC8551_WATCHDOG_5S 2 +#define SC8551_WATCHDOG_30S 3 + +/* Register 0Ch */ +#define SC8551_REG_0C 0x0C +#define SC8551_CHG_EN_MASK 0x80 +#define SC8551_CHG_EN_SHIFT 7 +#define SC8551_CHG_ENABLE 1 +#define SC8551_CHG_DISABLE 0 + +#define SC8551_MS_MASK 0x60 +#define SC8551_MS_SHIFT 5 +#define SC8551_MS_STANDALONE 0 +#define SC8551_MS_SLAVE 1 +#define SC8551_MS_MASTER 2 +#define SC8551_ROLE_STDALONE 0 +#define SC8551_ROLE_SLAVE 1 +#define SC8551_ROLE_MASTER 2 + +#define SC8551_FREQ_SHIFT_MASK 0x18 +#define SC8551_FREQ_SHIFT_SHIFT 3 +#define SC8551_FREQ_SHIFT_NORMINAL 0 +#define SC8551_FREQ_SHIFT_POSITIVE10 1 +#define SC8551_FREQ_SHIFT_NEGATIVE10 2 +#define SC8551_FREQ_SHIFT_SPREAD_SPECTRUM 3 + +#define SC8551_TSBUS_DIS_MASK 0x04 +#define SC8551_TSBUS_DIS_SHIFT 2 +#define SC8551_TSBUS_ENABLE 0 +#define SC8551_TSBUS_DISABLE 1 + +#define SC8551_TSBAT_DIS_MASK 0x02 +#define SC8551_TSBAT_DIS_SHIFT 1 +#define SC8551_TSBAT_ENABLE 0 +#define SC8551_TSBAT_DISABLE 1 + +/* Register 0Dh */ +#define SC8551_REG_0D 0x0D +#define SC8551_BAT_OVP_ALM_STAT_MASK 0x80 +#define SC8551_BAT_OVP_ALM_STAT_SHIFT 7 + +#define SC8551_BAT_OCP_ALM_STAT_MASK 0x40 +#define SC8551_BAT_OCP_ALM_STAT_SHIFT 6 + +#define SC8551_BUS_OVP_ALM_STAT_MASK 0x20 +#define SC8551_BUS_OVP_ALM_STAT_SHIFT 5 + +#define SC8551_BUS_OCP_ALM_STAT_MASK 0x10 +#define SC8551_BUS_OCP_ALM_STAT_SHIFT 4 + +#define SC8551_BAT_UCP_ALM_STAT_MASK 0x08 +#define SC8551_BAT_UCP_ALM_STAT_SHIFT 3 + +#define SC8551_ADAPTER_INSERT_STAT_MASK 0x04 +#define SC8551_ADAPTER_INSERT_STAT_SHIFT 2 + +#define SC8551_VBAT_INSERT_STAT_MASK 0x02 +#define SC8551_VBAT_INSERT_STAT_SHIFT 1 + +#define SC8551_ADC_DONE_STAT_MASK 0x01 +#define SC8551_ADC_DONE_STAT_SHIFT 0 +#define SC8551_ADC_DONE_STAT_COMPLETE 1 +#define SC8551_ADC_DONE_STAT_NOTCOMPLETE 0 + +/* Register 0Eh */ +#define SC8551_REG_0E 0x0E +#define SC8551_BAT_OVP_ALM_FLAG_MASK 0x80 +#define SC8551_BAT_OVP_ALM_FLAG_SHIFT 7 + +#define SC8551_BAT_OCP_ALM_FLAG_MASK 0x40 +#define SC8551_BAT_OCP_ALM_FLAG_SHIFT 6 + +#define SC8551_BUS_OVP_ALM_FLAG_MASK 0x20 +#define SC8551_BUS_OVP_ALM_FLAG_SHIFT 5 + +#define SC8551_BUS_OCP_ALM_FLAG_MASK 0x10 +#define SC8551_BUS_OCP_ALM_FLAG_SHIFT 4 + +#define SC8551_BAT_UCP_ALM_FLAG_MASK 0x08 +#define SC8551_BAT_UCP_ALM_FLAG_SHIFT 3 + +#define SC8551_ADAPTER_INSERT_FLAG_MASK 0x04 +#define SC8551_ADAPTER_INSERT_FLAG_SHIFT 2 + +#define SC8551_VBAT_INSERT_FLAG_MASK 0x02 +#define SC8551_VBAT_INSERT_FLAG_SHIFT 1 + +#define SC8551_ADC_DONE_FLAG_MASK 0x01 +#define SC8551_ADC_DONE_FLAG_SHIFT 0 +#define SC8551_ADC_DONE_FLAG_COMPLETE 1 +#define SC8551_ADC_DONE_FLAG_NOTCOMPLETE 0 + +/* Register 0Fh */ +#define SC8551_REG_0F 0x0F +#define SC8551_BAT_OVP_ALM_MASK_MASK 0x80 +#define SC8551_BAT_OVP_ALM_MASK_SHIFT 7 +#define SC8551_BAT_OVP_ALM_MASK_ENABLE 1 +#define SC8551_BAT_OVP_ALM_MASK_DISABLE 0 + +#define SC8551_BAT_OCP_ALM_MASK_MASK 0x40 +#define SC8551_BAT_OCP_ALM_MASK_SHIFT 6 +#define SC8551_BAT_OCP_ALM_MASK_ENABLE 1 +#define SC8551_BAT_OCP_ALM_MASK_DISABLE 0 + +#define SC8551_BUS_OVP_ALM_MASK_MASK 0x20 +#define SC8551_BUS_OVP_ALM_MASK_SHIFT 5 +#define SC8551_BUS_OVP_ALM_MASK_ENABLE 1 +#define SC8551_BUS_OVP_ALM_MASK_DISABLE 0 + +#define SC8551_BUS_OCP_ALM_MASK_MASK 0x10 +#define SC8551_BUS_OCP_ALM_MASK_SHIFT 4 +#define SC8551_BUS_OCP_ALM_MASK_ENABLE 1 +#define SC8551_BUS_OCP_ALM_MASK_DISABLE 0 + +#define SC8551_BAT_UCP_ALM_MASK_MASK 0x08 +#define SC8551_BAT_UCP_ALM_MASK_SHIFT 3 +#define SC8551_BAT_UCP_ALM_MASK_ENABLE 1 +#define SC8551_BAT_UCP_ALM_MASK_DISABLE 0 + +#define SC8551_ADAPTER_INSERT_MASK_MASK 0x04 +#define SC8551_ADAPTER_INSERT_MASK_SHIFT 2 +#define SC8551_ADAPTER_INSERT_MASK_ENABLE 1 +#define SC8551_ADAPTER_INSERT_MASK_DISABLE 0 + +#define SC8551_VBAT_INSERT_MASK_MASK 0x02 +#define SC8551_VBAT_INSERT_MASK_SHIFT 1 +#define SC8551_VBAT_INSERT_MASK_ENABLE 1 +#define SC8551_VBAT_INSERT_MASK_DISABLE 0 + +#define SC8551_ADC_DONE_MASK_MASK 0x01 +#define SC8551_ADC_DONE_MASK_SHIFT 0 +#define SC8551_ADC_DONE_MASK_ENABLE 1 +#define SC8551_ADC_DONE_MASK_DISABLE 0 + +/* Register 10h */ +#define SC8551_REG_10 0x10 +#define SC8551_BAT_OVP_FLT_STAT_MASK 0x80 +#define SC8551_BAT_OVP_FLT_STAT_SHIFT 7 + +#define SC8551_BAT_OCP_FLT_STAT_MASK 0x40 +#define SC8551_BAT_OCP_FLT_STAT_SHIFT 6 + +#define SC8551_BUS_OVP_FLT_STAT_MASK 0x20 +#define SC8551_BUS_OVP_FLT_STAT_SHIFT 5 + +#define SC8551_BUS_OCP_FLT_STAT_MASK 0x10 +#define SC8551_BUS_OCP_FLT_STAT_SHIFT 4 + +#define SC8551_TSBUS_TSBAT_ALM_STAT_MASK 0x08 +#define SC8551_TSBUS_TSBAT_ALM_STAT_SHIFT 3 + +#define SC8551_TSBAT_FLT_STAT_MASK 0x04 +#define SC8551_TSBAT_FLT_STAT_SHIFT 2 + +#define SC8551_TSBUS_FLT_STAT_MASK 0x02 +#define SC8551_TSBUS_FLT_STAT_SHIFT 1 + +#define SC8551_TDIE_ALM_STAT_MASK 0x01 +#define SC8551_TDIE_ALM_STAT_SHIFT 0 + +/* Register 11h */ +#define SC8551_REG_11 0x11 +#define SC8551_BAT_OVP_FLT_FLAG_MASK 0x80 +#define SC8551_BAT_OVP_FLT_FLAG_SHIFT 7 + +#define SC8551_BAT_OCP_FLT_FLAG_MASK 0x40 +#define SC8551_BAT_OCP_FLT_FLAG_SHIFT 6 + +#define SC8551_BUS_OVP_FLT_FLAG_MASK 0x20 +#define SC8551_BUS_OVP_FLT_FLAG_SHIFT 5 + +#define SC8551_BUS_OCP_FLT_FLAG_MASK 0x10 +#define SC8551_BUS_OCP_FLT_FLAG_SHIFT 4 + +#define SC8551_TSBUS_TSBAT_ALM_FLAG_MASK 0x08 +#define SC8551_TSBUS_TSBAT_ALM_FLAG_SHIFT 3 + +#define SC8551_TSBAT_FLT_FLAG_MASK 0x04 +#define SC8551_TSBAT_FLT_FLAG_SHIFT 2 + +#define SC8551_TSBUS_FLT_FLAG_MASK 0x02 +#define SC8551_TSBUS_FLT_FLAG_SHIFT 1 + +#define SC8551_TDIE_ALM_FLAG_MASK 0x01 +#define SC8551_TDIE_ALM_FLAG_SHIFT 0 + +/* Register 12h */ +#define SC8551_REG_12 0x12 +#define SC8551_BAT_OVP_FLT_MASK_MASK 0x80 +#define SC8551_BAT_OVP_FLT_MASK_SHIFT 7 +#define SC8551_BAT_OVP_FLT_MASK_ENABLE 1 +#define SC8551_BAT_OVP_FLT_MASK_DISABLE 0 + +#define SC8551_BAT_OCP_FLT_MASK_MASK 0x40 +#define SC8551_BAT_OCP_FLT_MASK_SHIFT 6 +#define SC8551_BAT_OCP_FLT_MASK_ENABLE 1 +#define SC8551_BAT_OCP_FLT_MASK_DISABLE 0 + +#define SC8551_BUS_OVP_FLT_MASK_MASK 0x20 +#define SC8551_BUS_OVP_FLT_MASK_SHIFT 5 +#define SC8551_BUS_OVP_FLT_MASK_ENABLE 1 +#define SC8551_BUS_OVP_FLT_MASK_DISABLE 0 + +#define SC8551_BUS_OCP_FLT_MASK_MASK 0x10 +#define SC8551_BUS_OCP_FLT_MASK_SHIFT 4 +#define SC8551_BUS_OCP_FLT_MASK_ENABLE 1 +#define SC8551_BUS_OCP_FLT_MASK_DISABLE 0 + +#define SC8551_TSBUS_TSBAT_ALM_MASK_MASK 0x08 +#define SC8551_TSBUS_TSBAT_ALM_MASK_SHIFT 3 +#define SC8551_TSBUS_TSBAT_ALM_MASK_ENABLE 1 +#define SC8551_TSBUS_TSBAT_ALM_MASK_DISABLE 0 + +#define SC8551_TSBAT_FLT_MASK_MASK 0x04 +#define SC8551_TSBAT_FLT_MASK_SHIFT 2 +#define SC8551_TSBAT_FLT_MASK_ENABLE 1 +#define SC8551_TSBAT_FLT_MASK_DISABLE 0 + +#define SC8551_TSBUS_FLT_MASK_MASK 0x02 +#define SC8551_TSBUS_FLT_MASK_SHIFT 1 +#define SC8551_TSBUS_FLT_MASK_ENABLE 1 +#define SC8551_TSBUS_FLT_MASK_DISABLE 0 + +#define SC8551_TDIE_ALM_MASK_MASK 0x01 +#define SC8551_TDIE_ALM_MASK_SHIFT 0 +#define SC8551_TDIE_ALM_MASK_ENABLE 1 +#define SC8551_TDIE_ALM_MASK_DISABLE 0 + +/* Register 13h */ +#define SC8551_REG_13 0x13 +#define SC8551_DEV_ID_MASK 0xFF +#define SC8551_DEV_ID_SHIFT 0 + +/* Register 14h */ +#define SC8551_REG_14 0x14 +#define SC8551_ADC_EN_MASK 0x80 +#define SC8551_ADC_EN_SHIFT 7 +#define SC8551_ADC_ENABLE 1 +#define SC8551_ADC_DISABLE 0 + +#define SC8551_ADC_RATE_MASK 0x40 +#define SC8551_ADC_RATE_SHIFT 6 +#define SC8551_ADC_RATE_CONTINOUS 0 +#define SC8551_ADC_RATE_ONESHOT 1 + +#define SC8551_IBUS_ADC_DIS_MASK 0x01 +#define SC8551_IBUS_ADC_DIS_SHIFT 0 +#define SC8551_IBUS_ADC_ENABLE 0 +#define SC8551_IBUS_ADC_DISABLE 1 + +/* Register 15h */ +#define SC8551_REG_15 0x15 +#define SC8551_VBUS_ADC_DIS_MASK 0x80 +#define SC8551_VBUS_ADC_DIS_SHIFT 7 +#define SC8551_VBUS_ADC_ENABLE 0 +#define SC8551_VBUS_ADC_DISABLE 1 + +#define SC8551_VAC_ADC_DIS_MASK 0x40 +#define SC8551_VAC_ADC_DIS_SHIFT 6 +#define SC8551_VAC_ADC_ENABLE 0 +#define SC8551_VAC_ADC_DISABLE 1 + +#define SC8551_VOUT_ADC_DIS_MASK 0x20 +#define SC8551_VOUT_ADC_DIS_SHIFT 5 +#define SC8551_VOUT_ADC_ENABLE 0 +#define SC8551_VOUT_ADC_DISABLE 1 + +#define SC8551_VBAT_ADC_DIS_MASK 0x10 +#define SC8551_VBAT_ADC_DIS_SHIFT 4 +#define SC8551_VBAT_ADC_ENABLE 0 +#define SC8551_VBAT_ADC_DISABLE 1 + +#define SC8551_IBAT_ADC_DIS_MASK 0x08 +#define SC8551_IBAT_ADC_DIS_SHIFT 3 +#define SC8551_IBAT_ADC_ENABLE 0 +#define SC8551_IBAT_ADC_DISABLE 1 + +#define SC8551_TSBUS_ADC_DIS_MASK 0x04 +#define SC8551_TSBUS_ADC_DIS_SHIFT 2 +#define SC8551_TSBUS_ADC_ENABLE 0 +#define SC8551_TSBUS_ADC_DISABLE 1 + +#define SC8551_TSBAT_ADC_DIS_MASK 0x02 +#define SC8551_TSBAT_ADC_DIS_SHIFT 1 +#define SC8551_TSBAT_ADC_ENABLE 0 +#define SC8551_TSBAT_ADC_DISABLE 1 + +#define SC8551_TDIE_ADC_DIS_MASK 0x01 +#define SC8551_TDIE_ADC_DIS_SHIFT 0 +#define SC8551_TDIE_ADC_ENABLE 0 +#define SC8551_TDIE_ADC_DISABLE 1 + +/* Register 16h */ +#define SC8551_REG_16 0x16 +#define SC8551_IBUS_POL_H_MASK 0x0F + +/* Register 17h */ +#define SC8551_REG_17 0x17 +#define SC8551_IBUS_POL_L_MASK 0xFF + +/* Register 18h */ +#define SC8551_REG_18 0x18 +#define SC8551_VBUS_POL_H_MASK 0x0F + +/* Register 19h */ +#define SC8551_REG_19 0x19 +#define SC8551_VBUS_POL_L_MASK 0xFF + +/* Register 1Ah */ +#define SC8551_REG_1A 0x1A +#define SC8551_VAC_POL_H_MASK 0x0F + +/* Register 1Bh */ +#define SC8551_REG_1B 0x1B +#define SC8551_VAC_POL_L_MASK 0xFF + +/* Register 1Ch */ +#define SC8551_REG_1C 0x1C +#define SC8551_VOUT_POL_H_MASK 0x0F + +/* Register 1Dh */ +#define SC8551_REG_1D 0x1D +#define SC8551_VOUT_POL_L_MASK 0x0F + +/* Register 1Eh */ +#define SC8551_REG_1E 0x1E +#define SC8551_VBAT_POL_H_MASK 0x0F + +/* Register 1Fh */ +#define SC8551_REG_1F 0x1F +#define SC8551_VBAT_POL_L_MASK 0xFF + +/* Register 20h */ +#define SC8551_REG_20 0x20 +#define SC8551_IBAT_POL_H_MASK 0x0F + +/* Register 21h */ +#define SC8551_REG_21 0x21 +#define SC8551_IBAT_POL_L_MASK 0xFF + +/* Register 26h */ +#define SC8551_REG_26 0x26 +#define SC8551_TDIE_POL_H_MASK 0x01 + +/* Register 2Bh */ +#define SC8551_REG_2B 0x2B +#define SC8551_SS_TIMEOUT_SET_MASK 0xE0 +#define SC8551_SS_TIMEOUT_SET_SHIFT 5 +#define SC8551_SS_TIMEOUT_DISABLE 0 +#define SC8551_SS_TIMEOUT_12P5MS 1 +#define SC8551_SS_TIMEOUT_25MS 2 +#define SC8551_SS_TIMEOUT_50MS 3 +#define SC8551_SS_TIMEOUT_100MS 4 +#define SC8551_SS_TIMEOUT_400MS 5 +#define SC8551_SS_TIMEOUT_1500MS 6 +#define SC8551_SS_TIMEOUT_100000MS 7 + +#define SC8551_EN_REGULATION_MASK 0x10 +#define SC8551_EN_REGULATION_SHIFT 4 +#define SC8551_EN_REGULATION_ENABLE 1 +#define SC8551_EN_REGULATION_DISABLE 0 + +#define SC8551_VOUT_OVP_DIS_MASK 0x08 +#define SC8551_VOUT_OVP_DIS_SHIFT 3 +#define SC8551_VOUT_OVP_ENABLE 1 +#define SC8551_VOUT_OVP_DISABLE 0 + +#define SC8551_IBUS_UCP_RISE_TH_MASK 0x04 +#define SC8551_IBUS_UCP_RISE_TH_SHIFT 2 +#define SC8551_IBUS_UCP_RISE_150MA 0 +#define SC8551_IBUS_UCP_RISE_250MA 1 + +#define SC8551_SET_IBAT_SNS_RES_MASK 0x02 +#define SC8551_SET_IBAT_SNS_RES_SHIFT 1 +#define SC8551_SET_IBAT_SNS_RES_2MHM 0 +#define SC8551_SET_IBAT_SNS_RES_5MHM 1 + +#define SC8551_VAC_PD_EN_MASK 0x01 +#define SC8551_VAC_PD_EN_SHIFT 0 +#define SC8551_VAC_PD_ENABLE 1 +#define SC8551_VAC_PD_DISABLE 0 + +/* Register 2Ch */ +#define SC8551_REG_2C 0x2C +#define SC8551_IBAT_REG_MASK 0xC0 +#define SC8551_IBAT_REG_SHIFT 6 +#define SC8551_IBAT_REG_200MA 0 +#define SC8551_IBAT_REG_300MA 1 +#define SC8551_IBAT_REG_400MA 2 +#define SC8551_IBAT_REG_500MA 3 +#define SC8551_VBAT_REG_MASK 0x30 +#define SC8551_VBAT_REG_SHIFT 4 +#define SC8551_VBAT_REG_50MV 0 +#define SC8551_VBAT_REG_100MV 1 +#define SC8551_VBAT_REG_150MV 2 +#define SC8551_VBAT_REG_200MV 3 + +#define SC8551_VBAT_REG_ACTIVE_STAT_MASK 0x08 +#define SC8551_IBAT_REG_ACTIVE_STAT_MASK 0x04 +#define SC8551_VDROP_OVP_ACTIVE_STAT_MASK 0x02 +#define SC8551_VOUT_OVP_ACTIVE_STAT_MASK 0x01 + +#define SC8551_REG_2D 0x2D +#define SC8551_VBAT_REG_ACTIVE_FLAG_MASK 0x80 +#define SC8551_IBAT_REG_ACTIVE_FLAG_MASK 0x40 +#define SC8551_VDROP_OVP_FLAG_MASK 0x20 +#define SC8551_VOUT_OVP_FLAG_MASK 0x10 +#define SC8551_VBAT_REG_ACTIVE_MASK_MASK 0x08 +#define SC8551_IBAT_REG_ACTIVE_MASK_MASK 0x04 +#define SC8551_VDROP_OVP_MASK_MASK 0x02 +#define SC8551_VOUT_OVP_MASK_MASK 0x01 + +#define SC8551_REG_2E 0x2E +#define SC8551_IBUS_LOW_DG_MASK 0x08 +#define SC8551_IBUS_LOW_DG_SHIFT 3 +#define SC8551_IBUS_LOW_DG_10US 0 +#define SC8551_IBUS_LOW_DG_5MS 1 + +#define SC8551_REG_2F 0x2F +#define SC8551_PMID2OUT_UVP_FLAG_MASK 0x08 +#define SC8551_PMID2OUT_OVP_FLAG_MASK 0x04 +#define SC8551_PMID2OUT_UVP_STAT_MASK 0x02 +#define SC8551_PMID2OUT_OVP_STAT_MASK 0x01 + +#define SC8551_REG_30 0x30 +#define SC8551_IBUS_REG_EN_MASK 0x80 +#define SC8551_IBUS_REG_EN_SHIFT 7 +#define SC8551_IBUS_REG_ENABLE 1 +#define SC8551_IBUS_REG_DISABLE 0 +#define SC8551_IBUS_REG_ACTIVE_STAT_MASK 0x40 +#define SC8551_IBUS_REG_ACTIVE_FLAG_MASK 0x20 +#define SC8551_IBUS_REG_ACTIVE_MASK_MASK 0x10 +#define SC8551_IBUS_REG_ACTIVE_MASK_SHIFT 4 +#define SC8551_IBUS_REG_ACTIVE_NOT_MASK 0 +#define SC8551_IBUS_REG_ACTIVE_MASK 1 +#define SC8551_IBUS_REG_MASK 0x0F +#define SC8551_IBUS_REG_SHIFT 0 +#define SC8551_IBUS_REG_BASE 1000 +#define SC8551_IBUS_REG_LSB 250 + +#define SC8551_REG_31 0x31 +#define SC8551_CHARGE_MODE_MASK 0x01 +#define SC8551_CHARGE_MODE_SHIFT 0 +#define SC8551_CHARGE_MODE_2_1 0 +#define SC8551_CHARGE_MODE_1_1 1 + +#define SC8551_REG_35 0x35 +#define SC8551_VBUS_IN_RANGE_MASK 0x01 +#define SC8551_VBUS_IN_RANGE_SHIFT 6 +#define SC8551_VBUS_IN_RANGE_ENABLE 0 +#define SC8551_VBUS_IN_RANGE_DISABLE 1 + + +#define SC8551_REG_36 0x36 +#define SC8551_OVPGATE_MASK 0x01 +#define SC8551_OVPGATE_SHIFT 3 +#define SC8551_OVPGATE_ENABLE 0 +#define SC8551_OVPGATE_DISABLE 1 + +#define VBUS_INSERT BIT(2) +#define VBAT_INSERT BIT(1) +#define ADC_DONE BIT(0) + +#define BAT_OVP_FAULT BIT(7) +#define BAT_OCP_FAULT BIT(6) +#define BUS_OVP_FAULT BIT(5) +#define BUS_OCP_FAULT BIT(4) + +/*below used for comm with other module*/ +#define BAT_OVP_FAULT_SHIFT 0 +#define BAT_OCP_FAULT_SHIFT 1 +#define BUS_OVP_FAULT_SHIFT 2 +#define BUS_OCP_FAULT_SHIFT 3 +#define BAT_THERM_FAULT_SHIFT 4 +#define BUS_THERM_FAULT_SHIFT 5 +#define DIE_THERM_FAULT_SHIFT 6 + +#define BAT_OVP_FAULT_MASK (1 << BAT_OVP_FAULT_SHIFT) +#define BAT_OCP_FAULT_MASK (1 << BAT_OCP_FAULT_SHIFT) +#define BUS_OVP_FAULT_MASK (1 << BUS_OVP_FAULT_SHIFT) +#define BUS_OCP_FAULT_MASK (1 << BUS_OCP_FAULT_SHIFT) +#define BAT_THERM_FAULT_MASK (1 << BAT_THERM_FAULT_SHIFT) +#define BUS_THERM_FAULT_MASK (1 << BUS_THERM_FAULT_SHIFT) +#define DIE_THERM_FAULT_MASK (1 << DIE_THERM_FAULT_SHIFT) + +#define BAT_OVP_ALARM_SHIFT 0 +#define BAT_OCP_ALARM_SHIFT 1 +#define BUS_OVP_ALARM_SHIFT 2 +#define BUS_OCP_ALARM_SHIFT 3 +#define BAT_THERM_ALARM_SHIFT 4 +#define BUS_THERM_ALARM_SHIFT 5 +#define DIE_THERM_ALARM_SHIFT 6 +#define BAT_UCP_ALARM_SHIFT 7 +#define BAT_OVP_ALARM_MASK (1 << BAT_OVP_ALARM_SHIFT) +#define BAT_OCP_ALARM_MASK (1 << BAT_OCP_ALARM_SHIFT) +#define BUS_OVP_ALARM_MASK (1 << BUS_OVP_ALARM_SHIFT) +#define BUS_OCP_ALARM_MASK (1 << BUS_OCP_ALARM_SHIFT) +#define BAT_THERM_ALARM_MASK (1 << BAT_THERM_ALARM_SHIFT) +#define BUS_THERM_ALARM_MASK (1 << BUS_THERM_ALARM_SHIFT) +#define DIE_THERM_ALARM_MASK (1 << DIE_THERM_ALARM_SHIFT) +#define BAT_UCP_ALARM_MASK (1 << BAT_UCP_ALARM_SHIFT) + +#define VBAT_REG_STATUS_SHIFT 0 +#define IBAT_REG_STATUS_SHIFT 1 + +#define VBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT) +#define IBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT) + +#define SC8551_DEBUG_BUF_LEN 30 + +enum { + ADC_IBUS, + ADC_VBUS, + ADC_VAC, + ADC_VOUT, + ADC_VBAT, + ADC_IBAT, + ADC_TBUS, + ADC_TBAT, + ADC_TDIE, + ADC_MAX_NUM, +}; + +struct sc8551_cfg { + bool bat_ovp_disable; + bool bat_ocp_disable; + + int bat_ovp_th; + int bat_ovp_alm_th; + int bat_ocp_th; + + bool bus_ocp_disable; + + int bus_ovp_th; + int bus_ocp_th; + + int ac_ovp_th; + + bool bat_therm_disable; + bool bus_therm_disable; + bool die_therm_disable; + + int sense_r_mohm; +}; + +struct sc8551 { + struct device *dev; + struct i2c_client *client; + + int part_no; + int revision; + + int mode; + + struct mutex data_lock; + struct mutex i2c_rw_lock; + + int irq; + + bool batt_present; + bool vbus_present; + + bool usb_present; + bool charge_enabled; + + int vbus_error; + + /* ADC reading */ + int vbat_volt; + int vbus_volt; + int vout_volt; + int vac_volt; + + int ibat_curr; + int ibus_curr; + + int die_temp; + + /* alarm/fault status */ + bool bat_ovp_fault; + bool bat_ocp_fault; + bool bus_ovp_fault; + bool bus_ocp_fault; + + bool vbat_reg; + bool ibat_reg; + + int prev_alarm; + int prev_fault; + + struct sc8551_cfg *cfg; + + struct power_supply_desc psy_desc; + struct power_supply_config psy_cfg; + struct power_supply *fc2_psy; +}; + +static int __sc8551_read_byte(struct sc8551 *sc, u8 reg, u8 *data) +{ + s32 ret; + + ret = i2c_smbus_read_byte_data(sc->client, reg); + if (ret < 0) { + dev_err(sc->dev, "i2c read fail: can't read from reg 0x%02X\n", reg); + return ret; + } + + *data = (u8) ret; + + return 0; +} + +static int __sc8551_write_byte(struct sc8551 *sc, int reg, u8 val) +{ + s32 ret; + + ret = i2c_smbus_write_byte_data(sc->client, reg, val); + if (ret < 0) { + dev_err(sc->dev, "i2c write fail: can't write 0x%02X to reg 0x%02X: %d\n", + val, reg, ret); + return ret; + } + return 0; +} + +static int sc8551_read_byte(struct sc8551 *sc, u8 reg, u8 *data) +{ + int ret; + + mutex_lock(&sc->i2c_rw_lock); + ret = __sc8551_read_byte(sc, reg, data); + mutex_unlock(&sc->i2c_rw_lock); + + return ret; +} + +static int sc8551_write_byte(struct sc8551 *sc, u8 reg, u8 data) +{ + int ret; + + mutex_lock(&sc->i2c_rw_lock); + ret = __sc8551_write_byte(sc, reg, data); + mutex_unlock(&sc->i2c_rw_lock); + + return ret; +} + +static int sc8551_update_bits(struct sc8551 *sc, + u8 reg, + u8 mask, + u8 data) +{ + int ret; + u8 tmp; + + mutex_lock(&sc->i2c_rw_lock); + ret = __sc8551_read_byte(sc, reg, &tmp); + if (ret) { + dev_err(sc->dev, "Failed: reg=%02X, ret=%d\n", reg, ret); + goto out; + } + + tmp &= ~mask; + tmp |= data & mask; + + ret = __sc8551_write_byte(sc, reg, tmp); + if (ret) + dev_err(sc->dev, "Failed: reg=%02X, ret=%d\n", reg, ret); + +out: + mutex_unlock(&sc->i2c_rw_lock); + return ret; +} + +static int sc8551_enable_charge(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_CHG_ENABLE; + else + val = SC8551_CHG_DISABLE; + + val <<= SC8551_CHG_EN_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_0C, + SC8551_CHG_EN_MASK, + val); + + return ret; +} + +static int sc8551_check_charge_enabled(struct sc8551 *sc, bool *enabled) +{ + int ret; + u8 val; + + ret = sc8551_read_byte(sc, SC8551_REG_0C, &val); + + if (!ret) + *enabled = !!(val & SC8551_CHG_EN_MASK); + return ret; +} + +static int sc8551_enable_wdt(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_WATCHDOG_ENABLE; + else + val = SC8551_WATCHDOG_DISABLE; + + val <<= SC8551_WATCHDOG_DIS_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_0B, + SC8551_WATCHDOG_DIS_MASK, + val); + return ret; +} + +static int sc8551_set_wdt(struct sc8551 *sc, int ms) +{ + int ret; + u8 val; + + if (ms == 500) + val = SC8551_WATCHDOG_0P5S; + else if (ms == 1000) + val = SC8551_WATCHDOG_1S; + else if (ms == 5000) + val = SC8551_WATCHDOG_5S; + else if (ms == 30000) + val = SC8551_WATCHDOG_30S; + else + val = SC8551_WATCHDOG_30S; + + val <<= SC8551_WATCHDOG_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_0B, + SC8551_WATCHDOG_MASK, + val); + return ret; +} + +static int sc8551_enable_batovp(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_BAT_OVP_ENABLE; + else + val = SC8551_BAT_OVP_DISABLE; + + val <<= SC8551_BAT_OVP_DIS_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_00, + SC8551_BAT_OVP_DIS_MASK, + val); + return ret; +} + +static int sc8551_set_batovp_th(struct sc8551 *sc, int threshold) +{ + int ret; + u8 val; + + if (threshold < SC8551_BAT_OVP_BASE) + threshold = SC8551_BAT_OVP_BASE; + + val = (threshold - SC8551_BAT_OVP_BASE) / SC8551_BAT_OVP_LSB; + + val <<= SC8551_BAT_OVP_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_00, + SC8551_BAT_OVP_MASK, + val); + return ret; +} + +static int sc8551_enable_batocp(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_BAT_OCP_ENABLE; + else + val = SC8551_BAT_OCP_DISABLE; + + val <<= SC8551_BAT_OCP_DIS_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_02, + SC8551_BAT_OCP_DIS_MASK, + val); + return ret; +} + +static int sc8551_set_batocp_th(struct sc8551 *sc, int threshold) +{ + int ret; + u8 val; + + if (threshold < SC8551_BAT_OCP_BASE) + threshold = SC8551_BAT_OCP_BASE; + + val = (threshold - SC8551_BAT_OCP_BASE) / SC8551_BAT_OCP_LSB; + + val <<= SC8551_BAT_OCP_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_02, + SC8551_BAT_OCP_MASK, + val); + return ret; +} + +static int sc8551_set_busovp_th(struct sc8551 *sc, int threshold) +{ + int ret; + u8 val; + + if (threshold < SC8551_BUS_OVP_BASE) + threshold = SC8551_BUS_OVP_BASE; + + val = (threshold - SC8551_BUS_OVP_BASE) / SC8551_BUS_OVP_LSB; + + val <<= SC8551_BUS_OVP_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_06, + SC8551_BUS_OVP_MASK, + val); + return ret; +} + +static int sc8551_enable_busocp(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_BUS_OCP_ENABLE; + else + val = SC8551_BUS_OCP_DISABLE; + + val <<= SC8551_BUS_OCP_DIS_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_08, + SC8551_BUS_OCP_DIS_MASK, + val); + return ret; +} + +static int sc8551_set_busocp_th(struct sc8551 *sc, int threshold) +{ + int ret; + u8 val; + + if (threshold < SC8551_BUS_OCP_BASE) + threshold = SC8551_BUS_OCP_BASE; + + val = (threshold - SC8551_BUS_OCP_BASE) / SC8551_BUS_OCP_LSB; + + val <<= SC8551_BUS_OCP_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_08, + SC8551_BUS_OCP_MASK, + val); + return ret; +} + +static int sc8551_set_acovp_th(struct sc8551 *sc, int threshold) +{ + int ret; + u8 val; + + if (threshold < SC8551_AC_OVP_BASE) + threshold = SC8551_AC_OVP_BASE; + + if (threshold == SC8551_AC_OVP_6P5V) + val = 0x07; + else + val = (threshold - SC8551_AC_OVP_BASE) / SC8551_AC_OVP_LSB; + + val <<= SC8551_AC_OVP_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_05, + SC8551_AC_OVP_MASK, val); + + return ret; + +} + +static int sc8551_set_vdrop_th(struct sc8551 *sc, int threshold) +{ + int ret; + u8 val; + + if (threshold == 300) + val = SC8551_VDROP_THRESHOLD_300MV; + else + val = SC8551_VDROP_THRESHOLD_400MV; + + val <<= SC8551_VDROP_THRESHOLD_SET_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_05, + SC8551_VDROP_THRESHOLD_SET_MASK, + val); + + return ret; +} + +static int sc8551_set_vdrop_deglitch(struct sc8551 *sc, int us) +{ + int ret; + u8 val; + + if (us == 8) + val = SC8551_VDROP_DEGLITCH_8US; + else + val = SC8551_VDROP_DEGLITCH_5MS; + + val <<= SC8551_VDROP_DEGLITCH_SET_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_05, + SC8551_VDROP_DEGLITCH_SET_MASK, + val); + return ret; +} + +static int sc8551_enable_bat_therm(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_TSBAT_ENABLE; + else + val = SC8551_TSBAT_DISABLE; + + val <<= SC8551_TSBAT_DIS_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_0C, + SC8551_TSBAT_DIS_MASK, val); + return ret; +} + +static int sc8551_enable_bus_therm(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_TSBUS_ENABLE; + else + val = SC8551_TSBUS_DISABLE; + + val <<= SC8551_TSBUS_DIS_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_0C, + SC8551_TSBUS_DIS_MASK, val); + return ret; +} + +static int sc8551_enable_adc(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_ADC_ENABLE; + else + val = SC8551_ADC_DISABLE; + + val <<= SC8551_ADC_EN_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_14, + SC8551_ADC_EN_MASK, val); + return ret; +} + +static int sc8551_set_adc_scanrate(struct sc8551 *sc, bool oneshot) +{ + int ret; + u8 val; + + if (oneshot) + val = SC8551_ADC_RATE_ONESHOT; + else + val = SC8551_ADC_RATE_CONTINOUS; + + val <<= SC8551_ADC_RATE_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_14, + SC8551_ADC_RATE_MASK, val); + return ret; +} + +static int sc8551_get_adc_data(struct sc8551 *sc, int channel, int *result) +{ + u8 val_l = 0, val_h = 0; + u16 val = 0; + int ret = 0; + + if (channel >= ADC_MAX_NUM) + return 0; + + ret = sc8551_read_byte(sc, SC8551_REG_16 + (channel << 1), &val_h); + if (ret < 0) + return ret; + + ret = sc8551_read_byte(sc, SC8551_REG_16 + (channel << 1) + 1, &val_l); + if (ret < 0) + return ret; + + val = (val_h << 8) | val_l; + + if (channel == ADC_IBUS) + val = val * 15625 / 10000; + else if (channel == ADC_VBUS) + val = val * 375 / 100; + else if (channel == ADC_VAC) + val = val * 5; + else if (channel == ADC_VOUT) + val = val * 125 / 100; + else if (channel == ADC_VBAT) + val = val * 125 / 100; + else if (channel == ADC_IBAT) + val = val * 3125 / 1000; + else if (channel == ADC_TDIE) + val = val * 5 / 10; + + *result = val; + + return ret; +} + +static int sc8551_set_adc_scan(struct sc8551 *sc, int channel, bool enable) +{ + int ret; + u8 reg; + u8 mask; + u8 shift; + u8 val; + + if (channel > ADC_MAX_NUM) + return -EINVAL; + + if (channel == ADC_IBUS) { + reg = SC8551_REG_14; + shift = SC8551_IBUS_ADC_DIS_SHIFT; + mask = SC8551_IBUS_ADC_DIS_MASK; + } else { + reg = SC8551_REG_15; + shift = 8 - channel; + mask = 1 << shift; + } + + if (enable) + val = 0 << shift; + else + val = 1 << shift; + + ret = sc8551_update_bits(sc, reg, mask, val); + + return ret; +} + +static int sc8551_set_alarm_int_mask(struct sc8551 *sc, u8 mask) +{ + int ret; + u8 val; + + ret = sc8551_read_byte(sc, SC8551_REG_0F, &val); + if (ret) + return ret; + + val |= mask; + + ret = sc8551_write_byte(sc, SC8551_REG_0F, val); + + return ret; +} + +static int sc8551_set_sense_resistor(struct sc8551 *sc, int r_mohm) +{ + int ret; + u8 val; + + if (r_mohm == 2) + val = SC8551_SET_IBAT_SNS_RES_2MHM; + else if (r_mohm == 5) + val = SC8551_SET_IBAT_SNS_RES_5MHM; + else + return -EINVAL; + + val <<= SC8551_SET_IBAT_SNS_RES_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_2B, + SC8551_SET_IBAT_SNS_RES_MASK, + val); + return ret; +} + +static int sc8551_enable_regulation(struct sc8551 *sc, bool enable) +{ + int ret; + u8 val; + + if (enable) + val = SC8551_EN_REGULATION_ENABLE; + else + val = SC8551_EN_REGULATION_DISABLE; + + val <<= SC8551_EN_REGULATION_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_2B, + SC8551_EN_REGULATION_MASK, + val); + + return ret; + +} + +static int sc8551_set_ss_timeout(struct sc8551 *sc, int timeout) +{ + int ret; + u8 val; + + switch (timeout) { + case 0: + val = SC8551_SS_TIMEOUT_DISABLE; + break; + case 12: + val = SC8551_SS_TIMEOUT_12P5MS; + break; + case 25: + val = SC8551_SS_TIMEOUT_25MS; + break; + case 50: + val = SC8551_SS_TIMEOUT_50MS; + break; + case 100: + val = SC8551_SS_TIMEOUT_100MS; + break; + case 400: + val = SC8551_SS_TIMEOUT_400MS; + break; + case 1500: + val = SC8551_SS_TIMEOUT_1500MS; + break; + case 100000: + val = SC8551_SS_TIMEOUT_100000MS; + break; + default: + val = SC8551_SS_TIMEOUT_DISABLE; + break; + } + + val <<= SC8551_SS_TIMEOUT_SET_SHIFT; + + ret = sc8551_update_bits(sc, + SC8551_REG_2B, + SC8551_SS_TIMEOUT_SET_MASK, + val); + + return ret; +} + +static int sc8551_set_ibat_reg_th(struct sc8551 *sc, int th_ma) +{ + int ret; + u8 val; + + if (th_ma == 200) + val = SC8551_IBAT_REG_200MA; + else if (th_ma == 300) + val = SC8551_IBAT_REG_300MA; + else if (th_ma == 400) + val = SC8551_IBAT_REG_400MA; + else if (th_ma == 500) + val = SC8551_IBAT_REG_500MA; + else + val = SC8551_IBAT_REG_500MA; + + val <<= SC8551_IBAT_REG_SHIFT; + ret = sc8551_update_bits(sc, + SC8551_REG_2C, + SC8551_IBAT_REG_MASK, + val); + + return ret; +} + +static int sc8551_set_vbat_reg_th(struct sc8551 *sc, int th_mv) +{ + int ret; + u8 val; + + if (th_mv == 50) + val = SC8551_VBAT_REG_50MV; + else if (th_mv == 100) + val = SC8551_VBAT_REG_100MV; + else if (th_mv == 150) + val = SC8551_VBAT_REG_150MV; + else + val = SC8551_VBAT_REG_200MV; + + val <<= SC8551_VBAT_REG_SHIFT; + + ret = sc8551_update_bits(sc, SC8551_REG_2C, + SC8551_VBAT_REG_MASK, + val); + + return ret; +} + +static int sc8551_get_work_mode(struct sc8551 *sc, int *mode) +{ + int ret; + u8 val; + + ret = sc8551_read_byte(sc, SC8551_REG_0C, &val); + + if (ret) { + dev_err(sc->dev, "Failed to read operation mode register\n"); + return ret; + } + + val = (val & SC8551_MS_MASK) >> SC8551_MS_SHIFT; + if (val == SC8551_MS_MASTER) + *mode = SC8551_ROLE_MASTER; + else if (val == SC8551_MS_SLAVE) + *mode = SC8551_ROLE_SLAVE; + else + *mode = SC8551_ROLE_STDALONE; + + pr_debug("work mode:%s\n", *mode == SC8551_ROLE_STDALONE ? "Standalone" : + (*mode == SC8551_ROLE_SLAVE ? "Slave" : "Master")); + return ret; +} + +static int sc8551_check_vbus_error_status(struct sc8551 *sc) +{ + int ret; + u8 data; + + ret = sc8551_read_byte(sc, SC8551_REG_0A, &data); + if (!ret) + sc->vbus_error = data; + + return ret; +} + +static void sc8551_check_alarm_status(struct sc8551 *sc) +{ + u8 flag = 0; + u8 stat = 0; + int ret; + + mutex_lock(&sc->data_lock); + + ret = sc8551_read_byte(sc, SC8551_REG_08, &flag); + if (!ret && (flag & SC8551_IBUS_UCP_FALL_FLAG_MASK)) + pr_debug("UCP_FLAG =0x%02X\n", + !!(flag & SC8551_IBUS_UCP_FALL_FLAG_MASK)); + + ret = sc8551_read_byte(sc, SC8551_REG_2D, &flag); + if (!ret && (flag & SC8551_VDROP_OVP_FLAG_MASK)) + pr_debug("VDROP_OVP_FLAG =0x%02X\n", + !!(flag & SC8551_VDROP_OVP_FLAG_MASK)); + + /*read to clear alarm flag*/ + ret = sc8551_read_byte(sc, SC8551_REG_0E, &flag); + if (!ret && flag) + pr_debug("INT_FLAG =0x%02X\n", flag); + + ret = sc8551_read_byte(sc, SC8551_REG_0D, &stat); + if (!ret && stat != sc->prev_alarm) { + pr_debug("INT_STAT = 0X%02x\n", stat); + sc->prev_alarm = stat; + sc->batt_present = !!(stat & VBAT_INSERT); + sc->vbus_present = !!(stat & VBUS_INSERT); + } + + ret = sc8551_read_byte(sc, SC8551_REG_08, &stat); + if (!ret && (stat & 0x50)) + dev_err(sc->dev, "Reg[05]BUS_UCPOVP = 0x%02X\n", stat); + + ret = sc8551_read_byte(sc, SC8551_REG_0A, &stat); + if (!ret && (stat & 0x02)) + dev_err(sc->dev, "Reg[0A]CONV_OCP = 0x%02X\n", stat); + + mutex_unlock(&sc->data_lock); +} + +static void sc8551_check_fault_status(struct sc8551 *sc) +{ + u8 flag = 0; + u8 stat = 0; + int ret; + + mutex_lock(&sc->data_lock); + + ret = sc8551_read_byte(sc, SC8551_REG_10, &stat); + if (!ret && stat) + dev_err(sc->dev, "FAULT_STAT = 0x%02X\n", stat); + + ret = sc8551_read_byte(sc, SC8551_REG_11, &flag); + if (!ret && flag) + dev_err(sc->dev, "FAULT_FLAG = 0x%02X\n", flag); + + if (!ret && flag != sc->prev_fault) { + sc->prev_fault = flag; + sc->bat_ovp_fault = !!(flag & BAT_OVP_FAULT); + sc->bat_ocp_fault = !!(flag & BAT_OCP_FAULT); + sc->bus_ovp_fault = !!(flag & BUS_OVP_FAULT); + sc->bus_ocp_fault = !!(flag & BUS_OCP_FAULT); + } + + mutex_unlock(&sc->data_lock); +} + +static int sc8551_detect_device(struct sc8551 *sc) +{ + int ret; + u8 data; + + ret = sc8551_read_byte(sc, SC8551_REG_13, &data); + if (ret == 0) { + sc->part_no = (data & SC8551_DEV_ID_MASK); + sc->part_no >>= SC8551_DEV_ID_SHIFT; + } + return ret; +} + +static int sc8551_parse_dt(struct sc8551 *sc, struct device *dev) +{ + int ret; + struct device_node *np = dev->of_node; + + sc->cfg = devm_kzalloc(dev, + sizeof(struct sc8551_cfg), + GFP_KERNEL); + + if (!sc->cfg) + return -ENOMEM; + + sc->cfg->bat_ovp_disable = of_property_read_bool(np, + "sc,sc8551,bat-ovp-disable"); + sc->cfg->bat_ocp_disable = of_property_read_bool(np, + "sc,sc8551,bat-ocp-disable"); + sc->cfg->bus_ocp_disable = of_property_read_bool(np, + "sc,sc8551,bus-ocp-disable"); + sc->cfg->bat_therm_disable = of_property_read_bool(np, + "sc,sc8551,bat-therm-disable"); + sc->cfg->bus_therm_disable = of_property_read_bool(np, + "sc,sc8551,bus-therm-disable"); + + ret = of_property_read_u32(np, "sc,sc8551,bat-ovp-threshold", + &sc->cfg->bat_ovp_th); + if (ret) { + dev_err(sc->dev, "failed to read bat-ovp-threshold\n"); + return ret; + } + ret = of_property_read_u32(np, "sc,sc8551,bat-ocp-threshold", + &sc->cfg->bat_ocp_th); + if (ret) { + dev_err(sc->dev, "failed to read bat-ocp-threshold\n"); + return ret; + } + ret = of_property_read_u32(np, "sc,sc8551,bus-ovp-threshold", + &sc->cfg->bus_ovp_th); + if (ret) { + dev_err(sc->dev, "failed to read bus-ovp-threshold\n"); + return ret; + } + ret = of_property_read_u32(np, "sc,sc8551,bus-ocp-threshold", + &sc->cfg->bus_ocp_th); + if (ret) { + dev_err(sc->dev, "failed to read bus-ocp-threshold\n"); + return ret; + } + + ret = of_property_read_u32(np, "sc,sc8551,ac-ovp-threshold", + &sc->cfg->ac_ovp_th); + if (ret) { + dev_err(sc->dev, "failed to read ac-ovp-threshold\n"); + return ret; + } + + ret = of_property_read_u32(np, "sc,sc8551,sense-resistor-mohm", + &sc->cfg->sense_r_mohm); + if (ret) { + dev_err(sc->dev, "failed to read sense-resistor-mohm\n"); + return ret; + } + + return 0; +} + +static int sc8551_init_protection(struct sc8551 *sc) +{ + int ret; + + ret = sc8551_enable_batovp(sc, !sc->cfg->bat_ovp_disable); + pr_debug("%s bat ovp %s\n", + sc->cfg->bat_ovp_disable ? "disable" : "enable", + !ret ? "successfully" : "failed"); + + ret = sc8551_enable_batocp(sc, !sc->cfg->bat_ocp_disable); + pr_debug("%s bat ocp %s\n", + sc->cfg->bat_ocp_disable ? "disable" : "enable", + !ret ? "successfully" : "failed"); + + ret = sc8551_enable_busocp(sc, !sc->cfg->bus_ocp_disable); + pr_debug("%s bus ocp %s\n", + sc->cfg->bus_ocp_disable ? "disable" : "enable", + !ret ? "successfully" : "failed"); + + ret = sc8551_enable_bat_therm(sc, !sc->cfg->bat_therm_disable); + pr_debug("%s bat therm %s\n", + sc->cfg->bat_therm_disable ? "disable" : "enable", + !ret ? "successfully" : "failed"); + + ret = sc8551_enable_bus_therm(sc, !sc->cfg->bus_therm_disable); + pr_debug("%s bus therm %s\n", + sc->cfg->bus_therm_disable ? "disable" : "enable", + !ret ? "successfully" : "failed"); + + ret = sc8551_set_batovp_th(sc, sc->cfg->bat_ovp_th); + pr_debug("set bat ovp th %d %s\n", sc->cfg->bat_ovp_th, + !ret ? "successfully" : "failed"); + + ret = sc8551_set_batocp_th(sc, sc->cfg->bat_ocp_th); + pr_debug("set bat ocp threshold %d %s\n", sc->cfg->bat_ocp_th, + !ret ? "successfully" : "failed"); + + ret = sc8551_set_busovp_th(sc, sc->cfg->bus_ovp_th); + pr_debug("set bus ovp threshold %d %s\n", sc->cfg->bus_ovp_th, + !ret ? "successfully" : "failed"); + + ret = sc8551_set_busocp_th(sc, sc->cfg->bus_ocp_th); + pr_debug("set bus ocp threshold %d %s\n", sc->cfg->bus_ocp_th, + !ret ? "successfully" : "failed"); + + ret = sc8551_set_acovp_th(sc, sc->cfg->ac_ovp_th); + pr_debug("set ac ovp threshold %d %s\n", sc->cfg->ac_ovp_th, + !ret ? "successfully" : "failed"); + + return 0; +} + +static int sc8551_init_adc(struct sc8551 *sc) +{ + int ret; + + ret = sc8551_set_adc_scanrate(sc, false); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_IBUS, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_VBUS, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_VOUT, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_VBAT, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_IBAT, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_TBUS, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_TBAT, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_TDIE, true); + if (ret) + return ret; + ret = sc8551_set_adc_scan(sc, ADC_VAC, true); + if (ret) + return ret; + + ret = sc8551_enable_adc(sc, true); + if (ret) + return ret; + + return 0; +} + +static int sc8551_init_int_src(struct sc8551 *sc) +{ + int ret; + + /*TODO:be careful ts bus and ts bat alarm bit mask is in + * fault mask register. + */ + ret = sc8551_set_alarm_int_mask(sc, ADC_DONE); + if (ret) + dev_err(sc->dev, "failed to set alarm mask:%d\n", ret); + + return ret; +} + +static int sc8551_init_regulation(struct sc8551 *sc) +{ + int ret; + + ret = sc8551_set_ibat_reg_th(sc, 300); + if (ret) + return ret; + ret = sc8551_set_vbat_reg_th(sc, 100); + if (ret) + return ret; + + ret = sc8551_set_vdrop_deglitch(sc, 5000); + if (ret) + return ret; + ret = sc8551_set_vdrop_th(sc, 400); + if (ret) + return ret; + + ret = sc8551_enable_regulation(sc, false); + if (ret) + return ret; + + ret = sc8551_write_byte(sc, SC8551_REG_2E, 0x08); + if (ret) + return ret; + + return 0; +} + +static int sc8551_init_device(struct sc8551 *sc) +{ + int ret; + + /* Reset registers to their default values */ + ret = sc8551_update_bits(sc, + SC8551_REG_0B, + SC8551_REG_RST_MASK, + SC8551_REG_RST_ENABLE << SC8551_REG_RST_SHIFT); + if (ret) + return ret; + ret = sc8551_enable_wdt(sc, false); + if (ret) + return ret; + ret = sc8551_set_wdt(sc, 30000); + if (ret) + return ret; + ret = sc8551_set_ss_timeout(sc, 100000); + if (ret) + return ret; + ret = sc8551_set_sense_resistor(sc, sc->cfg->sense_r_mohm); + if (ret) + return ret; + ret = sc8551_init_protection(sc); + if (ret) + return ret; + ret = sc8551_init_adc(sc); + if (ret) + return ret; + ret = sc8551_init_int_src(sc); + if (ret) + return ret; + ret = sc8551_init_regulation(sc); + if (ret) + return ret; + + return 0; +} + +static int sc8551_set_present(struct sc8551 *sc, bool present) +{ + sc->usb_present = present; + + if (present) + sc8551_init_device(sc); + return 0; +} + +static ssize_t registers_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sc8551 *sc = dev_get_drvdata(dev); + u8 tmpbuf[SC8551_DEBUG_BUF_LEN]; + int idx = 0; + int result; + u8 addr, val; + int len, ret; + + ret = sc8551_get_adc_data(sc, ADC_VBAT, &result); + if (!ret) + sc->vbat_volt = result; + + ret = sc8551_get_adc_data(sc, ADC_VAC, &result); + if (!ret) + sc->vac_volt = result; + + ret = sc8551_get_adc_data(sc, ADC_VBUS, &result); + if (!ret) + sc->vbus_volt = result; + + ret = sc8551_get_adc_data(sc, ADC_VOUT, &result); + if (!ret) + sc->vout_volt = result; + + ret = sc8551_get_adc_data(sc, ADC_IBUS, &result); + if (!ret) + sc->ibus_curr = result; + + ret = sc8551_get_adc_data(sc, ADC_TDIE, &result); + if (!ret) + sc->die_temp = result; + ret = sc8551_get_adc_data(sc, ADC_IBAT, &result); + if (!ret) + sc->ibat_curr = result; + + dev_err(sc->dev, "vbus_vol %d vbat_vol(vout) %d vout %d, vac: %d\n", + sc->vbus_volt, sc->vbat_volt, sc->vout_volt, sc->vac_volt); + dev_err(sc->dev, "ibus_curr %d ibat_curr %d\n", sc->ibus_curr, sc->ibat_curr); + dev_err(sc->dev, "die_temp %d\n", sc->die_temp); + + for (addr = SC8551_REG_00; addr <= SC8551_REG_36; addr++) { + ret = sc8551_read_byte(sc, addr, &val); + if (ret == 0) { + len = snprintf(tmpbuf, SC8551_DEBUG_BUF_LEN, + "Reg[%.2X] = 0x%.2x\n", + addr, + val); + memcpy(&buf[idx], tmpbuf, len); + idx += len; + } + } + + return idx; +} + +static ssize_t registers_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct sc8551 *sc = dev_get_drvdata(dev); + unsigned int reg; + unsigned int val; + int ret; + + ret = sscanf(buf, "%x %x", ®, &val); + if ((ret == 2) && (reg >= SC8551_REG_00) && (reg <= SC8551_REG_36)) + sc8551_write_byte(sc, + (unsigned char)reg, + (unsigned char)val); + + return count; +} +static DEVICE_ATTR_RW(registers); + +static void sc8551_create_device_node(struct device *dev) +{ + device_create_file(dev, &dev_attr_registers); +} + +static enum power_supply_property sc8551_charger_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CP_DIE_TEMPERATURE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CP_BAT_OVP_FAULT, + POWER_SUPPLY_PROP_CP_BAT_OCP_FAULT, + POWER_SUPPLY_PROP_CP_BUS_OVP_FAULT, + POWER_SUPPLY_PROP_CP_BUS_OCP_FAULT, + POWER_SUPPLY_PROP_CP_VBUS_HERROR_STATUS, + POWER_SUPPLY_PROP_CP_VBUS_LERROR_STATUS, +}; + +static int sc8551_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sc8551 *sc = power_supply_get_drvdata(psy); + int result; + u8 reg_val; + int ret; + + sc8551_check_alarm_status(sc); + sc8551_check_fault_status(sc); + sc8551_check_vbus_error_status(sc); + switch (psp) { + case POWER_SUPPLY_PROP_CP_CHARGING_ENABLED: + sc8551_check_charge_enabled(sc, &sc->charge_enabled); + val->intval = sc->charge_enabled; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = sc->usb_present; + break; + + case POWER_SUPPLY_PROP_ONLINE: + ret = sc8551_read_byte(sc, SC8551_REG_0D, ®_val); + if (!ret) + sc->vbus_present = !!(reg_val & VBUS_INSERT); + val->intval = sc->vbus_present; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = sc8551_get_adc_data(sc, ADC_VBAT, &result); + if (!ret) + sc->vbat_volt = result; + val->intval = sc->vbat_volt * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = sc8551_get_adc_data(sc, ADC_IBAT, &result); + if (!ret) + sc->ibat_curr = result; + val->intval = sc->ibat_curr * 1000; + break; + case POWER_SUPPLY_PROP_CP_VBUS: /* BUS_VOLTAGE */ + ret = sc8551_get_adc_data(sc, ADC_VBUS, &result); + if (!ret) + sc->vbus_volt = result; + val->intval = sc->vbus_volt * 1000; + break; + case POWER_SUPPLY_PROP_CP_IBUS: /* BUS_CURRENT */ + ret = sc8551_get_adc_data(sc, ADC_IBUS, &result); + if (!ret) + sc->ibus_curr = result; + val->intval = sc->ibus_curr * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: /* BUS_VOLTAGE */ + val->intval = 12000 * 1000; /* 20V */ + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: /* BUS_CURRENT */ + val->intval = 4500 * 1000; /* 4.75A */ + break; + case POWER_SUPPLY_PROP_CP_DIE_TEMPERATURE:/* DIE_TEMPERATURE */ + ret = sc8551_get_adc_data(sc, ADC_TDIE, &result); + if (!ret) + sc->die_temp = result; + val->intval = sc->die_temp; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = 4300 * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = 8000 * 1000; + break; + + case POWER_SUPPLY_PROP_CP_BAT_OVP_FAULT: + val->intval = sc->bat_ovp_fault; + break; + case POWER_SUPPLY_PROP_CP_BAT_OCP_FAULT: + val->intval = sc->bat_ocp_fault; + break; + case POWER_SUPPLY_PROP_CP_BUS_OVP_FAULT: + val->intval = sc->bus_ovp_fault; + break; + case POWER_SUPPLY_PROP_CP_BUS_OCP_FAULT: + val->intval = sc->bus_ocp_fault; + break; + case POWER_SUPPLY_PROP_CP_VBUS_HERROR_STATUS: + val->intval = (sc->vbus_error >> 0x04) & 0x01; + break; + case POWER_SUPPLY_PROP_CP_VBUS_LERROR_STATUS: + val->intval = (sc->vbus_error >> 0x05) & 0x01; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sc8551_charger_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct sc8551 *sc = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CP_CHARGING_ENABLED: + sc8551_enable_charge(sc, val->intval); + if (val->intval) + sc8551_enable_wdt(sc, true); + else + sc8551_enable_wdt(sc, false); + sc8551_check_charge_enabled(sc, &sc->charge_enabled); + break; + case POWER_SUPPLY_PROP_PRESENT: + sc8551_set_present(sc, !!val->intval); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sc8551_charger_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int ret; + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + ret = 1; + break; + default: + ret = 0; + break; + } + return ret; +} + +static int sc8551_psy_register(struct sc8551 *sc) +{ + sc->psy_cfg.drv_data = sc; + sc->psy_cfg.of_node = sc->dev->of_node; + + sc->psy_desc.name = "sc8551-standalone"; + + sc->psy_desc.type = POWER_SUPPLY_TYPE_CHARGE_PUMP; + sc->psy_desc.properties = sc8551_charger_props; + sc->psy_desc.num_properties = ARRAY_SIZE(sc8551_charger_props); + sc->psy_desc.get_property = sc8551_charger_get_property; + sc->psy_desc.set_property = sc8551_charger_set_property; + sc->psy_desc.property_is_writeable = sc8551_charger_is_writeable; + + sc->fc2_psy = devm_power_supply_register(sc->dev, + &sc->psy_desc, + &sc->psy_cfg); + if (IS_ERR(sc->fc2_psy)) { + dev_err(sc->dev, "failed to register fc2_psy\n"); + return PTR_ERR(sc->fc2_psy); + } + + return 0; +} + +/* + * interrupt does nothing, just info event change, other module could get info + * through power supply interface + */ +static irqreturn_t sc8551_charger_interrupt(int irq, void *dev_id) +{ + struct sc8551 *sc = dev_id; + int ret, value; + + ret = sc8551_get_adc_data(sc, ADC_VOUT, &value); + if (!ret) + sc->vbat_volt = value; + + ret = sc8551_get_adc_data(sc, ADC_IBAT, &value); + if (!ret) + sc->ibat_curr = value; + + ret = sc8551_get_adc_data(sc, ADC_VBUS, &value); + if (!ret) + sc->vbus_volt = value; + + ret = sc8551_get_adc_data(sc, ADC_IBUS, &value); + if (!ret) + sc->ibus_curr = value; + + ret = sc8551_get_adc_data(sc, ADC_TDIE, &value); + if (!ret) + sc->die_temp = value; + ret = sc8551_get_adc_data(sc, ADC_IBAT, &value); + if (!ret) + sc->ibat_curr = value; + + sc8551_check_alarm_status(sc); + sc8551_check_fault_status(sc); + sc8551_check_vbus_error_status(sc); + power_supply_changed(sc->fc2_psy); + + return IRQ_HANDLED; +} + +static int sc8551_init_irq(struct sc8551 *sc) +{ + int ret; + + sc->irq = sc->client->irq; + if (sc->irq <= 0) { + dev_err(sc->dev, "irq mapping fail\n"); + return 0; + } + + ret = devm_request_threaded_irq(sc->dev, + sc->irq, + NULL, + sc8551_charger_interrupt, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "sc8551 standalone irq", + sc); + if (ret < 0) + dev_err(sc->dev, "request irq for irq=%d failed, ret =%d\n", + sc->irq, ret); + + enable_irq_wake(sc->irq); + device_init_wakeup(sc->dev, 1); + + return 0; +} + +static void determine_initial_status(struct sc8551 *sc) +{ + if (sc->client->irq) + sc8551_charger_interrupt(sc->client->irq, sc); +} + +static const struct of_device_id sc8551_charger_match[] = { + { .compatible = "sc,sc8551-standalone", }, + {}, +}; + +static int sc8551_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sc8551 *sc; + const struct of_device_id *match; + struct device_node *node = client->dev.of_node; + int ret; + + sc = devm_kzalloc(&client->dev, sizeof(struct sc8551), GFP_KERNEL); + if (!sc) + return -ENOMEM; + + sc->dev = &client->dev; + + sc->client = client; + + mutex_init(&sc->i2c_rw_lock); + mutex_init(&sc->data_lock); + + ret = sc8551_detect_device(sc); + if (ret) { + dev_err(sc->dev, "No sc8551 device found!\n"); + return -ENODEV; + } + + i2c_set_clientdata(client, sc); + + match = of_match_node(sc8551_charger_match, node); + if (match == NULL) { + dev_err(sc->dev, "device tree match not found!\n"); + return -ENODEV; + } + sc8551_get_work_mode(sc, &sc->mode); + if (sc->mode != SC8551_ROLE_STDALONE) { + dev_err(sc->dev, "device operation mode mismatch with dts configuration\n"); + return -EINVAL; + } + + ret = sc8551_parse_dt(sc, &client->dev); + if (ret) + return -EIO; + + ret = sc8551_init_device(sc); + if (ret) { + dev_err(sc->dev, "Failed to init device\n"); + return ret; + } + + ret = sc8551_psy_register(sc); + if (ret) + return ret; + + ret = sc8551_init_irq(sc); + if (ret) + goto err_1; + + determine_initial_status(sc); + sc8551_create_device_node(&(client->dev)); + + return 0; + +err_1: + power_supply_unregister(sc->fc2_psy); + return ret; +} + +static int sc8551_charger_remove(struct i2c_client *client) +{ + struct sc8551 *sc = i2c_get_clientdata(client); + + + sc8551_enable_adc(sc, false); + + power_supply_unregister(sc->fc2_psy); + + mutex_destroy(&sc->data_lock); + mutex_destroy(&sc->i2c_rw_lock); + + return 0; +} + +static void sc8551_charger_shutdown(struct i2c_client *client) +{ + struct sc8551 *sc = i2c_get_clientdata(client); + + sc8551_enable_adc(sc, false); +} + +static const struct i2c_device_id sc8551_charger_id[] = { + {"sc8551-standalone", SC8551_ROLE_STDALONE}, + {}, +}; + +static struct i2c_driver sc8551_charger_driver = { + .driver = { + .name = "sc8551-charger", + .owner = THIS_MODULE, + .of_match_table = sc8551_charger_match, + }, + .id_table = sc8551_charger_id, + + .probe = sc8551_charger_probe, + .remove = sc8551_charger_remove, + .shutdown = sc8551_charger_shutdown, +}; + +module_i2c_driver(sc8551_charger_driver); + +MODULE_AUTHOR("Xu Shengfei "); +MODULE_DESCRIPTION("SC SC8551 Charge Pump Driver"); +MODULE_LICENSE("GPL"); +