267 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (C) 2013 Red Hat4 * Author: Rob Clark <robdclark@gmail.com>5 */6 7#include "hdmi.h"8 9struct hdmi_i2c_adapter {10 struct i2c_adapter base;11 struct hdmi *hdmi;12 bool sw_done;13 wait_queue_head_t ddc_event;14};15#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base)16 17static void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c)18{19 struct hdmi *hdmi = hdmi_i2c->hdmi;20 21 hdmi_write(hdmi, REG_HDMI_DDC_CTRL,22 HDMI_DDC_CTRL_SW_STATUS_RESET);23 hdmi_write(hdmi, REG_HDMI_DDC_CTRL,24 HDMI_DDC_CTRL_SOFT_RESET);25 26 hdmi_write(hdmi, REG_HDMI_DDC_SPEED,27 HDMI_DDC_SPEED_THRESHOLD(2) |28 HDMI_DDC_SPEED_PRESCALE(10));29 30 hdmi_write(hdmi, REG_HDMI_DDC_SETUP,31 HDMI_DDC_SETUP_TIMEOUT(0xff));32 33 /* enable reference timer for 27us */34 hdmi_write(hdmi, REG_HDMI_DDC_REF,35 HDMI_DDC_REF_REFTIMER_ENABLE |36 HDMI_DDC_REF_REFTIMER(27));37}38 39static int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c)40{41 struct hdmi *hdmi = hdmi_i2c->hdmi;42 struct drm_device *dev = hdmi->dev;43 uint32_t retry = 0xffff;44 uint32_t ddc_int_ctrl;45 46 do {47 --retry;48 49 hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,50 HDMI_DDC_INT_CTRL_SW_DONE_ACK |51 HDMI_DDC_INT_CTRL_SW_DONE_MASK);52 53 ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);54 55 } while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry);56 57 if (!retry) {58 DRM_DEV_ERROR(dev->dev, "timeout waiting for DDC\n");59 return -ETIMEDOUT;60 }61 62 hdmi_i2c->sw_done = false;63 64 return 0;65}66 67#define MAX_TRANSACTIONS 468 69static bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c)70{71 struct hdmi *hdmi = hdmi_i2c->hdmi;72 73 if (!hdmi_i2c->sw_done) {74 uint32_t ddc_int_ctrl;75 76 ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);77 78 if ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_MASK) &&79 (ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT)) {80 hdmi_i2c->sw_done = true;81 hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,82 HDMI_DDC_INT_CTRL_SW_DONE_ACK);83 }84 }85 86 return hdmi_i2c->sw_done;87}88 89static int msm_hdmi_i2c_xfer(struct i2c_adapter *i2c,90 struct i2c_msg *msgs, int num)91{92 struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);93 struct hdmi *hdmi = hdmi_i2c->hdmi;94 struct drm_device *dev = hdmi->dev;95 static const uint32_t nack[] = {96 HDMI_DDC_SW_STATUS_NACK0, HDMI_DDC_SW_STATUS_NACK1,97 HDMI_DDC_SW_STATUS_NACK2, HDMI_DDC_SW_STATUS_NACK3,98 };99 int indices[MAX_TRANSACTIONS];100 int ret, i, j, index = 0;101 uint32_t ddc_status, ddc_data, i2c_trans;102 103 num = min(num, MAX_TRANSACTIONS);104 105 WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE));106 107 if (num == 0)108 return num;109 110 init_ddc(hdmi_i2c);111 112 ret = ddc_clear_irq(hdmi_i2c);113 if (ret)114 return ret;115 116 for (i = 0; i < num; i++) {117 struct i2c_msg *p = &msgs[i];118 uint32_t raw_addr = p->addr << 1;119 120 if (p->flags & I2C_M_RD)121 raw_addr |= 1;122 123 ddc_data = HDMI_DDC_DATA_DATA(raw_addr) |124 HDMI_DDC_DATA_DATA_RW(DDC_WRITE);125 126 if (i == 0) {127 ddc_data |= HDMI_DDC_DATA_INDEX(0) |128 HDMI_DDC_DATA_INDEX_WRITE;129 }130 131 hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);132 index++;133 134 indices[i] = index;135 136 if (p->flags & I2C_M_RD) {137 index += p->len;138 } else {139 for (j = 0; j < p->len; j++) {140 ddc_data = HDMI_DDC_DATA_DATA(p->buf[j]) |141 HDMI_DDC_DATA_DATA_RW(DDC_WRITE);142 hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);143 index++;144 }145 }146 147 i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) |148 HDMI_I2C_TRANSACTION_REG_RW(149 (p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) |150 HDMI_I2C_TRANSACTION_REG_START;151 152 if (i == (num - 1))153 i2c_trans |= HDMI_I2C_TRANSACTION_REG_STOP;154 155 hdmi_write(hdmi, REG_HDMI_I2C_TRANSACTION(i), i2c_trans);156 }157 158 /* trigger the transfer: */159 hdmi_write(hdmi, REG_HDMI_DDC_CTRL,160 HDMI_DDC_CTRL_TRANSACTION_CNT(num - 1) |161 HDMI_DDC_CTRL_GO);162 163 ret = wait_event_timeout(hdmi_i2c->ddc_event, sw_done(hdmi_i2c), HZ/4);164 if (ret <= 0) {165 if (ret == 0)166 ret = -ETIMEDOUT;167 dev_warn(dev->dev, "DDC timeout: %d\n", ret);168 DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x",169 hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS),170 hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS),171 hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL));172 return ret;173 }174 175 ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS);176 177 /* read back results of any read transactions: */178 for (i = 0; i < num; i++) {179 struct i2c_msg *p = &msgs[i];180 181 if (!(p->flags & I2C_M_RD))182 continue;183 184 /* check for NACK: */185 if (ddc_status & nack[i]) {186 DBG("ddc_status=%08x", ddc_status);187 break;188 }189 190 ddc_data = HDMI_DDC_DATA_DATA_RW(DDC_READ) |191 HDMI_DDC_DATA_INDEX(indices[i]) |192 HDMI_DDC_DATA_INDEX_WRITE;193 194 hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);195 196 /* discard first byte: */197 hdmi_read(hdmi, REG_HDMI_DDC_DATA);198 199 for (j = 0; j < p->len; j++) {200 ddc_data = hdmi_read(hdmi, REG_HDMI_DDC_DATA);201 p->buf[j] = FIELD(ddc_data, HDMI_DDC_DATA_DATA);202 }203 }204 205 return i;206}207 208static u32 msm_hdmi_i2c_func(struct i2c_adapter *adapter)209{210 return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;211}212 213static const struct i2c_algorithm msm_hdmi_i2c_algorithm = {214 .master_xfer = msm_hdmi_i2c_xfer,215 .functionality = msm_hdmi_i2c_func,216};217 218void msm_hdmi_i2c_irq(struct i2c_adapter *i2c)219{220 struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);221 222 if (sw_done(hdmi_i2c))223 wake_up_all(&hdmi_i2c->ddc_event);224}225 226void msm_hdmi_i2c_destroy(struct i2c_adapter *i2c)227{228 struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);229 i2c_del_adapter(i2c);230 kfree(hdmi_i2c);231}232 233struct i2c_adapter *msm_hdmi_i2c_init(struct hdmi *hdmi)234{235 struct hdmi_i2c_adapter *hdmi_i2c;236 struct i2c_adapter *i2c = NULL;237 int ret;238 239 hdmi_i2c = kzalloc(sizeof(*hdmi_i2c), GFP_KERNEL);240 if (!hdmi_i2c) {241 ret = -ENOMEM;242 goto fail;243 }244 245 i2c = &hdmi_i2c->base;246 247 hdmi_i2c->hdmi = hdmi;248 init_waitqueue_head(&hdmi_i2c->ddc_event);249 250 251 i2c->owner = THIS_MODULE;252 snprintf(i2c->name, sizeof(i2c->name), "msm hdmi i2c");253 i2c->dev.parent = &hdmi->pdev->dev;254 i2c->algo = &msm_hdmi_i2c_algorithm;255 256 ret = i2c_add_adapter(i2c);257 if (ret)258 goto fail;259 260 return i2c;261 262fail:263 if (i2c)264 msm_hdmi_i2c_destroy(i2c);265 return ERR_PTR(ret);266}267