[U-Boot] [PATCH 070/126] x86: power: Add a PMC uclass
Bin Meng
bmeng.cn at gmail.com
Thu Oct 10 03:10:57 UTC 2019
Hi Simon,
On Wed, Sep 25, 2019 at 10:58 PM Simon Glass <sjg at chromium.org> wrote:
>
> Intel x86 SoCs have a power manager/controller which handles several
> power-related aspects of the platform. Add a uclass for this, with a few
> useful operations.
>
I don't like to create another x86 specific uclass for this power
management controller.
I see two options:
- Create a standard acpi-pmc uclass instead. Almost all registers in
the PMC module follow the ACPI spec, except a few
- Do not abstract this to a uclass level. Use platform codes to do
such. x86 SoC varies from one to another, and it's very hard to do a
one-size-fit-all job.
> Signed-off-by: Simon Glass <sjg at chromium.org>
> ---
>
> drivers/power/Kconfig | 2 +
> drivers/power/power_mgr/Kconfig | 25 +++
> drivers/power/power_mgr/Makefile | 6 +
> drivers/power/power_mgr/power-mgr-uclass.c | 191 +++++++++++++++++++++
> include/dm/uclass-id.h | 1 +
> include/power/power_mgr.h | 177 +++++++++++++++++++
> 6 files changed, 402 insertions(+)
> create mode 100644 drivers/power/power_mgr/Kconfig
> create mode 100644 drivers/power/power_mgr/Makefile
> create mode 100644 drivers/power/power_mgr/power-mgr-uclass.c
> create mode 100644 include/power/power_mgr.h
>
> diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
> index 9495dca33b9..1cb3f6d5e25 100644
> --- a/drivers/power/Kconfig
> +++ b/drivers/power/Kconfig
> @@ -4,6 +4,8 @@ source "drivers/power/domain/Kconfig"
>
> source "drivers/power/pmic/Kconfig"
>
> +source "drivers/power/power_mgr/Kconfig"
> +
> source "drivers/power/regulator/Kconfig"
>
> choice
> diff --git a/drivers/power/power_mgr/Kconfig b/drivers/power/power_mgr/Kconfig
> new file mode 100644
> index 00000000000..2731518462f
> --- /dev/null
> +++ b/drivers/power/power_mgr/Kconfig
> @@ -0,0 +1,25 @@
> +config POWER_MGR
> + bool "Power Manager (x86 PMC) support"
> + help
> + Enable support for an x86-style power-management controller which
> + provides features including checking whether the system started from
> + resume, powering off the system and enabling/disabling the reset
> + mechanism.
> +
> +config SPL_POWER_MGR
> + bool "Power Manager (x86 PMC) support in SPL"
> + default y if POWER_MGR
> + help
> + Enable support for an x86-style power-management controller which
> + provides features including checking whether the system started from
> + resume, powering off the system and enabling/disabling the reset
> + mechanism.
> +
> +config TPL_POWER_MGR
> + bool "Power Manager (x86 PMC) support in TPL"
Why would we need access PMC in both SPL and TPL?
> + default y if POWER_MGR
> + help
> + Enable support for an x86-style power-management controller which
> + provides features including checking whether the system started from
> + resume, powering off the system and enabling/disabling the reset
> + mechanism.
> diff --git a/drivers/power/power_mgr/Makefile b/drivers/power/power_mgr/Makefile
> new file mode 100644
> index 00000000000..87542f5248a
> --- /dev/null
> +++ b/drivers/power/power_mgr/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright 2019 Google LLC
> +
> +obj-$(CONFIG_$(SPL_TPL_)POWER_MGR) += power-mgr-uclass.o
> +
> diff --git a/drivers/power/power_mgr/power-mgr-uclass.c b/drivers/power/power_mgr/power-mgr-uclass.c
> new file mode 100644
> index 00000000000..0d73caf8cf6
> --- /dev/null
> +++ b/drivers/power/power_mgr/power-mgr-uclass.c
> @@ -0,0 +1,191 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +#define LOG_CATEGORY UCLASS_POWER_MGR
> +
> +#include <common.h>
> +#include <acpi_s3.h>
> +#include <dm.h>
> +#include <log.h>
> +#include <asm/io.h>
> +#ifdef CONFIG_CREATE_ARCH_SYMLINK
> +#include <asm/arch/gpio.h>
> +#endif
> +#include <power/power_mgr.h>
> +
> +enum {
> + PM1_STS = 0x00,
> + PM1_EN = 0x02,
> + PM1_CNT = 0x04,
> +
> + GPE0_STS = 0x20,
> + GPE0_EN = 0x30,
> +};
> +
> +struct tco_regs {
> + u32 tco_rld;
> + u32 tco_sts;
> + u32 tco1_cnt;
> + u32 tco_tmr;
> +};
> +
> +enum {
> + TCO_STS_TIMEOUT = 1 << 3,
> + TCO_STS_SECOND_TO_STS = 1 << 17,
> + TCO1_CNT_HLT = 1 << 11,
> +};
> +
> +static void pmc_fill_pm_reg_info(struct udevice *dev)
> +{
> + struct power_mgr_upriv *upriv = dev_get_uclass_priv(dev);
> + int i;
> +
> + upriv->pm1_sts = inw(upriv->acpi_base + PM1_STS);
> + upriv->pm1_en = inw(upriv->acpi_base + PM1_EN);
> + upriv->pm1_cnt = inw(upriv->acpi_base + PM1_CNT);
> +
> + log_debug("pm1_sts: %04x pm1_en: %04x pm1_cnt: %08x\n",
> + upriv->pm1_sts, upriv->pm1_en, upriv->pm1_cnt);
> +
> + for (i = 0; i < GPE0_REG_MAX; i++) {
> + upriv->gpe0_sts[i] = inl(upriv->acpi_base + GPE0_STS + i * 4);
> + upriv->gpe0_en[i] = inl(upriv->acpi_base + GPE0_EN + i * 4);
> + log_debug("gpe0_sts[%d]: %08x gpe0_en[%d]: %08x\n", i,
> + upriv->gpe0_sts[i], i, upriv->gpe0_en[i]);
> + }
> +}
> +
> +int pmc_disable_tco_base(ulong tco_base)
> +{
TCO is not defined in the ACPI spec. This is APL specific. It looks
this has something to do with the hardware watchdog?
Anyway, if we want to put such in an acpi-pmc uclass driver, we should
mark such op as optional.
> + struct tco_regs *regs = (struct tco_regs *)tco_base;
> +
> + debug("tco_base %lx = %x\n", (ulong)®s->tco1_cnt, TCO1_CNT_HLT);
> + setio_32(®s->tco1_cnt, TCO1_CNT_HLT);
> +
> + return 0;
> +}
> +
> +int pmc_init(struct udevice *dev)
> +{
> + const struct power_mgr_ops *ops = power_mgr_get_ops(dev);
> + int ret;
> +
> + pmc_fill_pm_reg_info(dev);
> + if (!ops->init)
> + return -ENOSYS;
> +
> + ret = ops->init(dev);
> + if (ret)
> + return log_msg_ret("Failed to init pmc", ret);
> +
> +#ifdef DEBUG
> + pmc_dump_info(dev);
> +#endif
> +
> + return 0;
> +}
> +
> +int pmc_prev_sleep_state(struct udevice *dev)
> +{
> + struct power_mgr_upriv *upriv = dev_get_uclass_priv(dev);
> + const struct power_mgr_ops *ops = power_mgr_get_ops(dev);
> + int prev_sleep_state = ACPI_S0; /* Default to S0 */
> +
> + if (upriv->pm1_sts & WAK_STS) {
> + switch (acpi_sleep_from_pm1(upriv->pm1_cnt)) {
> + case ACPI_S3:
> + if (IS_ENABLED(HAVE_ACPI_RESUME))
> + prev_sleep_state = ACPI_S3;
> + break;
> + case ACPI_S5:
> + prev_sleep_state = ACPI_S5;
> + break;
> + default:
> + break;
> + }
> +
> + /* Clear SLP_TYP */
> + outl(upriv->pm1_cnt & ~SLP_TYP, upriv->acpi_base + PM1_CNT);
> + }
> +
> + if (!ops->prev_sleep_state)
> + return prev_sleep_state;
> +
> + return ops->prev_sleep_state(dev, prev_sleep_state);
> +}
> +
> +int pmc_disable_tco(struct udevice *dev)
> +{
> + const struct power_mgr_ops *ops = power_mgr_get_ops(dev);
> +
> + pmc_fill_pm_reg_info(dev);
> + if (!ops->disable_tco)
> + return -ENOSYS;
> +
> + return ops->disable_tco(dev);
> +}
> +
> +int pmc_global_reset_set_enable(struct udevice *dev, bool enable)
> +{
> + const struct power_mgr_ops *ops = power_mgr_get_ops(dev);
> +
> + if (!ops->global_reset_set_enable)
> + return -ENOSYS;
> +
> + return ops->global_reset_set_enable(dev, enable);
> +}
> +
> +void pmc_dump_info(struct udevice *dev)
> +{
> + struct power_mgr_upriv *upriv = dev_get_uclass_priv(dev);
> + int i;
> +
> + printf("Device: %s\n", dev->name);
> + printf("ACPI base %x, pmc_bar0 %p, pmc_bar2 %p, gpe_cfg %p\n",
> + upriv->acpi_base, upriv->pmc_bar0, upriv->pmc_bar2,
> + upriv->gpe_cfg);
> + printf("pm1_sts: %04x pm1_en: %04x pm1_cnt: %08x\n",
> + upriv->pm1_sts, upriv->pm1_en, upriv->pm1_cnt);
> +
> + for (i = 0; i < GPE0_REG_MAX; i++) {
> + printf("gpe0_sts[%d]: %08x gpe0_en[%d]: %08x\n", i,
> + upriv->gpe0_sts[i], i, upriv->gpe0_en[i]);
> + }
> +
> + printf("prsts: %08x\n", upriv->prsts);
> + printf("tco_sts: %04x %04x\n", upriv->tco1_sts, upriv->tco2_sts);
> + printf("gen_pmcon1: %08x gen_pmcon2: %08x gen_pmcon3: %08x\n",
> + upriv->gen_pmcon1, upriv->gen_pmcon2, upriv->gen_pmcon3);
> +}
> +
> +int pmc_ofdata_to_uc_platdata(struct udevice *dev)
> +{
> + struct power_mgr_upriv *upriv = dev_get_uclass_priv(dev);
> + int ret;
> +
> + ret = dev_read_u32(dev, "gpe0-dwx-mask", &upriv->gpe0_dwx_mask);
> + if (ret)
> + return log_msg_ret("no gpe0-dwx-mask", ret);
> + ret = dev_read_u32(dev, "gpe0-dwx-shift-base",
> + &upriv->gpe0_dwx_shift_base);
> + if (ret)
> + return log_msg_ret("no gpe0-dwx-shift-base", ret);
> + ret = dev_read_u32(dev, "gpe0-sts", &upriv->gpe0_sts_reg);
> + if (ret)
> + return log_msg_ret("no gpe0-sts", ret);
> + upriv->gpe0_sts_reg += upriv->acpi_base;
> + ret = dev_read_u32(dev, "gpe0-en", &upriv->gpe0_en_reg);
> + if (ret)
> + return log_msg_ret("no gpe0-en", ret);
> + upriv->gpe0_en_reg += upriv->acpi_base;
> +
> + return 0;
> +}
> +
> +UCLASS_DRIVER(power_mgr) = {
> + .id = UCLASS_POWER_MGR,
> + .name = "power-mgr",
> + .per_device_auto_alloc_size = sizeof(struct power_mgr_upriv),
> +};
> diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
> index f431f3bf294..907fe257ad1 100644
> --- a/include/dm/uclass-id.h
> +++ b/include/dm/uclass-id.h
> @@ -79,6 +79,7 @@ enum uclass_id {
> UCLASS_PINCTRL, /* Pinctrl (pin muxing/configuration) device */
> UCLASS_PMIC, /* PMIC I/O device */
> UCLASS_POWER_DOMAIN, /* (SoC) Power domains */
> + UCLASS_POWER_MGR, /* (x86) Power managerment controller (PMC) */
> UCLASS_PWM, /* Pulse-width modulator */
> UCLASS_PWRSEQ, /* Power sequence device */
> UCLASS_RAM, /* RAM controller */
> diff --git a/include/power/power_mgr.h b/include/power/power_mgr.h
> new file mode 100644
> index 00000000000..7f6cdc5cd62
> --- /dev/null
> +++ b/include/power/power_mgr.h
> @@ -0,0 +1,177 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +#ifndef __POWER_MGR_H
> +#define __POWER_MGR_H
> +
> +enum {
> + GPE0_REG_MAX = 4,
> +};
> +
> +/**
> + * struct power_mgr_upriv - holds common data for the x86 PMC
> + *
> + * @pmc_bar0: Base address 0 of PMC
> + * @pmc_bar1: Base address 2 of PMC
> + * @acpi_base: Base address of ACPI block
> + * @pm1_sts: PM1 status
> + * @pm1_en: PM1 enable
> + * @pm1_cnt: PM1 control
> + * @gpe_cfg: Address of GPE_CFG register
> + * @gpe0_dwx_mask: Mask to use for each GPE0 (typically 7 or 0xf)
> + * @gpe0_dwx_shift_base: Base shift value to use for GPE0 (0 or 4)
> + * @gpe0_sts_req: GPE0 status register offset
> + * @gpe0_en_req: GPE0 enable register offset
> + * @gpe0_sts: GPE0 status values
> + * @gpe0_en: GPE0 enable values
> + * @gpe0_dw: GPE0 DW values
> + * @gpe0_count: Number of GPE0 registers
> + * @tco1_sts: TCO1 status
> + * @tco2_sts: TCO2 status
> + * @prsts: Power and reset status
> + * @gen_pmcon1: General power mgmt configuration 1
> + * @gen_pmcon2: General power mgmt configuration 2
> + * @gen_pmcon3: General power mgmt configuration 3
> + */
> +struct power_mgr_upriv {
> + void *pmc_bar0;
> + void *pmc_bar2;
> + u32 acpi_base;
> + u16 pm1_sts;
> + u16 pm1_en;
> + u32 pm1_cnt;
> + u32 *gpe_cfg;
> + u32 gpe0_dwx_mask;
> + u32 gpe0_dwx_shift_base;
> + u32 gpe0_sts_reg;
> + u32 gpe0_en_reg;
> + u32 gpe0_sts[GPE0_REG_MAX];
> + u32 gpe0_en[GPE0_REG_MAX];
> + u32 gpe0_dw[GPE0_REG_MAX];
> + int gpe0_count;
> + u16 tco1_sts;
> + u16 tco2_sts;
> + u32 prsts;
> + u32 gen_pmcon1;
> + u32 gen_pmcon2;
> + u32 gen_pmcon3;
> +};
> +
> +struct power_mgr_ops {
> + /**
> + * init() - Set up the PMC for use
> + *
> + * This reads the current state of the PMC. Most of the state is read
> + * automatically by the uclass since it is common.
> + *
> + * @dev: PMC device to use
> + * @return 0 if OK, -ve on error
> + */
> + int (*init)(struct udevice *dev);
> +
> + /**
> + * prev_sleep_state() - Get the previous sleep state (optional)
> + *
> + * This reads various state registers and returns the sleep state from
> + * which the system woke. If this method is not provided, the uclass
> + * will return a calculated value.
> + *
> + * @dev: PMC device to use
> + * @prev_sleep_state: Previous sleep state as calculated by the uclass.
> + * The method can use this as the return value or calculate its
> + * own.
> + *
> + * @return enum acpi_sleep_state indicating the previous sleep state
> + * (ACPI_S0, ACPI_S3 or ACPI_S5), or -ve on error
> + */
> + int (*prev_sleep_state)(struct udevice *dev, int prev_sleep_state);
> +
> + /**
> + * disable_tco() - Disable the timer/counter
> + *
> + * Disables the timer/counter in the PMC
> + *
> + * @dev: PMC device to use
> + * @return 0
> + */
> + int (*disable_tco)(struct udevice *dev);
> +
> + /**
> + * global_reset_set_enable() - Enable/Disable global reset
> + *
> + * Enable or disable global reset. If global reset is enabled, both hard
> + * reset and soft reset will trigger global reset, where both host and
> + * TXE are reset. This is cleared on cold boot, hard reset, soft reset
> + * and Sx.
> + *
> + * @dev: PMC device to use
> + * @enable: true to enable global reset, false to disable
> + * @return 0
> + */
> + int (*global_reset_set_enable)(struct udevice *dev, bool enable);
> +};
> +
> +#define power_mgr_get_ops(dev) ((struct power_mgr_ops *)(dev)->driver->ops)
> +
> +/**
> + * init() - Set up the PMC for use
> + *
> + * This reads the current state of the PMC. This reads in the common registers,
> + * then calls the device's init() method to read the SoC-specific registers.
> + *
> + * @return 0 if OK, -ve on error
> + */
> +int pmc_init(struct udevice *dev);
> +
> +/**
> + * pmc_prev_sleep_state() - Get the previous sleep state
> + *
> + * This reads various state registers and returns the sleep state from
> + * which the system woke.
> + *
> + * @return enum acpi_sleep_state indicating the previous sleep state
> + * (ACPI_S0, ACPI_S3 or ACPI_S5), or -ve on error
> + */
> +int pmc_prev_sleep_state(struct udevice *dev);
> +
> +/**
> + * pmc_disable_tco() - Disable the timer/counter
> + *
> + * Disables the timer/counter in the PMC
> + *
> + * @dev: PMC device to use
> + * @return 0
> + */
> +int pmc_disable_tco(struct udevice *dev);
> +
> +/**
> + * pmc_global_reset_set_enable() - Enable/Disable global reset
> + *
> + * Enable or disable global reset. If global reset is enabled, both hard
> + * reset and soft reset will trigger global reset, where both host and
> + * TXE are reset. This is cleared on cold boot, hard reset, soft reset
> + * and Sx.
> + *
> + * @dev: PMC device to use
> + * @enable: true to enable global reset, false to disable
> + * @return 0
> + */
> +int pmc_global_reset_set_enable(struct udevice *dev, bool enable);
> +
> +int pmc_ofdata_to_uc_platdata(struct udevice *dev);
> +
> +int pmc_disable_tco_base(ulong tco_base);
> +
> +void pmc_dump_info(struct udevice *dev);
> +
> +/**
> + * pmc_gpe_init() - Set up general-purpose events
> + *
> + * @dev: PMC device
> + * @return 0 if OK, -ve on error
> + */
> +int pmc_gpe_init(struct udevice *dev);
> +
> +#endif
Regards,
Bin
More information about the U-Boot
mailing list