diff options
Diffstat (limited to 'drivers/pmdomain/core.c')
-rw-r--r-- | drivers/pmdomain/core.c | 254 |
1 files changed, 234 insertions, 20 deletions
diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index ff5c7f2b69ce..0006ab3d0789 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -27,6 +27,16 @@ /* Provides a unique ID for each genpd device */ static DEFINE_IDA(genpd_ida); +/* The bus for genpd_providers. */ +static const struct bus_type genpd_provider_bus_type = { + .name = "genpd_provider", +}; + +/* The parent for genpd_provider devices. */ +static struct device genpd_provider_bus = { + .init_name = "genpd_provider", +}; + #define GENPD_RETRY_MAX_MS 250 /* Approximate */ #define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \ @@ -176,6 +186,7 @@ static const struct genpd_lock_ops genpd_raw_spin_ops = { #define genpd_is_rpm_always_on(genpd) (genpd->flags & GENPD_FLAG_RPM_ALWAYS_ON) #define genpd_is_opp_table_fw(genpd) (genpd->flags & GENPD_FLAG_OPP_TABLE_FW) #define genpd_is_dev_name_fw(genpd) (genpd->flags & GENPD_FLAG_DEV_NAME_FW) +#define genpd_is_no_sync_state(genpd) (genpd->flags & GENPD_FLAG_NO_SYNC_STATE) static inline bool irq_safe_dev_in_sleep_domain(struct device *dev, const struct generic_pm_domain *genpd) @@ -759,6 +770,39 @@ int dev_pm_genpd_rpm_always_on(struct device *dev, bool on) EXPORT_SYMBOL_GPL(dev_pm_genpd_rpm_always_on); /** + * dev_pm_genpd_is_on() - Get device's current power domain status + * + * @dev: Device to get the current power status + * + * This function checks whether the generic power domain associated with the + * given device is on or not by verifying if genpd_status_on equals + * GENPD_STATE_ON. + * + * Note: this function returns the power status of the genpd at the time of the + * call. The power status may change after due to activity from other devices + * sharing the same genpd. Therefore, this information should not be relied for + * long-term decisions about the device power state. + * + * Return: 'true' if the device's power domain is on, 'false' otherwise. + */ +bool dev_pm_genpd_is_on(struct device *dev) +{ + struct generic_pm_domain *genpd; + bool is_on; + + genpd = dev_to_genpd_safe(dev); + if (!genpd) + return false; + + genpd_lock(genpd); + is_on = genpd_status_on(genpd); + genpd_unlock(genpd); + + return is_on; +} +EXPORT_SYMBOL_GPL(dev_pm_genpd_is_on); + +/** * pm_genpd_inc_rejected() - Adjust the rejected/usage counts for an idle-state. * * @genpd: The PM domain the idle-state belongs to. @@ -920,11 +964,12 @@ static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, * The domain is already in the "power off" state. * System suspend is in progress. * The domain is configured as always on. + * The domain was on at boot and still need to stay on. * The domain has a subdomain being powered on. */ if (!genpd_status_on(genpd) || genpd->prepared_count > 0 || genpd_is_always_on(genpd) || genpd_is_rpm_always_on(genpd) || - atomic_read(&genpd->sd_count) > 0) + genpd->stay_on || atomic_read(&genpd->sd_count) > 0) return; /* @@ -1312,6 +1357,7 @@ err_poweroff: return ret; } +#ifndef CONFIG_PM_GENERIC_DOMAINS_OF static bool pd_ignore_unused; static int __init pd_ignore_unused_setup(char *__unused) { @@ -1335,14 +1381,19 @@ static int __init genpd_power_off_unused(void) pr_info("genpd: Disabling unused power domains\n"); mutex_lock(&gpd_list_lock); - list_for_each_entry(genpd, &gpd_list, gpd_list_node) + list_for_each_entry(genpd, &gpd_list, gpd_list_node) { + genpd_lock(genpd); + genpd->stay_on = false; + genpd_unlock(genpd); genpd_queue_power_off_work(genpd); + } mutex_unlock(&gpd_list_lock); return 0; } late_initcall_sync(genpd_power_off_unused); +#endif #ifdef CONFIG_PM_SLEEP @@ -2262,6 +2313,8 @@ static int genpd_alloc_data(struct generic_pm_domain *genpd) genpd->gd = gd; device_initialize(&genpd->dev); genpd->dev.release = genpd_provider_release; + genpd->dev.bus = &genpd_provider_bus_type; + genpd->dev.parent = &genpd_provider_bus; if (!genpd_is_dev_name_fw(genpd)) { dev_set_name(&genpd->dev, "%s", genpd->name); @@ -2339,6 +2392,8 @@ int pm_genpd_init(struct generic_pm_domain *genpd, INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); atomic_set(&genpd->sd_count, 0); genpd->status = is_off ? GENPD_STATE_OFF : GENPD_STATE_ON; + genpd->stay_on = !is_off; + genpd->sync_state = GENPD_SYNC_STATE_OFF; genpd->device_count = 0; genpd->provider = NULL; genpd->device_id = -ENXIO; @@ -2491,6 +2546,8 @@ struct of_genpd_provider { static LIST_HEAD(of_genpd_providers); /* Mutex to protect the list above. */ static DEFINE_MUTEX(of_genpd_mutex); +/* Used to prevent registering devices before the bus. */ +static bool genpd_bus_registered; /** * genpd_xlate_simple() - Xlate function for direct node-domain mapping @@ -2557,7 +2614,7 @@ static int genpd_add_provider(struct device_node *np, genpd_xlate_t xlate, cp->node = of_node_get(np); cp->data = data; cp->xlate = xlate; - fwnode_dev_initialized(&np->fwnode, true); + fwnode_dev_initialized(of_fwnode_handle(np), true); mutex_lock(&of_genpd_mutex); list_add(&cp->link, &of_genpd_providers); @@ -2584,6 +2641,11 @@ static bool genpd_present(const struct generic_pm_domain *genpd) return ret; } +static void genpd_sync_state(struct device *dev) +{ + return of_genpd_sync_state(dev->of_node); +} + /** * of_genpd_add_provider_simple() - Register a simple PM domain provider * @np: Device node pointer associated with the PM domain provider. @@ -2592,21 +2654,43 @@ static bool genpd_present(const struct generic_pm_domain *genpd) int of_genpd_add_provider_simple(struct device_node *np, struct generic_pm_domain *genpd) { + struct fwnode_handle *fwnode; + struct device *dev; int ret; if (!np || !genpd) return -EINVAL; + if (!genpd_bus_registered) + return -ENODEV; + if (!genpd_present(genpd)) return -EINVAL; genpd->dev.of_node = np; + fwnode = of_fwnode_handle(np); + dev = get_dev_from_fwnode(fwnode); + if (!dev && !genpd_is_no_sync_state(genpd)) { + genpd->sync_state = GENPD_SYNC_STATE_SIMPLE; + device_set_node(&genpd->dev, fwnode); + } else { + dev_set_drv_sync_state(dev, genpd_sync_state); + } + + put_device(dev); + + ret = device_add(&genpd->dev); + if (ret) + return ret; + /* Parse genpd OPP table */ if (!genpd_is_opp_table_fw(genpd) && genpd->set_performance_state) { ret = dev_pm_opp_of_add_table(&genpd->dev); - if (ret) - return dev_err_probe(&genpd->dev, ret, "Failed to add OPP table\n"); + if (ret) { + dev_err_probe(&genpd->dev, ret, "Failed to add OPP table\n"); + goto err_del; + } /* * Save table for faster processing while setting performance @@ -2617,19 +2701,22 @@ int of_genpd_add_provider_simple(struct device_node *np, } ret = genpd_add_provider(np, genpd_xlate_simple, genpd); - if (ret) { - if (genpd->opp_table) { - dev_pm_opp_put_opp_table(genpd->opp_table); - dev_pm_opp_of_remove_table(&genpd->dev); - } - - return ret; - } + if (ret) + goto err_opp; - genpd->provider = &np->fwnode; + genpd->provider = fwnode; genpd->has_provider = true; return 0; + +err_opp: + if (genpd->opp_table) { + dev_pm_opp_put_opp_table(genpd->opp_table); + dev_pm_opp_of_remove_table(&genpd->dev); + } +err_del: + device_del(&genpd->dev); + return ret; } EXPORT_SYMBOL_GPL(of_genpd_add_provider_simple); @@ -2642,15 +2729,30 @@ int of_genpd_add_provider_onecell(struct device_node *np, struct genpd_onecell_data *data) { struct generic_pm_domain *genpd; + struct fwnode_handle *fwnode; + struct device *dev; unsigned int i; int ret = -EINVAL; + bool sync_state = false; if (!np || !data) return -EINVAL; + if (!genpd_bus_registered) + return -ENODEV; + if (!data->xlate) data->xlate = genpd_xlate_onecell; + fwnode = of_fwnode_handle(np); + dev = get_dev_from_fwnode(fwnode); + if (!dev) + sync_state = true; + else + dev_set_drv_sync_state(dev, genpd_sync_state); + + put_device(dev); + for (i = 0; i < data->num_domains; i++) { genpd = data->domains[i]; @@ -2661,12 +2763,23 @@ int of_genpd_add_provider_onecell(struct device_node *np, genpd->dev.of_node = np; + if (sync_state && !genpd_is_no_sync_state(genpd)) { + genpd->sync_state = GENPD_SYNC_STATE_ONECELL; + device_set_node(&genpd->dev, fwnode); + sync_state = false; + } + + ret = device_add(&genpd->dev); + if (ret) + goto error; + /* Parse genpd OPP table */ if (!genpd_is_opp_table_fw(genpd) && genpd->set_performance_state) { ret = dev_pm_opp_of_add_table_indexed(&genpd->dev, i); if (ret) { dev_err_probe(&genpd->dev, ret, "Failed to add OPP table for index %d\n", i); + device_del(&genpd->dev); goto error; } @@ -2678,7 +2791,7 @@ int of_genpd_add_provider_onecell(struct device_node *np, WARN_ON(IS_ERR(genpd->opp_table)); } - genpd->provider = &np->fwnode; + genpd->provider = fwnode; genpd->has_provider = true; } @@ -2702,6 +2815,8 @@ error: dev_pm_opp_put_opp_table(genpd->opp_table); dev_pm_opp_of_remove_table(&genpd->dev); } + + device_del(&genpd->dev); } return ret; @@ -2727,17 +2842,19 @@ void of_genpd_del_provider(struct device_node *np) * so that the PM domain can be safely removed. */ list_for_each_entry(gpd, &gpd_list, gpd_list_node) { - if (gpd->provider == &np->fwnode) { + if (gpd->provider == of_fwnode_handle(np)) { gpd->has_provider = false; if (gpd->opp_table) { dev_pm_opp_put_opp_table(gpd->opp_table); dev_pm_opp_of_remove_table(&gpd->dev); } + + device_del(&gpd->dev); } } - fwnode_dev_initialized(&cp->node->fwnode, false); + fwnode_dev_initialized(of_fwnode_handle(cp->node), false); list_del(&cp->link); of_node_put(cp->node); kfree(cp); @@ -2916,7 +3033,7 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np) mutex_lock(&gpd_list_lock); list_for_each_entry_safe(gpd, tmp, &gpd_list, gpd_list_node) { - if (gpd->provider == &np->fwnode) { + if (gpd->provider == of_fwnode_handle(np)) { ret = genpd_remove(gpd); genpd = ret ? ERR_PTR(ret) : gpd; break; @@ -3179,6 +3296,9 @@ struct device *genpd_dev_pm_attach_by_id(struct device *dev, if (num_domains < 0 || index >= num_domains) return NULL; + if (!genpd_bus_registered) + return ERR_PTR(-ENODEV); + /* Allocate and register device on the genpd bus. */ virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL); if (!virt_dev) @@ -3269,7 +3389,7 @@ static int genpd_parse_state(struct genpd_power_state *genpd_state, genpd_state->power_on_latency_ns = 1000LL * exit_latency; genpd_state->power_off_latency_ns = 1000LL * entry_latency; - genpd_state->fwnode = &state_node->fwnode; + genpd_state->fwnode = of_fwnode_handle(state_node); return 0; } @@ -3355,9 +3475,103 @@ int of_genpd_parse_idle_states(struct device_node *dn, } EXPORT_SYMBOL_GPL(of_genpd_parse_idle_states); +/** + * of_genpd_sync_state() - A common sync_state function for genpd providers + * @np: The device node the genpd provider is associated with. + * + * The @np that corresponds to a genpd provider may provide one or multiple + * genpds. This function makes use @np to find the genpds that belongs to the + * provider. For each genpd we try a power-off. + */ +void of_genpd_sync_state(struct device_node *np) +{ + struct generic_pm_domain *genpd; + + if (!np) + return; + + mutex_lock(&gpd_list_lock); + list_for_each_entry(genpd, &gpd_list, gpd_list_node) { + if (genpd->provider == of_fwnode_handle(np)) { + genpd_lock(genpd); + genpd->stay_on = false; + genpd_power_off(genpd, false, 0); + genpd_unlock(genpd); + } + } + mutex_unlock(&gpd_list_lock); +} +EXPORT_SYMBOL_GPL(of_genpd_sync_state); + +static int genpd_provider_probe(struct device *dev) +{ + return 0; +} + +static void genpd_provider_sync_state(struct device *dev) +{ + struct generic_pm_domain *genpd = container_of(dev, struct generic_pm_domain, dev); + + switch (genpd->sync_state) { + case GENPD_SYNC_STATE_OFF: + break; + + case GENPD_SYNC_STATE_ONECELL: + of_genpd_sync_state(dev->of_node); + break; + + case GENPD_SYNC_STATE_SIMPLE: + genpd_lock(genpd); + genpd->stay_on = false; + genpd_power_off(genpd, false, 0); + genpd_unlock(genpd); + break; + + default: + break; + } +} + +static struct device_driver genpd_provider_drv = { + .name = "genpd_provider", + .bus = &genpd_provider_bus_type, + .probe = genpd_provider_probe, + .sync_state = genpd_provider_sync_state, + .suppress_bind_attrs = true, +}; + static int __init genpd_bus_init(void) { - return bus_register(&genpd_bus_type); + int ret; + + ret = device_register(&genpd_provider_bus); + if (ret) { + put_device(&genpd_provider_bus); + return ret; + } + + ret = bus_register(&genpd_provider_bus_type); + if (ret) + goto err_dev; + + ret = bus_register(&genpd_bus_type); + if (ret) + goto err_prov_bus; + + ret = driver_register(&genpd_provider_drv); + if (ret) + goto err_bus; + + genpd_bus_registered = true; + return 0; + +err_bus: + bus_unregister(&genpd_bus_type); +err_prov_bus: + bus_unregister(&genpd_provider_bus_type); +err_dev: + device_unregister(&genpd_provider_bus); + return ret; } core_initcall(genpd_bus_init); |