Merge branch 'for-linus-4.11' of git://git.kernel.org/pub/scm/linux/kernel/git/mason...
[sfrench/cifs-2.6.git] / drivers / media / cec-edid.c
1 /*
2  * cec-edid - HDMI Consumer Electronics Control EDID & CEC helper functions
3  *
4  * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5  *
6  * This program is free software; you may redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
11  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
12  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
14  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
15  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17  * SOFTWARE.
18  */
19
20 #include <linux/module.h>
21 #include <linux/kernel.h>
22 #include <linux/types.h>
23 #include <media/cec-edid.h>
24
25 /*
26  * This EDID is expected to be a CEA-861 compliant, which means that there are
27  * at least two blocks and one or more of the extensions blocks are CEA-861
28  * blocks.
29  *
30  * The returned location is guaranteed to be < size - 1.
31  */
32 static unsigned int cec_get_edid_spa_location(const u8 *edid, unsigned int size)
33 {
34         unsigned int blocks = size / 128;
35         unsigned int block;
36         u8 d;
37
38         /* Sanity check: at least 2 blocks and a multiple of the block size */
39         if (blocks < 2 || size % 128)
40                 return 0;
41
42         /*
43          * If there are fewer extension blocks than the size, then update
44          * 'blocks'. It is allowed to have more extension blocks than the size,
45          * since some hardware can only read e.g. 256 bytes of the EDID, even
46          * though more blocks are present. The first CEA-861 extension block
47          * should normally be in block 1 anyway.
48          */
49         if (edid[0x7e] + 1 < blocks)
50                 blocks = edid[0x7e] + 1;
51
52         for (block = 1; block < blocks; block++) {
53                 unsigned int offset = block * 128;
54
55                 /* Skip any non-CEA-861 extension blocks */
56                 if (edid[offset] != 0x02 || edid[offset + 1] != 0x03)
57                         continue;
58
59                 /* search Vendor Specific Data Block (tag 3) */
60                 d = edid[offset + 2] & 0x7f;
61                 /* Check if there are Data Blocks */
62                 if (d <= 4)
63                         continue;
64                 if (d > 4) {
65                         unsigned int i = offset + 4;
66                         unsigned int end = offset + d;
67
68                         /* Note: 'end' is always < 'size' */
69                         do {
70                                 u8 tag = edid[i] >> 5;
71                                 u8 len = edid[i] & 0x1f;
72
73                                 if (tag == 3 && len >= 5 && i + len <= end &&
74                                     edid[i + 1] == 0x03 &&
75                                     edid[i + 2] == 0x0c &&
76                                     edid[i + 3] == 0x00)
77                                         return i + 4;
78                                 i += len + 1;
79                         } while (i < end);
80                 }
81         }
82         return 0;
83 }
84
85 u16 cec_get_edid_phys_addr(const u8 *edid, unsigned int size,
86                            unsigned int *offset)
87 {
88         unsigned int loc = cec_get_edid_spa_location(edid, size);
89
90         if (offset)
91                 *offset = loc;
92         if (loc == 0)
93                 return CEC_PHYS_ADDR_INVALID;
94         return (edid[loc] << 8) | edid[loc + 1];
95 }
96 EXPORT_SYMBOL_GPL(cec_get_edid_phys_addr);
97
98 void cec_set_edid_phys_addr(u8 *edid, unsigned int size, u16 phys_addr)
99 {
100         unsigned int loc = cec_get_edid_spa_location(edid, size);
101         u8 sum = 0;
102         unsigned int i;
103
104         if (loc == 0)
105                 return;
106         edid[loc] = phys_addr >> 8;
107         edid[loc + 1] = phys_addr & 0xff;
108         loc &= ~0x7f;
109
110         /* update the checksum */
111         for (i = loc; i < loc + 127; i++)
112                 sum += edid[i];
113         edid[i] = 256 - sum;
114 }
115 EXPORT_SYMBOL_GPL(cec_set_edid_phys_addr);
116
117 u16 cec_phys_addr_for_input(u16 phys_addr, u8 input)
118 {
119         /* Check if input is sane */
120         if (WARN_ON(input == 0 || input > 0xf))
121                 return CEC_PHYS_ADDR_INVALID;
122
123         if (phys_addr == 0)
124                 return input << 12;
125
126         if ((phys_addr & 0x0fff) == 0)
127                 return phys_addr | (input << 8);
128
129         if ((phys_addr & 0x00ff) == 0)
130                 return phys_addr | (input << 4);
131
132         if ((phys_addr & 0x000f) == 0)
133                 return phys_addr | input;
134
135         /*
136          * All nibbles are used so no valid physical addresses can be assigned
137          * to the input.
138          */
139         return CEC_PHYS_ADDR_INVALID;
140 }
141 EXPORT_SYMBOL_GPL(cec_phys_addr_for_input);
142
143 int cec_phys_addr_validate(u16 phys_addr, u16 *parent, u16 *port)
144 {
145         int i;
146
147         if (parent)
148                 *parent = phys_addr;
149         if (port)
150                 *port = 0;
151         if (phys_addr == CEC_PHYS_ADDR_INVALID)
152                 return 0;
153         for (i = 0; i < 16; i += 4)
154                 if (phys_addr & (0xf << i))
155                         break;
156         if (i == 16)
157                 return 0;
158         if (parent)
159                 *parent = phys_addr & (0xfff0 << i);
160         if (port)
161                 *port = (phys_addr >> i) & 0xf;
162         for (i += 4; i < 16; i += 4)
163                 if ((phys_addr & (0xf << i)) == 0)
164                         return -EINVAL;
165         return 0;
166 }
167 EXPORT_SYMBOL_GPL(cec_phys_addr_validate);
168
169 MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
170 MODULE_DESCRIPTION("CEC EDID helper functions");
171 MODULE_LICENSE("GPL");