// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" static u32 deep_mode; static int deep_mode_init_done; bool is_clr_resume_reason; static unsigned int suspend_mode; #define SMCID_POWER_MANAGER 0x82000079 #define SMCSUBID_PM_DDR_ASR 0x1000 #define SMCSUBID_PM_DUMP_INFO 0x1001 #define SMCSUBID_PM_DEEPCTRL 0x1002 #define SMCSUBID_PM_GET_EE_STAT 0x1003 #if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND) static DEFINE_MUTEX(early_suspend_lock); static DEFINE_MUTEX(sysfs_trigger_lock); static LIST_HEAD(early_suspend_handlers); /* In order to handle legacy early_suspend driver, * here we export sysfs interface * for user space to write /sys/power/early_suspend_trigger to trigger * early_suspend/late resume call back. If user space do not trigger * early_suspend/late_resume, this op will be done * by PM_SUSPEND_PREPARE notify. */ unsigned int sysfs_trigger; unsigned int early_suspend_state; /* * Avoid run early_suspend/late_resume repeatedly. */ unsigned int already_early_suspend; /* early suspend debug flag */ unsigned int early_suspend_debug; /* kernel & bl31 & bl30 suspend debug flag */ unsigned int suspend_debug_flag; /* deep suspend enter flag */ static unsigned int deep_suspend; /* get power event from input */ static int power_event_available; /* get device name which send power event */ static char dev_name_event[128]; #define DRIVER_NAME "GX_PM" void register_early_suspend(struct early_suspend *handler) { struct list_head *pos; mutex_lock(&early_suspend_lock); list_for_each(pos, &early_suspend_handlers) { struct early_suspend *e; e = list_entry(pos, struct early_suspend, link); if (e->level > handler->level) break; } list_add_tail(&handler->link, pos); mutex_unlock(&early_suspend_lock); } EXPORT_SYMBOL(register_early_suspend); void unregister_early_suspend(struct early_suspend *handler) { mutex_lock(&early_suspend_lock); list_del(&handler->link); mutex_unlock(&early_suspend_lock); } EXPORT_SYMBOL(unregister_early_suspend); static int suspend_get_pm_env(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &early_suspend_debug)) { pr_err("early_suspend_debug error: %s\n", buf); return -EINVAL; } return 1; } __setup("initcall_debug=", suspend_get_pm_env); static inline void early_suspend(void) { struct early_suspend *pos; mutex_lock(&early_suspend_lock); if (!already_early_suspend) already_early_suspend = 1; else goto end_early_suspend; //set node to 0 notify other drivers begin early suspend deep_suspend = 0; if (early_suspend_debug) pr_info("%s: call handlers\n", __func__); list_for_each_entry(pos, &early_suspend_handlers, link) if (pos->suspend) { if (early_suspend_debug) pr_info("%s: %ps\n", __func__, pos->suspend); pos->suspend(pos); } if (early_suspend_debug) pr_info("%s: done\n", __func__); end_early_suspend: mutex_unlock(&early_suspend_lock); } static inline void late_resume(void) { struct early_suspend *pos; mutex_lock(&early_suspend_lock); if (already_early_suspend) already_early_suspend = 0; else goto end_late_resume; if (early_suspend_debug) pr_info("%s: call handlers\n", __func__); list_for_each_entry_reverse(pos, &early_suspend_handlers, link) if (pos->resume) { if (early_suspend_debug) pr_info("%s: %ps\n", __func__, pos->resume); pos->resume(pos); } if (early_suspend_debug) pr_info("%s: done\n", __func__); end_late_resume: mutex_unlock(&early_suspend_lock); } static ssize_t early_suspend_trigger_show(const struct class *class, const struct class_attribute *attr, char *buf) { unsigned int len; len = sprintf(buf, "%d\n", early_suspend_state); return len; } static ssize_t early_suspend_trigger_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { int ret; ret = kstrtouint(buf, 0, &early_suspend_state); pr_info("early_suspend_state=%d\n", early_suspend_state); if (ret) return -EINVAL; mutex_lock(&sysfs_trigger_lock); sysfs_trigger = 1; if (early_suspend_state == 0) late_resume(); else if (early_suspend_state == 1) early_suspend(); mutex_unlock(&sysfs_trigger_lock); return count; } static CLASS_ATTR_RW(early_suspend_trigger); #define SUSPEND_DEBUG_LOGLEVEL (0xf << (0)) #define SUSPEND_DEBUG_INITCALL_DEBUG (1U << (4)) static unsigned long __invoke_psci_fn_smc(unsigned long function_id, unsigned long arg0, unsigned long arg1, unsigned long arg2); static const char *suspend_debug_help_str = { "Usage:\n" "echo 0xXXXXXXXX > /sys/class/meson_pm/suspend_debug //enable suspend debug mode\n\n" "Suspend Debug State List:\n\n" "--------------------------------------------------------------------------------------------------\n" }; #define MAX_ENTRIES 32 struct Suspend_Debug_Mode { const char *description; int end_bit; int start_bit; int state; }; struct Suspend_Debug_Mode suspend_debug_mode[MAX_ENTRIES] = { {"[BL30] clk/pll status check and show clk gate setting", 31, 31, 0}, {"[BL30] mask RTC Wakeup source", 30, 30, 0}, {"[BL30] mask SARADC Wakeup source", 29, 29, 0}, {"[BL30] mask IR Wakeup source", 28, 28, 0}, {"[BL30] show pwm state and voltage", 27, 27, 0}, {"[BL30] function call log", 26, 26, 0}, {"[BL30] open dmc monitor log", 25, 25, 0}, {"[BL30] bl30 dump boot cpu's fsm and dsu's fsm", 24, 24, 0}, {"[BL30] aocpu do ddr access(reserved)", 23, 23, 0}, {"[BL30] skip power switch(vddee vddcpu vcc5v vcc3.3V etc)", 22, 22, 0}, {"[BL30] skip ddr suspend in bl30", 21, 21, 0}, {"[BL30] start debug task", 20, 20, 0}, {"[BL31] reserved", 19, 17, 0}, {"[BL31] skip clk gate switch", 16, 16, 0}, {"[BL31] dump nonboot cpu's fsm state", 15, 15, 0}, {"[BL31] skip ddr suspend in tlpm", 14, 14, 0}, {"[BL31] power domain clk/pll status check and show clk gate setting", 13, 13, 0}, {"[BL31] do clk reg check and rate check", 12, 12, 0}, {"[BL31] do bl31 ddr checksum in tlpm", 11, 11, 0}, {"[BL31] open dmc monitor log", 10, 10, 0}, {"[BL31] bl31 debug loglevel(0:without log;1: normal;2: function call log)", 9, 8, 0}, {"[KERNEL] reserved for dmc monitor", 7, 4, 0}, {"[KERNEL] set kernel printk loglevel to 0~9", 3, 0, 0}, }; static void check_suspend_debug_mode(void) { int i; for (i = 0; i < MAX_ENTRIES; i++) { int mask = (1 << (suspend_debug_mode[i].end_bit + 1 - suspend_debug_mode[i].start_bit)) - 1; int value = (suspend_debug_flag >> suspend_debug_mode[i].start_bit) & mask; suspend_debug_mode[i].state = value; } } static void set_suspend_debug_flag(int suspend_flag) { __invoke_psci_fn_smc(0x820000F3, 0, suspend_debug_flag & (~(SUSPEND_DEBUG_LOGLEVEL | SUSPEND_DEBUG_INITCALL_DEBUG)), 0); } static ssize_t suspend_debug_show(const struct class *class, const struct class_attribute *attr, char *buf) { unsigned int len; int i; len = sprintf(&buf[0], "suspend_debug_flag = 0x%x\n\n", suspend_debug_flag); len += sprintf(&buf[len], "%s\n", suspend_debug_help_str); check_suspend_debug_mode(); for (i = 0; i < MAX_ENTRIES; i++) { if (suspend_debug_mode[i].description) { if (suspend_debug_mode[i].start_bit == suspend_debug_mode[i].end_bit) len += sprintf(&buf[len], "%-80s bit[%d] \t%d\n", suspend_debug_mode[i].description, suspend_debug_mode[i].start_bit, suspend_debug_mode[i].state); else len += sprintf(&buf[len], "%-80s bit[%d:%d] \t%d\n", suspend_debug_mode[i].description, suspend_debug_mode[i].end_bit, suspend_debug_mode[i].start_bit, suspend_debug_mode[i].state); } } return len; } static ssize_t suspend_debug_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { int ret; ret = kstrtouint(buf, 0, &suspend_debug_flag); pr_info("suspend_debug_flag = 0x%x\n", suspend_debug_flag); if (ret) return -EINVAL; set_suspend_debug_flag(suspend_debug_flag); return count; } static CLASS_ATTR_RW(suspend_debug); static int suspend_get_debug_env(char *buf) { if (!buf) return -EINVAL; if (kstrtoint(buf, 0, &suspend_debug_flag)) { pr_err("suspend_debug_flag error: %s\n", buf); return -EINVAL; } return 1; } __setup("suspend_debug=", suspend_get_debug_env); static void lgcy_early_suspend(void) { mutex_lock(&sysfs_trigger_lock); if (!sysfs_trigger) early_suspend(); mutex_unlock(&sysfs_trigger_lock); } static void lgcy_late_resume(void) { mutex_lock(&sysfs_trigger_lock); if (!sysfs_trigger) late_resume(); mutex_unlock(&sysfs_trigger_lock); } static int lgcy_early_suspend_notify(struct notifier_block *nb, unsigned long event, void *dummy) { if (event == PM_SUSPEND_PREPARE) lgcy_early_suspend(); if (event == PM_POST_SUSPEND) lgcy_late_resume(); return NOTIFY_OK; } static struct notifier_block lgcy_early_suspend_notifier = { .notifier_call = lgcy_early_suspend_notify, }; static unsigned int lgcy_early_suspend_exit(struct platform_device *pdev) { int ret; ret = unregister_pm_notifier(&lgcy_early_suspend_notifier); return ret; } #endif /*CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND*/ typedef unsigned long (psci_fn)(unsigned long, unsigned long, unsigned long, unsigned long); static unsigned long __invoke_psci_fn_smc(unsigned long function_id, unsigned long arg0, unsigned long arg1, unsigned long arg2) { struct arm_smccc_res res; arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); return res.a0; } static void __iomem *debug_reg; static void __iomem *exit_reg; static unsigned int resume_reason; static unsigned int suspend_reason; static unsigned int clear_suspend_reason; static bool is_extd_resume_reason; /* * get_resume_value return the register value that stores * resume reason. */ static uint32_t get_resume_value(void) { u32 val = 0; if (exit_reg) { /* resume reason extension support for new soc such as s4/t3/sc2/s5... */ if (is_extd_resume_reason) val = readl_relaxed(exit_reg) & 0xff; /* other soc such as tm2/axg do not support resume reason extension */ else val = (readl_relaxed(exit_reg) >> 28) & 0xf; } return val; } /* * get_resume_reason always return last resume reason. */ unsigned int get_resume_reason(void) { unsigned int val = 0; unsigned int reason; val = get_resume_value(); if (is_extd_resume_reason) reason = val & 0x7f; else reason = val; return reason; } EXPORT_SYMBOL_GPL(get_resume_reason); /* * get_resume_method return last resume reason. * It can be cleared by clr_resume_method(). */ unsigned int get_resume_method(void) { return resume_reason; } EXPORT_SYMBOL_GPL(get_resume_method); /* * judge whether resume_type need power on lcd during resume. * */ bool is_screenon_resume_method(void) { return (get_resume_method() != RTC_WAKEUP) && (get_resume_method() != UNDEFINED_WAKEUP) && (get_resume_method() != WIFI_WAKEUP) && (get_resume_method() != ETH_PHY_WAKEUP); } EXPORT_SYMBOL_GPL(is_screenon_resume_method); static void set_resume_method(unsigned int val) { resume_reason = val; } static void clr_deep_mode(void) { if (deep_mode_init_done) __invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DEEPCTRL, SYSTEM_COLD_BOOT, 0); } static void set_deep_mode(void) { if (deep_mode_init_done) __invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DEEPCTRL, deep_mode, 0); } static int clr_suspend_notify(struct notifier_block *nb, unsigned long event, void *dummy) { if (event == PM_SUSPEND_PREPARE) { set_resume_method(UNDEFINED_WAKEUP); set_deep_mode(); } else if (event == PM_POST_SUSPEND) { clr_deep_mode(); } return NOTIFY_OK; } static struct notifier_block clr_suspend_notifier = { .notifier_call = clr_suspend_notify, }; unsigned int is_pm_s2idle_mode(void) { if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE) return 1; else return 0; } EXPORT_SYMBOL_GPL(is_pm_s2idle_mode); /*Call it as suspend_reason because of historical reasons. */ /*Actually, we should call it wakeup_reason. */ static ssize_t suspend_reason_show(const struct class *class, const struct class_attribute *attr, char *buf) { unsigned int len; unsigned int val; suspend_reason = get_resume_reason(); val = get_resume_value(); len = sprintf(buf, "%d\nreg val:0x%x\n", suspend_reason, val); return len; } static ssize_t suspend_reason_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { int ret; ret = kstrtouint(buf, 0, &suspend_reason); if (ret) return -EINVAL; return count; } static CLASS_ATTR_RW(suspend_reason); /*Call it as deep_suspend flag when it's val is 1 means last str is deep suspend */ ssize_t deep_suspend_show(const struct class *class, const struct class_attribute *attr, char *buf) { return sprintf(buf, "%d\n", deep_suspend); } static CLASS_ATTR_RO(deep_suspend); struct suspend_cmd_t { const char *name; int (*store)(void *data, const char *buf, size_t count); int (*show)(void *data, char *buf); }; struct suspend_mode_cmd_t { const char *version; struct suspend_cmd_t cmds[]; }; static int suspend_ddr_mode_store(void *data, const char *buf, size_t count) { int ret; ret = kstrtouint(buf, 0, &suspend_mode); if (ret) return ret; __invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DDR_ASR, suspend_mode, 0); return 0; } static int suspend_ddr_mode_show(void *data, char *buf) { (void)data; return scnprintf(buf, PAGE_SIZE, "DDR mode=%s\n", suspend_mode ? "ASR" : "SelfRefresh"); } bool ee_support_powerdown(void) { struct arm_smccc_res res; arm_smccc_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_GET_EE_STAT, 0, 0, 0, 0, 0, 0, &res); return res.a0 ? 0 : res.a1; } EXPORT_SYMBOL_GPL(ee_support_powerdown); static int suspend_ctrl_store(void *data, const char *buf, size_t count) { char cmd[16]; u32 arg1; (void)data; if (!deep_mode_init_done) return 0; if (sscanf(buf, "%15s %x", cmd, &arg1) == 2) { deep_mode = SET_MODULE_VAL(VERSION, PM_VERSION, arg1); } else { return -EINVAL; } return 0; } static int suspend_ctrl_show(void *data, char *buf) { (void)data; if (!deep_mode_init_done) return 0; return scnprintf(buf, PAGE_SIZE, "SUSPEND MODE=0x%x\n", deep_mode); } static inline int deep_mode_init(struct platform_device *pdev) { if (!of_property_read_u32(pdev->dev.of_node, "deep_mode", &deep_mode)) { //__invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DEEPCTRL, deep_mode, 0); deep_mode_init_done = 1; } clr_deep_mode(); return 0; } static struct suspend_mode_cmd_t suspend_cmds = { .version = "V01", .cmds = { {"0", suspend_ddr_mode_store, suspend_ddr_mode_show}, {"1", suspend_ddr_mode_store, NULL}, {"DEEPCTRL", suspend_ctrl_store, suspend_ctrl_show}, {NULL, NULL, NULL}, } }; static ssize_t suspend_mode_show(const struct class *class, const struct class_attribute *attr, char *buf) { unsigned int len = 0; int i; if (!deep_mode_init_done) { return scnprintf(buf, PAGE_SIZE, "Don't support deep mode config.\n"); } for (i = 0; suspend_cmds.cmds[i].name; i++) { if (suspend_cmds.cmds[i].show) len += suspend_cmds.cmds[i].show(&suspend_cmds, buf + len); } return len; } static ssize_t suspend_mode_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { char cmd[32]; int ret, i; if (sscanf(buf, "%31s", cmd) != 1) return -EINVAL; for (i = 0; suspend_cmds.cmds[i].name; i++) { if (strcmp(cmd, suspend_cmds.cmds[i].name) == 0) { ret = suspend_cmds.cmds[i].store(&suspend_cmds, buf, count); if (ret) return ret; return count; } } pr_warn("unknown command:%s\n", cmd); return -EINVAL; } static int panic_notify(struct notifier_block *self, unsigned long cmd, void *ptr) { clr_deep_mode(); return NOTIFY_DONE; } static CLASS_ATTR_RW(suspend_mode); static struct notifier_block panic_notifier = { .notifier_call = panic_notify, }; static ssize_t time_out_show(const struct class *class, const struct class_attribute *attr, char *buf) { unsigned int val = 0, len; val = readl_relaxed(debug_reg); len = sprintf(buf, "%d\n", val); return len; } static int sys_time_out; static ssize_t time_out_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { unsigned int time_out; int ret; ret = kstrtouint(buf, 10, &time_out); switch (ret) { case 0: sys_time_out = time_out; writel_relaxed(time_out, debug_reg); break; default: return -EINVAL; } return count; } static CLASS_ATTR_RW(time_out); /* The sys file interface: read state*/ static ssize_t power_event_state_show(const struct class *class, const struct class_attribute *attr, char *buf) { char def[] = "NULL"; char msg[] = "POWER_PRESSED"; unsigned int len = -1; if (power_event_available == 1) { len = snprintf(buf, sizeof(msg) + 1, "%s\n", msg); return len; } len = snprintf(buf, sizeof(def) + 1, "%s\n", def); return len; } static ssize_t power_event_state_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { char cmd[16]; memset(cmd, 0, sizeof(cmd)); size_t copy_len = min(count, sizeof(cmd) - 1); if (!buf) { pr_err("input buf is null.\n"); return -EFAULT; } memcpy(cmd, buf, copy_len); cmd[copy_len] = '\0'; // check input command: "clear" or "reset" if (strncmp(cmd, "clear", 5) == 0 || strncmp(cmd, "reset", 5) == 0) { power_event_available = 0; pr_info("Event flag cleared by user write\n"); return copy_len; } return -EFAULT; } static CLASS_ATTR_RW(power_event_state); /* The sys file interface: read name*/ static ssize_t power_event_name_show(const struct class *class, const struct class_attribute *attr, char *buf) { char def[] = "NULL"; unsigned int len = -1; if (power_event_available == 1) { len = snprintf(buf, sizeof(dev_name_event) + 1, "%s\n", dev_name_event); return len; } len = snprintf(buf, sizeof(def) + 1, "%s\n", def); return len; } static ssize_t power_event_name_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { return -EFAULT; } static CLASS_ATTR_RW(power_event_name); static struct attribute *meson_pm_attrs[] = { &class_attr_suspend_reason.attr, &class_attr_suspend_mode.attr, &class_attr_time_out.attr, &class_attr_deep_suspend.attr, #if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND) &class_attr_early_suspend_trigger.attr, #endif &class_attr_suspend_debug.attr, &class_attr_power_event_state.attr, &class_attr_power_event_name.attr, NULL, }; ATTRIBUTE_GROUPS(meson_pm); static struct class meson_pm_class = { .name = "meson_pm", .class_groups = meson_pm_groups, }; /* power down action */ static void do_custom_power_action(void) { power_event_available = 1; } /* Input Handler: do event */ static void power_handler_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { size_t copy_len; if (type == EV_KEY && code == KEY_POWER && value == 1) { if (handle && handle->dev && handle->dev->name) { memset(dev_name_event, 0, sizeof(dev_name_event)); copy_len = min(strlen(handle->dev->name), sizeof(dev_name_event) - 1); memcpy(dev_name_event, handle->dev->name, copy_len); dev_name_event[copy_len] = '\0'; } pr_info("Power key pressed from device: (%s)\n", dev_name_event); do_custom_power_action(); } } /* connect function: when handler match devices */ static int power_handler_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { struct input_handle *handle; int ret; handle = kzalloc(sizeof(*handle), GFP_KERNEL); if (!handle) return -ENOMEM; handle->dev = dev; handle->handler = handler; handle->name = DRIVER_NAME; ret = input_open_device(handle); if (ret) { pr_err("%s: Failed to open device %s\n", DRIVER_NAME, dev->name); kfree(handle); return ret; } ret = input_register_handle(handle); if (ret) { input_close_device(handle); kfree(handle); return ret; } pr_info("%s: Connected to device: %s\n", DRIVER_NAME, dev->name); return 0; } /* disconnect devices */ static void power_handler_disconnect(struct input_handle *handle) { pr_info("%s: Disconnected from device: %s\n", DRIVER_NAME, handle->dev->name); input_close_device(handle); input_unregister_handle(handle); kfree(handle); } /* match all input devices */ static const struct input_device_id power_handler_ids[] = { { .flags = INPUT_DEVICE_ID_MATCH_KEYBIT, .keybit = { [BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER) }, .driver_info = 1 }, { }, }; MODULE_DEVICE_TABLE(input, power_handler_ids); /* Input Handler */ static struct input_handler power_input_handler = { .event = power_handler_event, .connect = power_handler_connect, .disconnect = power_handler_disconnect, .name = DRIVER_NAME, .id_table = power_handler_ids, }; static int gx_pm_syscore_suspend(void) { if (sys_time_out) writel_relaxed(sys_time_out, debug_reg); return 0; } /*clear wakeup reason*/ static void gx_pm_syscore_shutdown(void) { u32 val; if (exit_reg && is_extd_resume_reason) { val = readl_relaxed(exit_reg); if ((val & 0x7f) == REMOTE_CUS_WAKEUP) { /*Defalut clear netflix wakeup reason*/ val &= ~REMOTE_CUS_WAKEUP; } else if (clear_suspend_reason) { if ((val & 0x7f) == clear_suspend_reason) val &= ~clear_suspend_reason; } writel_relaxed(val, exit_reg); } } static void gx_pm_syscore_resume(void) { sys_time_out = 0; set_resume_method(get_resume_reason()); } static struct syscore_ops gx_pm_syscore_ops = { .suspend = gx_pm_syscore_suspend, .resume = gx_pm_syscore_resume, .shutdown = gx_pm_syscore_shutdown, }; static int __init gx_pm_init_ops(void) { register_syscore_ops(&gx_pm_syscore_ops); return 0; } static int do_aml_reboot_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { switch (event) { case SYS_HALT: break; case SYS_POWER_OFF: set_deep_mode(); break; case SYS_RESTART: break; } return NOTIFY_DONE; } static struct notifier_block aml_reboot_notifier = { .notifier_call = do_aml_reboot_notifier, }; static int meson_pm_probe(struct platform_device *pdev) { unsigned int irq_pwrctrl; int err; power_event_available = 0; if (!of_property_read_u32(pdev->dev.of_node, "irq_pwrctrl", &irq_pwrctrl)) { pwr_ctrl_irq_set(irq_pwrctrl, 1, 0); } if (of_property_read_bool(pdev->dev.of_node, "extend_resume_reason")) is_extd_resume_reason = true; else is_extd_resume_reason = false; if (of_property_read_u32(pdev->dev.of_node, "clear_suspend_reason", &clear_suspend_reason)) { dev_info(&pdev->dev, "clear_suspend_reason not found, using default\n"); clear_suspend_reason = 0; } else { dev_info(&pdev->dev, "clear_suspend_reason found: 0x%x\n", clear_suspend_reason); } deep_mode_init(pdev); debug_reg = of_iomap(pdev->dev.of_node, 0); if (!debug_reg) return -ENOMEM; exit_reg = of_iomap(pdev->dev.of_node, 1); if (!exit_reg) return -ENOMEM; set_resume_method(get_resume_reason()); err = class_register(&meson_pm_class); if (unlikely(err)) return err; // register input handler err = input_register_handler(&power_input_handler); if (err) { pr_err("%s: Failed to register input handler\n", DRIVER_NAME); return err; } gx_pm_init_ops(); #if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND) err = register_pm_notifier(&lgcy_early_suspend_notifier); if (unlikely(err)) return err; #endif if (of_property_read_bool(pdev->dev.of_node, "clr_resume_reason")) is_clr_resume_reason = true; else is_clr_resume_reason = false; err = register_pm_notifier(&clr_suspend_notifier); if (unlikely(err)) return err; err = register_die_notifier(&panic_notifier); if (err != 0) return err; /* Register a call for panic conditions. */ err = atomic_notifier_chain_register(&panic_notifier_list, &panic_notifier); if (err != 0) return err; /* Register a reboot_notifier*/ err = register_reboot_notifier(&aml_reboot_notifier); if (err != 0) return err; if (suspend_debug_flag) set_suspend_debug_flag(suspend_debug_flag); return 0; } static __maybe_unused void meson_pm_remove(struct platform_device *pdev) { if (debug_reg) iounmap(debug_reg); if (exit_reg) iounmap(exit_reg); class_unregister(&meson_pm_class); input_unregister_handler(&power_input_handler); #if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND) lgcy_early_suspend_exit(pdev); #endif } static const struct of_device_id amlogic_pm_dt_match[] = { {.compatible = "amlogic, pm", }, {} }; static void meson_pm_shutdown(struct platform_device *pdev) { u32 val; if (exit_reg && is_clr_resume_reason && is_extd_resume_reason) { val = readl_relaxed(exit_reg); val &= (~0x7f); writel_relaxed(val, exit_reg); } } static int pm_platform_resume(struct device *dev) { return 0; } static int pm_platform_suspend(struct device *dev) { deep_suspend = 1; if (early_suspend_debug) pr_info("%s: set deep_suspend flag as 1\n", __func__); return 0; } static const struct dev_pm_ops pm_ops = { .resume = pm_platform_resume, .suspend = pm_platform_suspend, }; static struct platform_driver meson_pm_driver = { .probe = meson_pm_probe, .driver = { .name = "pm-meson", .owner = THIS_MODULE, .pm = &pm_ops, .of_match_table = amlogic_pm_dt_match, }, .remove = __exit_p(meson_pm_remove), .shutdown = meson_pm_shutdown, }; int __init pm_init(void) { return platform_driver_register(&meson_pm_driver); } void __exit pm_exit(void) { platform_driver_unregister(&meson_pm_driver); }