diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index e615c6307..4eed6fe7d 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -1091,6 +1091,38 @@ fn main() {
         (("octospi", "NCS"), quote!(crate::ospi::NSSPin)),
         (("octospi", "CLK"), quote!(crate::ospi::SckPin)),
         (("octospi", "NCLK"), quote!(crate::ospi::NckPin)),
+        (("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)),
+        (("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)),
+        (("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)),
+        (("tsc", "G1_IO4"), quote!(crate::tsc::G1IO4Pin)),
+        (("tsc", "G2_IO1"), quote!(crate::tsc::G2IO1Pin)),
+        (("tsc", "G2_IO2"), quote!(crate::tsc::G2IO2Pin)),
+        (("tsc", "G2_IO3"), quote!(crate::tsc::G2IO3Pin)),
+        (("tsc", "G2_IO4"), quote!(crate::tsc::G2IO4Pin)),
+        (("tsc", "G3_IO1"), quote!(crate::tsc::G3IO1Pin)),
+        (("tsc", "G3_IO2"), quote!(crate::tsc::G3IO2Pin)),
+        (("tsc", "G3_IO3"), quote!(crate::tsc::G3IO3Pin)),
+        (("tsc", "G3_IO4"), quote!(crate::tsc::G3IO4Pin)),
+        (("tsc", "G4_IO1"), quote!(crate::tsc::G4IO1Pin)),
+        (("tsc", "G4_IO2"), quote!(crate::tsc::G4IO2Pin)),
+        (("tsc", "G4_IO3"), quote!(crate::tsc::G4IO3Pin)),
+        (("tsc", "G4_IO4"), quote!(crate::tsc::G4IO4Pin)),
+        (("tsc", "G5_IO1"), quote!(crate::tsc::G5IO1Pin)),
+        (("tsc", "G5_IO2"), quote!(crate::tsc::G5IO2Pin)),
+        (("tsc", "G5_IO3"), quote!(crate::tsc::G5IO3Pin)),
+        (("tsc", "G5_IO4"), quote!(crate::tsc::G5IO4Pin)),
+        (("tsc", "G6_IO1"), quote!(crate::tsc::G6IO1Pin)),
+        (("tsc", "G6_IO2"), quote!(crate::tsc::G6IO2Pin)),
+        (("tsc", "G6_IO3"), quote!(crate::tsc::G6IO3Pin)),
+        (("tsc", "G6_IO4"), quote!(crate::tsc::G6IO4Pin)),
+        (("tsc", "G7_IO1"), quote!(crate::tsc::G7IO1Pin)),
+        (("tsc", "G7_IO2"), quote!(crate::tsc::G7IO2Pin)),
+        (("tsc", "G7_IO3"), quote!(crate::tsc::G7IO3Pin)),
+        (("tsc", "G7_IO4"), quote!(crate::tsc::G7IO4Pin)),
+        (("tsc", "G8_IO1"), quote!(crate::tsc::G8IO1Pin)),
+        (("tsc", "G8_IO2"), quote!(crate::tsc::G8IO2Pin)),
+        (("tsc", "G8_IO3"), quote!(crate::tsc::G8IO3Pin)),
+        (("tsc", "G8_IO4"), quote!(crate::tsc::G8IO4Pin)),
     ].into();
 
     for p in METADATA.peripherals {
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 029e01480..b4b9d5d12 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -105,6 +105,8 @@ pub mod sai;
 pub mod sdmmc;
 #[cfg(spi)]
 pub mod spi;
+#[cfg(tsc)]
+pub mod tsc;
 #[cfg(ucpd)]
 pub mod ucpd;
 #[cfg(uid)]
diff --git a/embassy-stm32/src/tsc/enums.rs b/embassy-stm32/src/tsc/enums.rs
new file mode 100644
index 000000000..0d34a43ec
--- /dev/null
+++ b/embassy-stm32/src/tsc/enums.rs
@@ -0,0 +1,238 @@
+use core::ops::BitOr;
+
+/// Pin defines
+#[allow(missing_docs)]
+pub enum TscIOPin {
+    Group1Io1,
+    Group1Io2,
+    Group1Io3,
+    Group1Io4,
+    Group2Io1,
+    Group2Io2,
+    Group2Io3,
+    Group2Io4,
+    Group3Io1,
+    Group3Io2,
+    Group3Io3,
+    Group3Io4,
+    Group4Io1,
+    Group4Io2,
+    Group4Io3,
+    Group4Io4,
+    Group5Io1,
+    Group5Io2,
+    Group5Io3,
+    Group5Io4,
+    Group6Io1,
+    Group6Io2,
+    Group6Io3,
+    Group6Io4,
+    #[cfg(any(tsc_v2, tsc_v3))]
+    Group7Io1,
+    #[cfg(any(tsc_v2, tsc_v3))]
+    Group7Io2,
+    #[cfg(any(tsc_v2, tsc_v3))]
+    Group7Io3,
+    #[cfg(any(tsc_v2, tsc_v3))]
+    Group7Io4,
+    #[cfg(tsc_v3)]
+    Group8Io1,
+    #[cfg(tsc_v3)]
+    Group8Io2,
+    #[cfg(tsc_v3)]
+    Group8Io3,
+    #[cfg(tsc_v3)]
+    Group8Io4,
+}
+
+impl BitOr<TscIOPin> for u32 {
+    type Output = u32;
+    fn bitor(self, rhs: TscIOPin) -> Self::Output {
+        let rhs: u32 = rhs.into();
+        self | rhs
+    }
+}
+
+impl BitOr<u32> for TscIOPin {
+    type Output = u32;
+    fn bitor(self, rhs: u32) -> Self::Output {
+        let val: u32 = self.into();
+        val | rhs
+    }
+}
+
+impl BitOr for TscIOPin {
+    type Output = u32;
+    fn bitor(self, rhs: Self) -> Self::Output {
+        let val: u32 = self.into();
+        let rhs: u32 = rhs.into();
+        val | rhs
+    }
+}
+
+impl Into<u32> for TscIOPin {
+    fn into(self) -> u32 {
+        match self {
+            TscIOPin::Group1Io1 => 0x00000001,
+            TscIOPin::Group1Io2 => 0x00000002,
+            TscIOPin::Group1Io3 => 0x00000004,
+            TscIOPin::Group1Io4 => 0x00000008,
+            TscIOPin::Group2Io1 => 0x00000010,
+            TscIOPin::Group2Io2 => 0x00000020,
+            TscIOPin::Group2Io3 => 0x00000040,
+            TscIOPin::Group2Io4 => 0x00000080,
+            TscIOPin::Group3Io1 => 0x00000100,
+            TscIOPin::Group3Io2 => 0x00000200,
+            TscIOPin::Group3Io3 => 0x00000400,
+            TscIOPin::Group3Io4 => 0x00000800,
+            TscIOPin::Group4Io1 => 0x00001000,
+            TscIOPin::Group4Io2 => 0x00002000,
+            TscIOPin::Group4Io3 => 0x00004000,
+            TscIOPin::Group4Io4 => 0x00008000,
+            TscIOPin::Group5Io1 => 0x00010000,
+            TscIOPin::Group5Io2 => 0x00020000,
+            TscIOPin::Group5Io3 => 0x00040000,
+            TscIOPin::Group5Io4 => 0x00080000,
+            TscIOPin::Group6Io1 => 0x00100000,
+            TscIOPin::Group6Io2 => 0x00200000,
+            TscIOPin::Group6Io3 => 0x00400000,
+            TscIOPin::Group6Io4 => 0x00800000,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            TscIOPin::Group7Io1 => 0x01000000,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            TscIOPin::Group7Io2 => 0x02000000,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            TscIOPin::Group7Io3 => 0x04000000,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            TscIOPin::Group7Io4 => 0x08000000,
+            #[cfg(tsc_v3)]
+            TscIOPin::Group8Io1 => 0x10000000,
+            #[cfg(tsc_v3)]
+            TscIOPin::Group8Io2 => 0x20000000,
+            #[cfg(tsc_v3)]
+            TscIOPin::Group8Io3 => 0x40000000,
+            #[cfg(tsc_v3)]
+            TscIOPin::Group8Io4 => 0x80000000,
+        }
+    }
+}
+
+/// Spread Spectrum Deviation
+#[derive(Copy, Clone)]
+pub struct SSDeviation(u8);
+impl SSDeviation {
+    /// Create new deviation value, acceptable inputs are 1-128
+    pub fn new(val: u8) -> Result<Self, ()> {
+        if val == 0 || val > 128 {
+            return Err(());
+        }
+        Ok(Self(val - 1))
+    }
+}
+
+impl Into<u8> for SSDeviation {
+    fn into(self) -> u8 {
+        self.0
+    }
+}
+
+/// Charge transfer pulse cycles
+#[allow(missing_docs)]
+#[derive(Copy, Clone, PartialEq)]
+pub enum ChargeTransferPulseCycle {
+    _1,
+    _2,
+    _3,
+    _4,
+    _5,
+    _6,
+    _7,
+    _8,
+    _9,
+    _10,
+    _11,
+    _12,
+    _13,
+    _14,
+    _15,
+    _16,
+}
+
+impl Into<u8> for ChargeTransferPulseCycle {
+    fn into(self) -> u8 {
+        match self {
+            ChargeTransferPulseCycle::_1 => 0,
+            ChargeTransferPulseCycle::_2 => 1,
+            ChargeTransferPulseCycle::_3 => 2,
+            ChargeTransferPulseCycle::_4 => 3,
+            ChargeTransferPulseCycle::_5 => 4,
+            ChargeTransferPulseCycle::_6 => 5,
+            ChargeTransferPulseCycle::_7 => 6,
+            ChargeTransferPulseCycle::_8 => 7,
+            ChargeTransferPulseCycle::_9 => 8,
+            ChargeTransferPulseCycle::_10 => 9,
+            ChargeTransferPulseCycle::_11 => 10,
+            ChargeTransferPulseCycle::_12 => 11,
+            ChargeTransferPulseCycle::_13 => 12,
+            ChargeTransferPulseCycle::_14 => 13,
+            ChargeTransferPulseCycle::_15 => 14,
+            ChargeTransferPulseCycle::_16 => 15,
+        }
+    }
+}
+
+/// Prescaler divider
+#[allow(missing_docs)]
+#[derive(Copy, Clone, PartialEq)]
+pub enum PGPrescalerDivider {
+    _1,
+    _2,
+    _4,
+    _8,
+    _16,
+    _32,
+    _64,
+    _128,
+}
+
+impl Into<u8> for PGPrescalerDivider {
+    fn into(self) -> u8 {
+        match self {
+            PGPrescalerDivider::_1 => 0,
+            PGPrescalerDivider::_2 => 1,
+            PGPrescalerDivider::_4 => 2,
+            PGPrescalerDivider::_8 => 3,
+            PGPrescalerDivider::_16 => 4,
+            PGPrescalerDivider::_32 => 5,
+            PGPrescalerDivider::_64 => 6,
+            PGPrescalerDivider::_128 => 7,
+        }
+    }
+}
+
+/// Max count
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum MaxCount {
+    _255,
+    _511,
+    _1023,
+    _2047,
+    _4095,
+    _8191,
+    _16383,
+}
+
+impl Into<u8> for MaxCount {
+    fn into(self) -> u8 {
+        match self {
+            MaxCount::_255 => 0,
+            MaxCount::_511 => 1,
+            MaxCount::_1023 => 2,
+            MaxCount::_2047 => 3,
+            MaxCount::_4095 => 4,
+            MaxCount::_8191 => 5,
+            MaxCount::_16383 => 6,
+        }
+    }
+}
diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs
new file mode 100644
index 000000000..bf583f04c
--- /dev/null
+++ b/embassy-stm32/src/tsc/mod.rs
@@ -0,0 +1,936 @@
+//! TSC Peripheral Interface
+//!
+//!
+//! # Example (stm32)
+//! ``` rust, ignore
+//!
+//! let mut device_config = embassy_stm32::Config::default();
+//! {
+//!     device_config.rcc.mux = ClockSrc::MSI(Msirange::RANGE_4MHZ);
+//! }
+//!
+//! let context = embassy_stm32::init(device_config);
+//!
+//! let config = tsc::Config {
+//!     ct_pulse_high_length: ChargeTransferPulseCycle::_2,
+//!     ct_pulse_low_length: ChargeTransferPulseCycle::_2,
+//!     spread_spectrum: false,
+//!     spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
+//!     spread_spectrum_prescaler: false,
+//!     pulse_generator_prescaler: PGPrescalerDivider::_4,
+//!     max_count_value: MaxCount::_8191,
+//!     io_default_mode: false,
+//!     synchro_pin_polarity: false,
+//!     acquisition_mode: false,
+//!     max_count_interrupt: false,
+//!     channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3,
+//!     shield_ios: TscIOPin::Group1Io3.into(),
+//!     sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2,
+//! };
+//!
+//! let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new();
+//! g1.set_io2(context.PB13, PinType::Sample);
+//! g1.set_io3(context.PB14, PinType::Shield);
+//!
+//! let mut g2: PinGroup<embassy_stm32::peripherals::TSC, G2> = PinGroup::new();
+//! g2.set_io1(context.PB4, PinType::Sample);
+//! g2.set_io2(context.PB5, PinType::Channel);
+//!
+//! let mut g7: PinGroup<embassy_stm32::peripherals::TSC, G7> = PinGroup::new();
+//! g7.set_io2(context.PE3, PinType::Sample);
+//! g7.set_io3(context.PE4, PinType::Channel);
+//!
+//! let mut touch_controller = tsc::Tsc::new(
+//!     context.TSC,
+//!     Some(g1),
+//!     Some(g2),
+//!     None,
+//!     None,
+//!     None,
+//!     None,
+//!     Some(g7),
+//!     None,
+//!     config,
+//! );
+//!
+//! touch_controller.discharge_io(true);
+//! Timer::after_millis(1).await;
+//!
+//! touch_controller.start();
+//!
+//! ```
+
+#![macro_use]
+
+/// Enums defined for peripheral parameters
+pub mod enums;
+
+use core::marker::PhantomData;
+
+use embassy_hal_internal::{into_ref, PeripheralRef};
+pub use enums::*;
+
+use crate::gpio::{AFType, AnyPin};
+use crate::pac::tsc::Tsc as Regs;
+use crate::rcc::RccPeripheral;
+use crate::{peripherals, Peripheral};
+
+#[cfg(tsc_v1)]
+const TSC_NUM_GROUPS: u32 = 6;
+#[cfg(tsc_v2)]
+const TSC_NUM_GROUPS: u32 = 7;
+#[cfg(tsc_v3)]
+const TSC_NUM_GROUPS: u32 = 8;
+
+/// Error type defined for TSC
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Error {
+    /// Test error for TSC
+    Test,
+}
+
+/// Pin type definition to control IO parameters
+pub enum PinType {
+    /// Sensing channel pin connected to an electrode
+    Channel,
+    /// Sampling capacitor pin, one required for every pin group
+    Sample,
+    /// Shield pin connected to capacitive sensing shield
+    Shield,
+}
+
+/// Peripheral state
+#[derive(PartialEq, Clone, Copy)]
+pub enum State {
+    /// Peripheral is being setup or reconfigured
+    Reset,
+    /// Ready to start acquisition
+    Ready,
+    /// In process of sensor acquisition
+    Busy,
+    /// Error occured during acquisition
+    Error,
+}
+
+/// Individual group status checked after acquisition reported as complete
+/// For groups with multiple channel pins, may take longer because acquisitions
+/// are done sequentially. Check this status before pulling count for each
+/// sampled channel
+#[derive(PartialEq)]
+pub enum GroupStatus {
+    /// Acquisition for channel still in progress
+    Ongoing,
+    /// Acquisition either not started or complete
+    Complete,
+}
+
+/// Group identifier used to interrogate status
+#[allow(missing_docs)]
+pub enum Group {
+    One,
+    Two,
+    Three,
+    Four,
+    Five,
+    Six,
+    #[cfg(any(tsc_v2, tsc_v3))]
+    Seven,
+    #[cfg(tsc_v3)]
+    Eight,
+}
+
+impl Into<usize> for Group {
+    fn into(self) -> usize {
+        match self {
+            Group::One => 0,
+            Group::Two => 1,
+            Group::Three => 2,
+            Group::Four => 3,
+            Group::Five => 4,
+            Group::Six => 5,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            Group::Seven => 6,
+            #[cfg(tsc_v3)]
+            Group::Eight => 7,
+        }
+    }
+}
+
+/// Peripheral configuration
+#[derive(Clone, Copy)]
+pub struct Config {
+    /// Duration of high state of the charge transfer pulse
+    pub ct_pulse_high_length: ChargeTransferPulseCycle,
+    /// Duration of the low state of the charge transfer pulse
+    pub ct_pulse_low_length: ChargeTransferPulseCycle,
+    /// Enable/disable of spread spectrum feature
+    pub spread_spectrum: bool,
+    /// Adds variable number of periods of the SS clk to pulse high state
+    pub spread_spectrum_deviation: SSDeviation,
+    /// Selects AHB clock divider used to generate SS clk
+    pub spread_spectrum_prescaler: bool,
+    /// Selects AHB clock divider used to generate pulse generator clk
+    pub pulse_generator_prescaler: PGPrescalerDivider,
+    /// Maximum number of charge tranfer pulses that can be generated before error
+    pub max_count_value: MaxCount,
+    /// Defines config of all IOs when no ongoing acquisition
+    pub io_default_mode: bool,
+    /// Polarity of sync input pin
+    pub synchro_pin_polarity: bool,
+    /// Acquisition starts when start bit is set or with sync pin input
+    pub acquisition_mode: bool,
+    /// Enable max count interrupt
+    pub max_count_interrupt: bool,
+    /// Channel IO mask
+    pub channel_ios: u32,
+    /// Shield IO mask
+    pub shield_ios: u32,
+    /// Sampling IO mask
+    pub sampling_ios: u32,
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        Self {
+            ct_pulse_high_length: ChargeTransferPulseCycle::_1,
+            ct_pulse_low_length: ChargeTransferPulseCycle::_1,
+            spread_spectrum: false,
+            spread_spectrum_deviation: SSDeviation::new(1).unwrap(),
+            spread_spectrum_prescaler: false,
+            pulse_generator_prescaler: PGPrescalerDivider::_1,
+            max_count_value: MaxCount::_255,
+            io_default_mode: false,
+            synchro_pin_polarity: false,
+            acquisition_mode: false,
+            max_count_interrupt: false,
+            channel_ios: 0,
+            shield_ios: 0,
+            sampling_ios: 0,
+        }
+    }
+}
+
+/// Pin struct that maintains usage
+#[allow(missing_docs)]
+pub struct TscPin<'d, T, C> {
+    _pin: PeripheralRef<'d, AnyPin>,
+    role: PinType,
+    phantom: PhantomData<(T, C)>,
+}
+
+enum GroupError {
+    NoSample,
+    ChannelShield,
+}
+
+/// Pin group definition
+/// Pins are organized into groups of four IOs, all groups with a
+/// sampling channel must also have a sampling capacitor channel.
+#[allow(missing_docs)]
+#[derive(Default)]
+pub struct PinGroup<'d, T, C> {
+    d1: Option<TscPin<'d, T, C>>,
+    d2: Option<TscPin<'d, T, C>>,
+    d3: Option<TscPin<'d, T, C>>,
+    d4: Option<TscPin<'d, T, C>>,
+}
+
+impl<'d, T: Instance, C> PinGroup<'d, T, C> {
+    /// Create new sensing group
+    pub fn new() -> Self {
+        Self {
+            d1: None,
+            d2: None,
+            d3: None,
+            d4: None,
+        }
+    }
+
+    fn contains_shield(&self) -> bool {
+        let mut shield_count = 0;
+
+        if let Some(pin) = &self.d1 {
+            if let PinType::Shield = pin.role {
+                shield_count += 1;
+            }
+        }
+
+        if let Some(pin) = &self.d2 {
+            if let PinType::Shield = pin.role {
+                shield_count += 1;
+            }
+        }
+
+        if let Some(pin) = &self.d3 {
+            if let PinType::Shield = pin.role {
+                shield_count += 1;
+            }
+        }
+
+        if let Some(pin) = &self.d4 {
+            if let PinType::Shield = pin.role {
+                shield_count += 1;
+            }
+        }
+
+        shield_count == 1
+    }
+
+    fn check_group(&self) -> Result<(), GroupError> {
+        let mut channel_count = 0;
+        let mut shield_count = 0;
+        let mut sample_count = 0;
+        if let Some(pin) = &self.d1 {
+            match pin.role {
+                PinType::Channel => {
+                    channel_count += 1;
+                }
+                PinType::Shield => {
+                    shield_count += 1;
+                }
+                PinType::Sample => {
+                    sample_count += 1;
+                }
+            }
+        }
+
+        if let Some(pin) = &self.d2 {
+            match pin.role {
+                PinType::Channel => {
+                    channel_count += 1;
+                }
+                PinType::Shield => {
+                    shield_count += 1;
+                }
+                PinType::Sample => {
+                    sample_count += 1;
+                }
+            }
+        }
+
+        if let Some(pin) = &self.d3 {
+            match pin.role {
+                PinType::Channel => {
+                    channel_count += 1;
+                }
+                PinType::Shield => {
+                    shield_count += 1;
+                }
+                PinType::Sample => {
+                    sample_count += 1;
+                }
+            }
+        }
+
+        if let Some(pin) = &self.d4 {
+            match pin.role {
+                PinType::Channel => {
+                    channel_count += 1;
+                }
+                PinType::Shield => {
+                    shield_count += 1;
+                }
+                PinType::Sample => {
+                    sample_count += 1;
+                }
+            }
+        }
+
+        // Every group requires one sampling capacitor
+        if sample_count != 1 {
+            return Err(GroupError::NoSample);
+        }
+
+        // Each group must have at least one shield or channel IO
+        if shield_count == 0 && channel_count == 0 {
+            return Err(GroupError::ChannelShield);
+        }
+
+        // Any group can either contain channel ios or a shield IO
+        if shield_count != 0 && channel_count != 0 {
+            return Err(GroupError::ChannelShield);
+        }
+
+        // No more than one shield IO is allow per group and amongst all groups
+        if shield_count > 1 {
+            return Err(GroupError::ChannelShield);
+        }
+
+        Ok(())
+    }
+}
+
+macro_rules! group_impl {
+    ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => {
+        impl<'d, T: Instance> PinGroup<'d, T, $group> {
+            #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")]
+            pub fn set_io1(&mut self, pin: impl Peripheral<P = impl $trait1<T>> + 'd, role: PinType) {
+                into_ref!(pin);
+                critical_section::with(|_| {
+                    pin.set_low();
+                    pin.set_as_af(
+                        pin.af_num(),
+                        match role {
+                            PinType::Channel => AFType::OutputPushPull,
+                            PinType::Sample => AFType::OutputOpenDrain,
+                            PinType::Shield => AFType::OutputPushPull,
+                        },
+                    );
+                    self.d1 = Some(TscPin {
+                        _pin: pin.map_into(),
+                        role: role,
+                        phantom: PhantomData,
+                    })
+                })
+            }
+
+            #[doc = concat!("Create a new pin2 for ", stringify!($group), " TSC group instance.")]
+            pub fn set_io2(&mut self, pin: impl Peripheral<P = impl $trait2<T>> + 'd, role: PinType) {
+                into_ref!(pin);
+                critical_section::with(|_| {
+                    pin.set_low();
+                    pin.set_as_af(
+                        pin.af_num(),
+                        match role {
+                            PinType::Channel => AFType::OutputPushPull,
+                            PinType::Sample => AFType::OutputOpenDrain,
+                            PinType::Shield => AFType::OutputPushPull,
+                        },
+                    );
+                    self.d2 = Some(TscPin {
+                        _pin: pin.map_into(),
+                        role: role,
+                        phantom: PhantomData,
+                    })
+                })
+            }
+
+            #[doc = concat!("Create a new pin3 for ", stringify!($group), " TSC group instance.")]
+            pub fn set_io3(&mut self, pin: impl Peripheral<P = impl $trait3<T>> + 'd, role: PinType) {
+                into_ref!(pin);
+                critical_section::with(|_| {
+                    pin.set_low();
+                    pin.set_as_af(
+                        pin.af_num(),
+                        match role {
+                            PinType::Channel => AFType::OutputPushPull,
+                            PinType::Sample => AFType::OutputOpenDrain,
+                            PinType::Shield => AFType::OutputPushPull,
+                        },
+                    );
+                    self.d3 = Some(TscPin {
+                        _pin: pin.map_into(),
+                        role: role,
+                        phantom: PhantomData,
+                    })
+                })
+            }
+
+            #[doc = concat!("Create a new pin4 for ", stringify!($group), " TSC group instance.")]
+            pub fn set_io4(&mut self, pin: impl Peripheral<P = impl $trait4<T>> + 'd, role: PinType) {
+                into_ref!(pin);
+                critical_section::with(|_| {
+                    pin.set_low();
+                    pin.set_as_af(
+                        pin.af_num(),
+                        match role {
+                            PinType::Channel => AFType::OutputPushPull,
+                            PinType::Sample => AFType::OutputOpenDrain,
+                            PinType::Shield => AFType::OutputPushPull,
+                        },
+                    );
+                    self.d4 = Some(TscPin {
+                        _pin: pin.map_into(),
+                        role: role,
+                        phantom: PhantomData,
+                    })
+                })
+            }
+        }
+    };
+}
+
+group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin);
+group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin);
+group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin);
+group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin);
+group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin);
+group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin);
+group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin);
+group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin);
+
+/// Group 1 marker type.
+pub enum G1 {}
+/// Group 2 marker type.
+pub enum G2 {}
+/// Group 3 marker type.
+pub enum G3 {}
+/// Group 4 marker type.
+pub enum G4 {}
+/// Group 5 marker type.
+pub enum G5 {}
+/// Group 6 marker type.
+pub enum G6 {}
+/// Group 7 marker type.
+pub enum G7 {}
+/// Group 8 marker type.
+pub enum G8 {}
+
+/// TSC driver
+pub struct Tsc<'d, T: Instance> {
+    _peri: PeripheralRef<'d, T>,
+    _g1: Option<PinGroup<'d, T, G1>>,
+    _g2: Option<PinGroup<'d, T, G2>>,
+    _g3: Option<PinGroup<'d, T, G3>>,
+    _g4: Option<PinGroup<'d, T, G4>>,
+    _g5: Option<PinGroup<'d, T, G5>>,
+    _g6: Option<PinGroup<'d, T, G6>>,
+    #[cfg(any(tsc_v2, tsc_v3))]
+    _g7: Option<PinGroup<'d, T, G7>>,
+    #[cfg(tsc_v3)]
+    _g8: Option<PinGroup<'d, T, G8>>,
+    state: State,
+    config: Config,
+}
+
+impl<'d, T: Instance> Tsc<'d, T> {
+    /// Create new TSC driver
+    pub fn new(
+        peri: impl Peripheral<P = T> + 'd,
+        g1: Option<PinGroup<'d, T, G1>>,
+        g2: Option<PinGroup<'d, T, G2>>,
+        g3: Option<PinGroup<'d, T, G3>>,
+        g4: Option<PinGroup<'d, T, G4>>,
+        g5: Option<PinGroup<'d, T, G5>>,
+        g6: Option<PinGroup<'d, T, G6>>,
+        #[cfg(any(tsc_v2, tsc_v3))] g7: Option<PinGroup<'d, T, G7>>,
+        #[cfg(tsc_v3)] g8: Option<PinGroup<'d, T, G8>>,
+        config: Config,
+    ) -> Self {
+        // Need to check valid pin configuration input
+        let g1 = g1.filter(|b| b.check_group().is_ok());
+        let g2 = g2.filter(|b| b.check_group().is_ok());
+        let g3 = g3.filter(|b| b.check_group().is_ok());
+        let g4 = g4.filter(|b| b.check_group().is_ok());
+        let g5 = g5.filter(|b| b.check_group().is_ok());
+        let g6 = g6.filter(|b| b.check_group().is_ok());
+        #[cfg(any(tsc_v2, tsc_v3))]
+        let g7 = g7.filter(|b| b.check_group().is_ok());
+        #[cfg(tsc_v3)]
+        let g8 = g8.filter(|b| b.check_group().is_ok());
+
+        match Self::check_shields(
+            &g1,
+            &g2,
+            &g3,
+            &g4,
+            &g5,
+            &g6,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            &g7,
+            #[cfg(tsc_v3)]
+            &g8,
+        ) {
+            Ok(()) => Self::new_inner(
+                peri,
+                g1,
+                g2,
+                g3,
+                g4,
+                g5,
+                g6,
+                #[cfg(any(tsc_v2, tsc_v3))]
+                g7,
+                #[cfg(tsc_v3)]
+                g8,
+                config,
+            ),
+            Err(_) => Self::new_inner(
+                peri,
+                None,
+                None,
+                None,
+                None,
+                None,
+                None,
+                #[cfg(any(tsc_v2, tsc_v3))]
+                None,
+                #[cfg(tsc_v3)]
+                None,
+                config,
+            ),
+        }
+    }
+
+    fn check_shields(
+        g1: &Option<PinGroup<'d, T, G1>>,
+        g2: &Option<PinGroup<'d, T, G2>>,
+        g3: &Option<PinGroup<'d, T, G3>>,
+        g4: &Option<PinGroup<'d, T, G4>>,
+        g5: &Option<PinGroup<'d, T, G5>>,
+        g6: &Option<PinGroup<'d, T, G6>>,
+        #[cfg(any(tsc_v2, tsc_v3))] g7: &Option<PinGroup<'d, T, G7>>,
+        #[cfg(tsc_v3)] g8: &Option<PinGroup<'d, T, G8>>,
+    ) -> Result<(), GroupError> {
+        let mut shield_count = 0;
+
+        if let Some(pin_group) = g1 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        if let Some(pin_group) = g2 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        if let Some(pin_group) = g3 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        if let Some(pin_group) = g4 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        if let Some(pin_group) = g5 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        if let Some(pin_group) = g6 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        #[cfg(any(tsc_v2, tsc_v3))]
+        if let Some(pin_group) = g7 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+        #[cfg(tsc_v3)]
+        if let Some(pin_group) = g8 {
+            if pin_group.contains_shield() {
+                shield_count += 1;
+            }
+        };
+
+        if shield_count > 1 {
+            return Err(GroupError::ChannelShield);
+        }
+
+        Ok(())
+    }
+
+    fn extract_groups(io_mask: u32) -> u32 {
+        let mut groups: u32 = 0;
+        for idx in 0..TSC_NUM_GROUPS {
+            if io_mask & (0x0F << idx * 4) != 0 {
+                groups |= 1 << idx
+            }
+        }
+        groups
+    }
+
+    fn new_inner(
+        peri: impl Peripheral<P = T> + 'd,
+        g1: Option<PinGroup<'d, T, G1>>,
+        g2: Option<PinGroup<'d, T, G2>>,
+        g3: Option<PinGroup<'d, T, G3>>,
+        g4: Option<PinGroup<'d, T, G4>>,
+        g5: Option<PinGroup<'d, T, G5>>,
+        g6: Option<PinGroup<'d, T, G6>>,
+        #[cfg(any(tsc_v2, tsc_v3))] g7: Option<PinGroup<'d, T, G7>>,
+        #[cfg(tsc_v3)] g8: Option<PinGroup<'d, T, G8>>,
+        config: Config,
+    ) -> Self {
+        into_ref!(peri);
+
+        T::enable_and_reset();
+
+        T::REGS.cr().modify(|w| {
+            w.set_tsce(true);
+            w.set_ctph(config.ct_pulse_high_length.into());
+            w.set_ctpl(config.ct_pulse_low_length.into());
+            w.set_sse(config.spread_spectrum);
+            // Prevent invalid configuration for pulse generator prescaler
+            if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1
+                && (config.pulse_generator_prescaler == PGPrescalerDivider::_1
+                    || config.pulse_generator_prescaler == PGPrescalerDivider::_2)
+            {
+                w.set_pgpsc(PGPrescalerDivider::_4.into());
+            } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2
+                && config.pulse_generator_prescaler == PGPrescalerDivider::_1
+            {
+                w.set_pgpsc(PGPrescalerDivider::_2.into());
+            } else {
+                w.set_pgpsc(config.pulse_generator_prescaler.into());
+            }
+            w.set_ssd(config.spread_spectrum_deviation.into());
+            w.set_sspsc(config.spread_spectrum_prescaler);
+
+            w.set_mcv(config.max_count_value.into());
+            w.set_syncpol(config.synchro_pin_polarity);
+            w.set_am(config.acquisition_mode);
+        });
+
+        // Set IO configuration
+        // Disable Schmitt trigger hysteresis on all used TSC IOs
+        T::REGS
+            .iohcr()
+            .write(|w| w.0 = !(config.channel_ios | config.shield_ios | config.sampling_ios));
+
+        // Set channel and shield IOs
+        T::REGS.ioccr().write(|w| w.0 = config.channel_ios | config.shield_ios);
+
+        // Set sampling IOs
+        T::REGS.ioscr().write(|w| w.0 = config.sampling_ios);
+
+        // Set the groups to be acquired
+        T::REGS
+            .iogcsr()
+            .write(|w| w.0 = Self::extract_groups(config.channel_ios));
+
+        // Disable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(false);
+            w.set_mceie(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        Self {
+            _peri: peri,
+            _g1: g1,
+            _g2: g2,
+            _g3: g3,
+            _g4: g4,
+            _g5: g5,
+            _g6: g6,
+            #[cfg(any(tsc_v2, tsc_v3))]
+            _g7: g7,
+            #[cfg(tsc_v3)]
+            _g8: g8,
+            state: State::Ready,
+            config,
+        }
+    }
+
+    /// Start charge transfer acquisition
+    pub fn start(&mut self) {
+        self.state = State::Busy;
+
+        // Disable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(false);
+            w.set_mceie(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        // Set the touch sensing IOs not acquired to the default mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(self.config.io_default_mode);
+        });
+
+        // Start the acquisition
+        T::REGS.cr().modify(|w| {
+            w.set_start(true);
+        });
+    }
+
+    /// Start charge transfer acquisition with interrupts enabled
+    pub fn start_it(&mut self) {
+        self.state = State::Busy;
+
+        // Enable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(true);
+            w.set_mceie(self.config.max_count_interrupt);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        // Set the touch sensing IOs not acquired to the default mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(self.config.io_default_mode);
+        });
+
+        // Start the acquisition
+        T::REGS.cr().modify(|w| {
+            w.set_start(true);
+        });
+    }
+
+    /// Stop charge transfer acquisition
+    pub fn stop(&mut self) {
+        T::REGS.cr().modify(|w| {
+            w.set_start(false);
+        });
+
+        // Set the touch sensing IOs in low power mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        self.state = State::Ready;
+    }
+
+    /// Stop charge transfer acquisition and clear interrupts
+    pub fn stop_it(&mut self) {
+        T::REGS.cr().modify(|w| {
+            w.set_start(false);
+        });
+
+        // Set the touch sensing IOs in low power mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(false);
+        });
+
+        // Disable interrupts
+        T::REGS.ier().modify(|w| {
+            w.set_eoaie(false);
+            w.set_mceie(false);
+        });
+
+        // Clear flags
+        T::REGS.icr().modify(|w| {
+            w.set_eoaic(true);
+            w.set_mceic(true);
+        });
+
+        self.state = State::Ready;
+    }
+
+    /// Wait for end of acquisition
+    pub fn poll_for_acquisition(&mut self) {
+        while self.get_state() == State::Busy {}
+    }
+
+    /// Get current state of acquisition
+    pub fn get_state(&mut self) -> State {
+        if self.state == State::Busy {
+            if T::REGS.isr().read().eoaf() {
+                if T::REGS.isr().read().mcef() {
+                    self.state = State::Error
+                } else {
+                    self.state = State::Ready
+                }
+            }
+        }
+        self.state
+    }
+
+    /// Get the individual group status to check acquisition complete
+    pub fn group_get_status(&mut self, index: Group) -> GroupStatus {
+        // Status bits are set by hardware when the acquisition on the corresponding
+        // enabled analog IO group is complete, cleared when new acquisition is started
+        let status = match index {
+            Group::One => T::REGS.iogcsr().read().g1s(),
+            Group::Two => T::REGS.iogcsr().read().g2s(),
+            Group::Three => T::REGS.iogcsr().read().g3s(),
+            Group::Four => T::REGS.iogcsr().read().g4s(),
+            Group::Five => T::REGS.iogcsr().read().g5s(),
+            Group::Six => T::REGS.iogcsr().read().g6s(),
+            #[cfg(any(tsc_v2, tsc_v3))]
+            Group::Seven => T::REGS.iogcsr().read().g7s(),
+            #[cfg(tsc_v3)]
+            Group::Eight => T::REGS.iogcsr().read().g8s(),
+        };
+        match status {
+            true => GroupStatus::Complete,
+            false => GroupStatus::Ongoing,
+        }
+    }
+
+    /// Get the count for the acquisiton, valid once group status is set
+    pub fn group_get_value(&mut self, index: Group) -> u16 {
+        T::REGS.iogcr(index.into()).read().cnt()
+    }
+
+    /// Discharge the IOs for subsequent acquisition
+    pub fn discharge_io(&mut self, status: bool) {
+        // Set the touch sensing IOs in low power mode
+        T::REGS.cr().modify(|w| {
+            w.set_iodef(!status);
+        });
+    }
+}
+
+impl<'d, T: Instance> Drop for Tsc<'d, T> {
+    fn drop(&mut self) {
+        T::disable();
+    }
+}
+
+pub(crate) trait SealedInstance {
+    const REGS: Regs;
+}
+
+/// TSC instance trait
+#[allow(private_bounds)]
+pub trait Instance: Peripheral<P = Self> + SealedInstance + RccPeripheral {}
+
+foreach_peripheral!(
+    (tsc, $inst:ident) => {
+        impl SealedInstance for peripherals::$inst {
+            const REGS: Regs = crate::pac::$inst;
+        }
+
+        impl Instance for peripherals::$inst {}
+    };
+);
+
+pin_trait!(G1IO1Pin, Instance);
+pin_trait!(G1IO2Pin, Instance);
+pin_trait!(G1IO3Pin, Instance);
+pin_trait!(G1IO4Pin, Instance);
+pin_trait!(G2IO1Pin, Instance);
+pin_trait!(G2IO2Pin, Instance);
+pin_trait!(G2IO3Pin, Instance);
+pin_trait!(G2IO4Pin, Instance);
+pin_trait!(G3IO1Pin, Instance);
+pin_trait!(G3IO2Pin, Instance);
+pin_trait!(G3IO3Pin, Instance);
+pin_trait!(G3IO4Pin, Instance);
+pin_trait!(G4IO1Pin, Instance);
+pin_trait!(G4IO2Pin, Instance);
+pin_trait!(G4IO3Pin, Instance);
+pin_trait!(G4IO4Pin, Instance);
+pin_trait!(G5IO1Pin, Instance);
+pin_trait!(G5IO2Pin, Instance);
+pin_trait!(G5IO3Pin, Instance);
+pin_trait!(G5IO4Pin, Instance);
+pin_trait!(G6IO1Pin, Instance);
+pin_trait!(G6IO2Pin, Instance);
+pin_trait!(G6IO3Pin, Instance);
+pin_trait!(G6IO4Pin, Instance);
+pin_trait!(G7IO1Pin, Instance);
+pin_trait!(G7IO2Pin, Instance);
+pin_trait!(G7IO3Pin, Instance);
+pin_trait!(G7IO4Pin, Instance);
+pin_trait!(G8IO1Pin, Instance);
+pin_trait!(G8IO2Pin, Instance);
+pin_trait!(G8IO3Pin, Instance);
+pin_trait!(G8IO4Pin, Instance);