18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Generic Syscon Poweroff Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2015, National Instruments Corp. 68c2ecf20Sopenharmony_ci * Author: Moritz Fischer <moritz.fischer@ettus.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/kallsyms.h> 108c2ecf20Sopenharmony_ci#include <linux/delay.h> 118c2ecf20Sopenharmony_ci#include <linux/io.h> 128c2ecf20Sopenharmony_ci#include <linux/notifier.h> 138c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 148c2ecf20Sopenharmony_ci#include <linux/of_address.h> 158c2ecf20Sopenharmony_ci#include <linux/of_device.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/pm.h> 188c2ecf20Sopenharmony_ci#include <linux/regmap.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic struct regmap *map; 218c2ecf20Sopenharmony_cistatic u32 offset; 228c2ecf20Sopenharmony_cistatic u32 value; 238c2ecf20Sopenharmony_cistatic u32 mask; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic void syscon_poweroff(void) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci /* Issue the poweroff */ 288c2ecf20Sopenharmony_ci regmap_update_bits(map, offset, mask, value); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci mdelay(1000); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci pr_emerg("Unable to poweroff system\n"); 338c2ecf20Sopenharmony_ci} 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic int syscon_poweroff_probe(struct platform_device *pdev) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci char symname[KSYM_NAME_LEN]; 388c2ecf20Sopenharmony_ci int mask_err, value_err; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap"); 418c2ecf20Sopenharmony_ci if (IS_ERR(map)) { 428c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "unable to get syscon"); 438c2ecf20Sopenharmony_ci return PTR_ERR(map); 448c2ecf20Sopenharmony_ci } 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) { 478c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "unable to read 'offset'"); 488c2ecf20Sopenharmony_ci return -EINVAL; 498c2ecf20Sopenharmony_ci } 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci value_err = of_property_read_u32(pdev->dev.of_node, "value", &value); 528c2ecf20Sopenharmony_ci mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask); 538c2ecf20Sopenharmony_ci if (value_err && mask_err) { 548c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "unable to read 'value' and 'mask'"); 558c2ecf20Sopenharmony_ci return -EINVAL; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci if (value_err) { 598c2ecf20Sopenharmony_ci /* support old binding */ 608c2ecf20Sopenharmony_ci value = mask; 618c2ecf20Sopenharmony_ci mask = 0xFFFFFFFF; 628c2ecf20Sopenharmony_ci } else if (mask_err) { 638c2ecf20Sopenharmony_ci /* support value without mask*/ 648c2ecf20Sopenharmony_ci mask = 0xFFFFFFFF; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (pm_power_off) { 688c2ecf20Sopenharmony_ci lookup_symbol_name((ulong)pm_power_off, symname); 698c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 708c2ecf20Sopenharmony_ci "pm_power_off already claimed %p %s", 718c2ecf20Sopenharmony_ci pm_power_off, symname); 728c2ecf20Sopenharmony_ci return -EBUSY; 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci pm_power_off = syscon_poweroff; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return 0; 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int syscon_poweroff_remove(struct platform_device *pdev) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci if (pm_power_off == syscon_poweroff) 838c2ecf20Sopenharmony_ci pm_power_off = NULL; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci return 0; 868c2ecf20Sopenharmony_ci} 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic const struct of_device_id syscon_poweroff_of_match[] = { 898c2ecf20Sopenharmony_ci { .compatible = "syscon-poweroff" }, 908c2ecf20Sopenharmony_ci {} 918c2ecf20Sopenharmony_ci}; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic struct platform_driver syscon_poweroff_driver = { 948c2ecf20Sopenharmony_ci .probe = syscon_poweroff_probe, 958c2ecf20Sopenharmony_ci .remove = syscon_poweroff_remove, 968c2ecf20Sopenharmony_ci .driver = { 978c2ecf20Sopenharmony_ci .name = "syscon-poweroff", 988c2ecf20Sopenharmony_ci .of_match_table = syscon_poweroff_of_match, 998c2ecf20Sopenharmony_ci }, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic int __init syscon_poweroff_register(void) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci return platform_driver_register(&syscon_poweroff_driver); 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_cidevice_initcall(syscon_poweroff_register); 107