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_device.h> 11#include <linux/platform_device.h> 12#include <linux/pm_domain.h> 13#include <dt-bindings/power/meson-a1-power.h> 14#include <linux/arm-smccc.h> 15#include <linux/firmware/meson/meson_sm.h> 16 17#define PWRC_ON 1 18#define PWRC_OFF 0 19 20struct meson_secure_pwrc_domain { 21 struct generic_pm_domain base; 22 unsigned int index; 23 struct meson_secure_pwrc *pwrc; 24}; 25 26struct meson_secure_pwrc { 27 struct meson_secure_pwrc_domain *domains; 28 struct genpd_onecell_data xlate; 29 struct meson_sm_firmware *fw; 30}; 31 32struct meson_secure_pwrc_domain_desc { 33 unsigned int index; 34 unsigned int flags; 35 char *name; 36 bool (*is_off)(struct meson_secure_pwrc_domain *pwrc_domain); 37}; 38 39struct meson_secure_pwrc_domain_data { 40 unsigned int count; 41 struct meson_secure_pwrc_domain_desc *domains; 42}; 43 44static bool pwrc_secure_is_off(struct meson_secure_pwrc_domain *pwrc_domain) 45{ 46 int is_off = 1; 47 48 if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_GET, &is_off, 49 pwrc_domain->index, 0, 0, 0, 0) < 0) 50 pr_err("failed to get power domain status\n"); 51 52 return is_off; 53} 54 55static int meson_secure_pwrc_off(struct generic_pm_domain *domain) 56{ 57 int ret = 0; 58 struct meson_secure_pwrc_domain *pwrc_domain = 59 container_of(domain, struct meson_secure_pwrc_domain, base); 60 61 if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL, 62 pwrc_domain->index, PWRC_OFF, 0, 0, 0) < 0) { 63 pr_err("failed to set power domain off\n"); 64 ret = -EINVAL; 65 } 66 67 return ret; 68} 69 70static int meson_secure_pwrc_on(struct generic_pm_domain *domain) 71{ 72 int ret = 0; 73 struct meson_secure_pwrc_domain *pwrc_domain = 74 container_of(domain, struct meson_secure_pwrc_domain, base); 75 76 if (meson_sm_call(pwrc_domain->pwrc->fw, SM_A1_PWRC_SET, NULL, 77 pwrc_domain->index, PWRC_ON, 0, 0, 0) < 0) { 78 pr_err("failed to set power domain on\n"); 79 ret = -EINVAL; 80 } 81 82 return ret; 83} 84 85#define SEC_PD(__name, __flag) \ 86[PWRC_##__name##_ID] = \ 87{ \ 88 .name = #__name, \ 89 .index = PWRC_##__name##_ID, \ 90 .is_off = pwrc_secure_is_off, \ 91 .flags = __flag, \ 92} 93 94static struct meson_secure_pwrc_domain_desc a1_pwrc_domains[] = { 95 SEC_PD(DSPA, 0), 96 SEC_PD(DSPB, 0), 97 /* UART should keep working in ATF after suspend and before resume */ 98 SEC_PD(UART, GENPD_FLAG_ALWAYS_ON), 99 /* DMC is for DDR PHY ana/dig and DMC, and should be always on */ 100 SEC_PD(DMC, GENPD_FLAG_ALWAYS_ON), 101 SEC_PD(I2C, 0), 102 SEC_PD(PSRAM, 0), 103 SEC_PD(ACODEC, 0), 104 SEC_PD(AUDIO, 0), 105 SEC_PD(OTP, 0), 106 SEC_PD(DMA, GENPD_FLAG_ALWAYS_ON | GENPD_FLAG_IRQ_SAFE), 107 SEC_PD(SD_EMMC, 0), 108 SEC_PD(RAMA, 0), 109 /* SRAMB is used as ATF runtime memory, and should be always on */ 110 SEC_PD(RAMB, GENPD_FLAG_ALWAYS_ON), 111 SEC_PD(IR, 0), 112 SEC_PD(SPICC, 0), 113 SEC_PD(SPIFC, 0), 114 SEC_PD(USB, 0), 115 /* NIC is for the Arm NIC-400 interconnect, and should be always on */ 116 SEC_PD(NIC, GENPD_FLAG_ALWAYS_ON), 117 SEC_PD(PDMIN, 0), 118 SEC_PD(RSA, 0), 119}; 120 121static int meson_secure_pwrc_probe(struct platform_device *pdev) 122{ 123 int i; 124 struct device_node *sm_np; 125 struct meson_secure_pwrc *pwrc; 126 const struct meson_secure_pwrc_domain_data *match; 127 128 match = of_device_get_match_data(&pdev->dev); 129 if (!match) { 130 dev_err(&pdev->dev, "failed to get match data\n"); 131 return -ENODEV; 132 } 133 134 sm_np = of_find_compatible_node(NULL, NULL, "amlogic,meson-gxbb-sm"); 135 if (!sm_np) { 136 dev_err(&pdev->dev, "no secure-monitor node\n"); 137 return -ENODEV; 138 } 139 140 pwrc = devm_kzalloc(&pdev->dev, sizeof(*pwrc), GFP_KERNEL); 141 if (!pwrc) { 142 of_node_put(sm_np); 143 return -ENOMEM; 144 } 145 146 pwrc->fw = meson_sm_get(sm_np); 147 of_node_put(sm_np); 148 if (!pwrc->fw) 149 return -EPROBE_DEFER; 150 151 pwrc->xlate.domains = devm_kcalloc(&pdev->dev, match->count, 152 sizeof(*pwrc->xlate.domains), 153 GFP_KERNEL); 154 if (!pwrc->xlate.domains) 155 return -ENOMEM; 156 157 pwrc->domains = devm_kcalloc(&pdev->dev, match->count, 158 sizeof(*pwrc->domains), GFP_KERNEL); 159 if (!pwrc->domains) 160 return -ENOMEM; 161 162 pwrc->xlate.num_domains = match->count; 163 platform_set_drvdata(pdev, pwrc); 164 165 for (i = 0 ; i < match->count ; ++i) { 166 struct meson_secure_pwrc_domain *dom = &pwrc->domains[i]; 167 168 if (!match->domains[i].index) 169 continue; 170 171 dom->pwrc = pwrc; 172 dom->index = match->domains[i].index; 173 dom->base.name = match->domains[i].name; 174 dom->base.flags = match->domains[i].flags; 175 dom->base.power_on = meson_secure_pwrc_on; 176 dom->base.power_off = meson_secure_pwrc_off; 177 178 pm_genpd_init(&dom->base, NULL, match->domains[i].is_off(dom)); 179 180 pwrc->xlate.domains[i] = &dom->base; 181 } 182 183 return of_genpd_add_provider_onecell(pdev->dev.of_node, &pwrc->xlate); 184} 185 186static struct meson_secure_pwrc_domain_data meson_secure_a1_pwrc_data = { 187 .domains = a1_pwrc_domains, 188 .count = ARRAY_SIZE(a1_pwrc_domains), 189}; 190 191static const struct of_device_id meson_secure_pwrc_match_table[] = { 192 { 193 .compatible = "amlogic,meson-a1-pwrc", 194 .data = &meson_secure_a1_pwrc_data, 195 }, 196 { /* sentinel */ } 197}; 198 199static struct platform_driver meson_secure_pwrc_driver = { 200 .probe = meson_secure_pwrc_probe, 201 .driver = { 202 .name = "meson_secure_pwrc", 203 .of_match_table = meson_secure_pwrc_match_table, 204 }, 205}; 206builtin_platform_driver(meson_secure_pwrc_driver); 207