Parcourir la source

[DeviceDriver] Add power management device driver.

armink il y a 6 ans
Parent
commit
2d59fe0310

+ 4 - 0
components/drivers/Kconfig

@@ -104,6 +104,10 @@ config RT_USING_MTD
         default n
     endif
 
+config RT_USING_PM
+    bool "Using Power Management device drivers"
+    default n
+
 config RT_USING_RTC
     bool "Using RTC device drivers"
     default n

+ 181 - 0
components/drivers/include/drivers/pm.h

@@ -0,0 +1,181 @@
+/*
+ * 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
+ */
+
+#ifndef __PM_H__
+#define __PM_H__
+
+#include <rtthread.h>
+
+#ifndef PM_HAS_CUSTOM_CONFIG
+
+/* All modes used for rt_pm_request() adn rt_pm_release() */
+enum
+{
+    /* run modes */
+    PM_RUN_MODE_NORMAL = 0,
+
+    /* sleep modes */
+    PM_SLEEP_MODE_SLEEP,
+    PM_SLEEP_MODE_TIMER,
+    PM_SLEEP_MODE_SHUTDOWN,
+};
+
+/* The name of all modes used in the msh command "pm_dump" */
+#define PM_MODE_NAMES           \
+{                               \
+    "Running Mode",             \
+                                \
+    "Sleep Mode",               \
+    "Timer Mode",               \
+    "Shutdown Mode",            \
+}
+
+/* run mode count : 1 */
+#define PM_RUN_MODE_COUNT       1
+/* sleep mode count : 3 */
+#define PM_SLEEP_MODE_COUNT     3
+
+/* support redefining default run mode */
+#ifndef PM_RUN_MODE_DEFAULT
+#define PM_RUN_MODE_DEFAULT     0
+#endif
+
+/* support redefining default sleep mode */
+#ifndef PM_SLEEP_MODE_DEFAULT
+#define PM_SLEEP_MODE_DEFAULT   (PM_SLEEP_MODE_START)
+#endif
+
+/* support redefining the minimum tick into sleep mode */
+#ifndef PM_MIN_ENTER_SLEEP_TICK
+#define PM_MIN_ENTER_SLEEP_TICK   (1)
+#endif
+
+#else /* PM_HAS_CUSTOM_CONFIG */
+
+#include <pm_cfg.h>
+
+#ifndef PM_RUN_MODE_COUNT
+#error  "You must defined PM_RUN_MODE_COUNT on pm_cfg.h"
+#endif
+
+#ifndef PM_SLEEP_MODE_COUNT
+#error  "You must defined PM_SLEEP_MODE_COUNT on pm_cfg.h"
+#endif
+
+#ifndef PM_MODE_DEFAULT
+#error  "You must defined PM_MODE_DEFAULT on pm_cfg.h"
+#endif
+
+#ifndef PM_MODE_NAMES
+#error  "You must defined PM_MODE_NAMES on pm_cfg.h"
+#endif
+
+#ifndef PM_RUN_MODE_DEFAULT
+#error  "You must defined PM_RUN_MODE_DEFAULT on pm_cfg.h"
+#endif
+
+/* The default sleep mode(PM_SLEEP_MODE_DEFAULT) are not required.
+ * If the default mode is defined, it is requested once in rt_system_pm_init()
+ */
+
+#endif /* PM_HAS_CUSTOM_CONFIG */
+
+/* run mode must start at 0 */
+#define PM_RUN_MODE_START       0
+/* the values of the run modes and sleep mode must be consecutive */
+#define PM_SLEEP_MODE_START     PM_RUN_MODE_COUNT
+/* all mode count */
+#define PM_MODE_COUNT           (PM_RUN_MODE_COUNT + PM_SLEEP_MODE_COUNT)
+/* The last mode, will be request in rt_system_pm_init() */
+#define PM_MODE_MAX             (PM_RUN_MODE_COUNT + PM_SLEEP_MODE_COUNT - 1)
+
+#if PM_MODE_COUNT > 32
+#error "The number of modes cannot exceed 32"
+#endif
+
+/**
+ * device control flag to request or release power
+ */
+#define RT_PM_DEVICE_CTRL_REQUEST   0x01
+#define RT_PM_DEVICE_CTRL_RELEASE   0x02
+
+struct rt_pm;
+
+/**
+ * low power mode operations
+ */
+struct rt_pm_ops
+{
+    void (*enter)(struct rt_pm *pm);
+    void (*exit)(struct rt_pm *pm);
+
+#if PM_RUN_MODE_COUNT > 1
+    void (*frequency_change)(struct rt_pm *pm, rt_uint32_t frequency);
+#endif
+
+    void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout);
+    void (*timer_stop)(struct rt_pm *pm);
+    rt_tick_t (*timer_get_tick)(struct rt_pm *pm);
+};
+
+struct rt_device_pm_ops
+{
+#if PM_RUN_MODE_COUNT > 1
+    void (*frequency_change)(const struct rt_device* device);
+#endif
+
+    void (*suspend)(const struct rt_device* device);
+    void (*resume) (const struct rt_device* device);
+};
+
+struct rt_device_pm
+{
+    const struct rt_device* device;
+    const struct rt_device_pm_ops* ops;
+};
+
+/**
+ * power management
+ */
+struct rt_pm
+{
+    struct rt_device parent;
+
+    /* modes */
+    rt_uint8_t modes[PM_MODE_COUNT];
+    rt_uint8_t current_mode;    /* current pm mode */
+    rt_uint8_t exit_count;
+
+    /* the list of device, which has PM feature */
+    rt_uint8_t device_pm_number;
+    struct rt_device_pm* device_pm;
+    struct rt_semaphore  device_lock;
+
+    /* if the mode has timer, the corresponding bit is 1*/
+    rt_uint32_t timer_mask;
+
+    const struct rt_pm_ops *ops;
+};
+
+void rt_pm_enter(void);
+void rt_pm_exit(void);
+
+void rt_pm_request(rt_ubase_t mode);
+void rt_pm_release(rt_ubase_t mode);
+
+void rt_pm_register_device(struct rt_device* device, const struct rt_device_pm_ops* ops);
+void rt_pm_unregister_device(struct rt_device* device);
+
+void rt_system_pm_init(const struct rt_pm_ops *ops,
+                       rt_uint8_t              timer_mask,
+                       void                   *user_data);
+
+#endif /* __PM_H__ */

+ 4 - 0
components/drivers/include/rtdevice.h

@@ -103,6 +103,10 @@ extern "C" {
 #include "drivers/rt_drv_pwm.h"
 #endif
 
+#ifdef RT_USING_PM
+#include "drivers/pm.h"
+#endif
+
 #ifdef RT_USING_WIFI
 #include "drivers/wlan.h"
 #endif

+ 14 - 0
components/drivers/pm/SConscript

@@ -0,0 +1,14 @@
+from building import *
+
+cwd = GetCurrentDir()
+src = []
+CPPPATH = [cwd + '/../include']
+group = []
+
+if GetDepend(['RT_USING_PM']):
+    src = src + ['pm.c']
+
+if len(src):
+    group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+
+Return('group')

+ 543 - 0
components/drivers/pm/pm.c

@@ -0,0 +1,543 @@
+/*
+ * 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);
+
+            rt_hw_interrupt_enable(level);
+
+            /* exit from low power mode */
+            rt_pm_exit();
+
+            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);
+
+        /* 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 */

+ 54 - 0
examples/pm/timer_app.c

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2006-2018, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2018-08-07     Tanek        first implementation
+ */
+
+#include <board.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#ifndef RT_USING_TIMER_SOFT
+    #error "Please enable soft timer feature!"
+#endif
+
+#define TIMER_APP_DEFAULT_TICK  (RT_TICK_PER_SECOND * 2)
+
+#ifdef RT_USING_PM
+
+static rt_timer_t timer1;
+
+static void _timeout_entry(void *parameter)
+{
+    rt_kprintf("current tick: %ld\n", rt_tick_get());
+}
+
+static int timer_app_init(void)
+{
+    timer1 = rt_timer_create("timer_app",
+                             _timeout_entry,
+                             RT_NULL,
+                             TIMER_APP_DEFAULT_TICK,
+                             RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
+    if (timer1 != RT_NULL)
+    {
+        rt_timer_start(timer1);
+
+        /* keep in timer mode */
+        rt_pm_request(PM_SLEEP_MODE_TIMER);
+
+        return 0;
+    }
+    else
+    {
+        return -1;
+    }
+}
+INIT_EXPORT_APP(timer_app_init);
+
+#endif /* RT_USING_PM */
+