/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CREATE_TRACE_POINTS #include #define TEMP_BASE_POINT 35 #define TEMP_MAX_POINT 95 #define CPU_HOTPLUG_LIMIT 80 #define CPU_BIT_MASK(cpu) BIT(cpu) #define DEFAULT_TEMP 40 #define DEFAULT_LOW_HYST_TEMP 10 #define DEFAULT_HIGH_HYST_TEMP 5 #define CLUSTER_OFFSET_FOR_MPIDR 8 #define MAX_CORES_PER_CLUSTER 4 #define MAX_NUM_OF_CLUSTERS 2 #define NUM_OF_CORNERS 10 #define DEFAULT_SCALING_FACTOR 1 #define ALLOCATE_2D_ARRAY(type)\ static type **allocate_2d_array_##type(int idx)\ {\ int i;\ type **ptr = NULL;\ if (!idx) \ return ERR_PTR(-EINVAL);\ ptr = kzalloc(sizeof(*ptr) * TEMP_DATA_POINTS, \ GFP_KERNEL);\ if (!ptr) { \ return ERR_PTR(-ENOMEM); \ } \ for (i = 0; i < TEMP_DATA_POINTS; i++) { \ ptr[i] = kzalloc(sizeof(*ptr[i]) * \ idx, GFP_KERNEL);\ if (!ptr[i]) {\ goto done;\ } \ } \ return ptr;\ done:\ for (i = 0; i < TEMP_DATA_POINTS; i++) \ kfree(ptr[i]);\ kfree(ptr);\ return ERR_PTR(-ENOMEM);\ } struct cpu_activity_info { int cpu; int mpidr; long temp; int sensor_id; struct sensor_threshold hi_threshold; struct sensor_threshold low_threshold; struct cpu_static_info *sp; }; struct cpu_static_info { uint32_t **power; cpumask_t mask; struct cpufreq_frequency_table *table; uint32_t *voltage; uint32_t num_of_freqs; }; static DEFINE_MUTEX(policy_update_mutex); static DEFINE_MUTEX(kthread_update_mutex); static DEFINE_SPINLOCK(update_lock); static struct delayed_work sampling_work; static struct completion sampling_completion; static struct task_struct *sampling_task; static int low_hyst_temp; static int high_hyst_temp; static struct platform_device *msm_core_pdev; static struct cpu_activity_info activity[NR_CPUS]; DEFINE_PER_CPU(struct cpu_pstate_pwr *, ptable); static struct cpu_pwr_stats cpu_stats[NR_CPUS]; static uint32_t scaling_factor; ALLOCATE_2D_ARRAY(uint32_t); static int poll_ms; module_param_named(polling_interval, poll_ms, int, S_IRUGO | S_IWUSR | S_IWGRP); static int disabled; module_param_named(disabled, disabled, int, S_IRUGO | S_IWUSR | S_IWGRP); static bool in_suspend; static bool activate_power_table; static int max_throttling_temp = 80; /* in C */ module_param_named(throttling_temp, max_throttling_temp, int, S_IRUGO | S_IWUSR | S_IWGRP); /* * Cannot be called from an interrupt context */ static void set_and_activate_threshold(uint32_t sensor_id, struct sensor_threshold *threshold) { if (sensor_set_trip(sensor_id, threshold)) { pr_err("%s: Error in setting trip %d\n", KBUILD_MODNAME, threshold->trip); return; } if (sensor_activate_trip(sensor_id, threshold, true)) { sensor_cancel_trip(sensor_id, threshold); pr_err("%s: Error in enabling trip %d\n", KBUILD_MODNAME, threshold->trip); return; } } static void set_threshold(struct cpu_activity_info *cpu_node) { if (cpu_node->sensor_id < 0) return; /* * Before operating on the threshold structure which is used by * thermal core ensure that the sensor is disabled to prevent * incorrect operations on the sensor list maintained by thermal code. */ sensor_activate_trip(cpu_node->sensor_id, &cpu_node->hi_threshold, false); sensor_activate_trip(cpu_node->sensor_id, &cpu_node->low_threshold, false); cpu_node->hi_threshold.temp = (cpu_node->temp + high_hyst_temp) * scaling_factor; cpu_node->low_threshold.temp = (cpu_node->temp - low_hyst_temp) * scaling_factor; /* * Set the threshold only if we are below the hotplug limit * Adding more work at this high temperature range, seems to * fail hotplug notifications. */ if (cpu_node->hi_threshold.temp < (CPU_HOTPLUG_LIMIT * scaling_factor)) set_and_activate_threshold(cpu_node->sensor_id, &cpu_node->hi_threshold); set_and_activate_threshold(cpu_node->sensor_id, &cpu_node->low_threshold); } static void samplequeue_handle(struct work_struct *work) { complete(&sampling_completion); } /* May be called from an interrupt context */ static void core_temp_notify(enum thermal_trip_type type, int temp, void *data) { struct cpu_activity_info *cpu_node = (struct cpu_activity_info *) data; trace_temp_notification(cpu_node->sensor_id, type, temp, cpu_node->temp); cpu_node->temp = temp / scaling_factor; complete(&sampling_completion); } static void repopulate_stats(int cpu) { int i; struct cpu_activity_info *cpu_node = &activity[cpu]; int temp_point; struct cpu_pstate_pwr *pt = per_cpu(ptable, cpu); if (!pt) return; if (cpu_node->temp < TEMP_BASE_POINT) temp_point = 0; else if (cpu_node->temp > TEMP_MAX_POINT) temp_point = TEMP_DATA_POINTS - 1; else temp_point = (cpu_node->temp - TEMP_BASE_POINT) / 5; cpu_stats[cpu].temp = cpu_node->temp; for (i = 0; i < cpu_node->sp->num_of_freqs; i++) pt[i].power = cpu_node->sp->power[temp_point][i]; trace_cpu_stats(cpu, cpu_stats[cpu].temp, pt[0].power, pt[cpu_node->sp->num_of_freqs-1].power); }; void trigger_cpu_pwr_stats_calc(void) { int cpu; static long prev_temp[NR_CPUS]; struct cpu_activity_info *cpu_node; long temp; if (disabled) return; spin_lock(&update_lock); for_each_online_cpu(cpu) { cpu_node = &activity[cpu]; if (cpu_node->sensor_id < 0) continue; if (cpu_node->temp == prev_temp[cpu]) sensor_get_temp(cpu_node->sensor_id, &temp); cpu_node->temp = temp / scaling_factor; prev_temp[cpu] = cpu_node->temp; /* * Do not populate/update stats before policy and ptable have * been updated. */ if (activate_power_table && cpu_stats[cpu].ptable && cpu_node->sp->table) repopulate_stats(cpu); } spin_unlock(&update_lock); } EXPORT_SYMBOL(trigger_cpu_pwr_stats_calc); void set_cpu_throttled(cpumask_t *mask, bool throttling) { int cpu; if (!mask) return; spin_lock(&update_lock); for_each_cpu(cpu, mask) cpu_stats[cpu].throttling = throttling; spin_unlock(&update_lock); } EXPORT_SYMBOL(set_cpu_throttled); static void update_related_freq_table(struct cpufreq_policy *policy) { int cpu, num_of_freqs; struct cpufreq_frequency_table *table; table = cpufreq_frequency_get_table(policy->cpu); if (!table) { pr_err("Couldn't get freq table for cpu%d\n", policy->cpu); return; } for (num_of_freqs = 0; table[num_of_freqs].frequency != CPUFREQ_TABLE_END;) num_of_freqs++; /* * Synchronous cores within cluster have the same * policy. Since these cores do not have the cpufreq * table initialized for all of them, copy the same * table to all the related cpus. */ for_each_cpu(cpu, policy->related_cpus) { activity[cpu].sp->table = table; activity[cpu].sp->num_of_freqs = num_of_freqs; } } static __ref int do_sampling(void *data) { int cpu; struct cpu_activity_info *cpu_node; static int prev_temp[NR_CPUS]; while (!kthread_should_stop()) { wait_for_completion(&sampling_completion); cancel_delayed_work(&sampling_work); mutex_lock(&kthread_update_mutex); if (in_suspend) goto unlock; trigger_cpu_pwr_stats_calc(); for_each_online_cpu(cpu) { cpu_node = &activity[cpu]; if (prev_temp[cpu] != cpu_node->temp) { prev_temp[cpu] = cpu_node->temp; set_threshold(cpu_node); trace_temp_threshold(cpu, cpu_node->temp, cpu_node->hi_threshold.temp / scaling_factor, cpu_node->low_threshold.temp / scaling_factor); } } if (!poll_ms) goto unlock; schedule_delayed_work(&sampling_work, msecs_to_jiffies(poll_ms)); unlock: mutex_unlock(&kthread_update_mutex); } return 0; } static void clear_static_power(struct cpu_static_info *sp) { int i; if (!sp) return; if (cpumask_first(&sp->mask) < num_possible_cpus()) return; for (i = 0; i < TEMP_DATA_POINTS; i++) kfree(sp->power[i]); kfree(sp->power); kfree(sp); } BLOCKING_NOTIFIER_HEAD(msm_core_stats_notifier_list); struct blocking_notifier_head *get_power_update_notifier(void) { return &msm_core_stats_notifier_list; } int register_cpu_pwr_stats_ready_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&msm_core_stats_notifier_list, nb); } static int update_userspace_power(struct sched_params __user *argp) { int i; int ret; int cpu; struct cpu_activity_info *node; struct cpu_static_info *sp, *clear_sp; int cpumask, cluster, mpidr; get_user(cpumask, &argp->cpumask); get_user(cluster, &argp->cluster); mpidr = cluster << 8; pr_debug("%s: cpumask %d, cluster: %d\n", __func__, cpumask, cluster); for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) { if (!(cpumask & 0x01)) continue; mpidr |= i; for_each_possible_cpu(cpu) { if (cpu_logical_map(cpu) == mpidr) break; } } if (cpu >= num_possible_cpus()) return -EINVAL; node = &activity[cpu]; /* Allocate new memory to copy cpumask specific power * information. */ sp = kzalloc(sizeof(*sp), GFP_KERNEL); if (!sp) return -ENOMEM; sp->power = allocate_2d_array_uint32_t(node->sp->num_of_freqs); if (IS_ERR_OR_NULL(sp->power)) { ret = PTR_ERR(sp->power); kfree(sp); return ret; } sp->num_of_freqs = node->sp->num_of_freqs; sp->voltage = node->sp->voltage; sp->table = node->sp->table; for (i = 0; i < TEMP_DATA_POINTS; i++) { ret = copy_from_user(sp->power[i], &argp->power[i][0], sizeof(sp->power[i][0]) * node->sp->num_of_freqs); if (ret) goto failed; } /* Copy the same power values for all the cpus in the cpumask * argp->cpumask within the cluster (argp->cluster) */ spin_lock(&update_lock); get_user(cpumask, &argp->cpumask); for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) { if (!(cpumask & 0x01)) continue; mpidr = (cluster << CLUSTER_OFFSET_FOR_MPIDR); mpidr |= i; for_each_possible_cpu(cpu) { if (!(cpu_logical_map(cpu) == mpidr)) continue; node = &activity[cpu]; clear_sp = node->sp; node->sp = sp; cpumask_set_cpu(cpu, &sp->mask); if (clear_sp) { cpumask_clear_cpu(cpu, &clear_sp->mask); clear_static_power(clear_sp); } cpu_stats[cpu].ptable = per_cpu(ptable, cpu); repopulate_stats(cpu); blocking_notifier_call_chain( &msm_core_stats_notifier_list, cpu, NULL); } } spin_unlock(&update_lock); activate_power_table = true; return 0; failed: for (i = 0; i < TEMP_DATA_POINTS; i++) kfree(sp->power[i]); kfree(sp->power); kfree(sp); return ret; } static long msm_core_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long ret = 0; struct cpu_activity_info *node = NULL; struct sched_params __user *argp = (struct sched_params __user *)arg; int i, cpu = num_possible_cpus(); int mpidr, cluster, cpumask; if (!argp) return -EINVAL; get_user(cluster, &argp->cluster); mpidr = (argp->cluster << (MAX_CORES_PER_CLUSTER * MAX_NUM_OF_CLUSTERS)); cpumask = argp->cpumask; switch (cmd) { case EA_LEAKAGE: ret = update_userspace_power(argp); if (ret) pr_err("Userspace power update failed with %ld\n", ret); break; case EA_VOLT: for (i = 0; cpumask > 0; i++, cpumask >>= 1) { for_each_possible_cpu(cpu) { if (cpu_logical_map(cpu) == (mpidr | i)) break; } } if (cpu >= num_possible_cpus()) break; mutex_lock(&policy_update_mutex); node = &activity[cpu]; if (!node->sp->table) { ret = -EINVAL; goto unlock; } ret = copy_to_user((void __user *)&argp->voltage[0], node->sp->voltage, sizeof(uint32_t) * node->sp->num_of_freqs); if (ret) break; for (i = 0; i < node->sp->num_of_freqs; i++) { ret = copy_to_user((void __user *)&argp->freq[i], &node->sp->table[i].frequency, sizeof(uint32_t)); if (ret) break; } unlock: mutex_unlock(&policy_update_mutex); break; default: break; } return ret; } #ifdef CONFIG_COMPAT static long msm_core_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { arg = (unsigned long)compat_ptr(arg); return msm_core_ioctl(file, cmd, arg); } #endif static int msm_core_open(struct inode *inode, struct file *file) { return 0; } static int msm_core_release(struct inode *inode, struct file *file) { return 0; } static inline void init_sens_threshold(struct sensor_threshold *threshold, enum thermal_trip_type trip, long temp, void *data) { threshold->trip = trip; threshold->temp = temp; threshold->data = data; threshold->notify = (void *)core_temp_notify; } static int msm_core_stats_init(struct device *dev, int cpu) { int i; struct cpu_activity_info *cpu_node; struct cpu_pstate_pwr *pstate = NULL; cpu_node = &activity[cpu]; cpu_stats[cpu].cpu = cpu; cpu_stats[cpu].temp = cpu_node->temp; cpu_stats[cpu].throttling = false; cpu_stats[cpu].len = cpu_node->sp->num_of_freqs; pstate = devm_kzalloc(dev, sizeof(*pstate) * cpu_node->sp->num_of_freqs, GFP_KERNEL); if (!pstate) return -ENOMEM; for (i = 0; i < cpu_node->sp->num_of_freqs; i++) pstate[i].freq = cpu_node->sp->table[i].frequency; per_cpu(ptable, cpu) = pstate; return 0; } static int msm_core_task_init(struct device *dev) { init_completion(&sampling_completion); sampling_task = kthread_run(do_sampling, NULL, "msm-core:sampling"); if (IS_ERR(sampling_task)) { pr_err("Failed to create do_sampling err: %ld\n", PTR_ERR(sampling_task)); return PTR_ERR(sampling_task); } return 0; } struct cpu_pwr_stats *get_cpu_pwr_stats(void) { return cpu_stats; } EXPORT_SYMBOL(get_cpu_pwr_stats); static int msm_get_power_values(int cpu, struct cpu_static_info *sp) { int i = 0, j; int ret = 0; uint64_t power; /* Calculate dynamic power spent for every frequency using formula: * Power = V * V * f * where V = voltage for frequency * f = frequency * */ sp->power = allocate_2d_array_uint32_t(sp->num_of_freqs); if (IS_ERR_OR_NULL(sp->power)) return PTR_ERR(sp->power); for (i = 0; i < TEMP_DATA_POINTS; i++) { for (j = 0; j < sp->num_of_freqs; j++) { power = sp->voltage[j] * sp->table[j].frequency; do_div(power, 1000); do_div(power, 1000); power *= sp->voltage[j]; do_div(power, 1000); sp->power[i][j] = power; } } return ret; } static int msm_get_voltage_levels(struct device *dev, int cpu, struct cpu_static_info *sp) { unsigned int *voltage; int i; int corner; struct dev_pm_opp *opp; struct device *cpu_dev = get_cpu_device(cpu); /* * Convert cpr corner voltage to average voltage of both * a53 and a57 votlage value */ int average_voltage[NUM_OF_CORNERS] = {0, 746, 841, 843, 940, 953, 976, 1024, 1090, 1100}; if (!cpu_dev) return -ENODEV; voltage = devm_kzalloc(dev, sizeof(*voltage) * sp->num_of_freqs, GFP_KERNEL); if (!voltage) return -ENOMEM; rcu_read_lock(); for (i = 0; i < sp->num_of_freqs; i++) { opp = dev_pm_opp_find_freq_exact(cpu_dev, sp->table[i].frequency * 1000, true); corner = dev_pm_opp_get_voltage(opp); if (corner > 400000) voltage[i] = corner / 1000; else if (corner > 0 && corner < ARRAY_SIZE(average_voltage)) voltage[i] = average_voltage[corner]; else voltage[i] = average_voltage[ARRAY_SIZE(average_voltage) - 1]; } rcu_read_unlock(); sp->voltage = voltage; return 0; } static int msm_core_dyn_pwr_init(struct platform_device *pdev, int cpu) { int ret = 0; if (!activity[cpu].sp->table) return 0; ret = msm_get_voltage_levels(&pdev->dev, cpu, activity[cpu].sp); if (ret) return ret; ret = msm_get_power_values(cpu, activity[cpu].sp); return ret; } static int msm_core_tsens_init(struct device_node *node, int cpu) { int ret = 0; char *key = NULL; struct device_node *phandle; const char *sensor_type = NULL; struct cpu_activity_info *cpu_node = &activity[cpu]; long temp; if (!node) return -ENODEV; key = "sensor"; phandle = of_parse_phandle(node, key, 0); if (!phandle) { pr_info("%s: No sensor mapping found for the core\n", __func__); /* Do not treat this as error as some targets might have * temperature notification only in userspace. * Use default temperature for the core. Userspace might * update the temperature once it is up. */ cpu_node->sensor_id = -ENODEV; cpu_node->temp = DEFAULT_TEMP; return 0; } key = "qcom,sensor-name"; ret = of_property_read_string(phandle, key, &sensor_type); if (ret) { pr_err("%s: Cannot read tsens id\n", __func__); return ret; } cpu_node->sensor_id = sensor_get_id((char *)sensor_type); if (cpu_node->sensor_id < 0) return cpu_node->sensor_id; key = "qcom,scaling-factor"; ret = of_property_read_u32(phandle, key, &scaling_factor); if (ret) { pr_info("%s: Cannot read tsens scaling factor\n", __func__); scaling_factor = DEFAULT_SCALING_FACTOR; } ret = sensor_get_temp(cpu_node->sensor_id, &temp); if (ret) return ret; cpu_node->temp = temp / scaling_factor; init_sens_threshold(&cpu_node->hi_threshold, THERMAL_TRIP_CONFIGURABLE_HI, (cpu_node->temp + high_hyst_temp) * scaling_factor, (void *)cpu_node); init_sens_threshold(&cpu_node->low_threshold, THERMAL_TRIP_CONFIGURABLE_LOW, (cpu_node->temp - low_hyst_temp) * scaling_factor, (void *)cpu_node); return ret; } static int msm_core_mpidr_init(struct device_node *phandle) { int ret = 0; char *key = NULL; int mpidr; key = "reg"; ret = of_property_read_u32(phandle, key, &mpidr); if (ret) { pr_err("%s: Cannot read mpidr\n", __func__); return ret; } return mpidr; } static int msm_core_cpu_policy_handler(struct notifier_block *nb, unsigned long val, void *data) { struct cpufreq_policy *policy = data; struct cpu_activity_info *cpu_info = &activity[policy->cpu]; int cpu; int ret; if (cpu_info->sp->table) return NOTIFY_OK; switch (val) { case CPUFREQ_CREATE_POLICY: mutex_lock(&policy_update_mutex); update_related_freq_table(policy); for_each_cpu(cpu, policy->related_cpus) { ret = msm_core_dyn_pwr_init(msm_core_pdev, cpu); if (ret) pr_debug("voltage-pwr table update failed\n"); ret = msm_core_stats_init(&msm_core_pdev->dev, cpu); if (ret) pr_debug("Stats table update failed\n"); } mutex_unlock(&policy_update_mutex); break; default: break; } return NOTIFY_OK; } struct notifier_block cpu_policy = { .notifier_call = msm_core_cpu_policy_handler }; static int system_suspend_handler(struct notifier_block *nb, unsigned long val, void *data) { int cpu; mutex_lock(&kthread_update_mutex); switch (val) { case PM_POST_HIBERNATION: case PM_POST_SUSPEND: case PM_POST_RESTORE: /* * Set completion event to read temperature and repopulate * stats */ in_suspend = 0; complete(&sampling_completion); break; case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: /* * cancel delayed work to be able to restart immediately * after system resume */ in_suspend = 1; cancel_delayed_work(&sampling_work); /* * cancel TSENS interrupts as we do not want to wake up from * suspend to take care of repopulate stats while the system is * in suspend */ for_each_possible_cpu(cpu) { if (activity[cpu].sensor_id < 0) continue; sensor_activate_trip(activity[cpu].sensor_id, &activity[cpu].hi_threshold, false); sensor_activate_trip(activity[cpu].sensor_id, &activity[cpu].low_threshold, false); } break; default: break; } mutex_unlock(&kthread_update_mutex); return NOTIFY_OK; } static int msm_core_freq_init(void) { int cpu; struct cpufreq_policy *policy; for_each_possible_cpu(cpu) { activity[cpu].sp = kzalloc(sizeof(*(activity[cpu].sp)), GFP_KERNEL); if (!activity[cpu].sp) return -ENOMEM; } for_each_online_cpu(cpu) { if (activity[cpu].sp->table) continue; policy = cpufreq_cpu_get(cpu); if (!policy) continue; update_related_freq_table(policy); cpufreq_cpu_put(policy); } return 0; } static int msm_core_params_init(struct platform_device *pdev) { int ret = 0; unsigned long cpu = 0; struct device_node *child_node = NULL; struct device_node *ea_node = NULL; char *key = NULL; int mpidr; for_each_possible_cpu(cpu) { child_node = of_get_cpu_node(cpu, NULL); if (!child_node) continue; mpidr = msm_core_mpidr_init(child_node); if (mpidr < 0) return mpidr; if (cpu >= num_possible_cpus()) continue; activity[cpu].mpidr = mpidr; key = "qcom,ea"; ea_node = of_parse_phandle(child_node, key, 0); if (!ea_node) { pr_err("%s Couldn't find the ea_node for cpu%lu\n", __func__, cpu); return -ENODEV; } ret = msm_core_tsens_init(ea_node, cpu); if (ret) return ret; if (!activity[cpu].sp->table) continue; ret = msm_core_dyn_pwr_init(msm_core_pdev, cpu); if (ret) pr_debug("voltage-pwr table update failed\n"); ret = msm_core_stats_init(&msm_core_pdev->dev, cpu); if (ret) pr_debug("Stats table update failed\n"); } return 0; } static const struct file_operations msm_core_ops = { .owner = THIS_MODULE, .unlocked_ioctl = msm_core_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = msm_core_compat_ioctl, #endif .open = msm_core_open, .release = msm_core_release, }; static struct miscdevice msm_core_device = { .minor = MISC_DYNAMIC_MINOR, .name = "pta", .fops = &msm_core_ops }; static void free_dyn_memory(void) { int i, cpu; for_each_possible_cpu(cpu) { if (activity[cpu].sp) { for (i = 0; i < TEMP_DATA_POINTS; i++) { if (!activity[cpu].sp->power) break; kfree(activity[cpu].sp->power[i]); } } kfree(activity[cpu].sp); } } static int uio_init(struct platform_device *pdev) { int ret = 0; struct uio_info *info = NULL; struct resource *clnt_res = NULL; u32 ea_mem_size = 0; phys_addr_t ea_mem_pyhsical = 0; clnt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!clnt_res) { pr_err("resource not found\n"); return -ENODEV; } info = devm_kzalloc(&pdev->dev, sizeof(struct uio_info), GFP_KERNEL); if (!info) return -ENOMEM; ea_mem_size = resource_size(clnt_res); ea_mem_pyhsical = clnt_res->start; if (ea_mem_size == 0) { pr_err("msm-core: memory size is zero"); return -EINVAL; } /* Setup device */ info->name = clnt_res->name; info->version = "1.0"; info->mem[0].addr = ea_mem_pyhsical; info->mem[0].size = ea_mem_size; info->mem[0].memtype = UIO_MEM_PHYS; ret = uio_register_device(&pdev->dev, info); if (ret) { pr_err("uio register failed ret=%d", ret); return ret; } dev_set_drvdata(&pdev->dev, info); return 0; } static int msm_core_dev_probe(struct platform_device *pdev) { int ret = 0; char *key = NULL; struct device_node *node; int cpu; struct uio_info *info; if (!pdev) return -ENODEV; msm_core_pdev = pdev; node = pdev->dev.of_node; if (!node) return -ENODEV; key = "qcom,low-hyst-temp"; ret = of_property_read_u32(node, key, &low_hyst_temp); if (ret) low_hyst_temp = DEFAULT_LOW_HYST_TEMP; key = "qcom,high-hyst-temp"; ret = of_property_read_u32(node, key, &high_hyst_temp); if (ret) high_hyst_temp = DEFAULT_HIGH_HYST_TEMP; key = "qcom,polling-interval"; ret = of_property_read_u32(node, key, &poll_ms); if (ret) pr_info("msm-core initialized without polling period\n"); key = "qcom,throttling-temp"; ret = of_property_read_u32(node, key, &max_throttling_temp); ret = uio_init(pdev); if (ret) return ret; ret = msm_core_freq_init(); if (ret) goto failed; ret = misc_register(&msm_core_device); if (ret) { pr_err("%s: Error registering device %d\n", __func__, ret); goto failed; } ret = msm_core_params_init(pdev); if (ret) goto failed; ret = msm_core_task_init(&pdev->dev); if (ret) goto failed; for_each_possible_cpu(cpu) set_threshold(&activity[cpu]); INIT_DEFERRABLE_WORK(&sampling_work, samplequeue_handle); schedule_delayed_work(&sampling_work, msecs_to_jiffies(0)); cpufreq_register_notifier(&cpu_policy, CPUFREQ_POLICY_NOTIFIER); pm_notifier(system_suspend_handler, 0); return 0; failed: info = dev_get_drvdata(&pdev->dev); uio_unregister_device(info); free_dyn_memory(); return ret; } static int msm_core_remove(struct platform_device *pdev) { int cpu; struct uio_info *info = dev_get_drvdata(&pdev->dev); uio_unregister_device(info); for_each_possible_cpu(cpu) { if (activity[cpu].sensor_id < 0) continue; sensor_cancel_trip(activity[cpu].sensor_id, &activity[cpu].hi_threshold); sensor_cancel_trip(activity[cpu].sensor_id, &activity[cpu].low_threshold); } free_dyn_memory(); misc_deregister(&msm_core_device); return 0; } static struct of_device_id msm_core_match_table[] = { {.compatible = "qcom,apss-core-ea"}, {}, }; static struct platform_driver msm_core_driver = { .probe = msm_core_dev_probe, .driver = { .name = "msm_core", .owner = THIS_MODULE, .of_match_table = msm_core_match_table, }, .remove = msm_core_remove, }; static int __init msm_core_init(void) { return platform_driver_register(&msm_core_driver); } late_initcall(msm_core_init);