| /* |
| * Copyright (c) 2014, 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) "armbw-pm: " 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_irq.h> |
| #include <linux/slab.h> |
| #include <linux/irq.h> |
| #include <linux/cpu_pm.h> |
| #include <linux/cpu.h> |
| #include "governor.h" |
| #include "governor_bw_hwmon.h" |
| |
| |
| #define DEFINE_CP15_READ(name, op1, n, m, op2) \ |
| static u32 read_##name(void) \ |
| { \ |
| u32 val; \ |
| asm volatile ("mrc p15, " #op1 ", %0, c" #n ", c" #m ", " #op2 \ |
| : "=r" (val)); \ |
| return val; \ |
| } |
| |
| #define DEFINE_CP15_WRITE(name, op1, n, m, op2) \ |
| static void write_##name(u32 val) \ |
| { \ |
| asm volatile ("mcr p15, " #op1 ", %0, c" #n ", c" #m", "#op2 \ |
| : : "r" (val)); \ |
| } |
| |
| #define DEFINE_CP15_RW(name, op1, n, m, op2) \ |
| DEFINE_CP15_READ(name, op1, n, m, op2) \ |
| DEFINE_CP15_WRITE(name, op1, n, m, op2) |
| |
| DEFINE_CP15_WRITE(pmselr, 0, 9, 12, 5) |
| DEFINE_CP15_WRITE(pmcntenset, 0, 9, 12, 1) |
| DEFINE_CP15_WRITE(pmcntenclr, 0, 9, 12, 2) |
| DEFINE_CP15_RW(pmovsr, 0, 9, 12, 3) |
| DEFINE_CP15_WRITE(pmxevtyper, 0, 9, 13, 1) |
| DEFINE_CP15_RW(pmxevcntr, 0, 9, 13, 2) |
| DEFINE_CP15_WRITE(pmintenset, 0, 9, 14, 1) |
| DEFINE_CP15_WRITE(pmintenclr, 0, 9, 14, 2) |
| DEFINE_CP15_WRITE(pmcr, 0, 9, 12, 0) |
| |
| struct bwmon_data { |
| int cpu; |
| u32 saved_evcntr; |
| unsigned long count; |
| u32 prev_rw_start_val; |
| u32 limit; |
| }; |
| |
| static DEFINE_SPINLOCK(bw_lock); |
| static struct bw_hwmon *globalhw; |
| static struct work_struct irqwork; |
| static int bw_irq; |
| static DEFINE_PER_CPU(struct bwmon_data, gov_data); |
| static int use_cnt; |
| static DEFINE_MUTEX(use_lock); |
| static struct workqueue_struct *bw_wq; |
| static u32 bytes_per_beat; |
| |
| #define RW_NUM 0x19 |
| #define RW_MON 0 |
| |
| static void mon_enable(void *info) |
| { |
| /* Clear previous overflow state for given counter*/ |
| write_pmovsr(BIT(RW_MON)); |
| /* Enable event counter n */ |
| write_pmcntenset(BIT(RW_MON)); |
| } |
| |
| static void mon_disable(void *info) |
| { |
| write_pmcntenclr(BIT(RW_MON)); |
| } |
| |
| static void mon_irq_enable(void *info) |
| { |
| write_pmintenset(BIT(RW_MON)); |
| } |
| |
| static void mon_irq_disable(void *info) |
| { |
| write_pmintenclr(BIT(RW_MON)); |
| } |
| |
| static void mon_set_counter(void *count) |
| { |
| write_pmxevcntr(*(u32 *) count); |
| } |
| |
| static void mon_bw_init(void *evcntrval) |
| { |
| u32 count; |
| |
| if (!evcntrval) |
| count = 0xFFFFFFFF; |
| else |
| count = *(u32 *) evcntrval; |
| |
| write_pmcr(BIT(0)); |
| write_pmselr(RW_MON); |
| write_pmxevtyper(RW_NUM); |
| write_pmxevcntr(count); |
| } |
| |
| static void percpu_bwirq_enable(void *info) |
| { |
| enable_percpu_irq(bw_irq, IRQ_TYPE_EDGE_RISING); |
| } |
| |
| static void percpu_bwirq_disable(void *info) |
| { |
| disable_percpu_irq(bw_irq); |
| } |
| |
| static irqreturn_t mon_intr_handler(int irq, void *dev_id) |
| { |
| queue_work(bw_wq, &irqwork); |
| return IRQ_HANDLED; |
| } |
| |
| static void bwmon_work(struct work_struct *work) |
| { |
| update_bw_hwmon(globalhw); |
| } |
| |
| static unsigned int beats_to_mbps(long long beats, unsigned int us) |
| { |
| beats *= USEC_PER_SEC; |
| beats *= bytes_per_beat; |
| do_div(beats, us); |
| beats = DIV_ROUND_UP_ULL(beats, SZ_1M); |
| |
| return beats; |
| } |
| |
| static unsigned int mbps_to_beats(unsigned long mbps, unsigned int ms, |
| unsigned int tolerance_percent) |
| { |
| mbps *= (100 + tolerance_percent) * ms; |
| mbps /= 100; |
| mbps = DIV_ROUND_UP(mbps, MSEC_PER_SEC); |
| mbps = mult_frac(mbps, SZ_1M, bytes_per_beat); |
| return mbps; |
| } |
| |
| static long mon_get_bw_count(u32 start_val) |
| { |
| u32 overflow, count; |
| |
| count = read_pmxevcntr(); |
| overflow = read_pmovsr(); |
| if (overflow & BIT(RW_MON)) |
| return 0xFFFFFFFF - start_val + count; |
| else |
| return count - start_val; |
| } |
| |
| static void get_beat_count(void *arg) |
| { |
| int cpu = smp_processor_id(); |
| struct bwmon_data *data = &per_cpu(gov_data, cpu); |
| |
| mon_disable(NULL); |
| data->count = mon_get_bw_count(data->prev_rw_start_val); |
| } |
| |
| static unsigned long measure_bw_and_set_irq(struct bw_hwmon *hw, |
| unsigned int tol, unsigned int us) |
| { |
| unsigned long bw = 0; |
| unsigned long tempbw; |
| int cpu; |
| struct bwmon_data *data; |
| unsigned int sample_ms = hw->df->profile->polling_ms; |
| |
| spin_lock(&bw_lock); |
| on_each_cpu(get_beat_count, NULL, true); |
| for_each_possible_cpu(cpu) { |
| data = &per_cpu(gov_data, cpu); |
| |
| tempbw = beats_to_mbps(data->count, us); |
| data->limit = mbps_to_beats(tempbw, sample_ms, tol); |
| data->prev_rw_start_val = 0xFFFFFFFF - data->limit; |
| if (cpu_online(cpu)) |
| smp_call_function_single(cpu, mon_set_counter, |
| &(data->prev_rw_start_val), true); |
| bw += tempbw; |
| data->count = 0; |
| } |
| on_each_cpu(mon_enable, NULL, true); |
| spin_unlock(&bw_lock); |
| return bw; |
| } |
| |
| static void save_hotplugstate(void) |
| { |
| int cpu = smp_processor_id(); |
| struct bwmon_data *data; |
| |
| data = &per_cpu(gov_data, cpu); |
| percpu_bwirq_disable(NULL); |
| mon_disable(NULL); |
| data->saved_evcntr = read_pmxevcntr(); |
| data->count = mon_get_bw_count(data->prev_rw_start_val); |
| } |
| |
| static void restore_hotplugstate(void) |
| { |
| int cpu = smp_processor_id(); |
| u32 count; |
| struct bwmon_data *data; |
| |
| data = &per_cpu(gov_data, cpu); |
| percpu_bwirq_enable(NULL); |
| if (data->count != 0) |
| count = data->saved_evcntr; |
| else |
| count = data->prev_rw_start_val = 0xFFFFFFFF - data->limit; |
| mon_bw_init(&count); |
| mon_irq_enable(NULL); |
| mon_enable(NULL); |
| } |
| |
| static void save_pmstate(void) |
| { |
| int cpu = smp_processor_id(); |
| struct bwmon_data *data; |
| |
| data = &per_cpu(gov_data, cpu); |
| mon_disable(NULL); |
| data->saved_evcntr = read_pmxevcntr(); |
| } |
| |
| static void restore_pmstate(void) |
| { |
| int cpu = smp_processor_id(); |
| u32 count; |
| struct bwmon_data *data; |
| |
| data = &per_cpu(gov_data, cpu); |
| count = data->saved_evcntr; |
| mon_bw_init(&count); |
| mon_irq_enable(NULL); |
| mon_enable(NULL); |
| } |
| |
| static int pm_notif(struct notifier_block *nb, unsigned long action, |
| void *data) |
| { |
| switch (action) { |
| case CPU_PM_ENTER: |
| save_pmstate(); |
| break; |
| case CPU_PM_ENTER_FAILED: |
| case CPU_PM_EXIT: |
| restore_pmstate(); |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block bwmon_cpu_pm_nb = { |
| .notifier_call = pm_notif, |
| }; |
| |
| static int hotplug_notif(struct notifier_block *nb, unsigned long action, |
| void *data) |
| { |
| switch (action) { |
| case CPU_DYING: |
| spin_lock(&bw_lock); |
| save_hotplugstate(); |
| spin_unlock(&bw_lock); |
| break; |
| case CPU_STARTING: |
| spin_lock(&bw_lock); |
| restore_hotplugstate(); |
| spin_unlock(&bw_lock); |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block cpu_hotplug_nb = { |
| .notifier_call = hotplug_notif, |
| }; |
| |
| static int register_notifier(void) |
| { |
| int ret = 0; |
| |
| mutex_lock(&use_lock); |
| if (use_cnt == 0) { |
| ret = cpu_pm_register_notifier(&bwmon_cpu_pm_nb); |
| if (ret) |
| goto out; |
| ret = register_cpu_notifier(&cpu_hotplug_nb); |
| if (ret) { |
| cpu_pm_unregister_notifier(&bwmon_cpu_pm_nb); |
| goto out; |
| } |
| } |
| use_cnt++; |
| out: |
| mutex_unlock(&use_lock); |
| return ret; |
| } |
| |
| static void unregister_notifier(void) |
| { |
| mutex_lock(&use_lock); |
| if (use_cnt == 1) { |
| unregister_cpu_notifier(&cpu_hotplug_nb); |
| cpu_pm_unregister_notifier(&bwmon_cpu_pm_nb); |
| } else if (use_cnt == 0) { |
| pr_warn("Notifier ref count unbalanced\n"); |
| goto out; |
| } |
| use_cnt--; |
| out: |
| mutex_unlock(&use_lock); |
| } |
| |
| static void stop_bw_hwmon(struct bw_hwmon *hw) |
| { |
| unregister_notifier(); |
| on_each_cpu(mon_disable, NULL, true); |
| on_each_cpu(mon_irq_disable, NULL, true); |
| on_each_cpu(percpu_bwirq_disable, NULL, true); |
| free_percpu_irq(bw_irq, &gov_data); |
| } |
| |
| static int start_bw_hwmon(struct bw_hwmon *hw, unsigned long mbps) |
| { |
| u32 limit; |
| int cpu; |
| struct bwmon_data *data; |
| struct device *dev = hw->df->dev.parent; |
| int ret; |
| |
| ret = request_percpu_irq(bw_irq, mon_intr_handler, |
| "bw_hwmon", &gov_data); |
| if (ret) { |
| dev_err(dev, "Unable to register interrupt handler!\n"); |
| return ret; |
| } |
| |
| get_online_cpus(); |
| on_each_cpu(mon_bw_init, NULL, true); |
| on_each_cpu(mon_disable, NULL, true); |
| |
| ret = register_notifier(); |
| if (ret) { |
| pr_err("Unable to register notifier\n"); |
| return ret; |
| } |
| |
| limit = mbps_to_beats(mbps, hw->df->profile->polling_ms, 0); |
| limit /= num_online_cpus(); |
| |
| for_each_possible_cpu(cpu) { |
| data = &per_cpu(gov_data, cpu); |
| data->limit = limit; |
| data->prev_rw_start_val = 0xFFFFFFFF - data->limit; |
| } |
| |
| INIT_WORK(&irqwork, bwmon_work); |
| |
| on_each_cpu(percpu_bwirq_enable, NULL, true); |
| on_each_cpu(mon_irq_enable, NULL, true); |
| on_each_cpu(mon_enable, NULL, true); |
| put_online_cpus(); |
| return 0; |
| } |
| |
| static int armbw_pm_driver_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct bw_hwmon *bw; |
| int ret; |
| |
| bw = devm_kzalloc(dev, sizeof(*bw), GFP_KERNEL); |
| if (!bw) |
| return -ENOMEM; |
| bw->dev = dev; |
| |
| bw_irq = platform_get_irq(pdev, 0); |
| if (bw_irq < 0) { |
| pr_err("Unable to get IRQ number\n"); |
| return bw_irq; |
| } |
| |
| ret = of_property_read_u32(dev->of_node, "qcom,bytes-per-beat", |
| &bytes_per_beat); |
| |
| if (ret) { |
| pr_err("Unable to read bytes per beat\n"); |
| return ret; |
| } |
| |
| bw->start_hwmon = &start_bw_hwmon; |
| bw->stop_hwmon = &stop_bw_hwmon; |
| bw->meas_bw_and_set_irq = &measure_bw_and_set_irq; |
| globalhw = bw; |
| |
| ret = register_bw_hwmon(dev, bw); |
| if (ret) { |
| pr_err("CPUBW hwmon registration failed\n"); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static struct of_device_id match_table[] = { |
| { .compatible = "qcom,armbw-pm" }, |
| {} |
| }; |
| |
| static struct platform_driver armbw_pm_driver = { |
| .probe = armbw_pm_driver_probe, |
| .driver = { |
| .name = "armbw-pm", |
| .of_match_table = match_table, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init armbw_pm_init(void) |
| { |
| bw_wq = alloc_workqueue("armbw-pm-bwmon", WQ_HIGHPRI, 2); |
| return platform_driver_register(&armbw_pm_driver); |
| } |
| module_init(armbw_pm_init); |
| |
| static void __exit armbw_pm_exit(void) |
| { |
| platform_driver_unregister(&armbw_pm_driver); |
| destroy_workqueue(bw_wq); |
| } |
| module_exit(armbw_pm_exit); |