程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - DRM encoder、bridge、connector基础知识

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2023.04
linux6.3
----------------------------------------------------------------------------------------------------------------------------

一、encoder数据结构

1.1 struct drm_encoder

linux内核使用struct drm_encoder来表示一个编码器,将crtc输出的图像信号转换成一定格式的数字信号,如HDMIDisplayPortMIPI等;定义在include/drm/drm_encoder.h:

/**
 * struct drm_encoder - central DRM encoder structure
 * @dev: parent DRM device
 * @head: list management
 * @base: base KMS object
 * @name: human readable name, can be overwritten by the driver
 * @funcs: control functions, can be NULL for simple managed encoders
 * @helper_private: mid-layer private data
 *
 * CRTCs drive pixels to encoders, which convert them into signals
 * appropriate for a given connector or set of connectors.
 */
struct drm_encoder {
        struct drm_device *dev;
        struct list_head head;

        struct drm_mode_object base;
        char *name;
        /**
         * @encoder_type:
         *
         * One of the DRM_MODE_ENCODER_<foo> types in drm_mode.h. The following
         * encoder types are defined thus far:
         *
         * - DRM_MODE_ENCODER_DAC for VGA and analog on DVI-I/DVI-A.
         *
         * - DRM_MODE_ENCODER_TMDS for DVI, HDMI and (embedded) DisplayPort.
         *
         * - DRM_MODE_ENCODER_LVDS for display panels, or in general any panel
         *   with a proprietary parallel connector.
         *
         * - DRM_MODE_ENCODER_TVDAC for TV output (Composite, S-Video,
         *   Component, SCART).
         *
         * - DRM_MODE_ENCODER_VIRTUAL for virtual machine displays
         *
         * - DRM_MODE_ENCODER_DSI for panels connected using the DSI serial bus.
         *
         * - DRM_MODE_ENCODER_DPI for panels connected using the DPI parallel
         *   bus.
         *
         * - DRM_MODE_ENCODER_DPMST for special fake encoders used to allow
         *   mutliple DP MST streams to share one physical encoder.
         */
        int encoder_type;

        /**
         * @index: Position inside the mode_config.list, can be used as an array
         * index. It is invariant over the lifetime of the encoder.
         */
        unsigned index;
        /**
         * @possible_crtcs: Bitmask of potential CRTC bindings, using
         * drm_crtc_index() as the index into the bitfield. The driver must set
         * the bits for all &drm_crtc objects this encoder can be connected to
         * before calling drm_dev_register().
         *
         * You will get a WARN if you get this wrong in the driver.
         *
         * Note that since CRTC objects can't be hotplugged the assigned indices
         * are stable and hence known before registering all objects.
         */
        uint32_t possible_crtcs;

        /**
         * @possible_clones: Bitmask of potential sibling encoders for cloning,
         * using drm_encoder_index() as the index into the bitfield. The driver
         * must set the bits for all &drm_encoder objects which can clone a
         * &drm_crtc together with this encoder before calling
         * drm_dev_register(). Drivers should set the bit representing the
         * encoder itself, too. Cloning bits should be set such that when two
         * encoders can be used in a cloned configuration, they both should have
         * each another bits set.
         *
         * As an exception to the above rule if the driver doesn't implement
         * any cloning it can leave @possible_clones set to 0. The core will
         * automagically fix this up by setting the bit for the encoder itself.
         *
         * You will get a WARN if you get this wrong in the driver.
         *
         * Note that since encoder objects can't be hotplugged the assigned indices
         * are stable and hence known before registering all objects.
         */
        uint32_t possible_clones;

        /**
         * @crtc: Currently bound CRTC, only really meaningful for non-atomic
         * drivers.  Atomic drivers should instead check
         * &drm_connector_state.crtc.
         */
        struct drm_crtc *crtc;

        /**
         * @bridge_chain: Bridges attached to this encoder. Drivers shall not
         * access this field directly.
         */
        struct list_head bridge_chain;

        const struct drm_encoder_funcs *funcs;
        const struct drm_encoder_helper_funcs *helper_private;
};

1.2 操作函数

1.2.1 struct drm_encoder_funcs

struct drm_encoder_funcs用于描述encoder的控制函数,定义在include/drm/drm_encoder.h

/**
 * struct drm_encoder_funcs - encoder controls
 *
 * Encoders sit between CRTCs and connectors.
 */
struct drm_encoder_funcs {
        /**
         * @reset:
         *
         * Reset encoder hardware and software state to off. This function isn't
         * called by the core directly, only through drm_mode_config_reset().
         * It's not a helper hook only for historical reasons.
         */
        void (*reset)(struct drm_encoder *encoder);

        /**
         * @destroy:
         *
         * Clean up encoder resources. This is only called at driver unload time
         * through drm_mode_config_cleanup() since an encoder cannot be
         * hotplugged in DRM.
         */
        void (*destroy)(struct drm_encoder *encoder);

        /**
         * @late_register:
         *
         * This optional hook can be used to register additional userspace
         * interfaces attached to the encoder like debugfs interfaces.
         * It is called late in the driver load sequence from drm_dev_register().
         * Everything added from this callback should be unregistered in
         * the early_unregister callback.
         *
         * Returns:
         *
         * 0 on success, or a negative error code on failure.
         */
        int (*late_register)(struct drm_encoder *encoder);

        /**
         * @early_unregister:
         *
         * This optional hook should be used to unregister the additional
         * userspace interfaces attached to the encoder from
         * @late_register. It is called from drm_dev_unregister(),
         * early in the driver unload sequence to disable userspace access
         * before data structures are torndown.
         */
        void (*early_unregister)(struct drm_encoder *encoder);
};
1.2.2 struct drm_encoder_helper_funcs

struct drm_encoder_helper_funcs定义了一些常用的encoder辅助操作函数,定义在include/drm/drm_modeset_helper_vtables.h

点击查看代码
/**
 * struct drm_encoder_helper_funcs - helper operations for encoders
 *
 * These hooks are used by the legacy CRTC helpers, the transitional plane
 * helpers and the new atomic modesetting helpers.
 */
struct drm_encoder_helper_funcs {
        /**
         * @dpms:
         *
         * Callback to control power levels on the encoder.  If the mode passed in
         * is unsupported, the provider must use the next lowest power level.
         * This is used by the legacy encoder helpers to implement DPMS
         * functionality in drm_helper_connector_dpms().
         *
         * This callback is also used to disable an encoder by calling it with
         * DRM_MODE_DPMS_OFF if the @disable hook isn't used.
         *
         * This callback is used by the legacy CRTC helpers.  Atomic helpers
         * also support using this hook for enabling and disabling an encoder to
         * facilitate transitions to atomic, but it is deprecated. Instead
         * @enable and @disable should be used.
         */
        void (*dpms)(struct drm_encoder *encoder, int mode);

        /**
         * @mode_valid:
         *
         * This callback is used to check if a specific mode is valid in this
         * encoder. This should be implemented if the encoder has some sort
         * of restriction in the modes it can display. For example, a given
         * encoder may be responsible to set a clock value. If the clock can
         * not produce all the values for the available modes then this callback
         * can be used to restrict the number of modes to only the ones that
         * can be displayed.
         *
         * This hook is used by the probe helpers to filter the mode list in
         * drm_helper_probe_single_connector_modes(), and it is used by the
         * atomic helpers to validate modes supplied by userspace in
         * drm_atomic_helper_check_modeset().
         *
         * This function is optional.
         *
         * NOTE:
         *
         * Since this function is both called from the check phase of an atomic
         * commit, and the mode validation in the probe paths it is not allowed
         * to look at anything else but the passed-in mode, and validate it
         * against configuration-invariant hardward constraints. Any further
         * limits which depend upon the configuration can only be checked in
         * @mode_fixup or @atomic_check.
         *
         * RETURNS:
         *
         * drm_mode_status Enum
         */
        enum drm_mode_status (*mode_valid)(struct drm_encoder *crtc,
                                           const struct drm_display_mode *mode);

        /**
         * @mode_fixup:
         *
         * This callback is used to validate and adjust a mode. The parameter
         * mode is the display mode that should be fed to the next element in
         * the display chain, either the final &drm_connector or a &drm_bridge.
         * The parameter adjusted_mode is the input mode the encoder requires. It
         * can be modified by this callback and does not need to match mode. See
         * also &drm_crtc_state.adjusted_mode for more details.
         *
         * This function is used by both legacy CRTC helpers and atomic helpers.
         * This hook is optional.
         *
         * NOTE:
         *
         * This function is called in the check phase of atomic modesets, which
         * can be aborted for any reason (including on userspace's request to
         * just check whether a configuration would be possible). Atomic drivers
         * MUST NOT touch any persistent state (hardware or software) or data
         * structures except the passed in adjusted_mode parameter.
         *
         * This is in contrast to the legacy CRTC helpers where this was
         * allowed.
         *
         * Atomic drivers which need to inspect and adjust more state should
         * instead use the @atomic_check callback. If @atomic_check is used,
         * this hook isn't called since @atomic_check allows a strict superset
         * of the functionality of @mode_fixup.
         *
         * Also beware that userspace can request its own custom modes, neither
         * core nor helpers filter modes to the list of probe modes reported by
         * the GETCONNECTOR IOCTL and stored in &drm_connector.modes. To ensure
         * that modes are filtered consistently put any encoder constraints and
         * limits checks into @mode_valid.
         *
         * RETURNS:
         *
         * True if an acceptable configuration is possible, false if the modeset
         * operation should be rejected.
         */
        bool (*mode_fixup)(struct drm_encoder *encoder,
                           const struct drm_display_mode *mode,
                           struct drm_display_mode *adjusted_mode);

        /**
         * @prepare:
         *
         * This callback should prepare the encoder for a subsequent modeset,
         * which in practice means the driver should disable the encoder if it
         * is running. Most drivers ended up implementing this by calling their
         * @dpms hook with DRM_MODE_DPMS_OFF.
         *
         * This callback is used by the legacy CRTC helpers.  Atomic helpers
         * also support using this hook for disabling an encoder to facilitate
         * transitions to atomic, but it is deprecated. Instead @disable should
         * be used.
         */
        void (*prepare)(struct drm_encoder *encoder);
        /**
         * @commit:
         *
         * This callback should commit the new mode on the encoder after a modeset,
         * which in practice means the driver should enable the encoder.  Most
         * drivers ended up implementing this by calling their @dpms hook with
         * DRM_MODE_DPMS_ON.
         *
         * This callback is used by the legacy CRTC helpers.  Atomic helpers
         * also support using this hook for enabling an encoder to facilitate
         * transitions to atomic, but it is deprecated. Instead @enable should
         * be used.
         */
        void (*commit)(struct drm_encoder *encoder);

        /**
         * @mode_set:
         *
         * This callback is used to update the display mode of an encoder.
         *
         * Note that the display pipe is completely off when this function is
         * called. Drivers which need hardware to be running before they program
         * the new display mode (because they implement runtime PM) should not
         * use this hook, because the helper library calls it only once and not
         * every time the display pipeline is suspend using either DPMS or the
         * new "ACTIVE" property. Such drivers should instead move all their
         * encoder setup into the @enable callback.
         *
         * This callback is used both by the legacy CRTC helpers and the atomic
         * modeset helpers. It is optional in the atomic helpers.
         *
         * NOTE:
         *
         * If the driver uses the atomic modeset helpers and needs to inspect
         * the connector state or connector display info during mode setting,
         * @atomic_mode_set can be used instead.
         */
        void (*mode_set)(struct drm_encoder *encoder,
                         struct drm_display_mode *mode,
                         struct drm_display_mode *adjusted_mode);

        /**
         * @atomic_mode_set:
         *
         * This callback is used to update the display mode of an encoder.
         *
         * Note that the display pipe is completely off when this function is
         * called. Drivers which need hardware to be running before they program
         * the new display mode (because they implement runtime PM) should not
         * use this hook, because the helper library calls it only once and not
         * every time the display pipeline is suspended using either DPMS or the
         * new "ACTIVE" property. Such drivers should instead move all their
         * encoder setup into the @enable callback.
         *
         * This callback is used by the atomic modeset helpers in place of the
         * @mode_set callback, if set by the driver. It is optional and should
         * be used instead of @mode_set if the driver needs to inspect the
         * connector state or display info, since there is no direct way to
         * go from the encoder to the current connector.
         */
        void (*atomic_mode_set)(struct drm_encoder *encoder,
                                struct drm_crtc_state *crtc_state,
                                struct drm_connector_state *conn_state);
        /**
         * @detect:
         *
         * This callback can be used by drivers who want to do detection on the
         * encoder object instead of in connector functions.
         *
         * It is not used by any helper and therefore has purely driver-specific
         * semantics. New drivers shouldn't use this and instead just implement
         * their own private callbacks.
         *
         * FIXME:
         *
         * This should just be converted into a pile of driver vfuncs.
         * Currently radeon, amdgpu and nouveau are using it.
         */
        enum drm_connector_status (*detect)(struct drm_encoder *encoder,
                                            struct drm_connector *connector);

        /**
         * @atomic_disable:
         *
         * This callback should be used to disable the encoder. With the atomic
         * drivers it is called before this encoder's CRTC has been shut off
         * using their own &drm_crtc_helper_funcs.atomic_disable hook. If that
         * sequence is too simple drivers can just add their own driver private
         * encoder hooks and call them from CRTC's callback by looping over all
         * encoders connected to it using for_each_encoder_on_crtc().
         *
         * This callback is a variant of @disable that provides the atomic state
         * to the driver. If @atomic_disable is implemented, @disable is not
         * called by the helpers.
         *
         * This hook is only used by atomic helpers. Atomic drivers don't need
         * to implement it if there's no need to disable anything at the encoder
         * level. To ensure that runtime PM handling (using either DPMS or the
         * new "ACTIVE" property) works @atomic_disable must be the inverse of
         * @atomic_enable.
         */
        void (*atomic_disable)(struct drm_encoder *encoder,
                               struct drm_atomic_state *state);

        /**
         * @atomic_enable:
         *
         * This callback should be used to enable the encoder. It is called
         * after this encoder's CRTC has been enabled using their own
         * &drm_crtc_helper_funcs.atomic_enable hook. If that sequence is
         * too simple drivers can just add their own driver private encoder
         * hooks and call them from CRTC's callback by looping over all encoders
         * connected to it using for_each_encoder_on_crtc().
         *
         * This callback is a variant of @enable that provides the atomic state
         * to the driver. If @atomic_enable is implemented, @enable is not
         * called by the helpers.
         *
         * This hook is only used by atomic helpers, it is the opposite of
         * @atomic_disable. Atomic drivers don't need to implement it if there's
         * no need to enable anything at the encoder level. To ensure that
         * runtime PM handling works @atomic_enable must be the inverse of
         * @atomic_disable.
         */
        void (*atomic_enable)(struct drm_encoder *encoder,
                              struct drm_atomic_state *state);
        /**
         * @disable:
         *
         * This callback should be used to disable the encoder. With the atomic
         * drivers it is called before this encoder's CRTC has been shut off
         * using their own &drm_crtc_helper_funcs.disable hook.  If that
         * sequence is too simple drivers can just add their own driver private
         * encoder hooks and call them from CRTC's callback by looping over all
         * encoders connected to it using for_each_encoder_on_crtc().
         *
         * This hook is used both by legacy CRTC helpers and atomic helpers.
         * Atomic drivers don't need to implement it if there's no need to
         * disable anything at the encoder level. To ensure that runtime PM
         * handling (using either DPMS or the new "ACTIVE" property) works
         * @disable must be the inverse of @enable for atomic drivers.
         *
         * For atomic drivers also consider @atomic_disable and save yourself
         * from having to read the NOTE below!
         *
         * NOTE:
         *
         * With legacy CRTC helpers there's a big semantic difference between
         * @disable and other hooks (like @prepare or @dpms) used to shut down a
         * encoder: @disable is only called when also logically disabling the
         * display pipeline and needs to release any resources acquired in
         * @mode_set (like shared PLLs, or again release pinned framebuffers).
         *
         * Therefore @disable must be the inverse of @mode_set plus @commit for
         * drivers still using legacy CRTC helpers, which is different from the
         * rules under atomic.
         */
        void (*disable)(struct drm_encoder *encoder);

        /**
         * @enable:
         *
         * This callback should be used to enable the encoder. With the atomic
         * drivers it is called after this encoder's CRTC has been enabled using
         * their own &drm_crtc_helper_funcs.enable hook.  If that sequence is
         * too simple drivers can just add their own driver private encoder
         * hooks and call them from CRTC's callback by looping over all encoders
         * connected to it using for_each_encoder_on_crtc().
         *
         * This hook is only used by atomic helpers, it is the opposite of
         * @disable. Atomic drivers don't need to implement it if there's no
         * need to enable anything at the encoder level. To ensure that
         * runtime PM handling (using either DPMS or the new "ACTIVE" property)
         * works @enable must be the inverse of @disable for atomic drivers.
         */
        void (*enable)(struct drm_encoder *encoder);

        /**
         * @atomic_check:
         *
         * This callback is used to validate encoder state for atomic drivers.
         * Since the encoder is the object connecting the CRTC and connector it
         * gets passed both states, to be able to validate interactions and
         * update the CRTC to match what the encoder needs for the requested
         * connector.
         *
         * Since this provides a strict superset of the functionality of
         * @mode_fixup (the requested and adjusted modes are both available
         * through the passed in &struct drm_crtc_state) @mode_fixup is not
         * called when @atomic_check is implemented.
         *
         * This function is used by the atomic helpers, but it is optional.
         *
         * NOTE:
         *
         * This function is called in the check phase of an atomic update. The
         * driver is not allowed to change anything outside of the free-standing
         * state objects passed-in or assembled in the overall &drm_atomic_state
         * update tracking structure.
         *
         * Also beware that userspace can request its own custom modes, neither
         * core nor helpers filter modes to the list of probe modes reported by
         * the GETCONNECTOR IOCTL and stored in &drm_connector.modes. To ensure
         * that modes are filtered consistently put any encoder constraints and
         * limits checks into @mode_valid.
         *
         * RETURNS:
         *
         * 0 on success, -EINVAL if the state or the transition can't be
         * supported, -ENOMEM on memory allocation failure and -EDEADLK if an
         * attempt to obtain another state object ran into a &drm_modeset_lock
         * deadlock.
         */
        int (*atomic_check)(struct drm_encoder *encoder,
                            struct drm_crtc_state *crtc_state,
                            struct drm_connector_state *conn_state);
};

二、bridge数据结构

2.1 struct drm_bridge

linux内核使用struct drm_bridge来表示一个桥接设备,一般用于注册encoder后面另外再接的转换芯片;定义在include/drm/drm_bridge.h:

/**
 * struct drm_bridge - central DRM bridge control structure
 */
struct drm_bridge {
        /** @base: inherit from &drm_private_object */
        struct drm_private_obj base;
        /** @dev: DRM device this bridge belongs to */
        struct drm_device *dev;
        /** @encoder: encoder to which this bridge is connected */
        struct drm_encoder *encoder;
        /** @chain_node: used to form a bridge chain */
        struct list_head chain_node;
#ifdef CONFIG_OF
        /** @of_node: device node pointer to the bridge */
        struct device_node *of_node;
#endif
        /** @list: to keep track of all added bridges */
        struct list_head list;
        /**
         * @timings:
         *
         * the timing specification for the bridge, if any (may be NULL)
         */
        const struct drm_bridge_timings *timings;
        /** @funcs: control functions */
        const struct drm_bridge_funcs *funcs;
        /** @driver_private: pointer to the bridge driver's internal context */
        void *driver_private;
        /** @ops: bitmask of operations supported by the bridge */
        enum drm_bridge_ops ops;
        /**
         * @type: Type of the connection at the bridge output
         * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
         * identifies the type of connected display.
         */
        int type;
        /**
         * @interlace_allowed: Indicate that the bridge can handle interlaced
         * modes.
         */
        bool interlace_allowed;
        /**
         * @pre_enable_prev_first: The bridge requires that the prev
         * bridge @pre_enable function is called before its @pre_enable,
         * and conversely for post_disable. This is most frequently a
         * requirement for DSI devices which need the host to be initialised
         * before the peripheral.
         */
        bool pre_enable_prev_first;
        /**
         * @ddc: Associated I2C adapter for DDC access, if any.
         */
        struct i2c_adapter *ddc;
        /** private: */
        /**
         * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
         */
        struct mutex hpd_mutex;
        /**
         * @hpd_cb: Hot plug detection callback, registered with
         * drm_bridge_hpd_enable().
         */
        void (*hpd_cb)(void *data, enum drm_connector_status status);
        /**
         * @hpd_data: Private data passed to the Hot plug detection callback
         * @hpd_cb.
         */
        void *hpd_data;
};

2.2struct drm_bridge_funcs

struct drm_bridge_funcs用于描述bridge的控制函数,定义在include/drm/drm_bridge.h

点击查看代码
/**
 * struct drm_bridge_funcs - drm_bridge control functions
 */
struct drm_bridge_funcs {
        /**
         * @attach:
         *
         * This callback is invoked whenever our bridge is being attached to a
         * &drm_encoder. The flags argument tunes the behaviour of the attach
         * operation (see DRM_BRIDGE_ATTACH_*).
         *
         * The @attach callback is optional.
         *
         * RETURNS:
         *
         * Zero on success, error code on failure.
         */
        int (*attach)(struct drm_bridge *bridge,
                      enum drm_bridge_attach_flags flags);

        /**
         * @detach:
         *
         * This callback is invoked whenever our bridge is being detached from a
         * &drm_encoder.
         *
         * The @detach callback is optional.
         */
        void (*detach)(struct drm_bridge *bridge);

        /**
         * @mode_valid:
         *
         * This callback is used to check if a specific mode is valid in this
         * bridge. This should be implemented if the bridge has some sort of
         * restriction in the modes it can display. For example, a given bridge
         * may be responsible to set a clock value. If the clock can not
         * produce all the values for the available modes then this callback
         * can be used to restrict the number of modes to only the ones that
         * can be displayed.
         *
         * This hook is used by the probe helpers to filter the mode list in
         * drm_helper_probe_single_connector_modes(), and it is used by the
         * atomic helpers to validate modes supplied by userspace in
         * drm_atomic_helper_check_modeset().
         *
         * The @mode_valid callback is optional.
         *
         * NOTE:
         *
         * Since this function is both called from the check phase of an atomic
         * commit, and the mode validation in the probe paths it is not allowed
         * to look at anything else but the passed-in mode, and validate it
         * against configuration-invariant hardward constraints. Any further
         * limits which depend upon the configuration can only be checked in
         * @mode_fixup.
         *
         * RETURNS:
         *
         * drm_mode_status Enum
         */
        enum drm_mode_status (*mode_valid)(struct drm_bridge *bridge,
                                           const struct drm_display_info *info,
                                           const struct drm_display_mode *mode);
        /**
         * @mode_fixup:
         *
         * This callback is used to validate and adjust a mode. The parameter
         * mode is the display mode that should be fed to the next element in
         * the display chain, either the final &drm_connector or the next
         * &drm_bridge. The parameter adjusted_mode is the input mode the bridge
         * requires. It can be modified by this callback and does not need to
         * match mode. See also &drm_crtc_state.adjusted_mode for more details.
         *
         * This is the only hook that allows a bridge to reject a modeset. If
         * this function passes all other callbacks must succeed for this
         * configuration.
         *
         * The mode_fixup callback is optional. &drm_bridge_funcs.mode_fixup()
         * is not called when &drm_bridge_funcs.atomic_check() is implemented,
         * so only one of them should be provided.
         *
         * NOTE:
         *
         * This function is called in the check phase of atomic modesets, which
         * can be aborted for any reason (including on userspace's request to
         * just check whether a configuration would be possible). Drivers MUST
         * NOT touch any persistent state (hardware or software) or data
         * structures except the passed in @state parameter.
         *
         * Also beware that userspace can request its own custom modes, neither
         * core nor helpers filter modes to the list of probe modes reported by
         * the GETCONNECTOR IOCTL and stored in &drm_connector.modes. To ensure
         * that modes are filtered consistently put any bridge constraints and
         * limits checks into @mode_valid.
         *
         * RETURNS:
         *
         * True if an acceptable configuration is possible, false if the modeset
         * operation should be rejected.
         */
        bool (*mode_fixup)(struct drm_bridge *bridge,
                           const struct drm_display_mode *mode,
                           struct drm_display_mode *adjusted_mode);
        /**
         * @disable:
         *
         * This callback should disable the bridge. It is called right before
         * the preceding element in the display pipe is disabled. If the
         * preceding element is a bridge this means it's called before that
         * bridge's @disable vfunc. If the preceding element is a &drm_encoder
         * it's called right before the &drm_encoder_helper_funcs.disable,
         * &drm_encoder_helper_funcs.prepare or &drm_encoder_helper_funcs.dpms
         * hook.
         *
         * The bridge can assume that the display pipe (i.e. clocks and timing
         * signals) feeding it is still running when this callback is called.
         *
         * The @disable callback is optional.
         *
         * NOTE:
         *
         * This is deprecated, do not use!
         * New drivers shall use &drm_bridge_funcs.atomic_disable.
         */
        void (*disable)(struct drm_bridge *bridge);

        /**
         * @post_disable:
         *
         * This callback should disable the bridge. It is called right after the
         * preceding element in the display pipe is disabled. If the preceding
         * element is a bridge this means it's called after that bridge's
         * @post_disable function. If the preceding element is a &drm_encoder
         * it's called right after the encoder's
         * &drm_encoder_helper_funcs.disable, &drm_encoder_helper_funcs.prepare
         * or &drm_encoder_helper_funcs.dpms hook.
         *
         * The bridge must assume that the display pipe (i.e. clocks and timing
         * singals) feeding it is no longer running when this callback is
         * called.
         *
         * The @post_disable callback is optional.
         *
         * NOTE:
         *
         * This is deprecated, do not use!
         * New drivers shall use &drm_bridge_funcs.atomic_post_disable.
         */
        void (*post_disable)(struct drm_bridge *bridge);

        /**
         * @mode_set:
         *
         * This callback should set the given mode on the bridge. It is called
         * after the @mode_set callback for the preceding element in the display
         * pipeline has been called already. If the bridge is the first element
         * then this would be &drm_encoder_helper_funcs.mode_set. The display
         * pipe (i.e.  clocks and timing signals) is off when this function is
         * called.
         *
         * The adjusted_mode parameter is the mode output by the CRTC for the
         * first bridge in the chain. It can be different from the mode
         * parameter that contains the desired mode for the connector at the end
         * of the bridges chain, for instance when the first bridge in the chain
         * performs scaling. The adjusted mode is mostly useful for the first
         * bridge in the chain and is likely irrelevant for the other bridges.
         *
         * For atomic drivers the adjusted_mode is the mode stored in
         * &drm_crtc_state.adjusted_mode.
         *
         * NOTE:
         *
         * This is deprecated, do not use!
         * New drivers shall set their mode in the
         * &drm_bridge_funcs.atomic_enable operation.
         */
        void (*mode_set)(struct drm_bridge *bridge,
                         const struct drm_display_mode *mode,
                         const struct drm_display_mode *adjusted_mode);
        /**
         * @pre_enable:
         *
         * This callback should enable the bridge. It is called right before
         * the preceding element in the display pipe is enabled. If the
         * preceding element is a bridge this means it's called before that
         * bridge's @pre_enable function. If the preceding element is a
         * &drm_encoder it's called right before the encoder's
         * &drm_encoder_helper_funcs.enable, &drm_encoder_helper_funcs.commit or
         * &drm_encoder_helper_funcs.dpms hook.
         *
         * The display pipe (i.e. clocks and timing signals) feeding this bridge
         * will not yet be running when this callback is called. The bridge must
         * not enable the display link feeding the next bridge in the chain (if
         * there is one) when this callback is called.
         *
         * The @pre_enable callback is optional.
         *
         * NOTE:
         *
         * This is deprecated, do not use!
         * New drivers shall use &drm_bridge_funcs.atomic_pre_enable.
         */
        void (*pre_enable)(struct drm_bridge *bridge);

        /**
         * @enable:
         *
         * This callback should enable the bridge. It is called right after
         * the preceding element in the display pipe is enabled. If the
         * preceding element is a bridge this means it's called after that
         * bridge's @enable function. If the preceding element is a
         * &drm_encoder it's called right after the encoder's
         * &drm_encoder_helper_funcs.enable, &drm_encoder_helper_funcs.commit or
         * &drm_encoder_helper_funcs.dpms hook.
         *
         * The bridge can assume that the display pipe (i.e. clocks and timing
         * signals) feeding it is running when this callback is called. This
         * callback must enable the display link feeding the next bridge in the
         * chain if there is one.
         *
         * The @enable callback is optional.
         *
         * NOTE:
         *
         * This is deprecated, do not use!
         * New drivers shall use &drm_bridge_funcs.atomic_enable.
         */
        void (*enable)(struct drm_bridge *bridge);

        /**
         * @atomic_pre_enable:
         *
         * This callback should enable the bridge. It is called right before
         * the preceding element in the display pipe is enabled. If the
         * preceding element is a bridge this means it's called before that
         * bridge's @atomic_pre_enable or @pre_enable function. If the preceding
         * element is a &drm_encoder it's called right before the encoder's
         * &drm_encoder_helper_funcs.atomic_enable hook.
         *
         * The display pipe (i.e. clocks and timing signals) feeding this bridge
         * will not yet be running when this callback is called. The bridge must
         * not enable the display link feeding the next bridge in the chain (if
         * there is one) when this callback is called.
         *
         * The @atomic_pre_enable callback is optional.
         */
        void (*atomic_pre_enable)(struct drm_bridge *bridge,
                                  struct drm_bridge_state *old_bridge_state);

        /**
         * @atomic_enable:
         *
         * This callback should enable the bridge. It is called right after
         * the preceding element in the display pipe is enabled. If the
         * preceding element is a bridge this means it's called after that
         * bridge's @atomic_enable or @enable function. If the preceding element
         * is a &drm_encoder it's called right after the encoder's
         * &drm_encoder_helper_funcs.atomic_enable hook.
         *
         * The bridge can assume that the display pipe (i.e. clocks and timing
         * signals) feeding it is running when this callback is called. This
         * callback must enable the display link feeding the next bridge in the
         * chain if there is one.
         *
         * The @atomic_enable callback is optional.
         */
        void (*atomic_enable)(struct drm_bridge *bridge,
                              struct drm_bridge_state *old_bridge_state);
        /**
         * @atomic_disable:
         *
         * This callback should disable the bridge. It is called right before
         * the preceding element in the display pipe is disabled. If the
         * preceding element is a bridge this means it's called before that
         * bridge's @atomic_disable or @disable vfunc. If the preceding element
         * is a &drm_encoder it's called right before the
         * &drm_encoder_helper_funcs.atomic_disable hook.
         *
         * The bridge can assume that the display pipe (i.e. clocks and timing
         * signals) feeding it is still running when this callback is called.
         *
         * The @atomic_disable callback is optional.
         */
        void (*atomic_disable)(struct drm_bridge *bridge,
                               struct drm_bridge_state *old_bridge_state);

        /**
         * @atomic_post_disable:
         *
         * This callback should disable the bridge. It is called right after the
         * preceding element in the display pipe is disabled. If the preceding
         * element is a bridge this means it's called after that bridge's
         * @atomic_post_disable or @post_disable function. If the preceding
         * element is a &drm_encoder it's called right after the encoder's
         * &drm_encoder_helper_funcs.atomic_disable hook.
         *
         * The bridge must assume that the display pipe (i.e. clocks and timing
         * signals) feeding it is no longer running when this callback is
         * called.
         *
         * The @atomic_post_disable callback is optional.
         */
        void (*atomic_post_disable)(struct drm_bridge *bridge,
                                    struct drm_bridge_state *old_bridge_state);

        /**
         * @atomic_duplicate_state:
         *
         * Duplicate the current bridge state object (which is guaranteed to be
         * non-NULL).
         *
         * The atomic_duplicate_state hook is mandatory if the bridge
         * implements any of the atomic hooks, and should be left unassigned
         * otherwise. For bridges that don't subclass &drm_bridge_state, the
         * drm_atomic_helper_bridge_duplicate_state() helper function shall be
         * used to implement this hook.
         *
         * RETURNS:
         * A valid drm_bridge_state object or NULL if the allocation fails.
         */
        struct drm_bridge_state *(*atomic_duplicate_state)(struct drm_bridge *bridge);

        /**
         * @atomic_destroy_state:
         *
         * Destroy a bridge state object previously allocated by
         * &drm_bridge_funcs.atomic_duplicate_state().
         *
         * The atomic_destroy_state hook is mandatory if the bridge implements
         * any of the atomic hooks, and should be left unassigned otherwise.
         * For bridges that don't subclass &drm_bridge_state, the
         * drm_atomic_helper_bridge_destroy_state() helper function shall be
         * used to implement this hook.
         */
        void (*atomic_destroy_state)(struct drm_bridge *bridge,
                                     struct drm_bridge_state *state);

        /**
         * @atomic_get_output_bus_fmts:
         *
         * Return the supported bus formats on the output end of a bridge.
         * The returned array must be allocated with kmalloc() and will be
         * freed by the caller. If the allocation fails, NULL should be
         * returned. num_output_fmts must be set to the returned array size.
         * Formats listed in the returned array should be listed in decreasing
         * preference order (the core will try all formats until it finds one
         * that works).
         *
         * This method is only called on the last element of the bridge chain
         * as part of the bus format negotiation process that happens in
         * &drm_atomic_bridge_chain_select_bus_fmts().
         * This method is optional. When not implemented, the core will
         * fall back to &drm_connector.display_info.bus_formats[0] if
         * &drm_connector.display_info.num_bus_formats > 0,
         * or to MEDIA_BUS_FMT_FIXED otherwise.
         */
        u32 *(*atomic_get_output_bus_fmts)(struct drm_bridge *bridge,
                                           struct drm_bridge_state *bridge_state,
                                           struct drm_crtc_state *crtc_state,
                                           struct drm_connector_state *conn_state,
                                           unsigned int *num_output_fmts);

        /**
         * @atomic_get_input_bus_fmts:
         *
         * Return the supported bus formats on the input end of a bridge for
         * a specific output bus format.
         *
         * The returned array must be allocated with kmalloc() and will be
         * freed by the caller. If the allocation fails, NULL should be
         * returned. num_input_fmts must be set to the returned array size.
         * Formats listed in the returned array should be listed in decreasing
         * preference order (the core will try all formats until it finds one
         * that works). When the format is not supported NULL should be
         * returned and num_input_fmts should be set to 0.
         *
         * This method is called on all elements of the bridge chain as part of
         * the bus format negotiation process that happens in
         * drm_atomic_bridge_chain_select_bus_fmts().
         * This method is optional. When not implemented, the core will bypass
         * bus format negotiation on this element of the bridge without
         * failing, and the previous element in the chain will be passed
         * MEDIA_BUS_FMT_FIXED as its output bus format.
         *
         * Bridge drivers that need to support being linked to bridges that are
         * not supporting bus format negotiation should handle the
         * output_fmt == MEDIA_BUS_FMT_FIXED case appropriately, by selecting a
         * sensible default value or extracting this information from somewhere
         * else (FW property, &drm_display_mode, &drm_display_info, ...)
         *
         * Note: Even if input format selection on the first bridge has no
         * impact on the negotiation process (bus format negotiation stops once
         * we reach the first element of the chain), drivers are expected to
         * return accurate input formats as the input format may be used to
         * configure the CRTC output appropriately.
         */
        u32 *(*atomic_get_input_bus_fmts)(struct drm_bridge *bridge,
                                          struct drm_bridge_state *bridge_state,
                                          struct drm_crtc_state *crtc_state,
                                          struct drm_connector_state *conn_state,
                                          u32 output_fmt,
                                          unsigned int *num_input_fmts);

        /**
         * @atomic_check:
         *
         * This method is responsible for checking bridge state correctness.
         * It can also check the state of the surrounding components in chain
         * to make sure the whole pipeline can work properly.
         *
         * &drm_bridge_funcs.atomic_check() hooks are called in reverse
         * order (from the last to the first bridge).
         *
         * This method is optional. &drm_bridge_funcs.mode_fixup() is not
         * called when &drm_bridge_funcs.atomic_check() is implemented, so only
         * one of them should be provided.
         *
         * If drivers need to tweak &drm_bridge_state.input_bus_cfg.flags or
         * &drm_bridge_state.output_bus_cfg.flags it should happen in
         * this function. By default the &drm_bridge_state.output_bus_cfg.flags
         * field is set to the next bridge
         * &drm_bridge_state.input_bus_cfg.flags value or
         * &drm_connector.display_info.bus_flags if the bridge is the last
         * element in the chain.
         *
         * RETURNS:
         * zero if the check passed, a negative error code otherwise.
         */
        int (*atomic_check)(struct drm_bridge *bridge,
                            struct drm_bridge_state *bridge_state,
                            struct drm_crtc_state *crtc_state,
                            struct drm_connector_state *conn_state);

        /**
         * @atomic_reset:
         *
         * Reset the bridge to a predefined state (or retrieve its current
         * state) and return a &drm_bridge_state object matching this state.
         * This function is called at attach time.
         *
         * The atomic_reset hook is mandatory if the bridge implements any of
         * the atomic hooks, and should be left unassigned otherwise. For
         * bridges that don't subclass &drm_bridge_state, the
         * drm_atomic_helper_bridge_reset() helper function shall be used to
         * implement this hook.
         *
         * Note that the atomic_reset() semantics is not exactly matching the
         * reset() semantics found on other components (connector, plane, ...).
         *
         * 1. The reset operation happens when the bridge is attached, not when
         *    drm_mode_config_reset() is called
         * 2. It's meant to be used exclusively on bridges that have been
         *    converted to the ATOMIC API
         *
         * RETURNS:
         * A valid drm_bridge_state object in case of success, an ERR_PTR()
         * giving the reason of the failure otherwise.
         */
        struct drm_bridge_state *(*atomic_reset)(struct drm_bridge *bridge);

        /**
         * @detect:
         *
         * Check if anything is attached to the bridge output.
         *
         * This callback is optional, if not implemented the bridge will be
         * considered as always having a component attached to its output.
         * Bridges that implement this callback shall set the
         * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
         *
         * RETURNS:
         *
         * drm_connector_status indicating the bridge output status.
         */
        enum drm_connector_status (*detect)(struct drm_bridge *bridge);

        /**
         * @get_modes:
         *
         * Fill all modes currently valid for the sink into the &drm_connector
         * with drm_mode_probed_add().
         *
         * The @get_modes callback is mostly intended to support non-probeable
         * displays such as many fixed panels. Bridges that support reading
         * EDID shall leave @get_modes unimplemented and implement the
         * &drm_bridge_funcs->get_edid callback instead.
         *
         * This callback is optional. Bridges that implement it shall set the
         * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
         *
         * The connector parameter shall be used for the sole purpose of
         * filling modes, and shall not be stored internally by bridge drivers
         * for future usage.
         *
         * RETURNS:
         *
         * The number of modes added by calling drm_mode_probed_add().
         */
        int (*get_modes)(struct drm_bridge *bridge,
                         struct drm_connector *connector);

        /**
         * @get_edid:
         *
         * Read and parse the EDID data of the connected display.
         *
         * The @get_edid callback is the preferred way of reporting mode
         * information for a display connected to the bridge output. Bridges
         * that support reading EDID shall implement this callback and leave
         * the @get_modes callback unimplemented.
         *
         * The caller of this operation shall first verify the output
         * connection status and refrain from reading EDID from a disconnected
         * output.
         *
         * This callback is optional. Bridges that implement it shall set the
         * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
        *
         * The connector parameter shall be used for the sole purpose of EDID
         * retrieval and parsing, and shall not be stored internally by bridge
         * drivers for future usage.
         *
         * RETURNS:
         *
         * An edid structure newly allocated with kmalloc() (or similar) on
         * success, or NULL otherwise. The caller is responsible for freeing
         * the returned edid structure with kfree().
         */
        struct edid *(*get_edid)(struct drm_bridge *bridge,
                                 struct drm_connector *connector);

        /**
         * @hpd_notify:
         *
         * Notify the bridge of hot plug detection.
         *
         * This callback is optional, it may be implemented by bridges that
         * need to be notified of display connection or disconnection for
         * internal reasons. One use case is to reset the internal state of CEC
         * controllers for HDMI bridges.
         */
        void (*hpd_notify)(struct drm_bridge *bridge,
                           enum drm_connector_status status);

        /**
         * @hpd_enable:
         *
         * Enable hot plug detection. From now on the bridge shall call
         * drm_bridge_hpd_notify() each time a change is detected in the output
         * connection status, until hot plug detection gets disabled with
         * @hpd_disable.
         *
         * This callback is optional and shall only be implemented by bridges
         * that support hot-plug notification without polling. Bridges that
         * implement it shall also implement the @hpd_disable callback and set
         * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
         */
        void (*hpd_enable)(struct drm_bridge *bridge);

        /**
         * @hpd_disable:
         *
         * Disable hot plug detection. Once this function returns the bridge
         * shall not call drm_bridge_hpd_notify() when a change in the output
         * connection status occurs.
         *
         * This callback is optional and shall only be implemented by bridges
         * that support hot-plug notification without polling. Bridges that
         * implement it shall also implement the @hpd_enable callback and set
         * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
         */
        void (*hpd_disable)(struct drm_bridge *bridge);

        /**
         * @debugfs_init:
         *
         * Allows bridges to create bridge-specific debugfs files.
         */
        void (*debugfs_init)(struct drm_bridge *bridge, struct dentry *root);
};

2.3 drm_bridge_attach

drm_bridge_attach定义在drivers/gpu/drm/drm_bridge.c,该函数的作用是将bridge连接到encoder的链中;

/**
 * drm_bridge_attach - attach the bridge to an encoder's chain
 *
 * @encoder: DRM encoder
 * @bridge: bridge to attach
 * @previous: previous bridge in the chain (optional)
 * @flags: DRM_BRIDGE_ATTACH_* flags
 *
 * Called by a kms driver to link the bridge to an encoder's chain. The previous
 * argument specifies the previous bridge in the chain. If NULL, the bridge is
 * linked directly at the encoder's output. Otherwise it is linked at the
 * previous bridge's output.
 *
 * If non-NULL the previous bridge must be already attached by a call to this
 * function.
 *
 * Note that bridges attached to encoders are auto-detached during encoder
 * cleanup in drm_encoder_cleanup(), so drm_bridge_attach() should generally
 * *not* be balanced with a drm_bridge_detach() in driver code.
 *
 * RETURNS:
 * Zero on success, error code on failure
 */
int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
                      struct drm_bridge *previous,
                      enum drm_bridge_attach_flags flags)
{
        int ret;

        if (!encoder || !bridge)
                return -EINVAL;

        if (previous && (!previous->dev || previous->encoder != encoder))
                return -EINVAL;

        if (bridge->dev)
                return -EBUSY;

        bridge->dev = encoder->dev;
        bridge->encoder = encoder;

        if (previous)
                list_add(&bridge->chain_node, &previous->chain_node);
        else
                list_add(&bridge->chain_node, &encoder->bridge_chain);

        if (bridge->funcs->attach) {
                ret = bridge->funcs->attach(bridge, flags);
                if (ret < 0)
                        goto err_reset_bridge;
        }

        if (bridge->funcs->atomic_reset) {
                struct drm_bridge_state *state;

                state = bridge->funcs->atomic_reset(bridge);
                if (IS_ERR(state)) {
                        ret = PTR_ERR(state);
                        goto err_detach_bridge;
                }

                drm_atomic_private_obj_init(bridge->dev, &bridge->base,
                                            &state->base,
                                            &drm_bridge_priv_state_funcs);
        }

        return 0;

err_detach_bridge:
        if (bridge->funcs->detach)
                bridge->funcs->detach(bridge);

err_reset_bridge:
        bridge->dev = NULL;
        bridge->encoder = NULL;
        list_del(&bridge->chain_node);

#ifdef CONFIG_OF
        DRM_ERROR("failed to attach bridge %pOF to encoder %s: %d\n",
                  bridge->of_node, encoder->name, ret);
#else
        DRM_ERROR("failed to attach bridge to encoder %s: %d\n",
                  encoder->name, ret);
#endif

        return ret;
}

三、connector数据结构

3.1 struct drm_connector

linux内核使用struct drm_connector来表示一个连接器,用于将encoder输出的信号传递给显示器,并与显示器建立连接;定义在include/drm/drm_connector.h:

点击查看代码
/**
 * struct drm_connector - central DRM connector control structure
 *
 * Each connector may be connected to one or more CRTCs, or may be clonable by
 * another connector if they can share a CRTC.  Each connector also has a specific
 * position in the broader display (referred to as a 'screen' though it could
 * span multiple monitors).
 */
struct drm_connector {
        /** @dev: parent DRM device */
        struct drm_device *dev;
        /** @kdev: kernel device for sysfs attributes */
        struct device *kdev;
        /** @attr: sysfs attributes */
        struct device_attribute *attr;
        /**
         * @fwnode: associated fwnode supplied by platform firmware
         *
         * Drivers can set this to associate a fwnode with a connector, drivers
         * are expected to get a reference on the fwnode when setting this.
         * drm_connector_cleanup() will call fwnode_handle_put() on this.
         */
        struct fwnode_handle *fwnode;

        /**
         * @head:
         *
         * List of all connectors on a @dev, linked from
         * &drm_mode_config.connector_list. Protected by
         * &drm_mode_config.connector_list_lock, but please only use
         * &drm_connector_list_iter to walk this list.
         */
        struct list_head head;

        /**
         * @global_connector_list_entry:
         *
         * Connector entry in the global connector-list, used by
         * drm_connector_find_by_fwnode().
         */
        struct list_head global_connector_list_entry;

        /** @base: base KMS object */
        struct drm_mode_object base;

        /** @name: human readable name, can be overwritten by the driver */
        char *name;

        /**
         * @mutex: Lock for general connector state, but currently only protects
         * @registered. Most of the connector state is still protected by
         * &drm_mode_config.mutex.
         */
        struct mutex mutex;

        /**
         * @index: Compacted connector index, which matches the position inside
         * the mode_config.list for drivers not supporting hot-add/removing. Can
         * be used as an array index. It is invariant over the lifetime of the
         * connector.
         */
        unsigned index;

        /**
         * @connector_type:
         * one of the DRM_MODE_CONNECTOR_<foo> types from drm_mode.h
         */
        int connector_type;
        /** @connector_type_id: index into connector type enum */
        int connector_type_id;
        /**
         * @interlace_allowed:
         * Can this connector handle interlaced modes? Only used by
         * drm_helper_probe_single_connector_modes() for mode filtering.
         */
        bool interlace_allowed;
        /**
         * @doublescan_allowed:
         * Can this connector handle doublescan? Only used by
         * drm_helper_probe_single_connector_modes() for mode filtering.
         */
        bool doublescan_allowed;
        /**
         * @stereo_allowed:
         * Can this connector handle stereo modes? Only used by
         * drm_helper_probe_single_connector_modes() for mode filtering.
         */
        bool stereo_allowed;

        /**
         * @ycbcr_420_allowed : This bool indicates if this connector is
         * capable of handling YCBCR 420 output. While parsing the EDID
         * blocks it's very helpful to know if the source is capable of
         * handling YCBCR 420 outputs.
         */
        bool ycbcr_420_allowed;

        /**
         * @registration_state: Is this connector initializing, exposed
         * (registered) with userspace, or unregistered?
         *
         * Protected by @mutex.
         */
        enum drm_connector_registration_state registration_state;

        /**
         * @modes:
         * Modes available on this connector (from fill_modes() + user).
         * Protected by &drm_mode_config.mutex.
         */
        struct list_head modes;

        /**
         * @status:
         * One of the drm_connector_status enums (connected, not, or unknown).
         * Protected by &drm_mode_config.mutex.
         */
        enum drm_connector_status status;

        /**
         * @probed_modes:
         * These are modes added by probing with DDC or the BIOS, before
         * filtering is applied. Used by the probe helpers. Protected by
         * &drm_mode_config.mutex.
         */
        struct list_head probed_modes;

        /**
         * @display_info: Display information is filled from EDID information
         * when a display is detected. For non hot-pluggable displays such as
         * flat panels in embedded systems, the driver should initialize the
         * &drm_display_info.width_mm and &drm_display_info.height_mm fields
         * with the physical size of the display.
         *
         * Protected by &drm_mode_config.mutex.
         */
        struct drm_display_info display_info;

        /** @funcs: connector control functions */
        const struct drm_connector_funcs *funcs;

        /**
         * @edid_blob_ptr: DRM property containing EDID if present. Protected by
         * &drm_mode_config.mutex. This should be updated only by calling
         * drm_connector_update_edid_property().
         */
        struct drm_property_blob *edid_blob_ptr;

        /** @properties: property tracking for this connector */
        struct drm_object_properties properties;

        /**
         * @scaling_mode_property: Optional atomic property to control the
         * upscaling. See drm_connector_attach_content_protection_property().
         */
        struct drm_property *scaling_mode_property;

        /**
         * @vrr_capable_property: Optional property to help userspace
         * query hardware support for variable refresh rate on a connector.
         * connector. Drivers can add the property to a connector by
         * calling drm_connector_attach_vrr_capable_property().
         *
         * This should be updated only by calling
         * drm_connector_set_vrr_capable_property().
         */
        struct drm_property *vrr_capable_property;

        /**
         * @colorspace_property: Connector property to set the suitable
         * colorspace supported by the sink.
         */
        struct drm_property *colorspace_property;

        /**
         * @path_blob_ptr:
         *
         * DRM blob property data for the DP MST path property. This should only
         * be updated by calling drm_connector_set_path_property().
         */
        struct drm_property_blob *path_blob_ptr;

        /**
         * @max_bpc_property: Default connector property for the max bpc to be
         * driven out of the connector.
         */
        struct drm_property *max_bpc_property;

        /** @privacy_screen: drm_privacy_screen for this connector, or NULL. */
        struct drm_privacy_screen *privacy_screen;

        /** @privacy_screen_notifier: privacy-screen notifier_block */
        struct notifier_block privacy_screen_notifier;

        /**
         * @privacy_screen_sw_state_property: Optional atomic property for the
         * connector to control the integrated privacy screen.
         */
        struct drm_property *privacy_screen_sw_state_property;

        /**
         * @privacy_screen_hw_state_property: Optional atomic property for the
         * connector to report the actual integrated privacy screen state.
         */
        struct drm_property *privacy_screen_hw_state_property;

#define DRM_CONNECTOR_POLL_HPD (1 << 0)
#define DRM_CONNECTOR_POLL_CONNECT (1 << 1)
#define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2)

        /**
         * @polled:
         *
         * Connector polling mode, a combination of
         *
         * DRM_CONNECTOR_POLL_HPD
         *     The connector generates hotplug events and doesn't need to be
         *     periodically polled. The CONNECT and DISCONNECT flags must not
         *     be set together with the HPD flag.
         *
         * DRM_CONNECTOR_POLL_CONNECT
         *     Periodically poll the connector for connection.
         *
         * DRM_CONNECTOR_POLL_DISCONNECT
         *     Periodically poll the connector for disconnection, without
         *     causing flickering even when the connector is in use. DACs should
         *     rarely do this without a lot of testing.
         *
         * Set to 0 for connectors that don't support connection status
         * discovery.
         */
        uint8_t polled;

        /**
         * @dpms: Current dpms state. For legacy drivers the
         * &drm_connector_funcs.dpms callback must update this. For atomic
         * drivers, this is handled by the core atomic code, and drivers must
         * only take &drm_crtc_state.active into account.
         */
        int dpms;

        /** @helper_private: mid-layer private data */
        const struct drm_connector_helper_funcs *helper_private;

        /** @cmdline_mode: mode line parsed from the kernel cmdline for this connector */
        struct drm_cmdline_mode cmdline_mode;
        /** @force: a DRM_FORCE_<foo> state for forced mode sets */
        enum drm_connector_force force;

        /**
         * @edid_override: Override EDID set via debugfs.
         *
         * Do not modify or access outside of the drm_edid_override_* family of
         * functions.
         */
        const struct drm_edid *edid_override;

        /**
         * @edid_override_mutex: Protect access to edid_override.
         */
        struct mutex edid_override_mutex;

        /** @epoch_counter: used to detect any other changes in connector, besides status */
        u64 epoch_counter;

        /**
         * @possible_encoders: Bit mask of encoders that can drive this
         * connector, drm_encoder_index() determines the index into the bitfield
         * and the bits are set with drm_connector_attach_encoder().
         */
        u32 possible_encoders;

        /**
         * @encoder: Currently bound encoder driving this connector, if any.
         * Only really meaningful for non-atomic drivers. Atomic drivers should
         * instead look at &drm_connector_state.best_encoder, and in case they
         * need the CRTC driving this output, &drm_connector_state.crtc.
         */
        struct drm_encoder *encoder;

#define MAX_ELD_BYTES   128
        /** @eld: EDID-like data, if present */
        uint8_t eld[MAX_ELD_BYTES];
        /** @latency_present: AV delay info from ELD, if found */
        bool latency_present[2];
        /**
         * @video_latency: Video latency info from ELD, if found.
         * [0]: progressive, [1]: interlaced
         */
        int video_latency[2];
        /**
         * @audio_latency: audio latency info from ELD, if found
         * [0]: progressive, [1]: interlaced
         */
        int audio_latency[2];

        /**
         * @ddc: associated ddc adapter.
         * A connector usually has its associated ddc adapter. If a driver uses
         * this field, then an appropriate symbolic link is created in connector
         * sysfs directory to make it easy for the user to tell which i2c
         * adapter is for a particular display.
         *
         * The field should be set by calling drm_connector_init_with_ddc().
         */
        struct i2c_adapter *ddc;

        /**
         * @null_edid_counter: track sinks that give us all zeros for the EDID.
         * Needed to workaround some HW bugs where we get all 0s
         */
        int null_edid_counter;

        /** @bad_edid_counter: track sinks that give us an EDID with invalid checksum */
        unsigned bad_edid_counter;

        /**
         * @edid_corrupt: Indicates whether the last read EDID was corrupt. Used
         * in Displayport compliance testing - Displayport Link CTS Core 1.2
         * rev1.1 4.2.2.6
         */
        bool edid_corrupt;
        /**
         * @real_edid_checksum: real edid checksum for corrupted edid block.
         * Required in Displayport 1.4 compliance testing
         * rev1.1 4.2.2.6
         */
        u8 real_edid_checksum;

        /** @debugfs_entry: debugfs directory for this connector */
        struct dentry *debugfs_entry;

        /**
         * @state:
         *
         * Current atomic state for this connector.
         *
         * This is protected by &drm_mode_config.connection_mutex. Note that
         * nonblocking atomic commits access the current connector state without
         * taking locks. Either by going through the &struct drm_atomic_state
         * pointers, see for_each_oldnew_connector_in_state(),
         * for_each_old_connector_in_state() and
         * for_each_new_connector_in_state(). Or through careful ordering of
         * atomic commit operations as implemented in the atomic helpers, see
         * &struct drm_crtc_commit.
         */
        struct drm_connector_state *state;

        /* DisplayID bits. FIXME: Extract into a substruct? */

        /**
         * @tile_blob_ptr:
         *
         * DRM blob property data for the tile property (used mostly by DP MST).
         * This is meant for screens which are driven through separate display
         * pipelines represented by &drm_crtc, which might not be running with
         * genlocked clocks. For tiled panels which are genlocked, like
         * dual-link LVDS or dual-link DSI, the driver should try to not expose
         * the tiling and virtualize both &drm_crtc and &drm_plane if needed.
         *
         * This should only be updated by calling
         * drm_connector_set_tile_property().
         */
        struct drm_property_blob *tile_blob_ptr;

        /** @has_tile: is this connector connected to a tiled monitor */
        bool has_tile;
        /** @tile_group: tile group for the connected monitor */
        struct drm_tile_group *tile_group;
        /** @tile_is_single_monitor: whether the tile is one monitor housing */
        bool tile_is_single_monitor;

        /** @num_h_tile: number of horizontal tiles in the tile group */
        /** @num_v_tile: number of vertical tiles in the tile group */
        uint8_t num_h_tile, num_v_tile;
        /** @tile_h_loc: horizontal location of this tile */
        /** @tile_v_loc: vertical location of this tile */
        uint8_t tile_h_loc, tile_v_loc;
        /** @tile_h_size: horizontal size of this tile. */
        /** @tile_v_size: vertical size of this tile. */
        uint16_t tile_h_size, tile_v_size;


        /**
         * @free_node:
         *
         * List used only by &drm_connector_list_iter to be able to clean up a
         * connector from any context, in conjunction with
         * &drm_mode_config.connector_free_work.
         */
        struct llist_node free_node;

        /** @hdr_sink_metadata: HDR Metadata Information read from sink */
        struct hdr_sink_metadata hdr_sink_metadata;
};
3.1.1 struct drm_display_info

linux内核使用struct drm_display_info来表示当前连接的显示设备的显示信息,它包含了一系列成员变量,用来描述显示设备的物理尺寸、颜色格式、总线格式等信息。定义在include/drm/drm_connector.h:

/**
 * struct drm_display_info - runtime data about the connected sink
 *
 * Describes a given display (e.g. CRT or flat panel) and its limitations. For
 * fixed display sinks like built-in panels there's not much difference between
 * this and &struct drm_connector. But for sinks with a real cable this
 * structure is meant to describe all the things at the other end of the cable.
 *
 * For sinks which provide an EDID this can be filled out by calling
 * drm_add_edid_modes().
 */
struct drm_display_info {
        /**
         * @width_mm: Physical width in mm.
         */
        unsigned int width_mm;

        /**
         * @height_mm: Physical height in mm.
         */
        unsigned int height_mm;

        /**
         * @bpc: Maximum bits per color channel. Used by HDMI and DP outputs.
         */
        unsigned int bpc;

        /**
         * @subpixel_order: Subpixel order of LCD panels.
         */
        enum subpixel_order subpixel_order;

#define DRM_COLOR_FORMAT_RGB444         (1<<0)
#define DRM_COLOR_FORMAT_YCBCR444       (1<<1)
#define DRM_COLOR_FORMAT_YCBCR422       (1<<2)
#define DRM_COLOR_FORMAT_YCBCR420       (1<<3)

        /**
         * @panel_orientation: Read only connector property for built-in panels,
         * indicating the orientation of the panel vs the device's casing.
         * drm_connector_init() sets this to DRM_MODE_PANEL_ORIENTATION_UNKNOWN.
         * When not UNKNOWN this gets used by the drm_fb_helpers to rotate the
         * fb to compensate and gets exported as prop to userspace.
         */
        int panel_orientation;

        /**
         * @color_formats: HDMI Color formats, selects between RGB and YCrCb
         * modes. Used DRM_COLOR_FORMAT\_ defines, which are _not_ the same ones
         * as used to describe the pixel format in framebuffers, and also don't
         * match the formats in @bus_formats which are shared with v4l.
         */
        u32 color_formats;

        /**
         * @bus_formats: Pixel data format on the wire, somewhat redundant with
         * @color_formats. Array of size @num_bus_formats encoded using
         * MEDIA_BUS_FMT\_ defines shared with v4l and media drivers.
         */
        const u32 *bus_formats;

        /**
         * @bus_formats: Pixel data format on the wire, somewhat redundant with
         * @color_formats. Array of size @num_bus_formats encoded using
         * MEDIA_BUS_FMT\_ defines shared with v4l and media drivers.
         */
        const u32 *bus_formats;
        /**
         * @num_bus_formats: Size of @bus_formats array.
         */
        unsigned int num_bus_formats;

        /**
         * @bus_flags: Additional information (like pixel signal polarity) for
         * the pixel data on the bus, using &enum drm_bus_flags values
         * DRM_BUS_FLAGS\_.
         */
        u32 bus_flags;

        /**
         * @max_tmds_clock: Maximum TMDS clock rate supported by the
         * sink in kHz. 0 means undefined.
         */
        int max_tmds_clock;

        /**
         * @dvi_dual: Dual-link DVI sink?
         */
        bool dvi_dual;

        /**
         * @is_hdmi: True if the sink is an HDMI device.
         *
         * This field shall be used instead of calling
         * drm_detect_hdmi_monitor() when possible.
         */
        bool is_hdmi;

        /**
         * @has_hdmi_infoframe: Does the sink support the HDMI infoframe?
         */
        bool has_hdmi_infoframe;

        /**
         * @rgb_quant_range_selectable: Does the sink support selecting
         * the RGB quantization range?
         */
        bool rgb_quant_range_selectable;

        /**
         * @edid_hdmi_rgb444_dc_modes: Mask of supported hdmi deep color modes
         * in RGB 4:4:4. Even more stuff redundant with @bus_formats.
         */
        u8 edid_hdmi_rgb444_dc_modes;

        /**
         * @edid_hdmi_ycbcr444_dc_modes: Mask of supported hdmi deep color
         * modes in YCbCr 4:4:4. Even more stuff redundant with @bus_formats.
         */
        u8 edid_hdmi_ycbcr444_dc_modes;

        /**
         * @cea_rev: CEA revision of the HDMI sink.
         */
        u8 cea_rev;

        /**
         * @hdmi: advance features of a HDMI sink.
         */
        struct drm_hdmi_info hdmi;

        /**
         * @non_desktop: Non desktop display (HMD).
         */
        bool non_desktop;

        /**
         * @monitor_range: Frequency range supported by monitor range descriptor
         */
        struct drm_monitor_range_info monitor_range;

        /**
         * @luminance_range: Luminance range supported by panel
         */
        struct drm_luminance_range_info luminance_range;

        /**
         * @mso_stream_count: eDP Multi-SST Operation (MSO) stream count from
         * the DisplayID VESA vendor block. 0 for conventional Single-Stream
         * Transport (SST), or 2 or 4 MSO streams.
         */
        u8 mso_stream_count;

        /**
         * @mso_pixel_overlap: eDP MSO segment pixel overlap, 0-8 pixels.
         */
        u8 mso_pixel_overlap;

        /**
         * @max_dsc_bpp: Maximum DSC target bitrate, if it is set to 0 the
         * monitor's default value is used instead.
         */
        u32 max_dsc_bpp;

        /**
         * @vics: Array of vics_len VICs. Internal to EDID parsing.
         */
        u8 *vics;

        /**
         * @vics_len: Number of elements in vics. Internal to EDID parsing.
         */
        int vics_len;

        /**
         * @quirks: EDID based quirks. Internal to EDID parsing.
         */
        u32 quirks;
};

其中:

  • width_mm:物理宽度,单位mm
  • height_mm:物理高度、单位mm
  • bpc: 每个颜色通道最大位数,适用于HDMIDP输出;
  • panel_orientation:一个只读属性,用于描述内置面板的方向与设备外壳的方向之间的关系。在调用drm_connector_init()函数时,这个属性会被设置为DRM_MODE_PANEL_ORIENTATION_UNKNOWN,表示方向未知。当方向不是未知时,图形驱动程序中的drm_fb_helpers会根据该属性来旋转帧缓冲以进行补偿,并将其作为属性导出给用户空间;
  • color_formats:用于选择HDMI的颜色格式,支持RGBYCrCb模式;这里使用了DRM_COLOR_FORMAT_定义的值来表示不同的颜色格式,这些值与用于描述framebuffer中像素格式的值是不同的。此外,它们也与与v4l共享的bus_formats中的格式不匹配;
  • bus_formats:表示在传输介质上的像素数据格式,与color_formats相比有些冗余。bus_formats是一个数组,其大小由num_bus_formats确定,使用MEDIA_BUS_FMT_定义对其进行编码,这些定义是和v4l(以及媒体驱动程序共享的;
  • num_bus_formatsbus_formats数组的长度;
  • bus_flags:它包含了有关总线上像素数据的附加信息,比如像素信号的极性。这些信息使用enum drm_bus_flags中定义的数值,其中包括了DRM_BUS_FLAGS_前缀;
  • max_tmds_clock:表示显示设备支持的TMDS时钟的最大速率,单位是千赫兹(kHz)。如果值为0,则表示未定义;
  • dvi_dual:是否支持双链DVI
  • is_hdmi:用于指示显示设备是否为HDMI设备,通过设置该字段来避免不必要地调用drm_detect_hdmi_monitor函数;
  • has_hdmi_infoframe:显示设备是否支持HDMI信息帧;
  • rgb_quant_range_selectable:表示显示设备是否支持选择RGB量化范围;
  • edid_hdmi_rgb444_dc_modes:用于表示支持的HDMI深色模式的位掩码,适用于RGB 4:4:4格式;
  • edid_hdmi_ycbcr444_dc_modes:用于表示支持的HDMI深色模式的位掩码,适用于YCbCr 4:4:4格式;
  • cea_rev:在HDMI接口中,CEA规范定义了一系列的视频和音频格式;该字段表示HDMI显示设备支持的CEA规范版本;
  • hdmiHDMI显示设备支持的高级功能;
  • non_desktop:表示非桌面显示;
  • monitor_range:存储从edid的``Detailed Monitor Descriptor`解析出的面板支持的频率范围;
  • luminance_range:存储从edid的``static hdr metadata.`解析出的面板支持的亮度范围;
  • mso_stream_counteDP Multi-SST Operation(MSO)流计数是从DisplayID VESA供应商块中获取的信息。它用于指示eDP(Embedded DisplayPort)接口支持的传输模式;
    • eDP接口使用传统的单流传输(SST)时,MSO流计数为0;
    • eDP接口支持多流操作(MSO)时,MSO流计数可以是2或4;
  • mso_pixel_overlap
  • max_dsc_bpp:最大DSC目标比特率;
  • vicsArray of vics_len VICs. Internal to EDID parsing
  • vics_lenNumber of elements in vics. Internal to EDID parsing
  • quirksEDID based quirks. Internal to EDID parsing.
3.1.2 struct drm_hdmi_info

linux内核使用struct drm_hdmi_info来表示当前连接的HDMI显示设备的信息,定义在include/drm/drm_connector.h:

/**
 * struct drm_hdmi_info - runtime information about the connected HDMI sink
 *
 * Describes if a given display supports advanced HDMI 2.0 features.
 * This information is available in CEA-861-F extension blocks (like HF-VSDB).
 */
struct drm_hdmi_info {
        /** @scdc: sink's scdc support and capabilities */
        struct drm_scdc scdc;

        /**
         * @y420_vdb_modes: bitmap of modes which can support ycbcr420
         * output only (not normal RGB/YCBCR444/422 outputs). The max VIC
         * defined by the CEA-861-G spec is 219, so the size is 256 bits to map
         * up to 256 VICs.
         */
        unsigned long y420_vdb_modes[BITS_TO_LONGS(256)];

        /**
         * @y420_cmdb_modes: bitmap of modes which can support ycbcr420
         * output also, along with normal HDMI outputs. The max VIC defined by
         * the CEA-861-G spec is 219, so the size is 256 bits to map up to 256
         * VICs.
         */
        unsigned long y420_cmdb_modes[BITS_TO_LONGS(256)];

        /** @y420_dc_modes: bitmap of deep color support index */
        u8 y420_dc_modes;

        /** @max_frl_rate_per_lane: support fixed rate link */
        u8 max_frl_rate_per_lane;

        /** @max_lanes: supported by sink */
        u8 max_lanes;

        /** @dsc_cap: DSC capabilities of the sink */
        struct drm_hdmi_dsc_cap dsc_cap;
};

3.2 操作函数

3.2.1 struct drm_connector_funcs

struct drm_connector_funcs用于描述connector的控制函数,定义在include/drm/drm_connector.h

点击查看代码
/**
 * struct drm_connector_funcs - control connectors on a given device
 *
 * Each CRTC may have one or more connectors attached to it.  The functions
 * below allow the core DRM code to control connectors, enumerate available modes,
 * etc.
 */
struct drm_connector_funcs {
        /**
         * @dpms:
         *
         * Legacy entry point to set the per-connector DPMS state. Legacy DPMS
         * is exposed as a standard property on the connector, but diverted to
         * this callback in the drm core. Note that atomic drivers don't
         * implement the 4 level DPMS support on the connector any more, but
         * instead only have an on/off "ACTIVE" property on the CRTC object.
         *
         * This hook is not used by atomic drivers, remapping of the legacy DPMS
         * property is entirely handled in the DRM core.
         *
         * RETURNS:
         *
         * 0 on success or a negative error code on failure.
         */
        int (*dpms)(struct drm_connector *connector, int mode);

        /**
         * @reset:
         *
         * Reset connector hardware and software state to off. This function isn't
         * called by the core directly, only through drm_mode_config_reset().
         * It's not a helper hook only for historical reasons.
         *
         * Atomic drivers can use drm_atomic_helper_connector_reset() to reset
         * atomic state using this hook.
         */
        void (*reset)(struct drm_connector *connector);

        /**
         * @detect:
         *
         * Check to see if anything is attached to the connector. The parameter
         * force is set to false whilst polling, true when checking the
         * connector due to a user request. force can be used by the driver to
         * avoid expensive, destructive operations during automated probing.
         *
         * This callback is optional, if not implemented the connector will be
         * considered as always being attached.
         *
         * FIXME:
         *
         * Note that this hook is only called by the probe helper. It's not in
         * the helper library vtable purely for historical reasons. The only DRM
         * core entry point to probe connector state is @fill_modes.
         *
         * Note that the helper library will already hold
         * &drm_mode_config.connection_mutex. Drivers which need to grab additional
         * locks to avoid races with concurrent modeset changes need to use
         * &drm_connector_helper_funcs.detect_ctx instead.
         *
         * Also note that this callback can be called no matter the
         * state the connector is in. Drivers that need the underlying
         * device to be powered to perform the detection will first need
         * to make sure it's been properly enabled.
         *
         * RETURNS:
         *
         * drm_connector_status indicating the connector's status.
         */
        enum drm_connector_status (*detect)(struct drm_connector *connector,
                                            bool force);

        /**
         * @force:
         *
         * This function is called to update internal encoder state when the
         * connector is forced to a certain state by userspace, either through
         * the sysfs interfaces or on the kernel cmdline. In that case the
         * @detect callback isn't called.
         *
         * FIXME:
         *
         * Note that this hook is only called by the probe helper. It's not in
         * the helper library vtable purely for historical reasons. The only DRM
         * core entry point to probe connector state is @fill_modes.
         */
        void (*force)(struct drm_connector *connector);

        /**
         * @fill_modes:
         *
         * Entry point for output detection and basic mode validation. The
         * driver should reprobe the output if needed (e.g. when hotplug
         * handling is unreliable), add all detected modes to &drm_connector.modes
         * and filter out any the device can't support in any configuration. It
         * also needs to filter out any modes wider or higher than the
         * parameters max_width and max_height indicate.
         *
         * The drivers must also prune any modes no longer valid from
         * &drm_connector.modes. Furthermore it must update
         * &drm_connector.status and &drm_connector.edid.  If no EDID has been
         * received for this output connector->edid must be NULL.
         *
         * Drivers using the probe helpers should use
         * drm_helper_probe_single_connector_modes() to implement this
         * function.
         *
         * RETURNS:
         *
         * The number of modes detected and filled into &drm_connector.modes.
         */
        int (*fill_modes)(struct drm_connector *connector, uint32_t max_width, uint32_t max_height);

        /**
         * @set_property:
         *
         * This is the legacy entry point to update a property attached to the
         * connector.
         *
         * This callback is optional if the driver does not support any legacy
         * driver-private properties. For atomic drivers it is not used because
         * property handling is done entirely in the DRM core.
         *
         * RETURNS:
         *
         * 0 on success or a negative error code on failure.
         */
        int (*set_property)(struct drm_connector *connector, struct drm_property *property,
                             uint64_t val);

        /**
         * @late_register:
         *
         * This optional hook can be used to register additional userspace
         * interfaces attached to the connector, light backlight control, i2c,
         * DP aux or similar interfaces. It is called late in the driver load
         * sequence from drm_connector_register() when registering all the
         * core drm connector interfaces. Everything added from this callback
         * should be unregistered in the early_unregister callback.
         * should be unregistered in the early_unregister callback.
         *
         * This is called while holding &drm_connector.mutex.
         *
         * Returns:
         *
         * 0 on success, or a negative error code on failure.
         */
        int (*late_register)(struct drm_connector *connector);

        /**
         * @early_unregister:
         *
         * This optional hook should be used to unregister the additional
         * userspace interfaces attached to the connector from
         * late_register(). It is called from drm_connector_unregister(),
         * early in the driver unload sequence to disable userspace access
         * before data structures are torndown.
         *
         * This is called while holding &drm_connector.mutex.
         */
        void (*early_unregister)(struct drm_connector *connector);

        /**
         * @destroy:
         *
         * Clean up connector resources. This is called at driver unload time
         * through drm_mode_config_cleanup(). It can also be called at runtime
         * when a connector is being hot-unplugged for drivers that support
         * connector hotplugging (e.g. DisplayPort MST).
         */
        void (*destroy)(struct drm_connector *connector);

        /**
         * @atomic_duplicate_state:
         *
         * Duplicate the current atomic state for this connector and return it.
         * The core and helpers guarantee that any atomic state duplicated with
         * this hook and still owned by the caller (i.e. not transferred to the
         * driver by calling &drm_mode_config_funcs.atomic_commit) will be
         * cleaned up by calling the @atomic_destroy_state hook in this
         * structure.
         *
         * This callback is mandatory for atomic drivers.
         *
         * Atomic drivers which don't subclass &struct drm_connector_state should use
         * drm_atomic_helper_connector_duplicate_state(). Drivers that subclass the
         * state structure to extend it with driver-private state should use
         * __drm_atomic_helper_connector_duplicate_state() to make sure shared state is
         * duplicated in a consistent fashion across drivers.
         *
         * It is an error to call this hook before &drm_connector.state has been
         * initialized correctly.
         *
         * NOTE:
         *
         * If the duplicate state references refcounted resources this hook must
         * acquire a reference for each of them. The driver must release these
         * references again in @atomic_destroy_state.
         *
         * RETURNS:
         *
         * Duplicated atomic state or NULL when the allocation failed.
         */
        struct drm_connector_state *(*atomic_duplicate_state)(struct drm_connector *connector);

        /**
         * @atomic_destroy_state:
         *
         * Destroy a state duplicated with @atomic_duplicate_state and release
         * or unreference all resources it references
         *
         * This callback is mandatory for atomic drivers.
         */
        void (*atomic_destroy_state)(struct drm_connector *connector,
                                     struct drm_connector_state *state);

        /**
         * @atomic_set_property:
         *
         * Decode a driver-private property value and store the decoded value
         * into the passed-in state structure. Since the atomic core decodes all
         * standardized properties (even for extensions beyond the core set of
         * properties which might not be implemented by all drivers) this
         * requires drivers to subclass the state structure.
         *
         * Such driver-private properties should really only be implemented for
         * truly hardware/vendor specific state. Instead it is preferred to
         * standardize atomic extension and decode the properties used to expose
         * such an extension in the core.
         *
         * Do not call this function directly, use
         * drm_atomic_connector_set_property() instead.
         *
         * This callback is optional if the driver does not support any
         * driver-private atomic properties.
         *
         * NOTE:
         *
         * This function is called in the state assembly phase of atomic
         * modesets, which can be aborted for any reason (including on
         * userspace's request to just check whether a configuration would be
         * possible). Drivers MUST NOT touch any persistent state (hardware or
         * software) or data structures except the passed in @state parameter.
         *
         * Also since userspace controls in which order properties are set this
         * function must not do any input validation (since the state update is
         * incomplete and hence likely inconsistent). Instead any such input
         * validation must be done in the various atomic_check callbacks.
         *
         * RETURNS:
         *
         * 0 if the property has been found, -EINVAL if the property isn't
         * implemented by the driver (which shouldn't ever happen, the core only
         * asks for properties attached to this connector). No other validation
         * is allowed by the driver. The core already checks that the property
         * value is within the range (integer, valid enum value, ...) the driver
         * set when registering the property.
         */
        int (*atomic_set_property)(struct drm_connector *connector,
                                   struct drm_connector_state *state,
                                   struct drm_property *property,
                                   uint64_t val);

        /**
         * @atomic_get_property:
         *
         * Reads out the decoded driver-private property. This is used to
         * implement the GETCONNECTOR IOCTL.
         *
         * Do not call this function directly, use
         * drm_atomic_connector_get_property() instead.
         *
         * This callback is optional if the driver does not support any
         * driver-private atomic properties.
         *
         * RETURNS:
         *
         * 0 on success, -EINVAL if the property isn't implemented by the
         * driver (which shouldn't ever happen, the core only asks for
         * properties attached to this connector).
         */
        int (*atomic_get_property)(struct drm_connector *connector,
                                   const struct drm_connector_state *state,
                                   struct drm_property *property,
                                   uint64_t *val);

        /**
         * @atomic_print_state:
         *
         * If driver subclasses &struct drm_connector_state, it should implement
         * this optional hook for printing additional driver specific state.
         *
         * Do not call this directly, use drm_atomic_connector_print_state()
         * instead.
         */
        void (*atomic_print_state)(struct drm_printer *p,
                                   const struct drm_connector_state *state);

        /**
         * @oob_hotplug_event:
         *
         * This will get called when a hotplug-event for a drm-connector
         * has been received from a source outside the display driver / device.
         */
        void (*oob_hotplug_event)(struct drm_connector *connector);

        /**
         * @debugfs_init:
         *
         * Allows connectors to create connector-specific debugfs files.
         */
        void (*debugfs_init)(struct drm_connector *connector, struct dentry *root);
};
3.2.2 struct drm_connector_helper_funcs

struct drm_connector_helper_funcs定义了一些常用的connector辅助操作函数,定义在include/drm/drm_modeset_helper_vtables.h

点击查看代码
/**
 * struct drm_connector_helper_funcs - helper operations for connectors
 *
 * These functions are used by the atomic and legacy modeset helpers and by the
 * probe helpers.
 */
struct drm_connector_helper_funcs {
        /**
         * @get_modes:
         *
         * This function should fill in all modes currently valid for the sink
         * into the &drm_connector.probed_modes list. It should also update the
         * EDID property by calling drm_connector_update_edid_property().
         *
         * The usual way to implement this is to cache the EDID retrieved in the
         * probe callback somewhere in the driver-private connector structure.
         * In this function drivers then parse the modes in the EDID and add
         * them by calling drm_add_edid_modes(). But connectors that drive a
         * fixed panel can also manually add specific modes using
         * drm_mode_probed_add(). Drivers which manually add modes should also
         * make sure that the &drm_connector.display_info,
         * &drm_connector.width_mm and &drm_connector.height_mm fields are
         * filled in.
         *
         * Note that the caller function will automatically add standard VESA
         * DMT modes up to 1024x768 if the .get_modes() helper operation returns
         * no mode and if the connector status is connector_status_connected or
         * connector_status_unknown. There is no need to call
         * drm_add_modes_noedid() manually in that case.
         *
         * Virtual drivers that just want some standard VESA mode with a given
         * resolution can call drm_add_modes_noedid(), and mark the preferred
         * one using drm_set_preferred_mode().
         *
         * This function is only called after the @detect hook has indicated
         * that a sink is connected and when the EDID isn't overridden through
         * sysfs or the kernel commandline.
         *
         * This callback is used by the probe helpers in e.g.
         * drm_helper_probe_single_connector_modes().
         *
         * To avoid races with concurrent connector state updates, the helper
         * libraries always call this with the &drm_mode_config.connection_mutex
         * held. Because of this it's safe to inspect &drm_connector->state.
         *
         * RETURNS:
         *
         * The number of modes added by calling drm_mode_probed_add().
         */
        int (*get_modes)(struct drm_connector *connector);

        /**
         * @detect_ctx:
         *
         * Check to see if anything is attached to the connector. The parameter
         * force is set to false whilst polling, true when checking the
         * connector due to a user request. force can be used by the driver to
         * avoid expensive, destructive operations during automated probing.
         *
         * This callback is optional, if not implemented the connector will be
         * considered as always being attached.
         *
         * This is the atomic version of &drm_connector_funcs.detect.
         *
         * To avoid races against concurrent connector state updates, the
         * helper libraries always call this with ctx set to a valid context,
         * and &drm_mode_config.connection_mutex will always be locked with
         * the ctx parameter set to this ctx. This allows taking additional
         * locks as required.
         *
         *
         * RETURNS:
         *
         * &drm_connector_status indicating the connector's status,
         * or the error code returned by drm_modeset_lock(), -EDEADLK.
         */
        int (*detect_ctx)(struct drm_connector *connector,
                          struct drm_modeset_acquire_ctx *ctx,
                          bool force);

        /**
         * @mode_valid:
         *
         * Callback to validate a mode for a connector, irrespective of the
         * specific display configuration.
         *
         * This callback is used by the probe helpers to filter the mode list
         * (which is usually derived from the EDID data block from the sink).
         * See e.g. drm_helper_probe_single_connector_modes().
         *
         * This function is optional.
         *
         * NOTE:
         *
         * This only filters the mode list supplied to userspace in the
         * GETCONNECTOR IOCTL. Compared to &drm_encoder_helper_funcs.mode_valid,
         * &drm_crtc_helper_funcs.mode_valid and &drm_bridge_funcs.mode_valid,
         * which are also called by the atomic helpers from
         * drm_atomic_helper_check_modeset(). This allows userspace to force and
         * ignore sink constraint (like the pixel clock limits in the screen's
         * EDID), which is useful for e.g. testing, or working around a broken
         * EDID. Any source hardware constraint (which always need to be
         * enforced) therefore should be checked in one of the above callbacks,
         * and not this one here.
         *
         * To avoid races with concurrent connector state updates, the helper
         * libraries always call this with the &drm_mode_config.connection_mutex
         * held. Because of this it's safe to inspect &drm_connector->state.
         *
         * RETURNS:
         *
         * Either &drm_mode_status.MODE_OK or one of the failure reasons in &enum
         * drm_mode_status.
         */
        enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
                                           struct drm_display_mode *mode);

        /**
         * @mode_valid_ctx:
         *
         * Callback to validate a mode for a connector, irrespective of the
         * specific display configuration.
         *
         * This callback is used by the probe helpers to filter the mode list
         * (which is usually derived from the EDID data block from the sink).
         * See e.g. drm_helper_probe_single_connector_modes().
         *
         * This function is optional, and is the atomic version of
         * &drm_connector_helper_funcs.mode_valid.
         *
         * To allow for accessing the atomic state of modesetting objects, the
         * helper libraries always call this with ctx set to a valid context,
         * and &drm_mode_config.connection_mutex will always be locked with
         * the ctx parameter set to @ctx. This allows for taking additional
         * locks as required.
         *
         * Even though additional locks may be acquired, this callback is
         * still expected not to take any constraints into account which would
         * be influenced by the currently set display state - such constraints
         * should be handled in the driver's atomic check. For example, if a
         * connector shares display bandwidth with other connectors then it
         * would be ok to validate the minimum bandwidth requirement of a mode
         * against the maximum possible bandwidth of the connector. But it
         * wouldn't be ok to take the current bandwidth usage of other
         * connectors into account, as this would change depending on the
         * display state.
         *
         * Returns:
         * 0 if &drm_connector_helper_funcs.mode_valid_ctx succeeded and wrote
         * the &enum drm_mode_status value to @status, or a negative error
         * code otherwise.
         *
         */
        int (*mode_valid_ctx)(struct drm_connector *connector,
                              struct drm_display_mode *mode,
                              struct drm_modeset_acquire_ctx *ctx,
                              enum drm_mode_status *status);

        /**
         * @best_encoder:
         *
         * This function should select the best encoder for the given connector.
         *
         * This function is used by both the atomic helpers (in the
         * drm_atomic_helper_check_modeset() function) and in the legacy CRTC
         * helpers.
         *
         * NOTE:
         *
         * In atomic drivers this function is called in the check phase of an
         * atomic update. The driver is not allowed to change or inspect
         * anything outside of arguments passed-in. Atomic drivers which need to
         * inspect dynamic configuration state should instead use
         * @atomic_best_encoder.
         *
         * You can leave this function to NULL if the connector is only
         * attached to a single encoder. In this case, the core will call
         * drm_connector_get_single_encoder() for you.
         *
         * RETURNS:
         *
         * Encoder that should be used for the given connector and connector
         * state, or NULL if no suitable encoder exists. Note that the helpers
         * will ensure that encoders aren't used twice, drivers should not check
         * for this.
         */
        struct drm_encoder *(*best_encoder)(struct drm_connector *connector);
        /**
         * @atomic_best_encoder:
         *
         * This is the atomic version of @best_encoder for atomic drivers which
         * need to select the best encoder depending upon the desired
         * configuration and can't select it statically.
         *
         * This function is used by drm_atomic_helper_check_modeset().
         * If it is not implemented, the core will fallback to @best_encoder
         * (or drm_connector_get_single_encoder() if @best_encoder is NULL).
         *
         * NOTE:
         *
         * This function is called in the check phase of an atomic update. The
         * driver is not allowed to change anything outside of the
         * &drm_atomic_state update tracking structure passed in.
         *
         * RETURNS:
         *
         * Encoder that should be used for the given connector and connector
         * state, or NULL if no suitable encoder exists. Note that the helpers
         * will ensure that encoders aren't used twice, drivers should not check
         * for this.
         */
        struct drm_encoder *(*atomic_best_encoder)(struct drm_connector *connector,
                                                   struct drm_atomic_state *state);

        /**
         * @atomic_check:
         *
         * This hook is used to validate connector state. This function is
         * called from &drm_atomic_helper_check_modeset, and is called when
         * a connector property is set, or a modeset on the crtc is forced.
         *
         * Because &drm_atomic_helper_check_modeset may be called multiple times,
         * this function should handle being called multiple times as well.
         *
         * This function is also allowed to inspect any other object's state and
         * can add more state objects to the atomic commit if needed. Care must
         * be taken though to ensure that state check and compute functions for
         * these added states are all called, and derived state in other objects
         * all updated. Again the recommendation is to just call check helpers
         * until a maximal configuration is reached.
         *
         * NOTE:
         *
         * This function is called in the check phase of an atomic update. The
         * driver is not allowed to change anything outside of the free-standing
         * state objects passed-in or assembled in the overall &drm_atomic_state
         * update tracking structure.
         *
         * RETURNS:
         *
         * 0 on success, -EINVAL if the state or the transition can't be
         * supported, -ENOMEM on memory allocation failure and -EDEADLK if an
         * attempt to obtain another state object ran into a &drm_modeset_lock
         * deadlock.
         */
        int (*atomic_check)(struct drm_connector *connector,
                            struct drm_atomic_state *state);
        /**
         * @atomic_commit:
         *
         * This hook is to be used by drivers implementing writeback connectors
         * that need a point when to commit the writeback job to the hardware.
         * The writeback_job to commit is available in the new connector state,
         * in &drm_connector_state.writeback_job.
         *
         * This hook is optional.
         *
         * This callback is used by the atomic modeset helpers.
         */
        void (*atomic_commit)(struct drm_connector *connector,
                              struct drm_atomic_state *state);

        /**
         * @prepare_writeback_job:
         *
         * As writeback jobs contain a framebuffer, drivers may need to
         * prepare and clean them up the same way they can prepare and
         * clean up framebuffers for planes. This optional connector operation
         * is used to support the preparation of writeback jobs. The job
         * prepare operation is called from drm_atomic_helper_prepare_planes()
         * for struct &drm_writeback_connector connectors only.
         *
         * This operation is optional.
         *
         * This callback is used by the atomic modeset helpers.
         */
        int (*prepare_writeback_job)(struct drm_writeback_connector *connector,
                                     struct drm_writeback_job *job);
        /**
         * @cleanup_writeback_job:
         *
         * This optional connector operation is used to support the
         * cleanup of writeback jobs. The job cleanup operation is called
         * from the existing drm_writeback_cleanup_job() function, invoked
         * both when destroying the job as part of an aborted commit, or when
         * the job completes.
         *
         * This operation is optional.
         *
         * This callback is used by the atomic modeset helpers.
         */
        void (*cleanup_writeback_job)(struct drm_writeback_connector *connector,
                                      struct drm_writeback_job *job);

        /**
         * @enable_hpd:
         *
         * Enable hot-plug detection for the connector.
         *
         * This operation is optional.
         *
         * This callback is used by the drm_kms_helper_poll_enable() helpers.
         */
        void (*enable_hpd)(struct drm_connector *connector);

        /**
         * @disable_hpd:
         *
         * Disable hot-plug detection for the connector.
         *
         * This operation is optional.
         *
         * This callback is used by the drm_kms_helper_poll_disable() helpers.
         */
        void (*disable_hpd)(struct drm_connector *connector);
};

四、encoder 核心API

4.1 encoder初始化

drm_encoder_init函数用于初始化encoder对象,定义在drivers/gpu/drm/drm_encoder.c

/**
 * drm_encoder_init - Init a preallocated encoder
 * @dev: drm device
 * @encoder: the encoder to init
 * @funcs: callbacks for this encoder
 * @encoder_type: user visible type of the encoder
 * @name: printf style format string for the encoder name, or NULL for default name
 *
 * Initializes a preallocated encoder. Encoder should be subclassed as part of
 * driver encoder objects. At driver unload time the driver's
 * &drm_encoder_funcs.destroy hook should call drm_encoder_cleanup() and kfree()
 * the encoder structure. The encoder structure should not be allocated with
 * devm_kzalloc().
 *
 * Note: consider using drmm_encoder_alloc() or drmm_encoder_init()
 * instead of drm_encoder_init() to let the DRM managed resource
 * infrastructure take care of cleanup and deallocation.
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int drm_encoder_init(struct drm_device *dev,
                     struct drm_encoder *encoder,
                     const struct drm_encoder_funcs *funcs,
                     int encoder_type, const char *name, ...)
{
        va_list ap;
        int ret;

        WARN_ON(!funcs->destroy);

        va_start(ap, name);
        ret = __drm_encoder_init(dev, encoder, funcs, encoder_type, name, ap);
        va_end(ap);

        return ret;
}

具体实现位于函数__drm_encoder_init

__printf(5, 0)
static int __drm_encoder_init(struct drm_device *dev,
                              struct drm_encoder *encoder,
                              const struct drm_encoder_funcs *funcs,
                              int encoder_type, const char *name, va_list ap)
{
        int ret;

        /* encoder index is used with 32bit bitmasks */
        if (WARN_ON(dev->mode_config.num_encoder >= 32))
                return -EINVAL;

        ret = drm_mode_object_add(dev, &encoder->base, DRM_MODE_OBJECT_ENCODER);
        if (ret)
                return ret;

        encoder->dev = dev;
        encoder->encoder_type = encoder_type;
        encoder->funcs = funcs;
        if (name) {
                encoder->name = kvasprintf(GFP_KERNEL, name, ap);
        } else {
                encoder->name = kasprintf(GFP_KERNEL, "%s-%d",
                                          drm_encoder_enum_list[encoder_type].name,
                                          encoder->base.id);
        }
        if (!encoder->name) {
                ret = -ENOMEM;
                goto out_put;
        }

        INIT_LIST_HEAD(&encoder->bridge_chain);
        list_add_tail(&encoder->head, &dev->mode_config.encoder_list);
        encoder->index = dev->mode_config.num_encoder++;

out_put:
        if (ret)
                drm_mode_object_unregister(dev, &encoder->base);

        return ret;
}

4.2 encoder注册

drm_encoder_register_all函数用于初始化encoder对象,定义在drivers/gpu/drm/drm_encoder.c

int drm_encoder_register_all(struct drm_device *dev)
{
        struct drm_encoder *encoder;
        int ret = 0;

        drm_for_each_encoder(encoder, dev) {
                if (encoder->funcs && encoder->funcs->late_register)
                        ret = encoder->funcs->late_register(encoder);
                if (ret)
                        return ret;
        }

        return 0;
}

这里直接遍历drm_device->mode_configencoder_list链表,依次调用encoder->funcs->late_register

五、encoder 核心API

5.1 connector初始化

drm_connector_init函数用于初始化connector对象,定义在drivers/gpu/drm/drm_connector.c

/**
 * drm_connector_init - Init a preallocated connector
 * @dev: DRM device
 * @connector: the connector to init
 * @funcs: callbacks for this connector
 * @connector_type: user visible type of the connector
 *
 * Initialises a preallocated connector. Connectors should be
 * subclassed as part of driver connector objects.
 *
 * At driver unload time the driver's &drm_connector_funcs.destroy hook
 * should call drm_connector_cleanup() and free the connector structure.
 * The connector structure should not be allocated with devm_kzalloc().
 *
 * Note: consider using drmm_connector_init() instead of
 * drm_connector_init() to let the DRM managed resource infrastructure
 * take care of cleanup and deallocation.
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int drm_connector_init(struct drm_device *dev,
                       struct drm_connector *connector,
                       const struct drm_connector_funcs *funcs,
                       int connector_type)
{
        if (drm_WARN_ON(dev, !(funcs && funcs->destroy)))
                return -EINVAL;

        return __drm_connector_init(dev, connector, funcs, connector_type, NULL);
}

具体实现位于函数__drm_connector_init

static int __drm_connector_init(struct drm_device *dev,
                                struct drm_connector *connector,
                                const struct drm_connector_funcs *funcs,
                                int connector_type,
                                struct i2c_adapter *ddc)
{
        struct drm_mode_config *config = &dev->mode_config;
        int ret;
        struct ida *connector_ida =
                &drm_connector_enum_list[connector_type].ida;

        WARN_ON(drm_drv_uses_atomic_modeset(dev) &&
                (!funcs->atomic_destroy_state ||
                 !funcs->atomic_duplicate_state));

        ret = __drm_mode_object_add(dev, &connector->base,
                                    DRM_MODE_OBJECT_CONNECTOR,
                                    false, drm_connector_free);
        if (ret)
                return ret;

        connector->base.properties = &connector->properties;
        connector->dev = dev;
        connector->funcs = funcs;

        /* connector index is used with 32bit bitmasks */
        ret = ida_alloc_max(&config->connector_ida, 31, GFP_KERNEL);
        if (ret < 0) {
                DRM_DEBUG_KMS("Failed to allocate %s connector index: %d\n",
                              drm_connector_enum_list[connector_type].name,
                              ret);
                goto out_put;
        }
        connector->index = ret;
        ret = 0;

        connector->connector_type = connector_type;
        connector->connector_type_id =
                ida_alloc_min(connector_ida, 1, GFP_KERNEL);
        if (connector->connector_type_id < 0) {
                ret = connector->connector_type_id;
                goto out_put_id;
        }
        connector->name =
                kasprintf(GFP_KERNEL, "%s-%d",
                          drm_connector_enum_list[connector_type].name,
                          connector->connector_type_id);
        if (!connector->name) {
                ret = -ENOMEM;
                goto out_put_type_id;
        }

        /* provide ddc symlink in sysfs */
        connector->ddc = ddc;

        INIT_LIST_HEAD(&connector->global_connector_list_entry);
        INIT_LIST_HEAD(&connector->probed_modes);
        INIT_LIST_HEAD(&connector->modes);
        mutex_init(&connector->mutex);
        mutex_init(&connector->edid_override_mutex);
        connector->edid_blob_ptr = NULL;
        connector->epoch_counter = 0;
        connector->tile_blob_ptr = NULL;
        connector->status = connector_status_unknown;
        connector->display_info.panel_orientation =
                DRM_MODE_PANEL_ORIENTATION_UNKNOWN;

        drm_connector_get_cmdline_mode(connector);

        /* We should add connectors at the end to avoid upsetting the connector
         * index too much.
         */
        spin_lock_irq(&config->connector_list_lock);
        list_add_tail(&connector->head, &config->connector_list);
        config->num_connector++;
        spin_unlock_irq(&config->connector_list_lock);

        if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
            connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
                drm_connector_attach_edid_property(connector);

        drm_object_attach_property(&connector->base,
                                      config->dpms_property, 0);

        drm_object_attach_property(&connector->base,
                                   config->link_status_property,
                                   0);

        drm_object_attach_property(&connector->base,
                                   config->non_desktop_property,
                                   0);
        drm_object_attach_property(&connector->base,
                                   config->tile_property,
                                   0);

        if (drm_core_check_feature(dev, DRIVER_ATOMIC)) {
                drm_object_attach_property(&connector->base, config->prop_crtc_id, 0);
        }

        connector->debugfs_entry = NULL;
out_put_type_id:
        if (ret)
                ida_free(connector_ida, connector->connector_type_id);
out_put_id:
        if (ret)
                ida_free(&config->connector_ida, connector->index);
out_put:
        if (ret)
                drm_mode_object_unregister(dev, &connector->base);

        return ret;
}

5.2 connector注册

drm_connector_register_all函数用于初始化connector对象,定义在drivers/gpu/drm/drm_connector.c

int drm_connector_register_all(struct drm_device *dev)
{
        struct drm_connector *connector;
        struct drm_connector_list_iter conn_iter;
        int ret = 0;

        drm_connector_list_iter_begin(dev, &conn_iter);
        drm_for_each_connector_iter(connector, &conn_iter) {
                ret = drm_connector_register(connector);
                if (ret)
                        break;
        }
        drm_connector_list_iter_end(&conn_iter);

        if (ret)
                drm_connector_unregister_all(dev);
        return ret;
}

这里直接遍历drm_device->mode_configconnector_list链表,依次调用drm_connector_register注册每一个connector

/**
 * drm_connector_register - register a connector
 * @connector: the connector to register
 *
 * Register userspace interfaces for a connector. Only call this for connectors
 * which can be hotplugged after drm_dev_register() has been called already,
 * e.g. DP MST connectors. All other connectors will be registered automatically
 * when calling drm_dev_register().
 *
 * When the connector is no longer available, callers must call
 * drm_connector_unregister().
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int drm_connector_register(struct drm_connector *connector)
{
        int ret = 0;
			
    	// 检查设备是否已经注册,如果没有注册则直接返回成功
        if (!connector->dev->registered)
                return 0;

    	// 互斥锁
        mutex_lock(&connector->mutex);
    
        // 检查connector的注册状态,如果不是正在初始化状态则解锁并返回
        if (connector->registration_state != DRM_CONNECTOR_INITIALIZING)
                goto unlock;

    	// 1. 关于DRM子系统中的sysfs接口,比如在sysfs创建/sys/class/drm/card0-HDMI-A-1
        ret = drm_sysfs_connector_add(connector);
        if (ret)
                goto unlock;
		
    	// 2. 关于DRM子系统中的sysfs接口,比如在debugfs创建/sys/kernel/debug/dri/0/HDMI-A-1
        drm_debugfs_connector_add(connector);

    	// 3. 如果定义了late_register函数,回调该函数
        if (connector->funcs->late_register) {
                ret = connector->funcs->late_register(connector);
                if (ret)
                        goto err_debugfs;
        }

    	// 通过connector->dev获取到对应的drm_device,然后注册connector这个modeset object
        drm_mode_object_register(connector->dev, &connector->base);
	
    	// 更新注册状态
        connector->registration_state = DRM_CONNECTOR_REGISTERED;

        /* Let userspace know we have a new connector */
        drm_sysfs_connector_hotplug_event(connector);

        if (connector->privacy_screen)
                drm_privacy_screen_register_notifier(connector->privacy_screen,
                                           &connector->privacy_screen_notifier);

        mutex_lock(&connector_list_lock);
    	// 将当前connector添加到connector_list链表
        list_add_tail(&connector->global_connector_list_entry, &connector_list);
        mutex_unlock(&connector_list_lock);
        goto unlock;

err_debugfs:
        drm_debugfs_connector_remove(connector);
        drm_sysfs_connector_remove(connector);
unlock:
        mutex_unlock(&connector->mutex);
        return ret;
}
5.2.1 drm_sysfs_connector_add

drm_sysfs_connector_add函数位于drivers/gpu/drm/drm_sysfs.c,这段代码是关于DRM子系统中的sysfs接口的实现。主要作用是在sysfs中添加connector的相关信息。

static DEVICE_ATTR_RW(status);
static DEVICE_ATTR_RO(enabled);
static DEVICE_ATTR_RO(dpms);
static DEVICE_ATTR_RO(modes);

static struct attribute *connector_dev_attrs[] = {
        &dev_attr_status.attr,      
        &dev_attr_enabled.attr,
        &dev_attr_dpms.attr,
        &dev_attr_modes.attr,
        NULL
};

// edid文件属性
static struct bin_attribute edid_attr = {
        .attr.name = "edid",
        .attr.mode = 0444,
        .size = 0,
        .read = edid_show,
};

static struct bin_attribute *connector_bin_attrs[] = {
        &edid_attr,
        NULL
};

static const struct attribute_group connector_dev_group = {
        .attrs = connector_dev_attrs,
        .bin_attrs = connector_bin_attrs,
};

static const struct attribute_group *connector_dev_groups[] = {
        &connector_dev_group,
        NULL
};

int drm_sysfs_connector_add(struct drm_connector *connector)
{
        struct drm_device *dev = connector->dev;
        struct device *kdev;
        int r;

        if (connector->kdev)
                return 0;
		// 动态分配内存,指向struct device
        kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
        if (!kdev)
                return -ENOMEM;

    	// 初始化设备,这个函数是device_register函数的前半部分的实现,主要用于设备的初始化
        device_initialize(kdev);
	    // 设置设备class
        kdev->class = drm_class;
        kdev->type = &drm_sysfs_device_connector;
    	// 设置父设备,一般为显卡设备card0,位于/sys/devices/platform/display-subsystem/drm/card0/
        kdev->parent = dev->primary->kdev;
        kdev->groups = connector_dev_groups;
        kdev->release = drm_sysfs_release;
    	// 设置驱动私有数据
        dev_set_drvdata(kdev, connector);
		
    	// 设置设备名称 比如:card0-HDMI-A-1
        r = dev_set_name(kdev, "card%d-%s", dev->primary->index, connector->name);
        if (r)
                goto err_free;

        DRM_DEBUG("adding \"%s\" to sysfs\n",
                  connector->name);

    	// 在/sys/class/drm/创建card%d-%s文件,由于没有设置主设备号,并不会创建设备节点/dev/drm/card%d-%s
        r = device_add(kdev);
        if (r) {
                drm_err(dev, "failed to register connector device: %d\n", r);
                goto err_free;
        }

        connector->kdev = kdev;

    	// 连接器如果指定了ddc通道
        if (connector->ddc)
            	// 创建ddc链接文件
                return sysfs_create_link(&connector->kdev->kobj,
                                 &connector->ddc->dev.kobj, "ddc");
        return 0;

err_free:
        put_device(kdev);
        return r;
}

该函数:

  • 首先调用 device_initialize 函数对 kdev 进行初始化设置,包括设置设备的类别、类型、父设备等信息,以及设置驱动私有数据;
  • 然后会调用 dev_set_name 函数为 kdev 设置设备名称;
  • 之后会调用 device_add 函数将 kdev 添加到sysfs中,并在/sys/class/drm/创建card%d-%s文件;
  • 如果添加成功,则将 kdev 对象赋值给连接器的 kdev 成员变量。

整个过程的作用是将connector的属性添加到sysfs中,使用户空间能够访问和控制connector

HDMI驱动为例,其会完成connector以及encoder的初始化和注册,因此我们可以在sysfs文件系统看到:

root@rk3399:~# ll /sys/class/drm
lrwxrwxrwx  1 root root    0 Mar 15 23:04 card0 -> ../../devices/platform/display-subsystem/drm/card0/
lrwxrwxrwx  1 root root    0 Mar 15 23:04 card0-HDMI-A-1 -> ../../devices/platform/display-subsystem/drm/card0/card0-HDMI-A-1/
lrwxrwxrwx  1 root root    0 Mar 15 23:04 card1 -> ../../devices/platform/ff9a0000.gpu/drm/card1/
lrwxrwxrwx  1 root root    0 Mar 15 23:04 renderD128 -> ../../devices/platform/ff9a0000.gpu/drm/renderD128/
-r--r--r--  1 root root 4096 Mar 15 23:04 version

其中card0-HDMI-A-1代表的是hdmi显示设备,由于这是一个链接文件,查看card0-HDMI-A-1目录结构;

root@rk3399:~# ll /sys/class/drm/card0-HDMI-A-1/
lrwxrwxrwx 1 root root    0 Mar 15 23:14 ddc -> ../../../../ff160000.i2c/i2c-7/
lrwxrwxrwx 1 root root    0 Mar 15 23:14 device -> ../../card0/
-r--r--r-- 1 root root 4096 Mar 15 23:14 dpms
-r--r--r-- 1 root root    0 Mar 15 23:14 edid
-r--r--r-- 1 root root 4096 Mar 15 23:04 enabled
-r--r--r-- 1 root root 4096 Mar 15 23:14 modes
drwxr-xr-x 2 root root    0 Mar 15 23:14 power/
-rw-r--r-- 1 root root 4096 Mar 15 23:04 status
lrwxrwxrwx 1 root root    0 Mar 15 23:04 subsystem -> ../../../../../../class/drm/
-rw-r--r-- 1 root root 4096 Mar 15 23:04 uevent

其中:

  • ddchdmi显示数据通道,指向i2c7,用来获取edidhdcp密钥等内容;
  • device:指向card0
  • edid:存储hdmi显示器的扩展显示标识数据;
  • enabledhdmi接口是否被启用或禁用;
  • modes:连接的hdmi显示器以及当前hdmi控制器同时支持的分辨率列表;
  • statushdmi接口连接状态的信息;
5.2.2 drm_debugfs_connector_add

drm_debugfs_connector_add函数位于drivers/gpu/drm/drm_debugfs.c,这段代码是关于DRM子系统中的debugfs接口的实现。主要作用是在debugfs中添加connector的调试文件,以便进行调试和诊断。

static const struct file_operations drm_edid_fops = {
        .owner = THIS_MODULE,
        .open = edid_open,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .write = edid_write
};


static const struct file_operations drm_connector_fops = {
        .owner = THIS_MODULE,
        .open = connector_open,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = single_release,
        .write = connector_write
};


void drm_debugfs_connector_add(struct drm_connector *connector)
{
    	// 获取drm_device中存储的primary
        struct drm_minor *minor = connector->dev->primary;
        struct dentry *root;

    	// 检查是否存在 debugfs_root
        if (!minor->debugfs_root)
                return;

    	// 在minor->debugfs_root根目录下(即/sys/kernel/debug/dri/%d)创建一个与connector名称对应的目录
        root = debugfs_create_dir(connector->name, minor->debugfs_root);
        connector->debugfs_entry = root;

        /* force */
        debugfs_create_file("force", 0644, root, connector,
                            &drm_connector_fops);

        /* edid */
        debugfs_create_file("edid_override", 0644, root, connector,
                            &drm_edid_fops);

        /* vrr range */
        debugfs_create_file("vrr_range", 0444, root, connector,
                            &vrr_range_fops);

        /* max bpc */
        debugfs_create_file("output_bpc", 0444, root, connector,
                            &output_bpc_fops);

        if (connector->funcs->debugfs_init)
                connector->funcs->debugfs_init(connector, root);
}

HDMI驱动为例,其会完成connector以及encoder的初始化和注册,因此我们可以在debugfs文件系统看到:

root@rk3399:~# ll /sys/kernel/debug/dri/
drwxr-xr-x  5 root root 0 Jan  1  1970 0/
drwxr-xr-x  2 root root 0 Jan  1  1970 1/
drwxr-xr-x  2 root root 0 Jan  1  1970 128/
root@rk3399:~# ll /sys/kernel/debug/dri/0/      
drwxr-xr-x 2 root root 0 Jan  1  1970 HDMI-A-1/
-r--r--r-- 1 root root 0 Jan  1  1970 clients
drwxr-xr-x 3 root root 0 Jan  1  1970 crtc-0/
drwxr-xr-x 3 root root 0 Jan  1  1970 crtc-1/
-r--r--r-- 1 root root 0 Jan  1  1970 framebuffer
-r--r--r-- 1 root root 0 Jan  1  1970 gem_names
-r--r--r-- 1 root root 0 Jan  1  1970 internal_clients
-r--r--r-- 1 root root 0 Jan  1  1970 name
-r--r--r-- 1 root root 0 Jan  1  1970 state
root@rk3399:~# ll /sys/kernel/debug/dri/0/HDMI-A-1/  
total 0
drwxr-xr-x 2 root root 0 Jan  1  1970 ./
drwxr-xr-x 5 root root 0 Jan  1  1970 ../
-rw-r--r-- 1 root root 0 Jan  1  1970 edid_override
-rw-r--r-- 1 root root 0 Jan  1  1970 force
-r--r--r-- 1 root root 0 Jan  1  1970 output_bpc
-r--r--r-- 1 root root 0 Jan  1  1970 vrr_range

其中/sys/kernel/debug/dri/0/HDMI-A-1/目录就是由drm_debugfs_connector_add创建完成的,其中包含了edid_overrideforceoutput_bpcvrr_range四个文件。

参考文章

[1] RK3399驱动开发 | 20 - 阿美林7寸mipi屏幕调试

[2] Linux MIPI DSI驱动开发 | 基于RK3399

[3] RK3399 LINUX-SDK MIPI屏幕驱动及调试

posted @ 2023-11-05 18:58  大奥特曼打小怪兽  阅读(1270)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步