mirror of
https://github.com/torvalds/linux.git
synced 2026-08-01 03:59:40 +02:00
power: supply: Add QTI battery charger
Add 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. Change-Id: I36a2efd0b66bc5f79515b5faa318c537a5927e76 Signed-off-by: Subbaraman Narayanamurthy <subbaram@codeaurora.org>
This commit is contained in:
parent
1c675f15c0
commit
dd2116df9a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
982
drivers/power/supply/qti_battery_charger.c
Normal file
982
drivers/power/supply/qti_battery_charger.c
Normal file
|
|
@ -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 <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rpmsg.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/soc/qcom/pmic_glink.h>
|
||||
#include <linux/soc/qcom/battery_charger.h>
|
||||
|
||||
#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");
|
||||
26
include/linux/soc/qcom/battery_charger.h
Normal file
26
include/linux/soc/qcom/battery_charger.h
Normal file
|
|
@ -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
|
||||
Loading…
Reference in New Issue
Block a user