linux/drivers/power/reset/syscon-poweroff.c
<<
>>
Prefs
   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_address.h>
  14#include <linux/of_device.h>
  15#include <linux/platform_device.h>
  16#include <linux/pm.h>
  17#include <linux/regmap.h>
  18
  19static struct regmap *map;
  20static u32 offset;
  21static u32 value;
  22static u32 mask;
  23
  24static void syscon_poweroff(void)
  25{
  26        /* Issue the poweroff */
  27        regmap_update_bits(map, offset, mask, value);
  28
  29        mdelay(1000);
  30
  31        pr_emerg("Unable to poweroff system\n");
  32}
  33
  34static int syscon_poweroff_probe(struct platform_device *pdev)
  35{
  36        int mask_err, value_err;
  37
  38        map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
  39        if (IS_ERR(map)) {
  40                dev_err(&pdev->dev, "unable to get syscon");
  41                return PTR_ERR(map);
  42        }
  43
  44        if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) {
  45                dev_err(&pdev->dev, "unable to read 'offset'");
  46                return -EINVAL;
  47        }
  48
  49        value_err = of_property_read_u32(pdev->dev.of_node, "value", &value);
  50        mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask);
  51        if (value_err && mask_err) {
  52                dev_err(&pdev->dev, "unable to read 'value' and 'mask'");
  53                return -EINVAL;
  54        }
  55
  56        if (value_err) {
  57                /* support old binding */
  58                value = mask;
  59                mask = 0xFFFFFFFF;
  60        } else if (mask_err) {
  61                /* support value without mask*/
  62                mask = 0xFFFFFFFF;
  63        }
  64
  65        if (pm_power_off) {
  66                dev_err(&pdev->dev, "pm_power_off already claimed for %ps",
  67                        pm_power_off);
  68                return -EBUSY;
  69        }
  70
  71        pm_power_off = syscon_poweroff;
  72
  73        return 0;
  74}
  75
  76static int syscon_poweroff_remove(struct platform_device *pdev)
  77{
  78        if (pm_power_off == syscon_poweroff)
  79                pm_power_off = NULL;
  80
  81        return 0;
  82}
  83
  84static const struct of_device_id syscon_poweroff_of_match[] = {
  85        { .compatible = "syscon-poweroff" },
  86        {}
  87};
  88
  89static struct platform_driver syscon_poweroff_driver = {
  90        .probe = syscon_poweroff_probe,
  91        .remove = syscon_poweroff_remove,
  92        .driver = {
  93                .name = "syscon-poweroff",
  94                .of_match_table = syscon_poweroff_of_match,
  95        },
  96};
  97
  98static int __init syscon_poweroff_register(void)
  99{
 100        return platform_driver_register(&syscon_poweroff_driver);
 101}
 102device_initcall(syscon_poweroff_register);
 103