1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright 2018 NXP
4 *	Dong Aisheng <aisheng.dong@nxp.com>
5 */
6
7#include <linux/clk-provider.h>
8#include <linux/err.h>
9#include <linux/io.h>
10#include <linux/module.h>
11#include <linux/of.h>
12#include <linux/platform_device.h>
13#include <linux/pm_runtime.h>
14#include <linux/slab.h>
15
16#include "clk-scu.h"
17#include "clk-imx8qxp-lpcg.h"
18
19#include <dt-bindings/clock/imx8-clock.h>
20
21/*
22 * struct imx8qxp_lpcg_data - Description of one LPCG clock
23 * @id: clock ID
24 * @name: clock name
25 * @parent: parent clock name
26 * @flags: common clock flags
27 * @offset: offset of this LPCG clock
28 * @bit_idx: bit index of this LPCG clock
29 * @hw_gate: whether supports HW autogate
30 *
31 * This structure describes one LPCG clock
32 */
33struct imx8qxp_lpcg_data {
34	int id;
35	char *name;
36	char *parent;
37	unsigned long flags;
38	u32 offset;
39	u8 bit_idx;
40	bool hw_gate;
41};
42
43/*
44 * struct imx8qxp_ss_lpcg - Description of one subsystem LPCG clocks
45 * @lpcg: LPCG clocks array of one subsystem
46 * @num_lpcg: the number of LPCG clocks
47 * @num_max: the maximum number of LPCG clocks
48 *
49 * This structure describes each subsystem LPCG clocks information
50 * which then will be used to create respective LPCGs clocks
51 */
52struct imx8qxp_ss_lpcg {
53	const struct imx8qxp_lpcg_data *lpcg;
54	u8 num_lpcg;
55	u8 num_max;
56};
57
58static const struct imx8qxp_lpcg_data imx8qxp_lpcg_adma[] = {
59	{ IMX_ADMA_LPCG_UART0_IPG_CLK, "uart0_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPUART_0_LPCG, 16, 0, },
60	{ IMX_ADMA_LPCG_UART0_BAUD_CLK, "uart0_lpcg_baud_clk", "uart0_clk", 0, ADMA_LPUART_0_LPCG, 0, 0, },
61	{ IMX_ADMA_LPCG_UART1_IPG_CLK, "uart1_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPUART_1_LPCG, 16, 0, },
62	{ IMX_ADMA_LPCG_UART1_BAUD_CLK, "uart1_lpcg_baud_clk", "uart1_clk", 0, ADMA_LPUART_1_LPCG, 0, 0, },
63	{ IMX_ADMA_LPCG_UART2_IPG_CLK, "uart2_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPUART_2_LPCG, 16, 0, },
64	{ IMX_ADMA_LPCG_UART2_BAUD_CLK, "uart2_lpcg_baud_clk", "uart2_clk", 0, ADMA_LPUART_2_LPCG, 0, 0, },
65	{ IMX_ADMA_LPCG_UART3_IPG_CLK, "uart3_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPUART_3_LPCG, 16, 0, },
66	{ IMX_ADMA_LPCG_UART3_BAUD_CLK, "uart3_lpcg_baud_clk", "uart3_clk", 0, ADMA_LPUART_3_LPCG, 0, 0, },
67	{ IMX_ADMA_LPCG_I2C0_IPG_CLK, "i2c0_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPI2C_0_LPCG, 16, 0, },
68	{ IMX_ADMA_LPCG_I2C0_CLK, "i2c0_lpcg_clk", "i2c0_clk", 0, ADMA_LPI2C_0_LPCG, 0, 0, },
69	{ IMX_ADMA_LPCG_I2C1_IPG_CLK, "i2c1_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPI2C_1_LPCG, 16, 0, },
70	{ IMX_ADMA_LPCG_I2C1_CLK, "i2c1_lpcg_clk", "i2c1_clk", 0, ADMA_LPI2C_1_LPCG, 0, 0, },
71	{ IMX_ADMA_LPCG_I2C2_IPG_CLK, "i2c2_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPI2C_2_LPCG, 16, 0, },
72	{ IMX_ADMA_LPCG_I2C2_CLK, "i2c2_lpcg_clk", "i2c2_clk", 0, ADMA_LPI2C_2_LPCG, 0, 0, },
73	{ IMX_ADMA_LPCG_I2C3_IPG_CLK, "i2c3_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_LPI2C_3_LPCG, 16, 0, },
74	{ IMX_ADMA_LPCG_I2C3_CLK, "i2c3_lpcg_clk", "i2c3_clk", 0, ADMA_LPI2C_3_LPCG, 0, 0, },
75
76	{ IMX_ADMA_LPCG_DSP_CORE_CLK, "dsp_lpcg_core_clk", "dma_ipg_clk_root", 0, ADMA_HIFI_LPCG, 28, 0, },
77	{ IMX_ADMA_LPCG_DSP_IPG_CLK, "dsp_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_HIFI_LPCG, 20, 0, },
78	{ IMX_ADMA_LPCG_DSP_ADB_CLK, "dsp_lpcg_adb_clk", "dma_ipg_clk_root", 0, ADMA_HIFI_LPCG, 16, 0, },
79	{ IMX_ADMA_LPCG_OCRAM_IPG_CLK, "ocram_lpcg_ipg_clk", "dma_ipg_clk_root", 0, ADMA_OCRAM_LPCG, 16, 0, },
80};
81
82static const struct imx8qxp_ss_lpcg imx8qxp_ss_adma = {
83	.lpcg = imx8qxp_lpcg_adma,
84	.num_lpcg = ARRAY_SIZE(imx8qxp_lpcg_adma),
85	.num_max = IMX_ADMA_LPCG_CLK_END,
86};
87
88static const struct imx8qxp_lpcg_data imx8qxp_lpcg_conn[] = {
89	{ IMX_CONN_LPCG_SDHC0_PER_CLK, "sdhc0_lpcg_per_clk", "sdhc0_clk", 0, CONN_USDHC_0_LPCG, 0, 0, },
90	{ IMX_CONN_LPCG_SDHC0_IPG_CLK, "sdhc0_lpcg_ipg_clk", "conn_ipg_clk_root", 0, CONN_USDHC_0_LPCG, 16, 0, },
91	{ IMX_CONN_LPCG_SDHC0_HCLK, "sdhc0_lpcg_ahb_clk", "conn_axi_clk_root", 0, CONN_USDHC_0_LPCG, 20, 0, },
92	{ IMX_CONN_LPCG_SDHC1_PER_CLK, "sdhc1_lpcg_per_clk", "sdhc1_clk", 0, CONN_USDHC_1_LPCG, 0, 0, },
93	{ IMX_CONN_LPCG_SDHC1_IPG_CLK, "sdhc1_lpcg_ipg_clk", "conn_ipg_clk_root", 0, CONN_USDHC_1_LPCG, 16, 0, },
94	{ IMX_CONN_LPCG_SDHC1_HCLK, "sdhc1_lpcg_ahb_clk", "conn_axi_clk_root", 0, CONN_USDHC_1_LPCG, 20, 0, },
95	{ IMX_CONN_LPCG_SDHC2_PER_CLK, "sdhc2_lpcg_per_clk", "sdhc2_clk", 0, CONN_USDHC_2_LPCG, 0, 0, },
96	{ IMX_CONN_LPCG_SDHC2_IPG_CLK, "sdhc2_lpcg_ipg_clk", "conn_ipg_clk_root", 0, CONN_USDHC_2_LPCG, 16, 0, },
97	{ IMX_CONN_LPCG_SDHC2_HCLK, "sdhc2_lpcg_ahb_clk", "conn_axi_clk_root", 0, CONN_USDHC_2_LPCG, 20, 0, },
98	{ IMX_CONN_LPCG_ENET0_ROOT_CLK, "enet0_ipg_root_clk", "enet0_clk", 0, CONN_ENET_0_LPCG, 0, 0, },
99	{ IMX_CONN_LPCG_ENET0_TX_CLK, "enet0_tx_clk", "enet0_clk", 0, CONN_ENET_0_LPCG, 4, 0, },
100	{ IMX_CONN_LPCG_ENET0_AHB_CLK, "enet0_ahb_clk", "conn_axi_clk_root", 0, CONN_ENET_0_LPCG, 8, 0, },
101	{ IMX_CONN_LPCG_ENET0_IPG_S_CLK, "enet0_ipg_s_clk", "conn_ipg_clk_root", 0, CONN_ENET_0_LPCG, 20, 0, },
102	{ IMX_CONN_LPCG_ENET0_IPG_CLK, "enet0_ipg_clk", "enet0_ipg_s_clk", 0, CONN_ENET_0_LPCG, 16, 0, },
103	{ IMX_CONN_LPCG_ENET1_ROOT_CLK, "enet1_ipg_root_clk", "enet1_clk", 0, CONN_ENET_1_LPCG, 0, 0, },
104	{ IMX_CONN_LPCG_ENET1_TX_CLK, "enet1_tx_clk", "enet1_clk", 0, CONN_ENET_1_LPCG, 4, 0, },
105	{ IMX_CONN_LPCG_ENET1_AHB_CLK, "enet1_ahb_clk", "conn_axi_clk_root", 0, CONN_ENET_1_LPCG, 8, 0, },
106	{ IMX_CONN_LPCG_ENET1_IPG_S_CLK, "enet1_ipg_s_clk", "conn_ipg_clk_root", 0, CONN_ENET_1_LPCG, 20, 0, },
107	{ IMX_CONN_LPCG_ENET1_IPG_CLK, "enet1_ipg_clk", "enet0_ipg_s_clk", 0, CONN_ENET_1_LPCG, 16, 0, },
108};
109
110static const struct imx8qxp_ss_lpcg imx8qxp_ss_conn = {
111	.lpcg = imx8qxp_lpcg_conn,
112	.num_lpcg = ARRAY_SIZE(imx8qxp_lpcg_conn),
113	.num_max = IMX_CONN_LPCG_CLK_END,
114};
115
116static const struct imx8qxp_lpcg_data imx8qxp_lpcg_lsio[] = {
117	{ IMX_LSIO_LPCG_PWM0_IPG_CLK, "pwm0_lpcg_ipg_clk", "pwm0_clk", 0, LSIO_PWM_0_LPCG, 0, 0, },
118	{ IMX_LSIO_LPCG_PWM0_IPG_HF_CLK, "pwm0_lpcg_ipg_hf_clk", "pwm0_clk", 0, LSIO_PWM_0_LPCG, 4, 0, },
119	{ IMX_LSIO_LPCG_PWM0_IPG_S_CLK, "pwm0_lpcg_ipg_s_clk", "pwm0_clk", 0, LSIO_PWM_0_LPCG, 16, 0, },
120	{ IMX_LSIO_LPCG_PWM0_IPG_SLV_CLK, "pwm0_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_0_LPCG, 20, 0, },
121	{ IMX_LSIO_LPCG_PWM0_IPG_MSTR_CLK, "pwm0_lpcg_ipg_mstr_clk", "pwm0_clk", 0, LSIO_PWM_0_LPCG, 24, 0, },
122	{ IMX_LSIO_LPCG_PWM1_IPG_CLK, "pwm1_lpcg_ipg_clk", "pwm1_clk", 0, LSIO_PWM_1_LPCG, 0, 0, },
123	{ IMX_LSIO_LPCG_PWM1_IPG_HF_CLK, "pwm1_lpcg_ipg_hf_clk", "pwm1_clk", 0, LSIO_PWM_1_LPCG, 4, 0, },
124	{ IMX_LSIO_LPCG_PWM1_IPG_S_CLK, "pwm1_lpcg_ipg_s_clk", "pwm1_clk", 0, LSIO_PWM_1_LPCG, 16, 0, },
125	{ IMX_LSIO_LPCG_PWM1_IPG_SLV_CLK, "pwm1_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_1_LPCG, 20, 0, },
126	{ IMX_LSIO_LPCG_PWM1_IPG_MSTR_CLK, "pwm1_lpcg_ipg_mstr_clk", "pwm1_clk", 0, LSIO_PWM_1_LPCG, 24, 0, },
127	{ IMX_LSIO_LPCG_PWM2_IPG_CLK, "pwm2_lpcg_ipg_clk", "pwm2_clk", 0, LSIO_PWM_2_LPCG, 0, 0, },
128	{ IMX_LSIO_LPCG_PWM2_IPG_HF_CLK, "pwm2_lpcg_ipg_hf_clk", "pwm2_clk", 0, LSIO_PWM_2_LPCG, 4, 0, },
129	{ IMX_LSIO_LPCG_PWM2_IPG_S_CLK, "pwm2_lpcg_ipg_s_clk", "pwm2_clk", 0, LSIO_PWM_2_LPCG, 16, 0, },
130	{ IMX_LSIO_LPCG_PWM2_IPG_SLV_CLK, "pwm2_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_2_LPCG, 20, 0, },
131	{ IMX_LSIO_LPCG_PWM2_IPG_MSTR_CLK, "pwm2_lpcg_ipg_mstr_clk", "pwm2_clk", 0, LSIO_PWM_2_LPCG, 24, 0, },
132	{ IMX_LSIO_LPCG_PWM3_IPG_CLK, "pwm3_lpcg_ipg_clk", "pwm3_clk", 0, LSIO_PWM_3_LPCG, 0, 0, },
133	{ IMX_LSIO_LPCG_PWM3_IPG_HF_CLK, "pwm3_lpcg_ipg_hf_clk", "pwm3_clk", 0, LSIO_PWM_3_LPCG, 4, 0, },
134	{ IMX_LSIO_LPCG_PWM3_IPG_S_CLK, "pwm3_lpcg_ipg_s_clk", "pwm3_clk", 0, LSIO_PWM_3_LPCG, 16, 0, },
135	{ IMX_LSIO_LPCG_PWM3_IPG_SLV_CLK, "pwm3_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_3_LPCG, 20, 0, },
136	{ IMX_LSIO_LPCG_PWM3_IPG_MSTR_CLK, "pwm3_lpcg_ipg_mstr_clk", "pwm3_clk", 0, LSIO_PWM_3_LPCG, 24, 0, },
137	{ IMX_LSIO_LPCG_PWM4_IPG_CLK, "pwm4_lpcg_ipg_clk", "pwm4_clk", 0, LSIO_PWM_4_LPCG, 0, 0, },
138	{ IMX_LSIO_LPCG_PWM4_IPG_HF_CLK, "pwm4_lpcg_ipg_hf_clk", "pwm4_clk", 0, LSIO_PWM_4_LPCG, 4, 0, },
139	{ IMX_LSIO_LPCG_PWM4_IPG_S_CLK, "pwm4_lpcg_ipg_s_clk", "pwm4_clk", 0, LSIO_PWM_4_LPCG, 16, 0, },
140	{ IMX_LSIO_LPCG_PWM4_IPG_SLV_CLK, "pwm4_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_4_LPCG, 20, 0, },
141	{ IMX_LSIO_LPCG_PWM4_IPG_MSTR_CLK, "pwm4_lpcg_ipg_mstr_clk", "pwm4_clk", 0, LSIO_PWM_4_LPCG, 24, 0, },
142	{ IMX_LSIO_LPCG_PWM5_IPG_CLK, "pwm5_lpcg_ipg_clk", "pwm5_clk", 0, LSIO_PWM_5_LPCG, 0, 0, },
143	{ IMX_LSIO_LPCG_PWM5_IPG_HF_CLK, "pwm5_lpcg_ipg_hf_clk", "pwm5_clk", 0, LSIO_PWM_5_LPCG, 4, 0, },
144	{ IMX_LSIO_LPCG_PWM5_IPG_S_CLK, "pwm5_lpcg_ipg_s_clk", "pwm5_clk", 0, LSIO_PWM_5_LPCG, 16, 0, },
145	{ IMX_LSIO_LPCG_PWM5_IPG_SLV_CLK, "pwm5_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_5_LPCG, 20, 0, },
146	{ IMX_LSIO_LPCG_PWM5_IPG_MSTR_CLK, "pwm5_lpcg_ipg_mstr_clk", "pwm5_clk", 0, LSIO_PWM_5_LPCG, 24, 0, },
147	{ IMX_LSIO_LPCG_PWM6_IPG_CLK, "pwm6_lpcg_ipg_clk", "pwm6_clk", 0, LSIO_PWM_6_LPCG, 0, 0, },
148	{ IMX_LSIO_LPCG_PWM6_IPG_HF_CLK, "pwm6_lpcg_ipg_hf_clk", "pwm6_clk", 0, LSIO_PWM_6_LPCG, 4, 0, },
149	{ IMX_LSIO_LPCG_PWM6_IPG_S_CLK, "pwm6_lpcg_ipg_s_clk", "pwm6_clk", 0, LSIO_PWM_6_LPCG, 16, 0, },
150	{ IMX_LSIO_LPCG_PWM6_IPG_SLV_CLK, "pwm6_lpcg_ipg_slv_clk", "lsio_bus_clk_root", 0, LSIO_PWM_6_LPCG, 20, 0, },
151	{ IMX_LSIO_LPCG_PWM6_IPG_MSTR_CLK, "pwm6_lpcg_ipg_mstr_clk", "pwm6_clk", 0, LSIO_PWM_6_LPCG, 24, 0, },
152};
153
154static const struct imx8qxp_ss_lpcg imx8qxp_ss_lsio = {
155	.lpcg = imx8qxp_lpcg_lsio,
156	.num_lpcg = ARRAY_SIZE(imx8qxp_lpcg_lsio),
157	.num_max = IMX_LSIO_LPCG_CLK_END,
158};
159
160#define IMX_LPCG_MAX_CLKS	8
161
162static struct clk_hw *imx_lpcg_of_clk_src_get(struct of_phandle_args *clkspec,
163					      void *data)
164{
165	struct clk_hw_onecell_data *hw_data = data;
166	unsigned int idx = clkspec->args[0] / 4;
167
168	if (idx >= hw_data->num) {
169		pr_err("%s: invalid index %u\n", __func__, idx);
170		return ERR_PTR(-EINVAL);
171	}
172
173	return hw_data->hws[idx];
174}
175
176static int imx_lpcg_parse_clks_from_dt(struct platform_device *pdev,
177				       struct device_node *np)
178{
179	const char *output_names[IMX_LPCG_MAX_CLKS];
180	const char *parent_names[IMX_LPCG_MAX_CLKS];
181	unsigned int bit_offset[IMX_LPCG_MAX_CLKS];
182	struct clk_hw_onecell_data *clk_data;
183	struct clk_hw **clk_hws;
184	void __iomem *base;
185	int count;
186	int idx;
187	int ret;
188	int i;
189
190	if (!of_device_is_compatible(np, "fsl,imx8qxp-lpcg"))
191		return -EINVAL;
192
193	base = devm_platform_ioremap_resource(pdev, 0);
194	if (IS_ERR(base))
195		return PTR_ERR(base);
196
197	count = of_property_count_u32_elems(np, "clock-indices");
198	if (count < 0) {
199		dev_err(&pdev->dev, "failed to count clocks\n");
200		return -EINVAL;
201	}
202
203	/*
204	 * A trick here is that we set the num of clks to the MAX instead
205	 * of the count from clock-indices because one LPCG supports up to
206	 * 8 clock outputs which each of them is fixed to 4 bits. Then we can
207	 * easily get the clock by clk-indices (bit-offset) / 4.
208	 * And the cost is very limited few pointers.
209	 */
210
211	clk_data = devm_kzalloc(&pdev->dev, struct_size(clk_data, hws,
212				IMX_LPCG_MAX_CLKS), GFP_KERNEL);
213	if (!clk_data)
214		return -ENOMEM;
215
216	clk_data->num = IMX_LPCG_MAX_CLKS;
217	clk_hws = clk_data->hws;
218
219	ret = of_property_read_u32_array(np, "clock-indices", bit_offset,
220					 count);
221	if (ret < 0) {
222		dev_err(&pdev->dev, "failed to read clock-indices\n");
223		return -EINVAL;
224	}
225
226	ret = of_clk_parent_fill(np, parent_names, count);
227	if (ret != count) {
228		dev_err(&pdev->dev, "failed to get clock parent names\n");
229		return count;
230	}
231
232	ret = of_property_read_string_array(np, "clock-output-names",
233					    output_names, count);
234	if (ret != count) {
235		dev_err(&pdev->dev, "failed to read clock-output-names\n");
236		return -EINVAL;
237	}
238
239	pm_runtime_get_noresume(&pdev->dev);
240	pm_runtime_set_active(&pdev->dev);
241	pm_runtime_set_autosuspend_delay(&pdev->dev, 500);
242	pm_runtime_use_autosuspend(&pdev->dev);
243	pm_runtime_enable(&pdev->dev);
244
245	for (i = 0; i < count; i++) {
246		idx = bit_offset[i] / 4;
247		if (idx >= IMX_LPCG_MAX_CLKS) {
248			dev_warn(&pdev->dev, "invalid bit offset of clock %d\n",
249				 i);
250			ret = -EINVAL;
251			goto unreg;
252		}
253
254		clk_hws[idx] = imx_clk_lpcg_scu_dev(&pdev->dev, output_names[i],
255						    parent_names[i], 0, base,
256						    bit_offset[i], false);
257		if (IS_ERR(clk_hws[idx])) {
258			dev_warn(&pdev->dev, "failed to register clock %d\n",
259				 idx);
260			ret = PTR_ERR(clk_hws[idx]);
261			goto unreg;
262		}
263	}
264
265	ret = devm_of_clk_add_hw_provider(&pdev->dev, imx_lpcg_of_clk_src_get,
266					  clk_data);
267	if (ret)
268		goto unreg;
269
270	pm_runtime_mark_last_busy(&pdev->dev);
271	pm_runtime_put_autosuspend(&pdev->dev);
272
273	return 0;
274
275unreg:
276	while (--i >= 0) {
277		idx = bit_offset[i] / 4;
278		if (clk_hws[idx])
279			imx_clk_lpcg_scu_unregister(clk_hws[idx]);
280	}
281
282	pm_runtime_disable(&pdev->dev);
283
284	return ret;
285}
286
287static int imx8qxp_lpcg_clk_probe(struct platform_device *pdev)
288{
289	struct device *dev = &pdev->dev;
290	struct device_node *np = dev->of_node;
291	struct clk_hw_onecell_data *clk_data;
292	const struct imx8qxp_ss_lpcg *ss_lpcg;
293	const struct imx8qxp_lpcg_data *lpcg;
294	struct resource *res;
295	struct clk_hw **clks;
296	void __iomem *base;
297	int ret;
298	int i;
299
300	/* try new binding to parse clocks from device tree first */
301	ret = imx_lpcg_parse_clks_from_dt(pdev, np);
302	if (!ret)
303		return 0;
304
305	ss_lpcg = of_device_get_match_data(dev);
306	if (!ss_lpcg)
307		return -ENODEV;
308
309	/*
310	 * Please don't replace this with devm_platform_ioremap_resource.
311	 *
312	 * devm_platform_ioremap_resource calls devm_ioremap_resource which
313	 * differs from devm_ioremap by also calling devm_request_mem_region
314	 * and preventing other mappings in the same area.
315	 *
316	 * On imx8 the LPCG nodes map entire subsystems and overlap
317	 * peripherals, this means that using devm_platform_ioremap_resource
318	 * will cause many devices to fail to probe including serial ports.
319	 */
320	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
321	if (!res)
322		return -EINVAL;
323	base = devm_ioremap(dev, res->start, resource_size(res));
324	if (!base)
325		return -ENOMEM;
326
327	clk_data = devm_kzalloc(&pdev->dev, struct_size(clk_data, hws,
328				ss_lpcg->num_max), GFP_KERNEL);
329	if (!clk_data)
330		return -ENOMEM;
331
332	clk_data->num = ss_lpcg->num_max;
333	clks = clk_data->hws;
334
335	for (i = 0; i < ss_lpcg->num_lpcg; i++) {
336		lpcg = ss_lpcg->lpcg + i;
337		clks[lpcg->id] = imx_clk_lpcg_scu(lpcg->name, lpcg->parent,
338						  lpcg->flags, base + lpcg->offset,
339						  lpcg->bit_idx, lpcg->hw_gate);
340	}
341
342	for (i = 0; i < clk_data->num; i++) {
343		if (IS_ERR(clks[i]))
344			pr_warn("i.MX clk %u: register failed with %ld\n",
345				i, PTR_ERR(clks[i]));
346	}
347
348	return of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
349}
350
351static const struct of_device_id imx8qxp_lpcg_match[] = {
352	{ .compatible = "fsl,imx8qxp-lpcg-adma", &imx8qxp_ss_adma, },
353	{ .compatible = "fsl,imx8qxp-lpcg-conn", &imx8qxp_ss_conn, },
354	{ .compatible = "fsl,imx8qxp-lpcg-lsio", &imx8qxp_ss_lsio, },
355	{ .compatible = "fsl,imx8qxp-lpcg", NULL },
356	{ /* sentinel */ }
357};
358
359static struct platform_driver imx8qxp_lpcg_clk_driver = {
360	.driver = {
361		.name = "imx8qxp-lpcg-clk",
362		.of_match_table = imx8qxp_lpcg_match,
363		.pm = &imx_clk_lpcg_scu_pm_ops,
364		.suppress_bind_attrs = true,
365	},
366	.probe = imx8qxp_lpcg_clk_probe,
367};
368
369module_platform_driver(imx8qxp_lpcg_clk_driver);
370
371MODULE_AUTHOR("Aisheng Dong <aisheng.dong@nxp.com>");
372MODULE_DESCRIPTION("NXP i.MX8QXP LPCG clock driver");
373MODULE_LICENSE("GPL v2");
374