From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-oi1-f169.google.com (mail-oi1-f169.google.com [209.85.167.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 412643E3DB4 for ; Tue, 21 Jul 2026 17:11:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.169 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784653895; cv=none; b=Dyr8/L4SARxRtHMvEdJkeRMxhUJHU9V00DzmSb4uUiScrkVTu75aEtNbZQ5h9N+2byJDZrNNOYV827FGJ7hi/kkh3r2SL8TJsSBKOA2bt561cmVwXw78bsXse1Hn5NJKb5L5kgWunDhrdylao7yFOu9AdBChguRqdJRzdwZIcOU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784653895; c=relaxed/simple; bh=dGVOfSMUqNVEBBS1zOc+ClC4ew/HXiywY4/bHj5D1Hc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lj0qO0G+li6chNQCDDxuzhoS9kQGZkNZdGYzMaFudkToFSCB2nNiaeYd3q5rJLNvMgPo3pOyEMrvMtSOIqhMAZrqvy8dup1lr+Q6N9VvnIGyfnyAq+byaV4FN10JqGQBpheI3NGL9/lxP213mi5KBtRUTOLKNtso45b+2J3ppD8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=s842AX9W; arc=none smtp.client-ip=209.85.167.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="s842AX9W" Received: by mail-oi1-f169.google.com with SMTP id 5614622812f47-495c63c4141so7324992b6e.2 for ; Tue, 21 Jul 2026 10:11:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784653891; x=1785258691; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=vqt9YJlx80A2itg1eEVU8Oy06B/azqTbdYj5Xhcs6pQ=; b=s842AX9WM+bvdeUrLLarJj9/vzYQH9jhO9JMngk4HwEV2tNxMxg+ixqzyKtBAvHHTK EGUY9nLpxP+9u53Od3Htm9TqB6Zum7kErZEh3ta9yUATnt7CTXJuHWQJfaJJx3Wq1VXH TjmF/5sfaTbmzluIzAd02x7Tz2XVHPGs0stth+NlL46Z+7ExHSfEFfhu84CEjOTeh4jj iBcftmIr0UtC9RySqMJ/2pgewah/+Gk6pt20jCgy8v76NoAluS8Hk+tRb2WQYYMUlMJ0 KeCXT1Dp+JIheXEk+qvaAkMVJcqj8lf1+5BNk1J6xa7UMScq7BRdrhEcxpk/JBFJeAAb kbzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784653891; x=1785258691; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=vqt9YJlx80A2itg1eEVU8Oy06B/azqTbdYj5Xhcs6pQ=; b=SrxafLK70nS0MCeBUAXX/DbQT8b337AGgLuMRhqnekR8/fw0JOhqIvDrDmD+OToXh5 QHVm5H1MNMdMUmBpef6V+W6sqlVa9YdqXGNBKDu9wNpBDdPxOHSdAGDtNd4AN3baGmUm 8PDyPSEgSbUmZuJN0pAOZoqjK1aNnVwuC7vQRg+Bi3B0cM6s6lYhv+vddE43Q/O2mSbP KI7S+xY6kbEAd8yOzd4cKbDuJKTmMf0nmCfBVzd4siFadpLCH9EEaXc8IAYT/YNvS/Ct TfNxDCbiYEy6V9dOazUxBUShUVm2Rco/FP0R5ixyPaIef0aHqLGVjMEKmM/Vh+V/8Md/ 7fSg== X-Gm-Message-State: AOJu0YzN5YZDtI9VIGzhE1wMvd6kzn5qck5GxeiwZCyHfx+VKU9uuPu+ 1xYoYz4UeOzOsMrCuftxEsnpeAJ5rVoDvv3UGmOSxB0bPfbf825BkFPU X-Gm-Gg: AfdE7cmcXM1IlPvT+9mWgsESe8OkloXiZ4654pWKFkoTMKAbziwvTNwgoPX6e+4QK6o H4mnrn56OSwPAztzIlp68PrnUfqh58Z8W6TewIV0aa/lcitt0qRw/jxfGNjL18Hi1GLbgGoFtZO Thr2QeRACow90BSAlFlfkvu4oQOgkI372S2M8MY5aMWydcBp6x2CN91y7Ba0bSJP7OZM0qgG5RQ 8YChNOPHMOdHxT3PLlHx8OTCVSioJaSI9i7MxlL6Nr67aytxVgUi1XsWirMORkGwZg9N7jnKCxv TduA85q9lSJfvXK4WiiJNxtSx6pI4lcunYxY7EFd53uTXcnpG9AjP9KNBmRcTQmdlJLGlkh9Waw Bgtmlev5SssEDr0lJ0aSHUYkLURbIn9YB0EUtVYJk6AAwokoLkAO4p7/9eY9gUnalWyRMIn9+WL +q X-Received: by 2002:a05:6808:14d6:b0:4a4:d2b:8e7a with SMTP id 5614622812f47-4a4d05205demr11105730b6e.35.1784653890658; Tue, 21 Jul 2026 10:11:30 -0700 (PDT) Received: from localhost.localdomain ([2600:1700:fb0:1bc0::54]) by smtp.gmail.com with ESMTPSA id 5614622812f47-4a4d3cadb50sm10213953b6e.9.2026.07.21.10.11.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 10:11:29 -0700 (PDT) From: Chris Morgan To: linux-rockchip@lists.infradead.org Cc: devicetree@vger.kernel.org, xsf@rock-chips.com, sre@kernel.org, heiko@sntech.de, conor+dt@kernel.org, krzk+dt@kernel.org, robh@kernel.org, Chris Morgan Subject: [PATCH V8 2/4] power: supply: sgm41542: Add SG Micro sgm41542 charger Date: Tue, 21 Jul 2026 12:09:05 -0500 Message-ID: <20260721170907.131731-3-macroalpha82@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260721170907.131731-1-macroalpha82@gmail.com> References: <20260721170907.131731-1-macroalpha82@gmail.com> Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Chris Morgan Add support for the SG Micro SGM41542 charger/boost converter. Driver was adapted from Rockchip BSP driver [1] and confirmed with vendor datasheet [2]. [1] https://github.com/rockchip-linux/kernel/blob/develop-6.6/drivers/power/supply/sgm41542_charger.c [2] https://www.sg-micro.de/rect/assets/1e8de70b-657e-4156-be68-a64fdbe8e418/SGM41541_SGM41542.pdf Signed-off-by: Chris Morgan --- drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/sgm41542_charger.c | 1111 +++++++++++++++++++++++ 3 files changed, 1120 insertions(+) create mode 100644 drivers/power/supply/sgm41542_charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f0ede1cecb6a..dadda6b78a03 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -866,6 +866,14 @@ config CHARGER_S2M devices provide USB power supply information and also required for USB OTG role switching. +config CHARGER_SGM41542 + tristate "SGM41542 charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select REGMAP_I2C + help + Say Y to enable support for the SGM41542 battery charger. + config CHARGER_SMB347 tristate "Summit Microelectronics SMB3XX Battery Charger" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 31fe4a145929..25d23ea3fcbd 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o +obj-$(CONFIG_CHARGER_SGM41542) += sgm41542_charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c new file mode 100644 index 000000000000..97a1e4575d3a --- /dev/null +++ b/drivers/power/supply/sgm41542_charger.c @@ -0,0 +1,1111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Charger driver for SGM4154x + * + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Xu Shengfei + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGM4154X_MANUFACTURER "SGMICRO" +#define SGM4154X_NAME "sgm41542" + +#define SGM4154X_CHRG_CTRL_0 0x00 +#define SGM4154X_HIZ_EN BIT(7) +#define SGM4154X_IINDPM_I_MASK GENMASK(4, 0) +#define SGM4154X_IINDPM_I_MIN_UA 100000 +#define SGM4154X_IINDPM_I_MAX_UA 3800000 +#define SGM4154X_IINDPM_STEP_UA 100000 +#define SGM4154X_IINDPM_DEF_UA 2400000 + +#define SGM4154X_CHRG_CTRL_1 0x01 +#define SGM4154X_WDT_RST BIT(6) +#define SGM4154X_OTG_EN BIT(5) +#define SGM4154X_CHRG_EN BIT(4) + +#define SGM4154X_CHRG_CTRL_2 0x02 +#define SGM4154X_BOOST_LIM BIT(7) +#define SGM4154X_ICHRG_CUR_MASK GENMASK(5, 0) +#define SGM4154X_ICHRG_I_STEP_UA 60000 +#define SGM4154X_ICHRG_I_MIN_UA 0 +#define SGM4154X_ICHRG_I_MAX_UA 3780000 +#define SGM4154X_ICHRG_I_DEF_UA 2040000 + +#define SGM4154X_CHRG_CTRL_3 0x03 +#define SGM4154X_PRECHRG_CUR_MASK GENMASK(7, 4) +#define SGM4154X_PRECHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_PRECHRG_I_MIN_UA 60000 +#define SGM4154X_PRECHRG_I_MAX_UA 780000 +#define SGM4154X_PRECHRG_I_DEF_UA 180000 +#define SGM4154X_TERMCHRG_CUR_MASK GENMASK(3, 0) +#define SGM4154X_TERMCHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_TERMCHRG_I_MIN_UA 60000 +#define SGM4154X_TERMCHRG_I_MAX_UA 960000 +#define SGM4154X_TERMCHRG_I_DEF_UA 180000 + +#define SGM4154X_CHRG_CTRL_4 0x04 +#define SGM4154X_VREG_V_MASK GENMASK(7, 3) +#define SGM4154X_VREG_V_MAX_UV 4624000 +#define SGM4154X_VREG_V_MIN_UV 3856000 +#define SGM4154X_VREG_V_DEF_UV 4208000 +#define SGM4154X_VREG_V_STEP_UV 32000 +#define SGM4154X_VRECHARGE BIT(0) +#define SGM4154X_VRECHRG_STEP_UV 100000 +#define SGM4154X_VRECHRG_OFFSET_UV 100000 + +#define SGM4154X_CHRG_CTRL_5 0x05 +#define SGM4154X_TERM_EN BIT(7) +#define SGM4154X_WDT_TIMER_MASK GENMASK(5, 4) + +#define SGM4154X_CHRG_CTRL_6 0x06 +#define SGM4154X_VAC_OVP_MASK GENMASK(7, 6) +#define SGM4154X_OVP_14V (BIT(7) | BIT(6)) +#define SGM4154X_OVP_10_5V BIT(7) +#define SGM4154X_OVP_6_5V BIT(6) +#define SGM4154X_OVP_5_5V 0 +#define SGM4154X_OVP_DEFAULT SGM4154X_OVP_14V +#define SGM4154X_BOOSTV GENMASK(5, 4) +#define SGM4154X_VINDPM_V_MASK GENMASK(3, 0) +#define SGM4154X_VINDPM_V_MIN_UV 3900000 +#define SGM4154X_VINDPM_V_MAX_UV 12000000 +#define SGM4154X_VINDPM_STEP_UV 100000 +#define SGM4154X_VINDPM_DEF_UV 4500000 + +#define SGM4154X_CHRG_CTRL_7 0x07 + +#define SGM4154X_CHRG_STAT 0x08 +#define SGM4154X_VBUS_STAT_MASK GENMASK(7, 5) +#define SGM4154X_OTG_MODE (BIT(7) | BIT(6) | BIT(5)) +#define SGM4154X_NON_STANDARD (BIT(7) | BIT(6)) +#define SGM4154X_UNKNOWN (BIT(7) | BIT(5)) +#define SGM4154X_USB_DCP (BIT(6) | BIT(5)) +#define SGM4154X_USB_CDP BIT(6) +#define SGM4154X_USB_SDP BIT(5) +#define SGM4154X_NOT_CHRGING 0 +#define SGM4154X_CHG_STAT_MASK GENMASK(4, 3) +#define SGM4154X_TERM_CHRG (BIT(4) | BIT(3)) +#define SGM4154X_FAST_CHRG BIT(4) +#define SGM4154X_PRECHRG BIT(3) +#define SGM4154X_PG_STAT BIT(2) +#define SGM4154X_THERM_STAT BIT(1) +#define SGM4154X_VSYS_STAT BIT(0) + +#define SGM4154X_CHRG_FAULT 0x09 +#define SGM4154X_TEMP_MASK GENMASK(2, 0) +#define SGM4154X_TEMP_HOT (BIT(2) | BIT(1)) +#define SGM4154X_TEMP_COLD (BIT(2) | BIT(0)) +#define SGM4154X_TEMP_COOL (BIT(1) | BIT(0)) +#define SGM4154X_TEMP_WARM BIT(1) +#define SGM4154X_TEMP_NORMAL BIT(0) + +#define SGM4154X_CHRG_CTRL_A 0x0a +#define SGM4154X_VBUS_GOOD BIT(7) +#define SGM4154X_VINDPM_INT_MASK BIT(1) +#define SGM4154X_IINDPM_INT_MASK BIT(0) + +#define SGM4154X_CHRG_CTRL_B 0x0b +#define SGM4154X_PN_ID (BIT(6) | BIT(5) | BIT(3)) +#define SGM4154X_PN_MASK GENMASK(6, 3) + +#define SGM4154X_CHRG_CTRL_C 0x0c + +#define SGM4154X_CHRG_CTRL_D 0x0d +#define SGM4154X_JEITA_EN BIT(0) + +#define SGM4154X_INPUT_DET 0x0e +#define SGM4154X_DPDM_ONGOING BIT(7) + +#define SGM4154X_CHRG_CTRL_F 0x0f +#define SGM4154X_VINDPM_OS_MASK GENMASK(1, 0) + +#define SGM4154X_DEFAULT_INPUT_CUR (500 * 1000) + +struct sgm4154x_init_data { + int ilim; /* input current limit */ + int vlim; /* minimum system voltage limit */ + int iterm; /* termination current */ + int iprechg; /* precharge current */ + int max_ichg; /* maximum charge current */ + int max_vreg; /* maximum charge voltage */ +}; + +struct sgm4154x_state { + bool vsys_stat; + bool therm_stat; + bool online; + u8 chrg_stat; + bool chrg_en; + bool vbus_gd; + u8 chrg_type; + u8 health; + u8 chrg_fault; + u8 ntc_fault; +}; + +struct sgm4154x_device { + struct device *dev; + struct power_supply *charger; + struct regmap *regmap; + struct sgm4154x_init_data init_data; + struct sgm4154x_state state; + struct regulator_dev *otg_rdev; + struct mutex lock; + bool watchdog_enable; + struct workqueue_struct *sgm_monitor_wq; + struct delayed_work sgm_delay_work; +}; + +enum SGM4154X_VINDPM_OS { + VINDPM_OS_3900MV, + VINDPM_OS_5900MV, + VINDPM_OS_7500MV, + VINDPM_OS_10500MV, +}; + +enum sgm4154x_wdt_values { + SGM4154X_WDT_TIMER_DISABLE = 0, + SGM4154X_WDT_TIMER_40S = 1, + SGM4154X_WDT_TIMER_80S = 2, + SGM4154X_WDT_TIMER_160S = 3, +}; + +static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_TERMCHRG_I_MIN_UA, SGM4154X_TERMCHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_TERMCHRG_I_MIN_UA) / SGM4154X_TERMCHRG_CURRENT_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_TERMCHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set term current error!\n"); + + return ret; +} + +static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_PRECHRG_I_MIN_UA, SGM4154X_PRECHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_PRECHRG_I_MIN_UA) / SGM4154X_PRECHRG_CURRENT_STEP_UA; + + reg_val = FIELD_PREP(SGM4154X_PRECHRG_CUR_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_PRECHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set precharge current error!\n"); + + return ret; +} + +static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_ICHRG_I_MIN_UA, sgm->init_data.max_ichg); + reg_val = cur_ua / SGM4154X_ICHRG_I_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_2, + SGM4154X_ICHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set icharge current error!\n"); + + return ret; +} + +static int sgm4154x_get_ichrg_curr(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_2, ®); + if (ret) { + dev_err(sgm->dev, "get charge current error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_ICHRG_CUR_MASK, reg); + + return val * SGM4154X_ICHRG_I_STEP_UA; +} + +static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt) +{ + int reg_val; + int ret; + + /* + * Note that the value of 0x01111 represents a "special value" + * corresponding to 4352000uV instead of the expected 4336000uV, + * per the datasheet. All other values are as expected. So not + * only do we need to clamp between max and min values, but + * also clamp anything below 4352000uv to 4304000uv to prevent + * overcharging. + */ + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, sgm->init_data.max_vreg); + if (chrg_volt < 4352000) + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, 4304000); + reg_val = (chrg_volt - SGM4154X_VREG_V_MIN_UV) / SGM4154X_VREG_V_STEP_UV; + reg_val = FIELD_PREP(SGM4154X_VREG_V_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VREG_V_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set charge voltage error!\n"); + + return ret; +} + +static int sgm4154x_get_chrg_volt(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_4, ®); + if (ret) { + dev_err(sgm->dev, "get charge voltage error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_VREG_V_MASK, reg); + + /* + * 0x01111 is a special value meaning 4352000uV, all other + * values are as expected based on the offset and step values. + */ + if (val == 0x0f) + return 4352000; + + return val * SGM4154X_VREG_V_STEP_UV + SGM4154X_VREG_V_MIN_UV; +} + +static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm, + unsigned int vindpm) +{ + enum SGM4154X_VINDPM_OS os_val; + unsigned int offset; + u8 reg_val; + int ret; + + + if (vindpm < SGM4154X_VINDPM_V_MIN_UV || + vindpm > SGM4154X_VINDPM_V_MAX_UV) { + dev_err(sgm->dev, "input voltage limit %u outside range\n", vindpm); + return -EINVAL; + } + + /* + * Supported ranges per the datasheet are as follows: + * 3.9v - 5.4v with a 3.9v offset + * 5.9v - 7.4v with a 5.9v offset + * 7.5v - 9.0v with a 7.5v offset + * 10.5v - 12.0v with a 10.5v offset + * Step size is a constant 100mv + */ + if (vindpm < 5900000) { + offset = 3900000; + vindpm = clamp(vindpm, offset, 5400000); + os_val = VINDPM_OS_3900MV; + } else if (vindpm < 7500000) { + offset = 5900000; + vindpm = clamp(vindpm, offset, 7400000); + os_val = VINDPM_OS_5900MV; + } else if (vindpm < 10500000) { + offset = 7500000; + vindpm = clamp(vindpm, offset, 9000000); + os_val = VINDPM_OS_7500MV; + } else { + offset = 10500000; + vindpm = clamp(vindpm, offset, 12000000); + os_val = VINDPM_OS_10500MV; + } + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_F, + SGM4154X_VINDPM_OS_MASK, + os_val); + if (ret) { + dev_err(sgm->dev, "set vin dpm error!\n"); + return ret; + } + + reg_val = (vindpm - offset) / SGM4154X_VINDPM_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, SGM4154X_CHRG_CTRL_6, + SGM4154X_VINDPM_V_MASK, reg_val); + if (ret) + dev_err(sgm->dev, "input voltage error!\n"); + + return ret; +} + +static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm) +{ + int reg_val; + int ret; + + if (iindpm < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Protect against a timing-issue edge case if a sysfs write occurs in the middle + * of a probe (very unlikely). + */ + if (sgm->init_data.ilim < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Per the datasheet, values between 100000uA and 3100000uA work + * as expected with the register defined as having a step of + * 100000 and a min/max of 100000 (0x00) through 3100000 (0x1e). + * The register value of 0x1f however corresponds to 3800000uA not + * 3200000uA as one would expect. + */ + if ((iindpm > SGM4154X_IINDPM_I_MAX_UA) || (iindpm > sgm->init_data.ilim)) + iindpm = min(SGM4154X_IINDPM_I_MAX_UA, sgm->init_data.ilim); + + if (iindpm > 3100000 && iindpm < SGM4154X_IINDPM_I_MAX_UA) + iindpm = 3100000; + + if (iindpm == SGM4154X_IINDPM_I_MAX_UA) + reg_val = 0x1f; + else + reg_val = (iindpm - SGM4154X_IINDPM_I_MIN_UA) / SGM4154X_IINDPM_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_0, + SGM4154X_IINDPM_I_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set input current limit error!\n"); + + return ret; +} + +static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm) +{ + int ret; + int ilim; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_0, &ilim); + if (ret) { + dev_err(sgm->dev, "get input current limit error!\n"); + return ret; + } + + ilim &= SGM4154X_IINDPM_I_MASK; + + /* Max value is not 3200000uA as expected but is 3800000uA */ + if (ilim == SGM4154X_IINDPM_I_MASK) + return SGM4154X_IINDPM_I_MAX_UA; + + ilim = ilim * SGM4154X_IINDPM_STEP_UA + SGM4154X_IINDPM_I_MIN_UA; + + return ilim; +} + +static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_WDT_RST, + SGM4154X_WDT_RST); + + if (ret) + dev_err(sgm->dev, "set watchdog timer error!\n"); + + return ret; +} + +static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, u8 time) +{ + int ret; + + if (time > SGM4154X_WDT_TIMER_160S) + return -EINVAL; + + time = FIELD_PREP(SGM4154X_WDT_TIMER_MASK, time); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_5, + SGM4154X_WDT_TIMER_MASK, + time); + + if (ret) { + dev_err(sgm->dev, "set watchdog timer error!\n"); + return ret; + } + + if (time) { + if (!sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); + sgm->watchdog_enable = true; + } else { + sgm->watchdog_enable = false; + sgm4154x_watchdog_timer_reset(sgm); + } + + return ret; +} + +static int sgm4154x_enable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + SGM4154X_CHRG_EN); + if (ret) + dev_err(sgm->dev, "enable charger error!\n"); + + return ret; +} + +static int sgm4154x_disable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + 0); + if (ret) + dev_err(sgm->dev, "disable charger error!\n"); + + return ret; +} + +static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm) +{ + int reg_val; + int ret; + + reg_val = SGM4154X_OVP_DEFAULT & SGM4154X_VAC_OVP_MASK; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_6, + SGM4154X_VAC_OVP_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set vac ovp error!\n"); + + return ret; +} + +static int sgm4154x_set_recharge_volt_ua(struct sgm4154x_device *sgm, int recharge_volt) +{ + int reg_val; + int ret; + + reg_val = (recharge_volt - SGM4154X_VRECHRG_OFFSET_UV) / SGM4154X_VRECHRG_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VRECHARGE, + reg_val); + if (ret) + dev_err(sgm->dev, "set recharger error!\n"); + + return ret; +} + +static int sgm4154x_get_state(struct sgm4154x_device *sgm, + struct sgm4154x_state *state) +{ + int reg, ret; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_STAT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_STAT fail\n"); + return ret; + } + state->chrg_type = reg & SGM4154X_VBUS_STAT_MASK; + state->chrg_stat = reg & SGM4154X_CHG_STAT_MASK; + state->online = !!(reg & SGM4154X_PG_STAT); + state->therm_stat = !!(reg & SGM4154X_THERM_STAT); + state->vsys_stat = !!(reg & SGM4154X_VSYS_STAT); + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_FAULT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_FAULT fail\n"); + return ret; + } + state->chrg_fault = reg; + state->ntc_fault = reg & SGM4154X_TEMP_MASK; + state->health = state->ntc_fault; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_A, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_CTRL_A fail\n"); + return ret; + } + state->vbus_gd = !!(reg & SGM4154X_VBUS_GOOD); + + return ret; +} + +static int sgm4154x_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_ONLINE: + return true; + default: + return false; + } +} + +static int sgm4154x_charger_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + int ret = -EINVAL; + + guard(mutex)(&sgm->lock); + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval) { + ret = sgm4154x_enable_charger(sgm); + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S); + } else { + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + ret = sgm4154x_disable_charger(sgm); + } + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = sgm4154x_set_input_curr_lim(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = sgm4154x_set_ichrg_curr(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = sgm4154x_set_chrg_volt(sgm, val->intval); + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int sgm4154x_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + struct sgm4154x_state state; + int ret; + + ret = sgm4154x_get_state(sgm, &state); + if (ret) { + dev_err(sgm->dev, "get state error!\n"); + return ret; + } + sgm->state = state; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.chrg_type || (state.chrg_type == SGM4154X_OTG_MODE)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (!state.chrg_stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.chrg_stat == SGM4154X_TERM_CHRG) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + switch (state.chrg_stat) { + case SGM4154X_PRECHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_FAST_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case SGM4154X_TERM_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_NOT_CHRGING: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = SGM4154X_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = SGM4154X_NAME; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = state.vbus_gd; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = sgm4154x_get_chrg_volt(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = sgm4154x_get_ichrg_curr(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + val->intval = sgm->init_data.vlim; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = sgm4154x_get_input_curr_lim(sgm); + if (val->intval < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return ret; +} + +static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private) +{ + struct sgm4154x_device *sgm4154x = private; + struct sgm4154x_state oldstate = sgm4154x->state; + struct sgm4154x_state state; + int ret; + + ret = sgm4154x_get_state(sgm4154x, &state); + if (ret) { + dev_err(sgm4154x->dev, "get state error!\n"); + return IRQ_NONE; + } + + sgm4154x->state = state; + + if (state.vbus_gd && !oldstate.vbus_gd) { + if (sgm4154x->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) { + ret = sgm4154x_set_input_curr_lim(sgm4154x, + sgm4154x->init_data.ilim); + if (ret) { + dev_err(sgm4154x->dev, "set input current error!\n"); + /* + * Reading IRQ clears interrupt, so return handled + * even if error. + */ + } + } + } + + power_supply_changed(sgm4154x->charger); + + return IRQ_HANDLED; +} + +static enum power_supply_property sgm4154x_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_PRESENT +}; + +static struct power_supply_desc sgm4154x_power_supply_desc = { + .name = "sgm4154x-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sgm4154x_power_supply_props, + .num_properties = ARRAY_SIZE(sgm4154x_power_supply_props), + .get_property = sgm4154x_charger_get_property, + .set_property = sgm4154x_charger_set_property, + .property_is_writeable = sgm4154x_property_is_writeable, +}; + +static const struct regmap_config sgm4154x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SGM4154X_CHRG_CTRL_F, + .cache_type = REGCACHE_NONE, +}; + +static int sgm4154x_power_supply_init(struct sgm4154x_device *sgm, + struct device *dev) +{ + struct power_supply_config psy_cfg = { .drv_data = sgm, + .fwnode = dev->fwnode, }; + + sgm->charger = devm_power_supply_register(sgm->dev, + &sgm4154x_power_supply_desc, + &psy_cfg); + if (IS_ERR(sgm->charger)) + return PTR_ERR(sgm->charger); + + return 0; +} + +static int sgm4154x_hw_init(struct sgm4154x_device *sgm) +{ + struct power_supply_battery_info *bat_info; + int ret; + u32 val; + + /* + * If unable to read devicetree info, use default/reset + * values from hardware. If the devicetree info has data out + * of range for any of the values, use the default value. + */ + sgm->init_data.iprechg = SGM4154X_PRECHRG_I_DEF_UA; + sgm->init_data.iterm = SGM4154X_TERMCHRG_I_DEF_UA; + sgm->init_data.max_ichg = SGM4154X_ICHRG_I_DEF_UA; + sgm->init_data.max_vreg = SGM4154X_VREG_V_DEF_UV; + sgm->init_data.vlim = SGM4154X_VINDPM_DEF_UV; + sgm->init_data.ilim = SGM4154X_IINDPM_DEF_UA; + + ret = power_supply_get_battery_info(sgm->charger, &bat_info); + if (ret) + dev_warn(sgm->dev, "sgm4154x: cannot read battery info\n"); + else { + if ((bat_info->constant_charge_current_max_ua >= SGM4154X_ICHRG_I_MIN_UA) && + (bat_info->constant_charge_current_max_ua <= SGM4154X_ICHRG_I_MAX_UA)) + sgm->init_data.max_ichg = bat_info->constant_charge_current_max_ua; + if ((bat_info->constant_charge_voltage_max_uv >= SGM4154X_VREG_V_MIN_UV) && + (bat_info->constant_charge_voltage_max_uv <= SGM4154X_VREG_V_MAX_UV)) + sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv; + if ((bat_info->charge_term_current_ua >= SGM4154X_TERMCHRG_I_MIN_UA) && + (bat_info->charge_term_current_ua <= SGM4154X_TERMCHRG_I_MAX_UA)) + sgm->init_data.iterm = bat_info->charge_term_current_ua; + if ((bat_info->precharge_current_ua >= SGM4154X_PRECHRG_I_MIN_UA) && + (bat_info->precharge_current_ua <= SGM4154X_PRECHRG_I_MAX_UA)) + sgm->init_data.iprechg = bat_info->precharge_current_ua; + + power_supply_put_battery_info(sgm->charger, bat_info); + } + + ret = device_property_read_u32(sgm->dev, + "input-voltage-limit-microvolt", + &val); + if (!ret) + sgm->init_data.vlim = clamp(val, SGM4154X_VINDPM_V_MIN_UV, + SGM4154X_VINDPM_V_MAX_UV); + + ret = device_property_read_u32(sgm->dev, + "input-current-limit-microamp", + &val); + if (!ret) + sgm->init_data.ilim = clamp(val, SGM4154X_IINDPM_I_MIN_UA, + SGM4154X_IINDPM_I_MAX_UA); + + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + if (ret) + return ret; + + ret = sgm4154x_set_prechrg_curr(sgm, sgm->init_data.iprechg); + if (ret) + return ret; + + ret = sgm4154x_set_chrg_volt(sgm, sgm->init_data.max_vreg); + if (ret) + return ret; + + ret = sgm4154x_set_term_curr(sgm, sgm->init_data.iterm); + if (ret) + return ret; + + ret = sgm4154x_set_ichrg_curr(sgm, sgm->init_data.max_ichg); + if (ret) + return ret; + + ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim); + if (ret) + return ret; + + ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim); + if (ret) + return ret; + + ret = sgm4154x_set_vac_ovp(sgm); + if (ret) + return ret; + + regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_D, + SGM4154X_JEITA_EN, + 0); + + regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_IINDPM_INT_MASK, + SGM4154X_IINDPM_INT_MASK); + + regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_VINDPM_INT_MASK, + SGM4154X_VINDPM_INT_MASK); + + /* + * Recharge microvolt set to 200000 by BSP driver instead of hardware + * default value of 100000. + */ + ret = sgm4154x_set_recharge_volt_ua(sgm, 200000); + + return ret; +} + +static const u32 sgm4154x_chg_otg_cur_ua[] = { + 1200000, 2000000, +}; + +static const struct regulator_ops sgm4154x_vbus_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .get_current_limit = regulator_get_current_limit_regmap, +}; + +static const struct regulator_desc sgm4154x_otg_rdesc = { + .of_match = "otg-vbus", + .name = "otg-vbus", + .regulators_node = of_match_ptr("regulators"), + .ops = &sgm4154x_vbus_ops, + .owner = THIS_MODULE, + .type = REGULATOR_VOLTAGE, + .min_uV = 4850000, + .uV_step = 150000, + .n_voltages = 4, + .vsel_reg = SGM4154X_CHRG_CTRL_6, + .vsel_mask = SGM4154X_BOOSTV, + .enable_reg = SGM4154X_CHRG_CTRL_1, + .enable_mask = SGM4154X_OTG_EN, + .curr_table = sgm4154x_chg_otg_cur_ua, + .n_current_limits = ARRAY_SIZE(sgm4154x_chg_otg_cur_ua), + .csel_reg = SGM4154X_CHRG_CTRL_2, + .csel_mask = SGM4154X_BOOST_LIM, +}; + +static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm) +{ + struct regulator_config config = { + .dev = sgm->dev, + .regmap = sgm->regmap, + .driver_data = sgm, + }; + + sgm->otg_rdev = devm_regulator_register(sgm->dev, + &sgm4154x_otg_rdesc, + &config); + + return PTR_ERR_OR_ZERO(sgm->otg_rdev); +} + +static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm) +{ + int ret; + int val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_B, &val); + if (ret) + return ret; + + if ((val & SGM4154X_PN_MASK) != SGM4154X_PN_ID) + dev_warn(sgm->dev, "sgm4154x device ID mismatch\n"); + + return 0; +} + +static void sgm_charger_work(struct work_struct *work) +{ + struct sgm4154x_device *sgm = + container_of(work, + struct sgm4154x_device, + sgm_delay_work.work); + + sgm4154x_watchdog_timer_reset(sgm); + if (sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); +} + +static void sgm4154x_disable_irq_wake(void *data) +{ + struct sgm4154x_device *sgm = data; + struct i2c_client *client = to_i2c_client(sgm->dev); + + disable_irq_wake(client->irq); +} + +static int sgm4154x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct sgm4154x_device *sgm; + int ret; + + sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL); + if (!sgm) + return -ENOMEM; + + sgm->dev = dev; + + sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config); + if (IS_ERR(sgm->regmap)) + return dev_err_probe(dev, PTR_ERR(sgm->regmap), + "Failed to allocate register map\n"); + + i2c_set_clientdata(client, sgm); + + ret = devm_mutex_init(dev, &sgm->lock); + if (ret) + return ret; + + ret = sgm4154x_hw_chipid_detect(sgm); + if (ret) + return dev_err_probe(dev, ret, "Unable to read HW ID\n"); + + devm_device_init_wakeup(dev); + + sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq", + WQ_MEM_RECLAIM | WQ_FREEZABLE); + if (!sgm->sgm_monitor_wq) + return -EINVAL; + + ret = devm_delayed_work_autocancel(dev, &sgm->sgm_delay_work, + sgm_charger_work); + if (ret) + return dev_err_probe(dev, ret, "Unable to register delayed work\n"); + + ret = sgm4154x_power_supply_init(sgm, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register power supply\n"); + + ret = sgm4154x_hw_init(sgm); + if (ret) + return dev_err_probe(dev, ret, "Cannot initialize the chip.\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + sgm4154x_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "sgm41542-irq", sgm); + if (ret) + return ret; + + ret = enable_irq_wake(client->irq); + if (!ret) { + ret = devm_add_action_or_reset(dev, sgm4154x_disable_irq_wake, sgm); + if (ret) + return ret; + } + } + + ret = sgm4154x_vbus_regulator_register(sgm); + if (ret) { + return dev_err_probe(dev, ret, + "Unable to register VBUS regulator\n"); + } + + return 0; +} + +static int __maybe_unused sgm4154x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + + /* + * Disable watchdog during suspend and stop delayed work. When + * delayed work is restarted after resume watchdog will be + * re-enabled if it was previously enabled. + */ + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + cancel_delayed_work_sync(&sgm->sgm_delay_work); + return 0; +} + +static int __maybe_unused sgm4154x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, 0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(sgm4154x_pm_ops, sgm4154x_suspend, sgm4154x_resume); + +static const struct i2c_device_id sgm4154x_i2c_ids[] = { + { "sgm41542" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids); + +static const struct of_device_id sgm4154x_of_match[] = { + { .compatible = "sgmicro,sgm41542", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sgm4154x_of_match); + +static struct i2c_driver sgm4154x_driver = { + .driver = { + .name = "sgm4154x-charger", + .of_match_table = sgm4154x_of_match, + .pm = &sgm4154x_pm_ops, + }, + .probe = sgm4154x_probe, + .id_table = sgm4154x_i2c_ids, +}; + +module_i2c_driver(sgm4154x_driver); + +MODULE_AUTHOR("Xu Shengfei "); +MODULE_DESCRIPTION("sgm4154x charger driver"); +MODULE_LICENSE("GPL"); -- 2.43.0 From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 939B3C4451C for ; Tue, 21 Jul 2026 17:11:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=AujsVM5TIMJr0ZbUmnriY2hb0YzbYlkkGPqWcfcCWKQ=; b=urRDI1n6W3TeAv FtVZ27dOUJx35PK+ZfHmDQ9zaDHoVEupkW2wbdbRLuzU4sCrv4ifStXM/CYfP2QSLHyZM0q5PtOfe P3z1dSlqOl8o9nrUPcvbxxN2i+cyjrViDt4N7n8UBCchQ72BB1kRe5GTyVwGotjNKD7EjznHcEg8F G53j33gGxHjCvC2onAD1SdYJeO7k9uZyKlt/+DblTJMU2Hzi9yFTXTANf45mf+8g7DM6jzpdW9tE7 Qo9vbUOG27cFZQr/pxdNjOkixAQBPhq+DZbkPQKzaBUIgCQIlk+PVWeshP9MyYvsyUu2dM0oxwDZB HhLsV6+GoqtGasWYxSYA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.99.1 #2 (Red Hat Linux)) id 1wmE0H-0000000A1HE-1lOk; Tue, 21 Jul 2026 17:11:37 +0000 Received: from mail-oi1-x22b.google.com ([2607:f8b0:4864:20::22b]) by bombadil.infradead.org with esmtps (Exim 4.99.1 #2 (Red Hat Linux)) id 1wmE0C-0000000A1FF-02sg for linux-rockchip@lists.infradead.org; Tue, 21 Jul 2026 17:11:34 +0000 Received: by mail-oi1-x22b.google.com with SMTP id 5614622812f47-489f3611e0cso7614563b6e.1 for ; Tue, 21 Jul 2026 10:11:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784653891; x=1785258691; darn=lists.infradead.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=vqt9YJlx80A2itg1eEVU8Oy06B/azqTbdYj5Xhcs6pQ=; b=nORhvcaEadKu3ny9zLjF74jUtBPkmLSolllbUTNnE94X0XgyAdqITR8Mr/SMngubvx GtomId/zPjeh/DDVP7tOrfOEN0x/3oAQAz6rDkCj25ZuSDSimy6SR7AS5lOPjuV2Vp9F LQSNXStrY5oX6oHMY8/d0MsDaG41sDKfk05fuAN8bsZ+9HVByJ2yaV/M/F3Bk3zXdamz jx47pU6dQ1nsH0YP5bWZyxLhI4R1Rjx13r9QfH2+q3Ke8tjXXxj1ALVg6PYElhmbqxcR z0k3mgeuTBWA7A1nbLwoYR+JJziW2Gq9F2dAe1GT6C1ptfuRgVSyvDGF2X8wwPuVjq9F UD0A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784653891; x=1785258691; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=vqt9YJlx80A2itg1eEVU8Oy06B/azqTbdYj5Xhcs6pQ=; b=fldHNphdOYn7jtsaF4VyywxDE1OzkQFZw3Z6fYf52fgNXhtZlmTxr3irK8XxVOTp2R hQ6QdWLzSxWVOd6AnDd+wrZ8DP62657dctUPckPJFzs6io10stqhfMd2iM+A/6naZH/T VFOvXMyasc9CkgqC1gw8Uwj0jJGJso9pF5ezNxYQff2xiyBKyOLzbHwQIUYSZxTS4TGx u86JuksRJ4IxUQOOC/6JkQJJiXG2R8923drKcGfnMe7UQYGp0fkFrK117Gt2OeJ3X7Ih zRo0Rkfj+wEkU+Te3cYJ7Zx9ZDXn0mecSj72tyy1qeOyU9758s9blFt6omFPcQxI18OP ShMA== X-Gm-Message-State: AOJu0Yx0gD1IWYAUNHzGqrg2tr4rp68svuebnO4v5KWkm56G27Lt9RzN Q5wPAAgMWP3fC9k2X3TpZRnr0PrUQH+i7pCcxOzVVLeJoFGLIkIOP/YS+8PqSg== X-Gm-Gg: AfdE7clw5qZPOYRS24E3PeXcPGpHuAL9S3OtW2lHudHjbFdUAgNFfyhkmMxstLlke7r D9iXKOFTmWrxGpghXIbS/n/5UXbBlPH4us+9bxPfnW2uY8jKkwMV+RNGMtMI2WLsIO6L2XEuMMT XqtYHt5sstLmVZ/vO8YrWQEmyVOxCXmgKH13YH0ScEVlD9cv0YD/vhbFxP+KyH8fxmI9vBqwbb+ Rpm3+WwHFiM5OQI7q3qb1q6z7V1vA8PhcsnkAf0Yja3AOAnbhKW+SkE8LNKbw2a1PbtcOaHKMNe ybh69PY9NXCyNy6/lV52c0pklT20LYI+DTQCbpS9NQHdA0u3TEYXf4KcN+QerokdO9s4NriJNjY ffslLMrpzhkEf01gHGakJeNL82RFTpiH727M2SXsrarAxEKhnwHMLJiPFazMW4oJLfw7nCMZSTu Zn X-Received: by 2002:a05:6808:14d6:b0:4a4:d2b:8e7a with SMTP id 5614622812f47-4a4d05205demr11105730b6e.35.1784653890658; Tue, 21 Jul 2026 10:11:30 -0700 (PDT) Received: from localhost.localdomain ([2600:1700:fb0:1bc0::54]) by smtp.gmail.com with ESMTPSA id 5614622812f47-4a4d3cadb50sm10213953b6e.9.2026.07.21.10.11.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 10:11:29 -0700 (PDT) From: Chris Morgan To: linux-rockchip@lists.infradead.org Cc: devicetree@vger.kernel.org, xsf@rock-chips.com, sre@kernel.org, heiko@sntech.de, conor+dt@kernel.org, krzk+dt@kernel.org, robh@kernel.org, Chris Morgan Subject: [PATCH V8 2/4] power: supply: sgm41542: Add SG Micro sgm41542 charger Date: Tue, 21 Jul 2026 12:09:05 -0500 Message-ID: <20260721170907.131731-3-macroalpha82@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260721170907.131731-1-macroalpha82@gmail.com> References: <20260721170907.131731-1-macroalpha82@gmail.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.9.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260721_101132_131996_A225E8EA X-CRM114-Status: GOOD ( 20.83 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org From: Chris Morgan Add support for the SG Micro SGM41542 charger/boost converter. Driver was adapted from Rockchip BSP driver [1] and confirmed with vendor datasheet [2]. [1] https://github.com/rockchip-linux/kernel/blob/develop-6.6/drivers/power/supply/sgm41542_charger.c [2] https://www.sg-micro.de/rect/assets/1e8de70b-657e-4156-be68-a64fdbe8e418/SGM41541_SGM41542.pdf Signed-off-by: Chris Morgan --- drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/sgm41542_charger.c | 1111 +++++++++++++++++++++++ 3 files changed, 1120 insertions(+) create mode 100644 drivers/power/supply/sgm41542_charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f0ede1cecb6a..dadda6b78a03 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -866,6 +866,14 @@ config CHARGER_S2M devices provide USB power supply information and also required for USB OTG role switching. +config CHARGER_SGM41542 + tristate "SGM41542 charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select REGMAP_I2C + help + Say Y to enable support for the SGM41542 battery charger. + config CHARGER_SMB347 tristate "Summit Microelectronics SMB3XX Battery Charger" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 31fe4a145929..25d23ea3fcbd 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o +obj-$(CONFIG_CHARGER_SGM41542) += sgm41542_charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c new file mode 100644 index 000000000000..97a1e4575d3a --- /dev/null +++ b/drivers/power/supply/sgm41542_charger.c @@ -0,0 +1,1111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Charger driver for SGM4154x + * + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Xu Shengfei + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGM4154X_MANUFACTURER "SGMICRO" +#define SGM4154X_NAME "sgm41542" + +#define SGM4154X_CHRG_CTRL_0 0x00 +#define SGM4154X_HIZ_EN BIT(7) +#define SGM4154X_IINDPM_I_MASK GENMASK(4, 0) +#define SGM4154X_IINDPM_I_MIN_UA 100000 +#define SGM4154X_IINDPM_I_MAX_UA 3800000 +#define SGM4154X_IINDPM_STEP_UA 100000 +#define SGM4154X_IINDPM_DEF_UA 2400000 + +#define SGM4154X_CHRG_CTRL_1 0x01 +#define SGM4154X_WDT_RST BIT(6) +#define SGM4154X_OTG_EN BIT(5) +#define SGM4154X_CHRG_EN BIT(4) + +#define SGM4154X_CHRG_CTRL_2 0x02 +#define SGM4154X_BOOST_LIM BIT(7) +#define SGM4154X_ICHRG_CUR_MASK GENMASK(5, 0) +#define SGM4154X_ICHRG_I_STEP_UA 60000 +#define SGM4154X_ICHRG_I_MIN_UA 0 +#define SGM4154X_ICHRG_I_MAX_UA 3780000 +#define SGM4154X_ICHRG_I_DEF_UA 2040000 + +#define SGM4154X_CHRG_CTRL_3 0x03 +#define SGM4154X_PRECHRG_CUR_MASK GENMASK(7, 4) +#define SGM4154X_PRECHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_PRECHRG_I_MIN_UA 60000 +#define SGM4154X_PRECHRG_I_MAX_UA 780000 +#define SGM4154X_PRECHRG_I_DEF_UA 180000 +#define SGM4154X_TERMCHRG_CUR_MASK GENMASK(3, 0) +#define SGM4154X_TERMCHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_TERMCHRG_I_MIN_UA 60000 +#define SGM4154X_TERMCHRG_I_MAX_UA 960000 +#define SGM4154X_TERMCHRG_I_DEF_UA 180000 + +#define SGM4154X_CHRG_CTRL_4 0x04 +#define SGM4154X_VREG_V_MASK GENMASK(7, 3) +#define SGM4154X_VREG_V_MAX_UV 4624000 +#define SGM4154X_VREG_V_MIN_UV 3856000 +#define SGM4154X_VREG_V_DEF_UV 4208000 +#define SGM4154X_VREG_V_STEP_UV 32000 +#define SGM4154X_VRECHARGE BIT(0) +#define SGM4154X_VRECHRG_STEP_UV 100000 +#define SGM4154X_VRECHRG_OFFSET_UV 100000 + +#define SGM4154X_CHRG_CTRL_5 0x05 +#define SGM4154X_TERM_EN BIT(7) +#define SGM4154X_WDT_TIMER_MASK GENMASK(5, 4) + +#define SGM4154X_CHRG_CTRL_6 0x06 +#define SGM4154X_VAC_OVP_MASK GENMASK(7, 6) +#define SGM4154X_OVP_14V (BIT(7) | BIT(6)) +#define SGM4154X_OVP_10_5V BIT(7) +#define SGM4154X_OVP_6_5V BIT(6) +#define SGM4154X_OVP_5_5V 0 +#define SGM4154X_OVP_DEFAULT SGM4154X_OVP_14V +#define SGM4154X_BOOSTV GENMASK(5, 4) +#define SGM4154X_VINDPM_V_MASK GENMASK(3, 0) +#define SGM4154X_VINDPM_V_MIN_UV 3900000 +#define SGM4154X_VINDPM_V_MAX_UV 12000000 +#define SGM4154X_VINDPM_STEP_UV 100000 +#define SGM4154X_VINDPM_DEF_UV 4500000 + +#define SGM4154X_CHRG_CTRL_7 0x07 + +#define SGM4154X_CHRG_STAT 0x08 +#define SGM4154X_VBUS_STAT_MASK GENMASK(7, 5) +#define SGM4154X_OTG_MODE (BIT(7) | BIT(6) | BIT(5)) +#define SGM4154X_NON_STANDARD (BIT(7) | BIT(6)) +#define SGM4154X_UNKNOWN (BIT(7) | BIT(5)) +#define SGM4154X_USB_DCP (BIT(6) | BIT(5)) +#define SGM4154X_USB_CDP BIT(6) +#define SGM4154X_USB_SDP BIT(5) +#define SGM4154X_NOT_CHRGING 0 +#define SGM4154X_CHG_STAT_MASK GENMASK(4, 3) +#define SGM4154X_TERM_CHRG (BIT(4) | BIT(3)) +#define SGM4154X_FAST_CHRG BIT(4) +#define SGM4154X_PRECHRG BIT(3) +#define SGM4154X_PG_STAT BIT(2) +#define SGM4154X_THERM_STAT BIT(1) +#define SGM4154X_VSYS_STAT BIT(0) + +#define SGM4154X_CHRG_FAULT 0x09 +#define SGM4154X_TEMP_MASK GENMASK(2, 0) +#define SGM4154X_TEMP_HOT (BIT(2) | BIT(1)) +#define SGM4154X_TEMP_COLD (BIT(2) | BIT(0)) +#define SGM4154X_TEMP_COOL (BIT(1) | BIT(0)) +#define SGM4154X_TEMP_WARM BIT(1) +#define SGM4154X_TEMP_NORMAL BIT(0) + +#define SGM4154X_CHRG_CTRL_A 0x0a +#define SGM4154X_VBUS_GOOD BIT(7) +#define SGM4154X_VINDPM_INT_MASK BIT(1) +#define SGM4154X_IINDPM_INT_MASK BIT(0) + +#define SGM4154X_CHRG_CTRL_B 0x0b +#define SGM4154X_PN_ID (BIT(6) | BIT(5) | BIT(3)) +#define SGM4154X_PN_MASK GENMASK(6, 3) + +#define SGM4154X_CHRG_CTRL_C 0x0c + +#define SGM4154X_CHRG_CTRL_D 0x0d +#define SGM4154X_JEITA_EN BIT(0) + +#define SGM4154X_INPUT_DET 0x0e +#define SGM4154X_DPDM_ONGOING BIT(7) + +#define SGM4154X_CHRG_CTRL_F 0x0f +#define SGM4154X_VINDPM_OS_MASK GENMASK(1, 0) + +#define SGM4154X_DEFAULT_INPUT_CUR (500 * 1000) + +struct sgm4154x_init_data { + int ilim; /* input current limit */ + int vlim; /* minimum system voltage limit */ + int iterm; /* termination current */ + int iprechg; /* precharge current */ + int max_ichg; /* maximum charge current */ + int max_vreg; /* maximum charge voltage */ +}; + +struct sgm4154x_state { + bool vsys_stat; + bool therm_stat; + bool online; + u8 chrg_stat; + bool chrg_en; + bool vbus_gd; + u8 chrg_type; + u8 health; + u8 chrg_fault; + u8 ntc_fault; +}; + +struct sgm4154x_device { + struct device *dev; + struct power_supply *charger; + struct regmap *regmap; + struct sgm4154x_init_data init_data; + struct sgm4154x_state state; + struct regulator_dev *otg_rdev; + struct mutex lock; + bool watchdog_enable; + struct workqueue_struct *sgm_monitor_wq; + struct delayed_work sgm_delay_work; +}; + +enum SGM4154X_VINDPM_OS { + VINDPM_OS_3900MV, + VINDPM_OS_5900MV, + VINDPM_OS_7500MV, + VINDPM_OS_10500MV, +}; + +enum sgm4154x_wdt_values { + SGM4154X_WDT_TIMER_DISABLE = 0, + SGM4154X_WDT_TIMER_40S = 1, + SGM4154X_WDT_TIMER_80S = 2, + SGM4154X_WDT_TIMER_160S = 3, +}; + +static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_TERMCHRG_I_MIN_UA, SGM4154X_TERMCHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_TERMCHRG_I_MIN_UA) / SGM4154X_TERMCHRG_CURRENT_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_TERMCHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set term current error!\n"); + + return ret; +} + +static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_PRECHRG_I_MIN_UA, SGM4154X_PRECHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_PRECHRG_I_MIN_UA) / SGM4154X_PRECHRG_CURRENT_STEP_UA; + + reg_val = FIELD_PREP(SGM4154X_PRECHRG_CUR_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_PRECHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set precharge current error!\n"); + + return ret; +} + +static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_ICHRG_I_MIN_UA, sgm->init_data.max_ichg); + reg_val = cur_ua / SGM4154X_ICHRG_I_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_2, + SGM4154X_ICHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set icharge current error!\n"); + + return ret; +} + +static int sgm4154x_get_ichrg_curr(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_2, ®); + if (ret) { + dev_err(sgm->dev, "get charge current error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_ICHRG_CUR_MASK, reg); + + return val * SGM4154X_ICHRG_I_STEP_UA; +} + +static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt) +{ + int reg_val; + int ret; + + /* + * Note that the value of 0x01111 represents a "special value" + * corresponding to 4352000uV instead of the expected 4336000uV, + * per the datasheet. All other values are as expected. So not + * only do we need to clamp between max and min values, but + * also clamp anything below 4352000uv to 4304000uv to prevent + * overcharging. + */ + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, sgm->init_data.max_vreg); + if (chrg_volt < 4352000) + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, 4304000); + reg_val = (chrg_volt - SGM4154X_VREG_V_MIN_UV) / SGM4154X_VREG_V_STEP_UV; + reg_val = FIELD_PREP(SGM4154X_VREG_V_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VREG_V_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set charge voltage error!\n"); + + return ret; +} + +static int sgm4154x_get_chrg_volt(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_4, ®); + if (ret) { + dev_err(sgm->dev, "get charge voltage error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_VREG_V_MASK, reg); + + /* + * 0x01111 is a special value meaning 4352000uV, all other + * values are as expected based on the offset and step values. + */ + if (val == 0x0f) + return 4352000; + + return val * SGM4154X_VREG_V_STEP_UV + SGM4154X_VREG_V_MIN_UV; +} + +static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm, + unsigned int vindpm) +{ + enum SGM4154X_VINDPM_OS os_val; + unsigned int offset; + u8 reg_val; + int ret; + + + if (vindpm < SGM4154X_VINDPM_V_MIN_UV || + vindpm > SGM4154X_VINDPM_V_MAX_UV) { + dev_err(sgm->dev, "input voltage limit %u outside range\n", vindpm); + return -EINVAL; + } + + /* + * Supported ranges per the datasheet are as follows: + * 3.9v - 5.4v with a 3.9v offset + * 5.9v - 7.4v with a 5.9v offset + * 7.5v - 9.0v with a 7.5v offset + * 10.5v - 12.0v with a 10.5v offset + * Step size is a constant 100mv + */ + if (vindpm < 5900000) { + offset = 3900000; + vindpm = clamp(vindpm, offset, 5400000); + os_val = VINDPM_OS_3900MV; + } else if (vindpm < 7500000) { + offset = 5900000; + vindpm = clamp(vindpm, offset, 7400000); + os_val = VINDPM_OS_5900MV; + } else if (vindpm < 10500000) { + offset = 7500000; + vindpm = clamp(vindpm, offset, 9000000); + os_val = VINDPM_OS_7500MV; + } else { + offset = 10500000; + vindpm = clamp(vindpm, offset, 12000000); + os_val = VINDPM_OS_10500MV; + } + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_F, + SGM4154X_VINDPM_OS_MASK, + os_val); + if (ret) { + dev_err(sgm->dev, "set vin dpm error!\n"); + return ret; + } + + reg_val = (vindpm - offset) / SGM4154X_VINDPM_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, SGM4154X_CHRG_CTRL_6, + SGM4154X_VINDPM_V_MASK, reg_val); + if (ret) + dev_err(sgm->dev, "input voltage error!\n"); + + return ret; +} + +static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm) +{ + int reg_val; + int ret; + + if (iindpm < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Protect against a timing-issue edge case if a sysfs write occurs in the middle + * of a probe (very unlikely). + */ + if (sgm->init_data.ilim < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Per the datasheet, values between 100000uA and 3100000uA work + * as expected with the register defined as having a step of + * 100000 and a min/max of 100000 (0x00) through 3100000 (0x1e). + * The register value of 0x1f however corresponds to 3800000uA not + * 3200000uA as one would expect. + */ + if ((iindpm > SGM4154X_IINDPM_I_MAX_UA) || (iindpm > sgm->init_data.ilim)) + iindpm = min(SGM4154X_IINDPM_I_MAX_UA, sgm->init_data.ilim); + + if (iindpm > 3100000 && iindpm < SGM4154X_IINDPM_I_MAX_UA) + iindpm = 3100000; + + if (iindpm == SGM4154X_IINDPM_I_MAX_UA) + reg_val = 0x1f; + else + reg_val = (iindpm - SGM4154X_IINDPM_I_MIN_UA) / SGM4154X_IINDPM_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_0, + SGM4154X_IINDPM_I_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set input current limit error!\n"); + + return ret; +} + +static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm) +{ + int ret; + int ilim; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_0, &ilim); + if (ret) { + dev_err(sgm->dev, "get input current limit error!\n"); + return ret; + } + + ilim &= SGM4154X_IINDPM_I_MASK; + + /* Max value is not 3200000uA as expected but is 3800000uA */ + if (ilim == SGM4154X_IINDPM_I_MASK) + return SGM4154X_IINDPM_I_MAX_UA; + + ilim = ilim * SGM4154X_IINDPM_STEP_UA + SGM4154X_IINDPM_I_MIN_UA; + + return ilim; +} + +static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_WDT_RST, + SGM4154X_WDT_RST); + + if (ret) + dev_err(sgm->dev, "set watchdog timer error!\n"); + + return ret; +} + +static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, u8 time) +{ + int ret; + + if (time > SGM4154X_WDT_TIMER_160S) + return -EINVAL; + + time = FIELD_PREP(SGM4154X_WDT_TIMER_MASK, time); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_5, + SGM4154X_WDT_TIMER_MASK, + time); + + if (ret) { + dev_err(sgm->dev, "set watchdog timer error!\n"); + return ret; + } + + if (time) { + if (!sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); + sgm->watchdog_enable = true; + } else { + sgm->watchdog_enable = false; + sgm4154x_watchdog_timer_reset(sgm); + } + + return ret; +} + +static int sgm4154x_enable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + SGM4154X_CHRG_EN); + if (ret) + dev_err(sgm->dev, "enable charger error!\n"); + + return ret; +} + +static int sgm4154x_disable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + 0); + if (ret) + dev_err(sgm->dev, "disable charger error!\n"); + + return ret; +} + +static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm) +{ + int reg_val; + int ret; + + reg_val = SGM4154X_OVP_DEFAULT & SGM4154X_VAC_OVP_MASK; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_6, + SGM4154X_VAC_OVP_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set vac ovp error!\n"); + + return ret; +} + +static int sgm4154x_set_recharge_volt_ua(struct sgm4154x_device *sgm, int recharge_volt) +{ + int reg_val; + int ret; + + reg_val = (recharge_volt - SGM4154X_VRECHRG_OFFSET_UV) / SGM4154X_VRECHRG_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VRECHARGE, + reg_val); + if (ret) + dev_err(sgm->dev, "set recharger error!\n"); + + return ret; +} + +static int sgm4154x_get_state(struct sgm4154x_device *sgm, + struct sgm4154x_state *state) +{ + int reg, ret; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_STAT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_STAT fail\n"); + return ret; + } + state->chrg_type = reg & SGM4154X_VBUS_STAT_MASK; + state->chrg_stat = reg & SGM4154X_CHG_STAT_MASK; + state->online = !!(reg & SGM4154X_PG_STAT); + state->therm_stat = !!(reg & SGM4154X_THERM_STAT); + state->vsys_stat = !!(reg & SGM4154X_VSYS_STAT); + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_FAULT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_FAULT fail\n"); + return ret; + } + state->chrg_fault = reg; + state->ntc_fault = reg & SGM4154X_TEMP_MASK; + state->health = state->ntc_fault; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_A, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_CTRL_A fail\n"); + return ret; + } + state->vbus_gd = !!(reg & SGM4154X_VBUS_GOOD); + + return ret; +} + +static int sgm4154x_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_ONLINE: + return true; + default: + return false; + } +} + +static int sgm4154x_charger_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + int ret = -EINVAL; + + guard(mutex)(&sgm->lock); + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval) { + ret = sgm4154x_enable_charger(sgm); + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S); + } else { + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + ret = sgm4154x_disable_charger(sgm); + } + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = sgm4154x_set_input_curr_lim(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = sgm4154x_set_ichrg_curr(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = sgm4154x_set_chrg_volt(sgm, val->intval); + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int sgm4154x_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + struct sgm4154x_state state; + int ret; + + ret = sgm4154x_get_state(sgm, &state); + if (ret) { + dev_err(sgm->dev, "get state error!\n"); + return ret; + } + sgm->state = state; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.chrg_type || (state.chrg_type == SGM4154X_OTG_MODE)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (!state.chrg_stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.chrg_stat == SGM4154X_TERM_CHRG) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + switch (state.chrg_stat) { + case SGM4154X_PRECHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_FAST_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case SGM4154X_TERM_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_NOT_CHRGING: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = SGM4154X_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = SGM4154X_NAME; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = state.vbus_gd; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = sgm4154x_get_chrg_volt(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = sgm4154x_get_ichrg_curr(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + val->intval = sgm->init_data.vlim; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = sgm4154x_get_input_curr_lim(sgm); + if (val->intval < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return ret; +} + +static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private) +{ + struct sgm4154x_device *sgm4154x = private; + struct sgm4154x_state oldstate = sgm4154x->state; + struct sgm4154x_state state; + int ret; + + ret = sgm4154x_get_state(sgm4154x, &state); + if (ret) { + dev_err(sgm4154x->dev, "get state error!\n"); + return IRQ_NONE; + } + + sgm4154x->state = state; + + if (state.vbus_gd && !oldstate.vbus_gd) { + if (sgm4154x->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) { + ret = sgm4154x_set_input_curr_lim(sgm4154x, + sgm4154x->init_data.ilim); + if (ret) { + dev_err(sgm4154x->dev, "set input current error!\n"); + /* + * Reading IRQ clears interrupt, so return handled + * even if error. + */ + } + } + } + + power_supply_changed(sgm4154x->charger); + + return IRQ_HANDLED; +} + +static enum power_supply_property sgm4154x_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_PRESENT +}; + +static struct power_supply_desc sgm4154x_power_supply_desc = { + .name = "sgm4154x-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sgm4154x_power_supply_props, + .num_properties = ARRAY_SIZE(sgm4154x_power_supply_props), + .get_property = sgm4154x_charger_get_property, + .set_property = sgm4154x_charger_set_property, + .property_is_writeable = sgm4154x_property_is_writeable, +}; + +static const struct regmap_config sgm4154x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SGM4154X_CHRG_CTRL_F, + .cache_type = REGCACHE_NONE, +}; + +static int sgm4154x_power_supply_init(struct sgm4154x_device *sgm, + struct device *dev) +{ + struct power_supply_config psy_cfg = { .drv_data = sgm, + .fwnode = dev->fwnode, }; + + sgm->charger = devm_power_supply_register(sgm->dev, + &sgm4154x_power_supply_desc, + &psy_cfg); + if (IS_ERR(sgm->charger)) + return PTR_ERR(sgm->charger); + + return 0; +} + +static int sgm4154x_hw_init(struct sgm4154x_device *sgm) +{ + struct power_supply_battery_info *bat_info; + int ret; + u32 val; + + /* + * If unable to read devicetree info, use default/reset + * values from hardware. If the devicetree info has data out + * of range for any of the values, use the default value. + */ + sgm->init_data.iprechg = SGM4154X_PRECHRG_I_DEF_UA; + sgm->init_data.iterm = SGM4154X_TERMCHRG_I_DEF_UA; + sgm->init_data.max_ichg = SGM4154X_ICHRG_I_DEF_UA; + sgm->init_data.max_vreg = SGM4154X_VREG_V_DEF_UV; + sgm->init_data.vlim = SGM4154X_VINDPM_DEF_UV; + sgm->init_data.ilim = SGM4154X_IINDPM_DEF_UA; + + ret = power_supply_get_battery_info(sgm->charger, &bat_info); + if (ret) + dev_warn(sgm->dev, "sgm4154x: cannot read battery info\n"); + else { + if ((bat_info->constant_charge_current_max_ua >= SGM4154X_ICHRG_I_MIN_UA) && + (bat_info->constant_charge_current_max_ua <= SGM4154X_ICHRG_I_MAX_UA)) + sgm->init_data.max_ichg = bat_info->constant_charge_current_max_ua; + if ((bat_info->constant_charge_voltage_max_uv >= SGM4154X_VREG_V_MIN_UV) && + (bat_info->constant_charge_voltage_max_uv <= SGM4154X_VREG_V_MAX_UV)) + sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv; + if ((bat_info->charge_term_current_ua >= SGM4154X_TERMCHRG_I_MIN_UA) && + (bat_info->charge_term_current_ua <= SGM4154X_TERMCHRG_I_MAX_UA)) + sgm->init_data.iterm = bat_info->charge_term_current_ua; + if ((bat_info->precharge_current_ua >= SGM4154X_PRECHRG_I_MIN_UA) && + (bat_info->precharge_current_ua <= SGM4154X_PRECHRG_I_MAX_UA)) + sgm->init_data.iprechg = bat_info->precharge_current_ua; + + power_supply_put_battery_info(sgm->charger, bat_info); + } + + ret = device_property_read_u32(sgm->dev, + "input-voltage-limit-microvolt", + &val); + if (!ret) + sgm->init_data.vlim = clamp(val, SGM4154X_VINDPM_V_MIN_UV, + SGM4154X_VINDPM_V_MAX_UV); + + ret = device_property_read_u32(sgm->dev, + "input-current-limit-microamp", + &val); + if (!ret) + sgm->init_data.ilim = clamp(val, SGM4154X_IINDPM_I_MIN_UA, + SGM4154X_IINDPM_I_MAX_UA); + + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + if (ret) + return ret; + + ret = sgm4154x_set_prechrg_curr(sgm, sgm->init_data.iprechg); + if (ret) + return ret; + + ret = sgm4154x_set_chrg_volt(sgm, sgm->init_data.max_vreg); + if (ret) + return ret; + + ret = sgm4154x_set_term_curr(sgm, sgm->init_data.iterm); + if (ret) + return ret; + + ret = sgm4154x_set_ichrg_curr(sgm, sgm->init_data.max_ichg); + if (ret) + return ret; + + ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim); + if (ret) + return ret; + + ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim); + if (ret) + return ret; + + ret = sgm4154x_set_vac_ovp(sgm); + if (ret) + return ret; + + regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_D, + SGM4154X_JEITA_EN, + 0); + + regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_IINDPM_INT_MASK, + SGM4154X_IINDPM_INT_MASK); + + regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_VINDPM_INT_MASK, + SGM4154X_VINDPM_INT_MASK); + + /* + * Recharge microvolt set to 200000 by BSP driver instead of hardware + * default value of 100000. + */ + ret = sgm4154x_set_recharge_volt_ua(sgm, 200000); + + return ret; +} + +static const u32 sgm4154x_chg_otg_cur_ua[] = { + 1200000, 2000000, +}; + +static const struct regulator_ops sgm4154x_vbus_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .get_current_limit = regulator_get_current_limit_regmap, +}; + +static const struct regulator_desc sgm4154x_otg_rdesc = { + .of_match = "otg-vbus", + .name = "otg-vbus", + .regulators_node = of_match_ptr("regulators"), + .ops = &sgm4154x_vbus_ops, + .owner = THIS_MODULE, + .type = REGULATOR_VOLTAGE, + .min_uV = 4850000, + .uV_step = 150000, + .n_voltages = 4, + .vsel_reg = SGM4154X_CHRG_CTRL_6, + .vsel_mask = SGM4154X_BOOSTV, + .enable_reg = SGM4154X_CHRG_CTRL_1, + .enable_mask = SGM4154X_OTG_EN, + .curr_table = sgm4154x_chg_otg_cur_ua, + .n_current_limits = ARRAY_SIZE(sgm4154x_chg_otg_cur_ua), + .csel_reg = SGM4154X_CHRG_CTRL_2, + .csel_mask = SGM4154X_BOOST_LIM, +}; + +static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm) +{ + struct regulator_config config = { + .dev = sgm->dev, + .regmap = sgm->regmap, + .driver_data = sgm, + }; + + sgm->otg_rdev = devm_regulator_register(sgm->dev, + &sgm4154x_otg_rdesc, + &config); + + return PTR_ERR_OR_ZERO(sgm->otg_rdev); +} + +static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm) +{ + int ret; + int val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_B, &val); + if (ret) + return ret; + + if ((val & SGM4154X_PN_MASK) != SGM4154X_PN_ID) + dev_warn(sgm->dev, "sgm4154x device ID mismatch\n"); + + return 0; +} + +static void sgm_charger_work(struct work_struct *work) +{ + struct sgm4154x_device *sgm = + container_of(work, + struct sgm4154x_device, + sgm_delay_work.work); + + sgm4154x_watchdog_timer_reset(sgm); + if (sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); +} + +static void sgm4154x_disable_irq_wake(void *data) +{ + struct sgm4154x_device *sgm = data; + struct i2c_client *client = to_i2c_client(sgm->dev); + + disable_irq_wake(client->irq); +} + +static int sgm4154x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct sgm4154x_device *sgm; + int ret; + + sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL); + if (!sgm) + return -ENOMEM; + + sgm->dev = dev; + + sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config); + if (IS_ERR(sgm->regmap)) + return dev_err_probe(dev, PTR_ERR(sgm->regmap), + "Failed to allocate register map\n"); + + i2c_set_clientdata(client, sgm); + + ret = devm_mutex_init(dev, &sgm->lock); + if (ret) + return ret; + + ret = sgm4154x_hw_chipid_detect(sgm); + if (ret) + return dev_err_probe(dev, ret, "Unable to read HW ID\n"); + + devm_device_init_wakeup(dev); + + sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq", + WQ_MEM_RECLAIM | WQ_FREEZABLE); + if (!sgm->sgm_monitor_wq) + return -EINVAL; + + ret = devm_delayed_work_autocancel(dev, &sgm->sgm_delay_work, + sgm_charger_work); + if (ret) + return dev_err_probe(dev, ret, "Unable to register delayed work\n"); + + ret = sgm4154x_power_supply_init(sgm, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register power supply\n"); + + ret = sgm4154x_hw_init(sgm); + if (ret) + return dev_err_probe(dev, ret, "Cannot initialize the chip.\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + sgm4154x_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "sgm41542-irq", sgm); + if (ret) + return ret; + + ret = enable_irq_wake(client->irq); + if (!ret) { + ret = devm_add_action_or_reset(dev, sgm4154x_disable_irq_wake, sgm); + if (ret) + return ret; + } + } + + ret = sgm4154x_vbus_regulator_register(sgm); + if (ret) { + return dev_err_probe(dev, ret, + "Unable to register VBUS regulator\n"); + } + + return 0; +} + +static int __maybe_unused sgm4154x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + + /* + * Disable watchdog during suspend and stop delayed work. When + * delayed work is restarted after resume watchdog will be + * re-enabled if it was previously enabled. + */ + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + cancel_delayed_work_sync(&sgm->sgm_delay_work); + return 0; +} + +static int __maybe_unused sgm4154x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, 0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(sgm4154x_pm_ops, sgm4154x_suspend, sgm4154x_resume); + +static const struct i2c_device_id sgm4154x_i2c_ids[] = { + { "sgm41542" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids); + +static const struct of_device_id sgm4154x_of_match[] = { + { .compatible = "sgmicro,sgm41542", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sgm4154x_of_match); + +static struct i2c_driver sgm4154x_driver = { + .driver = { + .name = "sgm4154x-charger", + .of_match_table = sgm4154x_of_match, + .pm = &sgm4154x_pm_ops, + }, + .probe = sgm4154x_probe, + .id_table = sgm4154x_i2c_ids, +}; + +module_i2c_driver(sgm4154x_driver); + +MODULE_AUTHOR("Xu Shengfei "); +MODULE_DESCRIPTION("sgm4154x charger driver"); +MODULE_LICENSE("GPL"); -- 2.43.0 _______________________________________________ Linux-rockchip mailing list Linux-rockchip@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-rockchip