162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * TI SCI Generic Power Domain Driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/
662306a36Sopenharmony_ci *	J Keerthy <j-keerthy@ti.com>
762306a36Sopenharmony_ci *	Dave Gerlach <d-gerlach@ti.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/err.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/pm_domain.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/soc/ti/ti_sci_protocol.h>
1762306a36Sopenharmony_ci#include <dt-bindings/soc/ti,sci_pm_domain.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/**
2062306a36Sopenharmony_ci * struct ti_sci_genpd_provider: holds common TI SCI genpd provider data
2162306a36Sopenharmony_ci * @ti_sci: handle to TI SCI protocol driver that provides ops to
2262306a36Sopenharmony_ci *	    communicate with system control processor.
2362306a36Sopenharmony_ci * @dev: pointer to dev for the driver for devm allocs
2462306a36Sopenharmony_ci * @pd_list: list of all the power domains on the device
2562306a36Sopenharmony_ci * @data: onecell data for genpd core
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_cistruct ti_sci_genpd_provider {
2862306a36Sopenharmony_ci	const struct ti_sci_handle *ti_sci;
2962306a36Sopenharmony_ci	struct device *dev;
3062306a36Sopenharmony_ci	struct list_head pd_list;
3162306a36Sopenharmony_ci	struct genpd_onecell_data data;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/**
3562306a36Sopenharmony_ci * struct ti_sci_pm_domain: TI specific data needed for power domain
3662306a36Sopenharmony_ci * @idx: index of the device that identifies it with the system
3762306a36Sopenharmony_ci *	 control processor.
3862306a36Sopenharmony_ci * @exclusive: Permissions for exclusive request or shared request of the
3962306a36Sopenharmony_ci *	       device.
4062306a36Sopenharmony_ci * @pd: generic_pm_domain for use with the genpd framework
4162306a36Sopenharmony_ci * @node: link for the genpd list
4262306a36Sopenharmony_ci * @parent: link to the parent TI SCI genpd provider
4362306a36Sopenharmony_ci */
4462306a36Sopenharmony_cistruct ti_sci_pm_domain {
4562306a36Sopenharmony_ci	int idx;
4662306a36Sopenharmony_ci	u8 exclusive;
4762306a36Sopenharmony_ci	struct generic_pm_domain pd;
4862306a36Sopenharmony_ci	struct list_head node;
4962306a36Sopenharmony_ci	struct ti_sci_genpd_provider *parent;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define genpd_to_ti_sci_pd(gpd) container_of(gpd, struct ti_sci_pm_domain, pd)
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/*
5562306a36Sopenharmony_ci * ti_sci_pd_power_off(): genpd power down hook
5662306a36Sopenharmony_ci * @domain: pointer to the powerdomain to power off
5762306a36Sopenharmony_ci */
5862306a36Sopenharmony_cistatic int ti_sci_pd_power_off(struct generic_pm_domain *domain)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain);
6162306a36Sopenharmony_ci	const struct ti_sci_handle *ti_sci = pd->parent->ti_sci;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	return ti_sci->ops.dev_ops.put_device(ti_sci, pd->idx);
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/*
6762306a36Sopenharmony_ci * ti_sci_pd_power_on(): genpd power up hook
6862306a36Sopenharmony_ci * @domain: pointer to the powerdomain to power on
6962306a36Sopenharmony_ci */
7062306a36Sopenharmony_cistatic int ti_sci_pd_power_on(struct generic_pm_domain *domain)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain);
7362306a36Sopenharmony_ci	const struct ti_sci_handle *ti_sci = pd->parent->ti_sci;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (pd->exclusive)
7662306a36Sopenharmony_ci		return ti_sci->ops.dev_ops.get_device_exclusive(ti_sci,
7762306a36Sopenharmony_ci								pd->idx);
7862306a36Sopenharmony_ci	else
7962306a36Sopenharmony_ci		return ti_sci->ops.dev_ops.get_device(ti_sci, pd->idx);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci/*
8362306a36Sopenharmony_ci * ti_sci_pd_xlate(): translation service for TI SCI genpds
8462306a36Sopenharmony_ci * @genpdspec: DT identification data for the genpd
8562306a36Sopenharmony_ci * @data: genpd core data for all the powerdomains on the device
8662306a36Sopenharmony_ci */
8762306a36Sopenharmony_cistatic struct generic_pm_domain *ti_sci_pd_xlate(
8862306a36Sopenharmony_ci					struct of_phandle_args *genpdspec,
8962306a36Sopenharmony_ci					void *data)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct genpd_onecell_data *genpd_data = data;
9262306a36Sopenharmony_ci	unsigned int idx = genpdspec->args[0];
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (genpdspec->args_count != 1 && genpdspec->args_count != 2)
9562306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (idx >= genpd_data->num_domains) {
9862306a36Sopenharmony_ci		pr_err("%s: invalid domain index %u\n", __func__, idx);
9962306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (!genpd_data->domains[idx])
10362306a36Sopenharmony_ci		return ERR_PTR(-ENOENT);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	genpd_to_ti_sci_pd(genpd_data->domains[idx])->exclusive =
10662306a36Sopenharmony_ci		genpdspec->args[1];
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return genpd_data->domains[idx];
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic const struct of_device_id ti_sci_pm_domain_matches[] = {
11262306a36Sopenharmony_ci	{ .compatible = "ti,sci-pm-domain", },
11362306a36Sopenharmony_ci	{ },
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ti_sci_pm_domain_matches);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic int ti_sci_pm_domain_probe(struct platform_device *pdev)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
12062306a36Sopenharmony_ci	struct ti_sci_genpd_provider *pd_provider;
12162306a36Sopenharmony_ci	struct ti_sci_pm_domain *pd;
12262306a36Sopenharmony_ci	struct device_node *np;
12362306a36Sopenharmony_ci	struct of_phandle_args args;
12462306a36Sopenharmony_ci	int ret;
12562306a36Sopenharmony_ci	u32 max_id = 0;
12662306a36Sopenharmony_ci	int index;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	pd_provider = devm_kzalloc(dev, sizeof(*pd_provider), GFP_KERNEL);
12962306a36Sopenharmony_ci	if (!pd_provider)
13062306a36Sopenharmony_ci		return -ENOMEM;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	pd_provider->ti_sci = devm_ti_sci_get_handle(dev);
13362306a36Sopenharmony_ci	if (IS_ERR(pd_provider->ti_sci))
13462306a36Sopenharmony_ci		return PTR_ERR(pd_provider->ti_sci);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	pd_provider->dev = dev;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	INIT_LIST_HEAD(&pd_provider->pd_list);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/* Find highest device ID used for power domains */
14162306a36Sopenharmony_ci	for_each_node_with_property(np, "power-domains") {
14262306a36Sopenharmony_ci		index = 0;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		while (1) {
14562306a36Sopenharmony_ci			ret = of_parse_phandle_with_args(np, "power-domains",
14662306a36Sopenharmony_ci							 "#power-domain-cells",
14762306a36Sopenharmony_ci							 index, &args);
14862306a36Sopenharmony_ci			if (ret)
14962306a36Sopenharmony_ci				break;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci			if (args.args_count >= 1 && args.np == dev->of_node) {
15262306a36Sopenharmony_ci				if (args.args[0] > max_id)
15362306a36Sopenharmony_ci					max_id = args.args[0];
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci				pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
15662306a36Sopenharmony_ci				if (!pd)
15762306a36Sopenharmony_ci					return -ENOMEM;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci				pd->pd.name = devm_kasprintf(dev, GFP_KERNEL,
16062306a36Sopenharmony_ci							     "pd:%d",
16162306a36Sopenharmony_ci							     args.args[0]);
16262306a36Sopenharmony_ci				if (!pd->pd.name)
16362306a36Sopenharmony_ci					return -ENOMEM;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci				pd->pd.power_off = ti_sci_pd_power_off;
16662306a36Sopenharmony_ci				pd->pd.power_on = ti_sci_pd_power_on;
16762306a36Sopenharmony_ci				pd->idx = args.args[0];
16862306a36Sopenharmony_ci				pd->parent = pd_provider;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci				pm_genpd_init(&pd->pd, NULL, true);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci				list_add(&pd->node, &pd_provider->pd_list);
17362306a36Sopenharmony_ci			}
17462306a36Sopenharmony_ci			index++;
17562306a36Sopenharmony_ci		}
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	pd_provider->data.domains =
17962306a36Sopenharmony_ci		devm_kcalloc(dev, max_id + 1,
18062306a36Sopenharmony_ci			     sizeof(*pd_provider->data.domains),
18162306a36Sopenharmony_ci			     GFP_KERNEL);
18262306a36Sopenharmony_ci	if (!pd_provider->data.domains)
18362306a36Sopenharmony_ci		return -ENOMEM;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	pd_provider->data.num_domains = max_id + 1;
18662306a36Sopenharmony_ci	pd_provider->data.xlate = ti_sci_pd_xlate;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	list_for_each_entry(pd, &pd_provider->pd_list, node)
18962306a36Sopenharmony_ci		pd_provider->data.domains[pd->idx] = &pd->pd;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	return of_genpd_add_provider_onecell(dev->of_node, &pd_provider->data);
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic struct platform_driver ti_sci_pm_domains_driver = {
19562306a36Sopenharmony_ci	.probe = ti_sci_pm_domain_probe,
19662306a36Sopenharmony_ci	.driver = {
19762306a36Sopenharmony_ci		.name = "ti_sci_pm_domains",
19862306a36Sopenharmony_ci		.of_match_table = ti_sci_pm_domain_matches,
19962306a36Sopenharmony_ci	},
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_cimodule_platform_driver(ti_sci_pm_domains_driver);
20262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
20362306a36Sopenharmony_ciMODULE_DESCRIPTION("TI System Control Interface (SCI) Power Domain driver");
20462306a36Sopenharmony_ciMODULE_AUTHOR("Dave Gerlach");
205