Home | History | Annotate | Line # | Download | only in drm
      1 /*	$NetBSD: drm_scdc_helper.c,v 1.3 2021/12/19 01:15:07 riastradh Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2015 NVIDIA Corporation. All rights reserved.
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a
      7  * copy of this software and associated documentation files (the "Software"),
      8  * to deal in the Software without restriction, including without limitation
      9  * the rights to use, copy, modify, merge, publish, distribute, sub license,
     10  * and/or sell copies of the Software, and to permit persons to whom the
     11  * Software is furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice (including the
     14  * next paragraph) shall be included in all copies or substantial portions
     15  * of the Software.
     16  *
     17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     19  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
     20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     23  * DEALINGS IN THE SOFTWARE.
     24  */
     25 
     26 #include <sys/cdefs.h>
     27 __KERNEL_RCSID(0, "$NetBSD: drm_scdc_helper.c,v 1.3 2021/12/19 01:15:07 riastradh Exp $");
     28 
     29 #include <linux/slab.h>
     30 #include <linux/delay.h>
     31 
     32 #include <drm/drm_print.h>
     33 #include <drm/drm_scdc_helper.h>
     34 
     35 /**
     36  * DOC: scdc helpers
     37  *
     38  * Status and Control Data Channel (SCDC) is a mechanism introduced by the
     39  * HDMI 2.0 specification. It is a point-to-point protocol that allows the
     40  * HDMI source and HDMI sink to exchange data. The same I2C interface that
     41  * is used to access EDID serves as the transport mechanism for SCDC.
     42  */
     43 
     44 #define SCDC_I2C_SLAVE_ADDRESS 0x54
     45 
     46 /**
     47  * drm_scdc_read - read a block of data from SCDC
     48  * @adapter: I2C controller
     49  * @offset: start offset of block to read
     50  * @buffer: return location for the block to read
     51  * @size: size of the block to read
     52  *
     53  * Reads a block of data from SCDC, starting at a given offset.
     54  *
     55  * Returns:
     56  * 0 on success, negative error code on failure.
     57  */
     58 ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
     59 		      size_t size)
     60 {
     61 	int ret;
     62 	struct i2c_msg msgs[2] = {
     63 		{
     64 			.addr = SCDC_I2C_SLAVE_ADDRESS,
     65 			.flags = 0,
     66 			.len = 1,
     67 			.buf = &offset,
     68 		}, {
     69 			.addr = SCDC_I2C_SLAVE_ADDRESS,
     70 			.flags = I2C_M_RD,
     71 			.len = size,
     72 			.buf = buffer,
     73 		}
     74 	};
     75 
     76 	ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
     77 	if (ret < 0)
     78 		return ret;
     79 	if (ret != ARRAY_SIZE(msgs))
     80 		return -EPROTO;
     81 
     82 	return 0;
     83 }
     84 EXPORT_SYMBOL(drm_scdc_read);
     85 
     86 /**
     87  * drm_scdc_write - write a block of data to SCDC
     88  * @adapter: I2C controller
     89  * @offset: start offset of block to write
     90  * @buffer: block of data to write
     91  * @size: size of the block to write
     92  *
     93  * Writes a block of data to SCDC, starting at a given offset.
     94  *
     95  * Returns:
     96  * 0 on success, negative error code on failure.
     97  */
     98 ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset,
     99 		       const void *buffer, size_t size)
    100 {
    101 	struct i2c_msg msg = {
    102 		.addr = SCDC_I2C_SLAVE_ADDRESS,
    103 		.flags = 0,
    104 		.len = 1 + size,
    105 		.buf = NULL,
    106 	};
    107 	void *data;
    108 	int err;
    109 
    110 	data = kmalloc(1 + size, GFP_KERNEL);
    111 	if (!data)
    112 		return -ENOMEM;
    113 
    114 	msg.buf = data;
    115 
    116 	memcpy(data, &offset, sizeof(offset));
    117 	memcpy((char *)data + 1, buffer, size);
    118 
    119 	err = i2c_transfer(adapter, &msg, 1);
    120 
    121 	kfree(data);
    122 
    123 	if (err < 0)
    124 		return err;
    125 	if (err != 1)
    126 		return -EPROTO;
    127 
    128 	return 0;
    129 }
    130 EXPORT_SYMBOL(drm_scdc_write);
    131 
    132 /**
    133  * drm_scdc_check_scrambling_status - what is status of scrambling?
    134  * @adapter: I2C adapter for DDC channel
    135  *
    136  * Reads the scrambler status over SCDC, and checks the
    137  * scrambling status.
    138  *
    139  * Returns:
    140  * True if the scrambling is enabled, false otherwise.
    141  */
    142 bool drm_scdc_get_scrambling_status(struct i2c_adapter *adapter)
    143 {
    144 	u8 status;
    145 	int ret;
    146 
    147 	ret = drm_scdc_readb(adapter, SCDC_SCRAMBLER_STATUS, &status);
    148 	if (ret < 0) {
    149 		DRM_DEBUG_KMS("Failed to read scrambling status: %d\n", ret);
    150 		return false;
    151 	}
    152 
    153 	return status & SCDC_SCRAMBLING_STATUS;
    154 }
    155 EXPORT_SYMBOL(drm_scdc_get_scrambling_status);
    156 
    157 /**
    158  * drm_scdc_set_scrambling - enable scrambling
    159  * @adapter: I2C adapter for DDC channel
    160  * @enable: bool to indicate if scrambling is to be enabled/disabled
    161  *
    162  * Writes the TMDS config register over SCDC channel, and:
    163  * enables scrambling when enable = 1
    164  * disables scrambling when enable = 0
    165  *
    166  * Returns:
    167  * True if scrambling is set/reset successfully, false otherwise.
    168  */
    169 bool drm_scdc_set_scrambling(struct i2c_adapter *adapter, bool enable)
    170 {
    171 	u8 config;
    172 	int ret;
    173 
    174 	ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config);
    175 	if (ret < 0) {
    176 		DRM_DEBUG_KMS("Failed to read TMDS config: %d\n", ret);
    177 		return false;
    178 	}
    179 
    180 	if (enable)
    181 		config |= SCDC_SCRAMBLING_ENABLE;
    182 	else
    183 		config &= ~SCDC_SCRAMBLING_ENABLE;
    184 
    185 	ret = drm_scdc_writeb(adapter, SCDC_TMDS_CONFIG, config);
    186 	if (ret < 0) {
    187 		DRM_DEBUG_KMS("Failed to enable scrambling: %d\n", ret);
    188 		return false;
    189 	}
    190 
    191 	return true;
    192 }
    193 EXPORT_SYMBOL(drm_scdc_set_scrambling);
    194 
    195 /**
    196  * drm_scdc_set_high_tmds_clock_ratio - set TMDS clock ratio
    197  * @adapter: I2C adapter for DDC channel
    198  * @set: ret or reset the high clock ratio
    199  *
    200  *
    201  *	TMDS clock ratio calculations go like this:
    202  *		TMDS character = 10 bit TMDS encoded value
    203  *
    204  *		TMDS character rate = The rate at which TMDS characters are
    205  *		transmitted (Mcsc)
    206  *
    207  *		TMDS bit rate = 10x TMDS character rate
    208  *
    209  *	As per the spec:
    210  *		TMDS clock rate for pixel clock < 340 MHz = 1x the character
    211  *		rate = 1/10 pixel clock rate
    212  *
    213  *		TMDS clock rate for pixel clock > 340 MHz = 0.25x the character
    214  *		rate = 1/40 pixel clock rate
    215  *
    216  *	Writes to the TMDS config register over SCDC channel, and:
    217  *		sets TMDS clock ratio to 1/40 when set = 1
    218  *
    219  *		sets TMDS clock ratio to 1/10 when set = 0
    220  *
    221  * Returns:
    222  * True if write is successful, false otherwise.
    223  */
    224 bool drm_scdc_set_high_tmds_clock_ratio(struct i2c_adapter *adapter, bool set)
    225 {
    226 	u8 config;
    227 	int ret;
    228 
    229 	ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config);
    230 	if (ret < 0) {
    231 		DRM_DEBUG_KMS("Failed to read TMDS config: %d\n", ret);
    232 		return false;
    233 	}
    234 
    235 	if (set)
    236 		config |= SCDC_TMDS_BIT_CLOCK_RATIO_BY_40;
    237 	else
    238 		config &= ~SCDC_TMDS_BIT_CLOCK_RATIO_BY_40;
    239 
    240 	ret = drm_scdc_writeb(adapter, SCDC_TMDS_CONFIG, config);
    241 	if (ret < 0) {
    242 		DRM_DEBUG_KMS("Failed to set TMDS clock ratio: %d\n", ret);
    243 		return false;
    244 	}
    245 
    246 	/*
    247 	 * The spec says that a source should wait minimum 1ms and maximum
    248 	 * 100ms after writing the TMDS config for clock ratio. Lets allow a
    249 	 * wait of upto 2ms here.
    250 	 */
    251 	usleep_range(1000, 2000);
    252 	return true;
    253 }
    254 EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio);
    255