Merge "drivers: soc: qcom: Add snapshot of cpucp communication drivers"

This commit is contained in:
qctecmdr 2022-09-28 17:04:05 -07:00 committed by Gerrit - the friendly Code Review server
commit 007a38d10e
35 changed files with 9463 additions and 0 deletions

View File

@ -134,6 +134,41 @@ config ARM_SCMI_TRANSPORT_VIRTIO_ATOMIC_ENABLE
in atomic context too, at the price of using a number of busy-waiting
primitives all over instead. If unsure say N.
config QTI_SCMI_PMU_PROTOCOL
tristate "Qualcomm Technologies, Inc. SCMI PMU vendor Protocol"
depends on ARM || ARM64 || COMPILE_TEST
depends on ARM_SCMI_PROTOCOL && QCOM_CPUCP
help
System Control and Management Interface (SCMI) pmu vendor protocol.
This protocol provides interface to communicate with micro controller
which maintains the PMU configuration for multiple clients.
This driver defines the comands or message ID's used for this
communication and also exposes the ops used by clients.
config QTI_SCMI_C1DCVS_PROTOCOL
tristate "Qualcomm Technologies, Inc. SCMI C1DCVS vendor Protocol"
depends on ARM || ARM64 || COMPILE_TEST
depends on ARM_SCMI_PROTOCOL && QCOM_CPUCP
help
System Control and Management Interface (SCMI) c1dcvs vendor protocol.
This protocol provides interface to communicate with micro controller
which maintains the c1dcvs algorithm.
This driver defines the comands or message ID's used for this
communication and also exposes the ops used by clients.
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_CPUCP
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
commands or message ID's used for this communication and also exposes
the ops used by clients.
endif #ARM_SCMI_PROTOCOL
config ARM_SCMI_POWER_DOMAIN

View File

@ -13,6 +13,9 @@ scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
obj-$(CONFIG_QTI_SCMI_PMU_PROTOCOL) += pmu_vendor.o
obj-$(CONFIG_QTI_SCMI_C1DCVS_PROTOCOL) += c1dcvs_vendor.o
obj-$(CONFIG_QTI_SCMI_MEMLAT_PROTOCOL) += memlat_vendor.o
ifeq ($(CONFIG_THUMB2_KERNEL)$(CONFIG_CC_IS_CLANG),yy)
# The use of R7 in the SMCCC conflicts with the compiler's use of R7 as a frame

View File

@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/scmi_c1dcvs.h>
#include "common.h"
#define SCMI_MAX_RX_SIZE 128
enum scmi_c1dcvs_protocol_cmd {
SET_ENABLE_C1DCVS = 11,
GET_ENABLE_C1DCVS,
SET_ENABLE_TRACE,
GET_ENABLE_TRACE,
SET_IPC_THRESH,
GET_IPC_THRESH,
SET_EFREQ_THRESH,
GET_EFREQ_THRESH,
SET_HYSTERESIS,
GET_HYSTERESIS,
};
struct c1dcvs_thresh {
unsigned int cluster;
unsigned int thresh;
};
static int scmi_send_tunable_c1dcvs(const struct scmi_protocol_handle *ph,
void *buf, u32 msg_id)
{
int ret;
struct scmi_xfer *t;
unsigned int *msg;
unsigned int *src = buf;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), sizeof(*msg),
&t);
if (ret)
return ret;
msg = t->tx.buf;
*msg = cpu_to_le32(*src);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_get_tunable_c1dcvs(const struct scmi_protocol_handle *ph,
void *buf, u32 msg_id)
{
int ret;
struct scmi_xfer *t;
struct c1dcvs_thresh *msg;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), SCMI_MAX_RX_SIZE,
&t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
memcpy(buf, t->rx.buf, t->rx.len);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_send_thresh_c1dcvs(const struct scmi_protocol_handle *ph,
void *buf, u32 msg_id)
{
int ret;
struct scmi_xfer *t;
struct c1dcvs_thresh *msg;
unsigned int *src = buf;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), sizeof(*msg),
&t);
if (ret)
return ret;
msg = t->tx.buf;
msg->cluster = cpu_to_le32(src[0]);
msg->thresh = cpu_to_le32(src[1]);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_set_enable_c1dcvs(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_tunable_c1dcvs(ph, buf, SET_ENABLE_C1DCVS);
}
static int scmi_get_enable_c1dcvs(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_get_tunable_c1dcvs(ph, buf, GET_ENABLE_C1DCVS);
}
static int scmi_set_enable_trace(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_tunable_c1dcvs(ph, buf, SET_ENABLE_TRACE);
}
static int scmi_get_enable_trace(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_get_tunable_c1dcvs(ph, buf, GET_ENABLE_TRACE);
}
static int scmi_set_ipc_thresh(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_thresh_c1dcvs(ph, buf, SET_IPC_THRESH);
}
static int scmi_get_ipc_thresh(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_get_tunable_c1dcvs(ph, buf, GET_IPC_THRESH);
}
static int scmi_set_efreq_thresh(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_thresh_c1dcvs(ph, buf, SET_EFREQ_THRESH);
}
static int scmi_get_efreq_thresh(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_get_tunable_c1dcvs(ph, buf, GET_EFREQ_THRESH);
}
static int scmi_set_hysteresis(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_tunable_c1dcvs(ph, buf, SET_HYSTERESIS);
}
static int scmi_get_hysteresis(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_get_tunable_c1dcvs(ph, buf, GET_HYSTERESIS);
}
static struct scmi_c1dcvs_vendor_ops c1dcvs_config_ops = {
.set_enable_c1dcvs = scmi_set_enable_c1dcvs,
.get_enable_c1dcvs = scmi_get_enable_c1dcvs,
.set_enable_trace = scmi_set_enable_trace,
.get_enable_trace = scmi_get_enable_trace,
.set_ipc_thresh = scmi_set_ipc_thresh,
.get_ipc_thresh = scmi_get_ipc_thresh,
.set_efreq_thresh = scmi_set_efreq_thresh,
.get_efreq_thresh = scmi_get_efreq_thresh,
.set_hysteresis = scmi_set_hysteresis,
.get_hysteresis = scmi_get_hysteresis,
};
static int scmi_c1dcvs_protocol_init(const struct scmi_protocol_handle *ph)
{
u32 version;
ph->xops->version_get(ph, &version);
dev_dbg(ph->dev, "version %d.%d\n",
PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
return 0;
}
static const struct scmi_protocol scmi_c1dcvs = {
.id = SCMI_C1DCVS_PROTOCOL,
.owner = THIS_MODULE,
.instance_init = &scmi_c1dcvs_protocol_init,
.ops = &c1dcvs_config_ops,
};
module_scmi_protocol(scmi_c1dcvs);
MODULE_DESCRIPTION("SCMI C1DCVS vendor Protocol");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,387 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/scmi_memlat.h>
#include "common.h"
#define MAX_MAP_ENTRIES 14
#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
#define INVALID_IDX 0xFF
enum scmi_memlat_protocol_cmd {
MEMLAT_SET_LOG_LEVEL = SCMI_VENDOR_MSG_START,
MEMLAT_FLUSH_LOGBUF,
MEMLAT_SET_MEM_GROUP = SCMI_VENDOR_MSG_MODULE_START,
MEMLAT_SET_MONITOR,
MEMLAT_SET_COMMON_EV_MAP,
MEMLAT_SET_GRP_EV_MAP,
MEMLAT_ADAPTIVE_LOW_FREQ,
MEMLAT_ADAPTIVE_HIGH_FREQ,
MEMLAT_GET_ADAPTIVE_CUR_FREQ,
MEMLAT_IPM_CEIL,
MEMLAT_FE_STALL_FLOOR,
MEMLAT_BE_STALL_FLOOR,
MEMLAT_WB_PCT,
MEMLAT_IPM_FILTER,
MEMLAT_FREQ_SCALE_PCT,
MEMLAT_FREQ_SCALE_LIMIT_MHZ,
MEMLAT_SAMPLE_MS,
MEMLAT_MON_FREQ_MAP,
MEMLAT_SET_MIN_FREQ,
MEMLAT_SET_MAX_FREQ,
MEMLAT_GET_CUR_FREQ,
MEMLAT_START_TIMER,
MEMLAT_STOP_TIMER,
MEMLAT_GET_TIMESTAMP,
MEMLAT_MAX_MSG
};
struct node_msg {
uint32_t cpumask;
uint32_t hw_type;
uint32_t mon_type;
uint32_t mon_idx;
char mon_name[MAX_NAME_LEN];
};
struct scalar_param_msg {
uint32_t hw_type;
uint32_t mon_idx;
uint32_t val;
};
struct map_table {
uint32_t v1;
uint32_t v2;
};
struct map_param_msg {
uint32_t hw_type;
uint32_t mon_idx;
uint32_t nr_rows;
struct map_table tbl[MAX_MAP_ENTRIES];
};
struct ev_map_msg {
uint32_t num_evs;
uint32_t hw_type;
uint8_t cid[MAX_EV_CNTRS];
};
static int scmi_set_ev_map(const struct scmi_protocol_handle *ph, u32 hw_type,
void *buf, u32 msg_id, u32 num_evs)
{
int ret, i = 0;
struct scmi_xfer *t;
struct ev_map_msg *msg;
uint8_t *src = buf;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), sizeof(*msg),
&t);
if (ret)
return ret;
msg = t->tx.buf;
msg->num_evs = cpu_to_le32(num_evs);
msg->hw_type = cpu_to_le32(hw_type);
for (i = 0; i < num_evs; i++)
msg->cid[i] = src[i];
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_set_grp_map(const struct scmi_protocol_handle *ph, u32 hw_type,
void *buf, u32 num_evs)
{
return scmi_set_ev_map(ph, hw_type, buf, MEMLAT_SET_GRP_EV_MAP, num_evs);
}
static int scmi_set_common_map(const struct scmi_protocol_handle *ph, void *buf, u32 num_evs)
{
return scmi_set_ev_map(ph, INVALID_IDX, buf, MEMLAT_SET_COMMON_EV_MAP, num_evs);
}
static int scmi_set_memgrp_mon(const struct scmi_protocol_handle *ph,
u32 cpus_mpidr, u32 hw_type, u32 mon_type,
u32 mon_idx, const char *mon_name, u32 msg_id)
{
int ret = 0;
struct scmi_xfer *t;
struct node_msg *msg;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), sizeof(*msg),
&t);
if (ret)
return ret;
msg = t->tx.buf;
msg->cpumask = cpu_to_le32(cpus_mpidr);
msg->hw_type = cpu_to_le32(hw_type);
msg->mon_type = cpu_to_le32(mon_type);
msg->mon_idx = cpu_to_le32(mon_idx);
if (mon_name)
snprintf(msg->mon_name, MAX_NAME_LEN, mon_name);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_set_mon(const struct scmi_protocol_handle *ph, u32 cpus_mpidr,
u32 hw_type, u32 mon_type, u32 mon_idx, const char *mon_name)
{
return scmi_set_memgrp_mon(ph, cpus_mpidr, hw_type, mon_type,
mon_idx, mon_name, MEMLAT_SET_MONITOR);
}
static int scmi_set_mem_grp(const struct scmi_protocol_handle *ph,
u32 cpus_mpidr, u32 hw_type)
{
return scmi_set_memgrp_mon(ph, cpus_mpidr, hw_type, 0,
0, NULL, MEMLAT_SET_MEM_GROUP);
}
static int scmi_freq_map(const struct scmi_protocol_handle *ph, u32 hw_type,
u32 mon_idx, 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 = ph->xops->xfer_get_init(ph, MEMLAT_MON_FREQ_MAP, sizeof(*msg),
sizeof(*msg), &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->hw_type = cpu_to_le32(hw_type);
msg->mon_idx = cpu_to_le32(mon_idx);
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 = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_set_tunable(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 msg_id, u32 mon_idx, u32 val)
{
int ret = 0;
struct scmi_xfer *t;
struct scalar_param_msg *msg;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg),
sizeof(*msg), &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->hw_type = cpu_to_le32(hw_type);
msg->mon_idx = cpu_to_le32(mon_idx);
msg->val = cpu_to_le32(val);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
#define scmi_send_cmd(name, _msg_id) \
static int scmi_##name(const struct scmi_protocol_handle *ph, \
u32 hw_type, u32 mon_idx, u32 val) \
{ \
return scmi_set_tunable(ph, hw_type, _msg_id, mon_idx, val); \
} \
scmi_send_cmd(ipm_ceil, MEMLAT_IPM_CEIL);
scmi_send_cmd(fe_stall_floor, MEMLAT_FE_STALL_FLOOR);
scmi_send_cmd(be_stall_floor, MEMLAT_BE_STALL_FLOOR);
scmi_send_cmd(wb_pct_thres, MEMLAT_WB_PCT);
scmi_send_cmd(wb_filter_ipm, MEMLAT_IPM_FILTER);
scmi_send_cmd(freq_scale_pct, MEMLAT_FREQ_SCALE_PCT);
scmi_send_cmd(freq_scale_limit_mhz, MEMLAT_FREQ_SCALE_LIMIT_MHZ);
scmi_send_cmd(min_freq, MEMLAT_SET_MIN_FREQ);
scmi_send_cmd(max_freq, MEMLAT_SET_MAX_FREQ);
scmi_send_cmd(adaptive_low_freq, MEMLAT_ADAPTIVE_LOW_FREQ);
scmi_send_cmd(adaptive_high_freq, MEMLAT_ADAPTIVE_HIGH_FREQ);
static int scmi_send_start_stop(const struct scmi_protocol_handle *ph, u32 msg_id)
{
int ret = 0;
struct scmi_xfer *t;
ret = ph->xops->xfer_get_init(ph, msg_id, 0, 0, &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_stop_timer(const struct scmi_protocol_handle *ph)
{
return scmi_send_start_stop(ph, MEMLAT_STOP_TIMER);
}
static int scmi_start_timer(const struct scmi_protocol_handle *ph)
{
return scmi_send_start_stop(ph, MEMLAT_START_TIMER);
}
static int scmi_flush_cpucp_log(const struct scmi_protocol_handle *ph)
{
return scmi_send_start_stop(ph, MEMLAT_FLUSH_LOGBUF);
}
static int scmi_set_global_var(const struct scmi_protocol_handle *ph, u32 val, u32 msg_id)
{
int ret = 0;
struct scmi_xfer *t;
u32 *ptr;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(u32), sizeof(u32), &t);
if (ret)
return ret;
ptr = (u32 *)t->tx.buf;
*ptr = cpu_to_le32(val);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_set_log_level(const struct scmi_protocol_handle *ph, u32 val)
{
return scmi_set_global_var(ph, val, MEMLAT_SET_LOG_LEVEL);
}
static int scmi_set_sample_ms(const struct scmi_protocol_handle *ph, u32 val)
{
return scmi_set_global_var(ph, val, MEMLAT_SAMPLE_MS);
}
static int scmi_get_timestamp(const struct scmi_protocol_handle *ph, void *buf)
{
int ret = 0;
struct scmi_xfer *t;
ret = ph->xops->xfer_get_init(ph, MEMLAT_GET_TIMESTAMP, sizeof(u32),
SCMI_MAX_RX_SIZE, &t);
if (ret)
return ret;
ret = ph->xops->do_xfer(ph, t);
if (t->rx.len != sizeof(u64))
return -EMSGSIZE;
memcpy(buf, t->rx.buf, t->rx.len);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_get_freq(const struct scmi_protocol_handle *ph, uint32_t hw_type,
uint32_t mon_idx, void *buf, uint32_t msg_id)
{
int ret = 0;
struct scmi_xfer *t;
struct scalar_param_msg *msg;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg),
SCMI_MAX_RX_SIZE, &t);
if (ret)
return ret;
msg = t->tx.buf;
msg->hw_type = cpu_to_le32(hw_type);
msg->mon_idx = cpu_to_le32(mon_idx);
ret = ph->xops->do_xfer(ph, t);
if (t->rx.len != sizeof(u32))
return -EMSGSIZE;
memcpy(buf, t->rx.buf, t->rx.len);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_get_cur_freq(const struct scmi_protocol_handle *ph, uint32_t hw_type,
uint32_t mon_idx, void *buf)
{
return scmi_get_freq(ph, hw_type, mon_idx, buf, MEMLAT_GET_CUR_FREQ);
}
static int scmi_get_adaptive_cur_freq(const struct scmi_protocol_handle *ph, uint32_t hw_type,
uint32_t mon_idx, void *buf)
{
return scmi_get_freq(ph, hw_type, mon_idx, buf, MEMLAT_GET_ADAPTIVE_CUR_FREQ);
}
static struct scmi_memlat_vendor_ops memlat_proto_ops = {
.set_mem_grp = scmi_set_mem_grp,
.freq_map = scmi_freq_map,
.set_mon = scmi_set_mon,
.set_common_ev_map = scmi_set_common_map,
.set_grp_ev_map = scmi_set_grp_map,
.adaptive_low_freq = scmi_adaptive_low_freq,
.adaptive_high_freq = scmi_adaptive_high_freq,
.get_adaptive_cur_freq = scmi_get_adaptive_cur_freq,
.ipm_ceil = scmi_ipm_ceil,
.fe_stall_floor = scmi_fe_stall_floor,
.be_stall_floor = scmi_be_stall_floor,
.sample_ms = scmi_set_sample_ms,
.wb_filter_ipm = scmi_wb_filter_ipm,
.wb_pct_thres = scmi_wb_pct_thres,
.freq_scale_pct = scmi_freq_scale_pct,
.freq_scale_limit_mhz = scmi_freq_scale_limit_mhz,
.min_freq = scmi_min_freq,
.max_freq = scmi_max_freq,
.get_cur_freq = scmi_get_cur_freq,
.start_timer = scmi_start_timer,
.stop_timer = scmi_stop_timer,
.set_log_level = scmi_set_log_level,
.flush_cpucp_log = scmi_flush_cpucp_log,
.get_timestamp = scmi_get_timestamp,
};
static int scmi_memlat_vendor_protocol_init(const struct scmi_protocol_handle *ph)
{
u32 version;
ph->xops->version_get(ph, &version);
dev_dbg(ph->dev, "memlat version %d.%d\n",
PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
return 0;
}
static const struct scmi_protocol scmi_memlat_vendor = {
.id = SCMI_PROTOCOL_MEMLAT,
.owner = THIS_MODULE,
.instance_init = &scmi_memlat_vendor_protocol_init,
.ops = &memlat_proto_ops,
};
module_scmi_protocol(scmi_memlat_vendor);
MODULE_DESCRIPTION("SCMI memlat vendor Protocol");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/scmi_pmu.h>
#include "common.h"
enum scmi_c1dcvs_protocol_cmd {
SET_PMU_MAP = 11,
SET_ENABLE_TRACE,
SET_ENABLE_CACHING,
};
struct pmu_map_msg {
uint8_t hw_cntrs[MAX_NUM_CPUS][MAX_CPUCP_EVT];
};
static int scmi_send_pmu_map(const struct scmi_protocol_handle *ph,
void *buf, u32 msg_id)
{
int ret, i, j;
struct scmi_xfer *t;
struct pmu_map_msg *msg;
uint8_t *src = buf;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), sizeof(*msg),
&t);
if (ret)
return ret;
msg = t->tx.buf;
for (i = 0; i < MAX_NUM_CPUS; i++)
for (j = 0; j < MAX_CPUCP_EVT; j++)
msg->hw_cntrs[i][j] = *((src + i * MAX_CPUCP_EVT) + j);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_send_tunable_pmu(const struct scmi_protocol_handle *ph,
void *buf, u32 msg_id)
{
int ret;
struct scmi_xfer *t;
unsigned int *msg;
unsigned int *src = buf;
ret = ph->xops->xfer_get_init(ph, msg_id, sizeof(*msg), sizeof(*msg),
&t);
if (ret)
return ret;
msg = t->tx.buf;
*msg = cpu_to_le32(*src);
ret = ph->xops->do_xfer(ph, t);
ph->xops->xfer_put(ph, t);
return ret;
}
static int scmi_pmu_map(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_pmu_map(ph, buf, SET_PMU_MAP);
}
static int scmi_set_enable_trace(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_tunable_pmu(ph, buf, SET_ENABLE_TRACE);
}
static int scmi_set_caching_enable(const struct scmi_protocol_handle *ph, void *buf)
{
return scmi_send_tunable_pmu(ph, buf, SET_ENABLE_CACHING);
}
static struct scmi_pmu_vendor_ops pmu_config_ops = {
.set_pmu_map = scmi_pmu_map,
.set_enable_trace = scmi_set_enable_trace,
.set_cache_enable = scmi_set_caching_enable,
};
static int scmi_pmu_protocol_init(const struct scmi_protocol_handle *ph)
{
u32 version;
ph->xops->version_get(ph, &version);
dev_dbg(ph->dev, "version %d.%d\n",
PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
return 0;
}
static const struct scmi_protocol scmi_pmu = {
.id = SCMI_PMU_PROTOCOL,
.owner = THIS_MODULE,
.instance_init = &scmi_pmu_protocol_init,
.ops = &pmu_config_ops,
};
module_scmi_protocol(scmi_pmu);
MODULE_DESCRIPTION("SCMI PMU vendor Protocol");
MODULE_LICENSE("GPL");

View File

@ -137,6 +137,15 @@ config QCOM_L3_PMU
Adds the L3 cache PMU into the perf events subsystem for
monitoring L3 cache events.
config QCOM_LLCC_PMU
tristate "Qualcomm Technologies LLCC PMU"
depends on ARCH_QCOM && ARM64
help
Provides support for the LLCC performance monitor unit (PMU) in
Qualcomm Technologies processors.
Adds the LLCC PMU into the perf events subsystem for monitoring
LLCC miss events.
config THUNDERX2_PMU
tristate "Cavium ThunderX2 SoC PMU UNCORE"
depends on ARCH_THUNDER2 || COMPILE_TEST

View File

@ -13,6 +13,7 @@ obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o
obj-$(CONFIG_RISCV_PMU) += riscv_pmu.o
obj-$(CONFIG_RISCV_PMU_LEGACY) += riscv_pmu_legacy.o
obj-$(CONFIG_RISCV_PMU_SBI) += riscv_pmu_sbi.o
obj-$(CONFIG_QCOM_LLCC_PMU) += qcom_llcc_pmu.o
obj-$(CONFIG_THUNDERX2_PMU) += thunderx2_pmu.o
obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o
obj-$(CONFIG_ARM_SPE_PMU) += arm_spe_pmu.o

View File

@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/ktime.h>
#include <soc/qcom/qcom_llcc_pmu.h>
enum llcc_pmu_version {
LLCC_PMU_VER1 = 1,
LLCC_PMU_VER2,
};
struct llcc_pmu {
struct pmu pmu;
struct hlist_node node;
void __iomem *lagg_base;
struct perf_event event;
enum llcc_pmu_version ver;
};
#define MON_CFG(m) ((m)->lagg_base + 0x200)
#define MON_CNT(m, cpu) ((m)->lagg_base + 0x220 + 0x4 * cpu)
#define LLCC_RD_EV QCOM_LLCC_PMU_RD_EV
#define ENABLE 0x1
#define CLEAR 0x10
#define CLEAR_POS 16
#define DISABLE 0x0
#define SCALING_FACTOR 0x3
#define NUM_COUNTERS NR_CPUS
#define VALUE_MASK 0xFFFFFF
static u64 llcc_stats[NUM_COUNTERS];
static unsigned int users;
static raw_spinlock_t counter_lock;
static raw_spinlock_t users_lock;
static ktime_t last_read;
static DEFINE_PER_CPU(unsigned int, users_alive);
static struct llcc_pmu *llccpmu;
int qcom_llcc_pmu_hw_type(u32 *type)
{
if (!llccpmu || !llccpmu->pmu.type)
return -EPROBE_DEFER;
*type = llccpmu->pmu.type;
return 0;
}
EXPORT_SYMBOL(qcom_llcc_pmu_hw_type);
static void mon_disable(int cpu)
{
u32 reg;
if (!llccpmu->ver) {
pr_err("LLCCPMU version not correct\n");
return;
}
switch (llccpmu->ver) {
case LLCC_PMU_VER1:
writel_relaxed(DISABLE, MON_CFG(llccpmu));
break;
case LLCC_PMU_VER2:
reg = readl_relaxed(MON_CFG(llccpmu));
reg &= ~(ENABLE << cpu);
writel_relaxed(reg, MON_CFG(llccpmu));
break;
}
}
static void mon_clear(int cpu)
{
int clear_bit = CLEAR_POS + cpu;
u32 reg;
if (!llccpmu->ver) {
pr_err("LLCCPMU version not correct\n");
return;
}
switch (llccpmu->ver) {
case LLCC_PMU_VER1:
writel_relaxed(CLEAR, MON_CFG(llccpmu));
break;
case LLCC_PMU_VER2:
reg = readl_relaxed(MON_CFG(llccpmu));
reg |= (ENABLE << clear_bit);
writel_relaxed(reg, MON_CFG(llccpmu));
reg &= ~(ENABLE << clear_bit);
writel_relaxed(reg, MON_CFG(llccpmu));
break;
}
}
static void mon_enable(int cpu)
{
u32 reg;
if (!llccpmu->ver) {
pr_err("LLCCPMU version not correct\n");
return;
}
switch (llccpmu->ver) {
case LLCC_PMU_VER1:
writel_relaxed(ENABLE, MON_CFG(llccpmu));
break;
case LLCC_PMU_VER2:
reg = readl_relaxed(MON_CFG(llccpmu));
reg |= (ENABLE << cpu);
writel_relaxed(reg, MON_CFG(llccpmu));
break;
}
}
static unsigned long read_cnt(int cpu)
{
unsigned long value;
if (!llccpmu->ver) {
pr_err("LLCCPMU version not correct\n");
return -EINVAL;
}
switch (llccpmu->ver) {
case LLCC_PMU_VER1:
value = readl_relaxed(MON_CNT(llccpmu, cpu));
break;
case LLCC_PMU_VER2:
value = readl_relaxed(MON_CNT(llccpmu, cpu));
break;
}
return value;
}
static int qcom_llcc_event_init(struct perf_event *event)
{
u64 config = event->attr.config;
if (config == LLCC_RD_EV) {
event->hw.config_base = event->attr.config;
return 0;
} else
return -ENOENT;
}
static void qcom_llcc_event_read(struct perf_event *event)
{
int i = 0, cpu = event->cpu;
unsigned long raw, irq_flags;
ktime_t cur;
raw_spin_lock_irqsave(&counter_lock, irq_flags);
if (llccpmu->ver == LLCC_PMU_VER1) {
cur = ktime_get();
if (ktime_ms_delta(cur, last_read) > 1) {
mon_disable(cpu);
for (i = 0; i < NUM_COUNTERS; i++) {
raw = read_cnt(i);
raw &= VALUE_MASK;
llcc_stats[i] += (u64) raw << SCALING_FACTOR;
}
last_read = cur;
mon_clear(cpu);
mon_enable(cpu);
}
} else {
mon_disable(cpu);
raw = read_cnt(cpu);
raw &= VALUE_MASK;
llcc_stats[cpu] += (u64) raw << SCALING_FACTOR;
mon_clear(cpu);
mon_enable(cpu);
}
if (!(event->hw.state & PERF_HES_STOPPED))
local64_set(&event->count, llcc_stats[cpu]);
raw_spin_unlock_irqrestore(&counter_lock, irq_flags);
}
static void qcom_llcc_event_start(struct perf_event *event, int flags)
{
if (flags & PERF_EF_RELOAD)
WARN_ON(!(event->hw.state & PERF_HES_UPTODATE));
event->hw.state = 0;
}
static void qcom_llcc_event_stop(struct perf_event *event, int flags)
{
qcom_llcc_event_read(event);
event->hw.state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
}
static int qcom_llcc_event_add(struct perf_event *event, int flags)
{
unsigned int cpu_users;
raw_spin_lock(&users_lock);
if (llccpmu->ver == LLCC_PMU_VER1) {
if (!users)
mon_enable(event->cpu);
users++;
} else {
cpu_users = per_cpu(users_alive, event->cpu);
if (!cpu_users)
mon_enable(event->cpu);
cpu_users++;
per_cpu(users_alive, event->cpu) = cpu_users;
}
raw_spin_unlock(&users_lock);
event->hw.state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
if (flags & PERF_EF_START)
qcom_llcc_event_start(event, PERF_EF_RELOAD);
return 0;
}
static void qcom_llcc_event_del(struct perf_event *event, int flags)
{
unsigned int cpu_users;
raw_spin_lock(&users_lock);
if (llccpmu->ver == LLCC_PMU_VER1) {
users--;
if (!users)
mon_disable(event->cpu);
} else {
cpu_users = per_cpu(users_alive, event->cpu);
cpu_users--;
if (!cpu_users)
mon_disable(event->cpu);
per_cpu(users_alive, event->cpu) = cpu_users;
}
raw_spin_unlock(&users_lock);
}
static int qcom_llcc_pmu_probe(struct platform_device *pdev)
{
struct resource *res;
int ret;
if (llccpmu) {
dev_err(&pdev->dev, "Only one LLCC PMU allowed!\n");
return -ENODEV;
}
llccpmu = devm_kzalloc(&pdev->dev, sizeof(struct llcc_pmu), GFP_KERNEL);
if (!llccpmu)
return -ENOMEM;
llccpmu->ver = (enum llcc_pmu_version)
of_device_get_match_data(&pdev->dev);
if (!llccpmu->ver) {
pr_err("Unknown device type!\n");
return -ENODEV;
}
llccpmu->pmu = (struct pmu) {
.task_ctx_nr = perf_invalid_context,
.type = 0,
.event_init = qcom_llcc_event_init,
.add = qcom_llcc_event_add,
.del = qcom_llcc_event_del,
.start = qcom_llcc_event_start,
.stop = qcom_llcc_event_stop,
.read = qcom_llcc_event_read,
};
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lagg-base");
llccpmu->lagg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(llccpmu->lagg_base)) {
dev_err(&pdev->dev, "Can't map PMU lagg base: @%pa\n",
&res->start);
return PTR_ERR(llccpmu->lagg_base);
}
raw_spin_lock_init(&counter_lock);
raw_spin_lock_init(&users_lock);
ret = perf_pmu_register(&llccpmu->pmu, "llcc-pmu", -1);
if (ret < 0)
dev_err(&pdev->dev, "Failed to register LLCC PMU (%d)\n", ret);
dev_info(&pdev->dev, "Registered llcc_pmu, type: %d\n",
llccpmu->pmu.type);
return 0;
}
static const struct of_device_id qcom_llcc_pmu_match_table[] = {
{ .compatible = "qcom,llcc-pmu-ver1", .data = (void *) LLCC_PMU_VER1 },
{ .compatible = "qcom,llcc-pmu-ver2", .data = (void *) LLCC_PMU_VER2 },
{}
};
static struct platform_driver qcom_llcc_pmu_driver = {
.driver = {
.name = "qcom-llcc-pmu",
.of_match_table = qcom_llcc_pmu_match_table,
},
.probe = qcom_llcc_pmu_probe,
};
module_platform_driver(qcom_llcc_pmu_driver);
MODULE_DESCRIPTION("QCOM LLCC PMU");
MODULE_LICENSE("GPL");

View File

@ -97,6 +97,27 @@ config QCOM_MSM_IPCC
Say Y here to compile the driver as a part of kernel or M to compile
as a module.
config QCOM_CPUCP
tristate "Qualcomm Technologies, Inc. CPUCP driver"
depends on MAILBOX
help
Qualcomm Technologies, Inc. CPUCP driver for MSM devices. This driver
acts as a mailbox controller to do doorbell between APSS and CPUCP
subsystem. Say yes here to enable rx and tx channel between both
the subsystems.
If unsure, say n.
config QTI_CPUCP_LOG
tristate "Qualcomm Technologies Inc. HW CPUCP Logging"
depends on QCOM_CPUCP
default n
help
CPUCP logging driver, this driver has the infra to collect logs
generated in CPUCP HW and log the buffers.
This driver register with IPC_Logging framework, to have dedicated
buffer for cpucp hw device.
config QCOM_LLCC
tristate "Qualcomm Technologies, Inc. LLCC driver"
depends on ARCH_QCOM || COMPILE_TEST
@ -402,6 +423,8 @@ config MINIDUMP_ALL_TASK_INFO
collection during panic. This can provide more
debug info.
source "drivers/soc/qcom/dcvs/Kconfig"
config QCOM_LOGBUF_VENDOR_HOOKS
tristate "QTI Logbuf Vendor Hooks Support"
depends on ARCH_QCOM && ANDROID_VENDOR_HOOKS

View File

@ -60,3 +60,6 @@ obj-$(CONFIG_QCOM_HUNG_TASK_ENH) += hung_task_enh.o
obj-$(CONFIG_GH_TLMM_VM_MEM_ACCESS) += gh_tlmm_vm_mem_access.o
obj-$(CONFIG_QCOM_RAMDUMP) += qcom_ramdump.o
obj-$(CONFIG_QCOM_ICC_BWMON) += icc-bwmon.o
obj-$(CONFIG_QCOM_DCVS) += dcvs/
obj-$(CONFIG_QCOM_CPUCP) += qcom_cpucp.o
obj-$(CONFIG_QTI_CPUCP_LOG) += cpucp_log.o

View File

@ -0,0 +1,351 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. 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 MAX_PRINT_SIZE 1024
#define MAX_BUF_NUM 4
#define MAX_RESIDUAL_SIZE MAX_PRINT_SIZE
#define SIZE_ADJUST 4
#define SRC_OFFSET 4
#define CREATE_TRACE_POINTS
#include "trace_cpucp.h"
struct remote_mem {
void __iomem *start;
unsigned long long size;
};
struct cpucp_buf {
struct list_head node;
char *buf;
u32 size;
u32 cpy_idx;
};
struct cpucp_log_info {
struct remote_mem *rmem;
struct mbox_client cl;
struct mbox_chan *ch;
struct delayed_work work;
struct device *dev;
void __iomem *base;
unsigned int rmem_idx;
unsigned int num_bufs;
unsigned int total_buf_size;
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 *cpucp_wq;
static inline bool get_last_newline(char *buf, int size, int *cnt)
{
int i;
for (i = (size - 1); i >= 0 ; i--) {
if (buf[i] == '\n') {
buf[i] = '\0';
*cnt = i + 1;
return true;
}
}
*cnt = size;
return false;
}
static void cpucp_log_work(struct work_struct *work)
{
struct cpucp_log_info *info = container_of(work,
struct cpucp_log_info,
work.work);
char *src;
int buf_start = 0;
int cnt = 0, print_size = 0, buf_size = 0;
bool ret;
char tmp_buf[MAX_PRINT_SIZE + 1];
struct cpucp_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 cpucp_buf, node);
list_del(&buf_node->node);
spin_unlock_irqrestore(&info->full_list_lock, flags);
buf_start = buf_node->cpy_idx - info->rem_len;
src = &buf_node->buf[buf_start];
buf_size = buf_node->size + info->rem_len;
if (info->rem_len) {
memcpy(&buf_node->buf[buf_start],
info->rem_buf, info->rem_len);
info->rem_len = 0;
}
do {
print_size = (buf_size >= MAX_PRINT_SIZE) ?
MAX_PRINT_SIZE : buf_size;
ret = get_last_newline(src, print_size, &cnt);
if (cnt == print_size) {
if (!ret && buf_size < MAX_PRINT_SIZE) {
info->rem_len = buf_size;
memcpy(info->rem_buf, src, buf_size);
goto out;
} else {
snprintf(tmp_buf, print_size + 1, "%s", src);
trace_cpucp_log(tmp_buf);
}
} else
trace_cpucp_log(src);
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 cpucp_buf *get_free_buffer(struct cpucp_log_info *info)
{
struct cpucp_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 cpucp_buf, node);
list_del(&buf_node->node);
spin_unlock_irqrestore(&info->free_list_lock, flags);
return buf_node;
}
static void cpucp_log_rx(struct mbox_client *client, void *msg)
{
struct cpucp_log_info *info = dev_get_drvdata(client->dev);
struct device *dev = info->dev;
struct cpucp_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;
}
marker = *(u32 *)(info->rmem)->start;
if (marker <= info->rmem->size) {
info->rmem_idx = 0;
rmem_size = marker;
} else if (marker <= info->total_buf_size) {
info->rmem_idx = 1;
rmem_size = marker - info->rmem->size;
} else {
pr_err("%s: Log marker incorrect: %u\n", __func__, marker);
return;
}
if (info->rmem_idx == 0) {
size_adj = SIZE_ADJUST;
src_offset = SRC_OFFSET;
}
rmem = info->rmem + info->rmem_idx;
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(cpucp_wq, &info->work, 0);
}
static int populate_free_buffers(struct cpucp_log_info *info,
int rmem_size)
{
int i = 0;
struct cpucp_buf *buf_nodes;
buf_nodes = devm_kzalloc(info->dev,
MAX_BUF_NUM * sizeof(struct cpucp_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 cpucp_log_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cpucp_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);
dev_err(dev, "Failed to io remap the region err: %d\n", ret);
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->total_buf_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;
cl = &info->cl;
cl->dev = dev;
cl->tx_block = false;
cl->knows_txdone = true;
cl->rx_callback = cpucp_log_rx;
dev_set_drvdata(dev, info);
INIT_DEFERRABLE_WORK(&info->work, &cpucp_log_work);
spin_lock_init(&info->free_list_lock);
spin_lock_init(&info->full_list_lock);
cpucp_wq = create_freezable_workqueue("cpucp_wq");
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;
}
dev_dbg(dev, "CPUCP logging initialized\n");
return 0;
exit:
kfree(info->rmem);
return ret;
}
static int cpucp_log_remove(struct platform_device *pdev)
{
struct cpucp_log_info *info;
info = dev_get_drvdata(&pdev->dev);
mbox_free_channel(info->ch);
return 0;
}
static const struct of_device_id cpucp_log[] = {
{.compatible = "qcom,cpucp-log"},
{},
};
static struct platform_driver cpucp_log_driver = {
.driver = {
.name = "cpucp-log",
.of_match_table = cpucp_log,
},
.probe = cpucp_log_probe,
.remove = cpucp_log_remove,
};
builtin_platform_driver(cpucp_log_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,91 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# QCOM DCVS drivers
#
config QCOM_PMU_LIB
tristate "QCOM PMU Driver"
depends on ARCH_QCOM
default n
help
This enables the QCOM PMU driver which manages and maintains
per-CPU performance counters that are available on some
Qualcomm Technologies, Inc. (QTI) chipsets. This includes
preserving them across CPU idle and hotplug events.
config QCOM_DCVS_FP
tristate "Enable QCOM DCVS Fast Path Interface"
depends on ARCH_QCOM && QCOM_RPMH
default n
help
This enables the QCOM DCVS FP (Fast Path) driver which relies on
certain "fast path" APIs from QCOM_RPMH to vote for various DCVS HW
blocks in a faster scheme than standard voting paths. This requires a
Qualcomm Technologies, Inc. (QTI) chipset that supports this feature.
config QCOM_DCVS
tristate "QCOM DCVS Driver"
select INTERCONNECT
default n
help
This enables the main QCOM DCVS framework which supports various
DCVS HW blocks and voting interfaces that are supported on some
Qualcomm Technologies, Inc. (QTI) chipsets. This includes voting
interfaces from both QCOM_DCVS_FP and INTERCONNECT.
config QCOM_MEMLAT
tristate "QCOM Memlat Driver"
depends on QCOM_DCVS && QCOM_PMU_LIB
default n
help
This enables the QCOM Memlat driver which monitors CPU performance
counters to identify memory latency bound workloads and vote for
DCVS HW (memory) frequencies through the QCOM DCVS framework. This
driver also configures epss memlat if enabled.
config QCOM_BWMON
tristate "QCOM BWMON Driver"
depends on QCOM_DCVS
default n
help
This enables the QCOM BWMON driver which monitors bandwidth counters
to identify memory bandwidth bound workloads and vote for DCVS HW
(memory) frequencies through the QCOM DCVS framework. This driver
handles voting for DDR and LLCC.
config QTI_HW_MEMLAT_SCMI_CLIENT
tristate "Qualcomm Technologies Inc. SCMI client driver for HW MEMLAT"
depends on QCOM_MEMLAT && QTI_SCMI_MEMLAT_PROTOCOL
default n
help
SCMI client driver registers itself with SCMI framework for memlat
vendor protocol, and also registers with the memlat interface
driver.
This driver delivers the memlat vendor protocol handle to interface
driver, and interface driver will use this handle to communicate with
memlat HW.
config QTI_PMU_SCMI_CLIENT
tristate "Qualcomm Technologies Inc. SCMI client driver for PMU"
depends on QTI_SCMI_PMU_PROTOCOL && QCOM_PMU_LIB
default n
help
SCMI client driver registers itself with SCMI framework for pmu
vendor protocol.
This driver delivers the pmu vendor protocol handle to interface
driver, and interface driver will use this handle to communicate with
rimps.
config QTI_C1DCVS_SCMI_CLIENT
tristate "Qualcomm Technologies Inc. SCMI client driver for cpudcvs"
depends on QTI_SCMI_C1DCVS_PROTOCOL
default n
help
SCMI client driver registers itself with SCMI framework for c1dcvs
vendor protocol.
This driver delivers the cpudcvs protocol handle to interface
driver, and interface driver will use this handle to communicate with
cpucp.

View File

@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_QCOM_PMU_LIB) += qcom-pmu-lib.o
qcom-pmu-lib-y := pmu_lib.o
obj-$(CONFIG_QCOM_DCVS_FP) += dcvs_fp.o
obj-$(CONFIG_QCOM_DCVS) += qcom-dcvs.o
qcom-dcvs-y := dcvs.o dcvs_icc.o dcvs_epss.o trace-dcvs.o
obj-$(CONFIG_QCOM_MEMLAT) += memlat.o
obj-$(CONFIG_QCOM_BWMON) += bwmon.o
obj-$(CONFIG_QTI_PMU_SCMI_CLIENT) += pmu_scmi.o
obj-$(CONFIG_QTI_C1DCVS_SCMI_CLIENT) += c1dcvs_scmi.o
obj-$(CONFIG_QTI_HW_MEMLAT_SCMI_CLIENT) += memlat_scmi.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,194 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2014-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _QCOM_BWMON_H
#define _QCOM_BWMON_H
#include <linux/kernel.h>
#include <soc/qcom/dcvs.h>
#define NUM_MBPS_ZONES 10
#define UP_WAKE 1
#define DOWN_WAKE 2
#define MBYTE (1ULL << 20)
#define MBPS_TO_KHZ(mbps, w) (mult_frac(mbps, MBYTE, w * 1000ULL))
#define KHZ_TO_MBPS(khz, w) (mult_frac(w * 1000ULL, khz, MBYTE))
#define to_bwmon(ptr) container_of(ptr, struct bwmon, hw)
enum mon_reg_type {
MON1,
MON2,
MON3,
};
struct bwmon_spec {
bool wrap_on_thres;
bool overflow;
bool throt_adj;
bool hw_sampling;
bool has_global_base;
enum mon_reg_type reg_type;
};
/**
* struct bw_hwmon - dev BW HW monitor info
* @start_hwmon: Start the HW monitoring of the dev BW
* @stop_hwmon: Stop the HW monitoring of dev BW
* @set_thres: Set the count threshold to generate an IRQ
* @set_hw_events: Set hw settings for up/down wake events
* @get_bytes_and_clear: Get the bytes transferred since the last call
* and reset the counter to start over.
* @set_throttle_adj: Set throttle adjust field to the given value
* @get_throttle_adj: Get the value written to throttle adjust field
* @dev: Pointer to device tied to this HW monitor
* @dcvs_hw: DCVS HW type that this HW is monitoring for
* @dcvs_path: DCVS Path type that this monitor votes on
* @node: Pointer to hwmon node that contains tunables
* @last_update_ts: Time that the last bwmon work was queued
* @work: bwmon monitor work
* @is_active: Toggled when HW monitor is started/stopped
* @up_wake_mbps: Setting for HW monitor to send IRQ for up wake
* @down_wake_mbps: Setting for HW monitor to send IRQ fow down wake
* @down_cnt: Setting for down sample count needed for wake
*/
struct bw_hwmon {
int (*start_hwmon)(struct bw_hwmon *hw,
unsigned long mbps);
void (*stop_hwmon)(struct bw_hwmon *hw);
unsigned long (*set_thres)(struct bw_hwmon *hw,
unsigned long bytes);
unsigned long (*set_hw_events)(struct bw_hwmon *hw,
unsigned int sample_ms);
unsigned long (*get_bytes_and_clear)(struct bw_hwmon *hw);
int (*set_throttle_adj)(struct bw_hwmon *hw,
uint adj);
u32 (*get_throttle_adj)(struct bw_hwmon *hw);
struct device *dev;
enum dcvs_hw_type dcvs_hw;
enum dcvs_path_type dcvs_path;
u32 dcvs_width;
struct hwmon_node *node;
ktime_t last_update_ts;
struct work_struct work;
bool is_active;
unsigned long up_wake_mbps;
unsigned long down_wake_mbps;
unsigned int down_cnt;
};
struct bwmon {
void __iomem *base;
void __iomem *global_base;
unsigned int mport;
int irq;
const struct bwmon_spec *spec;
struct device *dev;
struct bw_hwmon hw;
u32 hw_timer_hz;
u32 throttle_adj;
u32 sample_size_ms;
u32 intr_status;
u8 count_shift;
u32 thres_lim;
u32 byte_mask;
u32 byte_match;
};
struct hwmon_node {
u32 hw_min_freq;
u32 hw_max_freq;
u32 min_freq;
u32 max_freq;
struct dcvs_freq cur_freq;
u32 window_ms;
unsigned int guard_band_mbps;
unsigned int decay_rate;
unsigned int io_percent;
unsigned int bw_step;
unsigned int sample_ms;
unsigned int up_scale;
unsigned int up_thres;
unsigned int down_thres;
unsigned int down_count;
unsigned int hist_memory;
unsigned int hyst_trigger_count;
unsigned int hyst_length;
unsigned int idle_mbps;
unsigned int mbps_zones[NUM_MBPS_ZONES];
unsigned long prev_ab;
unsigned long bytes;
unsigned long max_mbps;
unsigned long hist_max_mbps;
unsigned long hist_mem;
unsigned long hyst_peak;
unsigned long hyst_mbps;
unsigned long hyst_trig_win;
unsigned long hyst_en;
unsigned long prev_req;
unsigned int wake;
unsigned int down_cnt;
ktime_t prev_ts;
ktime_t hist_max_ts;
bool sampled;
bool mon_started;
struct list_head list;
struct bw_hwmon *hw;
struct kobject kobj;
struct mutex mon_lock;
struct mutex update_lock;
};
/* BWMON register offsets */
#define GLB_INT_STATUS(m) ((m)->global_base + 0x100)
#define GLB_INT_CLR(m) ((m)->global_base + 0x108)
#define GLB_INT_EN(m) ((m)->global_base + 0x10C)
#define MON_INT_STATUS(m) ((m)->base + 0x100)
#define MON_INT_STATUS_MASK 0x03
#define MON2_INT_STATUS_MASK 0xF0
#define MON2_INT_STATUS_SHIFT 4
#define MON_INT_CLR(m) ((m)->base + 0x108)
#define MON_INT_EN(m) ((m)->base + 0x10C)
#define MON_INT_ENABLE 0x1
#define MON_EN(m) ((m)->base + 0x280)
#define MON_CLEAR(m) ((m)->base + 0x284)
#define MON_CNT(m) ((m)->base + 0x288)
#define MON_THRES(m) ((m)->base + 0x290)
#define MON_MASK(m) ((m)->base + 0x298)
#define MON_MATCH(m) ((m)->base + 0x29C)
#define MON2_EN(m) ((m)->base + 0x2A0)
#define MON2_CLEAR(m) ((m)->base + 0x2A4)
#define MON2_SW(m) ((m)->base + 0x2A8)
#define MON2_THRES_HI(m) ((m)->base + 0x2AC)
#define MON2_THRES_MED(m) ((m)->base + 0x2B0)
#define MON2_THRES_LO(m) ((m)->base + 0x2B4)
#define MON2_ZONE_ACTIONS(m) ((m)->base + 0x2B8)
#define MON2_ZONE_CNT_THRES(m) ((m)->base + 0x2BC)
#define MON2_BYTE_CNT(m) ((m)->base + 0x2D0)
#define MON2_WIN_TIMER(m) ((m)->base + 0x2D4)
#define MON2_ZONE_CNT(m) ((m)->base + 0x2D8)
#define MON2_ZONE_MAX(m, zone) ((m)->base + 0x2E0 + 0x4 * zone)
#define MON3_INT_STATUS(m) ((m)->base + 0x00)
#define MON3_INT_CLR(m) ((m)->base + 0x08)
#define MON3_INT_EN(m) ((m)->base + 0x0C)
#define MON3_INT_STATUS_MASK 0x0F
#define MON3_EN(m) ((m)->base + 0x10)
#define MON3_CLEAR(m) ((m)->base + 0x14)
#define MON3_MASK(m) ((m)->base + 0x18)
#define MON3_MATCH(m) ((m)->base + 0x1C)
#define MON3_SW(m) ((m)->base + 0x20)
#define MON3_THRES_HI(m) ((m)->base + 0x24)
#define MON3_THRES_MED(m) ((m)->base + 0x28)
#define MON3_THRES_LO(m) ((m)->base + 0x2C)
#define MON3_ZONE_ACTIONS(m) ((m)->base + 0x30)
#define MON3_ZONE_CNT_THRES(m) ((m)->base + 0x34)
#define MON3_BYTE_CNT(m) ((m)->base + 0x38)
#define MON3_WIN_TIMER(m) ((m)->base + 0x3C)
#define MON3_ZONE_CNT(m) ((m)->base + 0x40)
#define MON3_ZONE_MAX(m, zone) ((m)->base + 0x44 + 0x4 * zone)
#endif /* _QCOM_BWMON_H */

View File

@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/scmi_protocol.h>
#include <linux/scmi_c1dcvs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/scmi_c1dcvs.h>
#include <linux/slab.h>
static struct kobject c1dcvs_kobj;
static struct scmi_protocol_handle *ph;
static const struct scmi_c1dcvs_vendor_ops *ops;
static unsigned int user_c1dcvs_en;
static unsigned int kernel_c1dcvs_en;
static DEFINE_MUTEX(c1dcvs_lock);
struct qcom_c1dcvs_attr {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count);
};
#define to_c1dcvs_attr(_attr) \
container_of(_attr, struct qcom_c1dcvs_attr, attr)
#define C1DCVS_ATTR_RW(_name) \
static struct qcom_c1dcvs_attr _name = \
__ATTR(_name, 0644, show_##_name, store_##_name) \
#define store_c1dcvs_attr(name) \
static ssize_t store_##name(struct kobject *kobj, \
struct attribute *attr, const char *buf,\
size_t count) \
{ \
unsigned int var; \
int ret; \
\
if (!ops) \
return -ENODEV; \
\
ret = kstrtouint(buf, 10, &var); \
if (ret < 0) \
return ret; \
\
ret = ops->set_##name(ph, &var); \
return ((ret < 0) ? ret : count); \
} \
#define show_c1dcvs_attr(name) \
static ssize_t show_##name(struct kobject *kobj, \
struct attribute *attr, char *buf) \
{ \
unsigned int var; \
int ret; \
\
if (!ops) \
return -ENODEV; \
\
ret = ops->get_##name(ph, &var); \
if (ret < 0) \
return ret; \
\
return scnprintf(buf, PAGE_SIZE, "%lu\n", le32_to_cpu(var)); \
} \
/*
* Must hold c1dcvs_lock before calling this function
*/
static int update_enable_c1dcvs(void)
{
unsigned int enable = min(user_c1dcvs_en, kernel_c1dcvs_en);
if (!ops)
return -ENODEV;
return ops->set_enable_c1dcvs(ph, &enable);
}
static ssize_t store_enable_c1dcvs(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
unsigned int var;
int ret;
if (!ops)
return -ENODEV;
ret = kstrtouint(buf, 10, &var);
if (ret < 0)
return ret;
mutex_lock(&c1dcvs_lock);
user_c1dcvs_en = var;
ret = update_enable_c1dcvs();
mutex_unlock(&c1dcvs_lock);
return ((ret < 0) ? ret : count);
}
int c1dcvs_enable(bool enable)
{
unsigned int data = enable ? 1 : 0;
int ret;
if (!ops)
return -EPROBE_DEFER;
mutex_lock(&c1dcvs_lock);
kernel_c1dcvs_en = data;
ret = update_enable_c1dcvs();
mutex_unlock(&c1dcvs_lock);
return ret;
}
EXPORT_SYMBOL(c1dcvs_enable);
store_c1dcvs_attr(enable_trace);
show_c1dcvs_attr(enable_trace);
C1DCVS_ATTR_RW(enable_trace);
store_c1dcvs_attr(hysteresis);
show_c1dcvs_attr(hysteresis);
C1DCVS_ATTR_RW(hysteresis);
show_c1dcvs_attr(enable_c1dcvs);
C1DCVS_ATTR_RW(enable_c1dcvs);
#define store_c1dcvs_thresh(name) \
static ssize_t store_##name(struct kobject *kobj, \
struct attribute *attr, const char *buf,\
size_t count) \
{ \
int ret, i = 0; \
char *s = kstrdup(buf, GFP_KERNEL); \
unsigned int msg[2]; \
char *str; \
\
if (!ops) \
return -ENODEV; \
\
while (((str = strsep(&s, " ")) != NULL) && i < 2) { \
ret = kstrtouint(str, 10, &msg[i]); \
if (ret < 0) { \
pr_err("Invalid value :%d\n", ret); \
return -EINVAL; \
} \
i++; \
} \
\
pr_info("Input threshold :%lu for cluster :%lu\n", msg[1], msg[0]);\
ret = ops->set_##name(ph, msg); \
return ((ret < 0) ? ret : count); \
} \
#define show_c1dcvs_thresh(name) \
static ssize_t show_##name(struct kobject *kobj, \
struct attribute *attr, char *buf) \
{ \
unsigned int *vars = NULL; \
int i, ret, tot = 0; \
\
if (!ops) \
return -ENODEV; \
\
vars = kcalloc(num_possible_cpus(), sizeof(unsigned int), GFP_KERNEL);\
if (!vars) \
return -ENOMEM; \
\
ret = ops->get_##name(ph, vars); \
if (ret < 0) { \
kfree(vars); \
return ret; \
} \
\
for (i = 0; i < num_possible_cpus(); i++) { \
vars[i] = le32_to_cpu(vars[i]); \
if (!vars[i]) \
break; \
tot += scnprintf(buf + tot, PAGE_SIZE - tot, "%lu\t", vars[i]);\
} \
tot += scnprintf(buf + tot, PAGE_SIZE - tot, "\n"); \
\
kfree(vars); \
return tot; \
} \
store_c1dcvs_thresh(ipc_thresh);
show_c1dcvs_thresh(ipc_thresh);
C1DCVS_ATTR_RW(ipc_thresh);
store_c1dcvs_thresh(efreq_thresh);
show_c1dcvs_thresh(efreq_thresh);
C1DCVS_ATTR_RW(efreq_thresh);
static struct attribute *c1dcvs_settings_attrs[] = {
&enable_c1dcvs.attr,
&enable_trace.attr,
&ipc_thresh.attr,
&efreq_thresh.attr,
&hysteresis.attr,
NULL,
};
ATTRIBUTE_GROUPS(c1dcvs_settings);
static ssize_t attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct qcom_c1dcvs_attr *c1dcvs_attr = to_c1dcvs_attr(attr);
ssize_t ret = -EIO;
if (c1dcvs_attr->show)
ret = c1dcvs_attr->show(kobj, attr, buf);
return ret;
}
static ssize_t attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct qcom_c1dcvs_attr *c1dcvs_attr = to_c1dcvs_attr(attr);
ssize_t ret = -EIO;
if (c1dcvs_attr->store)
ret = c1dcvs_attr->store(kobj, attr, buf, count);
return ret;
}
static const struct sysfs_ops c1dcvs_sysfs_ops = {
.show = attr_show,
.store = attr_store,
};
static struct kobj_type c1dcvs_settings_ktype = {
.sysfs_ops = &c1dcvs_sysfs_ops,
.default_groups = c1dcvs_settings_groups,
};
static int scmi_c1dcvs_probe(struct scmi_device *sdev)
{
int ret;
if (!sdev)
return -ENODEV;
ops = sdev->handle->devm_protocol_get(sdev, SCMI_C1DCVS_PROTOCOL, &ph);
if (!ops)
return -ENODEV;
ret = kobject_init_and_add(&c1dcvs_kobj, &c1dcvs_settings_ktype,
&cpu_subsys.dev_root->kobj, "c1dcvs");
if (ret < 0) {
pr_err("failed to init c1 dcvs kobj: %d\n", ret);
kobject_put(&c1dcvs_kobj);
}
user_c1dcvs_en = kernel_c1dcvs_en = 1;
return 0;
}
static const struct scmi_device_id scmi_id_table[] = {
{ .protocol_id = SCMI_C1DCVS_PROTOCOL, .name = "scmi_c1dcvs_protocol" },
{ },
};
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
static struct scmi_driver scmi_c1dcvs_drv = {
.name = "scmi-c1dcvs-driver",
.probe = scmi_c1dcvs_probe,
.id_table = scmi_id_table,
};
module_scmi_driver(scmi_c1dcvs_drv);
MODULE_SOFTDEP("pre: c1dcvs_vendor");
MODULE_DESCRIPTION("ARM SCMI C1DCVS driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,902 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "qcom-dcvs: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <soc/qcom/dcvs.h>
#include <soc/qcom/of_common.h>
#include "dcvs_private.h"
#include "trace-dcvs.h"
static const char * const dcvs_hw_names[NUM_DCVS_HW_TYPES] = {
[DCVS_DDR] = "DDR",
[DCVS_LLCC] = "LLCC",
[DCVS_L3] = "L3",
[DCVS_DDRQOS] = "DDRQOS",
};
enum dcvs_type {
QCOM_DCVS_DEV,
QCOM_DCVS_HW,
QCOM_DCVS_PATH,
NUM_DCVS_TYPES
};
struct qcom_dcvs_spec {
enum dcvs_type type;
};
struct dcvs_voter {
const char *name;
struct dcvs_freq freq;
struct list_head node;
};
struct qcom_dcvs_data {
struct kobject kobj;
struct dcvs_hw *hw_devs[NUM_DCVS_HW_TYPES];
u32 num_hw;
u32 num_inited_hw;
bool inited;
};
static struct qcom_dcvs_data *dcvs_data;
static u32 get_target_freq(struct dcvs_path *path, u32 freq);
struct qcom_dcvs_attr {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count);
};
#define to_qcom_dcvs_attr(_attr) \
container_of(_attr, struct qcom_dcvs_attr, attr)
#define to_dcvs_hw(k) container_of(k, struct dcvs_hw, kobj)
#define DCVS_ATTR_RW(_name) \
static struct qcom_dcvs_attr _name = \
__ATTR(_name, 0644, show_##_name, store_##_name) \
#define DCVS_ATTR_RO(_name) \
static struct qcom_dcvs_attr _name = \
__ATTR(_name, 0444, show_##_name, NULL) \
#define show_attr(name) \
static ssize_t show_##name(struct kobject *kobj, \
struct attribute *attr, char *buf) \
{ \
struct dcvs_hw *hw = to_dcvs_hw(kobj); \
return scnprintf(buf, PAGE_SIZE, "%u\n", hw->name); \
} \
#define store_attr(name, _min, _max) \
static ssize_t store_##name(struct kobject *kobj, \
struct attribute *attr, const char *buf, \
size_t count) \
{ \
int ret; \
unsigned int val; \
struct dcvs_hw *hw = to_dcvs_hw(kobj); \
ret = kstrtouint(buf, 10, &val); \
if (ret < 0) \
return ret; \
val = max(val, _min); \
val = min(val, _max); \
hw->name = val; \
return count; \
} \
static ssize_t store_boost_freq(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
int ret;
unsigned int val;
struct dcvs_hw *hw = to_dcvs_hw(kobj);
struct dcvs_path *path;
struct dcvs_voter *voter;
struct dcvs_freq new_freq;
ret = kstrtouint(buf, 10, &val);
if (ret < 0)
return ret;
if (val > hw->hw_max_freq)
return -EINVAL;
/* boost_freq only supported on hw with slow path */
path = hw->dcvs_paths[DCVS_SLOW_PATH];
if (!path)
return -EPERM;
val = max(val, hw->hw_min_freq);
hw->boost_freq = val;
/* must re-aggregate votes to get new freq after boost update */
mutex_lock(&path->voter_lock);
new_freq.ib = new_freq.ab = 0;
new_freq.hw_type = hw->type;
list_for_each_entry(voter, &path->voter_list, node) {
new_freq.ib = max(voter->freq.ib, new_freq.ib);
new_freq.ab += voter->freq.ab;
}
new_freq.ib = get_target_freq(path, new_freq.ib);
if (new_freq.ib != path->cur_freq.ib) {
ret = path->commit_dcvs_freqs(path, &new_freq, 1);
if (ret < 0)
pr_err("Error setting boost freq: %d\n", ret);
}
mutex_unlock(&path->voter_lock);
trace_qcom_dcvs_boost(hw->type, path->type, hw->boost_freq,
new_freq.ib, new_freq.ab);
return count;
}
static ssize_t show_cur_freq(struct kobject *kobj,
struct attribute *attr, char *buf)
{
int i, cpu;
struct dcvs_hw *hw = to_dcvs_hw(kobj);
struct dcvs_path *path;
u32 cur_freq = 0;
for (i = 0; i < NUM_DCVS_PATHS; i++) {
path = hw->dcvs_paths[i];
if (!path)
continue;
if (path->type != DCVS_PERCPU_PATH) {
cur_freq = max(cur_freq, path->cur_freq.ib);
continue;
}
for_each_possible_cpu(cpu)
cur_freq = max(cur_freq, path->percpu_cur_freqs[cpu]);
}
return scnprintf(buf, PAGE_SIZE, "%lu\n", cur_freq);
}
static ssize_t show_available_frequencies(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct dcvs_hw *hw = to_dcvs_hw(kobj);
int i, cnt = 0;
for (i = 0; i < hw->table_len; i++)
cnt += scnprintf(buf + cnt, PAGE_SIZE - cnt, "%lu ",
hw->freq_table[i]);
if (cnt)
cnt--;
cnt += scnprintf(buf + cnt, PAGE_SIZE - cnt, "\n");
return cnt;
}
show_attr(hw_min_freq);
show_attr(hw_max_freq);
show_attr(boost_freq);
DCVS_ATTR_RO(hw_min_freq);
DCVS_ATTR_RO(hw_max_freq);
DCVS_ATTR_RW(boost_freq);
DCVS_ATTR_RO(cur_freq);
DCVS_ATTR_RO(available_frequencies);
static struct attribute *dcvs_hw_attrs[] = {
&hw_min_freq.attr,
&hw_max_freq.attr,
&boost_freq.attr,
&cur_freq.attr,
&available_frequencies.attr,
NULL,
};
ATTRIBUTE_GROUPS(dcvs_hw);
static ssize_t attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct qcom_dcvs_attr *dcvs_attr = to_qcom_dcvs_attr(attr);
ssize_t ret = -EIO;
if (dcvs_attr->show)
ret = dcvs_attr->show(kobj, attr, buf);
return ret;
}
static ssize_t attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct qcom_dcvs_attr *dcvs_attr = to_qcom_dcvs_attr(attr);
ssize_t ret = -EIO;
if (dcvs_attr->store)
ret = dcvs_attr->store(kobj, attr, buf, count);
return ret;
}
static const struct sysfs_ops qcom_dcvs_sysfs_ops = {
.show = attr_show,
.store = attr_store,
};
static struct kobj_type qcom_dcvs_ktype = {
.sysfs_ops = &qcom_dcvs_sysfs_ops,
};
static struct kobj_type dcvs_hw_ktype = {
.sysfs_ops = &qcom_dcvs_sysfs_ops,
.default_groups = dcvs_hw_groups,
};
static inline struct dcvs_path *get_dcvs_path(enum dcvs_hw_type hw,
enum dcvs_path_type path)
{
if (dcvs_data && dcvs_data->hw_devs[hw] &&
dcvs_data->hw_devs[hw]->dcvs_paths[path])
return dcvs_data->hw_devs[hw]->dcvs_paths[path];
return NULL;
}
static u32 get_target_freq(struct dcvs_path *path, u32 freq)
{
struct dcvs_hw *hw = path->hw;
u32 *freq_table = hw->freq_table;
u32 len = hw->table_len;
u32 target_freq = 0;
int i;
if (path->type == DCVS_SLOW_PATH)
freq = max(freq, hw->boost_freq);
for (i = 0; i < len; i++) {
if (freq <= freq_table[i]) {
target_freq = freq_table[i];
break;
}
}
if (i == len)
target_freq = freq_table[len-1];
return target_freq;
}
/* slow path update: does aggregation across multiple clients */
static int qcom_dcvs_sp_update(const char *name, struct dcvs_freq *votes,
u32 update_mask)
{
int i, ret = 0;
bool found = false;
struct dcvs_voter *voter;
struct dcvs_path *path;
struct dcvs_freq new_freq;
enum dcvs_hw_type hw_type;
if (!name || !votes || !update_mask)
return -EINVAL;
for (i = 0; i < NUM_DCVS_HW_TYPES; i++) {
if (!(update_mask & BIT(i)))
continue;
hw_type = votes[i].hw_type;
path = get_dcvs_path(hw_type, DCVS_SLOW_PATH);
if (!path)
return -EINVAL;
mutex_lock(&path->voter_lock);
new_freq.ib = new_freq.ab = 0;
new_freq.hw_type = hw_type;
found = false;
list_for_each_entry(voter, &path->voter_list, node) {
if (!strcmp(voter->name, name)) {
found = true;
voter->freq.ib = votes[i].ib;
voter->freq.ab = votes[i].ab;
}
new_freq.ib = max(voter->freq.ib, new_freq.ib);
new_freq.ab += voter->freq.ab;
}
if (!found) {
mutex_unlock(&path->voter_lock);
return -EINVAL;
}
new_freq.ib = get_target_freq(path, new_freq.ib);
if (new_freq.ib != path->cur_freq.ib ||
new_freq.ab != path->cur_freq.ab)
ret = path->commit_dcvs_freqs(path, &new_freq, 1);
mutex_unlock(&path->voter_lock);
if (ret < 0)
return ret;
trace_qcom_dcvs_update(name, hw_type, path->type, votes[i].ib,
new_freq.ib, votes[i].ab, new_freq.ab,
path->hw->boost_freq);
}
return ret;
}
/* fast path update: only single client allowed so lockless */
static int qcom_dcvs_fp_update(const char *name, struct dcvs_freq *votes,
u32 update_mask)
{
int i, ret = 0;
u32 commit_mask = 0;
struct dcvs_voter *voter;
struct dcvs_path *path;
struct dcvs_freq new_freqs[NUM_DCVS_HW_TYPES];
enum dcvs_hw_type hw_type;
if (!name || !votes || !update_mask)
return -EINVAL;
for (i = 0; i < NUM_DCVS_HW_TYPES; i++) {
if (!(update_mask & BIT(i)))
continue;
hw_type = votes[i].hw_type;
path = get_dcvs_path(hw_type, DCVS_FAST_PATH);
/* fast path requires votes be passed in correct order */
if (!path || i != hw_type)
return -EINVAL;
/* should match one and only voter in list */
voter = list_first_entry(&path->voter_list, struct dcvs_voter,
node);
if (!voter || strcmp(voter->name, name))
return -EINVAL;
/* no aggregation required since only single client allowed */
new_freqs[i].ib = get_target_freq(path, votes[i].ib);
if (new_freqs[i].ib != path->cur_freq.ib)
commit_mask |= BIT(i);
trace_qcom_dcvs_update(name, hw_type, path->type, votes[i].ib,
new_freqs[i].ib, 0, 0, 0);
}
if (commit_mask)
ret = path->commit_dcvs_freqs(path, new_freqs, commit_mask);
return ret;
}
/*
* percpu path update: only single client per cpu allowed so lockless.
* Also note that client is responsible for disabling preemption
*/
static int qcom_dcvs_percpu_update(const char *name, struct dcvs_freq *votes,
u32 update_mask)
{
int i, ret = 0;
u32 cpu = smp_processor_id();
struct dcvs_voter *voter;
struct dcvs_path *path;
struct dcvs_freq new_freq;
enum dcvs_hw_type hw_type;
if (!name || !votes || !update_mask)
return -EINVAL;
for (i = 0; i < NUM_DCVS_HW_TYPES; i++) {
if (!(update_mask & BIT(i)))
continue;
hw_type = votes[i].hw_type;
path = get_dcvs_path(hw_type, DCVS_PERCPU_PATH);
if (!path)
return -EINVAL;
/* should match one and only voter in list */
voter = list_first_entry(&path->voter_list, struct dcvs_voter,
node);
if (!voter || strcmp(voter->name, name))
return -EINVAL;
/* no aggregation required since only single client per cpu */
new_freq.ib = get_target_freq(path, votes[i].ib);
new_freq.hw_type = hw_type;
if (new_freq.ib != path->percpu_cur_freqs[cpu]) {
ret = path->commit_dcvs_freqs(path, &new_freq, 1);
if (ret < 0)
return ret;
}
trace_qcom_dcvs_update(name, hw_type, path->type, votes[i].ib,
new_freq.ib, 0, 0, 0);
}
return ret;
}
int qcom_dcvs_update_votes(const char *name, struct dcvs_freq *votes,
u32 update_mask, enum dcvs_path_type path)
{
switch (path) {
case DCVS_SLOW_PATH:
return qcom_dcvs_sp_update(name, votes, update_mask);
case DCVS_FAST_PATH:
return qcom_dcvs_fp_update(name, votes, update_mask);
case DCVS_PERCPU_PATH:
return qcom_dcvs_percpu_update(name, votes, update_mask);
default:
break;
}
return -EINVAL;
}
EXPORT_SYMBOL(qcom_dcvs_update_votes);
int qcom_dcvs_register_voter(const char *name, enum dcvs_hw_type hw_type,
enum dcvs_path_type path_type)
{
int ret = 0;
struct dcvs_voter *voter;
struct dcvs_path *path;
if (!name || hw_type >= NUM_DCVS_HW_TYPES || path_type >= NUM_DCVS_PATHS)
return -EINVAL;
if (!dcvs_data->inited)
return -EPROBE_DEFER;
path = get_dcvs_path(hw_type, path_type);
if (!path)
return -ENODEV;
mutex_lock(&path->voter_lock);
if (path_type == DCVS_FAST_PATH && path->num_voters >= 1) {
ret = -EINVAL;
goto unlock_out;
}
list_for_each_entry(voter, &path->voter_list, node)
if (!strcmp(voter->name, name)) {
ret = -EINVAL;
goto unlock_out;
}
voter = kzalloc(sizeof(*voter), GFP_KERNEL);
if (!voter) {
ret = -ENOMEM;
goto unlock_out;
}
voter->name = name;
list_add_tail(&voter->node, &path->voter_list);
path->num_voters++;
unlock_out:
mutex_unlock(&path->voter_lock);
return ret;
}
EXPORT_SYMBOL(qcom_dcvs_register_voter);
int qcom_dcvs_unregister_voter(const char *name, enum dcvs_hw_type hw_type,
enum dcvs_path_type path_type)
{
int ret = 0;
struct dcvs_voter *voter;
struct dcvs_path *path;
bool found = false;
if (!name || hw_type >= NUM_DCVS_HW_TYPES || path_type >= NUM_DCVS_PATHS)
return -EINVAL;
path = get_dcvs_path(hw_type, path_type);
if (!path)
return -ENODEV;
mutex_lock(&path->voter_lock);
list_for_each_entry(voter, &path->voter_list, node)
if (!strcmp(voter->name, name)) {
found = true;
break;
}
if (!found) {
ret = -EINVAL;
goto unlock_out;
}
path->num_voters--;
list_del(&voter->node);
kfree(voter);
unlock_out:
mutex_unlock(&path->voter_lock);
return ret;
}
EXPORT_SYMBOL(qcom_dcvs_unregister_voter);
struct kobject *qcom_dcvs_kobject_get(enum dcvs_hw_type type)
{
struct kobject *kobj;
if (type > NUM_DCVS_HW_TYPES)
return ERR_PTR(-EINVAL);
if (!dcvs_data->inited)
return ERR_PTR(-EPROBE_DEFER);
if (type == NUM_DCVS_HW_TYPES)
kobj = &dcvs_data->kobj;
else if (dcvs_data->hw_devs[type])
kobj = &dcvs_data->hw_devs[type]->kobj;
else
return ERR_PTR(-ENODEV);
if (!kobj->state_initialized)
return ERR_PTR(-ENODEV);
return kobj;
}
EXPORT_SYMBOL(qcom_dcvs_kobject_get);
int qcom_dcvs_hw_minmax_get(enum dcvs_hw_type hw_type, u32 *min, u32 *max)
{
struct dcvs_hw *hw;
if (hw_type >= NUM_DCVS_HW_TYPES)
return -EINVAL;
if (!dcvs_data->inited)
return -EPROBE_DEFER;
hw = dcvs_data->hw_devs[hw_type];
if (!hw)
return -ENODEV;
*min = hw->hw_min_freq;
*max = hw->hw_max_freq;
return 0;
}
EXPORT_SYMBOL(qcom_dcvs_hw_minmax_get);
struct device_node *qcom_dcvs_get_ddr_child_node(
struct device_node *of_parent)
{
struct device_node *of_child;
int dcvs_ddr_type = -1;
int of_ddr_type = of_fdt_get_ddrtype();
int ret;
for_each_child_of_node(of_parent, of_child) {
ret = of_property_read_u32(of_child, "qcom,ddr-type",
&dcvs_ddr_type);
if (!ret && (dcvs_ddr_type == of_ddr_type))
return of_child;
}
return NULL;
}
EXPORT_SYMBOL(qcom_dcvs_get_ddr_child_node);
static bool qcom_dcvs_hw_and_paths_inited(void)
{
int i;
struct dcvs_hw *hw;
if (dcvs_data->num_inited_hw < dcvs_data->num_hw)
return false;
for (i = 0; i < NUM_DCVS_HW_TYPES; i++) {
hw = dcvs_data->hw_devs[i];
if (!hw)
continue;
if (hw->num_inited_paths < hw->num_paths)
return false;
}
return true;
}
#define FTBL_PROP "qcom,freq-tbl"
static int populate_freq_table(struct device *dev, u32 **freq_table)
{
int ret, len;
struct device_node *of_node = dev->of_node;
if (of_parse_phandle(of_node, FTBL_PROP, 0))
of_node = of_parse_phandle(of_node, FTBL_PROP, 0);
if (of_get_child_count(of_node))
of_node = qcom_dcvs_get_ddr_child_node(of_node);
if (!of_find_property(of_node, FTBL_PROP, &len)) {
dev_err(dev, "Unable to find freq tbl prop\n");
return -EINVAL;
}
len /= sizeof(**freq_table);
if (!len) {
dev_err(dev, "Error: empty freq table\n");
return -EINVAL;
}
*freq_table = devm_kzalloc(dev, len * sizeof(**freq_table), GFP_KERNEL);
if (!*freq_table)
return -ENOMEM;
ret = of_property_read_u32_array(of_node, FTBL_PROP, *freq_table, len);
if (ret < 0) {
dev_err(dev, "Error reading freq table from DT: %d\n", ret);
return ret;
}
return len;
}
static int qcom_dcvs_dev_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
dcvs_data = devm_kzalloc(dev, sizeof(*dcvs_data), GFP_KERNEL);
if (!dcvs_data)
return -ENOMEM;
dcvs_data->num_hw = of_get_available_child_count(dev->of_node);
if (!dcvs_data->num_hw) {
dev_err(dev, "No dcvs hw nodes provided!\n");
return -ENODEV;
}
ret = kobject_init_and_add(&dcvs_data->kobj, &qcom_dcvs_ktype,
&cpu_subsys.dev_root->kobj, "bus_dcvs");
if (ret < 0) {
dev_err(dev, "failed to init qcom-dcvs kobj: %d\n", ret);
kobject_put(&dcvs_data->kobj);
return ret;
}
dev_dbg(dev, "Created kobj: %s\n", kobject_name(&dcvs_data->kobj));
return 0;
}
static int qcom_dcvs_hw_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
enum dcvs_hw_type hw_type = NUM_DCVS_HW_TYPES;
struct dcvs_hw *hw = NULL;
int ret = 0;
if (!dcvs_data) {
dev_err(dev, "Missing QCOM DCVS dev data\n");
return -ENODEV;
}
ret = of_property_read_u32(dev->of_node, QCOM_DCVS_HW_PROP, &hw_type);
if (ret < 0 || hw_type >= NUM_DCVS_HW_TYPES) {
dev_err(dev, "Invalid dcvs hw type:%d, ret:%d\n", hw_type, ret);
return -ENODEV;
}
hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL);
if (!hw)
return -ENOMEM;
hw->dev = dev;
hw->type = hw_type;
hw->num_paths = of_get_available_child_count(dev->of_node);
if (!hw->num_paths) {
dev_err(dev, "No dcvs paths provided!\n");
return -ENODEV;
}
if (hw_type == DCVS_L3)
ret = populate_l3_table(dev, &hw->freq_table);
else
ret = populate_freq_table(dev, &hw->freq_table);
if (ret <= 0) {
dev_err(dev, "Error reading freq table: %d\n", ret);
return ret;
}
hw->table_len = ret;
hw->hw_max_freq = hw->freq_table[hw->table_len-1];
hw->hw_min_freq = hw->freq_table[0];
ret = of_property_read_u32(dev->of_node, QCOM_DCVS_WIDTH_PROP,
&hw->width);
if (ret < 0 || !hw->width) {
dev_err(dev, "Missing or invalid bus-width: %d\n", ret);
return -EINVAL;
}
ret = kobject_init_and_add(&hw->kobj, &dcvs_hw_ktype,
&dcvs_data->kobj, dcvs_hw_names[hw_type]);
if (ret < 0) {
dev_err(dev, "failed to init dcvs hw kobj: %d\n", ret);
kobject_put(&hw->kobj);
return ret;
}
dev_dbg(dev, "Created hw kobj: %s\n", kobject_name(&hw->kobj));
dev_set_drvdata(dev, hw);
dcvs_data->hw_devs[hw_type] = hw;
dcvs_data->num_inited_hw++;
return ret;
}
static int qcom_dcvs_path_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
enum dcvs_path_type path_type = NUM_DCVS_PATHS;
struct dcvs_hw *hw = dev_get_drvdata(dev->parent);
struct dcvs_path *path = NULL;
struct dcvs_freq new_freqs[NUM_DCVS_HW_TYPES];
if (!hw) {
dev_err(dev, "QCOM DCVS HW not configured\n");
return -ENODEV;
}
ret = of_property_read_u32(dev->of_node, QCOM_DCVS_PATH_PROP,
&path_type);
if (ret < 0 || path_type >= NUM_DCVS_PATHS) {
dev_err(dev, "Invalid path type:%d, ret:%d\n", path_type, ret);
return -ENODEV;
}
path = devm_kzalloc(dev, sizeof(*path), GFP_KERNEL);
if (!path)
return -ENOMEM;
path->dev = dev;
path->type = path_type;
path->hw = hw;
switch (path_type) {
case DCVS_SLOW_PATH:
if (hw->type == DCVS_DDR || hw->type == DCVS_LLCC
|| hw->type == DCVS_DDRQOS)
ret = setup_icc_sp_device(dev, hw, path);
else if (hw->type == DCVS_L3)
ret = setup_epss_l3_sp_device(dev, hw, path);
if (ret < 0) {
dev_err(dev, "Error setting up sp dev: %d\n", ret);
return ret;
}
break;
case DCVS_FAST_PATH:
if (hw->type != DCVS_DDR && hw->type != DCVS_LLCC) {
dev_err(dev, "Unsupported HW for dcvs fp: %d\n", ret);
return -EINVAL;
}
ret = setup_ddrllcc_fp_device(dev, hw, path);
if (ret < 0) {
dev_err(dev, "Error setting up fp dev: %d\n", ret);
return ret;
}
break;
case DCVS_PERCPU_PATH:
if (hw->type != DCVS_L3) {
dev_err(dev, "Unsupported HW for path: %d\n", ret);
return -EINVAL;
}
path->percpu_cur_freqs = devm_kzalloc(dev, num_possible_cpus() *
sizeof(*path->percpu_cur_freqs),
GFP_KERNEL);
if (!path->percpu_cur_freqs)
return -ENOMEM;
ret = setup_epss_l3_percpu_device(dev, hw, path);
if (ret < 0) {
dev_err(dev, "Error setting up percpu dev: %d\n", ret);
return ret;
}
break;
default:
/* this should never happen */
return -EINVAL;
}
INIT_LIST_HEAD(&path->voter_list);
mutex_init(&path->voter_lock);
/* start slow paths with boost_freq = max_freq for better boot perf */
if (path->type == DCVS_SLOW_PATH) {
hw->boost_freq = hw->hw_max_freq;
new_freqs[hw->type].ib = hw->hw_max_freq;
new_freqs[hw->type].ab = 0;
new_freqs[hw->type].hw_type = hw->type;
ret = path->commit_dcvs_freqs(path, &new_freqs[hw->type], 1);
if (ret < 0)
dev_err(dev, "Err committing freq for path=%d\n", ret);
}
hw->dcvs_paths[path_type] = path;
hw->num_inited_paths++;
if (qcom_dcvs_hw_and_paths_inited())
dcvs_data->inited = true;
return ret;
}
static int qcom_dcvs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
const struct qcom_dcvs_spec *spec = of_device_get_match_data(dev);
enum dcvs_type type = NUM_DCVS_TYPES;
if (spec)
type = spec->type;
switch (type) {
case QCOM_DCVS_DEV:
if (dcvs_data) {
dev_err(dev, "Only one qcom-dcvs device allowed\n");
ret = -ENODEV;
break;
}
ret = qcom_dcvs_dev_probe(pdev);
if (!ret && of_get_available_child_count(dev->of_node))
of_platform_populate(dev->of_node, NULL, NULL, dev);
break;
case QCOM_DCVS_HW:
ret = qcom_dcvs_hw_probe(pdev);
if (!ret && of_get_available_child_count(dev->of_node))
of_platform_populate(dev->of_node, NULL, NULL, dev);
break;
case QCOM_DCVS_PATH:
ret = qcom_dcvs_path_probe(pdev);
break;
default:
dev_err(dev, "Invalid qcom-dcvs type: %u\n", type);
return -EINVAL;
}
if (ret < 0) {
dev_err(dev, "Failed to probe qcom-dcvs device: %d\n", ret);
return ret;
}
return 0;
}
static const struct qcom_dcvs_spec spec[] = {
[0] = { QCOM_DCVS_DEV },
[1] = { QCOM_DCVS_HW },
[2] = { QCOM_DCVS_PATH },
};
static const struct of_device_id qcom_dcvs_match_table[] = {
{ .compatible = "qcom,dcvs", .data = &spec[0] },
{ .compatible = "qcom,dcvs-hw", .data = &spec[1] },
{ .compatible = "qcom,dcvs-path", .data = &spec[2] },
{}
};
static struct platform_driver qcom_dcvs_driver = {
.probe = qcom_dcvs_probe,
.driver = {
.name = "qcom-dcvs",
.of_match_table = qcom_dcvs_match_table,
.suppress_bind_attrs = true,
},
};
module_platform_driver(qcom_dcvs_driver);
MODULE_DESCRIPTION("QCOM DCVS Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,272 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "qcom-dcvs-epss: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <soc/qcom/dcvs.h>
#include "dcvs_private.h"
struct epss_dev_data {
void __iomem *l3_base;
u32 l3_shared_offset;
u32 *l3_percpu_offsets;
};
struct epss_dev_data *epss_data;
static DEFINE_MUTEX(epss_lock);
#define L3_VOTING_OFFSET 0x90
#define L3_DOMAIN_OFFSET 0x1000
#define MAX_L3_ENTRIES 40U
#define INIT_HZ 300000000UL
#define XO_HZ 19200000UL
#define FTBL_ROW_SIZE 4
#define SRC_MASK GENMASK(31, 30)
#define SRC_SHIFT 30
#define MULT_MASK GENMASK(7, 0)
int populate_l3_table(struct device *dev, u32 **freq_table)
{
int idx, ret, len;
u32 data, src, mult;
unsigned long freq, prev_freq = 0;
struct resource res;
void __iomem *ftbl_base;
unsigned int ftbl_row_size;
u32 *tmp_l3_table;
idx = of_property_match_string(dev->of_node, "reg-names", "l3tbl-base");
if (idx < 0) {
dev_err(dev, "Unable to find l3tbl-base: %d\n", idx);
return -EINVAL;
}
ret = of_address_to_resource(dev->of_node, idx, &res);
if (ret < 0) {
dev_err(dev, "Unable to get resource from address: %d\n", ret);
return -EINVAL;
}
ftbl_base = ioremap(res.start, resource_size(&res));
if (!ftbl_base) {
dev_err(dev, "Unable to map l3tbl-base!\n");
return -ENOMEM;
}
ret = of_property_read_u32(dev->of_node, "qcom,ftbl-row-size",
&ftbl_row_size);
if (ret < 0)
ftbl_row_size = FTBL_ROW_SIZE;
tmp_l3_table = kcalloc(MAX_L3_ENTRIES, sizeof(*tmp_l3_table), GFP_KERNEL);
if (!tmp_l3_table) {
iounmap(ftbl_base);
return -ENOMEM;
}
for (idx = 0; idx < MAX_L3_ENTRIES; idx++) {
data = readl_relaxed(ftbl_base + idx * ftbl_row_size);
src = ((data & SRC_MASK) >> SRC_SHIFT);
mult = (data & MULT_MASK);
freq = src ? XO_HZ * mult : INIT_HZ;
/* Two of the same frequencies means end of table */
if (idx > 0 && prev_freq == freq)
break;
tmp_l3_table[idx] = freq / 1000UL;
prev_freq = freq;
}
len = idx;
*freq_table = devm_kzalloc(dev, len * sizeof(**freq_table), GFP_KERNEL);
if (!*freq_table) {
iounmap(ftbl_base);
return -ENOMEM;
}
for (idx = 0; idx < len; idx++)
(*freq_table)[idx] = tmp_l3_table[idx];
iounmap(ftbl_base);
kfree(tmp_l3_table);
return len;
}
static int commit_epss_l3(struct dcvs_path *path, struct dcvs_freq *freqs,
u32 update_mask, bool shared)
{
struct dcvs_hw *hw = path->hw;
struct epss_dev_data *d = path->data;
int cpu;
u32 idx, offset;
for (idx = 0; idx < hw->table_len; idx++)
if (freqs->ib <= hw->freq_table[idx])
break;
if (hw->type == DCVS_L3) {
if (shared)
offset = d->l3_shared_offset;
else {
cpu = smp_processor_id();
offset = d->l3_percpu_offsets[cpu];
}
writel_relaxed(idx, d->l3_base + offset);
}
path->cur_freq.ib = freqs->ib;
return 0;
}
static int commit_epss_l3_shared(struct dcvs_path *path,
struct dcvs_freq *freqs,
u32 update_mask)
{
return commit_epss_l3(path, freqs, update_mask, true);
}
static int commit_epss_l3_percpu(struct dcvs_path *path,
struct dcvs_freq *freqs,
u32 update_mask)
{
return commit_epss_l3(path, freqs, update_mask, false);
}
static int init_epss_data(struct device *dev)
{
int idx, ret = 0;
struct resource res;
epss_data = devm_kzalloc(dev, sizeof(*epss_data), GFP_KERNEL);
if (!epss_data)
return -ENOMEM;
idx = of_property_match_string(dev->parent->of_node, "reg-names",
"l3-base");
if (idx < 0) {
dev_err(dev, "%s: Unable to find l3-base: %d\n", __func__, idx);
return -EINVAL;
}
ret = of_address_to_resource(dev->parent->of_node, idx, &res);
if (ret < 0) {
dev_err(dev, "Unable to get resource from address: %d\n", ret);
return ret;
}
epss_data->l3_base = devm_ioremap(dev->parent, res.start,
resource_size(&res));
if (!epss_data->l3_base) {
dev_err(dev, "Unable to map l3-base!\n");
return -ENOMEM;
}
return ret;
}
static int populate_shared_offset(struct device *dev, u32 *offset)
{
int ret;
ret = of_property_read_u32(dev->of_node, "qcom,shared-offset", offset);
if (ret < 0) {
dev_err(dev, "Error reading shared offset: %d\n", ret);
return ret;
}
return ret;
}
#define PERCPU_OFFSETS "qcom,percpu-offsets"
static int populate_percpu_offsets(struct device *dev, u32 **cpu_offsets)
{
int ret, len;
struct device_node *of_node = dev->of_node;
if (of_parse_phandle(of_node, PERCPU_OFFSETS, 0))
of_node = of_parse_phandle(of_node, PERCPU_OFFSETS, 0);
if (!of_find_property(of_node, PERCPU_OFFSETS, &len)) {
dev_err(dev, "Unable to find percpu offsets prop!\n");
return -EINVAL;
}
len /= sizeof(**cpu_offsets);
if (len != num_possible_cpus()) {
dev_err(dev, "Invalid percpu offsets table len=%d\n", len);
return -EINVAL;
}
*cpu_offsets = devm_kzalloc(dev, len * sizeof(**cpu_offsets),
GFP_KERNEL);
if (!*cpu_offsets)
return -ENOMEM;
ret = of_property_read_u32_array(of_node, PERCPU_OFFSETS, *cpu_offsets,
len);
if (ret < 0) {
dev_err(dev, "Error reading percpu offsets from DT: %d\n", ret);
return ret;
}
return ret;
}
static int setup_epss_l3_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path, bool shared)
{
int ret = 0;
mutex_lock(&epss_lock);
if (!epss_data)
ret = init_epss_data(dev);
mutex_unlock(&epss_lock);
if (ret < 0)
return ret;
if (shared) {
ret = populate_shared_offset(dev, &epss_data->l3_shared_offset);
path->commit_dcvs_freqs = commit_epss_l3_shared;
} else {
ret = populate_percpu_offsets(dev, &epss_data->l3_percpu_offsets);
path->commit_dcvs_freqs = commit_epss_l3_percpu;
}
if (ret < 0)
return ret;
path->data = epss_data;
return ret;
}
int setup_epss_l3_sp_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path)
{
return setup_epss_l3_device(dev, hw, path, true);
}
int setup_epss_l3_percpu_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path)
{
return setup_epss_l3_device(dev, hw, path, false);
}

View File

@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "qcom-dcvs-fp: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <soc/qcom/cmd-db.h>
#include <soc/qcom/rpmh.h>
#include <soc/qcom/tcs.h>
#include <soc/qcom/dcvs.h>
#include "dcvs_private.h"
struct bcm_db {
__le32 unit;
__le16 width;
u8 vcd;
u8 reserved;
};
struct bcm_data {
u32 addr;
u32 unit;
u32 width;
u32 vcd;
};
enum ddrllcc_fp_idx {
DDR_IDX,
LLCC_IDX,
NUM_FP_CMDS
};
struct ddrllcc_fp_data {
struct device *dev;
struct dcvs_path *paths[NUM_FP_CMDS];
struct bcm_data bcms[NUM_FP_CMDS];
struct tcs_cmd tcs_cmds[NUM_FP_CMDS];
};
struct ddrllcc_fp_data *ddrllcc_data;
static DEFINE_MUTEX(ddrllcc_lock);
static int ddrllcc_fp_commit(struct dcvs_path *path, struct dcvs_freq *freqs,
u32 update_mask)
{
struct ddrllcc_fp_data *fp_data = path->data;
struct tcs_cmd *tcs_cmds = fp_data->tcs_cmds;
struct bcm_data *bcms = fp_data->bcms;
struct dcvs_path **paths = fp_data->paths;
struct device *dev = fp_data->dev;
u32 bcm_vote;
int i, ret = 0;
for (i = 0; i < NUM_FP_CMDS; i++) {
if (!(update_mask & BIT(i)))
continue;
bcm_vote = freqs[i].ib * bcms[i].width / bcms[i].unit;
tcs_cmds[i].data = BCM_TCS_CMD(1, 1, 0, bcm_vote);
}
ret = rpmh_update_fast_path(dev, tcs_cmds, NUM_FP_CMDS, update_mask);
if (ret < 0) {
dev_err(dev, "Error updating RPMH fast path: %d\n", ret);
return ret;
}
for (i = 0; i < NUM_FP_CMDS; i++) {
if (!(update_mask & BIT(i)))
continue;
paths[i]->cur_freq.ib = freqs[i].ib;
}
return ret;
}
static int populate_bcm_data(struct device *dev, struct bcm_data *bcm,
const char *of_prop)
{
const char *bcm_name;
const struct bcm_db *data;
size_t data_len = 0;
int ret;
ret = of_property_read_string(dev->of_node, of_prop, &bcm_name);
if (ret < 0) {
dev_err(dev, "Error reading %s: %d\n", of_prop, ret);
return ret;
}
bcm->addr = cmd_db_read_addr(bcm_name);
if (!bcm->addr) {
dev_err(dev, "Error getting addr for: %s\n", bcm_name);
return -EINVAL;
}
data = cmd_db_read_aux_data(bcm_name, &data_len);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
dev_err(dev, "Error reading %s aux data: %d\n", bcm_name, ret);
return ret;
}
if (data_len != sizeof(*data)) {
dev_err(dev, "Bad data len for %s: %d\n", bcm_name, data_len);
return -EINVAL;
}
bcm->unit = le32_to_cpu(data->unit) / 1000UL;
bcm->width = le16_to_cpu(data->width);
bcm->vcd = data->vcd;
dev_dbg(dev, "Got BCM %s: addr=%lu, unit=%lu, width=%lu, vcd=%lu\n",
bcm_name, bcm->addr, bcm->unit, bcm->width, bcm->vcd);
return 0;
}
int setup_ddrllcc_fp_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path)
{
int ret = 0;
if (hw->type != DCVS_DDR && hw->type != DCVS_LLCC)
return -EINVAL;
mutex_lock(&ddrllcc_lock);
if (!ddrllcc_data) {
dev_dbg(dev, "Probe deferred since FP not init yet\n");
mutex_unlock(&ddrllcc_lock);
return -EPROBE_DEFER;
}
path->data = ddrllcc_data;
path->commit_dcvs_freqs = ddrllcc_fp_commit;
if (hw->type == DCVS_DDR)
ddrllcc_data->paths[DDR_IDX] = path;
else
ddrllcc_data->paths[LLCC_IDX] = path;
mutex_unlock(&ddrllcc_lock);
return ret;
}
EXPORT_SYMBOL(setup_ddrllcc_fp_device);
#define DDR_BCM_PROP "qcom,ddr-bcm-name"
#define LLCC_BCM_PROP "qcom,llcc-bcm-name"
static int qcom_dcvs_fp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int i, ret = 0;
struct ddrllcc_fp_data *fp_data;
mutex_lock(&ddrllcc_lock);
if (ddrllcc_data) {
dev_err(dev, "Only one fast path client allowed\n");
ret = -EINVAL;
goto out;
}
fp_data = devm_kzalloc(dev, sizeof(*fp_data), GFP_KERNEL);
if (!fp_data) {
ret = -ENOMEM;
goto out;
}
fp_data->dev = dev;
ret = populate_bcm_data(dev, &fp_data->bcms[DDR_IDX], DDR_BCM_PROP);
if (ret < 0) {
dev_err(dev, "Error importing %s bcm data: %d\n",
DDR_BCM_PROP, ret);
goto out;
}
ret = populate_bcm_data(dev, &fp_data->bcms[LLCC_IDX], LLCC_BCM_PROP);
if (ret < 0) {
dev_err(dev, "Error importing %s bcm data: %d\n",
LLCC_BCM_PROP, ret);
goto out;
}
for (i = 0; i < NUM_FP_CMDS; i++) {
fp_data->tcs_cmds[i].addr = fp_data->bcms[i].addr;
fp_data->tcs_cmds[i].data = BCM_TCS_CMD(1, 1, 0, 0);
fp_data->tcs_cmds[i].wait = 0;
}
ret = rpmh_init_fast_path(dev, fp_data->tcs_cmds, NUM_FP_CMDS);
if (ret < 0) {
dev_err(dev, "Error initializing rpmh fast path: %d\n", ret);
goto out;
}
ret = rpmh_write_async(dev, RPMH_SLEEP_STATE, fp_data->tcs_cmds,
NUM_FP_CMDS);
if (ret < 0) {
dev_err(dev, "Error initing dcvs_fp sleep vote: %d\n", ret);
goto out;
}
for (i = 0; i < NUM_FP_CMDS; i++)
fp_data->tcs_cmds[i].data = BCM_TCS_CMD(1, 1, 0, 1);
ret = rpmh_write_async(dev, RPMH_WAKE_ONLY_STATE, fp_data->tcs_cmds,
NUM_FP_CMDS);
if (ret < 0) {
dev_err(dev, "Error initing dcvs_fp wake vote: %d\n", ret);
goto out;
}
ddrllcc_data = fp_data;
out:
mutex_unlock(&ddrllcc_lock);
return ret;
}
static const struct of_device_id qcom_dcvs_fp_match_table[] = {
{ .compatible = "qcom,dcvs-fp" },
{}
};
static struct platform_driver qcom_dcvs_fp_driver = {
.probe = qcom_dcvs_fp_probe,
.driver = {
.name = "qcom-dcvs-fp",
.of_match_table = qcom_dcvs_fp_match_table,
.suppress_bind_attrs = true,
},
};
module_platform_driver(qcom_dcvs_fp_driver);
MODULE_DESCRIPTION("QCOM DCVS FP Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "qcom-dcvs-icc: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/interconnect.h>
#include <soc/qcom/dcvs.h>
#include "dcvs_private.h"
struct icc_sp_data {
struct icc_path *icc_path;
};
static int commit_icc_freq(struct dcvs_path *path, struct dcvs_freq *freqs,
u32 update_mask)
{
int ret;
struct dcvs_hw *hw = path->hw;
struct icc_sp_data *sp_data = path->data;
struct icc_path *icc_path = sp_data->icc_path;
u32 icc_ib = freqs->ib * hw->width;
u32 icc_ab = freqs->ab * hw->width;
ret = icc_set_bw(icc_path, icc_ab, icc_ib);
if (ret < 0) {
dev_err(path->dev, "icc set bw request failed: %d\n", ret);
return ret;
}
path->cur_freq.ib = freqs->ib;
path->cur_freq.ab = freqs->ab;
return ret;
}
#define ACTIVE_ONLY_TAG 0x3
#define PERF_MODE_TAG 0x8
int setup_icc_sp_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path)
{
struct icc_sp_data *sp_data = NULL;
int ret = 0;
if (hw->type != DCVS_DDR && hw->type != DCVS_LLCC
&& hw->type != DCVS_DDRQOS)
return -EINVAL;
sp_data = devm_kzalloc(dev, sizeof(*sp_data), GFP_KERNEL);
if (!sp_data)
return -ENOMEM;
sp_data->icc_path = of_icc_get(dev, NULL);
if (IS_ERR(sp_data->icc_path)) {
ret = PTR_ERR(sp_data->icc_path);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Unable to register icc path: %d\n", ret);
return ret;
}
if (hw->type == DCVS_DDR || hw->type == DCVS_LLCC)
icc_set_tag(sp_data->icc_path, ACTIVE_ONLY_TAG);
else if (hw->type == DCVS_DDRQOS)
icc_set_tag(sp_data->icc_path, ACTIVE_ONLY_TAG | PERF_MODE_TAG);
path->data = sp_data;
path->commit_dcvs_freqs = commit_icc_freq;
return ret;
}

View File

@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _QCOM_DCVS_PRIVATE_H
#define _QCOM_DCVS_PRIVATE_H
#include <linux/kernel.h>
#include <soc/qcom/dcvs.h>
/*
* NOTE: cur_freq is maintained by SLOW and FAST paths only
* and percpu_cur_freqs is maintained by PERCPU paths only
*/
struct dcvs_path {
struct device *dev;
enum dcvs_path_type type;
struct dcvs_hw *hw;
struct dcvs_freq cur_freq;
u32 *percpu_cur_freqs;
struct list_head voter_list;
u32 num_voters;
struct mutex voter_lock;
void *data; /* node-specific private data */
int (*commit_dcvs_freqs)(struct dcvs_path *path,
struct dcvs_freq *freqs,
u32 update_mask);
};
struct dcvs_hw {
struct device *dev;
struct kobject kobj;
enum dcvs_hw_type type;
u32 *freq_table;
u32 table_len;
u32 width;
u32 hw_min_freq;
u32 hw_max_freq;
u32 boost_freq;
struct dcvs_path *dcvs_paths[NUM_DCVS_PATHS];
u32 num_paths;
u32 num_inited_paths;
};
int populate_l3_table(struct device *dev, u32 **freq_table);
int setup_epss_l3_sp_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path);
int setup_epss_l3_percpu_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path);
#if IS_ENABLED(CONFIG_QCOM_DCVS_FP)
int setup_ddrllcc_fp_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path);
#else
static inline int setup_ddrllcc_fp_device(struct device *dev,
struct dcvs_hw *hw, struct dcvs_path *path)
{
return -ENODEV;
}
#endif
int setup_icc_sp_device(struct device *dev, struct dcvs_hw *hw,
struct dcvs_path *path);
#endif /* _QCOM_DCVS_PRIVATE_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/scmi_protocol.h>
#include <linux/scmi_memlat.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 int cpucp_memlat_init(struct scmi_device *sdev);
static int scmi_memlat_probe(struct scmi_device *sdev)
{
if (!sdev)
return -ENODEV;
return cpucp_memlat_init(sdev);
}
static const struct scmi_device_id scmi_id_table[] = {
{ .protocol_id = SCMI_PROTOCOL_MEMLAT, .name = "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_SOFTDEP("pre: memlat_vendor");
MODULE_DESCRIPTION("ARM SCMI Memlat driver");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/scmi_protocol.h>
#include <linux/scmi_pmu.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>
#include <soc/qcom/pmu_lib.h>
static int scmi_pmu_probe(struct scmi_device *sdev)
{
if (!sdev)
return -ENODEV;
return rimps_pmu_init(sdev);
}
static const struct scmi_device_id scmi_id_table[] = {
{ .protocol_id = SCMI_PMU_PROTOCOL, .name = "scmi_pmu_protocol" },
{ },
};
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
static struct scmi_driver scmi_pmu_drv = {
.name = "scmi-pmu-driver",
.probe = scmi_pmu_probe,
.id_table = scmi_id_table,
};
module_scmi_driver(scmi_pmu_drv);
MODULE_SOFTDEP("pre: pmu_vendor");
MODULE_DESCRIPTION("ARM SCMI PMU driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <soc/qcom/dcvs.h>
#define CREATE_TRACE_POINTS
#include "trace-dcvs.h"
EXPORT_TRACEPOINT_SYMBOL(qcom_dcvs_update);
EXPORT_TRACEPOINT_SYMBOL(qcom_dcvs_boost);
EXPORT_TRACEPOINT_SYMBOL(memlat_dev_meas);
EXPORT_TRACEPOINT_SYMBOL(memlat_dev_update);
EXPORT_TRACEPOINT_SYMBOL(bw_hwmon_meas);
EXPORT_TRACEPOINT_SYMBOL(bw_hwmon_update);
EXPORT_TRACEPOINT_SYMBOL(bw_hwmon_debug);

View File

@ -0,0 +1,270 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#if !defined(_TRACE_DCVS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_DCVS_H
#undef TRACE_SYSTEM
#define TRACE_SYSTEM dcvs
#include <linux/tracepoint.h>
#include <soc/qcom/dcvs.h>
TRACE_EVENT(qcom_dcvs_update,
TP_PROTO(const char *name, int hw, int path, unsigned long ib,
unsigned long path_ib, unsigned long ab, unsigned long path_ab,
unsigned long boost_freq),
TP_ARGS(name, hw, path, ib, path_ib, ab, path_ab, boost_freq),
TP_STRUCT__entry(
__string(name, name)
__field(int, hw)
__field(int, path)
__field(unsigned long, ib)
__field(unsigned long, path_ib)
__field(unsigned long, ab)
__field(unsigned long, path_ab)
__field(unsigned long, boost_freq)
),
TP_fast_assign(
__assign_str(name, name);
__entry->hw = hw;
__entry->path = path;
__entry->ib = ib;
__entry->path_ib = path_ib;
__entry->ab = ab;
__entry->path_ab = path_ab;
__entry->boost_freq = boost_freq;
),
TP_printk("name=%s hw=%d path=%d ib=%lu path_ib=%lu ab=%lu path_ab=%lu boost=%lu",
__get_str(name),
__entry->hw,
__entry->path,
__entry->ib,
__entry->path_ib,
__entry->ab,
__entry->path_ab,
__entry->boost_freq)
);
TRACE_EVENT(qcom_dcvs_boost,
TP_PROTO(int hw, int path, unsigned long boost, unsigned long path_ib,
unsigned long path_ab),
TP_ARGS(hw, path, boost, path_ib, path_ab),
TP_STRUCT__entry(
__field(int, hw)
__field(int, path)
__field(unsigned long, boost)
__field(unsigned long, path_ib)
__field(unsigned long, path_ab)
),
TP_fast_assign(
__entry->hw = hw;
__entry->path = path;
__entry->boost = boost;
__entry->path_ib = path_ib;
__entry->path_ab = path_ab;
),
TP_printk("hw=%d path=%d boost=%lu path_ib=%lu path_ab=%lu",
__entry->hw,
__entry->path,
__entry->boost,
__entry->path_ib,
__entry->path_ab)
);
TRACE_EVENT(memlat_dev_meas,
TP_PROTO(const char *name, unsigned int dev_id, unsigned long inst,
unsigned long mem, unsigned long freq, unsigned int stall,
unsigned int wb, unsigned int ratio, unsigned int fe_stall),
TP_ARGS(name, dev_id, inst, mem, freq, stall, wb, ratio, fe_stall),
TP_STRUCT__entry(
__string(name, name)
__field(unsigned int, dev_id)
__field(unsigned long, inst)
__field(unsigned long, mem)
__field(unsigned long, freq)
__field(unsigned int, stall)
__field(unsigned int, wb)
__field(unsigned int, ratio)
__field(unsigned int, fe_stall)
),
TP_fast_assign(
__assign_str(name, name);
__entry->dev_id = dev_id;
__entry->inst = inst;
__entry->mem = mem;
__entry->freq = freq;
__entry->stall = stall;
__entry->wb = wb;
__entry->ratio = ratio;
__entry->fe_stall = fe_stall;
),
TP_printk("dev: %s, id=%u, inst=%lu, mem=%lu, freq=%lu, stall=%u, wb=%u, ratio=%u, fe_stall=%u",
__get_str(name),
__entry->dev_id,
__entry->inst,
__entry->mem,
__entry->freq,
__entry->stall,
__entry->wb,
__entry->ratio,
__entry->fe_stall)
);
TRACE_EVENT(memlat_dev_update,
TP_PROTO(const char *name, unsigned int dev_id, unsigned long inst,
unsigned long mem, unsigned long freq, unsigned long vote),
TP_ARGS(name, dev_id, inst, mem, freq, vote),
TP_STRUCT__entry(
__string(name, name)
__field(unsigned int, dev_id)
__field(unsigned long, inst)
__field(unsigned long, mem)
__field(unsigned long, freq)
__field(unsigned long, vote)
),
TP_fast_assign(
__assign_str(name, name);
__entry->dev_id = dev_id;
__entry->inst = inst;
__entry->mem = mem;
__entry->freq = freq;
__entry->vote = vote;
),
TP_printk("dev: %s, id=%u, inst=%lu, mem=%lu, freq=%lu, vote=%lu",
__get_str(name),
__entry->dev_id,
__entry->inst,
__entry->mem,
__entry->freq,
__entry->vote)
);
TRACE_EVENT(bw_hwmon_meas,
TP_PROTO(const char *name, unsigned long mbps,
unsigned long us, int wake),
TP_ARGS(name, mbps, us, wake),
TP_STRUCT__entry(
__string(name, name)
__field(unsigned long, mbps)
__field(unsigned long, us)
__field(int, wake)
),
TP_fast_assign(
__assign_str(name, name);
__entry->mbps = mbps;
__entry->us = us;
__entry->wake = wake;
),
TP_printk("dev: %s, mbps = %lu, us = %lu, wake = %d",
__get_str(name),
__entry->mbps,
__entry->us,
__entry->wake)
);
TRACE_EVENT(bw_hwmon_update,
TP_PROTO(const char *name, unsigned long mbps, unsigned long freq,
unsigned long up_thres, unsigned long down_thres),
TP_ARGS(name, mbps, freq, up_thres, down_thres),
TP_STRUCT__entry(
__string(name, name)
__field(unsigned long, mbps)
__field(unsigned long, freq)
__field(unsigned long, up_thres)
__field(unsigned long, down_thres)
),
TP_fast_assign(
__assign_str(name, name);
__entry->mbps = mbps;
__entry->freq = freq;
__entry->up_thres = up_thres;
__entry->down_thres = down_thres;
),
TP_printk("dev: %s, mbps = %lu, freq = %lu, up = %lu, down = %lu",
__get_str(name),
__entry->mbps,
__entry->freq,
__entry->up_thres,
__entry->down_thres)
);
TRACE_EVENT(bw_hwmon_debug,
TP_PROTO(const char *name, unsigned long mbps, unsigned long zone,
unsigned long hist_max, unsigned long hist_mem,
unsigned long hyst_mbps, unsigned long hyst_len),
TP_ARGS(name, mbps, zone, hist_max, hist_mem, hyst_mbps, hyst_len),
TP_STRUCT__entry(
__string(name, name)
__field(unsigned long, mbps)
__field(unsigned long, zone)
__field(unsigned long, hist_max)
__field(unsigned long, hist_mem)
__field(unsigned long, hyst_mbps)
__field(unsigned long, hyst_len)
),
TP_fast_assign(
__assign_str(name, name);
__entry->mbps = mbps;
__entry->zone = zone;
__entry->hist_max = hist_max;
__entry->hist_mem = hist_mem;
__entry->hyst_mbps = hyst_mbps;
__entry->hyst_len = hyst_len;
),
TP_printk("dev=%s mbps=%lu zone=%lu hist_max=%lu hist_mem=%lu hyst_mbps=%lu hyst_len=%lu",
__get_str(name),
__entry->mbps,
__entry->zone,
__entry->hist_max,
__entry->hist_mem,
__entry->hyst_mbps,
__entry->hyst_len)
);
#endif /* _TRACE_DCVS_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace-dcvs
#include <trace/define_trace.h>

View File

@ -0,0 +1,230 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. 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>
/* CPUCP Register offsets */
#define CPUCP_IPC_CHAN_SUPPORTED 2
#define CPUCP_SEND_IRQ_OFFSET 0xC
#define CPUCP_SEND_IRQ_VAL BIT(28)
#define CPUCP_CLEAR_IRQ_OFFSET 0x308
#define CPUCP_STATUS_IRQ_OFFSET 0x30C
#define CPUCP_CLEAR_IRQ_VAL BIT(3)
#define CPUCP_STATUS_IRQ_VAL BIT(3)
#define CPUCP_CLOCK_DOMAIN_OFFSET 0x1000
/**
* struct cpucp_ipc ipc per channel
* @mbox: mailbox-controller interface
* @chans: The mailbox clients' channel array
* @tx_irq_base: Memory address for sending irq
* @rx_irq_base: Memory address for receiving irq
* @dev: Device associated with this instance
* @irq: CPUCP to HLOS irq
* @num_chan: Number of ipc channels supported
*/
struct qcom_cpucp_ipc {
struct mbox_controller mbox;
struct mbox_chan chans[CPUCP_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_cpucp_rx_interrupt(int irq, void *p)
{
struct qcom_cpucp_ipc *cpucp_ipc;
u32 val;
int i;
cpucp_ipc = p;
for (i = 0; i < cpucp_ipc->num_chan; i++) {
val = readl(cpucp_ipc->rx_irq_base +
CPUCP_STATUS_IRQ_OFFSET + (i * CPUCP_CLOCK_DOMAIN_OFFSET));
if (val & CPUCP_STATUS_IRQ_VAL) {
val = CPUCP_CLEAR_IRQ_VAL;
writel(val, cpucp_ipc->rx_irq_base +
CPUCP_CLEAR_IRQ_OFFSET +
(i * CPUCP_CLOCK_DOMAIN_OFFSET));
/* Make sure reg write is complete before proceeding */
mb();
if (cpucp_ipc->chans[i].con_priv)
mbox_chan_received_data(&cpucp_ipc->chans[i]
, NULL);
}
}
return IRQ_HANDLED;
}
static void qcom_cpucp_mbox_shutdown(struct mbox_chan *chan)
{
chan->con_priv = NULL;
}
static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct qcom_cpucp_ipc *cpucp_ipc = container_of(chan->mbox,
struct qcom_cpucp_ipc, mbox);
writel(CPUCP_SEND_IRQ_VAL,
cpucp_ipc->tx_irq_base + CPUCP_SEND_IRQ_OFFSET);
return 0;
}
static struct mbox_chan *qcom_cpucp_mbox_xlate(struct mbox_controller *mbox,
const struct of_phandle_args *sp)
{
struct qcom_cpucp_ipc *cpucp_ipc = container_of(mbox,
struct qcom_cpucp_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 = cpucp_ipc;
return &mbox->chans[ind];
}
static const struct mbox_chan_ops cpucp_mbox_chan_ops = {
.send_data = qcom_cpucp_mbox_send_data,
.shutdown = qcom_cpucp_mbox_shutdown
};
static int qcom_cpucp_ipc_setup_mbox(struct qcom_cpucp_ipc *cpucp_ipc)
{
struct mbox_controller *mbox;
struct device *dev = cpucp_ipc->dev;
unsigned long i;
/* Initialize channel identifiers */
for (i = 0; i < ARRAY_SIZE(cpucp_ipc->chans); i++)
cpucp_ipc->chans[i].con_priv = NULL;
mbox = &cpucp_ipc->mbox;
mbox->dev = dev;
mbox->num_chans = cpucp_ipc->num_chan;
mbox->chans = cpucp_ipc->chans;
mbox->ops = &cpucp_mbox_chan_ops;
mbox->of_xlate = qcom_cpucp_mbox_xlate;
mbox->txdone_irq = false;
mbox->txdone_poll = false;
return mbox_controller_register(mbox);
}
static int qcom_cpucp_probe(struct platform_device *pdev)
{
struct qcom_cpucp_ipc *cpucp_ipc;
struct resource *res;
int ret;
cpucp_ipc = devm_kzalloc(&pdev->dev, sizeof(*cpucp_ipc), GFP_KERNEL);
if (!cpucp_ipc)
return -ENOMEM;
cpucp_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;
}
cpucp_ipc->tx_irq_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!cpucp_ipc->tx_irq_base) {
dev_err(&pdev->dev, "Failed to ioremap cpucp 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;
}
cpucp_ipc->rx_irq_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!cpucp_ipc->rx_irq_base) {
dev_err(&pdev->dev, "Failed to ioremap cpucp rx irq addr\n");
return -ENOMEM;
}
cpucp_ipc->irq = platform_get_irq(pdev, 0);
if (cpucp_ipc->irq < 0) {
dev_err(&pdev->dev, "Failed to get the IRQ\n");
return cpucp_ipc->irq;
}
cpucp_ipc->num_chan = CPUCP_IPC_CHAN_SUPPORTED;
ret = qcom_cpucp_ipc_setup_mbox(cpucp_ipc);
if (ret) {
dev_err(&pdev->dev, "Failed to create mailbox\n");
return ret;
}
ret = devm_request_irq(&pdev->dev, cpucp_ipc->irq,
qcom_cpucp_rx_interrupt, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
"qcom_cpucp", cpucp_ipc);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret);
goto err_mbox;
}
platform_set_drvdata(pdev, cpucp_ipc);
return 0;
err_mbox:
mbox_controller_unregister(&cpucp_ipc->mbox);
return ret;
}
static int qcom_cpucp_remove(struct platform_device *pdev)
{
struct qcom_cpucp_ipc *cpucp_ipc = platform_get_drvdata(pdev);
mbox_controller_unregister(&cpucp_ipc->mbox);
return 0;
}
static const struct of_device_id qcom_cpucp_of_match[] = {
{ .compatible = "qcom,cpucp"},
{}
};
MODULE_DEVICE_TABLE(of, qcom_cpucp_of_match);
static struct platform_driver qcom_cpucp_driver = {
.probe = qcom_cpucp_probe,
.remove = qcom_cpucp_remove,
.driver = {
.name = "qcom_cpucp",
.of_match_table = qcom_cpucp_of_match,
},
};
module_platform_driver(qcom_cpucp_driver);
MODULE_DESCRIPTION("QTI CPUCP Driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*
*/
#if !defined(_TRACE_CPUCP_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_CPUCP_H
#undef TRACE_SYSTEM
#define TRACE_SYSTEM cpucp
#include <linux/tracepoint.h>
TRACE_EVENT(cpucp_log,
TP_PROTO(char *str),
TP_ARGS(str),
TP_STRUCT__entry(
__string(str, str)
),
TP_fast_assign(
__assign_str(str, str);
),
TP_printk("%s\n", __get_str(str))
);
#endif /* _TRACE_CPUCP_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace_cpucp
#include <trace/define_trace.h>

View File

@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* scmi c1dcvs protocols header
*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _SCMI_C1DCVS_H
#define _SCMI_C1DCVS_H
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/types.h>
#define SCMI_C1DCVS_PROTOCOL 0x87
struct scmi_protocol_handle;
/**
* struct scmi_c1dcvs_vendor_ops - represents the various operations provided
* by scmi c1dcvs protocol
*/
struct scmi_c1dcvs_vendor_ops {
int (*set_enable_c1dcvs)(const struct scmi_protocol_handle *ph, void *buf);
int (*get_enable_c1dcvs)(const struct scmi_protocol_handle *ph, void *buf);
int (*set_enable_trace)(const struct scmi_protocol_handle *ph, void *buf);
int (*get_enable_trace)(const struct scmi_protocol_handle *ph, void *buf);
int (*set_ipc_thresh)(const struct scmi_protocol_handle *ph, void *buf);
int (*get_ipc_thresh)(const struct scmi_protocol_handle *ph, void *buf);
int (*set_efreq_thresh)(const struct scmi_protocol_handle *ph, void *buf);
int (*get_efreq_thresh)(const struct scmi_protocol_handle *ph, void *buf);
int (*set_hysteresis)(const struct scmi_protocol_handle *ph, void *buf);
int (*get_hysteresis)(const struct scmi_protocol_handle *ph, void *buf);
};
#if IS_ENABLED(CONFIG_QTI_C1DCVS_SCMI_CLIENT)
int c1dcvs_enable(bool enable);
#else
static inline int c1dcvs_enable(bool enable)
{
return -ENODEV;
}
#endif
#endif /* _SCMI_C1DCVS_H */

View File

@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* SCMI Vendor Protocols header
*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _SCMI_MEMLAT_H
#define _SCMI_MEMLAT_H
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/types.h>
#define SCMI_PROTOCOL_MEMLAT 0x80
#define MAX_EV_CNTRS 4 /* Maximum number of grp or common events */
#define MAX_NAME_LEN 20
struct scmi_protocol_handle;
/**
* struct scmi_memlat_vendor_ops - represents the various operations provided
* by SCMI HW Memlat Protocol
*/
struct scmi_memlat_vendor_ops {
int (*set_mem_grp)(const struct scmi_protocol_handle *ph,
u32 cpus_mpidr, u32 hw_type);
int (*set_mon)(const struct scmi_protocol_handle *ph, u32 cpus_mpidr,
u32 hw_type, u32 mon_type, u32 index, const char *mon_name);
int (*set_grp_ev_map)(const struct scmi_protocol_handle *ph, u32 hw_type,
void *buf, u32 num_evs);
int (*adaptive_low_freq)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*adaptive_high_freq)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*get_adaptive_cur_freq)(const struct scmi_protocol_handle *ph, u32 hw_type,
u32 mon_idx, void *buf);
int (*set_common_ev_map)(const struct scmi_protocol_handle *ph, void *buf,
u32 num_evs);
int (*ipm_ceil)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*fe_stall_floor)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*be_stall_floor)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*wb_pct_thres)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*wb_filter_ipm)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*freq_scale_pct)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*freq_scale_limit_mhz)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*sample_ms)(const struct scmi_protocol_handle *ph, u32 val);
int (*freq_map)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 nr_rows, void *buf);
int (*min_freq)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*max_freq)(const struct scmi_protocol_handle *ph,
u32 hw_type, u32 index, u32 val);
int (*get_cur_freq)(const struct scmi_protocol_handle *ph, u32 hw_type,
u32 mon_idx, void *buf);
int (*start_timer)(const struct scmi_protocol_handle *ph);
int (*stop_timer)(const struct scmi_protocol_handle *ph);
int (*set_log_level)(const struct scmi_protocol_handle *ph, u32 val);
int (*flush_cpucp_log)(const struct scmi_protocol_handle *ph);
int (*get_timestamp)(const struct scmi_protocol_handle *ph, void *buf);
};
#endif /* _SCMI_MEMLAT_H */

45
include/linux/scmi_pmu.h Normal file
View File

@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* SCMI PMU Protocols header
*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _SCMI_PMU_H
#define _SCMI_PMU_H
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/types.h>
#define SCMI_PMU_PROTOCOL 0x86
#define MAX_NUM_CPUS 8
enum cpucp_ev_idx {
CPU_CYC_EVT = 0,
CNT_CYC_EVT,
INST_RETIRED_EVT,
STALL_BACKEND_EVT,
L2D_CACHE_REFILL_EVT,
L2D_WB_EVT,
L3_CACHE_REFILL_EVT,
L3_ACCESS_EVT,
LLCC_CACHE_REFILL_EVT,
MAX_CPUCP_EVT,
};
struct scmi_protocol_handle;
/**
* struct scmi_pmu_vendor_ops - represents the various operations provided
* by SCMI PMU Protocol
*/
struct scmi_pmu_vendor_ops {
int (*set_pmu_map)(const struct scmi_protocol_handle *ph, void *buf);
int (*set_enable_trace)(const struct scmi_protocol_handle *ph, void *buf);
int (*set_cache_enable)(const struct scmi_protocol_handle *ph, void *buf);
};
#endif /* _SCMI_PMU_H */

79
include/soc/qcom/dcvs.h Normal file
View File

@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#ifndef _QCOM_DCVS_H
#define _QCOM_DCVS_H
#include <linux/kernel.h>
#define QCOM_DCVS_WIDTH_PROP "qcom,bus-width"
#define QCOM_DCVS_HW_PROP "qcom,dcvs-hw-type"
#define QCOM_DCVS_PATH_PROP "qcom,dcvs-path-type"
enum dcvs_hw_type {
DCVS_DDR,
DCVS_LLCC,
DCVS_L3,
DCVS_DDRQOS,
NUM_DCVS_HW_TYPES
};
enum dcvs_path_type {
DCVS_SLOW_PATH,
DCVS_FAST_PATH,
DCVS_PERCPU_PATH,
NUM_DCVS_PATHS
};
struct dcvs_freq {
u32 ib;
u32 ab;
enum dcvs_hw_type hw_type;
};
#if IS_ENABLED(CONFIG_QCOM_DCVS)
int qcom_dcvs_register_voter(const char *name, enum dcvs_hw_type hw_type,
enum dcvs_path_type path_type);
int qcom_dcvs_unregister_voter(const char *name, enum dcvs_hw_type hw_type,
enum dcvs_path_type path_type);
int qcom_dcvs_update_votes(const char *name, struct dcvs_freq *votes,
u32 update_mask, enum dcvs_path_type path);
struct kobject *qcom_dcvs_kobject_get(enum dcvs_hw_type type);
int qcom_dcvs_hw_minmax_get(enum dcvs_hw_type hw_type, u32 *min, u32 *max);
struct device_node *qcom_dcvs_get_ddr_child_node(struct device_node *of_parent);
#else
static inline int qcom_dcvs_register_voter(const char *name,
enum dcvs_hw_type hw_type, enum dcvs_path_type path_type)
{
return -ENODEV;
}
static inline int qcom_dcvs_unregister_voter(const char *name,
enum dcvs_hw_type hw_type, enum dcvs_path_type path_type)
{
return -ENODEV;
}
static inline int qcom_dcvs_update_votes(const char *name,
struct dcvs_freq *votes, u32 update_mask,
enum dcvs_path_type path)
{
return -ENODEV;
}
static inline struct kobject *qcom_dcvs_kobject_get(enum dcvs_hw_type type)
{
return NULL;
}
static inline int qcom_dcvs_hw_minmax_get(enum dcvs_hw_type hw_type, u32 *min,
u32 *max)
{
return -ENODEV;
}
static inline struct device_node *qcom_dcvs_get_ddr_child_node(
struct device_node *of_parent)
{
return NULL;
}
#endif
#endif /* _QCOM_DCVS_H */

View File

@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#ifndef _QCOM_PMU_H
#define _QCOM_PMU_H
#include <linux/kernel.h>
#include <linux/scmi_protocol.h>
#include <linux/scmi_pmu.h>
/* (1) ccntr + (6) evcntr + (1) llcc */
#define QCOM_PMU_MAX_EVS 8
#define INVALID_PMU_HW_IDX 0xFF
struct cpucp_hlos_map {
bool shared;
unsigned long cpus;
};
struct qcom_pmu_data {
u32 event_ids[QCOM_PMU_MAX_EVS];
u64 ev_data[QCOM_PMU_MAX_EVS];
u32 num_evs;
};
typedef void (*idle_fn_t)(struct qcom_pmu_data *data, int cpu, int state);
struct qcom_pmu_notif_node {
idle_fn_t idle_cb;
struct list_head node;
};
enum amu_counters {
SYS_AMU_CONST_CYC,
SYS_AMU_CORE_CYC,
SYS_AMU_INST_RET,
SYS_AMU_STALL_MEM,
SYS_AMU_MAX,
};
#if IS_ENABLED(CONFIG_QCOM_PMU_LIB)
int qcom_pmu_event_supported(u32 event_id, int cpu);
int qcom_get_cpucp_id(u32 event_id, int cpu);
int qcom_pmu_read(int cpu, u32 event_id, u64 *pmu_data);
int qcom_pmu_read_local(u32 event_id, u64 *pmu_data);
int qcom_pmu_read_all(int cpu, struct qcom_pmu_data *data);
int qcom_pmu_read_all_local(struct qcom_pmu_data *data);
int qcom_pmu_idle_register(struct qcom_pmu_notif_node *idle_node);
int qcom_pmu_idle_unregister(struct qcom_pmu_notif_node *idle_node);
int rimps_pmu_init(struct scmi_device *sdev);
#else
static inline int qcom_pmu_event_supported(u32 event_id, int cpu)
{
return -ENODEV;
}
static inline int qcom_get_cpucp_id(u32 event_id, int cpu)
{
return -ENODEV;
}
static inline int qcom_pmu_read(int cpu, u32 event_id, u64 *pmu_data)
{
return -ENODEV;
}
static inline int qcom_pmu_read_local(u32 event_id, u64 *pmu_data)
{
return -ENODEV;
}
static inline int qcom_pmu_read_all(int cpu, struct qcom_pmu_data *data)
{
return -ENODEV;
}
static inline int qcom_pmu_read_all_local(struct qcom_pmu_data *data)
{
return -ENODEV;
}
static inline int qcom_pmu_idle_register(struct qcom_pmu_notif_node *idle_node)
{
return -ENODEV;
}
static inline int qcom_pmu_idle_unregister(
struct qcom_pmu_notif_node *idle_node)
{
return -ENODEV;
}
static inline int rimps_pmu_init(struct scmi_device *sdev)
{
return -ENODEV;
}
#endif
#endif /* _QCOM_PMU_H */

View File

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*/
#ifndef _QCOM_LLCC_PMU_H
#define _QCOM_LLCC_PMU_H
#include <linux/kernel.h>
#define QCOM_LLCC_PMU_RD_EV 0x1000
#if IS_ENABLED(CONFIG_QCOM_LLCC_PMU)
int qcom_llcc_pmu_hw_type(u32 *type);
#else
static inline int qcom_llcc_pmu_hw_type(u32 *type)
{
return 0;
}
#endif
#endif /* _QCOM_LLCC_PMU_H */