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-2018 Xilinx, Inc.
   6 *
   7 *  Davorin Mista <davorin.mista@aggios.com>
   8 *  Jolly Shah <jollys@xilinx.com>
   9 *  Rajan Vaja <rajanv@xilinx.com>
  10 */
  11
  12#include <linux/err.h>
  13#include <linux/module.h>
  14#include <linux/of_platform.h>
  15#include <linux/platform_device.h>
  16#include <linux/pm_domain.h>
  17#include <linux/slab.h>
  18#include <linux/list.h>
  19#include <linux/firmware/xilinx/zynqmp/firmware.h>
  20#include <linux/soc/xilinx/zynqmp/power.h>
  21
  22#define DRIVER_NAME "zynqmp_gpd"
  23
  24/* Flag stating if PM nodes mapped to the PM domain has been requested */
  25#define ZYNQMP_PM_DOMAIN_REQUESTED      BIT(0)
  26
  27/**
  28 * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain
  29 * @gpd:                Generic power domain
  30 * @dev_list:           List of devices belong to power domain
  31 * @node_ids:           PM node IDs corresponding to device(s) inside PM domain
  32 * @node_id_num:        Number of PM node IDs
  33 * @flags:              ZynqMP PM domain flags
  34 */
  35struct zynqmp_pm_domain {
  36        struct generic_pm_domain gpd;
  37        struct list_head dev_list;
  38        u32 *node_ids;
  39        int node_id_num;
  40        u8 flags;
  41};
  42
  43/*
  44 * struct zynqmp_domain_device - Device node present in power domain
  45 * @dev: Device
  46 * &list: List member for the devices in domain list
  47 */
  48struct zynqmp_domain_device {
  49        struct device *dev;
  50        struct list_head list;
  51};
  52
  53/**
  54 * zynqmp_gpd_is_active_wakeup_path - Check if device is in wakeup source path
  55 * @dev: Device to check for wakeup source path
  56 * @not_used: Data member (not required)
  57 *
  58 * This function is checks device's child hierarchy and checks if any device is
  59 * set as wakeup source.
  60 *
  61 * Return:      1 if device is in wakeup source path else 0.
  62 */
  63static int zynqmp_gpd_is_active_wakeup_path(struct device *dev, void *not_used)
  64{
  65        int may_wakeup;
  66
  67        may_wakeup = device_may_wakeup(dev);
  68        if (may_wakeup)
  69                return may_wakeup;
  70
  71        return device_for_each_child(dev, NULL,
  72                        zynqmp_gpd_is_active_wakeup_path);
  73}
  74
  75/**
  76 * zynqmp_gpd_power_on - Power on PM domain
  77 * @domain:     Generic PM domain
  78 *
  79 * This function is called before devices inside a PM domain are resumed, to
  80 * power on PM domain.
  81 *
  82 * Return:      0 on success, error code otherwise.
  83 */
  84static int zynqmp_gpd_power_on(struct generic_pm_domain *domain)
  85{
  86        int i, status = 0;
  87        struct zynqmp_pm_domain *pd;
  88        const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
  89
  90        if (!eemi_ops || !eemi_ops->set_requirement)
  91                return status;
  92
  93        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
  94        for (i = 0; i < pd->node_id_num; i++) {
  95                status = eemi_ops->set_requirement(pd->node_ids[i],
  96                                        ZYNQMP_PM_CAPABILITY_ACCESS,
  97                                        ZYNQMP_PM_MAX_QOS,
  98                                        ZYNQMP_PM_REQUEST_ACK_BLOCKING);
  99                if (status)
 100                        break;
 101        }
 102        return status;
 103}
 104
 105/**
 106 * zynqmp_gpd_power_off - Power off PM domain
 107 * @domain:     Generic PM domain
 108 *
 109 * This function is called after devices inside a PM domain are suspended, to
 110 * power off PM domain.
 111 *
 112 * Return:      0 on success, error code otherwise.
 113 */
 114static int zynqmp_gpd_power_off(struct generic_pm_domain *domain)
 115{
 116        int i, status = 0;
 117        struct zynqmp_pm_domain *pd;
 118        struct zynqmp_domain_device *zdev, *tmp;
 119        u32 capabilities = 0;
 120        bool may_wakeup = 0;
 121        const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
 122
 123        if (!eemi_ops || !eemi_ops->set_requirement)
 124                return status;
 125
 126        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
 127
 128        /* If domain is already released there is nothing to be done */
 129        if (!(pd->flags & ZYNQMP_PM_DOMAIN_REQUESTED))
 130                return 0;
 131
 132        list_for_each_entry_safe(zdev, tmp, &pd->dev_list, list) {
 133                /* If device is in wakeup path, set capability to WAKEUP */
 134                may_wakeup = zynqmp_gpd_is_active_wakeup_path(zdev->dev, NULL);
 135                if (may_wakeup) {
 136                        dev_dbg(zdev->dev, "device is in wakeup path in %s\n",
 137                                domain->name);
 138                        capabilities = ZYNQMP_PM_CAPABILITY_WAKEUP;
 139                        break;
 140                }
 141        }
 142
 143        for (i = pd->node_id_num - 1; i >= 0; i--) {
 144                status = eemi_ops->set_requirement(pd->node_ids[i],
 145                                                   capabilities, 0,
 146                                                   ZYNQMP_PM_REQUEST_ACK_NO);
 147                /**
 148                 * If powering down of any node inside this domain fails,
 149                 * report and return the error
 150                 */
 151                if (status) {
 152                        pr_err("%s error %d, node %u\n", __func__, status,
 153                                pd->node_ids[i]);
 154                        return status;
 155                }
 156        }
 157
 158        return status;
 159}
 160
 161/**
 162 * zynqmp_gpd_attach_dev - Attach device to the PM domain
 163 * @domain:     Generic PM domain
 164 * @dev:        Device to attach
 165 *
 166 * Return:      0 on success, error code otherwise.
 167 */
 168static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain,
 169                                struct device *dev)
 170{
 171        int i, status;
 172        struct zynqmp_pm_domain *pd;
 173        struct zynqmp_domain_device *zdev;
 174        const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
 175
 176        if (!eemi_ops || !eemi_ops->request_node)
 177                return -ENXIO;
 178
 179        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
 180
 181        zdev = devm_kzalloc(dev, sizeof(*zdev), GFP_KERNEL);
 182        if (!zdev)
 183                return -ENOMEM;
 184
 185        zdev->dev = dev;
 186        list_add(&zdev->list, &pd->dev_list);
 187
 188        /* If this is not the first device to attach there is nothing to do */
 189        if (domain->device_count)
 190                return 0;
 191
 192        for (i = 0; i < pd->node_id_num; i++) {
 193                status = eemi_ops->request_node(pd->node_ids[i], 0, 0,
 194                                                ZYNQMP_PM_REQUEST_ACK_BLOCKING);
 195                /* If requesting a node fails print and return the error */
 196                if (status) {
 197                        pr_err("%s error %d, node %u\n", __func__, status,
 198                                        pd->node_ids[i]);
 199                        list_del(&zdev->list);
 200                        zdev->dev = NULL;
 201                        devm_kfree(dev, zdev);
 202                        return status;
 203                }
 204        }
 205
 206        pd->flags |= ZYNQMP_PM_DOMAIN_REQUESTED;
 207
 208        return 0;
 209}
 210
 211/**
 212 * zynqmp_gpd_detach_dev - Detach device from the PM domain
 213 * @domain:     Generic PM domain
 214 * @dev:        Device to detach
 215 */
 216static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain,
 217                                struct device *dev)
 218{
 219        int i, status;
 220        struct zynqmp_pm_domain *pd;
 221        struct zynqmp_domain_device *zdev, *tmp;
 222        const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
 223
 224        if (!eemi_ops || !eemi_ops->release_node)
 225                return;
 226
 227        pd = container_of(domain, struct zynqmp_pm_domain, gpd);
 228
 229        list_for_each_entry_safe(zdev, tmp, &pd->dev_list, list)
 230                if (zdev->dev == dev) {
 231                        list_del(&zdev->list);
 232                        zdev->dev = NULL;
 233                        devm_kfree(dev, zdev);
 234                }
 235
 236        /* If this is not the last device to detach there is nothing to do */
 237        if (domain->device_count)
 238                return;
 239
 240        for (i = 0; i < pd->node_id_num; i++) {
 241                status = eemi_ops->release_node(pd->node_ids[i]);
 242                /* If releasing a node fails print the error and return */
 243                if (status) {
 244                        pr_err("%s error %d, node %u\n", __func__, status,
 245                                pd->node_ids[i]);
 246                        return;
 247                }
 248        }
 249
 250        pd->flags &= ~ZYNQMP_PM_DOMAIN_REQUESTED;
 251}
 252
 253/**
 254 * zynqmp_gpd_probe - Initialize ZynqMP specific PM domains
 255 * @pdev:       Platform device pointer
 256 *
 257 * Description: This function populates struct zynqmp_pm_domain for each PM
 258 * domain and initalizes generic PM domain. If the "pd-id" DT property
 259 * of a certain domain is missing or invalid, that domain will be skipped.
 260 *
 261 * Return:      0 on success, error code otherwise.
 262 */
 263static int __init zynqmp_gpd_probe(struct platform_device *pdev)
 264{
 265        int ret;
 266        struct device_node *child_err, *child, *np = pdev->dev.of_node;
 267
 268        for_each_child_of_node(np, child) {
 269                struct zynqmp_pm_domain *pd;
 270
 271                pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL);
 272                if (!pd) {
 273                        ret = -ENOMEM;
 274                        goto err_cleanup;
 275                }
 276
 277                ret = of_property_count_u32_elems(child, "pd-id");
 278                if (ret <= 0)
 279                        goto err_cleanup;
 280
 281                pd->node_id_num = ret;
 282                pd->node_ids = devm_kcalloc(&pdev->dev, ret,
 283                                        sizeof(*pd->node_ids), GFP_KERNEL);
 284                if (!pd->node_ids) {
 285                        ret = -ENOMEM;
 286                        goto err_cleanup;
 287                }
 288
 289                ret = of_property_read_u32_array(child, "pd-id", pd->node_ids,
 290                                                        pd->node_id_num);
 291                if (ret)
 292                        goto err_cleanup;
 293
 294                pd->gpd.name = kstrdup(child->name, GFP_KERNEL);
 295                pd->gpd.power_off = zynqmp_gpd_power_off;
 296                pd->gpd.power_on = zynqmp_gpd_power_on;
 297                pd->gpd.attach_dev = zynqmp_gpd_attach_dev;
 298                pd->gpd.detach_dev = zynqmp_gpd_detach_dev;
 299
 300                /* Mark all PM domains as initially powered off */
 301                pm_genpd_init(&pd->gpd, NULL, true);
 302
 303                ret = of_genpd_add_provider_simple(child, &pd->gpd);
 304                if (ret)
 305                        goto err_cleanup;
 306
 307                INIT_LIST_HEAD(&pd->dev_list);
 308        }
 309
 310        return 0;
 311
 312err_cleanup:
 313        child_err = child;
 314        for_each_child_of_node(np, child) {
 315                if (child == child_err)
 316                        break;
 317                of_genpd_del_provider(child);
 318        }
 319
 320        return ret;
 321}
 322
 323static const struct of_device_id zynqmp_gpd_of_match[] = {
 324        { .compatible = "xlnx,zynqmp-genpd" },
 325        {},
 326};
 327
 328MODULE_DEVICE_TABLE(of, zynqmp_gpd_of_match);
 329
 330static struct platform_driver zynqmp_gpd_platform_driver = {
 331        .driver = {
 332                .name = DRIVER_NAME,
 333                .of_match_table = zynqmp_gpd_of_match,
 334        },
 335};
 336
 337static __init int zynqmp_gpd_init(void)
 338{
 339        return platform_driver_probe(&zynqmp_gpd_platform_driver,
 340                                     zynqmp_gpd_probe);
 341}
 342subsys_initcall(zynqmp_gpd_init);
 343