/* * Copyright (C) 2016 * Allwinner Technology Co., Ltd. * 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. * * 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 "sunxi-budget-cooling.h" static int boot_cpu; #ifdef CONFIG_CPU_AUTOHOTPLUG_ROOMAGE extern int autohotplug_roomage_limit(unsigned int cluster_id, unsigned int min, unsigned int max); #endif static int budget_get_any_online_cpu(const cpumask_t *mask) { int cpu, lastcpu = 0xffff; for_each_cpu(cpu, mask) { if ((cpu != boot_cpu) && cpu_online(cpu)) { if (lastcpu == 0xffff) lastcpu = cpu; else if (cpu > lastcpu) lastcpu = cpu; } } return lastcpu; } static int budget_get_any_offline_cpu(const cpumask_t *mask) { int cpu, lastcpu = -1; for_each_cpu(cpu, mask) { if ((cpu != boot_cpu) && !cpu_online(cpu)) { if (lastcpu == -1) lastcpu = cpu; else if (cpu < lastcpu) lastcpu = cpu; } } return lastcpu; } static int budget_get_online_cpus(const cpumask_t *mask) { int cpu, num = 0; for_each_cpu(cpu, mask) { if (cpu_online(cpu)) num++; } return num; } int __ref sunxi_hotplug_update_state(struct sunxi_budget_cooling_device *cooling_device, u32 cluster) { s32 ret = 0; u32 online = 0, i = 0, cpuid; u32 max, min; u32 takedown; u32 takeup; u32 cooling_state = cooling_device->cooling_state; unsigned long cpuid_l, flags; struct sunxi_budget_hotplug *hotplug = cooling_device->hotplug; if (NULL == hotplug) return 0; spin_lock_irqsave(&hotplug->lock, flags); hotplug->cluster_num_limit[cluster] = hotplug->tbl[cooling_state].cluster_num[cluster]; spin_unlock_irqrestore(&hotplug->lock, flags); max = (hotplug->cluster_num_roof[cluster] >= hotplug->cluster_num_limit[cluster]) ? hotplug-> cluster_num_limit[cluster] : hotplug->cluster_num_roof[cluster]; min = (hotplug->cluster_num_floor[cluster] >= max) ? max : hotplug->cluster_num_floor[cluster]; #ifdef CONFIG_CPU_AUTOHOTPLUG_ROOMAGE pr_info("CPU Budget hotplug: cluster%d min:%d max:%d\n", cluster, min, max); autohotplug_roomage_limit(cluster, min, max); #endif get_online_cpus(); for_each_online_cpu(i) { if (cpumask_test_cpu(i, &cooling_device->cluster_cpus[cluster])) online++; } put_online_cpus(); takeup = (online < max) ? (max - online) : 0; while (takeup) { cpuid = budget_get_any_offline_cpu(&cooling_device-> cluster_cpus[cluster]); if (cpuid < nr_cpu_ids) { pr_info ("CPU Budget:Try to up cpu %d, cluster%d online %d, max %d\n", cpuid, cluster, online, max); cpuid_l = cpuid; ret = work_on_cpu(boot_cpu, (long (*)(void *))cpu_up, (void *)cpuid_l); } takeup--; } takedown = (online > max) ? (online - max) : 0; while (takedown) { cpuid = budget_get_any_online_cpu(&cooling_device-> cluster_cpus[cluster]); if (cpuid < nr_cpu_ids) { pr_info ("CPU Budget:Try to down cpu %d, cluster%d online %d, max %d\n", cpuid, cluster, online, max); cpuid_l = cpuid; ret = work_on_cpu(boot_cpu, (long (*)(void *))cpu_down, (void *)cpuid_l); } takedown--; } return ret; } EXPORT_SYMBOL(sunxi_hotplug_update_state); int sunxi_hotplug_get_roomage(struct sunxi_budget_cooling_device *cooling_device, u32 *num_floor, u32 *num_roof, u32 cluster) { struct sunxi_budget_hotplug *hotplug = cooling_device->hotplug; if (NULL == hotplug) return 0; *num_floor = hotplug->cluster_num_floor[cluster]; *num_roof = hotplug->cluster_num_roof[cluster]; return 0; } EXPORT_SYMBOL(sunxi_hotplug_get_roomage); int sunxi_hotplug_set_roomage(struct sunxi_budget_cooling_device *cooling_device, u32 num_floor, u32 num_roof, u32 cluster) { unsigned long flags; struct sunxi_budget_hotplug *hotplug = cooling_device->hotplug; if (NULL == hotplug) return 0; spin_lock_irqsave(&hotplug->lock, flags); hotplug->cluster_num_floor[cluster] = num_floor; hotplug->cluster_num_roof[cluster] = num_roof; spin_unlock_irqrestore(&hotplug->lock, flags); sunxi_hotplug_update_state(cooling_device, cluster); return 0; } EXPORT_SYMBOL(sunxi_hotplug_set_roomage); static int hotplug_thermal_notifier(struct notifier_block *nfb, unsigned long action, void *hcpu) { struct sunxi_budget_hotplug *hotplug = container_of(nfb, struct sunxi_budget_hotplug, notifer); struct sunxi_budget_cooling_device *bcd = hotplug->bcd; u32 i; u32 online = 0; u32 max = 0; u32 cpu = (unsigned long)hcpu; switch (action) { case CPU_UP_PREPARE: for (i = 0; i < CLUSTER_MAX; i++) { if (!cpumask_test_cpu(cpu, &bcd->cluster_cpus[i])) continue; online = budget_get_online_cpus(&bcd->cluster_cpus[i]); if (hotplug->cluster_num_roof[i] >= hotplug->cluster_num_limit[i]) max = hotplug->cluster_num_limit[i]; else max = hotplug->cluster_num_roof[i]; if (online >= max) { pr_info ("CPU Budget:reject cpu %d to up, cluster%d online %d, limit %d\n", cpu, i, online, max); return NOTIFY_BAD; } } return NOTIFY_DONE; default: break; } return NOTIFY_DONE; } /** * sunxi_hotplug_cooling_parse - parse the hotplug limit value and fill in struct sunxi_hotplug_cooling_table */ static struct sunxi_hotplug_cooling_table *sunxi_hotplug_cooling_parse(struct device_node *np, u32 tbl_num, u32 cluster_num) { struct sunxi_hotplug_cooling_table *tbl; u32 i, j, ret = 0; char name[32]; tbl = kzalloc(tbl_num * sizeof(*tbl), GFP_KERNEL); if (IS_ERR_OR_NULL(tbl)) { pr_err ("cooling_dev: not enough memory for hotplug cooling table\n"); return NULL; } for (i = 0; i < tbl_num; i++) { sprintf(name, "state%d", i); for (j = 0; j < cluster_num; j++) { if (of_property_read_u32_index(np, (const char *)&name, (j * 2 + 1), &(tbl[i]. cluster_num[j]))) { pr_err("node %s get failed!\n", name); ret = -EBUSY; } } } if (ret) { kfree(tbl); tbl = NULL; } return tbl; } struct sunxi_budget_hotplug *sunxi_hotplug_cooling_register(struct sunxi_budget_cooling_device *bcd) { struct sunxi_budget_hotplug *hotplug; struct sunxi_hotplug_cooling_table *tbl; struct device_node *np = bcd->dev->of_node; u32 cluster; hotplug = kzalloc(sizeof(*hotplug), GFP_KERNEL); if (IS_ERR_OR_NULL(hotplug)) { pr_err ("cooling_dev: not enough memory for hotplug cooling data\n"); goto fail; } tbl = sunxi_hotplug_cooling_parse(np, bcd->state_num, bcd->cluster_num); if (!tbl) { kfree(hotplug); goto fail; } hotplug->tbl_num = bcd->state_num; hotplug->tbl = tbl; spin_lock_init(&hotplug->lock); for (cluster = 0; cluster < bcd->cluster_num; cluster++) { hotplug->cluster_num_limit[cluster] = hotplug->tbl[0].cluster_num[cluster]; hotplug->cluster_num_roof[cluster] = hotplug->cluster_num_limit[cluster]; } hotplug->notifer.notifier_call = hotplug_thermal_notifier; register_cpu_notifier(&(hotplug->notifer)); hotplug->bcd = bcd; boot_cpu = cpumask_first(cpu_online_mask); pr_info("CPU hotplug cooling register Success\n"); return hotplug; fail: return NULL; } EXPORT_SYMBOL(sunxi_hotplug_cooling_register); void sunxi_hotplug_cooling_unregister(struct sunxi_budget_cooling_device *bcd) { if (!bcd->hotplug) return; unregister_cpu_notifier(&(bcd->hotplug->notifer)); kfree(bcd->hotplug->tbl); kfree(bcd->hotplug); bcd->hotplug = NULL; return; } EXPORT_SYMBOL(sunxi_hotplug_cooling_unregister);