| /* | |
| * Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms and conditions of the GNU General Public License, | |
| * version 2, as published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
| * more details. | |
| */ | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/pm_domain.h> | |
| #include <linux/slab.h> | |
| #include <linux/version.h> | |
| #include <soc/tegra/bpmp.h> | |
| #include <soc/tegra/bpmp-abi.h> | |
| struct tegra_powergate_info { | |
| unsigned int id; | |
| char *name; | |
| }; | |
| struct tegra_powergate { | |
| struct generic_pm_domain genpd; | |
| struct tegra_bpmp *bpmp; | |
| unsigned int id; | |
| }; | |
| static inline struct tegra_powergate * | |
| to_tegra_powergate(struct generic_pm_domain *genpd) | |
| { | |
| return container_of(genpd, struct tegra_powergate, genpd); | |
| } | |
| static int tegra_bpmp_powergate_set_state(struct tegra_bpmp *bpmp, | |
| unsigned int id, u32 state) | |
| { | |
| struct mrq_pg_request request; | |
| struct tegra_bpmp_message msg; | |
| memset(&request, 0, sizeof(request)); | |
| request.cmd = CMD_PG_SET_STATE; | |
| request.id = id; | |
| request.set_state.state = state; | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.mrq = MRQ_PG; | |
| msg.tx.data = &request; | |
| msg.tx.size = sizeof(request); | |
| return tegra_bpmp_transfer(bpmp, &msg); | |
| } | |
| static int tegra_bpmp_powergate_get_state(struct tegra_bpmp *bpmp, | |
| unsigned int id) | |
| { | |
| struct mrq_pg_response response; | |
| struct mrq_pg_request request; | |
| struct tegra_bpmp_message msg; | |
| int err; | |
| memset(&request, 0, sizeof(request)); | |
| request.cmd = CMD_PG_GET_STATE; | |
| request.id = id; | |
| memset(&response, 0, sizeof(response)); | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.mrq = MRQ_PG; | |
| msg.tx.data = &request; | |
| msg.tx.size = sizeof(request); | |
| msg.rx.data = &response; | |
| msg.rx.size = sizeof(response); | |
| err = tegra_bpmp_transfer(bpmp, &msg); | |
| if (err < 0) | |
| return PG_STATE_OFF; | |
| return response.get_state.state; | |
| } | |
| static int tegra_bpmp_powergate_get_max_id(struct tegra_bpmp *bpmp) | |
| { | |
| struct mrq_pg_response response; | |
| struct mrq_pg_request request; | |
| struct tegra_bpmp_message msg; | |
| int err; | |
| memset(&request, 0, sizeof(request)); | |
| request.cmd = CMD_PG_GET_MAX_ID; | |
| memset(&response, 0, sizeof(response)); | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.mrq = MRQ_PG; | |
| msg.tx.data = &request; | |
| msg.tx.size = sizeof(request); | |
| msg.rx.data = &response; | |
| msg.rx.size = sizeof(response); | |
| err = tegra_bpmp_transfer(bpmp, &msg); | |
| if (err < 0) | |
| return err; | |
| return response.get_max_id.max_id; | |
| } | |
| static char *tegra_bpmp_powergate_get_name(struct tegra_bpmp *bpmp, | |
| unsigned int id) | |
| { | |
| struct mrq_pg_response response; | |
| struct mrq_pg_request request; | |
| struct tegra_bpmp_message msg; | |
| int err; | |
| memset(&request, 0, sizeof(request)); | |
| request.cmd = CMD_PG_GET_NAME; | |
| request.id = id; | |
| memset(&response, 0, sizeof(response)); | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.mrq = MRQ_PG; | |
| msg.tx.data = &request; | |
| msg.tx.size = sizeof(request); | |
| msg.rx.data = &response; | |
| msg.rx.size = sizeof(response); | |
| err = tegra_bpmp_transfer(bpmp, &msg); | |
| if (err < 0) | |
| return NULL; | |
| return kstrdup(response.get_name.name, GFP_KERNEL); | |
| } | |
| static inline bool tegra_bpmp_powergate_is_powered(struct tegra_bpmp *bpmp, | |
| unsigned int id) | |
| { | |
| return tegra_bpmp_powergate_get_state(bpmp, id) != PG_STATE_OFF; | |
| } | |
| static int tegra_powergate_power_on(struct generic_pm_domain *domain) | |
| { | |
| struct tegra_powergate *powergate = to_tegra_powergate(domain); | |
| struct tegra_bpmp *bpmp = powergate->bpmp; | |
| return tegra_bpmp_powergate_set_state(bpmp, powergate->id, | |
| PG_STATE_ON); | |
| } | |
| static int tegra_powergate_power_off(struct generic_pm_domain *domain) | |
| { | |
| struct tegra_powergate *powergate = to_tegra_powergate(domain); | |
| struct tegra_bpmp *bpmp = powergate->bpmp; | |
| return tegra_bpmp_powergate_set_state(bpmp, powergate->id, | |
| PG_STATE_OFF); | |
| } | |
| static struct tegra_powergate * | |
| tegra_powergate_add(struct tegra_bpmp *bpmp, | |
| const struct tegra_powergate_info *info) | |
| { | |
| struct tegra_powergate *powergate; | |
| bool off; | |
| int err; | |
| off = !tegra_bpmp_powergate_is_powered(bpmp, info->id); | |
| powergate = devm_kzalloc(bpmp->dev, sizeof(*powergate), GFP_KERNEL); | |
| if (!powergate) | |
| return ERR_PTR(-ENOMEM); | |
| powergate->id = info->id; | |
| powergate->bpmp = bpmp; | |
| powergate->genpd.name = kstrdup(info->name, GFP_KERNEL); | |
| powergate->genpd.power_on = tegra_powergate_power_on; | |
| powergate->genpd.power_off = tegra_powergate_power_off; | |
| err = pm_genpd_init(&powergate->genpd, NULL, off); | |
| if (err < 0) { | |
| kfree(powergate->genpd.name); | |
| return ERR_PTR(err); | |
| } | |
| return powergate; | |
| } | |
| static void tegra_powergate_remove(struct tegra_powergate *powergate) | |
| { | |
| struct generic_pm_domain *genpd = &powergate->genpd; | |
| struct tegra_bpmp *bpmp = powergate->bpmp; | |
| int err; | |
| err = pm_genpd_remove(genpd); | |
| if (err < 0) | |
| dev_err(bpmp->dev, "failed to remove power domain %s: %d\n", | |
| genpd->name, err); | |
| kfree(genpd->name); | |
| } | |
| static int | |
| tegra_bpmp_probe_powergates(struct tegra_bpmp *bpmp, | |
| struct tegra_powergate_info **powergatesp) | |
| { | |
| struct tegra_powergate_info *powergates; | |
| unsigned int max_id, id, count = 0; | |
| unsigned int num_holes = 0; | |
| int err; | |
| err = tegra_bpmp_powergate_get_max_id(bpmp); | |
| if (err < 0) | |
| return err; | |
| max_id = err; | |
| dev_dbg(bpmp->dev, "maximum powergate ID: %u\n", max_id); | |
| powergates = kcalloc(max_id + 1, sizeof(*powergates), GFP_KERNEL); | |
| if (!powergates) | |
| return -ENOMEM; | |
| for (id = 0; id <= max_id; id++) { | |
| struct tegra_powergate_info *info = &powergates[count]; | |
| info->name = tegra_bpmp_powergate_get_name(bpmp, id); | |
| if (!info->name || info->name[0] == '\0') { | |
| num_holes++; | |
| continue; | |
| } | |
| info->id = id; | |
| count++; | |
| } | |
| dev_dbg(bpmp->dev, "holes: %u\n", num_holes); | |
| *powergatesp = powergates; | |
| return count; | |
| } | |
| static int tegra_bpmp_add_powergates(struct tegra_bpmp *bpmp, | |
| struct tegra_powergate_info *powergates, | |
| unsigned int count) | |
| { | |
| struct genpd_onecell_data *genpd = &bpmp->genpd; | |
| struct generic_pm_domain **domains; | |
| struct tegra_powergate *powergate; | |
| unsigned int i; | |
| int err; | |
| domains = kcalloc(count, sizeof(*domains), GFP_KERNEL); | |
| if (!domains) | |
| return -ENOMEM; | |
| for (i = 0; i < count; i++) { | |
| powergate = tegra_powergate_add(bpmp, &powergates[i]); | |
| if (IS_ERR(powergate)) { | |
| err = PTR_ERR(powergate); | |
| goto remove; | |
| } | |
| dev_dbg(bpmp->dev, "added power domain %s\n", | |
| powergate->genpd.name); | |
| domains[i] = &powergate->genpd; | |
| } | |
| genpd->num_domains = count; | |
| genpd->domains = domains; | |
| return 0; | |
| remove: | |
| while (i--) { | |
| powergate = to_tegra_powergate(domains[i]); | |
| tegra_powergate_remove(powergate); | |
| } | |
| kfree(genpd->domains); | |
| return err; | |
| } | |
| static void tegra_bpmp_remove_powergates(struct tegra_bpmp *bpmp) | |
| { | |
| struct genpd_onecell_data *genpd = &bpmp->genpd; | |
| unsigned int i = genpd->num_domains; | |
| struct tegra_powergate *powergate; | |
| while (i--) { | |
| dev_dbg(bpmp->dev, "removing power domain %s\n", | |
| genpd->domains[i]->name); | |
| powergate = to_tegra_powergate(genpd->domains[i]); | |
| tegra_powergate_remove(powergate); | |
| } | |
| } | |
| static struct generic_pm_domain * | |
| tegra_powergate_xlate(struct of_phandle_args *spec, void *data) | |
| { | |
| struct generic_pm_domain *domain = ERR_PTR(-ENOENT); | |
| struct genpd_onecell_data *genpd = data; | |
| unsigned int i; | |
| for (i = 0; i < genpd->num_domains; i++) { | |
| struct tegra_powergate *powergate; | |
| powergate = to_tegra_powergate(genpd->domains[i]); | |
| if (powergate->id == spec->args[0]) { | |
| domain = &powergate->genpd; | |
| break; | |
| } | |
| } | |
| return domain; | |
| } | |
| int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp) | |
| { | |
| struct device_node *np = bpmp->dev->of_node; | |
| struct tegra_powergate_info *powergates; | |
| struct device *dev = bpmp->dev; | |
| unsigned int count, i; | |
| int err; | |
| err = tegra_bpmp_probe_powergates(bpmp, &powergates); | |
| if (err < 0) | |
| return err; | |
| count = err; | |
| dev_dbg(dev, "%u power domains probed\n", count); | |
| err = tegra_bpmp_add_powergates(bpmp, powergates, count); | |
| if (err < 0) | |
| goto free; | |
| bpmp->genpd.xlate = tegra_powergate_xlate; | |
| err = of_genpd_add_provider_onecell(np, &bpmp->genpd); | |
| if (err < 0) { | |
| dev_err(dev, "failed to add power domain provider: %d\n", err); | |
| tegra_bpmp_remove_powergates(bpmp); | |
| } | |
| free: | |
| for (i = 0; i < count; i++) | |
| kfree(powergates[i].name); | |
| kfree(powergates); | |
| return err; | |
| } |