Skip to content

Commit 7b3c72a

Browse files
authored
Merge pull request #294 from makermelissa/simplify_rev_codes
Added improved Pi Revision Code detection
2 parents 2b3b7ef + 7a1ff1b commit 7b3c72a

File tree

5 files changed

+484
-3
lines changed

5 files changed

+484
-3
lines changed

adafruit_platformdetect/board.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,16 @@ def _pi_id(self) -> Optional[str]:
201201
# Check for Pi boards:
202202
pi_rev_code = self._pi_rev_code()
203203
if pi_rev_code:
204-
for model, codes in boards._PI_REV_CODES.items():
205-
if pi_rev_code in codes:
206-
return model
204+
from adafruit_platformdetect.revcodes import PiDecoder
207205

206+
try:
207+
decoder = PiDecoder(pi_rev_code)
208+
model = boards._PI_MODELS[decoder.type_raw]
209+
if isinstance(model, dict):
210+
model = model[decoder.revision]
211+
return model
212+
except ValueError:
213+
pass
208214
# We may be on a non-Raspbian OS, so try to lazily determine
209215
# the version based on `get_device_model`
210216
else:

adafruit_platformdetect/constants/boards.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
RASPBERRY_PI_AVNET_IIOT_GW = "RASPBERY_PI_AVNET_IIOT_GW"
124124
RASPBERRY_PI_400 = "RASPBERRY_PI_400"
125125
RASPBERRY_PI_CM4 = "RASPBERRY_PI_CM4"
126+
RASPBERRY_PI_CM4S = "RASPBERRY_PI_CM4S"
126127

127128
ODROID_C1 = "ODROID_C1"
128129
ODROID_C1_PLUS = "ODROID_C1_PLUS"
@@ -341,6 +342,7 @@
341342
RASPBERRY_PI_CM3,
342343
RASPBERRY_PI_CM3_PLUS,
343344
RASPBERRY_PI_CM4,
345+
RASPBERRY_PI_CM4S,
344346
)
345347

346348
_ODROID_40_PIN_IDS = (
@@ -564,6 +566,31 @@
564566
RASPBERRY_PI_ZERO_2_W: ("902120", "2902120"),
565567
}
566568

569+
_PI_MODELS = {
570+
0x00: RASPBERRY_PI_A,
571+
0x01: {
572+
1.0: RASPBERRY_PI_B_REV1,
573+
2.0: RASPBERRY_PI_B_REV2,
574+
},
575+
0x02: RASPBERRY_PI_A_PLUS,
576+
0x03: RASPBERRY_PI_B_PLUS,
577+
0x04: RASPBERRY_PI_2B,
578+
0x06: RASPBERRY_PI_CM1,
579+
0x08: RASPBERRY_PI_3B,
580+
0x09: RASPBERRY_PI_ZERO,
581+
0x0A: RASPBERRY_PI_CM3,
582+
0x0B: RASPBERRY_PI_AVNET_IIOT_GW,
583+
0x0C: RASPBERRY_PI_ZERO_W,
584+
0x0D: RASPBERRY_PI_3B_PLUS,
585+
0x0E: RASPBERRY_PI_3A_PLUS,
586+
0x10: RASPBERRY_PI_CM3_PLUS,
587+
0x11: RASPBERRY_PI_4B,
588+
0x12: RASPBERRY_PI_ZERO_2_W,
589+
0x13: RASPBERRY_PI_400,
590+
0x14: RASPBERRY_PI_CM4,
591+
0x15: RASPBERRY_PI_CM4S,
592+
}
593+
567594
# Onion omega boards
568595
_ONION_OMEGA_BOARD_IDS = (ONION_OMEGA, ONION_OMEGA2)
569596

adafruit_platformdetect/revcodes.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
`adafruit_platformdetect.revcodes`
7+
================================================================================
8+
9+
Class to help with Raspberry Pi Rev Codes
10+
11+
* Author(s): Melissa LeBlanc-Williams
12+
13+
Implementation Notes
14+
--------------------
15+
16+
**Software and Dependencies:**
17+
18+
* Linux and Python 3.7 or Higher
19+
20+
Data values from https://github.com/raspberrypi/documentation/blob/develop/
21+
documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc#new-style-revision-codes
22+
23+
"""
24+
25+
NEW_OVERVOLTAGE = (
26+
"Overvoltage allowed",
27+
"Overvoltage disallowed",
28+
)
29+
30+
NEW_OTP_PROGRAM = (
31+
"OTP programming is allowed",
32+
"OTP programming is disallowed",
33+
)
34+
35+
NEW_OTP_READ = (
36+
"OTP reading is allowed",
37+
"OTP reading is disallowed",
38+
)
39+
40+
NEW_WARRANTY_BIT = (
41+
"Warranty is intact",
42+
"Warranty has been voided by overclocking",
43+
)
44+
45+
NEW_REV_STYLE = (
46+
"Old-style revision",
47+
"New-style revision",
48+
)
49+
50+
NEW_MEMORY_SIZE = (
51+
"256MB",
52+
"512MB",
53+
"1GB",
54+
"2GB",
55+
"4GB",
56+
"8GB",
57+
)
58+
59+
NEW_MANUFACTURER = (
60+
"Sony UK",
61+
"Egoman",
62+
"Embest",
63+
"Sony Japan",
64+
"Embest",
65+
"Stadium",
66+
)
67+
68+
NEW_PROCESSOR = (
69+
"BCM2835",
70+
"BCM2836",
71+
"BCM2837",
72+
"BCM2711",
73+
)
74+
75+
PI_TYPE = {
76+
0x00: "A",
77+
0x01: "B",
78+
0x02: "A+",
79+
0x03: "B+",
80+
0x04: "2B",
81+
0x05: "Alpha (early prototype)",
82+
0x06: "CM1",
83+
0x08: "3B",
84+
0x09: "Zero",
85+
0x0A: "CM3",
86+
0x0B: "Custom",
87+
0x0C: "Zero W",
88+
0x0D: "3B+",
89+
0x0E: "3A+",
90+
0x0F: "Internal use only",
91+
0x10: "CM3+",
92+
0x11: "4B",
93+
0x12: "Zero 2 W",
94+
0x13: "400",
95+
0x14: "CM4",
96+
0x15: "CM4S",
97+
}
98+
99+
OLD_MANUFACTURER = (
100+
"Sony UK",
101+
"Egoman",
102+
"Embest",
103+
"Qisda",
104+
)
105+
106+
OLD_MEMORY_SIZE = ("256MB", "512MB", "256MB/512MB")
107+
108+
NEW_REV_STRUCTURE = {
109+
"overvoltage": (31, 1, NEW_OVERVOLTAGE),
110+
"otp_program": (30, 1, NEW_OTP_PROGRAM),
111+
"otp_read": (29, 1, NEW_OTP_READ),
112+
"warranty": (25, 1, NEW_WARRANTY_BIT),
113+
"rev_style": (23, 1, NEW_REV_STYLE),
114+
"memory_size": (20, 3, NEW_MEMORY_SIZE),
115+
"manufacturer": (16, 4, NEW_MANUFACTURER),
116+
"processor": (12, 4, NEW_PROCESSOR),
117+
"type": (4, 8, PI_TYPE),
118+
"revision": (0, 4, int),
119+
}
120+
121+
OLD_REV_STRUCTURE = {
122+
"type": (0, PI_TYPE),
123+
"revision": (1, float),
124+
"memory_size": (2, OLD_MEMORY_SIZE),
125+
"manufacturer": (3, OLD_MANUFACTURER),
126+
}
127+
128+
OLD_REV_EXTRA_PROPS = {
129+
"warranty": (24, 1, NEW_WARRANTY_BIT),
130+
}
131+
132+
OLD_REV_LUT = {
133+
0x02: (1, 1.0, 0, 1),
134+
0x03: (1, 1.0, 0, 1),
135+
0x04: (1, 2.0, 0, 0),
136+
0x05: (1, 2.0, 0, 3),
137+
0x06: (1, 2.0, 0, 1),
138+
0x07: (0, 2.0, 0, 1),
139+
0x08: (0, 2.0, 0, 0),
140+
0x09: (0, 2.0, 0, 3),
141+
0x0D: (1, 2.0, 1, 1),
142+
0x0E: (1, 2.0, 1, 0),
143+
0x0F: (1, 2.0, 1, 1),
144+
0x10: (3, 1.2, 1, 0),
145+
0x11: (6, 1.0, 1, 0),
146+
0x12: (2, 1.1, 0, 0),
147+
0x13: (3, 1.2, 1, 2),
148+
0x14: (6, 1.0, 1, 2),
149+
0x15: (2, 1.1, 2, 2),
150+
}
151+
152+
153+
class PiDecoder:
154+
"""Raspberry Pi Revision Code Decoder"""
155+
156+
def __init__(self, rev_code):
157+
try:
158+
self.rev_code = int(rev_code, 16) & 0xFFFFFFFF
159+
except ValueError:
160+
print("Invalid revision code. It should be a hexadecimal value.")
161+
162+
def is_valid_code(self):
163+
"""Quickly check the validity of a code"""
164+
if self.is_new_format():
165+
for code_format in NEW_REV_STRUCTURE.values():
166+
lower_bit, bit_size, values = code_format
167+
prop_value = (self.rev_code >> lower_bit) & ((1 << bit_size) - 1)
168+
if not self._valid_value(prop_value, values):
169+
return False
170+
else:
171+
if (self.rev_code & 0xFFFF) not in OLD_REV_LUT.keys():
172+
return False
173+
for code_format in OLD_REV_STRUCTURE.values():
174+
index, values = code_format
175+
code_format = OLD_REV_LUT[self.rev_code & 0xFFFF]
176+
if index >= len(code_format):
177+
return False
178+
if not self._valid_value(code_format[index], values):
179+
return False
180+
return True
181+
182+
def _get_rev_prop_value(self, name, structure=None, raw=False):
183+
if structure is None:
184+
structure = NEW_REV_STRUCTURE
185+
if name not in structure.keys():
186+
raise ValueError(f"Unknown property {name}")
187+
lower_bit, bit_size, values = structure[name]
188+
prop_value = self._get_bits_value(lower_bit, bit_size)
189+
if not self._valid_value(prop_value, values):
190+
raise ValueError(f"Invalid value {prop_value} for property {name}")
191+
if raw:
192+
return prop_value
193+
return self._format_value(prop_value, values)
194+
195+
def _get_bits_value(self, lower_bit, bit_size):
196+
return (self.rev_code >> lower_bit) & ((1 << bit_size) - 1)
197+
198+
def _get_old_rev_prop_value(self, name, raw=False):
199+
if name not in OLD_REV_STRUCTURE.keys():
200+
raise ValueError(f"Unknown property {name}")
201+
index, values = OLD_REV_STRUCTURE[name]
202+
data = OLD_REV_LUT[self.rev_code & 0xFFFF]
203+
if index >= len(data):
204+
raise IndexError(f"Index {index} out of range for property {name}")
205+
if not self._valid_value(data[index], values):
206+
raise ValueError(f"Invalid value {data[index]} for property {name}")
207+
if raw:
208+
return data[index]
209+
return self._format_value(data[index], values)
210+
211+
@staticmethod
212+
def _format_value(value, valid_values):
213+
if valid_values is float or valid_values is int:
214+
return valid_values(value)
215+
return valid_values[value]
216+
217+
@staticmethod
218+
def _valid_value(value, valid_values):
219+
if valid_values is float or valid_values is int:
220+
return isinstance(value, valid_values)
221+
if isinstance(valid_values, (tuple, list)) and 0 <= value < len(valid_values):
222+
return True
223+
if isinstance(valid_values, dict) and value in valid_values.keys():
224+
return True
225+
return False
226+
227+
def _get_property(self, name, raw=False):
228+
if name not in NEW_REV_STRUCTURE:
229+
raise ValueError(f"Unknown property {name}")
230+
if self.is_new_format():
231+
return self._get_rev_prop_value(name, raw=raw)
232+
if name in OLD_REV_EXTRA_PROPS:
233+
return self._get_rev_prop_value(
234+
name, structure=OLD_REV_EXTRA_PROPS, raw=raw
235+
)
236+
return self._get_old_rev_prop_value(name, raw=raw)
237+
238+
def is_new_format(self):
239+
"""Check if the code is in the new format"""
240+
return self._get_rev_prop_value("rev_style", raw=True) == 1
241+
242+
@property
243+
def overvoltage(self):
244+
"""Overvoltage allowed/disallowed"""
245+
return self._get_property("overvoltage")
246+
247+
@property
248+
def warranty_bit(self):
249+
"""Warranty bit"""
250+
return self._get_property("warranty")
251+
252+
@property
253+
def otp_program(self):
254+
"""OTP programming allowed/disallowed"""
255+
return self._get_property("otp_program")
256+
257+
@property
258+
def otp_read(self):
259+
"""OTP reading allowed/disallowed"""
260+
return self._get_property("otp_read")
261+
262+
@property
263+
def rev_style(self):
264+
"""Revision Code style"""
265+
# Force new style for Rev Style
266+
return self._get_rev_prop_value("rev_style")
267+
268+
@property
269+
def memory_size(self):
270+
"""Memory size"""
271+
return self._get_property("memory_size")
272+
273+
@property
274+
def manufacturer(self):
275+
"""Manufacturer"""
276+
return self._get_property("manufacturer")
277+
278+
@property
279+
def processor(self):
280+
"""Processor"""
281+
return self._get_property("processor")
282+
283+
@property
284+
def type(self):
285+
"""Specific Model"""
286+
return self._get_property("type")
287+
288+
@property
289+
def type_raw(self):
290+
"""Raw Value of Specific Model"""
291+
return self._get_property("type", raw=True)
292+
293+
@property
294+
def revision(self):
295+
"""Revision Number"""
296+
return self._get_property("revision")

0 commit comments

Comments
 (0)