650 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
#define LOG_TAG "Earjack"
#include "cts_config.h"
#include "cts_platform.h"
#include "cts_core.h"
#include "cts_sysfs.h"
#include "cts_strerror.h"
#ifdef CONFIG_CTS_EARJACK_DETECT
struct cts_earjack_detect_data {
bool enable;
bool running;
bool state;
const char *earjack_state_filepath;
struct delayed_work poll_work;
u32 poll_interval;
#ifdef CONFIG_CTS_SYSFS
bool sysfs_attr_group_created;
#endif /* CONFIG_CTS_SYSFS */
struct chipone_ts_data *cts_data;
};
/* Over-ride setting for DTS */
//#define CFG_CTS_DEF_EARJACK_DET_ENABLE
#define CFG_CTS_DEF_EARJACK_POLL_INTERVAL 2000u
#if defined(CONFIG_MTK_PLATFORM)
#define CFG_CTS_DEF_EARJACK_STATE_FILEPATH \
"/sys/bus/platform/drivers/Accdet_Driver/state"
#elif defined(CONFIG_ARCH_SPREADRUM)
#define CFG_CTS_DEF_EARJACK_STATE_FILEPATH \
"/sys/kernel/headset/state"
#else
/* Current QCOM NOT support earjack detect, please disable it or define the file path */
#define CFG_CTS_DEF_EARJACK_STATE_FILEPATH ""
#endif
static const char *earjack_state_str(bool state)
{
return state ? "ATTACHED" : "DETACHED";
}
static int parse_earjack_detect_dt(struct cts_earjack_detect_data *ed_data,
struct device_node *np)
{
const char *filepath;
int ret;
cts_info("Parse dt");
#ifdef CFG_CTS_DEF_EARJACK_DET_ENABLE
ed_data->enable = true;
#else /* CFG_CTS_DEF_EARJACK_DET_ENABLE */
ed_data->enable =
of_property_read_bool(np, "chipone,touch-earjack-detect-enable");
#endif /* CFG_CTS_DEF_EARJACK_DET_ENABLE */
ret = of_property_read_string(np,
"chipone,touch-earjack-state-filepath", &filepath);
if (ret) {
cts_warn("Parse state filepath failed %d(%s)", ret, cts_strerror(ret));
filepath = CFG_CTS_DEF_EARJACK_STATE_FILEPATH;
}
ed_data->earjack_state_filepath = kstrdup(filepath, GFP_KERNEL);
if (ed_data->earjack_state_filepath == NULL) {
cts_err("Dup earjack state filepath failed");
return -ENOMEM;
}
ed_data->poll_interval = CFG_CTS_DEF_EARJACK_POLL_INTERVAL;
ret = of_property_read_u32(np,
"chipone,touch-earjack-poll-interval", &ed_data->poll_interval);
if (ret) {
cts_warn("Parse poll interval failed %d(%s)", ret, cts_strerror(ret));
}
return 0;
}
static int start_earjack_detect(struct cts_earjack_detect_data *ed_data)
{
if (!ed_data->enable) {
cts_warn("Start detect while NOT enabled");
return -EINVAL;
}
if (ed_data->running) {
cts_warn("Start detect while already RUNNING");
return 0;
}
if (ed_data->earjack_state_filepath == NULL ||
ed_data->earjack_state_filepath[0] == '\n') {
cts_warn("Start detect with filepath = NULL/NUL");
return -EINVAL;
}
cts_info("Start detect check file: '%s'", ed_data->earjack_state_filepath);
if (!queue_delayed_work(ed_data->cts_data->workqueue,
&ed_data->poll_work,
msecs_to_jiffies(ed_data->poll_interval))) {
cts_warn("Queue detect work while already on the queue");
}
ed_data->running = true;
return 0;
}
static int stop_earjack_detect(struct cts_earjack_detect_data *ed_data)
{
if (!ed_data->running) {
cts_warn("Stop detect while NOT running");
return 0;
}
cts_info("Stop detect");
if (!cancel_delayed_work_sync(&ed_data->poll_work)) {
cts_warn("Cancel poll work while NOT pending");
}
ed_data->running = false;
return 0;
}
static int get_earjack_state(struct cts_earjack_detect_data *ed_data)
{
int ret;
char buff[10];
u32 state;
#ifndef CFG_CTS_FOR_GKI
struct file *file = NULL;
loff_t pos = 0;
int read_size;
#endif
cts_dbg("Get state from file '%s'", ed_data->earjack_state_filepath);
#ifdef CFG_CTS_FOR_GKI
cts_info("%s(): filp_open is forbiddon with GKI Version!", __func__);
#else
file = filp_open(ed_data->earjack_state_filepath, O_RDONLY, 0);
if (IS_ERR(file)) {
ret = (int)PTR_ERR(file);
cts_err("Open file '%s' failed %d(%s)",
ed_data->earjack_state_filepath, ret, cts_strerror(ret));
return PTR_ERR(file);
}
memset(buff, 0, sizeof(buff));
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0)
read_size = kernel_read(file, buff, sizeof(buff), &pos);
#else
read_size = kernel_read(file, pos, buff, sizeof(buff));
#endif
if (read_size < 0) {
cts_err("Read state file '%s' failed %d(%s)",
ed_data->earjack_state_filepath,
read_size, cts_strerror(read_size));
filp_close(file, NULL);
return read_size;
}
cts_dbg("Read state file content: '%s'", buff);
ret = filp_close(file, NULL);
if (ret) {
cts_warn("Close file '%s' failed %d(%s)",
ed_data->earjack_state_filepath, ret, cts_strerror(ret));
}
#endif
ret = kstrtou32(buff, 0, &state);
if (ret) {
cts_err("Invalid string from state file: '%s'", buff);
return ret;
}
/* Assume "0" means detached */
ed_data->state = !!state;
cts_dbg("State: %s", earjack_state_str(ed_data->state));
return 0;
}
/* Sysfs */
#ifdef CONFIG_CTS_SYSFS
#define EARJACK_DET_SYSFS_GROUP_NAME "earjack-det"
static int enable_earjack_detect(struct cts_earjack_detect_data *ed_data)
{
cts_info("Enable detect");
ed_data->enable = true;
return 0;
}
static int disable_earjack_detect(struct cts_earjack_detect_data *ed_data)
{
int ret;
cts_info("Disable detect");
ret = stop_earjack_detect(ed_data);
if (ret) {
cts_err("Stop detect failed %d(%s)", ret, cts_strerror(ret));
}
ed_data->enable = false;
return 0;
}
static ssize_t earjack_detect_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'", attr->attr.name);
return scnprintf(buf, PAGE_SIZE,
"Earjack detect: %s\n",
ed_data->enable ? "ENABLED" : "DISABLED");
}
/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > enable */
static ssize_t earjack_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_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
bool enable;
int ret;
cts_info("Write sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s' size %zu",
attr->attr.name, count);
cts_parse_arg(buf, count);
if (cts_argc != 1) {
cts_err("Invalid num of args");
return -EINVAL;
}
ret = kstrtobool(cts_argv[0], &enable);
if (ret) {
cts_err("Invalid param of enable");
return ret;
}
if (enable) {
ret = enable_earjack_detect(ed_data);
} else {
ret = disable_earjack_detect(ed_data);
}
if (ret) {
cts_err("%s earjack detect failed %d(%s)",
enable ? "Enable" : "Disable", ret, cts_strerror(ret));
return ret;
}
return count;
}
static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO,
earjack_detect_enable_show, earjack_detect_enable_store);
static ssize_t earjack_detect_running_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'",
attr->attr.name);
return scnprintf(buf, PAGE_SIZE,
"Earjack detect: %sRunning\n",
ed_data->running ? "" : "Not-");
}
/* Echo 0/n/N/of/Of/Of/OF/1/y/Y/on/oN/On/ON > runing */
static ssize_t earjack_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_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
bool running;
int ret;
cts_info("Write sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s' size %zu",
attr->attr.name, count);
cts_parse_arg(buf, count);
if (cts_argc != 1) {
cts_err("Invalid num of args");
return -EINVAL;
}
ret = kstrtobool(cts_argv[0], &running);
if (ret) {
cts_err("Invalid param of running");
return ret;
}
if (running) {
ret = start_earjack_detect(ed_data);
} else {
ret = stop_earjack_detect(ed_data);
}
if (ret) {
cts_err("%s earjack detect failed %d(%s)",
running ? "Start" : "Stop", ret, cts_strerror(ret));
return ret;
}
return count;
}
static DEVICE_ATTR(running, S_IWUSR | S_IRUGO,
earjack_detect_running_show, earjack_detect_running_store);
static ssize_t earjack_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
int ret;
cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'",
attr->attr.name);
ret = get_earjack_state(ed_data);
if (ret) {
return scnprintf(buf, PAGE_SIZE,
"Get earjack state failed %d(%s)\n",
ret, cts_strerror(ret));
}
return scnprintf(buf, PAGE_SIZE,
"Earjack state: %s\n", earjack_state_str(ed_data->state));
}
static DEVICE_ATTR(state, S_IRUGO, earjack_state_show, NULL);
static ssize_t earjack_detect_param_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct chipone_ts_data *cts_data = dev_get_drvdata(dev);
struct cts_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
cts_info("Read sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s'",
attr->attr.name);
return scnprintf(buf, PAGE_SIZE,
"Filepath: '%s'\n"
"Poll int: %dms\n",
ed_data->earjack_state_filepath, ed_data->poll_interval);
}
/* echo filepath [interval] > param */
static ssize_t earjack_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_earjack_detect_data *ed_data = cts_data->earjack_detect_data;
u32 interval;
int ret;
cts_info("Write sysfs '"EARJACK_DET_SYSFS_GROUP_NAME"/%s' size %zu",
attr->attr.name, count);
cts_parse_arg(buf, count);
if (cts_argc < 1 || cts_argc > 2) {
cts_err("Invalid num of args");
return -EINVAL;
}
interval = ed_data->poll_interval;
if (cts_argc > 1) {
ret = kstrtou32(cts_argv[1], 0, &interval);
if (ret) {
cts_err("Arg interval is invalid");
return -EINVAL;
}
}
if (ed_data->earjack_state_filepath) {
kfree(ed_data->earjack_state_filepath);
}
ed_data->earjack_state_filepath = kstrdup(cts_argv[0], GFP_KERNEL);
if (ed_data->earjack_state_filepath == NULL) {
cts_err("Dup earjack state filepath failed");
return -ENOMEM;
}
ed_data->poll_interval = interval;
return count;
}
static DEVICE_ATTR(param, S_IWUSR | S_IRUGO,
earjack_detect_param_show, earjack_detect_param_store);
static struct attribute *earjack_detect_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_running.attr,
&dev_attr_state.attr,
&dev_attr_param.attr,
NULL
};
static const struct attribute_group earjack_detect_attr_group = {
.name = EARJACK_DET_SYSFS_GROUP_NAME,
.attrs = earjack_detect_attrs,
};
#endif /* CONFIG_CTS_SYSFS */
static void poll_earjack_state_work(struct work_struct *work)
{
struct cts_earjack_detect_data *ed_data;
bool prev_state = false;
int ret;
cts_dbg("Poll earjack state work");
ed_data = container_of(to_delayed_work(work),
struct cts_earjack_detect_data, poll_work);
prev_state = ed_data->state;
ret = get_earjack_state(ed_data);
if (ret) {
cts_err("Get state failed %d(%s)", ret, cts_strerror(ret));
} else {
if (ed_data->state != prev_state) {
cts_info("State changed: %s -> %s",
earjack_state_str(prev_state),
earjack_state_str(ed_data->state));
cts_lock_device(&ed_data->cts_data->cts_dev);
ret = cts_set_dev_earjack_attached(
&ed_data->cts_data->cts_dev, ed_data->state);
cts_unlock_device(&ed_data->cts_data->cts_dev);
if (ret) {
cts_err("Set dev earjack attached to %s failed %d(%s)",
earjack_state_str(ed_data->state),
ret, cts_strerror(ret));
/* Set to previous state, try set again in next loop */
ed_data->state = prev_state;
}
}
}
if (!queue_delayed_work(ed_data->cts_data->workqueue,
&ed_data->poll_work,
msecs_to_jiffies(ed_data->poll_interval))) {
cts_warn("Queue detect work while already on the queue");
}
}
int cts_earjack_detect_init(struct chipone_ts_data *cts_data)
{
struct cts_earjack_detect_data *ed_data;
int ret = 0;
if (cts_data == NULL) {
cts_err("Init detect with cts_data = NULL");
return -EFAULT;
}
ed_data = kzalloc(sizeof(*ed_data), GFP_KERNEL);
if (ed_data == NULL) {
cts_err("Alloc earjack detect data failed");
return -ENOMEM;
}
cts_info("Init detect");
#ifdef CONFIG_CTS_OF
ret = parse_earjack_detect_dt(ed_data, cts_data->device->of_node);
#else /* CONFIG_CTS_OF */
#ifdef CFG_CTS_DEF_EARJACK_DET_ENABLE
ed_data->enable = true;
#else /* CFG_CTS_DEF_EARJACK_DET_ENABLE */
ed_data->enable = false;
#endif /* CFG_CTS_DEF_EARJACK_DET_ENABLE */
ed_data->poll_interval = CFG_CTS_DEF_EARJACK_POLL_INTERVAL;
ed_data->earjack_state_filepath = kstrdup(
CFG_CTS_DEF_EARJACK_STATE_FILEPATH, GFP_KERNEL);
if (ed_data->earjack_state_filepath == NULL) {
cts_err("Dup earjack state filepath failed");
ret = -ENOMEM;
}
#endif /* CONFIG_CTS_OF */
if (ret) {
cts_err("Get detect param failed %d(%s)",
ret, cts_strerror(ret));
goto free_ed_data;
}
cts_info("Detect: %sABLED", ed_data->enable ? "EN" : "DIS");
cts_info(" Filepath: '%s'", ed_data->earjack_state_filepath);
cts_info(" Poll Int: %dms", ed_data->poll_interval);
INIT_DELAYED_WORK(&ed_data->poll_work, poll_earjack_state_work);
#ifdef CONFIG_CTS_SYSFS
cts_info("Create sysfs attr group '%s'", EARJACK_DET_SYSFS_GROUP_NAME);
ret = sysfs_create_group(&cts_data->device->kobj,
&earjack_detect_attr_group);
if (ret) {
cts_warn("Create sysfs attr group '%s' failed %d(%s)",
EARJACK_DET_SYSFS_GROUP_NAME, ret, cts_strerror(ret));
} else {
ed_data->sysfs_attr_group_created = true;
}
#endif /* CONFIG_CTS_SYSFS */
cts_data->earjack_detect_data = ed_data;
ed_data->cts_data = cts_data;
return 0;
free_ed_data:
kfree(ed_data);
return ret;
}
int cts_earjack_detect_deinit(struct chipone_ts_data *cts_data)
{
struct cts_earjack_detect_data *ed_data;
int ret;
if (cts_data == NULL) {
cts_err("Deinit detect with cts_data = NULL");
return -EFAULT;
}
ed_data = cts_data->earjack_detect_data;
if (ed_data == NULL) {
cts_warn("Deinit detect with earjack_detect_data = NULL");
return 0;
}
cts_info("Deinit detect");
if (ed_data->running) {
ret = stop_earjack_detect(ed_data);
if (ret) {
cts_err("Stop detect failed %d(%s)",
ret, cts_strerror(ret));
}
}
#ifdef CONFIG_CTS_SYSFS
if (ed_data->sysfs_attr_group_created) {
cts_info("Remove sysfs attr group '%s'", EARJACK_DET_SYSFS_GROUP_NAME);
sysfs_remove_group(&cts_data->device->kobj, &earjack_detect_attr_group);
ed_data->sysfs_attr_group_created = false;
}
#endif /* CONFIG_CTS_SYSFS */
if(ed_data->earjack_state_filepath) {
kfree(ed_data->earjack_state_filepath);
ed_data->earjack_state_filepath = NULL;
}
kfree(ed_data);
cts_data->earjack_detect_data = NULL;
return 0;
}
int cts_is_earjack_attached(struct chipone_ts_data *cts_data, bool *attached)
{
struct cts_earjack_detect_data *ed_data;
int ret;
if (cts_data == NULL) {
cts_err("Get state with cts_data = NULL");
return -EFAULT;
}
ed_data = cts_data->earjack_detect_data;
if (ed_data == NULL) {
cts_err("Get state with earjack_detect_data = NULL");
return -ENODEV;
}
ret = get_earjack_state(ed_data);
if (ret) {
cts_err("Get state failed %d(%s)", ret, cts_strerror(ret));
return ret;
}
cts_info("Get curr state: %s", earjack_state_str(ed_data->state));
*attached = ed_data->state;
return 0;
}
int cts_start_earjack_detect(struct chipone_ts_data *cts_data)
{
struct cts_earjack_detect_data *ed_data;
if (cts_data == NULL) {
cts_err("Start detect with cts_data = NULL");
return -EFAULT;
}
ed_data = cts_data->earjack_detect_data;
if (ed_data == NULL) {
cts_err("Start detect with earjack_detect_data = NULL");
return -ENODEV;
}
return start_earjack_detect(ed_data);
}
int cts_stop_earjack_detect(struct chipone_ts_data *cts_data)
{
struct cts_earjack_detect_data *ed_data;
if (cts_data == NULL) {
cts_err("Stop detect with cts_data = NULL");
return -EFAULT;
}
ed_data = cts_data->earjack_detect_data;
if (ed_data == NULL) {
cts_err("Stop detect with earjack_detect_data = NULL");
return -ENODEV;
}
return stop_earjack_detect(ed_data);
}
#endif /* CONFIG_CTS_EARJACK_DETECT */