diff mbox series

[U-Boot,v3,033/108] x86: power: Add an ACPI PMC uclass

Message ID 20191021033913.220758-33-sjg@chromium.org
State Superseded
Delegated to: Bin Meng
Headers show
Series x86: Add initial support for apollolake | expand

Commit Message

Simon Glass Oct. 21, 2019, 3:37 a.m. UTC
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.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

Changes in v3:
- Rename power-mgr uclass to acpi-pmc

Changes in v2: None

 drivers/power/Kconfig                    |   2 +
 drivers/power/acpi_pmc/Kconfig           |  25 +++
 drivers/power/acpi_pmc/Makefile          |   5 +
 drivers/power/acpi_pmc/acpi-pmc-uclass.c | 191 +++++++++++++++++++++++
 include/dm/uclass-id.h                   |   1 +
 include/power/acpi_pmc.h                 | 185 ++++++++++++++++++++++
 6 files changed, 409 insertions(+)
 create mode 100644 drivers/power/acpi_pmc/Kconfig
 create mode 100644 drivers/power/acpi_pmc/Makefile
 create mode 100644 drivers/power/acpi_pmc/acpi-pmc-uclass.c
 create mode 100644 include/power/acpi_pmc.h

Comments

Bin Meng Nov. 2, 2019, 5:52 a.m. UTC | #1
On Mon, Oct 21, 2019 at 11:40 AM Simon Glass <sjg@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.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
>
> Changes in v3:
> - Rename power-mgr uclass to acpi-pmc
>
> Changes in v2: None
>
>  drivers/power/Kconfig                    |   2 +
>  drivers/power/acpi_pmc/Kconfig           |  25 +++
>  drivers/power/acpi_pmc/Makefile          |   5 +
>  drivers/power/acpi_pmc/acpi-pmc-uclass.c | 191 +++++++++++++++++++++++
>  include/dm/uclass-id.h                   |   1 +
>  include/power/acpi_pmc.h                 | 185 ++++++++++++++++++++++
>  6 files changed, 409 insertions(+)
>  create mode 100644 drivers/power/acpi_pmc/Kconfig
>  create mode 100644 drivers/power/acpi_pmc/Makefile
>  create mode 100644 drivers/power/acpi_pmc/acpi-pmc-uclass.c
>  create mode 100644 include/power/acpi_pmc.h
>
> diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
> index 9495dca33b9..4b4f6d55568 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/acpi_pmc/Kconfig"

nits: this should be inserted in the alphabetical order

> +
>  source "drivers/power/regulator/Kconfig"
>
>  choice
> diff --git a/drivers/power/acpi_pmc/Kconfig b/drivers/power/acpi_pmc/Kconfig
> new file mode 100644
> index 00000000000..472a61a9fd1
> --- /dev/null
> +++ b/drivers/power/acpi_pmc/Kconfig
> @@ -0,0 +1,25 @@
> +config ACPI_PMC
> +       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_ACPI_PMC
> +       bool "Power Manager (x86 PMC) support in SPL"
> +       default y if ACPI_PMC
> +       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_ACPI_PMC
> +       bool "Power Manager (x86 PMC) support in TPL"
> +       default y if ACPI_PMC
> +       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/acpi_pmc/Makefile b/drivers/power/acpi_pmc/Makefile
> new file mode 100644
> index 00000000000..7c1ba05c9f3
> --- /dev/null
> +++ b/drivers/power/acpi_pmc/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright 2019 Google LLC
> +
> +obj-$(CONFIG_$(SPL_TPL_)ACPI_PMC) += acpi-pmc-uclass.o
> diff --git a/drivers/power/acpi_pmc/acpi-pmc-uclass.c b/drivers/power/acpi_pmc/acpi-pmc-uclass.c
> new file mode 100644
> index 00000000000..8800afecf10
> --- /dev/null
> +++ b/drivers/power/acpi_pmc/acpi-pmc-uclass.c
> @@ -0,0 +1,191 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +#define LOG_CATEGORY UCLASS_ACPI_PMC
> +
> +#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/acpi_pmc.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 acpi_pmc_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)
> +{
> +       struct tco_regs *regs = (struct tco_regs *)tco_base;
> +
> +       debug("tco_base %lx = %x\n", (ulong)&regs->tco1_cnt, TCO1_CNT_HLT);
> +       setio_32(&regs->tco1_cnt, TCO1_CNT_HLT);
> +
> +       return 0;
> +}
> +
> +int pmc_init(struct udevice *dev)
> +{
> +       const struct acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_upriv *upriv = dev_get_uclass_priv(dev);
> +       const struct acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_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 acpi_pmc_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(acpi_pmc) = {
> +       .id             = UCLASS_ACPI_PMC,
> +       .name           = "power-mgr",
> +       .per_device_auto_alloc_size     = sizeof(struct acpi_pmc_upriv),
> +};
> diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
> index f7f323752c2..d19ad1a12e6 100644
> --- a/include/dm/uclass-id.h
> +++ b/include/dm/uclass-id.h
> @@ -28,6 +28,7 @@ enum uclass_id {
>         UCLASS_AXI_EMUL,        /* sandbox AXI bus device emulator */
>
>         /* U-Boot uclasses start here - in alphabetical order */
> +       UCLASS_ACPI_PMC,        /* (x86) Power-management controller (PMC) */
>         UCLASS_ADC,             /* Analog-to-digital converter */
>         UCLASS_AHCI,            /* SATA disk controller */
>         UCLASS_AUDIO_CODEC,     /* Audio codec with control and data path */
> diff --git a/include/power/acpi_pmc.h b/include/power/acpi_pmc.h
> new file mode 100644
> index 00000000000..1f50c23f5f8
> --- /dev/null
> +++ b/include/power/acpi_pmc.h
> @@ -0,0 +1,185 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +#ifndef __ACPI_PMC_H
> +#define __ACPI_PMC_H
> +
> +enum {
> +       GPE0_REG_MAX    = 4,
> +};
> +
> +/**
> + * struct acpi_pmc_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 acpi_pmc_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 acpi_pmc_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.
> +        *
> +        * This is optional.
> +        *
> +        * @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.
> +        *
> +        * This is optional.
> +        *
> +        * @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
> +        *
> +        * This is optional.
> +        *
> +        * @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.
> +        *
> +        * This is optional.
> +        *
> +        * @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 acpi_pmc_get_ops(dev)  ((struct acpi_pmc_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
> --

Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Bin Meng Nov. 4, 2019, 8:02 a.m. UTC | #2
Hi Simon,

On Mon, Oct 21, 2019 at 11:40 AM Simon Glass <sjg@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.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
>
> Changes in v3:
> - Rename power-mgr uclass to acpi-pmc
>
> Changes in v2: None
>
>  drivers/power/Kconfig                    |   2 +
>  drivers/power/acpi_pmc/Kconfig           |  25 +++
>  drivers/power/acpi_pmc/Makefile          |   5 +
>  drivers/power/acpi_pmc/acpi-pmc-uclass.c | 191 +++++++++++++++++++++++
>  include/dm/uclass-id.h                   |   1 +
>  include/power/acpi_pmc.h                 | 185 ++++++++++++++++++++++
>  6 files changed, 409 insertions(+)
>  create mode 100644 drivers/power/acpi_pmc/Kconfig
>  create mode 100644 drivers/power/acpi_pmc/Makefile
>  create mode 100644 drivers/power/acpi_pmc/acpi-pmc-uclass.c
>  create mode 100644 include/power/acpi_pmc.h
>
> diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
> index 9495dca33b9..4b4f6d55568 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/acpi_pmc/Kconfig"
> +
>  source "drivers/power/regulator/Kconfig"
>
>  choice
> diff --git a/drivers/power/acpi_pmc/Kconfig b/drivers/power/acpi_pmc/Kconfig
> new file mode 100644
> index 00000000000..472a61a9fd1
> --- /dev/null
> +++ b/drivers/power/acpi_pmc/Kconfig
> @@ -0,0 +1,25 @@
> +config ACPI_PMC
> +       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_ACPI_PMC
> +       bool "Power Manager (x86 PMC) support in SPL"
> +       default y if ACPI_PMC
> +       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_ACPI_PMC
> +       bool "Power Manager (x86 PMC) support in TPL"
> +       default y if ACPI_PMC
> +       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/acpi_pmc/Makefile b/drivers/power/acpi_pmc/Makefile
> new file mode 100644
> index 00000000000..7c1ba05c9f3
> --- /dev/null
> +++ b/drivers/power/acpi_pmc/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright 2019 Google LLC
> +
> +obj-$(CONFIG_$(SPL_TPL_)ACPI_PMC) += acpi-pmc-uclass.o
> diff --git a/drivers/power/acpi_pmc/acpi-pmc-uclass.c b/drivers/power/acpi_pmc/acpi-pmc-uclass.c
> new file mode 100644
> index 00000000000..8800afecf10
> --- /dev/null
> +++ b/drivers/power/acpi_pmc/acpi-pmc-uclass.c
> @@ -0,0 +1,191 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +#define LOG_CATEGORY UCLASS_ACPI_PMC
> +
> +#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/acpi_pmc.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 acpi_pmc_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)
> +{
> +       struct tco_regs *regs = (struct tco_regs *)tco_base;
> +
> +       debug("tco_base %lx = %x\n", (ulong)&regs->tco1_cnt, TCO1_CNT_HLT);
> +       setio_32(&regs->tco1_cnt, TCO1_CNT_HLT);
> +
> +       return 0;
> +}
> +
> +int pmc_init(struct udevice *dev)
> +{
> +       const struct acpi_pmc_ops *ops = acpi_pmc_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;
> +}

I wonder shouldn't this be covered by the probe() method?

> +
> +int pmc_prev_sleep_state(struct udevice *dev)
> +{
> +       struct acpi_pmc_upriv *upriv = dev_get_uclass_priv(dev);
> +       const struct acpi_pmc_ops *ops = acpi_pmc_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);
> +}
> +

[snip]

Regards,
Bin
Simon Glass Nov. 21, 2019, 1:50 p.m. UTC | #3
Hi Bin,

On Mon, 4 Nov 2019 at 00:03, Bin Meng <bmeng.cn@gmail.com> wrote:
>
> Hi Simon,
>
> On Mon, Oct 21, 2019 at 11:40 AM Simon Glass <sjg@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.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> > Changes in v3:
> > - Rename power-mgr uclass to acpi-pmc
> >
> > Changes in v2: None
> >
> >  drivers/power/Kconfig                    |   2 +
> >  drivers/power/acpi_pmc/Kconfig           |  25 +++
> >  drivers/power/acpi_pmc/Makefile          |   5 +
> >  drivers/power/acpi_pmc/acpi-pmc-uclass.c | 191 +++++++++++++++++++++++
> >  include/dm/uclass-id.h                   |   1 +
> >  include/power/acpi_pmc.h                 | 185 ++++++++++++++++++++++
> >  6 files changed, 409 insertions(+)
> >  create mode 100644 drivers/power/acpi_pmc/Kconfig
> >  create mode 100644 drivers/power/acpi_pmc/Makefile
> >  create mode 100644 drivers/power/acpi_pmc/acpi-pmc-uclass.c
> >  create mode 100644 include/power/acpi_pmc.h
> >
[..]

> > +int pmc_init(struct udevice *dev)
> > +{
> > +       const struct acpi_pmc_ops *ops = acpi_pmc_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;
> > +}
>
> I wonder shouldn't this be covered by the probe() method?

This is called at present from SPL to read information that is not
needed in TPL. In effect the PMC is mostly just used for info in TPL,
so we don't want to

We could have a check in the probe() function and do different things
in TPL, but at the moment I feel that it is worth having an 'init'
function which actually sets things up when needed.

[..]

>
> [snip]
>
> Regards,
> Bin
diff mbox series

Patch

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 9495dca33b9..4b4f6d55568 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/acpi_pmc/Kconfig"
+
 source "drivers/power/regulator/Kconfig"
 
 choice
diff --git a/drivers/power/acpi_pmc/Kconfig b/drivers/power/acpi_pmc/Kconfig
new file mode 100644
index 00000000000..472a61a9fd1
--- /dev/null
+++ b/drivers/power/acpi_pmc/Kconfig
@@ -0,0 +1,25 @@ 
+config ACPI_PMC
+	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_ACPI_PMC
+	bool "Power Manager (x86 PMC) support in SPL"
+	default y if ACPI_PMC
+	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_ACPI_PMC
+	bool "Power Manager (x86 PMC) support in TPL"
+	default y if ACPI_PMC
+	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/acpi_pmc/Makefile b/drivers/power/acpi_pmc/Makefile
new file mode 100644
index 00000000000..7c1ba05c9f3
--- /dev/null
+++ b/drivers/power/acpi_pmc/Makefile
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2019 Google LLC
+
+obj-$(CONFIG_$(SPL_TPL_)ACPI_PMC) += acpi-pmc-uclass.o
diff --git a/drivers/power/acpi_pmc/acpi-pmc-uclass.c b/drivers/power/acpi_pmc/acpi-pmc-uclass.c
new file mode 100644
index 00000000000..8800afecf10
--- /dev/null
+++ b/drivers/power/acpi_pmc/acpi-pmc-uclass.c
@@ -0,0 +1,191 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#define LOG_CATEGORY UCLASS_ACPI_PMC
+
+#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/acpi_pmc.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 acpi_pmc_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)
+{
+	struct tco_regs *regs = (struct tco_regs *)tco_base;
+
+	debug("tco_base %lx = %x\n", (ulong)&regs->tco1_cnt, TCO1_CNT_HLT);
+	setio_32(&regs->tco1_cnt, TCO1_CNT_HLT);
+
+	return 0;
+}
+
+int pmc_init(struct udevice *dev)
+{
+	const struct acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_upriv *upriv = dev_get_uclass_priv(dev);
+	const struct acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_ops *ops = acpi_pmc_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 acpi_pmc_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 acpi_pmc_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(acpi_pmc) = {
+	.id		= UCLASS_ACPI_PMC,
+	.name		= "power-mgr",
+	.per_device_auto_alloc_size	= sizeof(struct acpi_pmc_upriv),
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index f7f323752c2..d19ad1a12e6 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -28,6 +28,7 @@  enum uclass_id {
 	UCLASS_AXI_EMUL,	/* sandbox AXI bus device emulator */
 
 	/* U-Boot uclasses start here - in alphabetical order */
+	UCLASS_ACPI_PMC,	/* (x86) Power-management controller (PMC) */
 	UCLASS_ADC,		/* Analog-to-digital converter */
 	UCLASS_AHCI,		/* SATA disk controller */
 	UCLASS_AUDIO_CODEC,	/* Audio codec with control and data path */
diff --git a/include/power/acpi_pmc.h b/include/power/acpi_pmc.h
new file mode 100644
index 00000000000..1f50c23f5f8
--- /dev/null
+++ b/include/power/acpi_pmc.h
@@ -0,0 +1,185 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#ifndef __ACPI_PMC_H
+#define __ACPI_PMC_H
+
+enum {
+	GPE0_REG_MAX	= 4,
+};
+
+/**
+ * struct acpi_pmc_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 acpi_pmc_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 acpi_pmc_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.
+	 *
+	 * This is optional.
+	 *
+	 * @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.
+	 *
+	 * This is optional.
+	 *
+	 * @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
+	 *
+	 * This is optional.
+	 *
+	 * @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.
+	 *
+	 * This is optional.
+	 *
+	 * @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 acpi_pmc_get_ops(dev)	((struct acpi_pmc_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