1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * PM MFD driver for Broadcom BCM2835 |
4 | * |
5 | * This driver binds to the PM block and creates the MFD device for |
6 | * the WDT and power drivers. |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/io.h> |
11 | #include <linux/mfd/bcm2835-pm.h> |
12 | #include <linux/mfd/core.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/types.h> |
18 | #include <linux/watchdog.h> |
19 | |
20 | static const struct mfd_cell bcm2835_pm_devs[] = { |
21 | { .name = "bcm2835-wdt" }, |
22 | }; |
23 | |
24 | static const struct mfd_cell bcm2835_power_devs[] = { |
25 | { .name = "bcm2835-power" }, |
26 | }; |
27 | |
28 | static int bcm2835_pm_get_pdata(struct platform_device *pdev, |
29 | struct bcm2835_pm *pm) |
30 | { |
31 | if (of_property_present(np: pm->dev->of_node, propname: "reg-names" )) { |
32 | struct resource *res; |
33 | |
34 | pm->base = devm_platform_ioremap_resource_byname(pdev, name: "pm" ); |
35 | if (IS_ERR(ptr: pm->base)) |
36 | return PTR_ERR(ptr: pm->base); |
37 | |
38 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "asb" ); |
39 | if (res) { |
40 | pm->asb = devm_ioremap_resource(dev: &pdev->dev, res); |
41 | if (IS_ERR(ptr: pm->asb)) |
42 | pm->asb = NULL; |
43 | } |
44 | |
45 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
46 | "rpivid_asb" ); |
47 | if (res) { |
48 | pm->rpivid_asb = devm_ioremap_resource(dev: &pdev->dev, res); |
49 | if (IS_ERR(ptr: pm->rpivid_asb)) |
50 | pm->rpivid_asb = NULL; |
51 | } |
52 | |
53 | return 0; |
54 | } |
55 | |
56 | /* If no 'reg-names' property is found we can assume we're using old DTB. */ |
57 | pm->base = devm_platform_ioremap_resource(pdev, index: 0); |
58 | if (IS_ERR(ptr: pm->base)) |
59 | return PTR_ERR(ptr: pm->base); |
60 | |
61 | pm->asb = devm_platform_ioremap_resource(pdev, index: 1); |
62 | if (IS_ERR(ptr: pm->asb)) |
63 | pm->asb = NULL; |
64 | |
65 | pm->rpivid_asb = devm_platform_ioremap_resource(pdev, index: 2); |
66 | if (IS_ERR(ptr: pm->rpivid_asb)) |
67 | pm->rpivid_asb = NULL; |
68 | |
69 | return 0; |
70 | } |
71 | |
72 | static int bcm2835_pm_probe(struct platform_device *pdev) |
73 | { |
74 | struct device *dev = &pdev->dev; |
75 | struct bcm2835_pm *pm; |
76 | int ret; |
77 | |
78 | pm = devm_kzalloc(dev, size: sizeof(*pm), GFP_KERNEL); |
79 | if (!pm) |
80 | return -ENOMEM; |
81 | platform_set_drvdata(pdev, data: pm); |
82 | |
83 | pm->dev = dev; |
84 | |
85 | ret = bcm2835_pm_get_pdata(pdev, pm); |
86 | if (ret) |
87 | return ret; |
88 | |
89 | ret = devm_mfd_add_devices(dev, id: -1, |
90 | cells: bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), |
91 | NULL, irq_base: 0, NULL); |
92 | if (ret) |
93 | return ret; |
94 | |
95 | /* |
96 | * We'll use the presence of the AXI ASB regs in the |
97 | * bcm2835-pm binding as the key for whether we can reference |
98 | * the full PM register range and support power domains. |
99 | */ |
100 | if (pm->asb) |
101 | return devm_mfd_add_devices(dev, id: -1, cells: bcm2835_power_devs, |
102 | ARRAY_SIZE(bcm2835_power_devs), |
103 | NULL, irq_base: 0, NULL); |
104 | return 0; |
105 | } |
106 | |
107 | static const struct of_device_id bcm2835_pm_of_match[] = { |
108 | { .compatible = "brcm,bcm2835-pm-wdt" , }, |
109 | { .compatible = "brcm,bcm2835-pm" , }, |
110 | { .compatible = "brcm,bcm2711-pm" , }, |
111 | {}, |
112 | }; |
113 | MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); |
114 | |
115 | static struct platform_driver bcm2835_pm_driver = { |
116 | .probe = bcm2835_pm_probe, |
117 | .driver = { |
118 | .name = "bcm2835-pm" , |
119 | .of_match_table = bcm2835_pm_of_match, |
120 | }, |
121 | }; |
122 | module_platform_driver(bcm2835_pm_driver); |
123 | |
124 | MODULE_AUTHOR("Eric Anholt <eric@anholt.net>" ); |
125 | MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM MFD" ); |
126 | |