blob: 39f412bbf2c1e60e8dfeb90b9dff7f90cb0f8553 [file] [log] [blame]
// 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");