Merge "soc: qcom: add QTI charger-ulog-glink driver"

This commit is contained in:
qctecmdr 2022-05-26 23:01:33 -07:00 committed by Gerrit - the friendly Code Review server
commit 9b66754069
4 changed files with 504 additions and 8 deletions

View File

@ -248,6 +248,15 @@ config QTI_BATTERY_GLINK_DEBUG
Glink from charger and gauging firmware running on a remote subsystem
(e.g. DSP).
config QTI_CHARGER_ULOG_GLINK
tristate "Enable support for QTI charger ulog glink driver"
depends on QTI_PMIC_GLINK && DEBUG_FS
help
Qualcomm Technologies, Inc. charger ulog glink driver helps to
obtain ulogs from battery charging and gauging stack over PMIC
Glink from the charger firmware running on a remote subsystem
(e.g. DSP).
config QTI_ALTMODE_GLINK
tristate "Type-C alternate mode over GLINK"
depends on QTI_PMIC_GLINK

View File

@ -21,6 +21,7 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QTI_PMIC_GLINK) += pmic_glink.o
obj-$(CONFIG_QTI_BATTERY_GLINK_DEBUG) += qti_battery_debug.o
obj-$(CONFIG_QTI_CHARGER_ULOG_GLINK) += charger-ulog-glink.o
obj-$(CONFIG_QTI_ALTMODE_GLINK) += altmode-glink.o
obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o
obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "altmode-glink: %s: " fmt, __func__
@ -8,6 +8,7 @@
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/ipc_logging.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/of_platform.h>
@ -27,10 +28,18 @@
#define MAX_NUM_PORTS 4
#define NUM_LOG_PAGES 10
#define IDR_KEY_GEN(svid, ind) (((svid) << 8) | (ind))
#define IDR_KEY(client) \
IDR_KEY_GEN((client)->data.svid, (client)->port_index)
#define altmode_dbg(fmt, ...) \
do { \
ipc_log_string(altmode_ipc_log, fmt, ##__VA_ARGS__); \
pr_debug(fmt, ##__VA_ARGS__); \
} while (0)
struct usbc_notify_ind_msg {
struct pmic_glink_hdr hdr;
u8 payload[NOTIFY_PAYLOAD_SIZE];
@ -125,6 +134,7 @@ static LIST_HEAD(probe_notify_list);
static DEFINE_MUTEX(notify_lock);
static void altmode_send_pan_ack(struct work_struct *work);
static void *altmode_ipc_log;
static struct altmode_dev *to_altmode_device(struct device_node *amdev_node)
{
@ -462,7 +472,7 @@ static void altmode_send_pan_en(struct work_struct *work)
}
atomic_set(&amdev->pan_en_sent, 1);
pr_debug("Sent PAN EN\n");
altmode_dbg("Sent PAN EN\n");
}
static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index)
@ -479,7 +489,7 @@ static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index)
return rc;
}
pr_debug("port %u: Sent PAN ACK\n", port_index);
altmode_dbg("port %u: Sent PAN ACK\n", port_index);
return rc;
}
@ -488,7 +498,7 @@ static void altmode_state_cb(void *priv, enum pmic_glink_state state)
{
struct altmode_dev *amdev = priv;
pr_debug("state: %d\n", state);
altmode_dbg("state: %d\n", state);
switch (state) {
case PMIC_GLINK_STATE_DOWN:
@ -527,7 +537,7 @@ static int altmode_callback(void *priv, void *data, size_t len)
struct altmode_client *amclient;
u8 port_index;
pr_debug("len: %zu owner: %u type: %u opcode %04x\n", len, hdr->owner,
altmode_dbg("len: %zu owner: %u type: %u opcode %04x\n", len, hdr->owner,
hdr->type, hdr->opcode);
/*
@ -544,7 +554,7 @@ static int altmode_callback(void *priv, void *data, size_t len)
break;
case USBC_NOTIFY_IND:
if (len != sizeof(*notify_msg)) {
pr_debug("Expected length %u, got: %zu\n",
altmode_dbg("Expected length %u, got: %zu\n",
sizeof(*notify_msg), len);
return -EINVAL;
}
@ -558,7 +568,7 @@ static int altmode_callback(void *priv, void *data, size_t len)
mutex_unlock(&amdev->client_lock);
if (!amclient) {
pr_debug("No client associated with SVID %#x port %u\n",
altmode_dbg("No client associated with SVID %#x port %u\n",
svid, port_index);
amdev->ack_port_index = port_index;
schedule_delayed_work(&amdev->send_pan_ack_work,
@ -566,7 +576,7 @@ static int altmode_callback(void *priv, void *data, size_t len)
return 0;
}
pr_debug("Payload: %*ph\n", NOTIFY_PAYLOAD_SIZE,
altmode_dbg("Payload: %*ph\n", NOTIFY_PAYLOAD_SIZE,
notify_msg->payload);
cancel_work_sync(&amclient->client_cb_work);
@ -727,6 +737,10 @@ static int altmode_probe(struct platform_device *pdev)
goto unreg_pmic_glink;
}
altmode_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "altmode", 0);
if (!altmode_ipc_log)
dev_warn(dev, "Error in creating ipc_log_context\n");
altmode_notify_clients(amdev);
return 0;
@ -765,6 +779,9 @@ static int altmode_remove(struct platform_device *pdev)
list_del(&client->c_node);
mutex_unlock(&amdev->client_lock);
ipc_log_context_destroy(altmode_ipc_log);
altmode_ipc_log = NULL;
rc = pmic_glink_unregister_client(amdev->pgclient);
if (rc < 0)
dev_err(amdev->dev, "Error in pmic_glink de-registration: %d\n",

View File

@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "CHARGER_ULOG: %s: " fmt, __func__
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/ipc_logging.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/soc/qcom/pmic_glink.h>
#define MSG_OWNER_CHG_ULOG 32778
#define MSG_TYPE_REQ_RESP 1
#define GET_CHG_ULOG_REQ 0x18
#define SET_CHG_ULOG_PROP_REQ 0x19
#define GET_CHG_INIT_ULOG_REQ 0x23
#define LOG_CATEGORY_INIT (1ULL << 32)
#define LOG_MIN_TIME_MS 500
#define LOG_DEFAULT_TIME_MS 1000
#define MAX_ULOG_SIZE 8192
#define NUM_LOG_PAGES 10
#define NUM_INIT_LOG_PAGES 4
struct set_ulog_prop_req_msg {
struct pmic_glink_hdr hdr;
u64 log_category;
u32 log_level;
};
struct get_ulog_req_msg {
struct pmic_glink_hdr hdr;
u32 log_size;
};
struct get_ulog_resp_msg {
struct pmic_glink_hdr hdr;
u8 buf[MAX_ULOG_SIZE];
};
struct chg_ulog_glink_dev {
struct device *dev;
struct pmic_glink_client *client;
struct dentry *debugfs_dir;
void *ipc_log;
void *ipc_init_log;
struct mutex lock;
struct completion ack;
struct delayed_work ulog_work;
u8 ulog_buf[MAX_ULOG_SIZE];
u64 log_category;
u32 log_level;
u32 log_time_ms;
bool log_enable;
bool init_log_enable;
};
#define WAIT_TIME_MS 1000
static int chg_ulog_write(struct chg_ulog_glink_dev *cd, void *data,
size_t len)
{
int rc;
mutex_lock(&cd->lock);
reinit_completion(&cd->ack);
rc = pmic_glink_write(cd->client, data, len);
if (!rc) {
rc = wait_for_completion_timeout(&cd->ack,
msecs_to_jiffies(WAIT_TIME_MS));
if (!rc) {
pr_err("Error, timed out sending message\n");
mutex_unlock(&cd->lock);
return -ETIMEDOUT;
}
rc = 0;
}
mutex_unlock(&cd->lock);
return rc;
}
static int chg_ulog_request(struct chg_ulog_glink_dev *cd, bool init)
{
struct get_ulog_req_msg req_msg = { { 0 } };
req_msg.hdr.owner = MSG_OWNER_CHG_ULOG;
req_msg.hdr.type = MSG_TYPE_REQ_RESP;
req_msg.hdr.opcode = init ? GET_CHG_INIT_ULOG_REQ : GET_CHG_ULOG_REQ;
req_msg.log_size = MAX_ULOG_SIZE;
return chg_ulog_write(cd, &req_msg, sizeof(req_msg));
}
static int chg_ulog_set_log_type(struct chg_ulog_glink_dev *cd, u64 category,
u32 level)
{
struct set_ulog_prop_req_msg req_msg = { { 0 } };
int rc;
req_msg.hdr.owner = MSG_OWNER_CHG_ULOG;
req_msg.hdr.type = MSG_TYPE_REQ_RESP;
req_msg.hdr.opcode = SET_CHG_ULOG_PROP_REQ;
req_msg.log_category = category;
req_msg.log_level = level;
rc = chg_ulog_write(cd, &req_msg, sizeof(req_msg));
if (!rc)
pr_debug("Set log category %llu log level %u\n", category,
level);
return rc;
}
static void chg_ulog_work(struct work_struct *work)
{
struct chg_ulog_glink_dev *cd = container_of(work,
struct chg_ulog_glink_dev,
ulog_work.work);
int rc;
rc = chg_ulog_request(cd, cd->init_log_enable);
if (rc)
pr_err("Error requesting ulog, rc=%d\n", rc);
else if (cd->log_enable || cd->init_log_enable)
schedule_delayed_work(&cd->ulog_work,
msecs_to_jiffies(cd->log_time_ms));
}
static void ulog_store(struct chg_ulog_glink_dev *cd, void *ipc_ctxt,
size_t len)
{
char *buf = cd->ulog_buf, *token = NULL;
if (buf[0] == '\0') {
pr_debug("buffer is NULL\n");
if (cd->init_log_enable)
cd->init_log_enable = false;
return;
}
buf[len - 1] = '\0';
if (len >= MAX_MSG_SIZE) {
do {
token = strsep((char **)&buf, "\n");
if (token)
ipc_log_string(ipc_ctxt, "%s", token);
} while (token);
} else {
ipc_log_string(ipc_ctxt, "%s", buf);
}
}
static void handle_ulog_message(struct chg_ulog_glink_dev *cd,
struct get_ulog_resp_msg *resp_msg,
size_t len)
{
void *ipc_ctxt;
if (len != sizeof(*resp_msg)) {
pr_err("Expected data length: %zu, received: %zu\n",
sizeof(*resp_msg), len);
return;
}
memcpy(cd->ulog_buf, resp_msg->buf, sizeof(cd->ulog_buf));
ipc_ctxt = (resp_msg->hdr.opcode == GET_CHG_INIT_ULOG_REQ)
? cd->ipc_init_log : cd->ipc_log;
ulog_store(cd, ipc_ctxt, len - sizeof(resp_msg->hdr));
}
static int chg_ulog_callback(void *priv, void *data, size_t len)
{
struct pmic_glink_hdr *hdr = data;
struct chg_ulog_glink_dev *cd = priv;
pr_debug("owner: %u type: %u opcode: %#x len: %zu\n", hdr->owner,
hdr->type, hdr->opcode, len);
switch (hdr->opcode) {
case SET_CHG_ULOG_PROP_REQ:
complete(&cd->ack);
break;
case GET_CHG_ULOG_REQ:
case GET_CHG_INIT_ULOG_REQ:
handle_ulog_message(cd, data, len);
complete(&cd->ack);
break;
default:
pr_err("Unknown opcode %u\n", hdr->opcode);
break;
}
return 0;
}
static int ulog_cat_get(void *data, u64 *val)
{
struct chg_ulog_glink_dev *cd = data;
*val = cd->log_category;
return 0;
}
static int ulog_cat_set(void *data, u64 val)
{
int rc;
struct chg_ulog_glink_dev *cd = data;
if (cd->log_enable) {
pr_err("Disable ulog before changing log category\n");
return -EINVAL;
}
if (val == cd->log_category)
return 0;
rc = chg_ulog_set_log_type(cd, val, cd->log_level);
if (rc)
pr_err("Couldn't set log_category rc=%d\n", rc);
else
cd->log_category = val;
return rc;
}
DEFINE_DEBUGFS_ATTRIBUTE(ulog_cat_fops, ulog_cat_get, ulog_cat_set,
"%llu\n");
static int ulog_level_get(void *data, u64 *val)
{
struct chg_ulog_glink_dev *cd = data;
*val = cd->log_level;
return 0;
}
static int ulog_level_set(void *data, u64 val)
{
int rc;
struct chg_ulog_glink_dev *cd = data;
u32 level = val;
if (cd->log_enable) {
pr_err("Disable ulog before changing log level\n");
return -EINVAL;
}
if (level == cd->log_level)
return 0;
rc = chg_ulog_set_log_type(cd, cd->log_category, level);
if (rc)
pr_err("Couldn't set log_level rc=%d\n", rc);
else
cd->log_level = level;
return rc;
}
DEFINE_DEBUGFS_ATTRIBUTE(ulog_level_fops, ulog_level_get, ulog_level_set,
"%llu\n");
static int ulog_en_get(void *data, u64 *val)
{
struct chg_ulog_glink_dev *cd = data;
*val = cd->log_enable;
return 0;
}
static int ulog_en_set(void *data, u64 val)
{
struct chg_ulog_glink_dev *cd = data;
bool en = val;
if (en == cd->log_enable)
return 0;
if (cd->log_category == LOG_CATEGORY_INIT)
cd->init_log_enable = en;
else
cd->log_enable = en;
if (en)
schedule_delayed_work(&cd->ulog_work,
msecs_to_jiffies(cd->log_time_ms));
else
cancel_delayed_work_sync(&cd->ulog_work);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(ulog_en_fops, ulog_en_get, ulog_en_set, "%llu\n");
static int ulog_time_get(void *data, u64 *val)
{
struct chg_ulog_glink_dev *cd = data;
*val = cd->log_time_ms;
return 0;
}
static int ulog_time_set(void *data, u64 val)
{
struct chg_ulog_glink_dev *cd = data;
if (val == cd->log_time_ms)
return 0;
if (val < LOG_MIN_TIME_MS)
return -EINVAL;
cd->log_time_ms = val;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(ulog_time_fops, ulog_time_get, ulog_time_set,
"%llu\n");
static int chg_ulog_add_debugfs(struct chg_ulog_glink_dev *cd)
{
struct dentry *dir, *file;
int rc;
dir = debugfs_create_dir("charger_ulog", NULL);
if (IS_ERR(dir)) {
rc = PTR_ERR(dir);
pr_err("Failed to create charger_ulog debugfs directory: %d\n",
rc);
return rc;
}
file = debugfs_create_file_unsafe("category", 0600, dir, cd,
&ulog_cat_fops);
if (IS_ERR(file)) {
rc = PTR_ERR(file);
pr_err("Failed to create category %d\n", rc);
goto out;
}
file = debugfs_create_file_unsafe("level", 0600, dir, cd,
&ulog_level_fops);
if (IS_ERR(file)) {
rc = PTR_ERR(file);
pr_err("Failed to create level %d\n", rc);
goto out;
}
file = debugfs_create_file_unsafe("enable", 0600, dir, cd,
&ulog_en_fops);
if (IS_ERR(file)) {
rc = PTR_ERR(file);
pr_err("Failed to create enable %d\n", rc);
goto out;
}
file = debugfs_create_file_unsafe("time_ms", 0600, dir, cd,
&ulog_time_fops);
if (IS_ERR(file)) {
rc = PTR_ERR(file);
pr_err("Failed to create time_ms %d\n", rc);
goto out;
}
cd->debugfs_dir = dir;
return 0;
out:
debugfs_remove_recursive(dir);
return rc;
}
static int chg_ulog_probe(struct platform_device *pdev)
{
struct chg_ulog_glink_dev *cd;
struct pmic_glink_client_data client_data = { };
int rc;
cd = devm_kzalloc(&pdev->dev, sizeof(*cd), GFP_KERNEL);
if (!cd)
return -ENOMEM;
mutex_init(&cd->lock);
init_completion(&cd->ack);
INIT_DELAYED_WORK(&cd->ulog_work, chg_ulog_work);
cd->log_time_ms = LOG_DEFAULT_TIME_MS;
platform_set_drvdata(pdev, cd);
cd->dev = &pdev->dev;
client_data.id = MSG_OWNER_CHG_ULOG;
client_data.name = "chg_ulog";
client_data.msg_cb = chg_ulog_callback;
client_data.priv = cd;
cd->client = pmic_glink_register_client(cd->dev, &client_data);
if (IS_ERR(cd->client))
return dev_err_probe(cd->dev, PTR_ERR(cd->client),
"Error in registering with pmic_glink %d\n");
rc = chg_ulog_add_debugfs(cd);
if (rc) {
pmic_glink_unregister_client(cd->client);
return dev_err_probe(cd->dev, -EINVAL, "Error in creating debugfs\n");
}
cd->ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "charger_ulog", 0);
if (!cd->ipc_log) {
pmic_glink_unregister_client(cd->client);
debugfs_remove_recursive(cd->debugfs_dir);
return dev_err_probe(cd->dev, -ENODEV, "Error in creating charger_ulog\n");
}
cd->ipc_init_log = ipc_log_context_create(NUM_INIT_LOG_PAGES,
"charger_ulog_init", 0);
if (!cd->ipc_init_log) {
pmic_glink_unregister_client(cd->client);
ipc_log_context_destroy(cd->ipc_log);
debugfs_remove_recursive(cd->debugfs_dir);
return dev_err_probe(cd->dev, -ENODEV, "Error in creating charger_ulog_init\n");
}
return 0;
}
static int chg_ulog_remove(struct platform_device *pdev)
{
struct chg_ulog_glink_dev *cd = platform_get_drvdata(pdev);
int rc;
debugfs_remove_recursive(cd->debugfs_dir);
cancel_delayed_work_sync(&cd->ulog_work);
rc = pmic_glink_unregister_client(cd->client);
if (rc < 0)
pr_err("Error unregistering from pmic_glink, rc=%d\n", rc);
ipc_log_context_destroy(cd->ipc_log);
ipc_log_context_destroy(cd->ipc_init_log);
return 0;
}
static const struct of_device_id chg_ulog_match_table[] = {
{ .compatible = "qcom,charger-ulog-glink" },
{},
};
static struct platform_driver chg_ulog_driver = {
.driver = {
.name = "charger_ulog_glink",
.of_match_table = chg_ulog_match_table,
},
.probe = chg_ulog_probe,
.remove = chg_ulog_remove,
};
module_platform_driver(chg_ulog_driver);
MODULE_DESCRIPTION("QTI charger ulog glink driver");
MODULE_LICENSE("GPL v2");