Files
2025-08-12 22:16:57 +02:00

557 lines
13 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "qcom-bwprof: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include "bwprof.h"
#include "trace-dcvs.h"
static LIST_HEAD(bwprof_list);
static DEFINE_MUTEX(bwprof_lock);
enum bwprof_type {
BWPROF_DEV,
BWPROF_MON,
NUM_BWPROF_TYPES
};
struct bwprof_spec {
enum bwprof_type type;
};
struct bwprof_sample {
ktime_t ts;
u32 meas_mbps;
u32 max_mbps;
u32 mem_freq;
};
struct bwmon_node {
struct device *dev;
struct kobject kobj;
void __iomem *base;
struct list_head list;
bool enabled;
struct bwprof_sample last_sample;
u64 curr_sample_bytes;
const char *client;
};
struct bwprof_dev_data {
struct device *dev;
struct kobject kobj;
void __iomem *memfreq_base;
struct work_struct work;
struct workqueue_struct *bwprof_wq;
u32 sample_ms;
u32 sample_cnt;
struct hrtimer bwprof_hrtimer;
u32 mon_count;
u32 bus_width;
};
static struct bwprof_dev_data *bwprof_data;
static void start_bwmon_node(struct bwmon_node *bw_node);
static void stop_bwmon_node(struct bwmon_node *bw_node);
struct qcom_bwprof_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_bwprof_attr(_attr) \
container_of(_attr, struct qcom_bwprof_attr, attr)
#define to_bwmon_node(k) container_of(k, struct bwmon_node, kobj)
#define BWPROF_ATTR_RW(_name) \
struct qcom_bwprof_attr _name = \
__ATTR(_name, 0644, show_##_name, store_##_name) \
#define BWPROF_ATTR_RO(_name) \
struct qcom_bwprof_attr _name = \
__ATTR(_name, 0444, show_##_name, NULL) \
#define SAMPLE_MIN_MS 100U
#define SAMPLE_MAX_MS 2000U
static ssize_t store_sample_ms(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
int ret;
unsigned int val;
ret = kstrtoint(buf, 10, &val);
if (ret)
return ret;
val = max(val, SAMPLE_MIN_MS);
val = min(val, SAMPLE_MAX_MS);
bwprof_data->sample_ms = val;
return count;
}
static ssize_t show_sample_ms(struct kobject *kobj,
struct attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u\n", bwprof_data->sample_ms);
}
static ssize_t store_mon_enabled(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
struct bwmon_node *bw_node = to_bwmon_node(kobj);
bool val;
int ret;
ret = kstrtobool(buf, &val);
if (ret)
return ret;
if (bw_node->enabled == val)
return count;
mutex_lock(&bwprof_lock);
bw_node->enabled = val;
if (val)
start_bwmon_node(bw_node);
else
stop_bwmon_node(bw_node);
mutex_unlock(&bwprof_lock);
return count;
}
static ssize_t show_mon_enabled(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct bwmon_node *bw_node = to_bwmon_node(kobj);
return scnprintf(buf, PAGE_SIZE, "%u\n", bw_node->enabled);
}
static ssize_t show_last_sample(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct bwmon_node *bw_node = to_bwmon_node(kobj);
struct bwprof_sample *sample = &bw_node->last_sample;
return scnprintf(buf, PAGE_SIZE, "%llu\t%u\t%u\t%u\n",
sample->ts, sample->meas_mbps, sample->max_mbps, sample->mem_freq);
}
static ssize_t show_client(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct bwmon_node *bw_node = to_bwmon_node(kobj);
return scnprintf(buf, PAGE_SIZE, "%s\n", bw_node->client);
}
static BWPROF_ATTR_RW(sample_ms);
static BWPROF_ATTR_RW(mon_enabled);
static BWPROF_ATTR_RO(last_sample);
static BWPROF_ATTR_RO(client);
static struct attribute *bwprof_attr[] = {
&sample_ms.attr,
NULL,
};
static struct attribute *mon_attr[] = {
&mon_enabled.attr,
&last_sample.attr,
&client.attr,
NULL,
};
static ssize_t attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct qcom_bwprof_attr *bwprof_attr = to_bwprof_attr(attr);
ssize_t ret = -EIO;
if (bwprof_attr->show)
ret = bwprof_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_bwprof_attr *bwprof_attr = to_bwprof_attr(attr);
ssize_t ret = -EIO;
if (bwprof_attr->store)
ret = bwprof_attr->store(kobj, attr, buf, count);
return ret;
}
static const struct sysfs_ops bwprof_sysfs_ops = {
.show = attr_show,
.store = attr_store,
};
static const struct kobj_type bwprof_ktype = {
.sysfs_ops = &bwprof_sysfs_ops,
.default_attrs = bwprof_attr,
};
static const struct kobj_type mon_ktype = {
.sysfs_ops = &bwprof_sysfs_ops,
.default_attrs = mon_attr,
};
static inline void bwmon_node_resume(struct bwmon_node *bw_node)
{
writel_relaxed(1, BWMON_EN(bw_node));
}
static inline void bwmon_node_pause(struct bwmon_node *bw_node)
{
writel_relaxed(0, BWMON_EN(bw_node));
}
#define BWMON_CLEAR_BIT 0x1
#define BWMON_CLEAR_ALL_BIT 0x2
static inline void bwmon_node_clear(struct bwmon_node *bw_node, bool clear_all)
{
if (clear_all)
writel_relaxed(BWMON_CLEAR_ALL_BIT, BWMON_CLEAR(bw_node));
else
writel_relaxed(BWMON_CLEAR_BIT, BWMON_CLEAR(bw_node));
/*
* In some hardware versions since BWMON_CLEAR(m) register does not have
* self-clearing capability it needs to be cleared explicitly. But we also
* need to ensure the writes to it are successful before clearing it.
*/
wmb();
writel_relaxed(0, BWMON_CLEAR(bw_node));
writel_relaxed(HW_SAMPLE_TICKS, BWMON_SW(bw_node));
}
#define ZONE_THRES_LIM 0xFFFF
#define ZONE_CNT_THRES_LIM 0xFFFFFFFF
static void configure_bwmon_node(struct bwmon_node *bw_node)
{
bwmon_node_pause(bw_node);
bwmon_node_clear(bw_node, false);
writel_relaxed(ZONE_THRES_LIM, BWMON_THRES_HI(bw_node));
writel_relaxed(ZONE_THRES_LIM, BWMON_THRES_MED(bw_node));
writel_relaxed(0, BWMON_THRES_LO(bw_node));
writel_relaxed(ZONE_CNT_THRES_LIM, BWMON_ZONE_CNT_THRES(bw_node));
writel_relaxed(0, BWMON_ZONE_ACTIONS(bw_node));
writel_relaxed(HW_SAMPLE_TICKS, BWMON_SW(bw_node));
}
/* Note: bwprof_lock must be held before calling this function */
static void start_bwmon_node(struct bwmon_node *bw_node)
{
configure_bwmon_node(bw_node);
bwmon_node_resume(bw_node);
if (!hrtimer_active(&bwprof_data->bwprof_hrtimer))
hrtimer_start(&bwprof_data->bwprof_hrtimer,
ms_to_ktime(HW_SAMPLE_MS), HRTIMER_MODE_REL_PINNED);
}
/* Note: bwprof_lock must be held before calling this function */
static void stop_bwmon_node(struct bwmon_node *bw_node)
{
bool all_disabled = true;
struct bwmon_node *itr;
bwmon_node_pause(bw_node);
bwmon_node_clear(bw_node, true);
memset(&bw_node->last_sample, 0, sizeof(bw_node->last_sample));
list_for_each_entry(itr, &bwprof_list, list) {
if (itr->enabled) {
all_disabled = false;
break;
}
}
if (all_disabled) {
hrtimer_cancel(&bwprof_data->bwprof_hrtimer);
cancel_work_sync(&bwprof_data->work);
}
}
#define PICOSECONDS_TO_MHZ(t) ((1000000 / t))
static inline u32 get_memfreq(void)
{
u32 memfreq;
memfreq = readl_relaxed(DDR_FREQ(bwprof_data));
memfreq = PICOSECONDS_TO_MHZ(memfreq);
return memfreq;
}
#define MAX_BYTE_COUNT_MASK 0xFFFF
#define MAX_BYTE_COUNT_SHIFT 16
static void get_bw_and_update_last_sample(struct bwmon_node *bw_node)
{
unsigned long count;
bwmon_node_pause(bw_node);
count = readl_relaxed(BWMON_ZONE1_MAX_BYTE_COUNT(bw_node)) &
MAX_BYTE_COUNT_MASK;
count <<= MAX_BYTE_COUNT_SHIFT;
bw_node->curr_sample_bytes += count;
bwmon_node_clear(bw_node, false);
bwmon_node_resume(bw_node);
}
static void bwprof_update_work(struct work_struct *work)
{
struct bwmon_node *bw_node;
ktime_t now = ktime_get();
u32 mem_freq, max_mbps;
bool update_last_sample = false;
mutex_lock(&bwprof_lock);
bwprof_data->sample_cnt++;
if (bwprof_data->sample_cnt * HW_SAMPLE_MS >= bwprof_data->sample_ms) {
update_last_sample = true;
bwprof_data->sample_cnt = 0;
mem_freq = get_memfreq();
max_mbps = bwprof_data->bus_width * mem_freq;
}
list_for_each_entry(bw_node, &bwprof_list, list) {
if (!bw_node->enabled)
continue;
get_bw_and_update_last_sample(bw_node);
if (update_last_sample) {
bw_node->last_sample.ts = now;
do_div(bw_node->curr_sample_bytes,
bwprof_data->sample_ms * USEC_PER_MSEC);
bw_node->last_sample.meas_mbps = bw_node->curr_sample_bytes;
bw_node->last_sample.mem_freq = mem_freq;
bw_node->last_sample.max_mbps = max_mbps;
trace_bwprof_last_sample(
dev_name(bw_node->dev),
bw_node->client,
bw_node->last_sample.ts,
bw_node->last_sample.meas_mbps,
bw_node->last_sample.max_mbps,
bw_node->last_sample.mem_freq
);
bw_node->curr_sample_bytes = 0;
}
}
mutex_unlock(&bwprof_lock);
}
#define MAX_NAME_LEN 20
#define QCOM_BWPROF_CLIENT_PROP "client"
static int bwprof_mon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct bwmon_node *bw_node;
char name[MAX_NAME_LEN];
struct resource *res;
int ret;
if (!bwprof_data) {
dev_err(dev, "Missing bwprof dev data!\n");
return -ENODEV;
}
bw_node = devm_kzalloc(dev, sizeof(*bw_node), GFP_KERNEL);
if (!bw_node)
return -ENOMEM;
bw_node->dev = dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base");
if (!res) {
dev_err(dev, "base not found!\n");
return -EINVAL;
}
bw_node->base = devm_ioremap(dev, res->start, resource_size(res));
if (!bw_node->base) {
dev_err(dev, "Unable map base!\n");
return -ENOMEM;
}
ret = of_property_read_string(dev->of_node, QCOM_BWPROF_CLIENT_PROP,
&bw_node->client);
if (ret < 0) {
dev_err(dev, "client not found!\n", ret);
return ret;
}
snprintf(name, MAX_NAME_LEN, "bwmon%d", bwprof_data->mon_count);
ret = kobject_init_and_add(&bw_node->kobj, &mon_ktype,
&bwprof_data->kobj, name);
if (ret < 0) {
dev_err(dev, "failed to init bwprof mon kobj: %d\n", ret);
kobject_put(&bw_node->kobj);
return ret;
}
configure_bwmon_node(bw_node);
mutex_lock(&bwprof_lock);
list_add_tail(&bw_node->list, &bwprof_list);
mutex_unlock(&bwprof_lock);
bwprof_data->mon_count++;
return 0;
}
static enum hrtimer_restart bwprof_hrtimer_handler(struct hrtimer *timer)
{
ktime_t now = ktime_get();
queue_work(bwprof_data->bwprof_wq, &bwprof_data->work);
hrtimer_forward(timer, now, ms_to_ktime(HW_SAMPLE_MS));
return HRTIMER_RESTART;
}
static int bwprof_dev_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
int ret;
bwprof_data = devm_kzalloc(dev, sizeof(*bwprof_data), GFP_KERNEL);
if (!bwprof_data)
return -ENOMEM;
bwprof_data->dev = dev;
bwprof_data->sample_ms = 100;
bwprof_data->mon_count = 0;
ret = of_property_read_u32(dev->of_node, "qcom,bus-width",
&bwprof_data->bus_width);
if (ret < 0 || !bwprof_data->bus_width) {
dev_err(dev, "Missing or invalid bus-width: %d\n", ret);
return -EINVAL;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem-freq");
if (!res) {
dev_err(dev, "mem-freq not found!\n");
return -EINVAL;
}
bwprof_data->memfreq_base = devm_ioremap(dev, res->start,
resource_size(res));
if (!bwprof_data->memfreq_base) {
dev_err(dev, "Unable map memfreq base!\n");
return -ENOMEM;
}
hrtimer_init(&bwprof_data->bwprof_hrtimer, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
bwprof_data->bwprof_hrtimer.function = bwprof_hrtimer_handler;
bwprof_data->bwprof_wq = create_freezable_workqueue("bwprof_wq");
if (!bwprof_data->bwprof_wq) {
dev_err(dev, "Couldn't create bwprof workqueue.\n");
return -ENOMEM;
}
INIT_WORK(&bwprof_data->work, &bwprof_update_work);
ret = kobject_init_and_add(&bwprof_data->kobj, &bwprof_ktype,
&cpu_subsys.dev_root->kobj, "bw_prof");
if (ret < 0) {
dev_err(dev, "failed to init bwprof kobj: %d\n", ret);
kobject_put(&bwprof_data->kobj);
return ret;
}
return 0;
}
static int qcom_bwprof_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0;
const struct bwprof_spec *spec;
enum bwprof_type type = NUM_BWPROF_TYPES;
spec = of_device_get_match_data(dev);
if (spec)
type = spec->type;
switch (type) {
case BWPROF_DEV:
if (bwprof_data) {
dev_err(dev, "only one bwprof device allowed\n");
ret = -ENODEV;
}
ret = bwprof_dev_probe(pdev);
if (!ret && of_get_available_child_count(dev->of_node))
of_platform_populate(dev->of_node, NULL, NULL, dev);
break;
case BWPROF_MON:
ret = bwprof_mon_probe(pdev);
break;
default:
/*
* This should never happen.
*/
dev_err(dev, "Invalid bwprof type specified: %u\n", type);
return -EINVAL;
}
if (ret < 0) {
dev_err(dev, "Failure to probe bwprof device: %d\n", ret);
return ret;
}
return 0;
}
static const struct bwprof_spec spec[] = {
[0] = { BWPROF_DEV },
[1] = { BWPROF_MON },
};
static const struct of_device_id qcom_bwprof_match_table[] = {
{ .compatible = "qcom,bwprof", .data = &spec[0] },
{ .compatible = "qcom,bwprof-mon", .data = &spec[1] },
{}
};
MODULE_DEVICE_TABLE(of, qcom_bwprof_match_table);
static struct platform_driver qcom_bwprof_driver = {
.probe = qcom_bwprof_driver_probe,
.driver = {
.name = "qcom-bwprof",
.of_match_table = qcom_bwprof_match_table,
.suppress_bind_attrs = true,
},
};
module_platform_driver(qcom_bwprof_driver);
MODULE_DESCRIPTION("QCOM BWPROF driver");
MODULE_LICENSE("GPL");