From 3d6ee287a3e341c88eafd0b4620b12d640b3736b Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 12 Mar 2013 20:26:02 +0100 Subject: clk: Introduce optional is_prepared callback To reflect whether a clk_hw is prepared the clk_hw may implement the optional is_prepared callback. If not implemented we fall back to use the software prepare counter. Signed-off-by: Ulf Hansson Acked-by: Linus Walleij Signed-off-by: Mike Turquette --- include/linux/clk-provider.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/linux') diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 7f197d7addb0..ee946862e058 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -45,6 +45,10 @@ struct clk_hw; * undo any work done in the @prepare callback. Called with * prepare_lock held. * + * @is_prepared: Queries the hardware to determine if the clock is prepared. + * This function is allowed to sleep. Optional, if this op is not + * set then the prepare count will be used. + * * @enable: Enable the clock atomically. This must not return until the * clock is generating a valid clock signal, usable by consumer * devices. Called with enable_lock held. This function must not @@ -108,6 +112,7 @@ struct clk_hw; struct clk_ops { int (*prepare)(struct clk_hw *hw); void (*unprepare)(struct clk_hw *hw); + int (*is_prepared)(struct clk_hw *hw); int (*enable)(struct clk_hw *hw); void (*disable)(struct clk_hw *hw); int (*is_enabled)(struct clk_hw *hw); @@ -351,6 +356,7 @@ unsigned int __clk_get_enable_count(struct clk *clk); unsigned int __clk_get_prepare_count(struct clk *clk); unsigned long __clk_get_rate(struct clk *clk); unsigned long __clk_get_flags(struct clk *clk); +bool __clk_is_prepared(struct clk *clk); bool __clk_is_enabled(struct clk *clk); struct clk *__clk_lookup(const char *name); -- cgit v1.2.3-71-gd317 From 3cc8247f1dce79511de8bf0f69ab02a46cc315b7 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Tue, 12 Mar 2013 20:26:04 +0100 Subject: clk: Introduce optional unprepare_unused callback An unprepare_unused callback is introduced due to the same reasons to why the disable_unused callback was added. During the clk_disable_unused sequence, those clk_hw that needs specific treatment with regards to being unprepared, shall implement the unprepare_unused callback. Signed-off-by: Ulf Hansson Acked-by: Linus Walleij Signed-off-by: Mike Turquette --- drivers/clk/clk.c | 7 +++++-- include/linux/clk-provider.h | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index c0141f3e1109..253792a46c08 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -352,9 +352,12 @@ static void clk_unprepare_unused_subtree(struct clk *clk) if (clk->flags & CLK_IGNORE_UNUSED) return; - if (__clk_is_prepared(clk)) - if (clk->ops->unprepare) + if (__clk_is_prepared(clk)) { + if (clk->ops->unprepare_unused) + clk->ops->unprepare_unused(clk->hw); + else if (clk->ops->unprepare) clk->ops->unprepare(clk->hw); + } } /* caller must hold prepare_lock */ diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index ee946862e058..56e6cc12c796 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -49,6 +49,10 @@ struct clk_hw; * This function is allowed to sleep. Optional, if this op is not * set then the prepare count will be used. * + * @unprepare_unused: Unprepare the clock atomically. Only called from + * clk_disable_unused for prepare clocks with special needs. + * Called with prepare mutex held. This function may sleep. + * * @enable: Enable the clock atomically. This must not return until the * clock is generating a valid clock signal, usable by consumer * devices. Called with enable_lock held. This function must not @@ -113,6 +117,7 @@ struct clk_ops { int (*prepare)(struct clk_hw *hw); void (*unprepare)(struct clk_hw *hw); int (*is_prepared)(struct clk_hw *hw); + void (*unprepare_unused)(struct clk_hw *hw); int (*enable)(struct clk_hw *hw); void (*disable)(struct clk_hw *hw); int (*is_enabled)(struct clk_hw *hw); -- cgit v1.2.3-71-gd317 From ce4f3313b05c836c21a91ac89f87dccf84ce9561 Mon Sep 17 00:00:00 2001 From: Peter De Schrijver Date: Fri, 22 Mar 2013 14:07:53 +0200 Subject: clk: add table lookup to mux Add a table lookup feature to the mux clock. Also allow arbitrary masks instead of the width. This will be used by some clocks on Tegra114. Also adapt the tegra periph clk because it uses struct clk_mux directly. Signed-off-by: Peter De Schrijver Tested-by: Stephen Warren Signed-off-by: Mike Turquette --- drivers/clk/clk-mux.c | 50 ++++++++++++++++++++++++++++++++++---------- drivers/clk/tegra/clk.h | 27 +++++++++++++++++------- include/linux/clk-private.h | 2 +- include/linux/clk-provider.h | 9 +++++++- 4 files changed, 67 insertions(+), 21 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c index 508c032edce4..25b1734560d0 100644 --- a/drivers/clk/clk-mux.c +++ b/drivers/clk/clk-mux.c @@ -32,6 +32,7 @@ static u8 clk_mux_get_parent(struct clk_hw *hw) { struct clk_mux *mux = to_clk_mux(hw); + int num_parents = __clk_get_num_parents(hw->clk); u32 val; /* @@ -42,7 +43,16 @@ static u8 clk_mux_get_parent(struct clk_hw *hw) * val = 0x4 really means "bit 2, index starts at bit 0" */ val = readl(mux->reg) >> mux->shift; - val &= (1 << mux->width) - 1; + val &= mux->mask; + + if (mux->table) { + int i; + + for (i = 0; i < num_parents; i++) + if (mux->table[i] == val) + return i; + return -EINVAL; + } if (val && (mux->flags & CLK_MUX_INDEX_BIT)) val = ffs(val) - 1; @@ -50,7 +60,7 @@ static u8 clk_mux_get_parent(struct clk_hw *hw) if (val && (mux->flags & CLK_MUX_INDEX_ONE)) val--; - if (val >= __clk_get_num_parents(hw->clk)) + if (val >= num_parents) return -EINVAL; return val; @@ -62,17 +72,22 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index) u32 val; unsigned long flags = 0; - if (mux->flags & CLK_MUX_INDEX_BIT) - index = (1 << ffs(index)); + if (mux->table) + index = mux->table[index]; - if (mux->flags & CLK_MUX_INDEX_ONE) - index++; + else { + if (mux->flags & CLK_MUX_INDEX_BIT) + index = (1 << ffs(index)); + + if (mux->flags & CLK_MUX_INDEX_ONE) + index++; + } if (mux->lock) spin_lock_irqsave(mux->lock, flags); val = readl(mux->reg); - val &= ~(((1 << mux->width) - 1) << mux->shift); + val &= ~(mux->mask << mux->shift); val |= index << mux->shift; writel(val, mux->reg); @@ -88,10 +103,10 @@ const struct clk_ops clk_mux_ops = { }; EXPORT_SYMBOL_GPL(clk_mux_ops); -struct clk *clk_register_mux(struct device *dev, const char *name, +struct clk *clk_register_mux_table(struct device *dev, const char *name, const char **parent_names, u8 num_parents, unsigned long flags, - void __iomem *reg, u8 shift, u8 width, - u8 clk_mux_flags, spinlock_t *lock) + void __iomem *reg, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock) { struct clk_mux *mux; struct clk *clk; @@ -113,9 +128,10 @@ struct clk *clk_register_mux(struct device *dev, const char *name, /* struct clk_mux assignments */ mux->reg = reg; mux->shift = shift; - mux->width = width; + mux->mask = mask; mux->flags = clk_mux_flags; mux->lock = lock; + mux->table = table; mux->hw.init = &init; clk = clk_register(dev, &mux->hw); @@ -125,3 +141,15 @@ struct clk *clk_register_mux(struct device *dev, const char *name, return clk; } + +struct clk *clk_register_mux(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u8 width, + u8 clk_mux_flags, spinlock_t *lock) +{ + u32 mask = BIT(width) - 1; + + return clk_register_mux_table(dev, name, parent_names, num_parents, + flags, reg, shift, mask, clk_mux_flags, + NULL, lock); +} diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h index 0744731c6229..a09d7dcaf183 100644 --- a/drivers/clk/tegra/clk.h +++ b/drivers/clk/tegra/clk.h @@ -355,15 +355,16 @@ struct clk *tegra_clk_register_periph_nodiv(const char *name, struct tegra_clk_periph *periph, void __iomem *clk_base, u32 offset); -#define TEGRA_CLK_PERIPH(_mux_shift, _mux_width, _mux_flags, \ +#define TEGRA_CLK_PERIPH(_mux_shift, _mux_mask, _mux_flags, \ _div_shift, _div_width, _div_frac_width, \ _div_flags, _clk_num, _enb_refcnt, _regs, \ - _gate_flags) \ + _gate_flags, _table) \ { \ .mux = { \ .flags = _mux_flags, \ .shift = _mux_shift, \ - .width = _mux_width, \ + .mask = _mux_mask, \ + .table = _table, \ }, \ .divider = { \ .flags = _div_flags, \ @@ -393,26 +394,36 @@ struct tegra_periph_init_data { const char *dev_id; }; -#define TEGRA_INIT_DATA(_name, _con_id, _dev_id, _parent_names, _offset, \ - _mux_shift, _mux_width, _mux_flags, _div_shift, \ +#define TEGRA_INIT_DATA_TABLE(_name, _con_id, _dev_id, _parent_names, _offset,\ + _mux_shift, _mux_mask, _mux_flags, _div_shift, \ _div_width, _div_frac_width, _div_flags, _regs, \ - _clk_num, _enb_refcnt, _gate_flags, _clk_id) \ + _clk_num, _enb_refcnt, _gate_flags, _clk_id, _table) \ { \ .name = _name, \ .clk_id = _clk_id, \ .parent_names = _parent_names, \ .num_parents = ARRAY_SIZE(_parent_names), \ - .periph = TEGRA_CLK_PERIPH(_mux_shift, _mux_width, \ + .periph = TEGRA_CLK_PERIPH(_mux_shift, _mux_mask, \ _mux_flags, _div_shift, \ _div_width, _div_frac_width, \ _div_flags, _clk_num, \ _enb_refcnt, _regs, \ - _gate_flags), \ + _gate_flags, _table), \ .offset = _offset, \ .con_id = _con_id, \ .dev_id = _dev_id, \ } +#define TEGRA_INIT_DATA(_name, _con_id, _dev_id, _parent_names, _offset,\ + _mux_shift, _mux_width, _mux_flags, _div_shift, \ + _div_width, _div_frac_width, _div_flags, _regs, \ + _clk_num, _enb_refcnt, _gate_flags, _clk_id) \ + TEGRA_INIT_DATA_TABLE(_name, _con_id, _dev_id, _parent_names, _offset,\ + _mux_shift, BIT(_mux_width) - 1, _mux_flags, \ + _div_shift, _div_width, _div_frac_width, _div_flags, \ + _regs, _clk_num, _enb_refcnt, _gate_flags, _clk_id,\ + NULL) + /** * struct clk_super_mux - super clock * diff --git a/include/linux/clk-private.h b/include/linux/clk-private.h index 9c7f5807824b..dd7adff76e81 100644 --- a/include/linux/clk-private.h +++ b/include/linux/clk-private.h @@ -152,7 +152,7 @@ struct clk { }, \ .reg = _reg, \ .shift = _shift, \ - .width = _width, \ + .mask = BIT(_width) - 1, \ .flags = _mux_flags, \ .lock = _lock, \ }; \ diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 56e6cc12c796..63ba3b740794 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -297,8 +297,9 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name, struct clk_mux { struct clk_hw hw; void __iomem *reg; + u32 *table; + u32 mask; u8 shift; - u8 width; u8 flags; spinlock_t *lock; }; @@ -307,11 +308,17 @@ struct clk_mux { #define CLK_MUX_INDEX_BIT BIT(1) extern const struct clk_ops clk_mux_ops; + struct clk *clk_register_mux(struct device *dev, const char *name, const char **parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_mux_flags, spinlock_t *lock); +struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock); + /** * struct clk_fixed_factor - fixed multiplier and divider clock * -- cgit v1.2.3-71-gd317 From ece70094f6ab2107d4313fa1802b13dab0234ac5 Mon Sep 17 00:00:00 2001 From: Prashant Gaikwad Date: Wed, 20 Mar 2013 17:30:34 +0530 Subject: clk: Add composite clock type Not all clocks are required to be decomposed into basic clock types but at the same time want to use the functionality provided by these basic clock types instead of duplicating. For example, Tegra SoC has ~100 clocks which can be decomposed into Mux -> Div -> Gate clock types making the clock count to ~300. Also, parent change operation can not be performed on gate clock which forces to use mux clock in driver if want to change the parent. Instead aggregate the basic clock types functionality into one clock and just use this clock for all operations. This clock type re-uses the functionality of basic clock types and not limited to basic clock types but any hardware-specific implementation. Signed-off-by: Prashant Gaikwad Signed-off-by: Mike Turquette --- drivers/clk/Makefile | 1 + drivers/clk/clk-composite.c | 201 +++++++++++++++++++++++++++++++++++++++++++ include/linux/clk-provider.h | 31 +++++++ 3 files changed, 233 insertions(+) create mode 100644 drivers/clk/clk-composite.c (limited to 'include/linux') diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 1c22f9dc721d..41cb123a2d02 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o obj-$(CONFIG_COMMON_CLK) += clk-gate.o obj-$(CONFIG_COMMON_CLK) += clk-mux.o +obj-$(CONFIG_COMMON_CLK) += clk-composite.o # SoCs specific obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c new file mode 100644 index 000000000000..097dee4fd209 --- /dev/null +++ b/drivers/clk/clk-composite.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2013 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw) + +static u8 clk_composite_get_parent(struct clk_hw *hw) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *mux_ops = composite->mux_ops; + struct clk_hw *mux_hw = composite->mux_hw; + + mux_hw->clk = hw->clk; + + return mux_ops->get_parent(mux_hw); +} + +static int clk_composite_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *mux_ops = composite->mux_ops; + struct clk_hw *mux_hw = composite->mux_hw; + + mux_hw->clk = hw->clk; + + return mux_ops->set_parent(mux_hw, index); +} + +static unsigned long clk_composite_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *div_ops = composite->div_ops; + struct clk_hw *div_hw = composite->div_hw; + + div_hw->clk = hw->clk; + + return div_ops->recalc_rate(div_hw, parent_rate); +} + +static long clk_composite_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *div_ops = composite->div_ops; + struct clk_hw *div_hw = composite->div_hw; + + div_hw->clk = hw->clk; + + return div_ops->round_rate(div_hw, rate, prate); +} + +static int clk_composite_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *div_ops = composite->div_ops; + struct clk_hw *div_hw = composite->div_hw; + + div_hw->clk = hw->clk; + + return div_ops->set_rate(div_hw, rate, parent_rate); +} + +static int clk_composite_is_enabled(struct clk_hw *hw) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *gate_ops = composite->gate_ops; + struct clk_hw *gate_hw = composite->gate_hw; + + gate_hw->clk = hw->clk; + + return gate_ops->is_enabled(gate_hw); +} + +static int clk_composite_enable(struct clk_hw *hw) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *gate_ops = composite->gate_ops; + struct clk_hw *gate_hw = composite->gate_hw; + + gate_hw->clk = hw->clk; + + return gate_ops->enable(gate_hw); +} + +static void clk_composite_disable(struct clk_hw *hw) +{ + struct clk_composite *composite = to_clk_composite(hw); + const struct clk_ops *gate_ops = composite->gate_ops; + struct clk_hw *gate_hw = composite->gate_hw; + + gate_hw->clk = hw->clk; + + gate_ops->disable(gate_hw); +} + +struct clk *clk_register_composite(struct device *dev, const char *name, + const char **parent_names, int num_parents, + struct clk_hw *mux_hw, const struct clk_ops *mux_ops, + struct clk_hw *div_hw, const struct clk_ops *div_ops, + struct clk_hw *gate_hw, const struct clk_ops *gate_ops, + unsigned long flags) +{ + struct clk *clk; + struct clk_init_data init; + struct clk_composite *composite; + struct clk_ops *clk_composite_ops; + + composite = kzalloc(sizeof(*composite), GFP_KERNEL); + if (!composite) { + pr_err("%s: could not allocate composite clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + init.name = name; + init.flags = flags | CLK_IS_BASIC; + init.parent_names = parent_names; + init.num_parents = num_parents; + + clk_composite_ops = &composite->ops; + + if (mux_hw && mux_ops) { + if (!mux_ops->get_parent || !mux_ops->set_parent) { + clk = ERR_PTR(-EINVAL); + goto err; + } + + composite->mux_hw = mux_hw; + composite->mux_ops = mux_ops; + clk_composite_ops->get_parent = clk_composite_get_parent; + clk_composite_ops->set_parent = clk_composite_set_parent; + } + + if (div_hw && div_ops) { + if (!div_ops->recalc_rate || !div_ops->round_rate || + !div_ops->set_rate) { + clk = ERR_PTR(-EINVAL); + goto err; + } + + composite->div_hw = div_hw; + composite->div_ops = div_ops; + clk_composite_ops->recalc_rate = clk_composite_recalc_rate; + clk_composite_ops->round_rate = clk_composite_round_rate; + clk_composite_ops->set_rate = clk_composite_set_rate; + } + + if (gate_hw && gate_ops) { + if (!gate_ops->is_enabled || !gate_ops->enable || + !gate_ops->disable) { + clk = ERR_PTR(-EINVAL); + goto err; + } + + composite->gate_hw = gate_hw; + composite->gate_ops = gate_ops; + clk_composite_ops->is_enabled = clk_composite_is_enabled; + clk_composite_ops->enable = clk_composite_enable; + clk_composite_ops->disable = clk_composite_disable; + } + + init.ops = clk_composite_ops; + composite->hw.init = &init; + + clk = clk_register(dev, &composite->hw); + if (IS_ERR(clk)) + goto err; + + if (composite->mux_hw) + composite->mux_hw->clk = clk; + + if (composite->div_hw) + composite->div_hw->clk = clk; + + if (composite->gate_hw) + composite->gate_hw->clk = clk; + + return clk; + +err: + kfree(composite); + return clk; +} diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 63ba3b740794..1f0352802794 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -342,6 +342,37 @@ struct clk *clk_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div); +/*** + * struct clk_composite - aggregate clock of mux, divider and gate clocks + * + * @hw: handle between common and hardware-specific interfaces + * @mux_hw: handle between composite and hardware-specifix mux clock + * @div_hw: handle between composite and hardware-specifix divider clock + * @gate_hw: handle between composite and hardware-specifix gate clock + * @mux_ops: clock ops for mux + * @div_ops: clock ops for divider + * @gate_ops: clock ops for gate + */ +struct clk_composite { + struct clk_hw hw; + struct clk_ops ops; + + struct clk_hw *mux_hw; + struct clk_hw *div_hw; + struct clk_hw *gate_hw; + + const struct clk_ops *mux_ops; + const struct clk_ops *div_ops; + const struct clk_ops *gate_ops; +}; + +struct clk *clk_register_composite(struct device *dev, const char *name, + const char **parent_names, int num_parents, + struct clk_hw *mux_hw, const struct clk_ops *mux_ops, + struct clk_hw *div_hw, const struct clk_ops *div_ops, + struct clk_hw *gate_hw, const struct clk_ops *gate_ops, + unsigned long flags); + /** * clk_register - allocate a new clock, register it and return an opaque cookie * @dev: device that is registering this clock -- cgit v1.2.3-71-gd317 From e874a6697710f52fa8ab29487a99034d5d96fdcc Mon Sep 17 00:00:00 2001 From: Emilio López Date: Mon, 25 Feb 2013 11:44:26 -0300 Subject: clk: arm: sunxi: Add a new clock driver for sunxi SOCs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements the base CPU clocks for sunxi devices. It has been tested using a slightly modified cpufreq driver from the linux-sunxi 3.0 tree. Additionally, document the new bindings introduced by this patch. Idling: / # cat /sys/kernel/debug/clk/clk_summary clock enable_cnt prepare_cnt rate --------------------------------------------------------------------- osc32k 0 0 32768 osc24M_fixed 0 0 24000000 osc24M 0 0 24000000 apb1_mux 0 0 24000000 apb1 0 0 24000000 pll1 0 0 60000000 cpu 0 0 60000000 axi 0 0 60000000 ahb 0 0 60000000 apb0 0 0 30000000 dummy 0 0 0 After "yes >/dev/null &": / # cat /sys/kernel/debug/clk/clk_summary clock enable_cnt prepare_cnt rate --------------------------------------------------------------------- osc32k 0 0 32768 osc24M_fixed 0 0 24000000 osc24M 0 0 24000000 apb1_mux 0 0 24000000 apb1 0 0 24000000 pll1 0 0 1008000000 cpu 0 0 1008000000 axi 0 0 336000000 ahb 0 0 168000000 apb0 0 0 84000000 dummy 0 0 0 Signed-off-by: Emilio López Acked-by: Maxime Ripard Signed-off-by: Mike Turquette --- Documentation/devicetree/bindings/clock/sunxi.txt | 44 +++ drivers/clk/Makefile | 1 + drivers/clk/sunxi/Makefile | 5 + drivers/clk/sunxi/clk-factors.c | 180 +++++++++++ drivers/clk/sunxi/clk-factors.h | 27 ++ drivers/clk/sunxi/clk-sunxi.c | 362 ++++++++++++++++++++++ drivers/clocksource/sunxi_timer.c | 4 +- include/linux/clk/sunxi.h | 22 ++ 8 files changed, 643 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/clock/sunxi.txt create mode 100644 drivers/clk/sunxi/Makefile create mode 100644 drivers/clk/sunxi/clk-factors.c create mode 100644 drivers/clk/sunxi/clk-factors.h create mode 100644 drivers/clk/sunxi/clk-sunxi.c create mode 100644 include/linux/clk/sunxi.h (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt new file mode 100644 index 000000000000..b23cfbdbcd6d --- /dev/null +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -0,0 +1,44 @@ +Device Tree Clock bindings for arch-sunxi + +This binding uses the common clock binding[1]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt + +Required properties: +- compatible : shall be one of the following: + "allwinner,sunxi-osc-clk" - for a gatable oscillator + "allwinner,sunxi-pll1-clk" - for the main PLL clock + "allwinner,sunxi-cpu-clk" - for the CPU multiplexer clock + "allwinner,sunxi-axi-clk" - for the sunxi AXI clock + "allwinner,sunxi-ahb-clk" - for the sunxi AHB clock + "allwinner,sunxi-apb0-clk" - for the sunxi APB0 clock + "allwinner,sunxi-apb1-clk" - for the sunxi APB1 clock + "allwinner,sunxi-apb1-mux-clk" - for the sunxi APB1 clock muxing + +Required properties for all clocks: +- reg : shall be the control register address for the clock. +- clocks : shall be the input parent clock(s) phandle for the clock +- #clock-cells : from common clock binding; shall be set to 0. + +For example: + +osc24M: osc24M@01c20050 { + #clock-cells = <0>; + compatible = "allwinner,sunxi-osc-clk"; + reg = <0x01c20050 0x4>; + clocks = <&osc24M_fixed>; +}; + +pll1: pll1@01c20000 { + #clock-cells = <0>; + compatible = "allwinner,sunxi-pll1-clk"; + reg = <0x01c20000 0x4>; + clocks = <&osc24M>; +}; + +cpu: cpu@01c20054 { + #clock-cells = <0>; + compatible = "allwinner,sunxi-cpu-clk"; + reg = <0x01c20054 0x4>; + clocks = <&osc32k>, <&osc24M>, <&pll1>; +}; diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 41cb123a2d02..79e98e416724 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -24,6 +24,7 @@ ifeq ($(CONFIG_COMMON_CLK), y) obj-$(CONFIG_ARCH_MMP) += mmp/ endif obj-$(CONFIG_MACH_LOONGSON1) += clk-ls1x.o +obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_ARCH_U8500) += ux500/ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o obj-$(CONFIG_ARCH_ZYNQ) += clk-zynq.o diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile new file mode 100644 index 000000000000..b5bac917612c --- /dev/null +++ b/drivers/clk/sunxi/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for sunxi specific clk +# + +obj-y += clk-sunxi.o clk-factors.o diff --git a/drivers/clk/sunxi/clk-factors.c b/drivers/clk/sunxi/clk-factors.c new file mode 100644 index 000000000000..88523f91d9b7 --- /dev/null +++ b/drivers/clk/sunxi/clk-factors.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2013 Emilio López + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Adjustable factor-based clock implementation + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-factors.h" + +/* + * DOC: basic adjustable factor-based clock that cannot gate + * + * Traits of this clock: + * prepare - clk_prepare only ensures that parents are prepared + * enable - clk_enable only ensures that parents are enabled + * rate - rate is adjustable. + * clk->rate = (parent->rate * N * (K + 1) >> P) / (M + 1) + * parent - fixed parent. No clk_set_parent support + */ + +struct clk_factors { + struct clk_hw hw; + void __iomem *reg; + struct clk_factors_config *config; + void (*get_factors) (u32 *rate, u32 parent, u8 *n, u8 *k, u8 *m, u8 *p); + spinlock_t *lock; +}; + +#define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw) + +#define SETMASK(len, pos) (((-1U) >> (31-len)) << (pos)) +#define CLRMASK(len, pos) (~(SETMASK(len, pos))) +#define FACTOR_GET(bit, len, reg) (((reg) & SETMASK(len, bit)) >> (bit)) + +#define FACTOR_SET(bit, len, reg, val) \ + (((reg) & CLRMASK(len, bit)) | (val << (bit))) + +static unsigned long clk_factors_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + u8 n = 1, k = 0, p = 0, m = 0; + u32 reg; + unsigned long rate; + struct clk_factors *factors = to_clk_factors(hw); + struct clk_factors_config *config = factors->config; + + /* Fetch the register value */ + reg = readl(factors->reg); + + /* Get each individual factor if applicable */ + if (config->nwidth != SUNXI_FACTORS_NOT_APPLICABLE) + n = FACTOR_GET(config->nshift, config->nwidth, reg); + if (config->kwidth != SUNXI_FACTORS_NOT_APPLICABLE) + k = FACTOR_GET(config->kshift, config->kwidth, reg); + if (config->mwidth != SUNXI_FACTORS_NOT_APPLICABLE) + m = FACTOR_GET(config->mshift, config->mwidth, reg); + if (config->pwidth != SUNXI_FACTORS_NOT_APPLICABLE) + p = FACTOR_GET(config->pshift, config->pwidth, reg); + + /* Calculate the rate */ + rate = (parent_rate * n * (k + 1) >> p) / (m + 1); + + return rate; +} + +static long clk_factors_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_factors *factors = to_clk_factors(hw); + factors->get_factors((u32 *)&rate, (u32)*parent_rate, + NULL, NULL, NULL, NULL); + + return rate; +} + +static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + u8 n, k, m, p; + u32 reg; + struct clk_factors *factors = to_clk_factors(hw); + struct clk_factors_config *config = factors->config; + unsigned long flags = 0; + + factors->get_factors((u32 *)&rate, (u32)parent_rate, &n, &k, &m, &p); + + if (factors->lock) + spin_lock_irqsave(factors->lock, flags); + + /* Fetch the register value */ + reg = readl(factors->reg); + + /* Set up the new factors - macros do not do anything if width is 0 */ + reg = FACTOR_SET(config->nshift, config->nwidth, reg, n); + reg = FACTOR_SET(config->kshift, config->kwidth, reg, k); + reg = FACTOR_SET(config->mshift, config->mwidth, reg, m); + reg = FACTOR_SET(config->pshift, config->pwidth, reg, p); + + /* Apply them now */ + writel(reg, factors->reg); + + /* delay 500us so pll stabilizes */ + __delay((rate >> 20) * 500 / 2); + + if (factors->lock) + spin_unlock_irqrestore(factors->lock, flags); + + return 0; +} + +static const struct clk_ops clk_factors_ops = { + .recalc_rate = clk_factors_recalc_rate, + .round_rate = clk_factors_round_rate, + .set_rate = clk_factors_set_rate, +}; + +/** + * clk_register_factors - register a factors clock with + * the clock framework + * @dev: device registering this clock + * @name: name of this clock + * @parent_name: name of clock's parent + * @flags: framework-specific flags + * @reg: register address to adjust factors + * @config: shift and width of factors n, k, m and p + * @get_factors: function to calculate the factors for a given frequency + * @lock: shared register lock for this clock + */ +struct clk *clk_register_factors(struct device *dev, const char *name, + const char *parent_name, + unsigned long flags, void __iomem *reg, + struct clk_factors_config *config, + void (*get_factors)(u32 *rate, u32 parent, + u8 *n, u8 *k, u8 *m, u8 *p), + spinlock_t *lock) +{ + struct clk_factors *factors; + struct clk *clk; + struct clk_init_data init; + + /* allocate the factors */ + factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL); + if (!factors) { + pr_err("%s: could not allocate factors clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + init.name = name; + init.ops = &clk_factors_ops; + init.flags = flags; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + /* struct clk_factors assignments */ + factors->reg = reg; + factors->config = config; + factors->lock = lock; + factors->hw.init = &init; + factors->get_factors = get_factors; + + /* register the clock */ + clk = clk_register(dev, &factors->hw); + + if (IS_ERR(clk)) + kfree(factors); + + return clk; +} diff --git a/drivers/clk/sunxi/clk-factors.h b/drivers/clk/sunxi/clk-factors.h new file mode 100644 index 000000000000..f49851cc4380 --- /dev/null +++ b/drivers/clk/sunxi/clk-factors.h @@ -0,0 +1,27 @@ +#ifndef __MACH_SUNXI_CLK_FACTORS_H +#define __MACH_SUNXI_CLK_FACTORS_H + +#include +#include + +#define SUNXI_FACTORS_NOT_APPLICABLE (0) + +struct clk_factors_config { + u8 nshift; + u8 nwidth; + u8 kshift; + u8 kwidth; + u8 mshift; + u8 mwidth; + u8 pshift; + u8 pwidth; +}; + +struct clk *clk_register_factors(struct device *dev, const char *name, + const char *parent_name, + unsigned long flags, void __iomem *reg, + struct clk_factors_config *config, + void (*get_factors) (u32 *rate, u32 parent_rate, + u8 *n, u8 *k, u8 *m, u8 *p), + spinlock_t *lock); +#endif diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c new file mode 100644 index 000000000000..d4ad1c22859e --- /dev/null +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -0,0 +1,362 @@ +/* + * Copyright 2013 Emilio López + * + * Emilio López + * + * 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 +#include +#include +#include +#include + +#include "clk-factors.h" + +static DEFINE_SPINLOCK(clk_lock); + +/** + * sunxi_osc_clk_setup() - Setup function for gatable oscillator + */ + +#define SUNXI_OSC24M_GATE 0 + +static void __init sunxi_osc_clk_setup(struct device_node *node) +{ + struct clk *clk; + const char *clk_name = node->name; + const char *parent; + void *reg; + + reg = of_iomap(node, 0); + + parent = of_clk_get_parent_name(node, 0); + + clk = clk_register_gate(NULL, clk_name, parent, CLK_IGNORE_UNUSED, + reg, SUNXI_OSC24M_GATE, 0, &clk_lock); + + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + + +/** + * sunxi_get_pll1_factors() - calculates n, k, m, p factors for PLL1 + * PLL1 rate is calculated as follows + * rate = (parent_rate * n * (k + 1) >> p) / (m + 1); + * parent_rate is always 24Mhz + */ + +static void sunxi_get_pll1_factors(u32 *freq, u32 parent_rate, + u8 *n, u8 *k, u8 *m, u8 *p) +{ + u8 div; + + /* Normalize value to a 6M multiple */ + div = *freq / 6000000; + *freq = 6000000 * div; + + /* we were called to round the frequency, we can now return */ + if (n == NULL) + return; + + /* m is always zero for pll1 */ + *m = 0; + + /* k is 1 only on these cases */ + if (*freq >= 768000000 || *freq == 42000000 || *freq == 54000000) + *k = 1; + else + *k = 0; + + /* p will be 3 for divs under 10 */ + if (div < 10) + *p = 3; + + /* p will be 2 for divs between 10 - 20 and odd divs under 32 */ + else if (div < 20 || (div < 32 && (div & 1))) + *p = 2; + + /* p will be 1 for even divs under 32, divs under 40 and odd pairs + * of divs between 40-62 */ + else if (div < 40 || (div < 64 && (div & 2))) + *p = 1; + + /* any other entries have p = 0 */ + else + *p = 0; + + /* calculate a suitable n based on k and p */ + div <<= *p; + div /= (*k + 1); + *n = div / 4; +} + + + +/** + * sunxi_get_apb1_factors() - calculates m, p factors for APB1 + * APB1 rate is calculated as follows + * rate = (parent_rate >> p) / (m + 1); + */ + +static void sunxi_get_apb1_factors(u32 *freq, u32 parent_rate, + u8 *n, u8 *k, u8 *m, u8 *p) +{ + u8 calcm, calcp; + + if (parent_rate < *freq) + *freq = parent_rate; + + parent_rate = (parent_rate + (*freq - 1)) / *freq; + + /* Invalid rate! */ + if (parent_rate > 32) + return; + + if (parent_rate <= 4) + calcp = 0; + else if (parent_rate <= 8) + calcp = 1; + else if (parent_rate <= 16) + calcp = 2; + else + calcp = 3; + + calcm = (parent_rate >> calcp) - 1; + + *freq = (parent_rate >> calcp) / (calcm + 1); + + /* we were called to round the frequency, we can now return */ + if (n == NULL) + return; + + *m = calcm; + *p = calcp; +} + + + +/** + * sunxi_factors_clk_setup() - Setup function for factor clocks + */ + +struct factors_data { + struct clk_factors_config *table; + void (*getter) (u32 *rate, u32 parent_rate, u8 *n, u8 *k, u8 *m, u8 *p); +}; + +static struct clk_factors_config pll1_config = { + .nshift = 8, + .nwidth = 5, + .kshift = 4, + .kwidth = 2, + .mshift = 0, + .mwidth = 2, + .pshift = 16, + .pwidth = 2, +}; + +static struct clk_factors_config apb1_config = { + .mshift = 0, + .mwidth = 5, + .pshift = 16, + .pwidth = 2, +}; + +static const __initconst struct factors_data pll1_data = { + .table = &pll1_config, + .getter = sunxi_get_pll1_factors, +}; + +static const __initconst struct factors_data apb1_data = { + .table = &apb1_config, + .getter = sunxi_get_apb1_factors, +}; + +static void __init sunxi_factors_clk_setup(struct device_node *node, + struct factors_data *data) +{ + struct clk *clk; + const char *clk_name = node->name; + const char *parent; + void *reg; + + reg = of_iomap(node, 0); + + parent = of_clk_get_parent_name(node, 0); + + clk = clk_register_factors(NULL, clk_name, parent, CLK_IGNORE_UNUSED, + reg, data->table, data->getter, &clk_lock); + + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + + +/** + * sunxi_mux_clk_setup() - Setup function for muxes + */ + +#define SUNXI_MUX_GATE_WIDTH 2 + +struct mux_data { + u8 shift; +}; + +static const __initconst struct mux_data cpu_data = { + .shift = 16, +}; + +static const __initconst struct mux_data apb1_mux_data = { + .shift = 24, +}; + +static void __init sunxi_mux_clk_setup(struct device_node *node, + struct mux_data *data) +{ + struct clk *clk; + const char *clk_name = node->name; + const char **parents = kmalloc(sizeof(char *) * 5, GFP_KERNEL); + void *reg; + int i = 0; + + reg = of_iomap(node, 0); + + while (i < 5 && (parents[i] = of_clk_get_parent_name(node, i)) != NULL) + i++; + + clk = clk_register_mux(NULL, clk_name, parents, i, 0, reg, + data->shift, SUNXI_MUX_GATE_WIDTH, + 0, &clk_lock); + + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + + +/** + * sunxi_divider_clk_setup() - Setup function for simple divider clocks + */ + +#define SUNXI_DIVISOR_WIDTH 2 + +struct div_data { + u8 shift; + u8 pow; +}; + +static const __initconst struct div_data axi_data = { + .shift = 0, + .pow = 0, +}; + +static const __initconst struct div_data ahb_data = { + .shift = 4, + .pow = 1, +}; + +static const __initconst struct div_data apb0_data = { + .shift = 8, + .pow = 1, +}; + +static void __init sunxi_divider_clk_setup(struct device_node *node, + struct div_data *data) +{ + struct clk *clk; + const char *clk_name = node->name; + const char *clk_parent; + void *reg; + + reg = of_iomap(node, 0); + + clk_parent = of_clk_get_parent_name(node, 0); + + clk = clk_register_divider(NULL, clk_name, clk_parent, 0, + reg, data->shift, SUNXI_DIVISOR_WIDTH, + data->pow ? CLK_DIVIDER_POWER_OF_TWO : 0, + &clk_lock); + if (clk) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } +} + + +/* Matches for of_clk_init */ +static const __initconst struct of_device_id clk_match[] = { + {.compatible = "fixed-clock", .data = of_fixed_clk_setup,}, + {.compatible = "allwinner,sunxi-osc-clk", .data = sunxi_osc_clk_setup,}, + {} +}; + +/* Matches for factors clocks */ +static const __initconst struct of_device_id clk_factors_match[] = { + {.compatible = "allwinner,sunxi-pll1-clk", .data = &pll1_data,}, + {.compatible = "allwinner,sunxi-apb1-clk", .data = &apb1_data,}, + {} +}; + +/* Matches for divider clocks */ +static const __initconst struct of_device_id clk_div_match[] = { + {.compatible = "allwinner,sunxi-axi-clk", .data = &axi_data,}, + {.compatible = "allwinner,sunxi-ahb-clk", .data = &ahb_data,}, + {.compatible = "allwinner,sunxi-apb0-clk", .data = &apb0_data,}, + {} +}; + +/* Matches for mux clocks */ +static const __initconst struct of_device_id clk_mux_match[] = { + {.compatible = "allwinner,sunxi-cpu-clk", .data = &cpu_data,}, + {.compatible = "allwinner,sunxi-apb1-mux-clk", .data = &apb1_mux_data,}, + {} +}; + +static void __init of_sunxi_table_clock_setup(const struct of_device_id *clk_match, + void *function) +{ + struct device_node *np; + const struct div_data *data; + const struct of_device_id *match; + void (*setup_function)(struct device_node *, const void *) = function; + + for_each_matching_node(np, clk_match) { + match = of_match_node(clk_match, np); + data = match->data; + setup_function(np, data); + } +} + +void __init sunxi_init_clocks(void) +{ + /* Register all the simple sunxi clocks on DT */ + of_clk_init(clk_match); + + /* Register factor clocks */ + of_sunxi_table_clock_setup(clk_factors_match, sunxi_factors_clk_setup); + + /* Register divider clocks */ + of_sunxi_table_clock_setup(clk_div_match, sunxi_divider_clk_setup); + + /* Register mux clocks */ + of_sunxi_table_clock_setup(clk_mux_match, sunxi_mux_clk_setup); +} diff --git a/drivers/clocksource/sunxi_timer.c b/drivers/clocksource/sunxi_timer.c index 4086b9167159..0ce85e29769b 100644 --- a/drivers/clocksource/sunxi_timer.c +++ b/drivers/clocksource/sunxi_timer.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #define TIMER_CTL_REG 0x00 #define TIMER_CTL_ENABLE (1 << 0) @@ -123,7 +123,7 @@ void __init sunxi_timer_init(void) if (irq <= 0) panic("Can't parse IRQ"); - of_clk_init(NULL); + sunxi_init_clocks(); clk = of_clk_get(node, 0); if (IS_ERR(clk)) diff --git a/include/linux/clk/sunxi.h b/include/linux/clk/sunxi.h new file mode 100644 index 000000000000..e074fdd5a236 --- /dev/null +++ b/include/linux/clk/sunxi.h @@ -0,0 +1,22 @@ +/* + * Copyright 2012 Maxime Ripard + * + * Maxime Ripard + * + * 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. + */ + +#ifndef __LINUX_CLK_SUNXI_H_ +#define __LINUX_CLK_SUNXI_H_ + +void __init sunxi_init_clocks(void); + +#endif -- cgit v1.2.3-71-gd317 From 3566d40c1a4617461b38c82059bdc41d622faa8b Mon Sep 17 00:00:00 2001 From: James Hogan Date: Mon, 25 Mar 2013 14:35:07 +0000 Subject: clk: fix clk_mux::flags kerneldoc The kerneldoc comment for struct clk_mux documented the non-existent num_clks instead of flags. Correct this. Signed-off-by: James Hogan Signed-off-by: Mike Turquette --- include/linux/clk-provider.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 1f0352802794..b1675074fe7c 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -284,7 +284,7 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name, * @reg: register controlling multiplexer * @shift: shift to multiplexer bit field * @width: width of mutliplexer bit field - * @num_clks: number of parent clocks + * @flags: hardware-specific flags * @lock: register lock * * Clock with multiple selectable parents. Implements .get_parent, .set_parent -- cgit v1.2.3-71-gd317 From 056b205316cc3dcf8a67cf813a26ff8a72bf3cb9 Mon Sep 17 00:00:00 2001 From: Soren Brinkmann Date: Tue, 2 Apr 2013 15:36:56 -0700 Subject: clk: divider: Introduce CLK_DIVIDER_ALLOW_ZERO flag Dividers which have CLK_DIVIDER_ONE_BASED set have a redundant state, being a divider value of zero. Some hardware implementations allow a zero divider which simply doesn't alter the frequency. I.e. it acts like a divide by one or bypassing the divider. This flag is used to handle such HW in the clk-divider model. Signed-off-by: Soren Brinkmann Signed-off-by: Mike Turquette --- drivers/clk/clk-divider.c | 5 +++-- include/linux/clk-provider.h | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index 68b402101170..6d9674160430 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -109,8 +109,9 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw, div = _get_div(divider, val); if (!div) { - WARN(1, "%s: Invalid divisor for clock %s\n", __func__, - __clk_get_name(hw->clk)); + WARN(!(divider->flags & CLK_DIVIDER_ALLOW_ZERO), + "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n", + __clk_get_name(hw->clk)); return parent_rate; } diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index b1675074fe7c..9fdfae74d669 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -249,9 +249,14 @@ struct clk_div_table { * CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the * register plus one. If CLK_DIVIDER_ONE_BASED is set then the divider is * the raw value read from the register, with the value of zero considered - * invalid + * invalid, unless CLK_DIVIDER_ALLOW_ZERO is set. * CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from * the hardware register + * CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors. For dividers which have + * CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor. + * Some hardware implementations gracefully handle this case and allow a + * zero divisor by not modifying their input clock + * (divide by one / bypass). */ struct clk_divider { struct clk_hw hw; @@ -265,6 +270,7 @@ struct clk_divider { #define CLK_DIVIDER_ONE_BASED BIT(0) #define CLK_DIVIDER_POWER_OF_TWO BIT(1) +#define CLK_DIVIDER_ALLOW_ZERO BIT(2) extern const struct clk_ops clk_divider_ops; struct clk *clk_register_divider(struct device *dev, const char *name, -- cgit v1.2.3-71-gd317 From fb72a0590b770f7da6a02bde6b8a147a3d9f6168 Mon Sep 17 00:00:00 2001 From: Soren Brinkmann Date: Wed, 3 Apr 2013 12:17:12 -0700 Subject: clk: Properly handle notifier return values Notifiers may return NOTIFY_(OK|DONE|STOP|BAD). The CCF uses an inconsistent mix of checking against NOTIFY_STOP or NOTIFY_BAD. This inconsistency leaves errors undetected in some cases: clk_set_parent() calls __clk_speculate_rates(), which stops when it hits a NOTIFIER_BAD (STOP is ignored), and passes this value back to the caller. clk_set_parent() compares this return value against NOTIFY_STOP only, ignoring NOTIFY_BAD returns. Use NOTIFY_STOP_MASK to detect a negative notifier return value and document all four return value options. Signed-off-by: Soren Brinkmann Signed-off-by: Mike Turquette --- drivers/clk/clk.c | 10 +++++----- include/linux/clk.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index de6b459de78e..d65ef178e6eb 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -1027,16 +1027,16 @@ static int __clk_speculate_rates(struct clk *clk, unsigned long parent_rate) else new_rate = parent_rate; - /* abort the rate change if a driver returns NOTIFY_BAD */ + /* abort rate change if a driver returns NOTIFY_BAD or NOTIFY_STOP */ if (clk->notifier_count) ret = __clk_notify(clk, PRE_RATE_CHANGE, clk->rate, new_rate); - if (ret == NOTIFY_BAD) + if (ret & NOTIFY_STOP_MASK) goto out; hlist_for_each_entry(child, &clk->children, child_node) { ret = __clk_speculate_rates(child, new_rate); - if (ret == NOTIFY_BAD) + if (ret & NOTIFY_STOP_MASK) break; } @@ -1129,7 +1129,7 @@ static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long even if (clk->notifier_count) { ret = __clk_notify(clk, event, clk->rate, clk->new_rate); - if (ret == NOTIFY_BAD) + if (ret & NOTIFY_STOP_MASK) fail_clk = clk; } @@ -1484,7 +1484,7 @@ int clk_set_parent(struct clk *clk, struct clk *parent) ret = __clk_speculate_rates(clk, p_rate); /* abort if a driver objects */ - if (ret == NOTIFY_STOP) + if (ret & NOTIFY_STOP_MASK) goto out; /* do the re-parent */ diff --git a/include/linux/clk.h b/include/linux/clk.h index b3ac22d0fc1f..9a6d04524b1a 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -28,16 +28,16 @@ struct clk; * PRE_RATE_CHANGE - called immediately before the clk rate is changed, * to indicate that the rate change will proceed. Drivers must * immediately terminate any operations that will be affected by the - * rate change. Callbacks may either return NOTIFY_DONE or - * NOTIFY_STOP. + * rate change. Callbacks may either return NOTIFY_DONE, NOTIFY_OK, + * NOTIFY_STOP or NOTIFY_BAD. * * ABORT_RATE_CHANGE: called if the rate change failed for some reason * after PRE_RATE_CHANGE. In this case, all registered notifiers on * the clk will be called with ABORT_RATE_CHANGE. Callbacks must - * always return NOTIFY_DONE. + * always return NOTIFY_DONE or NOTIFY_OK. * * POST_RATE_CHANGE - called after the clk rate change has successfully - * completed. Callbacks must always return NOTIFY_DONE. + * completed. Callbacks must always return NOTIFY_DONE or NOTIFY_OK. * */ #define PRE_RATE_CHANGE BIT(0) -- cgit v1.2.3-71-gd317 From 79b16641efabd14944dbfc2fde2ae1e8ae8413bc Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Fri, 12 Apr 2013 13:57:44 +0200 Subject: clk: add device tree fixed-factor-clock binding support Add support for DT "fixed-factor-clock" binding to the common fixed factor clock support. Signed-off-by: Gregory CLEMENT Tested-by: Christian Ruppert Signed-off-by: Mike Turquette --- .../bindings/clock/fixed-factor-clock.txt | 24 +++++++++++++++ drivers/clk/clk-fixed-factor.c | 36 ++++++++++++++++++++++ include/linux/clk-provider.h | 2 ++ 3 files changed, 62 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/fixed-factor-clock.txt (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/clock/fixed-factor-clock.txt b/Documentation/devicetree/bindings/clock/fixed-factor-clock.txt new file mode 100644 index 000000000000..5757f9abfc26 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/fixed-factor-clock.txt @@ -0,0 +1,24 @@ +Binding for simple fixed factor rate clock sources. + +This binding uses the common clock binding[1]. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt + +Required properties: +- compatible : shall be "fixed-factor-clock". +- #clock-cells : from common clock binding; shall be set to 0. +- clock-div: fixed divider. +- clock-mult: fixed multiplier. +- clocks: parent clock. + +Optional properties: +- clock-output-names : From common clock binding. + +Example: + clock { + compatible = "fixed-factor-clock"; + clocks = <&parentclk>; + #clock-cells = <0>; + div = <2>; + mult = <1>; + }; diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c index 1ef271e47594..9ff7d510faa3 100644 --- a/drivers/clk/clk-fixed-factor.c +++ b/drivers/clk/clk-fixed-factor.c @@ -11,6 +11,7 @@ #include #include #include +#include /* * DOC: basic fixed multiplier and divider clock that cannot gate @@ -96,3 +97,38 @@ struct clk *clk_register_fixed_factor(struct device *dev, const char *name, return clk; } +#ifdef CONFIG_OF +/** + * of_fixed_factor_clk_setup() - Setup function for simple fixed factor clock + */ +void __init of_fixed_factor_clk_setup(struct device_node *node) +{ + struct clk *clk; + const char *clk_name = node->name; + const char *parent_name; + u32 div, mult; + + if (of_property_read_u32(node, "clock-div", &div)) { + pr_err("%s Fixed factor clock <%s> must have a clock-div property\n", + __func__, node->name); + return; + } + + if (of_property_read_u32(node, "clock-mult", &mult)) { + pr_err("%s Fixed factor clock <%s> must have a clokc-mult property\n", + __func__, node->name); + return; + } + + of_property_read_string(node, "clock-output-names", &clk_name); + parent_name = of_clk_get_parent_name(node, 0); + + clk = clk_register_fixed_factor(NULL, clk_name, parent_name, 0, + mult, div); + if (!IS_ERR(clk)) + of_clk_add_provider(node, of_clk_src_simple_get, clk); +} +EXPORT_SYMBOL_GPL(of_fixed_factor_clk_setup); +CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock", + of_fixed_factor_clk_setup); +#endif diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 9fdfae74d669..e7b7cbc53815 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -325,6 +325,8 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, void __iomem *reg, u8 shift, u32 mask, u8 clk_mux_flags, u32 *table, spinlock_t *lock); +void of_fixed_factor_clk_setup(struct device_node *node); + /** * struct clk_fixed_factor - fixed multiplier and divider clock * -- cgit v1.2.3-71-gd317 From 9abd5f0555df6cd36130feb742f1def6d99c60fe Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Thu, 11 Apr 2013 21:42:29 +0200 Subject: clk: add si5351 i2c common clock driver This patch adds a common clock driver for Silicon Labs Si5351a/b/c i2c programmable clock generators. Currently, the driver does not support VXCO feature of si5351b. Passing platform_data or DT bindings selectively allows to overwrite stored Si5351 configuration which is very helpful for clock generators with empty eeprom configuration. Corresponding device tree binding documentation is also added. Signed-off-by: Sebastian Hesselbarth Tested-by: Daniel Mack Acked-by: Guenter Roeck Tested-by: Michal Bachraty Signed-off-by: Mike Turquette --- .../devicetree/bindings/clock/silabs,si5351.txt | 114 ++ .../devicetree/bindings/vendor-prefixes.txt | 1 + drivers/clk/Kconfig | 9 + drivers/clk/Makefile | 1 + drivers/clk/clk-si5351.c | 1510 ++++++++++++++++++++ drivers/clk/clk-si5351.h | 155 ++ include/linux/platform_data/si5351.h | 114 ++ 7 files changed, 1904 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/silabs,si5351.txt create mode 100644 drivers/clk/clk-si5351.c create mode 100644 drivers/clk/clk-si5351.h create mode 100644 include/linux/platform_data/si5351.h (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/clock/silabs,si5351.txt b/Documentation/devicetree/bindings/clock/silabs,si5351.txt new file mode 100644 index 000000000000..cc374651662c --- /dev/null +++ b/Documentation/devicetree/bindings/clock/silabs,si5351.txt @@ -0,0 +1,114 @@ +Binding for Silicon Labs Si5351a/b/c programmable i2c clock generator. + +Reference +[1] Si5351A/B/C Data Sheet + http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf + +The Si5351a/b/c are programmable i2c clock generators with upto 8 output +clocks. Si5351a also has a reduced pin-count package (MSOP10) where only +3 output clocks are accessible. The internal structure of the clock +generators can be found in [1]. + +==I2C device node== + +Required properties: +- compatible: shall be one of "silabs,si5351{a,a-msop,b,c}". +- reg: i2c device address, shall be 0x60 or 0x61. +- #clock-cells: from common clock binding; shall be set to 1. +- clocks: from common clock binding; list of parent clock + handles, shall be xtal reference clock or xtal and clkin for + si5351c only. +- #address-cells: shall be set to 1. +- #size-cells: shall be set to 0. + +Optional properties: +- silabs,pll-source: pair of (number, source) for each pll. Allows + to overwrite clock source of pll A (number=0) or B (number=1). + +==Child nodes== + +Each of the clock outputs can be overwritten individually by +using a child node to the I2C device node. If a child node for a clock +output is not set, the eeprom configuration is not overwritten. + +Required child node properties: +- reg: number of clock output. + +Optional child node properties: +- silabs,clock-source: source clock of the output divider stage N, shall be + 0 = multisynth N + 1 = multisynth 0 for output clocks 0-3, else multisynth4 + 2 = xtal + 3 = clkin (si5351c only) +- silabs,drive-strength: output drive strength in mA, shall be one of {2,4,6,8}. +- silabs,multisynth-source: source pll A(0) or B(1) of corresponding multisynth + divider. +- silabs,pll-master: boolean, multisynth can change pll frequency. + +==Example== + +/* 25MHz reference crystal */ +ref25: ref25M { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; +}; + +i2c-master-node { + + /* Si5351a msop10 i2c clock generator */ + si5351a: clock-generator@60 { + compatible = "silabs,si5351a-msop"; + reg = <0x60>; + #address-cells = <1>; + #size-cells = <0>; + #clock-cells = <1>; + + /* connect xtal input to 25MHz reference */ + clocks = <&ref25>; + + /* connect xtal input as source of pll0 and pll1 */ + silabs,pll-source = <0 0>, <1 0>; + + /* + * overwrite clkout0 configuration with: + * - 8mA output drive strength + * - pll0 as clock source of multisynth0 + * - multisynth0 as clock source of output divider + * - multisynth0 can change pll0 + * - set initial clock frequency of 74.25MHz + */ + clkout0 { + reg = <0>; + silabs,drive-strength = <8>; + silabs,multisynth-source = <0>; + silabs,clock-source = <0>; + silabs,pll-master; + clock-frequency = <74250000>; + }; + + /* + * overwrite clkout1 configuration with: + * - 4mA output drive strength + * - pll1 as clock source of multisynth1 + * - multisynth1 as clock source of output divider + * - multisynth1 can change pll1 + */ + clkout1 { + reg = <1>; + silabs,drive-strength = <4>; + silabs,multisynth-source = <1>; + silabs,clock-source = <0>; + pll-master; + }; + + /* + * overwrite clkout2 configuration with: + * - xtal as clock source of output divider + */ + clkout2 { + reg = <2>; + silabs,clock-source = <2>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 19e1ef73ab0d..ca608496507b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -48,6 +48,7 @@ samsung Samsung Semiconductor sbs Smart Battery System schindler Schindler sil Silicon Image +silabs Silicon Laboratories simtek sirf SiRF Technology, Inc. snps Synopsys, Inc. diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index a64caefdba12..186dd0edca8f 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -55,6 +55,15 @@ config COMMON_CLK_MAX77686 ---help--- This driver supports Maxim 77686 crystal oscillator clock. +config COMMON_CLK_SI5351 + tristate "Clock driver for SiLabs 5351A/B/C" + depends on I2C + select REGMAP_I2C + select RATIONAL + ---help--- + This driver supports Silicon Labs 5351A/B/C programmable clock + generators. + config CLK_TWL6040 tristate "External McPDM functional clock from twl6040" depends on TWL6040_CORE diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 79e98e416724..e7f7fe9b2f09 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -36,4 +36,5 @@ obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o +obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o diff --git a/drivers/clk/clk-si5351.c b/drivers/clk/clk-si5351.c new file mode 100644 index 000000000000..892728412e9d --- /dev/null +++ b/drivers/clk/clk-si5351.c @@ -0,0 +1,1510 @@ +/* + * clk-si5351.c: Silicon Laboratories Si5351A/B/C I2C Clock Generator + * + * Sebastian Hesselbarth + * Rabeeh Khoury + * + * References: + * [1] "Si5351A/B/C Data Sheet" + * http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf + * [2] "Manually Generating an Si5351 Register Map" + * http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-si5351.h" + +struct si5351_driver_data; + +struct si5351_parameters { + unsigned long p1; + unsigned long p2; + unsigned long p3; + int valid; +}; + +struct si5351_hw_data { + struct clk_hw hw; + struct si5351_driver_data *drvdata; + struct si5351_parameters params; + unsigned char num; +}; + +struct si5351_driver_data { + enum si5351_variant variant; + struct i2c_client *client; + struct regmap *regmap; + struct clk_onecell_data onecell; + + struct clk *pxtal; + const char *pxtal_name; + struct clk_hw xtal; + struct clk *pclkin; + const char *pclkin_name; + struct clk_hw clkin; + + struct si5351_hw_data pll[2]; + struct si5351_hw_data *msynth; + struct si5351_hw_data *clkout; +}; + +static const char const *si5351_input_names[] = { + "xtal", "clkin" +}; +static const char const *si5351_pll_names[] = { + "plla", "pllb", "vxco" +}; +static const char const *si5351_msynth_names[] = { + "ms0", "ms1", "ms2", "ms3", "ms4", "ms5", "ms6", "ms7" +}; +static const char const *si5351_clkout_names[] = { + "clk0", "clk1", "clk2", "clk3", "clk4", "clk5", "clk6", "clk7" +}; + +/* + * Si5351 i2c regmap + */ +static inline u8 si5351_reg_read(struct si5351_driver_data *drvdata, u8 reg) +{ + u32 val; + int ret; + + ret = regmap_read(drvdata->regmap, reg, &val); + if (ret) { + dev_err(&drvdata->client->dev, + "unable to read from reg%02x\n", reg); + return 0; + } + + return (u8)val; +} + +static inline int si5351_bulk_read(struct si5351_driver_data *drvdata, + u8 reg, u8 count, u8 *buf) +{ + return regmap_bulk_read(drvdata->regmap, reg, buf, count); +} + +static inline int si5351_reg_write(struct si5351_driver_data *drvdata, + u8 reg, u8 val) +{ + return regmap_write(drvdata->regmap, reg, val); +} + +static inline int si5351_bulk_write(struct si5351_driver_data *drvdata, + u8 reg, u8 count, const u8 *buf) +{ + return regmap_raw_write(drvdata->regmap, reg, buf, count); +} + +static inline int si5351_set_bits(struct si5351_driver_data *drvdata, + u8 reg, u8 mask, u8 val) +{ + return regmap_update_bits(drvdata->regmap, reg, mask, val); +} + +static inline u8 si5351_msynth_params_address(int num) +{ + if (num > 5) + return SI5351_CLK6_PARAMETERS + (num - 6); + return SI5351_CLK0_PARAMETERS + (SI5351_PARAMETERS_LENGTH * num); +} + +static void si5351_read_parameters(struct si5351_driver_data *drvdata, + u8 reg, struct si5351_parameters *params) +{ + u8 buf[SI5351_PARAMETERS_LENGTH]; + + switch (reg) { + case SI5351_CLK6_PARAMETERS: + case SI5351_CLK7_PARAMETERS: + buf[0] = si5351_reg_read(drvdata, reg); + params->p1 = buf[0]; + params->p2 = 0; + params->p3 = 1; + break; + default: + si5351_bulk_read(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf); + params->p1 = ((buf[2] & 0x03) << 16) | (buf[3] << 8) | buf[4]; + params->p2 = ((buf[5] & 0x0f) << 16) | (buf[6] << 8) | buf[7]; + params->p3 = ((buf[5] & 0xf0) << 12) | (buf[0] << 8) | buf[1]; + } + params->valid = 1; +} + +static void si5351_write_parameters(struct si5351_driver_data *drvdata, + u8 reg, struct si5351_parameters *params) +{ + u8 buf[SI5351_PARAMETERS_LENGTH]; + + switch (reg) { + case SI5351_CLK6_PARAMETERS: + case SI5351_CLK7_PARAMETERS: + buf[0] = params->p1 & 0xff; + si5351_reg_write(drvdata, reg, buf[0]); + break; + default: + buf[0] = ((params->p3 & 0x0ff00) >> 8) & 0xff; + buf[1] = params->p3 & 0xff; + /* save rdiv and divby4 */ + buf[2] = si5351_reg_read(drvdata, reg + 2) & ~0x03; + buf[2] |= ((params->p1 & 0x30000) >> 16) & 0x03; + buf[3] = ((params->p1 & 0x0ff00) >> 8) & 0xff; + buf[4] = params->p1 & 0xff; + buf[5] = ((params->p3 & 0xf0000) >> 12) | + ((params->p2 & 0xf0000) >> 16); + buf[6] = ((params->p2 & 0x0ff00) >> 8) & 0xff; + buf[7] = params->p2 & 0xff; + si5351_bulk_write(drvdata, reg, SI5351_PARAMETERS_LENGTH, buf); + } +} + +static bool si5351_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SI5351_DEVICE_STATUS: + case SI5351_INTERRUPT_STATUS: + case SI5351_PLL_RESET: + return true; + } + return false; +} + +static bool si5351_regmap_is_writeable(struct device *dev, unsigned int reg) +{ + /* reserved registers */ + if (reg >= 4 && reg <= 8) + return false; + if (reg >= 10 && reg <= 14) + return false; + if (reg >= 173 && reg <= 176) + return false; + if (reg >= 178 && reg <= 182) + return false; + /* read-only */ + if (reg == SI5351_DEVICE_STATUS) + return false; + return true; +} + +static struct regmap_config si5351_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .max_register = 187, + .writeable_reg = si5351_regmap_is_writeable, + .volatile_reg = si5351_regmap_is_volatile, +}; + +/* + * Si5351 xtal clock input + */ +static int si5351_xtal_prepare(struct clk_hw *hw) +{ + struct si5351_driver_data *drvdata = + container_of(hw, struct si5351_driver_data, xtal); + si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE, + SI5351_XTAL_ENABLE, SI5351_XTAL_ENABLE); + return 0; +} + +static void si5351_xtal_unprepare(struct clk_hw *hw) +{ + struct si5351_driver_data *drvdata = + container_of(hw, struct si5351_driver_data, xtal); + si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE, + SI5351_XTAL_ENABLE, 0); +} + +static const struct clk_ops si5351_xtal_ops = { + .prepare = si5351_xtal_prepare, + .unprepare = si5351_xtal_unprepare, +}; + +/* + * Si5351 clkin clock input (Si5351C only) + */ +static int si5351_clkin_prepare(struct clk_hw *hw) +{ + struct si5351_driver_data *drvdata = + container_of(hw, struct si5351_driver_data, clkin); + si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE, + SI5351_CLKIN_ENABLE, SI5351_CLKIN_ENABLE); + return 0; +} + +static void si5351_clkin_unprepare(struct clk_hw *hw) +{ + struct si5351_driver_data *drvdata = + container_of(hw, struct si5351_driver_data, clkin); + si5351_set_bits(drvdata, SI5351_FANOUT_ENABLE, + SI5351_CLKIN_ENABLE, 0); +} + +/* + * CMOS clock source constraints: + * The input frequency range of the PLL is 10Mhz to 40MHz. + * If CLKIN is >40MHz, the input divider must be used. + */ +static unsigned long si5351_clkin_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct si5351_driver_data *drvdata = + container_of(hw, struct si5351_driver_data, clkin); + unsigned long rate; + unsigned char idiv; + + rate = parent_rate; + if (parent_rate > 160000000) { + idiv = SI5351_CLKIN_DIV_8; + rate /= 8; + } else if (parent_rate > 80000000) { + idiv = SI5351_CLKIN_DIV_4; + rate /= 4; + } else if (parent_rate > 40000000) { + idiv = SI5351_CLKIN_DIV_2; + rate /= 2; + } else { + idiv = SI5351_CLKIN_DIV_1; + } + + si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, + SI5351_CLKIN_DIV_MASK, idiv); + + dev_dbg(&drvdata->client->dev, "%s - clkin div = %d, rate = %lu\n", + __func__, (1 << (idiv >> 6)), rate); + + return rate; +} + +static const struct clk_ops si5351_clkin_ops = { + .prepare = si5351_clkin_prepare, + .unprepare = si5351_clkin_unprepare, + .recalc_rate = si5351_clkin_recalc_rate, +}; + +/* + * Si5351 vxco clock input (Si5351B only) + */ + +static int si5351_vxco_prepare(struct clk_hw *hw) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + + dev_warn(&hwdata->drvdata->client->dev, "VXCO currently unsupported\n"); + + return 0; +} + +static void si5351_vxco_unprepare(struct clk_hw *hw) +{ +} + +static unsigned long si5351_vxco_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 0; +} + +static int si5351_vxco_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent) +{ + return 0; +} + +static const struct clk_ops si5351_vxco_ops = { + .prepare = si5351_vxco_prepare, + .unprepare = si5351_vxco_unprepare, + .recalc_rate = si5351_vxco_recalc_rate, + .set_rate = si5351_vxco_set_rate, +}; + +/* + * Si5351 pll a/b + * + * Feedback Multisynth Divider Equations [2] + * + * fVCO = fIN * (a + b/c) + * + * with 15 + 0/1048575 <= (a + b/c) <= 90 + 0/1048575 and + * fIN = fXTAL or fIN = fCLKIN/CLKIN_DIV + * + * Feedback Multisynth Register Equations + * + * (1) MSNx_P1[17:0] = 128 * a + floor(128 * b/c) - 512 + * (2) MSNx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c + * (3) MSNx_P3[19:0] = c + * + * Transposing (2) yields: (4) floor(128 * b/c) = (128 * b / MSNx_P2)/c + * + * Using (4) on (1) yields: + * MSNx_P1 = 128 * a + (128 * b/MSNx_P2)/c - 512 + * MSNx_P1 + 512 + MSNx_P2/c = 128 * a + 128 * b/c + * + * a + b/c = (MSNx_P1 + MSNx_P2/MSNx_P3 + 512)/128 + * = (MSNx_P1*MSNx_P3 + MSNx_P2 + 512*MSNx_P3)/(128*MSNx_P3) + * + */ +static int _si5351_pll_reparent(struct si5351_driver_data *drvdata, + int num, enum si5351_pll_src parent) +{ + u8 mask = (num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE; + + if (parent == SI5351_PLL_SRC_DEFAULT) + return 0; + + if (num > 2) + return -EINVAL; + + if (drvdata->variant != SI5351_VARIANT_C && + parent != SI5351_PLL_SRC_XTAL) + return -EINVAL; + + si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, mask, + (parent == SI5351_PLL_SRC_XTAL) ? 0 : mask); + return 0; +} + +static unsigned char si5351_pll_get_parent(struct clk_hw *hw) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + u8 mask = (hwdata->num == 0) ? SI5351_PLLA_SOURCE : SI5351_PLLB_SOURCE; + u8 val; + + val = si5351_reg_read(hwdata->drvdata, SI5351_PLL_INPUT_SOURCE); + + return (val & mask) ? 1 : 0; +} + +static int si5351_pll_set_parent(struct clk_hw *hw, u8 index) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + + if (hwdata->drvdata->variant != SI5351_VARIANT_C && + index > 0) + return -EPERM; + + if (index > 1) + return -EINVAL; + + return _si5351_pll_reparent(hwdata->drvdata, hwdata->num, + (index == 0) ? SI5351_PLL_SRC_XTAL : + SI5351_PLL_SRC_CLKIN); +} + +static unsigned long si5351_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS : + SI5351_PLLB_PARAMETERS; + unsigned long long rate; + + if (!hwdata->params.valid) + si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params); + + if (hwdata->params.p3 == 0) + return parent_rate; + + /* fVCO = fIN * (P1*P3 + 512*P3 + P2)/(128*P3) */ + rate = hwdata->params.p1 * hwdata->params.p3; + rate += 512 * hwdata->params.p3; + rate += hwdata->params.p2; + rate *= parent_rate; + do_div(rate, 128 * hwdata->params.p3); + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), + hwdata->params.p1, hwdata->params.p2, hwdata->params.p3, + parent_rate, (unsigned long)rate); + + return (unsigned long)rate; +} + +static long si5351_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + unsigned long rfrac, denom, a, b, c; + unsigned long long lltmp; + + if (rate < SI5351_PLL_VCO_MIN) + rate = SI5351_PLL_VCO_MIN; + if (rate > SI5351_PLL_VCO_MAX) + rate = SI5351_PLL_VCO_MAX; + + /* determine integer part of feedback equation */ + a = rate / *parent_rate; + + if (a < SI5351_PLL_A_MIN) + rate = *parent_rate * SI5351_PLL_A_MIN; + if (a > SI5351_PLL_A_MAX) + rate = *parent_rate * SI5351_PLL_A_MAX; + + /* find best approximation for b/c = fVCO mod fIN */ + denom = 1000 * 1000; + lltmp = rate % (*parent_rate); + lltmp *= denom; + do_div(lltmp, *parent_rate); + rfrac = (unsigned long)lltmp; + + b = 0; + c = 1; + if (rfrac) + rational_best_approximation(rfrac, denom, + SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c); + + /* calculate parameters */ + hwdata->params.p3 = c; + hwdata->params.p2 = (128 * b) % c; + hwdata->params.p1 = 128 * a; + hwdata->params.p1 += (128 * b / c); + hwdata->params.p1 -= 512; + + /* recalculate rate by fIN * (a + b/c) */ + lltmp = *parent_rate; + lltmp *= b; + do_div(lltmp, c); + + rate = (unsigned long)lltmp; + rate += *parent_rate * a; + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: a = %lu, b = %lu, c = %lu, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), a, b, c, + *parent_rate, rate); + + return rate; +} + +static int si5351_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + u8 reg = (hwdata->num == 0) ? SI5351_PLLA_PARAMETERS : + SI5351_PLLB_PARAMETERS; + + /* write multisynth parameters */ + si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params); + + /* plla/pllb ctrl is in clk6/clk7 ctrl registers */ + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_CTRL + hwdata->num, + SI5351_CLK_INTEGER_MODE, + (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0); + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), + hwdata->params.p1, hwdata->params.p2, hwdata->params.p3, + parent_rate, rate); + + return 0; +} + +static const struct clk_ops si5351_pll_ops = { + .set_parent = si5351_pll_set_parent, + .get_parent = si5351_pll_get_parent, + .recalc_rate = si5351_pll_recalc_rate, + .round_rate = si5351_pll_round_rate, + .set_rate = si5351_pll_set_rate, +}; + +/* + * Si5351 multisync divider + * + * for fOUT <= 150 MHz: + * + * fOUT = (fIN * (a + b/c)) / CLKOUTDIV + * + * with 6 + 0/1048575 <= (a + b/c) <= 1800 + 0/1048575 and + * fIN = fVCO0, fVCO1 + * + * Output Clock Multisynth Register Equations + * + * MSx_P1[17:0] = 128 * a + floor(128 * b/c) - 512 + * MSx_P2[19:0] = 128 * b - c * floor(128 * b/c) = (128*b) mod c + * MSx_P3[19:0] = c + * + * MS[6,7] are integer (P1) divide only, P2 = 0, P3 = 0 + * + * for 150MHz < fOUT <= 160MHz: + * + * MSx_P1 = 0, MSx_P2 = 0, MSx_P3 = 1, MSx_INT = 1, MSx_DIVBY4 = 11b + */ +static int _si5351_msynth_reparent(struct si5351_driver_data *drvdata, + int num, enum si5351_multisynth_src parent) +{ + if (parent == SI5351_MULTISYNTH_SRC_DEFAULT) + return 0; + + if (num > 8) + return -EINVAL; + + si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, SI5351_CLK_PLL_SELECT, + (parent == SI5351_MULTISYNTH_SRC_VCO0) ? 0 : + SI5351_CLK_PLL_SELECT); + return 0; +} + +static unsigned char si5351_msynth_get_parent(struct clk_hw *hw) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + u8 val; + + val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num); + + return (val & SI5351_CLK_PLL_SELECT) ? 1 : 0; +} + +static int si5351_msynth_set_parent(struct clk_hw *hw, u8 index) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + + return _si5351_msynth_reparent(hwdata->drvdata, hwdata->num, + (index == 0) ? SI5351_MULTISYNTH_SRC_VCO0 : + SI5351_MULTISYNTH_SRC_VCO1); +} + +static unsigned long si5351_msynth_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + u8 reg = si5351_msynth_params_address(hwdata->num); + unsigned long long rate; + unsigned long m; + + if (!hwdata->params.valid) + si5351_read_parameters(hwdata->drvdata, reg, &hwdata->params); + + if (hwdata->params.p3 == 0) + return parent_rate; + + /* + * multisync0-5: fOUT = (128 * P3 * fIN) / (P1*P3 + P2 + 512*P3) + * multisync6-7: fOUT = fIN / P1 + */ + rate = parent_rate; + if (hwdata->num > 5) { + m = hwdata->params.p1; + } else if ((si5351_reg_read(hwdata->drvdata, reg + 2) & + SI5351_OUTPUT_CLK_DIVBY4) == SI5351_OUTPUT_CLK_DIVBY4) { + m = 4; + } else { + rate *= 128 * hwdata->params.p3; + m = hwdata->params.p1 * hwdata->params.p3; + m += hwdata->params.p2; + m += 512 * hwdata->params.p3; + } + + if (m == 0) + return 0; + do_div(rate, m); + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, m = %lu, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), + hwdata->params.p1, hwdata->params.p2, hwdata->params.p3, + m, parent_rate, (unsigned long)rate); + + return (unsigned long)rate; +} + +static long si5351_msynth_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + unsigned long long lltmp; + unsigned long a, b, c; + int divby4; + + /* multisync6-7 can only handle freqencies < 150MHz */ + if (hwdata->num >= 6 && rate > SI5351_MULTISYNTH67_MAX_FREQ) + rate = SI5351_MULTISYNTH67_MAX_FREQ; + + /* multisync frequency is 1MHz .. 160MHz */ + if (rate > SI5351_MULTISYNTH_MAX_FREQ) + rate = SI5351_MULTISYNTH_MAX_FREQ; + if (rate < SI5351_MULTISYNTH_MIN_FREQ) + rate = SI5351_MULTISYNTH_MIN_FREQ; + + divby4 = 0; + if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ) + divby4 = 1; + + /* multisync can set pll */ + if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) { + /* + * find largest integer divider for max + * vco frequency and given target rate + */ + if (divby4 == 0) { + lltmp = SI5351_PLL_VCO_MAX; + do_div(lltmp, rate); + a = (unsigned long)lltmp; + } else + a = 4; + + b = 0; + c = 1; + + *parent_rate = a * rate; + } else { + unsigned long rfrac, denom; + + /* disable divby4 */ + if (divby4) { + rate = SI5351_MULTISYNTH_DIVBY4_FREQ; + divby4 = 0; + } + + /* determine integer part of divider equation */ + a = *parent_rate / rate; + if (a < SI5351_MULTISYNTH_A_MIN) + a = SI5351_MULTISYNTH_A_MIN; + if (hwdata->num >= 6 && a > SI5351_MULTISYNTH67_A_MAX) + a = SI5351_MULTISYNTH67_A_MAX; + else if (a > SI5351_MULTISYNTH_A_MAX) + a = SI5351_MULTISYNTH_A_MAX; + + /* find best approximation for b/c = fVCO mod fOUT */ + denom = 1000 * 1000; + lltmp = (*parent_rate) % rate; + lltmp *= denom; + do_div(lltmp, rate); + rfrac = (unsigned long)lltmp; + + b = 0; + c = 1; + if (rfrac) + rational_best_approximation(rfrac, denom, + SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX, + &b, &c); + } + + /* recalculate rate by fOUT = fIN / (a + b/c) */ + lltmp = *parent_rate; + lltmp *= c; + do_div(lltmp, a * c + b); + rate = (unsigned long)lltmp; + + /* calculate parameters */ + if (divby4) { + hwdata->params.p3 = 1; + hwdata->params.p2 = 0; + hwdata->params.p1 = 0; + } else { + hwdata->params.p3 = c; + hwdata->params.p2 = (128 * b) % c; + hwdata->params.p1 = 128 * a; + hwdata->params.p1 += (128 * b / c); + hwdata->params.p1 -= 512; + } + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: a = %lu, b = %lu, c = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), a, b, c, divby4, + *parent_rate, rate); + + return rate; +} + +static int si5351_msynth_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + u8 reg = si5351_msynth_params_address(hwdata->num); + int divby4 = 0; + + /* write multisynth parameters */ + si5351_write_parameters(hwdata->drvdata, reg, &hwdata->params); + + if (rate > SI5351_MULTISYNTH_DIVBY4_FREQ) + divby4 = 1; + + /* enable/disable integer mode and divby4 on multisynth0-5 */ + if (hwdata->num < 6) { + si5351_set_bits(hwdata->drvdata, reg + 2, + SI5351_OUTPUT_CLK_DIVBY4, + (divby4) ? SI5351_OUTPUT_CLK_DIVBY4 : 0); + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num, + SI5351_CLK_INTEGER_MODE, + (hwdata->params.p2 == 0) ? SI5351_CLK_INTEGER_MODE : 0); + } + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: p1 = %lu, p2 = %lu, p3 = %lu, divby4 = %d, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), + hwdata->params.p1, hwdata->params.p2, hwdata->params.p3, + divby4, parent_rate, rate); + + return 0; +} + +static const struct clk_ops si5351_msynth_ops = { + .set_parent = si5351_msynth_set_parent, + .get_parent = si5351_msynth_get_parent, + .recalc_rate = si5351_msynth_recalc_rate, + .round_rate = si5351_msynth_round_rate, + .set_rate = si5351_msynth_set_rate, +}; + +/* + * Si5351 clkout divider + */ +static int _si5351_clkout_reparent(struct si5351_driver_data *drvdata, + int num, enum si5351_clkout_src parent) +{ + u8 val; + + if (num > 8) + return -EINVAL; + + switch (parent) { + case SI5351_CLKOUT_SRC_MSYNTH_N: + val = SI5351_CLK_INPUT_MULTISYNTH_N; + break; + case SI5351_CLKOUT_SRC_MSYNTH_0_4: + /* clk0/clk4 can only connect to its own multisync */ + if (num == 0 || num == 4) + val = SI5351_CLK_INPUT_MULTISYNTH_N; + else + val = SI5351_CLK_INPUT_MULTISYNTH_0_4; + break; + case SI5351_CLKOUT_SRC_XTAL: + val = SI5351_CLK_INPUT_XTAL; + break; + case SI5351_CLKOUT_SRC_CLKIN: + if (drvdata->variant != SI5351_VARIANT_C) + return -EINVAL; + + val = SI5351_CLK_INPUT_CLKIN; + break; + default: + return 0; + } + + si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, + SI5351_CLK_INPUT_MASK, val); + return 0; +} + +static int _si5351_clkout_set_drive_strength( + struct si5351_driver_data *drvdata, int num, + enum si5351_drive_strength drive) +{ + u8 mask; + + if (num > 8) + return -EINVAL; + + switch (drive) { + case SI5351_DRIVE_2MA: + mask = SI5351_CLK_DRIVE_STRENGTH_2MA; + break; + case SI5351_DRIVE_4MA: + mask = SI5351_CLK_DRIVE_STRENGTH_4MA; + break; + case SI5351_DRIVE_6MA: + mask = SI5351_CLK_DRIVE_STRENGTH_6MA; + break; + case SI5351_DRIVE_8MA: + mask = SI5351_CLK_DRIVE_STRENGTH_8MA; + break; + default: + return 0; + } + + si5351_set_bits(drvdata, SI5351_CLK0_CTRL + num, + SI5351_CLK_DRIVE_STRENGTH_MASK, mask); + return 0; +} + +static int si5351_clkout_prepare(struct clk_hw *hw) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num, + SI5351_CLK_POWERDOWN, 0); + si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL, + (1 << hwdata->num), 0); + return 0; +} + +static void si5351_clkout_unprepare(struct clk_hw *hw) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num, + SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN); + si5351_set_bits(hwdata->drvdata, SI5351_OUTPUT_ENABLE_CTRL, + (1 << hwdata->num), (1 << hwdata->num)); +} + +static u8 si5351_clkout_get_parent(struct clk_hw *hw) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + int index = 0; + unsigned char val; + + val = si5351_reg_read(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num); + switch (val & SI5351_CLK_INPUT_MASK) { + case SI5351_CLK_INPUT_MULTISYNTH_N: + index = 0; + break; + case SI5351_CLK_INPUT_MULTISYNTH_0_4: + index = 1; + break; + case SI5351_CLK_INPUT_XTAL: + index = 2; + break; + case SI5351_CLK_INPUT_CLKIN: + index = 3; + break; + } + + return index; +} + +static int si5351_clkout_set_parent(struct clk_hw *hw, u8 index) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + enum si5351_clkout_src parent = SI5351_CLKOUT_SRC_DEFAULT; + + switch (index) { + case 0: + parent = SI5351_CLKOUT_SRC_MSYNTH_N; + break; + case 1: + parent = SI5351_CLKOUT_SRC_MSYNTH_0_4; + break; + case 2: + parent = SI5351_CLKOUT_SRC_XTAL; + break; + case 3: + parent = SI5351_CLKOUT_SRC_CLKIN; + break; + } + + return _si5351_clkout_reparent(hwdata->drvdata, hwdata->num, parent); +} + +static unsigned long si5351_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + unsigned char reg; + unsigned char rdiv; + + if (hwdata->num > 5) + reg = si5351_msynth_params_address(hwdata->num) + 2; + else + reg = SI5351_CLK6_7_OUTPUT_DIVIDER; + + rdiv = si5351_reg_read(hwdata->drvdata, reg); + if (hwdata->num == 6) { + rdiv &= SI5351_OUTPUT_CLK6_DIV_MASK; + } else { + rdiv &= SI5351_OUTPUT_CLK_DIV_MASK; + rdiv >>= SI5351_OUTPUT_CLK_DIV_SHIFT; + } + + return parent_rate >> rdiv; +} + +static long si5351_clkout_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + unsigned char rdiv; + + /* clkout6/7 can only handle output freqencies < 150MHz */ + if (hwdata->num >= 6 && rate > SI5351_CLKOUT67_MAX_FREQ) + rate = SI5351_CLKOUT67_MAX_FREQ; + + /* clkout freqency is 8kHz - 160MHz */ + if (rate > SI5351_CLKOUT_MAX_FREQ) + rate = SI5351_CLKOUT_MAX_FREQ; + if (rate < SI5351_CLKOUT_MIN_FREQ) + rate = SI5351_CLKOUT_MIN_FREQ; + + /* request frequency if multisync master */ + if (__clk_get_flags(hwdata->hw.clk) & CLK_SET_RATE_PARENT) { + /* use r divider for frequencies below 1MHz */ + rdiv = SI5351_OUTPUT_CLK_DIV_1; + while (rate < SI5351_MULTISYNTH_MIN_FREQ && + rdiv < SI5351_OUTPUT_CLK_DIV_128) { + rdiv += 1; + rate *= 2; + } + *parent_rate = rate; + } else { + unsigned long new_rate, new_err, err; + + /* round to closed rdiv */ + rdiv = SI5351_OUTPUT_CLK_DIV_1; + new_rate = *parent_rate; + err = abs(new_rate - rate); + do { + new_rate >>= 1; + new_err = abs(new_rate - rate); + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128) + break; + rdiv++; + err = new_err; + } while (1); + } + rate = *parent_rate >> rdiv; + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv), + *parent_rate, rate); + + return rate; +} + +static int si5351_clkout_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct si5351_hw_data *hwdata = + container_of(hw, struct si5351_hw_data, hw); + unsigned long new_rate, new_err, err; + unsigned char rdiv; + + /* round to closed rdiv */ + rdiv = SI5351_OUTPUT_CLK_DIV_1; + new_rate = parent_rate; + err = abs(new_rate - rate); + do { + new_rate >>= 1; + new_err = abs(new_rate - rate); + if (new_err > err || rdiv == SI5351_OUTPUT_CLK_DIV_128) + break; + rdiv++; + err = new_err; + } while (1); + + /* write output divider */ + switch (hwdata->num) { + case 6: + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER, + SI5351_OUTPUT_CLK6_DIV_MASK, rdiv); + break; + case 7: + si5351_set_bits(hwdata->drvdata, SI5351_CLK6_7_OUTPUT_DIVIDER, + SI5351_OUTPUT_CLK_DIV_MASK, + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT); + break; + default: + si5351_set_bits(hwdata->drvdata, + si5351_msynth_params_address(hwdata->num) + 2, + SI5351_OUTPUT_CLK_DIV_MASK, + rdiv << SI5351_OUTPUT_CLK_DIV_SHIFT); + } + + /* powerup clkout */ + si5351_set_bits(hwdata->drvdata, SI5351_CLK0_CTRL + hwdata->num, + SI5351_CLK_POWERDOWN, 0); + + dev_dbg(&hwdata->drvdata->client->dev, + "%s - %s: rdiv = %u, parent_rate = %lu, rate = %lu\n", + __func__, __clk_get_name(hwdata->hw.clk), (1 << rdiv), + parent_rate, rate); + + return 0; +} + +static const struct clk_ops si5351_clkout_ops = { + .prepare = si5351_clkout_prepare, + .unprepare = si5351_clkout_unprepare, + .set_parent = si5351_clkout_set_parent, + .get_parent = si5351_clkout_get_parent, + .recalc_rate = si5351_clkout_recalc_rate, + .round_rate = si5351_clkout_round_rate, + .set_rate = si5351_clkout_set_rate, +}; + +/* + * Si5351 i2c probe and DT + */ +#ifdef CONFIG_OF +static const struct of_device_id si5351_dt_ids[] = { + { .compatible = "silabs,si5351a", .data = (void *)SI5351_VARIANT_A, }, + { .compatible = "silabs,si5351a-msop", + .data = (void *)SI5351_VARIANT_A3, }, + { .compatible = "silabs,si5351b", .data = (void *)SI5351_VARIANT_B, }, + { .compatible = "silabs,si5351c", .data = (void *)SI5351_VARIANT_C, }, + { } +}; +MODULE_DEVICE_TABLE(of, si5351_dt_ids); + +static int si5351_dt_parse(struct i2c_client *client) +{ + struct device_node *child, *np = client->dev.of_node; + struct si5351_platform_data *pdata; + const struct of_device_id *match; + struct property *prop; + const __be32 *p; + int num = 0; + u32 val; + + if (np == NULL) + return 0; + + match = of_match_node(si5351_dt_ids, np); + if (match == NULL) + return -EINVAL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->variant = (enum si5351_variant)match->data; + pdata->clk_xtal = of_clk_get(np, 0); + if (!IS_ERR(pdata->clk_xtal)) + clk_put(pdata->clk_xtal); + pdata->clk_clkin = of_clk_get(np, 1); + if (!IS_ERR(pdata->clk_clkin)) + clk_put(pdata->clk_clkin); + + /* + * property silabs,pll-source : , [<..>] + * allow to selectively set pll source + */ + of_property_for_each_u32(np, "silabs,pll-source", prop, p, num) { + if (num >= 2) { + dev_err(&client->dev, + "invalid pll %d on pll-source prop\n", num); + return -EINVAL; + } + + p = of_prop_next_u32(prop, p, &val); + if (!p) { + dev_err(&client->dev, + "missing pll-source for pll %d\n", num); + return -EINVAL; + } + + switch (val) { + case 0: + pdata->pll_src[num] = SI5351_PLL_SRC_XTAL; + break; + case 1: + if (pdata->variant != SI5351_VARIANT_C) { + dev_err(&client->dev, + "invalid parent %d for pll %d\n", + val, num); + return -EINVAL; + } + pdata->pll_src[num] = SI5351_PLL_SRC_CLKIN; + break; + default: + dev_err(&client->dev, + "invalid parent %d for pll %d\n", val, num); + return -EINVAL; + } + } + + /* per clkout properties */ + for_each_child_of_node(np, child) { + if (of_property_read_u32(child, "reg", &num)) { + dev_err(&client->dev, "missing reg property of %s\n", + child->name); + return -EINVAL; + } + + if (num >= 8 || + (pdata->variant == SI5351_VARIANT_A3 && num >= 3)) { + dev_err(&client->dev, "invalid clkout %d\n", num); + return -EINVAL; + } + + if (!of_property_read_u32(child, "silabs,multisynth-source", + &val)) { + switch (val) { + case 0: + pdata->clkout[num].multisynth_src = + SI5351_MULTISYNTH_SRC_VCO0; + break; + case 1: + pdata->clkout[num].multisynth_src = + SI5351_MULTISYNTH_SRC_VCO1; + break; + default: + dev_err(&client->dev, + "invalid parent %d for multisynth %d\n", + val, num); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "silabs,clock-source", &val)) { + switch (val) { + case 0: + pdata->clkout[num].clkout_src = + SI5351_CLKOUT_SRC_MSYNTH_N; + break; + case 1: + pdata->clkout[num].clkout_src = + SI5351_CLKOUT_SRC_MSYNTH_0_4; + break; + case 2: + pdata->clkout[num].clkout_src = + SI5351_CLKOUT_SRC_XTAL; + break; + case 3: + if (pdata->variant != SI5351_VARIANT_C) { + dev_err(&client->dev, + "invalid parent %d for clkout %d\n", + val, num); + return -EINVAL; + } + pdata->clkout[num].clkout_src = + SI5351_CLKOUT_SRC_CLKIN; + break; + default: + dev_err(&client->dev, + "invalid parent %d for clkout %d\n", + val, num); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "silabs,drive-strength", + &val)) { + switch (val) { + case SI5351_DRIVE_2MA: + case SI5351_DRIVE_4MA: + case SI5351_DRIVE_6MA: + case SI5351_DRIVE_8MA: + pdata->clkout[num].drive = val; + break; + default: + dev_err(&client->dev, + "invalid drive strength %d for clkout %d\n", + val, num); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "clock-frequency", &val)) + pdata->clkout[num].rate = val; + + pdata->clkout[num].pll_master = + of_property_read_bool(child, "silabs,pll-master"); + } + client->dev.platform_data = pdata; + + return 0; +} +#else +static int si5351_dt_parse(struct i2c_client *client) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int si5351_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si5351_platform_data *pdata; + struct si5351_driver_data *drvdata; + struct clk_init_data init; + struct clk *clk; + const char *parent_names[4]; + u8 num_parents, num_clocks; + int ret, n; + + ret = si5351_dt_parse(client); + if (ret) + return ret; + + pdata = client->dev.platform_data; + if (!pdata) + return -EINVAL; + + drvdata = devm_kzalloc(&client->dev, sizeof(*drvdata), GFP_KERNEL); + if (drvdata == NULL) { + dev_err(&client->dev, "unable to allocate driver data\n"); + return -ENOMEM; + } + + i2c_set_clientdata(client, drvdata); + drvdata->client = client; + drvdata->variant = pdata->variant; + drvdata->pxtal = pdata->clk_xtal; + drvdata->pclkin = pdata->clk_clkin; + + drvdata->regmap = devm_regmap_init_i2c(client, &si5351_regmap_config); + if (IS_ERR(drvdata->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(drvdata->regmap); + } + + /* Disable interrupts */ + si5351_reg_write(drvdata, SI5351_INTERRUPT_MASK, 0xf0); + /* Set disabled output drivers to drive low */ + si5351_reg_write(drvdata, SI5351_CLK3_0_DISABLE_STATE, 0x00); + si5351_reg_write(drvdata, SI5351_CLK7_4_DISABLE_STATE, 0x00); + /* Ensure pll select is on XTAL for Si5351A/B */ + if (drvdata->variant != SI5351_VARIANT_C) + si5351_set_bits(drvdata, SI5351_PLL_INPUT_SOURCE, + SI5351_PLLA_SOURCE | SI5351_PLLB_SOURCE, 0); + + /* setup clock configuration */ + for (n = 0; n < 2; n++) { + ret = _si5351_pll_reparent(drvdata, n, pdata->pll_src[n]); + if (ret) { + dev_err(&client->dev, + "failed to reparent pll %d to %d\n", + n, pdata->pll_src[n]); + return ret; + } + } + + for (n = 0; n < 8; n++) { + ret = _si5351_msynth_reparent(drvdata, n, + pdata->clkout[n].multisynth_src); + if (ret) { + dev_err(&client->dev, + "failed to reparent multisynth %d to %d\n", + n, pdata->clkout[n].multisynth_src); + return ret; + } + + ret = _si5351_clkout_reparent(drvdata, n, + pdata->clkout[n].clkout_src); + if (ret) { + dev_err(&client->dev, + "failed to reparent clkout %d to %d\n", + n, pdata->clkout[n].clkout_src); + return ret; + } + + ret = _si5351_clkout_set_drive_strength(drvdata, n, + pdata->clkout[n].drive); + if (ret) { + dev_err(&client->dev, + "failed set drive strength of clkout%d to %d\n", + n, pdata->clkout[n].drive); + return ret; + } + } + + /* register xtal input clock gate */ + memset(&init, 0, sizeof(init)); + init.name = si5351_input_names[0]; + init.ops = &si5351_xtal_ops; + init.flags = 0; + if (!IS_ERR(drvdata->pxtal)) { + drvdata->pxtal_name = __clk_get_name(drvdata->pxtal); + init.parent_names = &drvdata->pxtal_name; + init.num_parents = 1; + } + drvdata->xtal.init = &init; + clk = devm_clk_register(&client->dev, &drvdata->xtal); + if (IS_ERR(clk)) { + dev_err(&client->dev, "unable to register %s\n", init.name); + return PTR_ERR(clk); + } + + /* register clkin input clock gate */ + if (drvdata->variant == SI5351_VARIANT_C) { + memset(&init, 0, sizeof(init)); + init.name = si5351_input_names[1]; + init.ops = &si5351_clkin_ops; + if (!IS_ERR(drvdata->pclkin)) { + drvdata->pclkin_name = __clk_get_name(drvdata->pclkin); + init.parent_names = &drvdata->pclkin_name; + init.num_parents = 1; + } + drvdata->clkin.init = &init; + clk = devm_clk_register(&client->dev, &drvdata->clkin); + if (IS_ERR(clk)) { + dev_err(&client->dev, "unable to register %s\n", + init.name); + return PTR_ERR(clk); + } + } + + /* Si5351C allows to mux either xtal or clkin to PLL input */ + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 2 : 1; + parent_names[0] = si5351_input_names[0]; + parent_names[1] = si5351_input_names[1]; + + /* register PLLA */ + drvdata->pll[0].num = 0; + drvdata->pll[0].drvdata = drvdata; + drvdata->pll[0].hw.init = &init; + memset(&init, 0, sizeof(init)); + init.name = si5351_pll_names[0]; + init.ops = &si5351_pll_ops; + init.flags = 0; + init.parent_names = parent_names; + init.num_parents = num_parents; + clk = devm_clk_register(&client->dev, &drvdata->pll[0].hw); + if (IS_ERR(clk)) { + dev_err(&client->dev, "unable to register %s\n", init.name); + return -EINVAL; + } + + /* register PLLB or VXCO (Si5351B) */ + drvdata->pll[1].num = 1; + drvdata->pll[1].drvdata = drvdata; + drvdata->pll[1].hw.init = &init; + memset(&init, 0, sizeof(init)); + if (drvdata->variant == SI5351_VARIANT_B) { + init.name = si5351_pll_names[2]; + init.ops = &si5351_vxco_ops; + init.flags = CLK_IS_ROOT; + init.parent_names = NULL; + init.num_parents = 0; + } else { + init.name = si5351_pll_names[1]; + init.ops = &si5351_pll_ops; + init.flags = 0; + init.parent_names = parent_names; + init.num_parents = num_parents; + } + clk = devm_clk_register(&client->dev, &drvdata->pll[1].hw); + if (IS_ERR(clk)) { + dev_err(&client->dev, "unable to register %s\n", init.name); + return -EINVAL; + } + + /* register clk multisync and clk out divider */ + num_clocks = (drvdata->variant == SI5351_VARIANT_A3) ? 3 : 8; + parent_names[0] = si5351_pll_names[0]; + if (drvdata->variant == SI5351_VARIANT_B) + parent_names[1] = si5351_pll_names[2]; + else + parent_names[1] = si5351_pll_names[1]; + + drvdata->msynth = devm_kzalloc(&client->dev, num_clocks * + sizeof(*drvdata->msynth), GFP_KERNEL); + drvdata->clkout = devm_kzalloc(&client->dev, num_clocks * + sizeof(*drvdata->clkout), GFP_KERNEL); + + drvdata->onecell.clk_num = num_clocks; + drvdata->onecell.clks = devm_kzalloc(&client->dev, + num_clocks * sizeof(*drvdata->onecell.clks), GFP_KERNEL); + + if (WARN_ON(!drvdata->msynth || !drvdata->clkout || + !drvdata->onecell.clks)) + return -ENOMEM; + + for (n = 0; n < num_clocks; n++) { + drvdata->msynth[n].num = n; + drvdata->msynth[n].drvdata = drvdata; + drvdata->msynth[n].hw.init = &init; + memset(&init, 0, sizeof(init)); + init.name = si5351_msynth_names[n]; + init.ops = &si5351_msynth_ops; + init.flags = 0; + if (pdata->clkout[n].pll_master) + init.flags |= CLK_SET_RATE_PARENT; + init.parent_names = parent_names; + init.num_parents = 2; + clk = devm_clk_register(&client->dev, &drvdata->msynth[n].hw); + if (IS_ERR(clk)) { + dev_err(&client->dev, "unable to register %s\n", + init.name); + return -EINVAL; + } + } + + num_parents = (drvdata->variant == SI5351_VARIANT_C) ? 4 : 3; + parent_names[2] = si5351_input_names[0]; + parent_names[3] = si5351_input_names[1]; + for (n = 0; n < num_clocks; n++) { + parent_names[0] = si5351_msynth_names[n]; + parent_names[1] = (n < 4) ? si5351_msynth_names[0] : + si5351_msynth_names[4]; + + drvdata->clkout[n].num = n; + drvdata->clkout[n].drvdata = drvdata; + drvdata->clkout[n].hw.init = &init; + memset(&init, 0, sizeof(init)); + init.name = si5351_clkout_names[n]; + init.ops = &si5351_clkout_ops; + init.flags = 0; + if (pdata->clkout[n].clkout_src == SI5351_CLKOUT_SRC_MSYNTH_N) + init.flags |= CLK_SET_RATE_PARENT; + init.parent_names = parent_names; + init.num_parents = num_parents; + clk = devm_clk_register(&client->dev, &drvdata->clkout[n].hw); + if (IS_ERR(clk)) { + dev_err(&client->dev, "unable to register %s\n", + init.name); + return -EINVAL; + } + drvdata->onecell.clks[n] = clk; + } + + ret = of_clk_add_provider(client->dev.of_node, of_clk_src_onecell_get, + &drvdata->onecell); + if (ret) { + dev_err(&client->dev, "unable to add clk provider\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id si5351_i2c_ids[] = { + { "silabs,si5351", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si5351_i2c_ids); + +static struct i2c_driver si5351_driver = { + .driver = { + .name = "si5351", + .of_match_table = of_match_ptr(si5351_dt_ids), + }, + .probe = si5351_i2c_probe, + .id_table = si5351_i2c_ids, +}; +module_i2c_driver(si5351_driver); + +MODULE_AUTHOR("Sebastian Hesselbarth + * Rabeeh Khoury + * + * 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. + */ + +#ifndef _CLK_SI5351_H_ +#define _CLK_SI5351_H_ + +#define SI5351_BUS_BASE_ADDR 0x60 + +#define SI5351_PLL_VCO_MIN 600000000 +#define SI5351_PLL_VCO_MAX 900000000 +#define SI5351_MULTISYNTH_MIN_FREQ 1000000 +#define SI5351_MULTISYNTH_DIVBY4_FREQ 150000000 +#define SI5351_MULTISYNTH_MAX_FREQ 160000000 +#define SI5351_MULTISYNTH67_MAX_FREQ SI5351_MULTISYNTH_DIVBY4_FREQ +#define SI5351_CLKOUT_MIN_FREQ 8000 +#define SI5351_CLKOUT_MAX_FREQ SI5351_MULTISYNTH_MAX_FREQ +#define SI5351_CLKOUT67_MAX_FREQ SI5351_MULTISYNTH67_MAX_FREQ + +#define SI5351_PLL_A_MIN 15 +#define SI5351_PLL_A_MAX 90 +#define SI5351_PLL_B_MAX (SI5351_PLL_C_MAX-1) +#define SI5351_PLL_C_MAX 1048575 +#define SI5351_MULTISYNTH_A_MIN 6 +#define SI5351_MULTISYNTH_A_MAX 1800 +#define SI5351_MULTISYNTH67_A_MAX 254 +#define SI5351_MULTISYNTH_B_MAX (SI5351_MULTISYNTH_C_MAX-1) +#define SI5351_MULTISYNTH_C_MAX 1048575 +#define SI5351_MULTISYNTH_P1_MAX ((1<<18)-1) +#define SI5351_MULTISYNTH_P2_MAX ((1<<20)-1) +#define SI5351_MULTISYNTH_P3_MAX ((1<<20)-1) + +#define SI5351_DEVICE_STATUS 0 +#define SI5351_INTERRUPT_STATUS 1 +#define SI5351_INTERRUPT_MASK 2 +#define SI5351_STATUS_SYS_INIT (1<<7) +#define SI5351_STATUS_LOL_B (1<<6) +#define SI5351_STATUS_LOL_A (1<<5) +#define SI5351_STATUS_LOS (1<<4) +#define SI5351_OUTPUT_ENABLE_CTRL 3 +#define SI5351_OEB_PIN_ENABLE_CTRL 9 +#define SI5351_PLL_INPUT_SOURCE 15 +#define SI5351_CLKIN_DIV_MASK (3<<6) +#define SI5351_CLKIN_DIV_1 (0<<6) +#define SI5351_CLKIN_DIV_2 (1<<6) +#define SI5351_CLKIN_DIV_4 (2<<6) +#define SI5351_CLKIN_DIV_8 (3<<6) +#define SI5351_PLLB_SOURCE (1<<3) +#define SI5351_PLLA_SOURCE (1<<2) + +#define SI5351_CLK0_CTRL 16 +#define SI5351_CLK1_CTRL 17 +#define SI5351_CLK2_CTRL 18 +#define SI5351_CLK3_CTRL 19 +#define SI5351_CLK4_CTRL 20 +#define SI5351_CLK5_CTRL 21 +#define SI5351_CLK6_CTRL 22 +#define SI5351_CLK7_CTRL 23 +#define SI5351_CLK_POWERDOWN (1<<7) +#define SI5351_CLK_INTEGER_MODE (1<<6) +#define SI5351_CLK_PLL_SELECT (1<<5) +#define SI5351_CLK_INVERT (1<<4) +#define SI5351_CLK_INPUT_MASK (3<<2) +#define SI5351_CLK_INPUT_XTAL (0<<2) +#define SI5351_CLK_INPUT_CLKIN (1<<2) +#define SI5351_CLK_INPUT_MULTISYNTH_0_4 (2<<2) +#define SI5351_CLK_INPUT_MULTISYNTH_N (3<<2) +#define SI5351_CLK_DRIVE_STRENGTH_MASK (3<<0) +#define SI5351_CLK_DRIVE_STRENGTH_2MA (0<<0) +#define SI5351_CLK_DRIVE_STRENGTH_4MA (1<<0) +#define SI5351_CLK_DRIVE_STRENGTH_6MA (2<<0) +#define SI5351_CLK_DRIVE_STRENGTH_8MA (3<<0) + +#define SI5351_CLK3_0_DISABLE_STATE 24 +#define SI5351_CLK7_4_DISABLE_STATE 25 +#define SI5351_CLK_DISABLE_STATE_LOW 0 +#define SI5351_CLK_DISABLE_STATE_HIGH 1 +#define SI5351_CLK_DISABLE_STATE_FLOAT 2 +#define SI5351_CLK_DISABLE_STATE_NEVER 3 + +#define SI5351_PARAMETERS_LENGTH 8 +#define SI5351_PLLA_PARAMETERS 26 +#define SI5351_PLLB_PARAMETERS 34 +#define SI5351_CLK0_PARAMETERS 42 +#define SI5351_CLK1_PARAMETERS 50 +#define SI5351_CLK2_PARAMETERS 58 +#define SI5351_CLK3_PARAMETERS 66 +#define SI5351_CLK4_PARAMETERS 74 +#define SI5351_CLK5_PARAMETERS 82 +#define SI5351_CLK6_PARAMETERS 90 +#define SI5351_CLK7_PARAMETERS 91 +#define SI5351_CLK6_7_OUTPUT_DIVIDER 92 +#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4) +#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0) +#define SI5351_OUTPUT_CLK_DIV_SHIFT 4 +#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0 +#define SI5351_OUTPUT_CLK_DIV_1 0 +#define SI5351_OUTPUT_CLK_DIV_2 1 +#define SI5351_OUTPUT_CLK_DIV_4 2 +#define SI5351_OUTPUT_CLK_DIV_8 3 +#define SI5351_OUTPUT_CLK_DIV_16 4 +#define SI5351_OUTPUT_CLK_DIV_32 5 +#define SI5351_OUTPUT_CLK_DIV_64 6 +#define SI5351_OUTPUT_CLK_DIV_128 7 +#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2) + +#define SI5351_SSC_PARAM0 149 +#define SI5351_SSC_PARAM1 150 +#define SI5351_SSC_PARAM2 151 +#define SI5351_SSC_PARAM3 152 +#define SI5351_SSC_PARAM4 153 +#define SI5351_SSC_PARAM5 154 +#define SI5351_SSC_PARAM6 155 +#define SI5351_SSC_PARAM7 156 +#define SI5351_SSC_PARAM8 157 +#define SI5351_SSC_PARAM9 158 +#define SI5351_SSC_PARAM10 159 +#define SI5351_SSC_PARAM11 160 +#define SI5351_SSC_PARAM12 161 + +#define SI5351_VXCO_PARAMETERS_LOW 162 +#define SI5351_VXCO_PARAMETERS_MID 163 +#define SI5351_VXCO_PARAMETERS_HIGH 164 + +#define SI5351_CLK0_PHASE_OFFSET 165 +#define SI5351_CLK1_PHASE_OFFSET 166 +#define SI5351_CLK2_PHASE_OFFSET 167 +#define SI5351_CLK3_PHASE_OFFSET 168 +#define SI5351_CLK4_PHASE_OFFSET 169 +#define SI5351_CLK5_PHASE_OFFSET 170 + +#define SI5351_PLL_RESET 177 +#define SI5351_PLL_RESET_B (1<<7) +#define SI5351_PLL_RESET_A (1<<5) + +#define SI5351_CRYSTAL_LOAD 183 +#define SI5351_CRYSTAL_LOAD_MASK (3<<6) +#define SI5351_CRYSTAL_LOAD_6PF (1<<6) +#define SI5351_CRYSTAL_LOAD_8PF (2<<6) +#define SI5351_CRYSTAL_LOAD_10PF (3<<6) + +#define SI5351_FANOUT_ENABLE 187 +#define SI5351_CLKIN_ENABLE (1<<7) +#define SI5351_XTAL_ENABLE (1<<6) +#define SI5351_MULTISYNTH_ENABLE (1<<4) + +#endif diff --git a/include/linux/platform_data/si5351.h b/include/linux/platform_data/si5351.h new file mode 100644 index 000000000000..92dabcaf6499 --- /dev/null +++ b/include/linux/platform_data/si5351.h @@ -0,0 +1,114 @@ +/* + * Si5351A/B/C programmable clock generator platform_data. + */ + +#ifndef __LINUX_PLATFORM_DATA_SI5351_H__ +#define __LINUX_PLATFORM_DATA_SI5351_H__ + +struct clk; + +/** + * enum si5351_variant - SiLabs Si5351 chip variant + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input) + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input) + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input) + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input) + */ +enum si5351_variant { + SI5351_VARIANT_A = 1, + SI5351_VARIANT_A3 = 2, + SI5351_VARIANT_B = 3, + SI5351_VARIANT_C = 4, +}; + +/** + * enum si5351_pll_src - Si5351 pll clock source + * @SI5351_PLL_SRC_DEFAULT: default, do not change eeprom config + * @SI5351_PLL_SRC_XTAL: pll source clock is XTAL input + * @SI5351_PLL_SRC_CLKIN: pll source clock is CLKIN input (Si5351C only) + */ +enum si5351_pll_src { + SI5351_PLL_SRC_DEFAULT = 0, + SI5351_PLL_SRC_XTAL = 1, + SI5351_PLL_SRC_CLKIN = 2, +}; + +/** + * enum si5351_multisynth_src - Si5351 multisynth clock source + * @SI5351_MULTISYNTH_SRC_DEFAULT: default, do not change eeprom config + * @SI5351_MULTISYNTH_SRC_VCO0: multisynth source clock is VCO0 + * @SI5351_MULTISYNTH_SRC_VCO1: multisynth source clock is VCO1/VXCO + */ +enum si5351_multisynth_src { + SI5351_MULTISYNTH_SRC_DEFAULT = 0, + SI5351_MULTISYNTH_SRC_VCO0 = 1, + SI5351_MULTISYNTH_SRC_VCO1 = 2, +}; + +/** + * enum si5351_clkout_src - Si5351 clock output clock source + * @SI5351_CLKOUT_SRC_DEFAULT: default, do not change eeprom config + * @SI5351_CLKOUT_SRC_MSYNTH_N: clkout N source clock is multisynth N + * @SI5351_CLKOUT_SRC_MSYNTH_0_4: clkout N source clock is multisynth 0 (N<4) + * or 4 (N>=4) + * @SI5351_CLKOUT_SRC_XTAL: clkout N source clock is XTAL + * @SI5351_CLKOUT_SRC_CLKIN: clkout N source clock is CLKIN (Si5351C only) + */ +enum si5351_clkout_src { + SI5351_CLKOUT_SRC_DEFAULT = 0, + SI5351_CLKOUT_SRC_MSYNTH_N = 1, + SI5351_CLKOUT_SRC_MSYNTH_0_4 = 2, + SI5351_CLKOUT_SRC_XTAL = 3, + SI5351_CLKOUT_SRC_CLKIN = 4, +}; + +/** + * enum si5351_drive_strength - Si5351 clock output drive strength + * @SI5351_DRIVE_DEFAULT: default, do not change eeprom config + * @SI5351_DRIVE_2MA: 2mA clock output drive strength + * @SI5351_DRIVE_4MA: 4mA clock output drive strength + * @SI5351_DRIVE_6MA: 6mA clock output drive strength + * @SI5351_DRIVE_8MA: 8mA clock output drive strength + */ +enum si5351_drive_strength { + SI5351_DRIVE_DEFAULT = 0, + SI5351_DRIVE_2MA = 2, + SI5351_DRIVE_4MA = 4, + SI5351_DRIVE_6MA = 6, + SI5351_DRIVE_8MA = 8, +}; + +/** + * struct si5351_clkout_config - Si5351 clock output configuration + * @clkout: clkout number + * @multisynth_src: multisynth source clock + * @clkout_src: clkout source clock + * @pll_master: if true, clkout can also change pll rate + * @drive: output drive strength + * @rate: initial clkout rate, or default if 0 + */ +struct si5351_clkout_config { + enum si5351_multisynth_src multisynth_src; + enum si5351_clkout_src clkout_src; + enum si5351_drive_strength drive; + bool pll_master; + unsigned long rate; +}; + +/** + * struct si5351_platform_data - Platform data for the Si5351 clock driver + * @variant: Si5351 chip variant + * @clk_xtal: xtal input clock + * @clk_clkin: clkin input clock + * @pll_src: array of pll source clock setting + * @clkout: array of clkout configuration + */ +struct si5351_platform_data { + enum si5351_variant variant; + struct clk *clk_xtal; + struct clk *clk_clkin; + enum si5351_pll_src pll_src[2]; + struct si5351_clkout_config clkout[8]; +}; + +#endif -- cgit v1.2.3-71-gd317 From d3a1c7be8361e2fbb6affbdb19de47ca48d6c402 Mon Sep 17 00:00:00 2001 From: Mike Turquette Date: Thu, 11 Apr 2013 11:31:36 -0700 Subject: clk: composite: rename 'div' references to 'rate' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename all div_hw and div_ops related variables and functions to use rate_hw, rate_ops, etc. This is to make the rate-change portion of the composite clk implementation more generic. A patch following this one will allow for fixed-rate clocks to reuse this infrastructure. Signed-off-by: Mike Turquette Reviewed-by: Prashant Gaikwad Tested-by: Emilio López Cc: Gregory CLEMENT --- drivers/clk/clk-composite.c | 40 ++++++++++++++++++++-------------------- include/linux/clk-provider.h | 14 +++++++------- 2 files changed, 27 insertions(+), 27 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 097dee4fd209..6f4728c6dbd1 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -47,36 +47,36 @@ static unsigned long clk_composite_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_composite *composite = to_clk_composite(hw); - const struct clk_ops *div_ops = composite->div_ops; - struct clk_hw *div_hw = composite->div_hw; + const struct clk_ops *rate_ops = composite->rate_ops; + struct clk_hw *rate_hw = composite->rate_hw; - div_hw->clk = hw->clk; + rate_hw->clk = hw->clk; - return div_ops->recalc_rate(div_hw, parent_rate); + return rate_ops->recalc_rate(rate_hw, parent_rate); } static long clk_composite_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { struct clk_composite *composite = to_clk_composite(hw); - const struct clk_ops *div_ops = composite->div_ops; - struct clk_hw *div_hw = composite->div_hw; + const struct clk_ops *rate_ops = composite->rate_ops; + struct clk_hw *rate_hw = composite->rate_hw; - div_hw->clk = hw->clk; + rate_hw->clk = hw->clk; - return div_ops->round_rate(div_hw, rate, prate); + return rate_ops->round_rate(rate_hw, rate, prate); } static int clk_composite_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_composite *composite = to_clk_composite(hw); - const struct clk_ops *div_ops = composite->div_ops; - struct clk_hw *div_hw = composite->div_hw; + const struct clk_ops *rate_ops = composite->rate_ops; + struct clk_hw *rate_hw = composite->rate_hw; - div_hw->clk = hw->clk; + rate_hw->clk = hw->clk; - return div_ops->set_rate(div_hw, rate, parent_rate); + return rate_ops->set_rate(rate_hw, rate, parent_rate); } static int clk_composite_is_enabled(struct clk_hw *hw) @@ -115,7 +115,7 @@ static void clk_composite_disable(struct clk_hw *hw) struct clk *clk_register_composite(struct device *dev, const char *name, const char **parent_names, int num_parents, struct clk_hw *mux_hw, const struct clk_ops *mux_ops, - struct clk_hw *div_hw, const struct clk_ops *div_ops, + struct clk_hw *rate_hw, const struct clk_ops *rate_ops, struct clk_hw *gate_hw, const struct clk_ops *gate_ops, unsigned long flags) { @@ -149,15 +149,15 @@ struct clk *clk_register_composite(struct device *dev, const char *name, clk_composite_ops->set_parent = clk_composite_set_parent; } - if (div_hw && div_ops) { - if (!div_ops->recalc_rate || !div_ops->round_rate || - !div_ops->set_rate) { + if (rate_hw && rate_ops) { + if (!rate_ops->recalc_rate || !rate_ops->round_rate || + !rate_ops->set_rate) { clk = ERR_PTR(-EINVAL); goto err; } - composite->div_hw = div_hw; - composite->div_ops = div_ops; + composite->rate_hw = rate_hw; + composite->rate_ops = rate_ops; clk_composite_ops->recalc_rate = clk_composite_recalc_rate; clk_composite_ops->round_rate = clk_composite_round_rate; clk_composite_ops->set_rate = clk_composite_set_rate; @@ -187,8 +187,8 @@ struct clk *clk_register_composite(struct device *dev, const char *name, if (composite->mux_hw) composite->mux_hw->clk = clk; - if (composite->div_hw) - composite->div_hw->clk = clk; + if (composite->rate_hw) + composite->rate_hw->clk = clk; if (composite->gate_hw) composite->gate_hw->clk = clk; diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index e7b7cbc53815..11860985fecb 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -354,11 +354,11 @@ struct clk *clk_register_fixed_factor(struct device *dev, const char *name, * struct clk_composite - aggregate clock of mux, divider and gate clocks * * @hw: handle between common and hardware-specific interfaces - * @mux_hw: handle between composite and hardware-specifix mux clock - * @div_hw: handle between composite and hardware-specifix divider clock - * @gate_hw: handle between composite and hardware-specifix gate clock + * @mux_hw: handle between composite and hardware-specific mux clock + * @rate_hw: handle between composite and hardware-specific rate clock + * @gate_hw: handle between composite and hardware-specific gate clock * @mux_ops: clock ops for mux - * @div_ops: clock ops for divider + * @rate_ops: clock ops for rate * @gate_ops: clock ops for gate */ struct clk_composite { @@ -366,18 +366,18 @@ struct clk_composite { struct clk_ops ops; struct clk_hw *mux_hw; - struct clk_hw *div_hw; + struct clk_hw *rate_hw; struct clk_hw *gate_hw; const struct clk_ops *mux_ops; - const struct clk_ops *div_ops; + const struct clk_ops *rate_ops; const struct clk_ops *gate_ops; }; struct clk *clk_register_composite(struct device *dev, const char *name, const char **parent_names, int num_parents, struct clk_hw *mux_hw, const struct clk_ops *mux_ops, - struct clk_hw *div_hw, const struct clk_ops *div_ops, + struct clk_hw *rate_hw, const struct clk_ops *rate_ops, struct clk_hw *gate_hw, const struct clk_ops *gate_ops, unsigned long flags); -- cgit v1.2.3-71-gd317