Commit | Line | Data |
---|---|---|
0a0f7cc2 PF |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright 2022 NXP | |
4 | */ | |
5 | ||
6 | #include <linux/clk.h> | |
7 | #include <linux/delay.h> | |
0a0f7cc2 | 8 | #include <linux/iopoll.h> |
ef2c1c84 | 9 | #include <linux/mod_devicetable.h> |
0a0f7cc2 PF |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> | |
12 | #include <linux/pm_domain.h> | |
13 | ||
14 | #define MIX_SLICE_SW_CTRL_OFF 0x20 | |
15 | #define SLICE_SW_CTRL_PSW_CTRL_OFF_MASK BIT(4) | |
16 | #define SLICE_SW_CTRL_PDN_SOFT_MASK BIT(31) | |
17 | ||
18 | #define MIX_FUNC_STAT_OFF 0xB4 | |
19 | ||
20 | #define FUNC_STAT_PSW_STAT_MASK BIT(0) | |
21 | #define FUNC_STAT_RST_STAT_MASK BIT(2) | |
22 | #define FUNC_STAT_ISO_STAT_MASK BIT(4) | |
23 | ||
24 | struct imx93_power_domain { | |
25 | struct generic_pm_domain genpd; | |
26 | struct device *dev; | |
27 | void __iomem *addr; | |
28 | struct clk_bulk_data *clks; | |
29 | int num_clks; | |
30 | bool init_off; | |
31 | }; | |
32 | ||
33 | #define to_imx93_pd(_genpd) container_of(_genpd, struct imx93_power_domain, genpd) | |
34 | ||
35 | static int imx93_pd_on(struct generic_pm_domain *genpd) | |
36 | { | |
37 | struct imx93_power_domain *domain = to_imx93_pd(genpd); | |
38 | void __iomem *addr = domain->addr; | |
39 | u32 val; | |
40 | int ret; | |
41 | ||
42 | ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks); | |
43 | if (ret) { | |
44 | dev_err(domain->dev, "failed to enable clocks for domain: %s\n", genpd->name); | |
45 | return ret; | |
46 | } | |
47 | ||
48 | val = readl(addr + MIX_SLICE_SW_CTRL_OFF); | |
49 | val &= ~SLICE_SW_CTRL_PDN_SOFT_MASK; | |
50 | writel(val, addr + MIX_SLICE_SW_CTRL_OFF); | |
51 | ||
52 | ret = readl_poll_timeout(addr + MIX_FUNC_STAT_OFF, val, | |
53 | !(val & FUNC_STAT_ISO_STAT_MASK), 1, 10000); | |
54 | if (ret) { | |
55 | dev_err(domain->dev, "pd_on timeout: name: %s, stat: %x\n", genpd->name, val); | |
56 | return ret; | |
57 | } | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | static int imx93_pd_off(struct generic_pm_domain *genpd) | |
63 | { | |
64 | struct imx93_power_domain *domain = to_imx93_pd(genpd); | |
65 | void __iomem *addr = domain->addr; | |
66 | int ret; | |
67 | u32 val; | |
68 | ||
69 | /* Power off MIX */ | |
70 | val = readl(addr + MIX_SLICE_SW_CTRL_OFF); | |
71 | val |= SLICE_SW_CTRL_PDN_SOFT_MASK; | |
72 | writel(val, addr + MIX_SLICE_SW_CTRL_OFF); | |
73 | ||
74 | ret = readl_poll_timeout(addr + MIX_FUNC_STAT_OFF, val, | |
75 | val & FUNC_STAT_PSW_STAT_MASK, 1, 1000); | |
76 | if (ret) { | |
77 | dev_err(domain->dev, "pd_off timeout: name: %s, stat: %x\n", genpd->name, val); | |
78 | return ret; | |
79 | } | |
80 | ||
81 | clk_bulk_disable_unprepare(domain->num_clks, domain->clks); | |
82 | ||
83 | return 0; | |
84 | }; | |
85 | ||
86 | static int imx93_pd_remove(struct platform_device *pdev) | |
87 | { | |
88 | struct imx93_power_domain *domain = platform_get_drvdata(pdev); | |
89 | struct device *dev = &pdev->dev; | |
90 | struct device_node *np = dev->of_node; | |
91 | ||
92 | if (!domain->init_off) | |
93 | clk_bulk_disable_unprepare(domain->num_clks, domain->clks); | |
94 | ||
95 | of_genpd_del_provider(np); | |
96 | pm_genpd_remove(&domain->genpd); | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
101 | static int imx93_pd_probe(struct platform_device *pdev) | |
102 | { | |
103 | struct device *dev = &pdev->dev; | |
104 | struct device_node *np = dev->of_node; | |
105 | struct imx93_power_domain *domain; | |
106 | int ret; | |
107 | ||
108 | domain = devm_kzalloc(dev, sizeof(*domain), GFP_KERNEL); | |
109 | if (!domain) | |
110 | return -ENOMEM; | |
111 | ||
112 | domain->addr = devm_platform_ioremap_resource(pdev, 0); | |
113 | if (IS_ERR(domain->addr)) | |
114 | return PTR_ERR(domain->addr); | |
115 | ||
116 | domain->num_clks = devm_clk_bulk_get_all(dev, &domain->clks); | |
117 | if (domain->num_clks < 0) | |
118 | return dev_err_probe(dev, domain->num_clks, "Failed to get domain's clocks\n"); | |
119 | ||
120 | domain->genpd.name = dev_name(dev); | |
121 | domain->genpd.power_off = imx93_pd_off; | |
122 | domain->genpd.power_on = imx93_pd_on; | |
123 | domain->dev = dev; | |
124 | ||
125 | domain->init_off = readl(domain->addr + MIX_FUNC_STAT_OFF) & FUNC_STAT_ISO_STAT_MASK; | |
126 | /* Just to sync the status of hardware */ | |
127 | if (!domain->init_off) { | |
128 | ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks); | |
129 | if (ret) { | |
130 | dev_err(domain->dev, "failed to enable clocks for domain: %s\n", | |
131 | domain->genpd.name); | |
132 | return ret; | |
133 | } | |
134 | } | |
135 | ||
136 | ret = pm_genpd_init(&domain->genpd, NULL, domain->init_off); | |
137 | if (ret) | |
0ba7b623 | 138 | goto err_clk_unprepare; |
0a0f7cc2 PF |
139 | |
140 | platform_set_drvdata(pdev, domain); | |
141 | ||
0ba7b623 CJ |
142 | ret = of_genpd_add_provider_simple(np, &domain->genpd); |
143 | if (ret) | |
144 | goto err_genpd_remove; | |
145 | ||
146 | return 0; | |
147 | ||
148 | err_genpd_remove: | |
149 | pm_genpd_remove(&domain->genpd); | |
150 | ||
151 | err_clk_unprepare: | |
152 | if (!domain->init_off) | |
153 | clk_bulk_disable_unprepare(domain->num_clks, domain->clks); | |
154 | ||
155 | return ret; | |
0a0f7cc2 PF |
156 | } |
157 | ||
158 | static const struct of_device_id imx93_pd_ids[] = { | |
159 | { .compatible = "fsl,imx93-src-slice" }, | |
160 | { } | |
161 | }; | |
162 | MODULE_DEVICE_TABLE(of, imx93_pd_ids); | |
163 | ||
164 | static struct platform_driver imx93_power_domain_driver = { | |
165 | .driver = { | |
166 | .name = "imx93_power_domain", | |
0a0f7cc2 PF |
167 | .of_match_table = imx93_pd_ids, |
168 | }, | |
169 | .probe = imx93_pd_probe, | |
170 | .remove = imx93_pd_remove, | |
171 | }; | |
172 | module_platform_driver(imx93_power_domain_driver); | |
173 | ||
174 | MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); | |
175 | MODULE_DESCRIPTION("NXP i.MX93 power domain driver"); | |
176 | MODULE_LICENSE("GPL"); |