/** * DOC: dp dual mode helpers * * Helper functions to deal with DP dual mode (aka. DP++) adaptors. * * Type 1: * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. * * Type 2: * Adaptor registers and sink DDC bus can be accessed either via I2C or * I2C-over-AUX. Source devices may choose to implement either of these * access methods.
*/
#define DP_DUAL_MODE_SLAVE_ADDRESS 0x40
/** * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) * @adapter: I2C adapter for the DDC bus * @offset: register offset * @buffer: buffer for return data * @size: size of the buffer * * Reads @size bytes from the DP dual mode adaptor registers * starting at @offset. * * Returns: * 0 on success, negative error code on failure
*/
ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter,
u8 offset, void *buffer, size_t size)
{
u8 zero = 0; char *tmpbuf = NULL; /* * As sub-addressing is not supported by all adaptors, * always explicitly read from the start and discard * any bytes that come before the requested offset. * This way, no matter whether the adaptor supports it * or not, we'll end up reading the proper data.
*/ struct i2c_msg msgs[] = {
{
.addr = DP_DUAL_MODE_SLAVE_ADDRESS,
.flags = 0,
.len = 1,
.buf = &zero,
},
{
.addr = DP_DUAL_MODE_SLAVE_ADDRESS,
.flags = I2C_M_RD,
.len = size + offset,
.buf = buffer,
},
}; int ret;
if (offset) {
tmpbuf = kmalloc(size + offset, GFP_KERNEL); if (!tmpbuf) return -ENOMEM;
msgs[1].buf = tmpbuf;
}
ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); if (tmpbuf)
memcpy(buffer, tmpbuf + offset, size);
kfree(tmpbuf);
if (ret < 0) return ret; if (ret != ARRAY_SIZE(msgs)) return -EPROTO;
return 0;
}
EXPORT_SYMBOL(drm_dp_dual_mode_read);
/** * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) * @adapter: I2C adapter for the DDC bus * @offset: register offset * @buffer: buffer for write data * @size: size of the buffer * * Writes @size bytes to the DP dual mode adaptor registers * starting at @offset. * * Returns: * 0 on success, negative error code on failure
*/
ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter,
u8 offset, constvoid *buffer, size_t size)
{ struct i2c_msg msg = {
.addr = DP_DUAL_MODE_SLAVE_ADDRESS,
.flags = 0,
.len = 1 + size,
.buf = NULL,
}; void *data; int ret;
data = kmalloc(msg.len, GFP_KERNEL); if (!data) return -ENOMEM;
/** * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor * @dev: &drm_device to use * @adapter: I2C adapter for the DDC bus * * Attempt to identify the type of the DP dual mode adaptor used. * * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not * certain whether we're dealing with a native HDMI port or * a type 1 DVI dual mode adaptor. The driver will have to use * some other hardware/driver specific mechanism to make that * distinction. * * Returns: * The type of the DP dual mode adaptor used
*/ enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(conststruct drm_device *dev, struct i2c_adapter *adapter)
{ char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {};
uint8_t adaptor_id = 0x00;
ssize_t ret;
/* * Let's see if the adaptor is there the by reading the * HDMI ID registers. * * Note that type 1 DVI adaptors are not required to implemnt * any registers, and that presents a problem for detection. * If the i2c transfer is nacked, we may or may not be dealing * with a type 1 DVI adaptor. Some other mechanism of detecting * the presence of the adaptor is required. One way would be * to check the state of the CONFIG1 pin, Another method would * simply require the driver to know whether the port is a DP++ * port or a native HDMI port. Both of these methods are entirely * hardware/driver specific so we can't deal with them here.
*/
ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID,
hdmi_id, sizeof(hdmi_id));
drm_dbg_kms(dev, "DP dual mode HDMI ID: %*pE (err %zd)\n",
ret ? 0 : (int)sizeof(hdmi_id), hdmi_id, ret); if (ret) return DRM_DP_DUAL_MODE_UNKNOWN;
ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID,
&adaptor_id, sizeof(adaptor_id));
drm_dbg_kms(dev, "DP dual mode adaptor ID: %02x (err %zd)\n", adaptor_id, ret); if (ret == 0) { if (is_lspcon_adaptor(hdmi_id, adaptor_id)) return DRM_DP_DUAL_MODE_LSPCON; if (is_type2_adaptor(adaptor_id)) { if (is_hdmi_adaptor(hdmi_id)) return DRM_DP_DUAL_MODE_TYPE2_HDMI; else return DRM_DP_DUAL_MODE_TYPE2_DVI;
} /* * If not a proper type 1 ID, still assume type 1, but let * the user know that we may have misdetected the type.
*/ if (!is_type1_adaptor(adaptor_id))
drm_err(dev, "Unexpected DP dual mode adaptor ID %02x\n", adaptor_id);
}
if (is_hdmi_adaptor(hdmi_id)) return DRM_DP_DUAL_MODE_TYPE1_HDMI; else return DRM_DP_DUAL_MODE_TYPE1_DVI;
}
EXPORT_SYMBOL(drm_dp_dual_mode_detect);
/** * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor * @dev: &drm_device to use * @type: DP dual mode adaptor type * @adapter: I2C adapter for the DDC bus * * Determine the max TMDS clock the adaptor supports based on the * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK * register (on type2 adaptors). As some type 1 adaptors have * problems with registers (see comments in drm_dp_dual_mode_detect()) * we don't read the register on those, instead we simply assume * a 165 MHz limit based on the specification. * * Returns: * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz.
*/ int drm_dp_dual_mode_max_tmds_clock(conststruct drm_device *dev, enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter)
{
uint8_t max_tmds_clock;
ssize_t ret;
/* native HDMI so no limit */ if (type == DRM_DP_DUAL_MODE_NONE) return 0;
/* * Type 1 adaptors are limited to 165MHz * Type 2 adaptors can tells us their limit
*/ if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) return 165000;
ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK,
&max_tmds_clock, sizeof(max_tmds_clock)); if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) {
drm_dbg_kms(dev, "Failed to query max TMDS clock\n"); return 165000;
}
/** * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor * @dev: &drm_device to use * @type: DP dual mode adaptor type * @adapter: I2C adapter for the DDC bus * @enabled: current state of the TMDS output buffers * * Get the state of the TMDS output buffers in the adaptor. For * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN * register. As some type 1 adaptors have problems with registers * (see comments in drm_dp_dual_mode_detect()) we don't read the * register on those, instead we simply assume that the buffers * are always enabled. * * Returns: * 0 on success, negative error code on failure
*/ int drm_dp_dual_mode_get_tmds_output(conststruct drm_device *dev, enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, bool *enabled)
{
uint8_t tmds_oen;
ssize_t ret;
ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN,
&tmds_oen, sizeof(tmds_oen)); if (ret) {
drm_dbg_kms(dev, "Failed to query state of TMDS output buffers\n"); return ret;
}
/** * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor * @dev: &drm_device to use * @type: DP dual mode adaptor type * @adapter: I2C adapter for the DDC bus * @enable: enable (as opposed to disable) the TMDS output buffers * * Set the state of the TMDS output buffers in the adaptor. For * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. * Type1 adaptors do not support any register writes. * * Returns: * 0 on success, negative error code on failure
*/ int drm_dp_dual_mode_set_tmds_output(conststruct drm_device *dev, enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, bool enable)
{
uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE;
ssize_t ret; int retry;
if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) return 0;
/* * LSPCON adapters in low-power state may ignore the first write, so * read back and verify the written value a few times.
*/ for (retry = 0; retry < 3; retry++) {
uint8_t tmp;
ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN,
&tmds_oen, sizeof(tmds_oen)); if (ret) {
drm_dbg_kms(dev, "Failed to %s TMDS output buffers (%d attempts)\n",
enable ? "enable" : "disable", retry + 1); return ret;
}
/** * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string * @type: DP dual mode adaptor type * * Returns: * String representation of the DP dual mode adaptor type
*/ constchar *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type)
{ switch (type) { case DRM_DP_DUAL_MODE_NONE: return"none"; case DRM_DP_DUAL_MODE_TYPE1_DVI: return"type 1 DVI"; case DRM_DP_DUAL_MODE_TYPE1_HDMI: return"type 1 HDMI"; case DRM_DP_DUAL_MODE_TYPE2_DVI: return"type 2 DVI"; case DRM_DP_DUAL_MODE_TYPE2_HDMI: return"type 2 HDMI"; case DRM_DP_DUAL_MODE_LSPCON: return"lspcon"; default:
WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); return"unknown";
}
}
EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name);
/** * drm_lspcon_get_mode: Get LSPCON's current mode of operation by * reading offset (0x80, 0x41) * @dev: &drm_device to use * @adapter: I2C-over-aux adapter * @mode: current lspcon mode of operation output variable * * Returns: * 0 on success, sets the current_mode value to appropriate mode * -error on failure
*/ int drm_lspcon_get_mode(conststruct drm_device *dev, struct i2c_adapter *adapter, enum drm_lspcon_mode *mode)
{
u8 data; int ret = 0; int retry;
if (!mode) {
drm_err(dev, "NULL input\n"); return -EINVAL;
}
/* Read Status: i2c over aux */ for (retry = 0; retry < 6; retry++) { if (retry)
usleep_range(500, 1000);
ret = drm_dp_dual_mode_read(adapter,
DP_DUAL_MODE_LSPCON_CURRENT_MODE,
&data, sizeof(data)); if (!ret) break;
}
/* * Confirm mode change by reading the status bit. * Sometimes, it takes a while to change the mode, * so wait and retry until time out or done.
*/ do {
ret = drm_lspcon_get_mode(dev, adapter, ¤t_mode); if (ret) {
drm_err(dev, "can't confirm LSPCON mode change\n"); return ret;
} else { if (current_mode != mode) {
msleep(10);
time_out -= 10;
} else {
drm_dbg_kms(dev, "LSPCON mode changed to %s\n",
mode == DRM_LSPCON_MODE_LS ? "LS" : "PCON"); return 0;
}
}
} while (time_out);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.