Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5db50d4 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drm/msm/dp: add support for dp aux" into msm-4.9

parents 36620f1f ae725622
Loading
Loading
Loading
Loading
+578 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2012-2017, 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.
 *
 */

#define pr_fmt(fmt)	"[drm-dp] %s: " fmt, __func__

#include <linux/delay.h>

#include "dp_aux.h"

#define DP_AUX_ENUM_STR(x)		#x

struct aux_buf {
	u8 *start;      /* buffer start addr */
	u8 *end;	/* buffer end addr */
	u8 *data;       /* data pou32er */
	u32 size;       /* size of buffer */
	u32 len;	/* dara length */
	u8 trans_num;   /* transaction number */
	enum aux_tx_mode tx_mode;
};

struct dp_aux_private {
	struct device *dev;
	struct dp_aux dp_aux;
	struct dp_catalog_aux *catalog;

	struct mutex mutex;
	struct completion comp;

	struct aux_cmd *cmds;
	struct aux_buf txp;
	struct aux_buf rxp;

	u32 aux_error_num;

	u8 txbuf[256];
	u8 rxbuf[256];
};

static char *dp_aux_get_error(u32 aux_error)
{
	switch (aux_error) {
	case DP_AUX_ERR_NONE:
		return DP_AUX_ENUM_STR(DP_AUX_ERR_NONE);
	case DP_AUX_ERR_ADDR:
		return DP_AUX_ENUM_STR(DP_AUX_ERR_ADDR);
	case DP_AUX_ERR_TOUT:
		return DP_AUX_ENUM_STR(DP_AUX_ERR_TOUT);
	case DP_AUX_ERR_NACK:
		return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK);
	case DP_AUX_ERR_DEFER:
		return DP_AUX_ENUM_STR(DP_AUX_ERR_DEFER);
	case DP_AUX_ERR_NACK_DEFER:
		return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK_DEFER);
	default:
		return "unknown";
	}
}

static void dp_aux_buf_init(struct aux_buf *buf, u8 *data, u32 size)
{
	buf->start     = data;
	buf->size      = size;
	buf->data      = buf->start;
	buf->end       = buf->start + buf->size;
	buf->len       = 0;
	buf->trans_num = 0;
	buf->tx_mode   = AUX_NATIVE;
}

static void dp_aux_buf_set(struct dp_aux_private *aux)
{
	init_completion(&aux->comp);
	mutex_init(&aux->mutex);

	dp_aux_buf_init(&aux->txp, aux->txbuf, sizeof(aux->txbuf));
	dp_aux_buf_init(&aux->rxp, aux->rxbuf, sizeof(aux->rxbuf));
}

static void dp_aux_buf_reset(struct aux_buf *buf)
{
	buf->data      = buf->start;
	buf->len       = 0;
	buf->trans_num = 0;
	buf->tx_mode   = AUX_NATIVE;

	memset(buf->start, 0x0, 256);
}

static void dp_aux_buf_push(struct aux_buf *buf, u32 len)
{
	buf->data += len;
	buf->len  += len;
}

static u32 dp_aux_buf_trailing(struct aux_buf *buf)
{
	return (u32)(buf->end - buf->data);
}

static u32 dp_aux_add_cmd(struct aux_buf *buf, struct aux_cmd *cmd)
{
	u8 data;
	u8 *bp, *cp;
	u32 i, len;

	if (cmd->ex_mode == AUX_READ)
		len = 4;
	else
		len = cmd->len + 4;

	if (dp_aux_buf_trailing(buf) < len) {
		pr_err("buf trailing error\n");
		return 0;
	}

	/*
	 * cmd fifo only has depth of 144 bytes
	 * limit buf length to 128 bytes here
	 */
	if ((buf->len + len) > 128) {
		pr_err("buf len error\n");
		return 0;
	}

	bp = buf->data;
	data = cmd->addr >> 16;
	data &= 0x0f;  /* 4 addr bits */

	if (cmd->ex_mode == AUX_READ)
		data |=  BIT(4);

	*bp++ = data;
	*bp++ = cmd->addr >> 8;
	*bp++ = cmd->addr;
	*bp++ = cmd->len - 1;

	if (cmd->ex_mode == AUX_WRITE) {
		cp = cmd->buf;

		for (i = 0; i < cmd->len; i++)
			*bp++ = *cp++;
	}

	dp_aux_buf_push(buf, len);

	buf->tx_mode = cmd->tx_mode;

	buf->trans_num++;

	return cmd->len - 1;
}

static u32 dp_aux_cmd_fifo_tx(struct dp_aux_private *aux)
{
	u8 *dp;
	u32 data, len, cnt;
	struct aux_buf *tp = &aux->txp;

	len = tp->len;
	if (len == 0) {
		pr_err("invalid len\n");
		return 0;
	}

	cnt = 0;
	dp = tp->start;

	while (cnt < len) {
		data = *dp;
		data <<= 8;
		data &= 0x00ff00;
		if (cnt == 0)
			data |= BIT(31);

		aux->catalog->data = data;
		aux->catalog->write_data(aux->catalog);

		cnt++;
		dp++;
	}

	data = (tp->trans_num - 1);
	if (tp->tx_mode == AUX_I2C) {
		data |= BIT(8); /* I2C */
		data |= BIT(10); /* NO SEND ADDR */
		data |= BIT(11); /* NO SEND STOP */
	}

	data |= BIT(9); /* GO */
	aux->catalog->data = data;
	aux->catalog->write_trans(aux->catalog);

	return tp->len;
}

static u32 dp_cmd_fifo_rx(struct dp_aux_private *aux, u32 len)
{
	u32 data;
	u8 *dp;
	u32 i;
	struct aux_buf *rp = &aux->rxp;

	data = 0;
	data |= BIT(31); /* INDEX_WRITE */
	data |= BIT(0);  /* read */

	aux->catalog->data = data;
	aux->catalog->write_data(aux->catalog);

	dp = rp->data;

	/* discard first byte */
	data = aux->catalog->read_data(aux->catalog);

	for (i = 0; i < len; i++) {
		data = aux->catalog->read_data(aux->catalog);
		*dp++ = (u8)((data >> 8) & 0xff);
	}

	rp->len = len;
	return len;
}

static void dp_aux_native_handler(struct dp_aux_private *aux)
{
	u32 isr = aux->catalog->isr1;

	if (isr & DP_INTR_AUX_I2C_DONE)
		aux->aux_error_num = DP_AUX_ERR_NONE;
	else if (isr & DP_INTR_WRONG_ADDR)
		aux->aux_error_num = DP_AUX_ERR_ADDR;
	else if (isr & DP_INTR_TIMEOUT)
		aux->aux_error_num = DP_AUX_ERR_TOUT;
	if (isr & DP_INTR_NACK_DEFER)
		aux->aux_error_num = DP_AUX_ERR_NACK;

	complete(&aux->comp);
}

static void dp_aux_i2c_handler(struct dp_aux_private *aux)
{
	u32 isr = aux->catalog->isr1;

	if (isr & DP_INTR_AUX_I2C_DONE) {
		if (isr & (DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER))
			aux->aux_error_num = DP_AUX_ERR_NACK;
		else
			aux->aux_error_num = DP_AUX_ERR_NONE;
	} else {
		if (isr & DP_INTR_WRONG_ADDR)
			aux->aux_error_num = DP_AUX_ERR_ADDR;
		else if (isr & DP_INTR_TIMEOUT)
			aux->aux_error_num = DP_AUX_ERR_TOUT;
		if (isr & DP_INTR_NACK_DEFER)
			aux->aux_error_num = DP_AUX_ERR_NACK_DEFER;
		if (isr & DP_INTR_I2C_NACK)
			aux->aux_error_num = DP_AUX_ERR_NACK;
		if (isr & DP_INTR_I2C_DEFER)
			aux->aux_error_num = DP_AUX_ERR_DEFER;
	}

	complete(&aux->comp);
}

static void dp_aux_isr(struct dp_aux *dp_aux)
{
	struct dp_aux_private *aux;

	if (!dp_aux) {
		pr_err("invalid input\n");
		return;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	aux->catalog->get_irq(aux->catalog);

	if (aux->cmds->tx_mode == AUX_NATIVE)
		dp_aux_native_handler(aux);
	else
		dp_aux_i2c_handler(aux);
}



static int dp_aux_write(struct dp_aux_private *aux)
{
	struct aux_cmd *cm;
	struct aux_buf *tp;
	u32 len, ret, timeout;

	mutex_lock(&aux->mutex);

	tp = &aux->txp;
	dp_aux_buf_reset(tp);

	cm = aux->cmds;
	while (cm) {
		ret = dp_aux_add_cmd(tp, cm);
		if (ret <= 0)
			break;

		if (!cm->next)
			break;
		cm++;
	}

	reinit_completion(&aux->comp);

	len = dp_aux_cmd_fifo_tx(aux);

	timeout = wait_for_completion_timeout(&aux->comp, HZ/4);
	if (!timeout)
		pr_err("aux write timeout\n");

	pr_debug("aux status %s\n",
		dp_aux_get_error(aux->aux_error_num));

	if (aux->aux_error_num == DP_AUX_ERR_NONE)
		ret = len;
	else
		ret = aux->aux_error_num;

	mutex_unlock(&aux->mutex);
	return  ret;
}

static int dp_aux_read(struct dp_aux_private *aux)
{
	struct aux_cmd *cm;
	struct aux_buf *tp, *rp;
	u32 len, ret, timeout;

	mutex_lock(&aux->mutex);

	tp = &aux->txp;
	rp = &aux->rxp;

	dp_aux_buf_reset(tp);
	dp_aux_buf_reset(rp);

	cm = aux->cmds;
	len = 0;

	while (cm) {
		ret = dp_aux_add_cmd(tp, cm);
		len += cm->len;

		if (ret <= 0)
			break;

		if (!cm->next)
			break;
		cm++;
	}

	reinit_completion(&aux->comp);

	dp_aux_cmd_fifo_tx(aux);

	timeout = wait_for_completion_timeout(&aux->comp, HZ/4);
	if (!timeout)
		pr_err("aux read timeout\n");

	pr_debug("aux status %s\n",
		dp_aux_get_error(aux->aux_error_num));

	if (aux->aux_error_num == DP_AUX_ERR_NONE)
		ret = dp_cmd_fifo_rx(aux, len);
	else
		ret = aux->aux_error_num;

	aux->cmds->buf = rp->data;

	mutex_unlock(&aux->mutex);

	return ret;
}

static int dp_aux_write_ex(struct dp_aux *dp_aux, u32 addr, u32 len,
				enum aux_tx_mode mode, u8 *buf)
{
	struct aux_cmd cmd = {0};
	struct dp_aux_private *aux;

	if (!dp_aux || !len) {
		pr_err("invalid input\n");
		return -EINVAL;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	cmd.ex_mode = AUX_WRITE;
	cmd.tx_mode = mode;
	cmd.addr    = addr;
	cmd.len     = len;
	cmd.buf     = buf;

	aux->cmds = &cmd;

	return dp_aux_write(aux);
}

static int dp_aux_read_ex(struct dp_aux *dp_aux, u32 addr, u32 len,
				enum aux_tx_mode mode, u8 **buf)
{
	int rc = 0;
	struct aux_cmd cmd = {0};
	struct dp_aux_private *aux;

	if (!dp_aux || !len) {
		pr_err("invalid input\n");
		rc = -EINVAL;
		goto end;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	cmd.ex_mode = AUX_READ;
	cmd.tx_mode = mode;
	cmd.addr    = addr;
	cmd.len     = len;

	aux->cmds = &cmd;

	rc = dp_aux_read(aux);
	if (rc <= 0) {
		rc = -EINVAL;
		goto end;
	}

	*buf = cmd.buf;
end:
	return rc;
}

static int dp_aux_process(struct dp_aux *dp_aux, struct aux_cmd *cmds)
{
	struct dp_aux_private *aux;

	if (!dp_aux || !cmds) {
		pr_err("invalid input\n");
		return -EINVAL;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	aux->cmds = cmds;

	if (cmds->ex_mode == AUX_READ)
		return dp_aux_read(aux);
	else
		return dp_aux_write(aux);
}

static bool dp_aux_ready(struct dp_aux *dp_aux)
{
	u8 data = 0;
	int count, ret;
	struct dp_aux_private *aux;

	if (!dp_aux) {
		pr_err("invalid input\n");
		goto error;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	for (count = 5; count; count--) {
		ret = dp_aux_write_ex(dp_aux, 0x50, 1, AUX_I2C, &data);
		if (ret >= 0)
			break;

		msleep(100);
	}

	if (count <= 0) {
		pr_err("aux chan NOT ready\n");
		goto error;
	}

	return true;
error:
	return false;
}

static void dp_aux_init(struct dp_aux *dp_aux, u32 *aux_cfg)
{
	struct dp_aux_private *aux;

	if (!dp_aux) {
		pr_err("invalid input\n");
		return;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	aux->catalog->reset(aux->catalog);
	aux->catalog->enable(aux->catalog, true);
	aux->catalog->setup(aux->catalog, aux_cfg);
}

static void dp_aux_deinit(struct dp_aux *dp_aux)
{
	struct dp_aux_private *aux;

	if (!dp_aux) {
		pr_err("invalid input\n");
		return;
	}

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	aux->catalog->enable(aux->catalog, false);
}

struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog)
{
	int rc = 0;
	struct dp_aux_private *aux;
	struct dp_aux *dp_aux;

	if (!catalog) {
		pr_err("invalid input\n");
		rc = -ENODEV;
		goto error;
	}

	aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL);
	if (!aux) {
		rc = -ENOMEM;
		goto error;
	}

	aux->dev = dev;

	dp_aux_buf_set(aux);

	aux->catalog = catalog;

	dp_aux = &aux->dp_aux;

	dp_aux->process = dp_aux_process;
	dp_aux->read    = dp_aux_read_ex;
	dp_aux->write   = dp_aux_write_ex;
	dp_aux->ready   = dp_aux_ready;
	dp_aux->isr     = dp_aux_isr;
	dp_aux->init    = dp_aux_init;
	dp_aux->deinit  = dp_aux_deinit;

	return dp_aux;
error:
	return ERR_PTR(rc);
}

void dp_aux_put(struct dp_aux *dp_aux)
{
	struct dp_aux_private *aux;

	if (!dp_aux)
		return;

	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);

	devm_kfree(aux->dev, aux);
}
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2012-2017, 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.
 *
 */

#ifndef _DP_AUX_H_
#define _DP_AUX_H_

#include "dp_catalog.h"

enum dp_aux_error {
	DP_AUX_ERR_NONE	= 0,
	DP_AUX_ERR_ADDR	= -1,
	DP_AUX_ERR_TOUT	= -2,
	DP_AUX_ERR_NACK	= -3,
	DP_AUX_ERR_DEFER	= -4,
	DP_AUX_ERR_NACK_DEFER	= -5,
};

enum aux_tx_mode {
	AUX_NATIVE,
	AUX_I2C,
};

enum aux_exe_mode {
	AUX_WRITE,
	AUX_READ,
};

struct aux_cmd {
	enum aux_exe_mode ex_mode;
	enum aux_tx_mode tx_mode;
	u32 addr;
	u32 len;
	u8 *buf;
	bool next;
};

struct dp_aux {
	int (*process)(struct dp_aux *aux, struct aux_cmd *cmd);
	int (*write)(struct dp_aux *aux, u32 addr, u32 len,
			enum aux_tx_mode mode, u8 *buf);
	int (*read)(struct dp_aux *aux, u32 addr, u32 len,
			enum aux_tx_mode mode, u8 **buf);
	bool (*ready)(struct dp_aux *aux);
	void (*isr)(struct dp_aux *aux);
	void (*init)(struct dp_aux *aux, u32 *aux_cfg);
	void (*deinit)(struct dp_aux *aux);
};

struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog);
void dp_aux_put(struct dp_aux *aux);

#endif /*__DP_AUX_H_*/