162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/of.h>
762306a36Sopenharmony_ci#include <linux/platform_device.h>
862306a36Sopenharmony_ci#include <linux/pm_domain.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <soc/tegra/bpmp.h>
1262306a36Sopenharmony_ci#include <soc/tegra/bpmp-abi.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistruct tegra_powergate_info {
1562306a36Sopenharmony_ci	unsigned int id;
1662306a36Sopenharmony_ci	char *name;
1762306a36Sopenharmony_ci};
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct tegra_powergate {
2062306a36Sopenharmony_ci	struct generic_pm_domain genpd;
2162306a36Sopenharmony_ci	struct tegra_bpmp *bpmp;
2262306a36Sopenharmony_ci	unsigned int id;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic inline struct tegra_powergate *
2662306a36Sopenharmony_cito_tegra_powergate(struct generic_pm_domain *genpd)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	return container_of(genpd, struct tegra_powergate, genpd);
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic int tegra_bpmp_powergate_set_state(struct tegra_bpmp *bpmp,
3262306a36Sopenharmony_ci					  unsigned int id, u32 state)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	struct mrq_pg_request request;
3562306a36Sopenharmony_ci	struct tegra_bpmp_message msg;
3662306a36Sopenharmony_ci	int err;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	memset(&request, 0, sizeof(request));
3962306a36Sopenharmony_ci	request.cmd = CMD_PG_SET_STATE;
4062306a36Sopenharmony_ci	request.id = id;
4162306a36Sopenharmony_ci	request.set_state.state = state;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
4462306a36Sopenharmony_ci	msg.mrq = MRQ_PG;
4562306a36Sopenharmony_ci	msg.tx.data = &request;
4662306a36Sopenharmony_ci	msg.tx.size = sizeof(request);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	err = tegra_bpmp_transfer(bpmp, &msg);
4962306a36Sopenharmony_ci	if (err < 0)
5062306a36Sopenharmony_ci		return err;
5162306a36Sopenharmony_ci	else if (msg.rx.ret < 0)
5262306a36Sopenharmony_ci		return -EINVAL;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return 0;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int tegra_bpmp_powergate_get_state(struct tegra_bpmp *bpmp,
5862306a36Sopenharmony_ci					  unsigned int id)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct mrq_pg_response response;
6162306a36Sopenharmony_ci	struct mrq_pg_request request;
6262306a36Sopenharmony_ci	struct tegra_bpmp_message msg;
6362306a36Sopenharmony_ci	int err;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	memset(&request, 0, sizeof(request));
6662306a36Sopenharmony_ci	request.cmd = CMD_PG_GET_STATE;
6762306a36Sopenharmony_ci	request.id = id;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	memset(&response, 0, sizeof(response));
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
7262306a36Sopenharmony_ci	msg.mrq = MRQ_PG;
7362306a36Sopenharmony_ci	msg.tx.data = &request;
7462306a36Sopenharmony_ci	msg.tx.size = sizeof(request);
7562306a36Sopenharmony_ci	msg.rx.data = &response;
7662306a36Sopenharmony_ci	msg.rx.size = sizeof(response);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	err = tegra_bpmp_transfer(bpmp, &msg);
7962306a36Sopenharmony_ci	if (err < 0)
8062306a36Sopenharmony_ci		return PG_STATE_OFF;
8162306a36Sopenharmony_ci	else if (msg.rx.ret < 0)
8262306a36Sopenharmony_ci		return -EINVAL;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return response.get_state.state;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic int tegra_bpmp_powergate_get_max_id(struct tegra_bpmp *bpmp)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct mrq_pg_response response;
9062306a36Sopenharmony_ci	struct mrq_pg_request request;
9162306a36Sopenharmony_ci	struct tegra_bpmp_message msg;
9262306a36Sopenharmony_ci	int err;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	memset(&request, 0, sizeof(request));
9562306a36Sopenharmony_ci	request.cmd = CMD_PG_GET_MAX_ID;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	memset(&response, 0, sizeof(response));
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
10062306a36Sopenharmony_ci	msg.mrq = MRQ_PG;
10162306a36Sopenharmony_ci	msg.tx.data = &request;
10262306a36Sopenharmony_ci	msg.tx.size = sizeof(request);
10362306a36Sopenharmony_ci	msg.rx.data = &response;
10462306a36Sopenharmony_ci	msg.rx.size = sizeof(response);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	err = tegra_bpmp_transfer(bpmp, &msg);
10762306a36Sopenharmony_ci	if (err < 0)
10862306a36Sopenharmony_ci		return err;
10962306a36Sopenharmony_ci	else if (msg.rx.ret < 0)
11062306a36Sopenharmony_ci		return -EINVAL;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return response.get_max_id.max_id;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic char *tegra_bpmp_powergate_get_name(struct tegra_bpmp *bpmp,
11662306a36Sopenharmony_ci					   unsigned int id)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct mrq_pg_response response;
11962306a36Sopenharmony_ci	struct mrq_pg_request request;
12062306a36Sopenharmony_ci	struct tegra_bpmp_message msg;
12162306a36Sopenharmony_ci	int err;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	memset(&request, 0, sizeof(request));
12462306a36Sopenharmony_ci	request.cmd = CMD_PG_GET_NAME;
12562306a36Sopenharmony_ci	request.id = id;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	memset(&response, 0, sizeof(response));
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
13062306a36Sopenharmony_ci	msg.mrq = MRQ_PG;
13162306a36Sopenharmony_ci	msg.tx.data = &request;
13262306a36Sopenharmony_ci	msg.tx.size = sizeof(request);
13362306a36Sopenharmony_ci	msg.rx.data = &response;
13462306a36Sopenharmony_ci	msg.rx.size = sizeof(response);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	err = tegra_bpmp_transfer(bpmp, &msg);
13762306a36Sopenharmony_ci	if (err < 0 || msg.rx.ret < 0)
13862306a36Sopenharmony_ci		return NULL;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	return kstrdup(response.get_name.name, GFP_KERNEL);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic inline bool tegra_bpmp_powergate_is_powered(struct tegra_bpmp *bpmp,
14462306a36Sopenharmony_ci						   unsigned int id)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	return tegra_bpmp_powergate_get_state(bpmp, id) != PG_STATE_OFF;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic int tegra_powergate_power_on(struct generic_pm_domain *domain)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	struct tegra_powergate *powergate = to_tegra_powergate(domain);
15262306a36Sopenharmony_ci	struct tegra_bpmp *bpmp = powergate->bpmp;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	return tegra_bpmp_powergate_set_state(bpmp, powergate->id,
15562306a36Sopenharmony_ci					      PG_STATE_ON);
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic int tegra_powergate_power_off(struct generic_pm_domain *domain)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct tegra_powergate *powergate = to_tegra_powergate(domain);
16162306a36Sopenharmony_ci	struct tegra_bpmp *bpmp = powergate->bpmp;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	return tegra_bpmp_powergate_set_state(bpmp, powergate->id,
16462306a36Sopenharmony_ci					      PG_STATE_OFF);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic struct tegra_powergate *
16862306a36Sopenharmony_citegra_powergate_add(struct tegra_bpmp *bpmp,
16962306a36Sopenharmony_ci		    const struct tegra_powergate_info *info)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct tegra_powergate *powergate;
17262306a36Sopenharmony_ci	bool off;
17362306a36Sopenharmony_ci	int err;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	off = !tegra_bpmp_powergate_is_powered(bpmp, info->id);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	powergate = devm_kzalloc(bpmp->dev, sizeof(*powergate), GFP_KERNEL);
17862306a36Sopenharmony_ci	if (!powergate)
17962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	powergate->id = info->id;
18262306a36Sopenharmony_ci	powergate->bpmp = bpmp;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	powergate->genpd.name = kstrdup(info->name, GFP_KERNEL);
18562306a36Sopenharmony_ci	powergate->genpd.power_on = tegra_powergate_power_on;
18662306a36Sopenharmony_ci	powergate->genpd.power_off = tegra_powergate_power_off;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	err = pm_genpd_init(&powergate->genpd, NULL, off);
18962306a36Sopenharmony_ci	if (err < 0) {
19062306a36Sopenharmony_ci		kfree(powergate->genpd.name);
19162306a36Sopenharmony_ci		return ERR_PTR(err);
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	return powergate;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic void tegra_powergate_remove(struct tegra_powergate *powergate)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	struct generic_pm_domain *genpd = &powergate->genpd;
20062306a36Sopenharmony_ci	struct tegra_bpmp *bpmp = powergate->bpmp;
20162306a36Sopenharmony_ci	int err;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	err = pm_genpd_remove(genpd);
20462306a36Sopenharmony_ci	if (err < 0)
20562306a36Sopenharmony_ci		dev_err(bpmp->dev, "failed to remove power domain %s: %d\n",
20662306a36Sopenharmony_ci			genpd->name, err);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	kfree(genpd->name);
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic int
21262306a36Sopenharmony_citegra_bpmp_probe_powergates(struct tegra_bpmp *bpmp,
21362306a36Sopenharmony_ci			    struct tegra_powergate_info **powergatesp)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	struct tegra_powergate_info *powergates;
21662306a36Sopenharmony_ci	unsigned int max_id, id, count = 0;
21762306a36Sopenharmony_ci	unsigned int num_holes = 0;
21862306a36Sopenharmony_ci	int err;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	err = tegra_bpmp_powergate_get_max_id(bpmp);
22162306a36Sopenharmony_ci	if (err < 0)
22262306a36Sopenharmony_ci		return err;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	max_id = err;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	dev_dbg(bpmp->dev, "maximum powergate ID: %u\n", max_id);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	powergates = kcalloc(max_id + 1, sizeof(*powergates), GFP_KERNEL);
22962306a36Sopenharmony_ci	if (!powergates)
23062306a36Sopenharmony_ci		return -ENOMEM;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	for (id = 0; id <= max_id; id++) {
23362306a36Sopenharmony_ci		struct tegra_powergate_info *info = &powergates[count];
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci		info->name = tegra_bpmp_powergate_get_name(bpmp, id);
23662306a36Sopenharmony_ci		if (!info->name || info->name[0] == '\0') {
23762306a36Sopenharmony_ci			num_holes++;
23862306a36Sopenharmony_ci			continue;
23962306a36Sopenharmony_ci		}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci		info->id = id;
24262306a36Sopenharmony_ci		count++;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	dev_dbg(bpmp->dev, "holes: %u\n", num_holes);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	*powergatesp = powergates;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return count;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic int tegra_bpmp_add_powergates(struct tegra_bpmp *bpmp,
25362306a36Sopenharmony_ci				     struct tegra_powergate_info *powergates,
25462306a36Sopenharmony_ci				     unsigned int count)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	struct genpd_onecell_data *genpd = &bpmp->genpd;
25762306a36Sopenharmony_ci	struct generic_pm_domain **domains;
25862306a36Sopenharmony_ci	struct tegra_powergate *powergate;
25962306a36Sopenharmony_ci	unsigned int i;
26062306a36Sopenharmony_ci	int err;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	domains = kcalloc(count, sizeof(*domains), GFP_KERNEL);
26362306a36Sopenharmony_ci	if (!domains)
26462306a36Sopenharmony_ci		return -ENOMEM;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
26762306a36Sopenharmony_ci		powergate = tegra_powergate_add(bpmp, &powergates[i]);
26862306a36Sopenharmony_ci		if (IS_ERR(powergate)) {
26962306a36Sopenharmony_ci			err = PTR_ERR(powergate);
27062306a36Sopenharmony_ci			goto remove;
27162306a36Sopenharmony_ci		}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci		dev_dbg(bpmp->dev, "added power domain %s\n",
27462306a36Sopenharmony_ci			powergate->genpd.name);
27562306a36Sopenharmony_ci		domains[i] = &powergate->genpd;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	genpd->num_domains = count;
27962306a36Sopenharmony_ci	genpd->domains = domains;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	return 0;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ciremove:
28462306a36Sopenharmony_ci	while (i--) {
28562306a36Sopenharmony_ci		powergate = to_tegra_powergate(domains[i]);
28662306a36Sopenharmony_ci		tegra_powergate_remove(powergate);
28762306a36Sopenharmony_ci	}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	kfree(domains);
29062306a36Sopenharmony_ci	return err;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic void tegra_bpmp_remove_powergates(struct tegra_bpmp *bpmp)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	struct genpd_onecell_data *genpd = &bpmp->genpd;
29662306a36Sopenharmony_ci	unsigned int i = genpd->num_domains;
29762306a36Sopenharmony_ci	struct tegra_powergate *powergate;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	while (i--) {
30062306a36Sopenharmony_ci		dev_dbg(bpmp->dev, "removing power domain %s\n",
30162306a36Sopenharmony_ci			genpd->domains[i]->name);
30262306a36Sopenharmony_ci		powergate = to_tegra_powergate(genpd->domains[i]);
30362306a36Sopenharmony_ci		tegra_powergate_remove(powergate);
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic struct generic_pm_domain *
30862306a36Sopenharmony_citegra_powergate_xlate(struct of_phandle_args *spec, void *data)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	struct generic_pm_domain *domain = ERR_PTR(-ENOENT);
31162306a36Sopenharmony_ci	struct genpd_onecell_data *genpd = data;
31262306a36Sopenharmony_ci	unsigned int i;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	for (i = 0; i < genpd->num_domains; i++) {
31562306a36Sopenharmony_ci		struct tegra_powergate *powergate;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci		powergate = to_tegra_powergate(genpd->domains[i]);
31862306a36Sopenharmony_ci		if (powergate->id == spec->args[0]) {
31962306a36Sopenharmony_ci			domain = &powergate->genpd;
32062306a36Sopenharmony_ci			break;
32162306a36Sopenharmony_ci		}
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return domain;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ciint tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	struct device_node *np = bpmp->dev->of_node;
33062306a36Sopenharmony_ci	struct tegra_powergate_info *powergates;
33162306a36Sopenharmony_ci	struct device *dev = bpmp->dev;
33262306a36Sopenharmony_ci	unsigned int count, i;
33362306a36Sopenharmony_ci	int err;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	err = tegra_bpmp_probe_powergates(bpmp, &powergates);
33662306a36Sopenharmony_ci	if (err < 0)
33762306a36Sopenharmony_ci		return err;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	count = err;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	dev_dbg(dev, "%u power domains probed\n", count);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	err = tegra_bpmp_add_powergates(bpmp, powergates, count);
34462306a36Sopenharmony_ci	if (err < 0)
34562306a36Sopenharmony_ci		goto free;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	bpmp->genpd.xlate = tegra_powergate_xlate;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	err = of_genpd_add_provider_onecell(np, &bpmp->genpd);
35062306a36Sopenharmony_ci	if (err < 0) {
35162306a36Sopenharmony_ci		dev_err(dev, "failed to add power domain provider: %d\n", err);
35262306a36Sopenharmony_ci		tegra_bpmp_remove_powergates(bpmp);
35362306a36Sopenharmony_ci	}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cifree:
35662306a36Sopenharmony_ci	for (i = 0; i < count; i++)
35762306a36Sopenharmony_ci		kfree(powergates[i].name);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	kfree(powergates);
36062306a36Sopenharmony_ci	return err;
36162306a36Sopenharmony_ci}
362