Skip to content

Commit 03aa126

Browse files
ndreysShawn Guo
authored andcommitted
soc: imx: Add GPCv2 power gating driver
Add code allowing for control of various power domains managed by GPCv2 IP block found in i.MX7 series of SoCs. Power domains covered by this patch are: - PCIE PHY - MIPI PHY - USB HSIC PHY - USB OTG1/2 PHY Support for any other power domain controlled by GPC is not present, and can be added at some later point. Testing of this code was done against a PCIe driver. Cc: [email protected] Cc: Lucas Stach <[email protected]> Cc: Fabio Estevam <[email protected]> Cc: Dong Aisheng <[email protected]> Cc: [email protected] Cc: [email protected] Signed-off-by: Andrey Smirnov <[email protected]> Signed-off-by: Shawn Guo <[email protected]>
1 parent 2d9eb1d commit 03aa126

File tree

4 files changed

+374
-0
lines changed

4 files changed

+374
-0
lines changed

drivers/soc/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers"
22

33
source "drivers/soc/bcm/Kconfig"
44
source "drivers/soc/fsl/Kconfig"
5+
source "drivers/soc/imx/Kconfig"
56
source "drivers/soc/mediatek/Kconfig"
67
source "drivers/soc/qcom/Kconfig"
78
source "drivers/soc/rockchip/Kconfig"

drivers/soc/imx/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
menu "i.MX SoC drivers"
2+
3+
config IMX7_PM_DOMAINS
4+
bool "i.MX7 PM domains"
5+
select PM_GENERIC_DOMAINS
6+
depends on SOC_IMX7D || (COMPILE_TEST && OF)
7+
default y if SOC_IMX7D
8+
9+
endmenu

drivers/soc/imx/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
obj-y += gpc.o
2+
obj-$(CONFIG_IMX7_PM_DOMAINS) += gpcv2.o

drivers/soc/imx/gpcv2.c

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/*
2+
* Copyright 2017 Impinj, Inc
3+
* Author: Andrey Smirnov <[email protected]>
4+
*
5+
* Based on the code of analogus driver:
6+
*
7+
* Copyright 2015-2017 Pengutronix, Lucas Stach <[email protected]>
8+
*
9+
* The code contained herein is licensed under the GNU General Public
10+
* License. You may obtain a copy of the GNU General Public License
11+
* Version 2 or later at the following locations:
12+
*
13+
* http://www.opensource.org/licenses/gpl-license.html
14+
* http://www.gnu.org/copyleft/gpl.html
15+
*/
16+
17+
#include <linux/platform_device.h>
18+
#include <linux/pm_domain.h>
19+
#include <linux/regmap.h>
20+
#include <linux/regulator/consumer.h>
21+
#include <dt-bindings/power/imx7-power.h>
22+
23+
#define GPC_LPCR_A7_BSC 0x000
24+
25+
#define GPC_PGC_CPU_MAPPING 0x0ec
26+
#define USB_HSIC_PHY_A7_DOMAIN BIT(6)
27+
#define USB_OTG2_PHY_A7_DOMAIN BIT(5)
28+
#define USB_OTG1_PHY_A7_DOMAIN BIT(4)
29+
#define PCIE_PHY_A7_DOMAIN BIT(3)
30+
#define MIPI_PHY_A7_DOMAIN BIT(2)
31+
32+
#define GPC_PU_PGC_SW_PUP_REQ 0x0f8
33+
#define GPC_PU_PGC_SW_PDN_REQ 0x104
34+
#define USB_HSIC_PHY_SW_Pxx_REQ BIT(4)
35+
#define USB_OTG2_PHY_SW_Pxx_REQ BIT(3)
36+
#define USB_OTG1_PHY_SW_Pxx_REQ BIT(2)
37+
#define PCIE_PHY_SW_Pxx_REQ BIT(1)
38+
#define MIPI_PHY_SW_Pxx_REQ BIT(0)
39+
40+
#define GPC_M4_PU_PDN_FLG 0x1bc
41+
42+
43+
#define PGC_MIPI 4
44+
#define PGC_PCIE 5
45+
#define PGC_USB_HSIC 8
46+
#define GPC_PGC_CTRL(n) (0x800 + (n) * 0x40)
47+
#define GPC_PGC_SR(n) (GPC_PGC_CTRL(n) + 0xc)
48+
49+
#define GPC_PGC_CTRL_PCR BIT(0)
50+
51+
struct imx7_pgc_domain {
52+
struct generic_pm_domain genpd;
53+
struct regmap *regmap;
54+
struct regulator *regulator;
55+
56+
unsigned int pgc;
57+
58+
const struct {
59+
u32 pxx;
60+
u32 map;
61+
} bits;
62+
63+
const int voltage;
64+
struct device *dev;
65+
};
66+
67+
static int imx7_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd,
68+
bool on)
69+
{
70+
struct imx7_pgc_domain *domain = container_of(genpd,
71+
struct imx7_pgc_domain,
72+
genpd);
73+
unsigned int offset = on ?
74+
GPC_PU_PGC_SW_PUP_REQ : GPC_PU_PGC_SW_PDN_REQ;
75+
const bool enable_power_control = !on;
76+
const bool has_regulator = !IS_ERR(domain->regulator);
77+
unsigned long deadline;
78+
int ret = 0;
79+
80+
regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING,
81+
domain->bits.map, domain->bits.map);
82+
83+
if (has_regulator && on) {
84+
ret = regulator_enable(domain->regulator);
85+
if (ret) {
86+
dev_err(domain->dev, "failed to enable regulator\n");
87+
goto unmap;
88+
}
89+
}
90+
91+
if (enable_power_control)
92+
regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc),
93+
GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR);
94+
95+
regmap_update_bits(domain->regmap, offset,
96+
domain->bits.pxx, domain->bits.pxx);
97+
98+
/*
99+
* As per "5.5.9.4 Example Code 4" in IMX7DRM.pdf wait
100+
* for PUP_REQ/PDN_REQ bit to be cleared
101+
*/
102+
deadline = jiffies + msecs_to_jiffies(1);
103+
while (true) {
104+
u32 pxx_req;
105+
106+
regmap_read(domain->regmap, offset, &pxx_req);
107+
108+
if (!(pxx_req & domain->bits.pxx))
109+
break;
110+
111+
if (time_after(jiffies, deadline)) {
112+
dev_err(domain->dev, "falied to command PGC\n");
113+
ret = -ETIMEDOUT;
114+
/*
115+
* If we were in a process of enabling a
116+
* domain and failed we might as well disable
117+
* the regulator we just enabled. And if it
118+
* was the opposite situation and we failed to
119+
* power down -- keep the regulator on
120+
*/
121+
on = !on;
122+
break;
123+
}
124+
125+
cpu_relax();
126+
}
127+
128+
if (enable_power_control)
129+
regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc),
130+
GPC_PGC_CTRL_PCR, 0);
131+
132+
if (has_regulator && !on) {
133+
int err;
134+
135+
err = regulator_disable(domain->regulator);
136+
if (err)
137+
dev_err(domain->dev,
138+
"failed to disable regulator: %d\n", ret);
139+
/* Preserve earlier error code */
140+
ret = ret ?: err;
141+
}
142+
unmap:
143+
regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING,
144+
domain->bits.map, 0);
145+
return ret;
146+
}
147+
148+
static int imx7_gpc_pu_pgc_sw_pup_req(struct generic_pm_domain *genpd)
149+
{
150+
return imx7_gpc_pu_pgc_sw_pxx_req(genpd, true);
151+
}
152+
153+
static int imx7_gpc_pu_pgc_sw_pdn_req(struct generic_pm_domain *genpd)
154+
{
155+
return imx7_gpc_pu_pgc_sw_pxx_req(genpd, false);
156+
}
157+
158+
static struct imx7_pgc_domain imx7_pgc_domains[] = {
159+
[IMX7_POWER_DOMAIN_MIPI_PHY] = {
160+
.genpd = {
161+
.name = "mipi-phy",
162+
},
163+
.bits = {
164+
.pxx = MIPI_PHY_SW_Pxx_REQ,
165+
.map = MIPI_PHY_A7_DOMAIN,
166+
},
167+
.voltage = 1000000,
168+
.pgc = PGC_MIPI,
169+
},
170+
171+
[IMX7_POWER_DOMAIN_PCIE_PHY] = {
172+
.genpd = {
173+
.name = "pcie-phy",
174+
},
175+
.bits = {
176+
.pxx = PCIE_PHY_SW_Pxx_REQ,
177+
.map = PCIE_PHY_A7_DOMAIN,
178+
},
179+
.voltage = 1000000,
180+
.pgc = PGC_PCIE,
181+
},
182+
183+
[IMX7_POWER_DOMAIN_USB_HSIC_PHY] = {
184+
.genpd = {
185+
.name = "usb-hsic-phy",
186+
},
187+
.bits = {
188+
.pxx = USB_HSIC_PHY_SW_Pxx_REQ,
189+
.map = USB_HSIC_PHY_A7_DOMAIN,
190+
},
191+
.voltage = 1200000,
192+
.pgc = PGC_USB_HSIC,
193+
},
194+
};
195+
196+
static int imx7_pgc_domain_probe(struct platform_device *pdev)
197+
{
198+
struct imx7_pgc_domain *domain = pdev->dev.platform_data;
199+
int ret;
200+
201+
domain->dev = &pdev->dev;
202+
203+
ret = pm_genpd_init(&domain->genpd, NULL, true);
204+
if (ret) {
205+
dev_err(domain->dev, "Failed to init power domain\n");
206+
return ret;
207+
}
208+
209+
domain->regulator = devm_regulator_get_optional(domain->dev, "power");
210+
if (IS_ERR(domain->regulator)) {
211+
if (PTR_ERR(domain->regulator) != -ENODEV) {
212+
dev_err(domain->dev, "Failed to get domain's regulator\n");
213+
return PTR_ERR(domain->regulator);
214+
}
215+
} else {
216+
regulator_set_voltage(domain->regulator,
217+
domain->voltage, domain->voltage);
218+
}
219+
220+
ret = of_genpd_add_provider_simple(domain->dev->of_node,
221+
&domain->genpd);
222+
if (ret) {
223+
dev_err(domain->dev, "Failed to add genpd provider\n");
224+
pm_genpd_remove(&domain->genpd);
225+
}
226+
227+
return ret;
228+
}
229+
230+
static int imx7_pgc_domain_remove(struct platform_device *pdev)
231+
{
232+
struct imx7_pgc_domain *domain = pdev->dev.platform_data;
233+
234+
of_genpd_del_provider(domain->dev->of_node);
235+
pm_genpd_remove(&domain->genpd);
236+
237+
return 0;
238+
}
239+
240+
static const struct platform_device_id imx7_pgc_domain_id[] = {
241+
{ "imx7-pgc-domain", },
242+
{ },
243+
};
244+
245+
static struct platform_driver imx7_pgc_domain_driver = {
246+
.driver = {
247+
.name = "imx7-pgc",
248+
},
249+
.probe = imx7_pgc_domain_probe,
250+
.remove = imx7_pgc_domain_remove,
251+
.id_table = imx7_pgc_domain_id,
252+
};
253+
builtin_platform_driver(imx7_pgc_domain_driver)
254+
255+
static int imx_gpcv2_probe(struct platform_device *pdev)
256+
{
257+
static const struct regmap_range yes_ranges[] = {
258+
regmap_reg_range(GPC_LPCR_A7_BSC,
259+
GPC_M4_PU_PDN_FLG),
260+
regmap_reg_range(GPC_PGC_CTRL(PGC_MIPI),
261+
GPC_PGC_SR(PGC_MIPI)),
262+
regmap_reg_range(GPC_PGC_CTRL(PGC_PCIE),
263+
GPC_PGC_SR(PGC_PCIE)),
264+
regmap_reg_range(GPC_PGC_CTRL(PGC_USB_HSIC),
265+
GPC_PGC_SR(PGC_USB_HSIC)),
266+
};
267+
static const struct regmap_access_table access_table = {
268+
.yes_ranges = yes_ranges,
269+
.n_yes_ranges = ARRAY_SIZE(yes_ranges),
270+
};
271+
static const struct regmap_config regmap_config = {
272+
.reg_bits = 32,
273+
.val_bits = 32,
274+
.reg_stride = 4,
275+
.rd_table = &access_table,
276+
.wr_table = &access_table,
277+
.max_register = SZ_4K,
278+
};
279+
struct device *dev = &pdev->dev;
280+
struct device_node *pgc_np, *np;
281+
struct regmap *regmap;
282+
struct resource *res;
283+
void __iomem *base;
284+
int ret;
285+
286+
pgc_np = of_get_child_by_name(dev->of_node, "pgc");
287+
if (!pgc_np) {
288+
dev_err(dev, "No power domains specified in DT\n");
289+
return -EINVAL;
290+
}
291+
292+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
293+
base = devm_ioremap_resource(dev, res);
294+
if (IS_ERR(base))
295+
return PTR_ERR(base);
296+
297+
regmap = devm_regmap_init_mmio(dev, base, &regmap_config);
298+
if (IS_ERR(regmap)) {
299+
ret = PTR_ERR(regmap);
300+
dev_err(dev, "failed to init regmap (%d)\n", ret);
301+
return ret;
302+
}
303+
304+
for_each_child_of_node(pgc_np, np) {
305+
struct platform_device *pd_pdev;
306+
struct imx7_pgc_domain *domain;
307+
u32 domain_index;
308+
309+
ret = of_property_read_u32(np, "reg", &domain_index);
310+
if (ret) {
311+
dev_err(dev, "Failed to read 'reg' property\n");
312+
of_node_put(np);
313+
return ret;
314+
}
315+
316+
if (domain_index >= ARRAY_SIZE(imx7_pgc_domains)) {
317+
dev_warn(dev,
318+
"Domain index %d is out of bounds\n",
319+
domain_index);
320+
continue;
321+
}
322+
323+
domain = &imx7_pgc_domains[domain_index];
324+
domain->regmap = regmap;
325+
domain->genpd.power_on = imx7_gpc_pu_pgc_sw_pup_req;
326+
domain->genpd.power_off = imx7_gpc_pu_pgc_sw_pdn_req;
327+
328+
pd_pdev = platform_device_alloc("imx7-pgc-domain",
329+
domain_index);
330+
if (!pd_pdev) {
331+
dev_err(dev, "Failed to allocate platform device\n");
332+
of_node_put(np);
333+
return -ENOMEM;
334+
}
335+
336+
pd_pdev->dev.platform_data = domain;
337+
pd_pdev->dev.parent = dev;
338+
pd_pdev->dev.of_node = np;
339+
340+
ret = platform_device_add(pd_pdev);
341+
if (ret) {
342+
platform_device_put(pd_pdev);
343+
of_node_put(np);
344+
return ret;
345+
}
346+
}
347+
348+
return 0;
349+
}
350+
351+
static const struct of_device_id imx_gpcv2_dt_ids[] = {
352+
{ .compatible = "fsl,imx7d-gpc" },
353+
{ }
354+
};
355+
356+
static struct platform_driver imx_gpc_driver = {
357+
.driver = {
358+
.name = "imx-gpcv2",
359+
.of_match_table = imx_gpcv2_dt_ids,
360+
},
361+
.probe = imx_gpcv2_probe,
362+
};
363+
builtin_platform_driver(imx_gpc_driver)

0 commit comments

Comments
 (0)