Quelle clk.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI clock support
*
* Copyright ( C ) 2013 Texas Instruments , Inc .
*
* Tero Kristo < t - kristo @ ti . com >
*/
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/ti.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/list.h>
#include <linux/minmax.h>
#include <linux/regmap.h>
#include <linux/string_helpers.h>
#include <linux/memblock.h>
#include <linux/device.h>
#include "clock.h"
#undef pr_fmt
#define pr_fmt(fmt) "%s: " fmt, __func__
static LIST_HEAD(clk_hw_omap_clocks);
struct ti_clk_ll_ops *ti_clk_ll_ops;
static struct device_node *clocks_node_ptr[CLK_MAX_MEMMAPS];
struct ti_clk_features ti_clk_features;
struct clk_iomap {
struct regmap *regmap;
void __iomem *mem;
};
static struct clk_iomap *clk_memmaps[CLK_MAX_MEMMAPS];
static void clk_memmap_writel(u32 val, const struct clk_omap_reg *reg)
{
struct clk_iomap *io = clk_memmaps[reg->index];
if (reg->ptr)
writel_relaxed(val, reg->ptr);
else if (io->regmap)
regmap_write(io->regmap, reg->offset, val);
else
writel_relaxed(val, io->mem + reg->offset);
}
static void _clk_rmw(u32 val, u32 mask, void __iomem *ptr)
{
u32 v;
v = readl_relaxed(ptr);
v &= ~mask;
v |= val;
writel_relaxed(v, ptr);
}
static void clk_memmap_rmw(u32 val, u32 mask, const struct clk_omap_reg *reg)
{
struct clk_iomap *io = clk_memmaps[reg->index];
if (reg->ptr) {
_clk_rmw(val, mask, reg->ptr);
} else if (io->regmap) {
regmap_update_bits(io->regmap, reg->offset, mask, val);
} else {
_clk_rmw(val, mask, io->mem + reg->offset);
}
}
static u32 clk_memmap_readl(const struct clk_omap_reg *reg)
{
u32 val;
struct clk_iomap *io = clk_memmaps[reg->index];
if (reg->ptr)
val = readl_relaxed(reg->ptr);
else if (io->regmap)
regmap_read(io->regmap, reg->offset, &val);
else
val = readl_relaxed(io->mem + reg->offset);
return val;
}
/**
* ti_clk_setup_ll_ops - setup low level clock operations
* @ ops : low level clock ops descriptor
*
* Sets up low level clock operations for TI clock driver . This is used
* to provide various callbacks for the clock driver towards platform
* specific code . Returns 0 on success , - EBUSY if ll_ops have been
* registered already .
*/
int ti_clk_setup_ll_ops(struct ti_clk_ll_ops *ops)
{
if (ti_clk_ll_ops) {
pr_err("Attempt to register ll_ops multiple times.\n" );
return -EBUSY;
}
ti_clk_ll_ops = ops;
ops->clk_readl = clk_memmap_readl;
ops->clk_writel = clk_memmap_writel;
ops->clk_rmw = clk_memmap_rmw;
return 0 ;
}
/*
* Eventually we could standardize to using ' _ ' for clk - * . c files to follow the
* TRM naming .
*/
static struct device_node *ti_find_clock_provider(const char *name)
{
char *tmp __free(kfree) = NULL;
struct device_node *np;
char *p;
tmp = kstrdup_and_replace(name, '-' , '_' , GFP_KERNEL);
if (!tmp)
return NULL;
/* Ignore a possible address for the node name */
p = strchr(tmp, '@' );
if (p)
*p = '\0' ;
/* Node named "clock" with "clock-output-names" */
for_each_node_with_property(np, "clock-output-names" ) {
if (of_property_match_string(np, "clock-output-names" , tmp) == 0 )
return np;
}
/* Fall back to using old node name base provider name */
return of_find_node_by_name(NULL, tmp);
}
/**
* ti_dt_clocks_register - register DT alias clocks during boot
* @ oclks : list of clocks to register
*
* Register alias or non - standard DT clock entries during boot . By
* default , DT clocks are found based on their clock - output - names
* property , or the clock node name for legacy cases . If any
* additional con - id / dev - id - > clock mapping is required , use this
* function to list these .
*/
void __init ti_dt_clocks_register(struct ti_dt_clk oclks[])
{
struct ti_dt_clk *c;
struct device_node *node, *parent, *child;
struct clk *clk;
struct of_phandle_args clkspec;
char buf[64 ];
char *ptr;
char *tags[2 ];
int i;
int num_args;
int ret;
static bool clkctrl_nodes_missing;
static bool has_clkctrl_data;
static bool compat_mode;
compat_mode = ti_clk_get_features()->flags & TI_CLK_CLKCTRL_COMPAT;
for (c = oclks; c->node_name != NULL; c++) {
strcpy(buf, c->node_name);
ptr = buf;
for (i = 0 ; i < 2 ; i++)
tags[i] = NULL;
num_args = 0 ;
while (*ptr) {
if (*ptr == ':' ) {
if (num_args >= 2 ) {
pr_warn("Bad number of tags on %s\n" ,
c->node_name);
return ;
}
tags[num_args++] = ptr + 1 ;
*ptr = 0 ;
}
ptr++;
}
if (num_args && clkctrl_nodes_missing)
continue ;
node = ti_find_clock_provider(buf);
if (num_args && compat_mode) {
parent = node;
child = of_get_child_by_name(parent, "clock" );
if (!child)
child = of_get_child_by_name(parent, "clk" );
if (child) {
of_node_put(parent);
node = child;
}
}
clkspec.np = node;
clkspec.args_count = num_args;
for (i = 0 ; i < num_args; i++) {
ret = kstrtoint(tags[i], i ? 10 : 16 , clkspec.args + i);
if (ret) {
pr_warn("Bad tag in %s at %d: %s\n" ,
c->node_name, i, tags[i]);
of_node_put(node);
return ;
}
}
clk = of_clk_get_from_provider(&clkspec);
of_node_put(node);
if (!IS_ERR(clk)) {
c->lk.clk = clk;
clkdev_add(&c->lk);
} else {
if (num_args && !has_clkctrl_data) {
struct device_node *np;
np = of_find_compatible_node(NULL, NULL,
"ti,clkctrl" );
if (np) {
has_clkctrl_data = true ;
of_node_put(np);
} else {
clkctrl_nodes_missing = true ;
pr_warn("missing clkctrl nodes, please update your dts.\n" );
continue ;
}
}
pr_warn("failed to lookup clock node %s, ret=%ld\n" ,
c->node_name, PTR_ERR(clk));
}
}
}
struct clk_init_item {
struct device_node *node;
void *user;
ti_of_clk_init_cb_t func;
struct list_head link;
};
static LIST_HEAD(retry_list);
/**
* ti_clk_retry_init - retries a failed clock init at later phase
* @ node : device node for the clock
* @ user : user data pointer
* @ func : init function to be called for the clock
*
* Adds a failed clock init to the retry list . The retry list is parsed
* once all the other clocks have been initialized .
*/
int __init ti_clk_retry_init(struct device_node *node, void *user,
ti_of_clk_init_cb_t func)
{
struct clk_init_item *retry;
pr_debug("%pOFn: adding to retry list...\n" , node);
retry = kzalloc(sizeof (*retry), GFP_KERNEL);
if (!retry)
return -ENOMEM;
retry->node = node;
retry->func = func;
retry->user = user;
list_add(&retry->link, &retry_list);
return 0 ;
}
/**
* ti_clk_get_reg_addr - get register address for a clock register
* @ node : device node for the clock
* @ index : register index from the clock node
* @ reg : pointer to target register struct
*
* Builds clock register address from device tree information , and returns
* the data via the provided output pointer @ reg . Returns 0 on success ,
* negative error value on failure .
*/
int ti_clk_get_reg_addr(struct device_node *node, int index,
struct clk_omap_reg *reg)
{
u32 clksel_addr, val;
bool is_clksel = false ;
int i, err;
for (i = 0 ; i < CLK_MAX_MEMMAPS; i++) {
if (clocks_node_ptr[i] == node->parent)
break ;
if (clocks_node_ptr[i] == node->parent->parent)
break ;
}
if (i == CLK_MAX_MEMMAPS) {
pr_err("clk-provider not found for %pOFn!\n" , node);
return -ENOENT;
}
reg->index = i;
if (of_device_is_compatible(node->parent, "ti,clksel" )) {
err = of_property_read_u32_index(node->parent, "reg" , index, &clksel_addr);
if (err) {
pr_err("%pOFn parent clksel must have reg[%d]!\n" , node, index);
return -EINVAL;
}
is_clksel = true ;
}
err = of_property_read_u32_index(node, "reg" , index, &val);
if (err && is_clksel) {
/* Legacy clksel with no reg and a possible ti,bit-shift property */
reg->offset = clksel_addr;
reg->bit = ti_clk_get_legacy_bit_shift(node);
reg->ptr = NULL;
return 0 ;
}
/* Updated clksel clock with a proper reg property */
if (is_clksel) {
reg->offset = clksel_addr;
reg->bit = val;
reg->ptr = NULL;
return 0 ;
}
/* Other clocks that may or may not have ti,bit-shift property */
reg->offset = val;
reg->bit = ti_clk_get_legacy_bit_shift(node);
reg->ptr = NULL;
return 0 ;
}
/**
* ti_clk_get_legacy_bit_shift - get bit shift for a clock register
* @ node : device node for the clock
*
* Gets the clock register bit shift using the legacy ti , bit - shift
* property . Only needed for legacy clock , and can be eventually
* dropped once all the composite clocks use a clksel node with a
* proper reg property .
*/
int ti_clk_get_legacy_bit_shift(struct device_node *node)
{
int err;
u32 val;
err = of_property_read_u32(node, "ti,bit-shift" , &val);
if (!err && in_range(val, 0 , 32 ))
return val;
return 0 ;
}
void ti_clk_latch(struct clk_omap_reg *reg, s8 shift)
{
u32 latch;
if (shift < 0 )
return ;
latch = 1 << shift;
ti_clk_ll_ops->clk_rmw(latch, latch, reg);
ti_clk_ll_ops->clk_rmw(0 , latch, reg);
ti_clk_ll_ops->clk_readl(reg); /* OCP barrier */
}
/**
* omap2_clk_provider_init - init master clock provider
* @ parent : master node
* @ index : internal index for clk_reg_ops
* @ syscon : syscon regmap pointer for accessing clock registers
* @ mem : iomem pointer for the clock provider memory area , only used if
* syscon is not provided
*
* Initializes a master clock IP block . This basically sets up the
* mapping from clocks node to the memory map index . All the clocks
* are then initialized through the common of_clk_init call , and the
* clocks will access their memory maps based on the node layout .
* Returns 0 in success .
*/
int __init omap2_clk_provider_init(struct device_node *parent, int index,
struct regmap *syscon, void __iomem *mem)
{
struct device_node *clocks;
struct clk_iomap *io;
/* get clocks for this parent */
clocks = of_get_child_by_name(parent, "clocks" );
if (!clocks) {
pr_err("%pOFn missing 'clocks' child node.\n" , parent);
return -EINVAL;
}
/* add clocks node info */
clocks_node_ptr[index] = clocks;
io = kzalloc(sizeof (*io), GFP_KERNEL);
if (!io)
return -ENOMEM;
io->regmap = syscon;
io->mem = mem;
clk_memmaps[index] = io;
return 0 ;
}
/**
* omap2_clk_legacy_provider_init - initialize a legacy clock provider
* @ index : index for the clock provider
* @ mem : iomem pointer for the clock provider memory area
*
* Initializes a legacy clock provider memory mapping .
*/
void __init omap2_clk_legacy_provider_init(int index, void __iomem *mem)
{
struct clk_iomap *io;
io = memblock_alloc_or_panic(sizeof (*io), SMP_CACHE_BYTES);
io->mem = mem;
clk_memmaps[index] = io;
}
/**
* ti_dt_clk_init_retry_clks - init clocks from the retry list
*
* Initializes any clocks that have failed to initialize before ,
* reasons being missing parent node ( s ) during earlier init . This
* typically happens only for DPLLs which need to have both of their
* parent clocks ready during init .
*/
void ti_dt_clk_init_retry_clks(void )
{
struct clk_init_item *retry;
struct clk_init_item *tmp;
int retries = 5 ;
while (!list_empty(&retry_list) && retries) {
list_for_each_entry_safe(retry, tmp, &retry_list, link) {
pr_debug("retry-init: %pOFn\n" , retry->node);
retry->func(retry->user, retry->node);
list_del(&retry->link);
kfree(retry);
}
retries--;
}
}
static const struct of_device_id simple_clk_match_table[] __initconst = {
{ .compatible = "fixed-clock" },
{ .compatible = "fixed-factor-clock" },
{ }
};
/**
* ti_dt_clk_name - init clock name from first output name or node name
* @ np : device node
*
* Use the first clock - output - name for the clock name if found . Fall back
* to legacy naming based on node name .
*/
const char *ti_dt_clk_name(struct device_node *np)
{
const char *name;
if (!of_property_read_string_index(np, "clock-output-names" , 0 ,
&name))
return name;
return np->name;
}
/**
* ti_clk_add_aliases - setup clock aliases
*
* Sets up any missing clock aliases . No return value .
*/
void __init ti_clk_add_aliases(void )
{
struct device_node *np;
struct clk *clk;
for_each_matching_node(np, simple_clk_match_table) {
struct of_phandle_args clkspec;
clkspec.np = np;
clk = of_clk_get_from_provider(&clkspec);
ti_clk_add_alias(clk, ti_dt_clk_name(np));
}
}
/**
* ti_clk_setup_features - setup clock features flags
* @ features : features definition to use
*
* Initializes the clock driver features flags based on platform
* provided data . No return value .
*/
void __init ti_clk_setup_features(struct ti_clk_features *features)
{
memcpy(&ti_clk_features, features, sizeof (*features));
}
/**
* ti_clk_get_features - get clock driver features flags
*
* Get TI clock driver features description . Returns a pointer
* to the current feature setup .
*/
const struct ti_clk_features *ti_clk_get_features(void )
{
return &ti_clk_features;
}
/**
* omap2_clk_enable_init_clocks - prepare & enable a list of clocks
* @ clk_names : ptr to an array of strings of clock names to enable
* @ num_clocks : number of clock names in @ clk_names
*
* Prepare and enable a list of clocks , named by @ clk_names . No
* return value . XXX Deprecated ; only needed until these clocks are
* properly claimed and enabled by the drivers or core code that uses
* them . XXX What code disables & calls clk_put on these clocks ?
*/
void omap2_clk_enable_init_clocks(const char **clk_names, u8 num_clocks)
{
struct clk *init_clk;
int i;
for (i = 0 ; i < num_clocks; i++) {
init_clk = clk_get(NULL, clk_names[i]);
if (WARN(IS_ERR(init_clk), "could not find init clock %s\n" ,
clk_names[i]))
continue ;
clk_prepare_enable(init_clk);
}
}
/**
* ti_clk_add_alias - add a clock alias for a TI clock
* @ clk : clock handle to create alias for
* @ con : connection ID for this clock
*
* Creates a clock alias for a TI clock . Allocates the clock lookup entry
* and assigns the data to it . Returns 0 if successful , negative error
* value otherwise .
*/
int ti_clk_add_alias(struct clk *clk, const char *con)
{
struct clk_lookup *cl;
if (!clk)
return 0 ;
if (IS_ERR(clk))
return PTR_ERR(clk);
cl = kzalloc(sizeof (*cl), GFP_KERNEL);
if (!cl)
return -ENOMEM;
cl->con_id = con;
cl->clk = clk;
clkdev_add(cl);
return 0 ;
}
/**
* of_ti_clk_register - register a TI clock to the common clock framework
* @ node : device node for this clock
* @ hw : hardware clock handle
* @ con : connection ID for this clock
*
* Registers a TI clock to the common clock framework , and adds a clock
* alias for it . Returns a handle to the registered clock if successful ,
* ERR_PTR value in failure .
*/
struct clk *of_ti_clk_register(struct device_node *node, struct clk_hw *hw,
const char *con)
{
struct clk *clk;
int ret;
ret = of_clk_hw_register(node, hw);
if (ret)
return ERR_PTR(ret);
clk = hw->clk;
ret = ti_clk_add_alias(clk, con);
if (ret) {
clk_unregister(clk);
return ERR_PTR(ret);
}
return clk;
}
/**
* of_ti_clk_register_omap_hw - register a clk_hw_omap to the clock framework
* @ node : device node for this clock
* @ hw : hardware clock handle
* @ con : connection ID for this clock
*
* Registers a clk_hw_omap clock to the clock framewor , adds a clock alias
* for it , and adds the list to the available clk_hw_omap type clocks .
* Returns a handle to the registered clock if successful , ERR_PTR value
* in failure .
*/
struct clk *of_ti_clk_register_omap_hw(struct device_node *node,
struct clk_hw *hw, const char *con)
{
struct clk *clk;
struct clk_hw_omap *oclk;
clk = of_ti_clk_register(node, hw, con);
if (IS_ERR(clk))
return clk;
oclk = to_clk_hw_omap(hw);
list_add(&oclk->node, &clk_hw_omap_clocks);
return clk;
}
/**
* omap2_clk_for_each - call function for each registered clk_hw_omap
* @ fn : pointer to a callback function
*
* Call @ fn for each registered clk_hw_omap , passing @ hw to each
* function . @ fn must return 0 for success or any other value for
* failure . If @ fn returns non - zero , the iteration across clocks
* will stop and the non - zero return value will be passed to the
* caller of omap2_clk_for_each ( ) .
*/
int omap2_clk_for_each(int (*fn)(struct clk_hw_omap *hw))
{
int ret;
struct clk_hw_omap *hw;
list_for_each_entry(hw, &clk_hw_omap_clocks, node) {
ret = (*fn)(hw);
if (ret)
break ;
}
return ret;
}
/**
* omap2_clk_is_hw_omap - check if the provided clk_hw is OMAP clock
* @ hw : clk_hw to check if it is an omap clock or not
*
* Checks if the provided clk_hw is OMAP clock or not . Returns true if
* it is , false otherwise .
*/
bool omap2_clk_is_hw_omap(struct clk_hw *hw)
{
struct clk_hw_omap *oclk;
list_for_each_entry(oclk, &clk_hw_omap_clocks, node) {
if (&oclk->hw == hw)
return true ;
}
return false ;
}
Messung V0.5 in Prozent C=96 H=95 G=95
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691