From be4f6e6ce617a962dda6f078af8a7b7f060a472b Mon Sep 17 00:00:00 2001 From: yanyh2 Date: Thu, 12 Nov 2020 20:48:49 +0800 Subject: [PATCH] dlkm: add qti_glink_charger driver A low level charger driver qti_glink_charger to support battery charging functions over glink and satisfy the interfaces requirements by common charger driver mmi_charger. Motorola charger driver architecture: ---------------------------------------------------------------------- Userspace Common Interfaces for Charging Features and Factory Test ---------------------------------------------------------------------- Kernel mmi_charger (Motorola demo, step, flash-fast charging, | factory and debugging interfaces) | qti_glink_charger (basic charging functions HW | specific factory debugging interfaces) | --------------------glink--------------------------------------------- | | ADSP Change-Id: I255413442f2137ebeb6200c2e82b8086cc846e54 Signed-off-by: yanyh2 Reviewed-on: https://gerrit.mot.com/1800646 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Wentao Xu Reviewed-by: Jianqi Yang Submit-Approved: Jira Key --- .../mmi_charger/qti_glink_charger/Android.mk | 10 + .../mmi_charger/qti_glink_charger/Kbuild | 7 + .../mmi_charger/qti_glink_charger/Makefile | 10 + .../qti_glink_charger/qti_glink_charger.c | 888 ++++++++++++++++++ 4 files changed, 915 insertions(+) create mode 100755 drivers/power/mmi_charger/qti_glink_charger/Android.mk create mode 100755 drivers/power/mmi_charger/qti_glink_charger/Kbuild create mode 100755 drivers/power/mmi_charger/qti_glink_charger/Makefile create mode 100644 drivers/power/mmi_charger/qti_glink_charger/qti_glink_charger.c diff --git a/drivers/power/mmi_charger/qti_glink_charger/Android.mk b/drivers/power/mmi_charger/qti_glink_charger/Android.mk new file mode 100755 index 000000000000..c4683f65546c --- /dev/null +++ b/drivers/power/mmi_charger/qti_glink_charger/Android.mk @@ -0,0 +1,10 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := qti_glink_charger.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +LOCAL_ADDITIONAL_DEPENDENCIES := $(KERNEL_MODULES_OUT)/mmi_charger.ko +KBUILD_OPTIONS_GKI += GKI_OBJ_MODULE_DIR=gki +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/drivers/power/mmi_charger/qti_glink_charger/Kbuild b/drivers/power/mmi_charger/qti_glink_charger/Kbuild new file mode 100755 index 000000000000..cf18ca310de1 --- /dev/null +++ b/drivers/power/mmi_charger/qti_glink_charger/Kbuild @@ -0,0 +1,7 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/drivers/power/mmi_charger + +obj-m += qti_glink_charger.o + +KBUILD_EXTRA_SYMBOLS += $(CURDIR)/$(KBUILD_EXTMOD)/../$(GKI_OBJ_MODULE_DIR)/Module.symvers diff --git a/drivers/power/mmi_charger/qti_glink_charger/Makefile b/drivers/power/mmi_charger/qti_glink_charger/Makefile new file mode 100755 index 000000000000..34b09dd815a7 --- /dev/null +++ b/drivers/power/mmi_charger/qti_glink_charger/Makefile @@ -0,0 +1,10 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean \ No newline at end of file diff --git a/drivers/power/mmi_charger/qti_glink_charger/qti_glink_charger.c b/drivers/power/mmi_charger/qti_glink_charger/qti_glink_charger.c new file mode 100644 index 000000000000..d4ffd88ededd --- /dev/null +++ b/drivers/power/mmi_charger/qti_glink_charger/qti_glink_charger.c @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2020 Motorola Mobility LLC + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mmi_charger.h" + +/* PPM specific definitions */ +#define MSG_OWNER_OEM 32782 +#define MSG_TYPE_REQ_RESP 1 +#define OEM_PROPERTY_DATA_SIZE 16 + +#define OEM_READ_BUF_REQ 0x10000 +#define OEM_WRITE_BUF_REQ 0x10001 + +#define OEM_WAIT_TIME_MS 5000 + +#define BATT_SN_UNKNOWN "unknown-sn" + +static bool debug_enabled = true; +module_param(debug_enabled, bool, 0600); +MODULE_PARM_DESC(debug_enabled, "Enable debug for qti glink charger driver"); + +enum oem_property_type { + OEM_PROP_BATT_INFO, + OEM_PROP_CHG_INFO, + OEM_PROP_CHG_PROFILE_INFO, + OEM_PROP_CHG_PROFILE_DATA, + OEM_PROP_CHG_FV, + OEM_PROP_CHG_FCC, + OEM_PROP_CHG_ITERM, + OEM_PROP_CHG_FG_ITERM, + OEM_PROP_CHG_BC_PMAX, + OEM_PROP_CHG_QC_PMAX, + OEM_PROP_CHG_SUSPEND, + OEM_PROP_CHG_DISABLE, + OEM_PROP_DEMO_MODE, + OEM_PROP_FACTORY_MODE, + OEM_PROP_FACTORY_VERSION, + OEM_PROP_MAX, +}; + +struct battery_info { + int batt_uv; + int batt_ua; + int batt_soc; /* 0 ~ 10000 indicating 0% to 100% */ + int batt_temp; /* hundredth degree */ + int batt_status; + int batt_full_uah; + int batt_design_uah; +}; + +struct charger_info { + int chrg_uv; + int chrg_ua; + int chrg_type; + int chrg_pmax_mw; + int chrg_present; +}; + +struct charger_profile_info { + int fg_iterm; + int chrg_iterm; + int max_fv_uv; + int max_fcc_ua; + int vfloat_comp_uv; + int demo_fv_uv; + int data_bk_size; /* number of byte for each data block */ + int data_size; /* number of byte for while data */ + int profile_id; /* profile id for charging profile selection in ADSP */ +}; + +struct oem_read_buf_req_msg { + struct pmic_glink_hdr hdr; + u32 oem_property_id; + u32 data_size; +}; + +struct oem_read_buf_resp_msg { + struct pmic_glink_hdr hdr; + u32 oem_property_id; + u32 buf[OEM_PROPERTY_DATA_SIZE]; + u32 data_size; +}; + +struct oem_write_buf_req_msg { + struct pmic_glink_hdr hdr; + u32 oem_property_id; + u32 buf[OEM_PROPERTY_DATA_SIZE]; + u32 data_size; +}; + +struct oem_write_buf_resp_msg { + struct pmic_glink_hdr hdr; + u32 ret_code; +}; + +struct qti_charger { + char *name; + struct device *dev; + struct pmic_glink_client *client; + struct completion read_ack; + struct completion write_ack; + struct mutex read_lock; + struct mutex write_lock; + struct oem_read_buf_resp_msg rx_buf; + atomic_t rx_valid; + struct work_struct setup_work; + atomic_t state; + u32 chrg_taper_cnt; + struct mmi_battery_info batt_info; + struct mmi_charger_info chg_info; + struct mmi_charger_cfg chg_cfg; + struct mmi_charger_driver *driver; + u32 *profile_data; + struct charger_profile_info profile_info; + void *ipc_log; + bool *debug_enabled; +}; + +static int find_profile_id(const char *batt_sn) +{ + int i; + struct profile_sn_map { + int id; + const char *sn; + } map_table[] = { + {0, BATT_SN_UNKNOWN}, + }; + + i = sizeof(map_table) / sizeof(struct profile_sn_map); + while (i > 0 && map_table[--i].sn && strcmp(map_table[i].sn, batt_sn)); + + return map_table[i].id; +} + +static int handle_oem_read_ack(struct qti_charger *chg, void *data, size_t len) +{ + if (len != sizeof(chg->rx_buf)) { + mmi_err(chg, "Incorrect received length %zu expected %lu\n", len, + sizeof(chg->rx_buf)); + atomic_set(&chg->rx_valid, 0); + return -EINVAL; + } + + memcpy(&chg->rx_buf, data, sizeof(chg->rx_buf)); + atomic_set(&chg->rx_valid, 1); + complete(&chg->read_ack); + mmi_dbg(chg, "read ack for property: %u\n", chg->rx_buf.oem_property_id); + + return 0; +} + +static int handle_oem_write_ack(struct qti_charger *chg, void *data, size_t len) +{ + struct oem_write_buf_resp_msg *msg_ptr; + + if (len != sizeof(*msg_ptr)) { + mmi_err(chg, "Incorrect received length %zu expected %lu\n", len, + sizeof(*msg_ptr)); + return -EINVAL; + } + + msg_ptr = data; + if (msg_ptr->ret_code) { + mmi_err(chg, "write ack, ret_code: %u\n", msg_ptr->ret_code); + return -EINVAL; + } + + mmi_dbg(chg, "write ack\n"); + complete(&chg->write_ack); + + return 0; +} + +static int oem_callback(void *priv, void *data, size_t len) +{ + struct pmic_glink_hdr *hdr = data; + struct qti_charger *chg = priv; + + mmi_dbg(chg, "owner: %u type: %u opcode: 0x%x len:%zu\n", hdr->owner, + hdr->type, hdr->opcode, len); + + if (hdr->opcode == OEM_READ_BUF_REQ) + handle_oem_read_ack(chg, data, len); + else if (hdr->opcode == OEM_WRITE_BUF_REQ) + handle_oem_write_ack(chg, data, len); + else + mmi_err(chg, "Unknown message opcode: %d\n", hdr->opcode); + + return 0; +} + +static void oem_state_cb(void *priv, enum pmic_glink_state state) +{ + struct qti_charger *chg = priv; + + mmi_dbg(chg, "state: %d\n", state); + + atomic_set(&chg->state, state); + + switch (state) { + case PMIC_GLINK_STATE_DOWN: + case PMIC_GLINK_STATE_UP: + schedule_work(&chg->setup_work); + break; + default: + break; + } +} + +static int qti_charger_write(struct qti_charger *chg, u32 property, + const void *val, size_t val_len) +{ + struct oem_write_buf_req_msg oem_buf = { { 0 } }; + int rc; + + if (val_len > (OEM_PROPERTY_DATA_SIZE * sizeof(u32))) { + mmi_err(chg, "Incorrect data length %zu for property: %u\n", + val_len, property); + return -EINVAL; + } + + if (atomic_read(&chg->state) == PMIC_GLINK_STATE_DOWN) + return 0; + + memset(&oem_buf, 0, sizeof(oem_buf)); + oem_buf.hdr.owner = MSG_OWNER_OEM; + oem_buf.hdr.type = MSG_TYPE_REQ_RESP; + oem_buf.hdr.opcode = OEM_WRITE_BUF_REQ; + oem_buf.oem_property_id = property; + oem_buf.data_size = val_len; + memcpy(oem_buf.buf, val, val_len); + + mutex_lock(&chg->write_lock); + reinit_completion(&chg->write_ack); + + mmi_dbg(chg, "Start data write for property: %u, len=%zu\n", + property, val_len); + + rc = pmic_glink_write(chg->client, &oem_buf, + sizeof(oem_buf)); + if (rc < 0) { + mmi_err(chg, "Error in sending message rc=%d on property: %u\n", + rc, property); + goto out; + } + + rc = wait_for_completion_timeout(&chg->write_ack, + msecs_to_jiffies(OEM_WAIT_TIME_MS)); + if (!rc) { + mmi_err(chg, "timed out on property: %u\n", property); + rc = -ETIMEDOUT; + goto out; + } else { + rc = 0; + } +out: + mmi_dbg(chg, "Complete data write for property: %u\n", property); + mutex_unlock(&chg->write_lock); + return rc; +} + +static int qti_charger_read(struct qti_charger *chg, u32 property, + void *val, size_t val_len) +{ + struct oem_read_buf_req_msg oem_buf = { { 0 } }; + int rc; + + if (val_len > (OEM_PROPERTY_DATA_SIZE * sizeof(u32))) { + mmi_err(chg, "Incorrect data length %zu for property: %u\n", + val_len, property); + return -EINVAL; + } + + if (atomic_read(&chg->state) == PMIC_GLINK_STATE_DOWN) + return 0; + + oem_buf.hdr.owner = MSG_OWNER_OEM; + oem_buf.hdr.type = MSG_TYPE_REQ_RESP; + oem_buf.hdr.opcode = OEM_READ_BUF_REQ; + oem_buf.oem_property_id = property; + oem_buf.data_size = val_len; + + mutex_lock(&chg->read_lock); + reinit_completion(&chg->read_ack); + + mmi_dbg(chg, "Start data read for property: %u, len=%zu\n", + property, val_len); + + rc = pmic_glink_write(chg->client, &oem_buf, + sizeof(oem_buf)); + if (rc < 0) { + mmi_err(chg, "Error in sending message rc=%d on property: %u\n", + rc, property); + goto out; + } + + rc = wait_for_completion_timeout(&chg->read_ack, + msecs_to_jiffies(OEM_WAIT_TIME_MS)); + if (!rc) { + mmi_err(chg, "timed out on property: %u\n", property); + rc = -ETIMEDOUT; + goto out; + } else { + rc = 0; + } + + if (!atomic_read(&chg->rx_valid)) { + rc = -ENODATA; + goto out; + } + + memcpy(val, chg->rx_buf.buf, val_len); + atomic_set(&chg->rx_valid, 0); +out: + mmi_dbg(chg, "Complete data read for property: %u\n", property); + mutex_unlock(&chg->read_lock); + + return rc; +} + +static int qti_charger_get_batt_info(void *data, struct mmi_battery_info *batt_info) +{ + int rc; + struct qti_charger *chg = data; + + rc = qti_charger_read(chg, OEM_PROP_BATT_INFO, + &chg->batt_info, + sizeof(struct battery_info)); + if (rc) + return rc; + + if (chg->chg_cfg.full_charged) + chg->batt_info.batt_status = POWER_SUPPLY_STATUS_FULL; + + chg->batt_info.batt_ma /= 1000; + chg->batt_info.batt_mv /= 1000; + chg->batt_info.batt_soc /= 100; + chg->batt_info.batt_temp /= 100; + memcpy(batt_info, &chg->batt_info, sizeof(struct mmi_battery_info)); + + return rc; +} + +static int qti_charger_get_chg_info(void *data, struct mmi_charger_info *chg_info) +{ + int rc; + struct qti_charger *chg = data; + + rc = qti_charger_read(chg, OEM_PROP_CHG_INFO, + &chg->chg_info, + sizeof(struct charger_info)); + if (rc) + return rc; + + chg->chg_info.chrg_mv /= 1000; + chg->chg_info.chrg_ma /= 1000; + memcpy(chg_info, &chg->chg_info, sizeof(struct mmi_charger_info)); + + return rc; +} + +static int qti_charger_config_charge(void *data, struct mmi_charger_cfg *config) +{ + int rc; + u32 value; + struct qti_charger *chg = data; + + /* configure the charger if changed */ + if (config->target_fv != chg->chg_cfg.target_fv) { + value = config->target_fv * 1000; + rc = qti_charger_write(chg, OEM_PROP_CHG_FV, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.target_fv = config->target_fv; + } + if (config->target_fcc != chg->chg_cfg.target_fcc) { + value = config->target_fcc * 1000; + rc = qti_charger_write(chg, OEM_PROP_CHG_FCC, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.target_fcc = config->target_fcc; + } + if (config->charger_suspend != chg->chg_cfg.charger_suspend) { + value = config->charger_suspend; + rc = qti_charger_write(chg, OEM_PROP_CHG_SUSPEND, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.charger_suspend = config->charger_suspend; + } + if (config->charging_disable != chg->chg_cfg.charging_disable) { + value = config->charging_disable; + rc = qti_charger_write(chg, OEM_PROP_CHG_DISABLE, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.charging_disable = config->charging_disable; + } + if (config->demo_mode != chg->chg_cfg.demo_mode) { + value = config->demo_mode; + rc = qti_charger_write(chg, OEM_PROP_DEMO_MODE, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.demo_mode = config->demo_mode; + } + if (config->factory_version != chg->chg_cfg.factory_version) { + value = config->factory_version; + rc = qti_charger_write(chg, OEM_PROP_FACTORY_VERSION, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.factory_version = config->factory_version; + } + if (config->factory_mode != chg->chg_cfg.factory_mode) { + value = config->factory_mode; + rc = qti_charger_write(chg, OEM_PROP_FACTORY_MODE, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.factory_mode = config->factory_mode; + } + + if (config->taper_kickoff != chg->chg_cfg.taper_kickoff) { + chg->chg_cfg.taper_kickoff = config->taper_kickoff; + chg->chrg_taper_cnt = 0; + } + + if (config->full_charged != chg->chg_cfg.full_charged) { + chg->chg_cfg.full_charged = config->full_charged; + } + + if (config->chrg_iterm != chg->chg_cfg.chrg_iterm) { + value = config->chrg_iterm; + rc = qti_charger_write(chg, OEM_PROP_CHG_ITERM, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.chrg_iterm = config->chrg_iterm; + } + if (config->fg_iterm != chg->chg_cfg.fg_iterm) { + value = config->fg_iterm; + rc = qti_charger_write(chg, OEM_PROP_CHG_FG_ITERM, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.fg_iterm = config->fg_iterm; + } + + if (config->dcp_pmax != chg->chg_cfg.dcp_pmax) { + value = config->dcp_pmax; + rc = qti_charger_write(chg, OEM_PROP_CHG_BC_PMAX, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.dcp_pmax = config->dcp_pmax; + } + + if (config->hvdcp_pmax != chg->chg_cfg.hvdcp_pmax) { + value = config->hvdcp_pmax; + rc = qti_charger_write(chg, OEM_PROP_CHG_QC_PMAX, + &value, + sizeof(value)); + if (!rc) + chg->chg_cfg.hvdcp_pmax = config->hvdcp_pmax; + } + + if (config->charging_reset != chg->chg_cfg.charging_reset) { + if (config->charging_reset) { + value = 1; + rc = qti_charger_write(chg, OEM_PROP_CHG_DISABLE, + &value, + sizeof(value)); + msleep(200); + value = 0; + rc = qti_charger_write(chg, OEM_PROP_CHG_DISABLE, + &value, + sizeof(value)); + } + chg->chg_cfg.charging_reset = config->charging_reset; + } + + return 0; +} + +#define TAPER_COUNT 2 +static bool qti_charger_is_charge_tapered(void *data, int tapered_ma) +{ + bool is_tapered = false; + struct qti_charger *chg = data; + + if (abs(chg->batt_info.batt_ma) <= tapered_ma) { + if (chg->chrg_taper_cnt >= TAPER_COUNT) { + is_tapered = true; + chg->chrg_taper_cnt = 0; + } else + chg->chrg_taper_cnt++; + } else + chg->chrg_taper_cnt = 0; + + return is_tapered; +} +static bool qti_charger_is_charge_halt(void *data) +{ + struct qti_charger *chg = data; + + if (chg->batt_info.batt_status == POWER_SUPPLY_STATUS_NOT_CHARGING || + chg->batt_info.batt_status == POWER_SUPPLY_STATUS_FULL) + return true; + + return false; +} +static int qti_charger_write_profile(struct qti_charger *chg) +{ + int rc; + char *data; + int offset; + int max_size = OEM_PROPERTY_DATA_SIZE * sizeof(u32); + + rc = qti_charger_write(chg, OEM_PROP_CHG_PROFILE_INFO, + &chg->profile_info, + sizeof(struct charger_profile_info)); + if (rc) { + mmi_err(chg, "qti charger write profile info failed, rc=%d\n", rc); + return rc; + } + + if (!chg->profile_data) + return 0; + + offset = 0; + data = (char *)chg->profile_data; + while (offset < chg->profile_info.data_size) { + if ((chg->profile_info.data_size - offset) > max_size) { + rc = qti_charger_write(chg, + OEM_PROP_CHG_PROFILE_DATA, + data + offset, + max_size); + offset += max_size; + } else { + rc = qti_charger_write(chg, + OEM_PROP_CHG_PROFILE_DATA, + data + offset, + chg->profile_info.data_size - offset); + offset += chg->profile_info.data_size - offset; + } + if (rc) { + mmi_err(chg, "qti charger write profile data failed, rc=%d\n", rc); + break; + } + } + + return rc; +} + +static int qti_charger_init(struct qti_charger *chg) +{ + int rc; + u32 value; + struct mmi_charger_driver *driver; + + if (chg->driver) { + mmi_warn(chg, "qti charger has already inited\n"); + return 0; + } + + chg->chg_cfg.factory_mode = mmi_is_factory_mode(); + value = chg->chg_cfg.factory_mode; + rc = qti_charger_write(chg, OEM_PROP_FACTORY_MODE, + &value, + sizeof(value)); + if (rc) { + mmi_err(chg, "qti charger set factory mode failed, rc=%d\n", rc); + return rc; + } + + chg->chg_cfg.factory_version = mmi_is_factory_version(); + value = chg->chg_cfg.factory_version; + rc = qti_charger_write(chg, OEM_PROP_FACTORY_VERSION, + &value, + sizeof(value)); + if (rc) { + mmi_err(chg, "qti charger set factory ver failed, rc=%d\n", rc); + return rc; + } + + rc = qti_charger_write_profile(chg); + if (rc) { + mmi_err(chg, "qti charger set profile data failed, rc=%d\n", rc); + return rc; + } + + driver = devm_kzalloc(chg->dev, + sizeof(struct mmi_charger_driver), + GFP_KERNEL); + if (!driver) + return -ENOMEM; + + /* init driver */ + driver->name = chg->name; + driver->dev = chg->dev; + driver->data = chg; + driver->get_batt_info = qti_charger_get_batt_info; + driver->get_chg_info = qti_charger_get_chg_info; + driver->config_charge = qti_charger_config_charge; + driver->is_charge_tapered = qti_charger_is_charge_tapered; + driver->is_charge_halt = qti_charger_is_charge_halt; + chg->driver = driver; + + /* register driver to mmi charger */ + rc = mmi_register_charger_driver(driver); + if (rc) { + mmi_err(chg, "qti charger init failed, rc=%d\n", rc); + } else { + mmi_info(chg, "qti charger init successfully\n"); + } + + return 0; +} + +static void qti_charger_deinit(struct qti_charger *chg) +{ + int rc; + + if (!chg->driver) { + mmi_info(chg, "qti charger has not inited yet\n"); + return; + } + + /* unregister driver from mmi charger */ + rc = mmi_unregister_charger_driver(chg->driver); + if (rc) { + mmi_err(chg, "qti charger deinit failed, rc=%d\n", rc); + } else { + devm_kfree(chg->dev, chg->driver); + chg->driver = NULL; + } +} + +static void qti_charger_setup_work(struct work_struct *work) +{ + struct qti_charger *chg = container_of(work, + struct qti_charger, setup_work); + enum pmic_glink_state state; + + state = atomic_read(&chg->state); + if (state == PMIC_GLINK_STATE_UP) { + qti_charger_init(chg); + } else if (state == PMIC_GLINK_STATE_DOWN) { + qti_charger_deinit(chg); + } +} + +static int qti_charger_parse_dt(struct qti_charger *chg) +{ + int rc; + int i, j; + int n = 0; + int bk_num; + int bk_size; + char bk_buf[128]; + int byte_len; + const char *df_sn = NULL, *dev_sn = NULL; + struct device_node *node; + + node = chg->dev->of_node; + dev_sn = mmi_get_battery_serialnumber(); + if (!dev_sn) { + rc = of_property_read_string(node, "mmi,df-serialnum", + &df_sn); + if (!rc && df_sn) { + mmi_info(chg, "Default Serial Number %s\n", df_sn); + } else { + mmi_err(chg, "No Default Serial Number defined\n"); + df_sn = BATT_SN_UNKNOWN; + } + strcpy(chg->batt_info.batt_sn, df_sn); + } else { + strcpy(chg->batt_info.batt_sn, dev_sn); + } + chg->profile_info.profile_id = find_profile_id(chg->batt_info.batt_sn); + + rc = of_property_read_u32(node, "mmi,chrg-iterm-ma", + &chg->profile_info.chrg_iterm); + if (rc) { + chg->profile_info.chrg_iterm = 300000; + } else { + chg->profile_info.chrg_iterm *= 1000; + } + + rc = of_property_read_u32(node, "mmi,fg-iterm-ma", + &chg->profile_info.fg_iterm); + if (rc) { + chg->profile_info.fg_iterm = + chg->profile_info.chrg_iterm + 50000; + } else { + chg->profile_info.fg_iterm *= 1000; + } + + rc = of_property_read_u32(node, "mmi,vfloat-comp-uv", + &chg->profile_info.vfloat_comp_uv); + if (rc) + chg->profile_info.vfloat_comp_uv = 0; + + rc = of_property_read_u32(node, "mmi,max-fv-mv", + &chg->profile_info.max_fv_uv); + if (rc) + chg->profile_info.max_fv_uv = 4400; + chg->profile_info.max_fv_uv *= 1000; + + rc = of_property_read_u32(node, "mmi,max-fcc-ma", + &chg->profile_info.max_fcc_ua); + if (rc) + chg->profile_info.max_fcc_ua = 4000; + chg->profile_info.max_fcc_ua *= 1000; + + rc = of_property_read_u32(node, "mmi,demo-fv-mv", + &chg->profile_info.demo_fv_uv); + if (rc) + chg->profile_info.demo_fv_uv = 4000; + chg->profile_info.demo_fv_uv *= 1000; + + rc = of_property_read_u32(node, "mmi,profile-data-block-size", + &chg->profile_info.data_bk_size); + if (rc) + chg->profile_info.data_bk_size = 4; + chg->profile_info.data_bk_size *= 4; + + chg->profile_info.data_size = 0; + if (of_find_property(node, "mmi,profile-data", &byte_len)) { + if (byte_len % chg->profile_info.data_bk_size) { + mmi_err(chg, "DT error wrong profile data\n"); + chg->profile_info.data_bk_size = 0; + return -ENODEV; + } + bk_num = byte_len / chg->profile_info.data_bk_size; + chg->profile_data = (u32 *)devm_kzalloc(chg->dev, byte_len, + GFP_KERNEL); + if (chg->profile_data == NULL) { + chg->profile_info.data_bk_size = 0; + return -ENOMEM; + } + + rc = of_property_read_u32_array(node, + "mmi,profile-data", + chg->profile_data, + byte_len / sizeof(u32)); + if (rc < 0) { + mmi_err(chg, "Couldn't read profile data, rc = %d\n", rc); + devm_kfree(chg->dev, chg->profile_data); + chg->profile_data = NULL; + chg->profile_info.data_bk_size = 0; + return rc; + } + + chg->profile_info.data_size = byte_len; + mmi_info(chg, "profile data: block size: %d, num: %d\n", + chg->profile_info.data_bk_size, bk_num); + bk_size = chg->profile_info.data_bk_size / 4; + for (i = 0; i < bk_num; i++) { + memset(bk_buf, '\0', sizeof(bk_buf)); + n = sprintf(bk_buf, "block%d:", i); + for (j = 0; j < bk_size; j++) { + n += sprintf(bk_buf + n, " %d", + chg->profile_data[i * bk_size + j]); + } + mmi_info(chg, "%s\n", bk_buf); + } + } + + return 0; +} + +static int qti_charger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pmic_glink_client_data client_data; + struct qti_charger *chg; + int rc; + + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + INIT_WORK(&chg->setup_work, qti_charger_setup_work); + mutex_init(&chg->read_lock); + mutex_init(&chg->write_lock); + init_completion(&chg->read_ack); + init_completion(&chg->write_ack); + atomic_set(&chg->rx_valid, 0); + atomic_set(&chg->state, PMIC_GLINK_STATE_UP); + platform_set_drvdata(pdev, chg); + chg->dev = dev; + chg->name = "qti_glink_charger"; + + chg->debug_enabled = &debug_enabled; + chg->ipc_log = ipc_log_context_create(MMI_LOG_PAGES, MMI_LOG_DIR, 0); + if (!chg->ipc_log) + mmi_warn(chg, "Error in creating ipc_log_context\n"); + + rc = qti_charger_parse_dt(chg); + if (rc) { + mmi_err(chg, "dt paser failed, rc=%d\n", rc); + return rc; + } + + client_data.id = MSG_OWNER_OEM; + client_data.name = "oem"; + client_data.msg_cb = oem_callback; + client_data.priv = chg; + client_data.state_cb = oem_state_cb; + + chg->client = pmic_glink_register_client(dev, &client_data); + if (IS_ERR(chg->client)) { + rc = PTR_ERR(chg->client); + if (rc != -EPROBE_DEFER) + mmi_err(chg, "Error in registering with pmic_glink rc=%d\n", + rc); + return rc; + } + + qti_charger_init(chg); + + return 0; +} + +static int qti_charger_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qti_charger *chg= dev_get_drvdata(dev); + int rc; + + qti_charger_deinit(chg); + + rc = pmic_glink_unregister_client(chg->client); + if (rc < 0) + mmi_err(chg, "pmic_glink_unregister_client failed rc=%d\n", + rc); + + return rc; +} + +static const struct of_device_id qti_charger_match_table[] = { + {.compatible = "mmi,qti-glink-charger"}, + {}, +}; + +static struct platform_driver qti_charger_driver = { + .driver = { + .name = "qti_glink_charger", + .of_match_table = qti_charger_match_table, + }, + .probe = qti_charger_probe, + .remove = qti_charger_remove, +}; + +module_platform_driver(qti_charger_driver); + +MODULE_DESCRIPTION("QTI Glink Charger Driver"); +MODULE_LICENSE("GPL v2");