1// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
2/*
3 * Copyright (c) 2019 Amlogic, Inc.
4 * Author: Jianxin Pan <jianxin.pan@amlogic.com>
5 */
6
7#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
8
9#include <linux/io.h>
10#include <linux/of.h>
11#include <linux/platform_device.h>
12#include <linux/pm_domain.h>
13#include <dt-bindings/power/meson-a1-power.h>
14#include <dt-bindings/power/amlogic,c3-pwrc.h>
15#include <dt-bindings/power/meson-s4-power.h>
16#include <linux/arm-smccc.h>
17#include <linux/firmware/meson/meson_sm.h>
18#include <linux/module.h>
19
20#define PWRC_ON		1
21#define PWRC_OFF	0
22
23struct meson_secure_pwrc_domain {
24	struct generic_pm_domain base;
25	unsigned int index;
26	struct meson_secure_pwrc *pwrc;
27};
28
29struct meson_secure_pwrc {
30	struct meson_secure_pwrc_domain *domains;
31	struct genpd_onecell_data xlate;
32	struct meson_sm_firmware *fw;
33};
34
35struct meson_secure_pwrc_domain_desc {
36	unsigned int index;
37	unsigned int flags;
38	char *name;
39	bool (*is_off)(struct meson_secure_pwrc_domain *pwrc_domain);
40};
41
42struct meson_secure_pwrc_domain_data {
43	unsigned int count;
44	struct meson_secure_pwrc_domain_desc *domains;
45};
46
47static bool pwrc_secure_is_off(struct meson_secure_pwrc_domain *pwrc_domain)
48{
49	int is_off = 1;
50
51	if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_GET, &is_off,
52			  pwrc_domain->index, 0, 0, 0, 0) < 0)
53		pr_err("failed to get power domain status\n");
54
55	return is_off;
56}
57
58static int meson_secure_pwrc_off(struct generic_pm_domain *domain)
59{
60	int ret = 0;
61	struct meson_secure_pwrc_domain *pwrc_domain =
62		container_of(domain, struct meson_secure_pwrc_domain, base);
63
64	if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL,
65			  pwrc_domain->index, PWRC_OFF, 0, 0, 0) < 0) {
66		pr_err("failed to set power domain off\n");
67		ret = -EINVAL;
68	}
69
70	return ret;
71}
72
73static int meson_secure_pwrc_on(struct generic_pm_domain *domain)
74{
75	int ret = 0;
76	struct meson_secure_pwrc_domain *pwrc_domain =
77		container_of(domain, struct meson_secure_pwrc_domain, base);
78
79	if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL,
80			  pwrc_domain->index, PWRC_ON, 0, 0, 0) < 0) {
81		pr_err("failed to set power domain on\n");
82		ret = -EINVAL;
83	}
84
85	return ret;
86}
87
88#define SEC_PD(__name, __flag)			\
89[PWRC_##__name##_ID] =				\
90{						\
91	.name = #__name,			\
92	.index = PWRC_##__name##_ID,		\
93	.is_off = pwrc_secure_is_off,	\
94	.flags = __flag,			\
95}
96
97static struct meson_secure_pwrc_domain_desc a1_pwrc_domains[] = {
98	SEC_PD(DSPA,	0),
99	SEC_PD(DSPB,	0),
100	/* UART should keep working in ATF after suspend and before resume */
101	SEC_PD(UART,	GENPD_FLAG_ALWAYS_ON),
102	/* DMC is for DDR PHY ana/dig and DMC, and should be always on */
103	SEC_PD(DMC,	GENPD_FLAG_ALWAYS_ON),
104	SEC_PD(I2C,	0),
105	SEC_PD(PSRAM,	0),
106	SEC_PD(ACODEC,	0),
107	SEC_PD(AUDIO,	0),
108	SEC_PD(OTP,	0),
109	SEC_PD(DMA,	GENPD_FLAG_ALWAYS_ON | GENPD_FLAG_IRQ_SAFE),
110	SEC_PD(SD_EMMC,	0),
111	SEC_PD(RAMA,	0),
112	/* SRAMB is used as ATF runtime memory, and should be always on */
113	SEC_PD(RAMB,	GENPD_FLAG_ALWAYS_ON),
114	SEC_PD(IR,	0),
115	SEC_PD(SPICC,	0),
116	SEC_PD(SPIFC,	0),
117	SEC_PD(USB,	0),
118	/* NIC is for the Arm NIC-400 interconnect, and should be always on */
119	SEC_PD(NIC,	GENPD_FLAG_ALWAYS_ON),
120	SEC_PD(PDMIN,	0),
121	SEC_PD(RSA,	0),
122};
123
124static struct meson_secure_pwrc_domain_desc c3_pwrc_domains[] = {
125	SEC_PD(C3_NNA,	0),
126	SEC_PD(C3_AUDIO,	GENPD_FLAG_ALWAYS_ON),
127	SEC_PD(C3_SDIOA,	GENPD_FLAG_ALWAYS_ON),
128	SEC_PD(C3_EMMC,	GENPD_FLAG_ALWAYS_ON),
129	SEC_PD(C3_USB_COMB, GENPD_FLAG_ALWAYS_ON),
130	SEC_PD(C3_SDCARD,	GENPD_FLAG_ALWAYS_ON),
131	SEC_PD(C3_ETH,	GENPD_FLAG_ALWAYS_ON),
132	SEC_PD(C3_GE2D,	GENPD_FLAG_ALWAYS_ON),
133	SEC_PD(C3_CVE,	GENPD_FLAG_ALWAYS_ON),
134	SEC_PD(C3_GDC_WRAP,	GENPD_FLAG_ALWAYS_ON),
135	SEC_PD(C3_ISP_TOP,		GENPD_FLAG_ALWAYS_ON),
136	SEC_PD(C3_MIPI_ISP_WRAP, GENPD_FLAG_ALWAYS_ON),
137	SEC_PD(C3_VCODEC,	0),
138};
139
140static struct meson_secure_pwrc_domain_desc s4_pwrc_domains[] = {
141	SEC_PD(S4_DOS_HEVC,	0),
142	SEC_PD(S4_DOS_VDEC,	0),
143	SEC_PD(S4_VPU_HDMI,	0),
144	SEC_PD(S4_USB_COMB,	0),
145	SEC_PD(S4_GE2D,		0),
146	/* ETH is for ethernet online wakeup, and should be always on */
147	SEC_PD(S4_ETH,		GENPD_FLAG_ALWAYS_ON),
148	SEC_PD(S4_DEMOD,	0),
149	SEC_PD(S4_AUDIO,	0),
150};
151
152static int meson_secure_pwrc_probe(struct platform_device *pdev)
153{
154	int i;
155	struct device_node *sm_np;
156	struct meson_secure_pwrc *pwrc;
157	const struct meson_secure_pwrc_domain_data *match;
158
159	match = of_device_get_match_data(&pdev->dev);
160	if (!match) {
161		dev_err(&pdev->dev, "failed to get match data\n");
162		return -ENODEV;
163	}
164
165	sm_np = of_find_compatible_node(NULL, NULL, "amlogic,meson-gxbb-sm");
166	if (!sm_np) {
167		dev_err(&pdev->dev, "no secure-monitor node\n");
168		return -ENODEV;
169	}
170
171	pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL);
172	if (!pwrc) {
173		of_node_put(sm_np);
174		return -ENOMEM;
175	}
176
177	pwrc->fw = meson_sm_get(sm_np);
178	of_node_put(sm_np);
179	if (!pwrc->fw)
180		return -EPROBE_DEFER;
181
182	pwrc->xlate.domains = devm_kcalloc(&pdev->dev, match->count,
183					   sizeof(*pwrc->xlate.domains),
184					   GFP_KERNEL);
185	if (!pwrc->xlate.domains)
186		return -ENOMEM;
187
188	pwrc->domains = devm_kcalloc(&pdev->dev, match->count,
189				     sizeof(*pwrc->domains), GFP_KERNEL);
190	if (!pwrc->domains)
191		return -ENOMEM;
192
193	pwrc->xlate.num_domains = match->count;
194	platform_set_drvdata(pdev, pwrc);
195
196	for (i = 0 ; i < match->count ; ++i) {
197		struct meson_secure_pwrc_domain *dom = &pwrc->domains[i];
198
199		if (!match->domains[i].name)
200			continue;
201
202		dom->pwrc = pwrc;
203		dom->index = match->domains[i].index;
204		dom->base.name = match->domains[i].name;
205		dom->base.flags = match->domains[i].flags;
206		dom->base.power_on = meson_secure_pwrc_on;
207		dom->base.power_off = meson_secure_pwrc_off;
208
209		pm_genpd_init(&dom->base, NULL, match->domains[i].is_off(dom));
210
211		pwrc->xlate.domains[i] = &dom->base;
212	}
213
214	return of_genpd_add_provider_onecell(pdev->dev.of_node, &pwrc->xlate);
215}
216
217static struct meson_secure_pwrc_domain_data meson_secure_a1_pwrc_data = {
218	.domains = a1_pwrc_domains,
219	.count = ARRAY_SIZE(a1_pwrc_domains),
220};
221
222static struct meson_secure_pwrc_domain_data amlogic_secure_c3_pwrc_data = {
223	.domains = c3_pwrc_domains,
224	.count = ARRAY_SIZE(c3_pwrc_domains),
225};
226
227static struct meson_secure_pwrc_domain_data meson_secure_s4_pwrc_data = {
228	.domains = s4_pwrc_domains,
229	.count = ARRAY_SIZE(s4_pwrc_domains),
230};
231
232static const struct of_device_id meson_secure_pwrc_match_table[] = {
233	{
234		.compatible = "amlogic,meson-a1-pwrc",
235		.data = &meson_secure_a1_pwrc_data,
236	},
237	{
238		.compatible = "amlogic,c3-pwrc",
239		.data = &amlogic_secure_c3_pwrc_data,
240	},
241	{
242		.compatible = "amlogic,meson-s4-pwrc",
243		.data = &meson_secure_s4_pwrc_data,
244	},
245	{ /* sentinel */ }
246};
247MODULE_DEVICE_TABLE(of, meson_secure_pwrc_match_table);
248
249static struct platform_driver meson_secure_pwrc_driver = {
250	.probe = meson_secure_pwrc_probe,
251	.driver = {
252		.name		= "meson_secure_pwrc",
253		.of_match_table	= meson_secure_pwrc_match_table,
254	},
255};
256module_platform_driver(meson_secure_pwrc_driver);
257MODULE_LICENSE("Dual MIT/GPL");
258