Contributors: 3
Author Tokens Token Proportion Commits Commit Proportion
Neil Armstrong 651 98.34% 1 33.33%
Andrew Halaney 10 1.51% 1 33.33%
Krzysztof Kozlowski 1 0.15% 1 33.33%
Total 662 3


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2019-2022, The Linux Foundation. All rights reserved.
 * Copyright (c) 2023, Linaro Ltd
 */
#include <linux/of_device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/soc/qcom/pdr.h>
#include <linux/debugfs.h>

#define CREATE_TRACE_POINTS
#include "pmic_pdcharger_ulog.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 LOG_DEFAULT_TIME_MS		1000

#define MAX_ULOG_SIZE			8192

struct pmic_pdcharger_ulog_hdr {
	__le32 owner;
	__le32 type;
	__le32 opcode;
};

struct pmic_pdcharger_ulog {
	struct rpmsg_device *rpdev;
	struct delayed_work ulog_work;
};

struct get_ulog_req_msg {
	struct pmic_pdcharger_ulog_hdr	hdr;
	u32				log_size;
};

struct get_ulog_resp_msg {
	struct pmic_pdcharger_ulog_hdr	hdr;
	u8				buf[MAX_ULOG_SIZE];
};

static int pmic_pdcharger_ulog_write_async(struct pmic_pdcharger_ulog *pg, void *data, size_t len)
{
	return rpmsg_send(pg->rpdev->ept, data, len);
}

static int pmic_pdcharger_ulog_request(struct pmic_pdcharger_ulog *pg)
{
	struct get_ulog_req_msg req_msg = {
		.hdr = {
			.owner = cpu_to_le32(MSG_OWNER_CHG_ULOG),
			.type = cpu_to_le32(MSG_TYPE_REQ_RESP),
			.opcode = cpu_to_le32(GET_CHG_ULOG_REQ)
		},
		.log_size = MAX_ULOG_SIZE
	};

	return pmic_pdcharger_ulog_write_async(pg, &req_msg, sizeof(req_msg));
}

static void pmic_pdcharger_ulog_work(struct work_struct *work)
{
	struct pmic_pdcharger_ulog *pg = container_of(work, struct pmic_pdcharger_ulog,
						      ulog_work.work);
	int rc;

	rc = pmic_pdcharger_ulog_request(pg);
	if (rc) {
		dev_err(&pg->rpdev->dev, "Error requesting ulog, rc=%d\n", rc);
		return;
	}
}

static void pmic_pdcharger_ulog_handle_message(struct pmic_pdcharger_ulog *pg,
					       struct get_ulog_resp_msg *resp_msg,
					       size_t len)
{
	char *token, *buf = resp_msg->buf;

	if (len != sizeof(*resp_msg)) {
		dev_err(&pg->rpdev->dev, "Expected data length: %zu, received: %zu\n",
			sizeof(*resp_msg), len);
		return;
	}

	buf[MAX_ULOG_SIZE - 1] = '\0';

	do {
		token = strsep((char **)&buf, "\n");
		if (token && strlen(token))
			trace_pmic_pdcharger_ulog_msg(token);
	} while (token);
}

static int pmic_pdcharger_ulog_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
					      int len, void *priv, u32 addr)
{
	struct pmic_pdcharger_ulog *pg = dev_get_drvdata(&rpdev->dev);
	struct pmic_pdcharger_ulog_hdr *hdr = data;
	u32 opcode;

	opcode = le32_to_cpu(hdr->opcode);

	switch (opcode) {
	case GET_CHG_ULOG_REQ:
		schedule_delayed_work(&pg->ulog_work, msecs_to_jiffies(LOG_DEFAULT_TIME_MS));
		pmic_pdcharger_ulog_handle_message(pg, data, len);
		break;
	default:
		dev_err(&pg->rpdev->dev, "Unknown opcode %u\n", opcode);
		break;
	}

	return 0;
}

static int pmic_pdcharger_ulog_rpmsg_probe(struct rpmsg_device *rpdev)
{
	struct pmic_pdcharger_ulog *pg;
	struct device *dev = &rpdev->dev;

	pg = devm_kzalloc(dev, sizeof(*pg), GFP_KERNEL);
	if (!pg)
		return -ENOMEM;

	pg->rpdev = rpdev;
	INIT_DELAYED_WORK(&pg->ulog_work, pmic_pdcharger_ulog_work);

	dev_set_drvdata(dev, pg);

	pmic_pdcharger_ulog_request(pg);

	return 0;
}

static void pmic_pdcharger_ulog_rpmsg_remove(struct rpmsg_device *rpdev)
{
	struct pmic_pdcharger_ulog *pg = dev_get_drvdata(&rpdev->dev);

	cancel_delayed_work_sync(&pg->ulog_work);
}

static const struct rpmsg_device_id pmic_pdcharger_ulog_rpmsg_id_match[] = {
	{ "PMIC_LOGS_ADSP_APPS" },
	{}
};
/*
 * No MODULE_DEVICE_TABLE intentionally: that's a debugging module, to be
 * loaded manually only.
 */

static struct rpmsg_driver pmic_pdcharger_ulog_rpmsg_driver = {
	.probe = pmic_pdcharger_ulog_rpmsg_probe,
	.remove = pmic_pdcharger_ulog_rpmsg_remove,
	.callback = pmic_pdcharger_ulog_rpmsg_callback,
	.id_table = pmic_pdcharger_ulog_rpmsg_id_match,
	.drv  = {
		.name  = "qcom_pmic_pdcharger_ulog_rpmsg",
	},
};

module_rpmsg_driver(pmic_pdcharger_ulog_rpmsg_driver);
MODULE_DESCRIPTION("Qualcomm PMIC ChargerPD ULOG driver");
MODULE_LICENSE("GPL");