// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2014 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #define MTK_CPUIDLE_PREPARE (0) #define MTK_CPUIDLE_RESUME (1) typedef int (*mtk_pwr_fn)(int type, struct cpuidle_driver *drv, int index); static mtk_pwr_fn mtk_pwr_conservation; static __always_inline int __mtk_lp_enter(int index) { return CPU_PM_CPU_IDLE_ENTER(arm_cpuidle_suspend, index); } static int mtk_idle_state_enter(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx) { int ret; if (mtk_pwr_conservation) { ret = mtk_pwr_conservation(MTK_CPUIDLE_PREPARE, drv, idx); idx = ret ? 0 : idx; ret = __mtk_lp_enter(idx); mtk_pwr_conservation(MTK_CPUIDLE_RESUME, drv, idx); } else ret = CPU_PM_CPU_IDLE_ENTER(arm_cpuidle_suspend, 0); return ret; } static int mtk_regular_idle_state_enter(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx) { return CPU_PM_CPU_IDLE_ENTER(arm_cpuidle_suspend, idx); } static struct cpuidle_driver mtk_cpuidle_driver __initdata = { .name = "mtk_cpuidle", .owner = THIS_MODULE, .states[0] = { .enter = mtk_regular_idle_state_enter, .exit_latency = 1, .target_residency = 1, .power_usage = UINT_MAX, .name = "wfi", .desc = "wfi", }, .state_count = 1 }; static const struct of_device_id mtk_idle_state_match[] __initconst = { { .compatible = "mediatek,idle-state", .data = mtk_idle_state_enter }, { .compatible = "arm,idle-state", .data = mtk_regular_idle_state_enter }, { }, }; static int mtk_cpuidle_pm_drv_probe(struct platform_device *pdev) { if (IS_ERR_OR_NULL(pdev->dev.platform_data)) goto mtk_probe_fail; mtk_pwr_conservation = *((mtk_pwr_fn *)pdev->dev.platform_data); return 0; mtk_probe_fail: return -ENODATA; } int mtk_cpuidle_pm_drv_remove(struct platform_device *pdev) { mtk_pwr_conservation = NULL; return 0; } static struct platform_driver mtk_cpuidle_pm_driver = { .driver = { .name = "mtk_cpuidle_pm", .owner = THIS_MODULE, }, .probe = mtk_cpuidle_pm_drv_probe, .remove = mtk_cpuidle_pm_drv_remove, }; static int __init mtk_cpuidle_driver_init(void) { int cpu, ret; struct cpuidle_driver *drv; struct cpuidle_device *dev; platform_driver_register(&mtk_cpuidle_pm_driver); for_each_possible_cpu(cpu) { drv = kmemdup(&mtk_cpuidle_driver, sizeof(*drv), GFP_KERNEL); if (!drv) { ret = -ENOMEM; goto out_fail; } drv->cpumask = (struct cpumask *)cpumask_of(cpu); #ifdef CONFIG_DT_IDLE_STATES ret = dt_init_idle_driver(drv, mtk_idle_state_match, 1); if (ret <= 0) { ret = ret ? : -ENODEV; goto out_kfree_drv; } #endif ret = arm_cpuidle_init(cpu); if (ret) { pr_err("CPU %d failed to init idle CPU ops\n", cpu); goto out_kfree_drv; } ret = cpuidle_register(drv, NULL); if (ret) goto out_kfree_drv; } return 0; out_kfree_drv: kfree(drv); out_fail: while (--cpu >= 0) { dev = per_cpu(cpuidle_devices, cpu); drv = cpuidle_get_cpu_driver(dev); cpuidle_unregister(drv); kfree(drv); } return ret; } device_initcall_sync(mtk_cpuidle_driver_init);