18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ledtrig-cpu.c - LED trigger based on CPU activity 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * This LED trigger will be registered for first 8 CPUs and named 68c2ecf20Sopenharmony_ci * as cpu0..cpu7. There's additional trigger called cpu that 78c2ecf20Sopenharmony_ci * is on when any CPU is active. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * If you want support for arbitrary number of CPUs, make it one trigger, 108c2ecf20Sopenharmony_ci * with additional sysfs file selecting which CPU to watch. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * It can be bound to any LED just like other triggers using either a 138c2ecf20Sopenharmony_ci * board file or via sysfs interface. 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * An API named ledtrig_cpu is exported for any user, who want to add CPU 168c2ecf20Sopenharmony_ci * activity indication in their code. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * Copyright 2011 Linus Walleij <linus.walleij@linaro.org> 198c2ecf20Sopenharmony_ci * Copyright 2011 - 2012 Bryan Wu <bryan.wu@canonical.com> 208c2ecf20Sopenharmony_ci */ 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include <linux/kernel.h> 238c2ecf20Sopenharmony_ci#include <linux/init.h> 248c2ecf20Sopenharmony_ci#include <linux/slab.h> 258c2ecf20Sopenharmony_ci#include <linux/percpu.h> 268c2ecf20Sopenharmony_ci#include <linux/syscore_ops.h> 278c2ecf20Sopenharmony_ci#include <linux/rwsem.h> 288c2ecf20Sopenharmony_ci#include <linux/cpu.h> 298c2ecf20Sopenharmony_ci#include "../leds.h" 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define MAX_NAME_LEN 8 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistruct led_trigger_cpu { 348c2ecf20Sopenharmony_ci bool is_active; 358c2ecf20Sopenharmony_ci char name[MAX_NAME_LEN]; 368c2ecf20Sopenharmony_ci struct led_trigger *_trig; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic struct led_trigger *trig_cpu_all; 428c2ecf20Sopenharmony_cistatic atomic_t num_active_cpus = ATOMIC_INIT(0); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci/** 458c2ecf20Sopenharmony_ci * ledtrig_cpu - emit a CPU event as a trigger 468c2ecf20Sopenharmony_ci * @evt: CPU event to be emitted 478c2ecf20Sopenharmony_ci * 488c2ecf20Sopenharmony_ci * Emit a CPU event on a CPU core, which will trigger a 498c2ecf20Sopenharmony_ci * bound LED to turn on or turn off. 508c2ecf20Sopenharmony_ci */ 518c2ecf20Sopenharmony_civoid ledtrig_cpu(enum cpu_led_event ledevt) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig); 548c2ecf20Sopenharmony_ci bool is_active = trig->is_active; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci /* Locate the correct CPU LED */ 578c2ecf20Sopenharmony_ci switch (ledevt) { 588c2ecf20Sopenharmony_ci case CPU_LED_IDLE_END: 598c2ecf20Sopenharmony_ci case CPU_LED_START: 608c2ecf20Sopenharmony_ci /* Will turn the LED on, max brightness */ 618c2ecf20Sopenharmony_ci is_active = true; 628c2ecf20Sopenharmony_ci break; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci case CPU_LED_IDLE_START: 658c2ecf20Sopenharmony_ci case CPU_LED_STOP: 668c2ecf20Sopenharmony_ci case CPU_LED_HALTED: 678c2ecf20Sopenharmony_ci /* Will turn the LED off */ 688c2ecf20Sopenharmony_ci is_active = false; 698c2ecf20Sopenharmony_ci break; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci default: 728c2ecf20Sopenharmony_ci /* Will leave the LED as it is */ 738c2ecf20Sopenharmony_ci break; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (is_active != trig->is_active) { 778c2ecf20Sopenharmony_ci unsigned int active_cpus; 788c2ecf20Sopenharmony_ci unsigned int total_cpus; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Update trigger state */ 818c2ecf20Sopenharmony_ci trig->is_active = is_active; 828c2ecf20Sopenharmony_ci atomic_add(is_active ? 1 : -1, &num_active_cpus); 838c2ecf20Sopenharmony_ci active_cpus = atomic_read(&num_active_cpus); 848c2ecf20Sopenharmony_ci total_cpus = num_present_cpus(); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci led_trigger_event(trig->_trig, 878c2ecf20Sopenharmony_ci is_active ? LED_FULL : LED_OFF); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci led_trigger_event(trig_cpu_all, 918c2ecf20Sopenharmony_ci DIV_ROUND_UP(LED_FULL * active_cpus, total_cpus)); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ledtrig_cpu); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic int ledtrig_cpu_syscore_suspend(void) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci ledtrig_cpu(CPU_LED_STOP); 1008c2ecf20Sopenharmony_ci return 0; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic void ledtrig_cpu_syscore_resume(void) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci ledtrig_cpu(CPU_LED_START); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void ledtrig_cpu_syscore_shutdown(void) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci ledtrig_cpu(CPU_LED_HALTED); 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic struct syscore_ops ledtrig_cpu_syscore_ops = { 1148c2ecf20Sopenharmony_ci .shutdown = ledtrig_cpu_syscore_shutdown, 1158c2ecf20Sopenharmony_ci .suspend = ledtrig_cpu_syscore_suspend, 1168c2ecf20Sopenharmony_ci .resume = ledtrig_cpu_syscore_resume, 1178c2ecf20Sopenharmony_ci}; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int ledtrig_online_cpu(unsigned int cpu) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci ledtrig_cpu(CPU_LED_START); 1228c2ecf20Sopenharmony_ci return 0; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic int ledtrig_prepare_down_cpu(unsigned int cpu) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci ledtrig_cpu(CPU_LED_STOP); 1288c2ecf20Sopenharmony_ci return 0; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic int __init ledtrig_cpu_init(void) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci unsigned int cpu; 1348c2ecf20Sopenharmony_ci int ret; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* Supports up to 9999 cpu cores */ 1378c2ecf20Sopenharmony_ci BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* 1408c2ecf20Sopenharmony_ci * Registering a trigger for all CPUs. 1418c2ecf20Sopenharmony_ci */ 1428c2ecf20Sopenharmony_ci led_trigger_register_simple("cpu", &trig_cpu_all); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* 1458c2ecf20Sopenharmony_ci * Registering CPU led trigger for each CPU core here 1468c2ecf20Sopenharmony_ci * ignores CPU hotplug, but after this CPU hotplug works 1478c2ecf20Sopenharmony_ci * fine with this trigger. 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 1508c2ecf20Sopenharmony_ci struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (cpu >= 8) 1538c2ecf20Sopenharmony_ci continue; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci snprintf(trig->name, MAX_NAME_LEN, "cpu%u", cpu); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci led_trigger_register_simple(trig->name, &trig->_trig); 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci register_syscore_ops(&ledtrig_cpu_syscore_ops); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "leds/trigger:starting", 1638c2ecf20Sopenharmony_ci ledtrig_online_cpu, ledtrig_prepare_down_cpu); 1648c2ecf20Sopenharmony_ci if (ret < 0) 1658c2ecf20Sopenharmony_ci pr_err("CPU hotplug notifier for ledtrig-cpu could not be registered: %d\n", 1668c2ecf20Sopenharmony_ci ret); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci return 0; 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_cidevice_initcall(ledtrig_cpu_init); 173