mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-11 23:24:22 -04:00
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 <yanyh2@motorola.com>
Reviewed-on: https://gerrit.mot.com/1800646
SME-Granted: SME Approvals Granted
SLTApproved: Slta Waiver
Tested-by: Jira Key
Reviewed-by: Wentao Xu <xuwt2@motorola.com>
Reviewed-by: Jianqi Yang <yangj@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
parent
6c2bbf2914
commit
be4f6e6ce6
4 changed files with 915 additions and 0 deletions
10
drivers/power/mmi_charger/qti_glink_charger/Android.mk
Executable file
10
drivers/power/mmi_charger/qti_glink_charger/Android.mk
Executable file
|
|
@ -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
|
||||
7
drivers/power/mmi_charger/qti_glink_charger/Kbuild
Executable file
7
drivers/power/mmi_charger/qti_glink_charger/Kbuild
Executable file
|
|
@ -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
|
||||
10
drivers/power/mmi_charger/qti_glink_charger/Makefile
Executable file
10
drivers/power/mmi_charger/qti_glink_charger/Makefile
Executable file
|
|
@ -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
|
||||
888
drivers/power/mmi_charger/qti_glink_charger/qti_glink_charger.c
Normal file
888
drivers/power/mmi_charger/qti_glink_charger/qti_glink_charger.c
Normal file
|
|
@ -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 <linux/version.h>
|
||||
#include <linux/alarmtimer.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/soc/qcom/pmic_glink.h>
|
||||
|
||||
#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");
|
||||
Loading…
Reference in a new issue