forked from Minki/linux
07549dfbd9
Fix build warning when building edp/edp_aux.o due to missing prototype for edp_aux_transfer. This function is only used in edp_aux.c so just make it static. Reported-by: Nicholas Mc Guire <hofrat@osadl.org> Signed-off-by: Rob Clark <robdclark@gmail.com>
273 lines
6.6 KiB
C
273 lines
6.6 KiB
C
/*
|
|
* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "edp.h"
|
|
#include "edp.xml.h"
|
|
|
|
#define AUX_CMD_FIFO_LEN 144
|
|
#define AUX_CMD_NATIVE_MAX 16
|
|
#define AUX_CMD_I2C_MAX 128
|
|
|
|
#define EDP_INTR_AUX_I2C_ERR \
|
|
(EDP_INTERRUPT_REG_1_WRONG_ADDR | EDP_INTERRUPT_REG_1_TIMEOUT | \
|
|
EDP_INTERRUPT_REG_1_NACK_DEFER | EDP_INTERRUPT_REG_1_WRONG_DATA_CNT | \
|
|
EDP_INTERRUPT_REG_1_I2C_NACK | EDP_INTERRUPT_REG_1_I2C_DEFER)
|
|
#define EDP_INTR_TRANS_STATUS \
|
|
(EDP_INTERRUPT_REG_1_AUX_I2C_DONE | EDP_INTR_AUX_I2C_ERR)
|
|
|
|
struct edp_aux {
|
|
void __iomem *base;
|
|
bool msg_err;
|
|
|
|
struct completion msg_comp;
|
|
|
|
/* To prevent the message transaction routine from reentry. */
|
|
struct mutex msg_mutex;
|
|
|
|
struct drm_dp_aux drm_aux;
|
|
};
|
|
#define to_edp_aux(x) container_of(x, struct edp_aux, drm_aux)
|
|
|
|
static int edp_msg_fifo_tx(struct edp_aux *aux, struct drm_dp_aux_msg *msg)
|
|
{
|
|
u32 data[4];
|
|
u32 reg, len;
|
|
bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
|
|
bool read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ);
|
|
u8 *msgdata = msg->buffer;
|
|
int i;
|
|
|
|
if (read)
|
|
len = 4;
|
|
else
|
|
len = msg->size + 4;
|
|
|
|
/*
|
|
* cmd fifo only has depth of 144 bytes
|
|
*/
|
|
if (len > AUX_CMD_FIFO_LEN)
|
|
return -EINVAL;
|
|
|
|
/* Pack cmd and write to HW */
|
|
data[0] = (msg->address >> 16) & 0xf; /* addr[19:16] */
|
|
if (read)
|
|
data[0] |= BIT(4); /* R/W */
|
|
|
|
data[1] = (msg->address >> 8) & 0xff; /* addr[15:8] */
|
|
data[2] = msg->address & 0xff; /* addr[7:0] */
|
|
data[3] = (msg->size - 1) & 0xff; /* len[7:0] */
|
|
|
|
for (i = 0; i < len; i++) {
|
|
reg = (i < 4) ? data[i] : msgdata[i - 4];
|
|
reg = EDP_AUX_DATA_DATA(reg); /* index = 0, write */
|
|
if (i == 0)
|
|
reg |= EDP_AUX_DATA_INDEX_WRITE;
|
|
edp_write(aux->base + REG_EDP_AUX_DATA, reg);
|
|
}
|
|
|
|
reg = 0; /* Transaction number is always 1 */
|
|
if (!native) /* i2c */
|
|
reg |= EDP_AUX_TRANS_CTRL_I2C;
|
|
|
|
reg |= EDP_AUX_TRANS_CTRL_GO;
|
|
edp_write(aux->base + REG_EDP_AUX_TRANS_CTRL, reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int edp_msg_fifo_rx(struct edp_aux *aux, struct drm_dp_aux_msg *msg)
|
|
{
|
|
u32 data;
|
|
u8 *dp;
|
|
int i;
|
|
u32 len = msg->size;
|
|
|
|
edp_write(aux->base + REG_EDP_AUX_DATA,
|
|
EDP_AUX_DATA_INDEX_WRITE | EDP_AUX_DATA_READ); /* index = 0 */
|
|
|
|
dp = msg->buffer;
|
|
|
|
/* discard first byte */
|
|
data = edp_read(aux->base + REG_EDP_AUX_DATA);
|
|
for (i = 0; i < len; i++) {
|
|
data = edp_read(aux->base + REG_EDP_AUX_DATA);
|
|
dp[i] = (u8)((data >> 8) & 0xff);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function does the real job to process an AUX transaction.
|
|
* It will call msm_edp_aux_ctrl() function to reset the AUX channel,
|
|
* if the waiting is timeout.
|
|
* The caller who triggers the transaction should avoid the
|
|
* msm_edp_aux_ctrl() running concurrently in other threads, i.e.
|
|
* start transaction only when AUX channel is fully enabled.
|
|
*/
|
|
static ssize_t edp_aux_transfer(struct drm_dp_aux *drm_aux,
|
|
struct drm_dp_aux_msg *msg)
|
|
{
|
|
struct edp_aux *aux = to_edp_aux(drm_aux);
|
|
ssize_t ret;
|
|
unsigned long time_left;
|
|
bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
|
|
bool read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ);
|
|
|
|
/* Ignore address only message */
|
|
if ((msg->size == 0) || (msg->buffer == NULL)) {
|
|
msg->reply = native ?
|
|
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
|
|
return msg->size;
|
|
}
|
|
|
|
/* msg sanity check */
|
|
if ((native && (msg->size > AUX_CMD_NATIVE_MAX)) ||
|
|
(msg->size > AUX_CMD_I2C_MAX)) {
|
|
pr_err("%s: invalid msg: size(%zu), request(%x)\n",
|
|
__func__, msg->size, msg->request);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&aux->msg_mutex);
|
|
|
|
aux->msg_err = false;
|
|
reinit_completion(&aux->msg_comp);
|
|
|
|
ret = edp_msg_fifo_tx(aux, msg);
|
|
if (ret < 0)
|
|
goto unlock_exit;
|
|
|
|
DBG("wait_for_completion");
|
|
time_left = wait_for_completion_timeout(&aux->msg_comp,
|
|
msecs_to_jiffies(300));
|
|
if (!time_left) {
|
|
/*
|
|
* Clear GO and reset AUX channel
|
|
* to cancel the current transaction.
|
|
*/
|
|
edp_write(aux->base + REG_EDP_AUX_TRANS_CTRL, 0);
|
|
msm_edp_aux_ctrl(aux, 1);
|
|
pr_err("%s: aux timeout,\n", __func__);
|
|
ret = -ETIMEDOUT;
|
|
goto unlock_exit;
|
|
}
|
|
DBG("completion");
|
|
|
|
if (!aux->msg_err) {
|
|
if (read) {
|
|
ret = edp_msg_fifo_rx(aux, msg);
|
|
if (ret < 0)
|
|
goto unlock_exit;
|
|
}
|
|
|
|
msg->reply = native ?
|
|
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
|
|
} else {
|
|
/* Reply defer to retry */
|
|
msg->reply = native ?
|
|
DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER;
|
|
/*
|
|
* The sleep time in caller is not long enough to make sure
|
|
* our H/W completes transactions. Add more defer time here.
|
|
*/
|
|
msleep(100);
|
|
}
|
|
|
|
/* Return requested size for success or retry */
|
|
ret = msg->size;
|
|
|
|
unlock_exit:
|
|
mutex_unlock(&aux->msg_mutex);
|
|
return ret;
|
|
}
|
|
|
|
void *msm_edp_aux_init(struct device *dev, void __iomem *regbase,
|
|
struct drm_dp_aux **drm_aux)
|
|
{
|
|
struct edp_aux *aux = NULL;
|
|
int ret;
|
|
|
|
DBG("");
|
|
aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL);
|
|
if (!aux)
|
|
return NULL;
|
|
|
|
aux->base = regbase;
|
|
mutex_init(&aux->msg_mutex);
|
|
init_completion(&aux->msg_comp);
|
|
|
|
aux->drm_aux.name = "msm_edp_aux";
|
|
aux->drm_aux.dev = dev;
|
|
aux->drm_aux.transfer = edp_aux_transfer;
|
|
ret = drm_dp_aux_register(&aux->drm_aux);
|
|
if (ret) {
|
|
pr_err("%s: failed to register drm aux: %d\n", __func__, ret);
|
|
mutex_destroy(&aux->msg_mutex);
|
|
}
|
|
|
|
if (drm_aux && aux)
|
|
*drm_aux = &aux->drm_aux;
|
|
|
|
return aux;
|
|
}
|
|
|
|
void msm_edp_aux_destroy(struct device *dev, struct edp_aux *aux)
|
|
{
|
|
if (aux) {
|
|
drm_dp_aux_unregister(&aux->drm_aux);
|
|
mutex_destroy(&aux->msg_mutex);
|
|
}
|
|
}
|
|
|
|
irqreturn_t msm_edp_aux_irq(struct edp_aux *aux, u32 isr)
|
|
{
|
|
if (isr & EDP_INTR_TRANS_STATUS) {
|
|
DBG("isr=%x", isr);
|
|
edp_write(aux->base + REG_EDP_AUX_TRANS_CTRL, 0);
|
|
|
|
if (isr & EDP_INTR_AUX_I2C_ERR)
|
|
aux->msg_err = true;
|
|
else
|
|
aux->msg_err = false;
|
|
|
|
complete(&aux->msg_comp);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void msm_edp_aux_ctrl(struct edp_aux *aux, int enable)
|
|
{
|
|
u32 data;
|
|
|
|
DBG("enable=%d", enable);
|
|
data = edp_read(aux->base + REG_EDP_AUX_CTRL);
|
|
|
|
if (enable) {
|
|
data |= EDP_AUX_CTRL_RESET;
|
|
edp_write(aux->base + REG_EDP_AUX_CTRL, data);
|
|
/* Make sure full reset */
|
|
wmb();
|
|
usleep_range(500, 1000);
|
|
|
|
data &= ~EDP_AUX_CTRL_RESET;
|
|
data |= EDP_AUX_CTRL_ENABLE;
|
|
edp_write(aux->base + REG_EDP_AUX_CTRL, data);
|
|
} else {
|
|
data &= ~EDP_AUX_CTRL_ENABLE;
|
|
edp_write(aux->base + REG_EDP_AUX_CTRL, data);
|
|
}
|
|
}
|
|
|