/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * 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 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(const char *relpath, 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 */ 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, 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, 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, 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(const char *relpath, 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 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(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 = (char *)clk + len; strlcpy((char *)clk->name, name, size); } clk->ops = ops; clk->num_parents = num_parents; clk->flags = flags; if (private_data) { clk->private_data = (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] = (char *)clk + off; strlcpy((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; }