diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nv04_dfp.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nv04_dfp.c | 73 |
1 files changed, 73 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nv04_dfp.c b/drivers/gpu/drm/nouveau/nv04_dfp.c index 3311f3a8c818..a5dcf7685800 100644 --- a/drivers/gpu/drm/nouveau/nv04_dfp.c +++ b/drivers/gpu/drm/nouveau/nv04_dfp.c | |||
@@ -34,6 +34,8 @@ | |||
34 | #include "nouveau_hw.h" | 34 | #include "nouveau_hw.h" |
35 | #include "nvreg.h" | 35 | #include "nvreg.h" |
36 | 36 | ||
37 | #include "i2c/sil164.h" | ||
38 | |||
37 | #define FP_TG_CONTROL_ON (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | \ | 39 | #define FP_TG_CONTROL_ON (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | \ |
38 | NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | \ | 40 | NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | \ |
39 | NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS) | 41 | NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS) |
@@ -144,6 +146,36 @@ void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode) | |||
144 | } | 146 | } |
145 | } | 147 | } |
146 | 148 | ||
149 | static struct drm_encoder *get_tmds_slave(struct drm_encoder *encoder) | ||
150 | { | ||
151 | struct drm_device *dev = encoder->dev; | ||
152 | struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; | ||
153 | struct drm_encoder *slave; | ||
154 | |||
155 | if (dcb->type != OUTPUT_TMDS || dcb->location == DCB_LOC_ON_CHIP) | ||
156 | return NULL; | ||
157 | |||
158 | /* Some BIOSes (e.g. the one in a Quadro FX1000) report several | ||
159 | * TMDS transmitters at the same I2C address, in the same I2C | ||
160 | * bus. This can still work because in that case one of them is | ||
161 | * always hard-wired to a reasonable configuration using straps, | ||
162 | * and the other one needs to be programmed. | ||
163 | * | ||
164 | * I don't think there's a way to know which is which, even the | ||
165 | * blob programs the one exposed via I2C for *both* heads, so | ||
166 | * let's do the same. | ||
167 | */ | ||
168 | list_for_each_entry(slave, &dev->mode_config.encoder_list, head) { | ||
169 | struct dcb_entry *slave_dcb = nouveau_encoder(slave)->dcb; | ||
170 | |||
171 | if (slave_dcb->type == OUTPUT_TMDS && get_slave_funcs(slave) && | ||
172 | slave_dcb->tmdsconf.slave_addr == dcb->tmdsconf.slave_addr) | ||
173 | return slave; | ||
174 | } | ||
175 | |||
176 | return NULL; | ||
177 | } | ||
178 | |||
147 | static bool nv04_dfp_mode_fixup(struct drm_encoder *encoder, | 179 | static bool nv04_dfp_mode_fixup(struct drm_encoder *encoder, |
148 | struct drm_display_mode *mode, | 180 | struct drm_display_mode *mode, |
149 | struct drm_display_mode *adjusted_mode) | 181 | struct drm_display_mode *adjusted_mode) |
@@ -429,6 +461,11 @@ static void nv04_dfp_commit(struct drm_encoder *encoder) | |||
429 | else | 461 | else |
430 | NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000); | 462 | NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000); |
431 | 463 | ||
464 | /* Init external transmitters */ | ||
465 | if (get_tmds_slave(encoder)) | ||
466 | get_slave_funcs(get_tmds_slave(encoder))->mode_set( | ||
467 | encoder, &nv_encoder->mode, &nv_encoder->mode); | ||
468 | |||
432 | helper->dpms(encoder, DRM_MODE_DPMS_ON); | 469 | helper->dpms(encoder, DRM_MODE_DPMS_ON); |
433 | 470 | ||
434 | NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n", | 471 | NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n", |
@@ -550,10 +587,42 @@ static void nv04_dfp_destroy(struct drm_encoder *encoder) | |||
550 | 587 | ||
551 | NV_DEBUG_KMS(encoder->dev, "\n"); | 588 | NV_DEBUG_KMS(encoder->dev, "\n"); |
552 | 589 | ||
590 | if (get_slave_funcs(encoder)) | ||
591 | get_slave_funcs(encoder)->destroy(encoder); | ||
592 | |||
553 | drm_encoder_cleanup(encoder); | 593 | drm_encoder_cleanup(encoder); |
554 | kfree(nv_encoder); | 594 | kfree(nv_encoder); |
555 | } | 595 | } |
556 | 596 | ||
597 | static void nv04_tmds_slave_init(struct drm_encoder *encoder) | ||
598 | { | ||
599 | struct drm_device *dev = encoder->dev; | ||
600 | struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; | ||
601 | struct nouveau_i2c_chan *i2c = nouveau_i2c_find(dev, 2); | ||
602 | struct i2c_board_info info[] = { | ||
603 | { | ||
604 | .type = "sil164", | ||
605 | .addr = (dcb->tmdsconf.slave_addr == 0x7 ? 0x3a : 0x38), | ||
606 | .platform_data = &(struct sil164_encoder_params) { | ||
607 | SIL164_INPUT_EDGE_RISING | ||
608 | } | ||
609 | }, | ||
610 | { } | ||
611 | }; | ||
612 | int type; | ||
613 | |||
614 | if (!nv_gf4_disp_arch(dev) || !i2c || | ||
615 | get_tmds_slave(encoder)) | ||
616 | return; | ||
617 | |||
618 | type = nouveau_i2c_identify(dev, "TMDS transmitter", info, 2); | ||
619 | if (type < 0) | ||
620 | return; | ||
621 | |||
622 | drm_i2c_encoder_init(dev, to_encoder_slave(encoder), | ||
623 | &i2c->adapter, &info[type]); | ||
624 | } | ||
625 | |||
557 | static const struct drm_encoder_helper_funcs nv04_lvds_helper_funcs = { | 626 | static const struct drm_encoder_helper_funcs nv04_lvds_helper_funcs = { |
558 | .dpms = nv04_lvds_dpms, | 627 | .dpms = nv04_lvds_dpms, |
559 | .save = nv04_dfp_save, | 628 | .save = nv04_dfp_save, |
@@ -616,6 +685,10 @@ nv04_dfp_create(struct drm_connector *connector, struct dcb_entry *entry) | |||
616 | encoder->possible_crtcs = entry->heads; | 685 | encoder->possible_crtcs = entry->heads; |
617 | encoder->possible_clones = 0; | 686 | encoder->possible_clones = 0; |
618 | 687 | ||
688 | if (entry->type == OUTPUT_TMDS && | ||
689 | entry->location != DCB_LOC_ON_CHIP) | ||
690 | nv04_tmds_slave_init(encoder); | ||
691 | |||
619 | drm_mode_connector_attach_encoder(connector, encoder); | 692 | drm_mode_connector_attach_encoder(connector, encoder); |
620 | return 0; | 693 | return 0; |
621 | } | 694 | } |