diff options
Diffstat (limited to 'drivers/devfreq/msmcci-hwmon.c')
-rw-r--r-- | drivers/devfreq/msmcci-hwmon.c | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/drivers/devfreq/msmcci-hwmon.c b/drivers/devfreq/msmcci-hwmon.c new file mode 100644 index 000000000000..e226c7b1ad0d --- /dev/null +++ b/drivers/devfreq/msmcci-hwmon.c @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "msmcci-hwmon: " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/spinlock.h> +#include <linux/cpu_pm.h> +#include <soc/qcom/scm.h> +#include "governor_cache_hwmon.h" + +#define EVNT_SEL 0x0 +#define EVNT_CNT_MATCH_VAL 0x18 +#define MATCH_FLG 0x30 +#define MATCH_FLG_CLR 0x48 +#define OVR_FLG 0x60 +#define OVR_FLG_CLR 0x78 +#define CNT_CTRL 0x94 +#define CNT_VALUE 0xAC + +#define ENABLE_OVR_FLG BIT(4) +#define ENABLE_MATCH_FLG BIT(5) +#define ENABLE_EVNT_CNT BIT(0) +#define RESET_EVNT_CNT BIT(1) + +#define CNT_DISABLE (ENABLE_OVR_FLG | ENABLE_MATCH_FLG) +#define CNT_RESET_CLR (ENABLE_OVR_FLG | ENABLE_MATCH_FLG) +#define CNT_ENABLE (ENABLE_OVR_FLG | ENABLE_MATCH_FLG | ENABLE_EVNT_CNT) +#define CNT_RESET (ENABLE_OVR_FLG | ENABLE_MATCH_FLG | RESET_EVNT_CNT) + +struct msmcci_hwmon { + struct list_head list; + + union { + phys_addr_t phys_base[MAX_NUM_GROUPS]; + void * __iomem virt_base[MAX_NUM_GROUPS]; + }; + int irq[MAX_NUM_GROUPS]; + u32 event_sel[MAX_NUM_GROUPS]; + int num_counters; + + /* + * Multiple interrupts might fire together for one device. + * In that case, only one re-evaluation needs to be done. + */ + struct mutex update_lock; + + /* For counter state save and restore */ + unsigned long cur_limit[MAX_NUM_GROUPS]; + unsigned long cur_count[MAX_NUM_GROUPS]; + bool mon_enabled; + + struct cache_hwmon hw; + struct device *dev; + bool secure_io; + bool irq_shared; +}; + +#define to_mon(ptr) container_of(ptr, struct msmcci_hwmon, hw) + +static LIST_HEAD(msmcci_hwmon_list); +static DEFINE_MUTEX(list_lock); + +static int use_cnt; +static DEFINE_MUTEX(notifier_reg_lock); + +static inline int write_mon_reg(struct msmcci_hwmon *m, int idx, + unsigned long offset, u32 value) +{ + int ret = 0; + + if (m->secure_io) + ret = scm_io_write(m->phys_base[idx] + offset, value); + else + writel_relaxed(value, m->virt_base[idx] + offset); + + return ret; +} + +static inline u32 read_mon_reg(struct msmcci_hwmon *m, int idx, + unsigned long offset) +{ + if (m->secure_io) + return scm_io_read(m->phys_base[idx] + offset); + else + return readl_relaxed(m->virt_base[idx] + offset); +} + +static int mon_init(struct msmcci_hwmon *m) +{ + int ret, i; + + for (i = 0; i < m->num_counters; i++) { + ret = write_mon_reg(m, i, EVNT_SEL, m->event_sel[i]); + if (ret) + return ret; + } + return 0; +} + +static void mon_enable(struct msmcci_hwmon *m) +{ + int i; + + for (i = 0; i < m->num_counters; i++) + write_mon_reg(m, i, CNT_CTRL, CNT_ENABLE); +} + +static void mon_disable(struct msmcci_hwmon *m) +{ + int i; + + for (i = 0; i < m->num_counters; i++) + write_mon_reg(m, i, CNT_CTRL, CNT_DISABLE); +} + +static bool mon_is_match_flag_set(struct msmcci_hwmon *m, int idx) +{ + return (bool)read_mon_reg(m, idx, MATCH_FLG); +} + +/* mon_clear_single() can only be called when monitor is disabled */ +static void mon_clear_single(struct msmcci_hwmon *m, int idx) +{ + write_mon_reg(m, idx, CNT_CTRL, CNT_RESET); + write_mon_reg(m, idx, CNT_CTRL, CNT_RESET_CLR); + /* reset counter before match/overflow flags are cleared */ + mb(); + write_mon_reg(m, idx, MATCH_FLG_CLR, 1); + write_mon_reg(m, idx, MATCH_FLG_CLR, 0); + write_mon_reg(m, idx, OVR_FLG_CLR, 1); + write_mon_reg(m, idx, OVR_FLG_CLR, 0); +} + +static void mon_set_limit_single(struct msmcci_hwmon *m, int idx, u32 limit) +{ + write_mon_reg(m, idx, EVNT_CNT_MATCH_VAL, limit); +} + +static irqreturn_t msmcci_hwmon_shared_intr_handler(int irq, void *dev) +{ + struct msmcci_hwmon *m = dev; + int idx = -1, i; + + for (i = 0; i < m->num_counters; i++) { + if (mon_is_match_flag_set(m, i)) { + idx = i; + break; + } + } + if (idx == -1) + return IRQ_NONE; + + update_cache_hwmon(&m->hw); + + return IRQ_HANDLED; +} + +static irqreturn_t msmcci_hwmon_intr_handler(int irq, void *dev) +{ + struct msmcci_hwmon *m = dev; + int idx = -1, i; + + for (i = 0; i < m->num_counters; i++) { + if (m->irq[i] == irq) { + idx = i; + break; + } + } + BUG_ON(idx == -1); + + /* + * Multiple independent interrupts could fire together and trigger + * update_cache_hwmon() for same device. If we don't lock, we + * could end up calling devfreq_monitor_start/stop() + * concurrently, which would cause timer/workqueue object + * corruption. However, we can't re-evaluate a few times back to + * back either because the very short window won't be + * representative. Since update_cache_hwmon() will clear match + * flags for all counters, interrupts for other counters can + * simply return if their match flags have already been cleared. + */ + mutex_lock(&m->update_lock); + if (mon_is_match_flag_set(m, idx)) + update_cache_hwmon(&m->hw); + mutex_unlock(&m->update_lock); + return IRQ_HANDLED; +} + +static unsigned long mon_read_count_single(struct msmcci_hwmon *m, int idx) +{ + unsigned long count, ovr; + + count = read_mon_reg(m, idx, CNT_VALUE); + ovr = read_mon_reg(m, idx, OVR_FLG); + if (ovr == 1) { + count += 0xFFFFFFFFUL; + dev_warn(m->dev, "Counter[%d]: overflowed\n", idx); + } + return count; +} + +static int count_to_mrps(unsigned long count, unsigned int us) +{ + do_div(count, us); + count++; + return count; +} + +static unsigned int mrps_to_count(unsigned int mrps, unsigned int ms, + unsigned int tolerance) +{ + mrps += tolerance; + mrps *= ms * USEC_PER_MSEC; + return mrps; +} + +static unsigned long meas_mrps_and_set_irq(struct cache_hwmon *hw, + unsigned int tol, unsigned int us, struct mrps_stats *mrps) +{ + struct msmcci_hwmon *m = to_mon(hw); + unsigned long count; + unsigned int sample_ms = hw->df->profile->polling_ms; + int i; + u32 limit; + + mon_disable(m); + + /* calculate mrps and set limit */ + for (i = 0; i < m->num_counters; i++) { + count = mon_read_count_single(m, i); + /* + * When CCI is power collapsed, counters are cleared. Add + * saved count to the current reading and clear saved count + * to ensure we won't apply it more than once. + */ + count += m->cur_count[i]; + m->cur_count[i] = 0; + + mrps->mrps[i] = count_to_mrps(count, us); + limit = mrps_to_count(mrps->mrps[i], sample_ms, tol); + + mon_clear_single(m, i); + mon_set_limit_single(m, i, limit); + /* save current limit for restoring after power collapse */ + m->cur_limit[i] = limit; + + dev_dbg(m->dev, "Counter[%d] count 0x%lx, limit 0x%x\n", + i, count, limit); + } + + /* + * There is no cycle counter for this device. + * Treat all cycles as busy. + */ + mrps->busy_percent = 100; + + /* re-enable monitor */ + mon_enable(m); + + return 0; +} + +static void msmcci_hwmon_save_state(void) +{ + int i; + struct msmcci_hwmon *m; + + list_for_each_entry(m, &msmcci_hwmon_list, list) { + if (!m->mon_enabled) + continue; + mon_disable(m); + /* + * Power collapse might happen multiple times before + * re-evaluation is done. Accumulate the saved count. + * Clear counter after read in case power collapse is + * aborted and register values are not wiped. + */ + for (i = 0; i < m->num_counters; i++) { + m->cur_count[i] += mon_read_count_single(m, i); + mon_clear_single(m, i); + } + } +} + +static void msmcci_hwmon_restore_limit(struct msmcci_hwmon *m, int i) +{ + u32 new_limit; + + if (m->cur_count[i] < m->cur_limit[i]) { + new_limit = m->cur_limit[i] - m->cur_count[i]; + } else { + /* + * If counter is larger than limit, interrupt should have + * fired and prevented power collapse from happening. Just + * in case the interrupt does not come, restore previous + * limit so that interrupt will be triggered at some point. + */ + new_limit = m->cur_limit[i]; + } + mon_set_limit_single(m, i, new_limit); + dev_dbg(m->dev, "Counter[%d] restore limit to 0x%x, saved count 0x%lx\n", + i, new_limit, m->cur_count[i]); +} + +static void msmcci_hwmon_restore_state(void) +{ + int i; + struct msmcci_hwmon *m; + + list_for_each_entry(m, &msmcci_hwmon_list, list) { + if (!m->mon_enabled) + continue; + mon_init(m); + for (i = 0; i < m->num_counters; i++) + msmcci_hwmon_restore_limit(m, i); + mon_enable(m); + } +} + +#define CCI_LEVEL 2 +static int msmcci_hwmon_pm_callback(struct notifier_block *nb, + unsigned long val, void *data) +{ + unsigned int level = (unsigned long) data; + + if (level != CCI_LEVEL) + return NOTIFY_DONE; + + /* + * When CCI power collapse callback happens, only current CPU + * would be executing code. Thus there is no need to hold + * mutex or spinlock. + */ + switch (val) { + case CPU_CLUSTER_PM_ENTER: + msmcci_hwmon_save_state(); + break; + case CPU_CLUSTER_PM_ENTER_FAILED: + case CPU_CLUSTER_PM_EXIT: + msmcci_hwmon_restore_state(); + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static struct notifier_block pm_notifier_block = { + .notifier_call = msmcci_hwmon_pm_callback, +}; + +static int register_pm_notifier(struct msmcci_hwmon *m) +{ + int ret; + + mutex_lock(¬ifier_reg_lock); + if (!use_cnt) { + ret = cpu_pm_register_notifier(&pm_notifier_block); + if (ret) { + dev_err(m->dev, "Failed to register for PM notification\n"); + mutex_unlock(¬ifier_reg_lock); + return ret; + } + } + use_cnt++; + mutex_unlock(¬ifier_reg_lock); + + return 0; +} + +static void unregister_pm_nofitifier(void) +{ + mutex_lock(¬ifier_reg_lock); + use_cnt--; + if (!use_cnt) + cpu_pm_unregister_notifier(&pm_notifier_block); + mutex_unlock(¬ifier_reg_lock); +} + +static int request_shared_interrupt(struct msmcci_hwmon *m) +{ + int ret; + + ret = request_threaded_irq(m->irq[HIGH], NULL, + msmcci_hwmon_shared_intr_handler, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(m->dev), m); + if (ret) + dev_err(m->dev, "Unable to register shared interrupt handler for irq %d\n", + m->irq[HIGH]); + + return ret; +} + +static int request_interrupts(struct msmcci_hwmon *m) +{ + int i, ret; + + for (i = 0; i < m->num_counters; i++) { + ret = request_threaded_irq(m->irq[i], NULL, + msmcci_hwmon_intr_handler, IRQF_ONESHOT, + dev_name(m->dev), m); + if (ret) { + dev_err(m->dev, "Unable to register interrupt handler for irq %d\n", + m->irq[i]); + goto irq_failure; + } + } + return 0; + +irq_failure: + for (i--; i > 0; i--) { + disable_irq(m->irq[i]); + free_irq(m->irq[i], m); + } + return ret; +} + +static int start_hwmon(struct cache_hwmon *hw, struct mrps_stats *mrps) +{ + struct msmcci_hwmon *m = to_mon(hw); + unsigned int sample_ms = hw->df->profile->polling_ms; + int ret, i; + u32 limit; + + ret = register_pm_notifier(m); + if (ret) + return ret; + + if (m->irq_shared) + ret = request_shared_interrupt(m); + else + ret = request_interrupts(m); + + if (ret) { + unregister_pm_nofitifier(); + return ret; + } + mon_init(m); + mon_disable(m); + for (i = 0; i < m->num_counters; i++) { + mon_clear_single(m, i); + limit = mrps_to_count(mrps->mrps[i], sample_ms, 0); + mon_set_limit_single(m, i, limit); + } + mon_enable(m); + m->mon_enabled = true; + + return 0; +} + +static void stop_hwmon(struct cache_hwmon *hw) +{ + struct msmcci_hwmon *m = to_mon(hw); + int i; + + m->mon_enabled = false; + mon_disable(m); + + for (i = 0; i < m->num_counters; i++) { + if (!m->irq_shared || i == HIGH) { + disable_irq(m->irq[i]); + free_irq(m->irq[i], m); + } + mon_clear_single(m, i); + } + + unregister_pm_nofitifier(); +} + +static int msmcci_hwmon_parse_dt(struct platform_device *pdev, + struct msmcci_hwmon *m, int idx) +{ + struct device *dev = &pdev->dev; + struct resource *res; + u32 sel; + int ret; + + if (idx >= MAX_NUM_GROUPS) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, idx); + if (!res) + return (idx == HIGH) ? -EINVAL : 0; + + if (m->secure_io) + m->phys_base[idx] = res->start; + else { + m->virt_base[idx] = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!m->virt_base[idx]) { + dev_err(dev, "failed to ioremap\n"); + return -ENOMEM; + } + } + + ret = of_property_read_u32_index(pdev->dev.of_node, + "qcom,counter-event-sel", idx, &sel); + if (ret) { + dev_err(dev, "Counter[%d] failed to read event sel\n", idx); + return ret; + } + m->event_sel[idx] = sel; + + if (!m->irq_shared || idx == HIGH) { + m->irq[idx] = platform_get_irq(pdev, idx); + if (m->irq[idx] < 0) { + dev_err(dev, "Counter[%d] failed to get IRQ number\n", + idx); + return m->irq[idx]; + } + } + m->num_counters++; + return 0; +} + +static int msmcci_hwmon_driver_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct msmcci_hwmon *m; + int ret; + + m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL); + if (!m) + return -ENOMEM; + m->dev = &pdev->dev; + + m->secure_io = of_property_read_bool(pdev->dev.of_node, + "qcom,secure-io"); + + m->irq_shared = of_property_read_bool(pdev->dev.of_node, + "qcom,shared-irq"); + + ret = msmcci_hwmon_parse_dt(pdev, m, HIGH); + if (ret) + return ret; + ret = msmcci_hwmon_parse_dt(pdev, m, MED); + if (ret) + return ret; + ret = msmcci_hwmon_parse_dt(pdev, m, LOW); + if (ret) + return ret; + + m->hw.of_node = of_parse_phandle(dev->of_node, "qcom,target-dev", 0); + if (!m->hw.of_node) { + dev_err(dev, "No target device specified\n"); + return -EINVAL; + } + m->hw.start_hwmon = &start_hwmon; + m->hw.stop_hwmon = &stop_hwmon; + m->hw.meas_mrps_and_set_irq = &meas_mrps_and_set_irq; + mutex_init(&m->update_lock); + + /* + * This tests whether secure IO for monitor registers + * is supported. + */ + ret = mon_init(m); + if (ret) { + dev_err(dev, "Failed to config monitor. Cache hwmon not registered\n"); + return ret; + } + + ret = register_cache_hwmon(dev, &m->hw); + if (ret) { + dev_err(dev, "MSMCCI cache hwmon registration failed\n"); + return ret; + } + + mutex_lock(&list_lock); + list_add_tail(&m->list, &msmcci_hwmon_list); + mutex_unlock(&list_lock); + + dev_info(dev, "MSMCCI cache hwmon registered\n"); + return 0; +} + +static struct of_device_id match_table[] = { + { .compatible = "qcom,msmcci-hwmon" }, + {} +}; + +static struct platform_driver msmcci_hwmon_driver = { + .probe = msmcci_hwmon_driver_probe, + .driver = { + .name = "msmcci-hwmon", + .of_match_table = match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init msmcci_hwmon_init(void) +{ + return platform_driver_register(&msmcci_hwmon_driver); +} +module_init(msmcci_hwmon_init); + +static void __exit msmcci_hwmon_exit(void) +{ + platform_driver_unregister(&msmcci_hwmon_driver); +} +module_exit(msmcci_hwmon_exit); + +MODULE_DESCRIPTION("QTI CCI performance monitor driver"); +MODULE_LICENSE("GPL v2"); |