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(pm->dev->of_node, "reg-names")) { 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 pm->soc = (uintptr_t)device_get_match_data(dev); 85 86 ret = bcm2835_pm_get_pdata(pdev, pm); 87 if (ret) 88 return ret; 89 90 ret = devm_mfd_add_devices(dev, -1, 91 bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), 92 NULL, 0, NULL); 93 if (ret) 94 return ret; 95 96 /* 97 * We'll use the presence of the AXI ASB regs in the 98 * bcm2835-pm binding as the key for whether we can reference 99 * the full PM register range and support power domains. 100 */ 101 if (pm->asb || pm->soc == BCM2835_PM_SOC_BCM2712) 102 return devm_mfd_add_devices(dev, -1, bcm2835_power_devs, 103 ARRAY_SIZE(bcm2835_power_devs), 104 NULL, 0, NULL); 105 return 0; 106 } 107 108 static const struct of_device_id bcm2835_pm_of_match[] = { 109 { .compatible = "brcm,bcm2835-pm-wdt", }, 110 { .compatible = "brcm,bcm2835-pm", .data = (void *)BCM2835_PM_SOC_BCM2835 }, 111 { .compatible = "brcm,bcm2711-pm", .data = (void *)BCM2835_PM_SOC_BCM2711 }, 112 { .compatible = "brcm,bcm2712-pm", .data = (void *)BCM2835_PM_SOC_BCM2712 }, 113 {}, 114 }; 115 MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); 116 117 static struct platform_driver bcm2835_pm_driver = { 118 .probe = bcm2835_pm_probe, 119 .driver = { 120 .name = "bcm2835-pm", 121 .of_match_table = bcm2835_pm_of_match, 122 }, 123 }; 124 module_platform_driver(bcm2835_pm_driver); 125 126 MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); 127 MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM MFD"); 128