File Index
Files and directories
Sign in
FILE

clk-s2mps11.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/clk-s2mps11.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2//
  3// clk-s2mps11.c - Clock driver for S2MPS11.
  4//
  5// Copyright (C) 2013,2014 Samsung Electornics
  6
  7#include <linux/module.h>
  8#include <linux/err.h>
  9#include <linux/of.h>
 10#include <linux/clkdev.h>
 11#include <linux/regmap.h>
 12#include <linux/clk-provider.h>
 13#include <linux/platform_device.h>
 14#include <linux/mfd/samsung/s2mpg10.h>
 15#include <linux/mfd/samsung/s2mps11.h>
 16#include <linux/mfd/samsung/s2mps13.h>
 17#include <linux/mfd/samsung/s2mps14.h>
 18#include <linux/mfd/samsung/s5m8767.h>
 19#include <linux/mfd/samsung/core.h>
 20
 21#include <dt-bindings/clock/samsung,s2mps11.h>
 22
 23struct s2mps11_clk {
 24	struct sec_pmic_dev *iodev;
 25	struct device_node *clk_np;
 26	struct clk_hw hw;
 27	struct clk *clk;
 28	struct clk_lookup *lookup;
 29	u32 mask;
 30	unsigned int reg;
 31};
 32
 33static struct s2mps11_clk *to_s2mps11_clk(struct clk_hw *hw)
 34{
 35	return container_of(hw, struct s2mps11_clk, hw);
 36}
 37
 38static int s2mps11_clk_prepare(struct clk_hw *hw)
 39{
 40	struct s2mps11_clk *s2mps11 = to_s2mps11_clk(hw);
 41
 42	return regmap_update_bits(s2mps11->iodev->regmap_pmic,
 43				 s2mps11->reg,
 44				 s2mps11->mask, s2mps11->mask);
 45}
 46
 47static void s2mps11_clk_unprepare(struct clk_hw *hw)
 48{
 49	struct s2mps11_clk *s2mps11 = to_s2mps11_clk(hw);
 50
 51	regmap_update_bits(s2mps11->iodev->regmap_pmic, s2mps11->reg,
 52			   s2mps11->mask, ~s2mps11->mask);
 53}
 54
 55static int s2mps11_clk_is_prepared(struct clk_hw *hw)
 56{
 57	int ret;
 58	u32 val;
 59	struct s2mps11_clk *s2mps11 = to_s2mps11_clk(hw);
 60
 61	ret = regmap_read(s2mps11->iodev->regmap_pmic,
 62				s2mps11->reg, &val);
 63	if (ret < 0)
 64		return -EINVAL;
 65
 66	return val & s2mps11->mask;
 67}
 68
 69static unsigned long s2mps11_clk_recalc_rate(struct clk_hw *hw,
 70					     unsigned long parent_rate)
 71{
 72	return 32768;
 73}
 74
 75static const struct clk_ops s2mps11_clk_ops = {
 76	.prepare	= s2mps11_clk_prepare,
 77	.unprepare	= s2mps11_clk_unprepare,
 78	.is_prepared	= s2mps11_clk_is_prepared,
 79	.recalc_rate	= s2mps11_clk_recalc_rate,
 80};
 81
 82/* This s2mps11_clks_init tructure is common to s2mps11, s2mps13 and s2mps14 */
 83static struct clk_init_data s2mps11_clks_init[S2MPS11_CLKS_NUM] = {
 84	[S2MPS11_CLK_AP] = {
 85		.name = "s2mps11_ap",
 86		.ops = &s2mps11_clk_ops,
 87	},
 88	[S2MPS11_CLK_CP] = {
 89		.name = "s2mps11_cp",
 90		.ops = &s2mps11_clk_ops,
 91	},
 92	[S2MPS11_CLK_BT] = {
 93		.name = "s2mps11_bt",
 94		.ops = &s2mps11_clk_ops,
 95	},
 96};
 97
 98static struct device_node *s2mps11_clk_parse_dt(struct platform_device *pdev,
 99		struct clk_init_data *clks_init)
100{
101	struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent);
102	struct device_node *clk_np;
103	int i;
104
105	if (!iodev->dev->of_node)
106		return ERR_PTR(-EINVAL);
107
108	clk_np = of_get_child_by_name(iodev->dev->of_node, "clocks");
109	if (!clk_np) {
110		dev_err(&pdev->dev, "could not find clock sub-node\n");
111		return ERR_PTR(-EINVAL);
112	}
113
114	for (i = 0; i < S2MPS11_CLKS_NUM; i++)
115		of_property_read_string_index(clk_np, "clock-output-names", i,
116				&clks_init[i].name);
117
118	return clk_np;
119}
120
121static int s2mps11_clk_probe(struct platform_device *pdev)
122{
123	struct sec_pmic_dev *iodev = dev_get_drvdata(pdev->dev.parent);
124	struct s2mps11_clk *s2mps11_clks;
125	struct clk_hw_onecell_data *clk_data;
126	unsigned int s2mps11_reg;
127	int i, ret = 0;
128	enum sec_device_type hwid = platform_get_device_id(pdev)->driver_data;
129
130	s2mps11_clks = devm_kcalloc(&pdev->dev, S2MPS11_CLKS_NUM,
131				sizeof(*s2mps11_clks), GFP_KERNEL);
132	if (!s2mps11_clks)
133		return -ENOMEM;
134
135	clk_data = devm_kzalloc(&pdev->dev,
136				struct_size(clk_data, hws, S2MPS11_CLKS_NUM),
137				GFP_KERNEL);
138	if (!clk_data)
139		return -ENOMEM;
140
141	clk_data->num = S2MPS11_CLKS_NUM;
142
143	switch (hwid) {
144	case S2MPG10:
145		s2mps11_reg = S2MPG10_PMIC_RTCBUF;
146		break;
147	case S2MPS11X:
148		s2mps11_reg = S2MPS11_REG_RTC_CTRL;
149		break;
150	case S2MPS13X:
151		s2mps11_reg = S2MPS13_REG_RTCCTRL;
152		break;
153	case S2MPS14X:
154		s2mps11_reg = S2MPS14_REG_RTCCTRL;
155		break;
156	case S5M8767X:
157		s2mps11_reg = S5M8767_REG_CTRL1;
158		break;
159	default:
160		dev_err(&pdev->dev, "Invalid device type\n");
161		return -EINVAL;
162	}
163
164	/* Store clocks of_node in first element of s2mps11_clks array */
165	s2mps11_clks->clk_np = s2mps11_clk_parse_dt(pdev, s2mps11_clks_init);
166	if (IS_ERR(s2mps11_clks->clk_np))
167		return PTR_ERR(s2mps11_clks->clk_np);
168
169	for (i = 0; i < S2MPS11_CLKS_NUM; i++) {
170		if (i == S2MPS11_CLK_CP && hwid == S2MPS14X)
171			continue; /* Skip clocks not present in some devices */
172		s2mps11_clks[i].iodev = iodev;
173		s2mps11_clks[i].hw.init = &s2mps11_clks_init[i];
174		s2mps11_clks[i].mask = 1 << i;
175		s2mps11_clks[i].reg = s2mps11_reg;
176
177		s2mps11_clks[i].clk = devm_clk_register(&pdev->dev,
178							&s2mps11_clks[i].hw);
179		if (IS_ERR(s2mps11_clks[i].clk)) {
180			dev_err(&pdev->dev, "Fail to register : %s\n",
181						s2mps11_clks_init[i].name);
182			ret = PTR_ERR(s2mps11_clks[i].clk);
183			goto err_reg;
184		}
185
186		s2mps11_clks[i].lookup = clkdev_hw_create(&s2mps11_clks[i].hw,
187					s2mps11_clks_init[i].name, NULL);
188		if (!s2mps11_clks[i].lookup) {
189			ret = -ENOMEM;
190			goto err_reg;
191		}
192		clk_data->hws[i] = &s2mps11_clks[i].hw;
193	}
194
195	of_clk_add_hw_provider(s2mps11_clks->clk_np, of_clk_hw_onecell_get,
196			       clk_data);
197
198	platform_set_drvdata(pdev, s2mps11_clks);
199
200	return ret;
201
202err_reg:
203	of_node_put(s2mps11_clks[0].clk_np);
204	while (--i >= 0)
205		clkdev_drop(s2mps11_clks[i].lookup);
206
207	return ret;
208}
209
210static void s2mps11_clk_remove(struct platform_device *pdev)
211{
212	struct s2mps11_clk *s2mps11_clks = platform_get_drvdata(pdev);
213	int i;
214
215	of_clk_del_provider(s2mps11_clks[0].clk_np);
216	/* Drop the reference obtained in s2mps11_clk_parse_dt */
217	of_node_put(s2mps11_clks[0].clk_np);
218
219	for (i = 0; i < S2MPS11_CLKS_NUM; i++) {
220		/* Skip clocks not present on S2MPS14 */
221		if (!s2mps11_clks[i].lookup)
222			continue;
223		clkdev_drop(s2mps11_clks[i].lookup);
224	}
225}
226
227static const struct platform_device_id s2mps11_clk_id[] = {
228	{ "s2mpg10-clk", S2MPG10},
229	{ "s2mps11-clk", S2MPS11X},
230	{ "s2mps13-clk", S2MPS13X},
231	{ "s2mps14-clk", S2MPS14X},
232	{ "s5m8767-clk", S5M8767X},
233	{ },
234};
235MODULE_DEVICE_TABLE(platform, s2mps11_clk_id);
236
237#ifdef CONFIG_OF
238/*
239 * Device is instantiated through parent MFD device and device matching is done
240 * through platform_device_id.
241 *
242 * However if device's DT node contains proper clock compatible and driver is
243 * built as a module, then the *module* matching will be done through DT aliases.
244 * This requires of_device_id table.  In the same time this will not change the
245 * actual *device* matching so do not add .of_match_table.
246 */
247static const struct of_device_id s2mps11_dt_match[] __used = {
248	{
249		.compatible = "samsung,s2mpg10-clk",
250		.data = (void *)S2MPG10,
251	}, {
252		.compatible = "samsung,s2mps11-clk",
253		.data = (void *)S2MPS11X,
254	}, {
255		.compatible = "samsung,s2mps13-clk",
256		.data = (void *)S2MPS13X,
257	}, {
258		.compatible = "samsung,s2mps14-clk",
259		.data = (void *)S2MPS14X,
260	}, {
261		.compatible = "samsung,s5m8767-clk",
262		.data = (void *)S5M8767X,
263	}, {
264		/* Sentinel */
265	},
266};
267MODULE_DEVICE_TABLE(of, s2mps11_dt_match);
268#endif
269
270static struct platform_driver s2mps11_clk_driver = {
271	.driver = {
272		.name  = "s2mps11-clk",
273	},
274	.probe = s2mps11_clk_probe,
275	.remove = s2mps11_clk_remove,
276	.id_table = s2mps11_clk_id,
277};
278module_platform_driver(s2mps11_clk_driver);
279
280MODULE_DESCRIPTION("S2MPS11 Clock Driver");
281MODULE_AUTHOR("Yadwinder Singh Brar <yadi.brar@samsung.com>");
282MODULE_LICENSE("GPL");
~/ Read-only file preview.