551 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			551 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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
 | 
						|
 */
 | 
						|
 | 
						|
#include <rthw.h>
 | 
						|
#include <rtthread.h>
 | 
						|
#include <drivers/pm.h>
 | 
						|
 | 
						|
#ifdef RT_USING_PM
 | 
						|
 | 
						|
static struct rt_pm _pm;
 | 
						|
 | 
						|
/**
 | 
						|
 * This function will suspend all registered devices
 | 
						|
 */
 | 
						|
static void _pm_device_suspend(void)
 | 
						|
{
 | 
						|
    int index;
 | 
						|
 | 
						|
    for (index = 0; index < _pm.device_pm_number; index++)
 | 
						|
    {
 | 
						|
        if (_pm.device_pm[index].ops->suspend != RT_NULL)
 | 
						|
        {
 | 
						|
            _pm.device_pm[index].ops->suspend(_pm.device_pm[index].device);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This function will resume all registered devices
 | 
						|
 */
 | 
						|
static void _pm_device_resume(void)
 | 
						|
{
 | 
						|
    int index;
 | 
						|
 | 
						|
    for (index = 0; index < _pm.device_pm_number; index++)
 | 
						|
    {
 | 
						|
        if (_pm.device_pm[index].ops->resume != RT_NULL)
 | 
						|
        {
 | 
						|
            _pm.device_pm[index].ops->resume(_pm.device_pm[index].device);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#if PM_RUN_MODE_COUNT > 1
 | 
						|
/**
 | 
						|
 * This function will update the frequency of all registered devices
 | 
						|
 */
 | 
						|
static void _pm_device_frequency_change(void)
 | 
						|
{
 | 
						|
    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)
 | 
						|
            _pm.device_pm[index].ops->frequency_change(_pm.device_pm[index].device);
 | 
						|
    }
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/**
 | 
						|
 * This function will enter corresponding power mode.
 | 
						|
 */
 | 
						|
void rt_pm_enter(void)
 | 
						|
{
 | 
						|
    rt_ubase_t level;
 | 
						|
    struct rt_pm *pm;
 | 
						|
    rt_uint32_t index;
 | 
						|
    rt_tick_t timeout_tick;
 | 
						|
 | 
						|
    pm = &_pm;
 | 
						|
 | 
						|
    /* disable interrupt before check run modes */
 | 
						|
    level = rt_hw_interrupt_disable();
 | 
						|
    /* check each run mode, and decide to swithc to run mode or sleep mode */
 | 
						|
    for (index = 0; index < PM_RUN_MODE_COUNT; index++)
 | 
						|
    {
 | 
						|
        if (pm->modes[index])
 | 
						|
        {
 | 
						|
            if (index > pm->current_mode)
 | 
						|
            {
 | 
						|
                pm->ops->exit(pm);
 | 
						|
                pm->current_mode = index;
 | 
						|
                pm->ops->enter(pm);
 | 
						|
#if PM_RUN_MODE_COUNT > 1
 | 
						|
                pm->ops->frequency_change(pm, 0);
 | 
						|
                _pm_device_frequency_change();
 | 
						|
#endif
 | 
						|
            }
 | 
						|
 | 
						|
            rt_hw_interrupt_enable(level);
 | 
						|
            /* The current mode is run mode, no need to check sleep mode */
 | 
						|
            return ;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    /* enable interrupt after check run modes */
 | 
						|
    rt_hw_interrupt_enable(level);
 | 
						|
 | 
						|
    level = rt_hw_interrupt_disable();
 | 
						|
    /* check each sleep mode to decide which mode can system sleep. */
 | 
						|
    for (index = PM_SLEEP_MODE_START; index < PM_SLEEP_MODE_START + PM_SLEEP_MODE_COUNT; index++)
 | 
						|
    {
 | 
						|
        if (pm->modes[index])
 | 
						|
        {
 | 
						|
            /* let mcu sleep when system is idle */
 | 
						|
 | 
						|
            /* run mode to sleep mode */
 | 
						|
            if (pm->current_mode < PM_SLEEP_MODE_START)
 | 
						|
            {
 | 
						|
                /* exit run mode */
 | 
						|
                pm->ops->exit(pm);
 | 
						|
            }
 | 
						|
 | 
						|
            /* set current power mode */
 | 
						|
            pm->current_mode = index;
 | 
						|
            pm->exit_count = 1;
 | 
						|
 | 
						|
            /* suspend all of devices with PM feature */
 | 
						|
            _pm_device_suspend();
 | 
						|
 | 
						|
            /* should start pm timer */
 | 
						|
            if (pm->timer_mask & (1 << index))
 | 
						|
            {
 | 
						|
                /* get next os tick */
 | 
						|
                timeout_tick = rt_timer_next_timeout_tick();
 | 
						|
                if (timeout_tick != RT_TICK_MAX)
 | 
						|
                {
 | 
						|
                    timeout_tick -= rt_tick_get();
 | 
						|
 | 
						|
#if defined(PM_MIN_ENTER_SLEEP_TICK) && PM_MIN_ENTER_SLEEP_TICK > 0
 | 
						|
                    if (timeout_tick < PM_MIN_ENTER_SLEEP_TICK)
 | 
						|
                    {
 | 
						|
                        rt_hw_interrupt_enable(level);
 | 
						|
                        /* limit the minimum time to enter timer sleep mode */
 | 
						|
                        return ;
 | 
						|
                    }
 | 
						|
#endif
 | 
						|
                }
 | 
						|
                /* startup pm timer */
 | 
						|
                pm->ops->timer_start(pm, timeout_tick);
 | 
						|
            }
 | 
						|
 | 
						|
            /* enter sleep and wait to be waken up */
 | 
						|
            pm->ops->enter(pm);
 | 
						|
 | 
						|
            /* exit from low power mode */
 | 
						|
            rt_pm_exit();
 | 
						|
 | 
						|
            rt_hw_interrupt_enable(level);
 | 
						|
            return ;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    rt_hw_interrupt_enable(level);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This function exits from sleep mode.
 | 
						|
 */
 | 
						|
void rt_pm_exit(void)
 | 
						|
{
 | 
						|
    rt_ubase_t level;
 | 
						|
    struct rt_pm *pm;
 | 
						|
    rt_tick_t delta_tick;
 | 
						|
 | 
						|
    pm = &_pm;
 | 
						|
 | 
						|
    level = rt_hw_interrupt_disable();
 | 
						|
 | 
						|
    if (pm->exit_count)
 | 
						|
    {
 | 
						|
        pm->exit_count = 0;
 | 
						|
 | 
						|
        if (pm->current_mode >= PM_SLEEP_MODE_START)
 | 
						|
        {
 | 
						|
            /* sleep mode with timer */
 | 
						|
            if (pm->timer_mask & (1 << pm->current_mode))
 | 
						|
            {
 | 
						|
                /* get the tick of pm timer */
 | 
						|
                delta_tick = pm->ops->timer_get_tick(pm);
 | 
						|
 | 
						|
                /* stop pm timer */
 | 
						|
                pm->ops->timer_stop(pm);
 | 
						|
 | 
						|
                if (delta_tick)
 | 
						|
                {
 | 
						|
                    /* adjust OS tick */
 | 
						|
                    rt_tick_set(rt_tick_get() + delta_tick);
 | 
						|
                    /* check system timer */
 | 
						|
                    rt_timer_check();
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            /* exit from sleep mode */
 | 
						|
            pm->ops->exit(pm);
 | 
						|
            /* resume the device with PM feature */
 | 
						|
            _pm_device_resume();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    rt_hw_interrupt_enable(level);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Upper application or device driver requests the system
 | 
						|
 * stall in corresponding power mode.
 | 
						|
 *
 | 
						|
 * @param parameter the parameter of run mode or sleep mode
 | 
						|
 */
 | 
						|
void rt_pm_request(rt_ubase_t mode)
 | 
						|
{
 | 
						|
    rt_ubase_t level;
 | 
						|
    struct rt_pm *pm;
 | 
						|
 | 
						|
    pm = &_pm;
 | 
						|
 | 
						|
    if (mode > PM_MODE_MAX)
 | 
						|
        return;
 | 
						|
 | 
						|
    level = rt_hw_interrupt_disable();
 | 
						|
 | 
						|
    /* update pm modes table */
 | 
						|
    pm->modes[mode] ++;
 | 
						|
 | 
						|
    /* request higter mode with a smaller mode value*/
 | 
						|
    if (mode < pm->current_mode)
 | 
						|
    {
 | 
						|
        /* the old current mode is RUN mode, need to all pm->ops->exit(),
 | 
						|
         * if not, it has already called in rt_pm_exit()
 | 
						|
         */
 | 
						|
        if (pm->current_mode < PM_SLEEP_MODE_START)
 | 
						|
        {
 | 
						|
            pm->ops->exit(pm);
 | 
						|
        }
 | 
						|
        else if (pm->exit_count)
 | 
						|
        {
 | 
						|
            /* call exeit when global interrupt is disable */
 | 
						|
            pm->ops->exit(pm);
 | 
						|
            pm->exit_count = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        /* update current mode */
 | 
						|
        pm->current_mode = mode;
 | 
						|
 | 
						|
        /* current mode is higher run mode */
 | 
						|
        if (mode < PM_SLEEP_MODE_START)
 | 
						|
        {
 | 
						|
            /* enter run mode */
 | 
						|
            pm->ops->enter(pm);
 | 
						|
#if PM_RUN_MODE_COUNT > 1
 | 
						|
            /* frequency change */
 | 
						|
            pm->ops->frequency_change(pm, 0);
 | 
						|
            _pm_device_frequency_change();
 | 
						|
#endif
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            /* do nothing when request higher sleep mode,
 | 
						|
             * and swithc to new sleep mode in rt_pm_enter()
 | 
						|
             */
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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
 | 
						|
 *
 | 
						|
 */
 | 
						|
void rt_pm_release(rt_ubase_t mode)
 | 
						|
{
 | 
						|
    rt_ubase_t level;
 | 
						|
    struct rt_pm *pm;
 | 
						|
 | 
						|
    pm = &_pm;
 | 
						|
 | 
						|
    if (mode > PM_MODE_MAX)
 | 
						|
        return;
 | 
						|
 | 
						|
    level = rt_hw_interrupt_disable();
 | 
						|
 | 
						|
    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
 | 
						|
 */
 | 
						|
void rt_pm_register_device(struct rt_device *device, const struct rt_device_pm_ops *ops)
 | 
						|
{
 | 
						|
    rt_ubase_t level;
 | 
						|
    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_sem_release(&(_pm.device_lock));
 | 
						|
 | 
						|
    rt_hw_interrupt_enable(level);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Unregister device from PM manager.
 | 
						|
 *
 | 
						|
 * @param device the device with PM feature
 | 
						|
 */
 | 
						|
void rt_pm_unregister_device(struct rt_device *device)
 | 
						|
{
 | 
						|
    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);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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);
 | 
						|
 | 
						|
    if (pos <= PM_MODE_MAX)
 | 
						|
    {
 | 
						|
        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;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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);
 | 
						|
 | 
						|
    /* todo : add to kernel source code */
 | 
						|
    rt_thread_idle_sethook(rt_pm_enter);
 | 
						|
 | 
						|
    rt_memset(pm->modes, 0, sizeof(pm->modes));
 | 
						|
    pm->current_mode = PM_RUN_MODE_DEFAULT;
 | 
						|
 | 
						|
    pm->timer_mask = timer_mask;
 | 
						|
 | 
						|
    pm->ops = ops;
 | 
						|
 | 
						|
    pm->device_pm = RT_NULL;
 | 
						|
    pm->device_pm_number = 0;
 | 
						|
 | 
						|
    /* initialize semaphore */
 | 
						|
    rt_sem_init(&(pm->device_lock), "pm", 1, RT_IPC_FLAG_FIFO);
 | 
						|
 | 
						|
    /* request in default running mode */
 | 
						|
    rt_pm_request(PM_RUN_MODE_DEFAULT);
 | 
						|
 | 
						|
#ifdef PM_SLEEP_MODE_DEFAULT
 | 
						|
    /* request in default sleep mode */
 | 
						|
    rt_pm_request(PM_SLEEP_MODE_DEFAULT);
 | 
						|
#endif
 | 
						|
 | 
						|
    /* must hold on deep shutdown mode */
 | 
						|
    rt_pm_request(PM_MODE_MAX);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef RT_USING_FINSH
 | 
						|
#include <finsh.h>
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
static void rt_pm_dump_status(void)
 | 
						|
{
 | 
						|
    static const char *pm_str[] = PM_MODE_NAMES;
 | 
						|
    rt_uint32_t index;
 | 
						|
    struct rt_pm *pm;
 | 
						|
 | 
						|
    pm = &_pm;
 | 
						|
 | 
						|
    rt_kprintf("| Power Management Mode | Counter | Timer |\n");
 | 
						|
    rt_kprintf("+-----------------------+---------+-------+\n");
 | 
						|
    for (index = 0; index <= PM_MODE_MAX; index ++)
 | 
						|
    {
 | 
						|
        int has_timer = 0;
 | 
						|
        if (pm->timer_mask & (1 << index))
 | 
						|
            has_timer = 1;
 | 
						|
 | 
						|
        rt_kprintf("| %021s | %7d | %5d |\n", pm_str[index], pm->modes[index], has_timer);
 | 
						|
    }
 | 
						|
    rt_kprintf("+-----------------------+---------+-------+\n");
 | 
						|
 | 
						|
    rt_kprintf("pm current mode: %s\n", pm_str[pm->current_mode]);
 | 
						|
}
 | 
						|
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 */
 |