1025 lines
28 KiB
C
1025 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#define LOG_TAG "Charger"
|
|
|
|
#include "cts_config.h"
|
|
#include "cts_platform.h"
|
|
#include "cts_core.h"
|
|
#include "cts_sysfs.h"
|
|
|
|
#ifdef CONFIG_CTS_CHARGER_DETECT
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0)
|
|
/**
|
|
* match_string - matches given string in an array
|
|
* @array: array of strings
|
|
* @n: number of strings in the array or -1 for NULL terminated arrays
|
|
* @string: string to match with
|
|
*
|
|
* Return:
|
|
* index of a @string in the @array if matches, or %-EINVAL otherwise.
|
|
*/
|
|
int match_string(const char *const *array, size_t n, const char *string)
|
|
{
|
|
int index;
|
|
const char *item;
|
|
|
|
for (index = 0; index < n; index++) {
|
|
item = array[index];
|
|
if (!item)
|
|
break;
|
|
if (!strcmp(item, string))
|
|
return index;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
|
|
#define CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
#endif
|
|
#define CFG_CTS_CHARGER_DETECT_PSY_POLL
|
|
|
|
#include <linux/power_supply.h>
|
|
#include <linux/notifier.h>
|
|
|
|
enum cts_charger_detect_type {
|
|
CTS_CHGR_DET_TYPE_NONE = 0,
|
|
CTS_CHGR_DET_TYPE_PSY_NOTIFY,
|
|
CTS_CHGR_DET_TYPE_POLL_PSP,
|
|
CTS_CHGR_DET_TYPE_MAX
|
|
};
|
|
|
|
/* Over-written setting for DTS */
|
|
/* #define CFG_CTS_DEF_CHGR_DET_ENABLE */
|
|
|
|
/* Default settings */
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
#define CFG_CTS_DEF_CHGR_DET_TYPE CTS_CHGR_DET_TYPE_PSY_NOTIFY
|
|
#else /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */
|
|
#define CFG_CTS_DEF_CHGR_DET_TYPE CTS_CHGR_DET_TYPE_POLL_PSP
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */
|
|
|
|
#define CFG_CTS_DEF_CHGR_DET_PSY_NAME "usb"
|
|
#define CFG_CTS_DEF_CHGR_DET_PSY_PROP POWER_SUPPLY_PROP_ONLINE
|
|
#define CFG_CTS_DEF_CHGR_DET_PSP_POLL_INTERVAL 2000u
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
|
|
/* Lower version has no power_supply_get_property() */
|
|
#define POWER_SUPPLY_GET_PROPERTY(psy, psp, val) \
|
|
power_supply_get_property(psy, psp, val)
|
|
#else
|
|
#define POWER_SUPPLY_GET_PROPERTY(psy, psp, val) \
|
|
psy->get_property(psy, psp, val)
|
|
#endif
|
|
|
|
struct cts_charger_detect_data {
|
|
bool enable;
|
|
bool running;
|
|
bool state;
|
|
|
|
/* Parameter */
|
|
enum cts_charger_detect_type type;
|
|
const char *psy_name;
|
|
enum power_supply_property psp;
|
|
u32 psp_poll_interval; /* Unit is ms */
|
|
|
|
/* Power supply notify */
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
bool notifier_registered;
|
|
struct notifier_block psy_notifier;
|
|
#endif
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL
|
|
/* Polling power supply property */
|
|
struct delayed_work psp_poll_work;
|
|
#endif
|
|
|
|
struct work_struct set_charger_state_work;
|
|
|
|
/* Sysfs */
|
|
#ifdef CONFIG_CTS_SYSFS
|
|
bool sysfs_created;
|
|
#endif
|
|
|
|
struct chipone_ts_data *cts_data;
|
|
|
|
};
|
|
|
|
#define POWER_SUPPLY_PROP_PREFIX "POWER_SUPPLY_PROP_"
|
|
#define POWER_SUPPLY_PROP_TOKEN(property) \
|
|
{.prop = POWER_SUPPLY_PROP_ ##property, \
|
|
.name = POWER_SUPPLY_PROP_PREFIX #property}
|
|
|
|
const static struct {
|
|
enum power_supply_property prop;
|
|
const char *name;
|
|
} power_supply_prop_token[] = {
|
|
POWER_SUPPLY_PROP_TOKEN(STATUS),
|
|
POWER_SUPPLY_PROP_TOKEN(PRESENT),
|
|
POWER_SUPPLY_PROP_TOKEN(ONLINE),
|
|
};
|
|
|
|
static const char *power_supply_prop_str(enum power_supply_property prop)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(power_supply_prop_token); i++) {
|
|
if (prop == power_supply_prop_token[i].prop)
|
|
return power_supply_prop_token[i].name;
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
static enum power_supply_property power_supply_prop_from_name(const char *name)
|
|
{
|
|
size_t offset = strlen(POWER_SUPPLY_PROP_PREFIX);
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(power_supply_prop_token); i++) {
|
|
if (strcasecmp(power_supply_prop_token[i].name + offset, name) == 0
|
|
|| strcasecmp(power_supply_prop_token[i].name, name) == 0) {
|
|
return power_supply_prop_token[i].prop;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static const char *charger_detect_type_text[] = {
|
|
"none", "notify", "poll",
|
|
};
|
|
|
|
static const char *charger_detect_type_str(enum cts_charger_detect_type type)
|
|
{
|
|
return type < ARRAY_SIZE(charger_detect_type_text) ?
|
|
charger_detect_type_text[type] : "Unknown";
|
|
}
|
|
|
|
static int parse_charger_detect_dt(struct cts_charger_detect_data *cd_data,
|
|
struct device_node *np)
|
|
{
|
|
const char *psy_name;
|
|
const char *type_str;
|
|
const char *psp_str;
|
|
int ret;
|
|
|
|
cts_info("Parse dt");
|
|
|
|
#ifdef CFG_CTS_DEF_CHGR_DET_ENABLE
|
|
cd_data->enable = true;
|
|
#else /* CFG_CTS_DEF_CHGR_DET_ENABLE */
|
|
cd_data->enable =
|
|
of_property_read_bool(np, "chipone,touch-charger-detect-enable");
|
|
#endif /* CFG_CTS_DEF_CHGR_DET_ENABLE */
|
|
|
|
cd_data->type = CFG_CTS_DEF_CHGR_DET_TYPE;
|
|
ret = of_property_read_string(np,
|
|
"chipone,touch-charger-detect-type", &type_str);
|
|
if (ret)
|
|
cts_warn("Parse detect type failed %d", ret);
|
|
else {
|
|
int type = match_string(charger_detect_type_text,
|
|
ARRAY_SIZE(charger_detect_type_text),
|
|
type_str);
|
|
if (type < 0)
|
|
cts_err("Parse detect type '%s' invalid", type_str);
|
|
else
|
|
cd_data->type = type;
|
|
}
|
|
|
|
ret = of_property_read_string(np, "chipone,touch-charger-detect-psy-name",
|
|
&psy_name);
|
|
if (ret) {
|
|
cts_warn("Parse detect psy name failed %d", ret);
|
|
psy_name = CFG_CTS_DEF_CHGR_DET_PSY_NAME;
|
|
} else {
|
|
if (power_supply_get_by_name(psy_name) == NULL) {
|
|
cts_warn("Power supply '%s' not found", psy_name);
|
|
psy_name = CFG_CTS_DEF_CHGR_DET_PSY_NAME;
|
|
}
|
|
}
|
|
cd_data->psy_name = kstrdup(psy_name, GFP_KERNEL);
|
|
if (cd_data->psy_name == NULL) {
|
|
cts_err("Alloc mem for psy name failed");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cd_data->psp = CFG_CTS_DEF_CHGR_DET_PSY_PROP;
|
|
ret = of_property_read_string(np, "chipone,touch-charger-detect-psp",
|
|
&psp_str);
|
|
if (ret) {
|
|
cts_warn("Parse detect psp failed %d", ret);
|
|
} else {
|
|
struct power_supply *psy;
|
|
enum power_supply_property psp;
|
|
union power_supply_propval val;
|
|
|
|
psp = power_supply_prop_from_name(psp_str);
|
|
if (psp < 0) {
|
|
cts_warn("Parse detect psp: '%s' invalid", psp_str);
|
|
} else {
|
|
psy = power_supply_get_by_name(cd_data->psy_name);
|
|
if (psy != NULL && POWER_SUPPLY_GET_PROPERTY(psy, psp, &val) >= 0) {
|
|
cts_err("Parse detect psp invalid");
|
|
} else {
|
|
cd_data->psp = psp;
|
|
}
|
|
}
|
|
}
|
|
|
|
cd_data->psp_poll_interval = CFG_CTS_DEF_CHGR_DET_PSP_POLL_INTERVAL;
|
|
ret = of_property_read_u32(np,
|
|
"chipone,touch-charger-detect-psp-poll-interval",
|
|
&cd_data->psp_poll_interval);
|
|
if (ret)
|
|
cts_warn("Parse detect psp poll interval failed %d", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int start_charger_detect(struct cts_charger_detect_data *cd_data)
|
|
{
|
|
if (!cd_data->enable) {
|
|
cts_warn("Start detect while NOT enabled");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cd_data->running) {
|
|
cts_warn("Start detect while already RUNNING");
|
|
return 0;
|
|
}
|
|
|
|
cts_info("Start detect type: %d(%s)",
|
|
cd_data->type, charger_detect_type_str(cd_data->type));
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
if (cd_data->type == CTS_CHGR_DET_TYPE_PSY_NOTIFY) {
|
|
int ret = power_supply_reg_notifier(&cd_data->psy_notifier);
|
|
|
|
if (ret) {
|
|
cts_err("Register notifier failed: %d", ret);
|
|
return ret;
|
|
}
|
|
cd_data->notifier_registered = true;
|
|
|
|
cd_data->running = true;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL
|
|
if (cd_data->type == CTS_CHGR_DET_TYPE_POLL_PSP) {
|
|
if (!queue_delayed_work(cd_data->cts_data->workqueue,
|
|
&cd_data->psp_poll_work,
|
|
msecs_to_jiffies(cd_data->psp_poll_interval))) {
|
|
cts_warn("Queue detect work while already on the queue");
|
|
}
|
|
cd_data->running = true;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_POLL */
|
|
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
static int stop_charger_detect(struct cts_charger_detect_data *cd_data)
|
|
{
|
|
if (!cd_data->running) {
|
|
cts_warn("Stop detect while NOT running");
|
|
return 0;
|
|
}
|
|
|
|
cts_info("Stop detect type: %d(%s)", cd_data->type,
|
|
charger_detect_type_str(cd_data->type));
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
if (cd_data->type == CTS_CHGR_DET_TYPE_PSY_NOTIFY) {
|
|
if (cd_data->notifier_registered) {
|
|
power_supply_unreg_notifier(&cd_data->psy_notifier);
|
|
cd_data->notifier_registered = false;
|
|
}
|
|
cd_data->running = false;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL
|
|
if (cd_data->type == CTS_CHGR_DET_TYPE_POLL_PSP) {
|
|
if (!cancel_delayed_work_sync(&cd_data->psp_poll_work))
|
|
cts_warn("Cancel poll psp work while NOT pending");
|
|
|
|
cd_data->running = false;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_POLL */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int enable_charger_detect(struct cts_charger_detect_data *cd_data)
|
|
{
|
|
cts_info("Enable detect type: %d(%s)", cd_data->type,
|
|
charger_detect_type_str(cd_data->type));
|
|
|
|
cd_data->enable = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int disable_charger_detect(struct cts_charger_detect_data *cd_data)
|
|
{
|
|
int ret;
|
|
|
|
cts_info("Disable detect type: %d(%s)", cd_data->type,
|
|
charger_detect_type_str(cd_data->type));
|
|
|
|
ret = stop_charger_detect(cd_data);
|
|
if (ret)
|
|
cts_err("Disable detect failed %d", ret);
|
|
|
|
cd_data->enable = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_charger_state(struct cts_charger_detect_data *cd_data)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval propval;
|
|
int ret;
|
|
|
|
cts_dbg("Get state from psy: '%s' prop: %d(%s)",
|
|
cd_data->psy_name, cd_data->psp,
|
|
power_supply_prop_str(cd_data->psp));
|
|
|
|
psy = power_supply_get_by_name(cd_data->psy_name);
|
|
if (cd_data->psy_name == NULL || psy == NULL) {
|
|
cts_err("Get state from psy: '%s' not found", cd_data->psy_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = POWER_SUPPLY_GET_PROPERTY(psy, cd_data->psp, &propval);
|
|
if (ret < 0) {
|
|
cts_err("Get state from psy: '%s' prop: %d(%s) failed",
|
|
cd_data->psy_name, cd_data->psp,
|
|
power_supply_prop_str(cd_data->psp));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* ONLY for bool type */
|
|
cd_data->state = !!propval.intval;
|
|
|
|
cts_dbg("State: %s", cd_data->state ? "ATTACHED" : "DETACHED");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int switch_charger_detect_type(struct cts_charger_detect_data *cd_data,
|
|
enum cts_charger_detect_type type)
|
|
{
|
|
bool running;
|
|
|
|
cts_info("Switch detect type to %d(%s)",
|
|
type, charger_detect_type_str(type));
|
|
|
|
if (type >= CTS_CHGR_DET_TYPE_MAX) {
|
|
cts_err("Switch detect type %d invalid", type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cd_data->type == type) {
|
|
cts_warn("Switch detect type equal");
|
|
return 0;
|
|
}
|
|
|
|
running = cd_data->running;
|
|
|
|
if (running) {
|
|
int ret = stop_charger_detect(cd_data);
|
|
if (ret) {
|
|
cts_err("Stop detect failed %d", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
cd_data->type = type;
|
|
|
|
if (running) {
|
|
int ret = start_charger_detect(cd_data);
|
|
if (ret) {
|
|
cts_err("Start detect failed %d", ret);
|
|
cd_data->type = CTS_CHGR_DET_TYPE_NONE;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_dev_charger_state_work(struct work_struct *work)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
int ret;
|
|
|
|
cd_data = container_of(work, struct cts_charger_detect_data,
|
|
set_charger_state_work);
|
|
|
|
cts_lock_device(&cd_data->cts_data->cts_dev);
|
|
ret = cts_set_dev_charger_attached(&cd_data->cts_data->cts_dev,
|
|
cd_data->state);
|
|
cts_unlock_device(&cd_data->cts_data->cts_dev);
|
|
if (ret) {
|
|
cts_err("Set dev charger attached to %s failed %d",
|
|
cd_data->state ? "ATTACHED" : "DETATCHED", ret);
|
|
/* Set to previous state, try set again in next loop */
|
|
cd_data->state = !cd_data->state;
|
|
}
|
|
}
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
static int psy_notify_callback(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
struct power_supply *psy;
|
|
bool prev_state;
|
|
int ret;
|
|
|
|
if (nb == NULL || data == NULL) {
|
|
cts_err("PSY notify callback with notifier %p or data %p = NULL",
|
|
nb, data);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
if (action != PSY_EVENT_PROP_CHANGED) {
|
|
cts_dbg("Notify event %ld not care", action);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
cd_data = container_of(nb, struct cts_charger_detect_data, psy_notifier);
|
|
|
|
psy = (struct power_supply *)data;
|
|
if (strcmp(psy->desc->name, cd_data->psy_name) != 0) {
|
|
cts_dbg("Notify from power supply '%s' not care", psy->desc->name);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
prev_state = cd_data->state;
|
|
|
|
ret = get_charger_state(cd_data);
|
|
if (ret < 0) {
|
|
cts_err("Get state failed %d", ret);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
cts_info("State changed: %s -> %s", prev_state ? "ATTACHED" : "DETACHED",
|
|
cd_data->state ? "ATTACHED" : "DETACHED");
|
|
|
|
if (!queue_work(cd_data->cts_data->workqueue,
|
|
&cd_data->set_charger_state_work)) {
|
|
cts_warn("Set device charger state work is PENDING");
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_NOTIFY */
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL
|
|
static void poll_psp_work(struct work_struct *work)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
bool prev_state;
|
|
int ret;
|
|
|
|
cts_dbg("Poll psp work");
|
|
|
|
cd_data = container_of(to_delayed_work(work),
|
|
struct cts_charger_detect_data, psp_poll_work);
|
|
|
|
prev_state = cd_data->state;
|
|
|
|
ret = get_charger_state(cd_data);
|
|
if (ret < 0) {
|
|
cts_err("Get state failed %d", ret);
|
|
} else {
|
|
if (cd_data->state != prev_state) {
|
|
cts_info("State changed: %s -> %s",
|
|
prev_state ? "ATTACHED" : "DETACHED",
|
|
cd_data->state ? "ATTACHED" : "DETACHED");
|
|
|
|
if (!queue_work(cd_data->cts_data->workqueue,
|
|
&cd_data->set_charger_state_work)) {
|
|
cts_warn("Set device charger state work is PENDING");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!queue_delayed_work(cd_data->cts_data->workqueue,
|
|
&cd_data->psp_poll_work,
|
|
msecs_to_jiffies(cd_data->psp_poll_interval))) {
|
|
cts_warn("Queue detect work while already on the queue");
|
|
}
|
|
}
|
|
#endif /* CFG_CTS_CHARGER_DETECT_PSY_POLL */
|
|
|
|
static int init_charger_detect(struct cts_charger_detect_data *cd_data)
|
|
{
|
|
int ret;
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_NOTIFY
|
|
cd_data->psy_notifier.notifier_call = psy_notify_callback;
|
|
#endif
|
|
|
|
#ifdef CFG_CTS_CHARGER_DETECT_PSY_POLL
|
|
INIT_DELAYED_WORK(&cd_data->psp_poll_work, poll_psp_work);
|
|
#endif
|
|
|
|
INIT_WORK(&cd_data->set_charger_state_work, set_dev_charger_state_work);
|
|
|
|
ret = get_charger_state(cd_data);
|
|
if (ret)
|
|
cts_err("Get state failed %d", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Sysfs */
|
|
#ifdef CONFIG_CTS_SYSFS
|
|
|
|
#define CHARGER_DET_SYSFS_GROUP_NAME "charger-det"
|
|
|
|
static ssize_t charger_detect_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
|
|
cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'",
|
|
attr->attr.name);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "Charger detect: %s\n",
|
|
cd_data->enable ? "ENABLED" : "DISABLED");
|
|
}
|
|
|
|
/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > enable */
|
|
static ssize_t charger_detect_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
bool enable;
|
|
int ret;
|
|
|
|
cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu",
|
|
attr->attr.name, count);
|
|
|
|
cts_parse_arg(buf, count);
|
|
|
|
if (argc != 1) {
|
|
cts_err("Invalid num of args");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = kstrtobool(argv[0], &enable);
|
|
if (ret) {
|
|
cts_err("Invalid param of enable");
|
|
return ret;
|
|
}
|
|
|
|
if (enable)
|
|
ret = enable_charger_detect(cd_data);
|
|
else
|
|
ret = disable_charger_detect(cd_data);
|
|
|
|
if (ret) {
|
|
cts_err("%s charger detect failed %d",
|
|
enable ? "Enable" : "Disable", ret);
|
|
return ret;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO,
|
|
charger_detect_enable_show, charger_detect_enable_store);
|
|
|
|
static ssize_t charger_detect_running_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
|
|
cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'",
|
|
attr->attr.name);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "Charger detect: %sRunning\n",
|
|
cd_data->running ? "" : "Not-");
|
|
}
|
|
|
|
/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > runing */
|
|
static ssize_t charger_detect_running_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
bool running;
|
|
int ret;
|
|
|
|
cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu",
|
|
attr->attr.name, count);
|
|
|
|
cts_parse_arg(buf, count);
|
|
|
|
if (argc != 1) {
|
|
cts_err("Invalid num of args");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = kstrtobool(argv[0], &running);
|
|
if (ret) {
|
|
cts_err("Invalid param of running");
|
|
return ret;
|
|
}
|
|
|
|
if (running)
|
|
ret = start_charger_detect(cd_data);
|
|
else
|
|
ret = stop_charger_detect(cd_data);
|
|
|
|
if (ret) {
|
|
cts_err("%s charger detect failed %d", running ? "Start" : "Stop", ret);
|
|
return ret;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(running, S_IWUSR | S_IRUGO,
|
|
charger_detect_running_show, charger_detect_running_store);
|
|
|
|
static ssize_t charger_detect_type_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
|
|
cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'",
|
|
attr->attr.name);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "Charger detect type: %d(%s)\n",
|
|
cd_data->type, charger_detect_type_str(cd_data->type));
|
|
}
|
|
|
|
/* echo charger_detect_type > type */
|
|
static ssize_t charger_detect_type_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
int type, ret;
|
|
|
|
cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu",
|
|
attr->attr.name, count);
|
|
|
|
cts_parse_arg(buf, count);
|
|
|
|
if (argc != 1) {
|
|
cts_err("");
|
|
return -EINVAL;
|
|
}
|
|
|
|
type = match_string(charger_detect_type_text,
|
|
ARRAY_SIZE(charger_detect_type_text), argv[0]);
|
|
if (type < 0) {
|
|
cts_err("Invalid charger detect type: '%s'", argv[0]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = switch_charger_detect_type(cd_data, type);
|
|
if (ret) {
|
|
cts_err("Switch charger detect type failed %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(type, S_IWUSR | S_IRUGO,
|
|
charger_detect_type_show, charger_detect_type_store);
|
|
|
|
static ssize_t charger_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
int ret;
|
|
|
|
cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'",
|
|
attr->attr.name);
|
|
|
|
ret = get_charger_state(cd_data);
|
|
if (ret) {
|
|
return scnprintf(buf, PAGE_SIZE, "Get charge state failed %d\n", ret);
|
|
}
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "Charger state: %s\n",
|
|
cd_data->state ? "ATTACHED" : "DETACHED");
|
|
}
|
|
|
|
static DEVICE_ATTR(state, S_IRUGO, charger_state_show, NULL);
|
|
|
|
static ssize_t charger_detect_param_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
struct power_supply *psy;
|
|
|
|
cts_info("Read sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s'",
|
|
attr->attr.name);
|
|
|
|
psy = power_supply_get_by_name(cd_data->psy_name);
|
|
|
|
return scnprintf(buf, PAGE_SIZE,
|
|
"Power supply name: %s(%sExist)\n"
|
|
"Power supply prop: %d(%s)\n"
|
|
"Poll interval : %dms\n",
|
|
cd_data->psy_name, psy == NULL ? "Non-" : "",
|
|
cd_data->psp, power_supply_prop_str(cd_data->psp),
|
|
cd_data->psp_poll_interval);
|
|
}
|
|
|
|
/* echo psy_name psy_property [interval] > param */
|
|
static ssize_t charger_detect_param_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
|
|
struct cts_charger_detect_data *cd_data = cts_data->charger_detect_data;
|
|
struct power_supply *psy;
|
|
union power_supply_propval propval;
|
|
enum power_supply_property psp;
|
|
u32 interval;
|
|
int ret;
|
|
|
|
cts_info("Write sysfs '" CHARGER_DET_SYSFS_GROUP_NAME "/%s' size %zu",
|
|
attr->attr.name, count);
|
|
|
|
cts_parse_arg(buf, count);
|
|
|
|
if (argc < 2 || argc > 3) {
|
|
cts_err("Invalid num of args");
|
|
return -EINVAL;
|
|
}
|
|
|
|
psy = power_supply_get_by_name(argv[0]);
|
|
if (psy == NULL) {
|
|
cts_err("Power supply '%s' not found", argv[0]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
psp = power_supply_prop_from_name(argv[1]);
|
|
if (psp < 0 || POWER_SUPPLY_GET_PROPERTY(psy, psp, &propval) < 0) {
|
|
cts_err("Power supply '%s' property '%s' not valid",
|
|
argv[0], power_supply_prop_str(psp));
|
|
return -EINVAL;
|
|
}
|
|
|
|
interval = cd_data->psp_poll_interval;
|
|
if (argc > 2) {
|
|
ret = kstrtou32(argv[2], 0, &interval);
|
|
if (ret) {
|
|
cts_err("Arg interval is invalid");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (cd_data->psy_name)
|
|
kfree(cd_data->psy_name);
|
|
|
|
cd_data->psy_name = kstrdup(argv[0], GFP_KERNEL);
|
|
if (cd_data->psy_name == NULL) {
|
|
cts_err("Dup power supply name failed");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
cd_data->psp = psp;
|
|
cd_data->psp_poll_interval = interval;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(param, S_IWUSR | S_IRUGO,
|
|
charger_detect_param_show, charger_detect_param_store);
|
|
|
|
static struct attribute *charger_detect_attrs[] = {
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_running.attr,
|
|
&dev_attr_type.attr,
|
|
&dev_attr_state.attr,
|
|
&dev_attr_param.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group charger_detect_attr_group = {
|
|
.name = CHARGER_DET_SYSFS_GROUP_NAME,
|
|
.attrs = charger_detect_attrs,
|
|
};
|
|
#endif /* CONFIG_CTS_SYSFS */
|
|
|
|
int cts_charger_detect_init(struct chipone_ts_data *cts_data)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
int ret = 0;
|
|
|
|
cts_info("Init detect");
|
|
|
|
if (cts_data == NULL) {
|
|
cts_err("Init detect while cts_data = NULL");
|
|
return -EFAULT;
|
|
}
|
|
|
|
cd_data = kzalloc(sizeof(*cd_data), GFP_KERNEL);
|
|
if (cd_data == NULL) {
|
|
cts_err("Alloc charger detect data failed");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#ifdef CONFIG_CTS_OF
|
|
ret = parse_charger_detect_dt(cd_data, cts_data->device->of_node);
|
|
#else /* CONFIG_CTS_OF */
|
|
#ifdef CFG_CTS_DEF_CHGR_DET_ENABLE
|
|
cd_data->enable = true;
|
|
#else /* CFG_CTS_DEF_CHGR_DET_ENABLE */
|
|
cd_data->enable = false;
|
|
#endif /* CFG_CTS_DEF_CHGR_DET_ENABLE */
|
|
|
|
cd_data->type = CFG_CTS_DEF_CHGR_DET_TYPE;
|
|
cd_data->psp = CFG_CTS_DEF_CHGR_DET_PSY_PROP;
|
|
cd_data->psp_poll_interval = CFG_CTS_DEF_CHGR_DET_PSP_POLL_INTERVAL;
|
|
cd_data->psy_name = kstrdup(CFG_CTS_DEF_CHGR_DET_PSY_NAME, GFP_KERNEL);
|
|
if (cd_data->psy_name == NULL) {
|
|
cts_err("Dup power supply name failed");
|
|
ret = -ENOMEM;
|
|
}
|
|
#endif /* CONFIG_CTS_OF */
|
|
if (ret) {
|
|
cts_err("Get detect param failed %d", ret);
|
|
goto free_cd_data;
|
|
}
|
|
|
|
cts_info("Detect: %sABLED", cd_data->enable ? "EN" : "DIS");
|
|
cts_info(" Type : %s", charger_detect_type_str(cd_data->type));
|
|
cts_info(" PSY Name: %s, prop: %d(%s)", cd_data->psy_name,
|
|
cd_data->psp, power_supply_prop_str(cd_data->psp));
|
|
cts_info(" Poll Int: %dms", cd_data->psp_poll_interval);
|
|
|
|
ret = init_charger_detect(cd_data);
|
|
if (ret) {
|
|
cts_err("Init detect failed %d", ret);
|
|
goto free_psy_name;
|
|
}
|
|
|
|
#ifdef CONFIG_CTS_SYSFS
|
|
cts_info("Create sysfs group '%s'", CHARGER_DET_SYSFS_GROUP_NAME);
|
|
ret = sysfs_create_group(&cts_data->device->kobj,
|
|
&charger_detect_attr_group);
|
|
if (ret)
|
|
cts_warn("Create sysfs group failed %d", ret);
|
|
else
|
|
cd_data->sysfs_created = true;
|
|
|
|
#endif /* CONFIG_CTS_SYSFS */
|
|
|
|
cts_data->charger_detect_data = cd_data;
|
|
cd_data->cts_data = cts_data;
|
|
|
|
return 0;
|
|
|
|
free_psy_name:
|
|
kfree(cd_data->psy_name);
|
|
free_cd_data:
|
|
kfree(cd_data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cts_charger_detect_deinit(struct chipone_ts_data *cts_data)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
int ret;
|
|
|
|
cts_info("Deinit detect");
|
|
|
|
if (cts_data == NULL) {
|
|
cts_err("Deinit detect with cts_data = NULL");
|
|
return -EFAULT;
|
|
}
|
|
|
|
cd_data = cts_data->charger_detect_data;
|
|
if (cd_data == NULL) {
|
|
cts_warn("Deinit detect with charger_detect_data = NULL");
|
|
return 0;
|
|
}
|
|
|
|
if (cd_data->running) {
|
|
ret = stop_charger_detect(cd_data);
|
|
if (ret)
|
|
cts_err("Stop detect failed %d", ret);
|
|
}
|
|
|
|
#ifdef CONFIG_CTS_SYSFS
|
|
if (cd_data->sysfs_created) {
|
|
cts_info("Remove sysfs group '%s'", CHARGER_DET_SYSFS_GROUP_NAME);
|
|
sysfs_remove_group(&cts_data->device->kobj,
|
|
&charger_detect_attr_group);
|
|
cd_data->sysfs_created = false;
|
|
}
|
|
#endif /* CONFIG_CTS_SYSFS */
|
|
|
|
if (cd_data->psy_name) {
|
|
cts_info("Kfree power supply name");
|
|
kfree(cd_data->psy_name);
|
|
cd_data->psy_name = NULL;
|
|
}
|
|
|
|
kfree(cd_data);
|
|
cts_data->charger_detect_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cts_is_charger_attached(struct chipone_ts_data *cts_data, bool *attached)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
int ret;
|
|
|
|
if (cts_data == NULL) {
|
|
cts_err("Get state with cts_data = NULL");
|
|
return -EFAULT;
|
|
}
|
|
|
|
cd_data = cts_data->charger_detect_data;
|
|
if (cd_data == NULL) {
|
|
cts_err("Get state with charger_detect_data = NULL");
|
|
return -ENODEV;
|
|
}
|
|
|
|
cts_info("Get state using type %d(%s) param",
|
|
cd_data->type, charger_detect_type_str(cd_data->type));
|
|
|
|
ret = get_charger_state(cd_data);
|
|
if (ret) {
|
|
cts_err("Get state failed %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
*attached = cd_data->state;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cts_start_charger_detect(struct chipone_ts_data *cts_data)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
|
|
if (cts_data == NULL) {
|
|
cts_err("Start detect with cts_data = NULL");
|
|
return -EFAULT;
|
|
}
|
|
|
|
cd_data = cts_data->charger_detect_data;
|
|
if (cd_data == NULL) {
|
|
cts_err("Start detect with charger_detect_data = NULL");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return start_charger_detect(cd_data);
|
|
}
|
|
|
|
int cts_stop_charger_detect(struct chipone_ts_data *cts_data)
|
|
{
|
|
struct cts_charger_detect_data *cd_data;
|
|
|
|
if (cts_data == NULL) {
|
|
cts_err("Stop detect with cts_data = NULL");
|
|
return -EFAULT;
|
|
}
|
|
|
|
cd_data = cts_data->charger_detect_data;
|
|
if (cd_data == NULL) {
|
|
cts_err("Stop detect with charger_detect_data = NULL");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return stop_charger_detect(cd_data);
|
|
}
|
|
#endif /* CONFIG_CTS_CHARGER_DETECT */
|