d499ac9d58
Signed-off-by: Petro Karashchenko <petro.karashchenko@gmail.com>
1272 lines
27 KiB
C
1272 lines
27 KiB
C
/****************************************************************************
|
|
* drivers/clk/clk.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 <nuttx/clk/clk.h>
|
|
#include <nuttx/clk/clk_provider.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/procfs.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/list.h>
|
|
#include <nuttx/mutex.h>
|
|
|
|
#include <debug.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define CLK_PROCFS_LINELEN 80
|
|
|
|
/****************************************************************************
|
|
* Private Datas
|
|
****************************************************************************/
|
|
|
|
static mutex_t g_clk_list_lock = NXMUTEX_INITIALIZER;
|
|
|
|
static struct list_node g_clk_root_list
|
|
= LIST_INITIAL_VALUE(g_clk_root_list);
|
|
static struct list_node g_clk_orphan_list
|
|
= LIST_INITIAL_VALUE(g_clk_orphan_list);
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static irqstate_t clk_list_lock(void);
|
|
static void clk_list_unlock(irqstate_t flags);
|
|
|
|
static int clk_fetch_parent_index(FAR struct clk_s *clk,
|
|
FAR struct clk_s *parent);
|
|
static void clk_init_parent(FAR struct clk_s *clk);
|
|
static void clk_reparent(FAR struct clk_s *clk, FAR struct clk_s *parent);
|
|
|
|
static uint32_t clk_recalc(FAR struct clk_s *clk, uint32_t parent_rate);
|
|
static void __clk_recalc_rate(FAR struct clk_s *clk);
|
|
|
|
static void clk_calc_subtree(FAR struct clk_s *clk, uint32_t new_rate,
|
|
FAR struct clk_s *new_parent,
|
|
uint8_t p_index);
|
|
static FAR struct clk_s *clk_calc_new_rates(FAR struct clk_s *clk,
|
|
uint32_t rate);
|
|
static void clk_change_rate(FAR struct clk_s *clk,
|
|
uint32_t best_parent_rate);
|
|
|
|
static uint32_t __clk_get_rate(FAR struct clk_s *clk);
|
|
static uint32_t __clk_round_rate(FAR struct clk_s *clk, uint32_t rate);
|
|
static int __clk_enable(FAR struct clk_s *clk);
|
|
static int __clk_disable(FAR struct clk_s *clk);
|
|
|
|
static FAR struct clk_s *__clk_lookup(FAR const char *name,
|
|
FAR struct clk_s *clk);
|
|
static int __clk_register(FAR struct clk_s *clk);
|
|
|
|
static void clk_disable_unused_subtree(FAR struct clk_s *clk);
|
|
static FAR struct clk_s *clk_lookup(FAR const char *name);
|
|
|
|
/* File system methods */
|
|
|
|
#if !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK) && defined(CONFIG_FS_PROCFS)
|
|
|
|
static int clk_procfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode);
|
|
static int clk_procfs_close(FAR struct file *filep);
|
|
static ssize_t clk_procfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static int clk_procfs_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp);
|
|
static int clk_procfs_stat(FAR const char *relpath, FAR struct stat *buf);
|
|
|
|
#endif /* !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK) && defined(CONFIG_FS_PROCFS) */
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
#if !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK) && defined(CONFIG_FS_PROCFS)
|
|
|
|
const struct procfs_operations g_clk_operations =
|
|
{
|
|
clk_procfs_open, /* open */
|
|
clk_procfs_close, /* close */
|
|
clk_procfs_read, /* read */
|
|
NULL, /* write */
|
|
NULL, /* poll */
|
|
|
|
clk_procfs_dup, /* dup */
|
|
|
|
NULL, /* opendir */
|
|
NULL, /* closedir */
|
|
NULL, /* readdir */
|
|
NULL, /* rewinddir */
|
|
|
|
clk_procfs_stat, /* stat */
|
|
};
|
|
|
|
#endif /* !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK) && defined(CONFIG_FS_PROCFS) */
|
|
|
|
/****************************************************************************
|
|
* Private Function
|
|
****************************************************************************/
|
|
|
|
#if !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK) && defined(CONFIG_FS_PROCFS)
|
|
|
|
static int clk_procfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
FAR struct procfs_file_s *priv;
|
|
|
|
if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0)
|
|
{
|
|
return -EACCES;
|
|
}
|
|
|
|
priv = kmm_zalloc(sizeof(struct procfs_file_s));
|
|
if (!priv)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
filep->f_priv = priv;
|
|
return OK;
|
|
}
|
|
|
|
static int clk_procfs_close(FAR struct file *filep)
|
|
{
|
|
FAR struct procfs_file_s *priv = filep->f_priv;
|
|
|
|
kmm_free(priv);
|
|
filep->f_priv = NULL;
|
|
return OK;
|
|
}
|
|
|
|
static size_t clk_procfs_printf(FAR char *buffer, size_t buflen,
|
|
FAR off_t *pos, FAR const char *fmt,
|
|
...)
|
|
{
|
|
char tmp[CLK_PROCFS_LINELEN];
|
|
size_t tmplen;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
tmplen = vsnprintf(tmp, sizeof(tmp), fmt, ap);
|
|
va_end(ap);
|
|
|
|
return procfs_memcpy(tmp, tmplen, buffer, buflen, pos);
|
|
}
|
|
|
|
static size_t clk_procfs_show_subtree(FAR struct clk_s *clk, int level,
|
|
FAR char *buffer, size_t buflen,
|
|
FAR off_t *pos, FAR irqstate_t *flags)
|
|
{
|
|
FAR struct clk_s *child;
|
|
size_t oldlen = buflen;
|
|
size_t ret;
|
|
|
|
if (strchr(clk_get_name(clk), '/'))
|
|
{
|
|
clk_list_unlock(*flags);
|
|
}
|
|
|
|
ret = clk_procfs_printf(buffer, buflen, pos, "%*s%-*s %11d %11u %11d\n",
|
|
level * 2, "", 40 - level * 2, clk_get_name(clk),
|
|
clk_is_enabled(clk), clk_get_rate(clk),
|
|
clk_get_phase(clk));
|
|
buffer += ret;
|
|
buflen -= ret;
|
|
|
|
if (strchr(clk_get_name(clk), '/'))
|
|
{
|
|
*flags = clk_list_lock();
|
|
}
|
|
|
|
if (buflen > 0)
|
|
{
|
|
list_for_every_entry(&clk->children, child, struct clk_s, node)
|
|
{
|
|
ret = clk_procfs_show_subtree(child, level + 1,
|
|
buffer, buflen, pos, flags);
|
|
buffer += ret;
|
|
buflen -= ret;
|
|
|
|
if (buflen == 0)
|
|
{
|
|
break; /* No enough space, return */
|
|
}
|
|
}
|
|
}
|
|
|
|
return oldlen - buflen;
|
|
}
|
|
|
|
static size_t clk_procfs_showtree(FAR char *buffer,
|
|
size_t buflen, FAR off_t *pos)
|
|
{
|
|
FAR struct clk_s *clk;
|
|
size_t oldlen = buflen;
|
|
irqstate_t flags;
|
|
size_t ret;
|
|
|
|
flags = clk_list_lock();
|
|
|
|
list_for_every_entry(&g_clk_root_list, clk, struct clk_s, node)
|
|
{
|
|
ret = clk_procfs_show_subtree(clk, 0, buffer, buflen, pos, &flags);
|
|
buffer += ret;
|
|
buflen -= ret;
|
|
|
|
if (buflen == 0)
|
|
{
|
|
goto out; /* No enough space, return */
|
|
}
|
|
}
|
|
|
|
list_for_every_entry(&g_clk_orphan_list, clk, struct clk_s, node)
|
|
{
|
|
ret = clk_procfs_show_subtree(clk, 0, buffer, buflen, pos, &flags);
|
|
buffer += ret;
|
|
buflen -= ret;
|
|
|
|
if (buflen == 0)
|
|
{
|
|
goto out; /* No enough space, return */
|
|
}
|
|
}
|
|
|
|
out:
|
|
clk_list_unlock(flags);
|
|
return oldlen - buflen;
|
|
}
|
|
|
|
static ssize_t clk_procfs_read(FAR struct file *filep,
|
|
FAR char *buffer, size_t buflen)
|
|
{
|
|
off_t pos = filep->f_pos;
|
|
size_t oldlen = buflen;
|
|
size_t ret;
|
|
|
|
ret = clk_procfs_printf(buffer, buflen, &pos,
|
|
"%8s%44s%12s%12s\n",
|
|
"clock", "enable_cnt", "rate", "phase");
|
|
buffer += ret;
|
|
buflen -= ret;
|
|
|
|
if (buflen > 0)
|
|
{
|
|
ret = clk_procfs_showtree(buffer, buflen, &pos);
|
|
buffer += ret;
|
|
buflen -= ret;
|
|
}
|
|
|
|
filep->f_pos += oldlen - buflen;
|
|
return oldlen - buflen;
|
|
}
|
|
|
|
static int clk_procfs_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp)
|
|
{
|
|
FAR struct procfs_file_s *oldpriv;
|
|
FAR struct procfs_file_s *newpriv;
|
|
|
|
oldpriv = oldp->f_priv;
|
|
DEBUGASSERT(oldpriv);
|
|
|
|
newpriv = kmm_zalloc(sizeof(struct procfs_file_s));
|
|
if (!newpriv)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memcpy(newpriv, oldpriv, sizeof(struct procfs_file_s));
|
|
newp->f_priv = newpriv;
|
|
return OK;
|
|
}
|
|
|
|
static int clk_procfs_stat(FAR const char *relpath, FAR struct stat *buf)
|
|
{
|
|
/* File/directory size, access block size */
|
|
|
|
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
|
|
buf->st_size = 0;
|
|
buf->st_blksize = 0;
|
|
buf->st_blocks = 0;
|
|
return OK;
|
|
}
|
|
|
|
#endif /* !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK) && defined(CONFIG_FS_PROCFS) */
|
|
|
|
static irqstate_t clk_list_lock(void)
|
|
{
|
|
if (!up_interrupt_context() && !sched_idletask())
|
|
{
|
|
nxmutex_lock(&g_clk_list_lock);
|
|
}
|
|
|
|
return enter_critical_section();
|
|
}
|
|
|
|
static void clk_list_unlock(irqstate_t flags)
|
|
{
|
|
leave_critical_section(flags);
|
|
|
|
if (!up_interrupt_context() && !sched_idletask())
|
|
{
|
|
nxmutex_unlock(&g_clk_list_lock);
|
|
}
|
|
}
|
|
|
|
static int clk_fetch_parent_index(FAR struct clk_s *clk,
|
|
FAR struct clk_s *parent)
|
|
{
|
|
int i;
|
|
|
|
if (!parent)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < clk->num_parents; i++)
|
|
{
|
|
if (!strcmp(clk->parent_names[i], parent->name))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void clk_reparent(FAR struct clk_s *clk, FAR struct clk_s *parent)
|
|
{
|
|
list_delete(&clk->node);
|
|
|
|
if (parent)
|
|
{
|
|
if (parent->new_child == clk)
|
|
{
|
|
parent->new_child = NULL;
|
|
}
|
|
list_add_head(&parent->children, &clk->node);
|
|
}
|
|
|
|
clk->parent = parent;
|
|
}
|
|
|
|
static uint32_t clk_recalc(FAR struct clk_s *clk, uint32_t parent_rate)
|
|
{
|
|
if (clk->ops->recalc_rate)
|
|
{
|
|
return clk->ops->recalc_rate(clk, parent_rate);
|
|
}
|
|
|
|
return parent_rate;
|
|
}
|
|
|
|
static void __clk_recalc_rate(FAR struct clk_s *clk)
|
|
{
|
|
uint32_t parent_rate = 0;
|
|
FAR struct clk_s *child;
|
|
|
|
if (clk->parent)
|
|
{
|
|
parent_rate = __clk_get_rate(clk->parent);
|
|
}
|
|
|
|
clk->rate = clk_recalc(clk, parent_rate);
|
|
|
|
list_for_every_entry(&clk->children, child, struct clk_s, node)
|
|
{
|
|
__clk_recalc_rate(child);
|
|
}
|
|
}
|
|
|
|
static void clk_calc_subtree(FAR struct clk_s *clk, uint32_t new_rate,
|
|
FAR struct clk_s *new_parent, uint8_t p_index)
|
|
{
|
|
FAR struct clk_s *child;
|
|
|
|
clk->new_rate = new_rate;
|
|
clk->new_parent = new_parent;
|
|
clk->new_parent_index = p_index;
|
|
|
|
clk->new_child = NULL;
|
|
if (new_parent && new_parent != clk->parent)
|
|
{
|
|
new_parent->new_child = clk;
|
|
}
|
|
|
|
list_for_every_entry(&clk->children, child, struct clk_s, node)
|
|
{
|
|
child->new_rate = clk_recalc(child, new_rate);
|
|
clk_calc_subtree(child, child->new_rate, NULL, 0);
|
|
}
|
|
}
|
|
|
|
static FAR struct clk_s *clk_calc_new_rates(FAR struct clk_s *clk,
|
|
uint32_t rate)
|
|
{
|
|
FAR struct clk_s *top = clk;
|
|
FAR struct clk_s *old_parent;
|
|
FAR struct clk_s *parent;
|
|
uint32_t best_parent_rate = 0;
|
|
uint32_t new_rate = 0;
|
|
int p_index = 0;
|
|
|
|
if (!clk)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
parent = old_parent = clk->parent;
|
|
if (parent)
|
|
{
|
|
best_parent_rate = __clk_get_rate(parent);
|
|
}
|
|
|
|
if (clk->ops->determine_rate)
|
|
{
|
|
new_rate = clk->ops->determine_rate(clk, rate,
|
|
&best_parent_rate, &parent);
|
|
}
|
|
else if (clk->ops->round_rate)
|
|
{
|
|
new_rate = clk->ops->round_rate(clk, rate, &best_parent_rate);
|
|
}
|
|
else if (!parent || !(clk->flags & CLK_SET_RATE_PARENT))
|
|
{
|
|
clk->new_rate = clk->rate;
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
top = clk_calc_new_rates(parent, rate);
|
|
new_rate = parent->new_rate;
|
|
goto out;
|
|
}
|
|
|
|
if (parent)
|
|
{
|
|
p_index = clk_fetch_parent_index(clk, parent);
|
|
if (p_index < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((clk->flags & CLK_SET_RATE_PARENT) && parent &&
|
|
best_parent_rate != __clk_get_rate(parent))
|
|
{
|
|
top = clk_calc_new_rates(parent, best_parent_rate);
|
|
}
|
|
|
|
out:
|
|
clk_calc_subtree(clk, new_rate, parent, p_index);
|
|
return top;
|
|
}
|
|
|
|
static void clk_change_rate(FAR struct clk_s *clk, uint32_t best_parent_rate)
|
|
{
|
|
FAR struct clk_s *child;
|
|
FAR struct clk_s *old_parent;
|
|
bool skip_set_rate = false;
|
|
|
|
old_parent = clk->parent;
|
|
|
|
if (clk->new_parent && clk->new_parent != clk->parent)
|
|
{
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_enable(old_parent);
|
|
clk_enable(clk->new_parent);
|
|
}
|
|
|
|
if (clk->enable_count)
|
|
{
|
|
clk_enable(clk->new_parent);
|
|
clk_enable(clk);
|
|
}
|
|
|
|
clk_reparent(clk, clk->new_parent);
|
|
|
|
if (clk->ops->set_rate_and_parent)
|
|
{
|
|
skip_set_rate = true;
|
|
clk->ops->set_rate_and_parent(clk, clk->new_rate, best_parent_rate,
|
|
clk->new_parent_index);
|
|
}
|
|
else if (clk->ops->set_parent)
|
|
{
|
|
clk->ops->set_parent(clk, clk->new_parent_index);
|
|
}
|
|
|
|
if (clk->enable_count)
|
|
{
|
|
clk_disable(clk);
|
|
clk_disable(old_parent);
|
|
}
|
|
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_disable(clk->new_parent);
|
|
clk_disable(old_parent);
|
|
}
|
|
}
|
|
|
|
if (!skip_set_rate && clk->ops->set_rate)
|
|
{
|
|
clk->ops->set_rate(clk, clk->new_rate, best_parent_rate);
|
|
}
|
|
|
|
clk->rate = clk->new_rate;
|
|
|
|
list_for_every_entry(&clk->children, child, struct clk_s, node)
|
|
{
|
|
if (child->new_parent && child->new_parent != clk)
|
|
{
|
|
continue;
|
|
}
|
|
if (child->new_rate != __clk_get_rate(child))
|
|
{
|
|
clk_change_rate(child, clk->new_rate);
|
|
}
|
|
}
|
|
|
|
if (clk->new_child && clk->new_child->new_rate !=
|
|
__clk_get_rate(clk->new_child))
|
|
{
|
|
clk_change_rate(clk->new_child, clk->new_rate);
|
|
}
|
|
}
|
|
|
|
static FAR struct clk_s *__clk_lookup(FAR const char *name,
|
|
FAR struct clk_s *clk)
|
|
{
|
|
FAR struct clk_s *child;
|
|
FAR struct clk_s *ret;
|
|
|
|
if (!strcmp(clk->name, name))
|
|
{
|
|
return clk;
|
|
}
|
|
|
|
list_for_every_entry(&clk->children, child, struct clk_s, node)
|
|
{
|
|
ret = __clk_lookup(name, child);
|
|
if (ret)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t __clk_get_rate(FAR struct clk_s *clk)
|
|
{
|
|
uint32_t parent_rate;
|
|
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (clk->rate == 0)
|
|
{
|
|
parent_rate = __clk_get_rate(clk->parent);
|
|
clk->rate = clk_recalc(clk, parent_rate);
|
|
}
|
|
|
|
return clk->rate;
|
|
}
|
|
|
|
static uint32_t __clk_round_rate(FAR struct clk_s *clk, uint32_t rate)
|
|
{
|
|
uint32_t parent_rate = 0;
|
|
FAR struct clk_s *parent;
|
|
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
parent = clk->parent;
|
|
if (parent)
|
|
{
|
|
parent_rate = __clk_get_rate(parent);
|
|
}
|
|
|
|
if (clk->ops->determine_rate)
|
|
{
|
|
return clk->ops->determine_rate(clk, rate, &parent_rate, &parent);
|
|
}
|
|
else if (clk->ops->round_rate)
|
|
{
|
|
return clk->ops->round_rate(clk, rate, &parent_rate);
|
|
}
|
|
else if (clk->flags & CLK_SET_RATE_PARENT)
|
|
{
|
|
return __clk_round_rate(clk->parent, rate);
|
|
}
|
|
else
|
|
{
|
|
return __clk_get_rate(clk);
|
|
}
|
|
}
|
|
|
|
static int __clk_enable(FAR struct clk_s *clk)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (clk->enable_count == 0)
|
|
{
|
|
ret = __clk_enable(clk->parent);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (clk->ops->enable)
|
|
{
|
|
ret = clk->ops->enable(clk);
|
|
if (ret < 0)
|
|
{
|
|
__clk_disable(clk->parent);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ++clk->enable_count;
|
|
}
|
|
|
|
static int __clk_disable(FAR struct clk_s *clk)
|
|
{
|
|
if (!clk || clk->enable_count == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (clk->flags & CLK_IS_CRITICAL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (--clk->enable_count == 0)
|
|
{
|
|
if (clk->ops->disable)
|
|
{
|
|
clk->ops->disable(clk);
|
|
}
|
|
|
|
if (clk->parent)
|
|
{
|
|
__clk_disable(clk->parent);
|
|
}
|
|
}
|
|
|
|
return clk->enable_count;
|
|
}
|
|
|
|
static void clk_init_parent(FAR struct clk_s *clk)
|
|
{
|
|
uint8_t index;
|
|
|
|
if (!clk->num_parents)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (clk->num_parents == 1)
|
|
{
|
|
clk->parent = clk_get(clk->parent_names[0]);
|
|
return;
|
|
}
|
|
|
|
if (!clk->ops->get_parent)
|
|
{
|
|
return;
|
|
};
|
|
|
|
index = clk->ops->get_parent(clk);
|
|
clk->parent = clk_get_parent_by_index(clk, index);
|
|
}
|
|
|
|
static int __clk_register(FAR struct clk_s *clk)
|
|
{
|
|
FAR struct clk_s *orphan;
|
|
FAR struct clk_s *temp;
|
|
irqstate_t flags;
|
|
uint8_t i;
|
|
|
|
if (!clk)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (clk_lookup(clk->name))
|
|
{
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (clk->ops->set_rate &&
|
|
!((clk->ops->round_rate || clk->ops->determine_rate) &&
|
|
clk->ops->recalc_rate))
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (clk->ops->set_parent && !clk->ops->get_parent)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (clk->ops->set_rate_and_parent &&
|
|
!(clk->ops->set_parent && clk->ops->set_rate))
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
clk_init_parent(clk);
|
|
|
|
flags = clk_list_lock();
|
|
|
|
if (clk->parent)
|
|
{
|
|
list_add_head(&clk->parent->children, &clk->node);
|
|
}
|
|
else if (!clk->num_parents)
|
|
{
|
|
list_add_head(&g_clk_root_list, &clk->node);
|
|
}
|
|
else
|
|
{
|
|
list_add_head(&g_clk_orphan_list, &clk->node);
|
|
}
|
|
|
|
list_for_every_entry_safe(&g_clk_orphan_list, orphan,
|
|
temp, struct clk_s, node)
|
|
{
|
|
if (orphan->num_parents && orphan->ops->get_parent)
|
|
{
|
|
i = orphan->ops->get_parent(orphan);
|
|
if (!strcmp(clk->name, orphan->parent_names[i]))
|
|
{
|
|
clk_reparent(orphan, clk);
|
|
}
|
|
}
|
|
else if (orphan->num_parents)
|
|
{
|
|
for (i = 0; i < orphan->num_parents; i++)
|
|
{
|
|
if (!strcmp(clk->name, orphan->parent_names[i]))
|
|
{
|
|
clk_reparent(orphan, clk);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
clk_list_unlock(flags);
|
|
return 0;
|
|
}
|
|
|
|
static void clk_disable_unused_subtree(FAR struct clk_s *clk)
|
|
{
|
|
FAR struct clk_s *child = NULL;
|
|
|
|
list_for_every_entry(&clk->children, child, struct clk_s, node)
|
|
{
|
|
clk_disable_unused_subtree(child);
|
|
}
|
|
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_enable(clk->parent);
|
|
}
|
|
|
|
if (clk->enable_count)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
if (clk_is_enabled(clk))
|
|
{
|
|
if (clk->flags & CLK_IS_CRITICAL)
|
|
{
|
|
__clk_enable(clk);
|
|
}
|
|
else if (clk->ops->disable)
|
|
{
|
|
clk->ops->disable(clk);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_disable(clk->parent);
|
|
}
|
|
}
|
|
|
|
static FAR struct clk_s *clk_lookup(FAR const char *name)
|
|
{
|
|
FAR struct clk_s *root_clk = NULL;
|
|
FAR struct clk_s *ret = NULL;
|
|
irqstate_t flags;
|
|
|
|
flags = clk_list_lock();
|
|
list_for_every_entry(&g_clk_root_list, root_clk, struct clk_s, node)
|
|
{
|
|
ret = __clk_lookup(name, root_clk);
|
|
if (ret)
|
|
{
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
list_for_every_entry(&g_clk_orphan_list, root_clk, struct clk_s, node)
|
|
{
|
|
ret = __clk_lookup(name, root_clk);
|
|
if (ret)
|
|
{
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
clk_list_unlock(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
void clk_disable_unused(void)
|
|
{
|
|
FAR struct clk_s *root_clk = NULL;
|
|
irqstate_t flags;
|
|
|
|
flags = clk_list_lock();
|
|
|
|
list_for_every_entry(&g_clk_root_list, root_clk, struct clk_s, node)
|
|
{
|
|
clk_disable_unused_subtree(root_clk);
|
|
}
|
|
|
|
list_for_every_entry(&g_clk_orphan_list, root_clk, struct clk_s, node)
|
|
{
|
|
clk_disable_unused_subtree(root_clk);
|
|
}
|
|
|
|
clk_list_unlock(flags);
|
|
}
|
|
|
|
int clk_disable(FAR struct clk_s *clk)
|
|
{
|
|
return __clk_disable(clk);
|
|
}
|
|
|
|
int clk_enable(FAR struct clk_s *clk)
|
|
{
|
|
return __clk_enable(clk);
|
|
}
|
|
|
|
uint32_t clk_round_rate(FAR struct clk_s *clk, uint32_t rate)
|
|
{
|
|
return __clk_round_rate(clk, rate);
|
|
}
|
|
|
|
int clk_set_rate(FAR struct clk_s *clk, uint32_t rate)
|
|
{
|
|
uint32_t parent_rate;
|
|
FAR struct clk_s *top;
|
|
int ret = 0;
|
|
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (rate == __clk_get_rate(clk))
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
if ((clk->flags & CLK_SET_RATE_GATE) && clk->enable_count)
|
|
{
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
top = clk_calc_new_rates(clk, rate);
|
|
if (!top)
|
|
{
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (top->new_parent)
|
|
{
|
|
parent_rate = __clk_get_rate(top->new_parent);
|
|
}
|
|
else if (top->parent)
|
|
{
|
|
parent_rate = __clk_get_rate(top->parent);
|
|
}
|
|
else
|
|
{
|
|
parent_rate = 0;
|
|
}
|
|
|
|
clk_change_rate(top, parent_rate);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int clk_set_rates(FAR const struct clk_rate_s *rates)
|
|
{
|
|
FAR struct clk_s *clk;
|
|
int ret;
|
|
|
|
if (!rates)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
while (rates->name)
|
|
{
|
|
clk = clk_get(rates->name);
|
|
if (!clk)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = clk_set_rate(clk, rates->rate);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
rates++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int clk_set_phase(FAR struct clk_s *clk, int degrees)
|
|
{
|
|
int ret = -EINVAL;
|
|
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
degrees %= 360;
|
|
if (degrees < 0)
|
|
{
|
|
degrees += 360;
|
|
}
|
|
|
|
if (clk->ops->set_phase)
|
|
{
|
|
ret = clk->ops->set_phase(clk, degrees);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int clk_get_phase(FAR struct clk_s *clk)
|
|
{
|
|
if (!clk || !clk->ops->get_phase)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return clk->ops->get_phase(clk);
|
|
}
|
|
|
|
FAR const char *clk_get_name(FAR const struct clk_s *clk)
|
|
{
|
|
return !clk ? NULL : clk->name;
|
|
}
|
|
|
|
int clk_is_enabled(FAR struct clk_s *clk)
|
|
{
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* when hardware .is_enabled missing, used software counter */
|
|
|
|
if (!clk->ops->is_enabled)
|
|
{
|
|
return clk->enable_count;
|
|
}
|
|
|
|
return clk->ops->is_enabled(clk);
|
|
}
|
|
|
|
FAR struct clk_s *clk_get(FAR const char *name)
|
|
{
|
|
FAR struct clk_s *clk;
|
|
|
|
if (!name)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
clk = clk_lookup(name);
|
|
|
|
#ifdef CONFIG_CLK_RPMSG
|
|
if (clk == NULL)
|
|
{
|
|
clk = clk_register_rpmsg(name, CLK_GET_RATE_NOCACHE);
|
|
}
|
|
#endif
|
|
|
|
return clk;
|
|
}
|
|
|
|
int clk_set_parent(FAR struct clk_s *clk, FAR struct clk_s *parent)
|
|
{
|
|
FAR struct clk_s *old_parent = NULL;
|
|
int ret = 0;
|
|
int index = 0;
|
|
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (clk->num_parents > 1 && !clk->ops->set_parent)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
if (clk->parent == parent)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
if ((clk->flags & CLK_SET_PARENT_GATE) && clk->enable_count)
|
|
{
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
if (parent)
|
|
{
|
|
index = clk_fetch_parent_index(clk, parent);
|
|
if (index < 0)
|
|
{
|
|
ret = index;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
old_parent = clk->parent;
|
|
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_enable(old_parent);
|
|
clk_enable(parent);
|
|
}
|
|
|
|
if (clk->enable_count)
|
|
{
|
|
clk_enable(parent);
|
|
clk_enable(clk);
|
|
}
|
|
|
|
clk_reparent(clk, parent);
|
|
|
|
if (parent && clk->ops->set_parent)
|
|
{
|
|
ret = clk->ops->set_parent(clk, index);
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
clk_reparent(clk, old_parent);
|
|
|
|
if (clk->enable_count)
|
|
{
|
|
clk_disable(clk);
|
|
clk_disable(parent);
|
|
}
|
|
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_disable(parent);
|
|
clk_disable(old_parent);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (clk->enable_count)
|
|
{
|
|
clk_disable(clk);
|
|
clk_disable(old_parent);
|
|
}
|
|
|
|
if (clk->flags & CLK_OPS_PARENT_ENABLE)
|
|
{
|
|
clk_disable(parent);
|
|
clk_disable(old_parent);
|
|
}
|
|
|
|
__clk_recalc_rate(clk);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
FAR struct clk_s *clk_get_parent_by_index(FAR struct clk_s *clk,
|
|
uint8_t index)
|
|
{
|
|
if (!clk || index >= clk->num_parents)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return clk_get(clk->parent_names[index]);
|
|
}
|
|
|
|
FAR struct clk_s *clk_get_parent(FAR struct clk_s *clk)
|
|
{
|
|
return !clk ? NULL : clk->parent;
|
|
}
|
|
|
|
uint32_t clk_get_rate(FAR struct clk_s *clk)
|
|
{
|
|
if (!clk)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (clk->flags & CLK_GET_RATE_NOCACHE)
|
|
{
|
|
__clk_recalc_rate(clk);
|
|
}
|
|
|
|
return __clk_get_rate(clk);
|
|
}
|
|
|
|
FAR struct clk_s *clk_register(FAR const char *name,
|
|
FAR const char * const *parent_names,
|
|
uint8_t num_parents, uint8_t flags,
|
|
FAR const struct clk_ops_s *ops,
|
|
FAR void *private_data, size_t private_size)
|
|
{
|
|
FAR struct clk_s *clk;
|
|
size_t size;
|
|
size_t off;
|
|
size_t len;
|
|
int i;
|
|
|
|
off = len = sizeof(struct clk_s) + num_parents * sizeof(FAR char *);
|
|
if (!(flags & CLK_PARENT_NAME_IS_STATIC))
|
|
{
|
|
for (i = 0; i < num_parents; i++)
|
|
{
|
|
len += strlen(parent_names[i]) + 1;
|
|
}
|
|
}
|
|
|
|
len += private_size;
|
|
|
|
if (flags & CLK_NAME_IS_STATIC)
|
|
{
|
|
clk = kmm_zalloc(len);
|
|
if (!clk)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
clk->name = name;
|
|
}
|
|
else
|
|
{
|
|
size = strlen(name) + 1;
|
|
clk = kmm_zalloc(len + size);
|
|
if (!clk)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
clk->name = (FAR char *)clk + len;
|
|
strlcpy((FAR char *)clk->name, name, size);
|
|
}
|
|
|
|
clk->ops = ops;
|
|
clk->num_parents = num_parents;
|
|
clk->flags = flags;
|
|
|
|
if (private_data)
|
|
{
|
|
clk->private_data = (FAR char *)clk + off;
|
|
memcpy(clk->private_data, private_data, private_size);
|
|
off += private_size;
|
|
}
|
|
|
|
for (i = 0; i < num_parents; i++)
|
|
{
|
|
if (flags & CLK_PARENT_NAME_IS_STATIC)
|
|
{
|
|
clk->parent_names[i] = parent_names[i];
|
|
}
|
|
else
|
|
{
|
|
clk->parent_names[i] = (FAR char *)clk + off;
|
|
strlcpy((FAR char *)clk->parent_names[i], parent_names[i],
|
|
len - off);
|
|
off += strlen(parent_names[i]) + 1;
|
|
}
|
|
}
|
|
|
|
list_initialize(&clk->node);
|
|
list_initialize(&clk->children);
|
|
|
|
if (!__clk_register(clk))
|
|
{
|
|
return clk;
|
|
}
|
|
|
|
kmm_free(clk);
|
|
return NULL;
|
|
}
|