// SPDX-License-Identifier: GPL-2.0-only OR MIT /* * Apple SoC PMGR device power state driver * * Copyright The Asahi Linux Contributors */ #include #include #include #include #include #include #include #define APPLE_CLKGEN_PSTATE 0 #define APPLE_CLKGEN_PSTATE_DESIRED GENMASK(3, 0) #define DCS_DEV_PSTATE_MIN_T600X 7 #define SYS_DEV_PSTATE_SUSPEND 1 enum sys_device { DEV_FABRIC, DEV_DCS, DEV_MAX, }; struct apple_pmgr_sys_device { void __iomem *base; u32 active_state; u32 suspend_state; }; struct apple_pmgr_misc_hw { u32 dev_min_ps[DEV_MAX]; }; struct apple_pmgr_misc { struct device *dev; struct apple_pmgr_sys_device devices[DEV_MAX]; }; static void apple_pmgr_sys_dev_set_pstate(struct apple_pmgr_misc *misc, enum sys_device dev, bool active) { u32 pstate; u32 val; if (!misc->devices[dev].base) return; if (active) pstate = misc->devices[dev].active_state; else pstate = misc->devices[dev].suspend_state; dev_dbg(misc->dev, "set %d ps to pstate %d\n", dev, pstate); val = readl_relaxed(misc->devices[dev].base + APPLE_CLKGEN_PSTATE); FIELD_MODIFY(APPLE_CLKGEN_PSTATE_DESIRED, &val, pstate); writel_relaxed(val, misc->devices[dev].base + APPLE_CLKGEN_PSTATE); } static int __maybe_unused apple_pmgr_misc_suspend_noirq(struct device *dev) { struct apple_pmgr_misc *misc = dev_get_drvdata(dev); int i; for (i = 0; i < DEV_MAX; i++) apple_pmgr_sys_dev_set_pstate(misc, i, false); return 0; } static int __maybe_unused apple_pmgr_misc_resume_noirq(struct device *dev) { struct apple_pmgr_misc *misc = dev_get_drvdata(dev); int i; for (i = 0; i < DEV_MAX; i++) apple_pmgr_sys_dev_set_pstate(misc, i, true); return 0; } static int apple_pmgr_init_device(struct apple_pmgr_misc *misc, const struct apple_pmgr_misc_hw *hw, enum sys_device dev, const char *device_name) { void __iomem *base; char name[32]; u32 val; snprintf(name, sizeof(name), "%s-ps", device_name); base = devm_platform_ioremap_resource_byname( to_platform_device(misc->dev), name); if (IS_ERR(base)) return PTR_ERR(base); val = readl_relaxed(base + APPLE_CLKGEN_PSTATE); misc->devices[dev].base = base; misc->devices[dev].active_state = FIELD_GET(APPLE_CLKGEN_PSTATE_DESIRED, val); misc->devices[dev].suspend_state = hw->dev_min_ps[dev]; return 0; } static int apple_pmgr_misc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct apple_pmgr_misc_hw *hw; struct apple_pmgr_misc *misc; int ret; misc = devm_kzalloc(dev, sizeof(*misc), GFP_KERNEL); if (!misc) return -ENOMEM; misc->dev = dev; hw = of_device_get_match_data(dev); if (!hw) return -EINVAL; ret = apple_pmgr_init_device(misc, hw, DEV_FABRIC, "fabric"); if (ret) return ret; ret = apple_pmgr_init_device(misc, hw, DEV_DCS, "dcs"); if (ret) return ret; platform_set_drvdata(pdev, misc); return 0; } static const struct apple_pmgr_misc_hw apple_pmgr_misc_hw_t600x = { .dev_min_ps = { [DEV_FABRIC] = SYS_DEV_PSTATE_SUSPEND, [DEV_DCS] = DCS_DEV_PSTATE_MIN_T600X, }, }; static const struct apple_pmgr_misc_hw apple_pmgr_misc_hw_t602x = { .dev_min_ps = { [DEV_FABRIC] = SYS_DEV_PSTATE_SUSPEND, [DEV_DCS] = SYS_DEV_PSTATE_SUSPEND, }, }; static const struct of_device_id apple_pmgr_misc_of_match[] = { { .compatible = "apple,t6000-pmgr-misc", .data = &apple_pmgr_misc_hw_t600x }, { .compatible = "apple,t6020-pmgr-misc", .data = &apple_pmgr_misc_hw_t602x }, {} }; MODULE_DEVICE_TABLE(of, apple_pmgr_misc_of_match); static const struct dev_pm_ops apple_pmgr_misc_pm_ops = { SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_pmgr_misc_suspend_noirq, apple_pmgr_misc_resume_noirq) }; static struct platform_driver apple_pmgr_misc_driver = { .probe = apple_pmgr_misc_probe, .driver = { .name = "apple-pmgr-misc", .of_match_table = apple_pmgr_misc_of_match, .pm = pm_ptr(&apple_pmgr_misc_pm_ops), }, }; MODULE_AUTHOR("Hector Martin "); MODULE_DESCRIPTION("PMGR misc driver for Apple SoCs"); MODULE_LICENSE("GPL"); module_platform_driver(apple_pmgr_misc_driver);