1 /* 2 * Generic Syscon Poweroff Driver 3 * 4 * Copyright (c) 2015, National Instruments Corp. 5 * Author: Moritz Fischer <moritz.fischer@ettus.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation; either version 2 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 */ 17 18 #include <linux/kallsyms.h> 19 #include <linux/delay.h> 20 #include <linux/io.h> 21 #include <linux/notifier.h> 22 #include <linux/mfd/syscon.h> 23 #include <linux/of_address.h> 24 #include <linux/of_device.h> 25 #include <linux/platform_device.h> 26 #include <linux/pm.h> 27 #include <linux/regmap.h> 28 29 static struct regmap *map; 30 static u32 offset; 31 static u32 value; 32 static u32 mask; 33 34 static void syscon_poweroff(void) 35 { 36 /* Issue the poweroff */ 37 regmap_update_bits(map, offset, mask, value); 38 39 mdelay(1000); 40 41 pr_emerg("Unable to poweroff system\n"); 42 } 43 44 static int syscon_poweroff_probe(struct platform_device *pdev) 45 { 46 char symname[KSYM_NAME_LEN]; 47 int mask_err, value_err; 48 49 map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap"); 50 if (IS_ERR(map)) { 51 dev_err(&pdev->dev, "unable to get syscon"); 52 return PTR_ERR(map); 53 } 54 55 if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) { 56 dev_err(&pdev->dev, "unable to read 'offset'"); 57 return -EINVAL; 58 } 59 60 value_err = of_property_read_u32(pdev->dev.of_node, "value", &value); 61 mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask); 62 if (value_err && mask_err) { 63 dev_err(&pdev->dev, "unable to read 'value' and 'mask'"); 64 return -EINVAL; 65 } 66 67 if (value_err) { 68 /* support old binding */ 69 value = mask; 70 mask = 0xFFFFFFFF; 71 } else if (mask_err) { 72 /* support value without mask*/ 73 mask = 0xFFFFFFFF; 74 } 75 76 if (pm_power_off) { 77 lookup_symbol_name((ulong)pm_power_off, symname); 78 dev_err(&pdev->dev, 79 "pm_power_off already claimed %p %s", 80 pm_power_off, symname); 81 return -EBUSY; 82 } 83 84 pm_power_off = syscon_poweroff; 85 86 return 0; 87 } 88 89 static int syscon_poweroff_remove(struct platform_device *pdev) 90 { 91 if (pm_power_off == syscon_poweroff) 92 pm_power_off = NULL; 93 94 return 0; 95 } 96 97 static const struct of_device_id syscon_poweroff_of_match[] = { 98 { .compatible = "syscon-poweroff" }, 99 {} 100 }; 101 102 static struct platform_driver syscon_poweroff_driver = { 103 .probe = syscon_poweroff_probe, 104 .remove = syscon_poweroff_remove, 105 .driver = { 106 .name = "syscon-poweroff", 107 .of_match_table = syscon_poweroff_of_match, 108 }, 109 }; 110 111 static int __init syscon_poweroff_register(void) 112 { 113 return platform_driver_register(&syscon_poweroff_driver); 114 } 115 device_initcall(syscon_poweroff_register); 116