This patch was submitted as RFC on June 20th 2012 (see https://lkml.org/lkml/2012/6/20/779 for more details) and resubmitted on March 8th 2013 (see https://lkml.org/lkml/2013/3/8/245 for more details). This is a new version which: * Gets rid of the platform bus notifier replacing it with a platform driver for the "virtual" sta2x11-clock-regs" device, which is created when three of the sta2x11 basic platform devices (sta2x11-sctl, sta2x11-apbreg and sta2x11-apb-soc-regs) have been probed. * Adds some more clocks (gpios, sdio/mmc, spi, i2c, dma). Clocks still have to be improved because not all the details have been modeled (for instance mmc clocks are actually muxed clocks, not fixed factor). Signed-off-by: Davide Ciminaghi <ciminaghi@xxxxxxxxx> Acked-by: Giancarlo Asnaghi <giancarlo.asnaghi@xxxxxx> --- arch/x86/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/sta2x11/Makefile | 1 + drivers/clk/sta2x11/clk-audio-pll.c | 149 ++++++ drivers/clk/sta2x11/clk-soc-pll.c | 95 ++++ drivers/clk/sta2x11/clk.c | 865 +++++++++++++++++++++++++++++++++++ drivers/clk/sta2x11/clk.h | 88 ++++ 7 files changed, 1200 insertions(+), 0 deletions(-) create mode 100644 drivers/clk/sta2x11/Makefile create mode 100644 drivers/clk/sta2x11/clk-audio-pll.c create mode 100644 drivers/clk/sta2x11/clk-soc-pll.c create mode 100644 drivers/clk/sta2x11/clk.c create mode 100644 drivers/clk/sta2x11/clk.h diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index ae05df92..fed3507 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -551,6 +551,7 @@ config STA2X11 depends on X86_32_NON_STANDARD && PCI select X86_DEV_DMA_OPS select X86_DMA_REMAP + select COMMON_CLK select SWIOTLB select MFD_STA2X11 select ARCH_REQUIRE_GPIOLIB diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 4038c2b..d2b7693 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_PLAT_SAMSUNG) += samsung/ obj-$(CONFIG_X86) += x86/ +obj-$(CONFIG_STA2X11) += sta2x11/ # Chip specific obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o diff --git a/drivers/clk/sta2x11/Makefile b/drivers/clk/sta2x11/Makefile new file mode 100644 index 0000000..60c319a --- /dev/null +++ b/drivers/clk/sta2x11/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_STA2X11) += clk.o clk-soc-pll.o clk-audio-pll.o diff --git a/drivers/clk/sta2x11/clk-audio-pll.c b/drivers/clk/sta2x11/clk-audio-pll.c new file mode 100644 index 0000000..8ed28a2 --- /dev/null +++ b/drivers/clk/sta2x11/clk-audio-pll.c @@ -0,0 +1,149 @@ +/* + * Copyright ST Microelectronics 2012 + * Author: Davide Ciminaghi <ciminaghi@xxxxxxxxx> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Common clock api implementation for sta2x11 + * audio-pll clock type implementation + */ +/* #define DEBUG */ + +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/clk-provider.h> +#include <asm/div64.h> +#include <asm/sta2x11.h> + +#include "clk.h" + +/** + * struct clk_audio_pll - sta2x11 audio pll clock + * @hw: clk_hw for the pll + * + * Soc pll + */ +struct clk_audio_pll { + struct clk_hw hw; + void __iomem *base; + spinlock_t *lock; +}; + +#define to_clk_audio_pll(_hw) container_of(_hw, struct clk_audio_pll, hw) + +static unsigned long clk_audio_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_audio_pll *audio_pll = to_clk_audio_pll(hw); + u32 scpllctl = readl(audio_pll->base + SCTL_SCPLLCTL); + u32 scpllfctrl = readl(audio_pll->base + SCTL_SCPLLFCTRL); + u16 scresfract = readl(audio_pll->base + SCTL_SCRESFRACT); + u64 fvco, inff, ndiv, fract, idf, phi, odf; + int frac_control, dither_disable; + + inff = parent_rate; + frac_control = scpllctl & SCTL_SCPLLCTL_FRAC_CONTROL; + ndiv = (scpllfctrl >> SCTL_SCPLLFCTRL_AUDIO_PLL_NDIV_SHIFT) & + SCTL_SCPLLFCTRL_AUDIO_PLL_NDIV_MASK; + fract = scresfract & SCTL_SCRESFRACT_MASK ; + idf = (scpllfctrl >> SCTL_SCPLLFCTRL_AUDIO_PLL_IDF_SHIFT) & + SCTL_SCPLLFCTRL_AUDIO_PLL_IDF_MASK; + idf = idf ? idf : 1; + odf = (scpllfctrl >> SCTL_SCPLLFCTRL_AUDIO_PLL_ODF_SHIFT) & + SCTL_SCPLLFCTRL_AUDIO_PLL_ODF_MASK; + odf = odf > 5 ? 32 : (1<<odf); + dither_disable = ((scpllfctrl >> SCTL_SCPLLFCTRL_DITHER_DISABLE_SHIFT) & + SCTL_SCPLLFCTRL_DITHER_DISABLE_MASK) ? 0 : 1; + + pr_debug("%s : refclk = %llu, scpllctl = 0x%08x\n", __func__, + inff, scpllctl); + pr_debug("%s : scpllfctrl = 0x%08x, scresfract = 0x%08x\n", + __func__, scpllfctrl, scresfract); + pr_debug("%s : ndiv = %llu, frac_control = %d, dither_disable = %d\n", + __func__, ndiv, frac_control, dither_disable); + pr_debug("%s: fract = %llu, idf = %llu, odf = %llu\n", + __func__, fract, idf, odf); + + fvco = frac_control ? + div_u64((inff*2*((ndiv<<17)+(fract<<1)+dither_disable ? 0 : 1)), + (idf<<17)) : + div_u64((inff * 2 * ndiv), idf); + phi = div_u64(fvco, (odf * 2)); + + pr_debug("%s: fvco = %llu Hz, phi = %llu Hz\n", __func__, fvco, phi); + + return phi; +} + +static int clk_audio_pll_enable(struct clk_hw *hw) +{ + struct clk_audio_pll *audio_pll = to_clk_audio_pll(hw); + u32 scpllctl; + unsigned long flags; + spin_lock_irqsave(audio_pll->lock, flags); + scpllctl = readl(audio_pll->base + SCTL_SCPLLCTL); + scpllctl &= ~SCTL_SCPLLCTL_AUDIO_PLL_PD; + writel(scpllctl, audio_pll->base + SCTL_SCPLLCTL); + spin_unlock_irqrestore(audio_pll->lock, flags); + return 0; +} + +static void clk_audio_pll_disable(struct clk_hw *hw) +{ + struct clk_audio_pll *audio_pll = to_clk_audio_pll(hw); + u32 scpllctl; + unsigned long flags; + spin_lock_irqsave(audio_pll->lock, flags); + scpllctl = readl(audio_pll->base + SCTL_SCPLLCTL); + scpllctl |= SCTL_SCPLLCTL_AUDIO_PLL_PD; + writel(scpllctl, audio_pll->base + SCTL_SCPLLCTL); + spin_unlock_irqrestore(audio_pll->lock, flags); +} + +static const struct clk_ops clk_soc_pll_ops = { + .enable = clk_audio_pll_enable, + .disable = clk_audio_pll_disable, + .recalc_rate = clk_audio_pll_recalc_rate, +}; + + +struct clk *register_sta2x11_clk_audio_pll(const char *name, + const char *parent_name, + void __iomem *base, spinlock_t *lock) +{ + struct clk_audio_pll *audio_pll; + struct clk *clk; + struct clk_init_data init; + + audio_pll = kzalloc(sizeof(*audio_pll), GFP_KERNEL); + if (!audio_pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_soc_pll_ops; + init.flags = 0; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + audio_pll->base = base; + audio_pll->lock = lock; + audio_pll->hw.init = &init; + + clk = clk_register(NULL, &audio_pll->hw); + if (IS_ERR(clk)) + kfree(audio_pll); + + return clk; +} diff --git a/drivers/clk/sta2x11/clk-soc-pll.c b/drivers/clk/sta2x11/clk-soc-pll.c new file mode 100644 index 0000000..8383bf9 --- /dev/null +++ b/drivers/clk/sta2x11/clk-soc-pll.c @@ -0,0 +1,95 @@ +/* + * Copyright ST Microelectronics 2012 + * Author: Davide Ciminaghi <ciminaghi@xxxxxxxxx> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Common clock api implementation for sta2x11 + * soc-pll clock type implementation + */ +/* #define DEBUG */ + +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <asm/sta2x11.h> + +#include "clk.h" + +/** + * struct clk_soc_pll - sta2x11 soc pll clock + * @hw: clk_hw for the pll + * + * Soc pll + */ +struct clk_soc_pll { + struct clk_hw hw; + void __iomem *base; + spinlock_t *lock; +}; + +#define to_clk_soc_pll(_hw) container_of(_hw, struct clk_soc_pll, hw) + +#define PLL1NMUL_MASK 0x7f +#define PLL1NMUL_SHIFT 3 + +static unsigned long clk_soc_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_soc_pll *soc_pll = to_clk_soc_pll(hw); + unsigned long out; + u32 scpllfctrl = readl(soc_pll->base + SCTL_SCPLLFCTRL); + u32 nmul = (scpllfctrl >> PLL1NMUL_SHIFT) & PLL1NMUL_MASK; + out = parent_rate * nmul; + pr_debug("%s : soc_pll->base = %p\n", __func__, soc_pll->base); + pr_debug("%s : scpllfctrl = 0x%08x\n", __func__, scpllfctrl); + pr_debug("%s : nmul = %d\n", __func__, nmul); + pr_debug("%s : calculated rate = %lu\n", __func__, out); + pr_debug("%s : parent_rate = %lu\n", __func__, parent_rate); + return out; +} + +static const struct clk_ops clk_soc_pll_ops = { + .recalc_rate = clk_soc_pll_recalc_rate, +}; + + +struct clk *register_sta2x11_clk_soc_pll(const char *name, + const char *parent_name, + void __iomem *base, spinlock_t *lock) +{ + struct clk_soc_pll *soc_pll; + struct clk *clk; + struct clk_init_data init; + + soc_pll = kzalloc(sizeof(*soc_pll), GFP_KERNEL); + if (!soc_pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &clk_soc_pll_ops; + init.flags = 0; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + soc_pll->base = base; + soc_pll->lock = lock; + soc_pll->hw.init = &init; + + clk = clk_register(NULL, &soc_pll->hw); + if (IS_ERR(clk)) + kfree(soc_pll); + + return clk; +} diff --git a/drivers/clk/sta2x11/clk.c b/drivers/clk/sta2x11/clk.c new file mode 100644 index 0000000..24087a6 --- /dev/null +++ b/drivers/clk/sta2x11/clk.c @@ -0,0 +1,865 @@ +/* + * Copyright ST Microelectronics 2012 + * Author: Davide Ciminaghi <ciminaghi@xxxxxxxxx> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Common clock api implementation for sta2x11 + */ +/* #define DEBUG */ + +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/clkdev.h> +#include <linux/pci-amba.h> +#include <asm/sta2x11.h> + +#include "clk.h" + +#define STA2X11_MAX_CLK_NAME_LEN 60 +#define STA2X11_MAX_CLK_NPARENTS 8 + +/* + * struct sta2x11_clk_data + * This structure is used to build the table listing all the + * clocks for a connext chip. + * + * @basename : basename of the clock (a .%d suffix will be added to + * deal with multiple connext instances). + * @type : sta2x11 clock type (see clk.h, enum sta2x11_clk_type) + * @reg_offset : the controlling register's offset for this clock + * @init : pointer to init function. When this pointer is not NULL, the + * pointed function is invoked before clock registration. This is used to + * fill the clock struct fields which are unknown at compile time (typically + * virtual addresses of controlling registers). + * @args : arguments needed for registration + */ +struct sta2x11_clk_data { + const char *basename; + int type; + unsigned int reg_offset; + void (*init)(struct sta2x11_clk_data *, struct sta2x11_instance_data *); + unsigned long flags; + union { + struct { + const char *parent_name; + unsigned long rate; + } fixed_rate_root; + struct { + const char *parent_name; + unsigned int mult; + unsigned int div; + } fixed_factor; + struct { + const char **parent_names; + u8 num_parents; + void __iomem *reg; + u8 shift; + u8 width; + u8 clk_mux_flags; + spinlock_t *lock; + } mux; + struct { + const char *parent_name; + void __iomem *base; + spinlock_t *lock; + } soc_pll; + struct { + const char *parent_name; + void __iomem *base; + spinlock_t *lock; + } audio_pll; + struct { + const char *parent_name; + void __iomem *reg; + u8 shift; + u8 width; + spinlock_t *lock; + struct clk_div_table *div_tab; + u8 tab_divider_flags; + } tab_divider; + } args; +}; + +/* Various helper macros used to setup the clock table */ + +/* + * Use this macro to declare a fixed clock with no parents + * + * @name : clock name + * @f : clock frequency + */ +#define DECLARE_FIXED_RATE_ROOT_CLK(n, f) \ + [n] = { \ + .basename = #n, \ + .type = fixed_rate_root, \ + .flags = CLK_IS_ROOT, \ + .args = { \ + .fixed_rate_root = { \ + .parent_name = NULL, \ + .rate = f, \ + } \ + } \ + } + +/* + * Use this macro to declare a fixed factor clock + * + * @n : clock name + * @parent_name = name of parent clock + * @flags = clock flags + * @mult = const mult factor + * @div = const div factor + */ +#define DECLARE_FIXED_FACTOR_CLK(n, pn, f, m, d) \ + [n] = { \ + .basename = #n, \ + .type = fixed_factor, \ + .reg_offset = 0, \ + .flags = f, \ + .args = { \ + .fixed_factor = { \ + .parent_name = #pn, \ + .mult = m, \ + .div = d, \ + } \ + } \ + } + +/* + * Use this macro to declare a mux clock + * + * @name : clock name + * @reg_offset : offset of controlling register + * @parent_names : names of parents + * @init : pointer to init function + * @flags : clock flags + * @shift : bitmask shift + * @width : bitmask width + * @clk_mux_flags : flags of clock mux + */ +#define DECLARE_MUX_CLK(n, ro, pn, in, f, s, w, cmf) \ + [n] = { \ + .basename = #n, \ + .type = mux, \ + .flags = f, \ + .reg_offset = ro, \ + .init = in, \ + .args = { \ + .mux = { \ + .parent_names = pn, \ + .num_parents = ARRAY_SIZE(pn), \ + .shift = s, \ + .width = w, \ + .clk_mux_flags = cmf, \ + }, \ + } \ + } + +/* + * Use this macro to declare a pll clock + * + * @n : clock name + * @parent_name : name of parent + * @type : clock type + */ +#ifndef xcat +#define xcat(a, b) a##b +#endif + +#define DECLARE_PLL_CLK(n, pn, t) \ + [n] = { \ + .basename = #n, \ + .type = t, \ + .init = xcat(t, _init), \ + .args = { \ + .t = { \ + .parent_name = #pn, \ + }, \ + } \ + } + +/* + * Use this macro to declare a soc-pll clock + * + * @n : clock name + * @parent_name : name of parent + */ +#define DECLARE_SOC_PLL_CLK(n, pn) DECLARE_PLL_CLK(n, pn, soc_pll) + +/* + * Use this macro to declare an audio-pll clock + * + * @n : clock name + * @parent_name : name of parent + */ +#define DECLARE_AUDIO_PLL_CLK(n, pn) DECLARE_PLL_CLK(n, pn, audio_pll) + +/* + * Use this macro to declare a tab-divider clock + * + * @n : clock name + * @ro : register offset + * @pn : parent name + * @in : init function + * @f : clock flags + * @s : controlling bitmask shift + * @w : controlling bitmask width + * @tdf : tab divider clock specific flags + */ +#define DECLARE_TAB_DIVIDER_CLK(n, ro, pn, in, f, s, w, tab, tdf) \ + [n] = { \ + .basename = #n, \ + .type = tab_divider, \ + .flags = f, \ + .reg_offset = ro, \ + .init = in, \ + .args = { \ + .tab_divider = { \ + .parent_name = #pn, \ + .shift = s, \ + .width = w, \ + .div_tab = tab, \ + .tab_divider_flags = tdf, \ + }, \ + } \ + } + +/* Arrays with parents */ + +static const char *soc_phi_parents[] = { + "soc_phia.%d", + "soc_phib.%d", +}; + +static const char *audio_pll_sdmmc_parents[] = { + "audio_pll_phi.%d", + "eaudio_pll_phi_div2.%d", +}; + +static const char *audio_pll_msp_parents[] = { + "audio_pll_phi_div4.%d", + "audio_pll_phi_div10.%d", +}; + +static const char *audio_pll_sarac_parents[] = { + "audio_pll_phi_div4.%d", + "audio_pll_phi.%d", +}; + +static const char *hclk_pre_parents[] = { + "soc_phi_byp_div3.%d", + "soc_phi_byp_div4.%d", + "soc_phi_byp_div6.%d", + "soc_phi_byp_div3.%d", +}; + +static const char *hclk_parents[] = { + "soc_phi_byp.%d", + "hclk_pre.%d", +}; + +/* + * Dividers' tables + */ + +/* phia odf */ +static struct clk_div_table phia_odf_tab[] = { + { + .val = 0, + .div = 1, + }, + { + .val = 1, + .div = 2, + }, + { + .val = 2, + .div = 4, + }, + { + .val = 3, + .div = 6, + }, + { + .val = 4, + .div = 8, + }, + { + .val = 5, + .div = 10, + }, + { + .val = 6, + .div = 12, + }, + { + .val = 7, + .div = 14, + }, + /* Terminator */ + { + .div = 0, + }, +}; + +/* phib dividers */ +static struct clk_div_table phib_div_tab[] = { + { + .val = 0, + .div = 3, + }, + { + .val = 1, + .div = 5, + }, + /* Terminator */ + { + .div = 0, + }, +}; + +/* + * Init functions + */ +/* + * Init function for soc-pll clock + */ +static void soc_pll_init(struct sta2x11_clk_data *cptr, + struct sta2x11_instance_data *id) +{ + cptr->args.soc_pll.base = id->sctl->regs; + cptr->args.soc_pll.lock = &id->sctl->lock; +} + +/* + * Init function for audio-pll clock + */ +static void audio_pll_init(struct sta2x11_clk_data *cptr, + struct sta2x11_instance_data *id) +{ + cptr->args.audio_pll.base = id->sctl->regs; + cptr->args.audio_pll.lock = &id->sctl->lock; +} + +/* + * Init functions for mux clocks + */ +static void +sctl_mux_clock_init(struct sta2x11_clk_data *cptr, + struct sta2x11_instance_data *id) +{ + cptr->args.mux.reg = id->sctl->regs + cptr->reg_offset; + cptr->args.mux.lock = &id->sctl->lock; +} + +/* + * Init function for tab divider clock + */ +static void +tab_divider_clock_init(struct sta2x11_clk_data *cptr, + struct sta2x11_instance_data *id) +{ + cptr->args.tab_divider.reg = id->sctl->regs + cptr->reg_offset; + cptr->args.tab_divider.lock = &id->sctl->lock; +} + +/* + * This table contains everything is needed to register all the clocks + * on a single connext instance + * + * TODO: this table shall be patched at startup to deal with the (very few + * at present) differences between STA2X11 based boards. + * + * TODO: complete this table with all the remaining clocks (mmc, msp, spi, ...) + */ +static struct sta2x11_clk_data clk_data[] = { + /* 24MHz refclk */ + DECLARE_FIXED_RATE_ROOT_CLK(xtal, 24000000), + /* Sata clk */ + DECLARE_FIXED_RATE_ROOT_CLK(sata, 100000000), + /* Eth clk */ + DECLARE_FIXED_RATE_ROOT_CLK(eth, 50000000), + /* Soc pll vco */ + DECLARE_SOC_PLL_CLK(soc_vco, xtal), + /* Soc pll vco dividers */ + DECLARE_TAB_DIVIDER_CLK(soc_phia, SCTL_SCPLLFCTRL, + soc_vco, tab_divider_clock_init, + 0, 0, 3, phia_odf_tab, 0), + DECLARE_TAB_DIVIDER_CLK(soc_phib, SCTL_SCCTL, soc_vco, + tab_divider_clock_init, + 0, 10, 1, phib_div_tab, 0), + DECLARE_MUX_CLK(soc_phi, SCTL_SCCTL, soc_phi_parents, + sctl_mux_clock_init, 0, 2, 1, 0), + /* + * TODO : IMPLEMENT THIS ONE AS A DIFFERENT TYPE OF CLOCK + * + * We need a mux clock controlled by a custom function: + * + * soc_phi_byp = soc_phi if pll is locked && !powered down && !bypassed + * soc_phi_byp = xtal otherwise + * + * For now we assume that the soc pll is never bypassed, so we just + * use a fixed factor clock to keep the correct names + */ + DECLARE_FIXED_FACTOR_CLK(soc_phi_byp, soc_phi, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(clk_48M, soc_phi_byp, 0, 1, 13), + DECLARE_FIXED_FACTOR_CLK(soc_phi_byp_div3, soc_phi_byp, 0, 1, 3), + DECLARE_FIXED_FACTOR_CLK(soc_phi_byp_div4, soc_phi_byp, 0, 1, 4), + DECLARE_FIXED_FACTOR_CLK(soc_phi_byp_div6, soc_phi_byp, 0, 1, 6), + DECLARE_FIXED_FACTOR_CLK(clk_52M, soc_phi_byp_div6, 0, 1, 2), + DECLARE_MUX_CLK(hclk_pre, SCTL_SCCTL, hclk_pre_parents, + sctl_mux_clock_init, 0, 0, 2, 0), + DECLARE_MUX_CLK(hclk, SCTL_SCCTL, hclk_parents, + sctl_mux_clock_init, 0, 9, 1, 0), + /* Audio pll derived clocks */ + DECLARE_AUDIO_PLL_CLK(audio_pll_phi, xtal), + DECLARE_FIXED_FACTOR_CLK(audio_pll_phi_div2, audio_pll_phi, 0, 1, 2), + DECLARE_FIXED_FACTOR_CLK(audio_pll_phi_div10, audio_pll_phi, 0, 1, 10), + DECLARE_FIXED_FACTOR_CLK(audio_pll_phi_div4, audio_pll_phi_div2, + 0, 1, 2), + DECLARE_MUX_CLK(audio_pll_sdmmc, SCTL_SCCTL, audio_pll_sdmmc_parents, + sctl_mux_clock_init, 0, 8, 1, 0), + DECLARE_MUX_CLK(audio_pll_msp, SCTL_SCCTL, audio_pll_msp_parents, + sctl_mux_clock_init, 0, 8, 1, 0), + DECLARE_MUX_CLK(audio_pll_sarac, SCTL_SCCTL, audio_pll_sarac_parents, + sctl_mux_clock_init, 0, 8, 1, 0), + /* Peripheral clocks for uarts. TODO: implement these as gated clocks */ + DECLARE_FIXED_FACTOR_CLK(hclk_uart0, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_uart1, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_uart2, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_uart3, hclk, 0, 1, 1), + /* Baud rate clocks for uarts. TODO: implement these as gated clocks */ + DECLARE_FIXED_FACTOR_CLK(bclk_uart0, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_uart1, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_uart2, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_uart3, clk_48M, 0, 1, 1), + /* Peripheral clocks for gpios. TODO: implement these as gated clocks */ + DECLARE_FIXED_FACTOR_CLK(hclk_gpio0, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_gpio1, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_gpio2, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_gpio3, hclk, 0, 1, 1), + /* Stmmac clock. TODO: implement this as a mux with sata */ + DECLARE_FIXED_FACTOR_CLK(stmmac_rmii, eth, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(stmmac_csr, stmmac_rmii, 0, 1, 2), + /* sdmmc CLOCKS: FIXME: MUXED CLOCKS */ + DECLARE_FIXED_FACTOR_CLK(sdmmc0, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(sdmmc1, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(sdmmc2, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(sdmmc3, clk_48M, 0, 1, 1), + /* Peripheral clocks for sdios */ + DECLARE_FIXED_FACTOR_CLK(hclk_sdio0, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_sdio1, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_sdio2, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_sdio3, hclk, 0, 1, 1), + /* Peripheral clocks for dma's */ + DECLARE_FIXED_FACTOR_CLK(hclk_soc_dma, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_audio_dma, hclk, 0, 1, 1), + /* Peripheral clocks for i2cs */ + DECLARE_FIXED_FACTOR_CLK(hclk_i2c0, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_i2c1, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_i2c2, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_i2c3, hclk, 0, 1, 1), + /* i2c clocks */ + DECLARE_FIXED_FACTOR_CLK(bclk_i2c0, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_i2c1, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_i2c2, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_i2c3, clk_48M, 0, 1, 1), + /* Peripheral clocks for spis */ + DECLARE_FIXED_FACTOR_CLK(hclk_spi0, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_spi1, hclk, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(hclk_spi2, hclk, 0, 1, 1), + /* spi clocks */ + DECLARE_FIXED_FACTOR_CLK(bclk_spi0, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_spi1, clk_48M, 0, 1, 1), + DECLARE_FIXED_FACTOR_CLK(bclk_spi2, clk_48M, 0, 1, 1), +}; + +/* + * Inline helpers + */ +static inline int get_sdio_clk_index(unsigned int fn) +{ + switch(fn) { + case 2: + return sdmmc1; + case 3: + return sdmmc2; + case 4: + return sdmmc3; + default: + return sta2x11_n_clks; + } +} + +static inline int get_i2c_bclk_index(unsigned int fn) +{ + return bclk_i2c0 + fn - 1; +} + +static inline int get_spi_bclk_index(unsigned int fn) +{ + return bclk_spi0 + fn - 2; +} + +/* + * The following code registers various clock types for the connext + */ + +typedef struct clk *(regfunc)(struct sta2x11_clk_data *, const char *, int); + +static __init struct clk * +do_register_fixed_rate_root(struct sta2x11_clk_data *cptr, + const char *name, + int instance_id) +{ + pr_debug("Registering fixed rate root clock %s, rate = %lu\n", + name, cptr->args.fixed_rate_root.rate); + return clk_register_fixed_rate(NULL, + name, + NULL, + cptr->flags, + cptr->args.fixed_rate_root.rate); +} + +static __init struct clk * +do_register_fixed_factor(struct sta2x11_clk_data *cptr, + const char *name, int instance_id) +{ + char parent_name[STA2X11_MAX_CLK_NAME_LEN]; + snprintf(parent_name, sizeof(parent_name), "%s.%d", + cptr->args.fixed_factor.parent_name, instance_id); + return clk_register_fixed_factor(NULL, name, + parent_name, + cptr->flags, + cptr->args.fixed_factor.mult, + cptr->args.fixed_factor.div); +} + +static __init struct clk * +do_register_mux(struct sta2x11_clk_data *cptr, + const char *name, int instance_id) +{ + int i, nparents = cptr->args.mux.num_parents; + char *parents[STA2X11_MAX_CLK_NPARENTS], *ptr; + char parent_names[STA2X11_MAX_CLK_NPARENTS*STA2X11_MAX_CLK_NAME_LEN]; + if (nparents > STA2X11_MAX_CLK_NPARENTS) + return ERR_PTR(-ENOMEM); + for (i = 0, ptr = parent_names; i < nparents; i++, + ptr += STA2X11_MAX_CLK_NAME_LEN) { + snprintf(ptr, STA2X11_MAX_CLK_NAME_LEN, + cptr->args.mux.parent_names[i], instance_id); + parents[i] = ptr; + } + return clk_register_mux(NULL, name, (const char **)parents, + cptr->args.mux.num_parents, cptr->flags, + cptr->args.mux.reg, cptr->args.mux.shift, + cptr->args.mux.width, + cptr->args.mux.clk_mux_flags, + cptr->args.mux.lock); +} + +static __init struct clk * +do_register_tab_divider(struct sta2x11_clk_data *cptr, + const char *name, int instance_id) +{ + char parent_name[STA2X11_MAX_CLK_NAME_LEN]; + snprintf(parent_name, sizeof(parent_name), "%s.%d", + cptr->args.tab_divider.parent_name, instance_id); + pr_debug("%s: registering tab_divider clock %s\n", __func__, name); + pr_debug("%s: parent = %s\n", __func__, parent_name); + return clk_register_divider_table(NULL, name, parent_name, + 0, cptr->args.tab_divider.reg, + cptr->args.tab_divider.shift, + cptr->args.tab_divider.width, + 0, cptr->args.tab_divider.div_tab, + cptr->args.tab_divider.lock); +} + +static __init struct clk * +do_register_soc_pll(struct sta2x11_clk_data *cptr, + const char *name, int instance_id) +{ + char parent_name[STA2X11_MAX_CLK_NAME_LEN]; + snprintf(parent_name, sizeof(parent_name), "%s.%d", + cptr->args.soc_pll.parent_name, instance_id); + pr_debug("%s: registering soc_pll clock %s\n", __func__, name); + pr_debug("%s: parent = %s\n", __func__, parent_name); + return register_sta2x11_clk_soc_pll(name, parent_name, + cptr->args.soc_pll.base, + cptr->args.soc_pll.lock); +} + +static __init struct clk * +do_register_audio_pll(struct sta2x11_clk_data *cptr, + const char *name, int instance_id) +{ + char parent_name[STA2X11_MAX_CLK_NAME_LEN]; + snprintf(parent_name, sizeof(parent_name), "%s.%d", + cptr->args.audio_pll.parent_name, instance_id); + pr_debug("%s: registering audio_pll clock %s\n", __func__, name); + pr_debug("%s: parent = %s\n", __func__, parent_name); + return register_sta2x11_clk_audio_pll(name, parent_name, + cptr->args.audio_pll.base, + cptr->args.audio_pll.lock); +} + +/* + * This function registers all the clocks listed in the clk_data table + * Such table is static and can be modified on a per-board basis at startup. + */ +static regfunc * regfuncs[] = { + [fixed_rate_root] = do_register_fixed_rate_root, + [fixed_factor] = do_register_fixed_factor, + [mux] = do_register_mux, + [tab_divider] = do_register_tab_divider, + [soc_pll] = do_register_soc_pll, + [audio_pll] = do_register_audio_pll, +}; + +static int register_clocks(struct sta2x11_instance_data *id, + struct platform_device *dev) +{ + int i; + struct sta2x11_clk_data *cptr; + struct clk *clk, **clks; + + /* + * When this function is called, kmalloc already works, so we should + * have no problem using it + */ + clks = kzalloc(sta2x11_n_clks * sizeof(struct clk *), GFP_KERNEL); + if (!clks) + return -ENOMEM; + + for (i = 0, cptr = clk_data; i < ARRAY_SIZE(clk_data); i++, cptr++) { + /* + * name can be on stack, since the clock framework does + * kstrdup on register + */ + char name[STA2X11_MAX_CLK_NAME_LEN]; + if (cptr->type < 0 || cptr->type > sta2x11_clk_ntypes) { + pr_err("%s: invalid type %d for clk %s, skipping\n", + __func__, cptr->type, + cptr->basename ? cptr->basename : "UNKNOWN"); + continue; + } + if (cptr->type == none) + /* Clock not implemented on this board */ + continue; + if (!regfuncs[cptr->type]) { + pr_err("%s : no regfunc for clk %s, skipping\n", + __func__, cptr->basename ? cptr->basename : + "UNKNOWN"); + continue; + } + /* + * Set up a clock name by adding an instance id to its + * basename + */ + snprintf(name, sizeof(name), "%s.%d", cptr->basename, + id->id); + /* + * This should add runtime data to the clock. In particular, + * it should add the controlling register's virtual address + * (which is unknown at compile time) + */ + if (cptr->init) + cptr->init(cptr, id); + /* Ok, now just register the clock */ + clk = regfuncs[cptr->type](cptr, name, id->id); + if (IS_ERR(clk)) { + pr_err("%s error registering clock %s\n", + __func__, name); + } else { + pr_info("%s: registered clock %s\n", __func__, name); + clks[i] = clk; + /* + A lookup is also added for each of the registered + clocks + */ + clk_register_clkdev(clks[i], name, NULL); + } + } + /* Finally assign registered clocks to the instance they belong to */ + platform_set_drvdata(dev, clks); + return 0; +} + +/* + * Driver for the sta2x11-clock-regs platform device + */ +static int sta2x11_clock_regs_probe(struct platform_device *dev) +{ + struct sta2x11_instance_data *id = dev_get_platdata(&dev->dev); + + dev_dbg(&dev->dev, "%s entered, id = %p", __func__, id); + if (!id) + return -EINVAL; + return register_clocks(id, dev); +} + +static struct platform_driver sta2x11_clock_regs_driver = { + .driver = { + .name = STA2X11_CLOCK_REGS_NAME, + .owner = THIS_MODULE, + }, + .probe = sta2x11_clock_regs_probe, +}; + +/* + * This function just registers a notifier for the platform bus to know + * when the sta2x11-sctl and sta2x11-apb-soc-regs devices have been bound + * to the relevant drivers + */ +static int __init sta2x11_init_clocks(void) +{ + return platform_driver_register(&sta2x11_clock_regs_driver); +} +subsys_initcall(sta2x11_init_clocks); + +static struct clk **get_instance_clocks(struct sta2x11_instance_data *idata) +{ + struct device *d, *start; + char name[strlen(STA2X11_CLOCK_REGS_NAME) + 3]; + snprintf(name, sizeof(name), "%s.%d", + STA2X11_CLOCK_REGS_NAME, idata->id); + for (start = NULL; ; start = d) { + d = bus_find_device_by_name(&platform_bus_type, start, name); + if (!d) + break; + WARN_ON(dev_get_platdata(d) != idata); + return platform_get_drvdata(to_platform_device(d)); + } + return NULL; +} + +/* + * This is invoked on pci_enable() for every connext pci device + * + * It registers a lookup for the device's clock (if needed), so that + * the driver can find it. + */ +static void clk_new_pdev(struct pci_dev *pdev) +{ + struct sta2x11_instance_data *instance = + sta2x11_dev_to_instance(&pdev->dev); + /* Initialize with an invalid index */ + enum sta2x11_clk clk_index = sta2x11_n_clks; + int sdio_index = -1; + struct clk **clks; + char *name; + if (!instance) + /* + * Just ignore devices not belonging to the connext chip + */ + return; + clks = get_instance_clocks(instance); + if (!clks) { + /* + If this is a connext device, it should have a corresponding + clocks device + */ + WARN_ON(1); + return; + } + + switch (pdev->device) { + case PCI_DEVICE_ID_STMICRO_UART_HWFC: + clk_index = pdev->devfn == 5 ? bclk_uart2 : bclk_uart3; + /* FALL THROUGH */ + case PCI_DEVICE_ID_STMICRO_UART_NO_HWFC: + if (clk_index == sta2x11_n_clks) + clk_index = + (pdev->devfn == 5 ? bclk_uart0 : bclk_uart1); + if (pci_amba_get_dev_name(&name, pdev, 0) < 0) { + /* Could get no name for the pci-amba device */ + WARN_ON(1); + return; + } + clk_register_clkdev(clks[clk_index], NULL, name); + break; + case PCI_DEVICE_ID_STMICRO_MAC: + clk_register_clkdev(clks[stmmac_csr], NULL, + dev_name(&pdev->dev)); + break; + case PCI_DEVICE_ID_STMICRO_GPIO: + { + int i; + for (i = 0; i < 4; i++) { + if (pci_amba_get_dev_name(&name, pdev, i) < 0) { + /* Could get no name for the pci-amba device */ + WARN_ON(1); + continue; + } + clk_register_clkdev(clks[hclk_gpio0 + i], NULL, name); + } + break; + } + case PCI_DEVICE_ID_STMICRO_SDIO_EMMC: + sdio_index = sdmmc0; + /* FALL THROUGH */ + case PCI_DEVICE_ID_STMICRO_SDIO: + if (pci_amba_get_dev_name(&name, pdev, 0) < 0) { + /* Could get no name for the pci-amba device */ + WARN_ON(1); + return; + } + if (sdio_index < 0) + sdio_index = get_sdio_clk_index(pdev->devfn); + if (sdio_index >= sta2x11_n_clks) { + pr_err("Unknown sdio device, not registering clock\n"); + break; + } + clk_register_clkdev(clks[sdio_index], NULL, name, + pdev->bus->number, pdev->devfn); + break; + case PCI_DEVICE_ID_STMICRO_AUDIO_ROUTER_DMA: + clk_index = hclk_audio_dma; + /* FALL THROUGH */ + case PCI_DEVICE_ID_STMICRO_SOC_DMA: + if (clk_index == sta2x11_n_clks) + clk_index = hclk_soc_dma; + if (pci_amba_get_dev_name(&name, pdev, 0) < 0) { + /* Could get no name for the pci-amba device */ + WARN_ON(1); + return; + } + clk_register_clkdev(clks[clk_index], NULL, name); + break; + case PCI_DEVICE_ID_STMICRO_I2C: + if (pci_amba_get_dev_name(&name, pdev, 0) < 0) { + /* Could get no name for the pci-amba device */ + WARN_ON(1); + return; + } + clk_index = get_i2c_bclk_index(pdev->devfn); + clk_register_clkdev(clks[clk_index], NULL, name); + break; + case PCI_DEVICE_ID_STMICRO_SPI_HS: + if (pci_amba_get_dev_name(&name, pdev, 0) < 0) { + /* Could get no name for the pci-amba device */ + WARN_ON(1); + return; + } + clk_index = get_spi_bclk_index(pdev->devfn); + clk_register_clkdev(clks[clk_index], NULL, name); + break; + /* TODO : ADD MORE ID's HERE */ + default: + dev_dbg(&pdev->dev, "clk: ignoring device\n"); + break; + } +} +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_STMICRO, PCI_ANY_ID, clk_new_pdev); diff --git a/drivers/clk/sta2x11/clk.h b/drivers/clk/sta2x11/clk.h new file mode 100644 index 0000000..7a2e0ef --- /dev/null +++ b/drivers/clk/sta2x11/clk.h @@ -0,0 +1,88 @@ +/* + * Copyright ST Microelectronics 2012 + * Author: Davide Ciminaghi <ciminaghi@xxxxxxxxx> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Common clock api implementation for sta2x11, main header file + */ +#ifndef __STA2X11_CLK_H__ +#define __STA2X11_CLK_H__ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/spinlock.h> + +/* + * Indices for connext clocks + */ +enum sta2x11_clk { + xtal, sata, pcie, sdvo, eth, usb, + soc_vco, soc_phia, soc_phib, audio_pll_phi, + soc_phi, soc_phi_byp, + soc_phi_byp_div3, soc_phi_byp_div4, soc_phi_byp_div6, + audio_pll_phi_div2, audio_pll_phi_div4, audio_pll_phi_div10, + audio_pll_msp, audio_pll_sarac, audio_pll_sdmmc, + hclk_pre, hclk, clk_48M, clk_52M, + /* Uarts' peripheral clocks */ + hclk_uart0, hclk_uart1, hclk_uart2, hclk_uart3, + /* Uarts' baud rate clocks */ + bclk_uart0, bclk_uart1, bclk_uart2, bclk_uart3, + /* gpios clock */ + hclk_gpio0, hclk_gpio1, hclk_gpio2, hclk_gpio3, + /* sdios clocks */ + hclk_sdio0, hclk_sdio1, hclk_sdio2, hclk_sdio3, + /* soc dma clock */ + hclk_soc_dma, + /* audio dma clock */ + hclk_audio_dma, + /* i2c clocks */ + hclk_i2c0, hclk_i2c1, hclk_i2c2, hclk_i2c3, + bclk_i2c0, bclk_i2c1, bclk_i2c2, bclk_i2c3, + /* spi clocks */ + hclk_spi0, hclk_spi1, hclk_spi2, + bclk_spi0, bclk_spi1, bclk_spi2, + sdmmc0, sdmmc1, sdmmc2, sdmmc3, + stmmac_csr, stmmac_rmii, + sta2x11_n_clks, +}; + +/* + * Clock types used on the connext + * + * By convention, a clock listed in clk_data[] with type == none, is not + * registered (usually because the clock itself is not active on the + * board the kernel is being run on). + */ +enum sta2x11_clk_type { + /* Not present on this board */ + none = 0, + fixed_rate_root = 1, + fixed_factor = 2, + mux = 3, + tab_divider = 4, + soc_pll = 5, + audio_pll = 6, + sta2x11_clk_ntypes, +}; + +struct clk *register_sta2x11_clk_soc_pll(const char *name, + const char *parent_name, + void __iomem *reg, + spinlock_t *lock); +struct clk *register_sta2x11_clk_audio_pll(const char *name, + const char *parent_name, + void __iomem *base, + spinlock_t *lock); +#endif /* __STA2X11_CLK_H__ */ -- 1.7.7.2 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html