1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * TI SCI Generic Power Domain Driver 4 * 5 * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/ 6 * J Keerthy <j-keerthy@ti.com> 7 * Dave Gerlach <d-gerlach@ti.com> 8 */ 9 10 #include <linux/err.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/platform_device.h> 14 #include <linux/pm_domain.h> 15 #include <linux/slab.h> 16 #include <linux/soc/ti/ti_sci_protocol.h> 17 #include <dt-bindings/soc/ti,sci_pm_domain.h> 18 19 /** 20 * struct ti_sci_genpd_provider: holds common TI SCI genpd provider data 21 * @ti_sci: handle to TI SCI protocol driver that provides ops to 22 * communicate with system control processor. 23 * @dev: pointer to dev for the driver for devm allocs 24 * @pd_list: list of all the power domains on the device 25 * @data: onecell data for genpd core 26 */ 27 struct ti_sci_genpd_provider { 28 const struct ti_sci_handle *ti_sci; 29 struct device *dev; 30 struct list_head pd_list; 31 struct genpd_onecell_data data; 32 }; 33 34 /** 35 * struct ti_sci_pm_domain: TI specific data needed for power domain 36 * @idx: index of the device that identifies it with the system 37 * control processor. 38 * @exclusive: Permissions for exclusive request or shared request of the 39 * device. 40 * @pd: generic_pm_domain for use with the genpd framework 41 * @node: link for the genpd list 42 * @parent: link to the parent TI SCI genpd provider 43 */ 44 struct ti_sci_pm_domain { 45 int idx; 46 u8 exclusive; 47 struct generic_pm_domain pd; 48 struct list_head node; 49 struct ti_sci_genpd_provider *parent; 50 }; 51 52 #define genpd_to_ti_sci_pd(gpd) container_of(gpd, struct ti_sci_pm_domain, pd) 53 54 /* 55 * ti_sci_pd_power_off(): genpd power down hook 56 * @domain: pointer to the powerdomain to power off 57 */ 58 static int ti_sci_pd_power_off(struct generic_pm_domain *domain) 59 { 60 struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain); 61 const struct ti_sci_handle *ti_sci = pd->parent->ti_sci; 62 63 return ti_sci->ops.dev_ops.put_device(ti_sci, pd->idx); 64 } 65 66 /* 67 * ti_sci_pd_power_on(): genpd power up hook 68 * @domain: pointer to the powerdomain to power on 69 */ 70 static int ti_sci_pd_power_on(struct generic_pm_domain *domain) 71 { 72 struct ti_sci_pm_domain *pd = genpd_to_ti_sci_pd(domain); 73 const struct ti_sci_handle *ti_sci = pd->parent->ti_sci; 74 75 if (pd->exclusive) 76 return ti_sci->ops.dev_ops.get_device_exclusive(ti_sci, 77 pd->idx); 78 else 79 return ti_sci->ops.dev_ops.get_device(ti_sci, pd->idx); 80 } 81 82 /* 83 * ti_sci_pd_xlate(): translation service for TI SCI genpds 84 * @genpdspec: DT identification data for the genpd 85 * @data: genpd core data for all the powerdomains on the device 86 */ 87 static struct generic_pm_domain *ti_sci_pd_xlate( 88 struct of_phandle_args *genpdspec, 89 void *data) 90 { 91 struct genpd_onecell_data *genpd_data = data; 92 unsigned int idx = genpdspec->args[0]; 93 94 if (genpdspec->args_count != 1 && genpdspec->args_count != 2) 95 return ERR_PTR(-EINVAL); 96 97 if (idx >= genpd_data->num_domains) { 98 pr_err("%s: invalid domain index %u\n", __func__, idx); 99 return ERR_PTR(-EINVAL); 100 } 101 102 if (!genpd_data->domains[idx]) 103 return ERR_PTR(-ENOENT); 104 105 genpd_to_ti_sci_pd(genpd_data->domains[idx])->exclusive = 106 genpdspec->args[1]; 107 108 return genpd_data->domains[idx]; 109 } 110 111 static const struct of_device_id ti_sci_pm_domain_matches[] = { 112 { .compatible = "ti,sci-pm-domain", }, 113 { }, 114 }; 115 MODULE_DEVICE_TABLE(of, ti_sci_pm_domain_matches); 116 117 static int ti_sci_pm_domain_probe(struct platform_device *pdev) 118 { 119 struct device *dev = &pdev->dev; 120 struct ti_sci_genpd_provider *pd_provider; 121 struct ti_sci_pm_domain *pd; 122 struct device_node *np; 123 struct of_phandle_args args; 124 int ret; 125 u32 max_id = 0; 126 int index; 127 128 pd_provider = devm_kzalloc(dev, sizeof(*pd_provider), GFP_KERNEL); 129 if (!pd_provider) 130 return -ENOMEM; 131 132 pd_provider->ti_sci = devm_ti_sci_get_handle(dev); 133 if (IS_ERR(pd_provider->ti_sci)) 134 return PTR_ERR(pd_provider->ti_sci); 135 136 pd_provider->dev = dev; 137 138 INIT_LIST_HEAD(&pd_provider->pd_list); 139 140 /* Find highest device ID used for power domains */ 141 for_each_node_with_property(np, "power-domains") { 142 index = 0; 143 144 while (1) { 145 ret = of_parse_phandle_with_args(np, "power-domains", 146 "#power-domain-cells", 147 index, &args); 148 if (ret) 149 break; 150 151 if (args.args_count >= 1 && args.np == dev->of_node) { 152 if (args.args[0] > max_id) 153 max_id = args.args[0]; 154 155 pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); 156 if (!pd) { 157 of_node_put(np); 158 return -ENOMEM; 159 } 160 161 pd->pd.name = devm_kasprintf(dev, GFP_KERNEL, 162 "pd:%d", 163 args.args[0]); 164 if (!pd->pd.name) { 165 of_node_put(np); 166 return -ENOMEM; 167 } 168 169 pd->pd.power_off = ti_sci_pd_power_off; 170 pd->pd.power_on = ti_sci_pd_power_on; 171 pd->idx = args.args[0]; 172 pd->parent = pd_provider; 173 174 pm_genpd_init(&pd->pd, NULL, true); 175 176 list_add(&pd->node, &pd_provider->pd_list); 177 } 178 index++; 179 } 180 } 181 182 pd_provider->data.domains = 183 devm_kcalloc(dev, max_id + 1, 184 sizeof(*pd_provider->data.domains), 185 GFP_KERNEL); 186 if (!pd_provider->data.domains) 187 return -ENOMEM; 188 189 pd_provider->data.num_domains = max_id + 1; 190 pd_provider->data.xlate = ti_sci_pd_xlate; 191 192 list_for_each_entry(pd, &pd_provider->pd_list, node) 193 pd_provider->data.domains[pd->idx] = &pd->pd; 194 195 return of_genpd_add_provider_onecell(dev->of_node, &pd_provider->data); 196 } 197 198 static struct platform_driver ti_sci_pm_domains_driver = { 199 .probe = ti_sci_pm_domain_probe, 200 .driver = { 201 .name = "ti_sci_pm_domains", 202 .of_match_table = ti_sci_pm_domain_matches, 203 }, 204 }; 205 module_platform_driver(ti_sci_pm_domains_driver); 206 MODULE_LICENSE("GPL v2"); 207 MODULE_DESCRIPTION("TI System Control Interface (SCI) Power Domain driver"); 208 MODULE_AUTHOR("Dave Gerlach"); 209