blob: 0909361a14d42b7a9244bbdfd3e371d70fc8e404 [file] [log] [blame]
/*
* Copyright (C) 2010-2011 Chia-I Wu <olvaffe@gmail.com>
* Copyright (C) 2010-2011 LunarG Inc.
*
* 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.
*/
#define LOG_TAG "GRALLOC-KMS"
#include <cutils/properties.h>
#include <cutils/log.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <math.h>
#include "gralloc_drm.h"
#include "gralloc_drm_priv.h"
#include <hardware_legacy/uevent.h>
#include <drm_fourcc.h>
/*
* Return true if a bo needs fb.
*/
int gralloc_drm_bo_need_fb(const struct gralloc_drm_bo_t *bo)
{
return ((bo->handle->usage & GRALLOC_USAGE_HW_FB) &&
bo->drm->swap_mode != DRM_SWAP_COPY);
}
static unsigned int drm_format_from_hal(int hal_format)
{
switch(hal_format) {
case HAL_PIXEL_FORMAT_RGB_888:
case HAL_PIXEL_FORMAT_BGRA_8888:
return DRM_FORMAT_XRGB8888;
case HAL_PIXEL_FORMAT_RGBX_8888:
return DRM_FORMAT_XBGR8888;
case HAL_PIXEL_FORMAT_RGBA_8888:
return DRM_FORMAT_RGBA8888;
case HAL_PIXEL_FORMAT_RGB_565:
return DRM_FORMAT_RGB565;
case HAL_PIXEL_FORMAT_YV12:
return DRM_FORMAT_YUV420;
case HAL_PIXEL_FORMAT_DRM_NV12:
return DRM_FORMAT_NV12;
default:
return 0;
}
}
/*
* Modify pitches, offsets and handles according to
* the format and return corresponding drm format value
*/
static int resolve_drm_format(struct gralloc_drm_bo_t *bo,
uint32_t *pitches, uint32_t *offsets, uint32_t *handles)
{
struct gralloc_drm_t *drm = bo->drm;
pitches[0] = bo->handle->stride;
handles[0] = bo->fb_handle;
/* driver takes care of HW specific padding, alignment etc. */
if (drm->drv->resolve_format)
drm->drv->resolve_format(drm->drv, bo,
pitches, offsets, handles);
return drm_format_from_hal(bo->handle->format);
}
/*
* Returns planes that are supported for a particular format
*/
unsigned int planes_for_format(struct gralloc_drm_t *drm,
int hal_format)
{
unsigned int i, j, mask = 0;
unsigned int drm_format = drm_format_from_hal(hal_format);
struct gralloc_drm_plane_t *plane = drm->planes;
/* no planes available */
if (!plane)
return 0;
/* iterate through planes, mark those that match format */
for (i=0; i<drm->plane_resources->count_planes; i++, plane++)
for (j=0; j<plane->drm_plane->count_formats; j++)
if (plane->drm_plane->formats[j] == drm_format)
mask |= (1U << plane->drm_plane->plane_id);
return mask;
}
/*
* Add a fb object for a bo.
*/
int gralloc_drm_bo_add_fb(struct gralloc_drm_bo_t *bo)
{
uint32_t pitches[4] = { 0, 0, 0, 0 };
uint32_t offsets[4] = { 0, 0, 0, 0 };
uint32_t handles[4] = { 0, 0, 0, 0 };
if (bo->fb_id)
return 0;
int drm_format = resolve_drm_format(bo, pitches, offsets, handles);
if (drm_format == 0) {
ALOGE("error resolving drm format");
return -EINVAL;
}
return drmModeAddFB2(bo->drm->fd,
bo->handle->width, bo->handle->height,
drm_format, handles, pitches, offsets,
(uint32_t *) &bo->fb_id, 0);
}
/*
* Remove a fb object for a bo.
*/
void gralloc_drm_bo_rm_fb(struct gralloc_drm_bo_t *bo)
{
if (bo->fb_id) {
drmModeRmFB(bo->drm->fd, bo->fb_id);
bo->fb_id = 0;
}
}
/*
* Program CRTC.
*/
static int drm_kms_set_crtc(struct gralloc_drm_t *drm,
struct gralloc_drm_output *output, int fb_id)
{
int ret;
ret = drmModeSetCrtc(drm->fd, output->crtc_id, fb_id,
0, 0, &output->connector_id, 1, &output->mode);
if (ret) {
ALOGE("failed to set crtc (%s) (crtc_id %d, fb_id %d, conn %d, mode %dx%d)",
strerror(errno), output->crtc_id, fb_id, output->connector_id,
output->mode.hdisplay, output->mode.vdisplay);
return ret;
}
if (drm->mode_quirk_vmwgfx)
ret = drmModeDirtyFB(drm->fd, fb_id, &drm->clip, 1);
return ret;
}
/*
* Callback for a page flip event.
*/
static void page_flip_handler(int fd, unsigned int sequence,
unsigned int tv_sec, unsigned int tv_usec,
void *user_data)
{
struct gralloc_drm_t *drm = (struct gralloc_drm_t *) user_data;
/* ack the last scheduled flip */
drm->current_front = drm->next_front;
drm->next_front = NULL;
}
/*
* Set a plane.
*/
static int gralloc_drm_bo_setplane(struct gralloc_drm_t *drm,
struct gralloc_drm_plane_t *plane)
{
struct gralloc_drm_bo_t *bo = NULL;
int err;
if (plane->handle)
bo = gralloc_drm_bo_from_handle(plane->handle);
// create a framebuffer if does not exist
if (bo && bo->fb_id == 0) {
err = gralloc_drm_bo_add_fb(bo);
if (err) {
ALOGE("%s: could not create drm fb, (%s)",
__func__, strerror(-err));
return err;
}
}
err = drmModeSetPlane(drm->fd,
plane->drm_plane->plane_id,
drm->primary.crtc_id,
bo ? bo->fb_id : 0,
0, // flags
plane->dst_x,
plane->dst_y,
plane->dst_w,
plane->dst_h,
plane->src_x << 16,
plane->src_y << 16,
plane->src_w << 16,
plane->src_h << 16);
if (err) {
/* clear plane_mask so that this buffer won't be tried again */
struct gralloc_drm_handle_t *drm_handle =
(struct gralloc_drm_handle_t *) plane->handle;
drm_handle->plane_mask = 0;
ALOGE("drmModeSetPlane : error (%s) (plane %d crtc %d fb %d)",
strerror(-err),
plane->drm_plane->plane_id,
drm->primary.crtc_id,
bo ? bo->fb_id : 0);
}
if (plane->prev)
gralloc_drm_bo_decref(plane->prev);
if (bo)
bo->refcount++;
plane->prev = bo;
return err;
}
/*
* Returns if a particular plane is supported with the implementation
*/
static unsigned is_plane_supported(const struct gralloc_drm_t *drm,
const struct gralloc_drm_plane_t *plane)
{
/* Planes are only supported on primary pipe for now */
return plane->drm_plane->possible_crtcs & (1 << drm->primary.pipe);
}
/*
* Sets all the active planes to be displayed.
*/
static void gralloc_drm_set_planes(struct gralloc_drm_t *drm)
{
struct gralloc_drm_plane_t *plane = drm->planes;
unsigned int i;
for (i = 0; i < drm->plane_resources->count_planes;
i++, plane++) {
/* plane is not in use at all */
if (!plane->active && !plane->handle)
continue;
/* plane is active, safety check if it is supported */
if (!is_plane_supported(drm, plane))
ALOGE("%s: plane %d is not supported",
__func__, plane->drm_plane->plane_id);
/*
* Disable overlay if it is not active
* or if there is error during setplane
*/
if (!plane->active)
plane->handle = 0;
if (gralloc_drm_bo_setplane(drm, plane))
plane->active = 0;
}
}
/*
* Interface for HWC, used to reserve a plane for a layer.
*/
int gralloc_drm_reserve_plane(struct gralloc_drm_t *drm,
buffer_handle_t handle,
uint32_t id,
uint32_t dst_x,
uint32_t dst_y,
uint32_t dst_w,
uint32_t dst_h,
uint32_t src_x,
uint32_t src_y,
uint32_t src_w,
uint32_t src_h)
{
unsigned int i, j;
struct gralloc_drm_handle_t *drm_handle =
gralloc_drm_handle(handle);
int plane_count = drm->plane_resources->count_planes;
struct gralloc_drm_plane_t *plane = drm->planes;
/* no supported planes for this handle */
if (!drm_handle->plane_mask) {
ALOGE("%s: buffer %p cannot be shown on a plane\n",
__func__, drm_handle);
return -EINVAL;
}
for (j = 0; j < plane_count; j++, plane++) {
/*
* handle may be suitable to be shown on a plane, in
* addition we need to check that this particular plane
* is supported by the current implementation
*/
if (!is_plane_supported(drm, plane))
continue;
/* if plane is available and can support this buffer */
if (!plane->active &&
drm_handle->plane_mask &
(1U << plane->drm_plane->plane_id)) {
plane->dst_x = dst_x;
plane->dst_y = dst_y;
plane->dst_w = dst_w;
plane->dst_h = dst_h;
plane->src_x = src_x;
plane->src_y = src_y;
plane->src_w = src_w;
plane->src_h = src_h;
plane->handle = handle;
plane->id = id;
plane->active = 1;
return 0;
}
}
/* no free planes available */
return -EBUSY;
}
/*
* Interface for HWC, used to disable all the overlays. Plane id
* is also set to 0 as it should be mappable to a particular layer only
* if it has been reserved with 'reserve_plane'.
*/
void gralloc_drm_disable_planes(struct gralloc_drm_t *drm)
{
struct gralloc_drm_plane_t *plane = drm->planes;
unsigned int i;
for (i = 0; i < drm->plane_resources->count_planes; i++, plane++) {
plane->active = 0;
plane->id = 0;
}
}
/*
* Interface for HWC, used to change handle of a reserved plane.
*/
int gralloc_drm_set_plane_handle(struct gralloc_drm_t *drm,
uint32_t id, buffer_handle_t handle)
{
struct gralloc_drm_plane_t *plane = drm->planes;
unsigned i;
for (i = 0; i < drm->plane_resources->count_planes; i++, plane++)
if (plane->active && plane->id == id) {
plane->handle = handle;
return 0;
}
return -EINVAL;
}
/*
* Schedule a page flip.
*/
static int drm_kms_page_flip(struct gralloc_drm_t *drm,
struct gralloc_drm_bo_t *bo)
{
int ret;
/* there is another flip pending */
while (drm->next_front) {
drm->waiting_flip = 1;
drmHandleEvent(drm->fd, &drm->evctx);
drm->waiting_flip = 0;
if (drm->next_front) {
/* record an error and break */
ALOGE("drmHandleEvent returned without flipping");
drm->current_front = drm->next_front;
drm->next_front = NULL;
}
}
if (!bo)
return 0;
pthread_mutex_lock(&drm->hdmi_mutex);
if (drm->hdmi.active && drm->hdmi_mode == HDMI_CLONED && drm->hdmi.bo) {
int dst_x1 = 0, dst_y1 = 0;
if (drm->hdmi.bo->handle->width > bo->handle->width)
dst_x1 = (drm->hdmi.bo->handle->width - bo->handle->width) / 2;
if (drm->hdmi.bo->handle->height > bo->handle->height)
dst_y1 = (drm->hdmi.bo->handle->height - bo->handle->height) / 2;
drm->drv->blit(drm->drv, drm->hdmi.bo, bo,
dst_x1, dst_y1,
dst_x1 + bo->handle->width,
dst_y1 + bo->handle->height,
0, 0, bo->handle->width, bo->handle->height);
ret = drmModePageFlip(drm->fd, drm->hdmi.crtc_id, drm->hdmi.bo->fb_id, 0, NULL);
if (ret && errno != EBUSY)
ALOGE("failed to perform page flip for hdmi (%s) (crtc %d fb %d))",
strerror(errno), drm->hdmi.crtc_id, drm->hdmi.bo->fb_id);
}
pthread_mutex_unlock(&drm->hdmi_mutex);
/* set planes to be displayed */
gralloc_drm_set_planes(drm);
ret = drmModePageFlip(drm->fd, drm->primary.crtc_id, bo->fb_id,
DRM_MODE_PAGE_FLIP_EVENT, (void *) drm);
if (ret) {
ALOGE("failed to perform page flip for primary (%s) (crtc %d fb %d))",
strerror(errno), drm->primary.crtc_id, bo->fb_id);
/* try to set mode for next frame */
if (errno != EBUSY)
drm->first_post = 1;
}
else
drm->next_front = bo;
return ret;
}
/*
* Wait for the next post.
*/
static void drm_kms_wait_for_post(struct gralloc_drm_t *drm, int flip)
{
unsigned int current, target;
drmVBlank vbl;
int ret;
if (drm->mode_quirk_vmwgfx)
return;
flip = !!flip;
memset(&vbl, 0, sizeof(vbl));
vbl.request.type = DRM_VBLANK_RELATIVE;
if (drm->vblank_secondary)
vbl.request.type |= DRM_VBLANK_SECONDARY;
vbl.request.sequence = 0;
/* get the current vblank */
ret = drmWaitVBlank(drm->fd, &vbl);
if (ret) {
ALOGW("failed to get vblank");
return;
}
current = vbl.reply.sequence;
if (drm->first_post)
target = current;
else
target = drm->last_swap + drm->swap_interval - flip;
/* wait for vblank */
if (current < target || !flip) {
memset(&vbl, 0, sizeof(vbl));
vbl.request.type = DRM_VBLANK_ABSOLUTE;
if (drm->vblank_secondary)
vbl.request.type |= DRM_VBLANK_SECONDARY;
if (!flip) {
vbl.request.type |= DRM_VBLANK_NEXTONMISS;
if (target < current)
target = current;
}
vbl.request.sequence = target;
ret = drmWaitVBlank(drm->fd, &vbl);
if (ret) {
ALOGW("failed to wait vblank");
return;
}
}
drm->last_swap = vbl.reply.sequence + flip;
}
/*
* Post a bo. This is not thread-safe.
*/
int gralloc_drm_bo_post(struct gralloc_drm_bo_t *bo)
{
struct gralloc_drm_t *drm = bo->drm;
int ret;
if (!bo->fb_id && drm->swap_mode != DRM_SWAP_COPY) {
ALOGE("unable to post bo %p without fb", bo);
return -EINVAL;
}
/* TODO spawn a thread to avoid waiting and race */
if (drm->first_post) {
if (drm->swap_mode == DRM_SWAP_COPY) {
struct gralloc_drm_bo_t *dst;
dst = (drm->next_front) ?
drm->next_front :
drm->current_front;
drm->drv->blit(drm->drv, dst, bo, 0, 0,
bo->handle->width,
bo->handle->height,
0, 0,
bo->handle->width,
bo->handle->height);
bo = dst;
}
ret = drm_kms_set_crtc(drm, &drm->primary, bo->fb_id);
if (!ret) {
drm->first_post = 0;
drm->current_front = bo;
if (drm->next_front == bo)
drm->next_front = NULL;
}
pthread_mutex_lock(&drm->hdmi_mutex);
if (drm->hdmi.active && drm->hdmi_mode == HDMI_CLONED && drm->hdmi.bo)
drm_kms_set_crtc(drm, &drm->hdmi, drm->hdmi.bo->fb_id);
pthread_mutex_unlock(&drm->hdmi_mutex);
return ret;
}
switch (drm->swap_mode) {
case DRM_SWAP_FLIP:
if (drm->swap_interval > 1)
drm_kms_wait_for_post(drm, 1);
ret = drm_kms_page_flip(drm, bo);
if (drm->next_front) {
/*
* wait if the driver says so or the current front
* will be written by CPU
*/
if (drm->mode_sync_flip ||
(drm->current_front->handle->usage &
GRALLOC_USAGE_SW_WRITE_MASK))
drm_kms_page_flip(drm, NULL);
}
break;
case DRM_SWAP_COPY:
drm_kms_wait_for_post(drm, 0);
drm->drv->blit(drm->drv, drm->current_front,
bo, 0, 0,
bo->handle->width,
bo->handle->height,
0, 0,
bo->handle->width,
bo->handle->height);
if (drm->mode_quirk_vmwgfx)
ret = drmModeDirtyFB(drm->fd, drm->current_front->fb_id, &drm->clip, 1);
ret = 0;
break;
case DRM_SWAP_SETCRTC:
drm_kms_wait_for_post(drm, 0);
ret = drm_kms_set_crtc(drm, &drm->primary, bo->fb_id);
pthread_mutex_lock(&drm->hdmi_mutex);
if (drm->hdmi.active && drm->hdmi_mode == HDMI_CLONED && drm->hdmi.bo)
drm_kms_set_crtc(drm, &drm->hdmi, drm->hdmi.bo->fb_id);
pthread_mutex_unlock(&drm->hdmi_mutex);
drm->current_front = bo;
break;
default:
/* no-op */
ret = 0;
break;
}
return ret;
}
static struct gralloc_drm_t *drm_singleton;
static void on_signal(int sig)
{
struct gralloc_drm_t *drm = drm_singleton;
/* wait the pending flip */
if (drm && drm->swap_mode == DRM_SWAP_FLIP && drm->next_front) {
/* there is race, but this function is hacky enough to ignore that */
if (drm_singleton->waiting_flip)
usleep(100 * 1000); /* 100ms */
else
drm_kms_page_flip(drm_singleton, NULL);
}
exit(-1);
}
static void drm_kms_init_features(struct gralloc_drm_t *drm)
{
const char *swap_mode;
/* call to the driver here, after KMS has been initialized */
drm->drv->init_kms_features(drm->drv, drm);
if (drm->swap_mode == DRM_SWAP_FLIP) {
struct sigaction act;
memset(&drm->evctx, 0, sizeof(drm->evctx));
drm->evctx.version = DRM_EVENT_CONTEXT_VERSION;
drm->evctx.page_flip_handler = page_flip_handler;
/*
* XXX GPU tends to freeze if the program is terminiated with a
* flip pending. What is the right way to handle the
* situation?
*/
sigemptyset(&act.sa_mask);
act.sa_handler = on_signal;
act.sa_flags = 0;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
drm_singleton = drm;
}
else if (drm->swap_mode == DRM_SWAP_COPY) {
struct gralloc_drm_bo_t *front;
int stride;
/* create the real front buffer */
front = gralloc_drm_bo_create(drm,
drm->primary.mode.hdisplay,
drm->primary.mode.vdisplay,
drm->primary.fb_format,
GRALLOC_USAGE_HW_FB);
if (front && gralloc_drm_bo_add_fb(front)) {
gralloc_drm_bo_decref(front);
front = NULL;
}
/* abuse next_front */
if (front)
drm->next_front = front;
else
drm->swap_mode = DRM_SWAP_SETCRTC;
}
switch (drm->swap_mode) {
case DRM_SWAP_FLIP:
swap_mode = "flip";
break;
case DRM_SWAP_COPY:
swap_mode = "copy";
break;
case DRM_SWAP_SETCRTC:
swap_mode = "set-crtc";
break;
default:
swap_mode = "no-op";
break;
}
ALOGD("will use %s for fb posting", swap_mode);
}
#define MARGIN_PERCENT 1.8 /* % of active vertical image*/
#define CELL_GRAN 8.0 /* assumed character cell granularity*/
#define MIN_PORCH 1 /* minimum front porch */
#define V_SYNC_RQD 3 /* width of vsync in lines */
#define H_SYNC_PERCENT 8.0 /* width of hsync as % of total line */
#define MIN_VSYNC_PLUS_BP 550.0 /* min time of vsync + back porch (microsec) */
#define M 600.0 /* blanking formula gradient */
#define C 40.0 /* blanking formula offset */
#define K 128.0 /* blanking formula scaling factor */
#define J 20.0 /* blanking formula scaling factor */
/* C' and M' are part of the Blanking Duty Cycle computation */
#define C_PRIME (((C - J) * K / 256.0) + J)
#define M_PRIME (K / 256.0 * M)
static drmModeModeInfoPtr generate_mode(int h_pixels, int v_lines, float freq)
{
float h_pixels_rnd;
float v_lines_rnd;
float v_field_rate_rqd;
float top_margin;
float bottom_margin;
float interlace;
float h_period_est;
float vsync_plus_bp;
float v_back_porch;
float total_v_lines;
float v_field_rate_est;
float h_period;
float v_field_rate;
float v_frame_rate;
float left_margin;
float right_margin;
float total_active_pixels;
float ideal_duty_cycle;
float h_blank;
float total_pixels;
float pixel_freq;
float h_freq;
float h_sync;
float h_front_porch;
float v_odd_front_porch_lines;
int interlaced = 0;
int margins = 0;
drmModeModeInfoPtr m = malloc(sizeof(drmModeModeInfo));
h_pixels_rnd = rint((float) h_pixels / CELL_GRAN) * CELL_GRAN;
v_lines_rnd = interlaced ? rint((float) v_lines) / 2.0 : rint((float) v_lines);
v_field_rate_rqd = interlaced ? (freq * 2.0) : (freq);
top_margin = margins ? rint(MARGIN_PERCENT / 100.0 * v_lines_rnd) : (0.0);
bottom_margin = margins ? rint(MARGIN_PERCENT / 100.0 * v_lines_rnd) : (0.0);
interlace = interlaced ? 0.5 : 0.0;
h_period_est = (((1.0 / v_field_rate_rqd) - (MIN_VSYNC_PLUS_BP / 1000000.0)) / (v_lines_rnd + (2 * top_margin) + MIN_PORCH + interlace) * 1000000.0);
vsync_plus_bp = rint(MIN_VSYNC_PLUS_BP / h_period_est);
v_back_porch = vsync_plus_bp - V_SYNC_RQD;
total_v_lines = v_lines_rnd + top_margin + bottom_margin + vsync_plus_bp + interlace + MIN_PORCH;
v_field_rate_est = 1.0 / h_period_est / total_v_lines * 1000000.0;
h_period = h_period_est / (v_field_rate_rqd / v_field_rate_est);
v_field_rate = 1.0 / h_period / total_v_lines * 1000000.0;
v_frame_rate = interlaced ? v_field_rate / 2.0 : v_field_rate;
left_margin = margins ? rint(h_pixels_rnd * MARGIN_PERCENT / 100.0 / CELL_GRAN) * CELL_GRAN : 0.0;
right_margin = margins ? rint(h_pixels_rnd * MARGIN_PERCENT / 100.0 / CELL_GRAN) * CELL_GRAN : 0.0;
total_active_pixels = h_pixels_rnd + left_margin + right_margin;
ideal_duty_cycle = C_PRIME - (M_PRIME * h_period / 1000.0);
h_blank = rint(total_active_pixels * ideal_duty_cycle / (100.0 - ideal_duty_cycle) / (2.0 * CELL_GRAN)) * (2.0 * CELL_GRAN);
total_pixels = total_active_pixels + h_blank;
pixel_freq = total_pixels / h_period;
h_freq = 1000.0 / h_period;
h_sync = rint(H_SYNC_PERCENT / 100.0 * total_pixels / CELL_GRAN) * CELL_GRAN;
h_front_porch = (h_blank / 2.0) - h_sync;
v_odd_front_porch_lines = MIN_PORCH + interlace;
m->clock = ceil(pixel_freq) * 1000;
m->hdisplay = (int) (h_pixels_rnd);
m->hsync_start = (int) (h_pixels_rnd + h_front_porch);
m->hsync_end = (int) (h_pixels_rnd + h_front_porch + h_sync);
m->htotal = (int) (total_pixels);
m->hskew = 0;
m->vdisplay = (int) (v_lines_rnd);
m->vsync_start = (int) (v_lines_rnd + v_odd_front_porch_lines);
m->vsync_end = (int) (int) (v_lines_rnd + v_odd_front_porch_lines + V_SYNC_RQD);
m->vtotal = (int) (total_v_lines);
m->vscan = 0;
m->vrefresh = freq;
m->flags = 10;
m->type = 64;
return (m);
}
static drmModeModeInfoPtr find_mode(drmModeConnectorPtr connector, int *bpp)
{
char value[PROPERTY_VALUE_MAX];
drmModeModeInfoPtr mode;
int dist, i;
int xres = 0, yres = 0, rate = 0;
int forcemode = 0;
if (property_get("debug.drm.mode", value, NULL)) {
char *p = value, *end;
/* parse <xres>x<yres>[@<bpp>] */
if (sscanf(value, "%dx%d@%d", &xres, &yres, bpp) != 3) {
*bpp = 0;
if (sscanf(value, "%dx%d", &xres, &yres) != 2)
xres = yres = 0;
}
if ((xres && yres) || *bpp) {
ALOGI("will find the closest match for %dx%d@%d",
xres, yres, *bpp);
}
} else if (property_get("debug.drm.mode.force", value, NULL)) {
char *p = value, *end;
*bpp = 0;
/* parse <xres>x<yres>[@<refreshrate>] */
if (sscanf(value, "%dx%d@%d", &xres, &yres, &rate) != 3) {
rate = 60;
if (sscanf(value, "%dx%d", &xres, &yres) != 2)
xres = yres = 0;
}
if (xres && yres && rate) {
ALOGI("will use %dx%d@%dHz", xres, yres, rate);
forcemode = 1;
}
} else {
*bpp = 0;
}
dist = INT_MAX;
if (forcemode)
mode = generate_mode(xres, yres, rate);
else {
mode = NULL;
for (i = 0; i < connector->count_modes; i++) {
drmModeModeInfoPtr m = &connector->modes[i];
int tmp;
if (xres && yres) {
tmp = (m->hdisplay - xres) * (m->hdisplay - xres) +
(m->vdisplay - yres) * (m->vdisplay - yres);
}
else {
/* use the first preferred mode */
tmp = (m->type & DRM_MODE_TYPE_PREFERRED) ? 0 : dist;
}
if (tmp < dist) {
mode = m;
dist = tmp;
if (!dist)
break;
}
}
}
/* fallback to the first mode */
if (!mode)
mode = &connector->modes[0];
ALOGI("Established mode:");
ALOGI("clock: %d, hdisplay: %d, hsync_start: %d, hsync_end: %d, htotal: %d, hskew: %d", mode->clock, mode->hdisplay, mode->hsync_start, mode->hsync_end, mode->htotal, mode->hskew);
ALOGI("vdisplay: %d, vsync_start: %d, vsync_end: %d, vtotal: %d, vscan: %d, vrefresh: %d", mode->vdisplay, mode->vsync_start, mode->vsync_end, mode->vtotal, mode->vscan, mode->vrefresh);
ALOGI("flags: %d, type: %d, name %s", mode->flags, mode->type, mode->name);
*bpp /= 8;
return mode;
}
/*
* Initialize KMS with a connector.
*/
static int drm_kms_init_with_connector(struct gralloc_drm_t *drm,
struct gralloc_drm_output *output, drmModeConnectorPtr connector)
{
drmModeEncoderPtr encoder;
drmModeModeInfoPtr mode;
static int used_crtcs = 0;
int bpp, i;
if (!connector->count_modes)
return -EINVAL;
encoder = drmModeGetEncoder(drm->fd, connector->encoders[0]);
if (!encoder)
return -EINVAL;
/* find first possible crtc which is not used yet */
for (i = 0; i < drm->resources->count_crtcs; i++) {
if (encoder->possible_crtcs & (1 << i) &&
(used_crtcs & (1 << i)) != (1 << i))
break;
}
used_crtcs |= (1 << i);
drmModeFreeEncoder(encoder);
if (i == drm->resources->count_crtcs)
return -EINVAL;
output->bo = NULL;
output->crtc_id = drm->resources->crtcs[i];
output->connector_id = connector->connector_id;
output->pipe = i;
/* print connector info */
if (connector->count_modes > 1) {
ALOGI("there are %d modes on connector 0x%x, type %d",
connector->count_modes,
connector->connector_id,
connector->connector_type);
for (i = 0; i < connector->count_modes; i++)
ALOGI(" %s", connector->modes[i].name);
}
else {
ALOGI("there is one mode on connector 0x%d: %s",
connector->connector_id,
connector->modes[0].name);
}
mode = find_mode(connector, &bpp);
ALOGI("the best mode is %s", mode->name);
output->mode = *mode;
switch (bpp) {
case 2:
output->fb_format = HAL_PIXEL_FORMAT_RGB_565;
break;
case 4:
default:
output->fb_format = HAL_PIXEL_FORMAT_BGRA_8888;
break;
}
if (connector->mmWidth && connector->mmHeight) {
output->xdpi = (output->mode.hdisplay * 25.4 / connector->mmWidth);
output->ydpi = (output->mode.vdisplay * 25.4 / connector->mmHeight);
}
else {
output->xdpi = 75;
output->ydpi = 75;
}
#ifdef DRM_MODE_FEATURE_DIRTYFB
drm->clip.x1 = 0;
drm->clip.y1 = 0;
drm->clip.x2 = output->mode.hdisplay;
drm->clip.y2 = output->mode.vdisplay;
#endif
return 0;
}
/*
* Fetch a connector of particular type
*/
static drmModeConnectorPtr fetch_connector(struct gralloc_drm_t *drm,
uint32_t type)
{
int i;
if (!drm->resources)
return NULL;
for (i = 0; i < drm->resources->count_connectors; i++) {
drmModeConnectorPtr connector =
connector = drmModeGetConnector(drm->fd,
drm->resources->connectors[i]);
if (connector) {
if (connector->connector_type == type &&
connector->connection == DRM_MODE_CONNECTED)
return connector;
drmModeFreeConnector(connector);
}
}
return NULL;
}
/*
* Initializes hdmi output with a connector and allocates
* a private framebuffer for it. This is called on startup if
* hdmi cable is connected and also on hotplug events.
*/
static void init_hdmi_output(struct gralloc_drm_t *drm,
drmModeConnectorPtr connector)
{
drm_kms_init_with_connector(drm, &drm->hdmi, connector);
ALOGD("%s, allocate private buffer for hdmi [%dx%d]",
__func__, drm->hdmi.mode.hdisplay, drm->hdmi.mode.vdisplay);
drm->hdmi.bo = gralloc_drm_bo_create(drm,
drm->hdmi.mode.hdisplay, drm->hdmi.mode.vdisplay,
drm->hdmi.fb_format,
GRALLOC_USAGE_SW_WRITE_OFTEN|GRALLOC_USAGE_HW_RENDER);
gralloc_drm_bo_add_fb(drm->hdmi.bo);
drm->hdmi_mode = HDMI_CLONED;
drm->hdmi.active = 1;
}
/*
* Thread that listens to uevents and checks if hdmi state changes
*/
static void *hdmi_observer(void *data)
{
static char uevent_desc[4096];
drmModeConnectorPtr hdmi;
struct gralloc_drm_t *drm =
(struct gralloc_drm_t *) data;
uevent_init();
memset(uevent_desc, 0, sizeof(uevent_desc));
while(1) {
/* this polls */
int len = uevent_next_event(uevent_desc, sizeof(uevent_desc) - 2);
if(len && strstr(uevent_desc, "devices/virtual/switch/hdmi")) {
/* check what changed */
const char *prop = uevent_desc + strlen(uevent_desc) + 1;
while (*prop) {
const char *state = strstr(prop, "SWITCH_STATE=");
if (state) {
unsigned int value = 0;
state += strlen("SWITCH_STATE=");
value = atoi(state);
pthread_mutex_lock(&drm->hdmi_mutex);
if (value) {
hdmi = fetch_connector(drm, DRM_MODE_CONNECTOR_HDMIA);
if (hdmi) {
ALOGD("init hdmi on hotplug event");
init_hdmi_output(drm, hdmi);
/* will trigger modeset */
drm->first_post = 1;
drmModeFreeConnector(hdmi);
pthread_mutex_unlock(&drm->hdmi_mutex);
}
break;
} else {
drm->hdmi.active = 0;
ALOGD("destroy hdmi private buffer");
gralloc_drm_bo_decref(drm->hdmi.bo);
drm->hdmi.bo = NULL;
pthread_mutex_unlock(&drm->hdmi_mutex);
break;
}
pthread_mutex_unlock(&drm->hdmi_mutex);
}
/* next property/value pair */
prop += strlen(prop) + 1;
if (prop - uevent_desc >= len)
break;
}
}
}
pthread_exit(NULL);
return 0;
}
/*
* Initialize KMS.
*/
int gralloc_drm_init_kms(struct gralloc_drm_t *drm)
{
drmModeConnectorPtr lvds, hdmi;
int i, ret;
if (drm->resources)
return 0;
drm->resources = drmModeGetResources(drm->fd);
if (!drm->resources) {
ALOGE("failed to get modeset resources");
return -EINVAL;
}
drm->plane_resources = drmModeGetPlaneResources(drm->fd);
if (!drm->plane_resources) {
ALOGD("no planes found from drm resources");
} else {
ALOGD("supported drm planes and formats");
/* fill a helper structure for hwcomposer */
drm->planes = calloc(drm->plane_resources->count_planes,
sizeof(struct gralloc_drm_plane_t));
for (i = 0; i < drm->plane_resources->count_planes; i++) {
unsigned int j;
drm->planes[i].drm_plane = drmModeGetPlane(drm->fd,
drm->plane_resources->planes[i]);
ALOGD("plane id %d", drm->planes[i].drm_plane->plane_id);
for (j = 0; j < drm->planes[i].drm_plane->count_formats; j++)
ALOGD(" format %c%c%c%c",
(drm->planes[i].drm_plane->formats[j]),
(drm->planes[i].drm_plane->formats[j])>>8,
(drm->planes[i].drm_plane->formats[j])>>16,
(drm->planes[i].drm_plane->formats[j])>>24);
}
}
/* find the crtc/connector/mode to use */
lvds = fetch_connector(drm, DRM_MODE_CONNECTOR_LVDS);
if (lvds) {
drm_kms_init_with_connector(drm, &drm->primary, lvds);
drmModeFreeConnector(lvds);
drm->primary.active = 1;
}
/* if still no connector, find first connected connector and try it */
if (!drm->primary.active) {
for (i = 0; i < drm->resources->count_connectors; i++) {
drmModeConnectorPtr connector;
connector = drmModeGetConnector(drm->fd,
drm->resources->connectors[i]);
if (connector) {
if (connector->connection == DRM_MODE_CONNECTED) {
if (!drm_kms_init_with_connector(drm,
&drm->primary, connector))
break;
}
drmModeFreeConnector(connector);
}
}
if (i == drm->resources->count_connectors) {
ALOGE("failed to find a valid crtc/connector/mode combination");
drmModeFreeResources(drm->resources);
drm->resources = NULL;
return -EINVAL;
}
}
/* check if hdmi is connected already */
hdmi = fetch_connector(drm, DRM_MODE_CONNECTOR_HDMIA);
if (hdmi) {
if (hdmi->connector_id == drm->primary.connector_id) {
/* special case: our primary connector is hdmi */
ALOGD("hdmi is the primary connector");
goto skip_hdmi_modes;
}
ALOGD("init hdmi on startup");
init_hdmi_output(drm, hdmi);
drmModeFreeConnector(hdmi);
}
goto skip_hdmi_modes;
/* launch hdmi observer thread */
pthread_mutex_init(&drm->hdmi_mutex, NULL);
pthread_create(&drm->hdmi_hotplug_thread, NULL, hdmi_observer, drm);
skip_hdmi_modes:
drm_kms_init_features(drm);
drm->first_post = 1;
return 0;
}
void gralloc_drm_fini_kms(struct gralloc_drm_t *drm)
{
switch (drm->swap_mode) {
case DRM_SWAP_FLIP:
drm_kms_page_flip(drm, NULL);
break;
case DRM_SWAP_COPY:
{
struct gralloc_drm_bo_t **bo = (drm->current_front) ?
&drm->current_front : &drm->next_front;
if (*bo)
gralloc_drm_bo_decref(*bo);
*bo = NULL;
}
break;
default:
break;
}
/* restore crtc? */
if (drm->resources) {
drmModeFreeResources(drm->resources);
drm->resources = NULL;
}
if (drm->planes) {
unsigned int i;
for (i = 0; i < drm->plane_resources->count_planes; i++)
drmModeFreePlane(drm->planes[i].drm_plane);
free(drm->planes);
drm->planes = NULL;
}
if (drm->plane_resources) {
drmModeFreePlaneResources(drm->plane_resources);
drm->plane_resources = NULL;
}
/* destroy private buffer of hdmi output */
if (drm->hdmi.bo)
gralloc_drm_bo_decref(drm->hdmi.bo);
drm_singleton = NULL;
}
int gralloc_drm_is_kms_initialized(struct gralloc_drm_t *drm)
{
return (drm->resources != NULL);
}
/*
* Initialize a framebuffer device with KMS info.
*/
void gralloc_drm_get_kms_info(struct gralloc_drm_t *drm,
struct framebuffer_device_t *fb)
{
*((uint32_t *) &fb->flags) = 0x0;
*((uint32_t *) &fb->width) = drm->primary.mode.hdisplay;
*((uint32_t *) &fb->height) = drm->primary.mode.vdisplay;
*((int *) &fb->stride) = drm->primary.mode.hdisplay;
*((float *) &fb->fps) = drm->primary.mode.vrefresh;
*((int *) &fb->format) = drm->primary.fb_format;
*((float *) &fb->xdpi) = drm->primary.xdpi;
*((float *) &fb->ydpi) = drm->primary.ydpi;
*((int *) &fb->minSwapInterval) = drm->swap_interval;
*((int *) &fb->maxSwapInterval) = drm->swap_interval;
}
/*
* Return true if fb posting is pipelined.
*/
int gralloc_drm_is_kms_pipelined(struct gralloc_drm_t *drm)
{
return (drm->swap_mode != DRM_SWAP_SETCRTC);
}