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 const 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 bool ti_sci_pm_idx_exists(struct ti_sci_genpd_provider *pd_provider, u32 idx) 118 { 119 struct ti_sci_pm_domain *pd; 120 121 list_for_each_entry(pd, &pd_provider->pd_list, node) { 122 if (pd->idx == idx) 123 return true; 124 } 125 126 return false; 127 } 128 129 static int ti_sci_pm_domain_probe(struct platform_device *pdev) 130 { 131 struct device *dev = &pdev->dev; 132 struct ti_sci_genpd_provider *pd_provider; 133 struct ti_sci_pm_domain *pd; 134 struct device_node *np; 135 struct of_phandle_args args; 136 int ret; 137 u32 max_id = 0; 138 int index; 139 140 pd_provider = devm_kzalloc(dev, sizeof(*pd_provider), GFP_KERNEL); 141 if (!pd_provider) 142 return -ENOMEM; 143 144 pd_provider->ti_sci = devm_ti_sci_get_handle(dev); 145 if (IS_ERR(pd_provider->ti_sci)) 146 return PTR_ERR(pd_provider->ti_sci); 147 148 pd_provider->dev = dev; 149 150 INIT_LIST_HEAD(&pd_provider->pd_list); 151 152 /* Find highest device ID used for power domains */ 153 for_each_node_with_property(np, "power-domains") { 154 index = 0; 155 156 while (1) { 157 ret = of_parse_phandle_with_args(np, "power-domains", 158 "#power-domain-cells", 159 index, &args); 160 if (ret) 161 break; 162 163 if (args.args_count >= 1 && args.np == dev->of_node) { 164 if (args.args[0] > max_id) { 165 max_id = args.args[0]; 166 } else { 167 if (ti_sci_pm_idx_exists(pd_provider, args.args[0])) { 168 index++; 169 continue; 170 } 171 } 172 173 pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); 174 if (!pd) { 175 of_node_put(np); 176 return -ENOMEM; 177 } 178 179 pd->pd.name = devm_kasprintf(dev, GFP_KERNEL, 180 "pd:%d", 181 args.args[0]); 182 if (!pd->pd.name) { 183 of_node_put(np); 184 return -ENOMEM; 185 } 186 187 pd->pd.power_off = ti_sci_pd_power_off; 188 pd->pd.power_on = ti_sci_pd_power_on; 189 pd->idx = args.args[0]; 190 pd->parent = pd_provider; 191 192 pm_genpd_init(&pd->pd, NULL, true); 193 194 list_add(&pd->node, &pd_provider->pd_list); 195 } 196 index++; 197 } 198 } 199 200 pd_provider->data.domains = 201 devm_kcalloc(dev, max_id + 1, 202 sizeof(*pd_provider->data.domains), 203 GFP_KERNEL); 204 if (!pd_provider->data.domains) 205 return -ENOMEM; 206 207 pd_provider->data.num_domains = max_id + 1; 208 pd_provider->data.xlate = ti_sci_pd_xlate; 209 210 list_for_each_entry(pd, &pd_provider->pd_list, node) 211 pd_provider->data.domains[pd->idx] = &pd->pd; 212 213 return of_genpd_add_provider_onecell(dev->of_node, &pd_provider->data); 214 } 215 216 static struct platform_driver ti_sci_pm_domains_driver = { 217 .probe = ti_sci_pm_domain_probe, 218 .driver = { 219 .name = "ti_sci_pm_domains", 220 .of_match_table = ti_sci_pm_domain_matches, 221 }, 222 }; 223 module_platform_driver(ti_sci_pm_domains_driver); 224 MODULE_LICENSE("GPL v2"); 225 MODULE_DESCRIPTION("TI System Control Interface (SCI) Power Domain driver"); 226 MODULE_AUTHOR("Dave Gerlach"); 227