18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * TI SCI Generic Power Domain Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/
68c2ecf20Sopenharmony_ci *	J Keerthy <j-keerthy@ti.com>
78c2ecf20Sopenharmony_ci *	Dave Gerlach <d-gerlach@ti.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/err.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/of.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/pm_domain.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci#include <linux/soc/ti/ti_sci_protocol.h>
178c2ecf20Sopenharmony_ci#include <dt-bindings/soc/ti,sci_pm_domain.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/**
208c2ecf20Sopenharmony_ci * struct ti_sci_genpd_provider: holds common TI SCI genpd provider data
218c2ecf20Sopenharmony_ci * @ti_sci: handle to TI SCI protocol driver that provides ops to
228c2ecf20Sopenharmony_ci *	    communicate with system control processor.
238c2ecf20Sopenharmony_ci * @dev: pointer to dev for the driver for devm allocs
248c2ecf20Sopenharmony_ci * @pd_list: list of all the power domains on the device
258c2ecf20Sopenharmony_ci * @data: onecell data for genpd core
268c2ecf20Sopenharmony_ci */
278c2ecf20Sopenharmony_cistruct ti_sci_genpd_provider {
288c2ecf20Sopenharmony_ci	const struct ti_sci_handle *ti_sci;
298c2ecf20Sopenharmony_ci	struct device *dev;
308c2ecf20Sopenharmony_ci	struct list_head pd_list;
318c2ecf20Sopenharmony_ci	struct genpd_onecell_data data;
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/**
358c2ecf20Sopenharmony_ci * struct ti_sci_pm_domain: TI specific data needed for power domain
368c2ecf20Sopenharmony_ci * @idx: index of the device that identifies it with the system
378c2ecf20Sopenharmony_ci *	 control processor.
388c2ecf20Sopenharmony_ci * @exclusive: Permissions for exclusive request or shared request of the
398c2ecf20Sopenharmony_ci *	       device.
408c2ecf20Sopenharmony_ci * @pd: generic_pm_domain for use with the genpd framework
418c2ecf20Sopenharmony_ci * @node: link for the genpd list
428c2ecf20Sopenharmony_ci * @parent: link to the parent TI SCI genpd provider
438c2ecf20Sopenharmony_ci */
448c2ecf20Sopenharmony_cistruct ti_sci_pm_domain {
458c2ecf20Sopenharmony_ci	int idx;
468c2ecf20Sopenharmony_ci	u8 exclusive;
478c2ecf20Sopenharmony_ci	struct generic_pm_domain pd;
488c2ecf20Sopenharmony_ci	struct list_head node;
498c2ecf20Sopenharmony_ci	struct ti_sci_genpd_provider *parent;
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci#define genpd_to_ti_sci_pd(gpd) container_of(gpd, struct ti_sci_pm_domain, pd)
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/*
558c2ecf20Sopenharmony_ci * ti_sci_pd_power_off(): genpd power down hook
568c2ecf20Sopenharmony_ci * @domain: pointer to the powerdomain to power off
578c2ecf20Sopenharmony_ci */
588c2ecf20Sopenharmony_cistatic int ti_sci_pd_power_off(struct generic_pm_domain *domain)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain);
618c2ecf20Sopenharmony_ci	const struct ti_sci_handle *ti_sci = pd->parent->ti_sci;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	return ti_sci->ops.dev_ops.put_device(ti_sci, pd->idx);
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci/*
678c2ecf20Sopenharmony_ci * ti_sci_pd_power_on(): genpd power up hook
688c2ecf20Sopenharmony_ci * @domain: pointer to the powerdomain to power on
698c2ecf20Sopenharmony_ci */
708c2ecf20Sopenharmony_cistatic int ti_sci_pd_power_on(struct generic_pm_domain *domain)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain);
738c2ecf20Sopenharmony_ci	const struct ti_sci_handle *ti_sci = pd->parent->ti_sci;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	if (pd->exclusive)
768c2ecf20Sopenharmony_ci		return ti_sci->ops.dev_ops.get_device_exclusive(ti_sci,
778c2ecf20Sopenharmony_ci								pd->idx);
788c2ecf20Sopenharmony_ci	else
798c2ecf20Sopenharmony_ci		return ti_sci->ops.dev_ops.get_device(ti_sci, pd->idx);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci/*
838c2ecf20Sopenharmony_ci * ti_sci_pd_xlate(): translation service for TI SCI genpds
848c2ecf20Sopenharmony_ci * @genpdspec: DT identification data for the genpd
858c2ecf20Sopenharmony_ci * @data: genpd core data for all the powerdomains on the device
868c2ecf20Sopenharmony_ci */
878c2ecf20Sopenharmony_cistatic struct generic_pm_domain *ti_sci_pd_xlate(
888c2ecf20Sopenharmony_ci					struct of_phandle_args *genpdspec,
898c2ecf20Sopenharmony_ci					void *data)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct genpd_onecell_data *genpd_data = data;
928c2ecf20Sopenharmony_ci	unsigned int idx = genpdspec->args[0];
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	if (genpdspec->args_count != 1 && genpdspec->args_count != 2)
958c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (idx >= genpd_data->num_domains) {
988c2ecf20Sopenharmony_ci		pr_err("%s: invalid domain index %u\n", __func__, idx);
998c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	if (!genpd_data->domains[idx])
1038c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOENT);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	genpd_to_ti_sci_pd(genpd_data->domains[idx])->exclusive =
1068c2ecf20Sopenharmony_ci		genpdspec->args[1];
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	return genpd_data->domains[idx];
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic const struct of_device_id ti_sci_pm_domain_matches[] = {
1128c2ecf20Sopenharmony_ci	{ .compatible = "ti,sci-pm-domain", },
1138c2ecf20Sopenharmony_ci	{ },
1148c2ecf20Sopenharmony_ci};
1158c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ti_sci_pm_domain_matches);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic int ti_sci_pm_domain_probe(struct platform_device *pdev)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1208c2ecf20Sopenharmony_ci	struct ti_sci_genpd_provider *pd_provider;
1218c2ecf20Sopenharmony_ci	struct ti_sci_pm_domain *pd;
1228c2ecf20Sopenharmony_ci	struct device_node *np = NULL;
1238c2ecf20Sopenharmony_ci	struct of_phandle_args args;
1248c2ecf20Sopenharmony_ci	int ret;
1258c2ecf20Sopenharmony_ci	u32 max_id = 0;
1268c2ecf20Sopenharmony_ci	int index;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	pd_provider = devm_kzalloc(dev, sizeof(*pd_provider), GFP_KERNEL);
1298c2ecf20Sopenharmony_ci	if (!pd_provider)
1308c2ecf20Sopenharmony_ci		return -ENOMEM;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	pd_provider->ti_sci = devm_ti_sci_get_handle(dev);
1338c2ecf20Sopenharmony_ci	if (IS_ERR(pd_provider->ti_sci))
1348c2ecf20Sopenharmony_ci		return PTR_ERR(pd_provider->ti_sci);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	pd_provider->dev = dev;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&pd_provider->pd_list);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* Find highest device ID used for power domains */
1418c2ecf20Sopenharmony_ci	while (1) {
1428c2ecf20Sopenharmony_ci		np = of_find_node_with_property(np, "power-domains");
1438c2ecf20Sopenharmony_ci		if (!np)
1448c2ecf20Sopenharmony_ci			break;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci		index = 0;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci		while (1) {
1498c2ecf20Sopenharmony_ci			ret = of_parse_phandle_with_args(np, "power-domains",
1508c2ecf20Sopenharmony_ci							 "#power-domain-cells",
1518c2ecf20Sopenharmony_ci							 index, &args);
1528c2ecf20Sopenharmony_ci			if (ret)
1538c2ecf20Sopenharmony_ci				break;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci			if (args.args_count >= 1 && args.np == dev->of_node) {
1568c2ecf20Sopenharmony_ci				if (args.args[0] > max_id)
1578c2ecf20Sopenharmony_ci					max_id = args.args[0];
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci				pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
1608c2ecf20Sopenharmony_ci				if (!pd)
1618c2ecf20Sopenharmony_ci					return -ENOMEM;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci				pd->pd.name = devm_kasprintf(dev, GFP_KERNEL,
1648c2ecf20Sopenharmony_ci							     "pd:%d",
1658c2ecf20Sopenharmony_ci							     args.args[0]);
1668c2ecf20Sopenharmony_ci				if (!pd->pd.name)
1678c2ecf20Sopenharmony_ci					return -ENOMEM;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci				pd->pd.power_off = ti_sci_pd_power_off;
1708c2ecf20Sopenharmony_ci				pd->pd.power_on = ti_sci_pd_power_on;
1718c2ecf20Sopenharmony_ci				pd->idx = args.args[0];
1728c2ecf20Sopenharmony_ci				pd->parent = pd_provider;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci				pm_genpd_init(&pd->pd, NULL, true);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci				list_add(&pd->node, &pd_provider->pd_list);
1778c2ecf20Sopenharmony_ci			}
1788c2ecf20Sopenharmony_ci			index++;
1798c2ecf20Sopenharmony_ci		}
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	pd_provider->data.domains =
1838c2ecf20Sopenharmony_ci		devm_kcalloc(dev, max_id + 1,
1848c2ecf20Sopenharmony_ci			     sizeof(*pd_provider->data.domains),
1858c2ecf20Sopenharmony_ci			     GFP_KERNEL);
1868c2ecf20Sopenharmony_ci	if (!pd_provider->data.domains)
1878c2ecf20Sopenharmony_ci		return -ENOMEM;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	pd_provider->data.num_domains = max_id + 1;
1908c2ecf20Sopenharmony_ci	pd_provider->data.xlate = ti_sci_pd_xlate;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	list_for_each_entry(pd, &pd_provider->pd_list, node)
1938c2ecf20Sopenharmony_ci		pd_provider->data.domains[pd->idx] = &pd->pd;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	return of_genpd_add_provider_onecell(dev->of_node, &pd_provider->data);
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic struct platform_driver ti_sci_pm_domains_driver = {
1998c2ecf20Sopenharmony_ci	.probe = ti_sci_pm_domain_probe,
2008c2ecf20Sopenharmony_ci	.driver = {
2018c2ecf20Sopenharmony_ci		.name = "ti_sci_pm_domains",
2028c2ecf20Sopenharmony_ci		.of_match_table = ti_sci_pm_domain_matches,
2038c2ecf20Sopenharmony_ci	},
2048c2ecf20Sopenharmony_ci};
2058c2ecf20Sopenharmony_cimodule_platform_driver(ti_sci_pm_domains_driver);
2068c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2078c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TI System Control Interface (SCI) Power Domain driver");
2088c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dave Gerlach");
209