1  // SPDX-License-Identifier: GPL-2.0
2  /*
3   * Lochnagar clock control
4   *
5   * Copyright (c) 2017-2018 Cirrus Logic, Inc. and
6   *                         Cirrus Logic International Semiconductor Ltd.
7   *
8   * Author: Charles Keepax <ckeepax@opensource.cirrus.com>
9   */
10  
11  #include <linux/clk-provider.h>
12  #include <linux/device.h>
13  #include <linux/module.h>
14  #include <linux/of.h>
15  #include <linux/platform_device.h>
16  #include <linux/property.h>
17  #include <linux/regmap.h>
18  
19  #include <linux/mfd/lochnagar1_regs.h>
20  #include <linux/mfd/lochnagar2_regs.h>
21  
22  #include <dt-bindings/clock/lochnagar.h>
23  
24  #define LOCHNAGAR_NUM_CLOCKS	(LOCHNAGAR_SPDIF_CLKOUT + 1)
25  
26  struct lochnagar_clk {
27  	const char * const name;
28  	struct clk_hw hw;
29  
30  	struct lochnagar_clk_priv *priv;
31  
32  	u16 cfg_reg;
33  	u16 ena_mask;
34  
35  	u16 src_reg;
36  	u16 src_mask;
37  };
38  
39  struct lochnagar_clk_priv {
40  	struct device *dev;
41  	struct regmap *regmap;
42  
43  	struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS];
44  };
45  
46  #define LN_PARENT(NAME) { .name = NAME, .fw_name = NAME }
47  
48  static const struct clk_parent_data lochnagar1_clk_parents[] = {
49  	LN_PARENT("ln-none"),
50  	LN_PARENT("ln-spdif-mclk"),
51  	LN_PARENT("ln-psia1-mclk"),
52  	LN_PARENT("ln-psia2-mclk"),
53  	LN_PARENT("ln-cdc-clkout"),
54  	LN_PARENT("ln-dsp-clkout"),
55  	LN_PARENT("ln-pmic-32k"),
56  	LN_PARENT("ln-gf-mclk1"),
57  	LN_PARENT("ln-gf-mclk3"),
58  	LN_PARENT("ln-gf-mclk2"),
59  	LN_PARENT("ln-gf-mclk4"),
60  };
61  
62  static const struct clk_parent_data lochnagar2_clk_parents[] = {
63  	LN_PARENT("ln-none"),
64  	LN_PARENT("ln-cdc-clkout"),
65  	LN_PARENT("ln-dsp-clkout"),
66  	LN_PARENT("ln-pmic-32k"),
67  	LN_PARENT("ln-spdif-mclk"),
68  	LN_PARENT("ln-clk-12m"),
69  	LN_PARENT("ln-clk-11m"),
70  	LN_PARENT("ln-clk-24m"),
71  	LN_PARENT("ln-clk-22m"),
72  	LN_PARENT("ln-clk-8m"),
73  	LN_PARENT("ln-usb-clk-24m"),
74  	LN_PARENT("ln-gf-mclk1"),
75  	LN_PARENT("ln-gf-mclk3"),
76  	LN_PARENT("ln-gf-mclk2"),
77  	LN_PARENT("ln-psia1-mclk"),
78  	LN_PARENT("ln-psia2-mclk"),
79  	LN_PARENT("ln-spdif-clkout"),
80  	LN_PARENT("ln-adat-mclk"),
81  	LN_PARENT("ln-usb-clk-12m"),
82  };
83  
84  #define LN1_CLK(ID, NAME, REG) \
85  	[LOCHNAGAR_##ID] = { \
86  		.name = NAME, \
87  		.cfg_reg = LOCHNAGAR1_##REG, \
88  		.ena_mask = LOCHNAGAR1_##ID##_ENA_MASK, \
89  		.src_reg = LOCHNAGAR1_##ID##_SEL, \
90  		.src_mask = LOCHNAGAR1_SRC_MASK, \
91  	}
92  
93  #define LN2_CLK(ID, NAME) \
94  	[LOCHNAGAR_##ID] = { \
95  		.name = NAME, \
96  		.cfg_reg = LOCHNAGAR2_##ID##_CTRL, \
97  		.src_reg = LOCHNAGAR2_##ID##_CTRL, \
98  		.ena_mask = LOCHNAGAR2_CLK_ENA_MASK, \
99  		.src_mask = LOCHNAGAR2_CLK_SRC_MASK, \
100  	}
101  
102  static const struct lochnagar_clk lochnagar1_clks[LOCHNAGAR_NUM_CLOCKS] = {
103  	LN1_CLK(CDC_MCLK1,      "ln-cdc-mclk1",  CDC_AIF_CTRL2),
104  	LN1_CLK(CDC_MCLK2,      "ln-cdc-mclk2",  CDC_AIF_CTRL2),
105  	LN1_CLK(DSP_CLKIN,      "ln-dsp-clkin",  DSP_AIF),
106  	LN1_CLK(GF_CLKOUT1,     "ln-gf-clkout1", GF_AIF1),
107  };
108  
109  static const struct lochnagar_clk lochnagar2_clks[LOCHNAGAR_NUM_CLOCKS] = {
110  	LN2_CLK(CDC_MCLK1,      "ln-cdc-mclk1"),
111  	LN2_CLK(CDC_MCLK2,      "ln-cdc-mclk2"),
112  	LN2_CLK(DSP_CLKIN,      "ln-dsp-clkin"),
113  	LN2_CLK(GF_CLKOUT1,     "ln-gf-clkout1"),
114  	LN2_CLK(GF_CLKOUT2,     "ln-gf-clkout2"),
115  	LN2_CLK(PSIA1_MCLK,     "ln-psia1-mclk"),
116  	LN2_CLK(PSIA2_MCLK,     "ln-psia2-mclk"),
117  	LN2_CLK(SPDIF_MCLK,     "ln-spdif-mclk"),
118  	LN2_CLK(ADAT_MCLK,      "ln-adat-mclk"),
119  	LN2_CLK(SOUNDCARD_MCLK, "ln-soundcard-mclk"),
120  };
121  
122  struct lochnagar_config {
123  	const struct clk_parent_data *parents;
124  	int nparents;
125  	const struct lochnagar_clk *clks;
126  };
127  
128  static const struct lochnagar_config lochnagar1_conf = {
129  	.parents = lochnagar1_clk_parents,
130  	.nparents = ARRAY_SIZE(lochnagar1_clk_parents),
131  	.clks = lochnagar1_clks,
132  };
133  
134  static const struct lochnagar_config lochnagar2_conf = {
135  	.parents = lochnagar2_clk_parents,
136  	.nparents = ARRAY_SIZE(lochnagar2_clk_parents),
137  	.clks = lochnagar2_clks,
138  };
139  
lochnagar_hw_to_lclk(struct clk_hw * hw)140  static inline struct lochnagar_clk *lochnagar_hw_to_lclk(struct clk_hw *hw)
141  {
142  	return container_of(hw, struct lochnagar_clk, hw);
143  }
144  
lochnagar_clk_prepare(struct clk_hw * hw)145  static int lochnagar_clk_prepare(struct clk_hw *hw)
146  {
147  	struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw);
148  	struct lochnagar_clk_priv *priv = lclk->priv;
149  	struct regmap *regmap = priv->regmap;
150  	int ret;
151  
152  	ret = regmap_update_bits(regmap, lclk->cfg_reg,
153  				 lclk->ena_mask, lclk->ena_mask);
154  	if (ret < 0)
155  		dev_dbg(priv->dev, "Failed to prepare %s: %d\n",
156  			lclk->name, ret);
157  
158  	return ret;
159  }
160  
lochnagar_clk_unprepare(struct clk_hw * hw)161  static void lochnagar_clk_unprepare(struct clk_hw *hw)
162  {
163  	struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw);
164  	struct lochnagar_clk_priv *priv = lclk->priv;
165  	struct regmap *regmap = priv->regmap;
166  	int ret;
167  
168  	ret = regmap_update_bits(regmap, lclk->cfg_reg, lclk->ena_mask, 0);
169  	if (ret < 0)
170  		dev_dbg(priv->dev, "Failed to unprepare %s: %d\n",
171  			lclk->name, ret);
172  }
173  
lochnagar_clk_set_parent(struct clk_hw * hw,u8 index)174  static int lochnagar_clk_set_parent(struct clk_hw *hw, u8 index)
175  {
176  	struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw);
177  	struct lochnagar_clk_priv *priv = lclk->priv;
178  	struct regmap *regmap = priv->regmap;
179  	int ret;
180  
181  	ret = regmap_update_bits(regmap, lclk->src_reg, lclk->src_mask, index);
182  	if (ret < 0)
183  		dev_dbg(priv->dev, "Failed to reparent %s: %d\n",
184  			lclk->name, ret);
185  
186  	return ret;
187  }
188  
lochnagar_clk_get_parent(struct clk_hw * hw)189  static u8 lochnagar_clk_get_parent(struct clk_hw *hw)
190  {
191  	struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw);
192  	struct lochnagar_clk_priv *priv = lclk->priv;
193  	struct regmap *regmap = priv->regmap;
194  	unsigned int val;
195  	int ret;
196  
197  	ret = regmap_read(regmap, lclk->src_reg, &val);
198  	if (ret < 0) {
199  		dev_dbg(priv->dev, "Failed to read parent of %s: %d\n",
200  			lclk->name, ret);
201  		return clk_hw_get_num_parents(hw);
202  	}
203  
204  	val &= lclk->src_mask;
205  
206  	return val;
207  }
208  
209  static const struct clk_ops lochnagar_clk_ops = {
210  	.prepare = lochnagar_clk_prepare,
211  	.unprepare = lochnagar_clk_unprepare,
212  	.determine_rate = clk_hw_determine_rate_no_reparent,
213  	.set_parent = lochnagar_clk_set_parent,
214  	.get_parent = lochnagar_clk_get_parent,
215  };
216  
217  static struct clk_hw *
lochnagar_of_clk_hw_get(struct of_phandle_args * clkspec,void * data)218  lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data)
219  {
220  	struct lochnagar_clk_priv *priv = data;
221  	unsigned int idx = clkspec->args[0];
222  
223  	if (idx >= ARRAY_SIZE(priv->lclks)) {
224  		dev_err(priv->dev, "Invalid index %u\n", idx);
225  		return ERR_PTR(-EINVAL);
226  	}
227  
228  	return &priv->lclks[idx].hw;
229  }
230  
231  static const struct of_device_id lochnagar_of_match[] = {
232  	{ .compatible = "cirrus,lochnagar1-clk", .data = &lochnagar1_conf },
233  	{ .compatible = "cirrus,lochnagar2-clk", .data = &lochnagar2_conf },
234  	{}
235  };
236  MODULE_DEVICE_TABLE(of, lochnagar_of_match);
237  
lochnagar_clk_probe(struct platform_device * pdev)238  static int lochnagar_clk_probe(struct platform_device *pdev)
239  {
240  	struct clk_init_data clk_init = {
241  		.ops = &lochnagar_clk_ops,
242  	};
243  	struct device *dev = &pdev->dev;
244  	struct lochnagar_clk_priv *priv;
245  	struct lochnagar_clk *lclk;
246  	struct lochnagar_config *conf;
247  	int ret, i;
248  
249  	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
250  	if (!priv)
251  		return -ENOMEM;
252  
253  	priv->dev = dev;
254  	priv->regmap = dev_get_regmap(dev->parent, NULL);
255  	conf = (struct lochnagar_config *)device_get_match_data(dev);
256  
257  	memcpy(priv->lclks, conf->clks, sizeof(priv->lclks));
258  
259  	clk_init.parent_data = conf->parents;
260  	clk_init.num_parents = conf->nparents;
261  
262  	for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) {
263  		lclk = &priv->lclks[i];
264  
265  		if (!lclk->name)
266  			continue;
267  
268  		clk_init.name = lclk->name;
269  
270  		lclk->priv = priv;
271  		lclk->hw.init = &clk_init;
272  
273  		ret = devm_clk_hw_register(dev, &lclk->hw);
274  		if (ret) {
275  			dev_err(dev, "Failed to register %s: %d\n",
276  				lclk->name, ret);
277  			return ret;
278  		}
279  	}
280  
281  	ret = devm_of_clk_add_hw_provider(dev, lochnagar_of_clk_hw_get, priv);
282  	if (ret < 0)
283  		dev_err(dev, "Failed to register provider: %d\n", ret);
284  
285  	return ret;
286  }
287  
288  static struct platform_driver lochnagar_clk_driver = {
289  	.driver = {
290  		.name = "lochnagar-clk",
291  		.of_match_table = lochnagar_of_match,
292  	},
293  	.probe = lochnagar_clk_probe,
294  };
295  module_platform_driver(lochnagar_clk_driver);
296  
297  MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>");
298  MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board");
299  MODULE_LICENSE("GPL v2");
300