162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Copyright 2015 Red Hat Inc.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
562306a36Sopenharmony_ci * copy of this software and associated documentation files (the "Software"),
662306a36Sopenharmony_ci * to deal in the Software without restriction, including without limitation
762306a36Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense,
862306a36Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the
962306a36Sopenharmony_ci * Software is furnished to do so, subject to the following conditions:
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * The above copyright notice and this permission notice shall be included in
1262306a36Sopenharmony_ci * all copies or substantial portions of the Software.
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1562306a36Sopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1662306a36Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
1762306a36Sopenharmony_ci * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
1862306a36Sopenharmony_ci * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1962306a36Sopenharmony_ci * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
2062306a36Sopenharmony_ci * OTHER DEALINGS IN THE SOFTWARE.
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * Authors: Dave Airlie
2362306a36Sopenharmony_ci */
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <drm/radeon_drm.h>
2662306a36Sopenharmony_ci#include "radeon.h"
2762306a36Sopenharmony_ci#include "nid.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define AUX_RX_ERROR_FLAGS (AUX_SW_RX_OVERFLOW |	     \
3062306a36Sopenharmony_ci			    AUX_SW_RX_HPD_DISCON |	     \
3162306a36Sopenharmony_ci			    AUX_SW_RX_PARTIAL_BYTE |	     \
3262306a36Sopenharmony_ci			    AUX_SW_NON_AUX_MODE |	     \
3362306a36Sopenharmony_ci			    AUX_SW_RX_SYNC_INVALID_L |	     \
3462306a36Sopenharmony_ci			    AUX_SW_RX_SYNC_INVALID_H |	     \
3562306a36Sopenharmony_ci			    AUX_SW_RX_INVALID_START |	     \
3662306a36Sopenharmony_ci			    AUX_SW_RX_RECV_NO_DET |	     \
3762306a36Sopenharmony_ci			    AUX_SW_RX_RECV_INVALID_H |	     \
3862306a36Sopenharmony_ci			    AUX_SW_RX_RECV_INVALID_V)
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define AUX_SW_REPLY_GET_BYTE_COUNT(x) (((x) >> 24) & 0x1f)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define BARE_ADDRESS_SIZE 3
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic const u32 aux_offset[] =
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	0x6200 - 0x6200,
4762306a36Sopenharmony_ci	0x6250 - 0x6200,
4862306a36Sopenharmony_ci	0x62a0 - 0x6200,
4962306a36Sopenharmony_ci	0x6300 - 0x6200,
5062306a36Sopenharmony_ci	0x6350 - 0x6200,
5162306a36Sopenharmony_ci	0x63a0 - 0x6200,
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cissize_t
5562306a36Sopenharmony_ciradeon_dp_aux_transfer_native(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct radeon_i2c_chan *chan =
5862306a36Sopenharmony_ci		container_of(aux, struct radeon_i2c_chan, aux);
5962306a36Sopenharmony_ci	struct drm_device *dev = chan->dev;
6062306a36Sopenharmony_ci	struct radeon_device *rdev = dev->dev_private;
6162306a36Sopenharmony_ci	int ret = 0, i;
6262306a36Sopenharmony_ci	uint32_t tmp, ack = 0;
6362306a36Sopenharmony_ci	int instance = chan->rec.i2c_id & 0xf;
6462306a36Sopenharmony_ci	u8 byte;
6562306a36Sopenharmony_ci	u8 *buf = msg->buffer;
6662306a36Sopenharmony_ci	int retry_count = 0;
6762306a36Sopenharmony_ci	int bytes;
6862306a36Sopenharmony_ci	int msize;
6962306a36Sopenharmony_ci	bool is_write = false;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (WARN_ON(msg->size > 16))
7262306a36Sopenharmony_ci		return -E2BIG;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	switch (msg->request & ~DP_AUX_I2C_MOT) {
7562306a36Sopenharmony_ci	case DP_AUX_NATIVE_WRITE:
7662306a36Sopenharmony_ci	case DP_AUX_I2C_WRITE:
7762306a36Sopenharmony_ci		is_write = true;
7862306a36Sopenharmony_ci		break;
7962306a36Sopenharmony_ci	case DP_AUX_NATIVE_READ:
8062306a36Sopenharmony_ci	case DP_AUX_I2C_READ:
8162306a36Sopenharmony_ci		break;
8262306a36Sopenharmony_ci	default:
8362306a36Sopenharmony_ci		return -EINVAL;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/* work out two sizes required */
8762306a36Sopenharmony_ci	msize = 0;
8862306a36Sopenharmony_ci	bytes = BARE_ADDRESS_SIZE;
8962306a36Sopenharmony_ci	if (msg->size) {
9062306a36Sopenharmony_ci		msize = msg->size - 1;
9162306a36Sopenharmony_ci		bytes++;
9262306a36Sopenharmony_ci		if (is_write)
9362306a36Sopenharmony_ci			bytes += msg->size;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	mutex_lock(&chan->mutex);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* switch the pad to aux mode */
9962306a36Sopenharmony_ci	tmp = RREG32(chan->rec.mask_clk_reg);
10062306a36Sopenharmony_ci	tmp |= (1 << 16);
10162306a36Sopenharmony_ci	WREG32(chan->rec.mask_clk_reg, tmp);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	/* setup AUX control register with correct HPD pin */
10462306a36Sopenharmony_ci	tmp = RREG32(AUX_CONTROL + aux_offset[instance]);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	tmp &= AUX_HPD_SEL(0x7);
10762306a36Sopenharmony_ci	tmp |= AUX_HPD_SEL(chan->rec.hpd);
10862306a36Sopenharmony_ci	tmp |= AUX_EN | AUX_LS_READ_EN;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	WREG32(AUX_CONTROL + aux_offset[instance], tmp);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* atombios appears to write this twice lets copy it */
11362306a36Sopenharmony_ci	WREG32(AUX_SW_CONTROL + aux_offset[instance],
11462306a36Sopenharmony_ci	       AUX_SW_WR_BYTES(bytes));
11562306a36Sopenharmony_ci	WREG32(AUX_SW_CONTROL + aux_offset[instance],
11662306a36Sopenharmony_ci	       AUX_SW_WR_BYTES(bytes));
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* write the data header into the registers */
11962306a36Sopenharmony_ci	/* request, address, msg size */
12062306a36Sopenharmony_ci	byte = (msg->request << 4) | ((msg->address >> 16) & 0xf);
12162306a36Sopenharmony_ci	WREG32(AUX_SW_DATA + aux_offset[instance],
12262306a36Sopenharmony_ci	       AUX_SW_DATA_MASK(byte) | AUX_SW_AUTOINCREMENT_DISABLE);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	byte = (msg->address >> 8) & 0xff;
12562306a36Sopenharmony_ci	WREG32(AUX_SW_DATA + aux_offset[instance],
12662306a36Sopenharmony_ci	       AUX_SW_DATA_MASK(byte));
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	byte = msg->address & 0xff;
12962306a36Sopenharmony_ci	WREG32(AUX_SW_DATA + aux_offset[instance],
13062306a36Sopenharmony_ci	       AUX_SW_DATA_MASK(byte));
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	byte = msize;
13362306a36Sopenharmony_ci	WREG32(AUX_SW_DATA + aux_offset[instance],
13462306a36Sopenharmony_ci	       AUX_SW_DATA_MASK(byte));
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/* if we are writing - write the msg buffer */
13762306a36Sopenharmony_ci	if (is_write) {
13862306a36Sopenharmony_ci		for (i = 0; i < msg->size; i++) {
13962306a36Sopenharmony_ci			WREG32(AUX_SW_DATA + aux_offset[instance],
14062306a36Sopenharmony_ci			       AUX_SW_DATA_MASK(buf[i]));
14162306a36Sopenharmony_ci		}
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* clear the ACK */
14562306a36Sopenharmony_ci	WREG32(AUX_SW_INTERRUPT_CONTROL + aux_offset[instance], AUX_SW_DONE_ACK);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* write the size and GO bits */
14862306a36Sopenharmony_ci	WREG32(AUX_SW_CONTROL + aux_offset[instance],
14962306a36Sopenharmony_ci	       AUX_SW_WR_BYTES(bytes) | AUX_SW_GO);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* poll the status registers - TODO irq support */
15262306a36Sopenharmony_ci	do {
15362306a36Sopenharmony_ci		tmp = RREG32(AUX_SW_STATUS + aux_offset[instance]);
15462306a36Sopenharmony_ci		if (tmp & AUX_SW_DONE) {
15562306a36Sopenharmony_ci			break;
15662306a36Sopenharmony_ci		}
15762306a36Sopenharmony_ci		usleep_range(100, 200);
15862306a36Sopenharmony_ci	} while (retry_count++ < 1000);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (retry_count >= 1000) {
16162306a36Sopenharmony_ci		dev_err(rdev->dev, "auxch hw never signalled completion, error %08x\n", tmp);
16262306a36Sopenharmony_ci		ret = -EIO;
16362306a36Sopenharmony_ci		goto done;
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (tmp & AUX_SW_RX_TIMEOUT) {
16762306a36Sopenharmony_ci		ret = -ETIMEDOUT;
16862306a36Sopenharmony_ci		goto done;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci	if (tmp & AUX_RX_ERROR_FLAGS) {
17162306a36Sopenharmony_ci		drm_dbg_kms_ratelimited(dev, "dp_aux_ch flags not zero: %08x\n", tmp);
17262306a36Sopenharmony_ci		ret = -EIO;
17362306a36Sopenharmony_ci		goto done;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	bytes = AUX_SW_REPLY_GET_BYTE_COUNT(tmp);
17762306a36Sopenharmony_ci	if (bytes) {
17862306a36Sopenharmony_ci		WREG32(AUX_SW_DATA + aux_offset[instance],
17962306a36Sopenharmony_ci		       AUX_SW_DATA_RW | AUX_SW_AUTOINCREMENT_DISABLE);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		tmp = RREG32(AUX_SW_DATA + aux_offset[instance]);
18262306a36Sopenharmony_ci		ack = (tmp >> 8) & 0xff;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		for (i = 0; i < bytes - 1; i++) {
18562306a36Sopenharmony_ci			tmp = RREG32(AUX_SW_DATA + aux_offset[instance]);
18662306a36Sopenharmony_ci			if (buf)
18762306a36Sopenharmony_ci				buf[i] = (tmp >> 8) & 0xff;
18862306a36Sopenharmony_ci		}
18962306a36Sopenharmony_ci		if (buf)
19062306a36Sopenharmony_ci			ret = bytes - 1;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	WREG32(AUX_SW_INTERRUPT_CONTROL + aux_offset[instance], AUX_SW_DONE_ACK);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (is_write)
19662306a36Sopenharmony_ci		ret = msg->size;
19762306a36Sopenharmony_cidone:
19862306a36Sopenharmony_ci	mutex_unlock(&chan->mutex);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (ret >= 0)
20162306a36Sopenharmony_ci		msg->reply = ack >> 4;
20262306a36Sopenharmony_ci	return ret;
20362306a36Sopenharmony_ci}
204