414 lines · c
1/*2 * Copyright (C) 2009 Red Hat <mjg@redhat.com>3 *4 * Permission is hereby granted, free of charge, to any person obtaining5 * a copy of this software and associated documentation files (the6 * "Software"), to deal in the Software without restriction, including7 * without limitation the rights to use, copy, modify, merge, publish,8 * distribute, sublicense, and/or sell copies of the Software, and to9 * permit persons to whom the Software is furnished to do so, subject to10 * the following conditions:11 *12 * The above copyright notice and this permission notice (including the13 * next paragraph) shall be included in all copies or substantial14 * portions of the Software.15 *16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.19 * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.23 *24 */25 26/*27 * Authors:28 * Matthew Garrett <mjg@redhat.com>29 *30 * Register locations derived from NVClock by Roderick Colenbrander31 */32 33#include <linux/apple-gmux.h>34#include <linux/backlight.h>35#include <linux/idr.h>36#include <drm/drm_probe_helper.h>37 38#include "nouveau_drv.h"39#include "nouveau_reg.h"40#include "nouveau_encoder.h"41#include "nouveau_connector.h"42#include "nouveau_acpi.h"43 44static struct ida bl_ida;45#define BL_NAME_SIZE 15 // 12 for name + 2 for digits + 1 for '\0'46 47static bool48nouveau_get_backlight_name(char backlight_name[BL_NAME_SIZE],49 struct nouveau_backlight *bl)50{51 const int nb = ida_alloc_max(&bl_ida, 99, GFP_KERNEL);52 53 if (nb < 0)54 return false;55 if (nb > 0)56 snprintf(backlight_name, BL_NAME_SIZE, "nv_backlight%d", nb);57 else58 snprintf(backlight_name, BL_NAME_SIZE, "nv_backlight");59 bl->id = nb;60 return true;61}62 63static int64nv40_get_intensity(struct backlight_device *bd)65{66 struct nouveau_encoder *nv_encoder = bl_get_data(bd);67 struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);68 struct nvif_object *device = &drm->client.device.object;69 int val = (nvif_rd32(device, NV40_PMC_BACKLIGHT) &70 NV40_PMC_BACKLIGHT_MASK) >> 16;71 72 return val;73}74 75static int76nv40_set_intensity(struct backlight_device *bd)77{78 struct nouveau_encoder *nv_encoder = bl_get_data(bd);79 struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);80 struct nvif_object *device = &drm->client.device.object;81 int val = bd->props.brightness;82 int reg = nvif_rd32(device, NV40_PMC_BACKLIGHT);83 84 nvif_wr32(device, NV40_PMC_BACKLIGHT,85 (val << 16) | (reg & ~NV40_PMC_BACKLIGHT_MASK));86 87 return 0;88}89 90static const struct backlight_ops nv40_bl_ops = {91 .options = BL_CORE_SUSPENDRESUME,92 .get_brightness = nv40_get_intensity,93 .update_status = nv40_set_intensity,94};95 96static int97nv40_backlight_init(struct nouveau_encoder *encoder,98 struct backlight_properties *props,99 const struct backlight_ops **ops)100{101 struct nouveau_drm *drm = nouveau_drm(encoder->base.base.dev);102 struct nvif_object *device = &drm->client.device.object;103 104 if (!(nvif_rd32(device, NV40_PMC_BACKLIGHT) & NV40_PMC_BACKLIGHT_MASK))105 return -ENODEV;106 107 props->max_brightness = 31;108 *ops = &nv40_bl_ops;109 return 0;110}111 112/*113 * eDP brightness callbacks need to happen under lock, since we need to114 * enable/disable the backlight ourselves for modesets115 */116static int117nv50_edp_get_brightness(struct backlight_device *bd)118{119 struct drm_connector *connector = dev_get_drvdata(bd->dev.parent);120 struct drm_device *dev = connector->dev;121 struct drm_crtc *crtc;122 struct drm_modeset_acquire_ctx ctx;123 int ret = 0;124 125 drm_modeset_acquire_init(&ctx, 0);126 127retry:128 ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);129 if (ret == -EDEADLK)130 goto deadlock;131 else if (ret < 0)132 goto out;133 134 crtc = connector->state->crtc;135 if (!crtc)136 goto out;137 138 ret = drm_modeset_lock(&crtc->mutex, &ctx);139 if (ret == -EDEADLK)140 goto deadlock;141 else if (ret < 0)142 goto out;143 144 if (!crtc->state->active)145 goto out;146 147 ret = bd->props.brightness;148out:149 drm_modeset_drop_locks(&ctx);150 drm_modeset_acquire_fini(&ctx);151 return ret;152deadlock:153 drm_modeset_backoff(&ctx);154 goto retry;155}156 157static int158nv50_edp_set_brightness(struct backlight_device *bd)159{160 struct drm_connector *connector = dev_get_drvdata(bd->dev.parent);161 struct nouveau_connector *nv_connector = nouveau_connector(connector);162 struct drm_device *dev = connector->dev;163 struct drm_crtc *crtc;164 struct drm_dp_aux *aux = &nv_connector->aux;165 struct nouveau_backlight *nv_bl = nv_connector->backlight;166 struct drm_modeset_acquire_ctx ctx;167 int ret = 0;168 169 drm_modeset_acquire_init(&ctx, 0);170retry:171 ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);172 if (ret == -EDEADLK)173 goto deadlock;174 else if (ret < 0)175 goto out;176 177 crtc = connector->state->crtc;178 if (!crtc)179 goto out;180 181 ret = drm_modeset_lock(&crtc->mutex, &ctx);182 if (ret == -EDEADLK)183 goto deadlock;184 else if (ret < 0)185 goto out;186 187 if (crtc->state->active)188 ret = drm_edp_backlight_set_level(aux, &nv_bl->edp_info, bd->props.brightness);189 190out:191 drm_modeset_drop_locks(&ctx);192 drm_modeset_acquire_fini(&ctx);193 return ret;194deadlock:195 drm_modeset_backoff(&ctx);196 goto retry;197}198 199static const struct backlight_ops nv50_edp_bl_ops = {200 .get_brightness = nv50_edp_get_brightness,201 .update_status = nv50_edp_set_brightness,202};203 204static int205nv50_get_intensity(struct backlight_device *bd)206{207 struct nouveau_encoder *nv_encoder = bl_get_data(bd);208 209 return nvif_outp_bl_get(&nv_encoder->outp);210}211 212static int213nv50_set_intensity(struct backlight_device *bd)214{215 struct nouveau_encoder *nv_encoder = bl_get_data(bd);216 217 return nvif_outp_bl_set(&nv_encoder->outp, backlight_get_brightness(bd));218}219 220static const struct backlight_ops nv50_bl_ops = {221 .options = BL_CORE_SUSPENDRESUME,222 .get_brightness = nv50_get_intensity,223 .update_status = nv50_set_intensity,224};225 226/* FIXME: perform backlight probing for eDP _before_ this, this only gets called after connector227 * registration which happens after the initial modeset228 */229static int230nv50_backlight_init(struct nouveau_backlight *bl,231 struct nouveau_connector *nv_conn,232 struct nouveau_encoder *nv_encoder,233 struct backlight_properties *props,234 const struct backlight_ops **ops)235{236 struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);237 238 /*239 * Note when this runs the connectors have not been probed yet,240 * so nv_conn->base.status is not set yet.241 */242 if (nvif_outp_bl_get(&nv_encoder->outp) < 0 ||243 drm_helper_probe_detect(&nv_conn->base, NULL, false) != connector_status_connected)244 return -ENODEV;245 246 if (nv_conn->type == DCB_CONNECTOR_eDP) {247 int ret;248 u16 current_level;249 u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE];250 u8 current_mode;251 252 ret = drm_dp_dpcd_read(&nv_conn->aux, DP_EDP_DPCD_REV, edp_dpcd,253 EDP_DISPLAY_CTL_CAP_SIZE);254 if (ret < 0)255 return ret;256 257 /* TODO: Add support for hybrid PWM/DPCD panels */258 if (drm_edp_backlight_supported(edp_dpcd) &&259 (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) &&260 (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) {261 NV_DEBUG(drm, "DPCD backlight controls supported on %s\n",262 nv_conn->base.name);263 264 ret = drm_edp_backlight_init(&nv_conn->aux, &bl->edp_info, 0, edp_dpcd,265 ¤t_level, ¤t_mode);266 if (ret < 0)267 return ret;268 269 ret = drm_edp_backlight_enable(&nv_conn->aux, &bl->edp_info, current_level);270 if (ret < 0) {271 NV_ERROR(drm, "Failed to enable backlight on %s: %d\n",272 nv_conn->base.name, ret);273 return ret;274 }275 276 *ops = &nv50_edp_bl_ops;277 props->brightness = current_level;278 props->max_brightness = bl->edp_info.max;279 bl->uses_dpcd = true;280 return 0;281 }282 }283 284 *ops = &nv50_bl_ops;285 props->max_brightness = 100;286 return 0;287}288 289int290nouveau_backlight_init(struct drm_connector *connector)291{292 struct nouveau_drm *drm = nouveau_drm(connector->dev);293 struct nouveau_backlight *bl;294 struct nouveau_encoder *nv_encoder = NULL;295 struct nvif_device *device = &drm->client.device;296 char backlight_name[BL_NAME_SIZE];297 struct backlight_properties props = {0};298 const struct backlight_ops *ops;299 int ret;300 301 if (apple_gmux_present()) {302 NV_INFO_ONCE(drm, "Apple GMUX detected: not registering Nouveau backlight interface\n");303 return 0;304 }305 306 if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)307 nv_encoder = find_encoder(connector, DCB_OUTPUT_LVDS);308 else if (connector->connector_type == DRM_MODE_CONNECTOR_eDP)309 nv_encoder = find_encoder(connector, DCB_OUTPUT_DP);310 else311 return 0;312 313 if (!nv_encoder)314 return 0;315 316 bl = kzalloc(sizeof(*bl), GFP_KERNEL);317 if (!bl)318 return -ENOMEM;319 320 switch (device->info.family) {321 case NV_DEVICE_INFO_V0_CURIE:322 ret = nv40_backlight_init(nv_encoder, &props, &ops);323 break;324 case NV_DEVICE_INFO_V0_TESLA:325 case NV_DEVICE_INFO_V0_FERMI:326 case NV_DEVICE_INFO_V0_KEPLER:327 case NV_DEVICE_INFO_V0_MAXWELL:328 case NV_DEVICE_INFO_V0_PASCAL:329 case NV_DEVICE_INFO_V0_VOLTA:330 case NV_DEVICE_INFO_V0_TURING:331 case NV_DEVICE_INFO_V0_AMPERE: //XXX: not confirmed332 ret = nv50_backlight_init(bl, nouveau_connector(connector),333 nv_encoder, &props, &ops);334 break;335 default:336 ret = 0;337 goto fail_alloc;338 }339 340 if (ret) {341 if (ret == -ENODEV)342 ret = 0;343 goto fail_alloc;344 }345 346 if (!nouveau_acpi_video_backlight_use_native()) {347 NV_INFO(drm, "Skipping nv_backlight registration\n");348 goto fail_alloc;349 }350 351 if (!nouveau_get_backlight_name(backlight_name, bl)) {352 NV_ERROR(drm, "Failed to retrieve a unique name for the backlight interface\n");353 goto fail_alloc;354 }355 356 props.type = BACKLIGHT_RAW;357 bl->dev = backlight_device_register(backlight_name, connector->kdev,358 nv_encoder, ops, &props);359 if (IS_ERR(bl->dev)) {360 if (bl->id >= 0)361 ida_free(&bl_ida, bl->id);362 ret = PTR_ERR(bl->dev);363 goto fail_alloc;364 }365 366 nouveau_connector(connector)->backlight = bl;367 if (!bl->dev->props.brightness)368 bl->dev->props.brightness =369 bl->dev->ops->get_brightness(bl->dev);370 backlight_update_status(bl->dev);371 372 return 0;373 374fail_alloc:375 kfree(bl);376 /*377 * If we get here we have an internal panel, but no nv_backlight,378 * try registering an ACPI video backlight device instead.379 */380 if (ret == 0)381 nouveau_acpi_video_register_backlight();382 383 return ret;384}385 386void387nouveau_backlight_fini(struct drm_connector *connector)388{389 struct nouveau_connector *nv_conn = nouveau_connector(connector);390 struct nouveau_backlight *bl = nv_conn->backlight;391 392 if (!bl)393 return;394 395 if (bl->id >= 0)396 ida_free(&bl_ida, bl->id);397 398 backlight_device_unregister(bl->dev);399 nv_conn->backlight = NULL;400 kfree(bl);401}402 403void404nouveau_backlight_ctor(void)405{406 ida_init(&bl_ida);407}408 409void410nouveau_backlight_dtor(void)411{412 ida_destroy(&bl_ida);413}414