incubator-nuttx/drivers/clk/clk.c

1350 lines
28 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 rmutex_t g_clk_list_lock = NXRMUTEX_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())
{
nxrmutex_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())
{
nxrmutex_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 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);
}
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;
}
for (index = 0; index < clk->num_parents; index++)
{
clk->parents[index] = clk_get(clk->parent_names[index]);
}
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;
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);
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;
}
}
}
}
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;
list_for_every_entry(&g_clk_root_list, root_clk, struct clk_s, node)
{
ret = __clk_lookup(name, root_clk);
if (ret)
{
return ret;
}
}
list_for_every_entry(&g_clk_orphan_list, root_clk, struct clk_s, node)
{
ret = __clk_lookup(name, root_clk);
if (ret)
{
return ret;
}
}
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)
{
irqstate_t flags;
int count;
flags = clk_list_lock();
count = __clk_disable(clk);
clk_list_unlock(flags);
return count;
}
int clk_enable(FAR struct clk_s *clk)
{
irqstate_t flags;
int count;
flags = clk_list_lock();
count = __clk_enable(clk);
clk_list_unlock(flags);
return count;
}
uint32_t clk_round_rate(FAR struct clk_s *clk, uint32_t rate)
{
irqstate_t flags;
uint32_t round;
flags = clk_list_lock();
round = __clk_round_rate(clk, rate);
clk_list_unlock(flags);
return round;
}
int clk_set_rate(FAR struct clk_s *clk, uint32_t rate)
{
uint32_t parent_rate;
FAR struct clk_s *top;
irqstate_t flags;
int ret = 0;
if (!clk)
{
return 0;
}
flags = clk_list_lock();
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:
clk_list_unlock(flags);
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;
irqstate_t flags;
if (!clk)
{
return 0;
}
flags = clk_list_lock();
degrees %= 360;
if (degrees < 0)
{
degrees += 360;
}
if (clk->ops->set_phase)
{
ret = clk->ops->set_phase(clk, degrees);
}
clk_list_unlock(flags);
return ret;
}
int clk_get_phase(FAR struct clk_s *clk)
{
irqstate_t flags;
int degrees;
if (!clk || !clk->ops->get_phase)
{
return 0;
}
flags = clk_list_lock();
degrees = clk->ops->get_phase(clk);
clk_list_unlock(flags);
return degrees;
}
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)
{
irqstate_t flags;
int ret;
flags = clk_list_lock();
ret = __clk_is_enabled(clk);
clk_list_unlock(flags);
return ret;
}
FAR struct clk_s *clk_get(FAR const char *name)
{
FAR struct clk_s *clk;
irqstate_t flags;
if (!name)
{
return NULL;
}
flags = clk_list_lock();
clk = clk_lookup(name);
clk_list_unlock(flags);
#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;
irqstate_t flags;
int ret = 0;
int index = 0;
if (!clk)
{
return 0;
}
if (clk->num_parents > 1 && !clk->ops->set_parent)
{
return -ENOSYS;
}
flags = clk_list_lock();
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:
clk_list_unlock(flags);
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;
}
if (clk->parents[index] == NULL)
{
clk->parents[index] = clk_get(clk->parent_names[index]);
}
return clk->parents[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)
{
irqstate_t flags;
uint32_t rate;
if (!clk)
{
return 0;
}
flags = clk_list_lock();
if (clk->flags & CLK_GET_RATE_NOCACHE)
{
__clk_recalc_rate(clk);
}
rate = __clk_get_rate(clk);
clk_list_unlock(flags);
return rate;
}
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;
irqstate_t irqflags;
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;
}
}
if (num_parents > 0)
{
clk->parents = kmm_zalloc(sizeof(struct clk_s *) * num_parents);
if (clk->parents == NULL)
{
goto out;
}
}
list_initialize(&clk->node);
list_initialize(&clk->children);
irqflags = clk_list_lock();
if (!__clk_register(clk))
{
clk_list_unlock(irqflags);
return clk;
}
out:
clk_list_unlock(irqflags);
if (clk->parents)
{
kmm_free(clk->parents);
}
kmm_free(clk);
return NULL;
}