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_find_property(pm->dev->of_node, "reg-names", NULL)) { 32 struct resource *res; 33 34 pm->base = devm_platform_ioremap_resource_byname(pdev, "pm"); 35 if (IS_ERR(pm->base)) 36 return PTR_ERR(pm->base); 37 38 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "asb"); 39 if (res) { 40 pm->asb = devm_ioremap_resource(&pdev->dev, res); 41 if (IS_ERR(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(&pdev->dev, res); 49 if (IS_ERR(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, 0); 58 if (IS_ERR(pm->base)) 59 return PTR_ERR(pm->base); 60 61 pm->asb = devm_platform_ioremap_resource(pdev, 1); 62 if (IS_ERR(pm->asb)) 63 pm->asb = NULL; 64 65 pm->rpivid_asb = devm_platform_ioremap_resource(pdev, 2); 66 if (IS_ERR(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, sizeof(*pm), GFP_KERNEL); 79 if (!pm) 80 return -ENOMEM; 81 platform_set_drvdata(pdev, 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, -1, 90 bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), 91 NULL, 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, -1, bcm2835_power_devs, 102 ARRAY_SIZE(bcm2835_power_devs), 103 NULL, 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 MODULE_LICENSE("GPL"); 127