207 lines
5.5 KiB
C
207 lines
5.5 KiB
C
/*
|
|
* Copyright 2015 Red Hat Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Authors: Dave Airlie
|
|
*/
|
|
#include <drm/drmP.h>
|
|
#include <drm/radeon_drm.h>
|
|
#include "radeon.h"
|
|
#include "nid.h"
|
|
|
|
#define AUX_RX_ERROR_FLAGS (AUX_SW_RX_OVERFLOW | \
|
|
AUX_SW_RX_HPD_DISCON | \
|
|
AUX_SW_RX_PARTIAL_BYTE | \
|
|
AUX_SW_NON_AUX_MODE | \
|
|
AUX_SW_RX_MIN_COUNT_VIOL | \
|
|
AUX_SW_RX_INVALID_STOP | \
|
|
AUX_SW_RX_SYNC_INVALID_L | \
|
|
AUX_SW_RX_SYNC_INVALID_H | \
|
|
AUX_SW_RX_INVALID_START | \
|
|
AUX_SW_RX_RECV_NO_DET | \
|
|
AUX_SW_RX_RECV_INVALID_H | \
|
|
AUX_SW_RX_RECV_INVALID_V)
|
|
|
|
#define AUX_SW_REPLY_GET_BYTE_COUNT(x) (((x) >> 24) & 0x1f)
|
|
|
|
#define BARE_ADDRESS_SIZE 3
|
|
|
|
static const u32 aux_offset[] =
|
|
{
|
|
0x6200 - 0x6200,
|
|
0x6250 - 0x6200,
|
|
0x62a0 - 0x6200,
|
|
0x6300 - 0x6200,
|
|
0x6350 - 0x6200,
|
|
0x63a0 - 0x6200,
|
|
};
|
|
|
|
ssize_t
|
|
radeon_dp_aux_transfer_native(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
|
|
{
|
|
struct radeon_i2c_chan *chan =
|
|
container_of(aux, struct radeon_i2c_chan, aux);
|
|
struct drm_device *dev = chan->dev;
|
|
struct radeon_device *rdev = dev->dev_private;
|
|
int ret = 0, i;
|
|
uint32_t tmp, ack = 0;
|
|
int instance = chan->rec.i2c_id & 0xf;
|
|
u8 byte;
|
|
u8 *buf = msg->buffer;
|
|
int retry_count = 0;
|
|
int bytes;
|
|
int msize;
|
|
bool is_write = false;
|
|
|
|
if (WARN_ON(msg->size > 16))
|
|
return -E2BIG;
|
|
|
|
switch (msg->request & ~DP_AUX_I2C_MOT) {
|
|
case DP_AUX_NATIVE_WRITE:
|
|
case DP_AUX_I2C_WRITE:
|
|
is_write = true;
|
|
break;
|
|
case DP_AUX_NATIVE_READ:
|
|
case DP_AUX_I2C_READ:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* work out two sizes required */
|
|
msize = 0;
|
|
bytes = BARE_ADDRESS_SIZE;
|
|
if (msg->size) {
|
|
msize = msg->size - 1;
|
|
bytes++;
|
|
if (is_write)
|
|
bytes += msg->size;
|
|
}
|
|
|
|
mutex_lock(&chan->mutex);
|
|
|
|
/* switch the pad to aux mode */
|
|
tmp = RREG32(chan->rec.mask_clk_reg);
|
|
tmp |= (1 << 16);
|
|
WREG32(chan->rec.mask_clk_reg, tmp);
|
|
|
|
/* setup AUX control register with correct HPD pin */
|
|
tmp = RREG32(AUX_CONTROL + aux_offset[instance]);
|
|
|
|
tmp &= AUX_HPD_SEL(0x7);
|
|
tmp |= AUX_HPD_SEL(chan->rec.hpd);
|
|
tmp |= AUX_EN | AUX_LS_READ_EN;
|
|
|
|
WREG32(AUX_CONTROL + aux_offset[instance], tmp);
|
|
|
|
/* atombios appears to write this twice lets copy it */
|
|
WREG32(AUX_SW_CONTROL + aux_offset[instance],
|
|
AUX_SW_WR_BYTES(bytes));
|
|
WREG32(AUX_SW_CONTROL + aux_offset[instance],
|
|
AUX_SW_WR_BYTES(bytes));
|
|
|
|
/* write the data header into the registers */
|
|
/* request, addres, msg size */
|
|
byte = (msg->request << 4);
|
|
WREG32(AUX_SW_DATA + aux_offset[instance],
|
|
AUX_SW_DATA_MASK(byte) | AUX_SW_AUTOINCREMENT_DISABLE);
|
|
|
|
byte = (msg->address >> 8) & 0xff;
|
|
WREG32(AUX_SW_DATA + aux_offset[instance],
|
|
AUX_SW_DATA_MASK(byte));
|
|
|
|
byte = msg->address & 0xff;
|
|
WREG32(AUX_SW_DATA + aux_offset[instance],
|
|
AUX_SW_DATA_MASK(byte));
|
|
|
|
byte = msize;
|
|
WREG32(AUX_SW_DATA + aux_offset[instance],
|
|
AUX_SW_DATA_MASK(byte));
|
|
|
|
/* if we are writing - write the msg buffer */
|
|
if (is_write) {
|
|
for (i = 0; i < msg->size; i++) {
|
|
WREG32(AUX_SW_DATA + aux_offset[instance],
|
|
AUX_SW_DATA_MASK(buf[i]));
|
|
}
|
|
}
|
|
|
|
/* clear the ACK */
|
|
WREG32(AUX_SW_INTERRUPT_CONTROL + aux_offset[instance], AUX_SW_DONE_ACK);
|
|
|
|
/* write the size and GO bits */
|
|
WREG32(AUX_SW_CONTROL + aux_offset[instance],
|
|
AUX_SW_WR_BYTES(bytes) | AUX_SW_GO);
|
|
|
|
/* poll the status registers - TODO irq support */
|
|
do {
|
|
tmp = RREG32(AUX_SW_STATUS + aux_offset[instance]);
|
|
if (tmp & AUX_SW_DONE) {
|
|
break;
|
|
}
|
|
usleep_range(100, 200);
|
|
} while (retry_count++ < 1000);
|
|
|
|
if (retry_count >= 1000) {
|
|
DRM_ERROR("auxch hw never signalled completion, error %08x\n", tmp);
|
|
ret = -EIO;
|
|
goto done;
|
|
}
|
|
|
|
if (tmp & AUX_SW_RX_TIMEOUT) {
|
|
DRM_DEBUG_KMS("dp_aux_ch timed out\n");
|
|
ret = -ETIMEDOUT;
|
|
goto done;
|
|
}
|
|
if (tmp & AUX_RX_ERROR_FLAGS) {
|
|
DRM_DEBUG_KMS("dp_aux_ch flags not zero: %08x\n", tmp);
|
|
ret = -EIO;
|
|
goto done;
|
|
}
|
|
|
|
bytes = AUX_SW_REPLY_GET_BYTE_COUNT(tmp);
|
|
if (bytes) {
|
|
WREG32(AUX_SW_DATA + aux_offset[instance],
|
|
AUX_SW_DATA_RW | AUX_SW_AUTOINCREMENT_DISABLE);
|
|
|
|
tmp = RREG32(AUX_SW_DATA + aux_offset[instance]);
|
|
ack = (tmp >> 8) & 0xff;
|
|
|
|
for (i = 0; i < bytes - 1; i++) {
|
|
tmp = RREG32(AUX_SW_DATA + aux_offset[instance]);
|
|
if (buf)
|
|
buf[i] = (tmp >> 8) & 0xff;
|
|
}
|
|
if (buf)
|
|
ret = bytes - 1;
|
|
}
|
|
|
|
WREG32(AUX_SW_INTERRUPT_CONTROL + aux_offset[instance], AUX_SW_DONE_ACK);
|
|
|
|
if (is_write)
|
|
ret = msg->size;
|
|
done:
|
|
mutex_unlock(&chan->mutex);
|
|
|
|
if (ret >= 0)
|
|
msg->reply = ack >> 4;
|
|
return ret;
|
|
}
|