/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);