linux/drivers/power/reset/syscon-poweroff.c
<<
>>
Prefs
   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
  29static struct regmap *map;
  30static u32 offset;
  31static u32 value;
  32static u32 mask;
  33
  34static 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
  44static 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
  89static 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
  97static const struct of_device_id syscon_poweroff_of_match[] = {
  98        { .compatible = "syscon-poweroff" },
  99        {}
 100};
 101
 102static 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
 111static int __init syscon_poweroff_register(void)
 112{
 113        return platform_driver_register(&syscon_poweroff_driver);
 114}
 115device_initcall(syscon_poweroff_register);
 116