summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Troast <ntroast@codeaurora.org>2016-05-12 18:01:06 -0700
committerKyle Yan <kyan@codeaurora.org>2016-06-17 15:17:27 -0700
commit05de9fa95f9afd923cb45592ab4226ae08ff30be (patch)
tree27982f314d96f625f51536ff13cee27ec4c65c42
parent76fd66f1e920febefe0a7a897061c52f27628c18 (diff)
qcom-charger: introduce SMB138X charger driver
This driver supports the SMB138X charger device. This charger peripheral is common among other chips, therefore the driver uses the smb library to support all common functionality. Register access is provided by the parent device via regmap. Interrupts are controlled by the parent device, and handlers are registered by the SMB138X charger driver. The power supply framework is used to communicate battery and usb properties to userspace and other driver consumers such as fuel gauge and USB. VBUS and VCONN regulators are registered for supporting OTG, and powered Type-C cables respectively. CRs-Fixed: 1023141 Change-Id: I119d33cdfdfc874b5d7f6137618ee3e590c72064 Signed-off-by: Nicholas Troast <ntroast@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/power/qcom-charger/smb138x-charger.txt199
-rw-r--r--drivers/power/qcom-charger/Kconfig12
-rw-r--r--drivers/power/qcom-charger/Makefile1
-rw-r--r--drivers/power/qcom-charger/smb138x-charger.c963
4 files changed, 1175 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/power/qcom-charger/smb138x-charger.txt b/Documentation/devicetree/bindings/power/qcom-charger/smb138x-charger.txt
new file mode 100644
index 000000000000..d41ffcaa4cae
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/qcom-charger/smb138x-charger.txt
@@ -0,0 +1,199 @@
+Qualcomm Technologies, Inc. SMB138X Charger Specific Bindings
+
+SMB138X Charger is an efficient programmable battery charger capable of charging
+a high-capacity lithium-ion battery over micro-USB or USB Type-C ultrafast with
+Quick Charge 2.0, Quick Charge 3.0 support. Wireless charging features full
+A4WP Rezence 1.2, WPC 1.2, and PMA support.
+
+=======================
+Required Node Structure
+=======================
+
+SMB138X Charger must be described in two levels of devices nodes.
+
+==================================
+First Level Node - SMB138X Charger
+==================================
+
+Charger specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: String which indicates the charging mode. Can be one of the
+ following:
+ Standalone/Parallel Master - "qcom,smb138x-charger"
+ Parallel Slave - "qcom,smb138x-parallel-slave"
+
+- qcom,suspend-input
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the charger should not draw
+ current from any of its input sources (USB, DC).
+
+- qcom,fcc-max-ua
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the maximum fast charge current in micro-amps.
+
+- qcom,usb-icl-ua
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the USB input current limit in micro-amps.
+
+- qcom,dc-icl-ua
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the DC input current limit in micro-amps.
+
+================================================
+Second Level Nodes - SMB138X Charger Peripherals
+================================================
+
+Peripheral specific properties:
+- reg
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Address and size of the peripheral's register block.
+
+- interrupts
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Peripheral interrupt specifier.
+
+- interrupt-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Interrupt names. This list must match up 1-to-1 with the
+ interrupts specified in the 'interrupts' property.
+
+=======================================
+Second Level Nodes - SMB138X Regulators
+=======================================
+
+The following regulator nodes are supported:
+"qcom,smb138x-vbus" - Regulator for enabling VBUS
+"qcom,smb138x-vconn" - Regulator for enabling VCONN
+
+- regulator-name
+ Usage: required
+ Value type: <string>
+ Definition: Specifies the name for this regulator.
+
+=======
+Example
+=======
+
+smb138x_charger: qcom,smb138x-charger {
+ compatible = "qcom,qpnp-smb138x-charger";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ qcom,suspend-input;
+ dpdm-supply = <&qusb_phy0>;
+
+ qcom,chgr@1000 {
+ reg = <0x1000 0x100>;
+ interrupts = <0x10 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x10 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x10 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x10 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x10 0x4 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "chg-error",
+ "chg-state-change",
+ "step-chg-state-change",
+ "step-chg-soc-update-fail",
+ "step-chg-soc-update-request";
+ };
+
+ qcom,otg@1100 {
+ reg = <0x1100 0x100>;
+ interrupts = <0x11 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x11 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x11 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x11 0x3 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "otg-fail",
+ "otg-overcurrent",
+ "otg-oc-dis-sw-sts",
+ "testmode-change-detect";
+ };
+
+ qcom,bat-if@1200 {
+ reg = <0x1200 0x100>;
+ interrupts = <0x12 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x12 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x12 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x12 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x12 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x12 0x5 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "bat-temp",
+ "bat-ocp",
+ "bat-ov",
+ "bat-low",
+ "bat-therm-or-id-missing",
+ "bat-terminal-missing";
+ };
+
+ qcom,usb-chgpth@1300 {
+ reg = <0x1300 0x100>;
+ interrupts = <0x13 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x5 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x6 IRQ_TYPE_EDGE_BOTH>,
+ <0x13 0x7 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "usbin-collapse",
+ "usbin-lt-3p6v",
+ "usbin-uv",
+ "usbin-ov",
+ "usbin-plugin",
+ "usbin-src-change",
+ "usbin-icl-change",
+ "type-c-change";
+ };
+
+ qcom,dc-chgpth@1400 {
+ reg = <0x1400 0x100>;
+ interrupts = <0x14 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x14 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x14 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x14 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x14 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x14 0x5 IRQ_TYPE_EDGE_BOTH>,
+ <0x14 0x6 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "dcin-collapse",
+ "dcin-lt-3p6v",
+ "dcin-uv",
+ "dcin-ov",
+ "dcin-plugin",
+ "div2-en-dg",
+ "dcin-icl-change";
+ };
+
+ qcom,chgr-misc@1600 {
+ reg = <0x1600 0x100>;
+ interrupts = <0x16 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x5 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x6 IRQ_TYPE_EDGE_BOTH>,
+ <0x16 0x7 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "wdog-snarl",
+ "wdog-bark",
+ "aicl-fail",
+ "aicl-done",
+ "high-duty-cycle",
+ "input-current-limiting",
+ "temperature-change",
+ "switcher-power-ok";
+ };
+};
diff --git a/drivers/power/qcom-charger/Kconfig b/drivers/power/qcom-charger/Kconfig
index 4a299b66e588..b37853b4f70c 100644
--- a/drivers/power/qcom-charger/Kconfig
+++ b/drivers/power/qcom-charger/Kconfig
@@ -77,6 +77,18 @@ config QPNP_SMB2
help
Enables support for the SMB2 charging peripheral
+config SMB138X_CHARGER
+ tristate "SMB138X Battery Charger"
+ depends on MFD_I2C_PMIC
+ select POWER_SUPPLY
+ help
+ Say Y to include support for SMB138X Battery Charger.
+ SMB1380 is a dual phase 6A battery charger, and SMB1381 is a single
+ phase 5A battery charger.
+ The driver supports charger enable/disable.
+ The driver reports the charger status via the power supply framework.
+ A charger status change triggers an IRQ via the device STAT pin.
+
config QPNP_QNOVO
bool "QPNP QNOVO driver"
depends on SPMI
diff --git a/drivers/power/qcom-charger/Makefile b/drivers/power/qcom-charger/Makefile
index ee2a335079e3..df7b78d4fc52 100644
--- a/drivers/power/qcom-charger/Makefile
+++ b/drivers/power/qcom-charger/Makefile
@@ -6,4 +6,5 @@ obj-$(CONFIG_MSM_BCL_CTL) += msm_bcl.o
obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
obj-$(CONFIG_BATTERY_BCL) += battery_current_limit.o
obj-$(CONFIG_QPNP_SMB2) += qpnp-smb2.o smb-lib.o pmic-voter.o
+obj-$(CONFIG_SMB138X_CHARGER) += smb138x-charger.o smb-lib.o pmic-voter.o
obj-$(CONFIG_QPNP_QNOVO) += qpnp-qnovo.o
diff --git a/drivers/power/qcom-charger/smb138x-charger.c b/drivers/power/qcom-charger/smb138x-charger.c
new file mode 100644
index 000000000000..2b65bf293cd1
--- /dev/null
+++ b/drivers/power/qcom-charger/smb138x-charger.c
@@ -0,0 +1,963 @@
+/* Copyright (c) 2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include "smb-reg.h"
+#include "smb-lib.h"
+#include "pmic-voter.h"
+
+#define SMB138X_DEFAULT_FCC_UA 1000000
+#define SMB138X_DEFAULT_ICL_UA 1500000
+
+static struct smb_params v1_params = {
+ .fcc = {
+ .name = "fast charge current",
+ .reg = FAST_CHARGE_CURRENT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4500000,
+ .step_u = 25000,
+ },
+ .fv = {
+ .name = "float voltage",
+ .reg = FLOAT_VOLTAGE_CFG_REG,
+ .min_u = 2500000,
+ .max_u = 5000000,
+ .step_u = 10000,
+ },
+ .usb_icl = {
+ .name = "usb input current limit",
+ .reg = USBIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+ .dc_icl = {
+ .name = "dc input current limit",
+ .reg = DCIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+};
+
+struct smb_dt_props {
+ bool suspend_input;
+ int fcc_ua;
+ int usb_icl_ua;
+ int dc_icl_ua;
+};
+
+struct smb138x {
+ struct smb_charger chg;
+ struct smb_dt_props dt;
+ struct power_supply *parallel_psy;
+};
+
+static int __debug_mask;
+module_param_named(
+ debug_mask, __debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+static int smb138x_parse_dt(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ int rc;
+
+ if (!node) {
+ pr_err("device tree node missing\n");
+ return -EINVAL;
+ }
+
+ chip->dt.suspend_input = of_property_read_bool(node,
+ "qcom,suspend-input");
+
+ rc = of_property_read_u32(node,
+ "qcom,fcc-max-ua", &chip->dt.fcc_ua);
+ if (rc < 0)
+ chip->dt.fcc_ua = SMB138X_DEFAULT_FCC_UA;
+
+ rc = of_property_read_u32(node,
+ "qcom,usb-icl-ua", &chip->dt.usb_icl_ua);
+ if (rc < 0)
+ chip->dt.usb_icl_ua = SMB138X_DEFAULT_ICL_UA;
+
+ rc = of_property_read_u32(node,
+ "qcom,dc-icl-ua", &chip->dt.dc_icl_ua);
+ if (rc < 0)
+ chip->dt.dc_icl_ua = SMB138X_DEFAULT_ICL_UA;
+
+ return 0;
+}
+
+/************************
+ * USB PSY REGISTRATION *
+ ************************/
+
+static enum power_supply_property smb138x_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_TYPEC_MODE,
+ POWER_SUPPLY_PROP_TYPEC_POWER_ROLE,
+ POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION,
+};
+
+static int smb138x_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smblib_get_prop_usb_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ rc = smblib_get_prop_usb_online(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = chg->voltage_min_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = chg->voltage_max_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = smblib_get_prop_usb_voltage_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_prop_usb_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPE:
+ val->intval = chg->usb_psy_desc.type;
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_MODE:
+ rc = smblib_get_prop_typec_mode(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ rc = smblib_get_prop_typec_power_role(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION:
+ rc = smblib_get_prop_typec_cc_orientation(chg, val);
+ break;
+ default:
+ pr_err("get prop %d is not supported\n", prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_usb_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ rc = smblib_set_prop_usb_voltage_min(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_set_prop_usb_voltage_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_set_prop_usb_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ rc = smblib_set_prop_typec_power_role(chg, val);
+ break;
+ default:
+ pr_err("set prop %d is not supported\n", prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_usb_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int smb138x_init_usb_psy(struct smb138x *chip)
+{
+ struct power_supply_config usb_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ chg->usb_psy_desc.name = "usb";
+ chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
+ chg->usb_psy_desc.properties = smb138x_usb_props;
+ chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb138x_usb_props);
+ chg->usb_psy_desc.get_property = smb138x_usb_get_prop;
+ chg->usb_psy_desc.set_property = smb138x_usb_set_prop;
+ chg->usb_psy_desc.property_is_writeable = smb138x_usb_prop_is_writeable;
+
+ usb_cfg.drv_data = chip;
+ usb_cfg.of_node = chg->dev->of_node;
+ chg->usb_psy = devm_power_supply_register(chg->dev,
+ &chg->usb_psy_desc,
+ &usb_cfg);
+ if (IS_ERR(chg->usb_psy)) {
+ pr_err("Couldn't register USB power supply\n");
+ return PTR_ERR(chg->usb_psy);
+ }
+
+ return 0;
+}
+
+/*************************
+ * BATT PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb138x_batt_props[] = {
+ POWER_SUPPLY_PROP_INPUT_SUSPEND,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int smb138x_batt_get_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ rc = smblib_get_prop_batt_status(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ rc = smblib_get_prop_batt_health(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smblib_get_prop_batt_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_get_prop_input_suspend(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ rc = smblib_get_prop_batt_charge_type(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = smblib_get_prop_batt_capacity(chg, val);
+ break;
+ default:
+ pr_err("batt power supply get prop %d not supported\n",
+ prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_batt_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_set_prop_input_suspend(chg, val);
+ break;
+ default:
+ pr_err("batt power supply set prop %d not supported\n",
+ prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_batt_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc batt_psy_desc = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = smb138x_batt_props,
+ .num_properties = ARRAY_SIZE(smb138x_batt_props),
+ .get_property = smb138x_batt_get_prop,
+ .set_property = smb138x_batt_set_prop,
+ .property_is_writeable = smb138x_batt_prop_is_writeable,
+};
+
+static int smb138x_init_batt_psy(struct smb138x *chip)
+{
+ struct power_supply_config batt_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ batt_cfg.drv_data = chip;
+ batt_cfg.of_node = chg->dev->of_node;
+ chg->batt_psy = devm_power_supply_register(chg->dev,
+ &batt_psy_desc,
+ &batt_cfg);
+ if (IS_ERR(chg->batt_psy)) {
+ pr_err("Couldn't register battery power supply\n");
+ return PTR_ERR(chg->batt_psy);
+ }
+
+ return rc;
+}
+
+/*****************************
+ * PARALLEL PSY REGISTRATION *
+ *****************************/
+
+static enum power_supply_property smb138x_parallel_props[] = {
+ POWER_SUPPLY_PROP_INPUT_SUSPEND,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int smb138x_parallel_get_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_get_usb_suspend(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_charge_param(chg, &chg->param.fcc,
+ &val->intval);
+ break;
+ default:
+ pr_err("parallel power supply get prop %d not supported\n",
+ prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_parallel_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_set_usb_suspend(chg, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval);
+ break;
+ default:
+ pr_err("parallel power supply set prop %d not supported\n",
+ prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_parallel_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ return 0;
+}
+
+static const struct power_supply_desc parallel_psy_desc = {
+ .name = "parallel",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = smb138x_parallel_props,
+ .num_properties = ARRAY_SIZE(smb138x_parallel_props),
+ .get_property = smb138x_parallel_get_prop,
+ .set_property = smb138x_parallel_set_prop,
+ .property_is_writeable = smb138x_parallel_prop_is_writeable,
+};
+
+static int smb138x_init_parallel_psy(struct smb138x *chip)
+{
+ struct power_supply_config parallel_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ parallel_cfg.drv_data = chip;
+ parallel_cfg.of_node = chg->dev->of_node;
+ chip->parallel_psy = devm_power_supply_register(chg->dev,
+ &parallel_psy_desc,
+ &parallel_cfg);
+ if (IS_ERR(chip->parallel_psy)) {
+ pr_err("Couldn't register parallel power supply\n");
+ return PTR_ERR(chip->parallel_psy);
+ }
+
+ return 0;
+}
+
+/******************************
+ * VBUS REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb138x_vbus_reg_ops = {
+ .enable = smblib_vbus_regulator_enable,
+ .disable = smblib_vbus_regulator_disable,
+ .is_enabled = smblib_vbus_regulator_is_enabled,
+};
+
+static int smb138x_init_vbus_regulator(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 0;
+
+ chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg),
+ GFP_KERNEL);
+ if (!chg->vbus_vreg)
+ return -ENOMEM;
+
+ cfg.dev = chg->dev;
+ cfg.driver_data = chip;
+
+ chg->vbus_vreg->rdesc.owner = THIS_MODULE;
+ chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE;
+ chg->vbus_vreg->rdesc.ops = &smb138x_vbus_reg_ops;
+ chg->vbus_vreg->rdesc.of_match = "qcom,smb138x-vbus";
+ chg->vbus_vreg->rdesc.name = "qcom,smb138x-vbus";
+
+ chg->vbus_vreg->rdev = devm_regulator_register(chg->dev,
+ &chg->vbus_vreg->rdesc, &cfg);
+ if (IS_ERR(chg->vbus_vreg->rdev)) {
+ rc = PTR_ERR(chg->vbus_vreg->rdev);
+ chg->vbus_vreg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't register VBUS regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/******************************
+ * VCONN REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb138x_vconn_reg_ops = {
+ .enable = smblib_vconn_regulator_enable,
+ .disable = smblib_vconn_regulator_disable,
+ .is_enabled = smblib_vconn_regulator_is_enabled,
+};
+
+static int smb138x_init_vconn_regulator(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 0;
+
+ chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg),
+ GFP_KERNEL);
+ if (!chg->vconn_vreg)
+ return -ENOMEM;
+
+ cfg.dev = chg->dev;
+ cfg.driver_data = chip;
+
+ chg->vconn_vreg->rdesc.owner = THIS_MODULE;
+ chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE;
+ chg->vconn_vreg->rdesc.ops = &smb138x_vconn_reg_ops;
+ chg->vconn_vreg->rdesc.of_match = "qcom,smb138x-vconn";
+ chg->vconn_vreg->rdesc.name = "qcom,smb138x-vconn";
+
+ chg->vconn_vreg->rdev = devm_regulator_register(chg->dev,
+ &chg->vconn_vreg->rdesc, &cfg);
+ if (IS_ERR(chg->vconn_vreg->rdev)) {
+ rc = PTR_ERR(chg->vconn_vreg->rdev);
+ chg->vconn_vreg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't register VCONN regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/***************************
+ * HARDWARE INITIALIZATION *
+ ***************************/
+
+static int smb138x_init_hw(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc;
+
+ /* votes must be cast before configuring software control */
+ vote(chg->usb_suspend_votable,
+ DEFAULT_VOTER, chip->dt.suspend_input, 0);
+ vote(chg->dc_suspend_votable,
+ DEFAULT_VOTER, chip->dt.suspend_input, 0);
+ vote(chg->fcc_votable,
+ DEFAULT_VOTER, true, chip->dt.fcc_ua);
+ vote(chg->usb_icl_votable,
+ DEFAULT_VOTER, true, chip->dt.usb_icl_ua);
+ vote(chg->dc_icl_votable,
+ DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
+
+ /* configure charge enable for software control; active high */
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure charge enable source rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable the charging path */
+ rc = smblib_enable_charging(chg, true);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * trigger the usb-typec-change interrupt only when the CC state
+ * changes, or there was a VBUS error
+ */
+ rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
+ TYPEC_CCSTATE_CHANGE_INT_EN_BIT
+ | TYPEC_VBUS_ERROR_INT_EN_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure Type-C interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VCONN for software control */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT,
+ VCONN_EN_SRC_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure VCONN for SW control rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VBUS for software control */
+ rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure VBUS for SW control rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure power role for dual-role */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure power role for DRP rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+/****************************
+ * DETERMINE INITIAL STATUS *
+ ****************************/
+
+static int smb138x_determine_initial_status(struct smb138x *chip)
+{
+ struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+
+ smblib_handle_usb_plugin(0, &irq_data);
+ smblib_handle_usb_typec_change(0, &irq_data);
+ smblib_handle_usb_source_change(0, &irq_data);
+
+ return 0;
+}
+
+/**************************
+ * INTERRUPT REGISTRATION *
+ **************************/
+
+struct smb138x_irq_info {
+ const char *name;
+ const irq_handler_t handler;
+};
+
+static const struct smb138x_irq_info smb138x_irqs[] = {
+/* CHARGER IRQs */
+ { "chg-error", smblib_handle_debug },
+ { "chg-state-change", smblib_handle_debug },
+ { "step-chg-state-change", smblib_handle_debug },
+ { "step-chg-soc-update-fail", smblib_handle_debug },
+ { "step-chg-soc-update-request", smblib_handle_debug },
+/* OTG IRQs */
+ { "otg-fail", smblib_handle_debug },
+ { "otg-overcurrent", smblib_handle_debug },
+ { "otg-oc-dis-sw-sts", smblib_handle_debug },
+ { "testmode-change-detect", smblib_handle_debug },
+/* BATTERY IRQs */
+ { "bat-temp", smblib_handle_batt_psy_changed },
+ { "bat-ocp", smblib_handle_batt_psy_changed },
+ { "bat-ov", smblib_handle_batt_psy_changed },
+ { "bat-low", smblib_handle_batt_psy_changed },
+ { "bat-therm-or-id-missing", smblib_handle_batt_psy_changed },
+ { "bat-terminal-missing", smblib_handle_batt_psy_changed },
+/* USB INPUT IRQs */
+ { "usbin-collapse", smblib_handle_debug },
+ { "usbin-lt-3p6v", smblib_handle_debug },
+ { "usbin-uv", smblib_handle_debug },
+ { "usbin-ov", smblib_handle_debug },
+ { "usbin-plugin", smblib_handle_usb_plugin },
+ { "usbin-src-change", smblib_handle_usb_source_change },
+ { "usbin-icl-change", smblib_handle_debug },
+ { "type-c-change", smblib_handle_usb_typec_change },
+/* DC INPUT IRQs */
+ { "dcin-collapse", smblib_handle_debug },
+ { "dcin-lt-3p6v", smblib_handle_debug },
+ { "dcin-uv", smblib_handle_debug },
+ { "dcin-ov", smblib_handle_debug },
+ { "dcin-plugin", smblib_handle_debug },
+ { "div2-en-dg", smblib_handle_debug },
+ { "dcin-icl-change", smblib_handle_debug },
+/* MISCELLANEOUS IRQs */
+ { "wdog-snarl", smblib_handle_debug },
+ { "wdog-bark", smblib_handle_debug },
+ { "aicl-fail", smblib_handle_debug },
+ { "aicl-done", smblib_handle_debug },
+ { "high-duty-cycle", smblib_handle_debug },
+ { "input-current-limiting", smblib_handle_debug },
+ { "temperature-change", smblib_handle_debug },
+ { "switcher-power-ok", smblib_handle_debug },
+};
+
+static int smb138x_get_irq_index_byname(const char *irq_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(smb138x_irqs); i++) {
+ if (strcmp(smb138x_irqs[i].name, irq_name) == 0)
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+static int smb138x_request_interrupt(struct smb138x *chip,
+ struct device_node *node,
+ const char *irq_name)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc, irq, irq_index;
+ struct smb_irq_data *irq_data;
+
+ irq = of_irq_get_byname(node, irq_name);
+ if (irq < 0) {
+ pr_err("Couldn't get irq %s byname\n", irq_name);
+ return irq;
+ }
+
+ irq_index = smb138x_get_irq_index_byname(irq_name);
+ if (irq_index < 0) {
+ pr_err("%s is not a defined irq\n", irq_name);
+ return irq_index;
+ }
+
+ irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL);
+ if (!irq_data)
+ return -ENOMEM;
+
+ irq_data->parent_data = chip;
+ irq_data->name = irq_name;
+
+ rc = devm_request_threaded_irq(chg->dev, irq, NULL,
+ smb138x_irqs[irq_index].handler,
+ IRQF_ONESHOT, irq_name, irq_data);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d\n", irq);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb138x_request_interrupts(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ struct device_node *child;
+ int rc = 0;
+ const char *name;
+ struct property *prop;
+
+ for_each_available_child_of_node(node, child) {
+ of_property_for_each_string(child, "interrupt-names",
+ prop, name) {
+ rc = smb138x_request_interrupt(chip, child, name);
+ if (rc < 0) {
+ pr_err("Coudn't request interrupt %s rc=%d\n",
+ name, rc);
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/*********
+ * PROBE *
+ *********/
+
+static int smb138x_master_probe(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ chg->param = v1_params;
+
+ rc = smblib_init(chg);
+ if (rc < 0) {
+ pr_err("Couldn't initialize smblib rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_vbus_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vbus regulator rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb138x_init_vconn_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vconn regulator rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb138x_init_usb_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_batt_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize batt psy rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_hw(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb138x_request_interrupts(chip);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb138x_slave_probe(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ chg->param = v1_params;
+
+ rc = smblib_init(chg);
+ if (rc < 0) {
+ pr_err("Couldn't initialize smblib rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_parallel_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize parallel psy rc=%d\n", rc);
+ return rc;
+ }
+
+ /* suspend usb input */
+ rc = smblib_set_usb_suspend(chg, true);
+ if (rc < 0) {
+ pr_err("Couldn't suspend USB input rc=%d\n", rc);
+ return rc;
+ }
+
+ /* initialize FCC to 0 */
+ rc = smblib_set_charge_param(chg, &chg->param.fcc, 0);
+ if (rc < 0) {
+ pr_err("Couldn't set 0 FCC rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable the charging path */
+ rc = smblib_enable_charging(chg, true);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure charge enable for software control; active high */
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure charge enable source rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static const struct of_device_id match_table[] = {
+ {
+ .compatible = "qcom,smb138x-charger",
+ .data = (void *) PARALLEL_MASTER
+ },
+ {
+ .compatible = "qcom,smb138x-parallel-slave",
+ .data = (void *) PARALLEL_SLAVE
+ },
+ { },
+};
+
+static int smb138x_probe(struct platform_device *pdev)
+{
+ struct smb138x *chip;
+ const struct of_device_id *id;
+ int rc = 0;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->chg.dev = &pdev->dev;
+ chip->chg.debug_mask = &__debug_mask;
+
+ chip->chg.regmap = dev_get_regmap(chip->chg.dev->parent, NULL);
+ if (!chip->chg.regmap) {
+ pr_err("parent regmap is missing\n");
+ return -EINVAL;
+ }
+
+ id = of_match_device(of_match_ptr(match_table), chip->chg.dev);
+ if (!id) {
+ pr_err("Couldn't find a matching device\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, chip);
+
+ chip->chg.mode = (enum smb_mode) id->data;
+ switch (chip->chg.mode) {
+ case PARALLEL_MASTER:
+ rc = smb138x_master_probe(chip);
+ break;
+ case PARALLEL_SLAVE:
+ rc = smb138x_slave_probe(chip);
+ break;
+ default:
+ pr_err("Couldn't find a matching mode %d\n", chip->chg.mode);
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ pr_info("SMB138X probed successfully mode=%d\n", chip->chg.mode);
+ return rc;
+
+cleanup:
+ platform_set_drvdata(pdev, NULL);
+ return rc;
+}
+
+static int smb138x_remove(struct platform_device *pdev)
+{
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver smb138x_driver = {
+ .driver = {
+ .name = "qcom,smb138x-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ },
+ .probe = smb138x_probe,
+ .remove = smb138x_remove,
+};
+module_platform_driver(smb138x_driver);
+
+MODULE_DESCRIPTION("QPNP SMB138X Charger Driver");
+MODULE_LICENSE("GPL v2");