linux/drivers/soc/xilinx/zynqmp_pm_domains.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * ZynqMP Generic PM domain support
   4 *
   5 *  Copyright (C) 2015-2019 Xilinx, Inc.
   6 *
   7 *  Davorin Mista <davorin.mista@aggios.com>
   8 *  Jolly Shah <jollys@xilinx.com>
   9 *  Rajan Vaja <rajan.vaja@xilinx.com>
  10 */
  11
  12#include <linux/err.h>
  13#include <linux/list.h>
  14#include <linux/module.h>
  15#include <linux/of_platform.h>
  16#include <linux/platform_device.h>
  17#include <linux/pm_domain.h>
  18#include <linux/slab.h>
  19
  20#include <linux/firmware/xlnx-zynqmp.h>
  21
  22#define ZYNQMP_NUM_DOMAINS              (100)
  23/* Flag stating if PM nodes mapped to the PM domain has been requested */
  24#define ZYNQMP_PM_DOMAIN_REQUESTED      BIT(0)
  25
  26static int min_capability;
  27
  28/**
  29 * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain
  30 * @gpd:                Generic power domain
  31 * @node_id:            PM node ID corresponding to device inside PM domain
  32 * @flags:              ZynqMP PM domain flags
  33 */
  34struct zynqmp_pm_domain {
  35        struct generic_pm_domain gpd;
  36        u32 node_id;
  37        u8 flags;
  38};
  39
  40/**
  41 * zynqmp_gpd_is_active_wakeup_path() - Check if device is in wakeup source
  42 *                                      path
  43 * @dev:        Device to check for wakeup source path
  44 * @not_used:   Data member (not required)
  45 *
  46 * This function is checks device's child hierarchy and checks if any device is
  47 * set as wakeup source.
  48 *
  49 * Return: 1 if device is in wakeup source path else 0
  50 */
  51static int zynqmp_gpd_is_active_wakeup_path(struct device *dev, void *not_used)
  52{
  53        int may_wakeup;
  54
  55        may_wakeup = device_may_wakeup(dev);
  56        if (may_wakeup)
  57                return may_wakeup;
  58
  59        return device_for_each_child(dev, NULL,
  60                        zynqmp_gpd_is_active_wakeup_path);
  61}
  62
  63/**
  64 * zynqmp_gpd_power_on() - Power on PM domain
  65 * @domain:     Generic PM domain
  66 *
  67 * This function is called before devices inside a PM domain are resumed, to
  68 * power on PM domain.
  69 *
  70 * Return: 0 on success, error code otherwise
  71 */
  72static int zynqmp_gpd_power_on(struct generic_pm_domain *domain)
  73{
  74        int ret;
  75        struct zynqmp_pm_domain *pd;
  76
  77        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
  78        ret = zynqmp_pm_set_requirement(pd->node_id,
  79                                        ZYNQMP_PM_CAPABILITY_ACCESS,
  80                                        ZYNQMP_PM_MAX_QOS,
  81                                        ZYNQMP_PM_REQUEST_ACK_BLOCKING);
  82        if (ret) {
  83                pr_err("%s() %s set requirement for node %d failed: %d\n",
  84                       __func__, domain->name, pd->node_id, ret);
  85                return ret;
  86        }
  87
  88        pr_debug("%s() Powered on %s domain\n", __func__, domain->name);
  89        return 0;
  90}
  91
  92/**
  93 * zynqmp_gpd_power_off() - Power off PM domain
  94 * @domain:     Generic PM domain
  95 *
  96 * This function is called after devices inside a PM domain are suspended, to
  97 * power off PM domain.
  98 *
  99 * Return: 0 on success, error code otherwise
 100 */
 101static int zynqmp_gpd_power_off(struct generic_pm_domain *domain)
 102{
 103        int ret;
 104        struct pm_domain_data *pdd, *tmp;
 105        struct zynqmp_pm_domain *pd;
 106        u32 capabilities = min_capability;
 107        bool may_wakeup;
 108
 109        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
 110
 111        /* If domain is already released there is nothing to be done */
 112        if (!(pd->flags & ZYNQMP_PM_DOMAIN_REQUESTED)) {
 113                pr_debug("%s() %s domain is already released\n",
 114                         __func__, domain->name);
 115                return 0;
 116        }
 117
 118        list_for_each_entry_safe(pdd, tmp, &domain->dev_list, list_node) {
 119                /* If device is in wakeup path, set capability to WAKEUP */
 120                may_wakeup = zynqmp_gpd_is_active_wakeup_path(pdd->dev, NULL);
 121                if (may_wakeup) {
 122                        dev_dbg(pdd->dev, "device is in wakeup path in %s\n",
 123                                domain->name);
 124                        capabilities = ZYNQMP_PM_CAPABILITY_WAKEUP;
 125                        break;
 126                }
 127        }
 128
 129        ret = zynqmp_pm_set_requirement(pd->node_id, capabilities, 0,
 130                                        ZYNQMP_PM_REQUEST_ACK_NO);
 131        /**
 132         * If powering down of any node inside this domain fails,
 133         * report and return the error
 134         */
 135        if (ret) {
 136                pr_err("%s() %s set requirement for node %d failed: %d\n",
 137                       __func__, domain->name, pd->node_id, ret);
 138                return ret;
 139        }
 140
 141        pr_debug("%s() Powered off %s domain\n", __func__, domain->name);
 142        return 0;
 143}
 144
 145/**
 146 * zynqmp_gpd_attach_dev() - Attach device to the PM domain
 147 * @domain:     Generic PM domain
 148 * @dev:        Device to attach
 149 *
 150 * Return: 0 on success, error code otherwise
 151 */
 152static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain,
 153                                 struct device *dev)
 154{
 155        int ret;
 156        struct zynqmp_pm_domain *pd;
 157
 158        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
 159
 160        /* If this is not the first device to attach there is nothing to do */
 161        if (domain->device_count)
 162                return 0;
 163
 164        ret = zynqmp_pm_request_node(pd->node_id, 0, 0,
 165                                     ZYNQMP_PM_REQUEST_ACK_BLOCKING);
 166        /* If requesting a node fails print and return the error */
 167        if (ret) {
 168                pr_err("%s() %s request failed for node %d: %d\n",
 169                       __func__, domain->name, pd->node_id, ret);
 170                return ret;
 171        }
 172
 173        pd->flags |= ZYNQMP_PM_DOMAIN_REQUESTED;
 174
 175        pr_debug("%s() %s attached to %s domain\n", __func__,
 176                 dev_name(dev), domain->name);
 177        return 0;
 178}
 179
 180/**
 181 * zynqmp_gpd_detach_dev() - Detach device from the PM domain
 182 * @domain:     Generic PM domain
 183 * @dev:        Device to detach
 184 */
 185static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain,
 186                                  struct device *dev)
 187{
 188        int ret;
 189        struct zynqmp_pm_domain *pd;
 190
 191        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
 192
 193        /* If this is not the last device to detach there is nothing to do */
 194        if (domain->device_count)
 195                return;
 196
 197        ret = zynqmp_pm_release_node(pd->node_id);
 198        /* If releasing a node fails print the error and return */
 199        if (ret) {
 200                pr_err("%s() %s release failed for node %d: %d\n",
 201                       __func__, domain->name, pd->node_id, ret);
 202                return;
 203        }
 204
 205        pd->flags &= ~ZYNQMP_PM_DOMAIN_REQUESTED;
 206
 207        pr_debug("%s() %s detached from %s domain\n", __func__,
 208                 dev_name(dev), domain->name);
 209}
 210
 211static struct generic_pm_domain *zynqmp_gpd_xlate
 212                                (struct of_phandle_args *genpdspec, void *data)
 213{
 214        struct genpd_onecell_data *genpd_data = data;
 215        unsigned int i, idx = genpdspec->args[0];
 216        struct zynqmp_pm_domain *pd;
 217
 218        pd = container_of(genpd_data->domains[0], struct zynqmp_pm_domain, gpd);
 219
 220        if (genpdspec->args_count != 1)
 221                return ERR_PTR(-EINVAL);
 222
 223        /* Check for existing pm domains */
 224        for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++) {
 225                if (pd[i].node_id == idx)
 226                        goto done;
 227        }
 228
 229        /**
 230         * Add index in empty node_id of power domain list as no existing
 231         * power domain found for current index.
 232         */
 233        for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++) {
 234                if (pd[i].node_id == 0) {
 235                        pd[i].node_id = idx;
 236                        break;
 237                }
 238        }
 239
 240done:
 241        if (!genpd_data->domains[i] || i == ZYNQMP_NUM_DOMAINS)
 242                return ERR_PTR(-ENOENT);
 243
 244        return genpd_data->domains[i];
 245}
 246
 247static int zynqmp_gpd_probe(struct platform_device *pdev)
 248{
 249        int i;
 250        struct genpd_onecell_data *zynqmp_pd_data;
 251        struct generic_pm_domain **domains;
 252        struct zynqmp_pm_domain *pd;
 253        struct device *dev = &pdev->dev;
 254
 255        pd = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*pd), GFP_KERNEL);
 256        if (!pd)
 257                return -ENOMEM;
 258
 259        zynqmp_pd_data = devm_kzalloc(dev, sizeof(*zynqmp_pd_data), GFP_KERNEL);
 260        if (!zynqmp_pd_data)
 261                return -ENOMEM;
 262
 263        zynqmp_pd_data->xlate = zynqmp_gpd_xlate;
 264
 265        domains = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*domains),
 266                               GFP_KERNEL);
 267        if (!domains)
 268                return -ENOMEM;
 269
 270        if (!of_device_is_compatible(dev->parent->of_node,
 271                                     "xlnx,zynqmp-firmware"))
 272                min_capability = ZYNQMP_PM_CAPABILITY_UNUSABLE;
 273
 274        for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++, pd++) {
 275                pd->node_id = 0;
 276                pd->gpd.name = kasprintf(GFP_KERNEL, "domain%d", i);
 277                pd->gpd.power_off = zynqmp_gpd_power_off;
 278                pd->gpd.power_on = zynqmp_gpd_power_on;
 279                pd->gpd.attach_dev = zynqmp_gpd_attach_dev;
 280                pd->gpd.detach_dev = zynqmp_gpd_detach_dev;
 281
 282                domains[i] = &pd->gpd;
 283
 284                /* Mark all PM domains as initially powered off */
 285                pm_genpd_init(&pd->gpd, NULL, true);
 286        }
 287
 288        zynqmp_pd_data->domains = domains;
 289        zynqmp_pd_data->num_domains = ZYNQMP_NUM_DOMAINS;
 290        of_genpd_add_provider_onecell(dev->parent->of_node, zynqmp_pd_data);
 291
 292        return 0;
 293}
 294
 295static int zynqmp_gpd_remove(struct platform_device *pdev)
 296{
 297        of_genpd_del_provider(pdev->dev.parent->of_node);
 298
 299        return 0;
 300}
 301
 302static struct platform_driver zynqmp_power_domain_driver = {
 303        .driver = {
 304                .name = "zynqmp_power_controller",
 305        },
 306        .probe = zynqmp_gpd_probe,
 307        .remove = zynqmp_gpd_remove,
 308};
 309module_platform_driver(zynqmp_power_domain_driver);
 310
 311MODULE_ALIAS("platform:zynqmp_power_controller");
 312