1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * cec-edid - HDMI Consumer Electronics Control EDID & CEC helper functions
4  *
5  * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
6  */
7 
8 #include <linux/module.h>
9 #include <linux/kernel.h>
10 #include <linux/types.h>
11 #include <media/cec.h>
12 
13 /*
14  * This EDID is expected to be a CEA-861 compliant, which means that there are
15  * at least two blocks and one or more of the extensions blocks are CEA-861
16  * blocks.
17  *
18  * The returned location is guaranteed to be < size - 1.
19  */
cec_get_edid_spa_location(const u8 * edid,unsigned int size)20 static unsigned int cec_get_edid_spa_location(const u8 *edid, unsigned int size)
21 {
22 	unsigned int blocks = size / 128;
23 	unsigned int block;
24 	u8 d;
25 
26 	/* Sanity check: at least 2 blocks and a multiple of the block size */
27 	if (blocks < 2 || size % 128)
28 		return 0;
29 
30 	/*
31 	 * If there are fewer extension blocks than the size, then update
32 	 * 'blocks'. It is allowed to have more extension blocks than the size,
33 	 * since some hardware can only read e.g. 256 bytes of the EDID, even
34 	 * though more blocks are present. The first CEA-861 extension block
35 	 * should normally be in block 1 anyway.
36 	 */
37 	if (edid[0x7e] + 1 < blocks)
38 		blocks = edid[0x7e] + 1;
39 
40 	for (block = 1; block < blocks; block++) {
41 		unsigned int offset = block * 128;
42 
43 		/* Skip any non-CEA-861 extension blocks */
44 		if (edid[offset] != 0x02 || edid[offset + 1] != 0x03)
45 			continue;
46 
47 		/* search Vendor Specific Data Block (tag 3) */
48 		d = edid[offset + 2] & 0x7f;
49 		/* Check if there are Data Blocks */
50 		if (d <= 4)
51 			continue;
52 		if (d > 4) {
53 			unsigned int i = offset + 4;
54 			unsigned int end = offset + d;
55 
56 			/* Note: 'end' is always < 'size' */
57 			do {
58 				u8 tag = edid[i] >> 5;
59 				u8 len = edid[i] & 0x1f;
60 
61 				if (tag == 3 && len >= 5 && i + len <= end &&
62 				    edid[i + 1] == 0x03 &&
63 				    edid[i + 2] == 0x0c &&
64 				    edid[i + 3] == 0x00)
65 					return i + 4;
66 				i += len + 1;
67 			} while (i < end);
68 		}
69 	}
70 	return 0;
71 }
72 
cec_get_edid_phys_addr(const u8 * edid,unsigned int size,unsigned int * offset)73 u16 cec_get_edid_phys_addr(const u8 *edid, unsigned int size,
74 			   unsigned int *offset)
75 {
76 	unsigned int loc = cec_get_edid_spa_location(edid, size);
77 
78 	if (offset)
79 		*offset = loc;
80 	if (loc == 0)
81 		return CEC_PHYS_ADDR_INVALID;
82 	return (edid[loc] << 8) | edid[loc + 1];
83 }
84 EXPORT_SYMBOL_GPL(cec_get_edid_phys_addr);
85 
cec_set_edid_phys_addr(u8 * edid,unsigned int size,u16 phys_addr)86 void cec_set_edid_phys_addr(u8 *edid, unsigned int size, u16 phys_addr)
87 {
88 	unsigned int loc = cec_get_edid_spa_location(edid, size);
89 	u8 sum = 0;
90 	unsigned int i;
91 
92 	if (loc == 0)
93 		return;
94 	edid[loc] = phys_addr >> 8;
95 	edid[loc + 1] = phys_addr & 0xff;
96 	loc &= ~0x7f;
97 
98 	/* update the checksum */
99 	for (i = loc; i < loc + 127; i++)
100 		sum += edid[i];
101 	edid[i] = 256 - sum;
102 }
103 EXPORT_SYMBOL_GPL(cec_set_edid_phys_addr);
104 
cec_phys_addr_for_input(u16 phys_addr,u8 input)105 u16 cec_phys_addr_for_input(u16 phys_addr, u8 input)
106 {
107 	/* Check if input is sane */
108 	if (WARN_ON(input == 0 || input > 0xf))
109 		return CEC_PHYS_ADDR_INVALID;
110 
111 	if (phys_addr == 0)
112 		return input << 12;
113 
114 	if ((phys_addr & 0x0fff) == 0)
115 		return phys_addr | (input << 8);
116 
117 	if ((phys_addr & 0x00ff) == 0)
118 		return phys_addr | (input << 4);
119 
120 	if ((phys_addr & 0x000f) == 0)
121 		return phys_addr | input;
122 
123 	/*
124 	 * All nibbles are used so no valid physical addresses can be assigned
125 	 * to the input.
126 	 */
127 	return CEC_PHYS_ADDR_INVALID;
128 }
129 EXPORT_SYMBOL_GPL(cec_phys_addr_for_input);
130 
cec_phys_addr_validate(u16 phys_addr,u16 * parent,u16 * port)131 int cec_phys_addr_validate(u16 phys_addr, u16 *parent, u16 *port)
132 {
133 	int i;
134 
135 	if (parent)
136 		*parent = phys_addr;
137 	if (port)
138 		*port = 0;
139 	if (phys_addr == CEC_PHYS_ADDR_INVALID)
140 		return 0;
141 	for (i = 0; i < 16; i += 4)
142 		if (phys_addr & (0xf << i))
143 			break;
144 	if (i == 16)
145 		return 0;
146 	if (parent)
147 		*parent = phys_addr & (0xfff0 << i);
148 	if (port)
149 		*port = (phys_addr >> i) & 0xf;
150 	for (i += 4; i < 16; i += 4)
151 		if ((phys_addr & (0xf << i)) == 0)
152 			return -EINVAL;
153 	return 0;
154 }
155 EXPORT_SYMBOL_GPL(cec_phys_addr_validate);
156