diff --git a/Makefile b/Makefile index bccfbaa..99cc8cf 100644 --- a/Makefile +++ b/Makefile @@ -638,6 +638,7 @@ libs-y += drivers/net/ libs-y += drivers/net/phy/ libs-y += drivers/pci/ libs-y += drivers/power/ \ + drivers/power/domain/ \ drivers/power/fuel_gauge/ \ drivers/power/mfd/ \ drivers/power/pmic/ \ diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 9e46f9e..fff175d 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -262,6 +262,16 @@ }; }; + pwrdom: power-domain { + compatible = "sandbox,power-domain"; + #power-domain-cells = <1>; + }; + + power-domain-test { + compatible = "sandbox,power-domain-test"; + power-domains = <&pwrdom 2>; + }; + ram { compatible = "sandbox,ram"; }; diff --git a/arch/sandbox/include/asm/power-domain.h b/arch/sandbox/include/asm/power-domain.h new file mode 100644 index 0000000..cad3885 --- /dev/null +++ b/arch/sandbox/include/asm/power-domain.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#ifndef __SANDBOX_POWER_DOMAIN_H +#define __SANDBOX_POWER_DOMAIN_H + +#include + +struct udevice; + +int sandbox_power_domain_query(struct udevice *dev, unsigned long id); + +int sandbox_power_domain_test_get(struct udevice *dev); +int sandbox_power_domain_test_on(struct udevice *dev); +int sandbox_power_domain_test_off(struct udevice *dev); +int sandbox_power_domain_test_free(struct udevice *dev); + +#endif diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 29e6d85..887d83a 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -176,3 +176,5 @@ CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y CONFIG_UT_ENV=y +CONFIG_POWER_DOMAIN=y +CONFIG_SANDBOX_POWER_DOMAIN=y diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 3c44167..b422703 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,5 +1,7 @@ menu "Power" +source "drivers/power/domain/Kconfig" + source "drivers/power/pmic/Kconfig" source "drivers/power/regulator/Kconfig" diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig new file mode 100644 index 0000000..b904097 --- /dev/null +++ b/drivers/power/domain/Kconfig @@ -0,0 +1,20 @@ +menu "Power Domain Support" + +config POWER_DOMAIN + bool "Enable power domain support using Driver Model" + depends on DM && OF_CONTROL + help + Enable support for the power domain driver class. Many SoCs allow + power to be applied to or removed from portions of the SoC (power + domains). This may be used to save power. This API provides the + means to control such power management hardware. + +config SANDBOX_POWER_DOMAIN + bool "Enable the sandbox power domain test driver" + depends on POWER_DOMAIN && SANDBOX + help + Enable support for a test power domain driver implementation, which + simply accepts requests to power on/off various HW modules without + actually doing anything beyond a little error checking. + +endmenu diff --git a/drivers/power/domain/Makefile b/drivers/power/domain/Makefile new file mode 100644 index 0000000..c18292f --- /dev/null +++ b/drivers/power/domain/Makefile @@ -0,0 +1,7 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. +# +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_POWER_DOMAIN) += power-domain-uclass.o +obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain.o +obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain-test.o diff --git a/drivers/power/domain/power-domain-uclass.c b/drivers/power/domain/power-domain-uclass.c new file mode 100644 index 0000000..1bb6262 --- /dev/null +++ b/drivers/power/domain/power-domain-uclass.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +static inline struct power_domain_ops *power_domain_dev_ops(struct udevice *dev) +{ + return (struct power_domain_ops *)dev->driver->ops; +} + +static int power_domain_of_xlate_default(struct power_domain *power_domain, + struct fdtdec_phandle_args *args) +{ + debug("%s(power_domain=%p)\n", __func__, power_domain); + + if (args->args_count != 1) { + debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + power_domain->id = args->args[0]; + + return 0; +} + +int power_domain_get(struct udevice *dev, struct power_domain *power_domain) +{ + struct fdtdec_phandle_args args; + int ret; + struct udevice *dev_power_domain; + struct power_domain_ops *ops; + + debug("%s(dev=%p, power_domain=%p)\n", __func__, dev, power_domain); + + ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, dev->of_offset, + "power-domains", + "#power-domain-cells", 0, 0, + &args); + if (ret) { + debug("%s: fdtdec_parse_phandle_with_args failed: %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_of_offset(UCLASS_POWER_DOMAIN, args.node, + &dev_power_domain); + if (ret) { + debug("%s: uclass_get_device_by_of_offset failed: %d\n", + __func__, ret); + return ret; + } + ops = power_domain_dev_ops(dev_power_domain); + + power_domain->dev = dev_power_domain; + if (ops->of_xlate) + ret = ops->of_xlate(power_domain, &args); + else + ret = power_domain_of_xlate_default(power_domain, &args); + if (ret) { + debug("of_xlate() failed: %d\n", ret); + return ret; + } + + ret = ops->request(power_domain); + if (ret) { + debug("ops->request() failed: %d\n", ret); + return ret; + } + + return 0; +} + +int power_domain_free(struct power_domain *power_domain) +{ + struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); + + debug("%s(power_domain=%p)\n", __func__, power_domain); + + return ops->free(power_domain); +} + +int power_domain_on(struct power_domain *power_domain) +{ + struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); + + debug("%s(power_domain=%p)\n", __func__, power_domain); + + return ops->on(power_domain); +} + +int power_domain_off(struct power_domain *power_domain) +{ + struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); + + debug("%s(power_domain=%p)\n", __func__, power_domain); + + return ops->off(power_domain); +} + +UCLASS_DRIVER(power_domain) = { + .id = UCLASS_POWER_DOMAIN, + .name = "power_domain", +}; diff --git a/drivers/power/domain/sandbox-power-domain-test.c b/drivers/power/domain/sandbox-power-domain-test.c new file mode 100644 index 0000000..92a3a2a --- /dev/null +++ b/drivers/power/domain/sandbox-power-domain-test.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include + +struct sandbox_power_domain_test { + struct power_domain pd; +}; + +int sandbox_power_domain_test_get(struct udevice *dev) +{ + struct sandbox_power_domain_test *sbrt = dev_get_priv(dev); + + return power_domain_get(dev, &sbrt->pd); +} + +int sandbox_power_domain_test_on(struct udevice *dev) +{ + struct sandbox_power_domain_test *sbrt = dev_get_priv(dev); + + return power_domain_on(&sbrt->pd); +} + +int sandbox_power_domain_test_off(struct udevice *dev) +{ + struct sandbox_power_domain_test *sbrt = dev_get_priv(dev); + + return power_domain_off(&sbrt->pd); +} + +int sandbox_power_domain_test_free(struct udevice *dev) +{ + struct sandbox_power_domain_test *sbrt = dev_get_priv(dev); + + return power_domain_free(&sbrt->pd); +} + +static const struct udevice_id sandbox_power_domain_test_ids[] = { + { .compatible = "sandbox,power-domain-test" }, + { } +}; + +U_BOOT_DRIVER(sandbox_power_domain_test) = { + .name = "sandbox_power_domain_test", + .id = UCLASS_MISC, + .of_match = sandbox_power_domain_test_ids, + .priv_auto_alloc_size = sizeof(struct sandbox_power_domain_test), +}; diff --git a/drivers/power/domain/sandbox-power-domain.c b/drivers/power/domain/sandbox-power-domain.c new file mode 100644 index 0000000..9071346 --- /dev/null +++ b/drivers/power/domain/sandbox-power-domain.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include + +#define SANDBOX_POWER_DOMAINS 3 + +struct sandbox_power_domain { + bool on[SANDBOX_POWER_DOMAINS]; +}; + +static int sandbox_power_domain_request(struct power_domain *power_domain) +{ + debug("%s(power_domain=%p)\n", __func__, power_domain); + + if (power_domain->id >= SANDBOX_POWER_DOMAINS) + return -EINVAL; + + return 0; +} + +static int sandbox_power_domain_free(struct power_domain *power_domain) +{ + debug("%s(power_domain=%p)\n", __func__, power_domain); + + return 0; +} + +static int sandbox_power_domain_on(struct power_domain *power_domain) +{ + struct sandbox_power_domain *sbr = dev_get_priv(power_domain->dev); + + debug("%s(power_domain=%p)\n", __func__, power_domain); + + sbr->on[power_domain->id] = true; + + return 0; +} + +static int sandbox_power_domain_off(struct power_domain *power_domain) +{ + struct sandbox_power_domain *sbr = dev_get_priv(power_domain->dev); + + debug("%s(power_domain=%p)\n", __func__, power_domain); + + sbr->on[power_domain->id] = false; + + return 0; +} + +static int sandbox_power_domain_bind(struct udevice *dev) +{ + debug("%s(dev=%p)\n", __func__, dev); + + return 0; +} + +static int sandbox_power_domain_probe(struct udevice *dev) +{ + debug("%s(dev=%p)\n", __func__, dev); + + return 0; +} + +static const struct udevice_id sandbox_power_domain_ids[] = { + { .compatible = "sandbox,power-domain" }, + { } +}; + +struct power_domain_ops sandbox_power_domain_ops = { + .request = sandbox_power_domain_request, + .free = sandbox_power_domain_free, + .on = sandbox_power_domain_on, + .off = sandbox_power_domain_off, +}; + +U_BOOT_DRIVER(sandbox_power_domain) = { + .name = "sandbox_power_domain", + .id = UCLASS_POWER_DOMAIN, + .of_match = sandbox_power_domain_ids, + .bind = sandbox_power_domain_bind, + .probe = sandbox_power_domain_probe, + .priv_auto_alloc_size = sizeof(struct sandbox_power_domain), + .ops = &sandbox_power_domain_ops, +}; + +int sandbox_power_domain_query(struct udevice *dev, unsigned long id) +{ + struct sandbox_power_domain *sbr = dev_get_priv(dev); + + debug("%s(dev=%p, id=%ld)\n", __func__, dev, id); + + if (id >= SANDBOX_POWER_DOMAINS) + return -EINVAL; + + return sbr->on[id]; +} diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index c5cdfc7..eb78c4d 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -59,6 +59,7 @@ enum uclass_id { UCLASS_PINCTRL, /* Pinctrl (pin muxing/configuration) device */ UCLASS_PMIC, /* PMIC I/O device */ UCLASS_PWM, /* Pulse-width modulator */ + UCLASS_POWER_DOMAIN, /* (SoC) Power domains */ UCLASS_PWRSEQ, /* Power sequence device */ UCLASS_RAM, /* RAM controller */ UCLASS_REGULATOR, /* Regulator device */ diff --git a/include/power-domain-uclass.h b/include/power-domain-uclass.h new file mode 100644 index 0000000..5878021 --- /dev/null +++ b/include/power-domain-uclass.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#ifndef _POWER_DOMAIN_UCLASS_H +#define _POWER_DOMAIN_UCLASS_H + +/* See power-domain.h for background documentation. */ + +#include + +struct udevice; + +/** + * struct power_domain_ops - The functions that a power domain controller driver + * must implement. + */ +struct power_domain_ops { + /** + * of_xlate - Translate a client's device-tree (OF) power domain + * specifier. + * + * The power domain core calls this function as the first step in + * implementing a client's power_domain_get() call. + * + * If this function pointer is set to NULL, the power domain core will + * use a default implementation, which assumes #power-domain-cells = + * <1>, and that the DT cell contains a simple integer power domain ID. + * + * At present, the power domain API solely supports device-tree. If + * this changes, other xxx_xlate() functions may be added to support + * those other mechanisms. + * + * @power_domain: The power domain struct to hold the + * translation result. + * @args: The power domain specifier values from device + * tree. + * @return 0 if OK, or a negative error code. + */ + int (*of_xlate)(struct power_domain *power_domain, + struct fdtdec_phandle_args *args); + /** + * request - Request a translated power domain. + * + * The power domain core calls this function as the second step in + * implementing a client's power_domain_get() call, following a + * successful xxx_xlate() call. + * + * @power_domain: The power domain to request; this has been + * filled in by a previous xxx_xlate() function + * call. + * @return 0 if OK, or a negative error code. + */ + int (*request)(struct power_domain *power_domain); + /** + * free - Free a previously requested power domain. + * + * This is the implementation of the client power_domain_free() API. + * + * @power_domain: The power domain to free. + * @return 0 if OK, or a negative error code. + */ + int (*free)(struct power_domain *power_domain); + /** + * on - Power on a power domain. + * + * @power_domain: The power domain to turn on. + * @return 0 if OK, or a negative error code. + */ + int (*on)(struct power_domain *power_domain); + /** + * off - Power off a power domain. + * + * @power_domain: The power domain to turn off. + * @return 0 if OK, or a negative error code. + */ + int (*off)(struct power_domain *power_domain); +}; + +#endif diff --git a/include/power-domain.h b/include/power-domain.h new file mode 100644 index 0000000..1099979 --- /dev/null +++ b/include/power-domain.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#ifndef _POWER_DOMAIN_H +#define _POWER_DOMAIN_H + +/** + * A power domain is a portion of an SoC or chip that is powered by a + * switchable source of power. In many cases, software has control over the + * power domain, and can turn the power source on or off. This is typically + * done to save power by powering off unused devices, or to enable software + * sequencing of initial powerup at boot. This API provides a means for + * drivers to turn power domains on and off. + * + * A driver that implements UCLASS_POWER_DOMAIN is a power domain controller or + * provider. A controller will often implement multiple separate power domains, + * since the hardware it manages often has this capability. + * power-domain-uclass.h describes the interface which power domain controllers + * must implement. + * + * Depending on the power domain controller hardware, changing the state of a + * power domain may require performing related operations on other resources. + * For example, some power domains may require certain clocks to be enabled + * whenever the power domain is powered on, or during the time when the power + * domain is transitioning state. These details are implementation-specific + * and should ideally be encapsulated entirely within the provider driver, or + * configured through mechanisms (e.g. device tree) that do not require client + * drivers to provide extra configuration information. + * + * Power domain consumers/clients are the drivers for HW modules within the + * power domain. This header file describes the API used by those drivers. + * + * In many cases, a single complex IO controller (e.g. a PCIe controller) will + * be the sole logic contained within a power domain. In such cases, it is + * logical for the relevant device driver to directly control that power + * domain. In other cases, multiple controllers, each with their own driver, + * may be contained in a single power domain. Any logic require to co-ordinate + * between drivers for these multiple controllers is beyond the scope of this + * API at present. Equally, this API does not define or implement any policy + * by which power domains are managed. + */ + +struct udevice; + +/** + * struct power_domain - A handle to (allowing control of) a single power domain. + * + * Clients provide storage for power domain handles. The content of the + * structure is managed solely by the power domain API and power domain + * drivers. A power domain struct is initialized by "get"ing the power domain + * struct. The power domain struct is passed to all other power domain APIs to + * identify which power domain to operate upon. + * + * @dev: The device which implements the power domain. + * @id: The power domain ID within the provider. + * + * Currently, the power domain API assumes that a single integer ID is enough + * to identify and configure any power domain for any power domain provider. If + * this assumption becomes invalid in the future, the struct could be expanded + * to either (a) add more fields to allow power domain providers to store + * additional information, or (b) replace the id field with an opaque pointer, + * which the provider would dynamically allocate during its .of_xlate op, and + * process during is .request op. This may require the addition of an extra op + * to clean up the allocation. + */ +struct power_domain { + struct udevice *dev; + /* + * Written by of_xlate. We assume a single id is enough for now. In the + * future, we might add more fields here. + */ + unsigned long id; +}; + +/** + * power_domain_get - Get/request the power domain for a device. + * + * This looks up and requests a power domain. Each device is assumed to have + * a single (or, at least one) power domain associated with it somehow, and + * that domain, or the first/default domain. The mapping of client device to + * provider power domain may be via device-tree properties, board-provided + * mapping tables, or some other mechanism. + * + * @dev: The client device. + * @power_domain A pointer to a power domain struct to initialize. + * @return 0 if OK, or a negative error code. + */ +int power_domain_get(struct udevice *dev, struct power_domain *power_domain); + +/** + * power_domain_free - Free a previously requested power domain. + * + * @power_domain: A power domain struct that was previously successfully + * requested by power_domain_get(). + * @return 0 if OK, or a negative error code. + */ +int power_domain_free(struct power_domain *power_domain); + +/** + * power_domain_on - Enable power to a power domain. + * + * @power_domain: A power domain struct that was previously successfully + * requested by power_domain_get(). + * @return 0 if OK, or a negative error code. + */ +int power_domain_on(struct power_domain *power_domain); + +/** + * power_domain_off - Disable power ot a power domain. + * + * @power_domain: A power domain struct that was previously successfully + * requested by power_domain_get(). + * @return 0 if OK, or a negative error code. + */ +int power_domain_off(struct power_domain *power_domain); + +#endif diff --git a/test/dm/Makefile b/test/dm/Makefile index cad3374..1885e17 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_LED) += led.o obj-$(CONFIG_DM_MAILBOX) += mailbox.o obj-$(CONFIG_DM_MMC) += mmc.o obj-$(CONFIG_DM_PCI) += pci.o +obj-$(CONFIG_POWER_DOMAIN) += power-domain.o obj-$(CONFIG_RAM) += ram.o obj-y += regmap.o obj-$(CONFIG_REMOTEPROC) += remoteproc.o diff --git a/test/dm/power-domain.c b/test/dm/power-domain.c new file mode 100644 index 0000000..379a8fa --- /dev/null +++ b/test/dm/power-domain.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include + +/* This must match the specifier for power-domains in the DT node */ +#define TEST_POWER_DOMAIN 2 + +static int dm_test_power_domain(struct unit_test_state *uts) +{ + struct udevice *dev_power_domain; + struct udevice *dev_test; + + ut_assertok(uclass_get_device_by_name(UCLASS_POWER_DOMAIN, + "power-domain", + &dev_power_domain)); + ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, 0)); + ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, + TEST_POWER_DOMAIN)); + + ut_assertok(uclass_get_device_by_name(UCLASS_MISC, "power-domain-test", + &dev_test)); + ut_assertok(sandbox_power_domain_test_get(dev_test)); + + ut_assertok(sandbox_power_domain_test_on(dev_test)); + ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, 0)); + ut_asserteq(1, sandbox_power_domain_query(dev_power_domain, + TEST_POWER_DOMAIN)); + + ut_assertok(sandbox_power_domain_test_off(dev_test)); + ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, 0)); + ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, + TEST_POWER_DOMAIN)); + + ut_assertok(sandbox_power_domain_test_free(dev_test)); + + return 0; +} +DM_TEST(dm_test_power_domain, DM_TESTF_SCAN_FDT);