nvidia-open-gpu-kernel-modules/kernel-open/nvidia-drm/nvidia-drm-encoder.c

353 lines
9.6 KiB
C

/*
* Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "nvidia-drm-conftest.h" /* NV_DRM_ATOMIC_MODESET_AVAILABLE */
#if defined(NV_DRM_ATOMIC_MODESET_AVAILABLE)
#include "nvidia-drm-priv.h"
#include "nvidia-drm-encoder.h"
#include "nvidia-drm-utils.h"
#include "nvidia-drm-connector.h"
#include "nvidia-drm-crtc.h"
#include "nvidia-drm-helper.h"
#include "nvmisc.h"
/*
* Commit fcd70cd36b9b ("drm: Split out drm_probe_helper.h")
* moves a number of helper function definitions from
* drm/drm_crtc_helper.h to a new drm_probe_helper.h.
*/
#if defined(NV_DRM_DRM_PROBE_HELPER_H_PRESENT)
#include <drm/drm_probe_helper.h>
#endif
#include <drm/drm_crtc_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
static void nv_drm_encoder_destroy(struct drm_encoder *encoder)
{
struct nv_drm_encoder *nv_encoder = to_nv_encoder(encoder);
drm_encoder_cleanup(encoder);
nv_drm_free(nv_encoder);
}
static const struct drm_encoder_funcs nv_encoder_funcs = {
.destroy = nv_drm_encoder_destroy,
};
static bool nv_drm_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void nv_drm_encoder_prepare(struct drm_encoder *encoder)
{
}
static void nv_drm_encoder_commit(struct drm_encoder *encoder)
{
}
static void nv_drm_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
}
static const struct drm_encoder_helper_funcs nv_encoder_helper_funcs = {
.mode_fixup = nv_drm_encoder_mode_fixup,
.prepare = nv_drm_encoder_prepare,
.commit = nv_drm_encoder_commit,
.mode_set = nv_drm_encoder_mode_set,
};
static uint32_t get_crtc_mask(struct drm_device *dev, uint32_t headMask)
{
struct drm_crtc *crtc = NULL;
uint32_t crtc_mask = 0x0;
nv_drm_for_each_crtc(crtc, dev) {
struct nv_drm_crtc *nv_crtc = to_nv_crtc(crtc);
if (headMask & NVBIT(nv_crtc->head)) {
crtc_mask |= drm_crtc_mask(crtc);
}
}
return crtc_mask;
}
/*
* Helper function to create new encoder for given NvKmsKapiDisplay
* with given signal format.
*/
static struct drm_encoder*
nv_drm_encoder_new(struct drm_device *dev,
NvKmsKapiDisplay hDisplay,
NvKmsConnectorSignalFormat format,
unsigned int crtc_mask)
{
struct nv_drm_device *nv_dev = to_nv_device(dev);
struct nv_drm_encoder *nv_encoder = NULL;
int ret = 0;
/* Allocate an NVIDIA encoder object */
nv_encoder = nv_drm_calloc(1, sizeof(*nv_encoder));
if (nv_encoder == NULL) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to allocate memory for NVIDIA-DRM encoder object");
return ERR_PTR(-ENOMEM);
}
nv_encoder->hDisplay = hDisplay;
/* Initialize the base encoder object and add it to the drm subsystem */
ret = drm_encoder_init(dev,
&nv_encoder->base, &nv_encoder_funcs,
nvkms_connector_signal_to_drm_encoder_signal(format)
#if defined(NV_DRM_ENCODER_INIT_HAS_NAME_ARG)
, NULL
#endif
);
if (ret != 0) {
nv_drm_free(nv_encoder);
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to initialize encoder created from NvKmsKapiDisplay 0x%08x",
hDisplay);
return ERR_PTR(ret);
}
nv_encoder->base.possible_crtcs = crtc_mask;
drm_encoder_helper_add(&nv_encoder->base, &nv_encoder_helper_funcs);
return &nv_encoder->base;
}
/*
* Add encoder for given NvKmsKapiDisplay
*/
struct drm_encoder*
nv_drm_add_encoder(struct drm_device *dev, NvKmsKapiDisplay hDisplay)
{
struct nv_drm_device *nv_dev = to_nv_device(dev);
struct NvKmsKapiStaticDisplayInfo *displayInfo = NULL;
struct NvKmsKapiConnectorInfo *connectorInfo = NULL;
struct drm_encoder *encoder = NULL;
struct nv_drm_encoder *nv_encoder = NULL;
struct drm_connector *connector = NULL;
int ret = 0;
/* Query NvKmsKapiStaticDisplayInfo and NvKmsKapiConnectorInfo */
if ((displayInfo = nv_drm_calloc(1, sizeof(*displayInfo))) == NULL) {
ret = -ENOMEM;
goto done;
}
if (!nvKms->getStaticDisplayInfo(nv_dev->pDevice, hDisplay, displayInfo)) {
ret = -EINVAL;
goto done;
}
connectorInfo = nvkms_get_connector_info(nv_dev->pDevice,
displayInfo->connectorHandle);
if (IS_ERR(connectorInfo)) {
ret = PTR_ERR(connectorInfo);
goto done;
}
/* Create and add drm encoder */
encoder = nv_drm_encoder_new(dev,
displayInfo->handle,
connectorInfo->signalFormat,
get_crtc_mask(dev, displayInfo->headMask));
if (IS_ERR(encoder)) {
ret = PTR_ERR(encoder);
goto done;
}
/* Get connector from respective physical index */
connector =
nv_drm_get_connector(dev,
connectorInfo->physicalIndex,
connectorInfo->type,
displayInfo->internal, displayInfo->dpAddress);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
goto failed_connector_encoder_attach;
}
/* Attach encoder and connector */
ret = nv_drm_connector_attach_encoder(connector, encoder);
if (ret != 0) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to attach encoder created from NvKmsKapiDisplay 0x%08x "
"to connector",
hDisplay);
goto failed_connector_encoder_attach;
}
nv_encoder = to_nv_encoder(encoder);
mutex_lock(&dev->mode_config.mutex);
nv_encoder->nv_connector = to_nv_connector(connector);
nv_drm_connector_mark_connection_status_dirty(nv_encoder->nv_connector);
mutex_unlock(&dev->mode_config.mutex);
goto done;
failed_connector_encoder_attach:
drm_encoder_cleanup(encoder);
nv_drm_free(encoder);
done:
nv_drm_free(displayInfo);
nv_drm_free(connectorInfo);
return ret != 0 ? ERR_PTR(ret) : encoder;
}
static inline struct nv_drm_encoder*
get_nv_encoder_from_nvkms_display(struct drm_device *dev,
NvKmsKapiDisplay hDisplay)
{
struct drm_encoder *encoder;
nv_drm_for_each_encoder(encoder, dev) {
struct nv_drm_encoder *nv_encoder = to_nv_encoder(encoder);
if (nv_encoder->hDisplay == hDisplay) {
return nv_encoder;
}
}
return NULL;
}
void nv_drm_handle_display_change(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay)
{
struct drm_device *dev = nv_dev->dev;
struct nv_drm_encoder *nv_encoder = NULL;
mutex_lock(&dev->mode_config.mutex);
nv_encoder = get_nv_encoder_from_nvkms_display(dev, hDisplay);
mutex_unlock(&dev->mode_config.mutex);
if (nv_encoder == NULL) {
return;
}
nv_drm_connector_mark_connection_status_dirty(nv_encoder->nv_connector);
schedule_delayed_work(&nv_dev->hotplug_event_work, 0);
}
void nv_drm_handle_dynamic_display_connected(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay)
{
struct drm_device *dev = nv_dev->dev;
struct drm_encoder *encoder = NULL;
struct nv_drm_encoder *nv_encoder = NULL;
/*
* Look for an existing encoder with the same hDisplay and
* use it if available.
*/
nv_encoder = get_nv_encoder_from_nvkms_display(dev, hDisplay);
if (nv_encoder != NULL) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Encoder with NvKmsKapiDisplay 0x%08x already exists.",
hDisplay);
return;
}
encoder = nv_drm_add_encoder(dev, hDisplay);
if (IS_ERR(encoder)) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to add encoder for NvKmsKapiDisplay 0x%08x",
hDisplay);
return;
}
/*
* On some kernels, DRM has the notion of a "primary group" that
* tracks the global mode setting state for the device.
*
* On kernels where DRM has a primary group, we need to reinitialize
* after adding encoders and connectors.
*/
#if defined(NV_DRM_REINIT_PRIMARY_MODE_GROUP_PRESENT)
drm_reinit_primary_mode_group(dev);
#endif
schedule_delayed_work(&nv_dev->hotplug_event_work, 0);
}
#endif