diff options
Diffstat (limited to 'drivers/devfreq/m4m-hwmon.c')
-rw-r--r-- | drivers/devfreq/m4m-hwmon.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/drivers/devfreq/m4m-hwmon.c b/drivers/devfreq/m4m-hwmon.c new file mode 100644 index 000000000000..51b077119cf3 --- /dev/null +++ b/drivers/devfreq/m4m-hwmon.c @@ -0,0 +1,429 @@ +/* + * 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) "m4m-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 "governor_cache_hwmon.h" + +#define cntr_offset(idx) (sizeof(u32) * idx) + +/* register offsets from base address */ +#define DCVS_VERSION(m) ((m)->base + 0x0) +#define GLOBAL_CR_CTL(m) ((m)->base + 0x8) +#define GLOBAL_CR_RESET(m) ((m)->base + 0xC) +#define OVSTAT(m) ((m)->base + 0x30) +#define OVCLR(m) ((m)->base + 0x34) +#define OVSET(m) ((m)->base + 0x3C) /* unused */ +#define EVCNTR(m, x) ((m)->base + 0x40 + cntr_offset(x)) +#define CNTCTL(m, x) ((m)->base + 0x100 + cntr_offset(x)) +/* counter 0/1 does not have type control */ +#define EVTYPER_START 2 +#define EVTYPER(x) ((m)->base + 0x140 + cntr_offset(x)) + +/* bitmasks for GLOBAL_CR_CTL and CNTCTLx */ +#define CNT_EN BIT(0) +#define IRQ_EN BIT(1) + +/* non-configurable counters */ +#define CYC_CNTR_IDX 0 +#define WASTED_CYC_CNTR_IDX 1 + +/* counter is 28-bit */ +#define CNT_MAX 0x0FFFFFFFU + +struct m4m_counter { + int idx; + u32 event_mask; + unsigned int last_start; +}; + +struct m4m_hwmon { + void __iomem *base; + struct m4m_counter cntr[MAX_NUM_GROUPS]; + int num_cntr; + int irq; + struct cache_hwmon hw; + struct device *dev; +}; + +#define to_mon(ptr) container_of(ptr, struct m4m_hwmon, hw) + +static DEFINE_SPINLOCK(init_lock); + +/* Should only be called once while HW is in POR state */ +static inline void mon_global_init(struct m4m_hwmon *m) +{ + writel_relaxed(CNT_EN | IRQ_EN, GLOBAL_CR_CTL(m)); +} + +static inline void _mon_disable_cntr_and_irq(struct m4m_hwmon *m, int cntr_idx) +{ + writel_relaxed(0, CNTCTL(m, cntr_idx)); +} + +static inline void _mon_enable_cntr_and_irq(struct m4m_hwmon *m, int cntr_idx) +{ + writel_relaxed(CNT_EN | IRQ_EN, CNTCTL(m, cntr_idx)); +} + +static void mon_disable(struct m4m_hwmon *m) +{ + int i; + + for (i = 0; i < m->num_cntr; i++) + _mon_disable_cntr_and_irq(m, m->cntr[i].idx); + /* make sure all counter/irq are indeed disabled */ + mb(); +} + +static void mon_enable(struct m4m_hwmon *m) +{ + int i; + + for (i = 0; i < m->num_cntr; i++) + _mon_enable_cntr_and_irq(m, m->cntr[i].idx); +} + +static inline void _mon_ov_clear(struct m4m_hwmon *m, int cntr_idx) +{ + writel_relaxed(BIT(cntr_idx), OVCLR(m)); +} + +static void mon_ov_clear(struct m4m_hwmon *m, enum request_group grp) +{ + _mon_ov_clear(m, m->cntr[grp].idx); +} + +static inline u32 mon_irq_status(struct m4m_hwmon *m) +{ + return readl_relaxed(OVSTAT(m)); +} + +static bool mon_is_ovstat_set(struct m4m_hwmon *m) +{ + int i; + u32 status = mon_irq_status(m); + + for (i = 0; i < m->num_cntr; i++) + if (status & BIT(m->cntr[i].idx)) + return true; + return false; +} + +/* counter must be stopped first */ +static unsigned long _mon_get_count(struct m4m_hwmon *m, + int cntr_idx, unsigned int start) +{ + unsigned long cnt; + u32 cur_cnt = readl_relaxed(EVCNTR(m, cntr_idx)); + u32 ov = readl_relaxed(OVSTAT(m)) & BIT(cntr_idx); + + if (!ov && cur_cnt < start) { + dev_warn(m->dev, "Counter%d overflowed but not detected\n", + cntr_idx); + ov = 1; + } + + if (ov) + cnt = CNT_MAX - start + cur_cnt; + else + cnt = cur_cnt - start; + + return cnt; +} + +static unsigned long mon_get_count(struct m4m_hwmon *m, + enum request_group grp) +{ + return _mon_get_count(m, m->cntr[grp].idx, m->cntr[grp].last_start); +} + +static inline void mon_set_limit(struct m4m_hwmon *m, enum request_group grp, + unsigned int limit) +{ + u32 start; + + if (limit >= CNT_MAX) + limit = CNT_MAX; + start = CNT_MAX - limit; + + writel_relaxed(start, EVCNTR(m, m->cntr[grp].idx)); + m->cntr[grp].last_start = start; +} + +static inline void mon_enable_cycle_cntr(struct m4m_hwmon *m) +{ + writel_relaxed(CNT_EN, CNTCTL(m, CYC_CNTR_IDX)); +} + +static inline void mon_disable_cycle_cntr(struct m4m_hwmon *m) +{ + _mon_disable_cntr_and_irq(m, CYC_CNTR_IDX); +} + +static inline unsigned long mon_get_cycle_count(struct m4m_hwmon *m) +{ + return _mon_get_count(m, CYC_CNTR_IDX, 0); +} + +static inline void mon_clear_cycle_cntr(struct m4m_hwmon *m) +{ + writel_relaxed(0, EVCNTR(m, CYC_CNTR_IDX)); + _mon_ov_clear(m, CYC_CNTR_IDX); +} + +static void mon_init(struct m4m_hwmon *m) +{ + static bool mon_inited; + unsigned long flags; + int i; + + spin_lock_irqsave(&init_lock, flags); + if (!mon_inited) + mon_global_init(m); + spin_unlock_irqrestore(&init_lock, flags); + + /* configure counter events */ + for (i = 0; i < m->num_cntr; i++) + writel_relaxed(m->cntr[i].event_mask, EVTYPER(m->cntr[i].idx)); +} + +static irqreturn_t m4m_hwmon_intr_handler(int irq, void *dev) +{ + struct m4m_hwmon *m = dev; + + if (mon_is_ovstat_set(m)) { + update_cache_hwmon(&m->hw); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +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 m4m_meas_mrps_and_set_irq(struct cache_hwmon *hw, + unsigned int tol, unsigned int us, struct mrps_stats *mrps) +{ + struct m4m_hwmon *m = to_mon(hw); + unsigned long count, cyc_count; + unsigned long f = hw->df->previous_freq; + unsigned int sample_ms = hw->df->profile->polling_ms; + int i; + u32 limit; + + mon_disable(m); + mon_disable_cycle_cntr(m); + + /* calculate mrps and set limit */ + for (i = 0; i < m->num_cntr; i++) { + count = mon_get_count(m, i); + mrps->mrps[i] = count_to_mrps(count, us); + limit = mrps_to_count(mrps->mrps[i], sample_ms, tol); + mon_ov_clear(m, i); + mon_set_limit(m, i, limit); + dev_dbg(m->dev, "Counter[%d] count 0x%lx, limit 0x%x\n", + m->cntr[i].idx, count, limit); + } + + /* get cycle count and calculate busy percent */ + cyc_count = mon_get_cycle_count(m); + mrps->busy_percent = mult_frac(cyc_count, 1000, us) * 100 / f; + mon_clear_cycle_cntr(m); + dev_dbg(m->dev, "Cycle count 0x%lx\n", cyc_count); + + /* re-enable monitor */ + mon_enable(m); + mon_enable_cycle_cntr(m); + + return 0; +} + +static int m4m_start_hwmon(struct cache_hwmon *hw, struct mrps_stats *mrps) +{ + struct m4m_hwmon *m = to_mon(hw); + unsigned int sample_ms = hw->df->profile->polling_ms; + int ret, i; + u32 limit; + + ret = request_threaded_irq(m->irq, NULL, m4m_hwmon_intr_handler, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(m->dev), m); + if (ret) { + dev_err(m->dev, "Unable to register for irq\n"); + return ret; + } + + mon_init(m); + mon_disable(m); + mon_disable_cycle_cntr(m); + for (i = 0; i < m->num_cntr; i++) { + mon_ov_clear(m, i); + limit = mrps_to_count(mrps->mrps[i], sample_ms, 0); + mon_set_limit(m, i, limit); + } + mon_clear_cycle_cntr(m); + mon_enable(m); + mon_enable_cycle_cntr(m); + + return 0; +} + +static void m4m_stop_hwmon(struct cache_hwmon *hw) +{ + struct m4m_hwmon *m = to_mon(hw); + int i; + + mon_disable(m); + free_irq(m->irq, m); + for (i = 0; i < m->num_cntr; i++) + mon_ov_clear(m, i); +} + +/* device probe functions */ +static struct of_device_id match_table[] = { + { .compatible = "qcom,m4m-hwmon" }, + {} +}; + +static int m4m_hwmon_parse_cntr(struct device *dev, + struct m4m_hwmon *m) +{ + u32 *data; + const char *prop_name = "qcom,counter-event-sel"; + int ret, len, i; + + if (!of_find_property(dev->of_node, prop_name, &len)) + return -EINVAL; + len /= sizeof(*data); + + if (len % 2 || len > MAX_NUM_GROUPS * 2) + return -EINVAL; + + data = devm_kcalloc(dev, len, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + ret = of_property_read_u32_array(dev->of_node, prop_name, data, len); + if (ret) + return ret; + + len /= 2; + m->num_cntr = len; + for (i = 0; i < len; i++) { + /* disallow non-configurable counters */ + if (data[i * 2] < EVTYPER_START) + return -EINVAL; + m->cntr[i].idx = data[i * 2]; + m->cntr[i].event_mask = data[i * 2 + 1]; + } + + devm_kfree(dev, data); + return 0; +} + +static int m4m_hwmon_driver_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct m4m_hwmon *m; + int ret; + + m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL); + if (!m) + return -ENOMEM; + m->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "base not found!\n"); + return -EINVAL; + } + m->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!m->base) + return -ENOMEM; + + m->irq = platform_get_irq(pdev, 0); + if (m->irq < 0) { + dev_err(dev, "Unable to get IRQ number\n"); + return m->irq; + } + + ret = m4m_hwmon_parse_cntr(dev, m); + if (ret) { + dev_err(dev, "Unable to parse counter events\n"); + return ret; + } + + m->hw.of_node = of_parse_phandle(dev->of_node, "qcom,target-dev", 0); + if (!m->hw.of_node) + return -EINVAL; + m->hw.start_hwmon = &m4m_start_hwmon; + m->hw.stop_hwmon = &m4m_stop_hwmon; + m->hw.meas_mrps_and_set_irq = &m4m_meas_mrps_and_set_irq; + + ret = register_cache_hwmon(dev, &m->hw); + if (ret) { + dev_err(dev, "Dev BW hwmon registration failed\n"); + return ret; + } + + return 0; +} + +static struct platform_driver m4m_hwmon_driver = { + .probe = m4m_hwmon_driver_probe, + .driver = { + .name = "m4m-hwmon", + .of_match_table = match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init m4m_hwmon_init(void) +{ + return platform_driver_register(&m4m_hwmon_driver); +} +module_init(m4m_hwmon_init); + +static void __exit m4m_hwmon_exit(void) +{ + platform_driver_unregister(&m4m_hwmon_driver); +} +module_exit(m4m_hwmon_exit); + +MODULE_DESCRIPTION("M4M hardware monitor driver"); +MODULE_LICENSE("GPL v2"); |