drm/nouveau/pm: rework to allow selecting separate profiles for ac/battery
authorBen Skeggs <bskeggs@redhat.com>
Tue, 24 Jan 2012 05:59:07 +0000 (15:59 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Tue, 13 Mar 2012 07:09:04 +0000 (17:09 +1000)
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Martin Peres <martin.peres@labri.fr>
drivers/gpu/drm/nouveau/nouveau_drv.h
drivers/gpu/drm/nouveau/nouveau_perf.c
drivers/gpu/drm/nouveau/nouveau_pm.c
drivers/gpu/drm/nouveau/nouveau_pm.h

index 531e435d9fb30bbe703bd144928434b0a4d221e4..009089e093f3c559930e41dc1386391a8d62dee4 100644 (file)
@@ -485,15 +485,27 @@ struct nouveau_pm_tbl_entry {
        u8 tUNK_20, tUNK_21;
 };
 
+struct nouveau_pm_profile;
+struct nouveau_pm_profile_func {
+       struct nouveau_pm_level *(*select)(struct nouveau_pm_profile *);
+};
+
+struct nouveau_pm_profile {
+       const struct nouveau_pm_profile_func *func;
+       struct list_head head;
+       char name[8];
+};
+
 #define NOUVEAU_PM_MAX_LEVEL 8
 struct nouveau_pm_level {
+       struct nouveau_pm_profile profile;
        struct device_attribute dev_attr;
        char name[32];
        int id;
 
+       struct nouveau_pm_memtiming timing;
        u32 memory;
        u16 memscript;
-       struct nouveau_pm_memtiming timing;
 
        u32 core;
        u32 shader;
@@ -542,6 +554,10 @@ struct nouveau_pm_engine {
        struct nouveau_pm_threshold_temp threshold_temp;
        struct nouveau_pm_fan fan;
 
+       struct nouveau_pm_profile *profile_ac;
+       struct nouveau_pm_profile *profile_dc;
+       struct list_head profiles;
+
        struct nouveau_pm_level boot;
        struct nouveau_pm_level *cur;
 
index cc8beb8e2f06bf9f3225714e86ea8a1e9c7a1174..e64901509f90a9b64557bc3093811fb1304a8c16 100644 (file)
@@ -394,6 +394,13 @@ nouveau_perf_init(struct drm_device *dev)
                snprintf(perflvl->name, sizeof(perflvl->name),
                         "performance_level_%d", i);
                perflvl->id = i;
+
+               snprintf(perflvl->profile.name, sizeof(perflvl->profile.name),
+                        "%d", perflvl->id);
+               perflvl->profile.func = &nouveau_pm_static_profile_func;
+               list_add_tail(&perflvl->profile.head, &pm->profiles);
+
+
                pm->nr_perflvl++;
        }
 }
index 7c25567fb56ae6adc2ebc8fe81e0b07085295897..4fff09e3f5928b78a9a0cac7eda7eccf2cf1f1c1 100644 (file)
@@ -168,51 +168,99 @@ error:
        return ret;
 }
 
+void
+nouveau_pm_trigger(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pm_engine *pm = &dev_priv->engine.pm;
+       struct nouveau_pm_profile *profile = NULL;
+       struct nouveau_pm_level *perflvl = NULL;
+       int ret;
+
+       /* select power profile based on current power source */
+       if (power_supply_is_system_supplied())
+               profile = pm->profile_ac;
+       else
+               profile = pm->profile_dc;
+
+       /* select performance level based on profile */
+       perflvl = profile->func->select(profile);
+
+       /* change perflvl, if necessary */
+       if (perflvl != pm->cur) {
+               struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer;
+               u64 time0 = ptimer->read(dev);
+
+               NV_INFO(dev, "setting performance level: %d", perflvl->id);
+               ret = nouveau_pm_perflvl_set(dev, perflvl);
+               if (ret)
+                       NV_INFO(dev, "> reclocking failed: %d\n\n", ret);
+
+               NV_INFO(dev, "> reclocking took %lluns\n\n",
+                            ptimer->read(dev) - time0);
+       }
+}
+
+static struct nouveau_pm_profile *
+profile_find(struct drm_device *dev, const char *string)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_pm_engine *pm = &dev_priv->engine.pm;
+       struct nouveau_pm_profile *profile;
+
+       list_for_each_entry(profile, &pm->profiles, head) {
+               if (!strncmp(profile->name, string, sizeof(profile->name)))
+                       return profile;
+       }
+
+       return NULL;
+}
+
 static int
 nouveau_pm_profile_set(struct drm_device *dev, const char *profile)
 {
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct nouveau_pm_engine *pm = &dev_priv->engine.pm;
-       struct nouveau_pm_level *perflvl = NULL;
-       u64 start_time;
-       int ret = 0;
-       long pl;
+       struct nouveau_pm_profile *ac = NULL, *dc = NULL;
+       char string[16], *cur = string, *ptr;
 
        /* safety precaution, for now */
        if (nouveau_perflvl_wr != 7777)
                return -EPERM;
 
-       if (!strncmp(profile, "boot", 4))
-               perflvl = &pm->boot;
-       else {
-               int i;
-               if (kstrtol(profile, 10, &pl) == -EINVAL)
-                       return -EINVAL;
-
-               for (i = 0; i < pm->nr_perflvl; i++) {
-                       if (pm->perflvl[i].id == pl) {
-                               perflvl = &pm->perflvl[i];
-                               break;
-                       }
-               }
+       strncpy(string, profile, sizeof(string));
+       if ((ptr = strchr(string, '\n')))
+               *ptr = '\0';
 
-               if (!perflvl)
-                       return -EINVAL;
-       }
+       ptr = strsep(&cur, ",");
+       if (ptr)
+               ac = profile_find(dev, ptr);
 
-       NV_INFO(dev, "setting performance level: %s", profile);
-       start_time = nv04_timer_read(dev);
-       ret = nouveau_pm_perflvl_set(dev, perflvl);
-       if (!ret) {
-               NV_INFO(dev, "> reclocking took %lluns\n\n",
-                       (nv04_timer_read(dev) - start_time));
-       } else {
-               NV_INFO(dev, "> reclocking failed\n\n");
-       }
+       ptr = strsep(&cur, ",");
+       if (ptr)
+               dc = profile_find(dev, ptr);
+       else
+               dc = ac;
 
-       return ret;
+       if (ac == NULL || dc == NULL)
+               return -EINVAL;
+
+       pm->profile_ac = ac;
+       pm->profile_dc = dc;
+       nouveau_pm_trigger(dev);
+       return 0;
+}
+
+static struct nouveau_pm_level *
+nouveau_pm_static_select(struct nouveau_pm_profile *profile)
+{
+       return container_of(profile, struct nouveau_pm_level, profile);
 }
 
+const struct nouveau_pm_profile_func nouveau_pm_static_profile_func = {
+       .select = nouveau_pm_static_select,
+};
+
 static int
 nouveau_pm_perflvl_get(struct drm_device *dev, struct nouveau_pm_level *perflvl)
 {
@@ -273,14 +321,15 @@ nouveau_pm_perflvl_info(struct nouveau_pm_level *perflvl, char *ptr, int len)
        if (perflvl->fanspeed)
                snprintf(f, sizeof(f), " fanspeed %d%%", perflvl->fanspeed);
 
-       snprintf(ptr, len, "%s%s%s%s%s\n", c, s, m,  v, f);
+       snprintf(ptr, len, "%s%s%s%s%s\n", c, s, m, v, f);
 }
 
 static ssize_t
 nouveau_pm_get_perflvl_info(struct device *d,
                            struct device_attribute *a, char *buf)
 {
-       struct nouveau_pm_level *perflvl = (struct nouveau_pm_level *)a;
+       struct nouveau_pm_level *perflvl =
+               container_of(a, struct nouveau_pm_level, dev_attr);
        char *ptr = buf;
        int len = PAGE_SIZE;
 
@@ -302,12 +351,8 @@ nouveau_pm_get_perflvl(struct device *d, struct device_attribute *a, char *buf)
        int len = PAGE_SIZE, ret;
        char *ptr = buf;
 
-       if (!pm->cur)
-               snprintf(ptr, len, "setting: boot\n");
-       else if (pm->cur == &pm->boot)
-               snprintf(ptr, len, "setting: boot\nc:");
-       else
-               snprintf(ptr, len, "setting: static %d\nc:", pm->cur->id);
+       snprintf(ptr, len, "profile: %s, %s\nc:",
+                pm->profile_ac->name, pm->profile_dc->name);
        ptr += strlen(buf);
        len -= strlen(buf);
 
@@ -776,6 +821,7 @@ nouveau_pm_acpi_event(struct notifier_block *nb, unsigned long val, void *data)
                bool ac = power_supply_is_system_supplied();
 
                NV_DEBUG(dev, "power supply changed: %s\n", ac ? "AC" : "DC");
+               nouveau_pm_trigger(dev);
        }
 
        return NOTIFY_OK;
@@ -802,6 +848,14 @@ nouveau_pm_init(struct drm_device *dev)
        }
 
        strncpy(pm->boot.name, "boot", 4);
+       strncpy(pm->boot.profile.name, "boot", 4);
+       pm->boot.profile.func = &nouveau_pm_static_profile_func;
+
+       INIT_LIST_HEAD(&pm->profiles);
+       list_add(&pm->boot.profile.head, &pm->profiles);
+
+       pm->profile_ac = &pm->boot.profile;
+       pm->profile_dc = &pm->boot.profile;
        pm->cur = &pm->boot;
 
        /* add performance levels from vbios */
@@ -818,13 +872,8 @@ nouveau_pm_init(struct drm_device *dev)
        NV_INFO(dev, "c:%s", info);
 
        /* switch performance levels now if requested */
-       if (nouveau_perflvl != NULL) {
-               ret = nouveau_pm_profile_set(dev, nouveau_perflvl);
-               if (ret) {
-                       NV_ERROR(dev, "error setting perflvl \"%s\": %d\n",
-                                nouveau_perflvl, ret);
-               }
-       }
+       if (nouveau_perflvl != NULL)
+               nouveau_pm_profile_set(dev, nouveau_perflvl);
 
        /* determine the current fan speed */
        pm->fan.percent = nouveau_pwmfan_get(dev);
index 1a8ae15803a0f61c8658be9c7293855a88afb340..3f82dfea61dd77ed2b543269d6b31624de5ddfba 100644 (file)
@@ -47,6 +47,8 @@ int  nouveau_mem_exec(struct nouveau_mem_exec_func *,
 int  nouveau_pm_init(struct drm_device *dev);
 void nouveau_pm_fini(struct drm_device *dev);
 void nouveau_pm_resume(struct drm_device *dev);
+extern const struct nouveau_pm_profile_func nouveau_pm_static_profile_func;
+void nouveau_pm_trigger(struct drm_device *dev);
 
 /* nouveau_volt.c */
 void nouveau_volt_init(struct drm_device *);