9
0
Fork 0

clk: initial common clk support

This adds barebox common clk support loosely based on the Kernel common
clk support. differences are:

- barebox does not need prepare/unprepare
- no parent rate propagation for set_rate
- struct clk is not really encapsulated from the drivers

Along with the clk support we have support for some basic clk building
blocks:

- clk-fixed
- clk-fixed-factor
- clk-mux
- clk-divider

clk-fixed and clk-fixed-factor are completely generic, clk-mux and clk-divider
are currently the way i.MX muxes/dividers are implemented.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
Sascha Hauer 2012-09-20 22:03:42 +02:00
parent 89b710e509
commit f2e2e596a2
8 changed files with 563 additions and 1 deletions

View File

@ -2,3 +2,6 @@
config CLKDEV_LOOKUP
bool
select HAVE_CLK
config COMMON_CLK
bool

View File

@ -1,2 +1,2 @@
obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed.o clk-divider.o clk-fixed-factor.o clk-mux.o
obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o

98
drivers/clk/clk-divider.c Normal file
View File

@ -0,0 +1,98 @@
/*
* clk-divider.c - generic barebox clock support. Based on Linux clk support
*
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <common.h>
#include <io.h>
#include <malloc.h>
#include <linux/clk.h>
#include <linux/err.h>
struct clk_divider {
struct clk clk;
u8 shift;
u8 width;
void __iomem *reg;
const char *parent;
};
static int clk_divider_set_rate(struct clk *clk, unsigned long rate,
unsigned long parent_rate)
{
struct clk_divider *div = container_of(clk, struct clk_divider, clk);
unsigned int val, divval;
if (rate > parent_rate)
rate = parent_rate;
if (!rate)
rate = 1;
divval = DIV_ROUND_UP(parent_rate, rate);
if (divval > (1 << div->width))
divval = 1 << (div->width);
divval--;
val = readl(div->reg);
val &= ~(((1 << div->width) - 1) << div->shift);
val |= divval << div->shift;
writel(val, div->reg);
return 0;
}
static unsigned long clk_divider_recalc_rate(struct clk *clk,
unsigned long parent_rate)
{
struct clk_divider *div = container_of(clk, struct clk_divider, clk);
unsigned int val;
val = readl(div->reg) >> div->shift;
val &= (1 << div->width) - 1;
val++;
return parent_rate / val;
}
struct clk_ops clk_divider_ops = {
.set_rate = clk_divider_set_rate,
.recalc_rate = clk_divider_recalc_rate,
};
struct clk *clk_divider(const char *name, const char *parent,
void __iomem *reg, u8 shift, u8 width)
{
struct clk_divider *div = xzalloc(sizeof(*div));
int ret;
div->shift = shift;
div->reg = reg;
div->width = width;
div->parent = parent;
div->clk.ops = &clk_divider_ops;
div->clk.name = name;
div->clk.parent_names = &div->parent;
div->clk.num_parents = 1;
ret = clk_register(&div->clk);
if (ret) {
free(div);
return ERR_PTR(ret);
}
return &div->clk;
}

View File

@ -0,0 +1,63 @@
/*
* clk-fixed-factor.c - generic barebox clock support. Based on Linux clk support
*
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <common.h>
#include <io.h>
#include <malloc.h>
#include <linux/clk.h>
#include <linux/err.h>
struct clk_fixed_factor {
struct clk clk;
int mult;
int div;
const char *parent;
};
static unsigned long clk_fixed_factor_recalc_rate(struct clk *clk,
unsigned long parent_rate)
{
struct clk_fixed_factor *f = container_of(clk, struct clk_fixed_factor, clk);
return (parent_rate / f->div) * f->mult;
}
struct clk_ops clk_fixed_factor_ops = {
.recalc_rate = clk_fixed_factor_recalc_rate,
};
struct clk *clk_fixed_factor(const char *name,
const char *parent, unsigned int mult, unsigned int div)
{
struct clk_fixed_factor *f = xzalloc(sizeof(*f));
int ret;
f->mult = mult;
f->div = div;
f->parent = parent;
f->clk.ops = &clk_fixed_factor_ops;
f->clk.name = name;
f->clk.parent_names = &f->parent;
f->clk.num_parents = 1;
ret = clk_register(&f->clk);
if (ret) {
free(f);
return ERR_PTR(ret);
}
return &f->clk;
}

55
drivers/clk/clk-fixed.c Normal file
View File

@ -0,0 +1,55 @@
/*
* clk-fixed.c - generic barebox clock support. Based on Linux clk support
*
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <common.h>
#include <malloc.h>
#include <linux/clk.h>
#include <linux/err.h>
struct clk_fixed {
struct clk clk;
unsigned long rate;
};
static unsigned long clk_fixed_recalc_rate(struct clk *clk,
unsigned long parent_rate)
{
struct clk_fixed *fix = container_of(clk, struct clk_fixed, clk);
return fix->rate;
}
struct clk_ops clk_fixed_ops = {
.recalc_rate = clk_fixed_recalc_rate,
};
struct clk *clk_fixed(const char *name, int rate)
{
struct clk_fixed *fix = xzalloc(sizeof *fix);
int ret;
fix->rate = rate;
fix->clk.ops = &clk_fixed_ops;
fix->clk.name = name;
ret = clk_register(&fix->clk);
if (ret) {
free(fix);
return ERR_PTR(ret);
}
return &fix->clk;
}

77
drivers/clk/clk-mux.c Normal file
View File

@ -0,0 +1,77 @@
/*
* clk-mux.c - generic barebox clock support. Based on Linux clk support
*
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <common.h>
#include <io.h>
#include <malloc.h>
#include <linux/clk.h>
#include <linux/err.h>
struct clk_mux {
struct clk clk;
void __iomem *reg;
int shift;
int width;
};
static int clk_mux_get_parent(struct clk *clk)
{
struct clk_mux *m = container_of(clk, struct clk_mux, clk);
int idx = readl(m->reg) >> m->shift & ((1 << m->width) - 1);
return idx;
}
static int clk_mux_set_parent(struct clk *clk, u8 idx)
{
struct clk_mux *m = container_of(clk, struct clk_mux, clk);
u32 val;
val = readl(m->reg);
val &= ~(((1 << m->width) - 1) << m->shift);
val |= idx << m->shift;
writel(val, m->reg);
return 0;
}
struct clk_ops clk_mux_ops = {
.get_parent = clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
};
struct clk *clk_mux(const char *name, void __iomem *reg,
u8 shift, u8 width, const char **parents, u8 num_parents)
{
struct clk_mux *m = xzalloc(sizeof(*m));
int ret;
m->reg = reg;
m->shift = shift;
m->width = width;
m->clk.ops = &clk_mux_ops;
m->clk.name = name;
m->clk.parent_names = parents;
m->clk.num_parents = num_parents;
ret = clk_register(&m->clk);
if (ret) {
free(m);
return ERR_PTR(ret);
}
return &m->clk;
}

224
drivers/clk/clk.c Normal file
View File

@ -0,0 +1,224 @@
/*
* clk.c - generic barebox clock support. Based on Linux clk support
*
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <common.h>
#include <errno.h>
#include <linux/clk.h>
#include <linux/err.h>
static LIST_HEAD(clks);
static int clk_parent_enable(struct clk *clk)
{
struct clk *parent = clk_get_parent(clk);
if (!IS_ERR_OR_NULL(parent))
return clk_enable(parent);
return 0;
}
static void clk_parent_disable(struct clk *clk)
{
struct clk *parent = clk_get_parent(clk);
if (!IS_ERR_OR_NULL(parent))
clk_disable(parent);
}
int clk_enable(struct clk *clk)
{
int ret;
if (!clk->enable_count) {
ret = clk_parent_enable(clk);
if (ret)
return ret;
if (clk->ops->enable) {
ret = clk->ops->enable(clk);
if (ret) {
clk_parent_disable(clk);
return ret;
}
}
}
clk->enable_count++;
return 0;
}
void clk_disable(struct clk *clk)
{
if (!clk->enable_count)
return;
clk->enable_count--;
if (!clk->enable_count) {
if (clk->ops->disable)
clk->ops->disable(clk);
clk_parent_disable(clk);
}
}
unsigned long clk_get_rate(struct clk *clk)
{
struct clk *parent;
unsigned long parent_rate = 0;
parent = clk_get_parent(clk);
if (!IS_ERR_OR_NULL(parent))
parent_rate = clk_get_rate(parent);
if (clk->ops->recalc_rate)
return clk->ops->recalc_rate(clk, parent_rate);
return parent_rate;
}
long clk_round_rate(struct clk *clk, unsigned long rate)
{
return clk_get_rate(clk);
}
int clk_set_rate(struct clk *clk, unsigned long rate)
{
struct clk *parent;
unsigned long parent_rate = 0;
parent = clk_get_parent(clk);
if (parent)
parent_rate = clk_get_rate(parent);
if (clk->ops->set_rate)
return clk->ops->set_rate(clk, rate, parent_rate);
return -ENOSYS;
}
struct clk *clk_lookup(const char *name)
{
struct clk *c;
if (!name)
return ERR_PTR(-ENODEV);
list_for_each_entry(c, &clks, list) {
if (!strcmp(c->name, name))
return c;
}
return ERR_PTR(-ENODEV);
}
int clk_set_parent(struct clk *clk, struct clk *parent)
{
int i;
if (!clk->num_parents)
return -EINVAL;
if (!clk->ops->set_parent)
return -EINVAL;
for (i = 0; i < clk->num_parents; i++) {
if (IS_ERR_OR_NULL(clk->parents[i]))
clk->parents[i] = clk_lookup(clk->parent_names[i]);
if (!IS_ERR_OR_NULL(clk->parents[i]))
if (clk->parents[i] == parent)
break;
}
if (i == clk->num_parents)
return -EINVAL;
return clk->ops->set_parent(clk, i);
}
struct clk *clk_get_parent(struct clk *clk)
{
int idx;
if (!clk->num_parents)
return ERR_PTR(-ENODEV);
if (clk->num_parents != 1) {
if (!clk->ops->get_parent)
return ERR_PTR(-EINVAL);
idx = clk->ops->get_parent(clk);
if (idx >= clk->num_parents)
return ERR_PTR(-ENODEV);
} else {
idx = 0;
}
if (IS_ERR_OR_NULL(clk->parents[idx]))
clk->parents[idx] = clk_lookup(clk->parent_names[idx]);
return clk->parents[idx];
}
int clk_register(struct clk *clk)
{
clk->parents = xzalloc(sizeof(struct clk *) * clk->num_parents);
list_add_tail(&clk->list, &clks);
return 0;
}
static void dump_one(struct clk *clk, int verbose, int indent)
{
struct clk *c;
printf("%*s%s (rate %ld, %sabled)\n", indent * 4, "", clk->name, clk_get_rate(clk),
clk->enable_count ? "en" : "dis");
if (verbose) {
if (clk->num_parents > 1) {
int i;
printf("%*s`---- possible parents: ", indent * 4, "");
for (i = 0; i < clk->num_parents; i++)
printf("%s ", clk->parent_names[i]);
printf("\n");
}
}
list_for_each_entry(c, &clks, list) {
struct clk *parent = clk_get_parent(c);
if (parent == clk) {
dump_one(c, verbose, indent + 1);
}
}
}
void clk_dump(int verbose)
{
struct clk *c;
list_for_each_entry(c, &clks, list) {
struct clk *parent = clk_get_parent(c);
if (IS_ERR_OR_NULL(parent))
dump_one(c, verbose, 0);
}
}

View File

@ -155,4 +155,46 @@ struct clk *clk_get_sys(const char *dev_id, const char *con_id);
int clk_add_alias(const char *alias, const char *alias_dev_name, char *id,
struct device_d *dev);
#ifdef CONFIG_COMMON_CLK
struct clk_ops {
int (*enable)(struct clk *clk);
void (*disable)(struct clk *clk);
int (*is_enabled)(struct clk *clk);
unsigned long (*recalc_rate)(struct clk *clk,
unsigned long parent_rate);
long (*round_rate)(struct clk *clk, unsigned long,
unsigned long *);
int (*set_parent)(struct clk *clk, u8 index);
int (*get_parent)(struct clk *clk);
int (*set_rate)(struct clk *clk, unsigned long,
unsigned long);
};
struct clk {
const struct clk_ops *ops;
int enable_count;
struct list_head list;
const char *name;
const char **parent_names;
int num_parents;
struct clk **parents;
};
struct clk *clk_fixed(const char *name, int rate);
struct clk *clk_divider(const char *name, const char *parent,
void __iomem *reg, u8 shift, u8 width);
struct clk *clk_fixed_factor(const char *name,
const char *parent, unsigned int mult, unsigned int div);
struct clk *clk_mux(const char *name, void __iomem *reg,
u8 shift, u8 width, const char **parents, u8 num_parents);
int clk_register(struct clk *clk);
struct clk *clk_lookup(const char *name);
void clk_dump(int verbose);
#endif
#endif