18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2013 Red Hat
48c2ecf20Sopenharmony_ci * Author: Rob Clark <robdclark@gmail.com>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include "hdmi.h"
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_cistruct hdmi_i2c_adapter {
108c2ecf20Sopenharmony_ci	struct i2c_adapter base;
118c2ecf20Sopenharmony_ci	struct hdmi *hdmi;
128c2ecf20Sopenharmony_ci	bool sw_done;
138c2ecf20Sopenharmony_ci	wait_queue_head_t ddc_event;
148c2ecf20Sopenharmony_ci};
158c2ecf20Sopenharmony_ci#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base)
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistatic void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c)
188c2ecf20Sopenharmony_ci{
198c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_i2c->hdmi;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
228c2ecf20Sopenharmony_ci			HDMI_DDC_CTRL_SW_STATUS_RESET);
238c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
248c2ecf20Sopenharmony_ci			HDMI_DDC_CTRL_SOFT_RESET);
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_DDC_SPEED,
278c2ecf20Sopenharmony_ci			HDMI_DDC_SPEED_THRESHOLD(2) |
288c2ecf20Sopenharmony_ci			HDMI_DDC_SPEED_PRESCALE(10));
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_DDC_SETUP,
318c2ecf20Sopenharmony_ci			HDMI_DDC_SETUP_TIMEOUT(0xff));
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	/* enable reference timer for 27us */
348c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_DDC_REF,
358c2ecf20Sopenharmony_ci			HDMI_DDC_REF_REFTIMER_ENABLE |
368c2ecf20Sopenharmony_ci			HDMI_DDC_REF_REFTIMER(27));
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_i2c->hdmi;
428c2ecf20Sopenharmony_ci	struct drm_device *dev = hdmi->dev;
438c2ecf20Sopenharmony_ci	uint32_t retry = 0xffff;
448c2ecf20Sopenharmony_ci	uint32_t ddc_int_ctrl;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	do {
478c2ecf20Sopenharmony_ci		--retry;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
508c2ecf20Sopenharmony_ci				HDMI_DDC_INT_CTRL_SW_DONE_ACK |
518c2ecf20Sopenharmony_ci				HDMI_DDC_INT_CTRL_SW_DONE_MASK);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci		ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	} while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry);
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	if (!retry) {
588c2ecf20Sopenharmony_ci		DRM_DEV_ERROR(dev->dev, "timeout waiting for DDC\n");
598c2ecf20Sopenharmony_ci		return -ETIMEDOUT;
608c2ecf20Sopenharmony_ci	}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	hdmi_i2c->sw_done = false;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	return 0;
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci#define MAX_TRANSACTIONS 4
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_i2c->hdmi;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (!hdmi_i2c->sw_done) {
748c2ecf20Sopenharmony_ci		uint32_t ddc_int_ctrl;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci		if ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_MASK) &&
798c2ecf20Sopenharmony_ci				(ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT)) {
808c2ecf20Sopenharmony_ci			hdmi_i2c->sw_done = true;
818c2ecf20Sopenharmony_ci			hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
828c2ecf20Sopenharmony_ci					HDMI_DDC_INT_CTRL_SW_DONE_ACK);
838c2ecf20Sopenharmony_ci		}
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return hdmi_i2c->sw_done;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic int msm_hdmi_i2c_xfer(struct i2c_adapter *i2c,
908c2ecf20Sopenharmony_ci		struct i2c_msg *msgs, int num)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
938c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_i2c->hdmi;
948c2ecf20Sopenharmony_ci	struct drm_device *dev = hdmi->dev;
958c2ecf20Sopenharmony_ci	static const uint32_t nack[] = {
968c2ecf20Sopenharmony_ci			HDMI_DDC_SW_STATUS_NACK0, HDMI_DDC_SW_STATUS_NACK1,
978c2ecf20Sopenharmony_ci			HDMI_DDC_SW_STATUS_NACK2, HDMI_DDC_SW_STATUS_NACK3,
988c2ecf20Sopenharmony_ci	};
998c2ecf20Sopenharmony_ci	int indices[MAX_TRANSACTIONS];
1008c2ecf20Sopenharmony_ci	int ret, i, j, index = 0;
1018c2ecf20Sopenharmony_ci	uint32_t ddc_status, ddc_data, i2c_trans;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	num = min(num, MAX_TRANSACTIONS);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE));
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	if (num == 0)
1088c2ecf20Sopenharmony_ci		return num;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	init_ddc(hdmi_i2c);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	ret = ddc_clear_irq(hdmi_i2c);
1138c2ecf20Sopenharmony_ci	if (ret)
1148c2ecf20Sopenharmony_ci		return ret;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	for (i = 0; i < num; i++) {
1178c2ecf20Sopenharmony_ci		struct i2c_msg *p = &msgs[i];
1188c2ecf20Sopenharmony_ci		uint32_t raw_addr = p->addr << 1;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci		if (p->flags & I2C_M_RD)
1218c2ecf20Sopenharmony_ci			raw_addr |= 1;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci		ddc_data = HDMI_DDC_DATA_DATA(raw_addr) |
1248c2ecf20Sopenharmony_ci				HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci		if (i == 0) {
1278c2ecf20Sopenharmony_ci			ddc_data |= HDMI_DDC_DATA_INDEX(0) |
1288c2ecf20Sopenharmony_ci					HDMI_DDC_DATA_INDEX_WRITE;
1298c2ecf20Sopenharmony_ci		}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
1328c2ecf20Sopenharmony_ci		index++;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci		indices[i] = index;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci		if (p->flags & I2C_M_RD) {
1378c2ecf20Sopenharmony_ci			index += p->len;
1388c2ecf20Sopenharmony_ci		} else {
1398c2ecf20Sopenharmony_ci			for (j = 0; j < p->len; j++) {
1408c2ecf20Sopenharmony_ci				ddc_data = HDMI_DDC_DATA_DATA(p->buf[j]) |
1418c2ecf20Sopenharmony_ci						HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
1428c2ecf20Sopenharmony_ci				hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
1438c2ecf20Sopenharmony_ci				index++;
1448c2ecf20Sopenharmony_ci			}
1458c2ecf20Sopenharmony_ci		}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) |
1488c2ecf20Sopenharmony_ci				HDMI_I2C_TRANSACTION_REG_RW(
1498c2ecf20Sopenharmony_ci						(p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) |
1508c2ecf20Sopenharmony_ci				HDMI_I2C_TRANSACTION_REG_START;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci		if (i == (num - 1))
1538c2ecf20Sopenharmony_ci			i2c_trans |= HDMI_I2C_TRANSACTION_REG_STOP;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_I2C_TRANSACTION(i), i2c_trans);
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	/* trigger the transfer: */
1598c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
1608c2ecf20Sopenharmony_ci			HDMI_DDC_CTRL_TRANSACTION_CNT(num - 1) |
1618c2ecf20Sopenharmony_ci			HDMI_DDC_CTRL_GO);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	ret = wait_event_timeout(hdmi_i2c->ddc_event, sw_done(hdmi_i2c), HZ/4);
1648c2ecf20Sopenharmony_ci	if (ret <= 0) {
1658c2ecf20Sopenharmony_ci		if (ret == 0)
1668c2ecf20Sopenharmony_ci			ret = -ETIMEDOUT;
1678c2ecf20Sopenharmony_ci		dev_warn(dev->dev, "DDC timeout: %d\n", ret);
1688c2ecf20Sopenharmony_ci		DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x",
1698c2ecf20Sopenharmony_ci				hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS),
1708c2ecf20Sopenharmony_ci				hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS),
1718c2ecf20Sopenharmony_ci				hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL));
1728c2ecf20Sopenharmony_ci		return ret;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/* read back results of any read transactions: */
1788c2ecf20Sopenharmony_ci	for (i = 0; i < num; i++) {
1798c2ecf20Sopenharmony_ci		struct i2c_msg *p = &msgs[i];
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci		if (!(p->flags & I2C_M_RD))
1828c2ecf20Sopenharmony_ci			continue;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci		/* check for NACK: */
1858c2ecf20Sopenharmony_ci		if (ddc_status & nack[i]) {
1868c2ecf20Sopenharmony_ci			DBG("ddc_status=%08x", ddc_status);
1878c2ecf20Sopenharmony_ci			break;
1888c2ecf20Sopenharmony_ci		}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci		ddc_data = HDMI_DDC_DATA_DATA_RW(DDC_READ) |
1918c2ecf20Sopenharmony_ci				HDMI_DDC_DATA_INDEX(indices[i]) |
1928c2ecf20Sopenharmony_ci				HDMI_DDC_DATA_INDEX_WRITE;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci		/* discard first byte: */
1978c2ecf20Sopenharmony_ci		hdmi_read(hdmi, REG_HDMI_DDC_DATA);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci		for (j = 0; j < p->len; j++) {
2008c2ecf20Sopenharmony_ci			ddc_data = hdmi_read(hdmi, REG_HDMI_DDC_DATA);
2018c2ecf20Sopenharmony_ci			p->buf[j] = FIELD(ddc_data, HDMI_DDC_DATA_DATA);
2028c2ecf20Sopenharmony_ci		}
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return i;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic u32 msm_hdmi_i2c_func(struct i2c_adapter *adapter)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_cistatic const struct i2c_algorithm msm_hdmi_i2c_algorithm = {
2148c2ecf20Sopenharmony_ci	.master_xfer	= msm_hdmi_i2c_xfer,
2158c2ecf20Sopenharmony_ci	.functionality	= msm_hdmi_i2c_func,
2168c2ecf20Sopenharmony_ci};
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_civoid msm_hdmi_i2c_irq(struct i2c_adapter *i2c)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	if (sw_done(hdmi_i2c))
2238c2ecf20Sopenharmony_ci		wake_up_all(&hdmi_i2c->ddc_event);
2248c2ecf20Sopenharmony_ci}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_civoid msm_hdmi_i2c_destroy(struct i2c_adapter *i2c)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
2298c2ecf20Sopenharmony_ci	i2c_del_adapter(i2c);
2308c2ecf20Sopenharmony_ci	kfree(hdmi_i2c);
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_cistruct i2c_adapter *msm_hdmi_i2c_init(struct hdmi *hdmi)
2348c2ecf20Sopenharmony_ci{
2358c2ecf20Sopenharmony_ci	struct hdmi_i2c_adapter *hdmi_i2c;
2368c2ecf20Sopenharmony_ci	struct i2c_adapter *i2c = NULL;
2378c2ecf20Sopenharmony_ci	int ret;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	hdmi_i2c = kzalloc(sizeof(*hdmi_i2c), GFP_KERNEL);
2408c2ecf20Sopenharmony_ci	if (!hdmi_i2c) {
2418c2ecf20Sopenharmony_ci		ret = -ENOMEM;
2428c2ecf20Sopenharmony_ci		goto fail;
2438c2ecf20Sopenharmony_ci	}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	i2c = &hdmi_i2c->base;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	hdmi_i2c->hdmi = hdmi;
2488c2ecf20Sopenharmony_ci	init_waitqueue_head(&hdmi_i2c->ddc_event);
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	i2c->owner = THIS_MODULE;
2528c2ecf20Sopenharmony_ci	i2c->class = I2C_CLASS_DDC;
2538c2ecf20Sopenharmony_ci	snprintf(i2c->name, sizeof(i2c->name), "msm hdmi i2c");
2548c2ecf20Sopenharmony_ci	i2c->dev.parent = &hdmi->pdev->dev;
2558c2ecf20Sopenharmony_ci	i2c->algo = &msm_hdmi_i2c_algorithm;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	ret = i2c_add_adapter(i2c);
2588c2ecf20Sopenharmony_ci	if (ret)
2598c2ecf20Sopenharmony_ci		goto fail;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	return i2c;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_cifail:
2648c2ecf20Sopenharmony_ci	if (i2c)
2658c2ecf20Sopenharmony_ci		msm_hdmi_i2c_destroy(i2c);
2668c2ecf20Sopenharmony_ci	return ERR_PTR(ret);
2678c2ecf20Sopenharmony_ci}
268