efl/src/lib/ecore_drm2/ecore_drm2_displays.c

533 lines
14 KiB
C

#include "ecore_drm2_private.h"
#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
#define EDID_OFFSET_DATA_BLOCKS 0x36
#define EDID_OFFSET_LAST_BLOCK 0x6c
#define EDID_OFFSET_PNPID 0x08
#define EDID_OFFSET_SERIAL 0x0c
static Eina_Thread_Queue *thq = NULL;
typedef struct
{
Eina_Thread_Queue_Msg head;
Ecore_Drm2_Thread_Op_Code code;
} Thread_Msg;
static const char *conn_types[] =
{
"None", "VGA", "DVI-I", "DVI-D", "DVI-A",
"Composite", "S-Video", "LVDS", "Component", "DIN",
"DisplayPort", "HDMI-A", "HDMI-B", "TV", "eDP", "Virtual", "DSI",
};
static void
_ecore_drm2_display_state_thread_send(Ecore_Drm2_Thread_Op_Code code)
{
Thread_Msg *msg;
void *ref;
msg = eina_thread_queue_send(thq, sizeof(Thread_Msg), &ref);
msg->code = code;
eina_thread_queue_send_done(thq, ref);
}
static char *
_ecore_drm2_display_name_get(Ecore_Drm2_Connector *conn)
{
char name[DRM_CONNECTOR_NAME_LEN];
const char *type = NULL;
if (conn->type < EINA_C_ARRAY_LENGTH(conn_types))
type = conn_types[conn->type];
else
type = "UNKNOWN";
snprintf(name, sizeof(name), "%s-%d", type, conn->drmConn->connector_type_id);
return strdup(name);
}
static void
_ecore_drm2_display_edid_parse_string(const uint8_t *data, char text[])
{
int i = 0, rep = 0;
strncpy(text, (const char *)data, 12);
for (; text[i] != '\0'; i++)
{
if ((text[i] == '\n') || (text[i] == '\r'))
{
text[i] = '\0';
break;
}
}
for (i = 0; text[i] != '\0'; i++)
{
if (!isprint(text[i]))
{
text[i] = '-';
rep++;
}
}
if (rep > 4) text[0] = '\0';
}
static int
_ecore_drm2_display_edid_parse(Ecore_Drm2_Display *disp, const uint8_t *data, size_t len)
{
int i = 0;
uint32_t serial;
if (len < 128) return -1;
if ((data[0] != 0x00) || (data[1] != 0xff)) return -1;
disp->edid.pnp[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
disp->edid.pnp[1] =
'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) +
((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
disp->edid.pnp[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
disp->edid.pnp[3] = '\0';
serial = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
serial += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
serial += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
serial += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
if (serial > 0)
sprintf(disp->edid.serial, "%lu", (unsigned long)serial);
for (i = EDID_OFFSET_DATA_BLOCKS; i <= EDID_OFFSET_LAST_BLOCK; i += 18)
{
if (data[i] != 0) continue;
if (data[i + 2] != 0) continue;
if (data[i + 3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME)
_ecore_drm2_display_edid_parse_string(&data[i + 5], disp->edid.monitor);
else if (data[i + 3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER)
_ecore_drm2_display_edid_parse_string(&data[i + 5], disp->edid.serial);
else if (data[i + 3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING)
_ecore_drm2_display_edid_parse_string(&data[i + 5], disp->edid.eisa);
}
return 0;
}
static void
_ecore_drm2_display_edid_get(Ecore_Drm2_Display *disp)
{
Ecore_Drm2_Connector_State *cstate;
int ret = 0;
cstate = disp->conn->state;
ret = _ecore_drm2_display_edid_parse(disp, cstate->edid.data, cstate->edid.len);
if (!ret)
{
if (disp->edid.pnp[0] != '\0')
eina_stringshare_replace(&disp->make, disp->edid.pnp);
if (disp->edid.monitor[0] != '\0')
eina_stringshare_replace(&disp->model, disp->edid.monitor);
if (disp->edid.serial[0] != '\0')
eina_stringshare_replace(&disp->serial, disp->edid.serial);
}
}
static void
_ecore_drm2_display_state_debug(Ecore_Drm2_Display *disp)
{
Eina_List *l = NULL;
Ecore_Drm2_Display_Mode *mode;
DBG("Display Atomic State Fill Complete");
DBG("\tName: %s", disp->name);
DBG("\tMake: %s", disp->make);
DBG("\tModel: %s", disp->model);
DBG("\tSerial: %s", disp->serial);
DBG("\tCrtc: %d", disp->crtc->id);
DBG("\tCrtc Pos: %d %d", disp->crtc->drmCrtc->x, disp->crtc->drmCrtc->y);
DBG("\tConnector: %d", disp->conn->id);
if (disp->backlight.path)
{
DBG("\tBacklight");
switch (disp->backlight.type)
{
case ECORE_DRM2_BACKLIGHT_RAW:
DBG("\t\tType: Raw");
break;
case ECORE_DRM2_BACKLIGHT_PLATFORM:
DBG("\t\tType: Platform");
break;
case ECORE_DRM2_BACKLIGHT_FIRMWARE:
DBG("\t\tType: Firmware");
break;
}
DBG("\t\tPath: %s", disp->backlight.path);
}
EINA_LIST_FOREACH(disp->modes, l, mode)
{
DBG("\tAdded Mode: %dx%d@%.1f%s%s%s, %.1f MHz",
mode->width, mode->height, mode->refresh / 1000.0,
(mode->flags & DRM_MODE_TYPE_PREFERRED) ? ", preferred" : "",
(mode->flags & DRM_MODE_TYPE_DEFAULT) ? ", current" : "",
(disp->conn->drmConn->count_modes == 0) ? ", built-in" : "",
mode->info.clock / 1000.0);
}
/* DBG("\tCloned: %d", disp->cloned); */
DBG("\tPrimary: %d", disp->primary);
DBG("\tEnabled: %d", disp->enabled);
DBG("\tConnected: %d", disp->connected);
}
static double
_ecore_drm2_display_backlight_value_get(Ecore_Drm2_Display *disp, const char *attr)
{
const char *b = NULL;
double ret = 0.0;
if ((!disp) || (!disp->backlight.path)) return 0.0;
b = eeze_udev_syspath_get_sysattr(disp->backlight.path, attr);
if (!b) return 0.0;
ret = strtod(b, NULL);
if (ret < 0) ret = 0.0;
return ret;
}
static void
_ecore_drm2_display_backlight_get(Ecore_Drm2_Display *disp)
{
Eina_List *devs, *l;
const char *dev, *t;
Ecore_Drm2_Backlight_Type type = 0;
Eina_Bool found = EINA_FALSE;
devs = eeze_udev_find_by_filter("backlight", NULL, NULL);
EINA_LIST_FOREACH(devs, l, dev)
{
t = eeze_udev_syspath_get_sysattr(dev, "type");
if (!t) continue;
if (!strcmp(t, "raw"))
type = ECORE_DRM2_BACKLIGHT_RAW;
else if (!strcmp(t, "platform"))
type = ECORE_DRM2_BACKLIGHT_PLATFORM;
else if (!strcmp(t, "firmware"))
type = ECORE_DRM2_BACKLIGHT_FIRMWARE;
if ((disp->conn->type == DRM_MODE_CONNECTOR_LVDS) ||
(disp->conn->type == DRM_MODE_CONNECTOR_eDP) ||
(type == ECORE_DRM2_BACKLIGHT_RAW))
found = EINA_TRUE;
eina_stringshare_del(t);
if (found) break;
}
if (found)
{
disp->backlight.type = type;
disp->backlight.path = eina_stringshare_add(dev);
disp->backlight.max =
_ecore_drm2_display_backlight_value_get(disp, "max_brightness");
disp->backlight.value =
_ecore_drm2_display_backlight_value_get(disp, "brightness");
}
EINA_LIST_FREE(devs, dev)
eina_stringshare_del(dev);
}
static Ecore_Drm2_Display_Mode *
_ecore_drm2_display_mode_create(const drmModeModeInfo *info)
{
Ecore_Drm2_Display_Mode *mode;
uint64_t refresh;
EINA_SAFETY_ON_NULL_RETURN_VAL(info, NULL);
EINA_SAFETY_ON_FALSE_RETURN_VAL((info->htotal > 0), NULL);
EINA_SAFETY_ON_FALSE_RETURN_VAL((info->vtotal > 0), NULL);
mode = calloc(1, sizeof(Ecore_Drm2_Display_Mode));
if (!mode) return NULL;
mode->flags = 0;
mode->width = info->hdisplay;
mode->height = info->vdisplay;
refresh = (info->clock * 1000000LL / info->htotal +
info->vtotal / 2) / info->vtotal;
if (info->flags & DRM_MODE_FLAG_INTERLACE)
refresh *= 2;
if (info->flags & DRM_MODE_FLAG_DBLSCAN)
refresh /= 2;
if (info->vscan > 1)
refresh /= info->vscan;
mode->refresh = refresh;
mode->info = *info;
if (info->type & DRM_MODE_TYPE_PREFERRED)
mode->flags |= DRM_MODE_TYPE_PREFERRED;
return mode;
}
static void
_ecore_drm2_display_modes_get(Ecore_Drm2_Display *disp)
{
int i = 0;
drmModeModeInfo crtc_mode;
Ecore_Drm2_Display_Mode *dmode;
Ecore_Drm2_Display_Mode *current = NULL, *pref = NULL, *best = NULL;
Eina_List *l = NULL;
memset(&crtc_mode, 0, sizeof(crtc_mode));
if (disp->crtc->drmCrtc->mode_valid)
crtc_mode = disp->crtc->drmCrtc->mode;
/* loop through connector modes and try to create mode */
for (; i < disp->conn->drmConn->count_modes; i++)
{
dmode =
_ecore_drm2_display_mode_create(&disp->conn->drmConn->modes[i]);
if (!dmode) continue;
/* append mode to display mode list */
disp->modes = eina_list_append(disp->modes, dmode);
}
/* try to select current mode */
EINA_LIST_REVERSE_FOREACH(disp->modes, l, dmode)
{
if (!memcmp(&crtc_mode, &dmode->info, sizeof(crtc_mode)))
current = dmode;
if (dmode->flags & DRM_MODE_TYPE_PREFERRED)
pref = dmode;
best = dmode;
}
if ((!current) && (crtc_mode.clock != 0))
{
current = _ecore_drm2_display_mode_create(&crtc_mode);
if (!current) goto err;
disp->modes = eina_list_append(disp->modes, current);
}
if (current) disp->current_mode = current;
else if (pref) disp->current_mode = pref;
else if (best) disp->current_mode = best;
if (!disp->current_mode) goto err;
disp->current_mode->flags |= DRM_MODE_TYPE_DEFAULT;
return;
err:
EINA_LIST_FREE(disp->modes, dmode)
free(dmode);
}
static void
_ecore_drm2_display_state_fill(Ecore_Drm2_Display *disp)
{
char *name = NULL;
/* get display name */
name = _ecore_drm2_display_name_get(disp->conn);
disp->name = eina_stringshare_add(name);
free(name);
disp->make = eina_stringshare_add("unknown");
disp->model = eina_stringshare_add("unknown");
disp->serial = eina_stringshare_add("unknown");
/* get edid and parse */
_ecore_drm2_display_edid_get(disp);
/* get physical dimensions */
disp->pw = disp->conn->drmConn->mmWidth;
disp->ph = disp->conn->drmConn->mmHeight;
/* get subpixel */
switch (disp->conn->drmConn->subpixel)
{
case DRM_MODE_SUBPIXEL_NONE:
disp->subpixel = 1;
break;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
disp->subpixel = 2;
break;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
disp->subpixel = 3;
break;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
disp->subpixel = 4;
break;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
disp->subpixel = 5;
break;
case DRM_MODE_SUBPIXEL_UNKNOWN:
default:
disp->subpixel = 0;
break;
}
/* get backlight values */
_ecore_drm2_display_backlight_get(disp);
/* get available display modes */
_ecore_drm2_display_modes_get(disp);
/* get gamma from crtc */
disp->gamma = disp->crtc->drmCrtc->gamma_size;
/* get connected state */
disp->connected = (disp->conn->drmConn->connection == DRM_MODE_CONNECTED);
/* send message to thread for debug printing display state */
_ecore_drm2_display_state_thread_send(ECORE_DRM2_THREAD_CODE_DEBUG);
}
static void
_ecore_drm2_display_state_thread(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Ecore_Drm2_Display *disp;
Thread_Msg *msg;
void *ref;
disp = data;
eina_thread_name_set(eina_thread_self(), "Ecore-drm2-display");
while (!ecore_thread_check(thread))
{
msg = eina_thread_queue_wait(thq, &ref);
if (msg)
{
switch (msg->code)
{
case ECORE_DRM2_THREAD_CODE_FILL:
_ecore_drm2_display_state_fill(disp);
break;
case ECORE_DRM2_THREAD_CODE_DEBUG:
_ecore_drm2_display_state_debug(disp);
break;
default:
break;
}
eina_thread_queue_wait_done(thq, ref);
}
}
}
static void
_ecore_drm2_display_state_thread_notify(void *data EINA_UNUSED, Ecore_Thread *thread EINA_UNUSED, void *msg)
{
free(msg);
}
Eina_Bool
_ecore_drm2_displays_create(Ecore_Drm2_Device *dev)
{
Ecore_Drm2_Display *disp;
Ecore_Drm2_Connector *c;
Ecore_Drm2_Crtc *crtc;
Eina_List *l = NULL, *ll = NULL;
/* go through list of connectors and create displays */
EINA_LIST_FOREACH(dev->conns, l, c)
{
drmModeEncoder *encoder;
drmModeCrtc *dcrtc;
/* try to get the encoder from drm */
encoder = sym_drmModeGetEncoder(dev->fd, c->drmConn->encoder_id);
if (!encoder) continue;
/* try to get the crtc from drm */
dcrtc = sym_drmModeGetCrtc(dev->fd, encoder->crtc_id);
if (!dcrtc) goto cont;
/* try to allocate space for new display */
disp = calloc(1, sizeof(Ecore_Drm2_Display));
if (!disp)
{
WRN("Could not allocate space for Display");
sym_drmModeFreeCrtc(dcrtc);
goto cont;
}
/* try to find crtc matching dcrtc->crtc_id and assign to display */
EINA_LIST_FOREACH(dev->crtcs, ll, crtc)
{
if (crtc->id == dcrtc->crtc_id)
{
disp->crtc = crtc;
break;
}
}
sym_drmModeFreeCrtc(dcrtc);
disp->fd = dev->fd;
disp->conn = c;
/* append this display to the list */
dev->displays = eina_list_append(dev->displays, disp);
disp->thread =
ecore_thread_feedback_run(_ecore_drm2_display_state_thread,
_ecore_drm2_display_state_thread_notify,
NULL, NULL, disp, EINA_TRUE);
cont:
sym_drmModeFreeEncoder(encoder);
}
return EINA_TRUE;
}
void
_ecore_drm2_displays_destroy(Ecore_Drm2_Device *dev)
{
Ecore_Drm2_Display *disp;
EINA_LIST_FREE(dev->displays, disp)
{
if (disp->thread) ecore_thread_cancel(disp->thread);
eina_stringshare_del(disp->serial);
eina_stringshare_del(disp->model);
eina_stringshare_del(disp->make);
eina_stringshare_del(disp->name);
free(disp);
}
eina_thread_queue_free(thq);
thq = NULL;
}
EAPI void
ecore_drm2_display_mode_set(Ecore_Drm2_Display *disp, Ecore_Drm2_Display_Mode *mode, int x, int y)
{
EINA_SAFETY_ON_NULL_RETURN(disp);
EINA_SAFETY_ON_NULL_RETURN(mode);
EINA_SAFETY_ON_NULL_RETURN(disp->crtc);
_ecore_drm2_crtc_mode_set(disp->crtc, mode, x, y);
}