162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2013 Freescale Semiconductor, Inc.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/bits.h>
762306a36Sopenharmony_ci#include <linux/clk-provider.h>
862306a36Sopenharmony_ci#include <linux/err.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include "clk.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci/**
1462306a36Sopenharmony_ci * struct clk_fixup_mux - imx integer fixup multiplexer clock
1562306a36Sopenharmony_ci * @mux: the parent class
1662306a36Sopenharmony_ci * @ops: pointer to clk_ops of parent class
1762306a36Sopenharmony_ci * @fixup: a hook to fixup the write value
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * The imx fixup multiplexer clock is a subclass of basic clk_mux
2062306a36Sopenharmony_ci * with an addtional fixup hook.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_cistruct clk_fixup_mux {
2362306a36Sopenharmony_ci	struct clk_mux mux;
2462306a36Sopenharmony_ci	const struct clk_ops *ops;
2562306a36Sopenharmony_ci	void (*fixup)(u32 *val);
2662306a36Sopenharmony_ci};
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic inline struct clk_fixup_mux *to_clk_fixup_mux(struct clk_hw *hw)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	struct clk_mux *mux = to_clk_mux(hw);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	return container_of(mux, struct clk_fixup_mux, mux);
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic u8 clk_fixup_mux_get_parent(struct clk_hw *hw)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct clk_fixup_mux *fixup_mux = to_clk_fixup_mux(hw);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return fixup_mux->ops->get_parent(&fixup_mux->mux.hw);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int clk_fixup_mux_set_parent(struct clk_hw *hw, u8 index)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct clk_fixup_mux *fixup_mux = to_clk_fixup_mux(hw);
4562306a36Sopenharmony_ci	struct clk_mux *mux = to_clk_mux(hw);
4662306a36Sopenharmony_ci	unsigned long flags;
4762306a36Sopenharmony_ci	u32 val;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	spin_lock_irqsave(mux->lock, flags);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	val = readl(mux->reg);
5262306a36Sopenharmony_ci	val &= ~(mux->mask << mux->shift);
5362306a36Sopenharmony_ci	val |= index << mux->shift;
5462306a36Sopenharmony_ci	fixup_mux->fixup(&val);
5562306a36Sopenharmony_ci	writel(val, mux->reg);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	spin_unlock_irqrestore(mux->lock, flags);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	return 0;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic const struct clk_ops clk_fixup_mux_ops = {
6362306a36Sopenharmony_ci	.determine_rate = clk_hw_determine_rate_no_reparent,
6462306a36Sopenharmony_ci	.get_parent = clk_fixup_mux_get_parent,
6562306a36Sopenharmony_ci	.set_parent = clk_fixup_mux_set_parent,
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistruct clk_hw *imx_clk_hw_fixup_mux(const char *name, void __iomem *reg,
6962306a36Sopenharmony_ci			      u8 shift, u8 width, const char * const *parents,
7062306a36Sopenharmony_ci			      int num_parents, void (*fixup)(u32 *val))
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct clk_fixup_mux *fixup_mux;
7362306a36Sopenharmony_ci	struct clk_hw *hw;
7462306a36Sopenharmony_ci	struct clk_init_data init;
7562306a36Sopenharmony_ci	int ret;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (!fixup)
7862306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	fixup_mux = kzalloc(sizeof(*fixup_mux), GFP_KERNEL);
8162306a36Sopenharmony_ci	if (!fixup_mux)
8262306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	init.name = name;
8562306a36Sopenharmony_ci	init.ops = &clk_fixup_mux_ops;
8662306a36Sopenharmony_ci	init.parent_names = parents;
8762306a36Sopenharmony_ci	init.num_parents = num_parents;
8862306a36Sopenharmony_ci	init.flags = 0;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	fixup_mux->mux.reg = reg;
9162306a36Sopenharmony_ci	fixup_mux->mux.shift = shift;
9262306a36Sopenharmony_ci	fixup_mux->mux.mask = BIT(width) - 1;
9362306a36Sopenharmony_ci	fixup_mux->mux.lock = &imx_ccm_lock;
9462306a36Sopenharmony_ci	fixup_mux->mux.hw.init = &init;
9562306a36Sopenharmony_ci	fixup_mux->ops = &clk_mux_ops;
9662306a36Sopenharmony_ci	fixup_mux->fixup = fixup;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	hw = &fixup_mux->mux.hw;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	ret = clk_hw_register(NULL, hw);
10162306a36Sopenharmony_ci	if (ret) {
10262306a36Sopenharmony_ci		kfree(fixup_mux);
10362306a36Sopenharmony_ci		return ERR_PTR(ret);
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return hw;
10762306a36Sopenharmony_ci}
108