diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 66fb29dfefaf..5b68941952c3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 00156a188c9f..cfc26ea18a37 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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 diff --git a/drivers/soc/qcom/altmode-glink.c b/drivers/soc/qcom/altmode-glink.c index ecf1e7a9af09..2697fd7ffda5 100644 --- a/drivers/soc/qcom/altmode-glink.c +++ b/drivers/soc/qcom/altmode-glink.c @@ -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 #include #include +#include #include #include #include @@ -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", diff --git a/drivers/soc/qcom/charger-ulog-glink.c b/drivers/soc/qcom/charger-ulog-glink.c new file mode 100644 index 000000000000..756ddded3d8d --- /dev/null +++ b/drivers/soc/qcom/charger-ulog-glink.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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");