1 /*
2  * Copyright (C) 2016 Socionext Inc.
3  *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  */
15 
16 #include <linux/clk-provider.h>
17 #include <linux/delay.h>
18 #include <linux/device.h>
19 #include <linux/regmap.h>
20 
21 #include "clk-uniphier.h"
22 
23 #define UNIPHIER_CLK_CPUGEAR_STAT	0	/* status */
24 #define UNIPHIER_CLK_CPUGEAR_SET	4	/* set */
25 #define UNIPHIER_CLK_CPUGEAR_UPD	8	/* update */
26 #define   UNIPHIER_CLK_CPUGEAR_UPD_BIT	BIT(0)
27 
28 struct uniphier_clk_cpugear {
29 	struct clk_hw hw;
30 	struct regmap *regmap;
31 	unsigned int regbase;
32 	unsigned int mask;
33 };
34 
35 #define to_uniphier_clk_cpugear(_hw) \
36 			container_of(_hw, struct uniphier_clk_cpugear, hw)
37 
38 static int uniphier_clk_cpugear_set_parent(struct clk_hw *hw, u8 index)
39 {
40 	struct uniphier_clk_cpugear *gear = to_uniphier_clk_cpugear(hw);
41 	int ret;
42 	unsigned int val;
43 
44 	ret = regmap_write_bits(gear->regmap,
45 				gear->regbase + UNIPHIER_CLK_CPUGEAR_SET,
46 				gear->mask, index);
47 	if (ret)
48 		return ret;
49 
50 	ret = regmap_write_bits(gear->regmap,
51 				gear->regbase + UNIPHIER_CLK_CPUGEAR_SET,
52 				UNIPHIER_CLK_CPUGEAR_UPD_BIT,
53 				UNIPHIER_CLK_CPUGEAR_UPD_BIT);
54 	if (ret)
55 		return ret;
56 
57 	return regmap_read_poll_timeout(gear->regmap,
58 				gear->regbase + UNIPHIER_CLK_CPUGEAR_UPD,
59 				val, !(val & UNIPHIER_CLK_CPUGEAR_UPD_BIT),
60 				0, 1);
61 }
62 
63 static u8 uniphier_clk_cpugear_get_parent(struct clk_hw *hw)
64 {
65 	struct uniphier_clk_cpugear *gear = to_uniphier_clk_cpugear(hw);
66 	int num_parents = clk_hw_get_num_parents(hw);
67 	int ret;
68 	unsigned int val;
69 
70 	ret = regmap_read(gear->regmap,
71 			  gear->regbase + UNIPHIER_CLK_CPUGEAR_STAT, &val);
72 	if (ret)
73 		return ret;
74 
75 	val &= gear->mask;
76 
77 	return val < num_parents ? val : -EINVAL;
78 }
79 
80 static const struct clk_ops uniphier_clk_cpugear_ops = {
81 	.determine_rate = __clk_mux_determine_rate,
82 	.set_parent = uniphier_clk_cpugear_set_parent,
83 	.get_parent = uniphier_clk_cpugear_get_parent,
84 };
85 
86 struct clk_hw *uniphier_clk_register_cpugear(struct device *dev,
87 					 struct regmap *regmap,
88 					 const char *name,
89 				const struct uniphier_clk_cpugear_data *data)
90 {
91 	struct uniphier_clk_cpugear *gear;
92 	struct clk_init_data init;
93 	int ret;
94 
95 	gear = devm_kzalloc(dev, sizeof(*gear), GFP_KERNEL);
96 	if (!gear)
97 		return ERR_PTR(-ENOMEM);
98 
99 	init.name = name;
100 	init.ops = &uniphier_clk_cpugear_ops;
101 	init.flags = CLK_SET_RATE_PARENT;
102 	init.parent_names = data->parent_names;
103 	init.num_parents = data->num_parents,
104 
105 	gear->regmap = regmap;
106 	gear->regbase = data->regbase;
107 	gear->mask = data->mask;
108 	gear->hw.init = &init;
109 
110 	ret = devm_clk_hw_register(dev, &gear->hw);
111 	if (ret)
112 		return ERR_PTR(ret);
113 
114 	return &gear->hw;
115 }
116