|
@@ -0,0 +1,206 @@
|
|
|
+/*
|
|
|
+ * 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;
|
|
|
+}
|