| /* Copyright (c) 2010-2013, 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 <linux/io.h> |
| #include <mach/board.h> |
| #include "mdss_hdmi_util.h" |
| |
| static struct msm_hdmi_mode_timing_info |
| hdmi_supported_video_mode_lut[HDMI_VFRMT_MAX]; |
| |
| void hdmi_del_supported_mode(u32 mode) |
| { |
| struct msm_hdmi_mode_timing_info *ret = NULL; |
| DEV_DBG("%s: removing %s\n", __func__, |
| msm_hdmi_mode_2string(mode)); |
| ret = &hdmi_supported_video_mode_lut[mode]; |
| if (ret != NULL && ret->supported) |
| ret->supported = false; |
| } |
| |
| const struct msm_hdmi_mode_timing_info *hdmi_get_supported_mode(u32 mode) |
| { |
| const struct msm_hdmi_mode_timing_info *ret = NULL; |
| |
| if (mode >= HDMI_VFRMT_MAX) |
| return NULL; |
| |
| ret = &hdmi_supported_video_mode_lut[mode]; |
| |
| if (ret == NULL || !ret->supported) |
| return NULL; |
| |
| return ret; |
| } /* hdmi_get_supported_mode */ |
| |
| int hdmi_get_video_id_code(struct msm_hdmi_mode_timing_info *timing_in) |
| { |
| int i, vic = -1; |
| struct msm_hdmi_mode_timing_info *supported_timing; |
| |
| if (!timing_in) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| goto exit; |
| } |
| |
| /* active_low_h, active_low_v and interlaced are not checked against */ |
| for (i = 0; i < HDMI_VFRMT_MAX; i++) { |
| supported_timing = &hdmi_supported_video_mode_lut[i]; |
| |
| if (!supported_timing->supported) |
| continue; |
| if (timing_in->active_h != supported_timing->active_h) |
| continue; |
| if (timing_in->front_porch_h != supported_timing->front_porch_h) |
| continue; |
| if (timing_in->pulse_width_h != supported_timing->pulse_width_h) |
| continue; |
| if (timing_in->back_porch_h != supported_timing->back_porch_h) |
| continue; |
| if (timing_in->active_v != supported_timing->active_v) |
| continue; |
| if (timing_in->front_porch_v != supported_timing->front_porch_v) |
| continue; |
| if (timing_in->pulse_width_v != supported_timing->pulse_width_v) |
| continue; |
| if (timing_in->back_porch_v != supported_timing->back_porch_v) |
| continue; |
| if (timing_in->pixel_freq != supported_timing->pixel_freq) |
| continue; |
| if (timing_in->refresh_rate != supported_timing->refresh_rate) |
| continue; |
| |
| vic = (int)supported_timing->video_format; |
| break; |
| } |
| |
| if (vic < 0) { |
| for (i = 0; i < HDMI_VFRMT_MAX; i++) { |
| supported_timing = &hdmi_supported_video_mode_lut[i]; |
| if (!supported_timing->supported) |
| continue; |
| if (timing_in->active_h != supported_timing->active_h) |
| continue; |
| if (timing_in->active_v != supported_timing->active_v) |
| continue; |
| vic = (int)supported_timing->video_format; |
| break; |
| } |
| } |
| |
| if (vic < 0) { |
| DEV_ERR("%s: timing is not supported h=%d v=%d\n", |
| __func__, timing_in->active_h, timing_in->active_v); |
| } |
| |
| exit: |
| DEV_DBG("%s: vic = %d timing = %s\n", __func__, vic, |
| msm_hdmi_mode_2string((u32)vic)); |
| |
| return vic; |
| } /* hdmi_get_video_id_code */ |
| |
| /* Table indicating the video format supported by the HDMI TX Core */ |
| /* Valid pclk rates (Mhz): 25.2, 27, 27.03, 74.25, 148.5, 268.5, 297 */ |
| void hdmi_setup_video_mode_lut(void) |
| { |
| MSM_HDMI_MODES_INIT_TIMINGS(hdmi_supported_video_mode_lut); |
| |
| /* Add all supported CEA modes to the lut */ |
| MSM_HDMI_MODES_SET_SUPP_TIMINGS( |
| hdmi_supported_video_mode_lut, MSM_HDMI_MODES_CEA); |
| |
| /* Add all supported extended hdmi modes to the lut */ |
| MSM_HDMI_MODES_SET_SUPP_TIMINGS( |
| hdmi_supported_video_mode_lut, MSM_HDMI_MODES_XTND); |
| |
| /* Add any other specific DVI timings (DVI modes, etc.) */ |
| MSM_HDMI_MODES_SET_SUPP_TIMINGS( |
| hdmi_supported_video_mode_lut, MSM_HDMI_MODES_DVI); |
| } /* hdmi_setup_video_mode_lut */ |
| |
| const char *hdmi_get_single_video_3d_fmt_2string(u32 format) |
| { |
| switch (format) { |
| case TOP_AND_BOTTOM: return "TAB"; |
| case FRAME_PACKING: return "FP"; |
| case SIDE_BY_SIDE_HALF: return "SSH"; |
| } |
| return ""; |
| } /* hdmi_get_single_video_3d_fmt_2string */ |
| |
| ssize_t hdmi_get_video_3d_fmt_2string(u32 format, char *buf, u32 size) |
| { |
| ssize_t ret, len = 0; |
| ret = scnprintf(buf, size, "%s", |
| hdmi_get_single_video_3d_fmt_2string( |
| format & FRAME_PACKING)); |
| len += ret; |
| |
| if (len && (format & TOP_AND_BOTTOM)) |
| ret = scnprintf(buf + len, size - len, ":%s", |
| hdmi_get_single_video_3d_fmt_2string( |
| format & TOP_AND_BOTTOM)); |
| else |
| ret = scnprintf(buf + len, size - len, "%s", |
| hdmi_get_single_video_3d_fmt_2string( |
| format & TOP_AND_BOTTOM)); |
| len += ret; |
| |
| if (len && (format & SIDE_BY_SIDE_HALF)) |
| ret = scnprintf(buf + len, size - len, ":%s", |
| hdmi_get_single_video_3d_fmt_2string( |
| format & SIDE_BY_SIDE_HALF)); |
| else |
| ret = scnprintf(buf + len, size - len, "%s", |
| hdmi_get_single_video_3d_fmt_2string( |
| format & SIDE_BY_SIDE_HALF)); |
| len += ret; |
| |
| return len; |
| } /* hdmi_get_video_3d_fmt_2string */ |
| |
| static void hdmi_ddc_print_data(struct hdmi_tx_ddc_data *ddc_data, |
| const char *caller) |
| { |
| if (!ddc_data) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| DEV_DBG("%s: buf=%p, d_len=0x%x, d_addr=0x%x, no_align=%d\n", |
| caller, ddc_data->data_buf, ddc_data->data_len, |
| ddc_data->dev_addr, ddc_data->no_align); |
| DEV_DBG("%s: offset=0x%x, req_len=0x%x, retry=%d, what=%s\n", |
| caller, ddc_data->offset, ddc_data->request_len, |
| ddc_data->retry, ddc_data->what); |
| } /* hdmi_ddc_print_data */ |
| |
| static int hdmi_ddc_clear_irq(struct hdmi_tx_ddc_ctrl *ddc_ctrl, |
| char *what) |
| { |
| u32 reg_val, time_out_count; |
| |
| if (!ddc_ctrl || !ddc_ctrl->io) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* clear pending and enable interrupt */ |
| time_out_count = 0xFFFF; |
| do { |
| --time_out_count; |
| /* Clear and Enable DDC interrupt */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, |
| BIT(2) | BIT(1)); |
| reg_val = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL); |
| } while ((reg_val & BIT(0)) && time_out_count); |
| |
| if (!time_out_count) { |
| DEV_ERR("%s[%s]: timedout\n", __func__, what); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } /*hdmi_ddc_clear_irq */ |
| |
| static int hdmi_ddc_read_retry(struct hdmi_tx_ddc_ctrl *ddc_ctrl, |
| struct hdmi_tx_ddc_data *ddc_data) |
| { |
| u32 reg_val, ndx, time_out_count; |
| int status = 0; |
| int log_retry_fail; |
| |
| if (!ddc_ctrl || !ddc_ctrl->io || !ddc_data) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!ddc_data->data_buf) { |
| status = -EINVAL; |
| DEV_ERR("%s[%s]: invalid buf\n", __func__, ddc_data->what); |
| goto error; |
| } |
| |
| hdmi_ddc_print_data(ddc_data, __func__); |
| |
| log_retry_fail = ddc_data->retry != 1; |
| again: |
| status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what); |
| if (status) |
| goto error; |
| |
| /* Ensure Device Address has LSB set to 0 to indicate Slave addr read */ |
| ddc_data->dev_addr &= 0xFE; |
| |
| /* |
| * 1. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #1 |
| * DATA_RW = 0x0 (write) |
| * DATA = linkAddress (primary link address and writing) |
| * INDEX = 0x0 (initial offset into buffer) |
| * INDEX_WRITE = 0x1 (setting initial offset) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| BIT(31) | (ddc_data->dev_addr << 8)); |
| |
| /* |
| * 2. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #2 |
| * DATA_RW = 0x0 (write) |
| * DATA = offsetAddress |
| * INDEX = 0x0 |
| * INDEX_WRITE = 0x0 (auto-increment by hardware) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, ddc_data->offset << 8); |
| |
| /* |
| * 3. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #3 |
| * DATA_RW = 0x0 (write) |
| * DATA = linkAddress + 1 (primary link address 0x74 and reading) |
| * INDEX = 0x0 |
| * INDEX_WRITE = 0x0 (auto-increment by hardware) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| (ddc_data->dev_addr | BIT(0)) << 8); |
| |
| /* Data setup is complete, now setup the transaction characteristics */ |
| |
| /* |
| * 4. Write to HDMI_I2C_TRANSACTION0 with the following fields set in |
| * order to handle characteristics of portion #1 and portion #2 |
| * RW0 = 0x0 (write) |
| * START0 = 0x1 (insert START bit) |
| * STOP0 = 0x0 (do NOT insert STOP bit) |
| * CNT0 = 0x1 (single byte transaction excluding address) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS0, BIT(12) | BIT(16)); |
| |
| /* |
| * 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in |
| * order to handle characteristics of portion #3 |
| * RW1 = 0x1 (read) |
| * START1 = 0x1 (insert START bit) |
| * STOP1 = 0x1 (insert STOP bit) |
| * CNT1 = data_len (it's 128 (0x80) for a blk read) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS1, |
| BIT(0) | BIT(12) | BIT(13) | (ddc_data->request_len << 16)); |
| |
| /* Trigger the I2C transfer */ |
| |
| /* |
| * 6. Write to HDMI_I2C_CONTROL to kick off the hardware. |
| * Note that NOTHING has been transmitted on the DDC lines up to this |
| * point. |
| * TRANSACTION_CNT = 0x1 (execute transaction0 followed by |
| * transaction1) |
| * SEND_RESET = Set to 1 to send reset sequence |
| * GO = 0x1 (kicks off hardware) |
| */ |
| INIT_COMPLETION(ddc_ctrl->ddc_sw_done); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(0) | BIT(20)); |
| |
| time_out_count = wait_for_completion_timeout( |
| &ddc_ctrl->ddc_sw_done, HZ/2); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, BIT(1)); |
| if (!time_out_count) { |
| if (ddc_data->retry-- > 0) { |
| DEV_INFO("%s: failed timout, retry=%d\n", __func__, |
| ddc_data->retry); |
| goto again; |
| } |
| status = -ETIMEDOUT; |
| DEV_ERR("%s: timedout(7), Int Ctrl=%08x\n", __func__, |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_INT_CTRL)); |
| DEV_ERR("%s: DDC SW Status=%08x, HW Status=%08x\n", |
| __func__, |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_SW_STATUS), |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_HW_STATUS)); |
| goto error; |
| } |
| |
| /* Read DDC status */ |
| reg_val = DSS_REG_R(ddc_ctrl->io, HDMI_DDC_SW_STATUS); |
| reg_val &= BIT(12) | BIT(13) | BIT(14) | BIT(15); |
| |
| /* Check if any NACK occurred */ |
| if (reg_val) { |
| /* SW_STATUS_RESET */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(3)); |
| |
| if (ddc_data->retry == 1) |
| /* SOFT_RESET */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(1)); |
| |
| if (ddc_data->retry-- > 0) { |
| DEV_DBG("%s(%s): failed NACK=0x%08x, retry=%d\n", |
| __func__, ddc_data->what, reg_val, |
| ddc_data->retry); |
| DEV_DBG("%s: daddr=0x%02x,off=0x%02x,len=%d\n", |
| __func__, ddc_data->dev_addr, |
| ddc_data->offset, ddc_data->data_len); |
| goto again; |
| } |
| status = -EIO; |
| if (log_retry_fail) { |
| DEV_ERR("%s(%s): failed NACK=0x%08x\n", |
| __func__, ddc_data->what, reg_val); |
| DEV_ERR("%s: daddr=0x%02x,off=0x%02x,len=%d\n", |
| __func__, ddc_data->dev_addr, |
| ddc_data->offset, ddc_data->data_len); |
| } |
| goto error; |
| } |
| |
| /* |
| * 8. ALL data is now available and waiting in the DDC buffer. |
| * Read HDMI_I2C_DATA with the following fields set |
| * RW = 0x1 (read) |
| * DATA = BCAPS (this is field where data is pulled from) |
| * INDEX = 0x3 (where the data has been placed in buffer by hardware) |
| * INDEX_WRITE = 0x1 (explicitly define offset) |
| */ |
| /* Write this data to DDC buffer */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| BIT(0) | (3 << 16) | BIT(31)); |
| |
| /* Discard first byte */ |
| DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA); |
| for (ndx = 0; ndx < ddc_data->data_len; ++ndx) { |
| reg_val = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA); |
| ddc_data->data_buf[ndx] = (u8)((reg_val & 0x0000FF00) >> 8); |
| } |
| |
| DEV_DBG("%s[%s] success\n", __func__, ddc_data->what); |
| |
| error: |
| return status; |
| } /* hdmi_ddc_read_retry */ |
| |
| void hdmi_ddc_config(struct hdmi_tx_ddc_ctrl *ddc_ctrl) |
| { |
| if (!ddc_ctrl || !ddc_ctrl->io) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return; |
| } |
| |
| /* Configure Pre-Scale multiplier & Threshold */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_SPEED, (10 << 16) | (2 << 0)); |
| |
| /* |
| * Setting 31:24 bits : Time units to wait before timeout |
| * when clock is being stalled by external sink device |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_SETUP, 0xFF000000); |
| |
| /* Enable reference timer to 19 micro-seconds */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_REF, (1 << 16) | (19 << 0)); |
| } /* hdmi_ddc_config */ |
| |
| int hdmi_ddc_isr(struct hdmi_tx_ddc_ctrl *ddc_ctrl) |
| { |
| u32 ddc_int_ctrl; |
| |
| if (!ddc_ctrl || !ddc_ctrl->io) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| ddc_int_ctrl = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL); |
| if ((ddc_int_ctrl & BIT(2)) && (ddc_int_ctrl & BIT(0))) { |
| /* SW_DONE INT occured, clr it */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, |
| ddc_int_ctrl | BIT(1)); |
| complete(&ddc_ctrl->ddc_sw_done); |
| } |
| |
| DEV_DBG("%s: ddc_int_ctrl=%04x\n", __func__, ddc_int_ctrl); |
| |
| return 0; |
| } /* hdmi_ddc_isr */ |
| |
| int hdmi_ddc_read(struct hdmi_tx_ddc_ctrl *ddc_ctrl, |
| struct hdmi_tx_ddc_data *ddc_data) |
| { |
| int rc = 0; |
| |
| if (!ddc_ctrl || !ddc_data) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| rc = hdmi_ddc_read_retry(ddc_ctrl, ddc_data); |
| if (!rc) |
| return rc; |
| |
| if (ddc_data->no_align) { |
| rc = hdmi_ddc_read_retry(ddc_ctrl, ddc_data); |
| } else { |
| ddc_data->request_len = 32 * ((ddc_data->data_len + 31) / 32); |
| rc = hdmi_ddc_read_retry(ddc_ctrl, ddc_data); |
| } |
| |
| return rc; |
| } /* hdmi_ddc_read */ |
| |
| int hdmi_ddc_read_seg(struct hdmi_tx_ddc_ctrl *ddc_ctrl, |
| struct hdmi_tx_ddc_data *ddc_data) |
| { |
| int status = 0; |
| u32 reg_val, ndx, time_out_count; |
| int log_retry_fail; |
| int seg_addr = 0x60, seg_num = 0x01; |
| |
| if (!ddc_ctrl || !ddc_ctrl->io || !ddc_data) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!ddc_data->data_buf) { |
| status = -EINVAL; |
| DEV_ERR("%s[%s]: invalid buf\n", __func__, ddc_data->what); |
| goto error; |
| } |
| |
| log_retry_fail = ddc_data->retry != 1; |
| |
| again: |
| status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what); |
| if (status) |
| goto error; |
| |
| /* Ensure Device Address has LSB set to 0 to indicate Slave addr read */ |
| ddc_data->dev_addr &= 0xFE; |
| |
| /* |
| * 1. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #1 |
| * DATA_RW = 0x0 (write) |
| * DATA = linkAddress (primary link address and writing) |
| * INDEX = 0x0 (initial offset into buffer) |
| * INDEX_WRITE = 0x1 (setting initial offset) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, BIT(31) | (seg_addr << 8)); |
| |
| /* |
| * 2. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #2 |
| * DATA_RW = 0x0 (write) |
| * DATA = offsetAddress |
| * INDEX = 0x0 |
| * INDEX_WRITE = 0x0 (auto-increment by hardware) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, seg_num << 8); |
| |
| /* |
| * 3. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #3 |
| * DATA_RW = 0x0 (write) |
| * DATA = linkAddress + 1 (primary link address 0x74 and reading) |
| * INDEX = 0x0 |
| * INDEX_WRITE = 0x0 (auto-increment by hardware) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, ddc_data->dev_addr << 8); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, ddc_data->offset << 8); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| (ddc_data->dev_addr | BIT(0)) << 8); |
| |
| /* Data setup is complete, now setup the transaction characteristics */ |
| |
| /* |
| * 4. Write to HDMI_I2C_TRANSACTION0 with the following fields set in |
| * order to handle characteristics of portion #1 and portion #2 |
| * RW0 = 0x0 (write) |
| * START0 = 0x1 (insert START bit) |
| * STOP0 = 0x0 (do NOT insert STOP bit) |
| * CNT0 = 0x1 (single byte transaction excluding address) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS0, BIT(12) | BIT(16)); |
| |
| /* |
| * 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in |
| * order to handle characteristics of portion #3 |
| * RW1 = 0x1 (read) |
| * START1 = 0x1 (insert START bit) |
| * STOP1 = 0x1 (insert STOP bit) |
| * CNT1 = data_len (it's 128 (0x80) for a blk read) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS1, BIT(12) | BIT(16)); |
| |
| /* |
| * 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in |
| * order to handle characteristics of portion #3 |
| * RW1 = 0x1 (read) |
| * START1 = 0x1 (insert START bit) |
| * STOP1 = 0x1 (insert STOP bit) |
| * CNT1 = data_len (it's 128 (0x80) for a blk read) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS2, |
| BIT(0) | BIT(12) | BIT(13) | (ddc_data->request_len << 16)); |
| |
| /* Trigger the I2C transfer */ |
| |
| /* |
| * 6. Write to HDMI_I2C_CONTROL to kick off the hardware. |
| * Note that NOTHING has been transmitted on the DDC lines up to this |
| * point. |
| * TRANSACTION_CNT = 0x2 (execute transaction0 followed by |
| * transaction1) |
| * GO = 0x1 (kicks off hardware) |
| */ |
| INIT_COMPLETION(ddc_ctrl->ddc_sw_done); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(0) | BIT(21)); |
| |
| time_out_count = wait_for_completion_timeout( |
| &ddc_ctrl->ddc_sw_done, HZ/2); |
| |
| reg_val = DSS_REG_R(ddc_ctrl->io, HDMI_DDC_INT_CTRL); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, reg_val & (~BIT(2))); |
| if (!time_out_count) { |
| if (ddc_data->retry-- > 0) { |
| DEV_INFO("%s: failed timout, retry=%d\n", __func__, |
| ddc_data->retry); |
| goto again; |
| } |
| status = -ETIMEDOUT; |
| DEV_ERR("%s: timedout(7), Int Ctrl=%08x\n", __func__, |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_INT_CTRL)); |
| DEV_ERR("%s: DDC SW Status=%08x, HW Status=%08x\n", |
| __func__, |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_SW_STATUS), |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_HW_STATUS)); |
| goto error; |
| } |
| |
| /* Read DDC status */ |
| reg_val = DSS_REG_R(ddc_ctrl->io, HDMI_DDC_SW_STATUS); |
| reg_val &= BIT(12) | BIT(13) | BIT(14) | BIT(15); |
| |
| /* Check if any NACK occurred */ |
| if (reg_val) { |
| /* SW_STATUS_RESET */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(3)); |
| if (ddc_data->retry == 1) |
| /* SOFT_RESET */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(1)); |
| if (ddc_data->retry-- > 0) { |
| DEV_DBG("%s(%s): failed NACK=0x%08x, retry=%d\n", |
| __func__, ddc_data->what, reg_val, |
| ddc_data->retry); |
| DEV_DBG("%s: daddr=0x%02x,off=0x%02x,len=%d\n", |
| __func__, ddc_data->dev_addr, |
| ddc_data->offset, ddc_data->data_len); |
| goto again; |
| } |
| status = -EIO; |
| if (log_retry_fail) { |
| DEV_ERR("%s(%s): failed NACK=0x%08x\n", |
| __func__, ddc_data->what, reg_val); |
| DEV_ERR("%s: daddr=0x%02x,off=0x%02x,len=%d\n", |
| __func__, ddc_data->dev_addr, |
| ddc_data->offset, ddc_data->data_len); |
| } |
| goto error; |
| } |
| |
| /* |
| * 8. ALL data is now available and waiting in the DDC buffer. |
| * Read HDMI_I2C_DATA with the following fields set |
| * RW = 0x1 (read) |
| * DATA = BCAPS (this is field where data is pulled from) |
| * INDEX = 0x5 (where the data has been placed in buffer by hardware) |
| * INDEX_WRITE = 0x1 (explicitly define offset) |
| */ |
| /* Write this data to DDC buffer */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| BIT(0) | (5 << 16) | BIT(31)); |
| |
| /* Discard first byte */ |
| DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA); |
| |
| for (ndx = 0; ndx < ddc_data->data_len; ++ndx) { |
| reg_val = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA); |
| ddc_data->data_buf[ndx] = (u8) ((reg_val & 0x0000FF00) >> 8); |
| } |
| |
| DEV_DBG("%s[%s] success\n", __func__, ddc_data->what); |
| |
| error: |
| return status; |
| } /* hdmi_ddc_read_seg */ |
| |
| int hdmi_ddc_write(struct hdmi_tx_ddc_ctrl *ddc_ctrl, |
| struct hdmi_tx_ddc_data *ddc_data) |
| { |
| u32 reg_val, ndx; |
| int status = 0, retry = 10; |
| u32 time_out_count; |
| |
| if (!ddc_ctrl || !ddc_ctrl->io || !ddc_data) { |
| DEV_ERR("%s: invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!ddc_data->data_buf) { |
| status = -EINVAL; |
| DEV_ERR("%s[%s]: invalid buf\n", __func__, ddc_data->what); |
| goto error; |
| } |
| |
| again: |
| status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what); |
| if (status) |
| goto error; |
| |
| /* Ensure Device Address has LSB set to 0 to indicate Slave addr read */ |
| ddc_data->dev_addr &= 0xFE; |
| |
| /* |
| * 1. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #1 |
| * DATA_RW = 0x1 (write) |
| * DATA = linkAddress (primary link address and writing) |
| * INDEX = 0x0 (initial offset into buffer) |
| * INDEX_WRITE = 0x1 (setting initial offset) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| BIT(31) | (ddc_data->dev_addr << 8)); |
| |
| /* |
| * 2. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #2 |
| * DATA_RW = 0x0 (write) |
| * DATA = offsetAddress |
| * INDEX = 0x0 |
| * INDEX_WRITE = 0x0 (auto-increment by hardware) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, ddc_data->offset << 8); |
| |
| /* |
| * 3. Write to HDMI_I2C_DATA with the following fields set in order to |
| * handle portion #3 |
| * DATA_RW = 0x0 (write) |
| * DATA = data_buf[ndx] |
| * INDEX = 0x0 |
| * INDEX_WRITE = 0x0 (auto-increment by hardware) |
| */ |
| for (ndx = 0; ndx < ddc_data->data_len; ++ndx) |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA, |
| ((u32)ddc_data->data_buf[ndx]) << 8); |
| |
| /* Data setup is complete, now setup the transaction characteristics */ |
| |
| /* |
| * 4. Write to HDMI_I2C_TRANSACTION0 with the following fields set in |
| * order to handle characteristics of portion #1 and portion #2 |
| * RW0 = 0x0 (write) |
| * START0 = 0x1 (insert START bit) |
| * STOP0 = 0x0 (do NOT insert STOP bit) |
| * CNT0 = 0x1 (single byte transaction excluding address) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS0, BIT(12) | BIT(16)); |
| |
| /* |
| * 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in |
| * order to handle characteristics of portion #3 |
| * RW1 = 0x1 (read) |
| * START1 = 0x1 (insert START bit) |
| * STOP1 = 0x1 (insert STOP bit) |
| * CNT1 = data_len (0xN (write N bytes of data)) |
| * Byte count for second transition (excluding the first |
| * Byte which is usually the address) |
| */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_TRANS1, |
| BIT(13) | ((ddc_data->data_len-1) << 16)); |
| |
| /* Trigger the I2C transfer */ |
| /* |
| * 6. Write to HDMI_I2C_CONTROL to kick off the hardware. |
| * Note that NOTHING has been transmitted on the DDC lines up to this |
| * point. |
| * TRANSACTION_CNT = 0x1 (execute transaction0 followed by |
| * transaction1) |
| * GO = 0x1 (kicks off hardware) |
| */ |
| INIT_COMPLETION(ddc_ctrl->ddc_sw_done); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(0) | BIT(20)); |
| |
| time_out_count = wait_for_completion_timeout( |
| &ddc_ctrl->ddc_sw_done, HZ/2); |
| |
| reg_val = DSS_REG_R(ddc_ctrl->io, HDMI_DDC_INT_CTRL); |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, reg_val & (~BIT(2))); |
| if (!time_out_count) { |
| if (retry-- > 0) { |
| DEV_INFO("%s[%s]: failed timout, retry=%d\n", __func__, |
| ddc_data->what, retry); |
| goto again; |
| } |
| status = -ETIMEDOUT; |
| DEV_ERR("%s[%s]: timedout, Int Ctrl=%08x\n", |
| __func__, ddc_data->what, |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_INT_CTRL)); |
| DEV_ERR("%s: DDC SW Status=%08x, HW Status=%08x\n", |
| __func__, |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_SW_STATUS), |
| DSS_REG_R(ddc_ctrl->io, HDMI_DDC_HW_STATUS)); |
| goto error; |
| } |
| |
| /* Read DDC status */ |
| reg_val = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_SW_STATUS); |
| reg_val &= 0x00001000 | 0x00002000 | 0x00004000 | 0x00008000; |
| |
| /* Check if any NACK occurred */ |
| if (reg_val) { |
| if (retry > 1) |
| /* SW_STATUS_RESET */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(3)); |
| else |
| /* SOFT_RESET */ |
| DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, BIT(1)); |
| |
| if (retry-- > 0) { |
| DEV_DBG("%s[%s]: failed NACK=%08x, retry=%d\n", |
| __func__, ddc_data->what, reg_val, retry); |
| msleep(100); |
| goto again; |
| } |
| status = -EIO; |
| DEV_ERR("%s[%s]: failed NACK: %08x\n", __func__, |
| ddc_data->what, reg_val); |
| goto error; |
| } |
| |
| DEV_DBG("%s[%s] success\n", __func__, ddc_data->what); |
| |
| error: |
| return status; |
| } /* hdmi_ddc_write */ |