Merge "defconfig: enable memlat hw log driver"

This commit is contained in:
qctecmdr 2020-09-14 06:19:35 -07:00 • committed by Gerrit - the friendly Code Review server
commit 9948a5bf34
11 changed files with 2597 additions and 0 deletions

View file

@ -169,3 +169,9 @@ CONFIG_QCOM_QFPROM_SYSFS=y
CONFIG_ARM_QCOM_CPUFREQ_HW_DEBUG=y
CONFIG_STM_PROTO_BASIC=y
CONFIG_INTERCONNECT_QCOM_CPUCP_L3=y
CONFIG_QCOM_RIMPS=y
CONFIG_QTI_SCMI_MEMLAT_PROTOCOL=y
CONFIG_QTI_HW_MEMLAT=y
CONFIG_QTI_HW_MEMLAT_SCMI_CLIENT=y
CONFIG_SRAM=y
CONFIG_QTI_HW_MEMLAT_LOG=y

View file

@ -27,6 +27,18 @@ config ARM_SCMI_PROTOCOL
This protocol library provides interface for all the client drivers
making use of the features offered by the SCMI.
config QTI_SCMI_MEMLAT_PROTOCOL
tristate "Qualcomm Technologies, Inc. SCMI MEMLAT vendor Protocol"
depends on ARM || ARM64 || COMPILE_TEST
depends on ARM_SCMI_PROTOCOL && QCOM_RIMPS
help
System Control and Management Interface (SCMI) memlat vendor protocol
this protocol provides interface to communicate with micro controller
which is executing the hw memlat governor
This driver defines the comands or message ID's used for this
communication and also exposes the ops used by clients.
config ARM_SCMI_POWER_DOMAIN
tristate "SCMI power domain driver"
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)

View file

@ -4,3 +4,4 @@ scmi-bus-y = bus.o
scmi-driver-y = driver.o
scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o
obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
obj-$(CONFIG_QTI_SCMI_MEMLAT_PROTOCOL) += memlat_vendor.o

View file

@ -0,0 +1,335 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include "common.h"
#define MAX_MAP_ENTRIES 14
#define MAX_PMU_ENTRIES 24
#define SCMI_VENDOR_MSG_START (3)
#define SCMI_VENDOR_MSG_MODULE_START (16)
#define SCMI_MAX_RX_SIZE 128
#define SCMI_MAX_GET_DATA_SIZE 124
enum scmi_memlat_protocol_cmd {
MEMLAT_SET_LOG_LEVEL = SCMI_VENDOR_MSG_START,
MEMLAT_SET_CPU_GROUP = SCMI_VENDOR_MSG_MODULE_START,
MEMLAT_SET_MONITOR,
MEMLAT_COMMON_PMU_MAP,
MEMLAT_MON_PMU_MAP,
MEMLAT_RATIO_CEIL,
MEMLAT_STALL_FLOOR,
MEMLAT_L3_L2WB_PCT,
MEMLAT_L3_IPM_FILTER,
MEMLAT_SAMPLE_MS,
MEMLAT_MON_FREQ_MAP,
MEMLAT_SET_MIN_FREQ,
MEMLAT_SET_MAX_FREQ,
MEMLAT_START_MONITOR,
MEMLAT_STOP_MONITOR,
MEMLAT_GET_DATA = 0xFF,
MEMLAT_MAX_MSG
};
struct node_msg {
uint32_t cpumask;
uint32_t mon_type;
};
struct scalar_param_msg {
uint32_t cpumask;
uint32_t mon_type;
uint32_t val;
};
struct map_table {
uint32_t v1;
uint32_t v2;
};
struct map_param_msg {
uint32_t cpumask;
uint32_t mon_type;
uint32_t nr_rows;
struct map_table tbl[MAX_MAP_ENTRIES];
};
struct pmu_map_msg {
uint32_t cpumask;
uint32_t mon_type;
uint32_t nr_entries;
uint32_t pmu[MAX_PMU_ENTRIES];
};
static int scmi_set_cpugrp_mon(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 msg_id)
{
int ret = 0;
struct scmi_xfer *t;
struct node_msg *msg;
ret = scmi_xfer_get_init(handle, msg_id,
SCMI_PROTOCOL_MEMLAT,
sizeof(*msg), sizeof(*msg), &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->cpumask = cpu_to_le32(cpus_mpidr);
msg->mon_type = cpu_to_le32(mon_type);
ret = scmi_do_xfer(handle, t);
scmi_xfer_put(handle, t);
return ret;
}
static int scmi_set_mon(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type)
{
return scmi_set_cpugrp_mon(handle, cpus_mpidr,
mon_type, MEMLAT_SET_MONITOR);
}
static int scmi_set_cpu_grp(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type)
{
return scmi_set_cpugrp_mon(handle, cpus_mpidr,
mon_type, MEMLAT_SET_CPU_GROUP);
}
static int scmi_send_pmu_map_command(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 nr_entries,
void *buf, u32 msg_id)
{
int ret, i = 0;
struct scmi_xfer *t;
struct pmu_map_msg *msg;
u32 *dst;
struct map_table *src = buf;
if (nr_entries > MAX_PMU_ENTRIES)
return -EINVAL;
ret = scmi_xfer_get_init(handle, msg_id,
SCMI_PROTOCOL_MEMLAT,
sizeof(*msg), sizeof(*msg), &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->cpumask = cpu_to_le32(cpus_mpidr);
msg->mon_type = cpu_to_le32(mon_type);
msg->nr_entries = cpu_to_le32(nr_entries);
dst = msg->pmu;
for (i = 0; i < nr_entries; i++)
dst[i] = cpu_to_le32(src[i].v2);
ret = scmi_do_xfer(handle, t);
scmi_xfer_put(handle, t);
return ret;
}
static int scmi_common_pmu_map(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type,
u32 nr_entries, void *buf)
{
return scmi_send_pmu_map_command(handle, cpus_mpidr,
mon_type, nr_entries, buf,
MEMLAT_COMMON_PMU_MAP);
}
static int scmi_mon_pmu_map(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type,
u32 nr_entries, void *buf)
{
return scmi_send_pmu_map_command(handle, cpus_mpidr,
mon_type, nr_entries, buf,
MEMLAT_MON_PMU_MAP);
}
static int scmi_freq_map(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type,
u32 nr_rows, void *buf)
{
int ret, i = 0;
struct scmi_xfer *t;
struct map_param_msg *msg;
struct map_table *tbl, *src = buf;
if (nr_rows > MAX_MAP_ENTRIES)
return -EINVAL;
ret = scmi_xfer_get_init(handle, MEMLAT_MON_FREQ_MAP,
SCMI_PROTOCOL_MEMLAT,
sizeof(*msg), sizeof(*msg), &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->cpumask = cpu_to_le32(cpus_mpidr);
msg->mon_type = cpu_to_le32(mon_type);
msg->nr_rows = cpu_to_le32(nr_rows);
tbl = msg->tbl;
for (i = 0; i < nr_rows; i++) {
tbl[i].v1 = cpu_to_le32(src[i].v1);
tbl[i].v2 = cpu_to_le32(src[i].v2);
}
ret = scmi_do_xfer(handle, t);
scmi_xfer_put(handle, t);
return ret;
}
#define scmi_send_cmd(name, _msg_id) \
static int scmi_##name(const struct scmi_handle *handle, \
u32 cpus_mpidr, u32 mon_type, u32 val) \
{ \
int ret = 0; \
struct scmi_xfer *t; \
struct scalar_param_msg *msg; \
ret = scmi_xfer_get_init(handle, _msg_id, \
SCMI_PROTOCOL_MEMLAT, \
sizeof(*msg), sizeof(*msg), &t); \
if (ret) \
return ret; \
msg = t->tx.buf; \
msg->cpumask = cpu_to_le32(cpus_mpidr); \
msg->mon_type = cpu_to_le32(mon_type); \
msg->val = cpu_to_le32(val); \
ret = scmi_do_xfer(handle, t); \
scmi_xfer_put(handle, t); \
return ret; \
} \
scmi_send_cmd(ratio_ceil, MEMLAT_RATIO_CEIL);
scmi_send_cmd(stall_floor, MEMLAT_STALL_FLOOR);
scmi_send_cmd(l2wb_pct, MEMLAT_L3_L2WB_PCT);
scmi_send_cmd(l2wb_filter, MEMLAT_L3_IPM_FILTER);
scmi_send_cmd(sample_ms, MEMLAT_SAMPLE_MS);
scmi_send_cmd(min_freq, MEMLAT_SET_MIN_FREQ);
scmi_send_cmd(max_freq, MEMLAT_SET_MAX_FREQ);
static int scmi_send_start_stop(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 msg_id)
{
int ret = 0;
struct scmi_xfer *t;
struct scalar_param_msg *msg;
ret = scmi_xfer_get_init(handle, msg_id,
SCMI_PROTOCOL_MEMLAT,
sizeof(*msg), sizeof(*msg), &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->cpumask = cpu_to_le32(cpus_mpidr);
msg->mon_type = cpu_to_le32(mon_type);
ret = scmi_do_xfer(handle, t);
scmi_xfer_put(handle, t);
return ret;
}
static int scmi_stop_mon(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type)
{
return scmi_send_start_stop(handle, cpus_mpidr,
mon_type, MEMLAT_STOP_MONITOR);
}
static int scmi_start_mon(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type)
{
return scmi_send_start_stop(handle, cpus_mpidr,
mon_type, MEMLAT_START_MONITOR);
}
static int scmi_get_data(const struct scmi_handle *handle, u8 *buf)
{
int ret = 0;
struct scmi_xfer *t;
u32 prev_cnt = 0;
struct scalar_param_msg *msg;
ret = scmi_xfer_get_init(handle, MEMLAT_GET_DATA,
SCMI_PROTOCOL_MEMLAT, sizeof(*msg),
SCMI_MAX_RX_SIZE, &t);
if (ret)
return ret;
do {
ret = scmi_do_xfer(handle, t);
if (ret == -ETIMEDOUT)
ret = scmi_do_xfer(handle, t);
if (ret < 0)
break;
memcpy((buf + prev_cnt), t->rx.buf, t->rx.len);
prev_cnt += t->rx.len;
} while (t->rx.len >= SCMI_MAX_GET_DATA_SIZE);
scmi_xfer_put(handle, t);
return ret;
}
static int scmi_set_log_level(const struct scmi_handle *handle, u32 val)
{
int ret = 0;
struct scmi_xfer *t;
u32 *ptr;
ret = scmi_xfer_get_init(handle, MEMLAT_SET_LOG_LEVEL,
SCMI_PROTOCOL_MEMLAT, sizeof(u32),
sizeof(u32), &t);
if (ret)
return ret;
ptr = (u32 *)t->tx.buf;
*ptr = cpu_to_le32(val);
ret = scmi_do_xfer(handle, t);
scmi_xfer_put(handle, t);
return ret;
}
static struct scmi_memlat_vendor_ops memlat_ops = {
.set_cpu_grp = scmi_set_cpu_grp,
.freq_map = scmi_freq_map,
.set_mon = scmi_set_mon,
.common_pmu_map = scmi_common_pmu_map,
.mon_pmu_map = scmi_mon_pmu_map,
.ratio_ceil = scmi_ratio_ceil,
.stall_floor = scmi_stall_floor,
.sample_ms = scmi_sample_ms,
.l2wb_filter = scmi_l2wb_filter,
.l2wb_pct = scmi_l2wb_pct,
.min_freq = scmi_min_freq,
.max_freq = scmi_max_freq,
.start_monitor = scmi_start_mon,
.stop_monitor = scmi_stop_mon,
.set_log_level = scmi_set_log_level,
.get_data = scmi_get_data,
};
static int scmi_memlat_vendor_protocol_init(struct scmi_handle *handle)
{
u32 version;
scmi_version_get(handle, SCMI_PROTOCOL_MEMLAT, &version);
dev_dbg(handle->dev, "memlat version %d.%d\n",
PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
handle->memlat_ops = &memlat_ops;
return 0;
}
static int __init scmi_memlat_init(void)
{
return scmi_protocol_register(SCMI_PROTOCOL_MEMLAT,
&scmi_memlat_vendor_protocol_init);
}
subsys_initcall(scmi_memlat_init);

View file

@ -148,6 +148,16 @@ config QCOM_IPCC
Say Y here to compile the driver as a part of kernel or M to compile
as a module.
config QCOM_RIMPS
tristate "Qualcomm Technologies, Inc. Rimps driver"
depends on MAILBOX
help
Qualcomm Technologies, Inc. RIMPS driver for MSM devices. This driver
acts as a mailbox controller to do doorbell between Apss and Rimps
subsystem. Say yes here to enable rx and tx channel between both
the subsystems.
If unsure, say n.
config QCOM_LLCC
tristate "Qualcomm Technologies, Inc. LLCC driver"
depends on ARCH_QCOM || COMPILE_TEST
@ -710,6 +720,42 @@ config QTI_SYSTEM_PM
config QTI_SYSTEM_PM_RPM
bool
config QTI_HW_MEMLAT
tristate "Qualcomm Technologies Inc. RIMPS memlat interface driver"
depends on PERF_EVENTS
default n
help
Interface driver between RIMPS memlat and userspace.
This driver is responsible for handling the communication with
RIMPS memlat such as setting up PMU events, passing tunables, frequency
table, PMU counters and saving the values of PMU counters during LPM and
hotplug.
config QTI_HW_MEMLAT_SCMI_CLIENT
tristate "Qualcomm Technologies Inc. SCMI client driver for HW MEMLAT"
depends on QTI_HW_MEMLAT
default n
help
SCMI client driver registers itself with SCMI framework for memlat
vendor protocol, and also registers with the memlat hw interface
driver.
This driver deliver the memlat vendor protocol handle to interface
driver, and interface driver will use this handle to communicate with
memlat HW.
config QTI_HW_MEMLAT_LOG
tristate "Qualcomm Technologies Inc. HW MEMLAT Logging"
depends on IPC_LOGGING && QCOM_RIMPS
default n
help
Memlat hw logging driver, this driver has the infra to collect logs
generated in MEMLAT HW and log the buffers.
This driver register with IPC_Logging framework, to have dedicated
buffer for memlat hw device.
if MSM_PM
menuconfig MSM_IDLE_STATS
bool "Collect idle statistics"

View file

@ -35,6 +35,7 @@ obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
obj-$(CONFIG_QCOM_PROXY_OF_CONSUMER) += qcom_proxy_of_consumer.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_IPCC) += qcom_ipcc.o
obj-$(CONFIG_QCOM_RIMPS) += qcom_rimps.o
obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o
obj-$(CONFIG_QCOM_APR) += apr.o
obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o
@ -92,3 +93,6 @@ obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o
endif
obj-$(CONFIG_QTI_SYS_PM_VX) += sys_pm_vx.o
obj-$(CONFIG_ICNSS2) += icnss2/
obj-$(CONFIG_QTI_HW_MEMLAT_SCMI_CLIENT) += memlat_scmi.o
obj-$(CONFIG_QTI_HW_MEMLAT) += rimps_memlat.o
obj-$(CONFIG_QTI_HW_MEMLAT_LOG) += rimps_log.o

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/scmi_protocol.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
extern void rimps_memlat_init(struct scmi_handle *handle);
static int scmi_memlat_probe(struct scmi_device *sdev)
{
struct scmi_handle *handle = sdev->handle;
if (!handle || !handle->memlat_ops)
return -ENODEV;
rimps_memlat_init(handle);
return 0;
}
static const struct scmi_device_id scmi_id_table[] = {
{ SCMI_PROTOCOL_MEMLAT },
};
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
static struct scmi_driver scmi_memlat_drv = {
.name = "scmi-memlat-driver",
.probe = scmi_memlat_probe,
.id_table = scmi_id_table,
};
module_scmi_driver(scmi_memlat_drv);
MODULE_DESCRIPTION("ARM SCMI Memlat driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/platform_device.h>
#include <linux/mailbox_controller.h>
/* RIMPS Register offsets */
#define RIMPS_IPC_CHAN_SUPPORTED 2
#define RIMPS_SEND_IRQ_OFFSET 0xC
#define RIMPS_SEND_IRQ_VAL BIT(28)
#define RIMPS_CLEAR_IRQ_OFFSET 0x308
#define RIMPS_STATUS_IRQ_OFFSET 0x30C
#define RIMPS_CLEAR_IRQ_VAL BIT(3)
#define RIMPS_STATUS_IRQ_VAL BIT(3)
#define RIMPS_CLOCK_DOMAIN_OFFSET 0x1000
/**
* struct rimps_ipc ipc per channel
* @mbox: mailbox-controller interface
* @chans: The mailbox clients' channel array
* @dev: Device associated with this instance
* @irq: Rimps to HLOS irq
*/
struct qcom_rimps_ipc {
struct mbox_controller mbox;
struct mbox_chan chans[RIMPS_IPC_CHAN_SUPPORTED];
void __iomem *tx_irq_base;
void __iomem *rx_irq_base;
struct device *dev;
int irq;
int num_chan;
};
static irqreturn_t qcom_rimps_rx_interrupt(int irq, void *p)
{
struct qcom_rimps_ipc *rimps_ipc;
u32 val;
int i;
rimps_ipc = p;
for (i = 0; i < rimps_ipc->num_chan; i++) {
val = readl_no_log(rimps_ipc->rx_irq_base +
RIMPS_STATUS_IRQ_OFFSET + (i * RIMPS_CLOCK_DOMAIN_OFFSET));
if (val & RIMPS_STATUS_IRQ_VAL) {
val = RIMPS_CLEAR_IRQ_VAL;
writel_no_log(val, rimps_ipc->rx_irq_base +
RIMPS_CLEAR_IRQ_OFFSET +
(i * RIMPS_CLOCK_DOMAIN_OFFSET));
/* Make sure register write is complete before proceeding */
mb();
if (rimps_ipc->chans[i].con_priv)
mbox_chan_received_data(&rimps_ipc->chans[i]
, NULL);
}
}
return IRQ_HANDLED;
}
static void qcom_rimps_mbox_shutdown(struct mbox_chan *chan)
{
chan->con_priv = NULL;
}
static int qcom_rimps_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct qcom_rimps_ipc *rimps_ipc = container_of(chan->mbox,
struct qcom_rimps_ipc, mbox);
writel_no_log(RIMPS_SEND_IRQ_VAL,
rimps_ipc->tx_irq_base + RIMPS_SEND_IRQ_OFFSET);
return 0;
}
static struct mbox_chan *qcom_rimps_mbox_xlate(struct mbox_controller *mbox,
const struct of_phandle_args *sp)
{
struct qcom_rimps_ipc *rimps_ipc = container_of(mbox,
struct qcom_rimps_ipc, mbox);
unsigned long ind = sp->args[0];
if (sp->args_count != 1)
return ERR_PTR(-EINVAL);
if (ind >= mbox->num_chans)
return ERR_PTR(-EINVAL);
if (mbox->chans[ind].con_priv)
return ERR_PTR(-EBUSY);
mbox->chans[ind].con_priv = rimps_ipc;
return &mbox->chans[ind];
}
static const struct mbox_chan_ops rimps_mbox_chan_ops = {
.send_data = qcom_rimps_mbox_send_data,
.shutdown = qcom_rimps_mbox_shutdown
};
static int qcom_rimps_ipc_setup_mbox(struct qcom_rimps_ipc *rimps_ipc)
{
struct mbox_controller *mbox;
struct device *dev = rimps_ipc->dev;
unsigned long i;
/* Initialize channel identifiers */
for (i = 0; i < ARRAY_SIZE(rimps_ipc->chans); i++)
rimps_ipc->chans[i].con_priv = NULL;
mbox = &rimps_ipc->mbox;
mbox->dev = dev;
mbox->num_chans = rimps_ipc->num_chan;
mbox->chans = rimps_ipc->chans;
mbox->ops = &rimps_mbox_chan_ops;
mbox->of_xlate = qcom_rimps_mbox_xlate;
mbox->txdone_irq = false;
mbox->txdone_poll = false;
return mbox_controller_register(mbox);
}
static int qcom_rimps_probe(struct platform_device *pdev)
{
struct qcom_rimps_ipc *rimps_ipc;
struct resource *res;
int ret;
rimps_ipc = devm_kzalloc(&pdev->dev, sizeof(*rimps_ipc), GFP_KERNEL);
if (!rimps_ipc)
return -ENOMEM;
rimps_ipc->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get the device base address\n");
return -ENODEV;
}
rimps_ipc->tx_irq_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!rimps_ipc->tx_irq_base) {
dev_err(&pdev->dev, "Failed to ioremap the rimps tx irq addr\n");
return -ENOMEM;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dev_err(&pdev->dev, "Failed to get the device base address\n");
return -ENODEV;
}
rimps_ipc->rx_irq_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!rimps_ipc->rx_irq_base) {
dev_err(&pdev->dev, "Failed to ioremap the rimps rx irq addr\n");
return -ENOMEM;
}
rimps_ipc->irq = platform_get_irq(pdev, 0);
if (rimps_ipc->irq < 0) {
dev_err(&pdev->dev, "Failed to get the IRQ\n");
return rimps_ipc->irq;
}
rimps_ipc->num_chan = RIMPS_IPC_CHAN_SUPPORTED;
ret = qcom_rimps_ipc_setup_mbox(rimps_ipc);
if (ret) {
dev_err(&pdev->dev, "Failed to create mailbox\n");
return ret;
}
ret = devm_request_irq(&pdev->dev, rimps_ipc->irq,
qcom_rimps_rx_interrupt, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "qcom_rimps", rimps_ipc);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret);
goto err_mbox;
}
platform_set_drvdata(pdev, rimps_ipc);
return 0;
err_mbox:
mbox_controller_unregister(&rimps_ipc->mbox);
return ret;
}
static int qcom_rimps_remove(struct platform_device *pdev)
{
struct qcom_rimps_ipc *rimps_ipc = platform_get_drvdata(pdev);
mbox_controller_unregister(&rimps_ipc->mbox);
return 0;
}
static const struct of_device_id qcom_rimps_of_match[] = {
{ .compatible = "qcom,rimps"},
{}
};
MODULE_DEVICE_TABLE(of, qcom_rimps_of_match);
static struct platform_driver qcom_rimps_driver = {
.probe = qcom_rimps_probe,
.remove = qcom_rimps_remove,
.driver = {
.name = "qcom_rimps",
.of_match_table = qcom_rimps_of_match,
},
};
static int __init qcom_rimps_init(void)
{
int ret;
ret = platform_driver_register(&qcom_rimps_driver);
if (ret)
pr_err("%s: qcom_rimps register failed %d\n", __func__, ret);
return ret;
}
#if IS_MODULE(CONFIG_QCOM_RIMPS)
module_init(qcom_rimps_init);
#else
arch_initcall(qcom_rimps_init);
#endif
static __exit void qcom_rimps_exit(void)
{
platform_driver_unregister(&qcom_rimps_driver);
}
module_exit(qcom_rimps_exit);
MODULE_DESCRIPTION("QTI RIMPS Driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/ipc_logging.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define NUM_LOG_PAGES 10
#define MAX_PRINT_SIZE 200
#define MAX_BUF_NUM 10
#define MAX_RESIDUAL_SIZE MAX_PRINT_SIZE
#define LOG_ID_MARKER 0x474F4C
#define LOG_ID_MARKER_SHIFT 24
#define LOG_ID_MARKER_MASK 0xFFFFFF
#define LOG_ID_MASK 0xFF
#define SIZE_ADJUST 4
#define SRC_OFFSET 4
enum sync_mode {
ping_pong,
log_id
};
struct remote_mem {
void __iomem *start;
unsigned long long size;
};
struct rimps_buf {
struct list_head node;
char *buf;
u32 size;
u32 cpy_idx;
};
struct rimps_log_info {
struct remote_mem *rmem;
struct mbox_client cl;
struct mbox_chan *ch;
struct delayed_work work;
struct device *dev;
void __iomem *base;
void *ipc_log_ctxt;
enum sync_mode sync_mode;
unsigned int rmem_idx;
unsigned int num_bufs;
char *rem_buf;
char *glb_buf;
int rem_len;
spinlock_t free_list_lock;
spinlock_t full_list_lock;
};
static LIST_HEAD(full_buffers_list);
static LIST_HEAD(free_buffers_list);
static struct workqueue_struct *rimps_wq;
static inline int get_next_logbuf(char *buf, int size)
{
int i;
for (i = (size - 1); i >= 0 ; i--) {
if (buf[i] == '\n')
break;
}
if (i >= 0)
buf[i] = '\0';
else
return size;
return i + 1;
}
static void rimps_log_work(struct work_struct *work)
{
struct rimps_log_info *info = container_of(work,
struct rimps_log_info,
work.work);
char *src;
int buf_start = 0;
int cnt = 0, print_size = 0, buf_size = 0;
char c;
struct rimps_buf *buf_node;
unsigned long flags;
while (1) {
spin_lock_irqsave(&info->full_list_lock, flags);
if (list_empty(&full_buffers_list)) {
spin_unlock_irqrestore(&info->full_list_lock, flags);
return;
}
buf_node = list_first_entry(&full_buffers_list,
struct rimps_buf, node);
list_del(&buf_node->node);
spin_unlock_irqrestore(&info->full_list_lock, flags);
buf_start = buf_node->cpy_idx - info->rem_len;
if (info->rem_len)
memcpy(&buf_node->buf[buf_start],
info->rem_buf, info->rem_len);
src = &buf_node->buf[buf_start];
buf_size = buf_node->size + info->rem_len;
do {
print_size = (buf_size >= MAX_PRINT_SIZE) ?
MAX_PRINT_SIZE : buf_size;
cnt = get_next_logbuf(src, print_size);
if (cnt == print_size) {
if (buf_size < MAX_PRINT_SIZE) {
info->rem_len = buf_size;
memcpy(info->rem_buf, src, buf_size);
goto out;
} else {
c = src[cnt - 1];
src[cnt - 1] = '\0';
ipc_log_string(info->ipc_log_ctxt,
"%s%c\n", src, c);
}
} else {
ipc_log_string(info->ipc_log_ctxt,
"%s\n", src, c);
}
buf_start += cnt;
buf_size -= cnt;
src = &buf_node->buf[buf_start];
} while (buf_size > 0);
out:
spin_lock_irqsave(&info->free_list_lock, flags);
list_add_tail(&buf_node->node, &free_buffers_list);
spin_unlock_irqrestore(&info->free_list_lock, flags);
}
}
static struct rimps_buf *get_free_buffer(struct rimps_log_info *info)
{
struct rimps_buf *buf_node;
unsigned long flags;
spin_lock_irqsave(&info->free_list_lock, flags);
if (list_empty(&free_buffers_list)) {
spin_unlock_irqrestore(&info->free_list_lock, flags);
return NULL;
}
buf_node = list_first_entry(&free_buffers_list,
struct rimps_buf, node);
list_del(&buf_node->node);
spin_unlock_irqrestore(&info->free_list_lock, flags);
return buf_node;
}
static void rimps_log_rx(struct mbox_client *client, void *msg)
{
struct rimps_log_info *info = dev_get_drvdata(client->dev);
struct device *dev = info->dev;
struct rimps_buf *buf_node;
struct remote_mem *rmem;
void __iomem *src;
u32 marker;
unsigned long long rmem_size;
unsigned long flags;
int src_offset = 0;
int size_adj = 0;
buf_node = get_free_buffer(info);
if (!buf_node) {
dev_err(dev, "global buffer full dropping buffers\n");
return;
}
if (info->sync_mode == log_id) {
marker = *(u32 *)(info->rmem)->start;
if ((marker & LOG_ID_MARKER_MASK) != LOG_ID_MARKER) {
pr_err("%s: Log signature incorrect\n", __func__);
return;
}
info->rmem_idx = ((marker >> LOG_ID_MARKER_SHIFT)
& LOG_ID_MASK);
if (info->rmem_idx >= info->num_bufs) {
dev_err(dev, "wrong index id dropping\n");
return;
}
if (info->rmem_idx == 0) {
size_adj = SIZE_ADJUST;
src_offset = SRC_OFFSET;
}
}
rmem = info->rmem + info->rmem_idx;
rmem_size = rmem->size - size_adj;
src = rmem->start + src_offset;
memcpy_fromio(&buf_node->buf[buf_node->cpy_idx],
src, rmem_size);
buf_node->size = rmem_size;
spin_lock_irqsave(&info->full_list_lock, flags);
list_add_tail(&buf_node->node, &full_buffers_list);
spin_unlock_irqrestore(&info->full_list_lock, flags);
if (!delayed_work_pending(&info->work))
queue_delayed_work(rimps_wq, &info->work, 0);
if (info->sync_mode == ping_pong) {
info->rmem_idx++;
if (info->rmem_idx == info->num_bufs)
info->rmem_idx = 0;
}
}
static int populate_free_buffers(struct rimps_log_info *info,
int rmem_size)
{
int i = 0;
struct rimps_buf *buf_nodes;
buf_nodes = devm_kzalloc(info->dev,
MAX_BUF_NUM * sizeof(struct rimps_buf),
GFP_KERNEL);
if (!buf_nodes)
return -ENOMEM;
for (i = 0; i < MAX_BUF_NUM; i++) {
buf_nodes[i].buf = &info->glb_buf[i * (rmem_size + MAX_PRINT_SIZE)];
buf_nodes[i].size = rmem_size;
buf_nodes[i].cpy_idx = MAX_PRINT_SIZE;
list_add_tail(&buf_nodes[i].node, &free_buffers_list);
}
return 0;
}
static int rimps_log_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rimps_log_info *info;
struct mbox_client *cl;
int ret, i = 0;
struct resource *res;
void __iomem *mem_base;
struct remote_mem *rmem;
int prev_size = 0;
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = dev;
rmem = kcalloc(pdev->num_resources, sizeof(struct remote_mem),
GFP_KERNEL);
if (!rmem)
return -ENOMEM;
info->rmem = rmem;
for (i = 0; i < pdev->num_resources; i++) {
struct remote_mem *rmem = &info->rmem[i];
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
if (!res) {
dev_err(dev,
"Failed to get the device base address\n");
ret = -ENODEV;
goto exit;
}
mem_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (IS_ERR(mem_base)) {
ret = PTR_ERR(mem_base);
goto exit;
}
rmem->start = mem_base;
rmem->size = resource_size(res);
if (prev_size && (rmem->size != prev_size)) {
ret = -EINVAL;
goto exit;
} else if (!prev_size) {
prev_size = rmem->size;
}
info->num_bufs++;
}
info->glb_buf = devm_kzalloc(dev, MAX_BUF_NUM *
(rmem->size + MAX_PRINT_SIZE),
GFP_KERNEL);
if (!info->glb_buf) {
ret = -ENOMEM;
goto exit;
}
info->rem_buf = devm_kzalloc(dev, MAX_RESIDUAL_SIZE, GFP_KERNEL);
if (!info->rem_buf) {
ret = -ENOMEM;
goto exit;
}
ret = populate_free_buffers(info, rmem->size);
if (ret < 0)
goto exit;
info->sync_mode = log_id;
cl = &info->cl;
cl->dev = dev;
cl->tx_block = false;
cl->knows_txdone = true;
cl->rx_callback = rimps_log_rx;
info->ch = mbox_request_channel(cl, 0);
if (IS_ERR(info->ch)) {
ret = PTR_ERR(info->ch);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Failed to request mbox info: %d\n", ret);
goto exit;
}
info->ipc_log_ctxt = ipc_log_context_create(NUM_LOG_PAGES,
dev_name(dev), 0);
if (!info->ipc_log_ctxt) {
dev_err(dev, "failed to create log context\n");
ret = -ENOMEM;
goto exit;
}
dev_set_drvdata(dev, info);
INIT_DEFERRABLE_WORK(&info->work, &rimps_log_work);
spin_lock_init(&info->free_list_lock);
spin_lock_init(&info->full_list_lock);
rimps_wq = create_freezable_workqueue("rimps_wq");
dev_info(dev, "RIMPS logging initialized\n");
return 0;
exit:
kfree(info->rmem);
return ret;
}
static int rimps_log_remove(struct platform_device *pdev)
{
struct rimps_log_info *info;
info = dev_get_drvdata(&pdev->dev);
mbox_free_channel(info->ch);
return 0;
}
static const struct of_device_id rimps_log[] = {
{.compatible = "qcom,rimps-log"},
{},
};
static struct platform_driver rimps_log_driver = {
.driver = {
.name = "rimps-log",
.of_match_table = rimps_log,
},
.probe = rimps_log_probe,
.remove = rimps_log_remove,
};
builtin_platform_driver(rimps_log_driver);
MODULE_LICENSE("GPL v2");

File diff suppressed because it is too large Load diff

View file

@ -207,6 +207,64 @@ struct scmi_reset_ops {
int (*deassert)(const struct scmi_handle *handle, u32 domain);
};
/**
* struct scmi_memlat_vendor_ops - represents the various operations provided
* by SCMI HW Memlat Protocol
*
* @cpu_grp: set the cpugrp
* @set_mon: set the supported monitors
* @common_pmu_map: sets the common PMU map supported by gov
* @mon_pmu_map: sets the common PMU map supported by gov
* @ratio_ceil: sets the ratio_ceil needed for hw memlat governor
* @stall_floor: sets the stall_floor needed for hw memlat governor
* @l2wb_pct: sets the stall_floor needed for hw memlat governor
* @l2wb_filter: sets the l2wb_filter needed for hw memlat governor
* @sample_ms: sets the sample_ms at this interval governor will poll
* @freq_map: sets the freq_map of the monitor
* @min_freq: sets the min_freq of monitor
* @max_freq: sets the max_freq of monitor
* @start_mon: starts monitor in rimps
* @stop_mon: stops monitor in rimps
* @log_level: configure the supported log_level in rimps
* @get_data: added for debug purpose gets the data structure information
*/
struct scmi_memlat_vendor_ops {
int (*set_cpu_grp)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type);
int (*set_mon)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type);
int (*common_pmu_map)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type,
u32 nr_rows, void *buf);
int (*mon_pmu_map)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type,
u32 nr_rows, void *buf);
int (*ratio_ceil)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*stall_floor)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*l2wb_pct)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*l2wb_filter)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*sample_ms)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*freq_map)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type,
u32 nr_rows, void *buf);
int (*min_freq)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*max_freq)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type, u32 val);
int (*start_monitor)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type);
int (*stop_monitor)(const struct scmi_handle *handle,
u32 cpus_mpidr, u32 mon_type);
int (*set_log_level)(const struct scmi_handle *handle, u32 val);
int (*get_data)(const struct scmi_handle *handle, u8 *buf);
};
/**
* struct scmi_handle - Handle returned to ARM SCMI clients for usage.
*
@ -236,6 +294,7 @@ struct scmi_handle {
struct scmi_power_ops *power_ops;
struct scmi_sensor_ops *sensor_ops;
struct scmi_reset_ops *reset_ops;
struct scmi_memlat_vendor_ops *memlat_ops;
/* for protocol internal use */
void *perf_priv;
void *clk_priv;
@ -252,6 +311,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_CLOCK = 0x14,
SCMI_PROTOCOL_SENSOR = 0x15,
SCMI_PROTOCOL_RESET = 0x16,
SCMI_PROTOCOL_MEMLAT = 0x80,
};
struct scmi_device {