18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2019 Amlogic, Inc.
48c2ecf20Sopenharmony_ci * Author: Jianxin Pan <jianxin.pan@amlogic.com>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/io.h>
108c2ecf20Sopenharmony_ci#include <linux/of_device.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/pm_domain.h>
138c2ecf20Sopenharmony_ci#include <dt-bindings/power/meson-a1-power.h>
148c2ecf20Sopenharmony_ci#include <linux/arm-smccc.h>
158c2ecf20Sopenharmony_ci#include <linux/firmware/meson/meson_sm.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define PWRC_ON		1
188c2ecf20Sopenharmony_ci#define PWRC_OFF	0
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistruct meson_secure_pwrc_domain {
218c2ecf20Sopenharmony_ci	struct generic_pm_domain base;
228c2ecf20Sopenharmony_ci	unsigned int index;
238c2ecf20Sopenharmony_ci	struct meson_secure_pwrc *pwrc;
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistruct meson_secure_pwrc {
278c2ecf20Sopenharmony_ci	struct meson_secure_pwrc_domain *domains;
288c2ecf20Sopenharmony_ci	struct genpd_onecell_data xlate;
298c2ecf20Sopenharmony_ci	struct meson_sm_firmware *fw;
308c2ecf20Sopenharmony_ci};
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistruct meson_secure_pwrc_domain_desc {
338c2ecf20Sopenharmony_ci	unsigned int index;
348c2ecf20Sopenharmony_ci	unsigned int flags;
358c2ecf20Sopenharmony_ci	char *name;
368c2ecf20Sopenharmony_ci	bool (*is_off)(struct meson_secure_pwrc_domain *pwrc_domain);
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct meson_secure_pwrc_domain_data {
408c2ecf20Sopenharmony_ci	unsigned int count;
418c2ecf20Sopenharmony_ci	struct meson_secure_pwrc_domain_desc *domains;
428c2ecf20Sopenharmony_ci};
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic bool pwrc_secure_is_off(struct meson_secure_pwrc_domain *pwrc_domain)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	int is_off = 1;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_GET, &is_off,
498c2ecf20Sopenharmony_ci			  pwrc_domain->index, 0, 0, 0, 0) < 0)
508c2ecf20Sopenharmony_ci		pr_err("failed to get power domain status\n");
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	return is_off;
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic int meson_secure_pwrc_off(struct generic_pm_domain *domain)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	int ret = 0;
588c2ecf20Sopenharmony_ci	struct meson_secure_pwrc_domain *pwrc_domain =
598c2ecf20Sopenharmony_ci		container_of(domain, struct meson_secure_pwrc_domain, base);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL,
628c2ecf20Sopenharmony_ci			  pwrc_domain->index, PWRC_OFF, 0, 0, 0) < 0) {
638c2ecf20Sopenharmony_ci		pr_err("failed to set power domain off\n");
648c2ecf20Sopenharmony_ci		ret = -EINVAL;
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	return ret;
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic int meson_secure_pwrc_on(struct generic_pm_domain *domain)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	int ret = 0;
738c2ecf20Sopenharmony_ci	struct meson_secure_pwrc_domain *pwrc_domain =
748c2ecf20Sopenharmony_ci		container_of(domain, struct meson_secure_pwrc_domain, base);
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL,
778c2ecf20Sopenharmony_ci			  pwrc_domain->index, PWRC_ON, 0, 0, 0) < 0) {
788c2ecf20Sopenharmony_ci		pr_err("failed to set power domain on\n");
798c2ecf20Sopenharmony_ci		ret = -EINVAL;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	return ret;
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci#define SEC_PD(__name, __flag)			\
868c2ecf20Sopenharmony_ci[PWRC_##__name##_ID] =				\
878c2ecf20Sopenharmony_ci{						\
888c2ecf20Sopenharmony_ci	.name = #__name,			\
898c2ecf20Sopenharmony_ci	.index = PWRC_##__name##_ID,		\
908c2ecf20Sopenharmony_ci	.is_off = pwrc_secure_is_off,	\
918c2ecf20Sopenharmony_ci	.flags = __flag,			\
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic struct meson_secure_pwrc_domain_desc a1_pwrc_domains[] = {
958c2ecf20Sopenharmony_ci	SEC_PD(DSPA,	0),
968c2ecf20Sopenharmony_ci	SEC_PD(DSPB,	0),
978c2ecf20Sopenharmony_ci	/* UART should keep working in ATF after suspend and before resume */
988c2ecf20Sopenharmony_ci	SEC_PD(UART,	GENPD_FLAG_ALWAYS_ON),
998c2ecf20Sopenharmony_ci	/* DMC is for DDR PHY ana/dig and DMC, and should be always on */
1008c2ecf20Sopenharmony_ci	SEC_PD(DMC,	GENPD_FLAG_ALWAYS_ON),
1018c2ecf20Sopenharmony_ci	SEC_PD(I2C,	0),
1028c2ecf20Sopenharmony_ci	SEC_PD(PSRAM,	0),
1038c2ecf20Sopenharmony_ci	SEC_PD(ACODEC,	0),
1048c2ecf20Sopenharmony_ci	SEC_PD(AUDIO,	0),
1058c2ecf20Sopenharmony_ci	SEC_PD(OTP,	0),
1068c2ecf20Sopenharmony_ci	SEC_PD(DMA,	GENPD_FLAG_ALWAYS_ON | GENPD_FLAG_IRQ_SAFE),
1078c2ecf20Sopenharmony_ci	SEC_PD(SD_EMMC,	0),
1088c2ecf20Sopenharmony_ci	SEC_PD(RAMA,	0),
1098c2ecf20Sopenharmony_ci	/* SRAMB is used as ATF runtime memory, and should be always on */
1108c2ecf20Sopenharmony_ci	SEC_PD(RAMB,	GENPD_FLAG_ALWAYS_ON),
1118c2ecf20Sopenharmony_ci	SEC_PD(IR,	0),
1128c2ecf20Sopenharmony_ci	SEC_PD(SPICC,	0),
1138c2ecf20Sopenharmony_ci	SEC_PD(SPIFC,	0),
1148c2ecf20Sopenharmony_ci	SEC_PD(USB,	0),
1158c2ecf20Sopenharmony_ci	/* NIC is for the Arm NIC-400 interconnect, and should be always on */
1168c2ecf20Sopenharmony_ci	SEC_PD(NIC,	GENPD_FLAG_ALWAYS_ON),
1178c2ecf20Sopenharmony_ci	SEC_PD(PDMIN,	0),
1188c2ecf20Sopenharmony_ci	SEC_PD(RSA,	0),
1198c2ecf20Sopenharmony_ci};
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int meson_secure_pwrc_probe(struct platform_device *pdev)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	int i;
1248c2ecf20Sopenharmony_ci	struct device_node *sm_np;
1258c2ecf20Sopenharmony_ci	struct meson_secure_pwrc *pwrc;
1268c2ecf20Sopenharmony_ci	const struct meson_secure_pwrc_domain_data *match;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	match = of_device_get_match_data(&pdev->dev);
1298c2ecf20Sopenharmony_ci	if (!match) {
1308c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to get match data\n");
1318c2ecf20Sopenharmony_ci		return -ENODEV;
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	sm_np = of_find_compatible_node(NULL, NULL, "amlogic,meson-gxbb-sm");
1358c2ecf20Sopenharmony_ci	if (!sm_np) {
1368c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no secure-monitor node\n");
1378c2ecf20Sopenharmony_ci		return -ENODEV;
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL);
1418c2ecf20Sopenharmony_ci	if (!pwrc) {
1428c2ecf20Sopenharmony_ci		of_node_put(sm_np);
1438c2ecf20Sopenharmony_ci		return -ENOMEM;
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	pwrc->fw = meson_sm_get(sm_np);
1478c2ecf20Sopenharmony_ci	of_node_put(sm_np);
1488c2ecf20Sopenharmony_ci	if (!pwrc->fw)
1498c2ecf20Sopenharmony_ci		return -EPROBE_DEFER;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	pwrc->xlate.domains = devm_kcalloc(&pdev->dev, match->count,
1528c2ecf20Sopenharmony_ci					   sizeof(*pwrc->xlate.domains),
1538c2ecf20Sopenharmony_ci					   GFP_KERNEL);
1548c2ecf20Sopenharmony_ci	if (!pwrc->xlate.domains)
1558c2ecf20Sopenharmony_ci		return -ENOMEM;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	pwrc->domains = devm_kcalloc(&pdev->dev, match->count,
1588c2ecf20Sopenharmony_ci				     sizeof(*pwrc->domains), GFP_KERNEL);
1598c2ecf20Sopenharmony_ci	if (!pwrc->domains)
1608c2ecf20Sopenharmony_ci		return -ENOMEM;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	pwrc->xlate.num_domains = match->count;
1638c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, pwrc);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	for (i = 0 ; i < match->count ; ++i) {
1668c2ecf20Sopenharmony_ci		struct meson_secure_pwrc_domain *dom = &pwrc->domains[i];
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci		if (!match->domains[i].index)
1698c2ecf20Sopenharmony_ci			continue;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		dom->pwrc = pwrc;
1728c2ecf20Sopenharmony_ci		dom->index = match->domains[i].index;
1738c2ecf20Sopenharmony_ci		dom->base.name = match->domains[i].name;
1748c2ecf20Sopenharmony_ci		dom->base.flags = match->domains[i].flags;
1758c2ecf20Sopenharmony_ci		dom->base.power_on = meson_secure_pwrc_on;
1768c2ecf20Sopenharmony_ci		dom->base.power_off = meson_secure_pwrc_off;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci		pm_genpd_init(&dom->base, NULL, match->domains[i].is_off(dom));
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci		pwrc->xlate.domains[i] = &dom->base;
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	return of_genpd_add_provider_onecell(pdev->dev.of_node, &pwrc->xlate);
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic struct meson_secure_pwrc_domain_data meson_secure_a1_pwrc_data = {
1878c2ecf20Sopenharmony_ci	.domains = a1_pwrc_domains,
1888c2ecf20Sopenharmony_ci	.count = ARRAY_SIZE(a1_pwrc_domains),
1898c2ecf20Sopenharmony_ci};
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_cistatic const struct of_device_id meson_secure_pwrc_match_table[] = {
1928c2ecf20Sopenharmony_ci	{
1938c2ecf20Sopenharmony_ci		.compatible = "amlogic,meson-a1-pwrc",
1948c2ecf20Sopenharmony_ci		.data = &meson_secure_a1_pwrc_data,
1958c2ecf20Sopenharmony_ci	},
1968c2ecf20Sopenharmony_ci	{ /* sentinel */ }
1978c2ecf20Sopenharmony_ci};
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic struct platform_driver meson_secure_pwrc_driver = {
2008c2ecf20Sopenharmony_ci	.probe = meson_secure_pwrc_probe,
2018c2ecf20Sopenharmony_ci	.driver = {
2028c2ecf20Sopenharmony_ci		.name		= "meson_secure_pwrc",
2038c2ecf20Sopenharmony_ci		.of_match_table	= meson_secure_pwrc_match_table,
2048c2ecf20Sopenharmony_ci	},
2058c2ecf20Sopenharmony_ci};
2068c2ecf20Sopenharmony_cibuiltin_platform_driver(meson_secure_pwrc_driver);
207