From b7a6f73833c89008dc6d3827a02e3b4c8f4852d4 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Wed, 12 Feb 2020 14:00:07 -0800 Subject: [PATCH 1/8] soc: qcom: qti_battery_debug: Update QBG device context dump length QBG device context dump length has been increased to 598 words in the charger firmware. Increase the length to support upto 4KB. Change-Id: I4a0a28044a54cdbde146101384f5a1ccf73b1562 Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/qti_battery_debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index a54930861d41..4f25ddebe8f3 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -20,7 +20,7 @@ #define BD_QBG_DUMP_REQ 0x36 /* Generic definitions */ -#define MAX_BUF_LEN (560 * sizeof(u32)) +#define MAX_BUF_LEN SZ_4K #define BD_WAIT_TIME_MS 1000 struct qbg_context_req_msg { From 1069d0fec8d749b5e61c66a732bb9f96f1ec4d01 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Thu, 20 Feb 2020 17:40:36 -0800 Subject: [PATCH 2/8] soc: qti_battery_debug: Add votables R/W support Add debugfs nodes to read and set votables from the firmware running on the remote subsystem (e.g. DSP). Also modify error handling logic in battery_dbg_add_debugfs(). Change-Id: I50f191add22cd500e04475f264e86cf16c6be48f Signed-off-by: Guru Das Srinagesh --- drivers/soc/qcom/qti_battery_debug.c | 535 ++++++++++++++++++++++++++- 1 file changed, 523 insertions(+), 12 deletions(-) diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index 4f25ddebe8f3..8e464d1af1ed 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -14,15 +14,47 @@ #include #include -/* owner/type/opcode for battery debug */ -#define MSG_OWNER_BD 32781 -#define MSG_TYPE_REQ_RESP 1 -#define BD_QBG_DUMP_REQ 0x36 +/* owner/type/opcodes for battery debug */ +#define MSG_OWNER_BD 32781 +#define MSG_TYPE_REQ_RESP 1 +#define BD_GET_AGGREGATOR_INFO_REQ 0x15 +#define BD_OVERWRITE_VOTABLE_REQ 0x16 +#define BD_GET_VOTABLE_REQ 0x17 +#define BD_QBG_DUMP_REQ 0x36 /* Generic definitions */ #define MAX_BUF_LEN SZ_4K #define BD_WAIT_TIME_MS 1000 +#define MAX_NUM_VOTABLES 12 +#define MAX_NUM_VOTERS 32 +#define MAX_NAME_LEN 12 + +struct all_votables_data { + u32 num_votables; + u32 num_voters; + char votables[MAX_NUM_VOTABLES][MAX_NAME_LEN]; + char voters[MAX_NUM_VOTERS][MAX_NAME_LEN]; +}; + +struct votable_data { + u32 votable_id; + u32 eff_val; + u8 voter_ids[MAX_NUM_VOTERS]; /* unused */ + u32 votes[MAX_NUM_VOTERS]; + u32 active_voter_mask; + u32 eff_voter; + u32 override_voter; +}; + +struct votable { + char *name; + u32 id; + struct battery_dbg_dev *bd; + struct votable_data data; + u32 override_val; +}; + struct qbg_context_req_msg { struct pmic_glink_hdr hdr; }; @@ -33,6 +65,31 @@ struct qbg_context_resp_msg { u8 buf[MAX_BUF_LEN]; }; +struct votables_list_req_msg { + struct pmic_glink_hdr hdr; +}; + +struct votables_list_resp_msg { + struct pmic_glink_hdr hdr; + struct all_votables_data all_data; +}; + +struct votable_req_msg { + struct pmic_glink_hdr hdr; + u32 votable_id; +}; + +struct votable_resp_msg { + struct pmic_glink_hdr hdr; + struct votable_data v_data; +}; + +struct override_req_msg { + struct pmic_glink_hdr hdr; + u32 votable_id; + u32 override_val; +}; + struct battery_dbg_dev { struct device *dev; struct pmic_glink_client *client; @@ -41,6 +98,9 @@ struct battery_dbg_dev { struct qbg_context_resp_msg qbg_dump; struct dentry *debugfs_dir; struct debugfs_blob_wrapper qbg_blob; + struct all_votables_data all_data; + struct votable *votable; + u8 override_voter_id; }; static int battery_dbg_write(struct battery_dbg_dev *bd, void *data, size_t len) @@ -91,6 +151,89 @@ static void handle_qbg_dump_message(struct battery_dbg_dev *bd, complete(&bd->ack); } +static void handle_override_message(struct battery_dbg_dev *bd, void *unused, + size_t len) +{ + pr_debug("override succeeded\n"); + complete(&bd->ack); +} + +static void handle_get_votable_message(struct battery_dbg_dev *bd, + struct votable_resp_msg *resp_msg, + size_t len) +{ + u32 id = resp_msg->v_data.votable_id; + + if (len != sizeof(*resp_msg)) { + pr_err("Expected data length: %zu, received: %zu\n", + sizeof(*resp_msg), len); + return; + } + + if (id >= MAX_NUM_VOTABLES) { + pr_err("Votable id %u exceeds max %d\n", id, MAX_NUM_VOTABLES); + return; + } + + if (resp_msg->v_data.active_voter_mask && + resp_msg->v_data.eff_voter >= MAX_NUM_VOTERS) { + pr_err("Effective voter id %u exceeds max %d\n", + resp_msg->v_data.eff_voter, MAX_NUM_VOTERS); + return; + } + + memcpy(&bd->votable[id].data, &resp_msg->v_data, + sizeof(resp_msg->v_data)); + + complete(&bd->ack); +} + +#define OVERRIDE_VOTER_NAME "glink" +static void handle_get_votables_list_message(struct battery_dbg_dev *bd, + struct votables_list_resp_msg *resp_msg, + size_t len) +{ + u8 i; + + if (len != sizeof(*resp_msg)) { + pr_err("Expected data length: %zu, received: %zu\n", + sizeof(*resp_msg), len); + return; + } + + if (resp_msg->all_data.num_votables >= MAX_NUM_VOTABLES) { + pr_err("Num votables %u exceeds max %d\n", + resp_msg->all_data.num_votables, MAX_NUM_VOTABLES); + return; + } + + if (resp_msg->all_data.num_voters >= MAX_NUM_VOTERS) { + pr_err("Num voters %u exceeds max %d\n", + resp_msg->all_data.num_voters, MAX_NUM_VOTERS); + return; + } + + memcpy(&bd->all_data, &resp_msg->all_data, sizeof(resp_msg->all_data)); + + for (i = 0; i < MAX_NUM_VOTABLES; i++) + bd->all_data.votables[i][MAX_NAME_LEN - 1] = '\0'; + + for (i = 0; i < MAX_NUM_VOTERS; i++) + bd->all_data.voters[i][MAX_NAME_LEN - 1] = '\0'; + + if (!bd->override_voter_id) { + for (i = 0; i < MAX_NUM_VOTERS; i++) { + if (!strcmp(bd->all_data.voters[i], + OVERRIDE_VOTER_NAME)) { + bd->override_voter_id = i; + break; + } + } + } + + complete(&bd->ack); +} + static int battery_dbg_callback(void *priv, void *data, size_t len) { struct pmic_glink_hdr *hdr = data; @@ -103,6 +246,15 @@ static int battery_dbg_callback(void *priv, void *data, size_t len) case BD_QBG_DUMP_REQ: handle_qbg_dump_message(bd, data, len); break; + case BD_GET_AGGREGATOR_INFO_REQ: + handle_get_votables_list_message(bd, data, len); + break; + case BD_GET_VOTABLE_REQ: + handle_get_votable_message(bd, data, len); + break; + case BD_OVERWRITE_VOTABLE_REQ: + handle_override_message(bd, data, len); + break; default: pr_err("Unknown opcode %u\n", hdr->opcode); break; @@ -126,28 +278,387 @@ static int get_qbg_context_write(void *data, u64 val) DEFINE_DEBUGFS_ATTRIBUTE(get_qbg_context_debugfs_ops, NULL, get_qbg_context_write, "%llu\n"); +static int battery_dbg_request_read_votable(struct battery_dbg_dev *bd, + u32 id) +{ + struct votable_req_msg req_msg = { { 0 } }; + + req_msg.hdr.owner = MSG_OWNER_BD; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = BD_GET_VOTABLE_REQ; + req_msg.votable_id = id; + + return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); +} + +#ifdef CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG +static int battery_dbg_request_override(struct battery_dbg_dev *bd, u32 id, + u32 val) +{ + struct override_req_msg req_msg = { { 0 } }; + + req_msg.hdr.owner = MSG_OWNER_BD; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = BD_OVERWRITE_VOTABLE_REQ; + req_msg.votable_id = id; + req_msg.override_val = val; + + pr_debug("requesting override of %s with value %u\n", + bd->votable[id].name, val); + + return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); +} +#endif + +static int active_show(struct seq_file *s, void *unused) +{ + int rc; + unsigned long voter_mask; + struct votable *v = s->private; + struct battery_dbg_dev *bd = v->bd; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + + seq_printf(s, "%#x\n", voter_mask); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(active); + +static int winvote_show(struct seq_file *s, void *unused) +{ + int rc, winvote; + unsigned long voter_mask; + struct votable *v = s->private; + struct battery_dbg_dev *bd = v->bd; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + winvote = voter_mask ? v->data.eff_val : -EINVAL; + + seq_printf(s, "%d\n", winvote); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(winvote); + +static int winner_show(struct seq_file *s, void *unused) +{ + int rc; + char *winner; + u32 eff_voter; + unsigned long voter_mask; + struct votable *v = s->private; + struct battery_dbg_dev *bd = v->bd; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + + if (voter_mask) { + eff_voter = v->data.eff_voter; + winner = bd->all_data.voters[eff_voter]; + } else { + winner = ""; + } + + seq_printf(s, "%s\n", winner); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(winner); + +static int voters_show(struct seq_file *s, void *unused) +{ + int rc, i; + unsigned long voter_mask; + struct votable *v = s->private; + struct battery_dbg_dev *bd = v->bd; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + + for_each_set_bit(i, &voter_mask, MAX_NUM_VOTERS) + seq_printf(s, "%s ", bd->all_data.voters[i]); + seq_puts(s, "\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(voters); + +static int votes_show(struct seq_file *s, void *unused) +{ + int rc, i; + unsigned long voter_mask; + struct votable *v = s->private; + struct battery_dbg_dev *bd = v->bd; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + + for_each_set_bit(i, &voter_mask, MAX_NUM_VOTERS) + seq_printf(s, "%d ", v->data.votes[i]); + seq_puts(s, "\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(votes); + +static int status_show(struct seq_file *s, void *unused) +{ + int rc, i, winvote; + char *winner; + u32 eff_voter; + unsigned long voter_mask; + struct votable *v = s->private; + struct battery_dbg_dev *bd = v->bd; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + + if (!voter_mask) { + seq_puts(s, "\n"); + return 0; + } + + winvote = v->data.eff_val; + eff_voter = v->data.eff_voter; + winner = bd->all_data.voters[eff_voter]; + + for_each_set_bit(i, &voter_mask, MAX_NUM_VOTERS) + seq_printf(s, " %-*s: %-*s: %d\n", MAX_NAME_LEN, + v->name, MAX_NAME_LEN, bd->all_data.voters[i], + v->data.votes[i]); + seq_printf(s, "EFFECTIVE: %-*s: %-*s: %d\n", MAX_NAME_LEN, v->name, + MAX_NAME_LEN, winner, winvote); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(status); + +#ifdef CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG +static int override_get(void *data, u64 *val) +{ + int rc; + struct votable *v = data; + struct battery_dbg_dev *bd = v->bd; + unsigned long voter_mask; + + rc = battery_dbg_request_read_votable(bd, v->id); + if (rc) { + pr_err("Failed to read %s votable: %d\n", v->name, rc); + return rc; + } + + voter_mask = v->data.active_voter_mask; + + /* Show override voter's vote only if override voter is active */ + if (test_bit(bd->override_voter_id, &voter_mask)) + *val = v->data.votes[bd->override_voter_id]; + else + *val = 0; + + return 0; +} + +static int override_set(void *data, u64 val) +{ + int rc; + struct votable *v = data; + struct battery_dbg_dev *bd = v->bd; + u32 set = val; + + rc = battery_dbg_request_override(bd, v->id, set); + if (rc) { + pr_err("%s override request failed: %d\n", v->name, rc); + return rc; + } + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(override_fops, override_get, override_set, "%llu\n"); + +static int battery_dbg_create_override_file(struct votable *v, + struct dentry *votable_dir) +{ + return PTR_ERR_OR_ZERO(debugfs_create_file_unsafe("override", 0600, + votable_dir, v, &override_fops)); + +} +#else +static int battery_dbg_create_override_file(struct votable *v, + struct dentry *votable_dir) +{ + return 0; +} +#endif + +static int battery_dbg_create_votable(struct battery_dbg_dev *bd, + struct dentry *votables_root_dir, + u32 id) +{ + int rc; + char *v_name = bd->all_data.votables[id]; + struct dentry *votable_dir; + + votable_dir = debugfs_create_dir(v_name, votables_root_dir); + if (IS_ERR(votable_dir)) { + pr_err("Failed to create %s debugfs directory\n", v_name); + return PTR_ERR(votable_dir); + } + + bd->votable[id].name = v_name; + + rc = PTR_ERR_OR_ZERO(debugfs_create_file("active", 0400, votable_dir, + &bd->votable[id], &active_fops)); + if (rc) + goto error; + + rc = PTR_ERR_OR_ZERO(debugfs_create_file("winvote", 0400, votable_dir, + &bd->votable[id], &winvote_fops)); + if (rc) + goto error; + + rc = PTR_ERR_OR_ZERO(debugfs_create_file("winner", 0400, votable_dir, + &bd->votable[id], &winner_fops)); + if (rc) + goto error; + + rc = PTR_ERR_OR_ZERO(debugfs_create_file("voters", 0400, votable_dir, + &bd->votable[id], &voters_fops)); + if (rc) + goto error; + + rc = PTR_ERR_OR_ZERO(debugfs_create_file("votes", 0400, votable_dir, + &bd->votable[id], &votes_fops)); + if (rc) + goto error; + + rc = PTR_ERR_OR_ZERO(debugfs_create_file("status", 0400, votable_dir, + &bd->votable[id], &status_fops)); + if (rc) + goto error; + + rc = battery_dbg_create_override_file(&bd->votable[id], votable_dir); + if (rc) + goto error; + + return 0; + +error: + pr_err("Failed to create debugfs file: %d\n", rc); + return rc; +} + +static int battery_dbg_get_votables_list(struct battery_dbg_dev *bd) +{ + struct votable_req_msg req_msg = { { 0 } }; + + req_msg.hdr.owner = MSG_OWNER_BD; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = BD_GET_AGGREGATOR_INFO_REQ; + + return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); +} + +static int battery_dbg_create_votables(struct battery_dbg_dev *bd, + struct dentry *bd_root_dir) +{ + int rc, id; + u32 num_votables; + struct dentry *votables_root_dir; + + votables_root_dir = debugfs_create_dir("votables", bd_root_dir); + if (IS_ERR(votables_root_dir)) { + pr_err("Failed to create votables root directory\n"); + return PTR_ERR(votables_root_dir); + } + + rc = battery_dbg_get_votables_list(bd); + if (rc) { + pr_err("Failed to get votables list: %d\n", rc); + return rc; + } + + num_votables = bd->all_data.num_votables; + + bd->votable = devm_kcalloc(bd->dev, num_votables, + sizeof(struct votable), GFP_KERNEL); + + for (id = 0; id < num_votables; id++) { + bd->votable[id].bd = bd; + bd->votable[id].id = id; + rc = battery_dbg_create_votable(bd, votables_root_dir, id); + if (rc) + return rc; + } + + return 0; +} + static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) { + int rc; struct dentry *bd_dir, *file; bd_dir = debugfs_create_dir("battery_debug", NULL); - if (!bd_dir) { - pr_err("Failed to create battery debugfs directory\n"); - return -ENOMEM; + if (IS_ERR(bd_dir)) { + rc = PTR_ERR(bd_dir); + pr_err("Failed to create battery debugfs directory: %d\n", rc); + return rc; } file = debugfs_create_file_unsafe("get_qbg_context", 0200, bd_dir, bd, &get_qbg_context_debugfs_ops); - if (!file) { - pr_err("Failed to create get_qbg_context debugfs file\n"); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create get_qbg_context debugfs file: %d\n", + rc); goto error; } bd->qbg_blob.data = bd->qbg_dump.buf; bd->qbg_blob.size = 0; file = debugfs_create_blob("qbg_context", 0444, bd_dir, &bd->qbg_blob); - if (!file) { - pr_err("Failed to create qbg_context debugfs file\n"); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create qbg_context debugfs file: %d\n", rc); + goto error; + } + + rc = battery_dbg_create_votables(bd, bd_dir); + if (rc) { + pr_err("Failed to create votables: %d\n", rc); goto error; } @@ -156,7 +667,7 @@ static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) return 0; error: debugfs_remove_recursive(bd_dir); - return -ENOMEM; + return rc; } static int battery_dbg_probe(struct platform_device *pdev) From cd857f2b69ded82e37fe0dd5158d026cbe59efd4 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Fri, 15 May 2020 16:06:43 -0700 Subject: [PATCH 3/8] soc: qti_battery_debug: Remove CONFIG_DEBUG_FS dependency As a preparatory move to adding device attributes to the battery_debug device, remove the dependency on DEBUG_FS for driver enablement and add config guards in the driver instead. Change-Id: I8fcb385a583564935ce472416552e25ca79f4a84 Signed-off-by: Guru Das Srinagesh --- drivers/soc/qcom/Kconfig | 1 - drivers/soc/qcom/qti_battery_debug.c | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 7ba223837447..c4a935776c3c 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -242,7 +242,6 @@ config QTI_PMIC_GLINK_CLIENT_DEBUG config QTI_BATTERY_GLINK_DEBUG tristate "Enable support for QTI battery glink debug driver" depends on QTI_PMIC_GLINK - depends on DEBUG_FS help Qualcomm Technologies, Inc. battery glink debug driver helps to obtain debug information for battery charging and gauging over PMIC diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index 8e464d1af1ed..c86ca10b7bb9 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -278,6 +278,7 @@ static int get_qbg_context_write(void *data, u64 val) DEFINE_DEBUGFS_ATTRIBUTE(get_qbg_context_debugfs_ops, NULL, get_qbg_context_write, "%llu\n"); +#ifdef CONFIG_DEBUG_FS static int battery_dbg_request_read_votable(struct battery_dbg_dev *bd, u32 id) { @@ -669,6 +670,12 @@ static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) debugfs_remove_recursive(bd_dir); return rc; } +#else +static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) +{ + return 0; +} +#endif static int battery_dbg_probe(struct platform_device *pdev) { From e4ea2b87418a89061cdc2e4f4fbe2df3980bec19 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Thu, 4 Jun 2020 19:13:38 -0700 Subject: [PATCH 4/8] soc: qcom: qti_battery_debug: Add NULL check Ensure that valid memory is allocated for the array of all votables before an attempt is made to populate it. Change-Id: I9a0c3e35e345a88560e39d47484348b6c476628d Signed-off-by: Guru Das Srinagesh --- drivers/soc/qcom/qti_battery_debug.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index c86ca10b7bb9..3a1d0c4da23c 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -615,6 +615,8 @@ static int battery_dbg_create_votables(struct battery_dbg_dev *bd, bd->votable = devm_kcalloc(bd->dev, num_votables, sizeof(struct votable), GFP_KERNEL); + if (!bd->votable) + return -ENOMEM; for (id = 0; id < num_votables; id++) { bd->votable[id].bd = bd; From d9197e1de2b886b8000b6d0d7e84a5bfdf7f5e1d Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Fri, 5 Jun 2020 10:36:45 -0700 Subject: [PATCH 5/8] soc: qti_battery_debug: Move qbg_context to device bin file Replace the "qbg_context" debugfs blob with a binary file associated with the battery_debug device that provides the same functionality as the existing debugfs blob for getting QBG context. Usage: # cd /sys/devices/platform/soc/soc:qcom,pmic_glink_log/\ soc:qcom,pmic_glink_log:qcom,battery_debug # echo 1 > qbg_context # cat qbg_context > file.bin Change-Id: I59cfea2880362a95d8c87c02b4d823bfdca4e1e2 Signed-off-by: Guru Das Srinagesh --- drivers/soc/qcom/qti_battery_debug.c | 89 +++++++++++++++++++--------- 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index 3a1d0c4da23c..2d24a19b967e 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -275,9 +275,6 @@ static int get_qbg_context_write(void *data, u64 val) return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); } -DEFINE_DEBUGFS_ATTRIBUTE(get_qbg_context_debugfs_ops, NULL, - get_qbg_context_write, "%llu\n"); - #ifdef CONFIG_DEBUG_FS static int battery_dbg_request_read_votable(struct battery_dbg_dev *bd, u32 id) @@ -629,34 +626,16 @@ static int battery_dbg_create_votables(struct battery_dbg_dev *bd, return 0; } -static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) +static void battery_dbg_add_debugfs(struct battery_dbg_dev *bd) { int rc; - struct dentry *bd_dir, *file; + struct dentry *bd_dir; bd_dir = debugfs_create_dir("battery_debug", NULL); if (IS_ERR(bd_dir)) { rc = PTR_ERR(bd_dir); pr_err("Failed to create battery debugfs directory: %d\n", rc); - return rc; - } - - file = debugfs_create_file_unsafe("get_qbg_context", 0200, bd_dir, bd, - &get_qbg_context_debugfs_ops); - if (IS_ERR(file)) { - rc = PTR_ERR(file); - pr_err("Failed to create get_qbg_context debugfs file: %d\n", - rc); - goto error; - } - - bd->qbg_blob.data = bd->qbg_dump.buf; - bd->qbg_blob.size = 0; - file = debugfs_create_blob("qbg_context", 0444, bd_dir, &bd->qbg_blob); - if (IS_ERR(file)) { - rc = PTR_ERR(file); - pr_err("Failed to create qbg_context debugfs file: %d\n", rc); - goto error; + return; } rc = battery_dbg_create_votables(bd, bd_dir); @@ -667,18 +646,68 @@ static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) bd->debugfs_dir = bd_dir; - return 0; + return; error: debugfs_remove_recursive(bd_dir); - return rc; + return; } #else -static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) +static void battery_dbg_add_debugfs(struct battery_dbg_dev *bd) { - return 0; + return; } #endif +static ssize_t qbg_blob_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + int rc; + struct device *dev = kobj_to_dev(kobj); + struct battery_dbg_dev *bd = dev_get_drvdata(dev); + + rc = get_qbg_context_write(bd, 0); /* second arg is ignored */ + if (rc < 0) + return rc; + + return count; +} + +static ssize_t qbg_blob_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct battery_dbg_dev *bd = dev_get_drvdata(dev); + + return memory_read_from_buffer(buf, count, &pos, bd->qbg_blob.data, + bd->qbg_blob.size); +} + +static struct bin_attribute qbg_blob = { + .attr = { + .name = "qbg_context", + .mode = 0600, + }, + .read = qbg_blob_read, + .write = qbg_blob_write, +}; + +static int battery_dbg_add_dev_attr(struct battery_dbg_dev *bd) +{ + int rc; + + bd->qbg_blob.data = bd->qbg_dump.buf; + bd->qbg_blob.size = 0; + + rc = device_create_bin_file(bd->dev, &qbg_blob); + if (rc) + dev_err(bd->dev, "Failed to create qbg_context bin file: %d\n", + rc); + + return rc; +} + static int battery_dbg_probe(struct platform_device *pdev) { struct battery_dbg_dev *bd; @@ -708,10 +737,12 @@ static int battery_dbg_probe(struct platform_device *pdev) init_completion(&bd->ack); platform_set_drvdata(pdev, bd); - rc = battery_dbg_add_debugfs(bd); + rc = battery_dbg_add_dev_attr(bd); if (rc < 0) goto out; + battery_dbg_add_debugfs(bd); + return 0; out: pmic_glink_unregister_client(bd->client); From ad98e248e2cb866b15c5d08062c738824c8ed2ec Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Thu, 7 Jan 2021 20:31:32 -0800 Subject: [PATCH 6/8] soc: qcom: qti_battery_debug: add support for 2S battery context dump Currently, qti_battery_debug driver supports collecting QBG context dump only for 1S battery. Add support to collect QBG context dump for more than single cell to support 2S battery. User can set "battery_cell_id" to select the QBG context dump they would like to receive from the charger firmware that runs on a remote subsystem. Change-Id: I863b908a8c5bfae441431d31ac4dc8a491a0310c Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/qti_battery_debug.c | 74 ++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index 2d24a19b967e..7c3cae51e283 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.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) "BATTERY_DBG: %s: " fmt, __func__ @@ -57,6 +57,7 @@ struct votable { struct qbg_context_req_msg { struct pmic_glink_hdr hdr; + u32 battery_cell_id; }; struct qbg_context_resp_msg { @@ -101,6 +102,7 @@ struct battery_dbg_dev { struct all_votables_data all_data; struct votable *votable; u8 override_voter_id; + u32 battery_cell_id; }; static int battery_dbg_write(struct battery_dbg_dev *bd, void *data, size_t len) @@ -263,18 +265,6 @@ static int battery_dbg_callback(void *priv, void *data, size_t len) return 0; } -static int get_qbg_context_write(void *data, u64 val) -{ - struct battery_dbg_dev *bd = data; - struct qbg_context_req_msg req_msg = { { 0 } }; - - req_msg.hdr.owner = MSG_OWNER_BD; - req_msg.hdr.type = MSG_TYPE_REQ_RESP; - req_msg.hdr.opcode = BD_QBG_DUMP_REQ; - - return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); -} - #ifdef CONFIG_DEBUG_FS static int battery_dbg_request_read_votable(struct battery_dbg_dev *bd, u32 id) @@ -658,6 +648,19 @@ static void battery_dbg_add_debugfs(struct battery_dbg_dev *bd) } #endif +static int get_qbg_context_write(void *data, u64 val) +{ + struct battery_dbg_dev *bd = data; + struct qbg_context_req_msg req_msg = { { 0 } }; + + req_msg.hdr.owner = MSG_OWNER_BD; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = BD_QBG_DUMP_REQ; + req_msg.battery_cell_id = bd->battery_cell_id; + + return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); +} + static ssize_t qbg_blob_write(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count) @@ -693,6 +696,44 @@ static struct bin_attribute qbg_blob = { .write = qbg_blob_write, }; + +static ssize_t battery_cell_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct battery_dbg_dev *bd = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", bd->battery_cell_id); +} + +static ssize_t battery_cell_id_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct battery_dbg_dev *bd = dev_get_drvdata(dev); + + if (kstrtou32(buf, 0, &bd->battery_cell_id)) + return -EINVAL; + + return count; +} + +static DEVICE_ATTR_RW(battery_cell_id); + +static struct attribute *battery_dbg_attrs[] = { + &dev_attr_battery_cell_id.attr, + NULL, +}; + +static struct bin_attribute *battery_dbg_bin_attrs[] = { + &qbg_blob, + NULL, +}; + +static const struct attribute_group battery_dbg_group = { + .attrs = battery_dbg_attrs, + .bin_attrs = battery_dbg_bin_attrs, +}; + static int battery_dbg_add_dev_attr(struct battery_dbg_dev *bd) { int rc; @@ -700,10 +741,10 @@ static int battery_dbg_add_dev_attr(struct battery_dbg_dev *bd) bd->qbg_blob.data = bd->qbg_dump.buf; bd->qbg_blob.size = 0; - rc = device_create_bin_file(bd->dev, &qbg_blob); + rc = sysfs_create_group(&bd->dev->kobj, &battery_dbg_group); if (rc) - dev_err(bd->dev, "Failed to create qbg_context bin file: %d\n", - rc); + dev_err(bd->dev, "Failed to create sysfs files for qbg_context: %d\n", + rc); return rc; } @@ -754,6 +795,7 @@ static int battery_dbg_remove(struct platform_device *pdev) struct battery_dbg_dev *bd = platform_get_drvdata(pdev); int rc; + sysfs_remove_group(&bd->dev->kobj, &battery_dbg_group); debugfs_remove_recursive(bd->debugfs_dir); rc = pmic_glink_unregister_client(bd->client); if (rc < 0) { From 46cbba294679a257a3cdd2a0904f99f0bc51b42f Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Fri, 6 Dec 2019 16:06:35 -0800 Subject: [PATCH 7/8] soc: qcom: Add Type-C alternate mode driver The Type-C standard defines various "alternate (alt) modes" which enable standard Type-C connectors to carry non-USB signals and data. This driver provides an interface for Type-C alternate mode clients to receive data such as Pin Assignment Notifications from the Type-C stack running on a remote subsystem (e.g. DSP) via the PMIC GLINK interface. Change-Id: Ic7eacb6901830cab181d91a1775cf02cddbccae2 Signed-off-by: Guru Das Srinagesh --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/altmode-glink.c | 581 +++++++++++++++++++++++++ include/linux/soc/qcom/altmode-glink.h | 86 ++++ 4 files changed, 677 insertions(+) create mode 100644 drivers/soc/qcom/altmode-glink.c create mode 100644 include/linux/soc/qcom/altmode-glink.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index c4a935776c3c..66fb29dfefaf 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_ALTMODE_GLINK + tristate "Type-C alternate mode over GLINK" + depends on QTI_PMIC_GLINK + help + The Qualcomm Technologies, Inc. Type-C alternate mode driver provides + an interface for Type-C alternate mode clients to receive data such + as Pin Assignment Notifications from the Type-C stack running on a + remote subsystem (e.g. DSP) via the PMIC GLINK interface. + config QCOM_SECURE_BUFFER tristate "Helper functions for secure buffers through TZ" depends on QCOM_SCM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 3a840a883413..00156a188c9f 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_ALTMODE_GLINK) += altmode-glink.o obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o obj-$(CONFIG_QCOM_SPM) += spm.o diff --git a/drivers/soc/qcom/altmode-glink.c b/drivers/soc/qcom/altmode-glink.c new file mode 100644 index 000000000000..9a5f674803bd --- /dev/null +++ b/drivers/soc/qcom/altmode-glink.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "altmode-glink: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSG_OWNER_USBC_PAN 32780 +#define MSG_TYPE_REQ_RESP 1 + +#define NOTIFY_PAYLOAD_SIZE 16 +#define USBC_WRITE_BUFFER_SIZE 8 + +#define USBC_CMD_WRITE_REQ 0x15 +#define USBC_NOTIFY_IND 0x16 + +#define ALTMODE_NAME_MAX_LEN 10 + +struct usbc_notify_ind_msg { + struct pmic_glink_hdr hdr; + u8 payload[NOTIFY_PAYLOAD_SIZE]; + u32 reserved; +}; + +struct usbc_write_buffer_req_msg { + struct pmic_glink_hdr hdr; + u8 buf[USBC_WRITE_BUFFER_SIZE]; + u32 reserved; +}; + +/** + * struct altmode_dev + * Definition of an altmode device. + * + * @dev: Altmode parent device for all client devices + * @name: Short descriptive name of this altmode device + * @pgclient: PMIC GLINK client to talk to remote subsystem + * @client_idr: idr list for altmode clients + * @client_lock: mutex protecting changes to client_idr + * @d_node: Linked list node to string together multiple amdev's + * @client_list: Linked list head keeping track of this device's clients + * @pan_en_sent: Flag to ensure PAN Enable msg is sent only once + * @send_pan_en_work: To schedule the sending of the PAN Enable message + * @probe_notifier: Inform clients of altmode probe completion + */ +struct altmode_dev { + struct device *dev; + char name[ALTMODE_NAME_MAX_LEN]; + struct pmic_glink_client *pgclient; + struct idr client_idr; + struct mutex client_lock; + struct list_head d_node; + struct list_head client_list; + atomic_t pan_en_sent; + struct delayed_work send_pan_en_work; + struct raw_notifier_head probe_notifier; +}; + +/** + * struct altmode_client + * Definition of a client of an altmode device. + * + * @amdev: Parent altmode device of this client + * @data: Supplied by client driver during registration + * @c_node: Linked list node for parent altmode device's client list + * @port_index: Type-C port index assigned by remote subystem + */ +struct altmode_client { + struct altmode_dev *amdev; + struct altmode_client_data data; + struct list_head c_node; + u8 port_index; +}; + +/** + * struct probe_notify_node + * Linked list node to keep track of altmode clients who, by design, + * register with the altmode framework before altmode probes. + * + * @amdev_name: Name of the altmode device client wants to bind to + * @nb: Client's notifier block + * @node: Linked list node for probe_notify_list + */ +struct probe_notify_node { + char *amdev_name; + struct notifier_block *nb; + struct list_head node; +}; + +/** + * struct list_head amdev_list + * List of altmode devices currently using this driver. + */ +static LIST_HEAD(amdev_list); +static DEFINE_MUTEX(amdev_lock); + +/** + * struct list_head probe_notify_list + * List of altmode clients that register to get notified upon probe + * completion. This list is traversed and clients are removed from this + * list after they have been registered with the altmode device's + * raw_notifier_head. + */ +static LIST_HEAD(probe_notify_list); +static DEFINE_MUTEX(notify_lock); + +static struct altmode_dev *get_amdev_from_dev(struct device *dev) +{ + struct altmode_dev *pos, *tmp; + + mutex_lock(&amdev_lock); + list_for_each_entry_safe(pos, tmp, &amdev_list, d_node) { + if (pos->dev == dev) { + mutex_unlock(&amdev_lock); + return pos; + } + } + mutex_unlock(&amdev_lock); + + return NULL; +} + +static int __altmode_send_data(struct altmode_dev *amdev, void *data, + size_t len) +{ + int rc; + struct usbc_write_buffer_req_msg msg = { { 0 } }; + + if (len > sizeof(msg.buf)) { + pr_err("len %zu exceeds msg buf's size: %zu\n", + len, USBC_WRITE_BUFFER_SIZE); + return -EINVAL; + } + + msg.hdr.owner = MSG_OWNER_USBC_PAN; + msg.hdr.type = MSG_TYPE_REQ_RESP; + msg.hdr.opcode = USBC_CMD_WRITE_REQ; + + memcpy(msg.buf, data, len); + + rc = pmic_glink_write(amdev->pgclient, &msg, sizeof(msg)); + if (rc < 0) + pr_err("Error in sending message: %d\n", rc); + + return rc; +} + +/** + * altmode_register_notifier() + * Register to be notified when altmode probes. + * + * @amdev_name: The altmode device being registered with by client + * @nb: Notifier block to get notified upon probe completion + * + * Returns: 0 upon success, negative upon errors. + */ +int altmode_register_notifier(const char *amdev_name, struct notifier_block *nb) +{ + struct probe_notify_node *notify_node; + + if (!amdev_name || !nb) + return -EINVAL; + + notify_node = kzalloc(sizeof(*notify_node), GFP_KERNEL); + if (!notify_node) + return -ENOMEM; + + notify_node->nb = nb; + notify_node->amdev_name = kstrdup(amdev_name, GFP_KERNEL); + if (!notify_node->amdev_name) { + kfree(notify_node); + return -ENOMEM; + } + + mutex_lock(¬ify_lock); + list_add(¬ify_node->node, &probe_notify_list); + mutex_unlock(¬ify_lock); + + return 0; +} +EXPORT_SYMBOL(altmode_register_notifier); + +/** + * altmode_deregister_notifier() + * Deregister probe completion notifier. + * + * @client: The altmode client obtained during registration + * @nb: Notifier block used for registration + * + * Returns: 0 upon success, negative upon errors. + */ +int altmode_deregister_notifier(struct altmode_client *client, + struct notifier_block *nb) +{ + struct altmode_dev *amdev; + + if (!client || !nb) + return -EINVAL; + + amdev = client->amdev; + if (!amdev) + return -ENODEV; + + return raw_notifier_chain_unregister(&amdev->probe_notifier, nb); +} +EXPORT_SYMBOL(altmode_deregister_notifier); + +/** + * altmode_send_data() + * Send data from altmode client to remote subsystem. + * + * @client: Parent altmode device of this client + * @data: Data to be sent + * @len: Length in bytes of the data to be sent + * + * Returns: 0 upon success, -EINVAL if len exceeds message buffer's + * capacity, and other negative error codes as appropriate. + */ +int altmode_send_data(struct altmode_client *client, void *data, + size_t len) +{ + struct altmode_dev *amdev = client->amdev; + + return __altmode_send_data(amdev, data, len); +} +EXPORT_SYMBOL(altmode_send_data); + +/** + * altmode_register_client() + * Register with altmode to receive PMIC GLINK messages from remote + * subsystem. + * + * @dev: Device of the parent altmode (platform) device + * @client_data: Details identifying altmode client uniquely + * + * Returns: Valid altmode client pointer upon success, ERR_PTRs + * upon errors. + * + * Notes: client_data should contain a unique SVID. + */ +struct altmode_client *altmode_register_client(struct device *dev, + const struct altmode_client_data *client_data) +{ + int rc; + struct altmode_dev *amdev; + struct altmode_client *amclient; + + if (!dev || !dev->parent) + return ERR_PTR(-ENODEV); + + if (!client_data->name || !client_data->priv || !client_data->callback + || !client_data->svid) + return ERR_PTR(-EINVAL); + + amdev = get_amdev_from_dev(dev); + if (!amdev) { + pr_err("No alt mode device exists for %s\n", client_data->name); + return ERR_PTR(-ENODEV); + } + + amclient = kzalloc(sizeof(*amclient), GFP_KERNEL); + if (!amclient) + return ERR_PTR(-ENOMEM); + + amclient->amdev = amdev; + amclient->port_index = U8_MAX; /* invalid */ + amclient->data.svid = client_data->svid; + amclient->data.priv = client_data->priv; + amclient->data.callback = client_data->callback; + amclient->data.name = kstrdup(client_data->name, GFP_KERNEL); + if (!amclient->data.name) { + kfree(amclient); + return ERR_PTR(-ENOMEM); + } + + mutex_lock(&amdev->client_lock); + rc = idr_alloc(&amdev->client_idr, amclient, amclient->data.svid, + amclient->data.svid + 1, GFP_KERNEL); + if (rc < 0) { + pr_err("Error in allocating idr for client %s: %d\n", + client_data->name, rc); + mutex_unlock(&amdev->client_lock); + kfree(amclient->data.name); + kfree(amclient); + return ERR_PTR(rc); + } + + list_add(&amclient->c_node, &amdev->client_list); + mutex_unlock(&amdev->client_lock); + + if (!atomic_read(&amdev->pan_en_sent)) + schedule_delayed_work(&amdev->send_pan_en_work, + msecs_to_jiffies(20)); + + return amclient; +} +EXPORT_SYMBOL(altmode_register_client); + +/** + * altmode_deregister_client() + * Deregister altmode client to stop receiving PMIC GLINK messages + * specific to its SVID from remote subsystem. + * + * @client: Client returned by altmode_register_client() + * + * Returns: 0 upon success, negative upon errors. + * + * Notes: This does not stop the transmission of the messages by + * the remote subsystem - only the reception of them by + * the client. + */ +int altmode_deregister_client(struct altmode_client *client) +{ + struct altmode_dev *amdev; + struct altmode_client *pos, *tmp; + + if (!client || !client->amdev) + return -ENODEV; + + amdev = client->amdev; + + mutex_lock(&amdev->client_lock); + idr_remove(&amdev->client_idr, client->data.svid); + + list_for_each_entry_safe(pos, tmp, &amdev->client_list, c_node) { + if (pos == client) + list_del(&pos->c_node); + } + mutex_unlock(&amdev->client_lock); + + kfree(client->data.name); + kfree(client); + + return 0; +} +EXPORT_SYMBOL(altmode_deregister_client); + +static void altmode_send_pan_en(struct work_struct *work) +{ + int rc; + u32 enable_msg = ALTMODE_PAN_EN; + struct altmode_dev *amdev = container_of(work, struct altmode_dev, + send_pan_en_work.work); + + rc = __altmode_send_data(amdev, &enable_msg, sizeof(enable_msg)); + if (rc < 0) { + pr_err("Failed to send PAN EN: %d\n", rc); + return; + } + + atomic_set(&amdev->pan_en_sent, 1); + pr_debug("Sent PAN EN\n"); +} + +static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index) +{ + int rc; + struct altmode_pan_ack_msg ack; + + ack.cmd_type = ALTMODE_PAN_ACK; + ack.port_index = port_index; + + rc = __altmode_send_data(amdev, &ack, sizeof(ack)); + if (rc < 0) { + pr_err("port %u: Failed to send PAN ACK\n", port_index); + return rc; + } + + pr_debug("port %d: Sent PAN ACK\n", port_index); + + return rc; +} + +#define USBC_NOTIFY_IND_MASK GENMASK(7, 0) +#define GET_OP(opcode) (opcode & USBC_NOTIFY_IND_MASK) +#define GET_SVID(opcode) (opcode >> 16) + +static int altmode_callback(void *priv, void *data, size_t len) +{ + u16 svid, op; + struct usbc_notify_ind_msg *notify_msg = data; + struct pmic_glink_hdr *hdr = ¬ify_msg->hdr; + struct altmode_dev *amdev = priv; + struct altmode_client *amclient; + u8 port_index; + + pr_debug("len: %zu owner: %u type: %u opcode %04x\n", len, hdr->owner, + hdr->type, hdr->opcode); + + /* + * For DisplayPort alt mode, the hdr->opcode is designed as follows: + * + * hdr->opcode = (svid << 16) | USBC_NOTIFY_IND + */ + op = GET_OP(hdr->opcode); + svid = GET_SVID(hdr->opcode); + port_index = notify_msg->payload[0]; + + mutex_lock(&amdev->client_lock); + amclient = idr_find(&amdev->client_idr, svid); + mutex_unlock(&amdev->client_lock); + + if (op == USBC_NOTIFY_IND) { + if (!amclient) { + pr_debug("No client associated with SVID %#x\n", svid); + altmode_send_ack(amdev, port_index); + return 0; + } + + if (amclient->port_index == U8_MAX) + amclient->port_index = port_index; + + pr_debug("Payload: %*ph\n", NOTIFY_PAYLOAD_SIZE, + notify_msg->payload); + amclient->data.callback(amclient->data.priv, + notify_msg->payload, len); + + } + + return 0; +} + +static void altmode_gather_clients(struct altmode_dev *amdev) +{ + struct probe_notify_node *pos, *tmp; + + mutex_lock(¬ify_lock); + list_for_each_entry_safe(pos, tmp, &probe_notify_list, node) { + if (!strcmp(pos->amdev_name, amdev->name)) { + raw_notifier_chain_register(&amdev->probe_notifier, + pos->nb); + /* + * Client's nb has been added to amdev's notifier, so + * it may be removed from the global list. + */ + list_del(&pos->node); + kfree(pos->amdev_name); + kfree(pos); + } + } + mutex_unlock(¬ify_lock); +} + +static void altmode_notify_clients(struct altmode_dev *amdev, + struct platform_device *pdev) +{ + altmode_gather_clients(amdev); + raw_notifier_call_chain(&amdev->probe_notifier, 0, pdev); +} + +static void altmode_device_add(struct altmode_dev *amdev) +{ + mutex_lock(&amdev_lock); + list_add(&amdev->d_node, &amdev_list); + mutex_unlock(&amdev_lock); +} + +static int altmode_probe(struct platform_device *pdev) +{ + int rc; + const char *str = NULL; + struct altmode_dev *amdev; + struct pmic_glink_client_data pgclient_data = { }; + struct device *dev = &pdev->dev; + + amdev = devm_kzalloc(&pdev->dev, sizeof(*amdev), GFP_KERNEL); + if (!amdev) + return -ENOMEM; + + rc = of_property_read_string(dev->of_node, "qcom,altmode-name", + &str); + if (rc < 0) { + dev_err(dev, "No altmode device name specified: %d\n", rc); + return rc; + } + + if (!str || (strlen(str) >= ALTMODE_NAME_MAX_LEN) || + !str_has_prefix(str, "altmode_")) { + dev_err(dev, "Incorrect altmode device name format\n"); + return -EINVAL; + } + + amdev->dev = dev; + strlcpy(amdev->name, str, ALTMODE_NAME_MAX_LEN); + + RAW_INIT_NOTIFIER_HEAD(&amdev->probe_notifier); + + pgclient_data.id = MSG_OWNER_USBC_PAN; + pgclient_data.name = "altmode"; + pgclient_data.msg_cb = altmode_callback; + pgclient_data.priv = amdev; + + amdev->pgclient = pmic_glink_register_client(amdev->dev, + &pgclient_data); + if (IS_ERR(amdev->pgclient)) { + rc = PTR_ERR(amdev->pgclient); + if (rc != -EPROBE_DEFER) + dev_err(dev, "Error in pmic_glink registration: %d\n", + rc); + return rc; + } + + platform_set_drvdata(pdev, amdev); + + mutex_init(&amdev->client_lock); + idr_init(&amdev->client_idr); + INIT_DELAYED_WORK(&amdev->send_pan_en_work, altmode_send_pan_en); + INIT_LIST_HEAD(&amdev->d_node); + INIT_LIST_HEAD(&amdev->client_list); + + altmode_device_add(amdev); + altmode_notify_clients(amdev, pdev); + + return 0; +} + +static void altmode_device_remove(struct altmode_dev *amdev) +{ + struct altmode_dev *pos, *tmp; + + atomic_set(&amdev->pan_en_sent, 0); + + mutex_lock(&amdev_lock); + list_for_each_entry_safe(pos, tmp, &amdev_list, d_node) { + if (pos == amdev) + list_del(&pos->d_node); + } + mutex_unlock(&amdev_lock); +} + +static int altmode_remove(struct platform_device *pdev) +{ + int rc; + struct altmode_dev *amdev = platform_get_drvdata(pdev); + struct altmode_client *client, *tmp; + + cancel_delayed_work_sync(&amdev->send_pan_en_work); + idr_destroy(&amdev->client_idr); + + mutex_lock(&amdev->client_lock); + list_for_each_entry_safe(client, tmp, &amdev->client_list, c_node) + list_del(&client->c_node); + mutex_unlock(&amdev->client_lock); + + altmode_device_remove(amdev); + + rc = pmic_glink_unregister_client(amdev->pgclient); + if (rc < 0) + dev_err(amdev->dev, "Error in pmic_glink de-registration: %d\n", + rc); + + return rc; +} + +static const struct of_device_id altmode_match_table[] = { + { .compatible = "qcom,altmode-glink" }, + {}, +}; + +static struct platform_driver altmode_driver = { + .driver = { + .name = "altmode-glink", + .of_match_table = altmode_match_table, + }, + .probe = altmode_probe, + .remove = altmode_remove, +}; +module_platform_driver(altmode_driver); + +MODULE_DESCRIPTION("QTI Type-C Alt Mode over GLINK"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/soc/qcom/altmode-glink.h b/include/linux/soc/qcom/altmode-glink.h new file mode 100644 index 000000000000..f93c1983c622 --- /dev/null +++ b/include/linux/soc/qcom/altmode-glink.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __ALTMODE_H__ +#define __ALTMODE_H__ + +#include + +/** + * struct altmode_client_data + * Uniquely define altmode client while registering with altmode framework. + * + * @svid: Unique ID for Type-C Altmode client devices + * @name: Short descriptive name to identify client + * @priv: Pointer to client driver's internal top level structure + * @callback: Callback function to receive PMIC GLINK message data + */ +struct altmode_client_data { + u16 svid; + const char *name; + void *priv; + int (*callback)(void *priv, void *data, size_t len); +}; + +struct altmode_client; + +enum altmode_send_msg_type { + ALTMODE_PAN_EN = 0x10, + ALTMODE_PAN_ACK, +}; + +struct altmode_pan_ack_msg { + u32 cmd_type; + u8 port_index; +}; + +#if IS_ENABLED(CONFIG_QTI_ALTMODE_GLINK) + +struct notifier_block; +struct device; + +int altmode_register_notifier(const char *amdev_name, + struct notifier_block *nb); +int altmode_deregister_notifier(struct altmode_client *client, + struct notifier_block *nb); +struct altmode_client *altmode_register_client(struct device *dev, + const struct altmode_client_data *client_data); +int altmode_deregister_client(struct altmode_client *client); +int altmode_send_data(struct altmode_client *client, void *data, size_t len); + +#else + +static inline int altmode_register_notifier(const char *amdev_name, + struct notifier_block *nb) +{ + return -ENODEV; +} + +static inline int altmode_deregister_notifier(struct altmode_client *client, + struct notifier_block *nb) +{ + return -ENODEV; +} + +static inline struct altmode_client *altmode_register_client(struct device *dev, + const struct altmode_client_data *client_data) +{ + return ERR_PTR(-ENODEV); +} + +static inline int altmode_deregister_client(struct altmode_client *client) +{ + return -ENODEV; +} + +static inline int altmode_send_data(struct altmode_client *client, void *data, + size_t len) +{ + return -ENODEV; +} + +#endif + +#endif From 44c3a6cfe811872dea16fabdae904809f530aaea Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 24 Mar 2020 18:41:51 -0700 Subject: [PATCH 8/8] soc: qcom: altmode-glink: add SSR support Add subsystem restart (SSR) support to altmode glink driver so that when subsystem goes down and comes up again, it can send PAN_EN to charger firmware to receive notifications about altmode interface. Change-Id: Ie011d5110b6c26790fe1dd6149115019f8bc261c Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/altmode-glink.c | 41 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/drivers/soc/qcom/altmode-glink.c b/drivers/soc/qcom/altmode-glink.c index 9a5f674803bd..bca5359e8704 100644 --- a/drivers/soc/qcom/altmode-glink.c +++ b/drivers/soc/qcom/altmode-glink.c @@ -381,6 +381,28 @@ static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index) return rc; } +static void altmode_state_cb(void *priv, enum pmic_glink_state state) +{ + struct altmode_dev *amdev = priv; + + pr_debug("state: %d\n", state); + + switch (state) { + case PMIC_GLINK_STATE_DOWN: + /* As of now, nothing to do */ + break; + case PMIC_GLINK_STATE_UP: + mutex_lock(&amdev->client_lock); + if (!list_empty(&amdev->client_list)) + schedule_delayed_work(&amdev->send_pan_en_work, + msecs_to_jiffies(20)); + mutex_unlock(&amdev->client_lock); + break; + default: + return; + } +} + #define USBC_NOTIFY_IND_MASK GENMASK(7, 0) #define GET_OP(opcode) (opcode & USBC_NOTIFY_IND_MASK) #define GET_SVID(opcode) (opcode >> 16) @@ -495,10 +517,17 @@ static int altmode_probe(struct platform_device *pdev) RAW_INIT_NOTIFIER_HEAD(&amdev->probe_notifier); + mutex_init(&amdev->client_lock); + idr_init(&amdev->client_idr); + INIT_DELAYED_WORK(&amdev->send_pan_en_work, altmode_send_pan_en); + INIT_LIST_HEAD(&amdev->d_node); + INIT_LIST_HEAD(&amdev->client_list); + pgclient_data.id = MSG_OWNER_USBC_PAN; pgclient_data.name = "altmode"; pgclient_data.msg_cb = altmode_callback; pgclient_data.priv = amdev; + pgclient_data.state_cb = altmode_state_cb; amdev->pgclient = pmic_glink_register_client(amdev->dev, &pgclient_data); @@ -507,21 +536,19 @@ static int altmode_probe(struct platform_device *pdev) if (rc != -EPROBE_DEFER) dev_err(dev, "Error in pmic_glink registration: %d\n", rc); - return rc; + goto error_register; } platform_set_drvdata(pdev, amdev); - mutex_init(&amdev->client_lock); - idr_init(&amdev->client_idr); - INIT_DELAYED_WORK(&amdev->send_pan_en_work, altmode_send_pan_en); - INIT_LIST_HEAD(&amdev->d_node); - INIT_LIST_HEAD(&amdev->client_list); - altmode_device_add(amdev); altmode_notify_clients(amdev, pdev); return 0; + +error_register: + idr_destroy(&amdev->client_idr); + return rc; } static void altmode_device_remove(struct altmode_dev *amdev)