drm/nouveau/kms/nv50-: Add basic DPCD backlight support for nouveau
authorLyude Paul <lyude@redhat.com>
Fri, 14 May 2021 18:15:03 +0000 (14:15 -0400)
committerLyude Paul <lyude@redhat.com>
Wed, 9 Jun 2021 17:35:56 +0000 (13:35 -0400)
This adds support for controlling panel backlights over eDP using VESA's
standard backlight control interface. Luckily, Nvidia was cool enough to
never come up with their own proprietary backlight control interface (at
least, not any that I or the laptop manufacturers I've talked to are aware
of), so this should work for any laptop panels which support the VESA
backlight control interface.

Note that we don't yet provide the panel backlight frequency to the DRM DP
backlight helpers. This should be fine for the time being, since it's not
required to get basic backlight controls working.

For reference: there's some mentions of PWM backlight values in
nouveau_reg.h, but I'm not sure these are the values we would want to use.
If we figure out how to get this information in the future, we'll have the
benefit of more granular backlight control.

Signed-off-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Ben Skeggs <bskeggs@redhat.com>
Cc: Jani Nikula <jani.nikula@intel.com>
Cc: Dave Airlie <airlied@gmail.com>
Cc: greg.depoire@gmail.com
Link: https://patchwork.freedesktop.org/patch/msgid/20210514181504.565252-10-lyude@redhat.com
drivers/gpu/drm/nouveau/dispnv50/disp.c
drivers/gpu/drm/nouveau/nouveau_backlight.c
drivers/gpu/drm/nouveau/nouveau_connector.h
drivers/gpu/drm/nouveau/nouveau_encoder.h

index f949767698fc8ee9a3d6ede4873f04088fd88d0d..093e1f7163b3142d605ec56b2028ae5c5d7fc9dc 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/hdmi.h>
 #include <linux/component.h>
+#include <linux/iopoll.h>
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
@@ -1649,15 +1650,30 @@ nv50_sor_update(struct nouveau_encoder *nv_encoder, u8 head,
        core->func->sor->ctrl(core, nv_encoder->or, nv_encoder->ctrl, asyh);
 }
 
+/* TODO: Should we extend this to PWM-only backlights?
+ * As well, should we add a DRM helper for waiting for the backlight to acknowledge
+ * the panel backlight has been shut off? Intel doesn't seem to do this, and uses a
+ * fixed time delay from the vbios…
+ */
 static void
 nv50_sor_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+       struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);
        struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc);
        struct nouveau_connector *nv_connector = nv50_outp_get_old_connector(state, nv_encoder);
+       struct nouveau_backlight *backlight = nv_connector->backlight;
        struct drm_dp_aux *aux = &nv_connector->aux;
+       int ret;
        u8 pwr;
 
+       if (backlight && backlight->uses_dpcd) {
+               ret = drm_edp_backlight_disable(aux, &backlight->edp_info);
+               if (ret < 0)
+                       NV_ERROR(drm, "Failed to disable backlight on [CONNECTOR:%d:%s]: %d\n",
+                                nv_connector->base.base.id, nv_connector->base.name, ret);
+       }
+
        if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {
                int ret = drm_dp_dpcd_readb(aux, DP_SET_POWER, &pwr);
 
@@ -1696,6 +1712,9 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
        struct drm_device *dev = encoder->dev;
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nouveau_connector *nv_connector;
+#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
+       struct nouveau_backlight *backlight;
+#endif
        struct nvbios *bios = &drm->vbios;
        bool hda = false;
        u8 proto = NV507D_SOR_SET_CONTROL_PROTOCOL_CUSTOM;
@@ -1770,6 +1789,14 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
                        proto = NV887D_SOR_SET_CONTROL_PROTOCOL_DP_B;
 
                nv50_audio_enable(encoder, nv_crtc, nv_connector, state, mode);
+
+#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
+               backlight = nv_connector->backlight;
+               if (backlight && backlight->uses_dpcd)
+                       drm_edp_backlight_enable(&nv_connector->aux, &backlight->edp_info,
+                                                (u16)backlight->dev->props.brightness);
+#endif
+
                break;
        default:
                BUG();
@@ -2295,6 +2322,7 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
        nv50_crc_atomic_start_reporting(state);
        if (!flushed)
                nv50_crc_atomic_release_notifier_contexts(state);
+
        drm_atomic_helper_commit_hw_done(state);
        drm_atomic_helper_cleanup_planes(dev, state);
        drm_atomic_helper_commit_cleanup_done(state);
index 72f35a2babcb20eef0450df7f25f4ee71dc2d9f0..1cbd71abc80aad3d9fe37499298a11b481e6948e 100644 (file)
 static struct ida bl_ida;
 #define BL_NAME_SIZE 15 // 12 for name + 2 for digits + 1 for '\0'
 
-struct nouveau_backlight {
-       struct backlight_device *dev;
-       int id;
-};
-
 static bool
 nouveau_get_backlight_name(char backlight_name[BL_NAME_SIZE],
                           struct nouveau_backlight *bl)
@@ -148,6 +143,98 @@ static const struct backlight_ops nv50_bl_ops = {
        .update_status = nv50_set_intensity,
 };
 
+/*
+ * eDP brightness callbacks need to happen under lock, since we need to
+ * enable/disable the backlight ourselves for modesets
+ */
+static int
+nv50_edp_get_brightness(struct backlight_device *bd)
+{
+       struct drm_connector *connector = dev_get_drvdata(bd->dev.parent);
+       struct drm_device *dev = connector->dev;
+       struct drm_crtc *crtc;
+       struct drm_modeset_acquire_ctx ctx;
+       int ret = 0;
+
+       drm_modeset_acquire_init(&ctx, 0);
+
+retry:
+       ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
+       if (ret == -EDEADLK)
+               goto deadlock;
+       else if (ret < 0)
+               goto out;
+
+       crtc = connector->state->crtc;
+       if (!crtc)
+               goto out;
+
+       ret = drm_modeset_lock(&crtc->mutex, &ctx);
+       if (ret == -EDEADLK)
+               goto deadlock;
+       else if (ret < 0)
+               goto out;
+
+       if (!crtc->state->active)
+               goto out;
+
+       ret = bd->props.brightness;
+out:
+       drm_modeset_drop_locks(&ctx);
+       drm_modeset_acquire_fini(&ctx);
+       return ret;
+deadlock:
+       drm_modeset_backoff(&ctx);
+       goto retry;
+}
+
+static int
+nv50_edp_set_brightness(struct backlight_device *bd)
+{
+       struct drm_connector *connector = dev_get_drvdata(bd->dev.parent);
+       struct nouveau_connector *nv_connector = nouveau_connector(connector);
+       struct drm_device *dev = connector->dev;
+       struct drm_crtc *crtc;
+       struct drm_dp_aux *aux = &nv_connector->aux;
+       struct nouveau_backlight *nv_bl = nv_connector->backlight;
+       struct drm_modeset_acquire_ctx ctx;
+       int ret = 0;
+
+       drm_modeset_acquire_init(&ctx, 0);
+retry:
+       ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
+       if (ret == -EDEADLK)
+               goto deadlock;
+       else if (ret < 0)
+               goto out;
+
+       crtc = connector->state->crtc;
+       if (!crtc)
+               goto out;
+
+       ret = drm_modeset_lock(&crtc->mutex, &ctx);
+       if (ret == -EDEADLK)
+               goto deadlock;
+       else if (ret < 0)
+               goto out;
+
+       if (crtc->state->active)
+               ret = drm_edp_backlight_set_level(aux, &nv_bl->edp_info, bd->props.brightness);
+
+out:
+       drm_modeset_drop_locks(&ctx);
+       drm_modeset_acquire_fini(&ctx);
+       return ret;
+deadlock:
+       drm_modeset_backoff(&ctx);
+       goto retry;
+}
+
+static const struct backlight_ops nv50_edp_bl_ops = {
+       .get_brightness = nv50_edp_get_brightness,
+       .update_status = nv50_edp_set_brightness,
+};
+
 static int
 nva3_get_intensity(struct backlight_device *bd)
 {
@@ -194,8 +281,13 @@ static const struct backlight_ops nva3_bl_ops = {
        .update_status = nva3_set_intensity,
 };
 
+/* FIXME: perform backlight probing for eDP _before_ this, this only gets called after connector
+ * registration which happens after the initial modeset
+ */
 static int
-nv50_backlight_init(struct nouveau_encoder *nv_encoder,
+nv50_backlight_init(struct nouveau_backlight *bl,
+                   struct nouveau_connector *nv_conn,
+                   struct nouveau_encoder *nv_encoder,
                    struct backlight_properties *props,
                    const struct backlight_ops **ops)
 {
@@ -205,6 +297,41 @@ nv50_backlight_init(struct nouveau_encoder *nv_encoder,
        if (!nvif_rd32(device, NV50_PDISP_SOR_PWM_CTL(ffs(nv_encoder->dcb->or) - 1)))
                return -ENODEV;
 
+       if (nv_conn->type == DCB_CONNECTOR_eDP) {
+               int ret;
+               u16 current_level;
+               u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE];
+               u8 current_mode;
+
+               ret = drm_dp_dpcd_read(&nv_conn->aux, DP_EDP_DPCD_REV, edp_dpcd,
+                                      EDP_DISPLAY_CTL_CAP_SIZE);
+               if (ret < 0)
+                       return ret;
+
+               if (drm_edp_backlight_supported(edp_dpcd)) {
+                       NV_DEBUG(drm, "DPCD backlight controls supported on %s\n",
+                                nv_conn->base.name);
+
+                       ret = drm_edp_backlight_init(&nv_conn->aux, &bl->edp_info, 0, edp_dpcd,
+                                                    &current_level, &current_mode);
+                       if (ret < 0)
+                               return ret;
+
+                       ret = drm_edp_backlight_enable(&nv_conn->aux, &bl->edp_info, current_level);
+                       if (ret < 0) {
+                               NV_ERROR(drm, "Failed to enable backlight on %s: %d\n",
+                                        nv_conn->base.name, ret);
+                               return ret;
+                       }
+
+                       *ops = &nv50_edp_bl_ops;
+                       props->brightness = current_level;
+                       props->max_brightness = bl->edp_info.max;
+                       bl->uses_dpcd = true;
+                       return 0;
+               }
+       }
+
        if (drm->client.device.info.chipset <= 0xa0 ||
            drm->client.device.info.chipset == 0xaa ||
            drm->client.device.info.chipset == 0xac)
@@ -245,6 +372,10 @@ nouveau_backlight_init(struct drm_connector *connector)
        if (!nv_encoder)
                return 0;
 
+       bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+       if (!bl)
+               return -ENOMEM;
+
        switch (device->info.family) {
        case NV_DEVICE_INFO_V0_CURIE:
                ret = nv40_backlight_init(nv_encoder, &props, &ops);
@@ -257,20 +388,19 @@ nouveau_backlight_init(struct drm_connector *connector)
        case NV_DEVICE_INFO_V0_VOLTA:
        case NV_DEVICE_INFO_V0_TURING:
        case NV_DEVICE_INFO_V0_AMPERE: //XXX: not confirmed
-               ret = nv50_backlight_init(nv_encoder, &props, &ops);
+               ret = nv50_backlight_init(bl, nouveau_connector(connector),
+                                         nv_encoder, &props, &ops);
                break;
        default:
-               return 0;
+               ret = 0;
+               goto fail_alloc;
        }
 
-       if (ret == -ENODEV)
-               return 0;
-       else if (ret)
-               return ret;
-
-       bl = kzalloc(sizeof(*bl), GFP_KERNEL);
-       if (!bl)
-               return -ENOMEM;
+       if (ret) {
+               if (ret == -ENODEV)
+                       ret = 0;
+               goto fail_alloc;
+       }
 
        if (!nouveau_get_backlight_name(backlight_name, bl)) {
                NV_ERROR(drm, "Failed to retrieve a unique name for the backlight interface\n");
@@ -287,7 +417,9 @@ nouveau_backlight_init(struct drm_connector *connector)
        }
 
        nouveau_connector(connector)->backlight = bl;
-       bl->dev->props.brightness = bl->dev->ops->get_brightness(bl->dev);
+       if (!bl->dev->props.brightness)
+               bl->dev->props.brightness =
+                       bl->dev->ops->get_brightness(bl->dev);
        backlight_update_status(bl->dev);
 
        return 0;
index d0b859c4a80ea64a520a37be94b5b71347a0ee49..40f90e353540b2793c2cb0dce8b0f911a7cf0833 100644 (file)
@@ -46,7 +46,14 @@ struct nvkm_i2c_port;
 struct dcb_output;
 
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
-struct nouveau_backlight;
+struct nouveau_backlight {
+       struct backlight_device *dev;
+
+       struct drm_edp_backlight_info edp_info;
+       bool uses_dpcd : 1;
+
+       int id;
+};
 #endif
 
 #define nouveau_conn_atom(p)                                                   \
index 1ffcc0a491fd22b8811ad06558cb6d511e4362e4..77c2fed76e8b15b50d613c881635f4aed8d056a9 100644 (file)
@@ -30,6 +30,7 @@
 #include <subdev/bios/dcb.h>
 
 #include <drm/drm_encoder_slave.h>
+#include <drm/drm_dp_helper.h>
 #include <drm/drm_dp_mst_helper.h>
 #include "dispnv04/disp.h"
 struct nv50_head_atom;