diff --git a/drivers/power/qti_battery_charger_mmi/Android.mk b/drivers/power/qti_battery_charger_mmi/Android.mk new file mode 100755 index 000000000000..63ecd8572105 --- /dev/null +++ b/drivers/power/qti_battery_charger_mmi/Android.mk @@ -0,0 +1,11 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := qti_battery_charger_mmi.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +KBUILD_OPTIONS_GKI += GKI_OBJ_MODULE_DIR=gki +include $(DLKM_DIR)/AndroidKernelModule.mk + +include $(call first-makefiles-under,$(LOCAL_PATH)) diff --git a/drivers/power/qti_battery_charger_mmi/Kbuild b/drivers/power/qti_battery_charger_mmi/Kbuild new file mode 100755 index 000000000000..b078ca741a82 --- /dev/null +++ b/drivers/power/qti_battery_charger_mmi/Kbuild @@ -0,0 +1,5 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/include + +obj-m += qti_battery_charger_mmi.o diff --git a/drivers/power/qti_battery_charger_mmi/qti_battery_charger_mmi.c b/drivers/power/qti_battery_charger_mmi/qti_battery_charger_mmi.c new file mode 100755 index 000000000000..5b1b2dbaac6e --- /dev/null +++ b/drivers/power/qti_battery_charger_mmi/qti_battery_charger_mmi.c @@ -0,0 +1,1542 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "qti_battery_mmi: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSG_OWNER_BC 32778 +#define MSG_TYPE_REQ_RESP 1 +#define MSG_TYPE_NOTIFY 2 + +/* opcode for battery charger */ +#define BC_SET_NOTIFY_REQ 0x04 +#define BC_NOTIFY_IND 0x07 +#define BC_BATTERY_STATUS_GET 0x30 +#define BC_BATTERY_STATUS_SET 0x31 +#define BC_USB_STATUS_GET 0x32 +#define BC_USB_STATUS_SET 0x33 +#define BC_SHIP_MODE_REQ_SET 0x36 +#define BC_SHUTDOWN_NOTIFY 0x47 +#define BC_GENERIC_NOTIFY 0x80 + +/* Generic definitions */ +#define MAX_STR_LEN 128 +#define BC_WAIT_TIME_MS 1000 +#define DEFAULT_RESTRICT_FCC_UA 1000000 + +enum psy_type { + PSY_TYPE_BATTERY, + PSY_TYPE_USB, + PSY_TYPE_MAX, +}; + +enum ship_mode_type { + SHIP_MODE_PMIC, + SHIP_MODE_PACK_SIDE, +}; + +/* property ids */ +enum battery_property_id { + BATT_STATUS, + BATT_HEALTH, + BATT_PRESENT, + BATT_CHG_TYPE, + BATT_CAPACITY, + BATT_SOH, + BATT_VOLT_OCV, + BATT_VOLT_NOW, + BATT_VOLT_MAX, + BATT_CURR_NOW, + BATT_CHG_CTRL_LIM, + BATT_CHG_CTRL_LIM_MAX, + BATT_TEMP, + BATT_TECHNOLOGY, + BATT_CHG_COUNTER, + BATT_CYCLE_COUNT, + BATT_CHG_FULL_DESIGN, + BATT_CHG_FULL, + BATT_MODEL_NAME, + BATT_TTF_AVG, + BATT_TTE_AVG, + BATT_RESISTANCE, + BATT_POWER_NOW, + BATT_POWER_AVG, + BATT_PROP_MAX, +}; + +enum usb_property_id { + USB_ONLINE, + USB_VOLT_NOW, + USB_VOLT_MAX, + USB_CURR_NOW, + USB_CURR_MAX, + USB_INPUT_CURR_LIMIT, + USB_TYPE, + USB_ADAP_TYPE, + USB_MOISTURE_DET_EN, + USB_MOISTURE_DET_STS, + USB_TEMP, + USB_REAL_TYPE, + USB_TYPEC_COMPLIANT, + USB_PROP_MAX, +}; + +enum { + QTI_POWER_SUPPLY_USB_TYPE_HVDCP = 0x80, + QTI_POWER_SUPPLY_USB_TYPE_HVDCP_3, + QTI_POWER_SUPPLY_USB_TYPE_HVDCP_3P5, +}; + +struct battery_charger_set_notify_msg { + struct pmic_glink_hdr hdr; + u32 battery_id; + u32 power_state; + u32 low_capacity; + u32 high_capacity; +}; + +struct battery_charger_notify_msg { + struct pmic_glink_hdr hdr; + u32 notification; +}; + +struct battery_charger_req_msg { + struct pmic_glink_hdr hdr; + u32 battery_id; + u32 property_id; + u32 value; +}; + +struct battery_charger_resp_msg { + struct pmic_glink_hdr hdr; + u32 property_id; + u32 value; + u32 ret_code; +}; + +struct battery_model_resp_msg { + struct pmic_glink_hdr hdr; + u32 property_id; + char model[MAX_STR_LEN]; +}; + +struct battery_charger_ship_mode_req_msg { + struct pmic_glink_hdr hdr; + u32 ship_mode_type; +}; + +struct psy_state { + struct power_supply *psy; + char *model; + const int *map; + u32 *prop; + u32 prop_count; + u32 opcode_get; + u32 opcode_set; +}; + +struct battery_chg_dev { + struct device *dev; + struct class battery_class; + struct pmic_glink_client *client; + struct mutex rw_lock; + struct completion ack; + struct psy_state psy_list[PSY_TYPE_MAX]; + struct power_supply *combo_psy; + struct power_supply *mmi_psy; + struct dentry *debugfs_dir; + u32 *thermal_levels; + int curr_thermal_level; + int num_thermal_levels; + atomic_t state; + struct work_struct subsys_up_work; + struct work_struct usb_type_work; + int fake_soc; + bool block_tx; + bool ship_mode_en; + bool debug_battery_detected; + struct notifier_block reboot_notifier; + u32 thermal_fcc_ua; + u32 restrict_fcc_ua; + u32 last_fcc_ua; + u32 usb_icl_ua; + bool restrict_chg_en; + /* To track the driver initialization status */ + bool initialized; +}; + +static const int battery_prop_map[BATT_PROP_MAX] = { + [BATT_STATUS] = POWER_SUPPLY_PROP_STATUS, + [BATT_HEALTH] = POWER_SUPPLY_PROP_HEALTH, + [BATT_PRESENT] = POWER_SUPPLY_PROP_PRESENT, + [BATT_CHG_TYPE] = POWER_SUPPLY_PROP_CHARGE_TYPE, + [BATT_CAPACITY] = POWER_SUPPLY_PROP_CAPACITY, + [BATT_VOLT_OCV] = POWER_SUPPLY_PROP_VOLTAGE_OCV, + [BATT_VOLT_NOW] = POWER_SUPPLY_PROP_VOLTAGE_NOW, + [BATT_VOLT_MAX] = POWER_SUPPLY_PROP_VOLTAGE_MAX, + [BATT_CURR_NOW] = POWER_SUPPLY_PROP_CURRENT_NOW, + [BATT_CHG_CTRL_LIM] = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + [BATT_CHG_CTRL_LIM_MAX] = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + [BATT_TEMP] = POWER_SUPPLY_PROP_TEMP, + [BATT_TECHNOLOGY] = POWER_SUPPLY_PROP_TECHNOLOGY, + [BATT_CHG_COUNTER] = POWER_SUPPLY_PROP_CHARGE_COUNTER, + [BATT_CYCLE_COUNT] = POWER_SUPPLY_PROP_CYCLE_COUNT, + [BATT_CHG_FULL_DESIGN] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + [BATT_CHG_FULL] = POWER_SUPPLY_PROP_CHARGE_FULL, + [BATT_MODEL_NAME] = POWER_SUPPLY_PROP_MODEL_NAME, + [BATT_TTF_AVG] = POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + [BATT_TTE_AVG] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + [BATT_POWER_NOW] = POWER_SUPPLY_PROP_POWER_NOW, + [BATT_POWER_AVG] = POWER_SUPPLY_PROP_POWER_AVG, +}; + +static const int usb_prop_map[USB_PROP_MAX] = { + [USB_ONLINE] = POWER_SUPPLY_PROP_ONLINE, + [USB_VOLT_NOW] = POWER_SUPPLY_PROP_VOLTAGE_NOW, + [USB_VOLT_MAX] = POWER_SUPPLY_PROP_VOLTAGE_MAX, + [USB_CURR_NOW] = POWER_SUPPLY_PROP_CURRENT_NOW, + [USB_CURR_MAX] = POWER_SUPPLY_PROP_CURRENT_MAX, + [USB_INPUT_CURR_LIMIT] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + [USB_ADAP_TYPE] = POWER_SUPPLY_PROP_USB_TYPE, + [USB_TEMP] = POWER_SUPPLY_PROP_TEMP, +}; + +/* Standard usb_type definitions similar to power_supply_sysfs.c */ +static const char * const power_supply_usb_type_text[] = { + "Unknown", "SDP", "DCP", "CDP", "ACA", "C", + "PD", "PD_DRP", "PD_PPS", "BrickID" +}; + +/* Custom usb_type definitions */ +static const char * const qc_power_supply_usb_type_text[] = { + "HVDCP", "HVDCP_3", "HVDCP_3P5" +}; + +static int battery_chg_write(struct battery_chg_dev *bcdev, void *data, + int len) +{ + int rc; + + /* + * When the subsystem goes down, it's better to return the last + * known values until it comes back up. Hence, return 0 so that + * pmic_glink_write() is not attempted until pmic glink is up. + */ + if (atomic_read(&bcdev->state) == PMIC_GLINK_STATE_DOWN) { + pr_debug("glink state is down\n"); + return 0; + } + + if (bcdev->debug_battery_detected && bcdev->block_tx) + return 0; + + mutex_lock(&bcdev->rw_lock); + reinit_completion(&bcdev->ack); + rc = pmic_glink_write(bcdev->client, data, len); + if (!rc) { + rc = wait_for_completion_timeout(&bcdev->ack, + msecs_to_jiffies(BC_WAIT_TIME_MS)); + if (!rc) { + pr_err("Error, timed out sending message\n"); + mutex_unlock(&bcdev->rw_lock); + return -ETIMEDOUT; + } + + rc = 0; + } + mutex_unlock(&bcdev->rw_lock); + + return rc; +} + +static int write_property_id(struct battery_chg_dev *bcdev, + struct psy_state *pst, u32 prop_id, u32 val) +{ + struct battery_charger_req_msg req_msg = { { 0 } }; + + req_msg.property_id = prop_id; + req_msg.battery_id = 0; + req_msg.value = val; + req_msg.hdr.owner = MSG_OWNER_BC; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = pst->opcode_set; + + pr_debug("psy: %s prop_id: %u val: %u\n", pst->psy->desc->name, + req_msg.property_id, val); + + return battery_chg_write(bcdev, &req_msg, sizeof(req_msg)); +} + +static int read_property_id(struct battery_chg_dev *bcdev, + struct psy_state *pst, u32 prop_id) +{ + struct battery_charger_req_msg req_msg = { { 0 } }; + + req_msg.property_id = prop_id; + req_msg.battery_id = 0; + req_msg.value = 0; + req_msg.hdr.owner = MSG_OWNER_BC; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = pst->opcode_get; + + pr_debug("psy: %s prop_id: %u\n", pst->psy->desc->name, + req_msg.property_id); + + return battery_chg_write(bcdev, &req_msg, sizeof(req_msg)); +} + +static int get_property_id(struct psy_state *pst, + enum power_supply_property prop) +{ + u32 i; + + for (i = 0; i < pst->prop_count; i++) + if (pst->map[i] == prop) + return i; + + pr_err("No property id for property %d in psy %s\n", prop, + pst->psy->desc->name); + + return -ENOENT; +} + +static void battery_chg_notify_enable(struct battery_chg_dev *bcdev) +{ + struct battery_charger_set_notify_msg req_msg = { { 0 } }; + int rc; + + /* Send request to enable notification */ + req_msg.hdr.owner = MSG_OWNER_BC; + req_msg.hdr.type = MSG_TYPE_NOTIFY; + req_msg.hdr.opcode = BC_SET_NOTIFY_REQ; + + rc = battery_chg_write(bcdev, &req_msg, sizeof(req_msg)); + if (rc < 0) + pr_err("Failed to enable notification rc=%d\n", rc); +} + +static void battery_chg_state_cb(void *priv, enum pmic_glink_state state) +{ + struct battery_chg_dev *bcdev = priv; + + pr_debug("state: %d\n", state); + + atomic_set(&bcdev->state, state); + if (state == PMIC_GLINK_STATE_UP) + schedule_work(&bcdev->subsys_up_work); +} + +static bool validate_message(struct battery_charger_resp_msg *resp_msg, + size_t len) +{ + if (len != sizeof(*resp_msg)) { + pr_err("Incorrect response length %zu for opcode %#x\n", len, + resp_msg->hdr.opcode); + return false; + } + + if (resp_msg->ret_code) { + pr_err("Error in response for opcode %#x prop_id %u, rc=%d\n", + resp_msg->hdr.opcode, resp_msg->property_id, + (int)resp_msg->ret_code); + return false; + } + + return true; +} + +#define MODEL_DEBUG_BOARD "Debug_Board" +static void handle_message(struct battery_chg_dev *bcdev, void *data, + size_t len) +{ + struct battery_charger_resp_msg *resp_msg = data; + struct battery_model_resp_msg *model_resp_msg = data; + struct psy_state *pst; + bool ack_set = false; + + switch (resp_msg->hdr.opcode) { + case BC_BATTERY_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + + /* Handle model response uniquely as it's a string */ + if (pst->model && len == sizeof(*model_resp_msg)) { + memcpy(pst->model, model_resp_msg->model, MAX_STR_LEN); + ack_set = true; + bcdev->debug_battery_detected = !strcmp(pst->model, + MODEL_DEBUG_BOARD); + break; + } + + /* Other response should be of same type as they've u32 value */ + if (validate_message(resp_msg, len) && + resp_msg->property_id < pst->prop_count) { + pst->prop[resp_msg->property_id] = resp_msg->value; + ack_set = true; + } + + break; + case BC_USB_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_USB]; + if (validate_message(resp_msg, len) && + resp_msg->property_id < pst->prop_count) { + pst->prop[resp_msg->property_id] = resp_msg->value; + ack_set = true; + } + + break; + case BC_BATTERY_STATUS_SET: + case BC_USB_STATUS_SET: + if (validate_message(data, len)) + ack_set = true; + + break; + case BC_SET_NOTIFY_REQ: + /* Always ACK response for notify request */ + ack_set = true; + break; + default: + pr_err("Unknown opcode: %u\n", resp_msg->hdr.opcode); + break; + } + + if (ack_set) + complete(&bcdev->ack); +} + +static struct power_supply_desc usb_psy_desc; + +static void battery_chg_update_usb_type_work(struct work_struct *work) +{ + struct battery_chg_dev *bcdev = container_of(work, + struct battery_chg_dev, usb_type_work); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int rc; + + rc = read_property_id(bcdev, pst, USB_ADAP_TYPE); + if (rc < 0) { + pr_err("Failed to read USB_ADAP_TYPE rc=%d\n", rc); + return; + } + + /* Reset usb_icl_ua whenever USB adapter type changes */ + if (pst->prop[USB_ADAP_TYPE] != POWER_SUPPLY_USB_TYPE_SDP && + pst->prop[USB_ADAP_TYPE] != POWER_SUPPLY_USB_TYPE_PD) + bcdev->usb_icl_ua = 0; + + pr_debug("usb_adap_type: %u\n", pst->prop[USB_ADAP_TYPE]); + + switch (pst->prop[USB_ADAP_TYPE]) { + case POWER_SUPPLY_USB_TYPE_SDP: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB; + break; + case POWER_SUPPLY_USB_TYPE_DCP: + case POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID: + case QTI_POWER_SUPPLY_USB_TYPE_HVDCP: + case QTI_POWER_SUPPLY_USB_TYPE_HVDCP_3: + case QTI_POWER_SUPPLY_USB_TYPE_HVDCP_3P5: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; + break; + case POWER_SUPPLY_USB_TYPE_CDP: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; + break; + case POWER_SUPPLY_USB_TYPE_ACA: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_ACA; + break; + case POWER_SUPPLY_USB_TYPE_C: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C; + break; + case POWER_SUPPLY_USB_TYPE_PD: + case POWER_SUPPLY_USB_TYPE_PD_DRP: + case POWER_SUPPLY_USB_TYPE_PD_PPS: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD; + break; + default: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB; + break; + } +} + +static void handle_notification(struct battery_chg_dev *bcdev, void *data, + size_t len) +{ + struct battery_charger_notify_msg *notify_msg = data; + struct psy_state *pst = NULL; + + if (len != sizeof(*notify_msg)) { + pr_err("Incorrect response length %zu\n", len); + return; + } + + pr_debug("notification: %#x\n", notify_msg->notification); + + switch (notify_msg->notification) { + case BC_BATTERY_STATUS_GET: + case BC_GENERIC_NOTIFY: + pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + break; + case BC_USB_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_USB]; + schedule_work(&bcdev->usb_type_work); + break; + default: + break; + } + + if (pst && pst->psy) { + /* + * For charger mode, keep the device awake at least for 50 ms + * so that device won't enter suspend when a non-SDP charger + * is removed. This would allow the userspace process like + * "charger" to be able to read power supply uevents to take + * appropriate actions (e.g. shutting down when the charger is + * unplugged). + */ + power_supply_changed(pst->psy); + pm_wakeup_dev_event(bcdev->dev, 50, true); + } +} + +static int battery_chg_callback(void *priv, void *data, size_t len) +{ + struct pmic_glink_hdr *hdr = data; + struct battery_chg_dev *bcdev = priv; + + pr_debug("owner: %u type: %u opcode: %#x len: %zu\n", hdr->owner, + hdr->type, hdr->opcode, len); + + if (!bcdev->initialized) { + pr_debug("Driver initialization failed: Dropping glink callback message\n"); + return 0; + } + + if (hdr->opcode == BC_NOTIFY_IND) + handle_notification(bcdev, data, len); + else + handle_message(bcdev, data, len); + + return 0; +} + +static const char *get_usb_type_name(u32 usb_type) +{ + u32 i; + + if (usb_type >= QTI_POWER_SUPPLY_USB_TYPE_HVDCP && + usb_type <= QTI_POWER_SUPPLY_USB_TYPE_HVDCP_3P5) { + for (i = 0; i < ARRAY_SIZE(qc_power_supply_usb_type_text); + i++) { + if (i == (usb_type - QTI_POWER_SUPPLY_USB_TYPE_HVDCP)) + return qc_power_supply_usb_type_text[i]; + } + return "Unknown"; + } + + for (i = 0; i < ARRAY_SIZE(power_supply_usb_type_text); i++) { + if (i == usb_type) + return power_supply_usb_type_text[i]; + } + + return "Unknown"; +} + +static int usb_psy_set_icl(struct battery_chg_dev *bcdev, u32 prop_id, int val) +{ + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + u32 temp; + int rc; + + rc = read_property_id(bcdev, pst, USB_ADAP_TYPE); + if (rc < 0) { + pr_err("Failed to read prop USB_ADAP_TYPE, rc=%d\n", rc); + return rc; + } + + /* Allow this only for SDP or USB_PD and not for other charger types */ + if (pst->prop[USB_ADAP_TYPE] != POWER_SUPPLY_USB_TYPE_SDP && + pst->prop[USB_ADAP_TYPE] != POWER_SUPPLY_USB_TYPE_PD) + return -EINVAL; + + /* + * Input current limit (ICL) can be set by different clients. E.g. USB + * driver can request for a current of 500/900 mA depending on the + * port type. Also, clients like EUD driver can pass 0 or -22 to + * suspend or unsuspend the input for its use case. + */ + + temp = val; + if (val < 0) + temp = UINT_MAX; + + rc = write_property_id(bcdev, pst, prop_id, temp); + if (rc < 0) { + pr_err("Failed to set ICL (%u uA) rc=%d\n", temp, rc); + } else { + pr_debug("Set ICL to %u\n", temp); + bcdev->usb_icl_ua = temp; + } + + return rc; +} + +static int usb_psy_get_prop(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *pval) +{ + struct battery_chg_dev *bcdev = power_supply_get_drvdata(psy); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int prop_id, rc; + + pval->intval = -ENODATA; + + prop_id = get_property_id(pst, prop); + if (prop_id < 0) + return prop_id; + + rc = read_property_id(bcdev, pst, prop_id); + if (rc < 0) + return rc; + + pval->intval = pst->prop[prop_id]; + if (prop == POWER_SUPPLY_PROP_TEMP) + pval->intval = DIV_ROUND_CLOSEST((int)pval->intval, 10); + + return 0; +} + +static int usb_psy_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *pval) +{ + struct battery_chg_dev *bcdev = power_supply_get_drvdata(psy); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int prop_id, rc = 0; + + prop_id = get_property_id(pst, prop); + if (prop_id < 0) + return prop_id; + + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + rc = usb_psy_set_icl(bcdev, prop_id, pval->intval); + break; + default: + break; + } + + return rc; +} + +static int usb_psy_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return 1; + default: + break; + } + + return 0; +} + +static enum power_supply_property usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_usb_type usb_psy_supported_types[] = { + POWER_SUPPLY_USB_TYPE_UNKNOWN, + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_ACA, + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_DRP, + POWER_SUPPLY_USB_TYPE_PD_PPS, + POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, +}; + +static struct power_supply_desc usb_psy_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = usb_props, + .num_properties = ARRAY_SIZE(usb_props), + .get_property = usb_psy_get_prop, + .set_property = usb_psy_set_prop, + .usb_types = usb_psy_supported_types, + .num_usb_types = ARRAY_SIZE(usb_psy_supported_types), + .property_is_writeable = usb_psy_prop_is_writeable, +}; + +static int __battery_psy_set_charge_current(struct battery_chg_dev *bcdev, + u32 fcc_ua) +{ + int rc; + + if (bcdev->restrict_chg_en) { + fcc_ua = min_t(u32, fcc_ua, bcdev->restrict_fcc_ua); + fcc_ua = min_t(u32, fcc_ua, bcdev->thermal_fcc_ua); + } + + rc = write_property_id(bcdev, &bcdev->psy_list[PSY_TYPE_BATTERY], + BATT_CHG_CTRL_LIM, fcc_ua); + if (rc < 0) { + pr_err("Failed to set FCC %u, rc=%d\n", fcc_ua, rc); + } else { + pr_debug("Set FCC to %u uA\n", fcc_ua); + bcdev->last_fcc_ua = fcc_ua; + } + + return rc; +} + +static int battery_psy_set_charge_current(struct battery_chg_dev *bcdev, + int val) +{ + int rc; + u32 fcc_ua, prev_fcc_ua; + + if (!bcdev->num_thermal_levels) + return 0; + + if (bcdev->num_thermal_levels < 0) { + pr_err("Incorrect num_thermal_levels\n"); + return -EINVAL; + } + + if (val < 0 || val > bcdev->num_thermal_levels) + return -EINVAL; + + fcc_ua = bcdev->thermal_levels[val]; + prev_fcc_ua = bcdev->thermal_fcc_ua; + bcdev->thermal_fcc_ua = fcc_ua; + + rc = __battery_psy_set_charge_current(bcdev, fcc_ua); + if (!rc) + bcdev->curr_thermal_level = val; + else + bcdev->thermal_fcc_ua = prev_fcc_ua; + + return rc; +} + +static int battery_psy_get_prop(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *pval) +{ + struct battery_chg_dev *bcdev = power_supply_get_drvdata(psy); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + int prop_id, rc; + + pval->intval = -ENODATA; + + /* + * The prop id of TIME_TO_FULL_NOW and TIME_TO_FULL_AVG is same. + * So, map the prop id of TIME_TO_FULL_AVG for TIME_TO_FULL_NOW. + */ + if (prop == POWER_SUPPLY_PROP_TIME_TO_FULL_NOW) + prop = POWER_SUPPLY_PROP_TIME_TO_FULL_AVG; + + prop_id = get_property_id(pst, prop); + if (prop_id < 0) + return prop_id; + + rc = read_property_id(bcdev, pst, prop_id); + if (rc < 0) + return rc; + + switch (prop) { + case POWER_SUPPLY_PROP_MODEL_NAME: + pval->strval = pst->model; + break; + case POWER_SUPPLY_PROP_CAPACITY: + pval->intval = DIV_ROUND_CLOSEST(pst->prop[prop_id], 100); + if (IS_ENABLED(CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG) && + (bcdev->fake_soc >= 0 && bcdev->fake_soc <= 100)) + pval->intval = bcdev->fake_soc; + break; + case POWER_SUPPLY_PROP_TEMP: + pval->intval = DIV_ROUND_CLOSEST((int)pst->prop[prop_id], 10); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + pval->intval = bcdev->curr_thermal_level; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + pval->intval = bcdev->num_thermal_levels; + break; + default: + pval->intval = pst->prop[prop_id]; + break; + } + + return rc; +} + +static int battery_psy_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *pval) +{ + struct battery_chg_dev *bcdev = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return battery_psy_set_charge_current(bcdev, pval->intval); + default: + return -EINVAL; + } + + return 0; +} + +static int battery_psy_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return 1; + default: + break; + } + + return 0; +} + +static enum power_supply_property battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_POWER_AVG, +}; + +static const struct power_supply_desc batt_psy_desc = { + .name = "main_battery", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = battery_props, + .num_properties = ARRAY_SIZE(battery_props), + .get_property = battery_psy_get_prop, + .set_property = battery_psy_set_prop, + .property_is_writeable = battery_psy_prop_is_writeable, +}; + +static int combo_psy_get_prop(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *pval) +{ + struct battery_chg_dev *bcdev = power_supply_get_drvdata(psy); + int rc = 0; + + if (!bcdev->mmi_psy) { + bcdev->mmi_psy = power_supply_get_by_name("mmi_battery"); + if (!bcdev->mmi_psy) { + pr_err("No mmi battery supply found.\n\n"); + pval->intval = -EINVAL; + return rc; + } + } + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + pval->intval = 1; + break; + default: + rc = power_supply_get_property(bcdev->mmi_psy, prop, pval); + if (rc < 0) { + pr_err("Get Unknown prop %d rc = %d\n", prop, rc); + /* soft fail so uevents are not blocked */ + rc = 0; + pval->intval = -EINVAL; + } + break; + } + + return rc; +} + +static int combo_psy_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *pval) +{ + struct battery_chg_dev *bcdev = power_supply_get_drvdata(psy); + int rc = 0; + + if (!bcdev->mmi_psy) { + bcdev->mmi_psy = power_supply_get_by_name("mmi_battery"); + if (!bcdev->mmi_psy) { + pr_err("No mmi battery supply found.\n\n"); + return -EINVAL; + } + } + + switch (prop) { + default: + rc = power_supply_set_property(bcdev->mmi_psy, prop, pval); + if (rc < 0) { + pr_err("Get Unknown prop %d rc = %d\n", prop, rc); + /* soft fail so uevents are not blocked */ + rc = 0; + } + break; + } + + return rc; +} + +static int combo_psy_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CYCLE_COUNT: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return 1; + default: + break; + } + + return 0; +} + +static enum power_supply_property combo_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, +}; + +static const struct power_supply_desc combo_psy_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = combo_props, + .num_properties = ARRAY_SIZE(combo_props), + .get_property = combo_psy_get_prop, + .set_property = combo_psy_set_prop, + .property_is_writeable = combo_psy_prop_is_writeable, +}; + +static int battery_chg_init_psy(struct battery_chg_dev *bcdev) +{ + struct power_supply_config psy_cfg = {}; + int rc; + + psy_cfg.drv_data = bcdev; + psy_cfg.of_node = bcdev->dev->of_node; + bcdev->psy_list[PSY_TYPE_BATTERY].psy = + devm_power_supply_register(bcdev->dev, &batt_psy_desc, + &psy_cfg); + if (IS_ERR(bcdev->psy_list[PSY_TYPE_BATTERY].psy)) { + rc = PTR_ERR(bcdev->psy_list[PSY_TYPE_BATTERY].psy); + pr_err("Failed to register battery power supply, rc=%d\n", rc); + return rc; + } + + bcdev->psy_list[PSY_TYPE_USB].psy = + devm_power_supply_register(bcdev->dev, &usb_psy_desc, &psy_cfg); + if (IS_ERR(bcdev->psy_list[PSY_TYPE_USB].psy)) { + rc = PTR_ERR(bcdev->psy_list[PSY_TYPE_USB].psy); + pr_err("Failed to register USB power supply, rc=%d\n", rc); + return rc; + } + + bcdev->combo_psy = + devm_power_supply_register(bcdev->dev, &combo_psy_desc, &psy_cfg); + if (IS_ERR(bcdev->combo_psy)) { + rc = PTR_ERR(bcdev->combo_psy); + pr_err("Failed to register combo power supply, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static void battery_chg_subsys_up_work(struct work_struct *work) +{ + struct battery_chg_dev *bcdev = container_of(work, + struct battery_chg_dev, subsys_up_work); + int rc; + + battery_chg_notify_enable(bcdev); + + /* + * Give some time after enabling notification so that USB adapter type + * information can be obtained properly which is essential for setting + * USB ICL. + */ + msleep(200); + + if (bcdev->last_fcc_ua) { + rc = __battery_psy_set_charge_current(bcdev, + bcdev->last_fcc_ua); + if (rc < 0) + pr_err("Failed to set FCC (%u uA), rc=%d\n", + bcdev->last_fcc_ua, rc); + } + + if (bcdev->usb_icl_ua) { + rc = usb_psy_set_icl(bcdev, USB_INPUT_CURR_LIMIT, + bcdev->usb_icl_ua); + if (rc < 0) + pr_err("Failed to set ICL(%u uA), rc=%d\n", + bcdev->usb_icl_ua, rc); + } +} + +static ssize_t usb_typec_compliant_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int rc; + + rc = read_property_id(bcdev, pst, USB_TYPEC_COMPLIANT); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%d\n", + (int)pst->prop[USB_TYPEC_COMPLIANT]); +} +static CLASS_ATTR_RO(usb_typec_compliant); + +static ssize_t usb_real_type_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int rc; + + rc = read_property_id(bcdev, pst, USB_REAL_TYPE); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%s\n", + get_usb_type_name(pst->prop[USB_REAL_TYPE])); +} +static CLASS_ATTR_RO(usb_real_type); + +static ssize_t restrict_cur_store(struct class *c, struct class_attribute *attr, + const char *buf, size_t count) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + int rc; + u32 fcc_ua, prev_fcc_ua; + + if (kstrtou32(buf, 0, &fcc_ua) || fcc_ua > bcdev->thermal_fcc_ua) + return -EINVAL; + + prev_fcc_ua = bcdev->restrict_fcc_ua; + bcdev->restrict_fcc_ua = fcc_ua; + if (bcdev->restrict_chg_en) { + rc = __battery_psy_set_charge_current(bcdev, fcc_ua); + if (rc < 0) { + bcdev->restrict_fcc_ua = prev_fcc_ua; + return rc; + } + } + + return count; +} + +static ssize_t restrict_cur_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + + return scnprintf(buf, PAGE_SIZE, "%u\n", bcdev->restrict_fcc_ua); +} +static CLASS_ATTR_RW(restrict_cur); + +static ssize_t restrict_chg_store(struct class *c, struct class_attribute *attr, + const char *buf, size_t count) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + int rc; + bool val; + + if (kstrtobool(buf, &val)) + return -EINVAL; + + bcdev->restrict_chg_en = val; + rc = __battery_psy_set_charge_current(bcdev, bcdev->restrict_chg_en ? + bcdev->restrict_fcc_ua : bcdev->thermal_fcc_ua); + if (rc < 0) + return rc; + + return count; +} + +static ssize_t restrict_chg_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + + return scnprintf(buf, PAGE_SIZE, "%d\n", bcdev->restrict_chg_en); +} +static CLASS_ATTR_RW(restrict_chg); + +static ssize_t fake_soc_store(struct class *c, struct class_attribute *attr, + const char *buf, size_t count) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + int val; + + if (kstrtoint(buf, 0, &val)) + return -EINVAL; + + bcdev->fake_soc = val; + pr_debug("Set fake soc to %d\n", val); + + if (IS_ENABLED(CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG) && pst->psy) + power_supply_changed(pst->psy); + + return count; +} + +static ssize_t fake_soc_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + + return scnprintf(buf, PAGE_SIZE, "%d\n", bcdev->fake_soc); +} +static CLASS_ATTR_RW(fake_soc); + + +static ssize_t moisture_detection_en_store(struct class *c, + struct class_attribute *attr, + const char *buf, size_t count) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + int rc; + bool val; + + if (kstrtobool(buf, &val)) + return -EINVAL; + + rc = write_property_id(bcdev, &bcdev->psy_list[PSY_TYPE_USB], + USB_MOISTURE_DET_EN, val); + if (rc < 0) + return rc; + + return count; +} + +static ssize_t moisture_detection_en_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int rc; + + rc = read_property_id(bcdev, pst, USB_MOISTURE_DET_EN); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%d\n", + pst->prop[USB_MOISTURE_DET_EN]); +} +static CLASS_ATTR_RW(moisture_detection_en); + +static ssize_t moisture_detection_status_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_USB]; + int rc; + + rc = read_property_id(bcdev, pst, USB_MOISTURE_DET_STS); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%d\n", + pst->prop[USB_MOISTURE_DET_STS]); +} +static CLASS_ATTR_RO(moisture_detection_status); + +static ssize_t resistance_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + int rc; + + rc = read_property_id(bcdev, pst, BATT_RESISTANCE); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%u\n", pst->prop[BATT_RESISTANCE]); +} +static CLASS_ATTR_RO(resistance); + +static ssize_t soh_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + int rc; + + rc = read_property_id(bcdev, pst, BATT_SOH); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%d\n", pst->prop[BATT_SOH]); +} +static CLASS_ATTR_RO(soh); + +static ssize_t ship_mode_en_store(struct class *c, struct class_attribute *attr, + const char *buf, size_t count) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + + if (kstrtobool(buf, &bcdev->ship_mode_en)) + return -EINVAL; + + return count; +} + +static ssize_t ship_mode_en_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + struct battery_chg_dev *bcdev = container_of(c, struct battery_chg_dev, + battery_class); + + return scnprintf(buf, PAGE_SIZE, "%d\n", bcdev->ship_mode_en); +} +static CLASS_ATTR_RW(ship_mode_en); + +static struct attribute *battery_class_attrs[] = { + &class_attr_soh.attr, + &class_attr_resistance.attr, + &class_attr_moisture_detection_status.attr, + &class_attr_moisture_detection_en.attr, + &class_attr_fake_soc.attr, + &class_attr_ship_mode_en.attr, + &class_attr_restrict_chg.attr, + &class_attr_restrict_cur.attr, + &class_attr_usb_real_type.attr, + &class_attr_usb_typec_compliant.attr, + NULL, +}; +ATTRIBUTE_GROUPS(battery_class); + +#ifdef CONFIG_DEBUG_FS +static void battery_chg_add_debugfs(struct battery_chg_dev *bcdev) +{ + int rc; + struct dentry *dir, *file; + + dir = debugfs_create_dir("battery_charger", NULL); + if (IS_ERR(dir)) { + rc = PTR_ERR(dir); + pr_err("Failed to create charger debugfs directory, rc=%d\n", + rc); + return; + } + + file = debugfs_create_bool("block_tx", 0600, dir, &bcdev->block_tx); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create block_tx debugfs file, rc=%d\n", + rc); + goto error; + } + + bcdev->debugfs_dir = dir; + + return; +error: + debugfs_remove_recursive(dir); +} +#else +static void battery_chg_add_debugfs(struct battery_chg_dev *bcdev) { } +#endif + +static int battery_chg_parse_dt(struct battery_chg_dev *bcdev) +{ + struct device_node *node = bcdev->dev->of_node; + struct psy_state *pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + int i, rc, len; + u32 prev, val; + + rc = of_property_count_elems_of_size(node, "qcom,thermal-mitigation", + sizeof(u32)); + if (rc <= 0) + return 0; + + len = rc; + + rc = read_property_id(bcdev, pst, BATT_CHG_CTRL_LIM_MAX); + if (rc < 0) { + pr_err("Failed to read prop BATT_CHG_CTRL_LIM_MAX, rc=%d\n", + rc); + return rc; + } + + prev = pst->prop[BATT_CHG_CTRL_LIM_MAX]; + + for (i = 0; i < len; i++) { + rc = of_property_read_u32_index(node, "qcom,thermal-mitigation", + i, &val); + if (rc < 0) + return rc; + + if (val > prev) { + pr_err("Thermal levels should be in descending order\n"); + bcdev->num_thermal_levels = -EINVAL; + return 0; + } + + prev = val; + } + + bcdev->thermal_levels = devm_kcalloc(bcdev->dev, len + 1, + sizeof(*bcdev->thermal_levels), + GFP_KERNEL); + if (!bcdev->thermal_levels) + return -ENOMEM; + + /* + * Element 0 is for normal charging current. Elements from index 1 + * onwards is for thermal mitigation charging currents. + */ + + bcdev->thermal_levels[0] = pst->prop[BATT_CHG_CTRL_LIM_MAX]; + + rc = of_property_read_u32_array(node, "qcom,thermal-mitigation", + &bcdev->thermal_levels[1], len); + if (rc < 0) { + pr_err("Error in reading qcom,thermal-mitigation, rc=%d\n", rc); + return rc; + } + + bcdev->num_thermal_levels = len; + bcdev->thermal_fcc_ua = pst->prop[BATT_CHG_CTRL_LIM_MAX]; + + return 0; +} + +static int battery_chg_ship_mode(struct notifier_block *nb, unsigned long code, + void *unused) +{ + struct battery_charger_notify_msg msg_notify = { { 0 } }; + struct battery_charger_ship_mode_req_msg msg = { { 0 } }; + struct battery_chg_dev *bcdev = container_of(nb, struct battery_chg_dev, + reboot_notifier); + int rc; + + msg_notify.hdr.owner = MSG_OWNER_BC; + msg_notify.hdr.type = MSG_TYPE_NOTIFY; + msg_notify.hdr.opcode = BC_SHUTDOWN_NOTIFY; + + rc = battery_chg_write(bcdev, &msg_notify, sizeof(msg_notify)); + if (rc < 0) + pr_err("Failed to send shutdown notification rc=%d\n", rc); + + if (!bcdev->ship_mode_en) + return NOTIFY_DONE; + + msg.hdr.owner = MSG_OWNER_BC; + msg.hdr.type = MSG_TYPE_REQ_RESP; + msg.hdr.opcode = BC_SHIP_MODE_REQ_SET; + msg.ship_mode_type = SHIP_MODE_PMIC; + + if (code == SYS_POWER_OFF) { + rc = battery_chg_write(bcdev, &msg, sizeof(msg)); + if (rc < 0) + pr_emerg("Failed to write ship mode: %d\n", rc); + } + + return NOTIFY_DONE; +} + +static int battery_chg_probe(struct platform_device *pdev) +{ + struct battery_chg_dev *bcdev; + struct device *dev = &pdev->dev; + struct pmic_glink_client_data client_data = { }; + int rc, i; + + bcdev = devm_kzalloc(&pdev->dev, sizeof(*bcdev), GFP_KERNEL); + if (!bcdev) + return -ENOMEM; + + bcdev->psy_list[PSY_TYPE_BATTERY].map = battery_prop_map; + bcdev->psy_list[PSY_TYPE_BATTERY].prop_count = BATT_PROP_MAX; + bcdev->psy_list[PSY_TYPE_BATTERY].opcode_get = BC_BATTERY_STATUS_GET; + bcdev->psy_list[PSY_TYPE_BATTERY].opcode_set = BC_BATTERY_STATUS_SET; + bcdev->psy_list[PSY_TYPE_USB].map = usb_prop_map; + bcdev->psy_list[PSY_TYPE_USB].prop_count = USB_PROP_MAX; + bcdev->psy_list[PSY_TYPE_USB].opcode_get = BC_USB_STATUS_GET; + bcdev->psy_list[PSY_TYPE_USB].opcode_set = BC_USB_STATUS_SET; + + for (i = 0; i < PSY_TYPE_MAX; i++) { + bcdev->psy_list[i].prop = + devm_kcalloc(&pdev->dev, bcdev->psy_list[i].prop_count, + sizeof(u32), GFP_KERNEL); + if (!bcdev->psy_list[i].prop) + return -ENOMEM; + } + + bcdev->psy_list[PSY_TYPE_BATTERY].model = + devm_kzalloc(&pdev->dev, MAX_STR_LEN, GFP_KERNEL); + if (!bcdev->psy_list[PSY_TYPE_BATTERY].model) + return -ENOMEM; + + mutex_init(&bcdev->rw_lock); + init_completion(&bcdev->ack); + INIT_WORK(&bcdev->subsys_up_work, battery_chg_subsys_up_work); + INIT_WORK(&bcdev->usb_type_work, battery_chg_update_usb_type_work); + atomic_set(&bcdev->state, PMIC_GLINK_STATE_UP); + bcdev->dev = dev; + + client_data.id = MSG_OWNER_BC; + client_data.name = "battery_charger"; + client_data.msg_cb = battery_chg_callback; + client_data.priv = bcdev; + client_data.state_cb = battery_chg_state_cb; + + bcdev->client = pmic_glink_register_client(dev, &client_data); + if (IS_ERR(bcdev->client)) { + rc = PTR_ERR(bcdev->client); + if (rc != -EPROBE_DEFER) + dev_err(dev, "Error in registering with pmic_glink %d\n", + rc); + return rc; + } + + bcdev->initialized = true; + bcdev->reboot_notifier.notifier_call = battery_chg_ship_mode; + bcdev->reboot_notifier.priority = 255; + register_reboot_notifier(&bcdev->reboot_notifier); + + rc = battery_chg_parse_dt(bcdev); + if (rc < 0) { + dev_err(dev, "Failed to parse dt rc=%d\n", rc); + goto error; + } + + bcdev->restrict_fcc_ua = DEFAULT_RESTRICT_FCC_UA; + platform_set_drvdata(pdev, bcdev); + bcdev->fake_soc = -EINVAL; + rc = battery_chg_init_psy(bcdev); + if (rc < 0) + goto error; + + bcdev->battery_class.name = "qcom-battery"; + bcdev->battery_class.class_groups = battery_class_groups; + rc = class_register(&bcdev->battery_class); + if (rc < 0) { + dev_err(dev, "Failed to create battery_class rc=%d\n", rc); + goto error; + } + + battery_chg_add_debugfs(bcdev); + battery_chg_notify_enable(bcdev); + device_init_wakeup(bcdev->dev, true); + schedule_work(&bcdev->usb_type_work); + + return 0; +error: + bcdev->initialized = false; + complete(&bcdev->ack); + pmic_glink_unregister_client(bcdev->client); + unregister_reboot_notifier(&bcdev->reboot_notifier); + return rc; +} + +static int battery_chg_remove(struct platform_device *pdev) +{ + struct battery_chg_dev *bcdev = platform_get_drvdata(pdev); + int rc; + + device_init_wakeup(bcdev->dev, false); + debugfs_remove_recursive(bcdev->debugfs_dir); + class_unregister(&bcdev->battery_class); + unregister_reboot_notifier(&bcdev->reboot_notifier); + rc = pmic_glink_unregister_client(bcdev->client); + if (rc < 0) { + pr_err("Error unregistering from pmic_glink, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static const struct of_device_id battery_chg_match_table[] = { + { .compatible = "qcom,battery-charger-mmi" }, + {}, +}; + +static struct platform_driver battery_chg_driver = { + .driver = { + .name = "qti_battery_charger_mmi", + .of_match_table = battery_chg_match_table, + }, + .probe = battery_chg_probe, + .remove = battery_chg_remove, +}; +module_platform_driver(battery_chg_driver); + +MODULE_DESCRIPTION("QTI Glink battery charger driver"); +MODULE_LICENSE("GPL v2");