forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
firmware: scpi: add device power domain support using genpd
This patch hooks up the support for device power domain provided by SCPI using the Linux generic power domain infrastructure. Cc: "Rafael J. Wysocki" <[email protected]> Tested-by: Mathieu Poirier <[email protected]> Tested-by: Jon Medhurst <[email protected]> Reviewed-by: Jon Medhurst <[email protected]> Reviewed-by: Kevin Hilman <[email protected]> Reviewed-by: Ulf Hansson <[email protected]> Signed-off-by: Sudeep Holla <[email protected]>
- Loading branch information
1 parent
43752b8
commit 8bec433
Showing
3 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* | ||
* SCPI Generic power domain support. | ||
* | ||
* Copyright (C) 2016 ARM Ltd. | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms and conditions of the GNU General Public License, | ||
* version 2, as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#include <linux/err.h> | ||
#include <linux/io.h> | ||
#include <linux/module.h> | ||
#include <linux/of_platform.h> | ||
#include <linux/pm_domain.h> | ||
#include <linux/scpi_protocol.h> | ||
|
||
struct scpi_pm_domain { | ||
struct generic_pm_domain genpd; | ||
struct scpi_ops *ops; | ||
u32 domain; | ||
char name[30]; | ||
}; | ||
|
||
/* | ||
* These device power state values are not well-defined in the specification. | ||
* In case, different implementations use different values, we can make these | ||
* specific to compatibles rather than getting these values from device tree. | ||
*/ | ||
enum scpi_power_domain_state { | ||
SCPI_PD_STATE_ON = 0, | ||
SCPI_PD_STATE_OFF = 3, | ||
}; | ||
|
||
#define to_scpi_pd(gpd) container_of(gpd, struct scpi_pm_domain, genpd) | ||
|
||
static int scpi_pd_power(struct scpi_pm_domain *pd, bool power_on) | ||
{ | ||
int ret; | ||
enum scpi_power_domain_state state; | ||
|
||
if (power_on) | ||
state = SCPI_PD_STATE_ON; | ||
else | ||
state = SCPI_PD_STATE_OFF; | ||
|
||
ret = pd->ops->device_set_power_state(pd->domain, state); | ||
if (ret) | ||
return ret; | ||
|
||
return !(state == pd->ops->device_get_power_state(pd->domain)); | ||
} | ||
|
||
static int scpi_pd_power_on(struct generic_pm_domain *domain) | ||
{ | ||
struct scpi_pm_domain *pd = to_scpi_pd(domain); | ||
|
||
return scpi_pd_power(pd, true); | ||
} | ||
|
||
static int scpi_pd_power_off(struct generic_pm_domain *domain) | ||
{ | ||
struct scpi_pm_domain *pd = to_scpi_pd(domain); | ||
|
||
return scpi_pd_power(pd, false); | ||
} | ||
|
||
static int scpi_pm_domain_probe(struct platform_device *pdev) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
struct device_node *np = dev->of_node; | ||
struct scpi_pm_domain *scpi_pd; | ||
struct genpd_onecell_data *scpi_pd_data; | ||
struct generic_pm_domain **domains; | ||
struct scpi_ops *scpi_ops; | ||
int ret, num_domains, i; | ||
|
||
scpi_ops = get_scpi_ops(); | ||
if (!scpi_ops) | ||
return -EPROBE_DEFER; | ||
|
||
if (!np) { | ||
dev_err(dev, "device tree node not found\n"); | ||
return -ENODEV; | ||
} | ||
|
||
if (!scpi_ops->device_set_power_state || | ||
!scpi_ops->device_get_power_state) { | ||
dev_err(dev, "power domains not supported in the firmware\n"); | ||
return -ENODEV; | ||
} | ||
|
||
ret = of_property_read_u32(np, "num-domains", &num_domains); | ||
if (ret) { | ||
dev_err(dev, "number of domains not found\n"); | ||
return -EINVAL; | ||
} | ||
|
||
scpi_pd = devm_kcalloc(dev, num_domains, sizeof(*scpi_pd), GFP_KERNEL); | ||
if (!scpi_pd) | ||
return -ENOMEM; | ||
|
||
scpi_pd_data = devm_kzalloc(dev, sizeof(*scpi_pd_data), GFP_KERNEL); | ||
if (!scpi_pd_data) | ||
return -ENOMEM; | ||
|
||
domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL); | ||
if (!domains) | ||
return -ENOMEM; | ||
|
||
for (i = 0; i < num_domains; i++, scpi_pd++) { | ||
domains[i] = &scpi_pd->genpd; | ||
|
||
scpi_pd->domain = i; | ||
scpi_pd->ops = scpi_ops; | ||
sprintf(scpi_pd->name, "%s.%d", np->name, i); | ||
scpi_pd->genpd.name = scpi_pd->name; | ||
scpi_pd->genpd.power_off = scpi_pd_power_off; | ||
scpi_pd->genpd.power_on = scpi_pd_power_on; | ||
|
||
/* | ||
* Treat all power domains as off at boot. | ||
* | ||
* The SCP firmware itself may have switched on some domains, | ||
* but for reference counting purpose, keep it this way. | ||
*/ | ||
pm_genpd_init(&scpi_pd->genpd, NULL, true); | ||
} | ||
|
||
scpi_pd_data->domains = domains; | ||
scpi_pd_data->num_domains = num_domains; | ||
|
||
of_genpd_add_provider_onecell(np, scpi_pd_data); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct of_device_id scpi_power_domain_ids[] = { | ||
{ .compatible = "arm,scpi-power-domains", }, | ||
{ /* sentinel */ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, scpi_power_domain_ids); | ||
|
||
static struct platform_driver scpi_power_domain_driver = { | ||
.driver = { | ||
.name = "scpi_power_domain", | ||
.of_match_table = scpi_power_domain_ids, | ||
}, | ||
.probe = scpi_pm_domain_probe, | ||
}; | ||
module_platform_driver(scpi_power_domain_driver); | ||
|
||
MODULE_AUTHOR("Sudeep Holla <[email protected]>"); | ||
MODULE_DESCRIPTION("ARM SCPI power domain driver"); | ||
MODULE_LICENSE("GPL v2"); |