brintos

brintos / linux-shallow public Read only

0
0
Text · 5.4 KiB · 26b210c Raw
222 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Copyright 2018 NXP.4 *   Dong Aisheng <aisheng.dong@nxp.com>5 */6 7#include <linux/clk-provider.h>8#include <linux/err.h>9#include <linux/io.h>10#include <linux/slab.h>11 12#include "clk.h"13 14struct clk_divider_gate {15	struct clk_divider divider;16	u32 cached_val;17};18 19static inline struct clk_divider_gate *to_clk_divider_gate(struct clk_hw *hw)20{21	struct clk_divider *div = to_clk_divider(hw);22 23	return container_of(div, struct clk_divider_gate, divider);24}25 26static unsigned long clk_divider_gate_recalc_rate_ro(struct clk_hw *hw,27						     unsigned long parent_rate)28{29	struct clk_divider *div = to_clk_divider(hw);30	unsigned int val;31 32	val = readl(div->reg) >> div->shift;33	val &= clk_div_mask(div->width);34	if (!val)35		return 0;36 37	return divider_recalc_rate(hw, parent_rate, val, div->table,38				   div->flags, div->width);39}40 41static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,42						  unsigned long parent_rate)43{44	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);45	struct clk_divider *div = to_clk_divider(hw);46	unsigned long flags;47	unsigned int val;48 49	spin_lock_irqsave(div->lock, flags);50 51	if (!clk_hw_is_enabled(hw)) {52		val = div_gate->cached_val;53	} else {54		val = readl(div->reg) >> div->shift;55		val &= clk_div_mask(div->width);56	}57 58	spin_unlock_irqrestore(div->lock, flags);59 60	if (!val)61		return 0;62 63	return divider_recalc_rate(hw, parent_rate, val, div->table,64				   div->flags, div->width);65}66 67static int clk_divider_determine_rate(struct clk_hw *hw,68				      struct clk_rate_request *req)69{70	return clk_divider_ops.determine_rate(hw, req);71}72 73static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,74				unsigned long parent_rate)75{76	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);77	struct clk_divider *div = to_clk_divider(hw);78	unsigned long flags;79	int value;80	u32 val;81 82	value = divider_get_val(rate, parent_rate, div->table,83				div->width, div->flags);84	if (value < 0)85		return value;86 87	spin_lock_irqsave(div->lock, flags);88 89	if (clk_hw_is_enabled(hw)) {90		val = readl(div->reg);91		val &= ~(clk_div_mask(div->width) << div->shift);92		val |= (u32)value << div->shift;93		writel(val, div->reg);94	} else {95		div_gate->cached_val = value;96	}97 98	spin_unlock_irqrestore(div->lock, flags);99 100	return 0;101}102 103static int clk_divider_enable(struct clk_hw *hw)104{105	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);106	struct clk_divider *div = to_clk_divider(hw);107	unsigned long flags;108	u32 val;109 110	if (!div_gate->cached_val) {111		pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));112		return -EINVAL;113	}114 115	spin_lock_irqsave(div->lock, flags);116	/* restore div val */117	val = readl(div->reg);118	val |= div_gate->cached_val << div->shift;119	writel(val, div->reg);120 121	spin_unlock_irqrestore(div->lock, flags);122 123	return 0;124}125 126static void clk_divider_disable(struct clk_hw *hw)127{128	struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);129	struct clk_divider *div = to_clk_divider(hw);130	unsigned long flags;131	u32 val;132 133	spin_lock_irqsave(div->lock, flags);134 135	/* store the current div val */136	val = readl(div->reg) >> div->shift;137	val &= clk_div_mask(div->width);138	div_gate->cached_val = val;139	writel(0, div->reg);140 141	spin_unlock_irqrestore(div->lock, flags);142}143 144static int clk_divider_is_enabled(struct clk_hw *hw)145{146	struct clk_divider *div = to_clk_divider(hw);147	u32 val;148 149	val = readl(div->reg) >> div->shift;150	val &= clk_div_mask(div->width);151 152	return val ? 1 : 0;153}154 155static const struct clk_ops clk_divider_gate_ro_ops = {156	.recalc_rate = clk_divider_gate_recalc_rate_ro,157	.determine_rate = clk_divider_determine_rate,158};159 160static const struct clk_ops clk_divider_gate_ops = {161	.recalc_rate = clk_divider_gate_recalc_rate,162	.determine_rate = clk_divider_determine_rate,163	.set_rate = clk_divider_gate_set_rate,164	.enable = clk_divider_enable,165	.disable = clk_divider_disable,166	.is_enabled = clk_divider_is_enabled,167};168 169/*170 * NOTE: In order to reuse the most code from the common divider,171 * we also design our divider following the way that provids an extra172 * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by173 * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY174 * flag which can be specified by user flexibly.175 */176struct clk_hw *imx_clk_hw_divider_gate(const char *name, const char *parent_name,177				    unsigned long flags, void __iomem *reg,178				    u8 shift, u8 width, u8 clk_divider_flags,179				    const struct clk_div_table *table,180				    spinlock_t *lock)181{182	struct clk_init_data init;183	struct clk_divider_gate *div_gate;184	struct clk_hw *hw;185	u32 val;186	int ret;187 188	div_gate  = kzalloc(sizeof(*div_gate), GFP_KERNEL);189	if (!div_gate)190		return ERR_PTR(-ENOMEM);191 192	init.name = name;193	if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)194		init.ops = &clk_divider_gate_ro_ops;195	else196		init.ops = &clk_divider_gate_ops;197	init.flags = flags;198	init.parent_names = parent_name ? &parent_name : NULL;199	init.num_parents = parent_name ? 1 : 0;200 201	div_gate->divider.reg = reg;202	div_gate->divider.shift = shift;203	div_gate->divider.width = width;204	div_gate->divider.lock = lock;205	div_gate->divider.table = table;206	div_gate->divider.hw.init = &init;207	div_gate->divider.flags = CLK_DIVIDER_ONE_BASED | clk_divider_flags;208	/* cache gate status */209	val = readl(reg) >> shift;210	val &= clk_div_mask(width);211	div_gate->cached_val = val;212 213	hw = &div_gate->divider.hw;214	ret = clk_hw_register(NULL, hw);215	if (ret) {216		kfree(div_gate);217		hw = ERR_PTR(ret);218	}219 220	return hw;221}222