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