1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2017 HiSilicon Technologies Co., Ltd.
4  *
5  * Simple HiSilicon phase clock implementation.
6  */
7 
8 #include <linux/err.h>
9 #include <linux/io.h>
10 #include <linux/module.h>
11 #include <linux/platform_device.h>
12 #include <linux/slab.h>
13 
14 #include "clk.h"
15 
16 struct clk_hisi_phase {
17 	struct clk_hw	hw;
18 	void __iomem	*reg;
19 	u32		*phase_degrees;
20 	u32		*phase_regvals;
21 	u8		phase_num;
22 	u32		mask;
23 	u8		shift;
24 	u8		flags;
25 	spinlock_t	*lock;
26 };
27 
28 #define to_clk_hisi_phase(_hw) container_of(_hw, struct clk_hisi_phase, hw)
29 
hisi_phase_regval_to_degrees(struct clk_hisi_phase * phase,u32 regval)30 static int hisi_phase_regval_to_degrees(struct clk_hisi_phase *phase,
31 					u32 regval)
32 {
33 	int i;
34 
35 	for (i = 0; i < phase->phase_num; i++)
36 		if (phase->phase_regvals[i] == regval)
37 			return phase->phase_degrees[i];
38 
39 	return -EINVAL;
40 }
41 
hisi_clk_get_phase(struct clk_hw * hw)42 static int hisi_clk_get_phase(struct clk_hw *hw)
43 {
44 	struct clk_hisi_phase *phase = to_clk_hisi_phase(hw);
45 	u32 regval;
46 
47 	regval = readl(phase->reg);
48 	regval = (regval & phase->mask) >> phase->shift;
49 
50 	return hisi_phase_regval_to_degrees(phase, regval);
51 }
52 
hisi_phase_degrees_to_regval(struct clk_hisi_phase * phase,int degrees)53 static int hisi_phase_degrees_to_regval(struct clk_hisi_phase *phase,
54 					int degrees)
55 {
56 	int i;
57 
58 	for (i = 0; i < phase->phase_num; i++)
59 		if (phase->phase_degrees[i] == degrees)
60 			return phase->phase_regvals[i];
61 
62 	return -EINVAL;
63 }
64 
hisi_clk_set_phase(struct clk_hw * hw,int degrees)65 static int hisi_clk_set_phase(struct clk_hw *hw, int degrees)
66 {
67 	struct clk_hisi_phase *phase = to_clk_hisi_phase(hw);
68 	unsigned long flags = 0;
69 	int regval;
70 	u32 val;
71 
72 	regval = hisi_phase_degrees_to_regval(phase, degrees);
73 	if (regval < 0)
74 		return regval;
75 
76 	spin_lock_irqsave(phase->lock, flags);
77 
78 	val = readl(phase->reg);
79 	val &= ~phase->mask;
80 	val |= regval << phase->shift;
81 	writel(val, phase->reg);
82 
83 	spin_unlock_irqrestore(phase->lock, flags);
84 
85 	return 0;
86 }
87 
88 static const struct clk_ops clk_phase_ops = {
89 	.get_phase = hisi_clk_get_phase,
90 	.set_phase = hisi_clk_set_phase,
91 };
92 
clk_register_hisi_phase(struct device * dev,const struct hisi_phase_clock * clks,void __iomem * base,spinlock_t * lock)93 struct clk *clk_register_hisi_phase(struct device *dev,
94 		const struct hisi_phase_clock *clks,
95 		void __iomem *base, spinlock_t *lock)
96 {
97 	struct clk_hisi_phase *phase;
98 	struct clk_init_data init;
99 
100 	phase = devm_kzalloc(dev, sizeof(struct clk_hisi_phase), GFP_KERNEL);
101 	if (!phase)
102 		return ERR_PTR(-ENOMEM);
103 
104 	init.name = clks->name;
105 	init.ops = &clk_phase_ops;
106 	init.flags = clks->flags;
107 	init.parent_names = clks->parent_names ? &clks->parent_names : NULL;
108 	init.num_parents = clks->parent_names ? 1 : 0;
109 
110 	phase->reg = base + clks->offset;
111 	phase->shift = clks->shift;
112 	phase->mask = (BIT(clks->width) - 1) << clks->shift;
113 	phase->lock = lock;
114 	phase->phase_degrees = clks->phase_degrees;
115 	phase->phase_regvals = clks->phase_regvals;
116 	phase->phase_num = clks->phase_num;
117 	phase->hw.init = &init;
118 
119 	return devm_clk_register(dev, &phase->hw);
120 }
121 EXPORT_SYMBOL_GPL(clk_register_hisi_phase);
122