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