/* linux/drivers/devfreq/exynos-devfreq.c * * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Samsung EXYNOS SoC series devfreq common driver * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 2 of the License, * or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../soc/samsung/cal-if/acpm_dvfs.h" #include #include #include #ifdef CONFIG_EXYNOS_DVFS_MANAGER #include #endif #ifdef CONFIG_EXYNOS_ACPM #include "../../soc/samsung/acpm/acpm.h" #include "../../soc/samsung/acpm/acpm_ipc.h" #endif #include "../governor.h" static struct exynos_devfreq_data **devfreq_data; static u32 freq_array[6]; static u32 boot_array[2]; #ifdef CONFIG_EXYNOS_DVFS_MANAGER static unsigned int ect_find_constraint_freq(struct ect_minlock_domain *ect_domain, unsigned int freq) { unsigned int i; for (i = 0; i < ect_domain->num_of_level; i++) if (ect_domain->level[i].main_frequencies == freq) break; return ect_domain->level[i].sub_frequencies; } #endif static int exynos_constraint_parse(struct exynos_devfreq_data *data, unsigned int min_freq, unsigned int max_freq) { struct device_node *np, *child; u32 num_child, constraint_dm_type, constraint_type; const char *devfreq_domain_name; int i = 0, j, const_flag = 1; void *min_block, *dvfs_block; struct ect_dvfs_domain *dvfs_domain; struct ect_minlock_domain *ect_domain; #ifdef CONFIG_EXYNOS_DVFS_MANAGER struct exynos_dm_freq *const_table; #endif np = of_get_child_by_name(data->dev->of_node, "skew"); if (!np) return 0; num_child = of_get_child_count(np); #ifdef CONFIG_EXYNOS_DVFS_MANAGER data->nr_constraint = num_child; data->constraint = kzalloc(sizeof(struct exynos_dm_constraint *) * num_child, GFP_KERNEL); #endif if (of_property_read_string(data->dev->of_node, "devfreq_domain_name", &devfreq_domain_name)) return -ENODEV; dvfs_block = ect_get_block(BLOCK_DVFS); if (dvfs_block == NULL) return -ENODEV; dvfs_domain = ect_dvfs_get_domain(dvfs_block, (char *)devfreq_domain_name); if (dvfs_domain == NULL) return -ENODEV; /* Although there is not any constraint, MIF table should be sent to FVP */ min_block = ect_get_block(BLOCK_MINLOCK); if (min_block == NULL) { dev_info(data->dev, "There is not a min block in ECT\n"); const_flag = 0; } ect_domain = ect_minlock_get_domain(min_block, (char *)devfreq_domain_name); if (ect_domain == NULL) { dev_info(data->dev, "There is not a domain in min block\n"); const_flag = 0; } for_each_available_child_of_node(np, child) { int use_level = 0; if (of_property_read_u32(child, "constraint_dm_type", &constraint_dm_type)) return -ENODEV; if (of_property_read_u32(child, "constraint_type", &constraint_type)) return -ENODEV; #ifdef CONFIG_EXYNOS_DVFS_MANAGER if (const_flag) { data->constraint[i] = kzalloc(sizeof(struct exynos_dm_constraint), GFP_KERNEL); if (data->constraint[i] == NULL) { dev_err(data->dev, "failed to allocate constraint\n"); return -ENOMEM; } const_table = kzalloc(sizeof(struct exynos_dm_freq) * ect_domain->num_of_level, GFP_KERNEL); if (const_table == NULL) { dev_err(data->dev, "failed to allocate constraint\n"); kfree(data->constraint[i]); return -ENOMEM; } data->constraint[i]->guidance = true; data->constraint[i]->constraint_type = constraint_type; data->constraint[i]->constraint_dm_type = constraint_dm_type; data->constraint[i]->table_length = ect_domain->num_of_level; data->constraint[i]->freq_table = const_table; } #endif for (j = 0; j < dvfs_domain->num_of_level; j++) { if (data->opp_list[j].freq > max_freq || data->opp_list[j].freq < min_freq) continue; #ifdef CONFIG_EXYNOS_DVFS_MANAGER if (const_flag) { const_table[use_level].master_freq = data->opp_list[j].freq; const_table[use_level].constraint_freq = ect_find_constraint_freq(ect_domain, data->opp_list[j].freq); } #endif use_level++; } i++; } return 0; } static int exynos_devfreq_update_fvp(struct exynos_devfreq_data *data, u32 min_freq, u32 max_freq) { int ret, ch_num, size, i, use_level = 0; u32 cmd[4]; struct ipc_config config; int nr_constraint = 0; #ifdef CONFIG_EXYNOS_DVFS_MANAGER int j; struct exynos_dm_constraint *constraint; nr_constraint = data->nr_constraint; #endif ret = acpm_ipc_request_channel(data->dev->of_node, NULL, &ch_num, &size); if (ret) { dev_err(data->dev, "acpm request channel is failed, id:%u, size:%u\n", ch_num, size); return -EINVAL; } config.cmd = cmd; config.response = true; config.indirection = false; /* constraint info update */ if (nr_constraint == 0) { for (i = 0; i < data->max_state; i++) { if (data->opp_list[i].freq > max_freq || data->opp_list[i].freq < min_freq) continue; config.cmd[0] = use_level; config.cmd[1] = data->opp_list[i].freq; config.cmd[2] = DATA_INIT; config.cmd[3] = 0; ret = acpm_ipc_send_data(ch_num, &config); if (ret) { dev_err(data->dev, "make constraint table is failed"); return -EINVAL; } use_level++; } } #ifdef CONFIG_EXYNOS_DVFS_MANAGER else { for (i = 0; i < data->nr_constraint; i++) { constraint = data->constraint[i]; for (j = 0; j < data->max_state; j++) { if (data->opp_list[j].freq > max_freq || data->opp_list[j].freq < min_freq) continue; config.cmd[0] = use_level; config.cmd[1] = data->opp_list[j].freq; config.cmd[2] = DATA_INIT; config.cmd[3] = constraint->freq_table[use_level].constraint_freq; ret = acpm_ipc_send_data(ch_num, &config); if (ret) { dev_err(data->dev, "make constraint table is failed"); return -EINVAL; } use_level++; } } /* Send MIF initial freq and the number of constraint data to FVP */ config.cmd[0] = use_level; config.cmd[1] = (unsigned int)data->devfreq_profile.initial_freq; config.cmd[2] = DATA_INIT; config.cmd[3] = SET_CONST; ret = acpm_ipc_send_data(ch_num, &config); if (ret) { dev_err(data->dev, "failed to send nr_constraint and init freq"); return -EINVAL; } } #endif return 0; } static int exynos_devfreq_reboot(struct exynos_devfreq_data *data) { if (pm_qos_request_active(&data->default_pm_qos_max)) pm_qos_update_request(&data->default_pm_qos_max, data->reboot_freq); return 0; } static int exynos_devfreq_get_freq(struct device *dev, u32 *cur_freq, struct clk *clk, struct exynos_devfreq_data *data) { if (data->pm_domain) { if (!exynos_pd_status(data->pm_domain)) { dev_err(dev, "power domain %s is offed\n", data->pm_domain->name); *cur_freq = 0; return -EINVAL; } } *cur_freq = (u32)cal_dfs_get_rate(data->dfs_id); if (*cur_freq == 0) { dev_err(dev, "failed get frequency from CAL\n"); return -EINVAL; } return 0; } static int exynos_devfreq_set_freq(struct device *dev, u32 new_freq, struct clk *clk, struct exynos_devfreq_data *data) { #ifdef CONFIG_EXYNOS_BTS if (data->bts_update) { if (data->new_freq < data->old_freq) bts_update_scen(BS_MIF_CHANGE, data->new_freq); } #endif if (data->pm_domain) { if (!exynos_pd_status(data->pm_domain)) { dev_err(dev, "power domain %s is offed\n", data->pm_domain->name); return -EINVAL; } } if (cal_dfs_set_rate(data->dfs_id, (unsigned long)new_freq)) { dev_err(dev, "failed set frequency to CAL (%uKhz)\n", new_freq); return -EINVAL; } #ifdef CONFIG_EXYNOS_BTS if (data->bts_update) { if (data->new_freq > data->old_freq) bts_update_scen(BS_MIF_CHANGE, data->new_freq); } #endif return 0; } static int exynos_devfreq_init_freq_table(struct exynos_devfreq_data *data) { u32 max_freq, min_freq; unsigned long tmp_max, tmp_min; struct dev_pm_opp *target_opp; u32 flags = 0; int i, ret; max_freq = (u32)cal_dfs_get_max_freq(data->dfs_id); if (!max_freq) { dev_err(data->dev, "failed get max frequency\n"); return -EINVAL; } dev_info(data->dev, "max_freq: %uKhz, get_max_freq: %uKhz\n", data->max_freq, max_freq); if (max_freq < data->max_freq) { flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; tmp_max = (unsigned long)max_freq; target_opp = devfreq_recommended_opp(data->dev, &tmp_max, flags); if (IS_ERR(target_opp)) { dev_err(data->dev, "not found valid OPP for max_freq\n"); return PTR_ERR(target_opp); } data->max_freq = (u32)dev_pm_opp_get_freq(target_opp); dev_pm_opp_put(target_opp); } /* min ferquency must be equal or under max frequency */ if (data->min_freq > data->max_freq) data->min_freq = data->max_freq; min_freq = (u32)cal_dfs_get_min_freq(data->dfs_id); if (!min_freq) { dev_err(data->dev, "failed get min frequency\n"); return -EINVAL; } dev_info(data->dev, "min_freq: %uKhz, get_min_freq: %uKhz\n", data->min_freq, min_freq); if (min_freq > data->min_freq) { flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; tmp_min = (unsigned long)min_freq; target_opp = devfreq_recommended_opp(data->dev, &tmp_min, flags); if (IS_ERR(target_opp)) { dev_err(data->dev, "not found valid OPP for min_freq\n"); return PTR_ERR(target_opp); } data->min_freq = (u32)dev_pm_opp_get_freq(target_opp); dev_pm_opp_put(target_opp); } dev_info(data->dev, "min_freq: %uKhz, max_freq: %uKhz\n", data->min_freq, data->max_freq); for (i = 0; i < data->max_state; i++) { if (data->opp_list[i].freq > data->max_freq || data->opp_list[i].freq < data->min_freq) dev_pm_opp_disable(data->dev, (unsigned long)data->opp_list[i].freq); } data->devfreq_profile.initial_freq = cal_dfs_get_boot_freq(data->dfs_id); data->devfreq_profile.suspend_freq = cal_dfs_get_resume_freq(data->dfs_id); ret = exynos_constraint_parse(data, min_freq, max_freq); if (ret) { dev_err(data->dev, "failed to parse constraint table\n"); return -EINVAL; } if (data->update_fvp) exynos_devfreq_update_fvp(data, min_freq, max_freq); if (data->use_acpm) { ret = exynos_acpm_set_init_freq(data->dfs_id, data->devfreq_profile.initial_freq); if (ret) { dev_err(data->dev, "failed to set init freq\n"); return -EINVAL; } } return 0; } #ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG static ssize_t show_exynos_devfreq_info(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; int i; count = snprintf(buf, PAGE_SIZE, "[Exynos DEVFREQ Data]\n" "devfreq dev name : %20s\n" "devfreq type : %20d\n" "Exynos SS flag : %20u\n", dev_name(data->dev), data->devfreq_type, data->ess_flag); count += snprintf(buf + count, PAGE_SIZE, "\n\n" "OPP list length : %20u\n", data->max_state); count += snprintf(buf + count, PAGE_SIZE, "freq opp table\n"); count += snprintf(buf + count, PAGE_SIZE, "\t idx freq volt\n"); for (i = 0; i < data->max_state; i++) count += snprintf(buf + count, PAGE_SIZE, "\t%5u %10u %10u\n", data->opp_list[i].idx, data->opp_list[i].freq, data->opp_list[i].volt); count += snprintf(buf + count, PAGE_SIZE, "default_qos : %20u\n" "initial_freq : %20lu\n" "min_freq : %20u\n" "max_freq : %20u\n" "boot_timeout(s) : %20u\n" "max_state : %20u\n", data->default_qos, data->devfreq_profile.initial_freq, data->min_freq, data->max_freq, data->boot_qos_timeout, data->max_state); count += snprintf(buf + count, PAGE_SIZE, "\n\n"); count += snprintf(buf + count, PAGE_SIZE, "governor_name : %20s\n", data->governor_name); return count; } static ssize_t show_exynos_devfreq_get_freq(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; u32 get_freq = 0; if (exynos_devfreq_get_freq(data->dev, &get_freq, data->clk, data)) dev_err(data->dev, "failed get freq\n"); count = snprintf(buf, PAGE_SIZE, "%10u Khz\n", get_freq); return count; } static int exynos_devfreq_cmu_dump(struct exynos_devfreq_data *data) { mutex_lock(&data->devfreq->lock); cal_vclk_dbg_info(data->dfs_id); mutex_unlock(&data->devfreq->lock); return 0; } static ssize_t show_exynos_devfreq_cmu_dump(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; mutex_lock(&data->lock); if (exynos_devfreq_cmu_dump(data)) dev_err(data->dev, "failed CMU Dump\n"); mutex_unlock(&data->lock); count = snprintf(buf, PAGE_SIZE, "Done\n"); return count; } static ssize_t show_debug_scaling_devfreq_max(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; int val; if (data->pm_qos_class_max) { val = pm_qos_read_req_value(data->pm_qos_class_max, &data->debug_pm_qos_max); if (val < 0) { dev_err(dev, "failed to read requested value\n"); return count; } count += snprintf(buf, PAGE_SIZE, "%d\n", val); } return count; } static ssize_t store_debug_scaling_devfreq_max(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ret; u32 qos_value; ret = sscanf(buf, "%u", &qos_value); if (ret != 1) return -EINVAL; if (data->pm_qos_class_max) { if (pm_qos_request_active(&data->debug_pm_qos_max)) pm_qos_update_request(&data->debug_pm_qos_max, qos_value); } return count; } static ssize_t show_debug_scaling_devfreq_min(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; int val; val = pm_qos_read_req_value(data->pm_qos_class, &data->debug_pm_qos_min); if (val < 0) { dev_err(dev, "failed to read requested value\n"); return count; } count += snprintf(buf, PAGE_SIZE, "%d\n", val); return count; } static ssize_t store_debug_scaling_devfreq_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ret; u32 qos_value; ret = sscanf(buf, "%u", &qos_value); if (ret != 1) return -EINVAL; if (pm_qos_request_active(&data->debug_pm_qos_min)) pm_qos_update_request(&data->debug_pm_qos_min, qos_value); return count; } static ssize_t show_exynos_devfreq_disable_pm_qos(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; count += snprintf(buf, PAGE_SIZE, "%s\n", data->devfreq->disabled_pm_qos ? "disabled" : "enabled"); return count; } static ssize_t store_exynos_devfreq_disable_pm_qos(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ret; u32 disable; ret = sscanf(buf, "%u", &disable); if (ret != 1) return -EINVAL; if (disable) data->devfreq->disabled_pm_qos = true; else data->devfreq->disabled_pm_qos = false; return count; } static DEVICE_ATTR(exynos_devfreq_info, 0640, show_exynos_devfreq_info, NULL); static DEVICE_ATTR(exynos_devfreq_get_freq, 0640, show_exynos_devfreq_get_freq, NULL); static DEVICE_ATTR(exynos_devfreq_cmu_dump, 0640, show_exynos_devfreq_cmu_dump, NULL); static DEVICE_ATTR(debug_scaling_devfreq_min, 0640, show_debug_scaling_devfreq_min, store_debug_scaling_devfreq_min); static DEVICE_ATTR(debug_scaling_devfreq_max, 0640, show_debug_scaling_devfreq_max, store_debug_scaling_devfreq_max); static DEVICE_ATTR(disable_pm_qos, 0640, show_exynos_devfreq_disable_pm_qos, store_exynos_devfreq_disable_pm_qos); static struct attribute *exynos_devfreq_sysfs_entries[] = { &dev_attr_exynos_devfreq_info.attr, &dev_attr_exynos_devfreq_get_freq.attr, &dev_attr_exynos_devfreq_cmu_dump.attr, &dev_attr_debug_scaling_devfreq_min.attr, &dev_attr_debug_scaling_devfreq_max.attr, &dev_attr_disable_pm_qos.attr, NULL, }; static struct attribute_group exynos_devfreq_attr_group = { .name = "exynos_data", .attrs = exynos_devfreq_sysfs_entries, }; #endif static ssize_t show_scaling_devfreq_min(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; int val; val = pm_qos_read_req_value(data->pm_qos_class, &data->sys_pm_qos_min); if (val < 0) { dev_err(dev, "failed to read requested value\n"); return count; } count += snprintf(buf, PAGE_SIZE, "%d\n", val); return count; } static ssize_t store_scaling_devfreq_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ret; u32 qos_value; ret = sscanf(buf, "%u", &qos_value); if (ret != 1) return -EINVAL; if (pm_qos_request_active(&data->sys_pm_qos_min)) pm_qos_update_request(&data->sys_pm_qos_min, qos_value); return count; } static DEVICE_ATTR(scaling_devfreq_min, 0640, show_scaling_devfreq_min, store_scaling_devfreq_min); /* get frequency and delay time data from string */ static unsigned int *get_tokenized_data(const char *buf, int *num_tokens) { const char *cp; int i; int ntokens = 1; unsigned int *tokenized_data; int err = -EINVAL; cp = buf; while ((cp = strpbrk(cp + 1, " :"))) ntokens++; if (!(ntokens & 0x1)) goto err; tokenized_data = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL); if (!tokenized_data) { err = -ENOMEM; goto err; } cp = buf; i = 0; while (i < ntokens) { if (sscanf(cp, "%u", &tokenized_data[i++]) != 1) goto err_kfree; cp = strpbrk(cp, " :"); if (!cp) break; cp++; } if (i != ntokens) goto err_kfree; *num_tokens = ntokens; return tokenized_data; err_kfree: kfree(tokenized_data); err: return ERR_PTR(err); } static ssize_t show_use_delay_time(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; mutex_lock(&data->devfreq->lock); count += snprintf(buf, PAGE_SIZE, "%s\n", (data->simple_interactive_data.use_delay_time) ? "true" : "false"); mutex_unlock(&data->devfreq->lock); return count; } static ssize_t store_use_delay_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ret, use_delay_time; ret = sscanf(buf, "%d", &use_delay_time); if (ret != 1) return -EINVAL; if (use_delay_time == 0 || use_delay_time == 1) { mutex_lock(&data->devfreq->lock); data->simple_interactive_data.use_delay_time = use_delay_time ? true : false; mutex_unlock(&data->devfreq->lock); } else { dev_info(data->dev, "This is invalid value: %d\n", use_delay_time); } return count; } static ssize_t show_delay_time(struct device *dev, struct device_attribute *attr, char *buf) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); ssize_t count = 0; int i; mutex_lock(&data->devfreq->lock); for (i = 0; i < data->simple_interactive_data.ndelay_time; i++) { count += snprintf(buf + count, PAGE_SIZE, "%d%s", data->simple_interactive_data.delay_time[i], (i == data->simple_interactive_data.ndelay_time - 1) ? "" : (i % 2) ? ":" : " "); } count += snprintf(buf + count, PAGE_SIZE, "\n"); mutex_unlock(&data->devfreq->lock); return count; } static ssize_t store_delay_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct device *parent = dev->parent; struct platform_device *pdev = container_of(parent, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ntokens; int *new_delay_time = NULL; new_delay_time = get_tokenized_data(buf , &ntokens); if (IS_ERR(new_delay_time)) return PTR_RET(new_delay_time); mutex_lock(&data->devfreq->lock); kfree(data->simple_interactive_data.delay_time); data->simple_interactive_data.delay_time = new_delay_time; data->simple_interactive_data.ndelay_time = ntokens; mutex_unlock(&data->devfreq->lock); return count; } static DEVICE_ATTR(use_delay_time, 0640, show_use_delay_time, store_use_delay_time); static DEVICE_ATTR(delay_time, 0640, show_delay_time, store_delay_time); static struct attribute *devfreq_interactive_sysfs_entries[] = { &dev_attr_use_delay_time.attr, &dev_attr_delay_time.attr, NULL, }; static struct attribute_group devfreq_delay_time_attr_group = { .name = "interactive", .attrs = devfreq_interactive_sysfs_entries, }; #ifdef CONFIG_EXYNOS_DVFS_MANAGER int find_exynos_devfreq_dm_type(struct device *dev, int *dm_type) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); *dm_type = data->dm_type; return 0; } struct devfreq *find_exynos_devfreq_device(void *devdata) { struct exynos_devfreq_data *data = devdata; if (!devdata) { pr_err("%s: failed get Devfreq type\n", __func__); return ERR_PTR(-EINVAL); } return data->devfreq; } #endif #ifdef CONFIG_OF #if defined(CONFIG_ECT) static int exynos_devfreq_parse_ect(struct exynos_devfreq_data *data, const char *dvfs_domain_name) { int i; void *dvfs_block; struct ect_dvfs_domain *dvfs_domain; dvfs_block = ect_get_block(BLOCK_DVFS); if (dvfs_block == NULL) return -ENODEV; dvfs_domain = ect_dvfs_get_domain(dvfs_block, (char *)dvfs_domain_name); if (dvfs_domain == NULL) return -ENODEV; data->max_state = dvfs_domain->num_of_level; data->opp_list = kzalloc(sizeof(struct exynos_devfreq_opp_table) * data->max_state, GFP_KERNEL); if (!data->opp_list) { pr_err("%s: failed to allocate opp_list\n", __func__); return -ENOMEM; } for (i = 0; i < dvfs_domain->num_of_level; ++i) { data->opp_list[i].idx = i; data->opp_list[i].freq = dvfs_domain->list_level[i].level; data->opp_list[i].volt = 0; } return 0; } #endif static int exynos_devfreq_parse_dt(struct device_node *np, struct exynos_devfreq_data *data) { const char *use_acpm, *bts_update; #if defined(CONFIG_ECT) const char *devfreq_domain_name; #endif const char *buf; const char *use_delay_time; const char *pd_name; const char *update_fvp; int ntokens; int not_using_ect = true; if (!np) return -ENODEV; if (of_property_read_u32(np, "devfreq_type", &data->devfreq_type)) return -ENODEV; if (of_property_read_u32(np, "pm_qos_class", &data->pm_qos_class)) return -ENODEV; if (of_property_read_u32(np, "pm_qos_class_max", &data->pm_qos_class_max)) return -ENODEV; if (of_property_read_u32(np, "ess_flag", &data->ess_flag)) return -ENODEV; #if defined(CONFIG_ECT) if (of_property_read_string(np, "devfreq_domain_name", &devfreq_domain_name)) return -ENODEV; not_using_ect = exynos_devfreq_parse_ect(data, devfreq_domain_name); #endif if (not_using_ect) { dev_err(data->dev, "cannot parse the DVFS info in ECT"); return -ENODEV; } if (of_property_read_string(np, "pd_name", &pd_name)) { dev_info(data->dev, "no power domain\n"); data->pm_domain = NULL; } else { dev_info(data->dev, "power domain: %s\n", pd_name); data->pm_domain = exynos_pd_lookup_name(pd_name); } if (of_property_read_u32_array(np, "freq_info", (u32 *)&freq_array, (size_t)(ARRAY_SIZE(freq_array)))) return -ENODEV; data->devfreq_profile.initial_freq = freq_array[0]; data->default_qos = freq_array[1]; data->devfreq_profile.suspend_freq = freq_array[2]; data->min_freq = freq_array[3]; data->max_freq = freq_array[4]; data->reboot_freq = freq_array[5]; if (of_property_read_u32_array(np, "boot_info", (u32 *)&boot_array, (size_t)(ARRAY_SIZE(boot_array)))) { data->boot_qos_timeout = 0; data->boot_freq = 0; dev_info(data->dev, "This doesn't use boot value\n"); } else { data->boot_qos_timeout = boot_array[0]; data->boot_freq = boot_array[1]; } if (of_property_read_u32(np, "governor", &data->gov_type)) return -ENODEV; if (data->gov_type == SIMPLE_INTERACTIVE) data->governor_name = "interactive"; else { dev_err(data->dev, "invalid governor name (%s)\n", data->governor_name); return -EINVAL; } if (!of_property_read_string(np, "use_acpm", &use_acpm)) { if (!strcmp(use_acpm, "true")) { data->use_acpm = true; } else { data->use_acpm = false; dev_info(data->dev, "This does not use acpm\n"); } } else { dev_info(data->dev, "This does not use acpm\n"); data->use_acpm = false; } if (!of_property_read_string(np, "bts_update", &bts_update)) { if (!strcmp(bts_update, "true")) { data->bts_update = true; } else { data->bts_update = false; dev_info(data->dev, "This does not bts update\n"); } } else { dev_info(data->dev, "This does not bts update\n"); data->bts_update = false; } if (!of_property_read_string(np, "update_fvp", &update_fvp)) { if (!strcmp(update_fvp, "true")) { data->update_fvp = true; } else { data->update_fvp = false; dev_info(data->dev, "This does not update fvp\n"); } } else { dev_info(data->dev, "This does not update fvp\n"); data->update_fvp = false; } if (of_property_read_u32(np, "dfs_id", &data->dfs_id) && of_property_match_string(np, "clock-names", buf)) return -ENODEV; if (data->gov_type == SIMPLE_INTERACTIVE) { if (of_property_read_string(np, "use_delay_time", &use_delay_time)) return -ENODEV; if (!strcmp(use_delay_time, "true")) { data->simple_interactive_data.use_delay_time = true; } else if (!strcmp(use_delay_time, "false")) { data->simple_interactive_data.use_delay_time = false; } else { dev_err(data->dev, "invalid use_delay_time : (%s)\n", use_delay_time); return -EINVAL; } if (data->simple_interactive_data.use_delay_time) { if (of_property_read_string(np, "delay_time_list", &buf)) { /* * If there is not delay time list, * delay time will be filled with default time */ data->simple_interactive_data.delay_time = kmalloc(sizeof(unsigned int), GFP_KERNEL); if (!data->simple_interactive_data.delay_time) { dev_err(data->dev, "Fail to allocate delay_time memory\n"); return -ENOMEM; } *(data->simple_interactive_data.delay_time) = DEFAULT_DELAY_TIME; data->simple_interactive_data.ndelay_time = DEFAULT_NDELAY_TIME; dev_info(data->dev, "set default delay time %d ms\n", DEFAULT_DELAY_TIME); } else { data->simple_interactive_data.delay_time = get_tokenized_data(buf, &ntokens); data->simple_interactive_data.ndelay_time = ntokens; } } } else { dev_err(data->dev, "not support governor type %u\n", data->gov_type); return -EINVAL; } #ifdef CONFIG_EXYNOS_DVFS_MANAGER if (of_property_read_u32(np, "dm-index", &data->dm_type)) { dev_err(data->dev, "not support dvfs manager\n"); return -ENODEV; } #endif return 0; } #else static int exynos_devfreq_parse_dt(struct device_node *np, struct exynos_devfrq_data *data) { return -EINVAL; } #endif s32 exynos_devfreq_get_opp_idx(struct exynos_devfreq_opp_table *table, unsigned int size, u32 freq) { int i; for (i = 0; i < size; ++i) { if (table[i].freq == freq) return i; } return -ENODEV; } static int exynos_init_freq_table(struct exynos_devfreq_data *data) { int i, ret; u32 freq, volt; for (i = 0; i < data->max_state; i++) { freq = data->opp_list[i].freq; volt = data->opp_list[i].volt; data->devfreq_profile.freq_table[i] = freq; ret = dev_pm_opp_add(data->dev, freq, volt); if (ret) { dev_err(data->dev, "failed to add opp entries %uKhz\n", freq); return ret; } else { dev_info(data->dev, "DEVFREQ : %8uKhz, %8uuV\n", freq, volt); } } ret = exynos_devfreq_init_freq_table(data); if (ret) { dev_err(data->dev, "failed init frequency table\n"); return ret; } return 0; } static int exynos_devfreq_reboot_notifier(struct notifier_block *nb, unsigned long val, void *v) { struct exynos_devfreq_data *data = container_of(nb, struct exynos_devfreq_data, reboot_notifier); if (pm_qos_request_active(&data->default_pm_qos_min)) pm_qos_update_request(&data->default_pm_qos_min, data->reboot_freq); if (exynos_devfreq_reboot(data)) { dev_err(data->dev, "failed reboot\n"); return NOTIFY_BAD; } return NOTIFY_OK; } static int exynos_devfreq_target(struct device *dev, unsigned long *target_freq, u32 flags) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); struct timeval before_target, after_target, before_setfreq, after_setfreq; struct dev_pm_opp *target_opp; u32 target_volt; s32 target_idx; s32 target_time = 0; int ret = 0; if (data->devfreq_disabled) return -EAGAIN; do_gettimeofday(&before_target); mutex_lock(&data->lock); target_opp = devfreq_recommended_opp(dev, target_freq, flags); if (IS_ERR(target_opp)) { dev_err(dev, "not found valid OPP table\n"); ret = PTR_ERR(target_opp); goto out; } *target_freq = dev_pm_opp_get_freq(target_opp); target_volt = (u32)dev_pm_opp_get_voltage(target_opp); dev_pm_opp_put(target_opp); target_idx = exynos_devfreq_get_opp_idx(data->opp_list, data->max_state, *target_freq); if (target_idx < 0) { ret = -EINVAL; goto out; } data->new_freq = (u32)(*target_freq); data->new_idx = target_idx; data->new_volt = target_volt; if (data->old_freq == data->new_freq) goto out; dev_dbg(dev, "LV_%d, %uKhz, %uuV ======> LV_%d, %uKhz, %uuV\n", data->old_idx, data->old_freq, data->old_volt, data->new_idx, data->new_freq, data->new_volt); #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ dbg_snapshot_freq(data->ess_flag, data->old_freq, data->new_freq, DSS_FLAG_IN); #endif do_gettimeofday(&before_setfreq); ret = exynos_devfreq_set_freq(dev, data->new_freq, data->clk, data); if (ret) { dev_err(dev, "failed set frequency (%uKhz --> %uKhz)\n", data->old_freq, data->new_freq); goto out; } do_gettimeofday(&after_setfreq); #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ dbg_snapshot_freq(data->ess_flag, data->old_freq, data->new_freq, DSS_FLAG_OUT); #endif data->old_freq = data->new_freq; data->old_idx = data->new_idx; data->old_volt = data->new_volt; out: mutex_unlock(&data->lock); do_gettimeofday(&after_target); target_time = (after_target.tv_sec - before_target.tv_sec) * USEC_PER_SEC + (after_target.tv_usec - before_target.tv_usec); data->target_delay = target_time; dev_dbg(dev, "target time: %d usec\n", target_time); return ret; } static int exynos_devfreq_suspend(struct device *dev) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); int ret = 0; #ifdef CONFIG_EXYNOS_DVFS_MANAGER #ifdef CONFIG_EXYNOS_ACPM int size, ch_num; unsigned int cmd[4]; struct ipc_config config; #endif #endif u32 get_freq = 0; #ifdef CONFIG_EXYNOS_DVFS_MANAGER if (data->use_acpm) { mutex_lock(&data->devfreq->lock); //send flag #ifdef CONFIG_EXYNOS_ACPM ret = acpm_ipc_request_channel(dev->of_node, NULL, &ch_num, &size); if (ret) { dev_err(dev, "acpm request channel is failed, id:%u, size:%u\n", ch_num, size); mutex_unlock(&data->devfreq->lock); return -EINVAL; } /* Initial value of release flag is true. * "true" means state of AP is running * "false means state of AP is sleep. */ config.cmd = cmd; config.response = true; config.indirection = false; config.cmd[0] = data->devfreq_type; config.cmd[1] = false; config.cmd[2] = DATA_INIT; config.cmd[3] = RELEASE; ret = acpm_ipc_send_data(ch_num, &config); if (ret) { dev_err(dev, "failed to send release infomation to FVP"); mutex_unlock(&data->devfreq->lock); return -EINVAL; } #endif data->devfreq->str_freq = data->devfreq_profile.suspend_freq; ret = update_devfreq(data->devfreq); if (ret && ret != -EAGAIN) { dev_err(&data->devfreq->dev, "devfreq failed with (%d) error\n", ret); mutex_unlock(&data->devfreq->lock); return NOTIFY_BAD; } mutex_unlock(&data->devfreq->lock); } #endif if (!data->use_acpm && pm_qos_request_active(&data->default_pm_qos_min)) pm_qos_update_request(&data->default_pm_qos_min, data->devfreq_profile.suspend_freq); if (exynos_devfreq_get_freq(data->dev, &get_freq, data->clk, data)) dev_err(data->dev, "failed get freq\n"); dev_info(data->dev, "Suspend_frequency is %u\n", get_freq); return ret; } static int exynos_devfreq_resume(struct device *dev) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct exynos_devfreq_data *data = platform_get_drvdata(pdev); #ifdef CONFIG_EXYNOS_DVFS_MANAGER #ifdef CONFIG_EXYNOS_ACPM int size, ch_num; unsigned int cmd[4]; struct ipc_config config; #endif #endif int ret = 0; u32 cur_freq; if (!exynos_devfreq_get_freq(data->dev, &cur_freq, data->clk, data)) dev_info(data->dev, "Resume frequency is %u\n", cur_freq); #ifdef CONFIG_EXYNOS_DVFS_MANAGER if (data->use_acpm) { mutex_lock(&data->devfreq->lock); //send flag #ifdef CONFIG_EXYNOS_ACPM ret = acpm_ipc_request_channel(dev->of_node, NULL, &ch_num, &size); if (ret) { dev_err(dev, "acpm request channel is failed, id:%u, size:%u\n", ch_num, size); mutex_unlock(&data->devfreq->lock); return -EINVAL; } config.cmd = cmd; config.response = true; config.indirection = false; config.cmd[0] = data->devfreq_type; config.cmd[1] = true; config.cmd[2] = DATA_INIT; config.cmd[3] = RELEASE; ret = acpm_ipc_send_data(ch_num, &config); if (ret) { dev_err(dev, "failed to send release infomation to FVP"); mutex_unlock(&data->devfreq->lock); return -EINVAL; } #endif data->devfreq->str_freq = 0; ret = update_devfreq(data->devfreq); if (ret && ret != -EAGAIN) { dev_err(&data->devfreq->dev, "devfreq failed with (%d) error\n", ret); mutex_unlock(&data->devfreq->lock); return NOTIFY_BAD; } mutex_unlock(&data->devfreq->lock); } #endif if (!data->use_acpm && pm_qos_request_active(&data->default_pm_qos_min)) pm_qos_update_request(&data->default_pm_qos_min, data->default_qos); return ret; } static int exynos_devfreq_probe(struct platform_device *pdev) { int ret = 0; struct exynos_devfreq_data *data; struct dev_pm_opp *init_opp; unsigned long init_freq = 0; #ifdef CONFIG_EXYNOS_DVFS_MANAGER int nr_constraint; #endif data = kzalloc(sizeof(struct exynos_devfreq_data), GFP_KERNEL); if (data == NULL) { dev_err(&pdev->dev, "failed to allocate devfreq data\n"); ret = -ENOMEM; goto err_data; } data->dev = &pdev->dev; mutex_init(&data->lock); /* parsing devfreq dts data for exynos */ ret = exynos_devfreq_parse_dt(data->dev->of_node, data); if (ret) { dev_err(data->dev, "failed to parse private data\n"); goto err_parse_dt; } data->devfreq_profile.max_state = data->max_state; data->devfreq_profile.target = exynos_devfreq_target; if (data->gov_type == SIMPLE_INTERACTIVE) { data->simple_interactive_data.pm_qos_class = data->pm_qos_class; data->simple_interactive_data.pm_qos_class_max = data->pm_qos_class_max; data->governor_data = &data->simple_interactive_data; } data->devfreq_profile.freq_table = kzalloc(sizeof(*(data->devfreq_profile.freq_table)) * data->max_state, GFP_KERNEL); if (data->devfreq_profile.freq_table == NULL) { dev_err(data->dev, "failed to allocate for freq_table\n"); ret = -ENOMEM; goto err_freqtable; } ret = exynos_init_freq_table(data); if (ret) { dev_err(data->dev, "failed initailize freq_table\n"); goto err_init_table; } devfreq_data[data->devfreq_type] = data; platform_set_drvdata(pdev, data); data->old_freq = (u32)data->devfreq_profile.initial_freq; data->old_idx = exynos_devfreq_get_opp_idx(data->opp_list, data->max_state, data->old_freq); if (data->old_idx < 0) { ret = -EINVAL; goto err_old_idx; } init_freq = (unsigned long)data->old_freq; init_opp = devfreq_recommended_opp(data->dev, &init_freq, 0); if (IS_ERR(init_opp)) { dev_err(data->dev, "not found valid OPP table for sync\n"); ret = PTR_ERR(init_opp); goto err_get_opp; } data->new_volt = (u32)dev_pm_opp_get_voltage(init_opp); dev_pm_opp_put(init_opp); dev_info(data->dev, "Initial Frequency: %ld, Initial Voltage: %d\n", init_freq, data->new_volt); data->old_volt = data->new_volt; #ifdef CONFIG_EXYNOS_DVFS_MANAGER ret = exynos_dm_data_init(data->dm_type, data, data->min_freq, data->max_freq, data->old_freq); if (ret) { dev_err(data->dev, "failed DVFS Manager data init\n"); goto err_dm_data_init; } for (nr_constraint = 0; nr_constraint < data->nr_constraint; nr_constraint++) { if(data->constraint[nr_constraint]) { ret = register_exynos_dm_constraint_table(data->dm_type, data->constraint[nr_constraint]); if (ret) { dev_err(data->dev,"failed registration constraint table(%d)\n", nr_constraint); goto err_dm_table; } } } #endif /* This flag guarantees initial frequency during boot time */ data->devfreq_disabled = true; data->devfreq = devfreq_add_device(data->dev, &data->devfreq_profile, data->governor_name, data->governor_data); if (IS_ERR(data->devfreq)) { dev_err(data->dev, "failed devfreq device added\n"); ret = -EINVAL; goto err_devfreq; } data->devfreq->min_freq = data->min_freq; data->devfreq->max_freq = data->max_freq; pm_qos_add_request(&data->sys_pm_qos_min, (int)data->pm_qos_class, data->min_freq); #ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG pm_qos_add_request(&data->debug_pm_qos_min, (int)data->pm_qos_class, data->min_freq); pm_qos_add_request(&data->debug_pm_qos_max, (int)data->pm_qos_class_max, data->max_freq); #endif if (data->pm_qos_class_max) pm_qos_add_request(&data->default_pm_qos_max, (int)data->pm_qos_class_max, data->max_freq); pm_qos_add_request(&data->default_pm_qos_min, (int)data->pm_qos_class, data->default_qos); pm_qos_add_request(&data->boot_pm_qos, (int)data->pm_qos_class, data->devfreq_profile.initial_freq); ret = devfreq_register_opp_notifier(data->dev, data->devfreq); if (ret) { dev_err(data->dev, "failed register opp notifier\n"); goto err_opp_noti; } data->reboot_notifier.notifier_call = exynos_devfreq_reboot_notifier; ret = register_reboot_notifier(&data->reboot_notifier); if (ret) { dev_err(data->dev, "failed register reboot notifier\n"); goto err_reboot_noti; } ret = sysfs_create_file(&data->devfreq->dev.kobj, &dev_attr_scaling_devfreq_min.attr); if (ret) dev_warn(data->dev, "failed create sysfs for devfreq pm_qos_min\n"); #ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG ret = sysfs_create_group(&data->devfreq->dev.kobj, &exynos_devfreq_attr_group); if (ret) dev_warn(data->dev, "failed create sysfs for devfreq data\n"); #endif ret = sysfs_create_group(&data->devfreq->dev.kobj, &devfreq_delay_time_attr_group); if (ret) dev_warn(data->dev, "failed create sysfs for devfreq data\n"); data->devfreq_disabled = false; if (!data->pm_domain) { /* set booting frequency during booting time */ pm_qos_update_request_timeout(&data->boot_pm_qos, data->boot_freq, data->boot_qos_timeout * USEC_PER_SEC); } else { pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); pm_qos_update_request(&data->boot_pm_qos, data->default_qos); pm_runtime_put_sync(&pdev->dev); } dev_info(data->dev, "devfreq is initialized!!\n"); return 0; err_reboot_noti: devfreq_unregister_opp_notifier(data->dev, data->devfreq); err_opp_noti: pm_qos_remove_request(&data->boot_pm_qos); pm_qos_remove_request(&data->default_pm_qos_min); if (data->pm_qos_class_max) pm_qos_remove_request(&data->default_pm_qos_max); #ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG pm_qos_remove_request(&data->debug_pm_qos_min); pm_qos_remove_request(&data->debug_pm_qos_max); #endif pm_qos_remove_request(&data->sys_pm_qos_min); devfreq_remove_device(data->devfreq); err_devfreq: #ifdef CONFIG_EXYNOS_DVFS_MANAGER for (; nr_constraint >= 0; nr_constraint--) { if (data->constraint[nr_constraint]) unregister_exynos_dm_constraint_table(data->dm_type, data->constraint[nr_constraint]); } err_dm_table: err_dm_data_init: #endif err_get_opp: err_old_idx: platform_set_drvdata(pdev, NULL); err_init_table: kfree(data->devfreq_profile.freq_table); err_freqtable: err_parse_dt: mutex_destroy(&data->lock); kfree(data); err_data: return ret; } static int exynos_devfreq_remove(struct platform_device *pdev) { struct exynos_devfreq_data *data = platform_get_drvdata(pdev); #ifdef CONFIG_EXYNOS_DVFS_MANAGER int nr_constraint; #endif sysfs_remove_file(&data->devfreq->dev.kobj, &dev_attr_scaling_devfreq_min.attr); #ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG sysfs_remove_group(&data->devfreq->dev.kobj, &exynos_devfreq_attr_group); #endif sysfs_remove_group(&data->devfreq->dev.kobj, &devfreq_delay_time_attr_group); unregister_reboot_notifier(&data->reboot_notifier); devfreq_unregister_opp_notifier(data->dev, data->devfreq); pm_qos_remove_request(&data->boot_pm_qos); pm_qos_remove_request(&data->default_pm_qos_min); if (data->pm_qos_class_max) pm_qos_remove_request(&data->default_pm_qos_max); #ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG pm_qos_remove_request(&data->debug_pm_qos_min); pm_qos_remove_request(&data->debug_pm_qos_max); #endif pm_qos_remove_request(&data->sys_pm_qos_min); devfreq_remove_device(data->devfreq); #ifdef CONFIG_EXYNOS_DVFS_MANAGER for (nr_constraint = 0; nr_constraint < data->nr_constraint; nr_constraint++) { if (data->constraint[nr_constraint]) unregister_exynos_dm_constraint_table(data->dm_type, data->constraint[nr_constraint]); } #endif platform_set_drvdata(pdev, NULL); kfree(data->devfreq_profile.freq_table); mutex_destroy(&data->lock); kfree(data); return 0; } static struct platform_device_id exynos_devfreq_driver_ids[] = { { .name = EXYNOS_DEVFREQ_MODULE_NAME, }, {}, }; MODULE_DEVICE_TABLE(platform, exynos_devfreq_driver_ids); static const struct of_device_id exynos_devfreq_match[] = { { .compatible = "samsung,exynos-devfreq", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_devfreq_match); static const struct dev_pm_ops exynos_devfreq_pm_ops = { .suspend_late = exynos_devfreq_suspend, .resume_early = exynos_devfreq_resume, }; static struct platform_driver exynos_devfreq_driver = { .probe = exynos_devfreq_probe, .remove = exynos_devfreq_remove, .id_table = exynos_devfreq_driver_ids, .driver = { .name = EXYNOS_DEVFREQ_MODULE_NAME, .owner = THIS_MODULE, .pm = &exynos_devfreq_pm_ops, .of_match_table = exynos_devfreq_match, }, }; static int exynos_devfreq_root_probe(struct platform_device *pdev) { struct device_node *np; int num_domains; np = pdev->dev.of_node; platform_driver_register(&exynos_devfreq_driver); /* alloc memory for devfreq data structure */ num_domains = of_get_child_count(np); devfreq_data = (struct exynos_devfreq_data **)kzalloc(sizeof(struct exynos_devfreq_data *) * num_domains, GFP_KERNEL); /* probe each devfreq node */ of_platform_populate(np, NULL, NULL, NULL); return 0; } static const struct of_device_id exynos_devfreq_root_match[] = { { .compatible = "samsung,exynos-devfreq-root", }, {}, }; static struct platform_driver exynos_devfreq_root_driver = { .probe = exynos_devfreq_root_probe, .driver = { .name = "exynos-devfreq-root", .owner = THIS_MODULE, .of_match_table = exynos_devfreq_root_match, }, }; module_platform_driver(exynos_devfreq_root_driver); MODULE_AUTHOR("Taekki Kim "); MODULE_DESCRIPTION("Samsung EXYNOS Soc series devfreq common driver"); MODULE_LICENSE("GPL");