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 | */ |
33 | struct 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 | */ |
52 | struct imx8qxp_ss_lpcg { |
53 | const struct imx8qxp_lpcg_data *lpcg; |
54 | u8 num_lpcg; |
55 | u8 num_max; |
56 | }; |
57 | |
58 | static 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 | |
82 | static 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 | |
88 | static 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 | |
110 | static 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 | |
116 | static 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 | |
154 | static 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 | |
162 | static 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(error: -EINVAL); |
171 | } |
172 | |
173 | return hw_data->hws[idx]; |
174 | } |
175 | |
176 | static 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(device: np, "fsl,imx8qxp-lpcg" )) |
191 | return -EINVAL; |
192 | |
193 | base = devm_platform_ioremap_resource(pdev, index: 0); |
194 | if (IS_ERR(ptr: base)) |
195 | return PTR_ERR(ptr: base); |
196 | |
197 | count = of_property_count_u32_elems(np, propname: "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(dev: &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, propname: "clock-indices" , out_values: bit_offset, |
220 | sz: 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, parents: parent_names, size: 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, propname: "clock-output-names" , |
233 | out_strs: output_names, sz: 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(dev: &pdev->dev); |
240 | pm_runtime_set_active(dev: &pdev->dev); |
241 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, delay: 500); |
242 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
243 | pm_runtime_enable(dev: &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(dev: &pdev->dev, name: output_names[i], |
255 | parent_name: parent_names[i], flags: 0, reg: base, |
256 | bit_idx: bit_offset[i], hw_gate: false); |
257 | if (IS_ERR(ptr: clk_hws[idx])) { |
258 | dev_warn(&pdev->dev, "failed to register clock %d\n" , |
259 | idx); |
260 | ret = PTR_ERR(ptr: clk_hws[idx]); |
261 | goto unreg; |
262 | } |
263 | } |
264 | |
265 | ret = devm_of_clk_add_hw_provider(dev: &pdev->dev, get: imx_lpcg_of_clk_src_get, |
266 | data: clk_data); |
267 | if (ret) |
268 | goto unreg; |
269 | |
270 | pm_runtime_mark_last_busy(dev: &pdev->dev); |
271 | pm_runtime_put_autosuspend(dev: &pdev->dev); |
272 | |
273 | return 0; |
274 | |
275 | unreg: |
276 | while (--i >= 0) { |
277 | idx = bit_offset[i] / 4; |
278 | if (clk_hws[idx]) |
279 | imx_clk_lpcg_scu_unregister(hw: clk_hws[idx]); |
280 | } |
281 | |
282 | pm_runtime_disable(dev: &pdev->dev); |
283 | |
284 | return ret; |
285 | } |
286 | |
287 | static 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, offset: res->start, size: resource_size(res)); |
324 | if (!base) |
325 | return -ENOMEM; |
326 | |
327 | clk_data = devm_kzalloc(dev: &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(name: lpcg->name, parent_name: lpcg->parent, |
338 | flags: lpcg->flags, reg: base + lpcg->offset, |
339 | bit_idx: lpcg->bit_idx, hw_gate: lpcg->hw_gate); |
340 | } |
341 | |
342 | for (i = 0; i < clk_data->num; i++) { |
343 | if (IS_ERR(ptr: 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, get: of_clk_hw_onecell_get, data: clk_data); |
349 | } |
350 | |
351 | static 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 | |
359 | static 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 | |
369 | module_platform_driver(imx8qxp_lpcg_clk_driver); |
370 | |
371 | MODULE_AUTHOR("Aisheng Dong <aisheng.dong@nxp.com>" ); |
372 | MODULE_DESCRIPTION("NXP i.MX8QXP LPCG clock driver" ); |
373 | MODULE_LICENSE("GPL v2" ); |
374 | |