forked from Minki/linux
efa5c01cd7
Current implementation of the genpd support over TI SCI uses a single genpd across the whole SoC, and attaches multiple devices to this. This solution has its drawbacks, like it is currently impossible to attach more than one power domain to a device; the core genpd implementation requires one genpd per power-domain entry in DT for a single device. Also, some devices like USB apparently require their own genpd during probe time, the current shared approach in use does not work at all. Switch the implementation over to use a single genpd per power domain entry in DT. The domains are registered with the onecell approach, but we also add our own xlate service due to recent introduction of the extended flag for TI SCI PM domains; genpd core xlate service requires a single cell per powerdomain, but we are using two cells. Signed-off-by: Tero Kristo <t-kristo@ti.com> Signed-off-by: Santosh Shilimkar <santosh.shilimkar@oracle.com>
207 lines
5.5 KiB
C
207 lines
5.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* TI SCI Generic Power Domain Driver
|
|
*
|
|
* Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/
|
|
* J Keerthy <j-keerthy@ti.com>
|
|
* Dave Gerlach <d-gerlach@ti.com>
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_domain.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/soc/ti/ti_sci_protocol.h>
|
|
#include <dt-bindings/soc/ti,sci_pm_domain.h>
|
|
|
|
/**
|
|
* struct ti_sci_genpd_provider: holds common TI SCI genpd provider data
|
|
* @ti_sci: handle to TI SCI protocol driver that provides ops to
|
|
* communicate with system control processor.
|
|
* @dev: pointer to dev for the driver for devm allocs
|
|
* @pd_list: list of all the power domains on the device
|
|
* @data: onecell data for genpd core
|
|
*/
|
|
struct ti_sci_genpd_provider {
|
|
const struct ti_sci_handle *ti_sci;
|
|
struct device *dev;
|
|
struct list_head pd_list;
|
|
struct genpd_onecell_data data;
|
|
};
|
|
|
|
/**
|
|
* struct ti_sci_pm_domain: TI specific data needed for power domain
|
|
* @idx: index of the device that identifies it with the system
|
|
* control processor.
|
|
* @exclusive: Permissions for exclusive request or shared request of the
|
|
* device.
|
|
* @pd: generic_pm_domain for use with the genpd framework
|
|
* @node: link for the genpd list
|
|
* @parent: link to the parent TI SCI genpd provider
|
|
*/
|
|
struct ti_sci_pm_domain {
|
|
int idx;
|
|
u8 exclusive;
|
|
struct generic_pm_domain pd;
|
|
struct list_head node;
|
|
struct ti_sci_genpd_provider *parent;
|
|
};
|
|
|
|
#define genpd_to_ti_sci_pd(gpd) container_of(gpd, struct ti_sci_pm_domain, pd)
|
|
|
|
/*
|
|
* ti_sci_pd_power_off(): genpd power down hook
|
|
* @domain: pointer to the powerdomain to power off
|
|
*/
|
|
static int ti_sci_pd_power_off(struct generic_pm_domain *domain)
|
|
{
|
|
struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain);
|
|
const struct ti_sci_handle *ti_sci = pd->parent->ti_sci;
|
|
|
|
return ti_sci->ops.dev_ops.put_device(ti_sci, pd->idx);
|
|
}
|
|
|
|
/*
|
|
* ti_sci_pd_power_on(): genpd power up hook
|
|
* @domain: pointer to the powerdomain to power on
|
|
*/
|
|
static int ti_sci_pd_power_on(struct generic_pm_domain *domain)
|
|
{
|
|
struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain);
|
|
const struct ti_sci_handle *ti_sci = pd->parent->ti_sci;
|
|
|
|
if (pd->exclusive)
|
|
return ti_sci->ops.dev_ops.get_device_exclusive(ti_sci,
|
|
pd->idx);
|
|
else
|
|
return ti_sci->ops.dev_ops.get_device(ti_sci, pd->idx);
|
|
}
|
|
|
|
/*
|
|
* ti_sci_pd_xlate(): translation service for TI SCI genpds
|
|
* @genpdspec: DT identification data for the genpd
|
|
* @data: genpd core data for all the powerdomains on the device
|
|
*/
|
|
static struct generic_pm_domain *ti_sci_pd_xlate(
|
|
struct of_phandle_args *genpdspec,
|
|
void *data)
|
|
{
|
|
struct genpd_onecell_data *genpd_data = data;
|
|
unsigned int idx = genpdspec->args[0];
|
|
|
|
if (genpdspec->args_count < 2)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
if (idx >= genpd_data->num_domains) {
|
|
pr_err("%s: invalid domain index %u\n", __func__, idx);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (!genpd_data->domains[idx])
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
genpd_to_ti_sci_pd(genpd_data->domains[idx])->exclusive =
|
|
genpdspec->args[1];
|
|
|
|
return genpd_data->domains[idx];
|
|
}
|
|
|
|
static const struct of_device_id ti_sci_pm_domain_matches[] = {
|
|
{ .compatible = "ti,sci-pm-domain", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ti_sci_pm_domain_matches);
|
|
|
|
static int ti_sci_pm_domain_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct ti_sci_genpd_provider *pd_provider;
|
|
struct ti_sci_pm_domain *pd;
|
|
struct device_node *np = NULL;
|
|
struct of_phandle_args args;
|
|
int ret;
|
|
u32 max_id = 0;
|
|
int index;
|
|
|
|
pd_provider = devm_kzalloc(dev, sizeof(*pd_provider), GFP_KERNEL);
|
|
if (!pd_provider)
|
|
return -ENOMEM;
|
|
|
|
pd_provider->ti_sci = devm_ti_sci_get_handle(dev);
|
|
if (IS_ERR(pd_provider->ti_sci))
|
|
return PTR_ERR(pd_provider->ti_sci);
|
|
|
|
pd_provider->dev = dev;
|
|
|
|
INIT_LIST_HEAD(&pd_provider->pd_list);
|
|
|
|
/* Find highest device ID used for power domains */
|
|
while (1) {
|
|
np = of_find_node_with_property(np, "power-domains");
|
|
if (!np)
|
|
break;
|
|
|
|
index = 0;
|
|
|
|
while (1) {
|
|
ret = of_parse_phandle_with_args(np, "power-domains",
|
|
"#power-domain-cells",
|
|
index, &args);
|
|
if (ret)
|
|
break;
|
|
|
|
if (args.args_count >= 1 && args.np == dev->of_node) {
|
|
if (args.args[0] > max_id)
|
|
max_id = args.args[0];
|
|
|
|
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
|
|
if (!pd)
|
|
return -ENOMEM;
|
|
|
|
pd->pd.name = devm_kasprintf(dev, GFP_KERNEL,
|
|
"pd:%d",
|
|
args.args[0]);
|
|
if (!pd->pd.name)
|
|
return -ENOMEM;
|
|
|
|
pd->pd.power_off = ti_sci_pd_power_off;
|
|
pd->pd.power_on = ti_sci_pd_power_on;
|
|
pd->idx = args.args[0];
|
|
pd->parent = pd_provider;
|
|
|
|
pm_genpd_init(&pd->pd, NULL, true);
|
|
|
|
list_add(&pd->node, &pd_provider->pd_list);
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
pd_provider->data.domains =
|
|
devm_kcalloc(dev, max_id + 1,
|
|
sizeof(*pd_provider->data.domains),
|
|
GFP_KERNEL);
|
|
|
|
pd_provider->data.num_domains = max_id + 1;
|
|
pd_provider->data.xlate = ti_sci_pd_xlate;
|
|
|
|
list_for_each_entry(pd, &pd_provider->pd_list, node)
|
|
pd_provider->data.domains[pd->idx] = &pd->pd;
|
|
|
|
return of_genpd_add_provider_onecell(dev->of_node, &pd_provider->data);
|
|
}
|
|
|
|
static struct platform_driver ti_sci_pm_domains_driver = {
|
|
.probe = ti_sci_pm_domain_probe,
|
|
.driver = {
|
|
.name = "ti_sci_pm_domains",
|
|
.of_match_table = ti_sci_pm_domain_matches,
|
|
},
|
|
};
|
|
module_platform_driver(ti_sci_pm_domains_driver);
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("TI System Control Interface (SCI) Power Domain driver");
|
|
MODULE_AUTHOR("Dave Gerlach");
|