GNU Linux-libre 4.19.264-gnu1
[releases.git] / drivers / clk / hisilicon / clk-hisi-phase.c
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
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
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
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
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 = clk_readl(phase->reg);
79         val &= ~phase->mask;
80         val |= regval << phase->shift;
81         clk_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
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 | CLK_IS_BASIC;
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);