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