| /* |
| * drivers/video/tegra/host/nvhost_acm.c |
| * |
| * Tegra Graphics Host Automatic Clock Management |
| * |
| * Copyright (c) 2010-2013, NVIDIA Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/stat.h> |
| #include <linux/string.h> |
| #include <linux/sched.h> |
| #include <linux/err.h> |
| #include <linux/export.h> |
| #include <linux/device.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/clk/tegra.h> |
| #include <trace/events/nvhost.h> |
| |
| #include <mach/powergate.h> |
| #include <mach/hardware.h> |
| #include <mach/mc.h> |
| #include <mach/pm_domains.h> |
| |
| #include "nvhost_acm.h" |
| #include "dev.h" |
| #include "bus_client.h" |
| |
| #define ACM_SUSPEND_WAIT_FOR_IDLE_TIMEOUT (2 * HZ) |
| #define POWERGATE_DELAY 10 |
| #define MAX_DEVID_LENGTH 16 |
| |
| #ifdef CONFIG_PM_GENERIC_DOMAINS |
| static int nvhost_module_power_on(struct generic_pm_domain *domain); |
| static int nvhost_module_power_off(struct generic_pm_domain *domain); |
| #endif |
| |
| DEFINE_MUTEX(client_list_lock); |
| |
| struct nvhost_module_client { |
| struct list_head node; |
| unsigned long rate[NVHOST_MODULE_MAX_CLOCKS]; |
| void *priv; |
| }; |
| |
| static void do_powergate_locked(int id) |
| { |
| nvhost_dbg_fn("%d", id); |
| if (id != -1 && tegra_powergate_is_powered(id)) |
| tegra_powergate_partition(id); |
| } |
| |
| static void do_unpowergate_locked(int id) |
| { |
| int ret = 0; |
| if (id != -1) { |
| ret = tegra_unpowergate_partition(id); |
| if (ret) |
| pr_err("%s: unpowergate failed: id = %d\n", |
| __func__, id); |
| } |
| } |
| |
| static void do_module_reset_locked(struct platform_device *dev) |
| { |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| /* assert module and mc client reset */ |
| if (pdata->powergate_ids[0] != -1) |
| tegra_powergate_mc_flush(pdata->powergate_ids[0]); |
| if (pdata->powergate_ids[0] != -1) |
| tegra_powergate_mc_disable(pdata->powergate_ids[0]); |
| if (pdata->clocks[0].reset) |
| tegra_periph_reset_assert(pdata->clk[0]); |
| |
| if (pdata->powergate_ids[1] != -1) |
| tegra_powergate_mc_flush(pdata->powergate_ids[1]); |
| if (pdata->powergate_ids[1] != -1) |
| tegra_powergate_mc_disable(pdata->powergate_ids[1]); |
| if (pdata->clocks[1].reset) |
| tegra_periph_reset_assert(pdata->clk[1]); |
| |
| udelay(POWERGATE_DELAY); |
| |
| /* deassert reset */ |
| if (pdata->clocks[0].reset) |
| tegra_periph_reset_deassert(pdata->clk[0]); |
| if (pdata->powergate_ids[0] != -1) |
| tegra_powergate_mc_enable(pdata->powergate_ids[0]); |
| if (pdata->powergate_ids[0] != -1) |
| tegra_powergate_mc_flush_done(pdata->powergate_ids[0]); |
| |
| if (pdata->clocks[1].reset) |
| tegra_periph_reset_deassert(pdata->clk[1]); |
| if (pdata->powergate_ids[1] != -1) |
| tegra_powergate_mc_enable(pdata->powergate_ids[1]); |
| if (pdata->powergate_ids[1] != -1) |
| tegra_powergate_mc_flush_done(pdata->powergate_ids[1]); |
| } |
| |
| void nvhost_module_reset(struct platform_device *dev) |
| { |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| dev_dbg(&dev->dev, |
| "%s: asserting %s module reset (id %d, id2 %d)\n", |
| __func__, dev_name(&dev->dev), |
| pdata->powergate_ids[0], pdata->powergate_ids[1]); |
| |
| mutex_lock(&pdata->lock); |
| do_module_reset_locked(dev); |
| mutex_unlock(&pdata->lock); |
| |
| if (pdata->finalize_poweron) |
| pdata->finalize_poweron(dev); |
| |
| dev_dbg(&dev->dev, "%s: module %s out of reset\n", |
| __func__, dev_name(&dev->dev)); |
| } |
| |
| void nvhost_module_busy(struct platform_device *dev) |
| { |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| /* Explicitly turn on the host1x clocks |
| * - This is needed as host1x driver sets ignore_children = true |
| * to cater the use case of display clock ON but host1x clock OFF |
| * in OS-Idle-Display-ON case |
| * - This was easily done in ACM as it only checked the ref count |
| * of host1x (or any device for that matter) to be zero before |
| * turning off its clock |
| * - However, runtime PM checks to see if *ANY* child of device is |
| * in ACTIVE state and if yes, it doesn't suspend the parent. As a |
| * result of this, display && host1x clocks remains ON during |
| * OS-Idle-Display-ON case |
| * - The code below fixes this use-case |
| */ |
| if (dev->dev.parent && (dev->dev.parent != &platform_bus)) |
| nvhost_module_busy(nvhost_get_parent(dev)); |
| |
| #ifdef CONFIG_PM_RUNTIME |
| pm_runtime_get_sync(&dev->dev); |
| #endif |
| |
| if (pdata->busy) |
| pdata->busy(dev); |
| } |
| |
| void nvhost_module_idle_mult(struct platform_device *dev, int refs) |
| { |
| int original_refs = refs; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| #ifdef CONFIG_PM_RUNTIME |
| if (atomic_read(&dev->dev.power.usage_count) == refs) { |
| if (pdata->idle) |
| pdata->idle(dev); |
| } |
| |
| while (refs--) { |
| pm_runtime_mark_last_busy(&dev->dev); |
| if (pdata->clockgate_delay) |
| pm_runtime_put_sync_autosuspend(&dev->dev); |
| else |
| pm_runtime_put(&dev->dev); |
| } |
| #else |
| if (pdata->idle) |
| pdata->idle(dev); |
| #endif |
| |
| /* Explicitly turn off the host1x clocks */ |
| if (dev->dev.parent && (dev->dev.parent != &platform_bus)) |
| nvhost_module_idle_mult(nvhost_get_parent(dev), original_refs); |
| } |
| |
| int nvhost_module_get_rate(struct platform_device *dev, unsigned long *rate, |
| int index) |
| { |
| struct clk *c; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| c = pdata->clk[index]; |
| if (!c) |
| return -EINVAL; |
| |
| /* Need to enable client to get correct rate */ |
| nvhost_module_busy(dev); |
| *rate = clk_get_rate(c); |
| nvhost_module_idle(dev); |
| return 0; |
| } |
| |
| static int nvhost_module_update_rate(struct platform_device *dev, int index) |
| { |
| unsigned long rate = 0; |
| struct nvhost_module_client *m; |
| unsigned long devfreq_rate, default_rate; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| int ret; |
| |
| if (!pdata->clk[index]) |
| return -EINVAL; |
| |
| /* If devfreq is on, use that clock rate, otherwise default */ |
| devfreq_rate = pdata->clocks[index].devfreq_rate; |
| default_rate = devfreq_rate ? |
| devfreq_rate : pdata->clocks[index].default_rate; |
| default_rate = clk_round_rate(pdata->clk[index], default_rate); |
| |
| list_for_each_entry(m, &pdata->client_list, node) { |
| unsigned long r = m->rate[index]; |
| if (!r) |
| r = default_rate; |
| rate = max(r, rate); |
| } |
| if (!rate) |
| rate = default_rate; |
| |
| trace_nvhost_module_update_rate(dev->name, |
| pdata->clocks[index].name, rate); |
| |
| ret = clk_set_rate(pdata->clk[index], rate); |
| |
| if (pdata->update_clk) |
| pdata->update_clk(dev); |
| |
| return ret; |
| |
| } |
| |
| int nvhost_module_set_rate(struct platform_device *dev, void *priv, |
| unsigned long rate, int index, int bBW) |
| { |
| struct nvhost_module_client *m; |
| int ret = 0; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| nvhost_dbg_fn("%s", dev->name); |
| |
| mutex_lock(&client_list_lock); |
| list_for_each_entry(m, &pdata->client_list, node) { |
| if (m->priv == priv) { |
| if (bBW) { |
| /* |
| * If client sets BW, then we need to |
| * convert it to freq. |
| * rate is Bps and input param of |
| * tegra_emc_bw_to_freq_req is KBps. |
| */ |
| unsigned int freq_khz = |
| tegra_emc_bw_to_freq_req |
| ((unsigned long)(rate >> 10)); |
| |
| m->rate[index] = |
| clk_round_rate(pdata->clk[index], |
| (unsigned long)(freq_khz << 10)); |
| } else |
| m->rate[index] = |
| clk_round_rate(pdata->clk[index], rate); |
| } |
| } |
| |
| ret = nvhost_module_update_rate(dev, index); |
| mutex_unlock(&client_list_lock); |
| return ret; |
| } |
| |
| int nvhost_module_add_client(struct platform_device *dev, void *priv) |
| { |
| struct nvhost_module_client *client; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| nvhost_dbg_fn("%s num_clks=%d priv=%p", dev->name, |
| pdata->num_clks, priv); |
| |
| client = kzalloc(sizeof(*client), GFP_KERNEL); |
| if (!client) |
| return -ENOMEM; |
| |
| INIT_LIST_HEAD(&client->node); |
| client->priv = priv; |
| |
| mutex_lock(&client_list_lock); |
| list_add_tail(&client->node, &pdata->client_list); |
| mutex_unlock(&client_list_lock); |
| |
| return 0; |
| } |
| |
| void nvhost_module_remove_client(struct platform_device *dev, void *priv) |
| { |
| int i; |
| struct nvhost_module_client *m; |
| int found = 0; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| nvhost_dbg_fn("%s priv=%p", dev->name, priv); |
| |
| mutex_lock(&client_list_lock); |
| list_for_each_entry(m, &pdata->client_list, node) { |
| if (priv == m->priv) { |
| list_del(&m->node); |
| found = 1; |
| break; |
| } |
| } |
| if (found) { |
| kfree(m); |
| for (i = 0; i < pdata->num_clks; i++) |
| nvhost_module_update_rate(dev, i); |
| } |
| mutex_unlock(&client_list_lock); |
| } |
| |
| static ssize_t powergate_delay_store(struct kobject *kobj, |
| struct kobj_attribute *attr, const char *buf, size_t count) |
| { |
| int powergate_delay = 0, ret = 0; |
| struct nvhost_device_power_attr *power_attribute = |
| container_of(attr, struct nvhost_device_power_attr, \ |
| power_attr[NVHOST_POWER_SYSFS_ATTRIB_POWERGATE_DELAY]); |
| struct platform_device *dev = power_attribute->ndev; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| if (!pdata->can_powergate) { |
| dev_info(&dev->dev, "does not support power-gating\n"); |
| return count; |
| } |
| |
| mutex_lock(&pdata->lock); |
| ret = sscanf(buf, "%d", &powergate_delay); |
| if (ret == 1 && powergate_delay >= 0) { |
| struct generic_pm_domain *genpd = |
| pd_to_genpd(dev->dev.pm_domain); |
| pdata->powergate_delay = powergate_delay; |
| pm_genpd_set_poweroff_delay(genpd, pdata->powergate_delay); |
| } |
| else |
| dev_err(&dev->dev, "Invalid powergate delay\n"); |
| mutex_unlock(&pdata->lock); |
| |
| return count; |
| } |
| |
| static ssize_t powergate_delay_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| int ret; |
| struct nvhost_device_power_attr *power_attribute = |
| container_of(attr, struct nvhost_device_power_attr, \ |
| power_attr[NVHOST_POWER_SYSFS_ATTRIB_POWERGATE_DELAY]); |
| struct platform_device *dev = power_attribute->ndev; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| mutex_lock(&pdata->lock); |
| ret = sprintf(buf, "%d\n", pdata->powergate_delay); |
| mutex_unlock(&pdata->lock); |
| |
| return ret; |
| } |
| |
| static ssize_t clockgate_delay_store(struct kobject *kobj, |
| struct kobj_attribute *attr, const char *buf, size_t count) |
| { |
| int clockgate_delay = 0, ret = 0; |
| struct nvhost_device_power_attr *power_attribute = |
| container_of(attr, struct nvhost_device_power_attr, \ |
| power_attr[NVHOST_POWER_SYSFS_ATTRIB_CLOCKGATE_DELAY]); |
| struct platform_device *dev = power_attribute->ndev; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| mutex_lock(&pdata->lock); |
| ret = sscanf(buf, "%d", &clockgate_delay); |
| if (ret == 1 && clockgate_delay >= 0) { |
| pdata->clockgate_delay = clockgate_delay; |
| pm_runtime_set_autosuspend_delay(&dev->dev, |
| pdata->clockgate_delay); |
| } |
| else |
| dev_err(&dev->dev, "Invalid clockgate delay\n"); |
| mutex_unlock(&pdata->lock); |
| |
| return count; |
| } |
| |
| static ssize_t clockgate_delay_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| int ret; |
| struct nvhost_device_power_attr *power_attribute = |
| container_of(attr, struct nvhost_device_power_attr, \ |
| power_attr[NVHOST_POWER_SYSFS_ATTRIB_CLOCKGATE_DELAY]); |
| struct platform_device *dev = power_attribute->ndev; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| mutex_lock(&pdata->lock); |
| ret = sprintf(buf, "%d\n", pdata->clockgate_delay); |
| mutex_unlock(&pdata->lock); |
| |
| return ret; |
| } |
| |
| int nvhost_module_set_devfreq_rate(struct platform_device *dev, int index, |
| unsigned long rate) |
| { |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| int ret; |
| |
| rate = clk_round_rate(pdata->clk[index], rate); |
| pdata->clocks[index].devfreq_rate = rate; |
| |
| trace_nvhost_module_set_devfreq_rate(dev->name, |
| pdata->clocks[index].name, rate); |
| |
| mutex_lock(&client_list_lock); |
| ret = nvhost_module_update_rate(dev, index); |
| mutex_unlock(&client_list_lock); |
| |
| return ret; |
| } |
| |
| int nvhost_module_init(struct platform_device *dev) |
| { |
| int i = 0, err = 0; |
| struct kobj_attribute *attr = NULL; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| /* initialize clocks to known state (=enabled) */ |
| pdata->num_clks = 0; |
| INIT_LIST_HEAD(&pdata->client_list); |
| while (i < NVHOST_MODULE_MAX_CLOCKS && pdata->clocks[i].name) { |
| char devname[MAX_DEVID_LENGTH]; |
| long rate = pdata->clocks[i].default_rate; |
| struct clk *c; |
| |
| snprintf(devname, MAX_DEVID_LENGTH, |
| (dev->id <= 0) ? "tegra_%s" : "tegra_%s.%d", |
| dev->name, dev->id); |
| c = clk_get_sys(devname, pdata->clocks[i].name); |
| if (IS_ERR(c)) { |
| dev_err(&dev->dev, "clk_get_sys failed for i=%d %s:%s", |
| i, devname, pdata->clocks[i].name); |
| /* arguably we should fail init here instead... */ |
| i++; |
| continue; |
| } |
| nvhost_dbg_fn("%s->clk[%d] -> %s:%s:%p", |
| dev->name, pdata->num_clks, |
| devname, pdata->clocks[i].name, |
| c); |
| rate = clk_round_rate(c, rate); |
| clk_prepare_enable(c); |
| clk_set_rate(c, rate); |
| pdata->clk[pdata->num_clks++] = c; |
| i++; |
| } |
| pdata->num_clks = i; |
| |
| /* reset the module */ |
| mutex_lock(&pdata->lock); |
| do_module_reset_locked(dev); |
| mutex_unlock(&pdata->lock); |
| |
| /* disable the clocks */ |
| for (i = 0; i < pdata->num_clks; ++i) |
| clk_disable_unprepare(pdata->clk[i]); |
| |
| /* power gate units that we can power gate */ |
| if (pdata->can_powergate) { |
| do_powergate_locked(pdata->powergate_ids[0]); |
| do_powergate_locked(pdata->powergate_ids[1]); |
| } else { |
| do_unpowergate_locked(pdata->powergate_ids[0]); |
| do_unpowergate_locked(pdata->powergate_ids[1]); |
| } |
| |
| /* Init the power sysfs attributes for this device */ |
| pdata->power_attrib = devm_kzalloc(&dev->dev, |
| sizeof(struct nvhost_device_power_attr), GFP_KERNEL); |
| if (!pdata->power_attrib) { |
| dev_err(&dev->dev, "Unable to allocate sysfs attributes\n"); |
| return -ENOMEM; |
| } |
| pdata->power_attrib->ndev = dev; |
| |
| pdata->power_kobj = kobject_create_and_add("acm", &dev->dev.kobj); |
| if (!pdata->power_kobj) { |
| dev_err(&dev->dev, "Could not add dir 'power'\n"); |
| err = -EIO; |
| goto fail_attrib_alloc; |
| } |
| |
| attr = &pdata->power_attrib->power_attr[NVHOST_POWER_SYSFS_ATTRIB_CLOCKGATE_DELAY]; |
| attr->attr.name = "clockgate_delay"; |
| attr->attr.mode = S_IWUSR | S_IRUGO; |
| attr->show = clockgate_delay_show; |
| attr->store = clockgate_delay_store; |
| sysfs_attr_init(&attr->attr); |
| if (sysfs_create_file(pdata->power_kobj, &attr->attr)) { |
| dev_err(&dev->dev, "Could not create sysfs attribute clockgate_delay\n"); |
| err = -EIO; |
| goto fail_clockdelay; |
| } |
| |
| attr = &pdata->power_attrib->power_attr[NVHOST_POWER_SYSFS_ATTRIB_POWERGATE_DELAY]; |
| attr->attr.name = "powergate_delay"; |
| attr->attr.mode = S_IWUSR | S_IRUGO; |
| attr->show = powergate_delay_show; |
| attr->store = powergate_delay_store; |
| sysfs_attr_init(&attr->attr); |
| if (sysfs_create_file(pdata->power_kobj, &attr->attr)) { |
| dev_err(&dev->dev, "Could not create sysfs attribute powergate_delay\n"); |
| err = -EIO; |
| goto fail_powergatedelay; |
| } |
| |
| if (pdata->clockgate_delay) { |
| pm_runtime_set_autosuspend_delay(&dev->dev, |
| pdata->clockgate_delay); |
| pm_runtime_use_autosuspend(&dev->dev); |
| } |
| pm_runtime_enable(&dev->dev); |
| if (!pm_runtime_enabled(&dev->dev)) |
| nvhost_module_enable_clk(&dev->dev); |
| return 0; |
| |
| fail_powergatedelay: |
| attr = &pdata->power_attrib->power_attr[NVHOST_POWER_SYSFS_ATTRIB_CLOCKGATE_DELAY]; |
| sysfs_remove_file(pdata->power_kobj, &attr->attr); |
| |
| fail_clockdelay: |
| kobject_put(pdata->power_kobj); |
| |
| fail_attrib_alloc: |
| kfree(pdata->power_attrib); |
| |
| return err; |
| } |
| EXPORT_SYMBOL(nvhost_module_init); |
| |
| int nvhost_module_suspend(struct device *dev) |
| { |
| struct nvhost_device_data *pdata = dev_get_drvdata(dev); |
| |
| if (pm_runtime_suspended(dev)) |
| return 0; |
| |
| if (pm_runtime_barrier(dev)) |
| return -EBUSY; |
| |
| if (pdata->suspend_ndev) |
| pdata->suspend_ndev(dev); |
| |
| return 0; |
| } |
| |
| int nvhost_module_resume(struct device *dev) |
| { |
| struct nvhost_device_data *pdata = dev_get_drvdata(dev); |
| |
| if (!pdata->can_powergate && pdata->finalize_poweron) { |
| nvhost_module_enable_clk(dev); |
| pdata->finalize_poweron(to_platform_device(dev)); |
| nvhost_module_disable_clk(dev); |
| } |
| |
| return 0; |
| } |
| |
| void nvhost_module_deinit(struct platform_device *dev) |
| { |
| int i; |
| struct kobj_attribute *attr = NULL; |
| struct nvhost_device_data *pdata = platform_get_drvdata(dev); |
| |
| nvhost_module_suspend(&dev->dev); |
| for (i = 0; i < pdata->num_clks; i++) |
| clk_put(pdata->clk[i]); |
| |
| if (pdata->power_kobj) { |
| for (i = 0; i < NVHOST_POWER_SYSFS_ATTRIB_MAX; i++) { |
| attr = &pdata->power_attrib->power_attr[i]; |
| sysfs_remove_file(pdata->power_kobj, &attr->attr); |
| } |
| |
| kobject_put(pdata->power_kobj); |
| } |
| |
| } |
| |
| #ifdef CONFIG_PM |
| const struct dev_pm_ops nvhost_module_pm_ops = { |
| #if defined(CONFIG_PM_RUNTIME) && !defined(CONFIG_PM_GENERIC_DOMAINS) |
| .runtime_suspend = nvhost_module_disable_clk, |
| .runtime_resume = nvhost_module_enable_clk, |
| #endif |
| }; |
| #endif |
| |
| /* common runtime pm and power domain APIs */ |
| int nvhost_module_add_domain(struct generic_pm_domain *domain, |
| struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct nvhost_device_data *pdata; |
| struct dev_power_governor *pm_domain_gov = NULL; |
| |
| pdata = platform_get_drvdata(pdev); |
| if (!pdata) |
| return -EINVAL; |
| |
| if (!pdata->can_powergate) |
| #ifdef CONFIG_PM_GENERIC_DOMAINS |
| pm_domain_gov = &pm_domain_always_on_gov; |
| |
| if (__pm_genpd_name_add_device(domain->name, &pdev->dev, NULL)) { |
| pm_genpd_init(domain, pm_domain_gov, true); |
| |
| domain->power_off = nvhost_module_power_off; |
| domain->power_on = nvhost_module_power_on; |
| domain->dev_ops.start = nvhost_module_enable_clk; |
| domain->dev_ops.stop = nvhost_module_disable_clk; |
| domain->dev_ops.save_state = nvhost_module_prepare_poweroff; |
| domain->dev_ops.restore_state = nvhost_module_finalize_poweron; |
| /* overwrite save/restore fptrs set by pm_genpd_init */ |
| domain->domain.ops.suspend = nvhost_client_device_suspend; |
| domain->domain.ops.resume = nvhost_client_device_resume; |
| |
| ret = pm_genpd_add_device(domain, &pdev->dev); |
| if (pdata->powergate_delay) |
| pm_genpd_set_poweroff_delay(domain, |
| pdata->powergate_delay); |
| tegra_pd_add_sd(domain); |
| } |
| #endif |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(nvhost_module_add_domain); |
| |
| int nvhost_module_enable_clk(struct device *dev) |
| { |
| int index = 0; |
| struct nvhost_device_data *pdata; |
| |
| /* enable parent's clock if required */ |
| if (dev->parent && dev->parent != &platform_bus) |
| nvhost_module_enable_clk(dev->parent); |
| |
| pdata = dev_get_drvdata(dev); |
| if (!pdata) |
| return -EINVAL; |
| |
| for (index = 0; index < pdata->num_clks; index++) { |
| int err = clk_prepare_enable(pdata->clk[index]); |
| if (err) { |
| dev_err(dev, "Cannot turn on clock %s", |
| pdata->clocks[index].name); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(nvhost_module_enable_clk); |
| |
| int nvhost_module_disable_clk(struct device *dev) |
| { |
| int index = 0; |
| struct nvhost_device_data *pdata; |
| |
| pdata = dev_get_drvdata(dev); |
| if (!pdata) |
| return -EINVAL; |
| |
| for (index = 0; index < pdata->num_clks; index++) |
| clk_disable_unprepare(pdata->clk[index]); |
| |
| /* disable parent's clock if required */ |
| if (dev->parent && dev->parent != &platform_bus) |
| nvhost_module_disable_clk(dev->parent); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(nvhost_module_disable_clk); |
| |
| #ifdef CONFIG_PM_GENERIC_DOMAINS |
| static int nvhost_module_power_on(struct generic_pm_domain *domain) |
| { |
| struct nvhost_device_data *pdata; |
| |
| pdata = container_of(domain, struct nvhost_device_data, pd); |
| |
| mutex_lock(&pdata->lock); |
| if (pdata->can_powergate) { |
| do_unpowergate_locked(pdata->powergate_ids[0]); |
| do_unpowergate_locked(pdata->powergate_ids[1]); |
| } |
| |
| if (pdata->powerup_reset) |
| do_module_reset_locked(pdata->pdev); |
| mutex_unlock(&pdata->lock); |
| |
| return 0; |
| } |
| |
| static int nvhost_module_power_off(struct generic_pm_domain *domain) |
| { |
| struct nvhost_device_data *pdata; |
| |
| pdata = container_of(domain, struct nvhost_device_data, pd); |
| |
| mutex_lock(&pdata->lock); |
| if (pdata->can_powergate) { |
| do_powergate_locked(pdata->powergate_ids[0]); |
| do_powergate_locked(pdata->powergate_ids[1]); |
| } |
| mutex_unlock(&pdata->lock); |
| |
| return 0; |
| } |
| |
| int nvhost_module_prepare_poweroff(struct device *dev) |
| { |
| struct nvhost_device_data *pdata; |
| |
| pdata = dev_get_drvdata(dev); |
| if (!pdata) |
| return -EINVAL; |
| |
| if (pdata->prepare_poweroff) |
| pdata->prepare_poweroff(to_platform_device(dev)); |
| |
| return 0; |
| } |
| |
| int nvhost_module_finalize_poweron(struct device *dev) |
| { |
| struct nvhost_device_data *pdata; |
| |
| pdata = dev_get_drvdata(dev); |
| if (!pdata) |
| return -EINVAL; |
| |
| if (pdata->finalize_poweron) |
| pdata->finalize_poweron(to_platform_device(dev)); |
| |
| return 0; |
| } |
| #endif |
| |
| /* public host1x power management APIs */ |
| bool nvhost_module_powered_ext(struct platform_device *dev) |
| { |
| struct platform_device *pdev; |
| |
| if (!nvhost_get_parent(dev)) { |
| dev_err(&dev->dev, "Module powered called with wrong dev\n"); |
| return 0; |
| } |
| |
| /* get the parent */ |
| pdev = to_platform_device(dev->dev.parent); |
| |
| return nvhost_module_powered(pdev); |
| } |
| |
| void nvhost_module_busy_ext(struct platform_device *dev) |
| { |
| struct platform_device *pdev; |
| |
| if (!nvhost_get_parent(dev)) { |
| dev_err(&dev->dev, "Module busy called with wrong dev\n"); |
| return; |
| } |
| |
| /* get the parent */ |
| pdev = to_platform_device(dev->dev.parent); |
| |
| nvhost_module_busy(pdev); |
| } |
| EXPORT_SYMBOL(nvhost_module_busy_ext); |
| |
| void nvhost_module_idle_ext(struct platform_device *dev) |
| { |
| struct platform_device *pdev; |
| |
| if (!nvhost_get_parent(dev)) { |
| dev_err(&dev->dev, "Module idle called with wrong dev\n"); |
| return; |
| } |
| |
| /* get the parent */ |
| pdev = to_platform_device(dev->dev.parent); |
| |
| nvhost_module_idle(pdev); |
| } |
| EXPORT_SYMBOL(nvhost_module_idle_ext); |