use core::{convert::Infallible, marker::PhantomData};
use embedded_hal_async::{digital, i2c};
use heapless::Vec;
use crate::{chip::*, reg};
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub enum Error<I2cE, IntE> {
I2C(I2cE),
Int(IntE),
InvalidData,
InvalidChip,
InvalidProductId,
}
#[doc(hidden)]
pub trait PinMarker {}
pub struct IntPin;
impl PinMarker for IntPin {}
pub struct NoIntPin;
impl PinMarker for NoIntPin {}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Gt9x<'a, C, I2C, PIN, PM, CM = ()>
where
C: Chip,
I2C: i2c::I2c,
PM: PinMarker,
CM: CacheMaintenance,
{
chip: PhantomData<C>,
pub i2c: I2C,
pub i2c_addr: u8,
pub int: Option<PIN>,
pm: PhantomData<PM>,
pub buf: &'a mut [u8],
cm: CM,
}
impl<'a, C, I2C> Gt9x<'a, C, I2C, (), NoIntPin, ()>
where
C: Chip,
I2C: i2c::I2c,
{
pub fn new(i2c: I2C, buf: &'a mut [u8]) -> Self {
Self::new_internal(i2c, buf, None, ())
}
}
impl<'a, C, I2C, PIN> Gt9x<'a, C, I2C, PIN, IntPin, ()>
where
C: Chip,
I2C: i2c::I2c,
PIN: digital::Wait,
{
pub fn new_int(i2c: I2C, buf: &'a mut [u8], int: PIN) -> Self {
Self::new_internal(i2c, buf, Some(int), ())
}
}
impl<'a, C, I2C, PIN, PM, CM> Gt9x<'a, C, I2C, PIN, PM, CM>
where
C: Chip,
I2C: i2c::I2c,
PM: PinMarker,
CM: CacheMaintenance,
{
fn new_internal(i2c: I2C, buf: &'a mut [u8], int: Option<PIN>, cm: CM) -> Self {
let required_len = (C::MAX_POINTS as usize * 8).max(4);
assert!(buf.len() >= required_len, "Buffer too small");
if let Some(alignment) = cm.required_alignment() {
assert!(
alignment > 0 && alignment.is_power_of_two(),
"Cache alignment must be a power of two."
);
assert!(
buf.as_ptr() as usize % alignment == 0,
"Buffer start address is not aligned as required by the cache manager."
);
assert!(
buf.len() % alignment == 0,
"Buffer length is not a multiple of the alignment required by the cache manager."
);
}
Gt9x {
chip: PhantomData,
i2c,
i2c_addr: 0x5D,
int,
pm: PhantomData,
buf,
cm,
}
}
pub fn set_i2c_addr(&mut self, addr: u8) {
self.i2c_addr = addr;
}
pub async fn write<E>(&mut self, write: &[u8]) -> Result<(), Error<I2C::Error, E>> {
self.i2c
.write(self.i2c_addr, write)
.await
.map_err(Error::I2C)
}
pub async fn write_read<E>(
&mut self,
write: &[u8],
len: usize,
) -> Result<(), Error<I2C::Error, E>> {
self.i2c
.write_read(self.i2c_addr, write, &mut self.buf[..len])
.await
.map_err(Error::I2C)?;
self.cm.invalidate_dcache_by_slice(&mut self.buf);
Ok(())
}
pub async fn init(&mut self) -> Result<(), Error<I2C::Error, Infallible>> {
self.write(reg::CMD::ModeRead.as_bytes()).await?;
self.write_read(reg::ID_ADDR, 4).await?;
match core::str::from_utf8(&self.buf[..4]) {
Ok(product_id) => {
#[cfg(feature = "defmt")]
defmt::trace!("Product ID: {}", product_id);
if product_id == C::ID {
self.write(reg::CLEAR_STATUS).await?;
Ok(())
} else {
#[cfg(feature = "defmt")]
defmt::error!("Expected: {}, found: {}", C::ID, product_id);
Err(Error::InvalidProductId)
}
}
Err(_) => {
#[cfg(feature = "defmt")]
defmt::error!("InvalidData: {}", self.buf[..4]);
Err(Error::InvalidData)
}
}
}
async fn _get_touches<E>(
&mut self,
) -> Result<Vec<Point, { C::MAX_POINTS as usize }>, Error<I2C::Error, E>>
where
[(); C::MAX_POINTS as usize]:,
{
self.write_read(reg::STATUS_ADDR, 1).await?;
let status = reg::Status::from_bits(self.buf[0]);
if status.num_points() == 0 || !status.buffer_status() {
self.write(reg::CLEAR_STATUS).await?;
return Ok(Vec::new());
}
self.write_read(®::POINT_BASE, status.num_points() as usize * 8)
.await?;
let mut points = Vec::new();
for i in 0..status.num_points().min(C::MAX_POINTS) {
let base = (i as usize) * 8;
let point = Point {
id: self.buf[base] & 0x0F,
x: u16::from_le_bytes([self.buf[base + 1], self.buf[base + 2]]),
y: u16::from_le_bytes([self.buf[base + 3], self.buf[base + 4]]),
area: u16::from_le_bytes([self.buf[base + 5], self.buf[base + 6]]),
};
points.push(point).map_err(|_| Error::InvalidData)?;
}
self.write(reg::CLEAR_STATUS).await?;
Ok(points)
}
}
impl<'a, C, I2C, PIN, CM> Gt9x<'a, C, I2C, PIN, IntPin, CM>
where
C: Chip,
I2C: i2c::I2c,
PIN: digital::Wait,
CM: CacheMaintenance,
{
pub fn new_int_cache(i2c: I2C, buf: &'a mut [u8], int: PIN, cm: CM) -> Self {
Self::new_internal(i2c, buf, Some(int), cm)
}
pub async fn get_touches(
&mut self,
) -> Result<Vec<Point, { C::MAX_POINTS as usize }>, Error<I2C::Error, PIN::Error>>
where
[(); C::MAX_POINTS as usize]:,
{
self.int
.as_mut()
.unwrap()
.wait_for_rising_edge()
.await
.map_err(Error::Int)?;
self._get_touches().await
}
}
impl<'a, C, I2C, CM> Gt9x<'a, C, I2C, (), NoIntPin, CM>
where
C: Chip,
I2C: i2c::I2c,
CM: CacheMaintenance,
{
pub fn new_cache(i2c: I2C, buf: &'a mut [u8], cm: CM) -> Self {
Self::new_internal(i2c, buf, None, cm)
}
pub async fn get_touches(
&mut self,
) -> Result<Vec<Point, { C::MAX_POINTS as usize }>, Error<I2C::Error, Infallible>>
where
[(); C::MAX_POINTS as usize]:,
{
self._get_touches().await
}
}
pub trait CacheMaintenance {
fn required_alignment(&self) -> Option<usize>;
fn invalidate_dcache_by_slice(&self, slice: &mut [u8]);
}
impl CacheMaintenance for () {
#[inline(always)]
fn required_alignment(&self) -> Option<usize> {
None
}
#[inline(always)]
fn invalidate_dcache_by_slice(&self, _slice: &mut [u8]) {}
}