1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * SCMI Generic power domain support. 4 * 5 * Copyright (C) 2018-2021 ARM Ltd. 6 */ 7 8 #include <linux/err.h> 9 #include <linux/io.h> 10 #include <linux/module.h> 11 #include <linux/pm_domain.h> 12 #include <linux/scmi_protocol.h> 13 14 static const struct scmi_power_proto_ops *power_ops; 15 16 struct scmi_pm_domain { 17 struct generic_pm_domain genpd; 18 const struct scmi_protocol_handle *ph; 19 const char *name; 20 u32 domain; 21 }; 22 23 #define to_scmi_pd(gpd) container_of(gpd, struct scmi_pm_domain, genpd) 24 25 static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on) 26 { 27 int ret; 28 u32 state, ret_state; 29 struct scmi_pm_domain *pd = to_scmi_pd(domain); 30 31 if (power_on) 32 state = SCMI_POWER_STATE_GENERIC_ON; 33 else 34 state = SCMI_POWER_STATE_GENERIC_OFF; 35 36 ret = power_ops->state_set(pd->ph, pd->domain, state); 37 if (!ret) 38 ret = power_ops->state_get(pd->ph, pd->domain, &ret_state); 39 if (!ret && state != ret_state) 40 return -EIO; 41 42 return ret; 43 } 44 45 static int scmi_pd_power_on(struct generic_pm_domain *domain) 46 { 47 return scmi_pd_power(domain, true); 48 } 49 50 static int scmi_pd_power_off(struct generic_pm_domain *domain) 51 { 52 return scmi_pd_power(domain, false); 53 } 54 55 static int scmi_pm_domain_probe(struct scmi_device *sdev) 56 { 57 int num_domains, i; 58 struct device *dev = &sdev->dev; 59 struct device_node *np = dev->of_node; 60 struct scmi_pm_domain *scmi_pd; 61 struct genpd_onecell_data *scmi_pd_data; 62 struct generic_pm_domain **domains; 63 const struct scmi_handle *handle = sdev->handle; 64 struct scmi_protocol_handle *ph; 65 66 if (!handle) 67 return -ENODEV; 68 69 power_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_POWER, &ph); 70 if (IS_ERR(power_ops)) 71 return PTR_ERR(power_ops); 72 73 num_domains = power_ops->num_domains_get(ph); 74 if (num_domains < 0) { 75 dev_err(dev, "number of domains not found\n"); 76 return num_domains; 77 } 78 79 scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL); 80 if (!scmi_pd) 81 return -ENOMEM; 82 83 scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL); 84 if (!scmi_pd_data) 85 return -ENOMEM; 86 87 domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL); 88 if (!domains) 89 return -ENOMEM; 90 91 for (i = 0; i < num_domains; i++, scmi_pd++) { 92 u32 state; 93 94 if (power_ops->state_get(ph, i, &state)) { 95 dev_warn(dev, "failed to get state for domain %d\n", i); 96 continue; 97 } 98 99 scmi_pd->domain = i; 100 scmi_pd->ph = ph; 101 scmi_pd->name = power_ops->name_get(ph, i); 102 scmi_pd->genpd.name = scmi_pd->name; 103 scmi_pd->genpd.power_off = scmi_pd_power_off; 104 scmi_pd->genpd.power_on = scmi_pd_power_on; 105 106 pm_genpd_init(&scmi_pd->genpd, NULL, 107 state == SCMI_POWER_STATE_GENERIC_OFF); 108 109 domains[i] = &scmi_pd->genpd; 110 } 111 112 scmi_pd_data->domains = domains; 113 scmi_pd_data->num_domains = num_domains; 114 115 dev_set_drvdata(dev, scmi_pd_data); 116 117 return of_genpd_add_provider_onecell(np, scmi_pd_data); 118 } 119 120 static void scmi_pm_domain_remove(struct scmi_device *sdev) 121 { 122 int i; 123 struct genpd_onecell_data *scmi_pd_data; 124 struct device *dev = &sdev->dev; 125 struct device_node *np = dev->of_node; 126 127 of_genpd_del_provider(np); 128 129 scmi_pd_data = dev_get_drvdata(dev); 130 for (i = 0; i < scmi_pd_data->num_domains; i++) { 131 if (!scmi_pd_data->domains[i]) 132 continue; 133 pm_genpd_remove(scmi_pd_data->domains[i]); 134 } 135 } 136 137 static const struct scmi_device_id scmi_id_table[] = { 138 { SCMI_PROTOCOL_POWER, "genpd" }, 139 { }, 140 }; 141 MODULE_DEVICE_TABLE(scmi, scmi_id_table); 142 143 static struct scmi_driver scmi_power_domain_driver = { 144 .name = "scmi-power-domain", 145 .probe = scmi_pm_domain_probe, 146 .remove = scmi_pm_domain_remove, 147 .id_table = scmi_id_table, 148 }; 149 module_scmi_driver(scmi_power_domain_driver); 150 151 MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); 152 MODULE_DESCRIPTION("ARM SCMI power domain driver"); 153 MODULE_LICENSE("GPL v2"); 154