1use anyhow::{bail, Context, Result};
8use serde::Deserialize;
9use std::collections::BTreeMap;
10use std::path::Path;
11
12#[derive(Debug, Clone, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct ChipLibrary {
15 pub name: String,
16 #[serde(default)]
18 pub vendor: Option<String>,
19 #[serde(default)]
21 pub datasheet: Option<String>,
22 pub family_id: u16,
23 pub sub_id: Option<u16>,
24 pub serial_clock: u16,
26 pub chip_control: [u8; 20],
29 #[serde(default)]
32 pub order: Vec<u8>,
33 #[serde(default, deserialize_with = "hex_keys")]
35 pub registers: BTreeMap<u8, [u8; 3]>,
36 pub chip_custom: Option<[u8; 16]>,
40 pub chip_custom_scan_patch: Option<ScanPatch>,
43 pub chip_custom_ex: Option<[u8; 4]>,
45 #[serde(default = "default_true")]
48 pub emit_record_84: bool,
49 pub gray_bits: Option<u8>,
52 #[serde(default, deserialize_with = "record01_offsets")]
55 pub record01_overrides: BTreeMap<usize, u8>,
56}
57
58fn hex_keys<'de, D, K, V>(d: D) -> std::result::Result<BTreeMap<K, V>, D::Error>
61where
62 D: serde::Deserializer<'de>,
63 K: HexKey,
64 V: serde::Deserialize<'de>,
65{
66 let raw: BTreeMap<String, V> = serde::Deserialize::deserialize(d)?;
67 let mut out = BTreeMap::new();
68 for (key, value) in raw {
69 let k = K::parse_hex(&key).map_err(serde::de::Error::custom)?;
70 if out.insert(k, value).is_some() {
71 return Err(serde::de::Error::custom(format!(
72 "key {key} given twice (spelled differently)"
73 )));
74 }
75 }
76 Ok(out)
77}
78
79pub(crate) fn record01_offsets<'de, D>(d: D) -> std::result::Result<BTreeMap<usize, u8>, D::Error>
81where
82 D: serde::Deserializer<'de>,
83{
84 let map: BTreeMap<usize, u8> = hex_keys(d)?;
85 if let Some(at) = map.keys().find(|&&at| at >= crate::RECORD01_LEN) {
86 return Err(serde::de::Error::custom(format!(
87 "record01_overrides offset {at:#05x} is past the record"
88 )));
89 }
90 Ok(map)
91}
92
93pub(crate) fn hex_offsets<S>(map: &BTreeMap<usize, u8>, s: S) -> std::result::Result<S::Ok, S::Error>
95where
96 S: serde::Serializer,
97{
98 s.collect_map(map.iter().map(|(at, v)| (format!("{at:#05X}"), v)))
99}
100
101pub(crate) trait HexKey: Ord {
102 fn parse_hex(s: &str) -> Result<Self>
103 where
104 Self: Sized;
105}
106
107impl HexKey for u8 {
108 fn parse_hex(s: &str) -> Result<Self> {
109 Self::from_str_radix(s.trim_start_matches("0x"), 16)
110 .with_context(|| format!("bad register id {s:?}"))
111 }
112}
113
114impl HexKey for usize {
115 fn parse_hex(s: &str) -> Result<Self> {
116 Self::from_str_radix(s.trim_start_matches("0x"), 16)
117 .with_context(|| format!("bad offset {s:?}"))
118 }
119}
120
121#[derive(Debug, Clone, Deserialize)]
122#[serde(deny_unknown_fields)]
123pub struct ScanPatch {
124 pub bytes: Vec<usize>,
125 pub mask: u8,
126 pub base: u8,
127}
128
129const fn default_true() -> bool {
130 true
131}
132
133impl ChipLibrary {
134 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
137 let path = path.as_ref();
138 let text =
139 std::fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?;
140 Self::parse(&text).with_context(|| format!("parse {}", path.display()))
141 }
142
143 pub fn parse(text: &str) -> Result<Self> {
148 Ok(toml::from_str(text)?)
149 }
150
151 fn reg(&self, id: u8) -> Result<[u8; 3]> {
152 self.registers
153 .get(&id)
154 .copied()
155 .with_context(|| format!("{}: no values for register {id:#04x}", self.name))
156 }
157
158 pub fn record_84(&self, scan: u8) -> Result<Option<[u8; 256]>> {
165 if !self.emit_record_84 || self.order.is_empty() {
166 return Ok(None);
167 }
168 if self.order.len() * 4 > 256 {
169 bail!(
170 "{}: {} registers do not fit a 256-byte record",
171 self.name,
172 self.order.len()
173 );
174 }
175 let mut out = [0u8; 256];
176 let (quads, _) = out.as_chunks_mut::<4>();
178 for (quad, ®) in quads.iter_mut().zip(&self.order) {
179 let rgb = if reg == 0x02 {
180 [scan.wrapping_sub(1) & 0x3F; 3]
181 } else {
182 self.reg(reg)?
183 };
184 quad[0] = reg;
185 quad[1..].copy_from_slice(&rgb);
186 }
187 Ok(Some(out))
188 }
189
190 #[must_use]
193 pub fn chip_custom_block(&self, scan: u8) -> Option<[u8; 16]> {
194 let mut block = self.chip_custom?;
195 if let Some(p) = &self.chip_custom_scan_patch {
196 for &i in &p.bytes {
197 if let Some(b) = block.get_mut(i) {
198 *b = p.base | (scan.wrapping_sub(1) & p.mask);
199 }
200 }
201 }
202 Some(block)
203 }
204
205 pub fn gray_bits(&self) -> Result<u8> {
212 if let Some(g) = self.gray_bits {
213 return Ok(g);
214 }
215 let r07 = self.reg(0x07)?[0];
216 let r03 = self.reg(0x03)?[0];
217 let g = 128u32 << ((r07 >> 3) & 3);
218 let m = if r03 < 0x40 { 64 } else { 32 };
219 Ok(match m * g {
220 x if x < 0x1000 => 12,
221 x if x < 0x2000 => 13,
222 x if x < 0x4000 => 14,
223 x if x < 0x8000 => 15,
224 _ => 16,
225 })
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 fn lib() -> ChipLibrary {
234 toml::from_str(
235 r#"
236 name = "t"
237 family_id = 1
238 serial_clock = 15
239 chip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
240 order = [0x02, 0x03, 0x07, 0xf0]
241 [registers]
242 0x02 = [0x3f, 0x3f, 0x3f]
243 0x03 = [0x3f, 0x3f, 0x3f]
244 0x07 = [0x04, 0x04, 0x04]
245 0xf0 = [4, 5, 6]
246 "#,
247 )
248 .unwrap()
249 }
250
251 #[test]
252 fn quads_land_in_order_with_scan_patch_and_zero_fill() {
253 let r = lib().record_84(16).unwrap().unwrap();
254 assert_eq!(&r[..4], &[0x02, 15, 15, 15], "reg 0x02 = scan - 1");
255 assert_eq!(&r[12..16], &[0xf0, 4, 5, 6]);
256 assert!(r[16..].iter().all(|&b| b == 0));
257 }
258
259 #[test]
260 fn hex_keys_are_typed_and_range_checked_at_load() {
261 let lib: ChipLibrary = toml::from_str(
262 r#"
263 name = "t"
264 family_id = 1
265 serial_clock = 15
266 chip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
267 [registers]
268 0x2 = [1, 2, 3]
269 [record01_overrides]
270 "0x02F" = 1
271 "0x2fb" = 2
272 "#,
273 )
274 .unwrap();
275 assert_eq!(lib.registers.get(&2), Some(&[1, 2, 3]));
276 assert_eq!(
277 lib.record01_overrides.iter().collect::<Vec<_>>(),
278 vec![(&0x2F, &1), (&0x2FB, &2)]
279 );
280
281 let past: std::result::Result<ChipLibrary, _> = toml::from_str(
282 "name = \"t\"\nfamily_id = 1\nserial_clock = 1\nchip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\n[record01_overrides]\n\"0x2FC\" = 1\n",
283 );
284 assert!(past.unwrap_err().to_string().contains("past the record"));
285 let twice: std::result::Result<ChipLibrary, _> = toml::from_str(
286 "name = \"t\"\nfamily_id = 1\nserial_clock = 1\nchip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\n[record01_overrides]\n\"0x02F\" = 1\n\"0x2F\" = 0\n",
287 );
288 assert!(twice.unwrap_err().to_string().contains("twice"));
289 }
290
291 #[test]
292 fn gray_bits_follow_the_vendor_formula() {
293 assert_eq!(lib().gray_bits().unwrap(), 14); }
295}