xref: /openbmc/linux/drivers/clk/hisilicon/clk-hisi-phase.c (revision c39f2d9db0fd81ea20bb5cce9b3f082ca63753e2)
1811f67ccStianshuliang // SPDX-License-Identifier: GPL-2.0
2811f67ccStianshuliang /*
3811f67ccStianshuliang  * Copyright (c) 2017 HiSilicon Technologies Co., Ltd.
4811f67ccStianshuliang  *
5811f67ccStianshuliang  * Simple HiSilicon phase clock implementation.
6811f67ccStianshuliang  */
7811f67ccStianshuliang 
8811f67ccStianshuliang #include <linux/err.h>
9811f67ccStianshuliang #include <linux/io.h>
10811f67ccStianshuliang #include <linux/module.h>
11811f67ccStianshuliang #include <linux/platform_device.h>
12811f67ccStianshuliang #include <linux/slab.h>
13811f67ccStianshuliang 
14811f67ccStianshuliang #include "clk.h"
15811f67ccStianshuliang 
16811f67ccStianshuliang struct clk_hisi_phase {
17811f67ccStianshuliang 	struct clk_hw	hw;
18811f67ccStianshuliang 	void __iomem	*reg;
19811f67ccStianshuliang 	u32		*phase_degrees;
20811f67ccStianshuliang 	u32		*phase_regvals;
21811f67ccStianshuliang 	u8		phase_num;
22811f67ccStianshuliang 	u32		mask;
23811f67ccStianshuliang 	u8		shift;
24811f67ccStianshuliang 	u8		flags;
25811f67ccStianshuliang 	spinlock_t	*lock;
26811f67ccStianshuliang };
27811f67ccStianshuliang 
28811f67ccStianshuliang #define to_clk_hisi_phase(_hw) container_of(_hw, struct clk_hisi_phase, hw)
29811f67ccStianshuliang 
hisi_phase_regval_to_degrees(struct clk_hisi_phase * phase,u32 regval)30811f67ccStianshuliang static int hisi_phase_regval_to_degrees(struct clk_hisi_phase *phase,
31811f67ccStianshuliang 					u32 regval)
32811f67ccStianshuliang {
33811f67ccStianshuliang 	int i;
34811f67ccStianshuliang 
35811f67ccStianshuliang 	for (i = 0; i < phase->phase_num; i++)
36811f67ccStianshuliang 		if (phase->phase_regvals[i] == regval)
37811f67ccStianshuliang 			return phase->phase_degrees[i];
38811f67ccStianshuliang 
39811f67ccStianshuliang 	return -EINVAL;
40811f67ccStianshuliang }
41811f67ccStianshuliang 
hisi_clk_get_phase(struct clk_hw * hw)42811f67ccStianshuliang static int hisi_clk_get_phase(struct clk_hw *hw)
43811f67ccStianshuliang {
44811f67ccStianshuliang 	struct clk_hisi_phase *phase = to_clk_hisi_phase(hw);
45811f67ccStianshuliang 	u32 regval;
46811f67ccStianshuliang 
47811f67ccStianshuliang 	regval = readl(phase->reg);
48811f67ccStianshuliang 	regval = (regval & phase->mask) >> phase->shift;
49811f67ccStianshuliang 
50811f67ccStianshuliang 	return hisi_phase_regval_to_degrees(phase, regval);
51811f67ccStianshuliang }
52811f67ccStianshuliang 
hisi_phase_degrees_to_regval(struct clk_hisi_phase * phase,int degrees)53811f67ccStianshuliang static int hisi_phase_degrees_to_regval(struct clk_hisi_phase *phase,
54811f67ccStianshuliang 					int degrees)
55811f67ccStianshuliang {
56811f67ccStianshuliang 	int i;
57811f67ccStianshuliang 
58811f67ccStianshuliang 	for (i = 0; i < phase->phase_num; i++)
59811f67ccStianshuliang 		if (phase->phase_degrees[i] == degrees)
60811f67ccStianshuliang 			return phase->phase_regvals[i];
61811f67ccStianshuliang 
62811f67ccStianshuliang 	return -EINVAL;
63811f67ccStianshuliang }
64811f67ccStianshuliang 
hisi_clk_set_phase(struct clk_hw * hw,int degrees)65811f67ccStianshuliang static int hisi_clk_set_phase(struct clk_hw *hw, int degrees)
66811f67ccStianshuliang {
67811f67ccStianshuliang 	struct clk_hisi_phase *phase = to_clk_hisi_phase(hw);
68811f67ccStianshuliang 	unsigned long flags = 0;
69811f67ccStianshuliang 	int regval;
70811f67ccStianshuliang 	u32 val;
71811f67ccStianshuliang 
72811f67ccStianshuliang 	regval = hisi_phase_degrees_to_regval(phase, degrees);
73811f67ccStianshuliang 	if (regval < 0)
74811f67ccStianshuliang 		return regval;
75811f67ccStianshuliang 
76811f67ccStianshuliang 	spin_lock_irqsave(phase->lock, flags);
77811f67ccStianshuliang 
78*5834fd75SJonas Gorski 	val = readl(phase->reg);
79811f67ccStianshuliang 	val &= ~phase->mask;
80811f67ccStianshuliang 	val |= regval << phase->shift;
81*5834fd75SJonas Gorski 	writel(val, phase->reg);
82811f67ccStianshuliang 
83811f67ccStianshuliang 	spin_unlock_irqrestore(phase->lock, flags);
84811f67ccStianshuliang 
85811f67ccStianshuliang 	return 0;
86811f67ccStianshuliang }
87811f67ccStianshuliang 
88a91f77efSStephen Boyd static const struct clk_ops clk_phase_ops = {
89811f67ccStianshuliang 	.get_phase = hisi_clk_get_phase,
90811f67ccStianshuliang 	.set_phase = hisi_clk_set_phase,
91811f67ccStianshuliang };
92811f67ccStianshuliang 
clk_register_hisi_phase(struct device * dev,const struct hisi_phase_clock * clks,void __iomem * base,spinlock_t * lock)93811f67ccStianshuliang struct clk *clk_register_hisi_phase(struct device *dev,
94811f67ccStianshuliang 		const struct hisi_phase_clock *clks,
95811f67ccStianshuliang 		void __iomem *base, spinlock_t *lock)
96811f67ccStianshuliang {
97811f67ccStianshuliang 	struct clk_hisi_phase *phase;
98811f67ccStianshuliang 	struct clk_init_data init;
99811f67ccStianshuliang 
100811f67ccStianshuliang 	phase = devm_kzalloc(dev, sizeof(struct clk_hisi_phase), GFP_KERNEL);
101811f67ccStianshuliang 	if (!phase)
102811f67ccStianshuliang 		return ERR_PTR(-ENOMEM);
103811f67ccStianshuliang 
104811f67ccStianshuliang 	init.name = clks->name;
105811f67ccStianshuliang 	init.ops = &clk_phase_ops;
1065a727ff6SStephen Boyd 	init.flags = clks->flags;
107811f67ccStianshuliang 	init.parent_names = clks->parent_names ? &clks->parent_names : NULL;
108811f67ccStianshuliang 	init.num_parents = clks->parent_names ? 1 : 0;
109811f67ccStianshuliang 
110811f67ccStianshuliang 	phase->reg = base + clks->offset;
111811f67ccStianshuliang 	phase->shift = clks->shift;
112811f67ccStianshuliang 	phase->mask = (BIT(clks->width) - 1) << clks->shift;
113811f67ccStianshuliang 	phase->lock = lock;
114811f67ccStianshuliang 	phase->phase_degrees = clks->phase_degrees;
115811f67ccStianshuliang 	phase->phase_regvals = clks->phase_regvals;
116811f67ccStianshuliang 	phase->phase_num = clks->phase_num;
117811f67ccStianshuliang 	phase->hw.init = &init;
118811f67ccStianshuliang 
119811f67ccStianshuliang 	return devm_clk_register(dev, &phase->hw);
120811f67ccStianshuliang }
121811f67ccStianshuliang EXPORT_SYMBOL_GPL(clk_register_hisi_phase);
122