use crate::error::{RtlSdrError, TunerError};
use crate::usb;
use super::R82xxPriv;
use super::constants::{NUM_REGS, REG_SHADOW_START, bitrev};
impl R82xxPriv {
pub(super) fn shadow_store(&mut self, reg: u8, val: &[u8]) {
let mut r = reg as i32 - i32::from(REG_SHADOW_START);
let mut offset = 0usize;
let mut len = val.len() as i32;
if r < 0 {
len += r;
offset = (-r) as usize;
r = 0;
}
if len <= 0 {
return;
}
let r = r as usize;
if len > (NUM_REGS - r) as i32 {
len = (NUM_REGS - r) as i32;
}
let len = len as usize;
self.regs[r..r + len].copy_from_slice(&val[offset..offset + len]);
}
pub(super) fn shadow_equal(&self, reg: u8, val: &[u8]) -> bool {
let r = reg as i32 - i32::from(REG_SHADOW_START);
let len = val.len() as i32;
if r < 0 || len < 0 || len > (NUM_REGS as i32 - r) {
return false;
}
let r = r as usize;
let len = len as usize;
self.regs[r..r + len] == val[..len]
}
pub(super) fn write(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
reg: u8,
val: &[u8],
) -> Result<(), RtlSdrError> {
if self.shadow_equal(reg, val) {
return Ok(());
}
let buf_capacity = self.buf.len();
let mut pos = 0usize;
let mut current_reg = reg;
let mut remaining = val.len();
while remaining > 0 {
let size = i2c_chunk_size(remaining, self.max_i2c_msg_len, buf_capacity);
self.buf[0] = current_reg;
self.buf[1..1 + size].copy_from_slice(&val[pos..pos + size]);
let rc = usb::i2c_write(handle, self.i2c_addr, &self.buf[..size + 1])?;
if rc != size + 1 {
return Err(TunerError::I2cTransferFailed {
operation: "write",
got: rc,
expected: size + 1,
}
.into());
}
current_reg += size as u8;
remaining -= size;
pos += size;
}
self.shadow_store(reg, val);
Ok(())
}
pub(super) fn write_reg(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
reg: u8,
val: u8,
) -> Result<(), RtlSdrError> {
self.write(handle, reg, &[val])
}
pub(super) fn read_cache_reg(&self, reg: u8) -> Option<u8> {
let r = reg as i32 - i32::from(REG_SHADOW_START);
if r >= 0 && (r as usize) < NUM_REGS {
Some(self.regs[r as usize])
} else {
None
}
}
pub(super) fn write_reg_mask(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
reg: u8,
val: u8,
bit_mask: u8,
) -> Result<(), RtlSdrError> {
let cached = self
.read_cache_reg(reg)
.ok_or(TunerError::ShadowCacheMiss { reg })?;
let new_val = (cached & !bit_mask) | (val & bit_mask);
self.write(handle, reg, &[new_val])
}
pub(super) fn read(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
reg: u8,
out: &mut [u8],
) -> Result<(), RtlSdrError> {
self.buf[0] = reg;
let rc = usb::i2c_write(handle, self.i2c_addr, &self.buf[..1])?;
if rc != 1 {
return Err(TunerError::I2cTransferFailed {
operation: "read addr",
got: rc,
expected: 1,
}
.into());
}
let rc = usb::i2c_read(handle, self.i2c_addr, &mut self.buf[1..1 + out.len()])?;
if rc != out.len() {
return Err(TunerError::I2cTransferFailed {
operation: "read data",
got: rc,
expected: out.len(),
}
.into());
}
for (i, byte) in self.buf[1..1 + out.len()].iter().enumerate() {
out[i] = bitrev(*byte);
}
Ok(())
}
}
fn i2c_chunk_size(remaining: usize, max_msg_len: usize, buf_capacity: usize) -> usize {
let effective_max = max_msg_len.clamp(2, buf_capacity);
remaining.min(effective_max - 1)
}
#[cfg(test)]
mod tests {
use super::{NUM_REGS, i2c_chunk_size};
const BUF_CAP: usize = NUM_REGS + 1;
#[test]
fn normal_max_msg_picks_max_msg_minus_one() {
assert_eq!(i2c_chunk_size(20, 8, BUF_CAP), 7);
assert_eq!(i2c_chunk_size(7, 8, BUF_CAP), 7);
}
#[test]
fn remaining_smaller_than_chunk_returns_remaining() {
assert_eq!(i2c_chunk_size(3, 8, BUF_CAP), 3);
assert_eq!(i2c_chunk_size(1, 8, BUF_CAP), 1);
}
#[test]
fn max_msg_below_floor_clamps_so_loop_makes_progress() {
assert_eq!(i2c_chunk_size(10, 1, BUF_CAP), 1);
assert_eq!(i2c_chunk_size(10, 0, BUF_CAP), 1);
}
#[test]
fn max_msg_above_buf_capacity_clamps_so_no_oob() {
assert_eq!(i2c_chunk_size(100, 1000, BUF_CAP), BUF_CAP - 1);
assert_eq!(i2c_chunk_size(100, BUF_CAP + 1, BUF_CAP), BUF_CAP - 1);
}
#[test]
fn chunk_size_always_makes_progress_when_remaining_positive() {
for max_msg in 0..=64 {
for remaining in 1..=64 {
let size = i2c_chunk_size(remaining, max_msg, BUF_CAP);
assert!(
size > 0,
"no progress: remaining={remaining}, max_msg={max_msg}"
);
}
}
}
}