diff options
Diffstat (limited to 'drivers/gpu/drm/ast/ast_dp.c')
-rw-r--r-- | drivers/gpu/drm/ast/ast_dp.c | 242 |
1 files changed, 229 insertions, 13 deletions
diff --git a/drivers/gpu/drm/ast/ast_dp.c b/drivers/gpu/drm/ast/ast_dp.c index ca022c287785..00b364f9a71e 100644 --- a/drivers/gpu/drm/ast/ast_dp.c +++ b/drivers/gpu/drm/ast/ast_dp.c @@ -4,32 +4,58 @@ #include <linux/firmware.h> #include <linux/delay.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + #include "ast_drv.h" -bool ast_astdp_is_connected(struct ast_device *ast) +static bool ast_astdp_is_connected(struct ast_device *ast) { if (!ast_get_index_reg_mask(ast, AST_IO_VGACRI, 0xDF, AST_IO_VGACRDF_HPD)) return false; return true; } -int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata) +static int ast_astdp_read_edid_block(void *data, u8 *buf, unsigned int block, size_t len) { - struct ast_device *ast = to_ast_device(dev); + struct ast_device *ast = data; + size_t rdlen = round_up(len, 4); int ret = 0; - u8 i; + unsigned int i; + + if (block > 0) + return -EIO; /* extension headers not supported */ + + /* + * Protect access to I/O registers from concurrent modesetting + * by acquiring the I/O-register lock. + */ + mutex_lock(&ast->modeset_lock); /* Start reading EDID data */ ast_set_index_reg_mask(ast, AST_IO_VGACRI, 0xe5, (u8)~AST_IO_VGACRE5_EDID_READ_DONE, 0x00); - for (i = 0; i < 32; i++) { + for (i = 0; i < rdlen; i += 4) { + unsigned int offset; unsigned int j; + u8 ediddata[4]; + u8 vgacre4; + + offset = (i + block * EDID_LENGTH) / 4; + if (offset >= 64) { + ret = -EIO; + goto out; + } + vgacre4 = offset; /* * CRE4[7:0]: Read-Pointer for EDID (Unit: 4bytes); valid range: 0~64 */ - ast_set_index_reg(ast, AST_IO_VGACRI, 0xe4, i); + ast_set_index_reg(ast, AST_IO_VGACRI, 0xe4, vgacre4); /* * CRD7[b0]: valid flag for EDID @@ -53,7 +79,7 @@ int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata) vgacrd7 = ast_get_index_reg(ast, AST_IO_VGACRI, 0xd7); if (vgacrd7 & AST_IO_VGACRD7_EDID_VALID_FLAG) { vgacrd6 = ast_get_index_reg(ast, AST_IO_VGACRI, 0xd6); - if (vgacrd6 == i) + if (vgacrd6 == offset) break; } } @@ -81,7 +107,8 @@ int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata) ediddata[2] = 0; } - ediddata += 4; + memcpy(buf, ediddata, min((len - i), 4)); + buf += 4; } out: @@ -89,6 +116,8 @@ out: ast_set_index_reg_mask(ast, AST_IO_VGACRI, 0xe5, (u8)~AST_IO_VGACRE5_EDID_READ_DONE, AST_IO_VGACRE5_EDID_READ_DONE); + mutex_unlock(&ast->modeset_lock); + return ret; } @@ -120,7 +149,7 @@ int ast_dp_launch(struct ast_device *ast) return 0; } -bool ast_dp_power_is_on(struct ast_device *ast) +static bool ast_dp_power_is_on(struct ast_device *ast) { u8 vgacre3; @@ -129,7 +158,7 @@ bool ast_dp_power_is_on(struct ast_device *ast) return !(vgacre3 & AST_DP_PHY_SLEEP); } -void ast_dp_power_on_off(struct drm_device *dev, bool on) +static void ast_dp_power_on_off(struct drm_device *dev, bool on) { struct ast_device *ast = to_ast_device(dev); // Read and Turn off DP PHY sleep @@ -141,9 +170,11 @@ void ast_dp_power_on_off(struct drm_device *dev, bool on) // DP Power on/off ast_set_index_reg_mask(ast, AST_IO_VGACRI, 0xE3, (u8) ~AST_DP_PHY_SLEEP, bE3); + + msleep(50); } -void ast_dp_link_training(struct ast_device *ast) +static void ast_dp_link_training(struct ast_device *ast) { struct drm_device *dev = &ast->base; int i; @@ -161,7 +192,7 @@ void ast_dp_link_training(struct ast_device *ast) drm_err(dev, "Link training failed\n"); } -void ast_dp_set_on_off(struct drm_device *dev, bool on) +static void ast_dp_set_on_off(struct drm_device *dev, bool on) { struct ast_device *ast = to_ast_device(dev); u8 video_on_off = on; @@ -180,7 +211,7 @@ void ast_dp_set_on_off(struct drm_device *dev, bool on) } } -void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mode) +static void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mode) { struct ast_device *ast = to_ast_device(crtc->dev); @@ -253,3 +284,188 @@ void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mo ast_set_index_reg_mask(ast, AST_IO_VGACRI, 0xE1, ASTDP_AND_CLEAR_MASK, ASTDP_MISC1); ast_set_index_reg_mask(ast, AST_IO_VGACRI, 0xE2, ASTDP_AND_CLEAR_MASK, ModeIdx); } + +static void ast_wait_for_vretrace(struct ast_device *ast) +{ + unsigned long timeout = jiffies + HZ; + u8 vgair1; + + do { + vgair1 = ast_io_read8(ast, AST_IO_VGAIR1_R); + } while (!(vgair1 & AST_IO_VGAIR1_VREFRESH) && time_before(jiffies, timeout)); +} + +/* + * Encoder + */ + +static const struct drm_encoder_funcs ast_astdp_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static void ast_astdp_encoder_helper_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_crtc *crtc = crtc_state->crtc; + struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state); + struct ast_vbios_mode_info *vbios_mode_info = &ast_crtc_state->vbios_mode_info; + + ast_dp_set_mode(crtc, vbios_mode_info); +} + +static void ast_astdp_encoder_helper_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_device *dev = encoder->dev; + struct ast_device *ast = to_ast_device(dev); + struct ast_connector *ast_connector = &ast->output.astdp.connector; + + if (ast_connector->physical_status == connector_status_connected) { + ast_dp_power_on_off(dev, AST_DP_POWER_ON); + ast_dp_link_training(ast); + + ast_wait_for_vretrace(ast); + ast_dp_set_on_off(dev, 1); + } +} + +static void ast_astdp_encoder_helper_atomic_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_device *dev = encoder->dev; + + ast_dp_set_on_off(dev, 0); + ast_dp_power_on_off(dev, AST_DP_POWER_OFF); +} + +static const struct drm_encoder_helper_funcs ast_astdp_encoder_helper_funcs = { + .atomic_mode_set = ast_astdp_encoder_helper_atomic_mode_set, + .atomic_enable = ast_astdp_encoder_helper_atomic_enable, + .atomic_disable = ast_astdp_encoder_helper_atomic_disable, +}; + +/* + * Connector + */ + +static int ast_astdp_connector_helper_get_modes(struct drm_connector *connector) +{ + struct ast_connector *ast_connector = to_ast_connector(connector); + int count; + + if (ast_connector->physical_status == connector_status_connected) { + struct ast_device *ast = to_ast_device(connector->dev); + const struct drm_edid *drm_edid; + + drm_edid = drm_edid_read_custom(connector, ast_astdp_read_edid_block, ast); + drm_edid_connector_update(connector, drm_edid); + count = drm_edid_connector_add_modes(connector); + drm_edid_free(drm_edid); + } else { + drm_edid_connector_update(connector, NULL); + + /* + * There's no EDID data without a connected monitor. Set BMC- + * compatible modes in this case. The XGA default resolution + * should work well for all BMCs. + */ + count = drm_add_modes_noedid(connector, 4096, 4096); + if (count) + drm_set_preferred_mode(connector, 1024, 768); + } + + return count; +} + +static int ast_astdp_connector_helper_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct ast_connector *ast_connector = to_ast_connector(connector); + struct drm_device *dev = connector->dev; + struct ast_device *ast = to_ast_device(connector->dev); + enum drm_connector_status status = connector_status_disconnected; + bool power_is_on; + + mutex_lock(&ast->modeset_lock); + + power_is_on = ast_dp_power_is_on(ast); + if (!power_is_on) + ast_dp_power_on_off(dev, true); + + if (ast_astdp_is_connected(ast)) + status = connector_status_connected; + + if (!power_is_on && status == connector_status_disconnected) + ast_dp_power_on_off(dev, false); + + mutex_unlock(&ast->modeset_lock); + + if (status != ast_connector->physical_status) + ++connector->epoch_counter; + ast_connector->physical_status = status; + + return connector_status_connected; +} + +static const struct drm_connector_helper_funcs ast_astdp_connector_helper_funcs = { + .get_modes = ast_astdp_connector_helper_get_modes, + .detect_ctx = ast_astdp_connector_helper_detect_ctx, +}; + +static const struct drm_connector_funcs ast_astdp_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ast_astdp_connector_init(struct drm_device *dev, struct drm_connector *connector) +{ + int ret; + + ret = drm_connector_init(dev, connector, &ast_astdp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) + return ret; + + drm_connector_helper_add(connector, &ast_astdp_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + + return 0; +} + +int ast_astdp_output_init(struct ast_device *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_crtc *crtc = &ast->crtc; + struct drm_encoder *encoder = &ast->output.astdp.encoder; + struct ast_connector *ast_connector = &ast->output.astdp.connector; + struct drm_connector *connector = &ast_connector->base; + int ret; + + ret = drm_encoder_init(dev, encoder, &ast_astdp_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ret; + drm_encoder_helper_add(encoder, &ast_astdp_encoder_helper_funcs); + + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = ast_astdp_connector_init(dev, connector); + if (ret) + return ret; + ast_connector->physical_status = connector->status; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} |