1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Clock driver for twl device.
4  *
5  * inspired by the driver for the Palmas device
6  */
7 
8 #include <linux/clk-provider.h>
9 #include <linux/mfd/twl.h>
10 #include <linux/module.h>
11 #include <linux/platform_device.h>
12 #include <linux/slab.h>
13 
14 #define VREG_STATE              2
15 #define TWL6030_CFG_STATE_OFF   0x00
16 #define TWL6030_CFG_STATE_ON    0x01
17 #define TWL6030_CFG_STATE_MASK  0x03
18 
19 struct twl_clock_info {
20 	struct device *dev;
21 	u8 base;
22 	struct clk_hw hw;
23 };
24 
25 static inline int
twlclk_read(struct twl_clock_info * info,unsigned int slave_subgp,unsigned int offset)26 twlclk_read(struct twl_clock_info *info, unsigned int slave_subgp,
27 	    unsigned int offset)
28 {
29 	u8 value;
30 	int status;
31 
32 	status = twl_i2c_read_u8(slave_subgp, &value,
33 				 info->base + offset);
34 	return (status < 0) ? status : value;
35 }
36 
37 static inline int
twlclk_write(struct twl_clock_info * info,unsigned int slave_subgp,unsigned int offset,u8 value)38 twlclk_write(struct twl_clock_info *info, unsigned int slave_subgp,
39 	     unsigned int offset, u8 value)
40 {
41 	return twl_i2c_write_u8(slave_subgp, value,
42 				info->base + offset);
43 }
44 
to_twl_clks_info(struct clk_hw * hw)45 static inline struct twl_clock_info *to_twl_clks_info(struct clk_hw *hw)
46 {
47 	return container_of(hw, struct twl_clock_info, hw);
48 }
49 
twl_clks_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)50 static unsigned long twl_clks_recalc_rate(struct clk_hw *hw,
51 					  unsigned long parent_rate)
52 {
53 	return 32768;
54 }
55 
twl6032_clks_prepare(struct clk_hw * hw)56 static int twl6032_clks_prepare(struct clk_hw *hw)
57 {
58 	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
59 	int ret;
60 
61 	ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
62 			   TWL6030_CFG_STATE_ON);
63 	if (ret < 0)
64 		dev_err(cinfo->dev, "clk prepare failed\n");
65 
66 	return ret;
67 }
68 
twl6032_clks_unprepare(struct clk_hw * hw)69 static void twl6032_clks_unprepare(struct clk_hw *hw)
70 {
71 	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
72 	int ret;
73 
74 	ret = twlclk_write(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE,
75 			   TWL6030_CFG_STATE_OFF);
76 	if (ret < 0)
77 		dev_err(cinfo->dev, "clk unprepare failed\n");
78 }
79 
twl6032_clks_is_prepared(struct clk_hw * hw)80 static int twl6032_clks_is_prepared(struct clk_hw *hw)
81 {
82 	struct twl_clock_info *cinfo = to_twl_clks_info(hw);
83 	int val;
84 
85 	val = twlclk_read(cinfo, TWL_MODULE_PM_RECEIVER, VREG_STATE);
86 	if (val < 0) {
87 		dev_err(cinfo->dev, "clk read failed\n");
88 		return val;
89 	}
90 
91 	val &= TWL6030_CFG_STATE_MASK;
92 
93 	return val == TWL6030_CFG_STATE_ON;
94 }
95 
96 static const struct clk_ops twl6032_clks_ops = {
97 	.prepare	= twl6032_clks_prepare,
98 	.unprepare	= twl6032_clks_unprepare,
99 	.is_prepared	= twl6032_clks_is_prepared,
100 	.recalc_rate	= twl_clks_recalc_rate,
101 };
102 
103 struct twl_clks_data {
104 	struct clk_init_data init;
105 	u8 base;
106 };
107 
108 static const struct twl_clks_data twl6032_clks[] = {
109 	{
110 		.init = {
111 			.name = "clk32kg",
112 			.ops = &twl6032_clks_ops,
113 			.flags = CLK_IGNORE_UNUSED,
114 		},
115 		.base = 0x8C,
116 	},
117 	{
118 		.init = {
119 			.name = "clk32kaudio",
120 			.ops = &twl6032_clks_ops,
121 			.flags = CLK_IGNORE_UNUSED,
122 		},
123 		.base = 0x8F,
124 	},
125 	{
126 		/* sentinel */
127 	}
128 };
129 
twl_clks_probe(struct platform_device * pdev)130 static int twl_clks_probe(struct platform_device *pdev)
131 {
132 	struct clk_hw_onecell_data *clk_data;
133 	const struct twl_clks_data *hw_data;
134 
135 	struct twl_clock_info *cinfo;
136 	int ret;
137 	int i;
138 	int count;
139 
140 	hw_data = twl6032_clks;
141 	for (count = 0; hw_data[count].init.name; count++)
142 		;
143 
144 	clk_data = devm_kzalloc(&pdev->dev,
145 				struct_size(clk_data, hws, count),
146 				GFP_KERNEL);
147 	if (!clk_data)
148 		return -ENOMEM;
149 
150 	clk_data->num = count;
151 	cinfo = devm_kcalloc(&pdev->dev, count, sizeof(*cinfo), GFP_KERNEL);
152 	if (!cinfo)
153 		return -ENOMEM;
154 
155 	for (i = 0; i < count; i++) {
156 		cinfo[i].base = hw_data[i].base;
157 		cinfo[i].dev = &pdev->dev;
158 		cinfo[i].hw.init = &hw_data[i].init;
159 		ret = devm_clk_hw_register(&pdev->dev, &cinfo[i].hw);
160 		if (ret) {
161 			return dev_err_probe(&pdev->dev, ret,
162 					     "Fail to register clock %s\n",
163 					     hw_data[i].init.name);
164 		}
165 		clk_data->hws[i] = &cinfo[i].hw;
166 	}
167 
168 	ret = devm_of_clk_add_hw_provider(&pdev->dev,
169 					  of_clk_hw_onecell_get, clk_data);
170 	if (ret < 0)
171 		return dev_err_probe(&pdev->dev, ret,
172 				     "Fail to add clock driver\n");
173 
174 	return 0;
175 }
176 
177 static const struct platform_device_id twl_clks_id[] = {
178 	{
179 		.name = "twl6032-clk",
180 	}, {
181 		/* sentinel */
182 	}
183 };
184 MODULE_DEVICE_TABLE(platform, twl_clks_id);
185 
186 static struct platform_driver twl_clks_driver = {
187 	.driver = {
188 		.name = "twl-clk",
189 	},
190 	.probe = twl_clks_probe,
191 	.id_table = twl_clks_id,
192 };
193 
194 module_platform_driver(twl_clks_driver);
195 
196 MODULE_DESCRIPTION("Clock driver for TWL Series Devices");
197 MODULE_LICENSE("GPL");
198