1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright 2022 NXP, Peng Fan <peng.fan@nxp.com>
4 */
5
6#include <linux/clk.h>
7#include <linux/device.h>
8#include <linux/module.h>
9#include <linux/of.h>
10#include <linux/platform_device.h>
11#include <linux/pm_domain.h>
12#include <linux/pm_runtime.h>
13#include <linux/regmap.h>
14#include <linux/sizes.h>
15
16#include <dt-bindings/power/fsl,imx93-power.h>
17
18#define BLK_SFT_RSTN	0x0
19#define BLK_CLK_EN	0x4
20#define BLK_MAX_CLKS	4
21
22#define DOMAIN_MAX_CLKS 4
23
24#define LCDIF_QOS_REG		0xC
25#define LCDIF_DEFAULT_QOS_OFF	12
26#define LCDIF_CFG_QOS_OFF	8
27
28#define PXP_QOS_REG		0x10
29#define PXP_R_DEFAULT_QOS_OFF	28
30#define PXP_R_CFG_QOS_OFF	24
31#define PXP_W_DEFAULT_QOS_OFF	20
32#define PXP_W_CFG_QOS_OFF	16
33
34#define ISI_CACHE_REG		0x14
35
36#define ISI_QOS_REG		0x1C
37#define ISI_V_DEFAULT_QOS_OFF	28
38#define ISI_V_CFG_QOS_OFF	24
39#define ISI_U_DEFAULT_QOS_OFF	20
40#define ISI_U_CFG_QOS_OFF	16
41#define ISI_Y_R_DEFAULT_QOS_OFF	12
42#define ISI_Y_R_CFG_QOS_OFF	8
43#define ISI_Y_W_DEFAULT_QOS_OFF	4
44#define ISI_Y_W_CFG_QOS_OFF	0
45
46#define PRIO_MASK		0xF
47
48#define PRIO(X)			(X)
49
50struct imx93_blk_ctrl_domain;
51
52struct imx93_blk_ctrl {
53	struct device *dev;
54	struct regmap *regmap;
55	int num_clks;
56	struct clk_bulk_data clks[BLK_MAX_CLKS];
57	struct imx93_blk_ctrl_domain *domains;
58	struct genpd_onecell_data onecell_data;
59};
60
61#define DOMAIN_MAX_QOS 4
62
63struct imx93_blk_ctrl_qos {
64	u32 reg;
65	u32 cfg_off;
66	u32 default_prio;
67	u32 cfg_prio;
68};
69
70struct imx93_blk_ctrl_domain_data {
71	const char *name;
72	const char * const *clk_names;
73	int num_clks;
74	u32 rst_mask;
75	u32 clk_mask;
76	int num_qos;
77	struct imx93_blk_ctrl_qos qos[DOMAIN_MAX_QOS];
78};
79
80struct imx93_blk_ctrl_domain {
81	struct generic_pm_domain genpd;
82	const struct imx93_blk_ctrl_domain_data *data;
83	struct clk_bulk_data clks[DOMAIN_MAX_CLKS];
84	struct imx93_blk_ctrl *bc;
85};
86
87struct imx93_blk_ctrl_data {
88	const struct imx93_blk_ctrl_domain_data *domains;
89	int num_domains;
90	const char * const *clk_names;
91	int num_clks;
92	const struct regmap_access_table *reg_access_table;
93};
94
95static inline struct imx93_blk_ctrl_domain *
96to_imx93_blk_ctrl_domain(struct generic_pm_domain *genpd)
97{
98	return container_of(genpd, struct imx93_blk_ctrl_domain, genpd);
99}
100
101static int imx93_blk_ctrl_set_qos(struct imx93_blk_ctrl_domain *domain)
102{
103	const struct imx93_blk_ctrl_domain_data *data = domain->data;
104	struct imx93_blk_ctrl *bc = domain->bc;
105	const struct imx93_blk_ctrl_qos *qos;
106	u32 val, mask;
107	int i;
108
109	for (i = 0; i < data->num_qos; i++) {
110		qos = &data->qos[i];
111
112		mask = PRIO_MASK << qos->cfg_off;
113		mask |= PRIO_MASK << (qos->cfg_off + 4);
114		val = qos->cfg_prio << qos->cfg_off;
115		val |= qos->default_prio << (qos->cfg_off + 4);
116
117		regmap_write_bits(bc->regmap, qos->reg, mask, val);
118
119		dev_dbg(bc->dev, "data->qos[i].reg 0x%x 0x%x\n", qos->reg, val);
120	}
121
122	return 0;
123}
124
125static int imx93_blk_ctrl_power_on(struct generic_pm_domain *genpd)
126{
127	struct imx93_blk_ctrl_domain *domain = to_imx93_blk_ctrl_domain(genpd);
128	const struct imx93_blk_ctrl_domain_data *data = domain->data;
129	struct imx93_blk_ctrl *bc = domain->bc;
130	int ret;
131
132	ret = clk_bulk_prepare_enable(bc->num_clks, bc->clks);
133	if (ret) {
134		dev_err(bc->dev, "failed to enable bus clocks\n");
135		return ret;
136	}
137
138	ret = clk_bulk_prepare_enable(data->num_clks, domain->clks);
139	if (ret) {
140		clk_bulk_disable_unprepare(bc->num_clks, bc->clks);
141		dev_err(bc->dev, "failed to enable clocks\n");
142		return ret;
143	}
144
145	ret = pm_runtime_get_sync(bc->dev);
146	if (ret < 0) {
147		pm_runtime_put_noidle(bc->dev);
148		dev_err(bc->dev, "failed to power up domain\n");
149		goto disable_clk;
150	}
151
152	/* ungate clk */
153	regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
154
155	/* release reset */
156	regmap_set_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask);
157
158	dev_dbg(bc->dev, "pd_on: name: %s\n", genpd->name);
159
160	return imx93_blk_ctrl_set_qos(domain);
161
162disable_clk:
163	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
164
165	clk_bulk_disable_unprepare(bc->num_clks, bc->clks);
166
167	return ret;
168}
169
170static int imx93_blk_ctrl_power_off(struct generic_pm_domain *genpd)
171{
172	struct imx93_blk_ctrl_domain *domain = to_imx93_blk_ctrl_domain(genpd);
173	const struct imx93_blk_ctrl_domain_data *data = domain->data;
174	struct imx93_blk_ctrl *bc = domain->bc;
175
176	dev_dbg(bc->dev, "pd_off: name: %s\n", genpd->name);
177
178	regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask);
179	regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
180
181	pm_runtime_put(bc->dev);
182
183	clk_bulk_disable_unprepare(data->num_clks, domain->clks);
184
185	clk_bulk_disable_unprepare(bc->num_clks, bc->clks);
186
187	return 0;
188}
189
190static struct lock_class_key blk_ctrl_genpd_lock_class;
191
192static int imx93_blk_ctrl_probe(struct platform_device *pdev)
193{
194	struct device *dev = &pdev->dev;
195	const struct imx93_blk_ctrl_data *bc_data = of_device_get_match_data(dev);
196	struct imx93_blk_ctrl *bc;
197	void __iomem *base;
198	int i, ret;
199
200	struct regmap_config regmap_config = {
201		.reg_bits	= 32,
202		.val_bits	= 32,
203		.reg_stride	= 4,
204		.rd_table	= bc_data->reg_access_table,
205		.wr_table	= bc_data->reg_access_table,
206		.max_register   = SZ_4K,
207	};
208
209	bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL);
210	if (!bc)
211		return -ENOMEM;
212
213	bc->dev = dev;
214
215	base = devm_platform_ioremap_resource(pdev, 0);
216	if (IS_ERR(base))
217		return PTR_ERR(base);
218
219	bc->regmap = devm_regmap_init_mmio(dev, base, &regmap_config);
220	if (IS_ERR(bc->regmap))
221		return dev_err_probe(dev, PTR_ERR(bc->regmap),
222				     "failed to init regmap\n");
223
224	bc->domains = devm_kcalloc(dev, bc_data->num_domains,
225				   sizeof(struct imx93_blk_ctrl_domain),
226				   GFP_KERNEL);
227	if (!bc->domains)
228		return -ENOMEM;
229
230	bc->onecell_data.num_domains = bc_data->num_domains;
231	bc->onecell_data.domains =
232		devm_kcalloc(dev, bc_data->num_domains,
233			     sizeof(struct generic_pm_domain *), GFP_KERNEL);
234	if (!bc->onecell_data.domains)
235		return -ENOMEM;
236
237	for (i = 0; i < bc_data->num_clks; i++)
238		bc->clks[i].id = bc_data->clk_names[i];
239	bc->num_clks = bc_data->num_clks;
240
241	ret = devm_clk_bulk_get(dev, bc->num_clks, bc->clks);
242	if (ret) {
243		dev_err_probe(dev, ret, "failed to get bus clock\n");
244		return ret;
245	}
246
247	for (i = 0; i < bc_data->num_domains; i++) {
248		const struct imx93_blk_ctrl_domain_data *data = &bc_data->domains[i];
249		struct imx93_blk_ctrl_domain *domain = &bc->domains[i];
250		int j;
251
252		domain->data = data;
253
254		for (j = 0; j < data->num_clks; j++)
255			domain->clks[j].id = data->clk_names[j];
256
257		ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks);
258		if (ret) {
259			dev_err_probe(dev, ret, "failed to get clock\n");
260			goto cleanup_pds;
261		}
262
263		domain->genpd.name = data->name;
264		domain->genpd.power_on = imx93_blk_ctrl_power_on;
265		domain->genpd.power_off = imx93_blk_ctrl_power_off;
266		domain->bc = bc;
267
268		ret = pm_genpd_init(&domain->genpd, NULL, true);
269		if (ret) {
270			dev_err_probe(dev, ret, "failed to init power domain\n");
271			goto cleanup_pds;
272		}
273
274		/*
275		 * We use runtime PM to trigger power on/off of the upstream GPC
276		 * domain, as a strict hierarchical parent/child power domain
277		 * setup doesn't allow us to meet the sequencing requirements.
278		 * This means we have nested locking of genpd locks, without the
279		 * nesting being visible at the genpd level, so we need a
280		 * separate lock class to make lockdep aware of the fact that
281		 * this are separate domain locks that can be nested without a
282		 * self-deadlock.
283		 */
284		lockdep_set_class(&domain->genpd.mlock,
285				  &blk_ctrl_genpd_lock_class);
286
287		bc->onecell_data.domains[i] = &domain->genpd;
288	}
289
290	pm_runtime_enable(dev);
291
292	ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data);
293	if (ret) {
294		dev_err_probe(dev, ret, "failed to add power domain provider\n");
295		goto cleanup_pds;
296	}
297
298	dev_set_drvdata(dev, bc);
299
300	return 0;
301
302cleanup_pds:
303	for (i--; i >= 0; i--)
304		pm_genpd_remove(&bc->domains[i].genpd);
305
306	return ret;
307}
308
309static void imx93_blk_ctrl_remove(struct platform_device *pdev)
310{
311	struct imx93_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
312	int i;
313
314	of_genpd_del_provider(pdev->dev.of_node);
315
316	for (i = 0; bc->onecell_data.num_domains; i++) {
317		struct imx93_blk_ctrl_domain *domain = &bc->domains[i];
318
319		pm_genpd_remove(&domain->genpd);
320	}
321}
322
323static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] = {
324	[IMX93_MEDIABLK_PD_MIPI_DSI] = {
325		.name = "mediablk-mipi-dsi",
326		.clk_names = (const char *[]){ "dsi" },
327		.num_clks = 1,
328		.rst_mask = BIT(11) | BIT(12),
329		.clk_mask = BIT(11) | BIT(12),
330	},
331	[IMX93_MEDIABLK_PD_MIPI_CSI] = {
332		.name = "mediablk-mipi-csi",
333		.clk_names = (const char *[]){ "cam", "csi" },
334		.num_clks = 2,
335		.rst_mask = BIT(9) | BIT(10),
336		.clk_mask = BIT(9) | BIT(10),
337	},
338	[IMX93_MEDIABLK_PD_PXP] = {
339		.name = "mediablk-pxp",
340		.clk_names = (const char *[]){ "pxp" },
341		.num_clks = 1,
342		.rst_mask = BIT(7) | BIT(8),
343		.clk_mask = BIT(7) | BIT(8),
344		.num_qos = 2,
345		.qos = {
346			{
347				.reg = PXP_QOS_REG,
348				.cfg_off = PXP_R_CFG_QOS_OFF,
349				.default_prio = PRIO(3),
350				.cfg_prio = PRIO(6),
351			}, {
352				.reg = PXP_QOS_REG,
353				.cfg_off = PXP_W_CFG_QOS_OFF,
354				.default_prio = PRIO(3),
355				.cfg_prio = PRIO(6),
356			}
357		}
358	},
359	[IMX93_MEDIABLK_PD_LCDIF] = {
360		.name = "mediablk-lcdif",
361		.clk_names = (const char *[]){ "disp", "lcdif" },
362		.num_clks = 2,
363		.rst_mask = BIT(4) | BIT(5) | BIT(6),
364		.clk_mask = BIT(4) | BIT(5) | BIT(6),
365		.num_qos = 1,
366		.qos = {
367			{
368			.reg = LCDIF_QOS_REG,
369			.cfg_off = LCDIF_CFG_QOS_OFF,
370			.default_prio = PRIO(3),
371			.cfg_prio = PRIO(7),
372			}
373		}
374	},
375	[IMX93_MEDIABLK_PD_ISI] = {
376		.name = "mediablk-isi",
377		.clk_names = (const char *[]){ "isi" },
378		.num_clks = 1,
379		.rst_mask = BIT(2) | BIT(3),
380		.clk_mask = BIT(2) | BIT(3),
381		.num_qos = 4,
382		.qos = {
383			{
384				.reg = ISI_QOS_REG,
385				.cfg_off = ISI_Y_W_CFG_QOS_OFF,
386				.default_prio = PRIO(3),
387				.cfg_prio = PRIO(7),
388			}, {
389				.reg = ISI_QOS_REG,
390				.cfg_off = ISI_Y_R_CFG_QOS_OFF,
391				.default_prio = PRIO(3),
392				.cfg_prio = PRIO(7),
393			}, {
394				.reg = ISI_QOS_REG,
395				.cfg_off = ISI_U_CFG_QOS_OFF,
396				.default_prio = PRIO(3),
397				.cfg_prio = PRIO(7),
398			}, {
399				.reg = ISI_QOS_REG,
400				.cfg_off = ISI_V_CFG_QOS_OFF,
401				.default_prio = PRIO(3),
402				.cfg_prio = PRIO(7),
403			}
404		}
405	},
406};
407
408static const struct regmap_range imx93_media_blk_ctl_yes_ranges[] = {
409	regmap_reg_range(BLK_SFT_RSTN, BLK_CLK_EN),
410	regmap_reg_range(LCDIF_QOS_REG, ISI_CACHE_REG),
411	regmap_reg_range(ISI_QOS_REG, ISI_QOS_REG),
412};
413
414static const struct regmap_access_table imx93_media_blk_ctl_access_table = {
415	.yes_ranges = imx93_media_blk_ctl_yes_ranges,
416	.n_yes_ranges = ARRAY_SIZE(imx93_media_blk_ctl_yes_ranges),
417};
418
419static const struct imx93_blk_ctrl_data imx93_media_blk_ctl_dev_data = {
420	.domains = imx93_media_blk_ctl_domain_data,
421	.num_domains = ARRAY_SIZE(imx93_media_blk_ctl_domain_data),
422	.clk_names = (const char *[]){ "axi", "apb", "nic", },
423	.num_clks = 3,
424	.reg_access_table = &imx93_media_blk_ctl_access_table,
425};
426
427static const struct of_device_id imx93_blk_ctrl_of_match[] = {
428	{
429		.compatible = "fsl,imx93-media-blk-ctrl",
430		.data = &imx93_media_blk_ctl_dev_data
431	}, {
432		/* Sentinel */
433	}
434};
435MODULE_DEVICE_TABLE(of, imx93_blk_ctrl_of_match);
436
437static struct platform_driver imx93_blk_ctrl_driver = {
438	.probe = imx93_blk_ctrl_probe,
439	.remove_new = imx93_blk_ctrl_remove,
440	.driver = {
441		.name = "imx93-blk-ctrl",
442		.of_match_table = imx93_blk_ctrl_of_match,
443	},
444};
445module_platform_driver(imx93_blk_ctrl_driver);
446
447MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
448MODULE_DESCRIPTION("i.MX93 BLK CTRL driver");
449MODULE_LICENSE("GPL");
450