m5cardputer 0.1.1

esp-idf-hal-based board support for the M5Stack Card Computer
// SPDX-License-Identifier: GPL-3.0-or-later

use ambassador::{Delegate, delegatable_trait_remote};
use display_interface_spi::SPIInterfaceNoCS;
use embedded_graphics::{prelude::{Size, DrawTarget}, geometry::OriginDimensions, Pixel, primitives::Rectangle};
use esp_idf_svc::hal::delay::Delay;
use esp_idf_svc::hal::{gpio::*, ledc};
use esp_idf_svc::hal::ledc::config::TimerConfig;
use esp_idf_svc::hal::ledc::{LedcTimerDriver, LedcDriver, LedcTimer, LedcChannel, SpeedMode};
use esp_idf_svc::hal::peripheral::Peripheral;
use esp_idf_svc::hal::spi::{SpiDriver, SpiDeviceDriver};
use esp_idf_svc::hal::units::Hertz;
use esp_idf_svc::sys::EspError;
use mipidsi::models::ST7789;

#[delegatable_trait_remote]
trait DrawTarget {
    type Color: PixelColor;
    type Error;
    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>;
    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Self::Color>;
    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error>;
    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error>;
}

#[delegatable_trait_remote]
pub trait OriginDimensions {
    fn size(&self) -> Size;
}

/// An [`embedded_graphics::prelude::DrawTarget`] for the 135x240 ST7789 display with PWM backlight support.
#[derive(Delegate)]
#[delegate(DrawTarget, target = "display")]
#[delegate(OriginDimensions, target = "display")]
pub struct Display<'a> {
    display: mipidsi::Display<SPIInterfaceNoCS<SpiDeviceDriver<'a, SpiDriver<'a>>, PinDriver<'a, Gpio34, Output>>, ST7789, PinDriver<'a, Gpio33, Output>>,
    backlight: LedcDriver<'a>
}

impl<'a> Display<'a> {
    pub fn new<Timer: Peripheral + 'a, Channel: Peripheral + 'a>(spi: SpiDeviceDriver<'a, SpiDriver<'a>>, rs_pin: Gpio34, rst_pin: Gpio33, bl_pin: Gpio38, ledc_timer: Timer, ledc_channel: Channel) -> Result<Self, EspError> where Timer::P: LedcTimer, Channel::P: LedcChannel {
        // Setup backlight
        // Don't drop this: backlight PWM will stop otherwise
        let backlight = Self::setup_bl_pwm(ledc_timer, ledc_channel, bl_pin)?;

        let lcd_rst = PinDriver::output(rst_pin).unwrap();
        let lcd_rs = PinDriver::output(rs_pin).unwrap();

        let di = display_interface_spi::SPIInterfaceNoCS::new(spi, lcd_rs);
        let display = mipidsi::Builder::st7789_pico1(di).with_orientation(mipidsi::Orientation::Landscape(true)).init(&mut Delay::new_default(), Some(lcd_rst)).unwrap();

        Ok(Self {
            display,
            backlight
        })
    }

    fn setup_bl_pwm<'d, Timer: Peripheral + 'd, Channel: Peripheral + 'd>(timer: Timer, channel: Channel, lcd_bl: Gpio38) -> Result<LedcDriver<'d>, EspError> where Timer::P: LedcTimer, Channel::P: LedcChannel {
        let timer_config = TimerConfig::new().speed_mode(SpeedMode::LowSpeed).resolution(ledc::Resolution::Bits8).frequency(Hertz(256).into());
        let bl_timer_driver = LedcTimerDriver::new(timer, &timer_config)?;
        let bl_driver = LedcDriver::new(channel, bl_timer_driver, lcd_bl)?;

        Ok(bl_driver)
    }

    pub fn set_brightness(&mut self, brightness: u8) -> Result<(), EspError> {
        self.backlight.set_duty(brightness as u32)
    }
}