syscon-poweroff.c 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Generic Syscon Poweroff Driver
  4. *
  5. * Copyright (c) 2015, National Instruments Corp.
  6. * Author: Moritz Fischer <[email protected]>
  7. */
  8. #include <linux/delay.h>
  9. #include <linux/io.h>
  10. #include <linux/notifier.h>
  11. #include <linux/mfd/syscon.h>
  12. #include <linux/of_address.h>
  13. #include <linux/of_device.h>
  14. #include <linux/platform_device.h>
  15. #include <linux/pm.h>
  16. #include <linux/regmap.h>
  17. static struct regmap *map;
  18. static u32 offset;
  19. static u32 value;
  20. static u32 mask;
  21. static void syscon_poweroff(void)
  22. {
  23. /* Issue the poweroff */
  24. regmap_update_bits(map, offset, mask, value);
  25. mdelay(1000);
  26. pr_emerg("Unable to poweroff system\n");
  27. }
  28. static int syscon_poweroff_probe(struct platform_device *pdev)
  29. {
  30. int mask_err, value_err;
  31. map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
  32. if (IS_ERR(map)) {
  33. dev_err(&pdev->dev, "unable to get syscon");
  34. return PTR_ERR(map);
  35. }
  36. if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) {
  37. dev_err(&pdev->dev, "unable to read 'offset'");
  38. return -EINVAL;
  39. }
  40. value_err = of_property_read_u32(pdev->dev.of_node, "value", &value);
  41. mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask);
  42. if (value_err && mask_err) {
  43. dev_err(&pdev->dev, "unable to read 'value' and 'mask'");
  44. return -EINVAL;
  45. }
  46. if (value_err) {
  47. /* support old binding */
  48. value = mask;
  49. mask = 0xFFFFFFFF;
  50. } else if (mask_err) {
  51. /* support value without mask*/
  52. mask = 0xFFFFFFFF;
  53. }
  54. if (pm_power_off) {
  55. dev_err(&pdev->dev, "pm_power_off already claimed for %ps",
  56. pm_power_off);
  57. return -EBUSY;
  58. }
  59. pm_power_off = syscon_poweroff;
  60. return 0;
  61. }
  62. static int syscon_poweroff_remove(struct platform_device *pdev)
  63. {
  64. if (pm_power_off == syscon_poweroff)
  65. pm_power_off = NULL;
  66. return 0;
  67. }
  68. static const struct of_device_id syscon_poweroff_of_match[] = {
  69. { .compatible = "syscon-poweroff" },
  70. {}
  71. };
  72. static struct platform_driver syscon_poweroff_driver = {
  73. .probe = syscon_poweroff_probe,
  74. .remove = syscon_poweroff_remove,
  75. .driver = {
  76. .name = "syscon-poweroff",
  77. .of_match_table = syscon_poweroff_of_match,
  78. },
  79. };
  80. static int __init syscon_poweroff_register(void)
  81. {
  82. return platform_driver_register(&syscon_poweroff_driver);
  83. }
  84. device_initcall(syscon_poweroff_register);