[<prev] [next>] [day] [month] [year] [list]
Message-ID: <tencent_DAD6E4E85F79FDC4DF2878B03940CA337108@qq.com>
Date: Mon, 13 Oct 2025 16:54:14 +0800
From: 1647395606@...com
To: sre@...nel.org
Cc: linus.walleij@...aro.org,
brgl@...ev.pl,
linux-kernel@...r.kernel.org,
linux-pm@...r.kernel.org,
linux-gpio@...r.kernel.org,
wangwenqiang <wenqiang.wang@...ot.com>
Subject: [PATCH] power: supply: Add SC8541 charger drivers
From: wangwenqiang <wenqiang.wang@...ot.com>
The SC8541 is a charger pump from South Chip.
By adjusting the voltage difference between the input and output terminals,
it can achieve a maximum charging current of 8A.
It has been verified that this driver can operate normally on the Qualcomm QCS615 platform.
Signed-off-by: wangwenqiang <wenqiang.wang@...ot.com>
---
drivers/power/supply/Kconfig | 7 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/sc8541_charger.c | 1624 +++++++++++++++++++++++++
3 files changed, 1632 insertions(+)
create mode 100644 drivers/power/supply/sc8541_charger.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 79ddb006e2da..b44e5ba46f79 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -1074,4 +1074,11 @@ config FUEL_GAUGE_MM8013
the state of charge, temperature, cycle count, actual and design
capacity, etc.
+config SC8541_CHARGER
+ tristate "SC8541 Charger Pump driver"
+ default n
+ help
+ If you say yes here you will get support for the SC8541 Charge Pump.
+ This driver can give support for SC8541 Int
+
endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index f943c9150b32..4542bab1765c 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -122,3 +122,4 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o
obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o
+obj-$(CONFIG_SC8541_CHARGER) += sc8541_charger.o
diff --git a/drivers/power/supply/sc8541_charger.c b/drivers/power/supply/sc8541_charger.c
new file mode 100644
index 000000000000..6900f9a9ebd6
--- /dev/null
+++ b/drivers/power/supply/sc8541_charger.c
@@ -0,0 +-2,1623 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+* Copyright (c) 2022 Southchip Semiconductor Technology(Shanghai) Co., Ltd.
+*/
+
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/err.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/debugfs.h>
+#include <linux/bitops.h>
+#include <linux/math64.h>
+#include <linux/regmap.h>
+
+#ifdef CONFIG_MTK_CLASS
+#include "charger_class.h"
+#endif /*CONFIG_MTK_CLASS*/
+
+#ifdef CONFIG_MTK_CHARGER_V4P19
+#include "mtk_charger_intf.h"
+#endif /*CONFIG_MTK_CHARGER_V4P19*/
+
+#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
+#include "dvchg_class.h"
+#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
+
+#define SC8541_DRV_VERSION "1.0.0_G"
+
+enum {
+ SC8541_STANDALONE = 0,
+ SC8541_MASTER,
+ SC8541_SLAVE,
+};
+
+static const char *sc8541_psy_name[] = {
+ [SC8541_STANDALONE] = "sc-cp-standalone",
+ [SC8541_MASTER] = "sc-cp-master",
+ [SC8541_SLAVE] = "sc-cp-slave",
+};
+
+static const char *sc8541_irq_name[] = {
+ [SC8541_STANDALONE] = "sc8541-standalone-irq",
+ [SC8541_MASTER] = "sc8541-master-irq",
+ [SC8541_SLAVE] = "sc8541-slave-irq",
+};
+
+static int sc8541_mode_data[] = {
+ [SC8541_STANDALONE] = SC8541_STANDALONE,
+ [SC8541_MASTER] = SC8541_MASTER,
+ [SC8541_SLAVE] = SC8541_SLAVE,
+};
+
+enum {
+ ADC_IBUS,
+ ADC_VBUS,
+ ADC_VAC1,
+ ADC_VAC2,
+ ADC_VOUT,
+ ADC_VBAT,
+ ADC_IBAT,
+ ADC_TSBUS,
+ ADC_TSBAT,
+ ADC_TDIE,
+ ADC_MAX_NUM,
+} ADC_CH;
+
+static const u32 sc8541_adc_accuracy_tbl[ADC_MAX_NUM] = {
+ 150000, /* IBUS */
+ 35000, /* VBUS */
+ 35000, /* VAC1 */
+ 35000, /* VAC2 */
+ 20000, /* VOUT */
+ 20000, /* VBAT */
+ 200000, /* IBAT */
+ 0, /* TSBUS */
+ 0, /* TSBAT */
+ 4, /* TDIE */
+};
+
+static const int sc8541_adc_m[] = {
+ 25, 375, 5, 5, 125, 125, 3125, 9766, 9766, 5};
+
+static const int sc8541_adc_l[] = {
+ 10, 100, 1, 1, 100, 100, 1000, 100000, 100000, 10};
+
+enum sc8541_notify {
+ SC8541_NOTIFY_OTHER = 0,
+ SC8541_NOTIFY_IBUSUCPF,
+ SC8541_NOTIFY_VBUSOVPALM,
+ SC8541_NOTIFY_VBATOVPALM,
+ SC8541_NOTIFY_IBUSOCP,
+ SC8541_NOTIFY_VBUSOVP,
+ SC8541_NOTIFY_IBATOCP,
+ SC8541_NOTIFY_VBATOVP,
+ SC8541_NOTIFY_VOUTOVP,
+ SC8541_NOTIFY_VDROVP,
+};
+
+enum sc8541_error_stata {
+ ERROR_VBUS_HIGH = 0,
+ ERROR_VBUS_LOW,
+ ERROR_VBUS_OVP,
+ ERROR_IBUS_OCP,
+ ERROR_VBAT_OVP,
+ ERROR_IBAT_OCP,
+};
+
+struct flag_bit {
+ int notify;
+ int mask;
+ char *name;
+};
+
+struct intr_flag {
+ int reg;
+ int len;
+ struct flag_bit bit[8];
+};
+
+static struct intr_flag cp_intr_flag[] = {
+ { .reg = 0x18, .len = 7, .bit = {
+ {.mask = BIT(0), .name = "vbus ovp alm flag", .notify = SC8541_NOTIFY_VBUSOVPALM},
+ {.mask = BIT(1), .name = "vbus ovp flag", .notify = SC8541_NOTIFY_VBUSOVP},
+ {.mask = BIT(3), .name = "ibat ocp alm flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(4), .name = "ibat ocp flag", .notify = SC8541_NOTIFY_IBATOCP},
+ {.mask = BIT(5), .name = "vout ovp flag", .notify = SC8541_NOTIFY_VOUTOVP},
+ {.mask = BIT(6), .name = "vbat ovp alm flag", .notify = SC8541_NOTIFY_VBATOVPALM},
+ {.mask = BIT(7), .name = "vbat ovp flag", .notify = SC8541_NOTIFY_VBATOVP},
+ },
+ },
+ { .reg = 0x19, .len = 3, .bit = {
+ {.mask = BIT(2), .name = "pin diag fall flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(5), .name = "ibus ucp fall flag", .notify = SC8541_NOTIFY_IBUSUCPF},
+ {.mask = BIT(7), .name = "ibus ocp flag", .notify = SC8541_NOTIFY_IBUSOCP},
+ },
+ },
+ { .reg = 0x1a, .len = 8, .bit = {
+ {.mask = BIT(0), .name = "acrb2 config flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(1), .name = "acrb1 config flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(2), .name = "vbus present flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(3), .name = "vac2 insert flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(4), .name = "vac1 insert flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(5), .name = "vat insert flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(6), .name = "vac2 ovp flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(7), .name = "vac1 ovp flag", .notify = SC8541_NOTIFY_OTHER},
+ },
+ },
+ { .reg = 0x1b, .len = 8, .bit = {
+ {.mask = BIT(0), .name = "wd timeout flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(1), .name = "tdie alm flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(2), .name = "tshut flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(3), .name = "tsbat flt flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(4), .name = "tsbus flt flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(5), .name = "tsbus tsbat alm flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(6), .name = "ss timeout flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(7), .name = "adc done flag", .notify = SC8541_NOTIFY_OTHER},
+ },
+ },
+ { .reg = 0x1c, .len = 3, .bit = {
+ {.mask = BIT(3), .name = "vbus errorlo flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(4), .name = "vbus errorhi flag", .notify = SC8541_NOTIFY_OTHER},
+ {.mask = BIT(6), .name = "cp switching flag", .notify = SC8541_NOTIFY_OTHER},
+ },
+ },
+};
+
+
+/************************************************************************/
+#define SC8541_DEVICE_ID 0x41
+
+#define SC8541_VBAT_OVP_MIN 3840
+#define SC8541_VBAT_OVP_MAX 5110
+#define SC8541_VBAT_OVP_STEP 10
+
+#define SC8541_IBAT_OCP_MIN 0
+#define SC8541_IBAT_OCP_MAX 12700
+#define SC8541_IBAT_OCP_STEP 10
+
+#define SC8541_VBUS_OVP_MIN 7000
+#define SC8541_VBUS_OVP_MAX 13350
+#define SC8541_VBUS_OVP_STEP 50
+
+#define SC8541_IBUS_OCP_MIN 1000
+#define SC8541_IBUS_OCP_MAX 8000
+#define SC8541_IBUS_OCP_STEP 250
+
+#define SC8541_REG18 0x18
+#define SC8541_REG25 0x25
+#define SC8541_REGMAX 0x42
+
+enum sc8541_reg_range {
+ SC8541_VBAT_OVP,
+ SC8541_IBAT_OCP,
+ SC8541_VBUS_OVP,
+ SC8541_IBUS_OCP,
+ SC8541_VBUS_OVP_ALM,
+ SC8541_VBAT_OVP_ALM,
+};
+
+struct reg_range {
+ u32 min;
+ u32 max;
+ u32 step;
+ u32 offset;
+ const u32 *table;
+ u16 num_table;
+ bool round_up;
+};
+
+#define SC8541_CHG_RANGE(_min, _max, _step, _offset, _ru) \
+{ \
+ .min = _min, \
+ .max = _max, \
+ .step = _step, \
+ .offset = _offset, \
+ .round_up = _ru, \
+}
+
+#define SC8541_CHG_RANGE_T(_table, _ru) \
+ { .table = _table, .num_table = ARRAY_SIZE(_table), .round_up = _ru, }
+
+
+static const struct reg_range sc8541_reg_range[] = {
+ [SC8541_VBAT_OVP] = SC8541_CHG_RANGE(3840, 5110, 10, 3840, false),
+ [SC8541_IBAT_OCP] = SC8541_CHG_RANGE(0, 12700, 100, 0, false),
+ [SC8541_VBUS_OVP] = SC8541_CHG_RANGE(7000, 13350, 50, 7000, false),
+ [SC8541_IBUS_OCP] = SC8541_CHG_RANGE(1000, 8000, 250, 1000, false),
+ [SC8541_VBUS_OVP_ALM] = SC8541_CHG_RANGE(7000, 13350, 50, 7000, false),
+ [SC8541_VBAT_OVP_ALM] = SC8541_CHG_RANGE(60, 960, 60, 60, false),
+};
+
+
+enum sc8541_fields {
+ VBAT_OVP_DIS, VBAT_OVP,
+ VBAT_OVP_ALM_DIS, VBAT_OVP_ALM,
+ IBAT_OCP_DIS, IBAT_OCP,
+ IBAT_OCP_ALM_DIS, IBAT_OCP_ALM,
+ IBUS_UCP_DIS, VBUS_IN_RANGE_DIS,
+ VBUS_PD_EN, VBUS_OVP,
+ VBUS_OVP_ALM_DIS, VBUS_OVP_ALM,
+ IBUS_OCP_DIS, IBUS_OCP,
+ TSHUT_DIS, TSBUS_FLT_DIS, TSBAT_FLT_DIS,
+ TDIE_ALM,
+ TSBUS_FLT,
+ TSBAT_FLT,
+ VAC1_OVP, VAC2_OVP, VAC1_PD_EN, VAC2_PD_EN,
+ REG_RST, OTG_EN, CHG_EN, MODE, ACDRV1_STAT, ACDRV2_STAT,
+ FSW_SET, WD_TIMEOUT, WD_TIMEOUT_DIS,
+ SET_IBAT_SNS_RES, SS_TIMEOUT, IBUS_UCP_FALL_DG,
+ VOUT_OVP_DIS, VOUT_OVP, MS,
+ CP_SWITCHING_STAT, VBUS_ERRORHI_STAT, VBUS_ERRORLO_STAT,
+ DEVICE_ID,
+ ADC_EN, ADC_RATE,
+ ACDRV_MANUAL_EN, ACDRV1_EN, ACDRV2_EN,
+ PMID2OUT_UVP, PMID2OUT_OVP, PMID2OUT_UVP_FLAG, PMID2OUT_OVP_FLAG,
+ F_MAX_FIELDS,
+};
+
+
+//REGISTER
+static const struct reg_field sc8541_reg_fields[] = {
+ /*reg00*/
+ [VBAT_OVP_DIS] = REG_FIELD(0x00, 7, 7),
+ [VBAT_OVP] = REG_FIELD(0x00, 0, 6),
+ /*reg01*/
+ [VBAT_OVP_ALM_DIS] = REG_FIELD(0x01, 7, 7),
+ [VBAT_OVP_ALM] = REG_FIELD(0x01, 0, 6),
+ /*reg02*/
+ [IBAT_OCP_DIS] = REG_FIELD(0x02, 7, 7),
+ [IBAT_OCP] = REG_FIELD(0x02, 0, 6),
+ /*reg03*/
+ [IBAT_OCP_ALM_DIS] = REG_FIELD(0x03, 7, 7),
+ [IBAT_OCP_ALM] = REG_FIELD(0x03, 0, 6),
+ /*reg05*/
+ [IBUS_UCP_DIS] = REG_FIELD(0x05, 7, 7),
+ [VBUS_IN_RANGE_DIS] = REG_FIELD(0x05, 2, 2),
+ /*reg06*/
+ [VBUS_PD_EN] = REG_FIELD(0x06, 7, 7),
+ [VBUS_OVP] = REG_FIELD(0x06, 0, 6),
+ /*reg07*/
+ [VBUS_OVP_ALM_DIS] = REG_FIELD(0x07, 7, 7),
+ [VBUS_OVP_ALM] = REG_FIELD(0x07, 0, 6),
+ /*reg08*/
+ [IBUS_OCP_DIS] = REG_FIELD(0x08, 7, 7),
+ [IBUS_OCP] = REG_FIELD(0x08, 0, 4),
+ /*reg0A*/
+ [TSHUT_DIS] = REG_FIELD(0x0A, 7, 7),
+ [TSBUS_FLT_DIS] = REG_FIELD(0x0A, 3, 3),
+ [TSBAT_FLT_DIS] = REG_FIELD(0x0A, 2, 2),
+ /*reg0B*/
+ [TDIE_ALM] = REG_FIELD(0x0B, 0, 7),
+ /*reg0C*/
+ [TSBUS_FLT] = REG_FIELD(0x0C, 0, 7),
+ /*reg0D*/
+ [TSBAT_FLT] = REG_FIELD(0x0D, 0, 7),
+ /*reg0E*/
+ [VAC1_OVP] = REG_FIELD(0x0E, 5, 7),
+ [VAC2_OVP] = REG_FIELD(0x0E, 2, 4),
+ [VAC1_PD_EN] = REG_FIELD(0x0E, 1, 1),
+ [VAC2_PD_EN] = REG_FIELD(0x0E, 0, 0),
+ /*reg0F*/
+ [REG_RST] = REG_FIELD(0x0F, 7, 7),
+ [OTG_EN] = REG_FIELD(0x0F, 5, 5),
+ [CHG_EN] = REG_FIELD(0x0F, 4, 4),
+ [MODE] = REG_FIELD(0x0F, 3, 3),
+ [ACDRV1_STAT] = REG_FIELD(0x0F, 1, 1),
+ [ACDRV2_STAT] = REG_FIELD(0x0F, 0, 0),
+ /*reg10*/
+ [FSW_SET] = REG_FIELD(0x10, 5, 7),
+ [WD_TIMEOUT] = REG_FIELD(0x10, 3, 4),
+ [WD_TIMEOUT_DIS] = REG_FIELD(0x10, 2, 2),
+ /*reg11*/
+ [SET_IBAT_SNS_RES] = REG_FIELD(0x11, 7, 7),
+ [SS_TIMEOUT] = REG_FIELD(0x11, 4, 6),
+ [IBUS_UCP_FALL_DG] = REG_FIELD(0x11, 2, 3),
+ /*reg12*/
+ [VOUT_OVP_DIS] = REG_FIELD(0x12, 7, 7),
+ [VOUT_OVP] = REG_FIELD(0x12, 5, 6),
+ [MS] = REG_FIELD(0x12, 0, 1),
+ /*reg17*/
+ [CP_SWITCHING_STAT] = REG_FIELD(0x17, 6, 6),
+ [VBUS_ERRORHI_STAT] = REG_FIELD(0x17, 4, 4),
+ [VBUS_ERRORLO_STAT] = REG_FIELD(0x17, 3, 3),
+ /*reg22*/
+ [DEVICE_ID] = REG_FIELD(0x22, 0, 7),
+ /*reg23*/
+ [ADC_EN] = REG_FIELD(0x23, 7, 7),
+ [ADC_RATE] = REG_FIELD(0x23, 6, 6),
+ /*reg40*/
+ [ACDRV_MANUAL_EN] = REG_FIELD(0x40, 6, 6),
+ [ACDRV1_EN] = REG_FIELD(0x40, 5, 5),
+ [ACDRV2_EN] = REG_FIELD(0x40, 4, 4),
+ /*reg42*/
+ [PMID2OUT_UVP] = REG_FIELD(0x42, 5, 7),
+ [PMID2OUT_OVP] = REG_FIELD(0x42, 2, 4),
+ [PMID2OUT_UVP_FLAG] = REG_FIELD(0x42, 1, 1),
+ [PMID2OUT_OVP_FLAG] = REG_FIELD(0x42, 0, 0),
+};
+
+static const struct regmap_config sc8541_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = SC8541_REGMAX,
+};
+
+/************************************************************************/
+
+struct sc8541_cfg_e {
+ int vbat_ovp_dis;
+ int vbat_ovp;
+ int vbat_ovp_alm_dis;
+ int vbat_ovp_alm;
+ int ibat_ocp_dis;
+ int ibat_ocp;
+ int ibat_ocp_alm_dis;
+ int ibat_ocp_alm;
+ int ibus_ucp_dis;
+ int vbus_in_range_dis;
+ int vbus_pd_en;
+ int vbus_ovp;
+ int vbus_ovp_alm_dis;
+ int vbus_ovp_alm;
+ int ibus_ocp_dis;
+ int ibus_ocp;
+ int tshut_dis;
+ int tsbus_flt_dis;
+ int tsbat_flt_dis;
+ int tdie_alm;
+ int tsbus_flt;
+ int tsbat_flt;
+ int vac1_ovp;
+ int vac2_ovp;
+ int vac1_pd_en;
+ int vac2_pd_en;
+ int fsw_set;
+ int wd_timeout;
+ int wd_timeout_dis;
+ int ibat_sns_r;
+ int ss_timeout;
+ int ibus_ucp_fall_dg;
+ int vout_ovp_dis;
+ int vout_ovp;
+ int pmid2out_uvp;
+ int pmid2out_ovp;
+};
+
+struct sc8541_chip {
+ struct device *dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct regmap_field *rmap_fields[F_MAX_FIELDS];
+
+ struct sc8541_cfg_e cfg;
+ int irq_gpio;
+ int irq;
+
+ int mode;
+
+ bool charge_enabled;
+ int usb_present;
+ int vbus_volt;
+ int ibus_curr;
+ int vbat_volt;
+ int ibat_curr;
+ int die_temp;
+
+#ifdef CONFIG_MTK_CLASS
+ struct charger_device *chg_dev;
+#endif /*CONFIG_MTK_CLASS*/
+
+#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
+ struct dvchg_dev *charger_pump;
+#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
+
+ const char *chg_dev_name;
+
+ struct power_supply_desc psy_desc;
+ struct power_supply_config psy_cfg;
+ struct power_supply *psy;
+};
+
+#ifdef CONFIG_MTK_CLASS
+static const struct charger_properties sc8541_chg_props = {
+ .alias_name = "sc8541_chg",
+};
+#endif /*CONFIG_MTK_CLASS*/
+
+
+/********************COMMON API***********************/
+__maybe_unused static u8 val2reg(enum sc8541_reg_range id, u32 val)
+{
+ int i;
+ u8 reg;
+ const struct reg_range *range = &sc8541_reg_range[id];
+
+ if (!range)
+ return val;
+
+ if (range->table) {
+ if (val <= range->table[0])
+ return 0;
+ for (i = 1; i < range->num_table - 1; i++) {
+ if (val == range->table[i])
+ return i;
+ if (val > range->table[i] &&
+ val < range->table[i + 1])
+ return range->round_up ? i + 1 : i;
+ }
+ return range->num_table - 1;
+ }
+ if (val <= range->min)
+ reg = 0;
+ else if (val >= range->max)
+ reg = (range->max - range->offset) / range->step;
+ else if (range->round_up)
+ reg = (val - range->offset) / range->step + 1;
+ else
+ reg = (val - range->offset) / range->step;
+ return reg;
+}
+
+__maybe_unused static u32 reg2val(enum sc8541_reg_range id, u8 reg)
+{
+ const struct reg_range *range = &sc8541_reg_range[id];
+
+ if (!range)
+ return reg;
+ return range->table ? range->table[reg] :
+ range->offset + range->step * reg;
+}
+/*********************************************************/
+static int sc8541_field_read(struct sc8541_chip *sc,
+ enum sc8541_fields field_id, int *val)
+{
+ int ret;
+
+ ret = regmap_field_read(sc->rmap_fields[field_id], val);
+ if (ret < 0) {
+ dev_err(sc->dev, "sc8541 read field %d fail: %d\n", field_id, ret);
+ }
+
+ return ret;
+}
+
+static int sc8541_field_write(struct sc8541_chip *sc,
+ enum sc8541_fields field_id, int val)
+{
+ int ret;
+
+ ret = regmap_field_write(sc->rmap_fields[field_id], val);
+ if (ret < 0) {
+ dev_err(sc->dev, "sc8541 read field %d fail: %d\n", field_id, ret);
+ }
+
+ return ret;
+}
+
+static int sc8541_read_block(struct sc8541_chip *sc,
+ int reg, uint8_t *val, int len)
+{
+ int ret;
+
+ ret = regmap_bulk_read(sc->regmap, reg, val, len);
+ if (ret < 0) {
+ dev_err(sc->dev, "sc8541 read %02x block failed %d\n", reg, ret);
+ }
+
+ return ret;
+}
+
+
+
+/*******************************************************/
+__maybe_unused static int sc8541_detect_device(struct sc8541_chip *sc)
+{
+ int ret;
+ int val;
+
+ ret = sc8541_field_read(sc, DEVICE_ID, &val);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s fail(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ if (val != SC8541_DEVICE_ID) {
+ dev_err(sc->dev, "%s not find SC8541, ID = 0x%02x\n", __func__, ret);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+__maybe_unused static int sc8541_reg_reset(struct sc8541_chip *sc)
+{
+ return sc8541_field_write(sc, REG_RST, 1);
+}
+
+__maybe_unused static int sc8541_dump_reg(struct sc8541_chip *sc)
+{
+ int ret;
+ int i;
+ int val;
+
+ for (i = 0; i <= SC8541_REGMAX; i++) {
+ ret = regmap_read(sc->regmap, i, &val);
+ dev_err(sc->dev, "%s reg[0x%02x] = 0x%02x\n",
+ __func__, i, val);
+ }
+
+ return ret;
+}
+
+__maybe_unused static int sc8541_enable_charge(struct sc8541_chip *sc, bool en)
+{
+ int ret;
+
+ dev_info(sc->dev, "%s:%d", __func__, en);
+
+ ret = sc8541_field_write(sc, CHG_EN, !!en);
+
+ return ret;
+}
+
+
+__maybe_unused static int sc8541_check_charge_enabled(struct sc8541_chip *sc, bool *enabled)
+{
+ int ret, val;
+
+ ret = sc8541_field_read(sc, CP_SWITCHING_STAT, &val);
+
+ *enabled = (bool)val;
+
+ dev_info(sc->dev, "%s:%d", __func__, val);
+
+ return ret;
+}
+
+__maybe_unused static int sc8541_get_status(struct sc8541_chip *sc, uint32_t *status)
+{
+ int ret, val;
+ *status = 0;
+
+ ret = sc8541_field_read(sc, VBUS_ERRORHI_STAT, &val);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s fail to read VBUS_ERRORHI_STAT(%d)\n", __func__, ret);
+ return ret;
+ }
+ if (val != 0)
+ *status |= BIT(ERROR_VBUS_HIGH);
+
+ ret = sc8541_field_read(sc, VBUS_ERRORLO_STAT, &val);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s fail to read VBUS_ERRORLO_STAT(%d)\n", __func__, ret);
+ return ret;
+ }
+ if (val != 0)
+ *status |= BIT(ERROR_VBUS_LOW);
+
+
+ return ret;
+
+}
+
+__maybe_unused static int sc8541_enable_adc(struct sc8541_chip *sc, bool en)
+{
+ dev_info(sc->dev, "%s:%d", __func__, en);
+ return sc8541_field_write(sc, ADC_EN, !!en);
+}
+
+__maybe_unused static int sc8541_set_adc_scanrate(struct sc8541_chip *sc, bool oneshot)
+{
+ dev_info(sc->dev, "%s:%d", __func__, oneshot);
+ return sc8541_field_write(sc, ADC_RATE, !!oneshot);
+}
+
+static int sc8541_get_adc_data(struct sc8541_chip *sc,
+ int channel, int *result)
+{
+ uint8_t val[2] = {0};
+ int ret;
+
+ if (channel >= ADC_MAX_NUM)
+ return -EINVAL;
+
+ sc8541_enable_adc(sc, true);
+ msleep(50);
+
+ ret = sc8541_read_block(sc, SC8541_REG25 + (channel << 1), val, 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ *result = (val[1] | (val[0] << 8)) *
+ sc8541_adc_m[channel] / sc8541_adc_l[channel];
+
+ dev_info(sc->dev, "%s %d %d", __func__, channel, *result);
+
+ sc8541_enable_adc(sc, false);
+
+ return ret;
+}
+
+__maybe_unused static int sc8541_set_busovp_th(struct sc8541_chip *sc, int threshold)
+{
+ int reg_val = val2reg(SC8541_VBUS_OVP, threshold);
+
+ dev_info(sc->dev, "%s:%d-%d", __func__, threshold, reg_val);
+
+ return sc8541_field_write(sc, VBUS_OVP, reg_val);
+}
+
+__maybe_unused static int sc8541_set_busocp_th(struct sc8541_chip *sc, int threshold)
+{
+ int reg_val = val2reg(SC8541_IBUS_OCP, threshold);
+
+ dev_info(sc->dev, "%s:%d-%d", __func__, threshold, reg_val);
+
+ return sc8541_field_write(sc, IBUS_OCP, reg_val);
+}
+
+__maybe_unused static int sc8541_set_batovp_th(struct sc8541_chip *sc, int threshold)
+{
+ int reg_val = val2reg(SC8541_VBAT_OVP, threshold);
+
+ dev_info(sc->dev, "%s:%d-%d", __func__, threshold, reg_val);
+
+ return sc8541_field_write(sc, VBAT_OVP, reg_val);
+}
+
+__maybe_unused static int sc8541_set_batocp_th(struct sc8541_chip *sc, int threshold)
+{
+ int reg_val = val2reg(SC8541_IBAT_OCP, threshold);
+
+ dev_info(sc->dev, "%s:%d-%d", __func__, threshold, reg_val);
+
+ return sc8541_field_write(sc, IBAT_OCP, reg_val);
+}
+
+__maybe_unused static int sc8541_set_vbusovp_alarm(struct sc8541_chip *sc, int threshold)
+{
+ int reg_val = val2reg(SC8541_VBUS_OVP_ALM, threshold);
+
+ dev_info(sc->dev, "%s:%d-%d", __func__, threshold, reg_val);
+
+ return sc8541_field_write(sc, VBUS_OVP_ALM, reg_val);
+}
+
+__maybe_unused static int sc8541_set_vbatovp_alarm(struct sc8541_chip *sc, int threshold)
+{
+ int reg_val = val2reg(SC8541_VBAT_OVP_ALM, threshold);
+
+ dev_info(sc->dev, "%s:%d-%d", __func__, threshold, reg_val);
+
+ return sc8541_field_write(sc, VBAT_OVP_ALM, reg_val);
+}
+
+__maybe_unused static int sc8541_disable_vbatovp_alarm(struct sc8541_chip *sc, bool en)
+{
+ int ret;
+
+ dev_info(sc->dev, "%s:%d", __func__, en);
+
+ ret = sc8541_field_write(sc, VBAT_OVP_ALM_DIS, !!en);
+
+ return ret;
+}
+
+__maybe_unused static int sc8541_disable_vbusovp_alarm(struct sc8541_chip *sc, bool en)
+{
+ int ret;
+
+ dev_info(sc->dev, "%s:%d", __func__, en);
+
+ ret = sc8541_field_write(sc, VBUS_OVP_ALM_DIS, !!en);
+
+ return ret;
+}
+
+
+__maybe_unused static int sc8541_is_vbuslowerr(struct sc8541_chip *sc, bool *err)
+{
+ int ret;
+ int val;
+
+ ret = sc8541_field_read(sc, VBUS_ERRORLO_STAT, &val);
+ if (ret < 0) {
+ return ret;
+ }
+
+ dev_info(sc->dev, "%s:%d", __func__, val);
+
+ *err = (bool)val;
+
+ return ret;
+}
+
+__maybe_unused static int sc8541_init_device(struct sc8541_chip *sc)
+{
+ int ret = 0;
+ int i;
+ struct {
+ enum sc8541_fields field_id;
+ int conv_data;
+ } props[] = {
+ {VBAT_OVP_DIS, sc->cfg.vbat_ovp_dis},
+ {VBAT_OVP, sc->cfg.vbat_ovp},
+ {VBAT_OVP_ALM_DIS, sc->cfg.vbat_ovp_alm_dis},
+ {VBAT_OVP_ALM, sc->cfg.vbat_ovp_alm},
+ {IBAT_OCP_DIS, sc->cfg.ibat_ocp_dis},
+ {IBAT_OCP, sc->cfg.ibat_ocp},
+ {IBAT_OCP_ALM_DIS, sc->cfg.ibat_ocp_alm_dis},
+ {IBAT_OCP_ALM, sc->cfg.ibat_ocp_alm},
+ {IBUS_UCP_DIS, sc->cfg.ibus_ucp_dis},
+ {VBUS_IN_RANGE_DIS, sc->cfg.vbus_in_range_dis},
+ {VBUS_PD_EN, sc->cfg.vbus_pd_en},
+ {VBUS_OVP, sc->cfg.vbus_ovp},
+ {VBUS_OVP_ALM_DIS, sc->cfg.vbus_ovp_alm_dis},
+ {VBUS_OVP_ALM, sc->cfg.vbus_ovp_alm},
+ {IBUS_OCP_DIS, sc->cfg.ibus_ocp_dis},
+ {IBUS_OCP, sc->cfg.ibus_ocp},
+ {TSHUT_DIS, sc->cfg.tshut_dis},
+ {TSBUS_FLT_DIS, sc->cfg.tsbus_flt_dis},
+ {TSBAT_FLT_DIS, sc->cfg.tsbat_flt_dis},
+ {TDIE_ALM, sc->cfg.tdie_alm},
+ {TSBUS_FLT, sc->cfg.tsbus_flt},
+ {TSBAT_FLT, sc->cfg.tsbat_flt},
+ {VAC1_OVP, sc->cfg.vac1_ovp},
+ {VAC2_OVP, sc->cfg.vac2_ovp},
+ {VAC1_PD_EN, sc->cfg.vac1_pd_en},
+ {VAC2_PD_EN, sc->cfg.vac2_pd_en},
+ {FSW_SET, sc->cfg.fsw_set},
+ {WD_TIMEOUT, sc->cfg.wd_timeout},
+ {WD_TIMEOUT_DIS, sc->cfg.wd_timeout_dis},
+ {SET_IBAT_SNS_RES, sc->cfg.ibat_sns_r},
+ {SS_TIMEOUT, sc->cfg.ss_timeout},
+ {IBUS_UCP_FALL_DG, sc->cfg.ibus_ucp_fall_dg},
+ {VOUT_OVP_DIS, sc->cfg.vout_ovp_dis},
+ {VOUT_OVP, sc->cfg.vout_ovp},
+ //{MS, sc->cfg.ms},
+ {PMID2OUT_UVP, sc->cfg.pmid2out_uvp},
+ {PMID2OUT_OVP, sc->cfg.pmid2out_ovp},
+ };
+
+ ret = sc8541_reg_reset(sc);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s Failed to reset registers(%d)\n", __func__, ret);
+ }
+ msleep(10);
+
+ for (i = 0; i < ARRAY_SIZE(props); i++) {
+ ret = sc8541_field_write(sc, props[i].field_id, props[i].conv_data);
+ }
+
+ if (sc->mode == SC8541_SLAVE) {
+ ret = sc8541_field_write(sc, VBUS_IN_RANGE_DIS, 1);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s Failed to set vbus in range(%d)\n", __func__, ret);
+ }
+ }
+
+ sc8541_dump_reg(sc);
+
+ return ret;
+}
+
+
+/*********************mtk charger interface start**********************************/
+#ifdef CONFIG_MTK_CLASS
+static inline int to_sc8541_adc(enum adc_channel chan)
+{
+ switch (chan) {
+ case ADC_CHANNEL_VBUS:
+ return ADC_VBUS;
+ case ADC_CHANNEL_VBAT:
+ return ADC_VBAT;
+ case ADC_CHANNEL_IBUS:
+ return ADC_IBUS;
+ case ADC_CHANNEL_IBAT:
+ return ADC_IBAT;
+ case ADC_CHANNEL_TEMP_JC:
+ return ADC_TDIE;
+ case ADC_CHANNEL_VOUT:
+ return ADC_VOUT;
+ default:
+ break;
+ }
+ return ADC_MAX_NUM;
+}
+
+
+static int mtk_sc8541_is_chg_enabled(struct charger_device *chg_dev, bool *en)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ret;
+
+ ret = sc8541_check_charge_enabled(sc, en);
+
+ return ret;
+}
+
+
+static int mtk_sc8541_enable_chg(struct charger_device *chg_dev, bool en)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ret;
+
+ ret = sc8541_enable_charge(sc, en);
+
+ return ret;
+}
+
+
+static int mtk_sc8541_set_vbusovp(struct charger_device *chg_dev, u32 uV)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int mv;
+
+ mv = uV / 1000;
+
+ return sc8541_set_busovp_th(sc, mv);
+}
+
+static int mtk_sc8541_set_ibusocp(struct charger_device *chg_dev, u32 uA)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ma;
+
+ ma = uA / 1000;
+
+ return sc8541_set_busocp_th(sc, ma);
+}
+
+static int mtk_sc8541_set_vbatovp(struct charger_device *chg_dev, u32 uV)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ret;
+
+ ret = sc8541_set_batovp_th(sc, uV/1000);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static int mtk_sc8541_set_ibatocp(struct charger_device *chg_dev, u32 uA)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ret;
+
+ ret = sc8541_set_batocp_th(sc, uA/1000);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+
+static int mtk_sc8541_get_adc(struct charger_device *chg_dev, enum adc_channel chan,
+ int *min, int *max)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+
+ sc8541_get_adc_data(sc, to_sc8541_adc(chan), max);
+
+ if (chan != ADC_CHANNEL_TEMP_JC)
+ *max = *max * 1000;
+
+ if (min != max)
+ *min = *max;
+
+ return 0;
+}
+
+static int mtk_sc8541_get_adc_accuracy(struct charger_device *chg_dev,
+ enum adc_channel chan, int *min, int *max)
+{
+ *min = *max = sc8541_adc_accuracy_tbl[to_sc8541_adc(chan)];
+ return 0;
+}
+
+static int mtk_sc8541_is_vbuslowerr(struct charger_device *chg_dev, bool *err)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+
+ return sc8541_is_vbuslowerr(sc, err);
+}
+
+static int mtk_sc8541_set_vbatovp_alarm(struct charger_device *chg_dev, u32 uV)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ret;
+
+ ret = sc8541_set_vbatovp_alarm(sc, uV/1000);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static int mtk_sc8541_reset_vbatovp_alarm(struct charger_device *chg_dev)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+
+ return 0;
+}
+
+static int mtk_sc8541_set_vbusovp_alarm(struct charger_device *chg_dev, u32 uV)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+ int ret;
+
+ ret = sc8541_set_vbusovp_alarm(sc, uV/1000);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static int mtk_sc8541_reset_vbusovp_alarm(struct charger_device *chg_dev)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+
+ return 0;
+}
+
+static int mtk_sc8541_init_chip(struct charger_device *chg_dev)
+{
+ struct sc8541_chip *sc = charger_get_data(chg_dev);
+
+ return sc8541_init_device(sc);
+}
+
+static const struct charger_ops sc8541_chg_ops = {
+ .enable = mtk_sc8541_enable_chg,
+ .is_enabled = mtk_sc8541_is_chg_enabled,
+ .get_adc = mtk_sc8541_get_adc,
+ .get_adc_accuracy = mtk_sc8541_get_adc_accuracy,
+ .set_vbusovp = mtk_sc8541_set_vbusovp,
+ .set_ibusocp = mtk_sc8541_set_ibusocp,
+ .set_vbatovp = mtk_sc8541_set_vbatovp,
+ .set_ibatocp = mtk_sc8541_set_ibatocp,
+ .init_chip = mtk_sc8541_init_chip,
+ .is_vbuslowerr = mtk_sc8541_is_vbuslowerr,
+ .set_vbatovp_alarm = mtk_sc8541_set_vbatovp_alarm,
+ .reset_vbatovp_alarm = mtk_sc8541_reset_vbatovp_alarm,
+ .set_vbusovp_alarm = mtk_sc8541_set_vbusovp_alarm,
+ .reset_vbusovp_alarm = mtk_sc8541_reset_vbusovp_alarm,
+};
+#endif /*CONFIG_MTK_CLASS*/
+/********************mtk charger interface end*************************************************/
+
+#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
+static inline int sc_to_sc8541_adc(enum sc_adc_channel chan)
+{
+ switch (chan) {
+ case SC_ADC_VBUS:
+ return ADC_VBUS;
+ case SC_ADC_VBAT:
+ return ADC_VBAT;
+ case SC_ADC_IBUS:
+ return ADC_IBUS;
+ case SC_ADC_IBAT:
+ return ADC_IBAT;
+ case SC_ADC_TDIE:
+ return ADC_TDIE;
+ default:
+ break;
+ }
+ return ADC_MAX_NUM;
+}
+
+
+static int sc_sc8541_set_enable(struct dvchg_dev *charger_pump, bool enable)
+{
+ struct sc8541_chip *sc = dvchg_get_private(charger_pump);
+ int ret;
+
+ ret = sc8541_enable_charge(sc, enable);
+
+ return ret;
+}
+
+static int sc_sc8541_get_is_enable(struct dvchg_dev *charger_pump, bool *enable)
+{
+ struct sc8541_chip *sc = dvchg_get_private(charger_pump);
+ int ret;
+
+ ret = sc8541_check_charge_enabled(sc, enable);
+
+ return ret;
+}
+
+static int sc_sc8541_get_status(struct dvchg_dev *charger_pump, uint32_t *status)
+{
+ struct sc8541_chip *sc = dvchg_get_private(charger_pump);
+ int ret = 0;
+
+ ret = sc8541_get_status(sc, status);
+
+ return ret;
+}
+
+static int sc_sc8541_get_adc_value(struct dvchg_dev *charger_pump, enum sc_adc_channel ch, int *value)
+{
+ struct sc8541_chip *sc = dvchg_get_private(charger_pump);
+ int ret = 0;
+
+ ret = sc8541_get_adc_data(sc, sc_to_sc8541_adc(ch), value);
+
+ return ret;
+}
+
+static struct dvchg_ops sc_sc8541_dvchg_ops = {
+ .set_enable = sc_sc8541_set_enable,
+ .get_status = sc_sc8541_get_status,
+ .get_is_enable = sc_sc8541_get_is_enable,
+ .get_adc_value = sc_sc8541_get_adc_value,
+};
+#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
+
+/********************creat devices note start*************************************************/
+static ssize_t sc8541_show_registers(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sc8541_chip *sc = dev_get_drvdata(dev);
+ u8 addr;
+ int val;
+ u8 tmpbuf[300];
+ int len;
+ int idx = 0;
+ int ret;
+
+ idx = snprintf(buf, PAGE_SIZE, "%s:\n", "sc8541");
+ for (addr = 0x0; addr <= SC8541_REGMAX; addr++) {
+ ret = regmap_read(sc->regmap, addr, &val);
+ if (ret == 0) {
+ len = snprintf(tmpbuf, PAGE_SIZE - idx,
+ "Reg[%.2X] = 0x%.2x\n", addr, val);
+ memcpy(&buf[idx], tmpbuf, len);
+ idx += len;
+ }
+ }
+
+ return idx;
+}
+
+static ssize_t sc8541_store_register(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct sc8541_chip *sc = dev_get_drvdata(dev);
+ int ret;
+ unsigned int reg;
+ unsigned int val;
+
+ ret = sscanf(buf, "%x %x", ®, &val);
+ if (ret == 2 && reg <= SC8541_REGMAX)
+ regmap_write(sc->regmap, (unsigned char)reg, (unsigned char)val);
+
+ return count;
+}
+
+static DEVICE_ATTR(registers, 0660, sc8541_show_registers, sc8541_store_register);
+
+static void sc8541_create_device_node(struct device *dev)
+{
+ device_create_file(dev, &dev_attr_registers);
+}
+/********************creat devices note end*************************************************/
+
+
+/*
+* interrupt does nothing, just info event chagne, other module could get info
+* through power supply interface
+*/
+#ifdef CONFIG_MTK_CLASS
+static inline int status_reg_to_charger(enum sc8541_notify notify)
+{
+ switch (notify) {
+ case SC8541_NOTIFY_IBUSUCPF:
+ return CHARGER_DEV_NOTIFY_IBUSUCP_FALL;
+ case SC8541_NOTIFY_VBUSOVPALM:
+ return CHARGER_DEV_NOTIFY_VBUSOVP_ALARM;
+ case SC8541_NOTIFY_VBATOVPALM:
+ return CHARGER_DEV_NOTIFY_VBATOVP_ALARM;
+ case SC8541_NOTIFY_IBUSOCP:
+ return CHARGER_DEV_NOTIFY_IBUSOCP;
+ case SC8541_NOTIFY_VBUSOVP:
+ return CHARGER_DEV_NOTIFY_VBUS_OVP;
+ case SC8541_NOTIFY_IBATOCP:
+ return CHARGER_DEV_NOTIFY_IBATOCP;
+ case SC8541_NOTIFY_VBATOVP:
+ return CHARGER_DEV_NOTIFY_BAT_OVP;
+ case SC8541_NOTIFY_VOUTOVP:
+ return CHARGER_DEV_NOTIFY_VOUTOVP;
+ default:
+ return -EINVAL;
+ break;
+ }
+ return -EINVAL;
+}
+#endif /*CONFIG_MTK_CLASS*/
+static void sc8541_check_fault_status(struct sc8541_chip *sc)
+{
+ int ret;
+ u8 flag = 0;
+ int i, j;
+#ifdef CONFIG_MTK_CLASS
+ int noti;
+#endif /*CONFIG_MTK_CLASS*/
+
+ for (i = 0; i < ARRAY_SIZE(cp_intr_flag); i++) {
+ ret = sc8541_read_block(sc, cp_intr_flag[i].reg, &flag, 1);
+ for (j = 0; j < cp_intr_flag[i].len; j++) {
+ if (flag & cp_intr_flag[i].bit[j].mask) {
+ dev_err(sc->dev, "trigger :%s\n", cp_intr_flag[i].bit[j].name);
+#ifdef CONFIG_MTK_CLASS
+ noti = status_reg_to_charger(cp_intr_flag[i].bit[j].notify);
+ if (noti >= 0) {
+ charger_dev_notify(sc->chg_dev, noti);
+ }
+#endif /*CONFIG_MTK_CLASS*/
+ }
+ }
+ }
+}
+
+static irqreturn_t sc8541_irq_handler(int irq, void *data)
+{
+ struct sc8541_chip *sc = data;
+
+ dev_err(sc->dev, "INT OCCURRED\n");
+
+ sc8541_check_fault_status(sc);
+
+ power_supply_changed(sc->psy);
+
+ return IRQ_HANDLED;
+}
+
+static int sc8541_register_interrupt(struct sc8541_chip *sc)
+{
+ int ret;
+
+ if (gpio_is_valid(sc->irq_gpio)) {
+ ret = gpio_request_one(sc->irq_gpio, GPIOF_IN, "sc8541_irq");
+ if (ret) {
+ dev_err(sc->dev, "failed to request sc8541_irq\n");
+ return -EINVAL;
+ }
+ sc->irq = gpio_to_irq(sc->irq_gpio);
+ if (sc->irq < 0) {
+ dev_err(sc->dev, "failed to gpio_to_irq\n");
+ return -EINVAL;
+ }
+ } else {
+ dev_err(sc->dev, "irq gpio not provided\n");
+ return -EINVAL;
+ }
+
+ if (sc->irq) {
+ ret = devm_request_threaded_irq(&sc->client->dev, sc->irq,
+ NULL, sc8541_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ sc8541_irq_name[sc->mode], sc);
+
+ if (ret < 0) {
+ dev_err(sc->dev, "request irq for irq=%d failed, ret =%d\n",
+ sc->irq, ret);
+ return ret;
+ }
+ enable_irq_wake(sc->irq);
+ }
+
+ return ret;
+}
+/********************interrupte end*************************************************/
+
+
+/************************psy start**************************************/
+static enum power_supply_property sc8541_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static int sc8541_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sc8541_chip *sc = power_supply_get_drvdata(psy);
+ int result;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ sc8541_check_charge_enabled(sc, &sc->charge_enabled);
+ val->intval = sc->charge_enabled;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = sc8541_get_adc_data(sc, ADC_VBUS, &result);
+ if (!ret)
+ sc->vbus_volt = result;
+ val->intval = sc->vbus_volt;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = sc8541_get_adc_data(sc, ADC_IBUS, &result);
+ if (!ret)
+ sc->ibus_curr = result;
+ val->intval = sc->ibus_curr;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = sc8541_get_adc_data(sc, ADC_VBAT, &result);
+ if (!ret)
+ sc->vbat_volt = result;
+ val->intval = sc->vbat_volt;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = sc8541_get_adc_data(sc, ADC_IBAT, &result);
+ if (!ret)
+ sc->ibat_curr = result;
+ val->intval = sc->ibat_curr;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = sc8541_get_adc_data(sc, ADC_TDIE, &result);
+ if (!ret)
+ sc->die_temp = result;
+ val->intval = sc->die_temp;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sc8541_charger_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct sc8541_chip *sc = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ sc8541_enable_charge(sc, val->intval);
+ dev_info(sc->dev, "POWER_SUPPLY_PROP_ONLINE: %s\n",
+ val->intval ? "enable" : "disable");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sc8541_charger_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ return 0;
+}
+
+static int sc8541_psy_register(struct sc8541_chip *sc)
+{
+ sc->psy_cfg.drv_data = sc;
+ sc->psy_cfg.fwnode = dev_fwnode(sc->dev);
+
+ sc->psy_desc.name = sc8541_psy_name[sc->mode];
+
+ sc->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
+ sc->psy_desc.properties = sc8541_charger_props;
+ sc->psy_desc.num_properties = ARRAY_SIZE(sc8541_charger_props);
+ sc->psy_desc.get_property = sc8541_charger_get_property;
+ sc->psy_desc.set_property = sc8541_charger_set_property;
+ sc->psy_desc.property_is_writeable = sc8541_charger_is_writeable;
+
+
+ sc->psy = devm_power_supply_register(sc->dev,
+ &sc->psy_desc, &sc->psy_cfg);
+ if (IS_ERR(sc->psy)) {
+ dev_err(sc->dev, "%s failed to register psy\n", __func__);
+ return PTR_ERR(sc->psy);
+ }
+
+ dev_info(sc->dev, "%s power supply register successfully\n", sc->psy_desc.name);
+
+ return 0;
+}
+
+
+/************************psy end**************************************/
+
+static int sc8541_set_work_mode(struct sc8541_chip *sc, int mode)
+{
+ sc->mode = mode;
+
+ dev_err(sc->dev, "work mode is %s\n", sc->mode == SC8541_STANDALONE
+ ? "standalone" : (sc->mode == SC8541_MASTER ? "master" : "slave"));
+
+ return 0;
+}
+
+static int sc8541_parse_dt(struct sc8541_chip *sc, struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ int i;
+ int ret;
+ struct {
+ char *name;
+ int *conv_data;
+ } props[] = {
+ {"sc,sc8541,vbat-ovp-dis", &(sc->cfg.vbat_ovp_dis)},
+ {"sc,sc8541,vbat-ovp", &(sc->cfg.vbat_ovp)},
+ {"sc,sc8541,vbat-ovp-alm-dis", &(sc->cfg.vbat_ovp_alm_dis)},
+ {"sc,sc8541,vbat-ovp-alm", &(sc->cfg.vbat_ovp_alm)},
+ {"sc,sc8541,ibat-ocp-dis", &(sc->cfg.ibat_ocp_dis)},
+ {"sc,sc8541,ibat-ocp", &(sc->cfg.ibat_ocp)},
+ {"sc,sc8541,ibat-ocp-alm-dis", &(sc->cfg.ibat_ocp_alm_dis)},
+ {"sc,sc8541,ibat-ocp-alm", &(sc->cfg.ibat_ocp_alm)},
+ {"sc,sc8541,ibus-ucp-dis", &(sc->cfg.ibus_ucp_dis)},
+ {"sc,sc8541,vbus-in-range-dis", &(sc->cfg.vbus_in_range_dis)},
+ {"sc,sc8541,vbus-pd-en", &(sc->cfg.vbus_pd_en)},
+ {"sc,sc8541,vbus-ovp", &(sc->cfg.vbus_ovp)},
+ {"sc,sc8541,vbus-ovp-alm-dis", &(sc->cfg.vbus_ovp_alm_dis)},
+ {"sc,sc8541,vbus-ovp-alm", &(sc->cfg.vbus_ovp_alm)},
+ {"sc,sc8541,ibus-ocp-dis", &(sc->cfg.ibus_ocp_dis)},
+ {"sc,sc8541,ibus-ocp", &(sc->cfg.ibus_ocp)},
+ {"sc,sc8541,tshut-dis", &(sc->cfg.tshut_dis)},
+ {"sc,sc8541,tsbus-flt-dis", &(sc->cfg.tsbus_flt_dis)},
+ {"sc,sc8541,tsbat-flt-dis", &(sc->cfg.tsbat_flt_dis)},
+ {"sc,sc8541,tdie-alm", &(sc->cfg.tdie_alm)},
+ {"sc,sc8541,tsbus-flt", &(sc->cfg.tsbus_flt)},
+ {"sc,sc8541,tsbat-flt", &(sc->cfg.tsbat_flt)},
+ {"sc,sc8541,vac1-ovp", &(sc->cfg.vac1_ovp)},
+ {"sc,sc8541,vac2-ovp", &(sc->cfg.vac2_ovp)},
+ {"sc,sc8541,vac1-pd-en", &(sc->cfg.vac1_pd_en)},
+ {"sc,sc8541,vac2-pd-en", &(sc->cfg.vac2_pd_en)},
+ {"sc,sc8541,fsw-set", &(sc->cfg.fsw_set)},
+ {"sc,sc8541,wd-timeout", &(sc->cfg.wd_timeout)},
+ {"sc,sc8541,wd-timeout-dis", &(sc->cfg.wd_timeout_dis)},
+ {"sc,sc8541,ibat-sns-r", &(sc->cfg.ibat_sns_r)},
+ {"sc,sc8541,ss-timeout", &(sc->cfg.ss_timeout)},
+ {"sc,sc8541,ibus-ucp-fall-dg", &(sc->cfg.ibus_ucp_fall_dg)},
+ {"sc,sc8541,vout-ovp-dis", &(sc->cfg.vout_ovp_dis)},
+ {"sc,sc8541,vout-ovp", &(sc->cfg.vout_ovp)},
+ //{"sc,sc8541,ms", &(sc->cfg.ms)},
+ {"sc,sc8541,pmid2out-uvp", &(sc->cfg.pmid2out_uvp)},
+ {"sc,sc8541,pmid2out-ovp", &(sc->cfg.pmid2out_ovp)},
+ };
+
+ /* initialize data for optional properties */
+ for (i = 0; i < ARRAY_SIZE(props); i++) {
+ ret = of_property_read_u32(np, props[i].name,
+ props[i].conv_data);
+ if (ret < 0) {
+ dev_err(sc->dev, "can not read %s\n", props[i].name);
+ return ret;
+ }
+ }
+
+ sc->irq_gpio = of_get_named_gpio(np, "sc8541,intr_gpio", 0);
+ if (!gpio_is_valid(sc->irq_gpio)) {
+ dev_err(sc->dev, "fail to valid gpio : %d\n", sc->irq_gpio);
+ return -EINVAL;
+ }
+
+ if (of_property_read_string(np, "charger_name", &sc->chg_dev_name) < 0) {
+ sc->chg_dev_name = "charger";
+ dev_err(sc->dev, "no charger name\n");
+ }
+
+ return 0;
+}
+
+static struct of_device_id sc8541_charger_match_table[] = {
+ { .compatible = "sc,sc8541-standalone",
+ .data = &sc8541_mode_data[SC8541_STANDALONE], },
+ { .compatible = "sc,sc8541-master",
+ .data = &sc8541_mode_data[SC8541_MASTER], },
+ { .compatible = "sc,sc8541-slave",
+ .data = &sc8541_mode_data[SC8541_SLAVE], },
+};
+
+static int sc8541_charger_probe(struct i2c_client *client)
+{
+ struct sc8541_chip *sc;
+ const struct of_device_id *match;
+ struct device_node *node = client->dev.of_node;
+ int ret, i;
+
+ dev_err(&client->dev, "%s (%s)\n", __func__, SC8541_DRV_VERSION);
+
+ sc = devm_kzalloc(&client->dev, sizeof(struct sc8541_chip), GFP_KERNEL);
+ if (!sc) {
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ sc->dev = &client->dev;
+ sc->client = client;
+
+ sc->regmap = devm_regmap_init_i2c(client,
+ &sc8541_regmap_config);
+ if (IS_ERR(sc->regmap)) {
+ dev_err(sc->dev, "Failed to initialize regmap\n");
+ ret = PTR_ERR(sc->regmap);
+ goto err_regmap_init;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sc8541_reg_fields); i++) {
+ const struct reg_field *reg_fields = sc8541_reg_fields;
+
+ sc->rmap_fields[i] =
+ devm_regmap_field_alloc(sc->dev,
+ sc->regmap,
+ reg_fields[i]);
+ if (IS_ERR(sc->rmap_fields[i])) {
+ dev_err(sc->dev, "cannot allocate regmap field\n");
+ ret = PTR_ERR(sc->rmap_fields[i]);
+ goto err_regmap_field;
+ }
+ }
+
+ ret = sc8541_detect_device(sc);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s detect device fail\n", __func__);
+ goto err_detect_dev;
+ }
+
+ i2c_set_clientdata(client, sc);
+ sc8541_create_device_node(&(client->dev));
+
+ match = of_match_node(sc8541_charger_match_table, node);
+ if (match == NULL) {
+ dev_err(sc->dev, "device tree match not found!\n");
+ goto err_match_node;
+ }
+
+ sc8541_set_work_mode(sc, *(int *)match->data);
+ if (ret) {
+ dev_err(sc->dev, "Fail to set work mode!\n");
+ goto err_set_mode;
+ }
+
+ ret = sc8541_parse_dt(sc, &client->dev);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s parse dt failed(%d)\n", __func__, ret);
+ goto err_parse_dt;
+ }
+
+ ret = sc8541_init_device(sc);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s init device failed(%d)\n", __func__, ret);
+ goto err_init_device;
+ }
+
+ ret = sc8541_psy_register(sc);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s psy register failed(%d)\n", __func__, ret);
+ goto err_register_psy;
+ }
+
+ ret = sc8541_register_interrupt(sc);
+ if (ret < 0) {
+ dev_err(sc->dev, "%s register irq fail(%d)\n",
+ __func__, ret);
+ goto err_register_irq;
+ }
+
+#ifdef CONFIG_MTK_CLASS
+ sc->chg_dev = charger_device_register(sc->chg_dev_name,
+ &client->dev, sc,
+ &sc8541_chg_ops,
+ &sc8541_chg_props);
+ if (IS_ERR_OR_NULL(sc->chg_dev)) {
+ ret = PTR_ERR(sc->chg_dev);
+ dev_err(sc->dev, "Fail to register charger!\n");
+ goto err_register_mtk_charger;
+ }
+#endif /*CONFIG_MTK_CLASS*/
+
+#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
+ sc->charger_pump = dvchg_register("sc_dvchg",
+ sc->dev, &sc_sc8541_dvchg_ops, sc);
+ if (IS_ERR_OR_NULL(sc->charger_pump)) {
+ ret = PTR_ERR(sc->charger_pump);
+ dev_err(sc->dev, "Fail to register charger!\n");
+ goto err_register_sc_charger;
+ }
+#endif /* CONFIG_SOUTHCHIP_DVCHG_CLASS */
+ dev_err(sc->dev, "sc8541[%s] probe successfully!\n",
+ sc->mode == SC8541_MASTER ? "master" : "slave");
+ return 0;
+
+err_register_psy:
+err_register_irq:
+#ifdef CONFIG_MTK_CLASS
+err_register_mtk_charger:
+#endif /*CONFIG_MTK_CLASS*/
+#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
+err_register_sc_charger:
+#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
+err_init_device:
+ power_supply_unregister(sc->psy);
+err_detect_dev:
+err_match_node:
+err_set_mode:
+err_parse_dt:
+err_regmap_init:
+err_regmap_field:
+ devm_kfree(&client->dev, sc);
+err_kzalloc:
+ dev_err(&client->dev, "sc8541 probe fail\n");
+ return ret;
+}
+
+
+static void sc8541_charger_remove(struct i2c_client *client)
+{
+ struct sc8541_chip *sc = i2c_get_clientdata(client);
+
+ power_supply_unregister(sc->psy);
+ devm_kfree(&client->dev, sc);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sc8541_suspend(struct device *dev)
+{
+ struct sc8541_chip *sc = dev_get_drvdata(dev);
+
+ dev_info(sc->dev, "Suspend successfully!");
+ if (device_may_wakeup(dev))
+ enable_irq_wake(sc->irq);
+ disable_irq(sc->irq);
+
+ return 0;
+}
+static int sc8541_resume(struct device *dev)
+{
+ struct sc8541_chip *sc = dev_get_drvdata(dev);
+
+ dev_info(sc->dev, "Resume successfully!");
+ if (device_may_wakeup(dev))
+ disable_irq_wake(sc->irq);
+ enable_irq(sc->irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sc8541_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(sc8541_suspend, sc8541_resume)
+};
+#endif
+
+static struct i2c_driver sc8541_charger_driver = {
+ .driver = {
+ .name = "sc8541",
+ .owner = THIS_MODULE,
+ .of_match_table = sc8541_charger_match_table,
+#ifdef CONFIG_PM_SLEEP
+ .pm = &sc8541_pm,
+#endif
+ },
+ .probe = sc8541_charger_probe,
+ .remove = sc8541_charger_remove,
+};
+
+module_i2c_driver(sc8541_charger_driver);
+
+MODULE_DESCRIPTION("SC SC8541 Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("South Chip <Aiden-yu@...thchip.com>");
+
--
2.25.1
Powered by blists - more mailing lists