pm.c 13.4 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2012-06-02     Bernard      the first version
 * 2018-08-02     Tanek        split run and sleep modes, support custom mode
10
 * 2019-04-28     Zero-Free    improve PM mode and device ops interface
11 12 13 14 15 16 17 18 19
 */

#include <rthw.h>
#include <rtthread.h>
#include <drivers/pm.h>

#ifdef RT_USING_PM

static struct rt_pm _pm;
20 21 22 23 24 25 26 27 28 29 30 31 32 33
static uint8_t _pm_default_sleep = RT_PM_DEFAULT_SLEEP_MODE;
static struct rt_pm_notify _pm_notify;

#define RT_PM_TICKLESS_THRESH (2)

RT_WEAK uint32_t rt_pm_enter_critical(void)
{
    return rt_hw_interrupt_disable();
}

RT_WEAK void rt_pm_exit_critical(uint32_t ctx)
{
    rt_hw_interrupt_enable(ctx);
}
34 35 36 37

/**
 * This function will suspend all registered devices
 */
38
static int _pm_device_suspend(uint8_t mode)
39 40 41 42 43 44 45
{
    int index;

    for (index = 0; index < _pm.device_pm_number; index++)
    {
        if (_pm.device_pm[index].ops->suspend != RT_NULL)
        {
46
            return _pm.device_pm[index].ops->suspend(_pm.device_pm[index].device, mode);
47 48
        }
    }
49 50

    return RT_EOK;
51 52 53 54 55
}

/**
 * This function will resume all registered devices
 */
56
static void _pm_device_resume(uint8_t mode)
57 58 59 60 61 62 63
{
    int index;

    for (index = 0; index < _pm.device_pm_number; index++)
    {
        if (_pm.device_pm[index].ops->resume != RT_NULL)
        {
64
            _pm.device_pm[index].ops->resume(_pm.device_pm[index].device, mode);
65 66 67 68 69 70 71
        }
    }
}

/**
 * This function will update the frequency of all registered devices
 */
72
static void _pm_device_frequency_change(uint8_t mode, uint32_t frequency)
73 74 75 76 77 78 79
{
    rt_uint32_t index;

    /* make the frequency change */
    for (index = 0; index < _pm.device_pm_number; index ++)
    {
        if (_pm.device_pm[index].ops->frequency_change != RT_NULL)
80
            _pm.device_pm[index].ops->frequency_change(_pm.device_pm[index].device, mode, frequency);
81 82 83 84
    }
}

/**
85
 * This function will update the system clock frequency when idle
86
 */
87
static void _pm_frequency_scaling(struct rt_pm *pm)
88
{
89
    rt_base_t level;
90

91
    if (pm->flags & RT_PM_FREQUENCY_PENDING)
92
    {
93 94 95 96 97 98 99
        level = rt_hw_interrupt_disable();
        /* change system runing mode */
        pm->ops->run(pm, pm->run_mode, pm->frequency);
        /* changer device frequency */
        _pm_device_frequency_change(pm->run_mode, pm->frequency);
        pm->flags &= ~RT_PM_FREQUENCY_PENDING;
        rt_hw_interrupt_enable(level);
100
    }
101
}
102

103 104 105 106 107 108 109 110 111 112
/**
 * This function selects the sleep mode according to the rt_pm_request/rt_pm_release count.
 */
static uint8_t _pm_select_sleep_mode(struct rt_pm *pm)
{
    int index;
    uint8_t mode;

    mode = _pm_default_sleep;
    for (index = PM_SLEEP_MODE_NONE; index < PM_SLEEP_MODE_MAX; index ++)
113 114 115
    {
        if (pm->modes[index])
        {
116 117
            mode = index;
            break;
118 119
        }
    }
120
    pm->sleep_mode = mode;
121

122
    return mode;
123 124 125
}

/**
126
 * This function changes the power sleep mode base on the result of selection
127
 */
128
static void _pm_change_sleep_mode(struct rt_pm *pm, uint8_t mode)
129
{
130 131 132
    rt_tick_t timeout_tick, delta_tick;
    rt_base_t level;
    int ret = RT_EOK;
133

134 135 136 137 138 139
    if (mode == PM_SLEEP_MODE_NONE)
    {
        pm->sleep_mode = mode;
        pm->ops->sleep(pm, PM_SLEEP_MODE_NONE);
    }
    else
140
    {
141 142 143 144 145
        level = rt_pm_enter_critical();

        /* Notify app will enter sleep mode */
        if (_pm_notify.notify)
            _pm_notify.notify(RT_PM_ENTER_SLEEP, mode, _pm_notify.data);
146

147 148 149
        /* Suspend all peripheral device */
        ret = _pm_device_suspend(mode);
        if (ret != RT_EOK)
150
        {
151 152 153 154
            _pm_device_resume(mode);
            if (_pm_notify.notify)
                _pm_notify.notify(RT_PM_EXIT_SLEEP, mode, _pm_notify.data);
            rt_pm_exit_critical(level);
155

156 157
            return;
        }
158

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
        /* Tickless*/
        if (pm->timer_mask & (0x01 << mode))
        {
            timeout_tick = rt_timer_next_timeout_tick();
            if (timeout_tick == RT_TICK_MAX)
            {
                if (pm->ops->timer_start)
                {
                    pm->ops->timer_start(pm, RT_TICK_MAX);
                }
            }
            else
            {
                timeout_tick = timeout_tick - rt_tick_get();
                if (timeout_tick < RT_PM_TICKLESS_THRESH)
                {
                    mode = PM_SLEEP_MODE_IDLE;
                }
                else
178
                {
179
                    pm->ops->timer_start(pm, timeout_tick);
180 181
                }
            }
182 183 184 185
        }

        /* enter lower power state */
        pm->ops->sleep(pm, mode);
186

187 188 189 190 191 192 193 194 195 196
        /* wake up from lower power state*/
        if (pm->timer_mask & (0x01 << mode))
        {
            delta_tick = pm->ops->timer_get_tick(pm);
            pm->ops->timer_stop(pm);
            if (delta_tick)
            {
                rt_tick_set(rt_tick_get() + delta_tick);
                rt_timer_check();
            }
197
        }
198 199 200 201 202 203 204 205

        /* resume all device */
        _pm_device_resume(pm->sleep_mode);

        if (_pm_notify.notify)
            _pm_notify.notify(RT_PM_EXIT_SLEEP, mode, _pm_notify.data);

        rt_pm_exit_critical(level);
206
    }
207
}
208

209 210 211 212 213 214 215 216 217 218 219 220 221
/**
 * This function will enter corresponding power mode.
 */
void rt_system_power_manager(void)
{
    uint8_t mode;

    /* CPU frequency scaling according to the runing mode settings */
    _pm_frequency_scaling(&_pm);

    /* Low Power Mode Processing */
    mode = _pm_select_sleep_mode(&_pm);
    _pm_change_sleep_mode(&_pm, mode);
222 223 224 225 226 227 228 229
}

/**
 * Upper application or device driver requests the system
 * stall in corresponding power mode.
 *
 * @param parameter the parameter of run mode or sleep mode
 */
230
void rt_pm_request(uint8_t mode)
231
{
232
    rt_base_t level;
233 234
    struct rt_pm *pm;

235
    if (mode > (PM_SLEEP_MODE_MAX - 1))
236 237 238
        return;

    level = rt_hw_interrupt_disable();
239 240 241
    pm = &_pm;
    if (pm->modes[mode] < 255)
        pm->modes[mode] ++;
242 243 244 245 246 247 248 249 250 251
    rt_hw_interrupt_enable(level);
}

/**
 * Upper application or device driver releases the stall
 * of corresponding power mode.
 *
 * @param parameter the parameter of run mode or sleep mode
 *
 */
252
void rt_pm_release(uint8_t mode)
253 254 255 256
{
    rt_ubase_t level;
    struct rt_pm *pm;

257
    if (mode > (PM_SLEEP_MODE_MAX - 1))
258 259 260
        return;

    level = rt_hw_interrupt_disable();
261
    pm = &_pm;
262 263 264 265 266 267 268 269 270 271 272
    if (pm->modes[mode] > 0)
        pm->modes[mode] --;
    rt_hw_interrupt_enable(level);
}

/**
 * Register a device with PM feature
 *
 * @param device the device with PM feature
 * @param ops the PM ops for device
 */
273
void rt_pm_device_register(struct rt_device *device, const struct rt_device_pm_ops *ops)
274
{
275
    rt_base_t level;
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    struct rt_device_pm *device_pm;

    RT_DEBUG_NOT_IN_INTERRUPT;

    level = rt_hw_interrupt_disable();

    device_pm = (struct rt_device_pm *)RT_KERNEL_REALLOC(_pm.device_pm,
                (_pm.device_pm_number + 1) * sizeof(struct rt_device_pm));
    if (device_pm != RT_NULL)
    {
        _pm.device_pm = device_pm;
        _pm.device_pm[_pm.device_pm_number].device = device;
        _pm.device_pm[_pm.device_pm_number].ops    = ops;
        _pm.device_pm_number += 1;
    }

    rt_hw_interrupt_enable(level);
}

/**
 * Unregister device from PM manager.
 *
 * @param device the device with PM feature
 */
300
void rt_pm_device_unregister(struct rt_device *device)
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
{
    rt_ubase_t level;
    rt_uint32_t index;
    RT_DEBUG_NOT_IN_INTERRUPT;

    level = rt_hw_interrupt_disable();

    for (index = 0; index < _pm.device_pm_number; index ++)
    {
        if (_pm.device_pm[index].device == device)
        {
            /* remove current entry */
            for (; index < _pm.device_pm_number - 1; index ++)
            {
                _pm.device_pm[index] = _pm.device_pm[index + 1];
            }

            _pm.device_pm[_pm.device_pm_number - 1].device = RT_NULL;
            _pm.device_pm[_pm.device_pm_number - 1].ops = RT_NULL;

            _pm.device_pm_number -= 1;
            /* break out and not touch memory */
            break;
        }
    }

    rt_hw_interrupt_enable(level);
}

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
/**
 * This function set notification callback for application
 */
void rt_pm_notify_set(void (*notify)(uint8_t event, uint8_t mode, void *data), void *data)
{
    _pm_notify.notify = notify;
    _pm_notify.data = data;
}

/**
 * This function set default sleep mode when no pm_request
 */
void rt_pm_default_set(uint8_t sleep_mode)
{
    _pm_default_sleep = sleep_mode;
}

347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
/**
 * RT-Thread device interface for PM device
 */
static rt_size_t _rt_pm_device_read(rt_device_t dev,
                                    rt_off_t    pos,
                                    void       *buffer,
                                    rt_size_t   size)
{
    struct rt_pm *pm;
    rt_size_t length;

    length = 0;
    pm = (struct rt_pm *)dev;
    RT_ASSERT(pm != RT_NULL);

362
    if (pos < PM_SLEEP_MODE_MAX)
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
    {
        int mode;

        mode = pm->modes[pos];
        length = rt_snprintf(buffer, size, "%d", mode);
    }

    return length;
}

static rt_size_t _rt_pm_device_write(rt_device_t dev,
                                     rt_off_t    pos,
                                     const void *buffer,
                                     rt_size_t   size)
{
    unsigned char request;

    if (size)
    {
        /* get request */
        request = *(unsigned char *)buffer;
        if (request == '1')
        {
            rt_pm_request(pos);
        }
        else if (request == '0')
        {
            rt_pm_release(pos);
        }
    }

    return 1;
}

static rt_err_t _rt_pm_device_control(rt_device_t dev,
                                      int         cmd,
                                      void       *args)
{
    rt_uint32_t mode;

    switch (cmd)
    {
    case RT_PM_DEVICE_CTRL_REQUEST:
        mode = (rt_uint32_t)args;
        rt_pm_request(mode);
        break;

    case RT_PM_DEVICE_CTRL_RELEASE:
        mode = (rt_uint32_t)args;
        rt_pm_release(mode);
        break;
    }

    return RT_EOK;
}

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
int rt_pm_run_mode_set(uint8_t mode, uint32_t frequency)
{
    rt_base_t level;
    struct rt_pm *pm;

    if (mode > PM_RUN_MODE_MAX)
        return -RT_EINVAL;

    level = rt_hw_interrupt_disable();
    pm = &_pm;
    if (mode < pm->run_mode)
    {
        /* change system runing mode */
        pm->ops->run(pm, mode, frequency);
        /* changer device frequency */
        _pm_device_frequency_change(mode, frequency);
    }
    else
    {
        pm->flags |= RT_PM_FREQUENCY_PENDING;
    }
    pm->frequency = frequency;
    pm->run_mode = mode;
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
/**
 * This function will initialize power management.
 *
 * @param ops the PM operations.
 * @param timer_mask indicates which mode has timer feature.
 * @param user_data user data
 */
void rt_system_pm_init(const struct rt_pm_ops *ops,
                       rt_uint8_t              timer_mask,
                       void                   *user_data)
{
    struct rt_device *device;
    struct rt_pm *pm;

    pm = &_pm;
    device = &(_pm.parent);

    device->type        = RT_Device_Class_PM;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

    device->init        = RT_NULL;
    device->open        = RT_NULL;
    device->close       = RT_NULL;
    device->read        = _rt_pm_device_read;
    device->write       = _rt_pm_device_write;
    device->control     = _rt_pm_device_control;
    device->user_data   = user_data;

    /* register PM device to the system */
    rt_device_register(device, "pm", RT_DEVICE_FLAG_RDWR);

    rt_memset(pm->modes, 0, sizeof(pm->modes));
480 481
    pm->sleep_mode = _pm_default_sleep;
    pm->run_mode   = RT_PM_DEFAULT_RUN_MODE;
482 483 484 485 486 487 488 489 490 491 492
    pm->timer_mask = timer_mask;

    pm->ops = ops;

    pm->device_pm = RT_NULL;
    pm->device_pm_number = 0;
}

#ifdef RT_USING_FINSH
#include <finsh.h>

493 494 495
static const char *_pm_sleep_str[] = PM_SLEEP_MODE_NAMES;
static const char *_pm_run_str[] = PM_RUN_MODE_NAMES;

496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
static void rt_pm_release_mode(int argc, char **argv)
{
    int mode = 0;
    if (argc >= 2)
    {
        mode = atoi(argv[1]);
    }

    rt_pm_release(mode);
}
MSH_CMD_EXPORT_ALIAS(rt_pm_release_mode, pm_release, release power management mode);

static void rt_pm_request_mode(int argc, char **argv)
{
    int mode = 0;
    if (argc >= 2)
    {
        mode = atoi(argv[1]);
    }

    rt_pm_request(mode);
}
MSH_CMD_EXPORT_ALIAS(rt_pm_request_mode, pm_request, request power management mode);

520 521 522 523 524 525 526 527 528 529 530 531
static void rt_pm_run_mode_switch(int argc, char **argv)
{
    int mode = 0;
    if (argc >= 2)
    {
        mode = atoi(argv[1]);
    }

    rt_pm_run_mode_set(mode, 0);
}
MSH_CMD_EXPORT_ALIAS(rt_pm_run_mode_switch, pm_run_set, switch power management run mode);

532 533 534 535 536 537 538 539 540
static void rt_pm_dump_status(void)
{
    rt_uint32_t index;
    struct rt_pm *pm;

    pm = &_pm;

    rt_kprintf("| Power Management Mode | Counter | Timer |\n");
    rt_kprintf("+-----------------------+---------+-------+\n");
541
    for (index = 0; index < PM_SLEEP_MODE_MAX; index ++)
542 543 544 545 546
    {
        int has_timer = 0;
        if (pm->timer_mask & (1 << index))
            has_timer = 1;

547
        rt_kprintf("| %021s | %7d | %5d |\n", _pm_sleep_str[index], pm->modes[index], has_timer);
548 549 550
    }
    rt_kprintf("+-----------------------+---------+-------+\n");

551 552
    rt_kprintf("pm current sleep mode: %s\n", _pm_sleep_str[pm->sleep_mode]);
    rt_kprintf("pm current run mode:   %s\n", _pm_run_str[pm->run_mode]);
553 554 555 556 557 558
}
FINSH_FUNCTION_EXPORT_ALIAS(rt_pm_dump_status, pm_dump, dump power management status);
MSH_CMD_EXPORT_ALIAS(rt_pm_dump_status, pm_dump, dump power management status);
#endif

#endif /* RT_USING_PM */