return 0;
}
-void gk20a_channel_update(struct channel_gk20a *c)
+void gk20a_channel_update(struct channel_gk20a *c, int nr_completed)
{
struct gk20a *g = c->g;
struct vm_gk20a *vm = c->vm;
struct channel_gk20a_job *job, *n;
+ int i;
mutex_lock(&c->jobs_lock);
list_for_each_entry_safe(job, n, &c->jobs, list) {
gk20a_channel_idle(g->dev);
}
mutex_unlock(&c->jobs_lock);
+
+ for (i = 0; i < nr_completed; i++)
+ gk20a_channel_idle(c->g->dev);
}
static int gk20a_submit_channel_gpfifo(struct channel_gk20a *c,
}
int channel_gk20a_commit_va(struct channel_gk20a *c);
-void gk20a_channel_update(struct channel_gk20a *c);
+void gk20a_channel_update(struct channel_gk20a *c, int nr_completed);
int gk20a_init_channel_support(struct gk20a *, u32 chid);
void gk20a_free_channel(struct channel_gk20a *ch, bool finish);
bool gk20a_channel_update_and_check_timeout(struct channel_gk20a *ch,
#include <trace/events/gk20a.h>
#include <linux/highmem.h>
+#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#define GK20A_NUM_CDEVS 5
+static int gk20a_pm_finalize_poweron(struct device *dev);
+static int gk20a_pm_prepare_poweroff(struct device *dev);
+
static inline void set_gk20a(struct platform_device *dev, struct gk20a *gk20a)
{
gk20a_get_platform(dev)->g = gk20a;
nvhost_dbg_fn("");
#ifndef CONFIG_PM_RUNTIME
- nvhost_gk20a_finalize_poweron(dev);
+ gk20a_pm_finalize_poweron(&dev->dev);
#endif
err = gk20a_init_mm_setup_sw(g);
{
nvhost_dbg_fn("");
#ifndef CONFIG_PM_RUNTIME
- nvhost_gk20a_prepare_poweroff(dev);
+ gk20a_pm_prepare_poweroff(&dev->dev);
#endif
}
WARN_ON(g->client_refcount < 0);
}
-int nvhost_gk20a_prepare_poweroff(struct platform_device *dev)
+static int gk20a_pm_prepare_poweroff(struct device *_dev)
{
+ struct platform_device *dev = to_platform_device(_dev);
struct gk20a *g = get_gk20a(dev);
int ret = 0;
g->gpu_characteristics.rev);
}
-int nvhost_gk20a_finalize_poweron(struct platform_device *dev)
+static int gk20a_pm_finalize_poweron(struct device *_dev)
{
+ struct platform_device *dev = to_platform_device(_dev);
struct gk20a *g = get_gk20a(dev);
int err, nice_value;
return ch;
}
+static int gk20a_pm_enable_clk(struct device *dev)
+{
+ int index = 0;
+ struct gk20a_platform *platform;
+
+ platform = dev_get_drvdata(dev);
+ if (!platform)
+ return -EINVAL;
+
+ for (index = 0; index < platform->num_clks; index++) {
+ int err = clk_prepare_enable(platform->clk[index]);
+ if (err)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gk20a_pm_disable_clk(struct device *dev)
+{
+ int index = 0;
+ struct gk20a_platform *platform;
+
+ platform = dev_get_drvdata(dev);
+ if (!platform)
+ return -EINVAL;
+
+ for (index = 0; index < platform->num_clks; index++)
+ clk_disable_unprepare(platform->clk[index]);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+const struct dev_pm_ops gk20a_pm_ops = {
+#if defined(CONFIG_PM_RUNTIME) && !defined(CONFIG_PM_GENERIC_DOMAINS)
+ .runtime_resume = gk20a_pm_enable_clk,
+ .runtime_suspend = gk20a_pm_disable_clk,
+#endif
+};
+#endif
+
+static int gk20a_pm_railgate(struct generic_pm_domain *domain)
+{
+ struct gk20a *g = container_of(domain, struct gk20a, pd);
+ struct gk20a_platform *platform = platform_get_drvdata(g->dev);
+ int ret = 0;
+
+ if (platform->railgate)
+ ret = platform->railgate(platform->g->dev);
+
+ return ret;
+}
+
+static int gk20a_pm_unrailgate(struct generic_pm_domain *domain)
+{
+ struct gk20a *g = container_of(domain, struct gk20a, pd);
+ struct gk20a_platform *platform = platform_get_drvdata(g->dev);
+ int ret = 0;
+
+ if (platform->unrailgate)
+ ret = platform->unrailgate(platform->g->dev);
+
+ return ret;
+}
+
+static int gk20a_pm_suspend(struct device *dev)
+{
+ struct gk20a_platform *platform = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (atomic_read(&dev->power.usage_count) > 1)
+ return -EBUSY;
+
+ ret = gk20a_pm_prepare_poweroff(dev);
+ if (ret)
+ return ret;
+
+ if (platform->suspend)
+ platform->suspend(dev);
+
+ return 0;
+}
+
+
+static int gk20a_pm_initialise_domain(struct platform_device *pdev)
+{
+ struct gk20a_platform *platform = platform_get_drvdata(pdev);
+ struct dev_power_governor *pm_domain_gov = NULL;
+ struct generic_pm_domain *domain = &platform->g->pd;
+ int ret = 0;
+
+ domain->name = kstrdup(pdev->name, GFP_KERNEL);
+
+ if (!platform->can_railgate)
+ pm_domain_gov = &pm_domain_always_on_gov;
+
+ pm_genpd_init(domain, pm_domain_gov, true);
+
+ domain->power_off = gk20a_pm_railgate;
+ domain->power_on = gk20a_pm_unrailgate;
+ domain->dev_ops.start = gk20a_pm_enable_clk;
+ domain->dev_ops.stop = gk20a_pm_disable_clk;
+ domain->dev_ops.save_state = gk20a_pm_prepare_poweroff;
+ domain->dev_ops.restore_state = gk20a_pm_finalize_poweron;
+ domain->dev_ops.suspend = gk20a_pm_suspend;
+ domain->dev_ops.resume = gk20a_pm_finalize_poweron;
+
+ device_set_wakeup_capable(&pdev->dev, 0);
+ ret = pm_genpd_add_device(domain, &pdev->dev);
+
+ if (platform->railgate_delay)
+ pm_genpd_set_poweroff_delay(domain, platform->railgate_delay);
+
+ return ret;
+}
+
+static int gk20a_pm_init(struct platform_device *dev)
+{
+ struct gk20a_platform *platform = platform_get_drvdata(dev);
+ int err = 0;
+
+ /* Initialise pm runtime */
+ if (platform->clockgate_delay) {
+ pm_runtime_set_autosuspend_delay(&dev->dev,
+ platform->clockgate_delay);
+ pm_runtime_use_autosuspend(&dev->dev);
+ }
+
+ pm_runtime_enable(&dev->dev);
+ if (!pm_runtime_enabled(&dev->dev))
+ gk20a_pm_enable_clk(&dev->dev);
+
+ /* Enable runtime railgating if possible. If not,
+ * turn on the rail now. */
+ if (platform->can_railgate && IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS))
+ platform->railgate(dev);
+ else
+ platform->unrailgate(dev);
+
+ /* genpd will take care of runtime power management if it is enabled */
+ if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS))
+ err = gk20a_pm_initialise_domain(dev);
+
+ return err;
+}
+
static int gk20a_probe(struct platform_device *dev)
{
struct gk20a *gk20a;
return err;
}
+ err = gk20a_pm_init(dev);
+ if (err) {
+ dev_err(&dev->dev, "pm init failed");
+ return err;
+ }
+
+ if (platform->late_probe) {
+ err = platform->late_probe(dev);
+ if (err) {
+ dev_err(&dev->dev, "late probe failed");
+ return err;
+ }
+ }
+
gpu_cdev = &gk20a->gk20a_cdev;
gpu_cdev->gk20a_freq_table_size = tegra_gpufreq_table_size_get();
gpu_cdev->gk20a_freq_state = 0;
.of_match_table = tegra_gk20a_of_match,
#endif
#ifdef CONFIG_PM
- .pm = &nvhost_module_pm_ops,
+ .pm = &gk20a_pm_ops,
#endif
}
};
int irq_stall;
int irq_nonstall;
+
+ /* Power domain representing power partition */
+ struct generic_pm_domain pd;
};
static inline unsigned long gk20a_get_gr_idle_timeout(struct gk20a *g)
static inline int support_gk20a_pmu(void){return 0;}
#endif
-int nvhost_gk20a_finalize_poweron(struct platform_device *dev);
-int nvhost_gk20a_prepare_poweroff(struct platform_device *dev);
-
void gk20a_create_sysfs(struct platform_device *dev);
#ifdef CONFIG_DEBUG_FS
void nvhost_gk20a_scale_callback(struct nvhost_device_profile *profile,
unsigned long freq)
{
- struct gk20a *g = get_gk20a(profile->pdev);
+ struct gk20a_platform *platform = platform_get_drvdata(profile->pdev);
struct nvhost_emc_params *emc_params = profile->private_data;
+ struct gk20a *g = get_gk20a(profile->pdev);
+
long after = gk20a_clk_get_rate(g);
long emc_target = nvhost_scale3d_get_emc_rate(emc_params, after);
- nvhost_module_set_devfreq_rate(profile->pdev, 2, emc_target);
+ clk_set_rate(platform->clk[2], emc_target);
}
/*
ptimer_scale_factor_show,
NULL);
+static ssize_t railgate_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct gk20a_platform *platform = dev_get_drvdata(dev);
+ int railgate_delay = 0, ret = 0;
+
+ if (!platform->can_railgate) {
+ dev_info(dev, "does not support power-gating\n");
+ return count;
+ }
+
+ ret = sscanf(buf, "%d", &railgate_delay);
+ if (ret == 1 && railgate_delay >= 0) {
+ struct generic_pm_domain *genpd = pd_to_genpd(dev->pm_domain);
+ platform->railgate_delay = railgate_delay;
+ pm_genpd_set_poweroff_delay(genpd, platform->railgate_delay);
+ } else
+ dev_err(dev, "Invalid powergate delay\n");
+
+ return count;
+}
+static ssize_t railgate_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gk20a_platform *platform = dev_get_drvdata(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", platform->railgate_delay);
+}
+static DEVICE_ATTR(railgate_delay, S_IRWXUGO, railgate_delay_show,
+ railgate_delay_store);
+
+static ssize_t clockgate_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct gk20a_platform *platform = dev_get_drvdata(dev);
+ int clockgate_delay = 0, ret = 0;
+
+ ret = sscanf(buf, "%d", &clockgate_delay);
+ if (ret == 1 && clockgate_delay >= 0) {
+ platform->clockgate_delay = clockgate_delay;
+ pm_runtime_set_autosuspend_delay(dev,
+ platform->clockgate_delay);
+ } else
+ dev_err(dev, "Invalid clockgate delay\n");
+
+ return count;
+}
+static ssize_t clockgate_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gk20a_platform *platform = dev_get_drvdata(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", platform->clockgate_delay);
+}
+static DEVICE_ATTR(clockgate_delay, S_IRWXUGO, clockgate_delay_show,
+ clockgate_delay_store);
+
static ssize_t counters_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
device_remove_file(dev, &dev_attr_elpg_enable);
device_remove_file(dev, &dev_attr_counters);
device_remove_file(dev, &dev_attr_counters_reset);
+ device_remove_file(dev, &dev_attr_railgate_delay);
+ device_remove_file(dev, &dev_attr_clockgate_delay);
}
void gk20a_create_sysfs(struct platform_device *dev)
error |= device_create_file(&dev->dev, &dev_attr_elpg_enable);
error |= device_create_file(&dev->dev, &dev_attr_counters);
error |= device_create_file(&dev->dev, &dev_attr_counters_reset);
+ error |= device_create_file(&dev->dev, &dev_attr_railgate_delay);
+ error |= device_create_file(&dev->dev, &dev_attr_clockgate_delay);
if (error)
dev_err(&dev->dev, "Failed to create sysfs attributes!\n");
struct gk20a *g;
/* Should be populated at probe. */
- bool can_powergate;
+ bool can_railgate;
/* Should be populated at probe. */
bool has_syncpoints;
/* Should be populated by probe. */
struct dentry *debugfs;
+ /* Clock configuration is stored here. Platform probe is responsible
+ * for filling this data. */
+ struct clk *clk[3];
+ int num_clks;
+
+ /* Delay before rail gated */
+ int railgate_delay;
+
+ /* Delay before clock gated */
+ int clockgate_delay;
+
/* Initialize the platform interface of the gk20a driver.
*
* The platform implementation of this function must
* state, and
* - populate the gk20a_platform structure (a pointer to the
* structure can be obtained by calling gk20a_get_platform).
+ *
+ * After this function is finished, the driver will initialise
+ * pm runtime and genpd based on the platform configuration.
*/
int (*probe)(struct platform_device *dev);
+ /* Second stage initialisation - called once all power management
+ * initialisations are done.
+ */
+ int (*late_probe)(struct platform_device *dev);
+
/* Called before submitting work to the gpu. The platform may use this
* hook to ensure that any other hw modules that the gpu depends on are
* powered. The platform implementation must count refs to this call. */
int (*secure_alloc)(struct platform_device *dev,
struct gr_ctx_buffer_desc *desc,
size_t size);
+
+ /* Device is going to be suspended */
+ int (*suspend)(struct device *);
+
+ /* Called to turn off the device */
+ int (*railgate)(struct platform_device *dev);
+
+ /* Called to turn on the device */
+ int (*unrailgate)(struct platform_device *dev);
+
};
static inline struct gk20a_platform *gk20a_get_platform(
#include "t124/syncpt_t124.h"
#include "../../../../../arch/arm/mach-tegra/iomap.h"
#include <linux/tegra-powergate.h>
+#include <linux/platform_data/tegra_edp.h>
#include <linux/nvhost_ioctl.h>
#include <linux/dma-buf.h>
#include <linux/nvmap.h>
#include <mach/irqs.h>
+#include <mach/pm_domains.h>
#include "gk20a.h"
#include "hal_gk20a.h"
}
#endif
+/*
+ * gk20a_tegra_railgate()
+ *
+ * Gate (disable) gk20a power rail
+ */
+
+static int gk20a_tegra_railgate(struct platform_device *pdev)
+{
+ if (tegra_powergate_is_powered(TEGRA_POWERGATE_GPU))
+ tegra_powergate_partition(TEGRA_POWERGATE_GPU);
+ return 0;
+}
+
+/*
+ * gk20a_tegra_unrailgate()
+ *
+ * Ungate (enable) gk20a power rail
+ */
+
+static int gk20a_tegra_unrailgate(struct platform_device *pdev)
+{
+ tegra_unpowergate_partition(TEGRA_POWERGATE_GPU);
+ return 0;
+}
+
+struct {
+ char *name;
+ unsigned long default_rate;
+} tegra_gk20a_clocks[] = {
+ {"PLLG_ref", UINT_MAX},
+ {"pwr", 204000000},
+ {"emc", UINT_MAX} };
+
+/*
+ * gk20a_tegra_get_clocks()
+ *
+ * This function finds clocks in tegra platform and populates
+ * the clock information to gk20a platform data.
+ */
+
+static int gk20a_tegra_get_clocks(struct platform_device *pdev)
+{
+ struct gk20a_platform *platform = platform_get_drvdata(pdev);
+ char devname[16];
+ int i;
+ int ret = 0;
+
+ snprintf(devname, sizeof(devname),
+ (pdev->id <= 0) ? "tegra_%s" : "tegra_%s.%d\n",
+ pdev->name, pdev->id);
+
+ platform->num_clks = 0;
+ for (i = 0; i < ARRAY_SIZE(tegra_gk20a_clocks); i++) {
+ long rate = tegra_gk20a_clocks[i].default_rate;
+ struct clk *c;
+
+ c = clk_get_sys(devname, tegra_gk20a_clocks[i].name);
+ if (IS_ERR(c)) {
+ ret = PTR_ERR(c);
+ goto err_get_clock;
+ }
+ rate = clk_round_rate(c, rate);
+ clk_set_rate(c, rate);
+ platform->clk[platform->num_clks++] = c;
+ platform->nvhost.clk[platform->nvhost.num_clks++] = c;
+ }
+ platform->num_clks = i;
+ platform->nvhost.num_clks = i;
+
+ return 0;
+
+err_get_clock:
+
+ while (i--)
+ clk_put(platform->clk[i]);
+ return ret;
+}
+
static int gk20a_tegra_probe(struct platform_device *dev)
{
- int err;
struct gk20a_platform *platform = gk20a_get_platform(dev);
- struct nvhost_device_data *pdata = &platform->nvhost;
if (tegra_get_chipid() == TEGRA_CHIPID_TEGRA13) {
t132_gk20a_tegra_platform.g = platform->g;
*platform = t132_gk20a_tegra_platform;
}
- pdata->pdev = dev;
- mutex_init(&pdata->lock);
+ gk20a_tegra_get_clocks(dev);
+
+ return 0;
+}
- /* Initialize clocks and power. */
- err = nvhost_module_init(dev);
- if (err)
- return err;
+static int gk20a_tegra_late_probe(struct platform_device *dev)
+{
+ int err;
+ struct gk20a_platform *platform = gk20a_get_platform(dev);
+ struct nvhost_device_data *pdata = &platform->nvhost;
-#ifdef CONFIG_PM_GENERIC_DOMAINS
- pdata->pd.name = "gk20a";
+ /* Make gk20a power domain a subdomain of mc */
+ tegra_pd_add_sd(&platform->g->pd);
- err = nvhost_module_add_domain(&pdata->pd, dev);
- if (err)
- goto fail;
-#endif
+ pdata->pdev = dev;
+ mutex_init(&pdata->lock);
err = nvhost_client_device_init(dev);
if (err) {
goto fail;
}
- platform->can_powergate = pdata->can_powergate;
platform->debugfs = pdata->debugfs;
return 0;
return err;
}
+static int gk20a_tegra_suspend(struct device *dev)
+{
+ nvhost_scale3d_suspend(dev);
+ tegra_edp_notify_gpu_load(0);
+
+ return 0;
+}
+
static struct resource gk20a_tegra_resources[] = {
{
.start = TEGRA_GK20A_BAR0_BASE,
.syncpts = {NVSYNCPT_GK20A_BASE},
.syncpt_base = NVSYNCPT_GK20A_BASE,
.class = NV_GRAPHICS_GPU_CLASS_ID,
- .clocks = {{"PLLG_ref", UINT_MAX},
- {"pwr", 204000000},
- {"emc", UINT_MAX},
- {} },
- .powergate_ids = { TEGRA_POWERGATE_GPU, -1 },
- NVHOST_DEFAULT_CLOCKGATE_DELAY,
- .powergate_delay = 500,
.moduleid = NVHOST_MODULE_GPU,
- .prepare_poweroff = nvhost_gk20a_prepare_poweroff,
- .finalize_poweron = nvhost_gk20a_finalize_poweron,
#ifdef CONFIG_GK20A_DEVFREQ
.busy = gk20a_scale_notify_busy,
.idle = gk20a_scale_notify_idle,
.qos_id = PM_QOS_GPU_FREQ_MIN,
#endif
},
+
.has_syncpoints = true,
+
+ /* power management configuration */
+ .railgate_delay = 500,
+ .clockgate_delay = 50,
+
.probe = gk20a_tegra_probe,
+ .late_probe = gk20a_tegra_late_probe,
+
+ /* power management callbacks */
+ .suspend = gk20a_tegra_suspend,
+ .railgate = gk20a_tegra_railgate,
+ .unrailgate = gk20a_tegra_unrailgate,
+
.channel_busy = gk20a_tegra_channel_busy,
.channel_idle = gk20a_tegra_channel_idle,
.secure_alloc = gk20a_tegra_secure_alloc,
.syncpts = {NVSYNCPT_GK20A_BASE},
.syncpt_base = NVSYNCPT_GK20A_BASE,
.class = NV_GRAPHICS_GPU_CLASS_ID,
- .clocks = {{"PLLG_ref", UINT_MAX},
- {"pwr", 204000000},
- {"emc", UINT_MAX},
- {} },
- .powergate_ids = { TEGRA_POWERGATE_GPU, -1 },
- NVHOST_DEFAULT_CLOCKGATE_DELAY,
- .powergate_delay = 500,
- .can_powergate = true,
.moduleid = NVHOST_MODULE_GPU,
- .prepare_poweroff = nvhost_gk20a_prepare_poweroff,
- .finalize_poweron = nvhost_gk20a_finalize_poweron,
#ifdef CONFIG_GK20A_DEVFREQ
.busy = gk20a_scale_notify_busy,
.idle = gk20a_scale_notify_idle,
.qos_id = PM_QOS_GPU_FREQ_MIN,
#endif
},
+
.has_syncpoints = true,
+
+ /* power management configuration */
+ .railgate_delay = 500,
+ .clockgate_delay = 50,
+ .can_railgate = true,
+
.probe = gk20a_tegra_probe,
+ .late_probe = gk20a_tegra_late_probe,
+
+ /* power management callbacks */
+ .suspend = gk20a_tegra_suspend,
+ .railgate = gk20a_tegra_railgate,
+ .unrailgate = gk20a_tegra_unrailgate,
+
.channel_busy = gk20a_tegra_channel_busy,
.channel_idle = gk20a_tegra_channel_idle,
.secure_alloc = gk20a_tegra_secure_alloc,
static void action_gpfifo_submit_complete(struct nvhost_waitlist *waiter)
{
struct channel_gk20a *ch20a = waiter->data;
- int nr_completed = waiter->count;
+
wake_up(&ch20a->submit_wq);
+
#if defined(CONFIG_TEGRA_GK20A)
- gk20a_channel_update(ch20a);
+ gk20a_channel_update(ch20a, waiter->count);
#endif
- nvhost_module_idle_mult(ch20a->ch->dev, nr_completed);
- /* TODO: add trace function */
}
static void action_wakeup(struct nvhost_waitlist *waiter)