| /* |
| * linux/drivers/exynos/soc/samsung/exynos-emc.c |
| * |
| * Copyright (c) 2018 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/kthread.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/sched/types.h> |
| #include <linux/irq_work.h> |
| #include <linux/kobject.h> |
| #include <linux/cpufreq.h> |
| #include <linux/suspend.h> |
| #include <linux/cpuidle.h> |
| |
| #include <soc/samsung/exynos-cpuhp.h> |
| #include <soc/samsung/cal-if.h> |
| #include <soc/samsung/exynos-pmu.h> |
| #include <soc/samsung/exynos-emc.h> |
| #include <dt-bindings/soc/samsung/exynos-emc.h> |
| |
| #include <trace/events/power.h> |
| #include "../../cpufreq/exynos-acme.h" |
| #include "../../../kernel/sched/sched.h" |
| |
| #define DEFAULT_BOOT_ENABLE_MS (40000) /* 40 s */ |
| |
| #define EMC_EVENT_NUM 2 |
| enum emc_event { |
| /* mode change finished and then waiting new mode */ |
| EMC_WAITING_NEW_MODE = 0, |
| |
| /* dynimic mode change by sched event */ |
| EMC_DYNIMIC_MODE_CHANGE_STARTED = (0x1 << 0), |
| |
| /* static mode change by user or pm scenarios */ |
| EMC_STATIC_MODE_CHANGE_STARTED = (0x1 << 1), |
| }; |
| |
| struct emc_mode { |
| struct list_head list; |
| const char *name; |
| struct cpumask cpus; |
| struct cpumask boost_cpus; |
| unsigned int ldsum_thr; |
| unsigned int cal_id; |
| unsigned int max_freq; |
| unsigned int change_latency; |
| unsigned int enabled; |
| |
| /* kobject for sysfs group */ |
| struct kobject kobj; |
| }; |
| |
| struct emc_domain { |
| struct list_head list; |
| const char *name; |
| struct cpumask cpus; |
| unsigned int role; |
| unsigned int cpu_heavy_thr; |
| unsigned int cpu_idle_thr; |
| unsigned int busy_ratio; |
| |
| unsigned long load; |
| unsigned long max; |
| |
| /* kobject for sysfs group */ |
| struct kobject kobj; |
| }; |
| |
| struct emc { |
| unsigned int enabled; |
| int blocked; |
| bool boostable; |
| unsigned int event; |
| unsigned int ctrl_type; |
| |
| struct list_head domains; |
| struct list_head modes; |
| |
| struct emc_mode *cur_mode; /* current mode */ |
| struct emc_mode *req_mode; /* requested mode */ |
| struct emc_mode *user_mode; /* user requesting mode */ |
| unsigned int in_progress; |
| struct cpumask heavy_cpus; /* cpus need to boost */ |
| struct cpumask busy_cpus; /* cpus need to online */ |
| /* loadsum of boostable and trigger domain */ |
| unsigned int ldsum; |
| |
| /* member for mode change */ |
| struct task_struct *task; |
| struct irq_work irq_work; |
| struct hrtimer timer; |
| wait_queue_head_t wait_q; |
| |
| /* member for max freq control */ |
| struct cpumask pre_cpu_mask; |
| struct cpumask pwr_cpu_mask; |
| unsigned long max_freq; |
| |
| /* member for sysfs */ |
| struct kobject kobj; |
| struct mutex attrib_lock; |
| |
| } emc; |
| |
| static DEFINE_SPINLOCK(emc_lock); |
| static DEFINE_MUTEX(emc_const_lock); |
| DEFINE_RAW_SPINLOCK(emc_load_lock); |
| /**********************************************************************************/ |
| /* Helper */ |
| /**********************************************************************************/ |
| /* return base mode. base mode is default and lowest boost mode */ |
| static struct emc_mode* emc_get_base_mode(void) |
| { |
| return list_first_entry(&emc.modes, struct emc_mode, list); |
| } |
| |
| /* return matches arg mask with mode->cpus */ |
| static struct emc_mode* emc_find_mode(struct cpumask *mask) |
| { |
| struct emc_mode *mode; |
| |
| list_for_each_entry(mode, &emc.modes, list) |
| if (cpumask_equal(&mode->cpus, mask)) |
| return mode; |
| |
| /* if don,t find any matehd mode, return base mode */ |
| return emc_get_base_mode(); |
| } |
| |
| /* return domain including arg cpu */ |
| static struct emc_domain* emc_find_domain(unsigned int cpu) |
| { |
| struct emc_domain *domain; |
| |
| list_for_each_entry(domain, &emc.domains, list) |
| if (cpumask_test_cpu(cpu, &domain->cpus)) |
| return domain; |
| |
| return NULL; /* error */ |
| } |
| |
| /* return boost domain */ |
| static struct emc_domain* emc_get_boost_domain(void) |
| { |
| /* HACK : Supports only one boostable domain */ |
| return list_last_entry(&emc.domains, struct emc_domain, list); |
| } |
| |
| /* update cpu capacity for scheduler */ |
| static int emc_update_capacity(struct cpumask *mask) |
| { |
| struct sched_domain *sd; |
| struct cpumask temp; |
| int cpu; |
| |
| rcu_read_lock(); |
| |
| cpumask_and(&temp, mask, cpu_active_mask); |
| if (cpumask_empty(&temp)) |
| goto exit; |
| cpu = cpumask_first(&temp); |
| |
| sd = rcu_dereference(per_cpu(sd_ea, cpu)); |
| if (!sd) |
| goto exit; |
| |
| while (sd->child) |
| sd = sd->child; |
| |
| update_group_capacity(sd, cpu); |
| |
| exit: |
| rcu_read_unlock(); |
| |
| return 0; |
| } |
| |
| void emc_check_available_freq(struct cpumask *cpus, unsigned int target_freq) |
| { |
| unsigned int max_freq; |
| struct emc_domain *domain = emc_get_boost_domain(); |
| int cpu = cpumask_first(cpus); |
| struct cpumask online_mask; |
| struct emc_mode *mode; |
| |
| cpumask_copy(&online_mask, cpu_online_mask); |
| mode = emc_find_mode(&online_mask); |
| |
| if (!cpumask_equal(cpus, &domain->cpus)) |
| return; |
| |
| if (mode) |
| max_freq = mode->max_freq; |
| else |
| max_freq = emc_get_base_mode()->max_freq; |
| |
| if (target_freq > max_freq) |
| panic("cpu%d target_freq(%d) is higher than max_freq(%d, mode %s)\n", |
| cpu, target_freq, max_freq, mode->name); |
| } |
| |
| /* check policy->max constaints and real clock violation */ |
| int emc_verify_constraints(void) |
| { |
| struct cpufreq_policy *policy; |
| struct emc_domain *domain; |
| unsigned int cpu, cur_freq; |
| |
| domain = emc_get_boost_domain(); |
| cpu = cpumask_first(&domain->cpus); |
| |
| policy = cpufreq_cpu_get(cpu); |
| if (!policy) { |
| pr_warn("EMC: can't get the policy of cpu %d\n", cpu); |
| goto check_real_freq; |
| } |
| |
| /* check policy max */ |
| if (policy->max > emc.max_freq) { |
| cpufreq_cpu_put(policy); |
| pr_warn("EMC: constraints isn't yet applyied(emc_max(%lu) < cur_max(%d)\n", |
| emc.max_freq, policy->max); |
| return 0; |
| } |
| cpufreq_cpu_put(policy); |
| |
| check_real_freq: |
| /* check whether real cpu freq within max constraints or not */ |
| cur_freq = exynos_cpufreq_get_locked(cpu); |
| if (cur_freq > emc.max_freq) { |
| pr_warn("EMC(%s: cur freq(%d) is higher than max(%lu), retring...\n", |
| __func__, cur_freq, emc.max_freq); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * return highest boost frequency |
| */ |
| unsigned int exynos_pstate_get_boost_freq(int cpu) |
| { |
| struct emc_domain *domain = emc_get_boost_domain(); |
| |
| if (!cpumask_test_cpu(cpu, &domain->cpus)) |
| return 0; |
| |
| return list_last_entry(&emc.modes, struct emc_mode, list)->max_freq; |
| } |
| |
| /**********************************************************************************/ |
| /* Update Load */ |
| /**********************************************************************************/ |
| /* update cpu and domain load */ |
| static int emc_update_load(void) |
| { |
| struct emc_domain *domain; |
| int cpu; |
| |
| list_for_each_entry(domain, &emc.domains, list) { |
| domain->load = 0; |
| domain->max = 0; |
| for_each_cpu_and(cpu, &domain->cpus, cpu_online_mask) { |
| struct rq *rq = cpu_rq(cpu); |
| struct sched_avg *sa = &rq->cfs.avg; |
| unsigned long load; |
| |
| domain->max += (rq->cpu_capacity_orig); |
| if (sa->util_avg > rq->cpu_capacity_orig) |
| load = rq->cpu_capacity_orig; |
| else |
| load = sa->util_avg; |
| |
| domain->load += load; |
| |
| trace_emc_cpu_load(cpu, load, rq->cpu_capacity_orig); |
| } |
| trace_emc_domain_load(domain->name, domain->load, domain->max); |
| } |
| |
| return 0; |
| } |
| |
| /* update domains's cpus status whether busy or idle */ |
| static int emc_update_domain_status(struct emc_domain *domain) |
| { |
| struct cpumask heavy_cpus, busy_cpus, idle_cpus; |
| int cpu; |
| |
| cpumask_clear(&heavy_cpus); |
| cpumask_clear(&busy_cpus); |
| cpumask_clear(&idle_cpus); |
| emc.ldsum = 0; |
| |
| /* |
| * Takes offline core like idle core |
| * IDLE_CPU : util_avg < domain->cpu_idle_thr |
| * BUSY_CPU : domain->cpu_idle_thr <= util_avg < domain->cpu_heavy_thr |
| * HEAVY_CPU : util_avg >= domain->cpu_heavy_thr |
| */ |
| for_each_cpu_and(cpu, &domain->cpus, cpu_online_mask) { |
| struct rq *rq = cpu_rq(cpu); |
| struct sched_avg *sa = &rq->cfs.avg; |
| |
| if (sa->util_avg >= domain->cpu_heavy_thr) { |
| cpumask_set_cpu(cpu, &heavy_cpus); |
| cpumask_set_cpu(cpu, &busy_cpus); |
| emc.ldsum += sa->util_avg; |
| } else if (sa->util_avg >= domain->cpu_idle_thr) { |
| cpumask_set_cpu(cpu, &busy_cpus); |
| emc.ldsum += sa->util_avg; |
| } else |
| cpumask_set_cpu(cpu, &idle_cpus); |
| } |
| |
| /* domain cpus status updated system cpus mask */ |
| cpumask_or(&emc.heavy_cpus, &emc.heavy_cpus, &heavy_cpus); |
| cpumask_or(&emc.busy_cpus, &emc.busy_cpus, &busy_cpus); |
| |
| trace_emc_domain_status(domain->name, |
| *(unsigned int *)cpumask_bits(&emc.heavy_cpus), |
| *(unsigned int *)cpumask_bits(&emc.busy_cpus), |
| *(unsigned int *)cpumask_bits(&heavy_cpus), |
| *(unsigned int *)cpumask_bits(&busy_cpus)); |
| |
| return 0; |
| } |
| |
| /* |
| * update imbalance_heavy_cpus & busy_cpus_mask |
| * and return true if there is status change |
| */ |
| static bool emc_update_system_status(void) |
| { |
| struct emc_domain *domain; |
| struct cpumask prev_heavy_cpus, prev_busy_cpus; |
| |
| /* back up prev mode and clear */ |
| cpumask_copy(&prev_heavy_cpus, &emc.heavy_cpus); |
| cpumask_copy(&prev_busy_cpus, &emc.busy_cpus); |
| cpumask_clear(&emc.heavy_cpus); |
| cpumask_clear(&emc.busy_cpus); |
| |
| /* update system status */ |
| list_for_each_entry(domain, &emc.domains, list) |
| emc_update_domain_status(domain); |
| |
| trace_emc_update_system_status(*(unsigned int *)cpumask_bits(&prev_heavy_cpus), |
| *(unsigned int *)cpumask_bits(&prev_busy_cpus), |
| *(unsigned int *)cpumask_bits(&emc.heavy_cpus), |
| *(unsigned int *)cpumask_bits(&emc.busy_cpus)); |
| /* |
| * Check whether prev_cpus status and latest_cpus status is different or not, |
| * if it is different, return true. true means we should check whether there is |
| * more adaptive mode or not |
| */ |
| if (!cpumask_equal(&prev_busy_cpus, &emc.busy_cpus) || |
| !cpumask_equal(&prev_heavy_cpus, &emc.heavy_cpus)) |
| return true; |
| |
| return false; |
| } |
| |
| /**********************************************************************************/ |
| /* MODE SELECTION */ |
| /**********************************************************************************/ |
| static int emc_domain_busy(struct emc_domain *domain) |
| { |
| if (domain->load > ((domain->max * domain->busy_ratio) / 100)) |
| return true;; |
| |
| return false; |
| } |
| |
| static bool emc_system_busy(void) |
| { |
| struct emc_domain *domain; |
| struct cpumask mask; |
| |
| list_for_each_entry(domain, &emc.domains, list) { |
| /* if all cpus of domain was hotplug out, skip */ |
| cpumask_and(&mask, &domain->cpus, cpu_online_mask); |
| if (cpumask_empty(&mask)) |
| continue; |
| |
| if (domain->busy_ratio && !emc_domain_busy(domain)) { |
| trace_emc_domain_busy(domain->name, domain->load, false); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * return true when system has boostable cpu |
| * To has boostable cpu, boostable domains must has heavy cpu |
| */ |
| static bool emc_has_boostable_cpu(void) |
| { |
| struct emc_domain *domain; |
| |
| domain = emc_get_boost_domain(); |
| |
| return cpumask_intersects(&emc.heavy_cpus, &domain->cpus); |
| } |
| |
| static struct emc_mode* emc_select_mode(void) |
| { |
| struct emc_mode *mode, *target_mode = NULL; |
| int need_online_cnt; |
| |
| /* if there is no boostable cpu, we don't need to booting */ |
| if (!emc_has_boostable_cpu()) |
| return emc_get_base_mode(); |
| |
| /* |
| * need_online_cnt: number of cpus that need online |
| */ |
| need_online_cnt = cpumask_weight(&emc.busy_cpus); |
| |
| /* In reverse order to find the most boostable mode */ |
| list_for_each_entry_reverse(mode, &emc.modes, list) { |
| if (!mode->enabled) |
| continue; |
| /* if ldsum_thr is 0, it means ldsum is disabled */ |
| if (!mode->ldsum_thr && emc.ldsum <= mode->ldsum_thr) { |
| target_mode = mode; |
| break; |
| } |
| if (need_online_cnt > cpumask_weight(&mode->boost_cpus)) |
| continue; |
| target_mode = mode; |
| break; |
| } |
| |
| if (!target_mode) |
| target_mode = emc_get_base_mode(); |
| |
| trace_emc_select_mode(target_mode->name, need_online_cnt); |
| |
| return target_mode; |
| } |
| |
| /* return latest adaptive mode */ |
| static struct emc_mode* emc_get_mode(bool updated) |
| { |
| /* |
| * if system is busy overall, return base mode. |
| * becuase system is busy, maybe base mode will be |
| * the best performance |
| */ |
| if (emc_system_busy()) |
| return emc_get_base_mode(); |
| |
| /* |
| * if system isn't busy overall and no status updated, |
| * keep previous mode |
| */ |
| if (!updated) |
| return emc.req_mode; |
| |
| /* if not all above, try to find more adaptive mode */ |
| return emc_select_mode(); |
| } |
| |
| /**********************************************************************************/ |
| /* Mode Change */ |
| /**********************************************************************************/ |
| /* static mode change function */ |
| static void emc_set_mode(struct emc_mode *target_mode) |
| { |
| unsigned long flags; |
| |
| if (hrtimer_active(&emc.timer)) |
| hrtimer_cancel(&emc.timer); |
| |
| spin_lock_irqsave(&emc_lock, flags); |
| emc.event = emc.event | EMC_STATIC_MODE_CHANGE_STARTED; |
| emc.req_mode = target_mode; |
| spin_unlock_irqrestore(&emc_lock, flags); |
| |
| wake_up(&emc.wait_q); |
| |
| return; |
| } |
| |
| static void emc_request_mode_change(struct emc_mode *target_mode) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&emc_lock, flags); |
| emc.req_mode = target_mode; |
| spin_unlock_irqrestore(&emc_lock, flags); |
| |
| irq_work_queue(&emc.irq_work); |
| } |
| |
| static void emc_irq_work(struct irq_work *irq_work) |
| { |
| /* |
| * If req_mode is changed before mode change latency, |
| * cancel requesting mode change |
| */ |
| if (hrtimer_active(&emc.timer)) |
| hrtimer_cancel(&emc.timer); |
| |
| /* if req_mode and cur_mode is same, skip the mode change */ |
| if (emc.req_mode == emc.cur_mode) |
| return; |
| |
| trace_emc_start_timer(emc.req_mode->name, emc.req_mode->change_latency); |
| |
| /* emc change applying req_mode after keeps same mode as change_latency */ |
| hrtimer_start(&emc.timer, |
| ms_to_ktime(emc.req_mode->change_latency), |
| HRTIMER_MODE_REL); |
| } |
| |
| static enum hrtimer_restart emc_mode_change_func(struct hrtimer *timer) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&emc_lock, flags); |
| emc.event = emc.event | EMC_DYNIMIC_MODE_CHANGE_STARTED; |
| spin_unlock_irqrestore(&emc_lock, flags); |
| |
| /* wake up mode change task */ |
| wake_up(&emc.wait_q); |
| return HRTIMER_NORESTART; |
| } |
| |
| static unsigned int emc_clear_event(void) |
| { |
| int i; |
| |
| /* find pending wake-up event */ |
| for (i = 0; i < EMC_EVENT_NUM; i++) |
| if (emc.event & (0x1 << i)) |
| break; |
| |
| /* clear event */ |
| emc.event = emc.event & ~(0x1 << i); |
| |
| return (0x1 << i); |
| } |
| |
| /* mode change function */ |
| static int emc_do_mode_change(void *data) |
| { |
| unsigned long flags; |
| |
| while (1) { |
| unsigned int event; |
| |
| wait_event(emc.wait_q, emc.event || kthread_should_park()); |
| if (kthread_should_park()) |
| break; |
| |
| spin_lock_irqsave(&emc_lock, flags); |
| emc.in_progress = 1; |
| event = emc_clear_event(); |
| |
| trace_emc_do_mode_change(emc.cur_mode->name, |
| emc.req_mode->name, emc.event); |
| |
| emc.cur_mode = emc.req_mode; |
| spin_unlock_irqrestore(&emc_lock, flags); |
| |
| /* request mode change */ |
| exynos_cpuhp_request("EMC", emc.cur_mode->cpus, emc.ctrl_type); |
| |
| dbg_snapshot_printk("EMC: mode change finished %s (cpus%d)\n", |
| emc.cur_mode->name, cpumask_weight(&emc.cur_mode->cpus)); |
| emc.in_progress = 0; |
| } |
| |
| return 0; |
| } |
| |
| void exynos_emc_update(int cpu) |
| { |
| unsigned long flags; |
| struct emc_mode *target_mode; |
| bool updated; |
| |
| if (!emc.enabled || emc.blocked > 0) |
| return; |
| |
| /* little cpus sikp updating bt mode */ |
| if (cpumask_test_cpu(cpu, cpu_coregroup_mask(0))) |
| return; |
| |
| if (!raw_spin_trylock_irqsave(&emc_load_lock, flags)) |
| return; |
| |
| /* if user set user_mode, always return user mode */ |
| if (unlikely(emc.user_mode)) { |
| target_mode = emc.user_mode; |
| goto skip_load_check; |
| } |
| |
| /* if current system is not boostable, always uses base_mode */ |
| if (!emc.boostable) { |
| target_mode = emc_get_base_mode(); |
| goto skip_load_check; |
| } |
| |
| /* update sched_load */ |
| emc_update_load(); |
| |
| /* update cpus status whether cpu is busy or heavy */ |
| updated = emc_update_system_status(); |
| |
| /* get mode */ |
| target_mode = emc_get_mode(updated); |
| |
| skip_load_check: |
| /* request mode */ |
| if (emc.req_mode != target_mode) |
| emc_request_mode_change(target_mode); |
| |
| raw_spin_unlock_irqrestore(&emc_load_lock, flags); |
| } |
| |
| /**********************************************************************************/ |
| /* Max Frequency Control */ |
| /**********************************************************************************/ |
| static int emc_update_domain_const(struct emc_domain *domain) |
| { |
| unsigned long timeout = jiffies + msecs_to_jiffies(1000); |
| struct cpumask mask; |
| int cpu; |
| |
| /* If there is no online cpu on the domain, skip policy update */ |
| cpumask_and(&mask, &domain->cpus, cpu_online_mask); |
| if (!cpumask_weight(&mask)) |
| return 0; |
| cpu = cpumask_first(&mask); |
| |
| /* if max constraints is not changed util 50ms, cancel cpu_up */ |
| cpufreq_update_policy(cpu); |
| while (!emc_verify_constraints()) { |
| cpufreq_update_policy(cpu); |
| if (time_after(jiffies, timeout)) { |
| panic("EMC: failed to update domain(cpu%d) constraints\n", cpu); |
| return -EBUSY; |
| } |
| udelay(100); |
| } |
| |
| /* update capacity for scheduler */ |
| emc_update_capacity(&mask); |
| |
| return 0; |
| } |
| |
| /* update constraints base on pre_cpu_mask */ |
| int emc_update_constraints(void) |
| { |
| struct emc_domain *domain; |
| int ret = 0; |
| |
| /* update max constraint of all domains related this cpu power buget */ |
| list_for_each_entry(domain, &emc.domains, list) { |
| if (domain->role & BOOSTER) |
| ret = emc_update_domain_const(domain); |
| if (ret) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void emc_set_const(struct cpumask *mask) |
| { |
| struct emc_mode *mode; |
| |
| mutex_lock(&emc_const_lock); |
| |
| mode = emc_find_mode(mask); |
| |
| if (mode->max_freq == emc.max_freq) |
| goto skip_update_const; |
| |
| emc.max_freq = mode->max_freq; |
| emc_update_constraints(); |
| |
| skip_update_const: |
| mutex_unlock(&emc_const_lock); |
| } |
| |
| static unsigned long emc_get_const(void) |
| { |
| if (!emc.enabled || emc.blocked > 0) |
| return emc_get_base_mode()->max_freq; |
| |
| return emc.max_freq; |
| } |
| |
| static int cpufreq_policy_notifier(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| struct cpufreq_policy *policy = data; |
| unsigned long max_freq; |
| struct emc_domain *domain = emc_find_domain(policy->cpu); |
| |
| if (!(domain->role & BOOSTER)) |
| return 0; |
| |
| switch (event) { |
| case CPUFREQ_ADJUST: |
| /* It updates max frequency of policy */ |
| max_freq = emc_get_const(); |
| |
| if (policy->max != max_freq) |
| cpufreq_verify_within_limits(policy, 0, max_freq); |
| |
| break; |
| case CPUFREQ_NOTIFY: |
| /* It updates boostable flag */ |
| if (policy->max < emc_get_base_mode()->max_freq) |
| emc.boostable = false; |
| else |
| emc.boostable = true; |
| break; |
| } |
| |
| return 0; |
| } |
| /* Notifier for cpufreq policy change */ |
| static struct notifier_block emc_policy_nb = { |
| .notifier_call = cpufreq_policy_notifier, |
| }; |
| |
| /* |
| * emc_update_cpu_pwr controls constraints acording |
| * to mode matched cpu power status to be changed. |
| * so, following function call order shouled be followed. |
| * |
| * CPU POWER ON Scenario : Call this function -> CPU_POWER_ON |
| * CPU POWER OFF Scenario : CPU_POWER_OFF -> Call this function |
| */ |
| static int emc_update_cpu_pwr(unsigned int cpu, bool on) |
| { |
| unsigned long timeout = jiffies + msecs_to_jiffies(100); |
| |
| /* |
| * check acutal cpu power. this function shuouled be call |
| * before power on or after power off |
| */ |
| while (exynos_cpu.power_state(cpu)) { |
| if (time_after(jiffies, timeout)) |
| panic("CPU%d %s power %s!\n", |
| cpu, on? "already" : "not yet", on? "on" : "off"); |
| |
| udelay(100); |
| } |
| |
| if (on) |
| cpumask_set_cpu(cpu, &emc.pwr_cpu_mask); |
| else |
| cpumask_clear_cpu(cpu, &emc.pwr_cpu_mask); |
| |
| trace_emc_update_cpu_pwr( |
| *(unsigned int *)cpumask_bits(&emc.pwr_cpu_mask), cpu, on); |
| |
| emc_set_const(&emc.pwr_cpu_mask); |
| |
| return 0; |
| } |
| |
| static int emc_pre_update_constraints(void) |
| { |
| emc_set_const(&emc.pre_cpu_mask); |
| |
| return 0; |
| } |
| |
| static int emc_update_pre_mask(unsigned int cpu, bool on) |
| { |
| struct cpumask temp; |
| struct emc_domain *domain = emc_get_boost_domain(); |
| |
| if (on) |
| cpumask_set_cpu(cpu, &emc.pre_cpu_mask); |
| else |
| cpumask_clear_cpu(cpu, &emc.pre_cpu_mask); |
| |
| /* |
| * If all cpus of boost cluster will be power down, |
| * change constraints before cpufreq is not working |
| */ |
| cpumask_and(&temp, &emc.pre_cpu_mask, &domain->cpus); |
| if (cpumask_empty(&temp)) |
| emc_pre_update_constraints(); |
| |
| return 0; |
| } |
| |
| /* |
| * emc_hp_callback MUST CALL emc_update_cpu_pwr |
| * to control max constraints and MUST KEEP following orders. |
| * |
| * hotplug OUT: cpu power DOWN -> Call emc_update_cpu_pwr |
| * hotplug IN : Call emc_update_cpu_pwr -> cpu power UP |
| */ |
| static int emc_cpu_on_callback(unsigned int cpu) |
| { |
| if (emc_update_cpu_pwr(cpu, true)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| int emc_cpu_pre_on_callback(unsigned int cpu) |
| { |
| emc_update_pre_mask(cpu, true); |
| return 0; |
| } |
| |
| static int emc_cpu_off_callback(unsigned int cpu) |
| { |
| if (emc_update_cpu_pwr(cpu, false)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| int emc_cpu_pre_off_callback(unsigned int cpu) |
| { |
| emc_update_pre_mask(cpu, false); |
| return 0; |
| } |
| |
| /**********************************************************************************/ |
| /* SYSFS */ |
| /**********************************************************************************/ |
| #define to_emc(k) container_of(k, struct emc, kobj) |
| #define to_domain(k) container_of(k, struct emc_domain, kobj) |
| #define to_mode(k) container_of(k, struct emc_mode, kobj) |
| |
| #define emc_show(file_name, object) \ |
| static ssize_t show_##file_name \ |
| (struct kobject *kobj, char *buf) \ |
| { \ |
| struct emc *emc = to_emc(kobj); \ |
| \ |
| return sprintf(buf, "%u\n", emc->object); \ |
| } |
| |
| #define emc_store(file_name, object) \ |
| static ssize_t store_##file_name \ |
| (struct kobject *kobj, const char *buf, size_t count) \ |
| { \ |
| int ret; \ |
| unsigned int val; \ |
| struct emc *emc = to_emc(kobj); \ |
| \ |
| ret = kstrtoint(buf, 10, &val); \ |
| if (ret) \ |
| return -EINVAL; \ |
| \ |
| emc->object = val; \ |
| \ |
| return ret ? ret : count; \ |
| } |
| |
| #define emc_domain_show(file_name, object) \ |
| static ssize_t show_##file_name \ |
| (struct kobject *kobj, char *buf) \ |
| { \ |
| struct emc_domain *domain = to_domain(kobj); \ |
| \ |
| return sprintf(buf, "%u\n", domain->object); \ |
| } |
| |
| #define emc_domain_store(file_name, object) \ |
| static ssize_t store_##file_name \ |
| (struct kobject *kobj, const char *buf, size_t count) \ |
| { \ |
| int ret; \ |
| unsigned int val; \ |
| struct emc_domain *domain = to_domain(kobj); \ |
| \ |
| ret = kstrtoint(buf, 10, &val); \ |
| if (ret) \ |
| return -EINVAL; \ |
| \ |
| domain->object = val; \ |
| \ |
| return ret ? ret : count; \ |
| } |
| |
| #define emc_mode_show(file_name, object) \ |
| static ssize_t show_##file_name \ |
| (struct kobject *kobj, char *buf) \ |
| { \ |
| struct emc_mode *mode = to_mode(kobj); \ |
| \ |
| return sprintf(buf, "%u\n", mode->object); \ |
| } |
| |
| #define emc_mode_store(file_name, object) \ |
| static ssize_t store_##file_name \ |
| (struct kobject *kobj, const char *buf, size_t count) \ |
| { \ |
| int ret; \ |
| unsigned int val; \ |
| struct emc_mode *mode = to_mode(kobj); \ |
| \ |
| ret = kstrtoint(buf, 10, &val); \ |
| if (ret) \ |
| return -EINVAL; \ |
| \ |
| mode->object = val; \ |
| \ |
| return ret ? ret : count; \ |
| } |
| |
| emc_show(enabled, enabled); |
| emc_show(ctrl_type, ctrl_type); |
| emc_show(boostable, boostable); |
| |
| emc_domain_store(cpu_heavy_thr, cpu_heavy_thr); |
| emc_domain_store(cpu_idle_thr, cpu_idle_thr); |
| emc_domain_store(busy_ratio, busy_ratio); |
| emc_domain_show(cpu_heavy_thr, cpu_heavy_thr); |
| emc_domain_show(cpu_idle_thr, cpu_idle_thr); |
| emc_domain_show(busy_ratio, busy_ratio); |
| |
| emc_mode_store(max_freq, max_freq); |
| emc_mode_store(change_latency, change_latency); |
| emc_mode_store(ldsum_thr, ldsum_thr); |
| emc_mode_store(mode_enabled, enabled); |
| emc_mode_show(max_freq, max_freq); |
| emc_mode_show(change_latency, change_latency); |
| emc_mode_show(ldsum_thr, ldsum_thr); |
| emc_mode_show(mode_enabled, enabled); |
| |
| static int emc_set_enable(bool enable); |
| static ssize_t store_enabled(struct kobject *kobj, |
| const char *buf, size_t count) |
| { |
| int ret; |
| unsigned int val; |
| |
| ret = kstrtoint(buf, 10, &val); |
| if (ret) |
| return -EINVAL; |
| |
| if (val > 0) |
| ret = emc_set_enable(true); |
| else |
| ret = emc_set_enable(false); |
| |
| return ret ? ret : count; |
| } |
| |
| static void __emc_set_disable(void) |
| { |
| struct emc_mode *base_mode = emc_get_base_mode(); |
| |
| spin_lock(&emc_lock); |
| emc.enabled = false; |
| emc.user_mode = 0; |
| emc.cur_mode = emc.req_mode = base_mode; |
| emc.event = 0; |
| smp_wmb(); |
| spin_unlock(&emc_lock); |
| |
| exynos_cpuhp_request("EMC", |
| base_mode->cpus, emc.ctrl_type); |
| |
| pr_info("EMC: Stop hotplug governor\n"); |
| } |
| |
| static void __emc_set_enable(void) |
| { |
| struct emc_mode *base_mode = emc_get_base_mode(); |
| |
| spin_lock(&emc_lock); |
| emc.user_mode = 0; |
| emc.cur_mode = emc.req_mode = base_mode; |
| emc.event = 0; |
| emc.enabled = true; |
| smp_wmb(); |
| spin_unlock(&emc_lock); |
| |
| pr_info("EMC: Start hotplug governor\n"); |
| } |
| |
| static int emc_set_enable(bool enable) |
| { |
| int start = true; |
| |
| spin_lock(&emc_lock); |
| |
| if (enable) |
| if (emc.enabled) { |
| pr_info("EMC: Already enabled\n"); |
| spin_unlock(&emc_lock); |
| goto skip; |
| } else { |
| start = true; |
| } |
| else |
| if (emc.enabled) { |
| start = false; |
| } else { |
| pr_info("EMC: Already disabled\n"); |
| spin_unlock(&emc_lock); |
| goto skip; |
| } |
| |
| spin_unlock(&emc_lock); |
| |
| if (start) |
| __emc_set_enable(); |
| else |
| __emc_set_disable(); |
| |
| skip: |
| return 0; |
| } |
| static ssize_t store_ctrl_type(struct kobject *kobj, |
| const char *buf, size_t count) |
| { |
| int ret; |
| unsigned int val; |
| |
| ret = kstrtoint(buf, 10, &val); |
| if (ret) |
| return -EINVAL; |
| |
| emc.ctrl_type = val ? FAST_HP : 0; |
| |
| return ret ? ret : count; |
| } |
| |
| static ssize_t store_user_mode(struct kobject *kobj, |
| const char *buf, size_t count) |
| { |
| int ret, i = 0; |
| unsigned int val; |
| struct emc_mode *mode; |
| |
| ret = kstrtoint(buf, 10, &val); |
| if (ret) |
| return -EINVAL; |
| |
| /* Cancel or Disable user mode */ |
| if (!val) { |
| emc.user_mode = NULL; |
| mode = emc_get_base_mode(); |
| emc_set_mode(mode); |
| goto exit; |
| } |
| |
| list_for_each_entry_reverse(mode, &emc.modes, list) |
| if (++i == val) |
| goto check_mode; |
| |
| /* input is invalid */ |
| pr_info("Input value is invalid\n"); |
| goto exit; |
| |
| check_mode: |
| if (!mode->enabled) { |
| pr_info("%s mode is not enabled\n", mode->name); |
| goto exit; |
| } |
| |
| emc.user_mode = mode; |
| emc_set_mode(mode); |
| exit: |
| return ret ? ret : count; |
| } |
| |
| static ssize_t show_user_mode(struct kobject *kobj, char *buf) |
| { |
| int ret = 0, i = 0; |
| struct emc_mode *mode; |
| |
| if (emc.user_mode) |
| return sprintf(buf, "%s\n", emc.user_mode->name); |
| |
| /* If user_mode is not set, show avaiable user mode */ |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "Available mode> 0:DISABLE "); |
| |
| list_for_each_entry_reverse(mode, &emc.modes, list) |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "%d:%s ", ++i, mode->name); |
| |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); |
| |
| return ret; |
| } |
| |
| static ssize_t show_domain_name(struct kobject *kobj, char *buf) |
| { |
| struct emc_domain *domain = to_domain(kobj); |
| |
| return sprintf(buf, "%s\n", domain->name); |
| } |
| |
| static ssize_t show_mode_name(struct kobject *kobj, char *buf) |
| { |
| struct emc_mode *mode = to_mode(kobj); |
| |
| return sprintf(buf, "%s\n", mode->name); |
| } |
| |
| struct emc_attr { |
| struct attribute attr; |
| ssize_t (*show)(struct kobject *, char *); |
| ssize_t (*store)(struct kobject *, const char *, size_t count); |
| }; |
| |
| #define emc_attr_ro(_name) \ |
| static struct emc_attr _name = \ |
| __ATTR(_name, 0444, show_##_name, NULL) |
| |
| #define emc_attr_rw(_name) \ |
| static struct emc_attr _name = \ |
| __ATTR(_name, 0644, show_##_name, store_##_name) |
| |
| #define to_attr(a) container_of(a, struct emc_attr, attr) |
| |
| static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) |
| { |
| struct emc_attr *hattr = to_attr(attr); |
| ssize_t ret; |
| |
| mutex_lock(&emc.attrib_lock); |
| ret = hattr->show(kobj, buf); |
| mutex_unlock(&emc.attrib_lock); |
| |
| return ret; |
| } |
| |
| static ssize_t store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct emc_attr *hattr = to_attr(attr); |
| ssize_t ret = -EINVAL; |
| |
| mutex_lock(&emc.attrib_lock); |
| ret = hattr->store(kobj, buf, count); |
| mutex_unlock(&emc.attrib_lock); |
| |
| return ret; |
| } |
| |
| emc_attr_rw(enabled); |
| emc_attr_rw(ctrl_type); |
| emc_attr_ro(boostable); |
| emc_attr_rw(user_mode); |
| |
| emc_attr_ro(domain_name); |
| emc_attr_rw(cpu_heavy_thr); |
| emc_attr_rw(cpu_idle_thr); |
| emc_attr_rw(busy_ratio); |
| |
| emc_attr_ro(mode_name); |
| emc_attr_rw(max_freq); |
| emc_attr_rw(change_latency); |
| emc_attr_rw(ldsum_thr); |
| emc_attr_rw(mode_enabled); |
| |
| static struct attribute *emc_attrs[] = { |
| &enabled.attr, |
| &ctrl_type.attr, |
| &boostable.attr, |
| &user_mode.attr, |
| NULL |
| }; |
| |
| static struct attribute *emc_domain_attrs[] = { |
| &domain_name.attr, |
| &cpu_heavy_thr.attr, |
| &cpu_idle_thr.attr, |
| &busy_ratio.attr, |
| NULL |
| }; |
| |
| static struct attribute *emc_mode_attrs[] = { |
| &mode_name.attr, |
| &max_freq.attr, |
| &change_latency.attr, |
| &ldsum_thr.attr, |
| &mode_enabled.attr, |
| NULL |
| }; |
| |
| static const struct sysfs_ops emc_sysfs_ops = { |
| .show = show, |
| .store = store, |
| }; |
| |
| static struct kobj_type ktype_domain = { |
| .sysfs_ops = &emc_sysfs_ops, |
| .default_attrs = emc_attrs, |
| }; |
| |
| static struct kobj_type ktype_emc_domain = { |
| .sysfs_ops = &emc_sysfs_ops, |
| .default_attrs = emc_domain_attrs, |
| }; |
| |
| static struct kobj_type ktype_emc_mode = { |
| .sysfs_ops = &emc_sysfs_ops, |
| .default_attrs = emc_mode_attrs, |
| }; |
| |
| /**********************************************************************************/ |
| /* Initialization */ |
| /**********************************************************************************/ |
| static void emc_boot_enable(struct work_struct *work); |
| static DECLARE_DELAYED_WORK(emc_boot_work, emc_boot_enable); |
| static void emc_boot_enable(struct work_struct *work) |
| { |
| /* infom exynos-cpu-hotplug driver that hp governor is ready */ |
| emc.blocked--; |
| } |
| |
| static int emc_pm_suspend_notifier(struct notifier_block *notifier, |
| unsigned long pm_event, void *v) |
| { |
| struct emc_mode *mode = emc_get_base_mode(); |
| |
| if (pm_event != PM_SUSPEND_PREPARE) |
| return NOTIFY_OK; |
| |
| /* disable frequency boosting */ |
| emc.blocked++; |
| emc_set_const(&mode->cpus); |
| |
| if (!emc_verify_constraints()) |
| BUG_ON(1); |
| |
| return NOTIFY_OK; |
| } |
| |
| static int emc_pm_resume_notifier(struct notifier_block *notifier, |
| unsigned long pm_event, void *v) |
| { |
| if (pm_event != PM_POST_SUSPEND) |
| return NOTIFY_OK; |
| |
| /* restore frequency boosting */ |
| emc.blocked--; |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block emc_suspend_nb = { |
| .notifier_call = emc_pm_suspend_notifier, |
| .priority = INT_MAX, |
| }; |
| |
| static struct notifier_block emc_resume_nb = { |
| .notifier_call = emc_pm_resume_notifier, |
| .priority = INT_MIN, |
| }; |
| |
| static void emc_print_inform(void) |
| { |
| struct emc_mode *mode; |
| struct emc_domain *domain; |
| char buf[10]; |
| int i = 0; |
| |
| pr_info("EMC: Mode Information\n"); |
| pr_info("ctrl_type: %d\n", emc.ctrl_type); |
| |
| list_for_each_entry(mode, &emc.modes, list) { |
| pr_info("mode%d name: %s\n", i, mode->name); |
| scnprintf(buf, sizeof(buf), "%*pbl", |
| cpumask_pr_args(&mode->cpus)); |
| pr_info("mode%d cpus: %s\n", i, buf); |
| scnprintf(buf, sizeof(buf), "%*pbl", |
| cpumask_pr_args(&mode->boost_cpus)); |
| pr_info("mode%d boost_cpus: %s\n", i, buf); |
| pr_info("mode%d ldsum_thr: %u\n", i, mode->ldsum_thr); |
| pr_info("mode%d cal-id: %u\n", i, mode->cal_id); |
| pr_info("mode%d max_freq: %u\n", i, mode->max_freq); |
| pr_info("mode%d change_latency: %u\n", i, mode->change_latency); |
| pr_info("mode%d enabled: %u\n", i, mode->enabled); |
| i++; |
| } |
| |
| i = 0; |
| pr_info("EMC: Domain Information\n"); |
| list_for_each_entry(domain, &emc.domains, list) { |
| pr_info("domain%d name: %s\n", i, domain->name); |
| scnprintf(buf, sizeof(buf), "%*pbl", |
| cpumask_pr_args(&domain->cpus)); |
| pr_info("domain%d cpus: %s\n", i, buf); |
| pr_info("domain%d role: %u\n", i, domain->role); |
| pr_info("domain%d cpu_heavy_thr: %u\n", i, domain->cpu_heavy_thr); |
| pr_info("domain%d cpu_idle_thr: %u\n", i, domain->cpu_idle_thr); |
| pr_info("domain%d busy_ratio: %u\n", i, domain->busy_ratio); |
| i++; |
| } |
| |
| return; |
| } |
| |
| static int __init emc_parse_mode(struct device_node *dn) |
| { |
| struct emc_mode *mode; |
| const char *buf; |
| unsigned int val = UINT_MAX; |
| |
| mode = kzalloc(sizeof(struct emc_mode), GFP_KERNEL); |
| if (!mode) |
| return -ENOBUFS; |
| |
| if (of_property_read_string(dn, "mode_name", &mode->name)) |
| goto free; |
| |
| if (of_property_read_string(dn, "cpus", &buf)) |
| goto free; |
| if (cpulist_parse(buf, &mode->cpus)) |
| goto free; |
| |
| if (!of_property_read_string(dn, "boost_cpus", &buf)) |
| if (cpulist_parse(buf, &mode->boost_cpus)) |
| goto free; |
| |
| if (!of_property_read_u32(dn, "cal-id", &mode->cal_id)) |
| val = cal_dfs_get_max_freq(mode->cal_id); |
| if (of_property_read_u32(dn, "max_freq", &mode->max_freq)) |
| mode->max_freq = UINT_MAX; |
| mode->max_freq = min(val, mode->max_freq); |
| if (mode->max_freq == UINT_MAX) |
| goto free; |
| |
| if(of_property_read_u32(dn, "change_latency", &mode->change_latency)) |
| goto free; |
| |
| if(of_property_read_u32(dn, "ldsum_thr", &mode->ldsum_thr)) |
| mode->ldsum_thr = 0; |
| |
| if (of_property_read_u32(dn, "enabled", &mode->enabled)) |
| goto free; |
| |
| list_add_tail(&mode->list, &emc.modes); |
| |
| return 0; |
| free: |
| pr_warn("EMC: failed to parse emc mode\n"); |
| kfree(mode); |
| return -EINVAL; |
| } |
| |
| static int __init emc_parse_domain(struct device_node *dn) |
| { |
| struct emc_domain *domain; |
| const char *buf; |
| |
| domain = kzalloc(sizeof(struct emc_domain), GFP_KERNEL); |
| if (!domain) |
| return -ENOBUFS; |
| |
| if (of_property_read_string(dn, "domain_name", &domain->name)) |
| goto free; |
| |
| if (of_property_read_string(dn, "cpus", &buf)) |
| goto free; |
| if (cpulist_parse(buf, &domain->cpus)) |
| goto free; |
| |
| if (of_property_read_u32(dn, "role", &domain->role)) |
| goto free; |
| |
| if (of_property_read_u32(dn, "cpu_heavy_thr", &domain->cpu_heavy_thr)) |
| goto free; |
| |
| if (of_property_read_u32(dn, "cpu_idle_thr", &domain->cpu_idle_thr)) |
| goto free; |
| |
| if (of_property_read_u32(dn, "busy_ratio", &domain->busy_ratio)) |
| goto free; |
| |
| list_add_tail(&domain->list, &emc.domains); |
| |
| return 0; |
| free: |
| pr_warn("EMC: failed to parse emc domain\n"); |
| kfree(domain); |
| return -EINVAL; |
| } |
| |
| static int __init emc_parse_dt(void) |
| { |
| struct device_node *root, *dn, *child; |
| unsigned int temp; |
| |
| root = of_find_node_by_name(NULL, "exynos_mode_changer"); |
| if (!root) |
| return -EINVAL; |
| |
| if (of_property_read_u32(root, "enabled", &temp)) |
| goto failure; |
| if (!temp) { |
| pr_info("EMC: EMC disabled\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(root, "ctrl_type", &temp)) |
| goto failure; |
| if (temp) |
| emc.ctrl_type = FAST_HP; |
| |
| /* parse emce modes */ |
| INIT_LIST_HEAD(&emc.modes); |
| |
| dn = of_find_node_by_name(root, "emc_modes"); |
| if (!dn) |
| goto failure; |
| for_each_child_of_node(dn, child) |
| if(emc_parse_mode(child)) |
| goto failure; |
| |
| /* parse emce domains */ |
| INIT_LIST_HEAD(&emc.domains); |
| |
| dn = of_find_node_by_name(root, "emc_domains"); |
| if (!dn) |
| goto failure; |
| for_each_child_of_node(dn, child) |
| if(emc_parse_domain(child)) |
| goto failure; |
| |
| return 0; |
| failure: |
| pr_warn("EMC: failed to parse dt\n"); |
| return -EINVAL; |
| } |
| |
| static int __init emc_mode_change_func_init(void) |
| { |
| struct sched_param param; |
| struct emc_mode *base_mode = emc_get_base_mode(); |
| |
| /* register cpu mode control */ |
| if (exynos_cpuhp_register("EMC", base_mode->cpus, emc.ctrl_type)) { |
| pr_err("EMC: Failed to register cpu control \n"); |
| return -EINVAL; |
| } |
| |
| /* init timer */ |
| hrtimer_init(&emc.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| emc.timer.function = emc_mode_change_func; |
| |
| /* init workqueue */ |
| init_waitqueue_head(&emc.wait_q); |
| init_irq_work(&emc.irq_work, emc_irq_work); |
| |
| /* create hp task */ |
| emc.task = kthread_create(emc_do_mode_change, NULL, "exynos_emc"); |
| if (IS_ERR(emc.task)) { |
| pr_err("EMC: Failed to create emc_thread \n"); |
| return -EINVAL; |
| } |
| |
| param.sched_priority = 20; |
| sched_setscheduler_nocheck(emc.task, SCHED_FIFO, ¶m); |
| set_cpus_allowed_ptr(emc.task, cpu_coregroup_mask(0)); |
| |
| wake_up_process(emc.task); |
| |
| return 0; |
| } |
| |
| static int __init __emc_sysfs_init(void) |
| { |
| int ret; |
| |
| ret = kobject_init_and_add(&emc.kobj, &ktype_domain, |
| power_kobj, "emc"); |
| if (ret) { |
| pr_err("EMC: failed to init emc.kobj: %d\n", ret); |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int __init __emc_domain_sysfs_init(struct emc_domain *domain, int num) |
| { |
| int ret; |
| |
| ret = kobject_init_and_add(&domain->kobj, &ktype_emc_domain, |
| &emc.kobj, "domain%u", num); |
| if (ret) { |
| pr_err("EMC: failed to init domain->kobj: %d\n", ret); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int __init __emc_mode_sysfs_init(struct emc_mode *mode, int num) |
| { |
| int ret; |
| |
| ret = kobject_init_and_add(&mode->kobj, &ktype_emc_mode, |
| &emc.kobj, "mode%u", num); |
| if (ret) { |
| pr_err("EMC: failed to init mode->kobj: %d\n", ret); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int __init emc_sysfs_init(void) |
| { |
| struct emc_domain *domain; |
| struct emc_mode *mode; |
| int i = 0; |
| |
| /* init attrb_lock */ |
| mutex_init(&emc.attrib_lock); |
| |
| /* init emc_sysfs */ |
| if (__emc_sysfs_init()) |
| goto failure; |
| |
| /* init emc domain sysfs */ |
| list_for_each_entry(domain, &emc.domains, list) |
| if (__emc_domain_sysfs_init(domain, i++)) |
| goto failure; |
| |
| /* init emc mode sysfs */ |
| i = 0; |
| list_for_each_entry(mode, &emc.modes, list) |
| if (__emc_mode_sysfs_init(mode, i++)) |
| goto failure; |
| |
| return 0; |
| |
| failure: |
| return -1; |
| } |
| |
| static int __init emc_max_freq_control_init(void) |
| { |
| cpufreq_register_notifier(&emc_policy_nb, |
| CPUFREQ_POLICY_NOTIFIER); |
| |
| /* Initial pre_cpu_mask should be sync-up cpu_online_mask */ |
| cpumask_copy(&emc.pre_cpu_mask, cpu_online_mask); |
| cpumask_copy(&emc.pwr_cpu_mask, cpu_online_mask); |
| |
| cpuhp_setup_state_nocalls(CPUHP_EXYNOS_BOOST_CTRL_POST, |
| "exynos_boost_ctrl_post", |
| emc_cpu_on_callback, |
| emc_cpu_off_callback); |
| cpuhp_setup_state_nocalls(CPUHP_EXYNOS_BOOST_CTRL_PRE, |
| "exynos_boost_ctrl_pre", |
| emc_cpu_pre_on_callback, |
| emc_cpu_pre_off_callback); |
| |
| return 0; |
| } |
| |
| static bool __init emc_boostable(void) |
| { |
| struct emc_mode *mode; |
| unsigned int freq = 0; |
| |
| if (cpumask_weight(cpu_online_mask) != NR_CPUS) |
| return false; |
| |
| list_for_each_entry(mode, &emc.modes, list) |
| freq = max(freq, mode->max_freq); |
| if (freq > emc_get_base_mode()->max_freq) |
| return true; |
| |
| return false; |
| } |
| |
| static void __init emc_pm_init(void) |
| { |
| /* register pm notifier */ |
| register_pm_notifier(&emc_suspend_nb); |
| register_pm_notifier(&emc_resume_nb); |
| } |
| |
| static int __init emc_init(void) |
| { |
| /* parse dt */ |
| if (emc_parse_dt()) |
| goto failure; |
| |
| /* check whether system is boostable or not */ |
| if (!emc_boostable()) { |
| pr_info("EMC: Doesn't support boosting\n"); |
| return 0; |
| } |
| emc.boostable = true; |
| emc.max_freq = emc_get_base_mode()->max_freq; |
| |
| /* init sysfs */ |
| if (emc_sysfs_init()) |
| goto failure; |
| |
| /* init max frequency control */ |
| if (emc_max_freq_control_init()) |
| goto failure; |
| |
| /* init mode change func */ |
| if (emc_mode_change_func_init()) |
| goto failure; |
| |
| /* init pm */ |
| emc_pm_init(); |
| |
| /* show emc infromation */ |
| emc_print_inform(); |
| |
| /* enable */ |
| emc.blocked = 1; /* it is released after boot lock time */ |
| emc_set_enable(true); |
| |
| /* keep the base mode during boot time */ |
| schedule_delayed_work_on(0, &emc_boot_work, |
| msecs_to_jiffies(DEFAULT_BOOT_ENABLE_MS)); |
| |
| return 0; |
| |
| failure: |
| pr_warn("EMC: Initialization failed \n"); |
| return 0; |
| } subsys_initcall(emc_init); |