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