On Mon, Jan 26, 2015 at 03:22:48PM +0000, Simon Farnsworth wrote:
> DisplayPort to DVI-D Dual Link adapters designed by Bizlink have bugs in
> their I2C over AUX implementation. They work fine with Windows, but fail
> with Linux.
> 
> It turns out that they cannot keep an I2C transaction open unless the
> previous read was 16 bytes; shorter reads can only be followed by a zero
> byte transfer ending the I2C transaction.
> 
> Copy Windows's behaviour, and read 16 bytes at a time. If we get a short
> reply, assume that there's a hardware bottleneck, and shrink our read size
> to match.
> 
> Signed-off-by: Simon Farnsworth <simon.farnsworth at onelan.co.uk>
> ---
> 
> v2 changes, after feedback from Thierry and Ville:
> 
>  * Handle short replies. I've decided (arbitrarily) that a short reply
>    results in us dropping back to the newly chosen size for the rest of this
>    I2C transaction. Thus, given an attempt to read the first 16 bytes of
>    EDID, and a sink that only does 4 bytes of buffering, we will see the
>    following AUX transfers for the EDID read (after address is set):
> 
>    <set address, block etc>
>    Read 16 bytes from I2C over AUX.
>    Reply with 4 bytes
>    Read 4 bytes
>    Reply with 4 bytes
>    Read 4 bytes
>    Reply with 4 bytes
>    Read 4 bytes
>    Reply with 4 bytes
>    <end I2C transaction>

I think that's agaisnt the spec. IIRC you have to keep repeating the
same transaction (meaning address/len are unchanged) until all the data
was transferred.

> Note that I've not looked at MST support - I have neither the DP 1.2 spec
> nor any MST branch devices, so I can't test anything I write or check it
> against a spec. It looks from the code, however, as if MST has the branch
> device do the split from a big request into small transactions.
> 
>  drivers/gpu/drm/drm_dp_helper.c | 42 
> ++++++++++++++++++++++-------------------
>  include/drm/drm_dp_helper.h     |  5 +++++
>  2 files changed, 28 insertions(+), 19 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c
> index 79968e3..701b201 100644
> --- a/drivers/gpu/drm/drm_dp_helper.c
> +++ b/drivers/gpu/drm/drm_dp_helper.c
> @@ -396,11 +396,13 @@ static u32 drm_dp_i2c_functionality(struct i2c_adapter 
> *adapter)
>   * retrying the transaction as appropriate.  It is assumed that the
>   * aux->transfer function does not modify anything in the msg other than the
>   * reply field.
> + *
> + * Returns bytes transferred on success, or a negative error code on failure.
>   */
>  static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg 
> *msg)
>  {
>       unsigned int retry;
> -     int err;
> +     int ret;
>  
>       /*
>        * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device
> @@ -409,14 +411,14 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, 
> struct drm_dp_aux_msg *msg)
>        */
>       for (retry = 0; retry < 7; retry++) {
>               mutex_lock(&aux->hw_mutex);
> -             err = aux->transfer(aux, msg);
> +             ret = aux->transfer(aux, msg);
>               mutex_unlock(&aux->hw_mutex);
> -             if (err < 0) {
> -                     if (err == -EBUSY)
> +             if (ret < 0) {
> +                     if (ret == -EBUSY)
>                               continue;
>  
> -                     DRM_DEBUG_KMS("transaction failed: %d\n", err);
> -                     return err;
> +                     DRM_DEBUG_KMS("transaction failed: %d\n", ret);
> +                     return ret;
>               }
>  
>  
> @@ -457,9 +459,7 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, 
> struct drm_dp_aux_msg *msg)
>                        * Both native ACK and I2C ACK replies received. We
>                        * can assume the transfer was successful.
>                        */
> -                     if (err < msg->size)
> -                             return -EPROTO;
> -                     return 0;
> +                     return ret;
>  
>               case DP_AUX_I2C_REPLY_NACK:
>                       DRM_DEBUG_KMS("I2C nack\n");
> @@ -487,6 +487,7 @@ static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, 
> struct i2c_msg *msgs,
>  {
>       struct drm_dp_aux *aux = adapter->algo_data;
>       unsigned int i, j;
> +     int transfer_size = DP_AUX_MAX_PAYLOAD_BYTES;
>       struct drm_dp_aux_msg msg;
>       int err = 0;
>  
> @@ -507,20 +508,23 @@ static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, 
> struct i2c_msg *msgs,
>               err = drm_dp_i2c_do_msg(aux, &msg);
>               if (err < 0)
>                       break;
> -             /*
> -              * Many hardware implementations support FIFOs larger than a
> -              * single byte, but it has been empirically determined that
> -              * transferring data in larger chunks can actually lead to
> -              * decreased performance. Therefore each message is simply
> -              * transferred byte-by-byte.
> +             /* Bizlink designed DP->DVI-D Dual Link adapters require the
> +                 * I2C over AUX packets to be as large as possible. If not,
> +                 * the I2C transactions never succeed.
> +              *
> +              * We therefore start by requesting 16 byte transfers. If
> +              * the hardware gives us a partial ACK, we stick to the new
> +              * smaller size from the partial ACK.
>                */
> -             for (j = 0; j < msgs[i].len; j++) {
> +             for (j = 0; j < msgs[i].len; j += transfer_size) {
>                       msg.buffer = msgs[i].buf + j;
> -                     msg.size = 1;
> +                     msg.size = min(transfer_size, msgs[i].len - j);
>  
> -                     err = drm_dp_i2c_do_msg(aux, &msg);
> -                     if (err < 0)
> +                     transfer_size = drm_dp_i2c_do_msg(aux, &msg);
> +                     if (transfer_size <= 0) {
> +                             err = transfer_size == 0 ? -EPROTO : 
> transfer_size;
>                               break;
> +                     }
>               }
>               if (err < 0)
>                       break;
> diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
> index 11f8c84..444d51b 100644
> --- a/include/drm/drm_dp_helper.h
> +++ b/include/drm/drm_dp_helper.h
> @@ -42,6 +42,8 @@
>   * 1.2 formally includes both eDP and DPI definitions.
>   */
>  
> +#define DP_AUX_MAX_PAYLOAD_BYTES     16
> +
>  #define DP_AUX_I2C_WRITE             0x0
>  #define DP_AUX_I2C_READ                      0x1
>  #define DP_AUX_I2C_STATUS            0x2
> @@ -519,6 +521,9 @@ struct drm_dp_aux_msg {
>   * transactions. The drm_dp_aux_register_i2c_bus() function registers an
>   * I2C adapter that can be passed to drm_probe_ddc(). Upon removal, drivers
>   * should call drm_dp_aux_unregister_i2c_bus() to remove the I2C adapter.
> + * The I2C adapter uses long transfers by default; if a partial response is
> + * received, the adapter will drop down to the size given by the partial
> + * response for this transaction only.
>   *
>   * Note that the aux helper code assumes that the .transfer() function
>   * only modifies the reply field of the drm_dp_aux_msg structure.  The
> -- 
> 2.1.0

-- 
Ville Syrjälä
Intel OTC

Reply via email to