Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 42 additions & 23 deletions esp-hal/src/i2c/master/low_level/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::soc::clocks::{ClockTree, I2cFunctionClockConfig};

#[cfg_attr(i2c_master_version = "1", path = "v1.rs")]
#[cfg_attr(i2c_master_version = "2", path = "v2.rs")]
Expand Down Expand Up @@ -172,10 +173,11 @@ fn set_filter(

#[expect(clippy::too_many_arguments)]
#[allow(unused)]
/// Configures the clock and timing parameters for the I2C peripheral.
/// Configures the timing parameters for the I2C peripheral.
///
/// Clock source selection is handled separately via the clock tree.
fn configure_clock(
info: &Info,
sclk_div: u32,
scl_low_period: u32,
scl_high_period: u32,
scl_wait_high_period: u32,
Expand All @@ -188,27 +190,6 @@ fn configure_clock(
timeout: Option<u32>,
) -> Result<(), ConfigError> {
unsafe {
cfg_if::cfg_if! {
if #[cfg(all(soc_has_pcr, soc_has_i2c1))] {
crate::peripherals::PCR::regs().i2c_sclk_conf(info.id as usize).modify(|_, w| {
w.i2c_sclk_sel().clear_bit();
w.i2c_sclk_div_num().bits((sclk_div - 1) as u8);
w.i2c_sclk_en().set_bit()
});
} else if #[cfg(soc_has_pcr)] {
crate::peripherals::PCR::regs().i2c_sclk_conf().modify(|_, w| {
w.i2c_sclk_sel().clear_bit();
w.i2c_sclk_div_num().bits((sclk_div - 1) as u8);
w.i2c_sclk_en().set_bit()
});
} else if #[cfg(i2c_master_version = "3")] {
info.regs().clk_conf().modify(|_, w| {
w.sclk_sel().clear_bit();
w.sclk_div_num().bits((sclk_div - 1) as u8)
});
}
}

// scl period
info.regs()
.scl_low_period()
Expand Down Expand Up @@ -297,6 +278,9 @@ pub struct Info {

/// SDA input signal.
pub sda_input: InputSignal,

/// I2C clock group instance.
pub clock_instance: crate::soc::clocks::I2cInstance,
}

impl Info {
Expand Down Expand Up @@ -367,6 +351,40 @@ impl PartialEq for Info {

unsafe impl Sync for Info {}

#[derive(Debug)]
pub(super) struct I2cClockGuard<'t> {
i2c: AnyI2c<'t>,
}

impl<'t> I2cClockGuard<'t> {
pub(super) fn new(i2c: AnyI2c<'t>) -> Self {
ClockTree::with(|clocks| {
let clock = i2c.info().clock_instance;
cfg_if::cfg_if! {
if #[cfg(i2c_master_version = "3")] {
let config = I2cFunctionClockConfig::new(Default::default(), 0);
} else {
let config = I2cFunctionClockConfig::new(Default::default());
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do crazy stuff like:

Suggested change
cfg_if::cfg_if! {
if #[cfg(i2c_master_version = "3")] {
let config = I2cFunctionClockConfig::new(Default::default(), 0);
} else {
let config = I2cFunctionClockConfig::new(Default::default());
}
}
let config = I2cFunctionClockConfig::new(Default::default(), #[cfg(i2c_master_version = "3")] 0);

clock.configure_function_clock(clocks, config);
clock.request_function_clock(clocks);
});
Self { i2c }
}
}

impl Drop for I2cClockGuard<'_> {
fn drop(&mut self) {
ClockTree::with(|clocks| {
self.i2c
.info()
.clock_instance
.release_function_clock(clocks);
});
}
}

#[derive(Clone, Copy)]
enum Deadline {
None,
Expand Down Expand Up @@ -1843,6 +1861,7 @@ for_each_i2c_master!(
scl_input: InputSignal::$scl,
sda_output: OutputSignal::$sda,
sda_input: InputSignal::$sda,
clock_instance: paste::paste! { crate::soc::clocks::I2cInstance::[<I2c $id>] },
};
(&PERIPHERAL, &STATE)
}
Expand Down
13 changes: 9 additions & 4 deletions esp-hal/src/i2c/master/low_level/v1.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use super::{Config, ConfigError, Driver, RegisterBlock, configure_clock};
use crate::soc::clocks::{ClockTree, I2cFunctionClockConfig};

/// Sets the frequency of the I2C interface by calculating and applying the
/// associated timings - corresponds to i2c_ll_cal_bus_clk and
/// i2c_ll_set_bus_timing in ESP-IDF
pub(super) fn set_frequency(driver: &Driver<'_>, clock_config: &Config) -> Result<(), ConfigError> {
let timeout = clock_config.timeout;

let source_clk = crate::soc::clocks::apb_clk_frequency();

let bus_freq = clock_config.frequency.as_hz();
let sclk = clock_config.clock_source;
let clock = driver.info.clock_instance;

// ESP32 I2C is hardwired to APB; configure_function_clock is a no-op.
let source_clk = ClockTree::with(|clocks| {
clock.configure_function_clock(clocks, I2cFunctionClockConfig::new(sclk));
clock.function_clock_config_frequency(clocks, I2cFunctionClockConfig::new(sclk))
});

let half_cycle: u32 = source_clk / bus_freq / 2;
let scl_low = half_cycle;
Expand Down Expand Up @@ -59,7 +65,6 @@ pub(super) fn set_frequency(driver: &Driver<'_>, clock_config: &Config) -> Resul

configure_clock(
driver.info,
0,
scl_low_period,
scl_high_period,
0,
Expand Down
89 changes: 46 additions & 43 deletions esp-hal/src/i2c/master/low_level/v2.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,61 @@
use super::{Config, ConfigError, Driver, RegisterBlock, configure_clock};
use crate::soc::clocks::{ClockTree, I2cFunctionClockConfig};

/// Sets the frequency of the I2C interface by calculating and applying the
/// associated timings - corresponds to i2c_ll_cal_bus_clk and
/// i2c_ll_set_bus_timing in ESP-IDF
pub(super) fn set_frequency(driver: &Driver<'_>, clock_config: &Config) -> Result<(), ConfigError> {
let timeout = clock_config.timeout;

// TODO: could be REF_TICK
let source_clk = crate::soc::clocks::apb_clk_frequency();

let bus_freq = clock_config.frequency.as_hz();
let sclk = clock_config.clock_source;
let clock = driver.info.clock_instance;

ClockTree::with(|clocks| -> Result<(), ConfigError> {
let source_clk =
clock.function_clock_config_frequency(clocks, I2cFunctionClockConfig::new(sclk));

let half_cycle: u32 = source_clk / bus_freq / 2;
// SCL
let scl_low = half_cycle;
// default, scl_wait_high < scl_high
let scl_high = half_cycle / 2 + 2;
let scl_wait_high = half_cycle - scl_high;
let sda_hold = half_cycle / 2;
// scl_wait_high < sda_sample <= scl_high
let sda_sample = half_cycle / 2 - 1;
let setup = half_cycle;
let hold = half_cycle;
let half_cycle: u32 = source_clk / bus_freq / 2;
// SCL
let scl_low = half_cycle;
// default, scl_wait_high < scl_high
let scl_high = half_cycle / 2 + 2;
let scl_wait_high = half_cycle - scl_high;
let sda_hold = half_cycle / 2;
// scl_wait_high < sda_sample <= scl_high
let sda_sample = half_cycle / 2 - 1;
let setup = half_cycle;
let hold = half_cycle;

// scl period
let scl_low_period = scl_low - 1;
let scl_high_period = scl_high;
let scl_wait_high_period = scl_wait_high;
// sda sample
let sda_hold_time = sda_hold;
let sda_sample_time = sda_sample;
// setup
let scl_rstart_setup_time = setup;
let scl_stop_setup_time = setup;
// hold
let scl_start_hold_time = hold - 1;
let scl_stop_hold_time = hold;
// scl period
let scl_low_period = scl_low - 1;
let scl_high_period = scl_high;
let scl_wait_high_period = scl_wait_high;
// sda sample
let sda_hold_time = sda_hold;
let sda_sample_time = sda_sample;
// setup
let scl_rstart_setup_time = setup;
let scl_stop_setup_time = setup;
// hold
let scl_start_hold_time = hold - 1;
let scl_stop_hold_time = hold;

configure_clock(
driver.info,
0,
scl_low_period,
scl_high_period,
scl_wait_high_period,
sda_hold_time,
sda_sample_time,
scl_rstart_setup_time,
scl_stop_setup_time,
scl_start_hold_time,
scl_stop_hold_time,
timeout.apb_cycles(half_cycle)?,
)?;
clock.configure_function_clock(clocks, I2cFunctionClockConfig::new(sclk));

Ok(())
configure_clock(
driver.info,
scl_low_period,
scl_high_period,
scl_wait_high_period,
sda_hold_time,
sda_sample_time,
scl_rstart_setup_time,
scl_stop_setup_time,
scl_start_hold_time,
scl_stop_hold_time,
timeout.apb_cycles(half_cycle)?,
)
})
}

/// Resets the transmit and receive FIFO buffers.
Expand Down
116 changes: 60 additions & 56 deletions esp-hal/src/i2c/master/low_level/v3.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,75 @@
use super::{Config, ConfigError, Driver, RegisterBlock, configure_clock};
use crate::soc::clocks::{ClockTree, I2cFunctionClockConfig};

/// Sets the frequency of the I2C interface by calculating and applying the
/// associated timings - corresponds to i2c_ll_cal_bus_clk and
/// i2c_ll_set_bus_timing in ESP-IDF
pub(super) fn set_frequency(driver: &Driver<'_>, clock_config: &Config) -> Result<(), ConfigError> {
let timeout = clock_config.timeout;

let source_clk = crate::soc::clocks::xtal_clk_frequency();

let bus_freq = clock_config.frequency.as_hz();
let sclk = clock_config.clock_source;
let clock = driver.info.clock_instance;

let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1;
let sclk_freq: u32 = source_clk / clkm_div;
let half_cycle: u32 = sclk_freq / bus_freq / 2;
// SCL
let scl_low = half_cycle;
// default, scl_wait_high < scl_high
// Make 80KHz as a boundary here, because when working at lower frequency, too
// much scl_wait_high will faster the frequency according to some
// hardware behaviors.
let scl_wait_high = if bus_freq >= 80 * 1000 {
half_cycle / 2 - 2
} else {
half_cycle / 4
};
let scl_high = half_cycle - scl_wait_high;
let sda_hold = half_cycle / 4;
let sda_sample = half_cycle / 2;
let setup = half_cycle;
let hold = half_cycle;
ClockTree::with(|clocks| -> Result<(), ConfigError> {
let source_clk =
clock.function_clock_config_frequency(clocks, I2cFunctionClockConfig::new(sclk, 0));

let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1;
let sclk_freq: u32 = source_clk / clkm_div;
let half_cycle: u32 = sclk_freq / bus_freq / 2;
// SCL
let scl_low = half_cycle;
// default, scl_wait_high < scl_high
// Make 80KHz as a boundary here, because when working at lower frequency, too
// much scl_wait_high will faster the frequency according to some
// hardware behaviors.
let scl_wait_high = if bus_freq >= 80 * 1000 {
half_cycle / 2 - 2
} else {
half_cycle / 4
};
let scl_high = half_cycle - scl_wait_high;
let sda_hold = half_cycle / 4;
let sda_sample = half_cycle / 2;
let setup = half_cycle;
let hold = half_cycle;

// According to the Technical Reference Manual, the following timings must be
// subtracted by 1. However, according to the practical measurement and
// some hardware behaviour, if wait_high_period and scl_high minus one.
// The SCL frequency would be a little higher than expected. Therefore, the
// solution here is not to minus scl_high as well as scl_wait high, and
// the frequency will be absolutely accurate to all frequency
// to some extent.
let scl_low_period = scl_low - 1;
let scl_high_period = scl_high;
let scl_wait_high_period = scl_wait_high;
// sda sample
let sda_hold_time = sda_hold - 1;
let sda_sample_time = sda_sample - 1;
// setup
let scl_rstart_setup_time = setup - 1;
let scl_stop_setup_time = setup - 1;
// hold
let scl_start_hold_time = hold - 1;
let scl_stop_hold_time = hold - 1;
// According to the Technical Reference Manual, the following timings must be
// subtracted by 1. However, according to the practical measurement and
// some hardware behaviour, if wait_high_period and scl_high minus one.
// The SCL frequency would be a little higher than expected. Therefore, the
// solution here is not to minus scl_high as well as scl_wait high, and
// the frequency will be absolutely accurate to all frequency
// to some extent.
let scl_low_period = scl_low - 1;
let scl_high_period = scl_high;
let scl_wait_high_period = scl_wait_high;
// sda sample
let sda_hold_time = sda_hold - 1;
let sda_sample_time = sda_sample - 1;
// setup
let scl_rstart_setup_time = setup - 1;
let scl_stop_setup_time = setup - 1;
// hold
let scl_start_hold_time = hold - 1;
let scl_stop_hold_time = hold - 1;

configure_clock(
driver.info,
clkm_div,
scl_low_period,
scl_high_period,
scl_wait_high_period,
sda_hold_time,
sda_sample_time,
scl_rstart_setup_time,
scl_stop_setup_time,
scl_start_hold_time,
scl_stop_hold_time,
timeout.apb_cycles(half_cycle)?,
)?;
clock.configure_function_clock(clocks, I2cFunctionClockConfig::new(sclk, clkm_div - 1));

Ok(())
configure_clock(
driver.info,
scl_low_period,
scl_high_period,
scl_wait_high_period,
sda_hold_time,
sda_sample_time,
scl_rstart_setup_time,
scl_stop_setup_time,
scl_start_hold_time,
scl_stop_hold_time,
timeout.apb_cycles(half_cycle)?,
)
})
}

/// Resets the transmit and receive FIFO buffers.
Expand Down
Loading
Loading