blob: 9342e20ffc24c37b5055085feae25972b6599a3c [file] [log] [blame]
/*
* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <drm_fourcc.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include "util.h"
#include "video.h"
#include "dbus_interface.h"
#include "dbus.h"
static int kms_open()
{
const char *module_list[] = {
"cirrus",
"exynos",
"i915",
"msm",
"rockchip",
"tegra",
};
int fd = -1;
unsigned i;
for (i = 0; i < ARRAY_SIZE(module_list); i++) {
fd = drmOpen(module_list[i], NULL);
if (fd >= 0)
break;
}
return fd;
}
static drmModeCrtc *find_crtc_for_connector(int fd,
drmModeRes *resources,
drmModeConnector *connector)
{
int i;
unsigned encoder_crtc_id = 0;
/* Find the encoder */
for (i = 0; i < resources->count_encoders; i++) {
drmModeEncoder *encoder =
drmModeGetEncoder(fd, resources->encoders[i]);
if (encoder) {
if (encoder->encoder_id == connector->encoder_id) {
encoder_crtc_id = encoder->crtc_id;
drmModeFreeEncoder(encoder);
break;
}
drmModeFreeEncoder(encoder);
}
}
if (!encoder_crtc_id)
return NULL;
/* Find the crtc */
for (i = 0; i < resources->count_crtcs; i++) {
drmModeCrtc *crtc = drmModeGetCrtc(fd, resources->crtcs[i]);
if (crtc) {
if (encoder_crtc_id == crtc->crtc_id)
return crtc;
drmModeFreeCrtc(crtc);
}
}
return NULL;
}
static bool is_connector_used(int fd,
drmModeRes *resources,
drmModeConnector *connector)
{
bool result = false;
drmModeCrtc *crtc = find_crtc_for_connector(fd, resources, connector);
if (crtc) {
result = crtc->buffer_id != 0;
drmModeFreeCrtc(crtc);
}
return result;
}
static drmModeConnector *find_used_connector_by_type(int fd,
drmModeRes *resources,
unsigned type)
{
int i;
for (i = 0; i < resources->count_connectors; i++) {
drmModeConnector *connector;
connector = drmModeGetConnector(fd, resources->connectors[i]);
if (connector) {
if ((connector->connector_type == type) &&
(connector->connection == DRM_MODE_CONNECTED))
return connector;
drmModeFreeConnector(connector);
}
}
return NULL;
}
static drmModeConnector *find_first_used_connector(int fd,
drmModeRes *resources)
{
int i;
for (i = 0; i < resources->count_connectors; i++) {
drmModeConnector *connector;
connector = drmModeGetConnector(fd, resources->connectors[i]);
if (connector) {
if (is_connector_used(fd, resources, connector))
return connector;
drmModeFreeConnector(connector);
}
}
return NULL;
}
static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources)
{
unsigned i = 0;
/*
* Find the LVDS/eDP/DSI connectors. Those are the main screens.
*/
unsigned kConnectorPriority[] = {
DRM_MODE_CONNECTOR_LVDS,
DRM_MODE_CONNECTOR_eDP,
DRM_MODE_CONNECTOR_DSI,
};
drmModeConnector *main_monitor_connector = NULL;
do {
main_monitor_connector = find_used_connector_by_type(fd,
resources,
kConnectorPriority[i]);
i++;
} while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
/*
* If we didn't find a connector, grab the first one in use.
*/
if (!main_monitor_connector)
main_monitor_connector =
find_first_used_connector(fd, resources);
return main_monitor_connector;
}
static void disable_connector(int fd,
drmModeRes *resources,
drmModeConnector *connector)
{
drmModeCrtc *crtc = find_crtc_for_connector(fd, resources, connector);
if (crtc) {
drmModeSetCrtc(fd, crtc->crtc_id, 0, // buffer_id
0, 0, // x,y
NULL, // connectors
0, // connector_count
NULL); // mode
drmModeFreeCrtc(crtc);
}
}
static void disable_non_main_connectors(int fd,
drmModeRes *resources,
drmModeConnector *main_connector)
{
int i;
for (i = 0; i < resources->count_connectors; i++) {
drmModeConnector *connector;
connector = drmModeGetConnector(fd, resources->connectors[i]);
if (connector->connector_id != main_connector->connector_id)
disable_connector(fd, resources, connector);
drmModeFreeConnector(connector);
}
}
static int video_buffer_create(video_t *video, drmModeCrtc *crtc, drmModeConnector *connector,
int *pitch)
{
struct drm_mode_create_dumb create_dumb;
int ret;
memset(&create_dumb, 0, sizeof (create_dumb));
create_dumb.bpp = 32;
create_dumb.width = crtc->mode.hdisplay;
create_dumb.height = crtc->mode.vdisplay;
ret = drmIoctl(video->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
if (ret) {
LOG(ERROR, "CREATE_DUMB failed");
return ret;
}
video->buffer_properties.size = create_dumb.size;
video->buffer_handle = create_dumb.handle;
struct drm_mode_map_dumb map_dumb;
map_dumb.handle = create_dumb.handle;
ret = drmIoctl(video->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
if (ret) {
LOG(ERROR, "MAP_DUMB failed");
goto destroy_buffer;
}
video->lock.map_offset = map_dumb.offset;
uint32_t offset = 0;
ret = drmModeAddFB2(video->fd, crtc->mode.hdisplay, crtc->mode.vdisplay,
DRM_FORMAT_XRGB8888, &create_dumb.handle,
&create_dumb.pitch, &offset, &video->fb_id, 0);
if (ret) {
LOG(ERROR, "drmModeAddFB2 failed");
goto destroy_buffer;
}
*pitch = create_dumb.pitch;
return ret;
destroy_buffer:
;
struct drm_mode_destroy_dumb destroy_dumb;
destroy_dumb.handle = create_dumb.handle;
drmIoctl(video->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
return ret;
}
video_t* video_init()
{
int32_t width, height, scaling, pitch;
int i;
video_t *new_video = (video_t*)calloc(1, sizeof(video_t));
new_video->fd = -1;
new_video->fd = kms_open();
if (new_video->fd < 0) {
LOG(ERROR, "Unable to open a KMS module");
return NULL;
}
if (drmSetMaster(new_video->fd) != 0) {
LOG(ERROR, "video_init unable to get master");
}
new_video->drm_resources = drmModeGetResources(new_video->fd);
if (!new_video->drm_resources) {
LOG(ERROR, "Unable to get mode resources");
goto fail;
}
new_video->main_monitor_connector = find_main_monitor(new_video->fd, new_video->drm_resources);
if (!new_video->main_monitor_connector) {
LOG(ERROR, "main_monitor_connector is nil");
goto fail;
}
for (i = 0; i < new_video->main_monitor_connector->count_props; i++) {
drmModePropertyPtr prop;
drmModePropertyBlobPtr blob_ptr;
prop = drmModeGetProperty(new_video->fd, new_video->main_monitor_connector->props[i]);
if (prop) {
if (strcmp(prop->name, "EDID") == 0) {
blob_ptr = drmModeGetPropertyBlob(new_video->fd,
new_video->main_monitor_connector->prop_values[i]);
if (blob_ptr) {
switch (new_video->main_monitor_connector->connector_type) {
case DRM_MODE_CONNECTOR_LVDS:
case DRM_MODE_CONNECTOR_eDP:
new_video->internal_panel = 1;
break;
default:
new_video->internal_panel = 0;
}
memcpy(&new_video->edid, blob_ptr->data, EDID_SIZE);
drmModeFreePropertyBlob(blob_ptr);
}
}
}
}
disable_non_main_connectors(new_video->fd,
new_video->drm_resources, new_video->main_monitor_connector);
new_video->crtc = find_crtc_for_connector(new_video->fd,
new_video->drm_resources, new_video->main_monitor_connector);
if (!new_video->crtc) {
LOG(ERROR, "unable to find a crtc");
goto fail;
}
if (video_buffer_create(new_video, new_video->crtc,
new_video->main_monitor_connector, &pitch)) {
LOG(ERROR, "video_buffer_create failed");
goto fail;
}
width = new_video->crtc->mode.hdisplay;
height = new_video->crtc->mode.vdisplay;
if (!new_video->main_monitor_connector->mmWidth)
scaling = 1;
else {
int dots_per_cm = width * 10 / new_video->main_monitor_connector->mmWidth;
if (dots_per_cm > 133)
scaling = 4;
else if (dots_per_cm > 100)
scaling = 3;
else if (dots_per_cm > 67)
scaling = 2;
else
scaling = 1;
}
new_video->buffer_properties.width = width;
new_video->buffer_properties.height = height;
new_video->buffer_properties.pitch = pitch;
new_video->buffer_properties.scaling = scaling;
if (drmDropMaster(new_video->fd) != 0) {
LOG(WARNING, "video_init unable to drop master");
}
return new_video;
fail:
if (new_video->drm_resources)
drmModeFreeResources(new_video->drm_resources);
if (new_video->main_monitor_connector)
drmModeFreeConnector(new_video->main_monitor_connector);
if (new_video->crtc)
drmModeFreeCrtc(new_video->crtc);
if (drmDropMaster(new_video->fd) != 0) {
LOG(WARNING, "video_init unable to drop master");
}
if (new_video->fd >= 0)
drmClose(new_video->fd);
return NULL;
}
int32_t video_setmode(video_t* video)
{
int32_t ret;
drmSetMaster(video->fd);
ret = drmModeSetCrtc(video->fd, video->crtc->crtc_id,
video->fb_id,
0, 0, // x,y
&video->main_monitor_connector->connector_id,
1, // connector_count
&video->crtc->mode); // mode
if (ret) {
LOG(ERROR, "Unable to set crtc");
goto done;
}
ret = drmModeSetCursor(video->fd, video->crtc->crtc_id,
0, 0, 0);
if (ret)
LOG(ERROR, "Unable to hide cursor");
done:
return ret;
}
void video_release(video_t* video)
{
drmDropMaster(video->fd);
}
void video_close(video_t *video)
{
struct drm_mode_destroy_dumb destroy_dumb;
if (!video)
return;
video_release(video);
destroy_dumb.handle = video->buffer_handle;
drmIoctl(video->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
if (video->fd >= 0) {
if (video->main_monitor_connector) {
disable_connector(video->fd, video->drm_resources,
video->main_monitor_connector);
drmModeFreeConnector(video->main_monitor_connector);
video->main_monitor_connector = NULL;
}
if (video->crtc) {
drmModeFreeCrtc(video->crtc);
video->crtc = NULL;
}
if (video->drm_resources) {
drmModeFreeResources(video->drm_resources);
video->drm_resources = NULL;
}
drmClose(video->fd);
video->fd = -1;
}
free(video);
}
uint32_t* video_lock(video_t *video)
{
if (video->lock.count == 0) {
video->lock.map =
mmap(0, video->buffer_properties.size, PROT_READ | PROT_WRITE,
MAP_SHARED, video->fd, video->lock.map_offset);
if (video->lock.map == MAP_FAILED) {
LOG(ERROR, "mmap failed");
return NULL;
}
}
return video->lock.map;
}
void video_unlock(video_t *video)
{
/* XXX Cache flush maybe? */
if (video->lock.count > 0) {
video->lock.count--;
}
if (video->lock.count == 0) {
munmap(video->lock.map, video->buffer_properties.size);
}
}
bool video_load_gamma_ramp(video_t *video, const char* filename)
{
int i;
int r = 0;
unsigned char red[kGammaSize];
unsigned char green[kGammaSize];
unsigned char blue[kGammaSize];
gamma_ramp_t *ramp;
FILE* f = fopen(filename, "rb");
if (f == NULL)
return false;
ramp = &video->gamma_ramp;
r += fread(red, sizeof(red), 1, f);
r += fread(green, sizeof(green), 1, f);
r += fread(blue, sizeof(blue), 1, f);
fclose(f);
if (r != 3)
return false;
for (i = 0; i < kGammaSize; ++i) {
ramp->red[i] = (uint16_t)red[i] * 257;
ramp->green[i] = (uint16_t)green[i] * 257;
ramp->blue[i] = (uint16_t)blue[i] * 257;
}
return true;
}
bool video_set_gamma(video_t* video, const char *filename)
{
bool status;
drmModeCrtcPtr mode;
int drm_status;
status = video_load_gamma_ramp(video, filename);
if (status == false) {
LOG(WARNING, "Unable to load gamma ramp");
return false;
}
mode = drmModeGetCrtc(video->fd, video->crtc->crtc_id);
drm_status = drmModeCrtcSetGamma(video->fd,
mode->crtc_id,
mode->gamma_size,
video->gamma_ramp.red,
video->gamma_ramp.green,
video->gamma_ramp.blue);
return drm_status == 0;
}
buffer_properties_t* video_get_buffer_properties(video_t *video)
{
return &video->buffer_properties;
}
int32_t video_getwidth(video_t *video)
{
return video->buffer_properties.width;
}
int32_t video_getheight(video_t *video)
{
return video->buffer_properties.height;
}
int32_t video_getpitch(video_t *video)
{
return video->buffer_properties.pitch;
}
int32_t video_getscaling(video_t *video)
{
return video->buffer_properties.scaling;
}