diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index b366e2fd8e97..8677b1976230 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -427,6 +427,19 @@ config BATTERY_TWL4030_MADC Say Y here to enable this dumb driver for batteries managed through the TWL4030 MADC. +config QTI_BATTERY_CHARGER + tristate "QTI Glink battery charger" + depends on QTI_PMIC_GLINK + help + Say Y here to enable Qualcomm Technologies, Inc. battery charger + driver which interfaces with the charger firmware running on the + remote subsystem (e.g. DSP) by communicating over PMIC Glink channel + and provides information about the battery, USB and wireless charging + by exposing them as power supply devices. + + To compile the driver as a module, choose M here: the module will be + called qti_battery_charger. + config CHARGER_88PM860X tristate "Marvell 88PM860x Charger driver" depends on MFD_88PM860X && BATTERY_88PM860X diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 2c1b264b2046..5e0c63c1e0d8 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -105,3 +105,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o +obj-$(CONFIG_QTI_BATTERY_CHARGER) += qti_battery_charger.o diff --git a/drivers/power/supply/qti_battery_charger.c b/drivers/power/supply/qti_battery_charger.c new file mode 100644 index 000000000000..42274c4a400c --- /dev/null +++ b/drivers/power/supply/qti_battery_charger.c @@ -0,0 +1,982 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "BATTERY_CHG: %s: " fmt, __func__ + +#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_WLS_STATUS_GET 0x34 +#define BC_WLS_STATUS_SET 0x35 + +/* Generic definitions */ +#define MAX_STR_LEN 128 +#define BC_WAIT_TIME_MS 1000 + +enum psy_type { + PSY_TYPE_BATTERY, + PSY_TYPE_USB, + PSY_TYPE_WLS, + PSY_TYPE_MAX, +}; + +/* 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_PROP_MAX, +}; + +enum wireless_property_id { + WLS_ONLINE, + WLS_VOLT_NOW, + WLS_VOLT_MAX, + WLS_CURR_NOW, + WLS_CURR_MAX, + WLS_TYPE, + WLS_BOOST_EN, + WLS_PROP_MAX, +}; + +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 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]; +}; + +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, +}; + +static const int wls_prop_map[WLS_PROP_MAX] = { + [WLS_ONLINE] = POWER_SUPPLY_PROP_ONLINE, + [WLS_VOLT_NOW] = POWER_SUPPLY_PROP_VOLTAGE_NOW, + [WLS_VOLT_MAX] = POWER_SUPPLY_PROP_VOLTAGE_MAX, + [WLS_CURR_NOW] = POWER_SUPPLY_PROP_CURRENT_NOW, + [WLS_CURR_MAX] = POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int battery_chg_write(struct battery_chg_dev *bcdev, void *data, + int len) +{ + int rc; + + 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; +} + +/** + * qti_battery_charger_get_prop() - Gets the property being requested + * + * @name: Power supply name + * @prop_id: Property id to be read + * @val: Pointer to value that needs to be updated + * + * Return: 0 if success, negative on error. + */ +int qti_battery_charger_get_prop(const char *name, + enum battery_charger_prop prop_id, int *val) +{ + struct power_supply *psy; + struct battery_chg_dev *bcdev; + struct psy_state *pst; + int rc = 0; + + if (prop_id >= BATTERY_CHARGER_PROP_MAX) + return -EINVAL; + + if (strcmp(name, "battery") && strcmp(name, "usb") && + strcmp(name, "wireless")) + return -EINVAL; + + psy = power_supply_get_by_name(name); + if (!psy) + return -ENODEV; + + bcdev = power_supply_get_drvdata(psy); + if (!bcdev) + return -ENODEV; + + power_supply_put(psy); + + switch (prop_id) { + case BATTERY_RESISTANCE: + pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + rc = read_property_id(bcdev, pst, BATT_RESISTANCE); + if (!rc) + *val = pst->prop[BATT_RESISTANCE]; + break; + default: + break; + } + + return rc; +} +EXPORT_SYMBOL(qti_battery_charger_get_prop); + +static bool validate_message(void *data, size_t len) +{ + struct battery_charger_resp_msg *resp_msg = data; + + 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; +} + +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; + u32 prop_id = resp_msg->property_id, val = resp_msg->value; + 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; + break; + } + + /* Other response should be of same type as they've u32 value */ + if (validate_message(data, len) && prop_id < pst->prop_count) { + pst->prop[prop_id] = val; + ack_set = true; + } + + break; + case BC_USB_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_USB]; + if (validate_message(data, len) && prop_id < pst->prop_count) { + pst->prop[prop_id] = val; + ack_set = true; + } + + break; + case BC_WLS_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_WLS]; + if (validate_message(data, len) && prop_id < pst->prop_count) { + pst->prop[prop_id] = val; + ack_set = true; + } + + break; + case BC_BATTERY_STATUS_SET: + case BC_USB_STATUS_SET: + case BC_WLS_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 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: + pst = &bcdev->psy_list[PSY_TYPE_BATTERY]; + break; + case BC_USB_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_USB]; + break; + case BC_WLS_STATUS_GET: + pst = &bcdev->psy_list[PSY_TYPE_WLS]; + break; + default: + break; + } + + if (pst && pst->psy) + power_supply_changed(pst->psy); +} + +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 (hdr->opcode == BC_NOTIFY_IND) + handle_notification(bcdev, data, len); + else + handle_message(bcdev, data, len); + + return 0; +} + +static int wls_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_WLS]; + 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]; + + return 0; +} + +static int wls_psy_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *pval) +{ + return 0; +} + +static int wls_psy_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + return 0; +} + +static enum power_supply_property wls_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, +}; + +static const struct power_supply_desc wls_psy_desc = { + .name = "wireless", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = wls_props, + .num_properties = ARRAY_SIZE(wls_props), + .get_property = wls_psy_get_prop, + .set_property = wls_psy_set_prop, + .property_is_writeable = wls_psy_prop_is_writeable, +}; + +static int usb_psy_set_icl(struct battery_chg_dev *bcdev, u32 prop_id, int val) +{ + u32 temp; + int rc; + + /* + * 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, &bcdev->psy_list[PSY_TYPE_USB], prop_id, + temp); + if (!rc) + pr_debug("Set ICL to %u\n", 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]; + + 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, +}; + +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 const 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_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; + + 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); + break; + case POWER_SUPPLY_PROP_TEMP: + pval->intval = DIV_ROUND_CLOSEST((int)pst->prop[prop_id], 10); + 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) +{ + return 0; +} + +static int battery_psy_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + 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_EMPTY_AVG, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_POWER_AVG, +}; + +static const struct power_supply_desc batt_psy_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .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 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->psy_list[PSY_TYPE_WLS].psy = + devm_power_supply_register(bcdev->dev, &wls_psy_desc, &psy_cfg); + if (IS_ERR(bcdev->psy_list[PSY_TYPE_WLS].psy)) { + rc = PTR_ERR(bcdev->psy_list[PSY_TYPE_WLS].psy); + pr_err("Failed to register wireless power supply, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static ssize_t wireless_boost_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_WLS], + WLS_BOOST_EN, val); + if (rc < 0) + return rc; + + return count; +} + +static ssize_t wireless_boost_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_WLS]; + int rc; + + rc = read_property_id(bcdev, pst, WLS_BOOST_EN); + if (rc < 0) + return rc; + + return scnprintf(buf, PAGE_SIZE, "%d\n", pst->prop[WLS_BOOST_EN]); +} +static CLASS_ATTR_RW(wireless_boost_en); + +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 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_wireless_boost_en.attr, + NULL, +}; +ATTRIBUTE_GROUPS(battery_class); + +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; + struct battery_charger_set_notify_msg req_msg = { { 0 } }; + 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; + bcdev->psy_list[PSY_TYPE_WLS].map = wls_prop_map; + bcdev->psy_list[PSY_TYPE_WLS].prop_count = WLS_PROP_MAX; + bcdev->psy_list[PSY_TYPE_WLS].opcode_get = BC_WLS_STATUS_GET; + bcdev->psy_list[PSY_TYPE_WLS].opcode_set = BC_WLS_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; + + 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; + + 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; + } + + platform_set_drvdata(pdev, bcdev); + mutex_init(&bcdev->rw_lock); + init_completion(&bcdev->ack); + 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) { + pr_err("Failed to create battery_class rc=%d\n", rc); + goto error; + } + + /* 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); + + return 0; +error: + pmic_glink_unregister_client(bcdev->client); + return rc; +} + +static int battery_chg_remove(struct platform_device *pdev) +{ + struct battery_chg_dev *bcdev = platform_get_drvdata(pdev); + int rc; + + class_unregister(&bcdev->battery_class); + 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" }, + {}, +}; + +static struct platform_driver battery_chg_driver = { + .driver = { + .name = "qti_battery_charger", + .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"); diff --git a/include/linux/soc/qcom/battery_charger.h b/include/linux/soc/qcom/battery_charger.h new file mode 100644 index 000000000000..67660c72acec --- /dev/null +++ b/include/linux/soc/qcom/battery_charger.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef _BATTERY_CHARGER_H +#define _BATTERY_CHARGER_H + +enum battery_charger_prop { + BATTERY_RESISTANCE, + BATTERY_CHARGER_PROP_MAX, +}; + +#if IS_ENABLED(CONFIG_QTI_BATTERY_CHARGER) +int qti_battery_charger_get_prop(const char *name, + enum battery_charger_prop prop_id, int *val); +#else +static inline int +qti_battery_charger_get_prop(const char *name, + enum battery_charger_prop prop_id, int *val) +{ + return -EINVAL; +} +#endif + +#endif