Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Generic Syscon Poweroff Driver
0004  *
0005  * Copyright (c) 2015, National Instruments Corp.
0006  * Author: Moritz Fischer <moritz.fischer@ettus.com>
0007  */
0008 
0009 #include <linux/delay.h>
0010 #include <linux/io.h>
0011 #include <linux/notifier.h>
0012 #include <linux/mfd/syscon.h>
0013 #include <linux/of_address.h>
0014 #include <linux/of_device.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/pm.h>
0017 #include <linux/regmap.h>
0018 
0019 static struct regmap *map;
0020 static u32 offset;
0021 static u32 value;
0022 static u32 mask;
0023 
0024 static void syscon_poweroff(void)
0025 {
0026     /* Issue the poweroff */
0027     regmap_update_bits(map, offset, mask, value);
0028 
0029     mdelay(1000);
0030 
0031     pr_emerg("Unable to poweroff system\n");
0032 }
0033 
0034 static int syscon_poweroff_probe(struct platform_device *pdev)
0035 {
0036     int mask_err, value_err;
0037 
0038     map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
0039     if (IS_ERR(map)) {
0040         dev_err(&pdev->dev, "unable to get syscon");
0041         return PTR_ERR(map);
0042     }
0043 
0044     if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) {
0045         dev_err(&pdev->dev, "unable to read 'offset'");
0046         return -EINVAL;
0047     }
0048 
0049     value_err = of_property_read_u32(pdev->dev.of_node, "value", &value);
0050     mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask);
0051     if (value_err && mask_err) {
0052         dev_err(&pdev->dev, "unable to read 'value' and 'mask'");
0053         return -EINVAL;
0054     }
0055 
0056     if (value_err) {
0057         /* support old binding */
0058         value = mask;
0059         mask = 0xFFFFFFFF;
0060     } else if (mask_err) {
0061         /* support value without mask*/
0062         mask = 0xFFFFFFFF;
0063     }
0064 
0065     if (pm_power_off) {
0066         dev_err(&pdev->dev, "pm_power_off already claimed for %ps",
0067             pm_power_off);
0068         return -EBUSY;
0069     }
0070 
0071     pm_power_off = syscon_poweroff;
0072 
0073     return 0;
0074 }
0075 
0076 static int syscon_poweroff_remove(struct platform_device *pdev)
0077 {
0078     if (pm_power_off == syscon_poweroff)
0079         pm_power_off = NULL;
0080 
0081     return 0;
0082 }
0083 
0084 static const struct of_device_id syscon_poweroff_of_match[] = {
0085     { .compatible = "syscon-poweroff" },
0086     {}
0087 };
0088 
0089 static struct platform_driver syscon_poweroff_driver = {
0090     .probe = syscon_poweroff_probe,
0091     .remove = syscon_poweroff_remove,
0092     .driver = {
0093         .name = "syscon-poweroff",
0094         .of_match_table = syscon_poweroff_of_match,
0095     },
0096 };
0097 
0098 static int __init syscon_poweroff_register(void)
0099 {
0100     return platform_driver_register(&syscon_poweroff_driver);
0101 }
0102 device_initcall(syscon_poweroff_register);