* drivers/devfreq/devfreq.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <debug.h>
#include <nuttx/devfreq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/sched.h>
#include <nuttx/spinlock.h>
* Pre-processor Definitions
****************************************************************************/
* Private Data
****************************************************************************/
static struct list_node g_devfreq_list = LIST_INITIAL_VALUE(g_devfreq_list);
static spinlock_t g_devfreq_list_lock = SP_UNLOCKED;
* Private Function Prototypes
****************************************************************************/
static int devfreq_init_governor(FAR struct devfreq_s *devfreq);
static void devfreq_exit_governor(FAR struct devfreq_s *devfreq);
static int devfreq_start_governor(FAR struct devfreq_s *devfreq);
static void devfreq_stop_governor(FAR struct devfreq_s *devfreq);
static void devfreq_limit_governor(FAR struct devfreq_s *devfreq);
static ssize_t devfreq_table_find_freq(FAR struct devfreq_s *devfreq,
uint32_t target_freq,
int relation);
static int devfreq_table_validate(FAR struct devfreq_s *devfreq);
static void devfreq_refresh_limit(FAR struct devfreq_s *devfreq);
static int devfreq_driver_target(FAR struct devfreq_s *devfreq,
uint32_t target_freq,
int relation);
* Private Functions
****************************************************************************/
* Name: devfreq_init_governor
*
* Description:
* Initialize governor
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
static int devfreq_init_governor(FAR struct devfreq_s *devfreq)
{
if (!devfreq->governor)
{
return -EINVAL;
}
if (devfreq->governor->init)
{
return devfreq->governor->init(devfreq);
}
return 0;
}
* Name: devfreq_exit_governor
*
* Description:
* Exit governor
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* None
*
****************************************************************************/
static void devfreq_exit_governor(FAR struct devfreq_s *devfreq)
{
if (!devfreq->governor)
{
return;
}
if (devfreq->governor->exit)
{
devfreq->governor->exit(devfreq);
}
}
* Name: devfreq_start_governor
*
* Description:
* Start governor
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero (OK) on success; a negated errno on failure
*
****************************************************************************/
static int devfreq_start_governor(FAR struct devfreq_s *devfreq)
{
if (devfreq->suspended)
{
return 0;
}
if (!devfreq->governor)
{
return -EINVAL;
}
if (devfreq->governor->start)
{
int ret = devfreq->governor->start(devfreq);
if (ret < 0)
{
return ret;
}
}
devfreq_limit_governor(devfreq);
return 0;
}
* Name: devfreq_stop_governor
*
* Description:
* Stop governor
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
static void devfreq_stop_governor(FAR struct devfreq_s *devfreq)
{
if (devfreq->suspended || !devfreq->governor)
{
return;
}
if (devfreq->governor->stop)
{
devfreq->governor->stop(devfreq);
}
}
* Name: devfreq_limit_governor
*
* Description:
* Limit governor
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
static void devfreq_limit_governor(FAR struct devfreq_s *devfreq)
{
if (devfreq->suspended || !devfreq->governor)
{
return;
}
if (devfreq->governor->limit)
{
uint32_t freq = devfreq->governor->limit(devfreq);
devfreq_driver_target(devfreq, freq, DEVFREQ_RELATION_L);
}
}
* Name: devfreq_table_find_freq
*
* Description:
* Find frequency in table
*
* Input Parameters:
* devfreq - devfreq device
* target_freq - target frequency
* relation - relation
*
* Returned Value:
* target index on success; negated errno on failure
*
****************************************************************************/
static ssize_t devfreq_table_find_freq(FAR struct devfreq_s *devfreq,
uint32_t target_freq,
int relation)
{
ssize_t best = -ENOENT;
size_t i;
if (relation == DEVFREQ_RELATION_L)
{
for (i = 0; devfreq->freq_table[i] != DEVFREQ_ENTRY_END; i++)
{
if (devfreq->freq_table[i] == DEVFREQ_ENTRY_INVALID)
{
continue;
}
if (devfreq->freq_table[i] >= target_freq)
{
best = i;
break;
}
}
}
else if (relation == DEVFREQ_RELATION_H)
{
for (i = 0; devfreq->freq_table[i] != DEVFREQ_ENTRY_END; i++)
{
if (devfreq->freq_table[i] == DEVFREQ_ENTRY_INVALID)
{
continue;
}
if (devfreq->freq_table[i] > target_freq)
{
break;
}
best = i;
}
}
return best;
}
* Name: devfreq_table_validate
*
* Description:
* Validate table
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
static int devfreq_table_validate(FAR struct devfreq_s *devfreq)
{
FAR const uint32_t *table = devfreq->freq_table;
uint32_t prv_freq = 0;
uint32_t min_freq = UINT32_MAX;
uint32_t max_freq = 0;
size_t i;
if (!table)
{
return -EINVAL;
}
for (i = 0; table[i] != DEVFREQ_ENTRY_END; i++)
{
if (table[i] == DEVFREQ_ENTRY_INVALID)
{
continue;
}
if (i && table[i] <= prv_freq)
{
return -EINVAL;
}
if (table[i] < min_freq)
{
min_freq = table[i];
}
if (table[i] > max_freq)
{
max_freq = table[i];
}
prv_freq = table[i];
}
devfreq->min = min_freq;
devfreq->max = max_freq;
return 0;
}
* Name: devfreq_refresh_limit
*
* Description:
* Refresh limit
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* None
*
****************************************************************************/
static void devfreq_refresh_limit(FAR struct devfreq_s *devfreq)
{
uint32_t min;
uint32_t max;
ssize_t idx;
min = qos_get_value(&devfreq->constraints, QOS_REQ_MIN);
max = qos_get_value(&devfreq->constraints, QOS_REQ_MAX);
if (min > max)
{
min = max;
}
idx = devfreq_table_find_freq(devfreq, min, DEVFREQ_RELATION_L);
if (idx >= 0)
{
devfreq->min = devfreq->freq_table[idx];
}
idx = devfreq_table_find_freq(devfreq, max, DEVFREQ_RELATION_H);
if (idx >= 0)
{
devfreq->max = devfreq->freq_table[idx];
}
devfreq_limit_governor(devfreq);
}
* Name: devfreq_driver_target
*
* Description:
* Set target frequency
*
* Input Parameters:
* devfreq - devfreq device
* target_freq - target frequency
* relation - relation to target frequency
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
static int devfreq_driver_target(FAR struct devfreq_s *devfreq,
uint32_t target_freq,
int relation)
{
struct devfreq_notifier_s freq;
ssize_t idx;
int ret;
if (!devfreq)
{
return -EINVAL;
}
idx = devfreq_table_find_freq(devfreq, target_freq, relation);
if (idx < 0)
{
return -ENOENT;
}
target_freq = devfreq->freq_table[idx];
if (target_freq == devfreq->cur)
{
return 0;
}
freq.old = devfreq->cur;
freq.new = target_freq;
blocking_notifier_call_chain(&devfreq->notifier_list,
DEVFREQ_PRECHANGE, &freq);
ret = devfreq->driver->target_index(devfreq, idx);
blocking_notifier_call_chain(&devfreq->notifier_list,
DEVFREQ_POSTCHANGE, &freq);
if (ret < 0)
{
freq.old = target_freq;
freq.new = devfreq->cur;
blocking_notifier_call_chain(&devfreq->notifier_list,
DEVFREQ_PRECHANGE, &freq);
blocking_notifier_call_chain(&devfreq->notifier_list,
DEVFREQ_POSTCHANGE, &freq);
return ret;
}
devfreq->cur = target_freq;
return 0;
}
* Public Functions
****************************************************************************/
* Name: devfreq_register
*
* Description:
* Register devfreq device
*
* Input Parameters:
* name - device name
* governor - governor
* driver - driver
* priv - private data
*
* Returned Value:
* devfreq device on success; NULL on failure
*
****************************************************************************/
FAR struct devfreq_s *devfreq_register(
FAR const char *name,
FAR const struct devfreq_governor_s *governor,
FAR const struct devfreq_driver_s *driver,
FAR void *priv)
{
FAR struct devfreq_s *devfreq = devfreq_find_by_name(name);
irqstate_t flags;
if (devfreq || !driver)
{
return NULL;
}
devfreq = (FAR struct devfreq_s *)kmm_zalloc(sizeof(struct devfreq_s));
if (!devfreq)
{
return NULL;
}
qos_constraints_init(&devfreq->constraints);
BLOCKING_INIT_NOTIFIER_HEAD(&devfreq->notifier_list);
nxmutex_init(&devfreq->lock);
strlcpy(devfreq->name, name, NAME_MAX);
devfreq->driver = driver;
devfreq->priv = priv;
devfreq->suspended = false;
devfreq->freq_table = driver->get_table(devfreq);
devfreq->min = 0;
devfreq->max = UINT32_MAX;
devfreq->cur = driver->get_frequency(devfreq);
if (!devfreq->freq_table)
{
goto out;
}
if (devfreq_table_validate(devfreq) < 0)
{
goto out;
}
if (!governor)
{
goto out;
}
devfreq->governor = governor;
if (devfreq_init_governor(devfreq) < 0)
{
goto out;
}
devfreq_start_governor(devfreq);
flags = spin_lock_irqsave(&g_devfreq_list_lock);
list_add_tail(&g_devfreq_list, &devfreq->node);
spin_unlock_irqrestore(&g_devfreq_list_lock, flags);
return devfreq;
out:
nxmutex_destroy(&devfreq->lock);
nxmutex_destroy(&devfreq->notifier_list.mutex);
kmm_free(devfreq);
return NULL;
}
* Name: devfreq_unregister
*
* Description:
* Unregister devfreq device
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_unregister(FAR struct devfreq_s *devfreq)
{
irqstate_t flags;
if (!devfreq)
{
return -EINVAL;
}
flags = spin_lock_irqsave(&g_devfreq_list_lock);
list_delete(&devfreq->node);
spin_unlock_irqrestore(&g_devfreq_list_lock, flags);
devfreq_stop_governor(devfreq);
devfreq_exit_governor(devfreq);
nxmutex_destroy(&devfreq->lock);
nxmutex_destroy(&devfreq->notifier_list.mutex);
kmm_free(devfreq);
return 0;
}
* Name: devfreq_suspend
*
* Description:
* Suspend devfreq device
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_suspend(FAR struct devfreq_s *devfreq)
{
nxmutex_lock(&devfreq->lock);
devfreq_stop_governor(devfreq);
if (devfreq->driver->suspend)
{
int ret = devfreq->driver->suspend(devfreq);
if (ret < 0)
{
nxmutex_unlock(&devfreq->lock);
return ret;
}
}
devfreq->suspended = true;
nxmutex_unlock(&devfreq->lock);
return 0;
}
* Name: devfreq_resume
*
* Description:
* Resume devfreq device
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_resume(struct devfreq_s *devfreq)
{
nxmutex_lock(&devfreq->lock);
if (devfreq->driver->resume)
{
int ret = devfreq->driver->resume(devfreq);
if (ret < 0)
{
nxmutex_unlock(&devfreq->lock);
return ret;
}
}
devfreq->suspended = false;
devfreq_start_governor(devfreq);
nxmutex_unlock(&devfreq->lock);
return 0;
}
* Name: devfreq_register_notifier
*
* Description:
* Register notifier
*
* Input Parameters:
* devfreq - devfreq device
* nb - notifier block
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_register_notifier(FAR struct devfreq_s *devfreq,
FAR struct notifier_block *nb)
{
if (!devfreq || !nb)
{
return -EINVAL;
}
blocking_notifier_chain_register(&devfreq->notifier_list, nb);
return 0;
}
* Name: devfreq_unregister_notifier
*
* Description:
* Unregister notifier
*
* Input Parameters:
* devfreq - devfreq device
* nb - notifier block
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_unregister_notifier(FAR struct devfreq_s *devfreq,
FAR struct notifier_block *nb)
{
if (!devfreq || !nb)
{
return -EINVAL;
}
blocking_notifier_chain_unregister(&devfreq->notifier_list, nb);
return 0;
}
* Name: devfreq_get_frequency
*
* Description:
* Get current frequency
*
* Input Parameters:
* devfreq - devfreq device
*
* Returned Value:
* Current frequency
*
****************************************************************************/
FAR uint32_t devfreq_get_frequency(FAR struct devfreq_s *devfreq)
{
return devfreq->driver->get_frequency(devfreq);
}
* Name: devfreq_qos_add_request
*
* Description:
* Add a new request
*
* Input Parameters:
* devfreq - devfreq device
* min - minimum frequency
* max - maximum frequency
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
FAR struct qos_request_s *devfreq_qos_add_request(
FAR struct devfreq_s *devfreq,
uint32_t min, uint32_t max)
{
FAR struct qos_request_s *req;
if (!devfreq)
{
return NULL;
}
nxmutex_lock(&devfreq->lock);
req = qos_add_request(&devfreq->constraints, min, max);
devfreq_refresh_limit(devfreq);
nxmutex_unlock(&devfreq->lock);
return req;
}
* Name: devfreq_qos_update_request
*
* Description:
* Update a request
*
* Input Parameters:
* qos - devfreq qos
* min - minimum frequency
* max - maximum frequency
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_qos_update_request(FAR struct devfreq_s *devfreq,
FAR struct qos_request_s *req,
uint32_t min, uint32_t max)
{
int ret;
if (!devfreq || !req)
{
return -EINVAL;
}
nxmutex_lock(&devfreq->lock);
ret = qos_update_request(&devfreq->constraints, req, min, max);
if (ret < 0)
{
nxmutex_unlock(&devfreq->lock);
return ret;
}
devfreq_refresh_limit(devfreq);
nxmutex_unlock(&devfreq->lock);
return ret;
}
* Name: devfreq_qos_remove_request
*
* Description:
* Remove a request
*
* Input Parameters:
* qos - devfreq qos
*
* Returned Value:
* Zero on success; a negated errno on failure
*
****************************************************************************/
int devfreq_qos_remove_request(FAR struct devfreq_s *devfreq,
FAR struct qos_request_s *req)
{
int ret;
if (!devfreq || !req)
{
return -EINVAL;
}
nxmutex_lock(&devfreq->lock);
ret = qos_remove_request(&devfreq->constraints, req);
if (ret < 0)
{
nxmutex_unlock(&devfreq->lock);
return ret;
}
devfreq_refresh_limit(devfreq);
nxmutex_unlock(&devfreq->lock);
return ret;
}
* Name: devfreq_find_by_name
*
* Description:
* find a devfreq entry from global list by name
*
* Input Parameters:
* name - devfreq name
*
* Returned Value:
* devfreq handle
*
****************************************************************************/
FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name)
{
FAR struct devfreq_s *devfreq;
irqstate_t flags;
if (!name)
{
return NULL;
}
flags = spin_lock_irqsave(&g_devfreq_list_lock);
list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node)
{
if (!strcmp(devfreq->name, name))
{
spin_unlock_irqrestore(&g_devfreq_list_lock, flags);
return devfreq;
}
}
spin_unlock_irqrestore(&g_devfreq_list_lock, flags);
return NULL;
}
* Name: devfreq_find_by_index
*
* Description:
* find a devfreq entry from global list by index
*
* Input Parameters:
* index - devfreq index
*
* Returned Value:
* devfreq handle
*
****************************************************************************/
FAR struct devfreq_s *devfreq_find_by_index(size_t index)
{
FAR struct devfreq_s *devfreq;
irqstate_t flags;
size_t i = 0;
flags = spin_lock_irqsave(&g_devfreq_list_lock);
list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node)
{
if (index == i++)
{
spin_unlock_irqrestore(&g_devfreq_list_lock, flags);
return devfreq;
}
}
spin_unlock_irqrestore(&g_devfreq_list_lock, flags);
return NULL;
}