Skip to main content

panelspec/
chips.rs

1//! Driver-chip library (`config/chips/*.toml`).
2//!
3//! The vendor's default register table for a chip, the chip-control block
4//! the card's config carries for it, and the rules the vendor applies when
5//! it builds record 0x84.
6
7use 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    /// The evidence behind the values, as in a spec's `[meta] status`;
17    /// `derived` (a vendor default) unless the file says otherwise.
18    #[serde(default)]
19    pub status: crate::Status,
20    /// Who makes the chip.
21    #[serde(default)]
22    pub vendor: Option<String>,
23    /// Datasheet.
24    #[serde(default)]
25    pub datasheet: Option<String>,
26    pub family_id: u16,
27    pub sub_id: Option<u16>,
28    /// Vendor default serial clock for the chip (record +0x021).
29    pub serial_clock: u16,
30    /// The 20-byte `SChipControl` block the vendor's `ResetChipControl`
31    /// emits for this chip (record 0x01 +0x0C4).
32    pub chip_control: [u8; 20],
33    /// Register ids in record order. Absent for chips without an addressed
34    /// register table (the non-SH S-PWM parts, e.g. SM16169S).
35    #[serde(default)]
36    pub order: Vec<u8>,
37    /// Register id (spelled `0x..` in the file) → R, G, B values.
38    #[serde(default, deserialize_with = "hex_keys")]
39    pub registers: BTreeMap<u8, [u8; 3]>,
40    /// The 16-byte `SChipCustom` block (record 0x01 +0x06A) when the chip's
41    /// configuration lives there instead of in record 0x84. When absent the
42    /// generator writes only the PWM-flag/serial-clock pair the SH chips use.
43    pub chip_custom: Option<[u8; 16]>,
44    /// The scan patch the vendor applies to `chip_custom` on load:
45    /// `byte = base | ((scan - 1) & mask)` for each listed byte.
46    pub chip_custom_scan_patch: Option<ScanPatch>,
47    /// `SChipCustomEX` (record 0x01 +0x0E0..+0x0E3).
48    pub chip_custom_ex: Option<[u8; 4]>,
49    /// The vendor omits record 0x84 for non-addressed chips; a zeroed record
50    /// is not the same file.
51    #[serde(default = "default_true")]
52    pub emit_record_84: bool,
53    /// Grey depth as a literal, for chips whose depth is not derived from
54    /// registers 0x07/0x03.
55    pub gray_bits: Option<u8>,
56    /// Record 0x01 bytes this chip id sets differently from the 0x14C
57    /// baseline; applied before the spec's own overrides.
58    #[serde(default, deserialize_with = "record01_offsets")]
59    pub record01_overrides: BTreeMap<usize, u8>,
60}
61
62/// `0x..` map keys parsed at load. Two spellings of one key (`0x02F`/`0x2F`)
63/// are refused rather than letting the map pick one.
64fn hex_keys<'de, D, K, V>(d: D) -> std::result::Result<BTreeMap<K, V>, D::Error>
65where
66    D: serde::Deserializer<'de>,
67    K: HexKey,
68    V: serde::Deserialize<'de>,
69{
70    let raw: BTreeMap<String, V> = serde::Deserialize::deserialize(d)?;
71    let mut out = BTreeMap::new();
72    for (key, value) in raw {
73        let k = K::parse_hex(&key).map_err(serde::de::Error::custom)?;
74        if out.insert(k, value).is_some() {
75            return Err(serde::de::Error::custom(format!(
76                "key {key} given twice (spelled differently)"
77            )));
78        }
79    }
80    Ok(out)
81}
82
83/// `hex_keys` for record 0x01 offsets, which must lie inside the record.
84pub(crate) fn record01_offsets<'de, D>(d: D) -> std::result::Result<BTreeMap<usize, u8>, D::Error>
85where
86    D: serde::Deserializer<'de>,
87{
88    let map: BTreeMap<usize, u8> = hex_keys(d)?;
89    if let Some(at) = map.keys().find(|&&at| at >= crate::RECORD01_LEN) {
90        return Err(serde::de::Error::custom(format!(
91            "record01_overrides offset {at:#05x} is past the record"
92        )));
93    }
94    Ok(map)
95}
96
97/// The reverse of `record01_offsets`: keys written as `"0x02F"`.
98pub(crate) fn hex_offsets<S>(map: &BTreeMap<usize, u8>, s: S) -> std::result::Result<S::Ok, S::Error>
99where
100    S: serde::Serializer,
101{
102    s.collect_map(map.iter().map(|(at, v)| (format!("{at:#05X}"), v)))
103}
104
105pub(crate) trait HexKey: Ord {
106    fn parse_hex(s: &str) -> Result<Self>
107    where
108        Self: Sized;
109}
110
111impl HexKey for u8 {
112    fn parse_hex(s: &str) -> Result<Self> {
113        Self::from_str_radix(s.trim_start_matches("0x"), 16)
114            .with_context(|| format!("bad register id {s:?}"))
115    }
116}
117
118impl HexKey for usize {
119    fn parse_hex(s: &str) -> Result<Self> {
120        Self::from_str_radix(s.trim_start_matches("0x"), 16)
121            .with_context(|| format!("bad offset {s:?}"))
122    }
123}
124
125#[derive(Debug, Clone, Deserialize)]
126#[serde(deny_unknown_fields)]
127pub struct ScanPatch {
128    pub bytes: Vec<usize>,
129    pub mask: u8,
130    pub base: u8,
131}
132
133const fn default_true() -> bool {
134    true
135}
136
137impl ChipLibrary {
138    /// # Errors
139    /// Fails on a missing or malformed file.
140    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
141        let path = path.as_ref();
142        let text =
143            std::fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?;
144        Self::parse(&text).with_context(|| format!("parse {}", path.display()))
145    }
146
147    /// Read a library from TOML text.
148    ///
149    /// # Errors
150    /// Fails on malformed TOML, an unknown field or a bad hex key.
151    pub fn parse(text: &str) -> Result<Self> {
152        Ok(toml::from_str(text)?)
153    }
154
155    fn reg(&self, id: u8) -> Result<[u8; 3]> {
156        self.registers
157            .get(&id)
158            .copied()
159            .with_context(|| format!("{}: no values for register {id:#04x}", self.name))
160    }
161
162    /// Record 0x84: `(register, R, G, B)` quads in library order, zero-filled
163    /// to 256 bytes, with the vendor's post-load patch of register 0x02 to
164    /// `scan - 1` (`ResetChipCustom`, chip 0x14C case).
165    ///
166    /// # Errors
167    /// Fails if the order names a register the table lacks, or overflows.
168    pub fn record_84(&self, scan: u8) -> Result<Option<[u8; 256]>> {
169        if !self.emit_record_84 || self.order.is_empty() {
170            return Ok(None);
171        }
172        if self.order.len() * 4 > 256 {
173            bail!(
174                "{}: {} registers do not fit a 256-byte record",
175                self.name,
176                self.order.len()
177            );
178        }
179        let mut out = [0u8; 256];
180        // as_chunks_mut keeps the fixed width in the type.
181        let (quads, _) = out.as_chunks_mut::<4>();
182        for (quad, &reg) in quads.iter_mut().zip(&self.order) {
183            let rgb = if reg == 0x02 {
184                [scan.wrapping_sub(1) & 0x3F; 3]
185            } else {
186                self.reg(reg)?
187            };
188            quad[0] = reg;
189            quad[1..].copy_from_slice(&rgb);
190        }
191        Ok(Some(out))
192    }
193
194    /// The `SChipCustom` block for a scan count, with the vendor's load-time
195    /// patch applied; `None` for chips configured through record 0x84.
196    #[must_use]
197    pub fn chip_custom_block(&self, scan: u8) -> Option<[u8; 16]> {
198        let mut block = self.chip_custom?;
199        if let Some(p) = &self.chip_custom_scan_patch {
200            for &i in &p.bytes {
201                if let Some(b) = block.get_mut(i) {
202                    *b = p.base | (scan.wrapping_sub(1) & p.mask);
203                }
204            }
205        }
206        Some(block)
207    }
208
209    /// Grayscale depth the vendor derives from the registers
210    /// (`GetSupporttedGray`, chip 0x14C branch): line-gray from reg 0x07
211    /// bits 4:3 times a multiplier from reg 0x03, bucketed into 12..16 bits.
212    ///
213    /// # Errors
214    /// Fails if registers 0x03 or 0x07 are missing.
215    pub fn gray_bits(&self) -> Result<u8> {
216        if let Some(g) = self.gray_bits {
217            return Ok(g);
218        }
219        let r07 = self.reg(0x07)?[0];
220        let r03 = self.reg(0x03)?[0];
221        let g = 128u32 << ((r07 >> 3) & 3);
222        let m = if r03 < 0x40 { 64 } else { 32 };
223        Ok(match m * g {
224            x if x < 0x1000 => 12,
225            x if x < 0x2000 => 13,
226            x if x < 0x4000 => 14,
227            x if x < 0x8000 => 15,
228            _ => 16,
229        })
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    fn lib() -> ChipLibrary {
238        toml::from_str(
239            r#"
240            name = "t"
241            family_id = 1
242            serial_clock = 15
243            chip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
244            order = [0x02, 0x03, 0x07, 0xf0]
245            [registers]
246            0x02 = [0x3f, 0x3f, 0x3f]
247            0x03 = [0x3f, 0x3f, 0x3f]
248            0x07 = [0x04, 0x04, 0x04]
249            0xf0 = [4, 5, 6]
250            "#,
251        )
252        .unwrap()
253    }
254
255    #[test]
256    fn quads_land_in_order_with_scan_patch_and_zero_fill() {
257        let r = lib().record_84(16).unwrap().unwrap();
258        assert_eq!(&r[..4], &[0x02, 15, 15, 15], "reg 0x02 = scan - 1");
259        assert_eq!(&r[12..16], &[0xf0, 4, 5, 6]);
260        assert!(r[16..].iter().all(|&b| b == 0));
261    }
262
263    #[test]
264    fn hex_keys_are_typed_and_range_checked_at_load() {
265        let lib: ChipLibrary = toml::from_str(
266            r#"
267            name = "t"
268            family_id = 1
269            serial_clock = 15
270            chip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
271            [registers]
272            0x2 = [1, 2, 3]
273            [record01_overrides]
274            "0x02F" = 1
275            "0x2fb" = 2
276            "#,
277        )
278        .unwrap();
279        assert_eq!(lib.registers.get(&2), Some(&[1, 2, 3]));
280        assert_eq!(
281            lib.record01_overrides.iter().collect::<Vec<_>>(),
282            vec![(&0x2F, &1), (&0x2FB, &2)]
283        );
284
285        let past: 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\"0x2FC\" = 1\n",
287        );
288        assert!(past.unwrap_err().to_string().contains("past the record"));
289        let twice: std::result::Result<ChipLibrary, _> = toml::from_str(
290            "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",
291        );
292        assert!(twice.unwrap_err().to_string().contains("twice"));
293    }
294
295    #[test]
296    fn gray_bits_follow_the_vendor_formula() {
297        assert_eq!(lib().gray_bits().unwrap(), 14); // 128 x 64 = 0x2000 -> 14
298    }
299}