structmutexmutex;/* for state below and previous_mode */ enum drm_connector_force force; /* mutex-protected force state */ structdrm_connector *curr_conn;/* current connector (only valid when !disabled) */ bool disabled; /* DRM has disabled our bridge */ bool bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */
/** * struct rockchip_hdmi_chip_data - splite the grf setting of kind of chips * @lcdsel_grf_reg: grf register offset of lcdc select * @lcdsel_big: reg value of selecting vop big for HDMI * @lcdsel_lit: reg value of selecting vop little for HDMI */ structrockchip_hdmi_chip_data { int lcdsel_grf_reg; u32 lcdsel_big; u32 lcdsel_lit; };
/* * If we failed to find the CRTC(s) which this encoder is * supposed to be connected to, it's because the CRTC has * not been registered yet. Defer probing, and hope that * the required CRTC is added later. */ if (encoder->possible_crtcs == 0) return -EPROBE_DEFER;
// 解析hdmi设备节点,比如clocks 、rockchip,grf、avdd-0v9、avdd-1v8等属性; ret = rockchip_hdmi_parse_dt(hdmi); if (ret) { if (ret != -EPROBE_DEFER) DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n"); return ret; }
// 查找并获取一个可选的 PHY(物理层设备)的引用 hdmi->phy = devm_phy_optional_get(dev, "hdmi"); if (IS_ERR(hdmi->phy)) { ret = PTR_ERR(hdmi->phy); if (ret != -EPROBE_DEFER) DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n"); return ret; }
// 使能AVDD_0V9电源 ret = regulator_enable(hdmi->avdd_0v9); if (ret) { DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret); goto err_avdd_0v9; }
// 使能AVDD_1V8电源 ret = regulator_enable(hdmi->avdd_1v8); if (ret) { DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret); goto err_avdd_1v8; }
// 准备和使能时钟 ret = clk_prepare_enable(hdmi->ref_clk); if (ret) { DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n", ret); goto err_clk; }
// 初始化HDMI接口 hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); /* * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), * which would have called the encoder cleanup. Do it manually. */ if (IS_ERR(hdmi->hdmi)) { ret = PTR_ERR(hdmi->hdmi); goto err_bind; }
/** * drm_of_find_possible_crtcs - find the possible CRTCs for an encoder port * @dev: DRM device * @port: encoder port to scan for endpoints * * Scan all endpoints attached to a port, locate their attached CRTCs, * and generate the DRM mask of CRTCs which may be attached to this * encoder. * * See Documentation/devicetree/bindings/graph.txt for the bindings. */ uint32_tdrm_of_find_possible_crtcs(struct drm_device *dev, struct device_node *port)// hdmi设备节点 { structdevice_node *remote_port, *ep; uint32_t possible_crtcs = 0;
/** * DOC: overview * * A set of helper functions to aid DRM drivers in parsing standard DT * properties. */
/** * drm_of_crtc_port_mask - find the mask of a registered CRTC by port OF node * @dev: DRM device * @port: port OF node * * Given a port OF node, return the possible mask of the corresponding * CRTC within a device's list of CRTCs. Returns zero if not found. */ uint32_tdrm_of_crtc_port_mask(struct drm_device *dev, struct device_node *port)// vopb_out_hdmi设备节点 { unsignedint index = 0; structdrm_crtc *tmp;
/* * Get the endpoint id of the remote endpoint of the given encoder. This * information is used by the VOP2 driver to identify the encoder. * * @rkencoder: The encoder to get the remote endpoint id from * @np: The encoder device node * @port: The number of the port leading to the VOP2 * @reg: The endpoint number leading to the VOP2 */ introckchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rkencoder, struct device_node *np, int port, int reg) { structof_endpointep; structdevice_node *en, *ren; int ret;
// 通过遍历父设备节点的所有子节点(端点节点)来查找符合指定port=0和reg=0的端点节点,这里返回的是hdmi_in_vopb设备节点 en = of_graph_get_endpoint_by_regs(np, port, reg); if (!en) return -ENOENT;
// 获取en设备节点remote-endpoin属性指定的设备节点,即vopb_out_hdmi设备节点 ren = of_graph_get_remote_endpoint(en); if (!ren) return -ENOENT;
// 解析vopb_out_hdmi设备节点的属性,并将解析结果存储到ep ret = of_graph_parse_endpoint(ren, &ep); if (ret) return ret; // 由于vopb_out_hdmi设备节点的reg属性=2,所以此处赋值为2 rkencoder->crtc_endpoint_id = ep.id;
/** * of_graph_get_endpoint_by_regs() - get endpoint node of specific identifiers * @parent: pointer to the parent device node * @port_reg: identifier (value of reg property) of the parent port node * @reg: identifier (value of reg property) of the endpoint node * * Return: An 'endpoint' node pointer which is identified by reg and at the same * is the child of a port node identified by port_reg. reg and port_reg are * ignored when they are -1. Use of_node_put() on the pointer when done. */ struct device_node *of_graph_get_endpoint_by_regs( const struct device_node *parent, int port_reg, int reg) { structof_endpointendpoint; structdevice_node *node = NULL; // 遍历parent结点下的每个endpoint结点 for_each_endpoint_of_node(parent, node) { // 解析端点的信息,并将结果存储在endpoint of_graph_parse_endpoint(node, &endpoint); // 对比传入的port_reg和reg参数与当前端点节点的属性值,如果匹配则返回该端点节点的指针 if (((port_reg == -1) || (endpoint.port == port_reg)) && ((reg == -1) || (endpoint.id == reg))) return node; }
/** * of_graph_parse_endpoint() - parse common endpoint node properties * @node: pointer to endpoint device_node * @endpoint: pointer to the OF endpoint data structure * * The caller should hold a reference to @node. */ intof_graph_parse_endpoint(const struct device_node *node, struct of_endpoint *endpoint) { // endpoint的父结点是port结点 structdevice_node *port_node = of_get_parent(node);
WARN_ONCE(!port_node, "%s(): endpoint %pOF has no parent node\n", __func__, node);
// 填充0 memset(endpoint, 0, sizeof(*endpoint));
// 设置endpoint所属的port节点 endpoint->local_node = node; /* * It doesn't matter whether the two calls below succeed. * If they don't then the default value 0 is used. * port结点下的reg属性值是endpoint->port值 * endpoint节点reg属性值是endpoint->id值 */ of_property_read_u32(port_node, "reg", &endpoint->port); of_property_read_u32(node, "reg", &endpoint->id);
/** * devm_regulator_get - Resource managed regulator_get() * @dev: device to supply * @id: supply name or regulator ID. * * Managed regulator_get(). Regulators returned from this function are * automatically regulator_put() on driver detach. See regulator_get() for more * information. */ struct regulator *devm_regulator_get(struct device *dev, constchar *id) { return _devm_regulator_get(dev, id, NORMAL_GET); // NORMAL_GET值为0 }
// 0 >= 3 不会进入 if (get_type >= MAX_GET_TYPE) { dev_err(dev, "invalid type %d in %s\n", get_type, __func__); return ERR_PTR(-EINVAL); } // 不会进入 if (id == NULL) { pr_err("get() with no identifier\n"); return ERR_PTR(-EINVAL); }
// 首先通过设备树的方式去查找rdev,如果没有找到,在通过regulator_map_list查找rdev,regulator_map_list在regulator_rdev注册的时候初始化的 rdev = regulator_dev_lookup(dev, id); // 找不到 进入 if (IS_ERR(rdev)) { ret = PTR_ERR(rdev);
/* * If regulator_dev_lookup() fails with error other * than -ENODEV our job here is done, we simply return it. */ if (ret != -ENODEV) return ERR_PTR(ret);
if (!have_full_constraints()) { dev_warn(dev, "incomplete constraints, dummy supplies not allowed\n"); return ERR_PTR(-ENODEV); }
switch (get_type) { case NORMAL_GET: // 进入,返回一个dummy regulator /* * Assume that a regulator is physically present and * enabled, even if it isn't hooked up, and just * provide a dummy. */ dev_warn(dev, "supply %s not found, using dummy regulator\n", id); rdev = dummy_regulator_rdev; get_device(&rdev->dev); break;
case EXCLUSIVE_GET: dev_warn(dev, "dummy supplies not allowed for exclusive requests\n"); fallthrough;
/** * devm_phy_optional_get() - lookup and obtain a reference to an optional phy. * @dev: device that requests this phy * @string: the phy name as given in the dt data or phy device name * for non-dt case * * Gets the phy using phy_get(), and associates a device with it using * devres. On driver detach, release function is invoked on the devres * data, then, devres data is freed. This differs to devm_phy_get() in * that if the phy does not exist, it is not considered an error and * -ENODEV will not be returned. Instead the NULL phy is returned, * which can be passed to all other phy consumer calls. */ struct phy *devm_phy_optional_get(struct device *dev, constchar *string) { // 获取PHY structphy *phy = devm_phy_get(dev, string);
/** * regulator_enable - enable regulator output * @regulator: regulator source * * Request that the regulator be enabled with the regulator output at * the predefined voltage or current value. Calls to regulator_enable() * must be balanced with calls to regulator_disable(). * * NOTE: the output value can be set by other drivers, boot loader or may be * hardwired in the regulator. */ intregulator_enable(struct regulator *regulator) { structregulator_dev *rdev = regulator->rdev; structww_acquire_ctxww_ctx; int ret;
regulator_lock_dependent(rdev, &ww_ctx); ret = _regulator_enable(regulator); regulator_unlock_dependent(rdev, &ww_ctx);
/* * PHY configuration function for the DWC HDMI 3D TX PHY. Based on the available * information the DWC MHL PHY has the same register layout and is thus also * supported by this function. */ staticinthdmi_phy_configure_dwc_hdmi_3d_tx(struct dw_hdmi *hdmi, const struct dw_hdmi_plat_data *pdata, unsignedlong mpixelclock) { conststructdw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg; conststructdw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr; conststructdw_hdmi_phy_config *phy_config = pdata->phy_config;
/* TOFIX Will need 420 specific PHY configuration tables */
/* PLL/MPLL Cfg - always match on final entry */ for (; mpll_config->mpixelclock != ~0UL; mpll_config++) if (mpixelclock <= mpll_config->mpixelclock) break;
for (; curr_ctrl->mpixelclock != ~0UL; curr_ctrl++) if (mpixelclock <= curr_ctrl->mpixelclock) break;
for (; phy_config->mpixelclock != ~0UL; phy_config++) if (mpixelclock <= phy_config->mpixelclock) break;
// 8. 申请中断,中断处理函数设置为dw_hdmi_hardirq,中断线程化的处理函数设置为dw_hdmi_irq ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, dw_hdmi_irq, IRQF_SHARED, dev_name(dev), hdmi); if (ret) goto err_iahb;
/* * To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator * N and cts values before enabling phy */ hdmi_init_clk_regenerator(hdmi); /* If DDC bus is not specified, try to register HDMI I2C bus,不会进入 */ if (!hdmi->ddc) { /* Look for (optional) stuff related to unwedging */ hdmi->pinctrl = devm_pinctrl_get(dev); if (!IS_ERR(hdmi->pinctrl)) { hdmi->unwedge_state = pinctrl_lookup_state(hdmi->pinctrl, "unwedge"); hdmi->default_state = pinctrl_lookup_state(hdmi->pinctrl, "default");
if (IS_ERR(hdmi->default_state) || IS_ERR(hdmi->unwedge_state)) { if (!IS_ERR(hdmi->unwedge_state)) dev_warn(dev, "Unwedge requires default pinctrl\n"); hdmi->default_state = NULL; hdmi->unwedge_state = NULL; } }
// 直接返回 if (!hdmi->plat_data->output_port) return0;
// 通过遍历父设备节点的所有子节点(端点节点)来查找符合指定port_reg=0的端点节点,这里返回的是hdmi_in_vopb设备节点 endpoint = of_graph_get_endpoint_by_regs(hdmi->dev->of_node, hdmi->plat_data->output_port, -1); if (!endpoint) { /* * On platforms whose bindings don't make the output port * mandatory (such as Rockchip) the plat_data->output_port * field isn't set, so it's safe to make this a fatal error. */ dev_err(hdmi->dev, "Missing endpoint in port@%u\n", hdmi->plat_data->output_port); return -ENODEV; }
// 首先获取endpoint设备节点remote-endpoin属性指定的设备节点,即vopb_out_hdmi设备节点,并向上查找父设备节点,直至找到vopb设备节点 remote = of_graph_get_remote_port_parent(endpoint); of_node_put(endpoint); if (!remote) { dev_err(hdmi->dev, "Endpoint in port@%u unconnected\n", hdmi->plat_data->output_port); return -ENODEV; }
// 判断这个设备节点是否处于启用状态 即status = "ok" if (!of_device_is_available(remote)) { dev_err(hdmi->dev, "port@%u remote device is disabled\n", hdmi->plat_data->output_port); of_node_put(remote); return -ENODEV; }
// find the bridge corresponding to the device node in the global bridge list bridge_list hdmi->next_bridge = of_drm_find_bridge(remote); of_node_put(remote); if (!hdmi->next_bridge) return -EPROBE_DEFER;
/** * of_drm_find_bridge - find the bridge corresponding to the device node in * the global bridge list * * @np: device node * * RETURNS: * drm_bridge control struct on success, NULL on failure */ struct drm_bridge *of_drm_find_bridge(struct device_node *np) { structdrm_bridge *bridge;
/* * Reset HDMI DDC I2C master controller and mute I2CM interrupts. * Even if we are using a separate i2c adapter doing this doesn't * hurt. */ dw_hdmi_i2c_init(hdmi);
// 如果指定了,则执行 if (hdmi->phy.ops->setup_hpd) hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); }
/** * drm_bridge_add - add the given bridge to the global bridge list * * @bridge: bridge control structure */ voiddrm_bridge_add(struct drm_bridge *bridge) { mutex_init(&bridge->hpd_mutex);
/** * drm_get_edid - get EDID data, if available * @connector: connector we're probing * @adapter: I2C adapter to use for DDC * * Poke the given I2C channel to grab EDID data if possible. If found, * attach it to the connector. * * Return: Pointer to valid EDID or NULL if we couldn't find any. */ struct edid *drm_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter) { structedid *edid;
if (connector->force == DRM_FORCE_OFF) returnNULL;
if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter)) returnNULL;
/** * drm_add_edid_modes - add modes from EDID data, if available * @connector: connector we're probing * @edid: EDID data * * Add the specified modes to the connector's mode list. Also fills out the * &drm_display_info structure and ELD in @connector with any information which * can be derived from the edid. * * This function is deprecated. Use drm_edid_connector_add_modes() instead. * * Return: The number of modes added or 0 if we couldn't find any. */ intdrm_add_edid_modes(struct drm_connector *connector, struct edid *edid) { structdrm_edid _drm_edid; conststructdrm_edid *drm_edid;
/* * Initializer helper for legacy interfaces, where we have no choice but to * trust edid size. Not for general purpose use. */ staticconst struct drm_edid *drm_edid_legacy_init(struct drm_edid *drm_edid, const struct edid *edid) { if (!edid) returnNULL;
(8-9) ID Manufacture Name : MIT (10-11) ID Product Code : 0000 (12-15) ID Serial Number : N/A (16) Week of Manufacture : 0 (17) Year of Manufacture : 2022
(18) EDID Version Number : 1 (19) EDID Revision Number: 3
(20) Video Input Definition : Digital DFP 1.x Compatible
(21) Maximum Horizontal Image Size: 0 cm (22) Maximum Vertical Image Size : 0 cm (23) Display Gamma : 2.20 (24) Power Management and Supported Feature(s): Standby, Suspend, Active Off/Very Low Power, RGB Color, sRGB, Preferred Timing Mode
(25-34) Color Characteristics Red Chromaticity : Rx = 0.658 Ry = 0.328 Green Chromaticity : Gx = 0.269 Gy = 0.594 Blue Chromaticity : Bx = 0.134 By = 0.120 Default White Point: Wx = 0.313 Wy = 0.329 ......
/* * Digital sink with "DFP 1.x compliant TMDS" according to EDID 1.3? * * For such displays, the DFP spec 1.0, section 3.10 "EDID support" * tells us to assume 8 bpc color depth if the EDID doesn't have * extensions which tell otherwise. */ if (info->bpc == 0 && edid->revision == 3 && edid->input & DRM_EDID_DIGITAL_DFP_1_X) { // 进入 DRM_EDID_DIGITAL_DFP_1_X = 1<<0 info->bpc = 8; drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Assigning DFP sink color depth as %d bpc.\n", connector->base.id, connector->name, info->bpc); }
/* Only defined for 1.4 with digital displays,对于EDID 1.4以下版本跳转到out */ if (edid->revision < 4) goto out;
switch (edid->input & DRM_EDID_DIGITAL_DEPTH_MASK) { case DRM_EDID_DIGITAL_DEPTH_6: info->bpc = 6; break; case DRM_EDID_DIGITAL_DEPTH_8: info->bpc = 8; break; case DRM_EDID_DIGITAL_DEPTH_10: info->bpc = 10; break; case DRM_EDID_DIGITAL_DEPTH_12: info->bpc = 12; break; case DRM_EDID_DIGITAL_DEPTH_14: info->bpc = 14; break; case DRM_EDID_DIGITAL_DEPTH_16: info->bpc = 16; break; case DRM_EDID_DIGITAL_DEPTH_UNDEF: default: info->bpc = 0; break; }
drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Assigning EDID-1.4 digital sink color depth as %d bpc.\n", connector->base.id, connector->name, info->bpc);
if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB444) info->color_formats |= DRM_COLOR_FORMAT_YCBCR444; if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) info->color_formats |= DRM_COLOR_FORMAT_YCBCR422;
// 设置版本号,未设置则进入,cea_rev设置为03H, if (!info->cea_rev) info->cea_rev = edid_ext[1];
if (info->cea_rev != edid_ext[1]) drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] CEA extension version mismatch %u != %u\n", connector->base.id, connector->name, info->cea_rev, edid_ext[1]);
/* The existence of a CTA extension should imply RGB support */ info->color_formats = DRM_COLOR_FORMAT_RGB444; // 是否支持YCbCr 4:4:4 支持 if (edid_ext[3] & EDID_CEA_YCRCB444) info->color_formats |= DRM_COLOR_FORMAT_YCBCR444; // 是否支持YCbCr 4:2:2 支持 if (edid_ext[3] & EDID_CEA_YCRCB422) info->color_formats |= DRM_COLOR_FORMAT_YCBCR422; } drm_edid_iter_end(&edid_iter);
// 第一个Timing Descriptor会进入,其他的不会进入 if (closure->preferred) newmode->type |= DRM_MODE_TYPE_PREFERRED;
/* * Detailed modes are limited to 10kHz pixel clock resolution, * so fix up anything that looks like CEA/HDMI mode, but the clock * is just slightly off. */ fixup_detailed_cea_mode_clock(closure->connector, newmode);
drm_cvt_mode函数用于create a modeline based on the CVT algorithm,这个函数根据hdisplay、vdisplay和vrefresh调用CVT算法来生成Modeline。它基于Graham Loveridge于2003年4月9日编写的VESA(TM) Coordinated Video Timing Generator。
/** * drm_cvt_mode -create a modeline based on the CVT algorithm * @dev: drm device * @hdisplay: hdisplay size * @vdisplay: vdisplay size * @vrefresh: vrefresh rate * @reduced: whether to use reduced blanking * @interlaced: whether to compute an interlaced mode * @margins: whether to add margins (borders) * * This function is called to generate the modeline based on CVT algorithm * according to the hdisplay, vdisplay, vrefresh. * It is based from the VESA(TM) Coordinated Video Timing Generator by * Graham Loveridge April 9, 2003 available at * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls * * And it is copied from xf86CVTmode in xserver/hw/xfree86/modes/xf86cvt.c. * What I have done is to translate it by using integer calculation. * * Returns: * The modeline based on the CVT algorithm stored in a drm_display_mode object. * The display mode object is allocated with drm_mode_create(). Returns NULL * when no mode could be allocated. */ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh, bool reduced, bool interlaced, bool margins) { #define HV_FACTOR 1000 /* 1) top/bottom margin size (% of height) - default: 1.8, */ #define CVT_MARGIN_PERCENTAGE 18 /* 2) character cell horizontal granularity (pixels) - default 8 */ #define CVT_H_GRANULARITY 8 /* 3) Minimum vertical porch (lines) - default 3 */ #define CVT_MIN_V_PORCH 3 /* 4) Minimum number of vertical back porch lines - default 6 */ #define CVT_MIN_V_BPORCH 6 /* Pixel Clock step (kHz) */ #define CVT_CLOCK_STEP 250 structdrm_display_mode *drm_mode; unsignedint vfieldrate, hperiod; int hdisplay_rnd, hmargin, vdisplay_rnd, vmargin, vsync; int interlace; u64 tmp;
if (!hdisplay || !vdisplay) returnNULL;
/* allocate the drm_display_mode structure. If failure, we will * return directly drm_mode = drm_mode_create(dev); if (!drm_mode) return NULL; /* the CVT default refresh rate is 60Hz */ if (!vrefresh) vrefresh = 60;
/* the required field fresh rate */ if (interlaced) vfieldrate = vrefresh * 2; else vfieldrate = vrefresh;
/* * Get standard modes from EDID and add them. Standard modes can be calculated * using the appropriate standard (DMT, GTF, or CVT). Grab them from EDID and * add them to the list. */ staticintadd_standard_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { int i, modes = 0; structdetailed_mode_closureclosure = { .connector = connector, .drm_edid = drm_edid, };
// i<8 循环8次,遍历每一个Standard Timing for (i = 0; i < EDID_STD_TIMINGS; i++) { structdrm_display_mode *newmode; // 解析每一个Standard Timing newmode = drm_mode_std(connector, drm_edid, &drm_edid->edid->standard_timings[i]); if (newmode) { drm_mode_probed_add(connector, newmode); modes++; } }
/* * If this connector already has a mode for this size and refresh * rate (because it came from detailed or CVT info), use that * instead. This way we don't have to guess at interlace or * reduced blanking. 如果此连接器已经具有适合该尺寸和刷新率的模式,则使用该模式 */ list_for_each_entry(m, &connector->probed_modes, head) if (m->hdisplay == hsize && m->vdisplay == vsize && drm_mode_vrefresh(m) == vrefresh_rate) returnNULL; /* HDTV hack, part 2,跳过 */ if (hsize == 1366 && vsize == 768 && vrefresh_rate == 60) { mode = drm_cvt_mode(dev, 1366, 768, vrefresh_rate, 0, 0, false); if (!mode) returnNULL; mode->hdisplay = 1366; mode->hsync_start = mode->hsync_start - 1; mode->hsync_end = mode->hsync_end - 1; return mode; }
/* check whether it can be found in default mode table,支持rb,进入 */ if (drm_monitor_supports_rb(drm_edid)) { // 遍历DMT模式列表,查找与给定参数匹配的显示模式(最后一个参数指明需要减少空白) mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate, true); // NULL if (mode) return mode; } // 遍历DMT模式列表,查找与给定参数匹配的显示模式(最后一个参数指明不需要减少空白) mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate, false); // NULL if (mode) return mode;
/* okay, generate it */ switch (timing_level) { case LEVEL_DMT: break; case LEVEL_GTF: // 进入 create the modeline based on the GTF algorithm,有关GTF算法咱们就深究了 mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0); break; case LEVEL_GTF2: mode = drm_gtf2_mode(dev, drm_edid, hsize, vsize, vrefresh_rate); break; case LEVEL_CVT: // create a modeline based on the CVT algorithm mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0, false); break; } return mode; }
/* * drm_mode_find_dmt - Create a copy of a mode if present in DMT * @dev: Device to duplicate against * @hsize: Mode width * @vsize: Mode height * @fresh: Mode refresh rate * @rb: Mode reduced-blanking-ness * * Walk the DMT mode list looking for a match for the given parameters. * * Return: A newly allocated copy of the mode, or NULL if not found. */ struct drm_display_mode *drm_mode_find_dmt(struct drm_device *dev, int hsize, int vsize, int fresh, bool rb) { int i;
// 遍历drm_dmt_modes数组,该数组保存了一组标准显示模式参数,比如640x350@60Hz、800x600@60Hz、1280x800@60Hz等 for (i = 0; i < ARRAY_SIZE(drm_dmt_modes); i++) { conststructdrm_display_mode *ptr = &drm_dmt_modes[i];
// 行有效像素匹配 1152 if (hsize != ptr->hdisplay) continue; // 帧有效行匹配 864 if (vsize != ptr->vdisplay) continue; // 刷新率匹配 if (fresh != drm_mode_vrefresh(ptr)) continue; // rb匹配 if (rb != mode_is_rb(ptr)) continue; // 拷贝副本 return drm_mode_duplicate(dev, ptr); }
/* * Get established modes from EDID and add them. Each EDID block contains a * bitmap of the supported "established modes" list (defined above). Tease them * out and add them to the global modes list. */ staticintadd_established_modes(struct drm_connector *connector, const struct drm_edid *drm_edid) { structdrm_device *dev = connector->dev; conststructedid *edid = drm_edid->edid; // 转换为unsigned long类型,描述17个通用时序。若支持,则相应的位为1。 unsignedlong est_bits = edid->established_timings.t1 | (edid->established_timings.t2 << 8) | ((edid->established_timings.mfg_rsvd & 0x80) << 9); int i, modes = 0; structdetailed_mode_closureclosure = { .connector = connector, .drm_edid = drm_edid, };
// i<=16 循环17次,遍历每一个Established Timing for (i = 0; i <= EDID_EST_TIMINGS; i++) { // 位为1,表示支持当前Established Timing if (est_bits & (1<<i)) { structdrm_display_mode *newmode; // edid_est_modes数组中定义了这17个Established Timings对应的显示模式,因此这里实际上就是进行拷贝副本 newmode = drm_mode_duplicate(dev, &edid_est_modes[i]); if (newmode) { // 添加到connector的probed_modes链表 drm_mode_probed_add(connector, newmode); modes++; } } }
/* Don't add CTA modes if the CTA extension block is missing */ if (!drm_edid_has_cta_extension(drm_edid)) return0;
/* * Go through all probed modes and create a new mode * with the alternate clock for certain CEA modes. */ list_for_each_entry(mode, &connector->probed_modes, head) { conststructdrm_display_mode *cea_mode = NULL; structdrm_display_mode *newmode; u8 vic = drm_match_cea_mode(mode); unsignedint clock1, clock2;
if (mode->clock != clock1 && mode->clock != clock2) continue;
newmode = drm_mode_duplicate(dev, cea_mode); if (!newmode) continue;
/* Carry over the stereo flags */ newmode->flags |= mode->flags & DRM_MODE_FLAG_3D_MASK;
/* * The current mode could be either variant. Make * sure to pick the "other" clock for the new mode. */ if (mode->clock != clock1) newmode->clock = clock1; else newmode->clock = clock2;