// SPDX-License-Identifier: GPL-2.0-only /* * RISC-V SBI CPU idle driver. * * Copyright (c) 2021 Western Digital Corporation or its affiliates. * Copyright (c) 2022 Ventana Micro Systems Inc.
*/
if (!riscv_sbi_suspend_state_is_valid(*state)) {
pr_warn("Invalid SBI suspend state %#x\n", *state); return -EINVAL;
}
return 0;
}
staticint sbi_dt_cpu_init_topology(struct cpuidle_driver *drv, struct sbi_cpuidle_data *data, unsignedint state_count, int cpu)
{ /* Currently limit the hierarchical topology to be used in OSI mode. */ if (!sbi_cpuidle_use_osi) return 0;
data->dev = dt_idle_attach_cpu(cpu, "sbi"); if (IS_ERR_OR_NULL(data->dev)) return PTR_ERR_OR_ZERO(data->dev);
/* * Using the deepest state for the CPU to trigger a potential selection * of a shared state for the domain, assumes the domain states are all * deeper states.
*/
drv->states[state_count - 1].flags |= CPUIDLE_FLAG_RCU_IDLE;
drv->states[state_count - 1].enter = sbi_enter_domain_idle_state;
drv->states[state_count - 1].enter_s2idle =
sbi_enter_s2idle_domain_idle_state;
sbi_cpuidle_use_cpuhp = true;
struct device_node *cpu_node __free(device_node) = of_cpu_device_node_get(cpu); if (!cpu_node) return -ENODEV;
states = devm_kcalloc(dev, state_count, sizeof(*states), GFP_KERNEL); if (!states) return -ENOMEM;
/* Parse SBI specific details from state DT nodes */ for (i = 1; i < state_count; i++) {
state_node = of_get_cpu_state_node(cpu_node, i - 1); if (!state_node) break;
ret = sbi_dt_parse_state_node(state_node, &states[i]);
of_node_put(state_node);
if (ret) return ret;
pr_debug("sbi-state %#x index %d\n", states[i], i);
} if (i != state_count) return -ENODEV;
/* Initialize optional data, used for the hierarchical topology. */
ret = sbi_dt_cpu_init_topology(drv, data, state_count, cpu); if (ret < 0) return ret;
/* Store states in the per-cpu struct. */
data->states = states;
/* RISC-V architectural WFI to be represented as state index 0. */
drv->states[0].enter = sbi_cpuidle_enter_state;
drv->states[0].exit_latency = 1;
drv->states[0].target_residency = 1;
drv->states[0].power_usage = UINT_MAX;
strcpy(drv->states[0].name, "WFI");
strcpy(drv->states[0].desc, "RISC-V WFI");
/* * If no DT idle states are detected (ret == 0) let the driver * initialization fail accordingly since there is no reason to * initialize the idle driver if only wfi is supported, the * default archictectural back-end already executes wfi * on idle entry.
*/
ret = dt_init_idle_driver(drv, sbi_cpuidle_state_match, 1); if (ret <= 0) {
pr_debug("HART%ld: failed to parse DT idle states\n",
cpuid_to_hartid_map(cpu)); return ret ? : -ENODEV;
}
state_count = ret + 1; /* Include WFI state as well */
/* Initialize idle states from DT. */
ret = sbi_cpuidle_dt_init_states(dev, drv, cpu, state_count); if (ret) {
pr_err("HART%ld: failed to init idle states\n",
cpuid_to_hartid_map(cpu)); return ret;
}
if (cpuidle_disabled()) return 0;
ret = cpuidle_register(drv, NULL); if (ret) goto deinit;
/* Allow power off when OSI is available. */ if (sbi_cpuidle_use_osi)
pd->power_off = sbi_cpuidle_pd_power_off; else
pd->flags |= GENPD_FLAG_ALWAYS_ON;
/* Use governor for CPU PM domains if it has some states to manage. */
pd_gov = pd->states ? &pm_domain_cpu_gov : NULL;
ret = pm_genpd_init(pd, pd_gov, false); if (ret) goto free_pd_prov;
ret = of_genpd_add_provider_simple(np, pd); if (ret) goto remove_pd;
staticint sbi_genpd_probe(struct device_node *np)
{ int ret = 0, pd_count = 0;
if (!np) return -ENODEV;
/* * Parse child nodes for the "#power-domain-cells" property and * initialize a genpd/genpd-of-provider pair when it's found.
*/
for_each_child_of_node_scoped(np, node) { if (!of_property_present(node, "#power-domain-cells")) continue;
ret = sbi_pd_init(node); if (ret) goto remove_pd;
pd_count++;
}
/* Bail out if not using the hierarchical CPU topology. */ if (!pd_count) goto no_pd;
/* Link genpd masters/subdomains to model the CPU topology. */
ret = dt_idle_pd_init_topology(np); if (ret) goto remove_pd;
return 0;
remove_pd:
sbi_pd_remove();
pr_err("failed to create CPU PM domains ret=%d\n", ret);
no_pd: return ret;
}
/* Detect OSI support based on CPU DT nodes */
sbi_cpuidle_use_osi = true;
for_each_possible_cpu(cpu) { struct device_node *np __free(device_node) = of_cpu_device_node_get(cpu); if (np &&
of_property_present(np, "power-domains") &&
of_property_present(np, "power-domain-names")) { continue;
} else {
sbi_cpuidle_use_osi = false; break;
}
}
/* Populate generic power domains from DT nodes */
pds_node = of_find_node_by_path("/cpus/power-domains"); if (pds_node) {
ret = sbi_genpd_probe(pds_node);
of_node_put(pds_node); if (ret) return ret;
}
/* Initialize CPU idle driver for each present CPU */
for_each_present_cpu(cpu) {
ret = sbi_cpuidle_init_cpu(&pdev->dev, cpu); if (ret) {
pr_debug("HART%ld: idle driver init failed\n",
cpuid_to_hartid_map(cpu)); goto out_fail;
}
}
/* Setup CPU hotplut notifiers */
sbi_idle_init_cpuhp();
if (cpuidle_disabled())
pr_info("cpuidle is disabled\n"); else
pr_info("idle driver registered for all CPUs\n");
return 0;
out_fail: while (--cpu >= 0) {
dev = per_cpu(cpuidle_devices, cpu);
drv = cpuidle_get_cpu_driver(dev);
cpuidle_unregister(drv);
sbi_cpuidle_deinit_cpu(cpu);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.