123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- // 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_attrs[] = {
- &sample_ms.attr,
- NULL,
- };
- ATTRIBUTE_GROUPS(bwprof);
- static struct attribute *mon_attrs[] = {
- &mon_enabled.attr,
- &last_sample.attr,
- &client.attr,
- NULL,
- };
- ATTRIBUTE_GROUPS(mon);
- 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 struct kobj_type bwprof_ktype = {
- .sysfs_ops = &bwprof_sysfs_ops,
- .default_groups = bwprof_groups,
- };
- static struct kobj_type mon_ktype = {
- .sysfs_ops = &bwprof_sysfs_ops,
- .default_groups = mon_groups,
- };
- 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!: %d\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");
|