x86: sandbox: Add a PMC emulator and test
Add a simple PMC for sandbox to permit tests to run.
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
diff --git a/arch/Kconfig b/arch/Kconfig
index 141e48b..e1f1fcd 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -133,6 +133,9 @@
imply PHYLIB
imply DM_MDIO
imply DM_MDIO_MUX
+ imply ACPI_PMC
+ imply ACPI_PMC_SANDBOX
+ imply CMD_PMC
config SH
bool "SuperH architecture"
diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi
index f09bc70..7bf144f 100644
--- a/arch/sandbox/dts/sandbox.dtsi
+++ b/arch/sandbox/dts/sandbox.dtsi
@@ -100,6 +100,17 @@
};
pci-controller {
+ pci@1e,0 {
+ compatible = "sandbox,pmc";
+ reg = <0xf000 0 0 0 0>;
+ sandbox,emul = <&pmc_emul>;
+ gpe0-dwx-mask = <0xf>;
+ gpe0-dwx-shift-base = <4>;
+ gpe0-dw = <6 7 9>;
+ gpe0-sts = <0x20>;
+ gpe0-en = <0x30>;
+ };
+
pci@1f,0 {
compatible = "pci-generic";
reg = <0xf800 0 0 0 0>;
@@ -109,6 +120,9 @@
emul {
compatible = "sandbox,pci-emul-parent";
+ pmc_emul: emul@1e,0 {
+ compatible = "sandbox,pmc-emul";
+ };
swap_case_emul: emul@1f,0 {
compatible = "sandbox,swap-case";
};
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index fdb08f2..9990567 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -471,6 +471,17 @@
0x01000810 0 0 0 0>;
sandbox,emul = <&swap_case_emul0_1>;
};
+ pci@1e,0 {
+ compatible = "sandbox,pmc";
+ reg = <0xf000 0 0 0 0>;
+ sandbox,emul = <&pmc_emul1e>;
+ acpi-base = <0x400>;
+ gpe0-dwx-mask = <0xf>;
+ gpe0-dwx-shift-base = <4>;
+ gpe0-dw = <6 7 9>;
+ gpe0-sts = <0x20>;
+ gpe0-en = <0x30>;
+ };
pci@1f,0 {
compatible = "pci-generic";
/* reg 0 is at 0x10, using FDT_PCI_SPACE_IO */
@@ -491,6 +502,9 @@
swap_case_emul0_1f: emul0@1f,0 {
compatible = "sandbox,swap-case";
};
+ pmc_emul1e: emul@1e,0 {
+ compatible = "sandbox,pmc-emul";
+ };
};
pci1: pci-controller1 {
diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h
index b885e1a..fa40d21 100644
--- a/arch/sandbox/include/asm/test.h
+++ b/arch/sandbox/include/asm/test.h
@@ -13,6 +13,7 @@
#define SANDBOX_PCI_VENDOR_ID 0x1234
#define SANDBOX_PCI_SWAP_CASE_EMUL_ID 0x5678
+#define SANDBOX_PCI_PMC_EMUL_ID 0x5677
#define SANDBOX_PCI_CLASS_CODE PCI_CLASS_CODE_COMM
#define SANDBOX_PCI_CLASS_SUB_CODE PCI_CLASS_SUB_CODE_COMM_SERIAL
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 1e4cf14..4e29e7b 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -228,6 +228,14 @@
help
Print GPL license text
+config CMD_PMC
+ bool "pmc"
+ help
+ Provides access to the Intel Power-Management Controller (PMC) so
+ that its state can be examined. This does not currently support
+ changing the state but it is still useful for debugging and seeing
+ what is going on.
+
config CMD_REGINFO
bool "reginfo"
depends on PPC
diff --git a/cmd/Makefile b/cmd/Makefile
index 3ac7104..12e898d 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -109,6 +109,7 @@
obj-$(CONFIG_CMD_PCI) += pci.o
endif
obj-$(CONFIG_CMD_PINMUX) += pinmux.o
+obj-$(CONFIG_CMD_PMC) += pmc.o
obj-$(CONFIG_CMD_PXE) += pxe.o pxe_utils.o
obj-$(CONFIG_CMD_WOL) += wol.o
obj-$(CONFIG_CMD_QFW) += qfw.o
diff --git a/cmd/pmc.c b/cmd/pmc.c
new file mode 100644
index 0000000..cafeba9
--- /dev/null
+++ b/cmd/pmc.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel PMC command
+ *
+ * Copyright 2019 Google LLC
+ */
+
+#include <common.h>
+#include <command.h>
+#include <dm.h>
+#include <power/acpi_pmc.h>
+
+static int get_pmc_dev(struct udevice **devp)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = uclass_first_device_err(UCLASS_ACPI_PMC, &dev);
+ if (ret) {
+ printf("Could not find device (err=%d)\n", ret);
+ return ret;
+ }
+ ret = pmc_init(dev);
+ if (ret) {
+ printf("Could not init device (err=%d)\n", ret);
+ return ret;
+ }
+ *devp = dev;
+
+ return 0;
+}
+
+static int do_pmc_init(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = get_pmc_dev(&dev);
+ if (ret)
+ return CMD_RET_FAILURE;
+
+ return 0;
+}
+
+static int do_pmc_info(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = get_pmc_dev(&dev);
+ if (ret)
+ return CMD_RET_FAILURE;
+ pmc_dump_info(dev);
+
+ return 0;
+}
+
+static cmd_tbl_t cmd_pmc_sub[] = {
+ U_BOOT_CMD_MKENT(init, 0, 1, do_pmc_init, "", ""),
+ U_BOOT_CMD_MKENT(info, 0, 1, do_pmc_info, "", ""),
+};
+
+static int do_pmc(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
+{
+ const cmd_tbl_t *cp;
+
+ if (argc < 2) /* no subcommand */
+ return cmd_usage(cmdtp);
+
+ cp = find_cmd_tbl(argv[1], &cmd_pmc_sub[0], ARRAY_SIZE(cmd_pmc_sub));
+ if (!cp)
+ return CMD_RET_USAGE;
+
+ return cp->cmd(cmdtp, flag, argc, argv);
+}
+
+U_BOOT_CMD(
+ pmc, 2, 1, do_pmc, "Power-management controller info",
+ "info - read state and show info about the PMC\n"
+ "pmc init - read state from the PMC\n"
+ );
diff --git a/drivers/Makefile b/drivers/Makefile
index e977f19..cb8c215 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -25,6 +25,7 @@
obj-$(CONFIG_$(SPL_)DM_MAILBOX) += mailbox/
obj-$(CONFIG_$(SPL_)REMOTEPROC) += remoteproc/
obj-$(CONFIG_$(SPL_TPL_)TPM) += tpm/
+obj-$(CONFIG_$(SPL_TPL_)ACPI_PMC) += power/acpi_pmc/
ifndef CONFIG_TPL_BUILD
ifdef CONFIG_SPL_BUILD
diff --git a/drivers/power/acpi_pmc/Kconfig b/drivers/power/acpi_pmc/Kconfig
index 472a61a..fcd50e3 100644
--- a/drivers/power/acpi_pmc/Kconfig
+++ b/drivers/power/acpi_pmc/Kconfig
@@ -23,3 +23,12 @@
provides features including checking whether the system started from
resume, powering off the system and enabling/disabling the reset
mechanism.
+
+config ACPI_PMC_SANDBOX
+ bool "Test power manager (PMC) for sandbox"
+ depends on ACPI_PMC && SANDBOX
+ help
+ This driver emulates a PMC (Power-Management Controller) so that
+ the uclass logic can be tested. You can use the 'pmc' command to
+ access information from the driver. It uses I/O access to read
+ from the PMC.
diff --git a/drivers/power/acpi_pmc/Makefile b/drivers/power/acpi_pmc/Makefile
index 7c1ba05..115788f 100644
--- a/drivers/power/acpi_pmc/Makefile
+++ b/drivers/power/acpi_pmc/Makefile
@@ -3,3 +3,4 @@
# Copyright 2019 Google LLC
obj-$(CONFIG_$(SPL_TPL_)ACPI_PMC) += acpi-pmc-uclass.o
+obj-$(CONFIG_$(SPL_TPL_)ACPI_PMC_SANDBOX) += sandbox.o pmc_emul.o
diff --git a/drivers/power/acpi_pmc/pmc_emul.c b/drivers/power/acpi_pmc/pmc_emul.c
new file mode 100644
index 0000000..15cc7ac
--- /dev/null
+++ b/drivers/power/acpi_pmc/pmc_emul.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PCI emulation device for an x86 Power-Management Controller (PMC)
+ *
+ * Copyright 2019 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <pci.h>
+#include <asm/test.h>
+#include <power/acpi_pmc.h>
+
+/**
+ * struct pmc_emul_platdata - platform data for this device
+ *
+ * @command: Current PCI command value
+ * @bar: Current base address values
+ */
+struct pmc_emul_platdata {
+ u16 command;
+ u32 bar[6];
+};
+
+enum {
+ MEMMAP_SIZE = 0x80,
+};
+
+static struct pci_bar {
+ int type;
+ u32 size;
+} barinfo[] = {
+ { PCI_BASE_ADDRESS_MEM_TYPE_32, MEMMAP_SIZE },
+ { 0, 0 },
+ { 0, 0 },
+ { 0, 0 },
+ { PCI_BASE_ADDRESS_SPACE_IO, 256 },
+};
+
+struct pmc_emul_priv {
+ u8 regs[MEMMAP_SIZE];
+};
+
+static int sandbox_pmc_emul_read_config(struct udevice *emul, uint offset,
+ ulong *valuep, enum pci_size_t size)
+{
+ struct pmc_emul_platdata *plat = dev_get_platdata(emul);
+
+ switch (offset) {
+ case PCI_COMMAND:
+ *valuep = plat->command;
+ break;
+ case PCI_HEADER_TYPE:
+ *valuep = 0;
+ break;
+ case PCI_VENDOR_ID:
+ *valuep = SANDBOX_PCI_VENDOR_ID;
+ break;
+ case PCI_DEVICE_ID:
+ *valuep = SANDBOX_PCI_PMC_EMUL_ID;
+ break;
+ case PCI_CLASS_DEVICE:
+ if (size == PCI_SIZE_8) {
+ *valuep = SANDBOX_PCI_CLASS_SUB_CODE;
+ } else {
+ *valuep = (SANDBOX_PCI_CLASS_CODE << 8) |
+ SANDBOX_PCI_CLASS_SUB_CODE;
+ }
+ break;
+ case PCI_CLASS_CODE:
+ *valuep = SANDBOX_PCI_CLASS_CODE;
+ break;
+ case PCI_BASE_ADDRESS_0:
+ case PCI_BASE_ADDRESS_1:
+ case PCI_BASE_ADDRESS_2:
+ case PCI_BASE_ADDRESS_3:
+ case PCI_BASE_ADDRESS_4:
+ case PCI_BASE_ADDRESS_5: {
+ int barnum;
+ u32 *bar;
+
+ barnum = pci_offset_to_barnum(offset);
+ bar = &plat->bar[barnum];
+
+ *valuep = sandbox_pci_read_bar(*bar, barinfo[barnum].type,
+ barinfo[barnum].size);
+ break;
+ }
+ case PCI_CAPABILITY_LIST:
+ *valuep = PCI_CAP_ID_PM_OFFSET;
+ break;
+ }
+
+ return 0;
+}
+
+static int sandbox_pmc_emul_write_config(struct udevice *emul, uint offset,
+ ulong value, enum pci_size_t size)
+{
+ struct pmc_emul_platdata *plat = dev_get_platdata(emul);
+
+ switch (offset) {
+ case PCI_COMMAND:
+ plat->command = value;
+ break;
+ case PCI_BASE_ADDRESS_0:
+ case PCI_BASE_ADDRESS_1: {
+ int barnum;
+ u32 *bar;
+
+ barnum = pci_offset_to_barnum(offset);
+ bar = &plat->bar[barnum];
+
+ debug("w bar %d=%lx\n", barnum, value);
+ *bar = value;
+ /* space indicator (bit#0) is read-only */
+ *bar |= barinfo[barnum].type;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int sandbox_pmc_emul_find_bar(struct udevice *emul, unsigned int addr,
+ int *barnump, unsigned int *offsetp)
+{
+ struct pmc_emul_platdata *plat = dev_get_platdata(emul);
+ int barnum;
+
+ for (barnum = 0; barnum < ARRAY_SIZE(barinfo); barnum++) {
+ unsigned int size = barinfo[barnum].size;
+ u32 base = plat->bar[barnum] & ~PCI_BASE_ADDRESS_SPACE;
+
+ if (addr >= base && addr < base + size) {
+ *barnump = barnum;
+ *offsetp = addr - base;
+ return 0;
+ }
+ }
+ *barnump = -1;
+
+ return -ENOENT;
+}
+
+static int sandbox_pmc_emul_read_io(struct udevice *dev, unsigned int addr,
+ ulong *valuep, enum pci_size_t size)
+{
+ unsigned int offset;
+ int barnum;
+ int ret;
+
+ ret = sandbox_pmc_emul_find_bar(dev, addr, &barnum, &offset);
+ if (ret)
+ return ret;
+
+ if (barnum == 4)
+ *valuep = offset;
+ else if (barnum == 0)
+ *valuep = offset;
+
+ return 0;
+}
+
+static int sandbox_pmc_emul_write_io(struct udevice *dev, unsigned int addr,
+ ulong value, enum pci_size_t size)
+{
+ unsigned int offset;
+ int barnum;
+ int ret;
+
+ ret = sandbox_pmc_emul_find_bar(dev, addr, &barnum, &offset);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int sandbox_pmc_emul_map_physmem(struct udevice *dev,
+ phys_addr_t addr, unsigned long *lenp,
+ void **ptrp)
+{
+ struct pmc_emul_priv *priv = dev_get_priv(dev);
+ unsigned int offset, avail;
+ int barnum;
+ int ret;
+
+ ret = sandbox_pmc_emul_find_bar(dev, addr, &barnum, &offset);
+ if (ret)
+ return ret;
+
+ if (barnum == 0) {
+ *ptrp = priv->regs + offset;
+ avail = barinfo[0].size - offset;
+ if (avail > barinfo[0].size)
+ *lenp = 0;
+ else
+ *lenp = min(*lenp, (ulong)avail);
+
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int sandbox_pmc_probe(struct udevice *dev)
+{
+ struct pmc_emul_priv *priv = dev_get_priv(dev);
+ int i;
+
+ for (i = 0; i < MEMMAP_SIZE; i++)
+ priv->regs[i] = i;
+
+ return 0;
+}
+
+static struct dm_pci_emul_ops sandbox_pmc_emul_emul_ops = {
+ .read_config = sandbox_pmc_emul_read_config,
+ .write_config = sandbox_pmc_emul_write_config,
+ .read_io = sandbox_pmc_emul_read_io,
+ .write_io = sandbox_pmc_emul_write_io,
+ .map_physmem = sandbox_pmc_emul_map_physmem,
+};
+
+static const struct udevice_id sandbox_pmc_emul_ids[] = {
+ { .compatible = "sandbox,pmc-emul" },
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_pmc_emul_emul) = {
+ .name = "sandbox_pmc_emul_emul",
+ .id = UCLASS_PCI_EMUL,
+ .of_match = sandbox_pmc_emul_ids,
+ .ops = &sandbox_pmc_emul_emul_ops,
+ .probe = sandbox_pmc_probe,
+ .priv_auto_alloc_size = sizeof(struct pmc_emul_priv),
+ .platdata_auto_alloc_size = sizeof(struct pmc_emul_platdata),
+};
+
+static struct pci_device_id sandbox_pmc_emul_supported[] = {
+ { PCI_VDEVICE(SANDBOX, SANDBOX_PCI_PMC_EMUL_ID) },
+ {},
+};
+
+U_BOOT_PCI_DEVICE(sandbox_pmc_emul_emul, sandbox_pmc_emul_supported);
diff --git a/drivers/power/acpi_pmc/sandbox.c b/drivers/power/acpi_pmc/sandbox.c
new file mode 100644
index 0000000..7fbbf97
--- /dev/null
+++ b/drivers/power/acpi_pmc/sandbox.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sandbox PMC for testing
+ *
+ * Copyright 2019 Google LLC
+ */
+
+#define LOG_CATEGORY UCLASS_ACPI_PMC
+
+#include <common.h>
+#include <dm.h>
+#include <asm/io.h>
+#include <power/acpi_pmc.h>
+
+#define GPIO_GPE_CFG 0x1050
+
+/* Memory mapped IO registers behind PMC_BASE_ADDRESS */
+#define PRSTS 0x1000
+#define GEN_PMCON1 0x1020
+#define GEN_PMCON2 0x1024
+#define GEN_PMCON3 0x1028
+
+/* Offset of TCO registers from ACPI base I/O address */
+#define TCO_REG_OFFSET 0x60
+#define TCO1_STS 0x64
+#define TCO2_STS 0x66
+#define TCO1_CNT 0x68
+#define TCO2_CNT 0x6a
+
+struct sandbox_pmc_priv {
+ ulong base;
+};
+
+static int sandbox_pmc_fill_power_state(struct udevice *dev)
+{
+ struct acpi_pmc_upriv *upriv = dev_get_uclass_priv(dev);
+
+ upriv->tco1_sts = inw(upriv->acpi_base + TCO1_STS);
+ upriv->tco2_sts = inw(upriv->acpi_base + TCO2_STS);
+
+ upriv->prsts = readl(upriv->pmc_bar0 + PRSTS);
+ upriv->gen_pmcon1 = readl(upriv->pmc_bar0 + GEN_PMCON1);
+ upriv->gen_pmcon2 = readl(upriv->pmc_bar0 + GEN_PMCON2);
+ upriv->gen_pmcon3 = readl(upriv->pmc_bar0 + GEN_PMCON3);
+
+ return 0;
+}
+
+static int sandbox_prev_sleep_state(struct udevice *dev, int prev_sleep_state)
+{
+ return prev_sleep_state;
+}
+
+static int sandbox_disable_tco(struct udevice *dev)
+{
+ struct acpi_pmc_upriv *upriv = dev_get_uclass_priv(dev);
+
+ pmc_disable_tco_base(upriv->acpi_base + TCO_REG_OFFSET);
+
+ return 0;
+}
+
+static int sandbox_pmc_probe(struct udevice *dev)
+{
+ struct acpi_pmc_upriv *upriv = dev_get_uclass_priv(dev);
+ struct udevice *bus;
+ ulong base;
+
+ uclass_first_device(UCLASS_PCI, &bus);
+ base = dm_pci_read_bar32(dev, 0);
+ if (base == FDT_ADDR_T_NONE)
+ return log_msg_ret("No base address", -EINVAL);
+ upriv->pmc_bar0 = map_sysmem(base, 0x2000);
+ upriv->gpe_cfg = (u32 *)(upriv->pmc_bar0 + GPIO_GPE_CFG);
+
+ return pmc_ofdata_to_uc_platdata(dev);
+}
+
+static struct acpi_pmc_ops sandbox_pmc_ops = {
+ .init = sandbox_pmc_fill_power_state,
+ .prev_sleep_state = sandbox_prev_sleep_state,
+ .disable_tco = sandbox_disable_tco,
+};
+
+static const struct udevice_id sandbox_pmc_ids[] = {
+ { .compatible = "sandbox,pmc" },
+ { }
+};
+
+U_BOOT_DRIVER(pmc_sandbox) = {
+ .name = "pmc_sandbox",
+ .id = UCLASS_ACPI_PMC,
+ .of_match = sandbox_pmc_ids,
+ .probe = sandbox_pmc_probe,
+ .ops = &sandbox_pmc_ops,
+ .priv_auto_alloc_size = sizeof(struct sandbox_pmc_priv),
+};
diff --git a/test/dm/Makefile b/test/dm/Makefile
index 0c2fd5c..10a19a0 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -36,6 +36,7 @@
obj-$(CONFIG_PCH) += pch.o
obj-$(CONFIG_PHY) += phy.o
obj-$(CONFIG_POWER_DOMAIN) += power-domain.o
+obj-$(CONFIG_ACPI_PMC) += pmc.o
obj-$(CONFIG_DM_PWM) += pwm.o
obj-$(CONFIG_RAM) += ram.o
obj-y += regmap.o
diff --git a/test/dm/pmc.c b/test/dm/pmc.c
new file mode 100644
index 0000000..1a22283
--- /dev/null
+++ b/test/dm/pmc.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test for power-management controller uclass (PMC)
+ *
+ * Copyright 2019 Google LLC
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <power/acpi_pmc.h>
+#include <dm/test.h>
+#include <test/ut.h>
+
+/* Base test of the PMC uclass */
+static int dm_test_pmc_base(struct unit_test_state *uts)
+{
+ struct acpi_pmc_upriv *upriv;
+ struct udevice *dev;
+
+ ut_assertok(uclass_first_device_err(UCLASS_ACPI_PMC, &dev));
+
+ ut_assertok(pmc_disable_tco(dev));
+ ut_assertok(pmc_init(dev));
+ ut_assertok(pmc_prev_sleep_state(dev));
+
+ /* Check some values to see that I/O works */
+ upriv = dev_get_uclass_priv(dev);
+ ut_asserteq(0x24, upriv->gpe0_sts[1]);
+ ut_asserteq(0x64, upriv->tco1_sts);
+
+ return 0;
+}
+DM_TEST(dm_test_pmc_base, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);