/*
 * symclock_trunc.c - userland replica of transmitter_control_v1_6's deep
 * color math on DIG_TRANSMITTER_CONTROL_PARAMETERS_V1_6.ulSymClock
 * (sys/dev/drm/amd/display/dc/bios/command_table.c:857, :869-890).
 *
 * The struct field is ULONG (32-bit, atombios.h:1559-1575
 * "ULONG ulSymClock; // Symbol Clock in 10Khz"), but the deep-color ratio
 * code uses the 16-bit v1_5 helpers on it (copy-paste from
 * transmitter_control_v1_5 where the field is USHORT usSymClock):
 *
 *	params.ulSymClock = cntl->pixel_clock/10;                  // :857
 *	...
 *	case COLOR_DEPTH_101010:
 *		params.ulSymClock =
 *			cpu_to_le16((le16_to_cpu(params.ulSymClock) * 30) / 24); // :873-874
 *
 * le16_to_cpu(u32) truncates the input to 16 bits and cpu_to_le16(...)
 * truncates the result to 16 bits.  On the little-endian x86-64 target
 * these are no-ops except for the truncation.  Any symbol clock whose
 * 10KHz value exceeds 65535 (i.e. pixel_clock > 655.35 MHz) is computed
 * wrong; the initial assignment at :857 is also not converted at all.
 */
#include <stdio.h>
#include <stdint.h>

/* exact replicas of the kernel expressions (LE host: pure truncation) */
static uint32_t le16_to_cpu_u32(uint32_t v) { return v & 0xffffu; }
static uint32_t cpu_to_le16_ret32(uint32_t v) { return v & 0xffffu; }

int main(void)
{
	/* deep-color pixel clocks, KHz, HDMI_TYPE_A (v1_6 path) */
	struct { uint32_t pix_khz; int depth; int num; const char *tag; } cases[] = {
		{ 148500, 8,  24, "1080p60   8bpc (safe zone)" },
		{ 594000, 10, 30, "4K60     10bpc x30/24" },
		{ 594000, 12, 36, "4K60     12bpc x36/24" },
		{ 594000, 16, 48, "4K60     16bpc x48/24" },
		{ 340000, 16, 48, "HDMI1.4 4K30 16bpc x48/24" },
	};
	unsigned i;
	int bad = 0;

	printf("== DF-4048 replica: command_table.c:869-890 le16 math on 32-bit ==\n");
	printf("== ulSymClock (ULONG, 10KHz units)                       ==\n\n");
	printf("%-28s %10s %12s %12s %8s\n",
	    "case", "in(10KHz)", "correct", "produced", "ok?");

	for (i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
		uint32_t ul = cases[i].pix_khz / 10;		/* :857 */
		uint64_t correct =
		    ((uint64_t)ul * cases[i].num) / 24;		/* intended math */
		uint32_t produced = cpu_to_le16_ret32(
		    (le16_to_cpu_u32(ul) * cases[i].num) / 24);	/* :873-874 shape */

		int ok = (uint64_t)produced == correct;
		if (!ok)
			bad++;
		printf("%-28s %10u %12llu %12u %8s\n",
		    cases[i].tag, ul, (unsigned long long)correct,
		    produced, ok ? "yes" : "TRUNC");
	}

	printf("\nexpected: all cases compute correctly; observed: %d/%zu "
	    "truncated\n", bad, sizeof(cases)/sizeof(cases[0]));
	printf("(also: :857 assigns without any cpu_to_le32 conversion - "
	    "moot on LE x86, wrong on BE)\n");
	return bad ? 1 : 0;
}
