| // SPDX-License-Identifier: GPL-2.0-only OR MIT | |
| /* | |
| * Apple SoC PMGR device power state driver | |
| * | |
| * Copyright The Asahi Linux Contributors | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/bitops.h> | |
| #include <linux/err.h> | |
| #include <linux/io.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #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 <marcan@marcan.st>"); | |
| MODULE_DESCRIPTION("PMGR misc driver for Apple SoCs"); | |
| MODULE_LICENSE("GPL"); | |
| module_platform_driver(apple_pmgr_misc_driver); |