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);