1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Generic Syscon Poweroff Driver 4 * 5 * Copyright (c) 2015, National Instruments Corp. 6 * Author: Moritz Fischer <moritz.fischer@ettus.com> 7 */ 8 9 #include <linux/delay.h> 10 #include <linux/io.h> 11 #include <linux/notifier.h> 12 #include <linux/mfd/syscon.h> 13 #include <linux/of.h> 14 #include <linux/platform_device.h> 15 #include <linux/pm.h> 16 #include <linux/regmap.h> 17 18 static struct regmap *map; 19 static u32 offset; 20 static u32 value; 21 static u32 mask; 22 23 static void syscon_poweroff(void) 24 { 25 /* Issue the poweroff */ 26 regmap_update_bits(map, offset, mask, value); 27 28 mdelay(1000); 29 30 pr_emerg("Unable to poweroff system\n"); 31 } 32 33 static int syscon_poweroff_probe(struct platform_device *pdev) 34 { 35 struct device *dev = &pdev->dev; 36 int mask_err, value_err; 37 38 map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap"); 39 if (IS_ERR(map)) { 40 map = syscon_node_to_regmap(dev->parent->of_node); 41 if (IS_ERR(map)) { 42 dev_err(dev, "unable to get syscon"); 43 return PTR_ERR(map); 44 } 45 } 46 47 if (of_property_read_u32(dev->of_node, "offset", &offset)) { 48 dev_err(dev, "unable to read 'offset'"); 49 return -EINVAL; 50 } 51 52 value_err = of_property_read_u32(dev->of_node, "value", &value); 53 mask_err = of_property_read_u32(dev->of_node, "mask", &mask); 54 if (value_err && mask_err) { 55 dev_err(dev, "unable to read 'value' and 'mask'"); 56 return -EINVAL; 57 } 58 59 if (value_err) { 60 /* support old binding */ 61 value = mask; 62 mask = 0xFFFFFFFF; 63 } else if (mask_err) { 64 /* support value without mask*/ 65 mask = 0xFFFFFFFF; 66 } 67 68 if (pm_power_off) { 69 dev_err(dev, "pm_power_off already claimed for %ps", 70 pm_power_off); 71 return -EBUSY; 72 } 73 74 pm_power_off = syscon_poweroff; 75 76 return 0; 77 } 78 79 static int syscon_poweroff_remove(struct platform_device *pdev) 80 { 81 if (pm_power_off == syscon_poweroff) 82 pm_power_off = NULL; 83 84 return 0; 85 } 86 87 static const struct of_device_id syscon_poweroff_of_match[] = { 88 { .compatible = "syscon-poweroff" }, 89 {} 90 }; 91 92 static struct platform_driver syscon_poweroff_driver = { 93 .probe = syscon_poweroff_probe, 94 .remove = syscon_poweroff_remove, 95 .driver = { 96 .name = "syscon-poweroff", 97 .of_match_table = syscon_poweroff_of_match, 98 }, 99 }; 100 builtin_platform_driver(syscon_poweroff_driver); 101