| // 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); |