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