diff --git a/Documentation/components/drivers/special/cpufreq.rst b/Documentation/components/drivers/special/cpufreq.rst new file mode 100644 index 0000000000000..ae29d915595a3 --- /dev/null +++ b/Documentation/components/drivers/special/cpufreq.rst @@ -0,0 +1,200 @@ +===================== +CPU Frequency Scaling +===================== + +The CPU frequency framework lets several unrelated parts of the system have +an opinion about how fast the CPU should run, and resolves those opinions +into one frequency. A platform supplies a lower half: a table of the +frequencies its hardware supports and a way to move between them. Everything +above that is arbitration. + +It is enabled with ``CONFIG_CPUFREQ``. There is one policy per system. + +Design +====== + +Each requester installs a ``[min, max]`` window that it can live with. A +thermal cooling device installs one as a zone heats up, an application +holding ``/dev/cpufreq`` installs one, a power manager installs one. The +framework keeps every window and, whenever the set changes, recomputes a +single answer: + +1. Take the lowest ``max`` across all installed requests. A request that + passes ``CPUFREQ_NO_LIMIT`` for a bound leaves that side unconstrained + and does not participate. +#. Choose the highest table entry at or below that ceiling. +#. Apply it through the lower half, if it differs from the entry currently + applied. + +Speed is therefore the default: with no requests installed, the top table +entry is selected, and any single requester can pull the system down. + +Two consequences of this are worth stating plainly, because they are design +decisions rather than oversights: + +- **A floor never raises the frequency.** Because the resolver already picks + the highest permitted entry, any ``min`` that can be satisfied already is. + The ``min`` field is accepted and stored so that a request expresses a + complete window, but it does not participate in the calculation. +- **When windows do not intersect, the lowest ceiling wins.** A requester + asking for less speed is presumed to be protecting something, so its + ceiling is honoured and a conflicting floor is not. + +The lower half is never told who asked for what. It only ever hears "go to +table entry N". + +The Lower Half +============== + +A platform provides a ``struct cpufreq_driver``. ``get_table`` and +``target_index`` are mandatory; the rest may be NULL: + +.. code-block:: c + + struct cpufreq_driver + { + CODE FAR const struct cpufreq_frequency_table * + (*get_table)(FAR struct cpufreq_policy *policy); + CODE int (*target_index)(FAR struct cpufreq_policy *policy, + unsigned int index); + CODE int (*get_frequency)(FAR struct cpufreq_policy *policy); + CODE int (*suspend)(FAR struct cpufreq_policy *policy); + CODE int (*resume)(FAR struct cpufreq_policy *policy); + }; + +``get_table`` + Returns the frequency table. It must ascend, and it must end with an entry + whose ``frequency`` is ``CPUFREQ_TABLE_END``. + +``target_index`` + Moves the hardware to the table entry at ``index``. This is the only call + that changes the frequency. + +``get_frequency`` + Reports where the hardware actually is, in table units. Called once during + initialisation so the framework can start from the truth rather than an + assumption. If NULL, the framework assumes the lowest entry. + +``suspend`` and ``resume`` + Called from ``cpufreq_suspend()`` and ``cpufreq_resume()``. + +The frequency unit is the lower half's choice. kHz is the Linux convention +and a reasonable default, but the framework does not care as long as every +consumer of the same policy agrees. + +Bring the framework up once, after the hardware it drives is ready: + +.. code-block:: c + + static struct cpufreq_driver g_mychip_cpufreq = + { + .get_table = mychip_get_table, + .target_index = mychip_target_index, + .get_frequency = mychip_get_frequency, + }; + + cpufreq_init(&g_mychip_cpufreq); + +``cpufreq_init()`` returns ``-EBUSY`` if called twice, and registers +``/dev/cpufreq`` when ``CONFIG_CPUFREQ_CHARDEV`` is set. + +.. note:: + ``driver`` must remain the first member of ``struct cpufreq_policy``. + Existing consumers reach the lower half by casting a policy pointer. + +In-kernel Requests +================== + +Kernel code constrains the frequency through three calls: + +.. code-block:: c + + FAR struct cpufreq_qos *qos; + + qos = cpufreq_qos_add_request(cpufreq_policy_get(), + CPUFREQ_NO_LIMIT, /* min */ + 800000); /* max */ + + cpufreq_qos_update_request(qos, CPUFREQ_NO_LIMIT, 1200000); + + cpufreq_qos_remove_request(qos); + +Each call re-resolves the frequency before returning. +``cpufreq_qos_remove_request()`` frees the request. + +``cpufreq_policy_get()`` returns NULL before ``cpufreq_init()`` has run. + +/dev/cpufreq +============ + +With ``CONFIG_CPUFREQ_CHARDEV`` the policy is also a character device, so an +application can read the frequency and install a request of its own. Each +open descriptor owns at most one request, which is released when the +descriptor closes, including on task exit. + +``CPUFREQIOC_GET_FREQUENCY`` + Arg: ``FAR unsigned int *``. Receives the current frequency. + +``CPUFREQIOC_SET_REQUEST`` + Arg: ``FAR const struct cpufreq_request_s *``. Installs or updates this + descriptor's request. Either bound may be ``CPUFREQ_NO_LIMIT``. + +``CPUFREQIOC_CLEAR_REQUEST`` + No argument. Removes this descriptor's request. + +``CPUFREQIOC_GET_TABLE`` + Arg: ``FAR struct cpufreq_table_query_s *``. Copies out the frequency + table. Set ``frequencies`` and ``maxlen``; ``nentries`` is returned as the + number the table really has, which may exceed ``maxlen``. + +.. code-block:: c + + struct cpufreq_request_s req = + { + .min = CPUFREQ_NO_LIMIT, + .max = 800000, + }; + + int fd = open("/dev/cpufreq", O_RDONLY); + ioctl(fd, CPUFREQIOC_SET_REQUEST, (unsigned long)&req); + + /* The cap holds for as long as this descriptor is open */ + + close(fd); + +Thermal Integration +=================== + +``CONFIG_THERMAL_CDEV_CPUFREQ`` registers CPU frequency as a thermal cooling +device, so a thermal zone can throttle the CPU with no board code in +between. The cooling device counts states downward from the top of the +frequency table: state 0 is unthrottled, and each step installs a tighter +window through the same QoS interface described above. + +See :doc:`../thermal/index` for how zones, trip points and cooling devices +fit together. + +Suspend and Resume +================== + +.. code-block:: c + + cpufreq_suspend(); + cpufreq_resume(); + +These pass through to the lower half's ``suspend`` and ``resume``. While +suspended, the resolver leaves the hardware alone: requests are still +accepted and still recorded, and whatever they resolve to is applied on +resume. + +Configuration +============= + +``CONFIG_CPUFREQ`` + Enables the framework. + +``CONFIG_CPUFREQ_CHARDEV`` + Registers ``/dev/cpufreq``. Default on. + +``CONFIG_THERMAL_CDEV_CPUFREQ`` + Registers the thermal cooling device. Requires ``CONFIG_THERMAL``. diff --git a/Documentation/components/drivers/special/index.rst b/Documentation/components/drivers/special/index.rst index 1444d74eb9b04..4241a4d4f0273 100644 --- a/Documentation/components/drivers/special/index.rst +++ b/Documentation/components/drivers/special/index.rst @@ -24,6 +24,7 @@ following section. audio.rst clk.rst + cpufreq.rst devicetree.rst devmem.rst dma.rst diff --git a/drivers/Kconfig b/drivers/Kconfig index 20e77b4a97165..5671b17d2dc20 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -41,6 +41,7 @@ source "drivers/power/Kconfig" source "drivers/regmap/Kconfig" source "drivers/rpmsg/Kconfig" source "drivers/rptun/Kconfig" +source "drivers/cpufreq/Kconfig" source "drivers/sensors/Kconfig" source "drivers/serial/Kconfig" source "drivers/thermal/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index dd39ff4d7f169..b79b235210e85 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -63,6 +63,7 @@ include rpmsg/Make.defs include rptun/Make.defs include sensors/Make.defs include serial/Make.defs +include cpufreq/Make.defs include spi/Make.defs include syslog/Make.defs include thermal/Make.defs diff --git a/drivers/cpufreq/CMakeLists.txt b/drivers/cpufreq/CMakeLists.txt new file mode 100644 index 0000000000000..20b38e4c7ee9a --- /dev/null +++ b/drivers/cpufreq/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# drivers/cpufreq/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# +# ############################################################################## + +if(CONFIG_CPUFREQ) + target_sources(drivers PRIVATE cpufreq.c) +endif() diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig new file mode 100644 index 0000000000000..85a7b5a01f7a7 --- /dev/null +++ b/drivers/cpufreq/Kconfig @@ -0,0 +1,27 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig CPUFREQ + bool "CPU frequency scaling" + default n + ---help--- + A single-policy CPU frequency framework. A platform provides a + lower half (a frequency table and a way to move between its + entries). Each requester (a thermal cooling device, an + application, a power manager) installs a [min, max] request, + and the resolved frequency is the highest table entry under + the lowest ceiling. + +if CPUFREQ + +config CPUFREQ_CHARDEV + bool "/dev/cpufreq character device" + default y + ---help--- + Expose the policy as /dev/cpufreq. Each open descriptor owns + at most one frequency request, installed by ioctl and released + on close, including on task exit. + +endif # CPUFREQ diff --git a/drivers/cpufreq/Make.defs b/drivers/cpufreq/Make.defs new file mode 100644 index 0000000000000..8e2af3cf95c47 --- /dev/null +++ b/drivers/cpufreq/Make.defs @@ -0,0 +1,30 @@ +############################################################################ +# drivers/cpufreq/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# +############################################################################ + +ifeq ($(CONFIG_CPUFREQ),y) + +CSRCS += cpufreq.c + +DEPPATH += --dep-path cpufreq +VPATH += cpufreq + +endif diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c new file mode 100644 index 0000000000000..b27af2a289120 --- /dev/null +++ b/drivers/cpufreq/cpufreq.c @@ -0,0 +1,533 @@ +/**************************************************************************** + * drivers/cpufreq/cpufreq.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +/* CPU frequency scaling: one policy, arbitrated by windows. + * + * Each requester (a thermal cooling device, a holder of /dev/cpufreq, a + * power manager) installs a [min, max] window. The resolved frequency is + * the highest table entry in the intersection of every window, so speed is + * the default and any one requester can cap it. When the windows do not + * intersect the lowest maximum wins. + * + * The lower half only ever hears "go to table entry N". + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_CPUFREQ_CHARDEV +static int cpufreq_open(FAR struct file *filep); +static int cpufreq_close(FAR struct file *filep); +static int cpufreq_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static FAR struct cpufreq_policy *g_cpufreq_policy; + +#ifdef CONFIG_CPUFREQ_CHARDEV +static const struct file_operations g_cpufreq_fops = +{ + cpufreq_open, /* open */ + cpufreq_close, /* close */ + NULL, /* read */ + NULL, /* write */ + NULL, /* seek */ + cpufreq_ioctl, /* ioctl */ +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cpufreq_resolve + * + * Description: + * Recompute the target from every installed request and apply it if it + * moved. Called with the policy lock held. + * + ****************************************************************************/ + +static int cpufreq_resolve(FAR struct cpufreq_policy *policy) +{ + FAR struct cpufreq_qos *qos; + FAR dq_entry_t *entry; + unsigned int hi = ~0u; + unsigned int best; + unsigned int i; + int ret = OK; + + /* Only the ceilings matter to the pick: the table ascends, so the + * highest entry under the lowest ceiling honours any satisfiable floor + * automatically, and a floor that collides with a ceiling loses. + */ + + for (entry = dq_peek(&policy->requests); entry != NULL; + entry = dq_next(entry)) + { + qos = container_of(entry, struct cpufreq_qos, node); + + if (qos->max != CPUFREQ_NO_LIMIT && qos->max < hi) + { + hi = qos->max; + } + } + + best = 0; + for (i = 0; i < policy->nentries; i++) + { + if (policy->table[i].frequency <= hi) + { + best = i; + } + } + + if (best != policy->current && !policy->suspended) + { + ret = policy->driver->target_index(policy, best); + if (ret >= 0) + { + policy->current = best; + } + } + + return ret; +} + +/**************************************************************************** + * Name: File operations for /dev/cpufreq + * + * Description: + * Each open descriptor owns at most one request, installed by ioctl + * and withdrawn on clear or close, so a request is released when its + * descriptor closes, including on task exit. + * + ****************************************************************************/ + +#ifdef CONFIG_CPUFREQ_CHARDEV + +static int cpufreq_open(FAR struct file *filep) +{ + filep->f_priv = NULL; + return OK; +} + +static int cpufreq_close(FAR struct file *filep) +{ + FAR struct cpufreq_qos *qos = filep->f_priv; + + if (qos != NULL) + { + cpufreq_qos_remove_request(qos); + filep->f_priv = NULL; + } + + return OK; +} + +static int cpufreq_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + FAR struct cpufreq_qos *qos = filep->f_priv; + int ret = OK; + + if (policy == NULL) + { + return -ENODEV; + } + + switch (cmd) + { + case CPUFREQIOC_GET_FREQUENCY: + { + FAR unsigned int *freq = (FAR unsigned int *)(uintptr_t)arg; + + if (freq == NULL) + { + return -EINVAL; + } + + if (policy->driver->get_frequency != NULL) + { + ret = policy->driver->get_frequency(policy); + if (ret < 0) + { + return ret; + } + + *freq = (unsigned int)ret; + ret = OK; + } + else + { + *freq = policy->table[policy->current].frequency; + } + } + break; + + case CPUFREQIOC_SET_REQUEST: + { + FAR const struct cpufreq_request_s *req = + (FAR const struct cpufreq_request_s *)(uintptr_t)arg; + + if (req == NULL) + { + return -EINVAL; + } + + if (qos != NULL) + { + ret = cpufreq_qos_update_request(qos, req->min, req->max); + } + else + { + qos = cpufreq_qos_add_request(policy, req->min, req->max); + if (qos == NULL) + { + return -ENOMEM; + } + + filep->f_priv = qos; + } + } + break; + + case CPUFREQIOC_CLEAR_REQUEST: + { + if (qos != NULL) + { + ret = cpufreq_qos_remove_request(qos); + filep->f_priv = NULL; + } + } + break; + + case CPUFREQIOC_GET_TABLE: + { + FAR struct cpufreq_table_query_s *query = + (FAR struct cpufreq_table_query_s *)(uintptr_t)arg; + unsigned int i; + + if (query == NULL) + { + return -EINVAL; + } + + query->nentries = policy->nentries; + if (query->frequencies != NULL) + { + for (i = 0; i < policy->nentries && i < query->maxlen; i++) + { + query->frequencies[i] = policy->table[i].frequency; + } + } + } + break; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +#endif /* CONFIG_CPUFREQ_CHARDEV */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cpufreq_init + ****************************************************************************/ + +int cpufreq_init(FAR struct cpufreq_driver *driver) +{ + FAR struct cpufreq_policy *policy; + FAR const struct cpufreq_frequency_table *table; + unsigned int count; + int ret; + + if (driver == NULL || driver->get_table == NULL || + driver->target_index == NULL) + { + return -EINVAL; + } + + if (g_cpufreq_policy != NULL) + { + return -EBUSY; + } + + policy = kmm_zalloc(sizeof(*policy)); + if (policy == NULL) + { + return -ENOMEM; + } + + policy->driver = driver; + + table = driver->get_table(policy); + if (table == NULL) + { + kmm_free(policy); + return -EINVAL; + } + + for (count = 0; table[count].frequency != CPUFREQ_TABLE_END; count++) + { + if (count > 0 && + table[count].frequency <= table[count - 1].frequency) + { + syslog(LOG_ERR, "cpufreq: table must ascend\n"); + kmm_free(policy); + return -EINVAL; + } + } + + if (count < 2) + { + syslog(LOG_ERR, "cpufreq: table too short to be worth scaling\n"); + kmm_free(policy); + return -EINVAL; + } + + policy->table = table; + policy->nentries = count; + policy->current = count - 1; /* Assume fastest until told otherwise */ + nxmutex_init(&policy->lock); + dq_init(&policy->requests); + + /* Prefer the lower half's reported frequency to the assumption above */ + + if (driver->get_frequency != NULL) + { + ret = driver->get_frequency(policy); + if (ret > 0) + { + unsigned int i; + + for (i = 0; i < count; i++) + { + if (table[i].frequency == (unsigned int)ret) + { + policy->current = i; + break; + } + } + } + } + + g_cpufreq_policy = policy; + +#ifdef CONFIG_CPUFREQ_CHARDEV + ret = register_driver("/dev/cpufreq", &g_cpufreq_fops, 0666, NULL); + if (ret < 0) + { + syslog(LOG_ERR, "cpufreq: cannot register /dev/cpufreq: %d\n", ret); + + /* The framework itself is still useful; carry on without it */ + } +#endif + + return OK; +} + +/**************************************************************************** + * Name: cpufreq_policy_get + ****************************************************************************/ + +FAR struct cpufreq_policy *cpufreq_policy_get(void) +{ + return g_cpufreq_policy; +} + +/**************************************************************************** + * Name: cpufreq_qos_add_request + ****************************************************************************/ + +FAR struct cpufreq_qos *cpufreq_qos_add_request( + FAR struct cpufreq_policy *policy, + unsigned int min, unsigned int max) +{ + FAR struct cpufreq_qos *qos; + + if (policy == NULL) + { + return NULL; + } + + qos = kmm_zalloc(sizeof(*qos)); + if (qos == NULL) + { + return NULL; + } + + qos->min = min; + qos->max = max; + + nxmutex_lock(&policy->lock); + dq_addlast(&qos->node, &policy->requests); + cpufreq_resolve(policy); + nxmutex_unlock(&policy->lock); + + return qos; +} + +/**************************************************************************** + * Name: cpufreq_qos_update_request + ****************************************************************************/ + +int cpufreq_qos_update_request(FAR struct cpufreq_qos *qos, + unsigned int min, unsigned int max) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret; + + if (qos == NULL || policy == NULL) + { + return -EINVAL; + } + + nxmutex_lock(&policy->lock); + qos->min = min; + qos->max = max; + ret = cpufreq_resolve(policy); + nxmutex_unlock(&policy->lock); + + return ret; +} + +/**************************************************************************** + * Name: cpufreq_qos_remove_request + ****************************************************************************/ + +int cpufreq_qos_remove_request(FAR struct cpufreq_qos *qos) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret; + + if (qos == NULL || policy == NULL) + { + return -EINVAL; + } + + nxmutex_lock(&policy->lock); + dq_rem(&qos->node, &policy->requests); + ret = cpufreq_resolve(policy); + nxmutex_unlock(&policy->lock); + + kmm_free(qos); + return ret; +} + +/**************************************************************************** + * Name: cpufreq_suspend + ****************************************************************************/ + +int cpufreq_suspend(void) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret = OK; + + if (policy == NULL) + { + return -ENODEV; + } + + nxmutex_lock(&policy->lock); + if (!policy->suspended) + { + if (policy->driver->suspend != NULL) + { + ret = policy->driver->suspend(policy); + } + + if (ret >= 0) + { + policy->suspended = true; + } + } + + nxmutex_unlock(&policy->lock); + return ret; +} + +/**************************************************************************** + * Name: cpufreq_resume + ****************************************************************************/ + +int cpufreq_resume(void) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret = OK; + + if (policy == NULL) + { + return -ENODEV; + } + + nxmutex_lock(&policy->lock); + if (policy->suspended) + { + if (policy->driver->resume != NULL) + { + ret = policy->driver->resume(policy); + } + + if (ret >= 0) + { + policy->suspended = false; + + /* Anything that changed while asleep applies now */ + + cpufreq_resolve(policy); + } + } + + nxmutex_unlock(&policy->lock); + return ret; +} diff --git a/drivers/thermal/thermal_cpufreq_cooling.c b/drivers/thermal/thermal_cpufreq_cooling.c index 7758decd6bd24..2718c3666b2b6 100644 --- a/drivers/thermal/thermal_cpufreq_cooling.c +++ b/drivers/thermal/thermal_cpufreq_cooling.c @@ -24,6 +24,8 @@ * Included Files ****************************************************************************/ +#include + #include #include diff --git a/include/nuttx/cpufreq.h b/include/nuttx/cpufreq.h new file mode 100644 index 0000000000000..88dc048f9ab83 --- /dev/null +++ b/include/nuttx/cpufreq.h @@ -0,0 +1,244 @@ +/**************************************************************************** + * include/nuttx/cpufreq.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_CPUFREQ_H +#define __INCLUDE_NUTTX_CPUFREQ_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#ifdef CONFIG_CPUFREQ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Terminates a frequency table */ + +#define CPUFREQ_TABLE_END (~0u) + +/* Passed as a QoS bound to leave that side unconstrained */ + +#define CPUFREQ_NO_LIMIT (0) + +/* ioctl commands for the optional /dev/cpufreq character device. + * + * CPUFREQIOC_GET_FREQUENCY Arg: FAR unsigned int *. Receives the + * current frequency. + * CPUFREQIOC_SET_REQUEST Arg: FAR const struct cpufreq_request_s *. + * Installs or updates this file descriptor's + * QoS request. Released on close. + * CPUFREQIOC_CLEAR_REQUEST Arg: none. Removes this descriptor's + * request. + * CPUFREQIOC_GET_TABLE Arg: FAR struct cpufreq_table_query_s *. + * Copies out the frequency table. + */ + +#define CPUFREQIOC_GET_FREQUENCY _CPUFREQIOC(0x0001) +#define CPUFREQIOC_SET_REQUEST _CPUFREQIOC(0x0002) +#define CPUFREQIOC_CLEAR_REQUEST _CPUFREQIOC(0x0003) +#define CPUFREQIOC_GET_TABLE _CPUFREQIOC(0x0004) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct cpufreq_policy; + +/* One entry of a driver's frequency table. The table is ascending and + * ends with an entry whose frequency is CPUFREQ_TABLE_END. The unit is + * the lower half's choice (kHz by Linux convention), as long as every + * consumer of the same policy agrees. + */ + +struct cpufreq_frequency_table +{ + unsigned int frequency; +}; + +/* The lower half: what a platform must provide. get_table and + * target_index are mandatory; the rest may be NULL. + */ + +struct cpufreq_driver +{ + CODE FAR const struct cpufreq_frequency_table * + (*get_table)(FAR struct cpufreq_policy *policy); + CODE int (*target_index)(FAR struct cpufreq_policy *policy, + unsigned int index); + CODE int (*get_frequency)(FAR struct cpufreq_policy *policy); + CODE int (*suspend)(FAR struct cpufreq_policy *policy); + CODE int (*resume)(FAR struct cpufreq_policy *policy); +}; + +/* The policy: one per system. The driver pointer must stay the first + * member; existing consumers reach the driver by casting the policy. + */ + +struct cpufreq_policy +{ + FAR struct cpufreq_driver *driver; + + /* Internal to the upper half */ + + FAR const struct cpufreq_frequency_table *table; + unsigned int nentries; + unsigned int current; /* Index of the entry last applied */ + bool suspended; + mutex_t lock; + dq_queue_t requests; +}; + +/* One QoS request: a [min, max] window the resolved frequency must + * respect. See cpufreq.c for how competing windows resolve. + */ + +struct cpufreq_qos +{ + dq_entry_t node; + unsigned int min; + unsigned int max; +}; + +/* Argument of CPUFREQIOC_SET_REQUEST */ + +struct cpufreq_request_s +{ + unsigned int min; /* Lowest acceptable, or CPUFREQ_NO_LIMIT */ + unsigned int max; /* Highest acceptable, or CPUFREQ_NO_LIMIT */ +}; + +/* Argument of CPUFREQIOC_GET_TABLE */ + +struct cpufreq_table_query_s +{ + FAR unsigned int *frequencies; /* Where to put them */ + unsigned int maxlen; /* Room, in entries */ + unsigned int nentries; /* How many the table really has */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: cpufreq_init + * + * Description: + * Bring up the framework over a lower half. Called once, by the + * platform, after the hardware it drives is ready. Registers + * /dev/cpufreq when CONFIG_CPUFREQ_CHARDEV is enabled. + * + * Input Parameters: + * driver - The lower half. Must outlive the framework. + * + * Returned Value: + * Zero on success; a negated errno otherwise. -EBUSY if called twice. + * + ****************************************************************************/ + +int cpufreq_init(FAR struct cpufreq_driver *driver); + +/**************************************************************************** + * Name: cpufreq_policy_get + * + * Description: + * The system's policy, or NULL before cpufreq_init. + * + ****************************************************************************/ + +FAR struct cpufreq_policy *cpufreq_policy_get(void); + +/**************************************************************************** + * Name: cpufreq_qos_add_request + * + * Description: + * Constrain the frequency to [min, max] and re-resolve. Either bound + * may be CPUFREQ_NO_LIMIT. + * + * Returned Value: + * The request, to update or remove later; NULL on failure. + * + ****************************************************************************/ + +FAR struct cpufreq_qos *cpufreq_qos_add_request( + FAR struct cpufreq_policy *policy, + unsigned int min, unsigned int max); + +/**************************************************************************** + * Name: cpufreq_qos_update_request + * + * Description: + * Change an installed request's window and re-resolve. + * + ****************************************************************************/ + +int cpufreq_qos_update_request(FAR struct cpufreq_qos *qos, + unsigned int min, unsigned int max); + +/**************************************************************************** + * Name: cpufreq_qos_remove_request + * + * Description: + * Withdraw a request and re-resolve without it. The request is freed. + * + ****************************************************************************/ + +int cpufreq_qos_remove_request(FAR struct cpufreq_qos *qos); + +/**************************************************************************** + * Name: cpufreq_suspend / cpufreq_resume + * + * Description: + * Hand the hardware to the lower half's suspend path and back. While + * suspended the resolver leaves the hardware alone; requests are still + * accepted and take effect on resume. + * + ****************************************************************************/ + +int cpufreq_suspend(void); +int cpufreq_resume(void); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* CONFIG_CPUFREQ */ +#endif /* __INCLUDE_NUTTX_CPUFREQ_H */ diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h index 4533ec6922d05..3869bb55b4970 100644 --- a/include/nuttx/fs/ioctl.h +++ b/include/nuttx/fs/ioctl.h @@ -115,6 +115,7 @@ #define _PTPBASE (0x4700) /* PTP ioctl commands */ #define _DSHOTIOCBASE (0x4800) /* Dshot device ioctl commands */ #define _PULSECOUNTBASE (0x4900) /* Pulse count driver ioctl commands */ +#define _CPUFREQIOCBASE (0x4a00) /* CPU frequency ioctl commands */ #define _WLIOCBASE (0x8b00) /* Wireless modules ioctl network commands */ /* boardctl() commands share the same number space */ @@ -852,6 +853,11 @@ #define _PULSECOUNTIOCVALID(c) (_IOC_TYPE(c)==_PULSECOUNTBASE) #define _PULSECOUNTIOC(nr) _IOC(_PULSECOUNTBASE,nr) +/* CPU frequency driver ioctl definitions ***********************************/ + +#define _CPUFREQIOCVALID(c) (_IOC_TYPE(c)==_CPUFREQIOCBASE) +#define _CPUFREQIOC(nr) _IOC(_CPUFREQIOCBASE,nr) + /**************************************************************************** * Public Type Definitions ****************************************************************************/