Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * ZynqMP Generic PM domain support
0004  *
0005  *  Copyright (C) 2015-2019 Xilinx, Inc.
0006  *
0007  *  Davorin Mista <davorin.mista@aggios.com>
0008  *  Jolly Shah <jollys@xilinx.com>
0009  *  Rajan Vaja <rajan.vaja@xilinx.com>
0010  */
0011 
0012 #include <linux/err.h>
0013 #include <linux/list.h>
0014 #include <linux/module.h>
0015 #include <linux/of_platform.h>
0016 #include <linux/platform_device.h>
0017 #include <linux/pm_domain.h>
0018 #include <linux/slab.h>
0019 
0020 #include <linux/firmware/xlnx-zynqmp.h>
0021 
0022 #define ZYNQMP_NUM_DOMAINS      (100)
0023 
0024 static int min_capability;
0025 
0026 /**
0027  * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain
0028  * @gpd:        Generic power domain
0029  * @node_id:        PM node ID corresponding to device inside PM domain
0030  * @requested:      The PM node mapped to the PM domain has been requested
0031  */
0032 struct zynqmp_pm_domain {
0033     struct generic_pm_domain gpd;
0034     u32 node_id;
0035     bool requested;
0036 };
0037 
0038 #define to_zynqmp_pm_domain(pm_domain) \
0039     container_of(pm_domain, struct zynqmp_pm_domain, gpd)
0040 
0041 /**
0042  * zynqmp_gpd_is_active_wakeup_path() - Check if device is in wakeup source
0043  *                  path
0044  * @dev:    Device to check for wakeup source path
0045  * @not_used:   Data member (not required)
0046  *
0047  * This function is checks device's child hierarchy and checks if any device is
0048  * set as wakeup source.
0049  *
0050  * Return: 1 if device is in wakeup source path else 0
0051  */
0052 static int zynqmp_gpd_is_active_wakeup_path(struct device *dev, void *not_used)
0053 {
0054     int may_wakeup;
0055 
0056     may_wakeup = device_may_wakeup(dev);
0057     if (may_wakeup)
0058         return may_wakeup;
0059 
0060     return device_for_each_child(dev, NULL,
0061             zynqmp_gpd_is_active_wakeup_path);
0062 }
0063 
0064 /**
0065  * zynqmp_gpd_power_on() - Power on PM domain
0066  * @domain: Generic PM domain
0067  *
0068  * This function is called before devices inside a PM domain are resumed, to
0069  * power on PM domain.
0070  *
0071  * Return: 0 on success, error code otherwise
0072  */
0073 static int zynqmp_gpd_power_on(struct generic_pm_domain *domain)
0074 {
0075     struct zynqmp_pm_domain *pd = to_zynqmp_pm_domain(domain);
0076     int ret;
0077 
0078     ret = zynqmp_pm_set_requirement(pd->node_id,
0079                     ZYNQMP_PM_CAPABILITY_ACCESS,
0080                     ZYNQMP_PM_MAX_QOS,
0081                     ZYNQMP_PM_REQUEST_ACK_BLOCKING);
0082     if (ret) {
0083         dev_err(&domain->dev,
0084             "failed to set requirement to 0x%x for PM node id %d: %d\n",
0085             ZYNQMP_PM_CAPABILITY_ACCESS, pd->node_id, ret);
0086         return ret;
0087     }
0088 
0089     dev_dbg(&domain->dev, "set requirement to 0x%x for PM node id %d\n",
0090         ZYNQMP_PM_CAPABILITY_ACCESS, pd->node_id);
0091 
0092     return 0;
0093 }
0094 
0095 /**
0096  * zynqmp_gpd_power_off() - Power off PM domain
0097  * @domain: Generic PM domain
0098  *
0099  * This function is called after devices inside a PM domain are suspended, to
0100  * power off PM domain.
0101  *
0102  * Return: 0 on success, error code otherwise
0103  */
0104 static int zynqmp_gpd_power_off(struct generic_pm_domain *domain)
0105 {
0106     struct zynqmp_pm_domain *pd = to_zynqmp_pm_domain(domain);
0107     int ret;
0108     struct pm_domain_data *pdd, *tmp;
0109     u32 capabilities = min_capability;
0110     bool may_wakeup;
0111 
0112     /* If domain is already released there is nothing to be done */
0113     if (!pd->requested) {
0114         dev_dbg(&domain->dev, "PM node id %d is already released\n",
0115             pd->node_id);
0116         return 0;
0117     }
0118 
0119     list_for_each_entry_safe(pdd, tmp, &domain->dev_list, list_node) {
0120         /* If device is in wakeup path, set capability to WAKEUP */
0121         may_wakeup = zynqmp_gpd_is_active_wakeup_path(pdd->dev, NULL);
0122         if (may_wakeup) {
0123             dev_dbg(pdd->dev, "device is in wakeup path in %s\n",
0124                 domain->name);
0125             capabilities = ZYNQMP_PM_CAPABILITY_WAKEUP;
0126             break;
0127         }
0128     }
0129 
0130     ret = zynqmp_pm_set_requirement(pd->node_id, capabilities, 0,
0131                     ZYNQMP_PM_REQUEST_ACK_NO);
0132     if (ret) {
0133         dev_err(&domain->dev,
0134             "failed to set requirement to 0x%x for PM node id %d: %d\n",
0135             capabilities, pd->node_id, ret);
0136         return ret;
0137     }
0138 
0139     dev_dbg(&domain->dev, "set requirement to 0x%x for PM node id %d\n",
0140         capabilities, pd->node_id);
0141 
0142     return 0;
0143 }
0144 
0145 /**
0146  * zynqmp_gpd_attach_dev() - Attach device to the PM domain
0147  * @domain: Generic PM domain
0148  * @dev:    Device to attach
0149  *
0150  * Return: 0 on success, error code otherwise
0151  */
0152 static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain,
0153                  struct device *dev)
0154 {
0155     struct zynqmp_pm_domain *pd = to_zynqmp_pm_domain(domain);
0156     struct device_link *link;
0157     int ret;
0158 
0159     link = device_link_add(dev, &domain->dev, DL_FLAG_SYNC_STATE_ONLY);
0160     if (!link)
0161         dev_dbg(&domain->dev, "failed to create device link for %s\n",
0162             dev_name(dev));
0163 
0164     /* If this is not the first device to attach there is nothing to do */
0165     if (domain->device_count)
0166         return 0;
0167 
0168     ret = zynqmp_pm_request_node(pd->node_id, 0, 0,
0169                      ZYNQMP_PM_REQUEST_ACK_BLOCKING);
0170     if (ret) {
0171         dev_err(&domain->dev, "%s request failed for node %d: %d\n",
0172             domain->name, pd->node_id, ret);
0173         return ret;
0174     }
0175 
0176     pd->requested = true;
0177 
0178     dev_dbg(&domain->dev, "%s requested PM node id %d\n",
0179         dev_name(dev), pd->node_id);
0180 
0181     return 0;
0182 }
0183 
0184 /**
0185  * zynqmp_gpd_detach_dev() - Detach device from the PM domain
0186  * @domain: Generic PM domain
0187  * @dev:    Device to detach
0188  */
0189 static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain,
0190                   struct device *dev)
0191 {
0192     struct zynqmp_pm_domain *pd = to_zynqmp_pm_domain(domain);
0193     int ret;
0194 
0195     /* If this is not the last device to detach there is nothing to do */
0196     if (domain->device_count)
0197         return;
0198 
0199     ret = zynqmp_pm_release_node(pd->node_id);
0200     if (ret) {
0201         dev_err(&domain->dev, "failed to release PM node id %d: %d\n",
0202             pd->node_id, ret);
0203         return;
0204     }
0205 
0206     pd->requested = false;
0207 
0208     dev_dbg(&domain->dev, "%s released PM node id %d\n",
0209         dev_name(dev), pd->node_id);
0210 }
0211 
0212 static struct generic_pm_domain *zynqmp_gpd_xlate
0213                 (struct of_phandle_args *genpdspec, void *data)
0214 {
0215     struct genpd_onecell_data *genpd_data = data;
0216     unsigned int i, idx = genpdspec->args[0];
0217     struct zynqmp_pm_domain *pd;
0218 
0219     pd = to_zynqmp_pm_domain(genpd_data->domains[0]);
0220 
0221     if (genpdspec->args_count != 1)
0222         return ERR_PTR(-EINVAL);
0223 
0224     /* Check for existing pm domains */
0225     for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++) {
0226         if (pd[i].node_id == idx)
0227             goto done;
0228     }
0229 
0230     /**
0231      * Add index in empty node_id of power domain list as no existing
0232      * power domain found for current index.
0233      */
0234     for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++) {
0235         if (pd[i].node_id == 0) {
0236             pd[i].node_id = idx;
0237             break;
0238         }
0239     }
0240 
0241 done:
0242     if (!genpd_data->domains[i] || i == ZYNQMP_NUM_DOMAINS)
0243         return ERR_PTR(-ENOENT);
0244 
0245     return genpd_data->domains[i];
0246 }
0247 
0248 static int zynqmp_gpd_probe(struct platform_device *pdev)
0249 {
0250     int i;
0251     struct genpd_onecell_data *zynqmp_pd_data;
0252     struct generic_pm_domain **domains;
0253     struct zynqmp_pm_domain *pd;
0254     struct device *dev = &pdev->dev;
0255 
0256     pd = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*pd), GFP_KERNEL);
0257     if (!pd)
0258         return -ENOMEM;
0259 
0260     zynqmp_pd_data = devm_kzalloc(dev, sizeof(*zynqmp_pd_data), GFP_KERNEL);
0261     if (!zynqmp_pd_data)
0262         return -ENOMEM;
0263 
0264     zynqmp_pd_data->xlate = zynqmp_gpd_xlate;
0265 
0266     domains = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*domains),
0267                    GFP_KERNEL);
0268     if (!domains)
0269         return -ENOMEM;
0270 
0271     if (!of_device_is_compatible(dev->parent->of_node,
0272                      "xlnx,zynqmp-firmware"))
0273         min_capability = ZYNQMP_PM_CAPABILITY_UNUSABLE;
0274 
0275     for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++, pd++) {
0276         pd->node_id = 0;
0277         pd->gpd.name = kasprintf(GFP_KERNEL, "domain%d", i);
0278         pd->gpd.power_off = zynqmp_gpd_power_off;
0279         pd->gpd.power_on = zynqmp_gpd_power_on;
0280         pd->gpd.attach_dev = zynqmp_gpd_attach_dev;
0281         pd->gpd.detach_dev = zynqmp_gpd_detach_dev;
0282 
0283         domains[i] = &pd->gpd;
0284 
0285         /* Mark all PM domains as initially powered off */
0286         pm_genpd_init(&pd->gpd, NULL, true);
0287     }
0288 
0289     zynqmp_pd_data->domains = domains;
0290     zynqmp_pd_data->num_domains = ZYNQMP_NUM_DOMAINS;
0291     of_genpd_add_provider_onecell(dev->parent->of_node, zynqmp_pd_data);
0292 
0293     return 0;
0294 }
0295 
0296 static int zynqmp_gpd_remove(struct platform_device *pdev)
0297 {
0298     of_genpd_del_provider(pdev->dev.parent->of_node);
0299 
0300     return 0;
0301 }
0302 
0303 static void zynqmp_gpd_sync_state(struct device *dev)
0304 {
0305     int ret;
0306 
0307     ret = zynqmp_pm_init_finalize();
0308     if (ret)
0309         dev_warn(dev, "failed to release power management to firmware\n");
0310 }
0311 
0312 static struct platform_driver zynqmp_power_domain_driver = {
0313     .driver = {
0314         .name = "zynqmp_power_controller",
0315         .sync_state = zynqmp_gpd_sync_state,
0316     },
0317     .probe = zynqmp_gpd_probe,
0318     .remove = zynqmp_gpd_remove,
0319 };
0320 module_platform_driver(zynqmp_power_domain_driver);
0321 
0322 MODULE_ALIAS("platform:zynqmp_power_controller");