| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Copyright (c) 2018 BayLibre, SAS. | 
 |  * Author: Jerome Brunet <jbrunet@baylibre.com> | 
 |  */ | 
 |  | 
 | #include <linux/module.h> | 
 | #include "clk-regmap.h" | 
 |  | 
 | static int clk_regmap_gate_endisable(struct clk_hw *hw, int enable) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk); | 
 | 	int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; | 
 |  | 
 | 	set ^= enable; | 
 |  | 
 | 	return regmap_update_bits(clk->map, gate->offset, BIT(gate->bit_idx), | 
 | 				  set ? BIT(gate->bit_idx) : 0); | 
 | } | 
 |  | 
 | static int clk_regmap_gate_enable(struct clk_hw *hw) | 
 | { | 
 | 	return clk_regmap_gate_endisable(hw, 1); | 
 | } | 
 |  | 
 | static void clk_regmap_gate_disable(struct clk_hw *hw) | 
 | { | 
 | 	clk_regmap_gate_endisable(hw, 0); | 
 | } | 
 |  | 
 | static int clk_regmap_gate_is_enabled(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk); | 
 | 	unsigned int val; | 
 |  | 
 | 	regmap_read(clk->map, gate->offset, &val); | 
 | 	if (gate->flags & CLK_GATE_SET_TO_DISABLE) | 
 | 		val ^= BIT(gate->bit_idx); | 
 |  | 
 | 	val &= BIT(gate->bit_idx); | 
 |  | 
 | 	return val ? 1 : 0; | 
 | } | 
 |  | 
 | const struct clk_ops clk_regmap_gate_ops = { | 
 | 	.enable = clk_regmap_gate_enable, | 
 | 	.disable = clk_regmap_gate_disable, | 
 | 	.is_enabled = clk_regmap_gate_is_enabled, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_regmap_gate_ops); | 
 |  | 
 | const struct clk_ops clk_regmap_gate_ro_ops = { | 
 | 	.is_enabled = clk_regmap_gate_is_enabled, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_regmap_gate_ro_ops); | 
 |  | 
 | static unsigned long clk_regmap_div_recalc_rate(struct clk_hw *hw, | 
 | 						unsigned long prate) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk); | 
 | 	unsigned int val; | 
 | 	int ret; | 
 |  | 
 | 	ret = regmap_read(clk->map, div->offset, &val); | 
 | 	if (ret) | 
 | 		/* Gives a hint that something is wrong */ | 
 | 		return 0; | 
 |  | 
 | 	val >>= div->shift; | 
 | 	val &= clk_div_mask(div->width); | 
 | 	return divider_recalc_rate(hw, prate, val, div->table, div->flags, | 
 | 				   div->width); | 
 | } | 
 |  | 
 | static int clk_regmap_div_determine_rate(struct clk_hw *hw, | 
 | 					 struct clk_rate_request *req) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk); | 
 | 	unsigned int val; | 
 | 	int ret; | 
 |  | 
 | 	/* if read only, just return current value */ | 
 | 	if (div->flags & CLK_DIVIDER_READ_ONLY) { | 
 | 		ret = regmap_read(clk->map, div->offset, &val); | 
 | 		if (ret) | 
 | 			return ret; | 
 |  | 
 | 		val >>= div->shift; | 
 | 		val &= clk_div_mask(div->width); | 
 |  | 
 | 		return divider_ro_determine_rate(hw, req, div->table, | 
 | 						 div->width, div->flags, val); | 
 | 	} | 
 |  | 
 | 	return divider_determine_rate(hw, req, div->table, div->width, | 
 | 				      div->flags); | 
 | } | 
 |  | 
 | static int clk_regmap_div_set_rate(struct clk_hw *hw, unsigned long rate, | 
 | 				   unsigned long parent_rate) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk); | 
 | 	unsigned int val; | 
 | 	int ret; | 
 |  | 
 | 	ret = divider_get_val(rate, parent_rate, div->table, div->width, | 
 | 			      div->flags); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	val = (unsigned int)ret << div->shift; | 
 | 	return regmap_update_bits(clk->map, div->offset, | 
 | 				  clk_div_mask(div->width) << div->shift, val); | 
 | }; | 
 |  | 
 | /* Would prefer clk_regmap_div_ro_ops but clashes with qcom */ | 
 |  | 
 | const struct clk_ops clk_regmap_divider_ops = { | 
 | 	.recalc_rate = clk_regmap_div_recalc_rate, | 
 | 	.determine_rate = clk_regmap_div_determine_rate, | 
 | 	.set_rate = clk_regmap_div_set_rate, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_regmap_divider_ops); | 
 |  | 
 | const struct clk_ops clk_regmap_divider_ro_ops = { | 
 | 	.recalc_rate = clk_regmap_div_recalc_rate, | 
 | 	.determine_rate = clk_regmap_div_determine_rate, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_regmap_divider_ro_ops); | 
 |  | 
 | static u8 clk_regmap_mux_get_parent(struct clk_hw *hw) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk); | 
 | 	unsigned int val; | 
 | 	int ret; | 
 |  | 
 | 	ret = regmap_read(clk->map, mux->offset, &val); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	val >>= mux->shift; | 
 | 	val &= mux->mask; | 
 | 	return clk_mux_val_to_index(hw, mux->table, mux->flags, val); | 
 | } | 
 |  | 
 | static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk); | 
 | 	unsigned int val = clk_mux_index_to_val(mux->table, mux->flags, index); | 
 |  | 
 | 	return regmap_update_bits(clk->map, mux->offset, | 
 | 				  mux->mask << mux->shift, | 
 | 				  val << mux->shift); | 
 | } | 
 |  | 
 | static int clk_regmap_mux_determine_rate(struct clk_hw *hw, | 
 | 					 struct clk_rate_request *req) | 
 | { | 
 | 	struct clk_regmap *clk = to_clk_regmap(hw); | 
 | 	struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk); | 
 |  | 
 | 	return clk_mux_determine_rate_flags(hw, req, mux->flags); | 
 | } | 
 |  | 
 | const struct clk_ops clk_regmap_mux_ops = { | 
 | 	.get_parent = clk_regmap_mux_get_parent, | 
 | 	.set_parent = clk_regmap_mux_set_parent, | 
 | 	.determine_rate = clk_regmap_mux_determine_rate, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_regmap_mux_ops); | 
 |  | 
 | const struct clk_ops clk_regmap_mux_ro_ops = { | 
 | 	.get_parent = clk_regmap_mux_get_parent, | 
 | }; | 
 | EXPORT_SYMBOL_GPL(clk_regmap_mux_ro_ops); | 
 |  | 
 | MODULE_DESCRIPTION("Amlogic regmap backed clock driver"); | 
 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); | 
 | MODULE_LICENSE("GPL v2"); |