|  | /* | 
|  | * Copyright (c) 2015 NVIDIA Corporation. All rights reserved. | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a | 
|  | * copy of this software and associated documentation files (the "Software"), | 
|  | * to deal in the Software without restriction, including without limitation | 
|  | * the rights to use, copy, modify, merge, publish, distribute, sub license, | 
|  | * and/or sell copies of the Software, and to permit persons to whom the | 
|  | * Software is furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice (including the | 
|  | * next paragraph) shall be included in all copies or substantial portions | 
|  | * of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL | 
|  | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | 
|  | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | 
|  | * DEALINGS IN THE SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include <linux/slab.h> | 
|  | #include <linux/delay.h> | 
|  |  | 
|  | #include <drm/drm_print.h> | 
|  | #include <drm/drm_scdc_helper.h> | 
|  |  | 
|  | /** | 
|  | * DOC: scdc helpers | 
|  | * | 
|  | * Status and Control Data Channel (SCDC) is a mechanism introduced by the | 
|  | * HDMI 2.0 specification. It is a point-to-point protocol that allows the | 
|  | * HDMI source and HDMI sink to exchange data. The same I2C interface that | 
|  | * is used to access EDID serves as the transport mechanism for SCDC. | 
|  | */ | 
|  |  | 
|  | #define SCDC_I2C_SLAVE_ADDRESS 0x54 | 
|  |  | 
|  | /** | 
|  | * drm_scdc_read - read a block of data from SCDC | 
|  | * @adapter: I2C controller | 
|  | * @offset: start offset of block to read | 
|  | * @buffer: return location for the block to read | 
|  | * @size: size of the block to read | 
|  | * | 
|  | * Reads a block of data from SCDC, starting at a given offset. | 
|  | * | 
|  | * Returns: | 
|  | * 0 on success, negative error code on failure. | 
|  | */ | 
|  | ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer, | 
|  | size_t size) | 
|  | { | 
|  | int ret; | 
|  | struct i2c_msg msgs[2] = { | 
|  | { | 
|  | .addr = SCDC_I2C_SLAVE_ADDRESS, | 
|  | .flags = 0, | 
|  | .len = 1, | 
|  | .buf = &offset, | 
|  | }, { | 
|  | .addr = SCDC_I2C_SLAVE_ADDRESS, | 
|  | .flags = I2C_M_RD, | 
|  | .len = size, | 
|  | .buf = buffer, | 
|  | } | 
|  | }; | 
|  |  | 
|  | ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | if (ret != ARRAY_SIZE(msgs)) | 
|  | return -EPROTO; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL(drm_scdc_read); | 
|  |  | 
|  | /** | 
|  | * drm_scdc_write - write a block of data to SCDC | 
|  | * @adapter: I2C controller | 
|  | * @offset: start offset of block to write | 
|  | * @buffer: block of data to write | 
|  | * @size: size of the block to write | 
|  | * | 
|  | * Writes a block of data to SCDC, starting at a given offset. | 
|  | * | 
|  | * Returns: | 
|  | * 0 on success, negative error code on failure. | 
|  | */ | 
|  | ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset, | 
|  | const void *buffer, size_t size) | 
|  | { | 
|  | struct i2c_msg msg = { | 
|  | .addr = SCDC_I2C_SLAVE_ADDRESS, | 
|  | .flags = 0, | 
|  | .len = 1 + size, | 
|  | .buf = NULL, | 
|  | }; | 
|  | void *data; | 
|  | int err; | 
|  |  | 
|  | data = kmalloc(1 + size, GFP_KERNEL); | 
|  | if (!data) | 
|  | return -ENOMEM; | 
|  |  | 
|  | msg.buf = data; | 
|  |  | 
|  | memcpy(data, &offset, sizeof(offset)); | 
|  | memcpy(data + 1, buffer, size); | 
|  |  | 
|  | err = i2c_transfer(adapter, &msg, 1); | 
|  |  | 
|  | kfree(data); | 
|  |  | 
|  | if (err < 0) | 
|  | return err; | 
|  | if (err != 1) | 
|  | return -EPROTO; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL(drm_scdc_write); | 
|  |  | 
|  | /** | 
|  | * drm_scdc_get_scrambling_status - what is status of scrambling? | 
|  | * @adapter: I2C adapter for DDC channel | 
|  | * | 
|  | * Reads the scrambler status over SCDC, and checks the | 
|  | * scrambling status. | 
|  | * | 
|  | * Returns: | 
|  | * True if the scrambling is enabled, false otherwise. | 
|  | */ | 
|  | bool drm_scdc_get_scrambling_status(struct i2c_adapter *adapter) | 
|  | { | 
|  | u8 status; | 
|  | int ret; | 
|  |  | 
|  | ret = drm_scdc_readb(adapter, SCDC_SCRAMBLER_STATUS, &status); | 
|  | if (ret < 0) { | 
|  | DRM_DEBUG_KMS("Failed to read scrambling status: %d\n", ret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return status & SCDC_SCRAMBLING_STATUS; | 
|  | } | 
|  | EXPORT_SYMBOL(drm_scdc_get_scrambling_status); | 
|  |  | 
|  | /** | 
|  | * drm_scdc_set_scrambling - enable scrambling | 
|  | * @adapter: I2C adapter for DDC channel | 
|  | * @enable: bool to indicate if scrambling is to be enabled/disabled | 
|  | * | 
|  | * Writes the TMDS config register over SCDC channel, and: | 
|  | * enables scrambling when enable = 1 | 
|  | * disables scrambling when enable = 0 | 
|  | * | 
|  | * Returns: | 
|  | * True if scrambling is set/reset successfully, false otherwise. | 
|  | */ | 
|  | bool drm_scdc_set_scrambling(struct i2c_adapter *adapter, bool enable) | 
|  | { | 
|  | u8 config; | 
|  | int ret; | 
|  |  | 
|  | ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config); | 
|  | if (ret < 0) { | 
|  | DRM_DEBUG_KMS("Failed to read TMDS config: %d\n", ret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (enable) | 
|  | config |= SCDC_SCRAMBLING_ENABLE; | 
|  | else | 
|  | config &= ~SCDC_SCRAMBLING_ENABLE; | 
|  |  | 
|  | ret = drm_scdc_writeb(adapter, SCDC_TMDS_CONFIG, config); | 
|  | if (ret < 0) { | 
|  | DRM_DEBUG_KMS("Failed to enable scrambling: %d\n", ret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  | EXPORT_SYMBOL(drm_scdc_set_scrambling); | 
|  |  | 
|  | /** | 
|  | * drm_scdc_set_high_tmds_clock_ratio - set TMDS clock ratio | 
|  | * @adapter: I2C adapter for DDC channel | 
|  | * @set: ret or reset the high clock ratio | 
|  | * | 
|  | * | 
|  | *	TMDS clock ratio calculations go like this: | 
|  | *		TMDS character = 10 bit TMDS encoded value | 
|  | * | 
|  | *		TMDS character rate = The rate at which TMDS characters are | 
|  | *		transmitted (Mcsc) | 
|  | * | 
|  | *		TMDS bit rate = 10x TMDS character rate | 
|  | * | 
|  | *	As per the spec: | 
|  | *		TMDS clock rate for pixel clock < 340 MHz = 1x the character | 
|  | *		rate = 1/10 pixel clock rate | 
|  | * | 
|  | *		TMDS clock rate for pixel clock > 340 MHz = 0.25x the character | 
|  | *		rate = 1/40 pixel clock rate | 
|  | * | 
|  | *	Writes to the TMDS config register over SCDC channel, and: | 
|  | *		sets TMDS clock ratio to 1/40 when set = 1 | 
|  | * | 
|  | *		sets TMDS clock ratio to 1/10 when set = 0 | 
|  | * | 
|  | * Returns: | 
|  | * True if write is successful, false otherwise. | 
|  | */ | 
|  | bool drm_scdc_set_high_tmds_clock_ratio(struct i2c_adapter *adapter, bool set) | 
|  | { | 
|  | u8 config; | 
|  | int ret; | 
|  |  | 
|  | ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config); | 
|  | if (ret < 0) { | 
|  | DRM_DEBUG_KMS("Failed to read TMDS config: %d\n", ret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (set) | 
|  | config |= SCDC_TMDS_BIT_CLOCK_RATIO_BY_40; | 
|  | else | 
|  | config &= ~SCDC_TMDS_BIT_CLOCK_RATIO_BY_40; | 
|  |  | 
|  | ret = drm_scdc_writeb(adapter, SCDC_TMDS_CONFIG, config); | 
|  | if (ret < 0) { | 
|  | DRM_DEBUG_KMS("Failed to set TMDS clock ratio: %d\n", ret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The spec says that a source should wait minimum 1ms and maximum | 
|  | * 100ms after writing the TMDS config for clock ratio. Lets allow a | 
|  | * wait of up to 2ms here. | 
|  | */ | 
|  | usleep_range(1000, 2000); | 
|  | return true; | 
|  | } | 
|  | EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio); |