mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 11:37:06 +02:00
Merge "drivers: soc: qcom: Add snapshot of cpucp communication drivers"
This commit is contained in:
commit
007a38d10e
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
164
drivers/firmware/arm_scmi/c1dcvs_vendor.c
Normal file
164
drivers/firmware/arm_scmi/c1dcvs_vendor.c
Normal 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");
|
||||
387
drivers/firmware/arm_scmi/memlat_vendor.c
Normal file
387
drivers/firmware/arm_scmi/memlat_vendor.c
Normal 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");
|
||||
106
drivers/firmware/arm_scmi/pmu_vendor.c
Normal file
106
drivers/firmware/arm_scmi/pmu_vendor.c
Normal 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");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
325
drivers/perf/qcom_llcc_pmu.c
Normal file
325
drivers/perf/qcom_llcc_pmu.c
Normal 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");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
351
drivers/soc/qcom/cpucp_log.c
Normal file
351
drivers/soc/qcom/cpucp_log.c
Normal 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");
|
||||
91
drivers/soc/qcom/dcvs/Kconfig
Normal file
91
drivers/soc/qcom/dcvs/Kconfig
Normal 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.
|
||||
11
drivers/soc/qcom/dcvs/Makefile
Normal file
11
drivers/soc/qcom/dcvs/Makefile
Normal 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
|
||||
1842
drivers/soc/qcom/dcvs/bwmon.c
Normal file
1842
drivers/soc/qcom/dcvs/bwmon.c
Normal file
File diff suppressed because it is too large
Load Diff
194
drivers/soc/qcom/dcvs/bwmon.h
Normal file
194
drivers/soc/qcom/dcvs/bwmon.h
Normal 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 */
|
||||
285
drivers/soc/qcom/dcvs/c1dcvs_scmi.c
Normal file
285
drivers/soc/qcom/dcvs/c1dcvs_scmi.c
Normal 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");
|
||||
902
drivers/soc/qcom/dcvs/dcvs.c
Normal file
902
drivers/soc/qcom/dcvs/dcvs.c
Normal 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");
|
||||
272
drivers/soc/qcom/dcvs/dcvs_epss.c
Normal file
272
drivers/soc/qcom/dcvs/dcvs_epss.c
Normal 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);
|
||||
}
|
||||
237
drivers/soc/qcom/dcvs/dcvs_fp.c
Normal file
237
drivers/soc/qcom/dcvs/dcvs_fp.c
Normal 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");
|
||||
77
drivers/soc/qcom/dcvs/dcvs_icc.c
Normal file
77
drivers/soc/qcom/dcvs/dcvs_icc.c
Normal 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;
|
||||
}
|
||||
65
drivers/soc/qcom/dcvs/dcvs_private.h
Normal file
65
drivers/soc/qcom/dcvs/dcvs_private.h
Normal 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 */
|
||||
1901
drivers/soc/qcom/dcvs/memlat.c
Normal file
1901
drivers/soc/qcom/dcvs/memlat.c
Normal file
File diff suppressed because it is too large
Load Diff
41
drivers/soc/qcom/dcvs/memlat_scmi.c
Normal file
41
drivers/soc/qcom/dcvs/memlat_scmi.c
Normal 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");
|
||||
1183
drivers/soc/qcom/dcvs/pmu_lib.c
Normal file
1183
drivers/soc/qcom/dcvs/pmu_lib.c
Normal file
File diff suppressed because it is too large
Load Diff
40
drivers/soc/qcom/dcvs/pmu_scmi.c
Normal file
40
drivers/soc/qcom/dcvs/pmu_scmi.c
Normal 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");
|
||||
18
drivers/soc/qcom/dcvs/trace-dcvs.c
Normal file
18
drivers/soc/qcom/dcvs/trace-dcvs.c
Normal 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);
|
||||
270
drivers/soc/qcom/dcvs/trace-dcvs.h
Normal file
270
drivers/soc/qcom/dcvs/trace-dcvs.h
Normal 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>
|
||||
230
drivers/soc/qcom/qcom_cpucp.c
Normal file
230
drivers/soc/qcom/qcom_cpucp.c
Normal 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");
|
||||
40
drivers/soc/qcom/trace_cpucp.h
Normal file
40
drivers/soc/qcom/trace_cpucp.h
Normal 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>
|
||||
48
include/linux/scmi_c1dcvs.h
Normal file
48
include/linux/scmi_c1dcvs.h
Normal 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 */
|
||||
|
||||
71
include/linux/scmi_memlat.h
Normal file
71
include/linux/scmi_memlat.h
Normal 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
45
include/linux/scmi_pmu.h
Normal 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
79
include/soc/qcom/dcvs.h
Normal 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 */
|
||||
92
include/soc/qcom/pmu_lib.h
Normal file
92
include/soc/qcom/pmu_lib.h
Normal 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 */
|
||||
22
include/soc/qcom/qcom_llcc_pmu.h
Normal file
22
include/soc/qcom/qcom_llcc_pmu.h
Normal 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 */
|
||||
Loading…
Reference in New Issue
Block a user