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