/* * Copyright (c) 2017 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * EXYNOS - AFM(Adaptive Frequency Manager) support * Auther : LEE DAEYEONG (daeyeong.lee@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 #include #include /* HTU */ #define OCPMCTL (0x0) #define OCPDEBOUNCER (0x10) #define OCPINTCTL (0x20) #define OCPTOPPWRTHRESH (0x48) #define OCPTHROTTCTL (0x6c) #define OCPTHROTTCNTA (0x80) #define OCPTHROTTPROFD (0x84) #define OCPTHROTTCNTM (0x118) #define GLOBALTHROTTEN (0x200) #define OCPFILTER (0x300) #define OCPMCTL_EN_VALUE (0x1) #define QACTIVEENABLE_CLK_VALUE (0x1 << 31) #define HIU1ST (0x1 << 5) #define OCPTHROTTERRA_VALUE (0x1 << 27) #define TCRST_MASK (0x7 << 21) #define TCRST_VALUE (0x5 << 21) #define TRW_VALUE (0x2 << 3) #define TEW_VALUE (0x0 << 2) #define TDT_MASK (0xfff << 20) #define TDT_VALUE (0x8 << 20) #define TDC_MASK (0xfff << 4) #define OCPTHROTTERRAEN_VALUE (0x1 << 1) #define TDCEN_VALUE (0x1 << 0) #define IRP_MASK (0x3f) #define IRP_SHIFT (24) #define OCPTHROTTCNTM_EN_VALUE (0x1 << 31) #define EN_MASK (0x1) #define RELEASE_MAX_LIMIT 0 #define SET_MAX_LIMIT 1 #define CLUSTER_OFF (-1) #define AFM_WARN_ENABLE 1 #define AFM_WARN_DISABLE 0 #define MAIN_PMIC 1 #define SUB_PMIC 2 #define BOOT_CPU 0 struct afm_stats { unsigned int max_state; unsigned int last_index; unsigned long long last_time; unsigned long long total_trans; unsigned int *freq_table; unsigned long long *time_in_state; }; struct afm_domain { bool enabled; bool flag; int irq; struct work_struct work; struct delayed_work delayed_work; void __iomem * base; unsigned int base_addr; struct i2c_client *i2c; struct afm_stats *stats; struct cpumask cpus; struct cpumask possible_cpus; unsigned int cpu; unsigned int clipped_freq; unsigned int max_freq; unsigned int max_freq_wo_afm; unsigned int min_freq; unsigned int cur_freq; unsigned int down_step; struct freq_qos_request max_qos_req; unsigned int release_duration; unsigned int throttle_cnt; unsigned int profile_started; unsigned int s2mps_afm_offset; struct kobject kobj; struct list_head list; unsigned int interrupt_src; struct cpumask interrupt_affinity; int (*pmic_update_reg)(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); int (*pmic_get_i2c)(struct i2c_client **i2c); }; struct exynos_afm_data { struct list_head afm_domain_list; }; static struct exynos_afm_data *afm_data; static int waiting_freq_change; static bool ocp_reactor_disabled; extern int main_pmic_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); extern int main_pmic_get_i2c(struct i2c_client **i2c); extern int sub_pmic_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); extern int sub_pmic_get_i2c(struct i2c_client **i2c); /****************************************************************/ /* HELPER FUNCTION */ /****************************************************************/ #define SYS_READ(base, reg, val) do { val = __raw_readl(base + reg); } while (0); #define SYS_WRITE(base, reg, val) do { __raw_writel(val, base + reg); } while (0); #define S2MPS_AFM_WARN_EN_SHIFT 7 static int control_afm_warn(struct afm_domain *afm_dom, int enable) { int ret; ret = afm_dom->pmic_update_reg(afm_dom->i2c, afm_dom->s2mps_afm_offset, ((enable) << S2MPS_AFM_WARN_EN_SHIFT), (1 << S2MPS_AFM_WARN_EN_SHIFT)); if (ret) pr_err("exynos-afm: cpu%d: Failed to control afm warn\n", afm_dom->cpu); return ret; } static int get_afm_freq_index(struct afm_stats *stats, unsigned int freq) { int index; for (index = 0; index < stats->max_state; index++) if (stats->freq_table[index] == freq) return index; return -1; } static void update_afm_stats(struct afm_domain *dom) { struct afm_stats *stats = dom->stats; unsigned long long cur_time = get_jiffies_64(); /* If OCP operation is disabled, do not update OCP stats */ if (dom->enabled == false) return; stats->time_in_state[stats->last_index] += cur_time - stats->last_time; stats->last_time = cur_time; } static unsigned int get_afm_target_max_limit(struct afm_domain *afm_dom) { struct afm_stats *stats = afm_dom->stats; unsigned int index, ret_freq; /* Find the position of the current frequency in the frequency table. */ index = get_afm_freq_index(stats, afm_dom->clipped_freq); /* Return itself */ ret_freq = stats->freq_table[index]; return ret_freq; } static void set_afm_max_limit(struct afm_domain *afm_dom, bool set_max_limit) { unsigned int target_max; if (afm_dom->cpu == CLUSTER_OFF) { dbg_snapshot_printk("OCP_cpus_off\n"); return; } /* * If the down step is greater than 0, * the target max limit is set to a frequency * that is lower than the current frequency by a down step. * Otherwise release the target max limit to max frequency. */ if (set_max_limit) { target_max = get_afm_target_max_limit(afm_dom); if (target_max) { afm_dom->clipped_freq = target_max; pr_debug("AFM : CPU %d max limit is set to %u kHz\n", afm_dom->cpu, afm_dom->clipped_freq); } else return; } else { afm_dom->clipped_freq = afm_dom->max_freq; pr_debug("AFM : CPU %d max limit is released\n", afm_dom->cpu); } dbg_snapshot_printk("[CPU %d]AFM_enter:%ukHz(%s)\n", afm_dom->cpu, afm_dom->clipped_freq, afm_dom->down_step ? "throttle" : "release"); freq_qos_update_request(&afm_dom->max_qos_req, afm_dom->clipped_freq); dbg_snapshot_printk("[CPU %d]AFM_exit:%ukHz(%s)\n", afm_dom->cpu, afm_dom->clipped_freq, afm_dom->down_step ? "throttle" : "release"); /* Whenever afm max limit is changed, afm stats should be updated. */ update_afm_stats(afm_dom); afm_dom->stats->last_index = get_afm_freq_index(afm_dom->stats, afm_dom->clipped_freq); afm_dom->stats->total_trans++; } #define CURRENT_METER_MODE (0) #define CPU_UTIL_MODE (1) #define BUCK2_COEFF (46875) #define ONE_HUNDRED (100) /* * Check BPC condition based on cpu load information. * If sum util_avg of each core is lower than configured ratio of capacity, * BPC condition is true. */ static bool is_bpc_condition(struct afm_domain *afm_dom) { /* TO DO if NEEDED */ return true; } static void control_afm_operation(struct afm_domain *afm_dom, bool enable) { if (afm_dom->enabled == enable) return; if (afm_dom->cpu == CLUSTER_OFF) { pr_info("all AFM releated cpus are off\n"); return; } /* * When enabling OCP operation, first enable OCP_WARN and release OCP max limit. * Conversely, when disabling OCP operation, first press OCP max limit and disable OCP_WARN. */ if (enable) { unsigned long long cur_time = get_jiffies_64(); control_afm_warn(afm_dom, AFM_WARN_ENABLE); /* Release OCP max limit */ afm_dom->clipped_freq = afm_dom->max_freq; freq_qos_update_request(&afm_dom->max_qos_req, afm_dom->clipped_freq); /* Re-init OCP stats */ afm_dom->stats->last_index = get_afm_freq_index(afm_dom->stats, afm_dom->clipped_freq); afm_dom->stats->last_time = cur_time; } else { cancel_delayed_work_sync(&afm_dom->delayed_work); afm_dom->flag = false; /* Update OCP stats before disabling OCP operation */ update_afm_stats(afm_dom); /* Press OCP max limit to max frequency without OCP */ afm_dom->clipped_freq = afm_dom->max_freq_wo_afm; freq_qos_update_request(&afm_dom->max_qos_req, afm_dom->clipped_freq); control_afm_warn(afm_dom, AFM_WARN_DISABLE); } afm_dom->enabled = enable; pr_info("AFM operation is %s\n", (enable)?"enabled":"disabled"); dbg_snapshot_printk("AFM_%s\n", (enable)?"enabled":"disabled"); } /****************************************************************/ /* AFM PROFILER */ /****************************************************************/ static void afm_profile_start_ipi(void *arg) { struct afm_domain *afm_dom = arg; SYS_WRITE(afm_dom->base, OCPTHROTTCNTM, (1 << 31)); } static void afm_profile_end_ipi(void *arg) { struct afm_domain *afm_dom = arg; SYS_READ(afm_dom->base, OCPTHROTTCNTM, afm_dom->throttle_cnt); afm_dom->throttle_cnt = afm_dom->throttle_cnt & ~(1 << 31); } static void afm_profile_start(struct afm_domain *afm_dom) { if (afm_dom->profile_started) { pr_err("AFM: CPU %d : profile is ongoing\n", afm_dom->cpu); return; } afm_dom->profile_started = true; smp_call_function_any(&afm_dom->cpus, afm_profile_start_ipi, afm_dom, 1); pr_info("AFM: CPU %d : Profile started\n", afm_dom->cpu); } static void afm_profile_end(struct afm_domain *afm_dom) { if (!afm_dom->profile_started) { pr_err("AFM: CPU %d : Profile is not ongoing\n", afm_dom->cpu); return; } afm_dom->profile_started = false; smp_call_function_any(&afm_dom->cpus, afm_profile_end_ipi, afm_dom, 1); pr_info("AFM: CPU %d : Profile finished", afm_dom->cpu); } /****************************************************************/ /* OCP INTERRUPT HANDLER */ /****************************************************************/ #define SWI_ENABLE (1) #define SWI_DISABLE (0) #define POLL_PERIOD (1000) static void control_afm_interrupt(struct afm_domain *afm_dom, int enable) { int val; SYS_READ(afm_dom->base, OCPTHROTTCNTA, val); if (enable) val |= OCPTHROTTERRAEN_VALUE; else val &= ~OCPTHROTTERRAEN_VALUE; SYS_WRITE(afm_dom->base, OCPTHROTTCNTA, val); } static void check_ocp_reactor_disabled(void *arg) { struct afm_domain *afm_dom = arg; int val; SYS_READ(afm_dom->base, OCPMCTL, val); if (!(val & EN_MASK)) ocp_reactor_disabled = true; } static void init_htu_register_ipi(void *arg) { struct afm_domain *afm_dom = arg; int irp, val; int reactor; irp = (afm_dom->max_freq * 2) / afm_dom->min_freq - 2; if (irp > IRP_MASK) irp = IRP_MASK; SYS_READ(afm_dom->base, OCPTOPPWRTHRESH, val); val &= ~(IRP_MASK << IRP_SHIFT); val |= (irp << IRP_SHIFT); /* disable OCP Controller before change IRP */ SYS_READ(afm_dom->base, OCPMCTL, reactor); reactor &= ~OCPMCTL_EN_VALUE; SYS_WRITE(afm_dom->base, OCPMCTL, reactor); SYS_WRITE(afm_dom->base, OCPTOPPWRTHRESH, val); /* enable OCP controller after change IRP */ reactor |= OCPMCTL_EN_VALUE; SYS_WRITE(afm_dom->base, OCPMCTL, reactor); control_afm_interrupt(afm_dom, SWI_ENABLE); } static int check_afm_interrupt(struct afm_domain *afm_dom) { int val; SYS_READ(afm_dom->base, OCPTHROTTCTL, val); val = val & OCPTHROTTERRA_VALUE; return val; } static void clear_afm_interrupt(struct afm_domain *afm_dom) { int val; SYS_READ(afm_dom->base, OCPTHROTTCTL, val); val |= OCPTHROTTERRA_VALUE; SYS_WRITE(afm_dom->base, OCPTHROTTCTL, val); val &= ~OCPTHROTTERRA_VALUE; SYS_WRITE(afm_dom->base, OCPTHROTTCTL, val); } static void clear_afm_throttling_duration_counter(struct afm_domain *afm_dom) { int val; SYS_READ(afm_dom->base, OCPTHROTTCNTA, val); val &= ~TDC_MASK; SYS_WRITE(afm_dom->base, OCPTHROTTCNTA, val); } static void exynos_afm_work(struct work_struct *work) { struct afm_domain *afm_dom = container_of(work, struct afm_domain, work); if (!cpumask_test_cpu(smp_processor_id(), &afm_dom->cpus)) return; afm_dom->flag = true; set_afm_max_limit(afm_dom, SET_MAX_LIMIT); if (waiting_freq_change) usleep_range(POLL_PERIOD, 2 * POLL_PERIOD); /* Before enabling OCP interrupt, clear releated register fields */ clear_afm_throttling_duration_counter(afm_dom); clear_afm_interrupt(afm_dom); /* After finish interrupt handling, enable OCP interrupt. */ control_afm_interrupt(afm_dom, SWI_ENABLE); cancel_delayed_work_sync(&afm_dom->delayed_work); schedule_delayed_work(&afm_dom->delayed_work, msecs_to_jiffies(afm_dom->release_duration)); } static void exynos_afm_work_release(struct work_struct *work) { struct delayed_work *dw = container_of(work, struct delayed_work, work); struct afm_domain *afm_dom = container_of(dw, struct afm_domain, delayed_work); /* * If BPC condition is true, release afm max limit. * Otherwise extend afm max limit as release duration. */ if (is_bpc_condition(afm_dom)) { afm_dom->flag = false; set_afm_max_limit(afm_dom, RELEASE_MAX_LIMIT); } else schedule_delayed_work(&afm_dom->delayed_work, msecs_to_jiffies(afm_dom->release_duration)); } static irqreturn_t exynos_afm_irq_handler(int irq, void *id) { struct afm_domain *afm_dom; bool irq_handled = false; if (ocp_reactor_disabled) return IRQ_NONE; list_for_each_entry(afm_dom, &afm_data->afm_domain_list, list) { if (afm_dom->irq != irq) continue; /* Check the source of interrupt */ if (!check_afm_interrupt(afm_dom)) continue; if (afm_dom->cpu != CLUSTER_OFF) { /* Before start interrupt handling, disable OCP interrupt. */ control_afm_interrupt(afm_dom, SWI_DISABLE); schedule_work_on(afm_dom->cpu, &afm_dom->work); } clear_afm_throttling_duration_counter(afm_dom); clear_afm_interrupt(afm_dom); irq_handled = true; } if (!irq_handled) return IRQ_NONE; return IRQ_HANDLED; } /****************************************************************/ /* EXTERNAL EVENT HANDLER */ /****************************************************************/ static int exynos_afm_cpuhp_callback(unsigned int cpu, bool up) { bool afm_dom_found = false; struct cpumask mask; struct afm_domain *afm_dom; if (ocp_reactor_disabled) return 0; list_for_each_entry(afm_dom, &afm_data->afm_domain_list, list) if (cpumask_test_cpu(cpu, &afm_dom->cpus)) { afm_dom_found = true; break; } if (!afm_dom_found) return 0; cpumask_and(&mask, &afm_dom->cpus, cpu_online_mask); if (up) { /* * The first incomming cpu in afm data binds * afm interrupt on data->cpus. */ if (cpumask_weight(&mask) == 1) { afm_dom->cpu = cpu; irq_set_affinity_hint(afm_dom->irq, &afm_dom->interrupt_affinity); control_afm_interrupt(afm_dom, SWI_ENABLE); } } else { cpumask_clear_cpu(cpu, &mask); /* If all data->cpus off, update data->cpu as CLUSTER_OFF */ if (cpumask_empty(&mask)) afm_dom->cpu = CLUSTER_OFF; else afm_dom->cpu = cpumask_any(&mask); } return 0; } static int exynos_afm_cpu_up_callback(unsigned int cpu) { return exynos_afm_cpuhp_callback(cpu, true); } static int exynos_afm_cpu_down_callback(unsigned int cpu) { return exynos_afm_cpuhp_callback(cpu, false); } unsigned int get_afm_clipped_freq(int cpu) { bool afm_dom_found = false; struct afm_domain *afm_dom; if (ocp_reactor_disabled) return 0; list_for_each_entry(afm_dom, &afm_data->afm_domain_list, list) if (cpumask_test_cpu(cpu, &afm_dom->possible_cpus)) { afm_dom_found = true; break; } if (!afm_dom_found) return 0; return afm_dom->clipped_freq; } EXPORT_SYMBOL(get_afm_clipped_freq); /****************************************************************/ /* SYSFS INTERFACE */ /****************************************************************/ struct afm_attr { struct attribute attr; ssize_t (*show)(struct kobject *, char *); ssize_t (*store)(struct kobject *, const char *, size_t count); }; #define to_afm_dom(k) container_of(k, struct afm_domain, kobj) #define AFM_ATTR_RW(name) \ struct afm_attr afm_attr_##name = \ __ATTR(name, 0644, afm_##name##_show, afm_##name##_store) #define AFM_ATTR_RO(name) \ struct afm_attr afm_attr_##name = \ __ATTR(name, 0444, afm_##name##_show, NULL) #define afm_show(name, type) \ static ssize_t afm_##name##_show(struct kobject *kobj, char *buf) \ { \ int ret; \ struct afm_domain *afm_dom = to_afm_dom(kobj); \ \ ret = snprintf(buf, PAGE_SIZE, "%d\n", afm_dom->type); \ \ return ret; \ } #define afm_store(name, type) \ static ssize_t afm_##name##_store(struct kobject *kobj, const char *buf, size_t count) \ { \ unsigned int val; \ struct afm_domain *afm_dom = to_afm_dom(kobj); \ \ if (sscanf(buf, "%d", &val) != 1) \ return -EINVAL; \ \ afm_dom->type = val; \ \ return count; \ } afm_show(flag, flag); afm_store(down_step, down_step); afm_show(down_step, down_step); afm_store(release_duration, release_duration); afm_show(release_duration, release_duration); afm_show(clipped_freq, clipped_freq); static ssize_t afm_enable_show(struct kobject *kobj, char *buf) { int ret = 0; struct afm_domain *afm_dom = to_afm_dom(kobj); ret = snprintf(buf, PAGE_SIZE, "%d\n", afm_dom->enabled); return ret; } static ssize_t afm_enable_store(struct kobject *kobj, const char *buf, size_t count) { unsigned int val; struct afm_domain *afm_dom = to_afm_dom(kobj); if (sscanf(buf, "%d", &val) != 1) return -EINVAL; control_afm_operation(afm_dom, val); return count; } static ssize_t afm_total_trans_show(struct kobject *kobj, char *buf) { int ret; struct afm_domain *afm_dom = to_afm_dom(kobj); ret = snprintf(buf, PAGE_SIZE, "%llu\n", afm_dom->stats->total_trans); return ret; } static ssize_t afm_time_in_state_show(struct kobject *kobj, char *buf) { struct afm_domain *afm_dom = to_afm_dom(kobj); struct afm_stats *stats = afm_dom->stats; ssize_t len = 0; int i; update_afm_stats(afm_dom); for (i = 0; i < afm_dom->stats->max_state; i++) { len += snprintf(buf + len, PAGE_SIZE - len, "%u %llu\n", stats->freq_table[i], (unsigned long long)jiffies_64_to_clock_t(stats->time_in_state[i])); } return len; } static ssize_t afm_profile_show(struct kobject *kobj, char *buf) { struct afm_domain *afm_dom = to_afm_dom(kobj); int ret; if (afm_dom->profile_started) ret = snprintf(buf, PAGE_SIZE, "profile is ongoing\n"); else ret = snprintf(buf, PAGE_SIZE, "%d\n", afm_dom->throttle_cnt); return ret; } static ssize_t afm_profile_store(struct kobject *kobj, const char *buf, size_t count) { unsigned int val; struct afm_domain *afm_dom = to_afm_dom(kobj); if (sscanf(buf, "%d", &val) != 1) return -EINVAL; if (val) afm_profile_start(afm_dom); else afm_profile_end(afm_dom); return count; } static AFM_ATTR_RW(enable); static AFM_ATTR_RO(flag); static AFM_ATTR_RW(down_step); static AFM_ATTR_RW(release_duration); static AFM_ATTR_RO(clipped_freq); static AFM_ATTR_RO(total_trans); static AFM_ATTR_RO(time_in_state); static AFM_ATTR_RW(profile); static struct attribute *exynos_afm_attrs[] = { &afm_attr_enable.attr, &afm_attr_flag.attr, &afm_attr_down_step.attr, &afm_attr_release_duration.attr, &afm_attr_clipped_freq.attr, &afm_attr_total_trans.attr, &afm_attr_time_in_state.attr, &afm_attr_profile.attr, NULL, }; static ssize_t show(struct kobject *kobj, struct attribute *at, char *buf) { struct afm_attr *fvattr = container_of(at, struct afm_attr, attr); return fvattr->show(kobj, buf); } static ssize_t store(struct kobject *kobj, struct attribute *at, const char *buf, size_t count) { struct afm_attr *fvattr = container_of(at, struct afm_attr, attr); return fvattr->store(kobj, buf, count); } static const struct sysfs_ops afm_sysfs_ops = { .show = show, .store = store, }; static struct kobj_type ktype_afm = { .sysfs_ops = &afm_sysfs_ops, .default_attrs = exynos_afm_attrs, }; /****************************************************************/ /* INITIALIZE EXYNOS AFM DRIVER */ /****************************************************************/ static int alloc_pmic_function(struct afm_domain *afm_dom) { int ret = 0; switch (afm_dom->interrupt_src) { case MAIN_PMIC: afm_dom->pmic_update_reg = main_pmic_update_reg; afm_dom->pmic_get_i2c = main_pmic_get_i2c; break; case SUB_PMIC: afm_dom->pmic_update_reg = sub_pmic_update_reg; afm_dom->pmic_get_i2c = sub_pmic_get_i2c; break; default: pr_err("exynos-afm: cpu%d: Failed to init pmic function", afm_dom->cpu); ret = -ENODEV; break; } /* * Regulator could be unloaded when probing afm driver, and if so * fail to probe it. Let's check whether regulator is loaded * and if not, defer probing afm driver. */ if (afm_dom->pmic_get_i2c(&afm_dom->i2c)) { pr_err("exynos-afm: cpu%d: Failed to load regulator", afm_dom->cpu); return -ENODEV; } return ret; } static int afm_dt_parsing(struct device_node *dn, struct afm_domain *afm_dom) { const char *buf, *buf2; int ret = 0; ret |= of_property_read_u32(dn, "down-step", &afm_dom->down_step); ret |= of_property_read_u32(dn, "max-freq-wo-afm", &afm_dom->max_freq_wo_afm); ret |= of_property_read_u32(dn, "release-duration", &afm_dom->release_duration); ret |= of_property_read_u32(dn, "s2mps-afm-offset", &afm_dom->s2mps_afm_offset); ret |= of_property_read_u32(dn, "htu-base-addr", &afm_dom->base_addr); ret |= of_property_read_u32(dn, "interrupt-src", &afm_dom->interrupt_src); ret |= of_property_read_string(dn, "sibling-cpus", &buf); if (ret) return ret; cpulist_parse(buf, &afm_dom->cpus); cpumask_and(&afm_dom->possible_cpus, &afm_dom->cpus, cpu_possible_mask); cpumask_and(&afm_dom->cpus, &afm_dom->cpus, cpu_possible_mask); cpumask_and(&afm_dom->cpus, &afm_dom->cpus, cpu_online_mask); afm_dom->cpu = cpumask_first(&afm_dom->cpus); if (of_property_read_string(dn, "interrupt-affinity", &buf2)) cpumask_copy(&afm_dom->interrupt_affinity, &afm_dom->cpus); else cpulist_parse(buf2, &afm_dom->interrupt_affinity); if (alloc_pmic_function(afm_dom)) return -ENODEV; if (cpumask_weight(&afm_dom->cpus) == 0) { control_afm_warn(afm_dom, AFM_WARN_DISABLE); return -ENODEV; } return 0; } static void afm_stats_create_table(struct afm_domain *afm_dom, struct cpufreq_policy *policy) { unsigned int i = 0, count = 0, alloc_size; struct afm_stats *stats; struct cpufreq_frequency_table *pos, *table; if (unlikely(!afm_dom)) return; if (afm_dom && afm_dom->stats) return; table = policy->freq_table; if (unlikely(!table)) return; stats = kzalloc(sizeof(*stats), GFP_KERNEL); if (!stats) return; cpufreq_for_each_valid_entry(pos, table) count++; alloc_size = count * sizeof(int) + count * sizeof(u64); stats->time_in_state = kzalloc(alloc_size, GFP_KERNEL); if (!stats->time_in_state) goto free_stat; stats->freq_table = (unsigned int *)(stats->time_in_state + count); stats->max_state = count; cpufreq_for_each_valid_entry(pos, table) stats->freq_table[i++] = pos->frequency; stats->last_time = get_jiffies_64(); stats->last_index = get_afm_freq_index(stats, afm_dom->clipped_freq); afm_dom->stats = stats; return; free_stat: kfree(stats); } static int init_afm_domain(struct device_node *dn, struct platform_device *pdev) { struct afm_domain *afm_dom; struct cpufreq_policy *policy; int ret; afm_dom = kzalloc(sizeof(struct afm_domain), GFP_KERNEL); if (!afm_dom) return -ENOMEM; INIT_LIST_HEAD(&afm_dom->list); list_add(&afm_dom->list, &afm_data->afm_domain_list); ret = afm_dt_parsing(dn, afm_dom); if (ret) { dev_err(&pdev->dev, "Failed to parse AFM data\n"); return -ENODEV; } afm_dom->base = ioremap(afm_dom->base_addr, SZ_64K); if (!afm_dom->i2c) { dev_err(&pdev->dev, "Failed to get s2mps19 i2c_client\n"); return -ENODEV; } smp_call_function_any(&afm_dom->cpus, check_ocp_reactor_disabled, afm_dom, 1); if (ocp_reactor_disabled) { dev_err(&pdev->dev, "OCP reactor is disabled\n"); return -ENODEV; } INIT_WORK(&afm_dom->work, exynos_afm_work); INIT_DELAYED_WORK(&afm_dom->delayed_work, exynos_afm_work_release); if (kobject_init_and_add(&afm_dom->kobj, &ktype_afm, &pdev->dev.kobj, "cpu%d", afm_dom->cpu)) { dev_err(&pdev->dev, "Failed to init sysfs\n"); return -ENOMEM; } policy = cpufreq_cpu_get(cpumask_first(&afm_dom->cpus)); if (!policy) { dev_err(&pdev->dev, "Failed to get policy\n"); return -ENODEV; } afm_dom->enabled = true; afm_dom->flag = false; afm_dom->min_freq = policy->cpuinfo.min_freq; afm_dom->max_freq = policy->cpuinfo.max_freq; afm_dom->clipped_freq = afm_dom->max_freq; afm_stats_create_table(afm_dom, policy); smp_call_function_any(&afm_dom->cpus, init_htu_register_ipi, afm_dom, 1); ret = freq_qos_tracer_add_request(&policy->constraints, &afm_dom->max_qos_req, FREQ_QOS_MAX, afm_dom->clipped_freq); if (ret < 0) return ret; afm_dom->irq = irq_of_parse_and_map(dn, 0); if (afm_dom->irq <= 0) { dev_err(&pdev->dev, "Failed to get IRQ\n"); return -ENODEV; } ret = devm_request_irq(&pdev->dev, afm_dom->irq, exynos_afm_irq_handler, IRQF_TRIGGER_HIGH | IRQF_SHARED, dev_name(&pdev->dev), afm_data); if (ret) { dev_err(&pdev->dev, "Failed to request IRQ handler: %d\n", afm_dom->irq); return -ENODEV; } irq_set_affinity_hint(afm_dom->irq, &afm_dom->interrupt_affinity); cpufreq_cpu_put(policy); pr_info("exynos-afm: AFM data(cpu%d) structure update complete\n", afm_dom->cpu); return 0; } static int check_regulator(void) { struct i2c_client *regulator_i2c = NULL; if (main_pmic_get_i2c(®ulator_i2c)) return -ENODEV; if (sub_pmic_get_i2c(®ulator_i2c)) return -ENODEV; return 0; } static int exynos_afm_suspend(struct device *dev) { return 0; } static int exynos_afm_resume(struct device *dev) { struct afm_domain *afm_dom; bool afm_dom_found = false; if (ocp_reactor_disabled) return 0; list_for_each_entry(afm_dom, &afm_data->afm_domain_list, list) if (cpumask_test_cpu(BOOT_CPU, &afm_dom->cpus)) { afm_dom_found = true; break; } if (!afm_dom_found) return 0; irq_set_affinity_hint(afm_dom->irq, &afm_dom->interrupt_affinity); control_afm_interrupt(afm_dom, SWI_ENABLE); return 0; } static int exynos_afm_probe(struct platform_device *pdev) { struct device_node *dn = pdev->dev.of_node, *child; if (check_regulator()) return -EPROBE_DEFER; afm_data = kzalloc(sizeof(struct exynos_afm_data), GFP_KERNEL); if (!afm_data) return -ENOMEM; INIT_LIST_HEAD(&afm_data->afm_domain_list); if (of_property_read_bool(dn, "waiting-freq-change")) { waiting_freq_change = 1; pr_info("exynos-afm: enable waiting-freq-change"); } for_each_child_of_node(dn, child) { if (init_afm_domain(child, pdev)) { pr_err("exynos-afm: Failed to init init_afm_domain\n"); goto free_data; } } platform_set_drvdata(pdev, afm_data); cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "exynos:afm", exynos_afm_cpu_up_callback, exynos_afm_cpu_down_callback); dev_info(&pdev->dev, "Complete AFM Handler initialization\n"); return 0; free_data: kfree(afm_data); return -ENODEV; } static const struct of_device_id of_exynos_afm_match[] = { { .compatible = "samsung,exynos-afm", }, { }, }; MODULE_DEVICE_TABLE(of, of_exynos_afm_match); static const struct dev_pm_ops exynos_afm_pm_ops = { .suspend = exynos_afm_suspend, .resume = exynos_afm_resume, }; static struct platform_driver exynos_afm_driver = { .driver = { .name = "exynos-afm", .owner = THIS_MODULE, .pm = &exynos_afm_pm_ops, .of_match_table = of_exynos_afm_match, }, .probe = exynos_afm_probe, }; module_platform_driver(exynos_afm_driver); MODULE_SOFTDEP("pre: exynos-acme"); MODULE_DESCRIPTION("Exynos AFM drvier"); MODULE_LICENSE("GPL");