linux/drivers/cpuidle/cpuidle-cps.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2014 Imagination Technologies
   4 * Author: Paul Burton <paul.burton@mips.com>
   5 */
   6
   7#include <linux/cpu_pm.h>
   8#include <linux/cpuidle.h>
   9#include <linux/init.h>
  10
  11#include <asm/idle.h>
  12#include <asm/pm-cps.h>
  13
  14/* Enumeration of the various idle states this driver may enter */
  15enum cps_idle_state {
  16        STATE_WAIT = 0,         /* MIPS wait instruction, coherent */
  17        STATE_NC_WAIT,          /* MIPS wait instruction, non-coherent */
  18        STATE_CLOCK_GATED,      /* Core clock gated */
  19        STATE_POWER_GATED,      /* Core power gated */
  20        STATE_COUNT
  21};
  22
  23static int cps_nc_enter(struct cpuidle_device *dev,
  24                        struct cpuidle_driver *drv, int index)
  25{
  26        enum cps_pm_state pm_state;
  27        int err;
  28
  29        /*
  30         * At least one core must remain powered up & clocked in order for the
  31         * system to have any hope of functioning.
  32         *
  33         * TODO: don't treat core 0 specially, just prevent the final core
  34         * TODO: remap interrupt affinity temporarily
  35         */
  36        if (cpus_are_siblings(0, dev->cpu) && (index > STATE_NC_WAIT))
  37                index = STATE_NC_WAIT;
  38
  39        /* Select the appropriate cps_pm_state */
  40        switch (index) {
  41        case STATE_NC_WAIT:
  42                pm_state = CPS_PM_NC_WAIT;
  43                break;
  44        case STATE_CLOCK_GATED:
  45                pm_state = CPS_PM_CLOCK_GATED;
  46                break;
  47        case STATE_POWER_GATED:
  48                pm_state = CPS_PM_POWER_GATED;
  49                break;
  50        default:
  51                BUG();
  52                return -EINVAL;
  53        }
  54
  55        /* Notify listeners the CPU is about to power down */
  56        if ((pm_state == CPS_PM_POWER_GATED) && cpu_pm_enter())
  57                return -EINTR;
  58
  59        /* Enter that state */
  60        err = cps_pm_enter_state(pm_state);
  61
  62        /* Notify listeners the CPU is back up */
  63        if (pm_state == CPS_PM_POWER_GATED)
  64                cpu_pm_exit();
  65
  66        return err ?: index;
  67}
  68
  69static struct cpuidle_driver cps_driver = {
  70        .name                   = "cpc_cpuidle",
  71        .owner                  = THIS_MODULE,
  72        .states = {
  73                [STATE_WAIT] = MIPS_CPUIDLE_WAIT_STATE,
  74                [STATE_NC_WAIT] = {
  75                        .enter  = cps_nc_enter,
  76                        .exit_latency           = 200,
  77                        .target_residency       = 450,
  78                        .name   = "nc-wait",
  79                        .desc   = "non-coherent MIPS wait",
  80                },
  81                [STATE_CLOCK_GATED] = {
  82                        .enter  = cps_nc_enter,
  83                        .exit_latency           = 300,
  84                        .target_residency       = 700,
  85                        .flags  = CPUIDLE_FLAG_TIMER_STOP,
  86                        .name   = "clock-gated",
  87                        .desc   = "core clock gated",
  88                },
  89                [STATE_POWER_GATED] = {
  90                        .enter  = cps_nc_enter,
  91                        .exit_latency           = 600,
  92                        .target_residency       = 1000,
  93                        .flags  = CPUIDLE_FLAG_TIMER_STOP,
  94                        .name   = "power-gated",
  95                        .desc   = "core power gated",
  96                },
  97        },
  98        .state_count            = STATE_COUNT,
  99        .safe_state_index       = 0,
 100};
 101
 102static void __init cps_cpuidle_unregister(void)
 103{
 104        int cpu;
 105        struct cpuidle_device *device;
 106
 107        for_each_possible_cpu(cpu) {
 108                device = &per_cpu(cpuidle_dev, cpu);
 109                cpuidle_unregister_device(device);
 110        }
 111
 112        cpuidle_unregister_driver(&cps_driver);
 113}
 114
 115static int __init cps_cpuidle_init(void)
 116{
 117        int err, cpu, i;
 118        struct cpuidle_device *device;
 119
 120        /* Detect supported states */
 121        if (!cps_pm_support_state(CPS_PM_POWER_GATED))
 122                cps_driver.state_count = STATE_CLOCK_GATED + 1;
 123        if (!cps_pm_support_state(CPS_PM_CLOCK_GATED))
 124                cps_driver.state_count = STATE_NC_WAIT + 1;
 125        if (!cps_pm_support_state(CPS_PM_NC_WAIT))
 126                cps_driver.state_count = STATE_WAIT + 1;
 127
 128        /* Inform the user if some states are unavailable */
 129        if (cps_driver.state_count < STATE_COUNT) {
 130                pr_info("cpuidle-cps: limited to ");
 131                switch (cps_driver.state_count - 1) {
 132                case STATE_WAIT:
 133                        pr_cont("coherent wait\n");
 134                        break;
 135                case STATE_NC_WAIT:
 136                        pr_cont("non-coherent wait\n");
 137                        break;
 138                case STATE_CLOCK_GATED:
 139                        pr_cont("clock gating\n");
 140                        break;
 141                }
 142        }
 143
 144        /*
 145         * Set the coupled flag on the appropriate states if this system
 146         * requires it.
 147         */
 148        if (coupled_coherence)
 149                for (i = STATE_NC_WAIT; i < cps_driver.state_count; i++)
 150                        cps_driver.states[i].flags |= CPUIDLE_FLAG_COUPLED;
 151
 152        err = cpuidle_register_driver(&cps_driver);
 153        if (err) {
 154                pr_err("Failed to register CPS cpuidle driver\n");
 155                return err;
 156        }
 157
 158        for_each_possible_cpu(cpu) {
 159                device = &per_cpu(cpuidle_dev, cpu);
 160                device->cpu = cpu;
 161#ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
 162                cpumask_copy(&device->coupled_cpus, &cpu_sibling_map[cpu]);
 163#endif
 164
 165                err = cpuidle_register_device(device);
 166                if (err) {
 167                        pr_err("Failed to register CPU%d cpuidle device\n",
 168                               cpu);
 169                        goto err_out;
 170                }
 171        }
 172
 173        return 0;
 174err_out:
 175        cps_cpuidle_unregister();
 176        return err;
 177}
 178device_initcall(cps_cpuidle_init);
 179