| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (c) 2017 Intel Corporation | |
| */ | |
| #include <dm.h> | |
| #include <regmap.h> | |
| #include <syscon.h> | |
| #include <asm/cpu.h> | |
| #include <asm/pmu.h> | |
| #include <linux/delay.h> | |
| #include <linux/errno.h> | |
| #include <linux/io.h> | |
| /* Registers */ | |
| struct pmu_regs { | |
| u32 sts; | |
| u32 cmd; | |
| u32 ics; | |
| u32 reserved; | |
| u32 wkc[4]; | |
| u32 wks[4]; | |
| u32 ssc[4]; | |
| u32 sss[4]; | |
| }; | |
| /* Bits in PMU_REGS_STS */ | |
| #define PMU_REGS_STS_BUSY (1 << 8) | |
| struct pmu_mid { | |
| struct pmu_regs *regs; | |
| }; | |
| static int pmu_read_status(struct pmu_regs *regs) | |
| { | |
| int retry = 500000; | |
| u32 val; | |
| do { | |
| val = readl(®s->sts); | |
| if (!(val & PMU_REGS_STS_BUSY)) | |
| return 0; | |
| udelay(1); | |
| } while (--retry); | |
| printf("WARNING: PMU still busy\n"); | |
| return -EBUSY; | |
| } | |
| static int pmu_power_lss(struct pmu_regs *regs, unsigned int lss, bool on) | |
| { | |
| unsigned int offset = (lss * 2) / 32; | |
| unsigned int shift = (lss * 2) % 32; | |
| u32 ssc; | |
| int ret; | |
| /* Check PMU status */ | |
| ret = pmu_read_status(regs); | |
| if (ret) | |
| return ret; | |
| /* Read PMU values */ | |
| ssc = readl(®s->sss[offset]); | |
| /* Modify PMU values */ | |
| if (on) | |
| ssc &= ~(0x3 << shift); /* D0 */ | |
| else | |
| ssc |= 0x3 << shift; /* D3hot */ | |
| /* Write modified PMU values */ | |
| writel(ssc, ®s->ssc[offset]); | |
| /* Update modified PMU values */ | |
| writel(0x00002201, ®s->cmd); | |
| /* Check PMU status */ | |
| return pmu_read_status(regs); | |
| } | |
| int pmu_turn_power(unsigned int lss, bool on) | |
| { | |
| struct pmu_mid *pmu; | |
| struct udevice *dev; | |
| int ret; | |
| ret = syscon_get_by_driver_data(X86_SYSCON_PMU, &dev); | |
| if (ret) | |
| return ret; | |
| pmu = dev_get_priv(dev); | |
| return pmu_power_lss(pmu->regs, lss, on); | |
| } | |
| static int pmu_mid_probe(struct udevice *dev) | |
| { | |
| struct pmu_mid *pmu = dev_get_priv(dev); | |
| pmu->regs = syscon_get_first_range(X86_SYSCON_PMU); | |
| return 0; | |
| } | |
| static const struct udevice_id pmu_mid_match[] = { | |
| { .compatible = "intel,pmu-mid", .data = X86_SYSCON_PMU }, | |
| { /* sentinel */ } | |
| }; | |
| U_BOOT_DRIVER(intel_mid_pmu) = { | |
| .name = "pmu_mid", | |
| .id = UCLASS_SYSCON, | |
| .of_match = pmu_mid_match, | |
| .probe = pmu_mid_probe, | |
| .priv_auto = sizeof(struct pmu_mid), | |
| }; |