1*a64b88a3Sriastradh /* $NetBSD: drm_scdc_helper.c,v 1.3 2021/12/19 01:15:07 riastradh Exp $ */
24e390cabSriastradh
34e390cabSriastradh /*
44e390cabSriastradh * Copyright (c) 2015 NVIDIA Corporation. All rights reserved.
54e390cabSriastradh *
64e390cabSriastradh * Permission is hereby granted, free of charge, to any person obtaining a
74e390cabSriastradh * copy of this software and associated documentation files (the "Software"),
84e390cabSriastradh * to deal in the Software without restriction, including without limitation
94e390cabSriastradh * the rights to use, copy, modify, merge, publish, distribute, sub license,
104e390cabSriastradh * and/or sell copies of the Software, and to permit persons to whom the
114e390cabSriastradh * Software is furnished to do so, subject to the following conditions:
124e390cabSriastradh *
134e390cabSriastradh * The above copyright notice and this permission notice (including the
144e390cabSriastradh * next paragraph) shall be included in all copies or substantial portions
154e390cabSriastradh * of the Software.
164e390cabSriastradh *
174e390cabSriastradh * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
184e390cabSriastradh * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
194e390cabSriastradh * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
204e390cabSriastradh * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
214e390cabSriastradh * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
224e390cabSriastradh * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
234e390cabSriastradh * DEALINGS IN THE SOFTWARE.
244e390cabSriastradh */
254e390cabSriastradh
264e390cabSriastradh #include <sys/cdefs.h>
27*a64b88a3Sriastradh __KERNEL_RCSID(0, "$NetBSD: drm_scdc_helper.c,v 1.3 2021/12/19 01:15:07 riastradh Exp $");
284e390cabSriastradh
294e390cabSriastradh #include <linux/slab.h>
304e390cabSriastradh #include <linux/delay.h>
314e390cabSriastradh
324e390cabSriastradh #include <drm/drm_print.h>
334e390cabSriastradh #include <drm/drm_scdc_helper.h>
344e390cabSriastradh
354e390cabSriastradh /**
364e390cabSriastradh * DOC: scdc helpers
374e390cabSriastradh *
384e390cabSriastradh * Status and Control Data Channel (SCDC) is a mechanism introduced by the
394e390cabSriastradh * HDMI 2.0 specification. It is a point-to-point protocol that allows the
404e390cabSriastradh * HDMI source and HDMI sink to exchange data. The same I2C interface that
414e390cabSriastradh * is used to access EDID serves as the transport mechanism for SCDC.
424e390cabSriastradh */
434e390cabSriastradh
444e390cabSriastradh #define SCDC_I2C_SLAVE_ADDRESS 0x54
454e390cabSriastradh
464e390cabSriastradh /**
474e390cabSriastradh * drm_scdc_read - read a block of data from SCDC
484e390cabSriastradh * @adapter: I2C controller
494e390cabSriastradh * @offset: start offset of block to read
504e390cabSriastradh * @buffer: return location for the block to read
514e390cabSriastradh * @size: size of the block to read
524e390cabSriastradh *
534e390cabSriastradh * Reads a block of data from SCDC, starting at a given offset.
544e390cabSriastradh *
554e390cabSriastradh * Returns:
564e390cabSriastradh * 0 on success, negative error code on failure.
574e390cabSriastradh */
drm_scdc_read(struct i2c_adapter * adapter,u8 offset,void * buffer,size_t size)584e390cabSriastradh ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer,
594e390cabSriastradh size_t size)
604e390cabSriastradh {
614e390cabSriastradh int ret;
624e390cabSriastradh struct i2c_msg msgs[2] = {
634e390cabSriastradh {
644e390cabSriastradh .addr = SCDC_I2C_SLAVE_ADDRESS,
654e390cabSriastradh .flags = 0,
664e390cabSriastradh .len = 1,
674e390cabSriastradh .buf = &offset,
684e390cabSriastradh }, {
694e390cabSriastradh .addr = SCDC_I2C_SLAVE_ADDRESS,
704e390cabSriastradh .flags = I2C_M_RD,
714e390cabSriastradh .len = size,
724e390cabSriastradh .buf = buffer,
734e390cabSriastradh }
744e390cabSriastradh };
754e390cabSriastradh
764e390cabSriastradh ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
774e390cabSriastradh if (ret < 0)
784e390cabSriastradh return ret;
794e390cabSriastradh if (ret != ARRAY_SIZE(msgs))
804e390cabSriastradh return -EPROTO;
814e390cabSriastradh
824e390cabSriastradh return 0;
834e390cabSriastradh }
844e390cabSriastradh EXPORT_SYMBOL(drm_scdc_read);
854e390cabSriastradh
864e390cabSriastradh /**
874e390cabSriastradh * drm_scdc_write - write a block of data to SCDC
884e390cabSriastradh * @adapter: I2C controller
894e390cabSriastradh * @offset: start offset of block to write
904e390cabSriastradh * @buffer: block of data to write
914e390cabSriastradh * @size: size of the block to write
924e390cabSriastradh *
934e390cabSriastradh * Writes a block of data to SCDC, starting at a given offset.
944e390cabSriastradh *
954e390cabSriastradh * Returns:
964e390cabSriastradh * 0 on success, negative error code on failure.
974e390cabSriastradh */
drm_scdc_write(struct i2c_adapter * adapter,u8 offset,const void * buffer,size_t size)984e390cabSriastradh ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset,
994e390cabSriastradh const void *buffer, size_t size)
1004e390cabSriastradh {
1014e390cabSriastradh struct i2c_msg msg = {
1024e390cabSriastradh .addr = SCDC_I2C_SLAVE_ADDRESS,
1034e390cabSriastradh .flags = 0,
1044e390cabSriastradh .len = 1 + size,
1054e390cabSriastradh .buf = NULL,
1064e390cabSriastradh };
1074e390cabSriastradh void *data;
1084e390cabSriastradh int err;
1094e390cabSriastradh
1104e390cabSriastradh data = kmalloc(1 + size, GFP_KERNEL);
1114e390cabSriastradh if (!data)
1124e390cabSriastradh return -ENOMEM;
1134e390cabSriastradh
1144e390cabSriastradh msg.buf = data;
1154e390cabSriastradh
1164e390cabSriastradh memcpy(data, &offset, sizeof(offset));
117*a64b88a3Sriastradh memcpy((char *)data + 1, buffer, size);
1184e390cabSriastradh
1194e390cabSriastradh err = i2c_transfer(adapter, &msg, 1);
1204e390cabSriastradh
1214e390cabSriastradh kfree(data);
1224e390cabSriastradh
1234e390cabSriastradh if (err < 0)
1244e390cabSriastradh return err;
1254e390cabSriastradh if (err != 1)
1264e390cabSriastradh return -EPROTO;
1274e390cabSriastradh
1284e390cabSriastradh return 0;
1294e390cabSriastradh }
1304e390cabSriastradh EXPORT_SYMBOL(drm_scdc_write);
1314e390cabSriastradh
1324e390cabSriastradh /**
1334e390cabSriastradh * drm_scdc_check_scrambling_status - what is status of scrambling?
1344e390cabSriastradh * @adapter: I2C adapter for DDC channel
1354e390cabSriastradh *
1364e390cabSriastradh * Reads the scrambler status over SCDC, and checks the
1374e390cabSriastradh * scrambling status.
1384e390cabSriastradh *
1394e390cabSriastradh * Returns:
1404e390cabSriastradh * True if the scrambling is enabled, false otherwise.
1414e390cabSriastradh */
drm_scdc_get_scrambling_status(struct i2c_adapter * adapter)1424e390cabSriastradh bool drm_scdc_get_scrambling_status(struct i2c_adapter *adapter)
1434e390cabSriastradh {
1444e390cabSriastradh u8 status;
1454e390cabSriastradh int ret;
1464e390cabSriastradh
1474e390cabSriastradh ret = drm_scdc_readb(adapter, SCDC_SCRAMBLER_STATUS, &status);
1484e390cabSriastradh if (ret < 0) {
1494e390cabSriastradh DRM_DEBUG_KMS("Failed to read scrambling status: %d\n", ret);
1504e390cabSriastradh return false;
1514e390cabSriastradh }
1524e390cabSriastradh
1534e390cabSriastradh return status & SCDC_SCRAMBLING_STATUS;
1544e390cabSriastradh }
1554e390cabSriastradh EXPORT_SYMBOL(drm_scdc_get_scrambling_status);
1564e390cabSriastradh
1574e390cabSriastradh /**
1584e390cabSriastradh * drm_scdc_set_scrambling - enable scrambling
1594e390cabSriastradh * @adapter: I2C adapter for DDC channel
1604e390cabSriastradh * @enable: bool to indicate if scrambling is to be enabled/disabled
1614e390cabSriastradh *
1624e390cabSriastradh * Writes the TMDS config register over SCDC channel, and:
1634e390cabSriastradh * enables scrambling when enable = 1
1644e390cabSriastradh * disables scrambling when enable = 0
1654e390cabSriastradh *
1664e390cabSriastradh * Returns:
1674e390cabSriastradh * True if scrambling is set/reset successfully, false otherwise.
1684e390cabSriastradh */
drm_scdc_set_scrambling(struct i2c_adapter * adapter,bool enable)1694e390cabSriastradh bool drm_scdc_set_scrambling(struct i2c_adapter *adapter, bool enable)
1704e390cabSriastradh {
1714e390cabSriastradh u8 config;
1724e390cabSriastradh int ret;
1734e390cabSriastradh
1744e390cabSriastradh ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config);
1754e390cabSriastradh if (ret < 0) {
1764e390cabSriastradh DRM_DEBUG_KMS("Failed to read TMDS config: %d\n", ret);
1774e390cabSriastradh return false;
1784e390cabSriastradh }
1794e390cabSriastradh
1804e390cabSriastradh if (enable)
1814e390cabSriastradh config |= SCDC_SCRAMBLING_ENABLE;
1824e390cabSriastradh else
1834e390cabSriastradh config &= ~SCDC_SCRAMBLING_ENABLE;
1844e390cabSriastradh
1854e390cabSriastradh ret = drm_scdc_writeb(adapter, SCDC_TMDS_CONFIG, config);
1864e390cabSriastradh if (ret < 0) {
1874e390cabSriastradh DRM_DEBUG_KMS("Failed to enable scrambling: %d\n", ret);
1884e390cabSriastradh return false;
1894e390cabSriastradh }
1904e390cabSriastradh
1914e390cabSriastradh return true;
1924e390cabSriastradh }
1934e390cabSriastradh EXPORT_SYMBOL(drm_scdc_set_scrambling);
1944e390cabSriastradh
1954e390cabSriastradh /**
1964e390cabSriastradh * drm_scdc_set_high_tmds_clock_ratio - set TMDS clock ratio
1974e390cabSriastradh * @adapter: I2C adapter for DDC channel
1984e390cabSriastradh * @set: ret or reset the high clock ratio
1994e390cabSriastradh *
2004e390cabSriastradh *
2014e390cabSriastradh * TMDS clock ratio calculations go like this:
2024e390cabSriastradh * TMDS character = 10 bit TMDS encoded value
2034e390cabSriastradh *
2044e390cabSriastradh * TMDS character rate = The rate at which TMDS characters are
2054e390cabSriastradh * transmitted (Mcsc)
2064e390cabSriastradh *
2074e390cabSriastradh * TMDS bit rate = 10x TMDS character rate
2084e390cabSriastradh *
2094e390cabSriastradh * As per the spec:
2104e390cabSriastradh * TMDS clock rate for pixel clock < 340 MHz = 1x the character
2114e390cabSriastradh * rate = 1/10 pixel clock rate
2124e390cabSriastradh *
2134e390cabSriastradh * TMDS clock rate for pixel clock > 340 MHz = 0.25x the character
2144e390cabSriastradh * rate = 1/40 pixel clock rate
2154e390cabSriastradh *
2164e390cabSriastradh * Writes to the TMDS config register over SCDC channel, and:
2174e390cabSriastradh * sets TMDS clock ratio to 1/40 when set = 1
2184e390cabSriastradh *
2194e390cabSriastradh * sets TMDS clock ratio to 1/10 when set = 0
2204e390cabSriastradh *
2214e390cabSriastradh * Returns:
2224e390cabSriastradh * True if write is successful, false otherwise.
2234e390cabSriastradh */
drm_scdc_set_high_tmds_clock_ratio(struct i2c_adapter * adapter,bool set)2244e390cabSriastradh bool drm_scdc_set_high_tmds_clock_ratio(struct i2c_adapter *adapter, bool set)
2254e390cabSriastradh {
2264e390cabSriastradh u8 config;
2274e390cabSriastradh int ret;
2284e390cabSriastradh
2294e390cabSriastradh ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config);
2304e390cabSriastradh if (ret < 0) {
2314e390cabSriastradh DRM_DEBUG_KMS("Failed to read TMDS config: %d\n", ret);
2324e390cabSriastradh return false;
2334e390cabSriastradh }
2344e390cabSriastradh
2354e390cabSriastradh if (set)
2364e390cabSriastradh config |= SCDC_TMDS_BIT_CLOCK_RATIO_BY_40;
2374e390cabSriastradh else
2384e390cabSriastradh config &= ~SCDC_TMDS_BIT_CLOCK_RATIO_BY_40;
2394e390cabSriastradh
2404e390cabSriastradh ret = drm_scdc_writeb(adapter, SCDC_TMDS_CONFIG, config);
2414e390cabSriastradh if (ret < 0) {
2424e390cabSriastradh DRM_DEBUG_KMS("Failed to set TMDS clock ratio: %d\n", ret);
2434e390cabSriastradh return false;
2444e390cabSriastradh }
2454e390cabSriastradh
2464e390cabSriastradh /*
2474e390cabSriastradh * The spec says that a source should wait minimum 1ms and maximum
2484e390cabSriastradh * 100ms after writing the TMDS config for clock ratio. Lets allow a
2494e390cabSriastradh * wait of upto 2ms here.
2504e390cabSriastradh */
2514e390cabSriastradh usleep_range(1000, 2000);
2524e390cabSriastradh return true;
2534e390cabSriastradh }
2544e390cabSriastradh EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio);
255