• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Clock driver for Palmas device.
3  *
4  * Copyright (c) 2013, NVIDIA Corporation.
5  * Copyright (c) 2013-2014 Texas Instruments, Inc.
6  *
7  * Author:	Laxman Dewangan <ldewangan@nvidia.com>
8  *		Peter Ujfalusi <peter.ujfalusi@ti.com>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation version 2.
13  *
14  * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
15  * whether express or implied; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  */
19 
20 #include <linux/clk.h>
21 #include <linux/clk-provider.h>
22 #include <linux/mfd/palmas.h>
23 #include <linux/module.h>
24 #include <linux/of.h>
25 #include <linux/of_device.h>
26 #include <linux/platform_device.h>
27 #include <linux/slab.h>
28 
29 #define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1	1
30 #define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2	2
31 #define PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP	3
32 
33 struct palmas_clk32k_desc {
34 	const char *clk_name;
35 	unsigned int control_reg;
36 	unsigned int enable_mask;
37 	unsigned int sleep_mask;
38 	unsigned int sleep_reqstr_id;
39 	int delay;
40 };
41 
42 struct palmas_clock_info {
43 	struct device *dev;
44 	struct clk_hw hw;
45 	struct palmas *palmas;
46 	const struct palmas_clk32k_desc *clk_desc;
47 	int ext_control_pin;
48 };
49 
to_palmas_clks_info(struct clk_hw * hw)50 static inline struct palmas_clock_info *to_palmas_clks_info(struct clk_hw *hw)
51 {
52 	return container_of(hw, struct palmas_clock_info, hw);
53 }
54 
palmas_clks_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)55 static unsigned long palmas_clks_recalc_rate(struct clk_hw *hw,
56 					     unsigned long parent_rate)
57 {
58 	return 32768;
59 }
60 
palmas_clks_prepare(struct clk_hw * hw)61 static int palmas_clks_prepare(struct clk_hw *hw)
62 {
63 	struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
64 	int ret;
65 
66 	ret = palmas_update_bits(cinfo->palmas, PALMAS_RESOURCE_BASE,
67 				 cinfo->clk_desc->control_reg,
68 				 cinfo->clk_desc->enable_mask,
69 				 cinfo->clk_desc->enable_mask);
70 	if (ret < 0)
71 		dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n",
72 			cinfo->clk_desc->control_reg, ret);
73 	else if (cinfo->clk_desc->delay)
74 		udelay(cinfo->clk_desc->delay);
75 
76 	return ret;
77 }
78 
palmas_clks_unprepare(struct clk_hw * hw)79 static void palmas_clks_unprepare(struct clk_hw *hw)
80 {
81 	struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
82 	int ret;
83 
84 	/*
85 	 * Clock can be disabled through external pin if it is externally
86 	 * controlled.
87 	 */
88 	if (cinfo->ext_control_pin)
89 		return;
90 
91 	ret = palmas_update_bits(cinfo->palmas, PALMAS_RESOURCE_BASE,
92 				 cinfo->clk_desc->control_reg,
93 				 cinfo->clk_desc->enable_mask, 0);
94 	if (ret < 0)
95 		dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n",
96 			cinfo->clk_desc->control_reg, ret);
97 }
98 
palmas_clks_is_prepared(struct clk_hw * hw)99 static int palmas_clks_is_prepared(struct clk_hw *hw)
100 {
101 	struct palmas_clock_info *cinfo = to_palmas_clks_info(hw);
102 	int ret;
103 	u32 val;
104 
105 	if (cinfo->ext_control_pin)
106 		return 1;
107 
108 	ret = palmas_read(cinfo->palmas, PALMAS_RESOURCE_BASE,
109 			  cinfo->clk_desc->control_reg, &val);
110 	if (ret < 0) {
111 		dev_err(cinfo->dev, "Reg 0x%02x read failed, %d\n",
112 			cinfo->clk_desc->control_reg, ret);
113 		return ret;
114 	}
115 	return !!(val & cinfo->clk_desc->enable_mask);
116 }
117 
118 static const struct clk_ops palmas_clks_ops = {
119 	.prepare	= palmas_clks_prepare,
120 	.unprepare	= palmas_clks_unprepare,
121 	.is_prepared	= palmas_clks_is_prepared,
122 	.recalc_rate	= palmas_clks_recalc_rate,
123 };
124 
125 struct palmas_clks_of_match_data {
126 	struct clk_init_data init;
127 	const struct palmas_clk32k_desc desc;
128 };
129 
130 static const struct palmas_clks_of_match_data palmas_of_clk32kg = {
131 	.init = {
132 		.name = "clk32kg",
133 		.ops = &palmas_clks_ops,
134 		.flags = CLK_IGNORE_UNUSED,
135 	},
136 	.desc = {
137 		.clk_name = "clk32kg",
138 		.control_reg = PALMAS_CLK32KG_CTRL,
139 		.enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,
140 		.sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,
141 		.sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KG,
142 		.delay = 200,
143 	},
144 };
145 
146 static const struct palmas_clks_of_match_data palmas_of_clk32kgaudio = {
147 	.init = {
148 		.name = "clk32kgaudio",
149 		.ops = &palmas_clks_ops,
150 		.flags = CLK_IGNORE_UNUSED,
151 	},
152 	.desc = {
153 		.clk_name = "clk32kgaudio",
154 		.control_reg = PALMAS_CLK32KGAUDIO_CTRL,
155 		.enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE,
156 		.sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP,
157 		.sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KGAUDIO,
158 		.delay = 200,
159 	},
160 };
161 
162 static const struct of_device_id palmas_clks_of_match[] = {
163 	{
164 		.compatible = "ti,palmas-clk32kg",
165 		.data = &palmas_of_clk32kg,
166 	},
167 	{
168 		.compatible = "ti,palmas-clk32kgaudio",
169 		.data = &palmas_of_clk32kgaudio,
170 	},
171 	{ },
172 };
173 MODULE_DEVICE_TABLE(of, palmas_clks_of_match);
174 
palmas_clks_get_clk_data(struct platform_device * pdev,struct palmas_clock_info * cinfo)175 static void palmas_clks_get_clk_data(struct platform_device *pdev,
176 				     struct palmas_clock_info *cinfo)
177 {
178 	struct device_node *node = pdev->dev.of_node;
179 	unsigned int prop;
180 	int ret;
181 
182 	ret = of_property_read_u32(node, "ti,external-sleep-control",
183 				   &prop);
184 	if (ret)
185 		return;
186 
187 	switch (prop) {
188 	case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1:
189 		prop = PALMAS_EXT_CONTROL_ENABLE1;
190 		break;
191 	case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2:
192 		prop = PALMAS_EXT_CONTROL_ENABLE2;
193 		break;
194 	case PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP:
195 		prop = PALMAS_EXT_CONTROL_NSLEEP;
196 		break;
197 	default:
198 		dev_warn(&pdev->dev, "%pOFn: Invalid ext control option: %u\n",
199 			 node, prop);
200 		prop = 0;
201 		break;
202 	}
203 	cinfo->ext_control_pin = prop;
204 }
205 
palmas_clks_init_configure(struct palmas_clock_info * cinfo)206 static int palmas_clks_init_configure(struct palmas_clock_info *cinfo)
207 {
208 	int ret;
209 
210 	ret = palmas_update_bits(cinfo->palmas, PALMAS_RESOURCE_BASE,
211 				 cinfo->clk_desc->control_reg,
212 				 cinfo->clk_desc->sleep_mask, 0);
213 	if (ret < 0) {
214 		dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n",
215 			cinfo->clk_desc->control_reg, ret);
216 		return ret;
217 	}
218 
219 	if (cinfo->ext_control_pin) {
220 		ret = clk_prepare(cinfo->hw.clk);
221 		if (ret < 0) {
222 			dev_err(cinfo->dev, "Clock prep failed, %d\n", ret);
223 			return ret;
224 		}
225 
226 		ret = palmas_ext_control_req_config(cinfo->palmas,
227 					cinfo->clk_desc->sleep_reqstr_id,
228 					cinfo->ext_control_pin, true);
229 		if (ret < 0) {
230 			dev_err(cinfo->dev, "Ext config for %s failed, %d\n",
231 				cinfo->clk_desc->clk_name, ret);
232 			clk_unprepare(cinfo->hw.clk);
233 			return ret;
234 		}
235 	}
236 
237 	return ret;
238 }
palmas_clks_probe(struct platform_device * pdev)239 static int palmas_clks_probe(struct platform_device *pdev)
240 {
241 	struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
242 	struct device_node *node = pdev->dev.of_node;
243 	const struct palmas_clks_of_match_data *match_data;
244 	struct palmas_clock_info *cinfo;
245 	int ret;
246 
247 	match_data = of_device_get_match_data(&pdev->dev);
248 	if (!match_data)
249 		return 1;
250 
251 	cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
252 	if (!cinfo)
253 		return -ENOMEM;
254 
255 	palmas_clks_get_clk_data(pdev, cinfo);
256 	platform_set_drvdata(pdev, cinfo);
257 
258 	cinfo->dev = &pdev->dev;
259 	cinfo->palmas = palmas;
260 
261 	cinfo->clk_desc = &match_data->desc;
262 	cinfo->hw.init = &match_data->init;
263 	ret = devm_clk_hw_register(&pdev->dev, &cinfo->hw);
264 	if (ret) {
265 		dev_err(&pdev->dev, "Fail to register clock %s, %d\n",
266 			match_data->desc.clk_name, ret);
267 		return ret;
268 	}
269 
270 	ret = palmas_clks_init_configure(cinfo);
271 	if (ret < 0) {
272 		dev_err(&pdev->dev, "Clock config failed, %d\n", ret);
273 		return ret;
274 	}
275 
276 	ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &cinfo->hw);
277 	if (ret < 0)
278 		dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret);
279 	return ret;
280 }
281 
palmas_clks_remove(struct platform_device * pdev)282 static int palmas_clks_remove(struct platform_device *pdev)
283 {
284 	of_clk_del_provider(pdev->dev.of_node);
285 	return 0;
286 }
287 
288 static struct platform_driver palmas_clks_driver = {
289 	.driver = {
290 		.name = "palmas-clk",
291 		.of_match_table = palmas_clks_of_match,
292 	},
293 	.probe = palmas_clks_probe,
294 	.remove = palmas_clks_remove,
295 };
296 
297 module_platform_driver(palmas_clks_driver);
298 
299 MODULE_DESCRIPTION("Clock driver for Palmas Series Devices");
300 MODULE_ALIAS("platform:palmas-clk");
301 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
302 MODULE_LICENSE("GPL v2");
303