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