/* * Copyright (c) 2011 Benjamin Franzke * * 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 #include #include #include #include #include #include #include "wfhandle.h" #include "wfdport.h" #include "wfddevice.h" #include "wfdpipeline.h" #include struct wfd_port { drmModeConnectorPtr connector; int modes_count; WFDPortMode *modes; uint32_t possible_crtcs; drmModeModeInfoPtr mode; WFDPortMode mode_handle; struct wfd_pipeline *pipeline; int pipeline_updated; uint32_t choosen_pipeline; uint32_t dpms_enum_id; WFDint power_mode; uint32_t edid_enum_id; float gamma; float gamma_range[2]; }; WFDint wfd_enumerate_ports(struct wfd_device *device, WFDint *port_ids, WFDint port_id_count, WFDint *filter_list) { drmModeResPtr resources = wfd_device_get_resources(device); int count = 0; int i; if (port_ids == NULL) return resources->count_connectors; for (i = 0; i < resources->count_connectors && count < port_id_count; ++i) { port_ids[count] = resources->connectors[i]; count++; } return count; } static void port_modes_destroy(struct wfd_device *device, struct wfd_port *port) { int i; for (i = 0; i < port->modes_count; ++i) wf_handle_destroy(port->modes[i]); free(port->modes); port->modes = NULL; } void wfd_port_destroy(struct wfd_device *device, struct wfd_port *port) { if (port->choosen_pipeline) { uint32_t *crtcs = wfd_device_get_choosen_crtcs(device); *crtcs &= ~(1 << port->choosen_pipeline); } port_modes_destroy(device, port); drmModeFreeConnector(port->connector); free(port); } static int port_update_modes(struct wfd_device *device, struct wfd_port *port) { int i; if (port->connector->connection != DRM_MODE_CONNECTED) return 0; if (port->modes) port_modes_destroy(device, port); port->modes = calloc(port->connector->count_modes, sizeof(WFDPortMode)); if (port->modes == NULL) return -1; port->modes_count = port->connector->count_modes; for (i = 0; i < port->modes_count; ++i) { port->modes[i] = wf_handle_create(&port->connector->modes[i], MODE_HANDLE); } return 0; } struct wfd_port * wfd_port_create(struct wfd_device *device, WFDint port_id, const WFDint *attrib_list) { struct wfd_port *port; int drm_fd = wfd_device_get_fd(device); drmModeEncoderPtr encoder; drmModePropertyPtr prop; int i; port = calloc(1, sizeof *port); if (port == NULL) return NULL; port->power_mode = WFD_POWER_MODE_OFF; port->connector = drmModeGetConnector(drm_fd, port_id); if (port->connector == NULL) { free(port); return NULL; } if (port_update_modes(device, port) < 0) { free(port->connector); free(port); return NULL; } /* intersect encoder's possible_crtc to be sure the crtc we choose * is compatible with all encoders the kernel might choose. * Radeon DDX does this as well, all other KMS based DDX * use the first encoder's possible_crtcs * */ port->possible_crtcs = ~0; for (i = 0; i < port->connector->count_encoders; ++i) { encoder = drmModeGetEncoder(drm_fd, port->connector->encoders[i]); port->possible_crtcs &= encoder->possible_crtcs; drmModeFreeEncoder(encoder); } port->edid_enum_id = 0; port->dpms_enum_id = 0; for (i = 0; i < port->connector->count_props; ++i) { prop = drmModeGetProperty(drm_fd, port->connector->props[i]); if (!prop) continue; if (prop->flags && DRM_MODE_PROP_ENUM && strcmp(prop->name, "DPMS") == 0) { port->dpms_enum_id = port->connector->props[i]; } else if (prop->flags && DRM_MODE_PROP_BLOB && strcmp(prop->name, "EDID") == 0) { port->edid_enum_id = port->connector->props[i]; } drmModeFreeProperty(prop); } port->mode = NULL; port->mode_handle = WFD_INVALID_HANDLE; port->gamma = 1.0; port->gamma_range[0] = 0.1; port->gamma_range[1] = 10.0; return port; } static uint32_t wfd_port_choose_pipeline(struct wfd_device *device, struct wfd_port *port) { drmModeResPtr resources = wfd_device_get_resources(device); int i; uint32_t *crtcs; if (port->choosen_pipeline) return port->choosen_pipeline; crtcs = wfd_device_get_choosen_crtcs(device); for (i = 0; i < resources->count_crtcs; ++i) { if (port->possible_crtcs & (1 << i) && !(*crtcs & (1 << resources->crtcs[i]))) { *crtcs |= 1 << resources->crtcs[i]; port->choosen_pipeline = resources->crtcs[i]; break; } } return port->choosen_pipeline; } static WFDint wfd_port_get_type(struct wfd_device *device, struct wfd_port *port) { switch (port->connector->connector_type){ case DRM_MODE_CONNECTOR_DVII: case DRM_MODE_CONNECTOR_DVID: case DRM_MODE_CONNECTOR_DVIA: return WFD_PORT_TYPE_DVI; case DRM_MODE_CONNECTOR_Composite: return WFD_PORT_TYPE_COMPOSITE; /* FIXME: request subconnector info for TV */ case DRM_MODE_CONNECTOR_TV: case DRM_MODE_CONNECTOR_SVIDEO: return WFD_PORT_TYPE_SVIDEO; case DRM_MODE_CONNECTOR_Component: return WFD_PORT_TYPE_COMPONENT_YPbPr; case DRM_MODE_CONNECTOR_DisplayPort: return WFD_PORT_TYPE_DISPLAYPORT; case DRM_MODE_CONNECTOR_HDMIA: case DRM_MODE_CONNECTOR_HDMIB: return WFD_PORT_TYPE_HDMI; case DRM_MODE_CONNECTOR_LVDS: case DRM_MODE_CONNECTOR_eDP: return WFD_PORT_TYPE_INTERNAL; /* FIXME: find appriate WFD_PORT types for vga and 9PinDIN */ case DRM_MODE_CONNECTOR_Unknown: case DRM_MODE_CONNECTOR_9PinDIN: case DRM_MODE_CONNECTOR_VGA: default: return WFD_PORT_TYPE_OTHER; }; } WFDint wfd_port_get_attribi(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute) { switch (attribute) { case WFD_PORT_ID: return port->connector->connector_id; case WFD_PORT_TYPE: return wfd_port_get_type(device, port); case WFD_PORT_DETACHABLE: switch (port->connector->connector_type) { case DRM_MODE_CONNECTOR_LVDS: case DRM_MODE_CONNECTOR_eDP: return WFD_FALSE; default: return WFD_TRUE; } case WFD_PORT_ATTACHED: return port->connector->connection == DRM_MODE_CONNECTED; case WFD_PORT_FILL_PORT_AREA: return WFD_TRUE; /* no effect as FILL_PORT_AREA = true */ case WFD_PORT_BACKGROUND_COLOR: return 0; case WFD_PORT_FLIP: case WFD_PORT_MIRROR: return WFD_FALSE; case WFD_PORT_ROTATION: return 0; case WFD_PORT_POWER_MODE: return port->power_mode; case WFD_PORT_PARTIAL_REFRESH_SUPPORT: return WFD_PARTIAL_REFRESH_NONE; case WFD_PORT_PARTIAL_REFRESH_ENABLE: return WFD_FALSE; case WFD_PORT_PIPELINE_ID_COUNT: return wfd_port_choose_pipeline(device, port) != 0 ? 1 : 0; case WFD_PORT_PROTECTION_ENABLE: return WFD_FALSE; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } return 0; } WFDfloat wfd_port_get_attribf(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute) { switch (attribute) { case WFD_PORT_GAMMA: return port->gamma; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } return 0.0; } static void wfd_port_get_bindable_pipelines(struct wfd_device *device, struct wfd_port *port, int count, int *value) { uint32_t pipeline; if (count != 1) return; pipeline = wfd_port_choose_pipeline(device, port); if (pipeline != 0) *value = pipeline; } void wfd_port_get_attribiv(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute, WFDint count, WFDint *value) { switch (attribute) { case WFD_PORT_NATIVE_RESOLUTION: if (count != 2) return; if (port->connector->count_modes > 0) { value[0] = port->connector->modes[0].hdisplay; value[1] = port->connector->modes[0].vdisplay; } else { value[0] = value[1] = 0; } break; case WFD_PORT_BACKGROUND_COLOR: if (count != 3) return; value[0] = 0; break; case WFD_PORT_PARTIAL_REFRESH_MAXIMUM: if (count != 2) return; value[0] = value[1] = 0; break; case WFD_PORT_PARTIAL_REFRESH_RECTANGLE: if (count != 4) return; value[0] = value[1] = value[2] = value[3] = 0; break; case WFD_PORT_BINDABLE_PIPELINE_IDS: wfd_port_get_bindable_pipelines(device, port, count, value); break; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } } void wfd_port_get_attribfv(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute, WFDint count, WFDfloat *value) { switch (attribute) { case WFD_PORT_PHYSICAL_SIZE: if (count != 2) return; value[0] = (WFDfloat) port->connector->mmWidth; value[1] = (WFDfloat) port->connector->mmHeight; break; case WFD_PORT_BACKGROUND_COLOR: if (count != 3) return; value[0] = value[1] = value[2] = 0.0; break; case WFD_PORT_GAMMA_RANGE: if (count != 2) return; value[0] = port->gamma_range[0]; value[1] = port->gamma_range[1]; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } } void wfd_port_set_attribi(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute, WFDint value) { switch (attribute) { case WFD_PORT_POWER_MODE: switch (value) { case WFD_POWER_MODE_ON: case WFD_POWER_MODE_LIMITED_USE: case WFD_POWER_MODE_SUSPEND: case WFD_POWER_MODE_OFF: break; default: return; } port->power_mode = value; break; case WFD_PORT_PARTIAL_REFRESH_ENABLE: if (value == WFD_TRUE) wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); break; case WFD_PORT_PROTECTION_ENABLE: if (value == WFD_TRUE) wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); break; /* ignored by WFD_PORT_MODE_{FLIP_MIRROR,ROTATION}_SUPPORT */ case WFD_PORT_FLIP: case WFD_PORT_MIRROR: case WFD_PORT_ROTATION: /* ignored as FILL_PORT_AREA = true */ case WFD_PORT_BACKGROUND_COLOR: break; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } } void wfd_port_set_attribf(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute, WFDfloat value) { switch (attribute) { case WFD_PORT_GAMMA: if (value < port->gamma_range[0] || value > port->gamma_range[1]) { wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return; } port->gamma = value; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } } void wfd_port_set_attribiv(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute, WFDint count, const WFDint *value) { switch (attribute) { case WFD_PORT_PARTIAL_REFRESH_RECTANGLE: if (count != 4) return; if (value[2] > 0 || value[3] > 0) wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); break; /* ignored as FILL_PORT_AREA = true */ case WFD_PORT_BACKGROUND_COLOR: break; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } } void wfd_port_set_attribfv(struct wfd_device *device, struct wfd_port *port, WFDPortConfigAttrib attribute, WFDint count, const WFDfloat *value) { switch (attribute) { /* ignored as FILL_PORT_AREA = true */ case WFD_PORT_BACKGROUND_COLOR: break; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } } WFDint wfd_port_get_modes(struct wfd_device *device, struct wfd_port *port, WFDPortMode *modes, WFDint modes_count) { int i; int count_modes = port->modes_count; int count; if (port->connector->connection != DRM_MODE_CONNECTED) return 0; if (modes == NULL) return count_modes; for (i = 0, count = 0; i < count_modes && count < modes_count; ++i) { modes[count++] = port->modes[i]; } return count; } void wfd_port_set_mode(struct wfd_device *device, struct wfd_port *port, WFDPortMode mode_handle) { drmModeModeInfoPtr mode; mode = wf_handle_get_object(mode_handle, MODE_HANDLE); if (mode == NULL) return; port->mode = mode; port->mode_handle = mode_handle; } WFDPortMode wfd_port_get_current_mode(struct wfd_device *device, struct wfd_port *port) { if (port->mode_handle == WFD_INVALID_HANDLE) wfd_device_set_error(device, WFD_ERROR_NOT_SUPPORTED); return port->mode_handle; } int wfd_port_mode_get_attribi(struct wfd_device *device, struct wfd_port *port, WFDPortMode mode_handle, WFDPortModeAttrib attrib) { drmModeModeInfoPtr mode; mode = wf_handle_get_object(mode_handle, MODE_HANDLE); if (mode == NULL) return 0; switch (attrib) { case WFD_PORT_MODE_WIDTH: return mode->hdisplay; case WFD_PORT_MODE_HEIGHT: return mode->vdisplay; case WFD_PORT_MODE_REFRESH_RATE: return mode->vrefresh; case WFD_PORT_MODE_FLIP_MIRROR_SUPPORT: return WFD_FALSE; case WFD_PORT_MODE_ROTATION_SUPPORT: return WFD_ROTATION_SUPPORT_NONE; case WFD_PORT_MODE_INTERLACED: return !!(mode->flags & DRM_MODE_FLAG_INTERLACE); default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } return 0; } WFDfloat wfd_port_mode_get_attribf(struct wfd_device *device, struct wfd_port *port, WFDPortMode mode_handle, WFDPortModeAttrib attrib) { drmModeModeInfoPtr mode; mode = wf_handle_get_object(mode_handle, MODE_HANDLE); if (mode == NULL) return 0; switch (attrib) { case WFD_PORT_MODE_REFRESH_RATE: return (WFDfloat) mode->vrefresh; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); break; } return 0.0; } int wfd_port_get_display_data_formats(struct wfd_device *device, struct wfd_port *port, WFDDisplayDataFormat *format, WFDint format_count) { if (format == NULL) return 1; if (format_count >= 1 && port->edid_enum_id != 0) { format[0] = WFD_DISPLAY_DATA_FORMAT_EDID_V1; return 1; } return 0; } int wfd_port_get_display_data(struct wfd_device *device, struct wfd_port *port, WFDDisplayDataFormat format, WFDuint8 *data, WFDint data_count) { int fd = wfd_device_get_fd(device); drmModePropertyBlobPtr edid_blob; int num; if (port->edid_enum_id == 0) { wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return 0; } switch (format) { case WFD_DISPLAY_DATA_FORMAT_EDID_V1: break; case WFD_DISPLAY_DATA_FORMAT_EDID_V2: case WFD_DISPLAY_DATA_FORMAT_DISPLAYID: default: wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return 0; } edid_blob = drmModeGetPropertyBlob(fd, port->edid_enum_id); num = edid_blob->length; if (data != NULL) { if (data_count < num) num = data_count; memcpy(data, edid_blob->data, num); } drmModeFreePropertyBlob(edid_blob); return num; } static inline double dmin(double a, double b) { return a < b ? a : b; } static void wfd_port_commit_gamma(struct wfd_device *device, struct wfd_port *port) { int fd = wfd_device_get_fd(device); uint16_t *gamma; uint16_t *red, *green, *blue; int i, size = 256; gamma = malloc(3 * size * sizeof *gamma); if (gamma == NULL) return; for (i = 0; i < size; ++i) { if (port->gamma == 1.0) { gamma[i] = (i << 8) + i; continue; } gamma[i] = dmin(pow((double)i/(double)(size-1), port->gamma), 1.0) * 65535.0; } red = green = blue = gamma; drmModeCrtcSetGamma(fd, port->connector->connector_id, size, red, green, blue); free(gamma); } int wfd_port_commit(struct wfd_device *device, struct wfd_port *port) { int fd = wfd_device_get_fd(device); int ret; int dest_rect[4]; uint64_t power_mode; wfd_pipeline_commit(device, port->pipeline); if (port->pipeline_updated == 0) return 0; assert(port->mode != NULL); /* FIXME: implicit image scale needed if dest_rect width and height * != source_rect width and height? */ wfd_pipeline_get_attribiv(device, port->pipeline, WFD_PIPELINE_DESTINATION_RECTANGLE, 4, dest_rect); ret = drmModeSetCrtc(fd, wfd_pipeline_get_crtc_id(port->pipeline), wfd_pipeline_get_fb_id(port->pipeline), dest_rect[0], dest_rect[1], &port->connector->connector_id, 1, port->mode); if (ret) { fprintf(stderr, "drmModeSetCrtc: %d %m\n", ret); return ret; } switch (port->power_mode) { case WFD_POWER_MODE_ON: power_mode = DRM_MODE_DPMS_ON; break; case WFD_POWER_MODE_LIMITED_USE: power_mode = DRM_MODE_DPMS_STANDBY; break; case WFD_POWER_MODE_SUSPEND: power_mode = DRM_MODE_DPMS_SUSPEND; break; case WFD_POWER_MODE_OFF: power_mode = DRM_MODE_DPMS_OFF; break; default: power_mode = WFD_POWER_MODE_ON; break; } ret = drmModeConnectorSetProperty(fd, port->connector->connector_id, port->dpms_enum_id, power_mode); if (ret) { fprintf(stderr, "drmModeconnectorSetProperty: %d %m\n", ret); return ret; } wfd_port_commit_gamma(device, port); return ret; } int wfd_port_bind_pipeline(struct wfd_device *device, struct wfd_port *port, struct wfd_pipeline *pipeline) { port->pipeline = pipeline; wfd_pipeline_set_port(device, pipeline, port); port->pipeline_updated = 1; return 0; }