Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ba24f27 authored by Guru Das Srinagesh's avatar Guru Das Srinagesh
Browse files

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: default avatarGuru Das Srinagesh <gurus@codeaurora.org>
parent 7960ff65
Loading
Loading
Loading
Loading
+523 −12
Original line number Diff line number Diff line
@@ -14,15 +14,47 @@
#include <linux/slab.h>
#include <linux/soc/qcom/pmic_glink.h>

/* owner/type/opcode for battery debug */
/* 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)