diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/clk/ti/clkctrl.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/drivers/clk/ti/clkctrl.c b/drivers/clk/ti/clkctrl.c new file mode 100644 index 000000000..2c2564aca --- /dev/null +++ b/drivers/clk/ti/clkctrl.c @@ -0,0 +1,580 @@ +/* + * OMAP clkctrl clock support + * + * Copyright (C) 2017 Texas Instruments, Inc. + * + * Tero Kristo <t-kristo@ti.com> + * + * 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 "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/clk/ti.h> +#include <linux/delay.h> +#include <linux/timekeeping.h> +#include "clock.h" + +#define NO_IDLEST 0x1 + +#define OMAP4_MODULEMODE_MASK 0x3 + +#define MODULEMODE_HWCTRL 0x1 +#define MODULEMODE_SWCTRL 0x2 + +#define OMAP4_IDLEST_MASK (0x3 << 16) +#define OMAP4_IDLEST_SHIFT 16 + +#define CLKCTRL_IDLEST_FUNCTIONAL 0x0 +#define CLKCTRL_IDLEST_INTERFACE_IDLE 0x2 +#define CLKCTRL_IDLEST_DISABLED 0x3 + +/* These timeouts are in us */ +#define OMAP4_MAX_MODULE_READY_TIME 2000 +#define OMAP4_MAX_MODULE_DISABLE_TIME 5000 + +static bool _early_timeout = true; + +struct omap_clkctrl_provider { + void __iomem *base; + struct list_head clocks; + char *clkdm_name; +}; + +struct omap_clkctrl_clk { + struct clk_hw *clk; + u16 reg_offset; + int bit_offset; + struct list_head node; +}; + +union omap4_timeout { + u32 cycles; + ktime_t start; +}; + +static const struct omap_clkctrl_data default_clkctrl_data[] __initconst = { + { 0 }, +}; + +static u32 _omap4_idlest(u32 val) +{ + val &= OMAP4_IDLEST_MASK; + val >>= OMAP4_IDLEST_SHIFT; + + return val; +} + +static bool _omap4_is_idle(u32 val) +{ + val = _omap4_idlest(val); + + return val == CLKCTRL_IDLEST_DISABLED; +} + +static bool _omap4_is_ready(u32 val) +{ + val = _omap4_idlest(val); + + return val == CLKCTRL_IDLEST_FUNCTIONAL || + val == CLKCTRL_IDLEST_INTERFACE_IDLE; +} + +static bool _omap4_is_timeout(union omap4_timeout *time, u32 timeout) +{ + /* + * There are two special cases where ktime_to_ns() can't be + * used to track the timeouts. First one is during early boot + * when the timers haven't been initialized yet. The second + * one is during suspend-resume cycle while timekeeping is + * being suspended / resumed. Clocksource for the system + * can be from a timer that requires pm_runtime access, which + * will eventually bring us here with timekeeping_suspended, + * during both suspend entry and resume paths. This happens + * at least on am43xx platform. Account for flakeyness + * with udelay() by multiplying the timeout value by 2. + */ + if (unlikely(_early_timeout || timekeeping_suspended)) { + if (time->cycles++ < timeout) { + udelay(1 * 2); + return false; + } + } else { + if (!ktime_to_ns(time->start)) { + time->start = ktime_get(); + return false; + } + + if (ktime_us_delta(ktime_get(), time->start) < timeout) { + cpu_relax(); + return false; + } + } + + return true; +} + +static int __init _omap4_disable_early_timeout(void) +{ + _early_timeout = false; + + return 0; +} +arch_initcall(_omap4_disable_early_timeout); + +static int _omap4_clkctrl_clk_enable(struct clk_hw *hw) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 val; + int ret; + union omap4_timeout timeout = { 0 }; + + if (clk->clkdm) { + ret = ti_clk_ll_ops->clkdm_clk_enable(clk->clkdm, hw->clk); + if (ret) { + WARN(1, + "%s: could not enable %s's clockdomain %s: %d\n", + __func__, clk_hw_get_name(hw), + clk->clkdm_name, ret); + return ret; + } + } + + if (!clk->enable_bit) + return 0; + + val = ti_clk_ll_ops->clk_readl(&clk->enable_reg); + + val &= ~OMAP4_MODULEMODE_MASK; + val |= clk->enable_bit; + + ti_clk_ll_ops->clk_writel(val, &clk->enable_reg); + + if (clk->flags & NO_IDLEST) + return 0; + + /* Wait until module is enabled */ + while (!_omap4_is_ready(ti_clk_ll_ops->clk_readl(&clk->enable_reg))) { + if (_omap4_is_timeout(&timeout, OMAP4_MAX_MODULE_READY_TIME)) { + pr_err("%s: failed to enable\n", clk_hw_get_name(hw)); + return -EBUSY; + } + } + + return 0; +} + +static void _omap4_clkctrl_clk_disable(struct clk_hw *hw) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 val; + union omap4_timeout timeout = { 0 }; + + if (!clk->enable_bit) + goto exit; + + val = ti_clk_ll_ops->clk_readl(&clk->enable_reg); + + val &= ~OMAP4_MODULEMODE_MASK; + + ti_clk_ll_ops->clk_writel(val, &clk->enable_reg); + + if (clk->flags & NO_IDLEST) + goto exit; + + /* Wait until module is disabled */ + while (!_omap4_is_idle(ti_clk_ll_ops->clk_readl(&clk->enable_reg))) { + if (_omap4_is_timeout(&timeout, + OMAP4_MAX_MODULE_DISABLE_TIME)) { + pr_err("%s: failed to disable\n", clk_hw_get_name(hw)); + break; + } + } + +exit: + if (clk->clkdm) + ti_clk_ll_ops->clkdm_clk_disable(clk->clkdm, hw->clk); +} + +static int _omap4_clkctrl_clk_is_enabled(struct clk_hw *hw) +{ + struct clk_hw_omap *clk = to_clk_hw_omap(hw); + u32 val; + + val = ti_clk_ll_ops->clk_readl(&clk->enable_reg); + + if (val & clk->enable_bit) + return 1; + + return 0; +} + +static const struct clk_ops omap4_clkctrl_clk_ops = { + .enable = _omap4_clkctrl_clk_enable, + .disable = _omap4_clkctrl_clk_disable, + .is_enabled = _omap4_clkctrl_clk_is_enabled, + .init = omap2_init_clk_clkdm, +}; + +static struct clk_hw *_ti_omap4_clkctrl_xlate(struct of_phandle_args *clkspec, + void *data) +{ + struct omap_clkctrl_provider *provider = data; + struct omap_clkctrl_clk *entry; + bool found = false; + + if (clkspec->args_count != 2) + return ERR_PTR(-EINVAL); + + pr_debug("%s: looking for %x:%x\n", __func__, + clkspec->args[0], clkspec->args[1]); + + list_for_each_entry(entry, &provider->clocks, node) { + if (entry->reg_offset == clkspec->args[0] && + entry->bit_offset == clkspec->args[1]) { + found = true; + break; + } + } + + if (!found) + return ERR_PTR(-EINVAL); + + return entry->clk; +} + +static int __init +_ti_clkctrl_clk_register(struct omap_clkctrl_provider *provider, + struct device_node *node, struct clk_hw *clk_hw, + u16 offset, u8 bit, const char * const *parents, + int num_parents, const struct clk_ops *ops) +{ + struct clk_init_data init = { NULL }; + struct clk *clk; + struct omap_clkctrl_clk *clkctrl_clk; + int ret = 0; + + init.name = kasprintf(GFP_KERNEL, "%s:%s:%04x:%d", node->parent->name, + node->name, offset, bit); + clkctrl_clk = kzalloc(sizeof(*clkctrl_clk), GFP_KERNEL); + if (!init.name || !clkctrl_clk) { + ret = -ENOMEM; + goto cleanup; + } + + clk_hw->init = &init; + init.parent_names = parents; + init.num_parents = num_parents; + init.ops = ops; + init.flags = CLK_IS_BASIC; + + clk = ti_clk_register(NULL, clk_hw, init.name); + if (IS_ERR_OR_NULL(clk)) { + ret = -EINVAL; + goto cleanup; + } + + clkctrl_clk->reg_offset = offset; + clkctrl_clk->bit_offset = bit; + clkctrl_clk->clk = clk_hw; + + list_add(&clkctrl_clk->node, &provider->clocks); + + return 0; + +cleanup: + kfree(init.name); + kfree(clkctrl_clk); + return ret; +} + +static void __init +_ti_clkctrl_setup_gate(struct omap_clkctrl_provider *provider, + struct device_node *node, u16 offset, + const struct omap_clkctrl_bit_data *data, + void __iomem *reg) +{ + struct clk_hw_omap *clk_hw; + + clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); + if (!clk_hw) + return; + + clk_hw->enable_bit = data->bit; + clk_hw->enable_reg.ptr = reg; + + if (_ti_clkctrl_clk_register(provider, node, &clk_hw->hw, offset, + data->bit, data->parents, 1, + &omap_gate_clk_ops)) + kfree(clk_hw); +} + +static void __init +_ti_clkctrl_setup_mux(struct omap_clkctrl_provider *provider, + struct device_node *node, u16 offset, + const struct omap_clkctrl_bit_data *data, + void __iomem *reg) +{ + struct clk_omap_mux *mux; + int num_parents = 0; + const char * const *pname; + + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return; + + pname = data->parents; + while (*pname) { + num_parents++; + pname++; + } + + mux->mask = num_parents; + if (!(mux->flags & CLK_MUX_INDEX_ONE)) + mux->mask--; + + mux->mask = (1 << fls(mux->mask)) - 1; + + mux->shift = data->bit; + mux->reg.ptr = reg; + + if (_ti_clkctrl_clk_register(provider, node, &mux->hw, offset, + data->bit, data->parents, num_parents, + &ti_clk_mux_ops)) + kfree(mux); +} + +static void __init +_ti_clkctrl_setup_div(struct omap_clkctrl_provider *provider, + struct device_node *node, u16 offset, + const struct omap_clkctrl_bit_data *data, + void __iomem *reg) +{ + struct clk_omap_divider *div; + const struct omap_clkctrl_div_data *div_data = data->data; + u8 div_flags = 0; + + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + return; + + div->reg.ptr = reg; + div->shift = data->bit; + div->flags = div_data->flags; + + if (div->flags & CLK_DIVIDER_POWER_OF_TWO) + div_flags |= CLKF_INDEX_POWER_OF_TWO; + + if (ti_clk_parse_divider_data((int *)div_data->dividers, 0, + div_data->max_div, div_flags, + &div->width, &div->table)) { + pr_err("%s: Data parsing for %pOF:%04x:%d failed\n", __func__, + node, offset, data->bit); + kfree(div); + return; + } + + if (_ti_clkctrl_clk_register(provider, node, &div->hw, offset, + data->bit, data->parents, 1, + &ti_clk_divider_ops)) + kfree(div); +} + +static void __init +_ti_clkctrl_setup_subclks(struct omap_clkctrl_provider *provider, + struct device_node *node, + const struct omap_clkctrl_reg_data *data, + void __iomem *reg) +{ + const struct omap_clkctrl_bit_data *bits = data->bit_data; + + if (!bits) + return; + + while (bits->bit) { + switch (bits->type) { + case TI_CLK_GATE: + _ti_clkctrl_setup_gate(provider, node, data->offset, + bits, reg); + break; + + case TI_CLK_DIVIDER: + _ti_clkctrl_setup_div(provider, node, data->offset, + bits, reg); + break; + + case TI_CLK_MUX: + _ti_clkctrl_setup_mux(provider, node, data->offset, + bits, reg); + break; + + default: + pr_err("%s: bad subclk type: %d\n", __func__, + bits->type); + return; + } + bits++; + } +} + +static void __init _clkctrl_add_provider(void *data, + struct device_node *np) +{ + of_clk_add_hw_provider(np, _ti_omap4_clkctrl_xlate, data); +} + +static void __init _ti_omap4_clkctrl_setup(struct device_node *node) +{ + struct omap_clkctrl_provider *provider; + const struct omap_clkctrl_data *data = default_clkctrl_data; + const struct omap_clkctrl_reg_data *reg_data; + struct clk_init_data init = { NULL }; + struct clk_hw_omap *hw; + struct clk *clk; + struct omap_clkctrl_clk *clkctrl_clk; + const __be32 *addrp; + u32 addr; + int ret; + + addrp = of_get_address(node, 0, NULL, NULL); + addr = (u32)of_translate_address(node, addrp); + +#ifdef CONFIG_ARCH_OMAP4 + if (of_machine_is_compatible("ti,omap4")) + data = omap4_clkctrl_data; +#endif +#ifdef CONFIG_SOC_OMAP5 + if (of_machine_is_compatible("ti,omap5")) + data = omap5_clkctrl_data; +#endif +#ifdef CONFIG_SOC_DRA7XX + if (of_machine_is_compatible("ti,dra7")) + data = dra7_clkctrl_data; +#endif +#ifdef CONFIG_SOC_AM33XX + if (of_machine_is_compatible("ti,am33xx")) + data = am3_clkctrl_data; +#endif +#ifdef CONFIG_SOC_AM43XX + if (of_machine_is_compatible("ti,am4372")) + data = am4_clkctrl_data; + if (of_machine_is_compatible("ti,am438x")) + data = am438x_clkctrl_data; +#endif +#ifdef CONFIG_SOC_TI81XX + if (of_machine_is_compatible("ti,dm814")) + data = dm814_clkctrl_data; + + if (of_machine_is_compatible("ti,dm816")) + data = dm816_clkctrl_data; +#endif + + while (data->addr) { + if (addr == data->addr) + break; + + data++; + } + + if (!data->addr) { + pr_err("%pOF not found from clkctrl data.\n", node); + return; + } + + provider = kzalloc(sizeof(*provider), GFP_KERNEL); + if (!provider) + return; + + provider->base = of_iomap(node, 0); + + provider->clkdm_name = kmalloc(strlen(node->parent->name) + 3, + GFP_KERNEL); + if (!provider->clkdm_name) { + kfree(provider); + return; + } + + /* + * Create default clkdm name, replace _cm from end of parent node + * name with _clkdm + */ + strcpy(provider->clkdm_name, node->parent->name); + provider->clkdm_name[strlen(provider->clkdm_name) - 2] = 0; + strcat(provider->clkdm_name, "clkdm"); + + INIT_LIST_HEAD(&provider->clocks); + + /* Generate clocks */ + reg_data = data->regs; + + while (reg_data->parent) { + hw = kzalloc(sizeof(*hw), GFP_KERNEL); + if (!hw) + return; + + hw->enable_reg.ptr = provider->base + reg_data->offset; + + _ti_clkctrl_setup_subclks(provider, node, reg_data, + hw->enable_reg.ptr); + + if (reg_data->flags & CLKF_SW_SUP) + hw->enable_bit = MODULEMODE_SWCTRL; + if (reg_data->flags & CLKF_HW_SUP) + hw->enable_bit = MODULEMODE_HWCTRL; + if (reg_data->flags & CLKF_NO_IDLEST) + hw->flags |= NO_IDLEST; + + if (reg_data->clkdm_name) + hw->clkdm_name = reg_data->clkdm_name; + else + hw->clkdm_name = provider->clkdm_name; + + init.parent_names = ®_data->parent; + init.num_parents = 1; + init.flags = 0; + if (reg_data->flags & CLKF_SET_RATE_PARENT) + init.flags |= CLK_SET_RATE_PARENT; + init.name = kasprintf(GFP_KERNEL, "%s:%s:%04x:%d", + node->parent->name, node->name, + reg_data->offset, 0); + clkctrl_clk = kzalloc(sizeof(*clkctrl_clk), GFP_KERNEL); + if (!init.name || !clkctrl_clk) + goto cleanup; + + init.ops = &omap4_clkctrl_clk_ops; + hw->hw.init = &init; + + clk = ti_clk_register(NULL, &hw->hw, init.name); + if (IS_ERR_OR_NULL(clk)) + goto cleanup; + + clkctrl_clk->reg_offset = reg_data->offset; + clkctrl_clk->clk = &hw->hw; + + list_add(&clkctrl_clk->node, &provider->clocks); + + reg_data++; + } + + ret = of_clk_add_hw_provider(node, _ti_omap4_clkctrl_xlate, provider); + if (ret == -EPROBE_DEFER) + ti_clk_retry_init(node, provider, _clkctrl_add_provider); + + return; + +cleanup: + kfree(hw); + kfree(init.name); + kfree(clkctrl_clk); +} +CLK_OF_DECLARE(ti_omap4_clkctrl_clock, "ti,clkctrl", + _ti_omap4_clkctrl_setup); |