Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Serge Semin | 2904 | 100.00% | 3 | 100.00% |
Total | 2904 | 3 |
// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC * * Authors: * Serge Semin <Sergey.Semin@baikalelectronics.ru> * Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru> * * Baikal-T1 CCU Dividers interface driver */ #define pr_fmt(fmt) "bt1-ccu-div: " fmt #include <linux/kernel.h> #include <linux/printk.h> #include <linux/bits.h> #include <linux/bitfield.h> #include <linux/slab.h> #include <linux/clk-provider.h> #include <linux/of.h> #include <linux/spinlock.h> #include <linux/regmap.h> #include <linux/delay.h> #include <linux/time64.h> #include <linux/debugfs.h> #include "ccu-div.h" #define CCU_DIV_CTL 0x00 #define CCU_DIV_CTL_EN BIT(0) #define CCU_DIV_CTL_RST BIT(1) #define CCU_DIV_CTL_SET_CLKDIV BIT(2) #define CCU_DIV_CTL_CLKDIV_FLD 4 #define CCU_DIV_CTL_CLKDIV_MASK(_width) \ GENMASK((_width) + CCU_DIV_CTL_CLKDIV_FLD - 1, CCU_DIV_CTL_CLKDIV_FLD) #define CCU_DIV_CTL_LOCK_SHIFTED BIT(27) #define CCU_DIV_CTL_GATE_REF_BUF BIT(28) #define CCU_DIV_CTL_LOCK_NORMAL BIT(31) #define CCU_DIV_LOCK_CHECK_RETRIES 50 #define CCU_DIV_CLKDIV_MIN 0 #define CCU_DIV_CLKDIV_MAX(_mask) \ ((_mask) >> CCU_DIV_CTL_CLKDIV_FLD) /* * Use the next two methods until there are generic field setter and * getter available with non-constant mask support. */ static inline u32 ccu_div_get(u32 mask, u32 val) { return (val & mask) >> CCU_DIV_CTL_CLKDIV_FLD; } static inline u32 ccu_div_prep(u32 mask, u32 val) { return (val << CCU_DIV_CTL_CLKDIV_FLD) & mask; } static inline unsigned long ccu_div_lock_delay_ns(unsigned long ref_clk, unsigned long div) { u64 ns = 4ULL * (div ?: 1) * NSEC_PER_SEC; do_div(ns, ref_clk); return ns; } static inline unsigned long ccu_div_calc_freq(unsigned long ref_clk, unsigned long div) { return ref_clk / (div ?: 1); } static int ccu_div_var_update_clkdiv(struct ccu_div *div, unsigned long parent_rate, unsigned long divider) { unsigned long nd; u32 val = 0; u32 lock; int count; nd = ccu_div_lock_delay_ns(parent_rate, divider); if (div->features & CCU_DIV_LOCK_SHIFTED) lock = CCU_DIV_CTL_LOCK_SHIFTED; else lock = CCU_DIV_CTL_LOCK_NORMAL; regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_SET_CLKDIV, CCU_DIV_CTL_SET_CLKDIV); /* * Until there is nsec-version of readl_poll_timeout() is available * we have to implement the next polling loop. */ count = CCU_DIV_LOCK_CHECK_RETRIES; do { ndelay(nd); regmap_read(div->sys_regs, div->reg_ctl, &val); if (val & lock) return 0; } while (--count); return -ETIMEDOUT; } static int ccu_div_var_enable(struct clk_hw *hw) { struct clk_hw *parent_hw = clk_hw_get_parent(hw); struct ccu_div *div = to_ccu_div(hw); unsigned long flags; u32 val = 0; int ret; if (!parent_hw) { pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw)); return -EINVAL; } regmap_read(div->sys_regs, div->reg_ctl, &val); if (val & CCU_DIV_CTL_EN) return 0; spin_lock_irqsave(&div->lock, flags); ret = ccu_div_var_update_clkdiv(div, clk_hw_get_rate(parent_hw), ccu_div_get(div->mask, val)); if (!ret) regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_EN, CCU_DIV_CTL_EN); spin_unlock_irqrestore(&div->lock, flags); if (ret) pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw)); return ret; } static int ccu_div_gate_enable(struct clk_hw *hw) { struct ccu_div *div = to_ccu_div(hw); unsigned long flags; spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_EN, CCU_DIV_CTL_EN); spin_unlock_irqrestore(&div->lock, flags); return 0; } static void ccu_div_gate_disable(struct clk_hw *hw) { struct ccu_div *div = to_ccu_div(hw); unsigned long flags; spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_EN, 0); spin_unlock_irqrestore(&div->lock, flags); } static int ccu_div_gate_is_enabled(struct clk_hw *hw) { struct ccu_div *div = to_ccu_div(hw); u32 val = 0; regmap_read(div->sys_regs, div->reg_ctl, &val); return !!(val & CCU_DIV_CTL_EN); } static int ccu_div_buf_enable(struct clk_hw *hw) { struct ccu_div *div = to_ccu_div(hw); unsigned long flags; spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_GATE_REF_BUF, 0); spin_unlock_irqrestore(&div->lock, flags); return 0; } static void ccu_div_buf_disable(struct clk_hw *hw) { struct ccu_div *div = to_ccu_div(hw); unsigned long flags; spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_GATE_REF_BUF, CCU_DIV_CTL_GATE_REF_BUF); spin_unlock_irqrestore(&div->lock, flags); } static int ccu_div_buf_is_enabled(struct clk_hw *hw) { struct ccu_div *div = to_ccu_div(hw); u32 val = 0; regmap_read(div->sys_regs, div->reg_ctl, &val); return !(val & CCU_DIV_CTL_GATE_REF_BUF); } static unsigned long ccu_div_var_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct ccu_div *div = to_ccu_div(hw); unsigned long divider; u32 val = 0; regmap_read(div->sys_regs, div->reg_ctl, &val); divider = ccu_div_get(div->mask, val); return ccu_div_calc_freq(parent_rate, divider); } static inline unsigned long ccu_div_var_calc_divider(unsigned long rate, unsigned long parent_rate, unsigned int mask) { unsigned long divider; divider = parent_rate / rate; return clamp_t(unsigned long, divider, CCU_DIV_CLKDIV_MIN, CCU_DIV_CLKDIV_MAX(mask)); } static long ccu_div_var_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct ccu_div *div = to_ccu_div(hw); unsigned long divider; divider = ccu_div_var_calc_divider(rate, *parent_rate, div->mask); return ccu_div_calc_freq(*parent_rate, divider); } /* * This method is used for the clock divider blocks, which support the * on-the-fly rate change. So due to lacking the EN bit functionality * they can't be gated before the rate adjustment. */ static int ccu_div_var_set_rate_slow(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct ccu_div *div = to_ccu_div(hw); unsigned long flags, divider; u32 val; int ret; divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask); if (divider == 1 && div->features & CCU_DIV_SKIP_ONE) { divider = 0; } else if (div->features & CCU_DIV_SKIP_ONE_TO_THREE) { if (divider == 1 || divider == 2) divider = 0; else if (divider == 3) divider = 4; } val = ccu_div_prep(div->mask, divider); spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask, val); ret = ccu_div_var_update_clkdiv(div, parent_rate, divider); spin_unlock_irqrestore(&div->lock, flags); if (ret) pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw)); return ret; } /* * This method is used for the clock divider blocks, which don't support * the on-the-fly rate change. */ static int ccu_div_var_set_rate_fast(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct ccu_div *div = to_ccu_div(hw); unsigned long flags, divider; u32 val; divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask); val = ccu_div_prep(div->mask, divider); /* * Also disable the clock divider block if it was enabled by default * or by the bootloader. */ spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask | CCU_DIV_CTL_EN, val); spin_unlock_irqrestore(&div->lock, flags); return 0; } static unsigned long ccu_div_fixed_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct ccu_div *div = to_ccu_div(hw); return ccu_div_calc_freq(parent_rate, div->divider); } static long ccu_div_fixed_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct ccu_div *div = to_ccu_div(hw); return ccu_div_calc_freq(*parent_rate, div->divider); } static int ccu_div_fixed_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { return 0; } #ifdef CONFIG_DEBUG_FS struct ccu_div_dbgfs_bit { struct ccu_div *div; const char *name; u32 mask; }; #define CCU_DIV_DBGFS_BIT_ATTR(_name, _mask) { \ .name = _name, \ .mask = _mask \ } static const struct ccu_div_dbgfs_bit ccu_div_bits[] = { CCU_DIV_DBGFS_BIT_ATTR("div_en", CCU_DIV_CTL_EN), CCU_DIV_DBGFS_BIT_ATTR("div_rst", CCU_DIV_CTL_RST), CCU_DIV_DBGFS_BIT_ATTR("div_bypass", CCU_DIV_CTL_SET_CLKDIV), CCU_DIV_DBGFS_BIT_ATTR("div_buf", CCU_DIV_CTL_GATE_REF_BUF), CCU_DIV_DBGFS_BIT_ATTR("div_lock", CCU_DIV_CTL_LOCK_NORMAL) }; #define CCU_DIV_DBGFS_BIT_NUM ARRAY_SIZE(ccu_div_bits) /* * It can be dangerous to change the Divider settings behind clock framework * back, therefore we don't provide any kernel config based compile time option * for this feature to enable. */ #undef CCU_DIV_ALLOW_WRITE_DEBUGFS #ifdef CCU_DIV_ALLOW_WRITE_DEBUGFS static int ccu_div_dbgfs_bit_set(void *priv, u64 val) { const struct ccu_div_dbgfs_bit *bit = priv; struct ccu_div *div = bit->div; unsigned long flags; spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, bit->mask, val ? bit->mask : 0); spin_unlock_irqrestore(&div->lock, flags); return 0; } static int ccu_div_dbgfs_var_clkdiv_set(void *priv, u64 val) { struct ccu_div *div = priv; unsigned long flags; u32 data; val = clamp_t(u64, val, CCU_DIV_CLKDIV_MIN, CCU_DIV_CLKDIV_MAX(div->mask)); data = ccu_div_prep(div->mask, val); spin_lock_irqsave(&div->lock, flags); regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask, data); spin_unlock_irqrestore(&div->lock, flags); return 0; } #define ccu_div_dbgfs_mode 0644 #else /* !CCU_DIV_ALLOW_WRITE_DEBUGFS */ #define ccu_div_dbgfs_bit_set NULL #define ccu_div_dbgfs_var_clkdiv_set NULL #define ccu_div_dbgfs_mode 0444 #endif /* !CCU_DIV_ALLOW_WRITE_DEBUGFS */ static int ccu_div_dbgfs_bit_get(void *priv, u64 *val) { const struct ccu_div_dbgfs_bit *bit = priv; struct ccu_div *div = bit->div; u32 data = 0; regmap_read(div->sys_regs, div->reg_ctl, &data); *val = !!(data & bit->mask); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_bit_fops, ccu_div_dbgfs_bit_get, ccu_div_dbgfs_bit_set, "%llu\n"); static int ccu_div_dbgfs_var_clkdiv_get(void *priv, u64 *val) { struct ccu_div *div = priv; u32 data = 0; regmap_read(div->sys_regs, div->reg_ctl, &data); *val = ccu_div_get(div->mask, data); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_var_clkdiv_fops, ccu_div_dbgfs_var_clkdiv_get, ccu_div_dbgfs_var_clkdiv_set, "%llu\n"); static int ccu_div_dbgfs_fixed_clkdiv_get(void *priv, u64 *val) { struct ccu_div *div = priv; *val = div->divider; return 0; } DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_fixed_clkdiv_fops, ccu_div_dbgfs_fixed_clkdiv_get, NULL, "%llu\n"); static void ccu_div_var_debug_init(struct clk_hw *hw, struct dentry *dentry) { struct ccu_div *div = to_ccu_div(hw); struct ccu_div_dbgfs_bit *bits; int didx, bidx, num = 2; const char *name; num += !!(div->flags & CLK_SET_RATE_GATE) + !!(div->features & CCU_DIV_RESET_DOMAIN); bits = kcalloc(num, sizeof(*bits), GFP_KERNEL); if (!bits) return; for (didx = 0, bidx = 0; bidx < CCU_DIV_DBGFS_BIT_NUM; ++bidx) { name = ccu_div_bits[bidx].name; if (!(div->flags & CLK_SET_RATE_GATE) && !strcmp("div_en", name)) { continue; } if (!(div->features & CCU_DIV_RESET_DOMAIN) && !strcmp("div_rst", name)) { continue; } if (!strcmp("div_buf", name)) continue; bits[didx] = ccu_div_bits[bidx]; bits[didx].div = div; if (div->features & CCU_DIV_LOCK_SHIFTED && !strcmp("div_lock", name)) { bits[didx].mask = CCU_DIV_CTL_LOCK_SHIFTED; } debugfs_create_file_unsafe(bits[didx].name, ccu_div_dbgfs_mode, dentry, &bits[didx], &ccu_div_dbgfs_bit_fops); ++didx; } debugfs_create_file_unsafe("div_clkdiv", ccu_div_dbgfs_mode, dentry, div, &ccu_div_dbgfs_var_clkdiv_fops); } static void ccu_div_gate_debug_init(struct clk_hw *hw, struct dentry *dentry) { struct ccu_div *div = to_ccu_div(hw); struct ccu_div_dbgfs_bit *bit; bit = kmalloc(sizeof(*bit), GFP_KERNEL); if (!bit) return; *bit = ccu_div_bits[0]; bit->div = div; debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, &ccu_div_dbgfs_bit_fops); debugfs_create_file_unsafe("div_clkdiv", 0400, dentry, div, &ccu_div_dbgfs_fixed_clkdiv_fops); } static void ccu_div_buf_debug_init(struct clk_hw *hw, struct dentry *dentry) { struct ccu_div *div = to_ccu_div(hw); struct ccu_div_dbgfs_bit *bit; bit = kmalloc(sizeof(*bit), GFP_KERNEL); if (!bit) return; *bit = ccu_div_bits[3]; bit->div = div; debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, &ccu_div_dbgfs_bit_fops); } static void ccu_div_fixed_debug_init(struct clk_hw *hw, struct dentry *dentry) { struct ccu_div *div = to_ccu_div(hw); debugfs_create_file_unsafe("div_clkdiv", 0400, dentry, div, &ccu_div_dbgfs_fixed_clkdiv_fops); } #else /* !CONFIG_DEBUG_FS */ #define ccu_div_var_debug_init NULL #define ccu_div_gate_debug_init NULL #define ccu_div_buf_debug_init NULL #define ccu_div_fixed_debug_init NULL #endif /* !CONFIG_DEBUG_FS */ static const struct clk_ops ccu_div_var_gate_to_set_ops = { .enable = ccu_div_var_enable, .disable = ccu_div_gate_disable, .is_enabled = ccu_div_gate_is_enabled, .recalc_rate = ccu_div_var_recalc_rate, .round_rate = ccu_div_var_round_rate, .set_rate = ccu_div_var_set_rate_fast, .debug_init = ccu_div_var_debug_init }; static const struct clk_ops ccu_div_var_nogate_ops = { .recalc_rate = ccu_div_var_recalc_rate, .round_rate = ccu_div_var_round_rate, .set_rate = ccu_div_var_set_rate_slow, .debug_init = ccu_div_var_debug_init }; static const struct clk_ops ccu_div_gate_ops = { .enable = ccu_div_gate_enable, .disable = ccu_div_gate_disable, .is_enabled = ccu_div_gate_is_enabled, .recalc_rate = ccu_div_fixed_recalc_rate, .round_rate = ccu_div_fixed_round_rate, .set_rate = ccu_div_fixed_set_rate, .debug_init = ccu_div_gate_debug_init }; static const struct clk_ops ccu_div_buf_ops = { .enable = ccu_div_buf_enable, .disable = ccu_div_buf_disable, .is_enabled = ccu_div_buf_is_enabled, .debug_init = ccu_div_buf_debug_init }; static const struct clk_ops ccu_div_fixed_ops = { .recalc_rate = ccu_div_fixed_recalc_rate, .round_rate = ccu_div_fixed_round_rate, .set_rate = ccu_div_fixed_set_rate, .debug_init = ccu_div_fixed_debug_init }; struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *div_init) { struct clk_parent_data parent_data = { }; struct clk_init_data hw_init = { }; struct ccu_div *div; int ret; if (!div_init) return ERR_PTR(-EINVAL); div = kzalloc(sizeof(*div), GFP_KERNEL); if (!div) return ERR_PTR(-ENOMEM); /* * Note since Baikal-T1 System Controller registers are MMIO-backed * we won't check the regmap IO operations return status, because it * must be zero anyway. */ div->hw.init = &hw_init; div->id = div_init->id; div->reg_ctl = div_init->base + CCU_DIV_CTL; div->sys_regs = div_init->sys_regs; div->flags = div_init->flags; div->features = div_init->features; spin_lock_init(&div->lock); hw_init.name = div_init->name; hw_init.flags = div_init->flags; if (div_init->type == CCU_DIV_VAR) { if (hw_init.flags & CLK_SET_RATE_GATE) hw_init.ops = &ccu_div_var_gate_to_set_ops; else hw_init.ops = &ccu_div_var_nogate_ops; div->mask = CCU_DIV_CTL_CLKDIV_MASK(div_init->width); } else if (div_init->type == CCU_DIV_GATE) { hw_init.ops = &ccu_div_gate_ops; div->divider = div_init->divider; } else if (div_init->type == CCU_DIV_BUF) { hw_init.ops = &ccu_div_buf_ops; } else if (div_init->type == CCU_DIV_FIXED) { hw_init.ops = &ccu_div_fixed_ops; div->divider = div_init->divider; } else { ret = -EINVAL; goto err_free_div; } if (!div_init->parent_name) { ret = -EINVAL; goto err_free_div; } parent_data.fw_name = div_init->parent_name; parent_data.name = div_init->parent_name; hw_init.parent_data = &parent_data; hw_init.num_parents = 1; ret = of_clk_hw_register(div_init->np, &div->hw); if (ret) goto err_free_div; return div; err_free_div: kfree(div); return ERR_PTR(ret); } void ccu_div_hw_unregister(struct ccu_div *div) { clk_hw_unregister(&div->hw); kfree(div); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1