root/drivers/power/reset/arm-versatile-reboot.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2014 Linaro Ltd.
 *
 * Author: Linus Walleij <linus.walleij@linaro.org>
 */
#include <linux/init.h>
#include <linux/mfd/syscon.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/of.h>

#define INTEGRATOR_HDR_CTRL_OFFSET      0x0C
#define INTEGRATOR_HDR_LOCK_OFFSET      0x14
#define INTEGRATOR_CM_CTRL_RESET        (1 << 3)

#define VERSATILE_SYS_LOCK_OFFSET       0x20
#define VERSATILE_SYS_RESETCTL_OFFSET   0x40

/* Magic unlocking token used on all Versatile boards */
#define VERSATILE_LOCK_VAL              0xA05F

/*
 * We detect the different syscon types from the compatible strings.
 */
enum versatile_reboot {
        INTEGRATOR_REBOOT_CM,
        VERSATILE_REBOOT_CM,
        REALVIEW_REBOOT_EB,
        REALVIEW_REBOOT_PB1176,
        REALVIEW_REBOOT_PB11MP,
        REALVIEW_REBOOT_PBA8,
        REALVIEW_REBOOT_PBX,
};

/* Pointer to the system controller */
static struct regmap *syscon_regmap;
static enum versatile_reboot versatile_reboot_type;

static const struct of_device_id versatile_reboot_of_match[] = {
        {
                .compatible = "arm,core-module-integrator",
                .data = (void *)INTEGRATOR_REBOOT_CM
        },
        {
                .compatible = "arm,core-module-versatile",
                .data = (void *)VERSATILE_REBOOT_CM,
        },
        {
                .compatible = "arm,realview-eb-syscon",
                .data = (void *)REALVIEW_REBOOT_EB,
        },
        {
                .compatible = "arm,realview-pb1176-syscon",
                .data = (void *)REALVIEW_REBOOT_PB1176,
        },
        {
                .compatible = "arm,realview-pb11mp-syscon",
                .data = (void *)REALVIEW_REBOOT_PB11MP,
        },
        {
                .compatible = "arm,realview-pba8-syscon",
                .data = (void *)REALVIEW_REBOOT_PBA8,
        },
        {
                .compatible = "arm,realview-pbx-syscon",
                .data = (void *)REALVIEW_REBOOT_PBX,
        },
        {},
};

static int versatile_reboot(struct notifier_block *this, unsigned long mode,
                            void *cmd)
{
        /* Unlock the reset register */
        /* Then hit reset on the different machines */
        switch (versatile_reboot_type) {
        case INTEGRATOR_REBOOT_CM:
                regmap_write(syscon_regmap, INTEGRATOR_HDR_LOCK_OFFSET,
                             VERSATILE_LOCK_VAL);
                regmap_update_bits(syscon_regmap,
                                   INTEGRATOR_HDR_CTRL_OFFSET,
                                   INTEGRATOR_CM_CTRL_RESET,
                                   INTEGRATOR_CM_CTRL_RESET);
                break;
        case VERSATILE_REBOOT_CM:
                regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
                             VERSATILE_LOCK_VAL);
                regmap_update_bits(syscon_regmap,
                                   VERSATILE_SYS_RESETCTL_OFFSET,
                                   0x0107,
                                   0x0105);
                regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
                             0);
                break;
        case REALVIEW_REBOOT_EB:
                regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
                             VERSATILE_LOCK_VAL);
                regmap_write(syscon_regmap,
                             VERSATILE_SYS_RESETCTL_OFFSET, 0x0008);
                break;
        case REALVIEW_REBOOT_PB1176:
                regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
                             VERSATILE_LOCK_VAL);
                regmap_write(syscon_regmap,
                             VERSATILE_SYS_RESETCTL_OFFSET, 0x0100);
                break;
        case REALVIEW_REBOOT_PB11MP:
        case REALVIEW_REBOOT_PBA8:
                regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
                             VERSATILE_LOCK_VAL);
                regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
                             0x0000);
                regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
                             0x0004);
                break;
        case REALVIEW_REBOOT_PBX:
                regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
                             VERSATILE_LOCK_VAL);
                regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
                             0x00f0);
                regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
                             0x00f4);
                break;
        }
        dsb();

        return NOTIFY_DONE;
}

static struct notifier_block versatile_reboot_nb = {
        .notifier_call = versatile_reboot,
        .priority = 192,
};

static int __init versatile_reboot_probe(void)
{
        const struct of_device_id *reboot_id;
        struct device_node *np;
        int err;

        np = of_find_matching_node_and_match(NULL, versatile_reboot_of_match,
                                                 &reboot_id);
        if (!np)
                return -ENODEV;
        versatile_reboot_type = (enum versatile_reboot)reboot_id->data;

        syscon_regmap = syscon_node_to_regmap(np);
        of_node_put(np);
        if (IS_ERR(syscon_regmap))
                return PTR_ERR(syscon_regmap);

        err = register_restart_handler(&versatile_reboot_nb);
        if (err)
                return err;

        pr_info("versatile reboot driver registered\n");
        return 0;
}
device_initcall(versatile_reboot_probe);