1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Spreadtrum Communications Inc. |
4 | * Copyright (C) 2018 Linaro Ltd. |
5 | */ |
6 | |
7 | #include <linux/cpu.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/pm.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/syscore_ops.h> |
14 | |
15 | #define SC27XX_PWR_PD_HW 0xc2c |
16 | #define SC27XX_PWR_OFF_EN BIT(0) |
17 | #define SC27XX_SLP_CTRL 0xdf0 |
18 | #define SC27XX_LDO_XTL_EN BIT(3) |
19 | |
20 | static struct regmap *regmap; |
21 | |
22 | /* |
23 | * On Spreadtrum platform, we need power off system through external SC27xx |
24 | * series PMICs, and it is one similar SPI bus mapped by regmap to access PMIC, |
25 | * which is not fast io access. |
26 | * |
27 | * So before stopping other cores, we need release other cores' resource by |
28 | * taking cpus down to avoid racing regmap or spi mutex lock when poweroff |
29 | * system through PMIC. |
30 | */ |
31 | static void sc27xx_poweroff_shutdown(void) |
32 | { |
33 | #ifdef CONFIG_HOTPLUG_CPU |
34 | int cpu; |
35 | |
36 | for_each_online_cpu(cpu) { |
37 | if (cpu != smp_processor_id()) |
38 | remove_cpu(cpu); |
39 | } |
40 | #endif |
41 | } |
42 | |
43 | static struct syscore_ops poweroff_syscore_ops = { |
44 | .shutdown = sc27xx_poweroff_shutdown, |
45 | }; |
46 | |
47 | static void sc27xx_poweroff_do_poweroff(void) |
48 | { |
49 | /* Disable the external subsys connection's power firstly */ |
50 | regmap_write(map: regmap, SC27XX_SLP_CTRL, SC27XX_LDO_XTL_EN); |
51 | |
52 | regmap_write(map: regmap, SC27XX_PWR_PD_HW, SC27XX_PWR_OFF_EN); |
53 | } |
54 | |
55 | static int sc27xx_poweroff_probe(struct platform_device *pdev) |
56 | { |
57 | if (regmap) |
58 | return -EINVAL; |
59 | |
60 | regmap = dev_get_regmap(dev: pdev->dev.parent, NULL); |
61 | if (!regmap) |
62 | return -ENODEV; |
63 | |
64 | pm_power_off = sc27xx_poweroff_do_poweroff; |
65 | register_syscore_ops(ops: &poweroff_syscore_ops); |
66 | return 0; |
67 | } |
68 | |
69 | static struct platform_driver sc27xx_poweroff_driver = { |
70 | .probe = sc27xx_poweroff_probe, |
71 | .driver = { |
72 | .name = "sc27xx-poweroff" , |
73 | }, |
74 | }; |
75 | module_platform_driver(sc27xx_poweroff_driver); |
76 | |
77 | MODULE_DESCRIPTION("Power off driver for SC27XX PMIC Device" ); |
78 | MODULE_AUTHOR("Baolin Wang <baolin.wang@unisoc.com>" ); |
79 | MODULE_LICENSE("GPL v2" ); |
80 | |