esp_hal/psram/
esp32s2.rs

1use super::PsramSize;
2use crate::peripherals::{EXTMEM, SPI0, SPI1};
3
4const EXTMEM_ORIGIN: usize = 0x3f500000;
5
6// Cache Speed
7#[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
8#[cfg_attr(feature = "defmt", derive(defmt::Format))]
9#[allow(missing_docs)]
10pub enum PsramCacheSpeed {
11    PsramCacheS80m = 1,
12    PsramCacheS40m,
13    PsramCacheS26m,
14    PsramCacheS20m,
15    #[default]
16    PsramCacheMax,
17}
18
19/// PSRAM configuration
20#[derive(Copy, Clone, Debug, Default)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22pub struct PsramConfig {
23    /// PSRAM size
24    pub size: PsramSize,
25    /// Cache Speed
26    pub speed: PsramCacheSpeed,
27}
28
29/// Initialize PSRAM to be used for data.
30///
31/// Returns the start of the mapped memory and the size
32#[procmacros::ram]
33pub(crate) fn init_psram(config: PsramConfig) {
34    let mut config = config;
35    utils::psram_init(&mut config);
36
37    #[allow(unused)]
38    enum CacheLayout {
39        Invalid    = 0,
40        ICacheLow  = 1 << 0,
41        ICacheHigh = 1 << 1,
42        DCacheLow  = 1 << 2,
43        DCacheHigh = 1 << 3,
44    }
45
46    const MMU_ACCESS_SPIRAM: u32 = 1 << 16;
47
48    const CACHE_SIZE_8KB: u32 = 0;
49    const CACHE_4WAYS_ASSOC: u32 = 0;
50    const CACHE_LINE_SIZE_16B: u32 = 0;
51
52    unsafe extern "C" {
53        /// Allocate memory to used by ICache and DCache.
54        ///
55        /// [`sram0_layout`]: u32 the usage of first 8KB internal memory block,
56        /// can be CACHE_MEMORY_INVALID,
57        /// CACHE_MEMORY_ICACHE_LOW,
58        /// CACHE_MEMORY_ICACHE_HIGH, CACHE_MEMORY_DCACHE_LOW and
59        /// CACHE_MEMORY_DCACHE_HIGH
60        /// [`sram1_layout`]: the usage of second 8KB internal memory block,
61        /// [`sram2_layout`]: the usage of third 8KB internal memory block
62        /// [`sram3_layout`]: the usage of forth 8KB internal memory block
63        fn Cache_Allocate_SRAM(
64            sram0_layout: u32,
65            sram1_layout: u32,
66            sram2_layout: u32,
67            sram3_layout: u32,
68        );
69
70        /// Set DCache mmu mapping.
71        ///
72        /// [`ext_ram`]: u32 DPORT_MMU_ACCESS_FLASH for flash, DPORT_MMU_ACCESS_SPIRAM for spiram, DPORT_MMU_INVALID for invalid.
73        /// [`vaddr`]: u32 Virtual address in CPU address space.
74        /// [`paddr`]: u32 Physical address in external memory. Should be aligned by psize.
75        /// [`psize`]: u32 Page size of DCache, in kilobytes. Should be 64 here.
76        /// [`num`]: u32 Pages to be set.
77        /// [`fixes`]: u32 0 for physical pages grow with virtual pages, other for virtual pages map to same physical page.
78        fn cache_dbus_mmu_set(
79            ext_ram: u32,
80            vaddr: u32,
81            paddr: u32,
82            psize: u32,
83            num: u32,
84            fixed: u32,
85        ) -> i32;
86
87        /// Set DCache modes: cache size, associate ways and cache line size.
88        ///
89        /// [`cache_size`]: u32 the cache size, can be CACHE_SIZE_HALF and CACHE_SIZE_FULL
90        /// [`ways`]: u32 the associate ways of cache, can only be CACHE_4WAYS_ASSOC
91        /// [`cache_line_size`]: u32 the cache line size, can be CACHE_LINE_SIZE_16B, CACHE_LINE_SIZE_32B
92        fn Cache_Set_DCache_Mode(cache_size: u32, ways: u32, cache_line_size: u32);
93
94        /// Invalidate all cache items in DCache.
95        fn Cache_Invalidate_DCache_All();
96    }
97
98    unsafe {
99        Cache_Allocate_SRAM(
100            CacheLayout::ICacheLow as u32,
101            CacheLayout::DCacheLow as u32,
102            CacheLayout::Invalid as u32,
103            CacheLayout::Invalid as u32,
104        );
105        Cache_Set_DCache_Mode(CACHE_SIZE_8KB, CACHE_4WAYS_ASSOC, CACHE_LINE_SIZE_16B);
106        Cache_Invalidate_DCache_All();
107
108        const START_PAGE: u32 = 0;
109
110        if cache_dbus_mmu_set(
111            MMU_ACCESS_SPIRAM,
112            EXTMEM_ORIGIN as u32,
113            START_PAGE << 16,
114            64,
115            config.size.get() as u32 / 1024 / 64, // number of pages to map
116            0,
117        ) != 0
118        {
119            panic!("cache_dbus_mmu_set failed");
120        }
121
122        EXTMEM::regs().pro_dcache_ctrl1().modify(|_, w| {
123            w.pro_dcache_mask_bus0().clear_bit();
124            w.pro_dcache_mask_bus1().clear_bit();
125            w.pro_dcache_mask_bus2().clear_bit()
126        });
127    }
128
129    unsafe {
130        super::MAPPED_PSRAM.memory_range = EXTMEM_ORIGIN..EXTMEM_ORIGIN + config.size.get();
131    }
132}
133
134pub(crate) mod utils {
135    use super::*;
136
137    const PSRAM_RESET_EN: u16 = 0x66;
138    const PSRAM_RESET: u16 = 0x99;
139    const PSRAM_DEVICE_ID: u16 = 0x9F;
140    const CS_PSRAM_SEL: u8 = 1 << 1;
141
142    /// PS-RAM addressing mode
143    #[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
144    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
145    #[allow(unused)]
146    pub enum PsramVaddrMode {
147        /// App and pro CPU use their own flash cache for external RAM access
148        #[default]
149        Normal = 0,
150        /// App and pro CPU share external RAM caches: pro CPU has low2M, app
151        /// CPU has high 2M
152        Lowhigh,
153        /// App and pro CPU share external RAM caches: pro CPU does even 32yte
154        /// ranges, app does odd ones.
155        Evenodd,
156    }
157
158    // Function initializes the PSRAM by configuring GPIO pins, resetting the PSRAM,
159    // and enabling Quad I/O (QIO) mode. It also calls the psram_cache_init
160    // function to configure cache parameters and read/write commands.
161    pub(crate) fn psram_init(config: &mut PsramConfig) {
162        psram_gpio_config();
163
164        if config.size.is_auto() {
165            psram_disable_qio_mode();
166
167            // read chip id
168            let mut dev_id = 0u32;
169            psram_exec_cmd(
170                CommandMode::PsramCmdSpi,
171                PSRAM_DEVICE_ID,
172                8, // command and command bit len
173                0,
174                24, // address and address bit len
175                0,  // dummy bit len
176                core::ptr::null(),
177                0, // tx data and tx bit len
178                &mut dev_id as *mut _ as *mut u8,
179                24,           // rx data and rx bit len
180                CS_PSRAM_SEL, // cs bit mask
181                false,
182            );
183            info!("chip id = {:x}", dev_id);
184
185            let size = if dev_id != 0xffffff {
186                const PSRAM_ID_EID_S: u32 = 16;
187                const PSRAM_ID_EID_M: u32 = 0xff;
188                const PSRAM_EID_SIZE_M: u32 = 0x07;
189                const PSRAM_EID_SIZE_S: u32 = 5;
190
191                let size_id = (((dev_id >> PSRAM_ID_EID_S) & PSRAM_ID_EID_M) >> PSRAM_EID_SIZE_S)
192                    & PSRAM_EID_SIZE_M;
193
194                const PSRAM_EID_SIZE_32MBITS: u32 = 1;
195                const PSRAM_EID_SIZE_64MBITS: u32 = 2;
196
197                match size_id {
198                    PSRAM_EID_SIZE_64MBITS => 8 * 1024 * 1024,
199                    PSRAM_EID_SIZE_32MBITS => 4 * 1024 * 1024,
200                    _ => 2 * 1024 * 1024,
201                }
202            } else {
203                0
204            };
205
206            info!("size is {}", size);
207
208            config.size = PsramSize::Size(size);
209        }
210
211        psram_reset_mode();
212        psram_enable_qio_mode();
213
214        psram_cache_init(config.speed, PsramVaddrMode::Normal);
215    }
216
217    // send reset command to psram, in spi mode
218    fn psram_reset_mode() {
219        psram_exec_cmd(
220            CommandMode::PsramCmdSpi,
221            PSRAM_RESET_EN,
222            8, // command and command bit len
223            0,
224            0, // address and address bit len
225            0, // dummy bit len
226            core::ptr::null(),
227            0, // tx data and tx bit len
228            core::ptr::null_mut(),
229            0,            // rx data and rx bit len
230            CS_PSRAM_SEL, // cs bit mask
231            false,
232        ); // whether is program/erase operation
233
234        psram_exec_cmd(
235            CommandMode::PsramCmdSpi,
236            PSRAM_RESET,
237            8, // command and command bit len
238            0,
239            0, // address and address bit len
240            0, // dummy bit len
241            core::ptr::null(),
242            0, // tx data and tx bit len
243            core::ptr::null_mut(),
244            0,            // rx data and rx bit len
245            CS_PSRAM_SEL, // cs bit mask
246            false,
247        ); // whether is program/erase operation
248    }
249
250    /// Enter QPI mode
251    fn psram_enable_qio_mode() {
252        const PSRAM_ENTER_QMODE: u16 = 0x35;
253        const CS_PSRAM_SEL: u8 = 1 << 1;
254
255        psram_exec_cmd(
256            CommandMode::PsramCmdSpi,
257            PSRAM_ENTER_QMODE,
258            8, // command and command bit len
259            0,
260            0, // address and address bit len
261            0, // dummy bit len
262            core::ptr::null(),
263            0, // tx data and tx bit len
264            core::ptr::null_mut(),
265            0,            // rx data and rx bit len
266            CS_PSRAM_SEL, // cs bit mask
267            false,        // whether is program/erase operation
268        );
269    }
270
271    /// Exit QPI mode
272    fn psram_disable_qio_mode() {
273        const PSRAM_EXIT_QMODE: u16 = 0xF5;
274        const CS_PSRAM_SEL: u8 = 1 << 1;
275
276        psram_exec_cmd(
277            CommandMode::PsramCmdQpi,
278            PSRAM_EXIT_QMODE,
279            8, // command and command bit len
280            0,
281            0, // address and address bit len
282            0, // dummy bit len
283            core::ptr::null(),
284            0, // tx data and tx bit len
285            core::ptr::null_mut(),
286            0,            // rx data and rx bit len
287            CS_PSRAM_SEL, // cs bit mask
288            false,        // whether is program/erase operation
289        );
290    }
291
292    #[derive(PartialEq)]
293    #[allow(unused)]
294    enum CommandMode {
295        PsramCmdQpi = 0,
296        PsramCmdSpi = 1,
297    }
298
299    #[expect(clippy::too_many_arguments)]
300    #[inline(always)]
301    fn psram_exec_cmd(
302        mode: CommandMode,
303        cmd: u16,
304        cmd_bit_len: u16,
305        addr: u32,
306        addr_bit_len: u32,
307        dummy_bits: u32,
308        mosi_data: *const u8,
309        mosi_bit_len: u32,
310        miso_data: *mut u8,
311        miso_bit_len: u32,
312        cs_mask: u8,
313        is_write_erase_operation: bool,
314    ) {
315        unsafe extern "C" {
316            ///  Start a spi user command sequence
317            ///  [`spi_num`] spi port
318            ///  [`rx_buf`] buffer pointer to receive data
319            ///  [`rx_len`] receive data length in byte
320            ///  [`cs_en_mask`] decide which cs to use, 0 for cs0, 1 for cs1
321            ///  [`is_write_erase`] to indicate whether this is a write or erase
322            /// operation, since the CPU would check permission
323            fn esp_rom_spi_cmd_start(
324                spi_num: u32,
325                rx_buf: *const u8,
326                rx_len: u16,
327                cs_en_mask: u8,
328                is_write_erase: bool,
329            );
330        }
331
332        unsafe {
333            let spi1 = SPI1::regs();
334            let backup_usr = spi1.user().read().bits();
335            let backup_usr1 = spi1.user1().read().bits();
336            let backup_usr2 = spi1.user2().read().bits();
337            let backup_ctrl = spi1.ctrl().read().bits();
338            psram_set_op_mode(mode);
339            _psram_exec_cmd(
340                cmd,
341                cmd_bit_len,
342                &addr,
343                addr_bit_len,
344                dummy_bits,
345                mosi_data,
346                mosi_bit_len,
347                miso_data,
348                miso_bit_len,
349            );
350            esp_rom_spi_cmd_start(
351                1,
352                miso_data,
353                (miso_bit_len / 8) as u16,
354                cs_mask,
355                is_write_erase_operation,
356            );
357
358            spi1.user().write(|w| w.bits(backup_usr));
359            spi1.user1().write(|w| w.bits(backup_usr1));
360            spi1.user2().write(|w| w.bits(backup_usr2));
361            spi1.ctrl().write(|w| w.bits(backup_ctrl));
362        }
363    }
364
365    #[expect(clippy::too_many_arguments)]
366    #[inline(always)]
367    fn _psram_exec_cmd(
368        cmd: u16,
369        cmd_bit_len: u16,
370        addr: *const u32,
371        addr_bit_len: u32,
372        dummy_bits: u32,
373        mosi_data: *const u8,
374        mosi_bit_len: u32,
375        miso_data: *mut u8,
376        miso_bit_len: u32,
377    ) {
378        #[repr(C)]
379        #[allow(non_camel_case_types)]
380        struct esp_rom_spi_cmd_t {
381            cmd: u16,             // Command value
382            cmd_bit_len: u16,     // Command byte length
383            addr: *const u32,     // Point to address value
384            addr_bit_len: u32,    // Address byte length
385            tx_data: *const u32,  // Point to send data buffer
386            tx_data_bit_len: u32, // Send data byte length.
387            rx_data: *mut u32,    // Point to recevie data buffer
388            rx_data_bit_len: u32, // Recevie Data byte length.
389            dummy_bit_len: u32,
390        }
391
392        unsafe extern "C" {
393            /// Config the spi user command
394            /// [`spi_num`] spi port
395            /// [`pcmd`] pointer to accept the spi command struct
396            fn esp_rom_spi_cmd_config(spi_num: u32, pcmd: *const esp_rom_spi_cmd_t);
397        }
398
399        let conf = esp_rom_spi_cmd_t {
400            cmd,
401            cmd_bit_len,
402            addr,
403            addr_bit_len,
404            tx_data: mosi_data as *const u32,
405            tx_data_bit_len: mosi_bit_len,
406            rx_data: miso_data as *mut u32,
407            rx_data_bit_len: miso_bit_len,
408            dummy_bit_len: dummy_bits,
409        };
410
411        unsafe {
412            esp_rom_spi_cmd_config(1, &conf);
413        }
414    }
415
416    fn psram_set_op_mode(mode: CommandMode) {
417        unsafe extern "C" {
418            fn esp_rom_spi_set_op_mode(spi: u32, mode: u32);
419        }
420
421        const ESP_ROM_SPIFLASH_QIO_MODE: u32 = 0;
422        const ESP_ROM_SPIFLASH_SLOWRD_MODE: u32 = 5;
423
424        unsafe {
425            match mode {
426                CommandMode::PsramCmdQpi => {
427                    esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_QIO_MODE);
428                    SPI1::regs().ctrl().modify(|_, w| w.fcmd_quad().set_bit());
429                }
430                CommandMode::PsramCmdSpi => {
431                    esp_rom_spi_set_op_mode(1, ESP_ROM_SPIFLASH_SLOWRD_MODE);
432                }
433            }
434        }
435    }
436
437    #[repr(C)]
438    struct PsRamIo {
439        flash_clk_io: u8,
440        flash_cs_io: u8,
441        psram_clk_io: u8,
442        psram_cs_io: u8,
443        psram_spiq_sd0_io: u8,
444        psram_spid_sd1_io: u8,
445        psram_spiwp_sd3_io: u8,
446        psram_spihd_sd2_io: u8,
447    }
448
449    fn psram_gpio_config() {
450        unsafe extern "C" {
451            fn esp_rom_efuse_get_flash_gpio_info() -> u32;
452
453            /// Enable Quad I/O pin functions
454            ///
455            /// Sets the HD & WP pin functions for Quad I/O modes, based on the
456            /// efuse SPI pin configuration.
457            ///
458            /// [`wp_gpio_num`]: u8 Number of the WP pin to reconfigure for quad I/O
459            /// [`spiconfig`]: u32 Pin configuration, as returned from ets_efuse_get_spiconfig().
460            /// - If this parameter is 0, default SPI pins are used and wp_gpio_num parameter is
461            ///   ignored.
462            /// - If this parameter is 1, default HSPI pins are used and wp_gpio_num parameter is
463            ///   ignored.
464            /// - For other values, this parameter encodes the HD pin number and also the CLK pin
465            ///   number. CLK pin selection is used to determine if HSPI or SPI peripheral will be
466            ///   used (use HSPI if CLK pin is the HSPI clock pin, otherwise use SPI).
467            //   Both HD & WP pins are configured via GPIO matrix to map to the selected peripheral.
468            fn esp_rom_spiflash_select_qio_pins(wp_gpio_num: u8, spiconfig: u32);
469        }
470
471        let psram_io = PsRamIo {
472            flash_clk_io: 30, // SPI_CLK_GPIO_NUM
473            flash_cs_io: 29,  // SPI_CS0_GPIO_NUM
474            psram_clk_io: 30,
475            psram_cs_io: 26,        // SPI_CS1_GPIO_NUM
476            psram_spiq_sd0_io: 31,  // SPI_Q_GPIO_NUM
477            psram_spid_sd1_io: 32,  // SPI_D_GPIO_NUM
478            psram_spiwp_sd3_io: 28, // SPI_WP_GPIO_NUM
479            psram_spihd_sd2_io: 27, // SPI_HD_GPIO_NUM
480        };
481
482        const ESP_ROM_EFUSE_FLASH_DEFAULT_SPI: u32 = 0;
483
484        unsafe {
485            let spiconfig = esp_rom_efuse_get_flash_gpio_info();
486            if spiconfig == ESP_ROM_EFUSE_FLASH_DEFAULT_SPI {
487                // FLASH pins(except wp / hd) are all configured via IO_MUX in
488                // rom.
489            } else {
490                // this case is currently not yet supported
491                panic!(
492                    "Unsupported for now! The case 'FLASH pins are all configured via GPIO matrix in ROM.' is not yet supported."
493                );
494
495                // FLASH pins are all configured via GPIO matrix in ROM.
496                // psram_io.flash_clk_io =
497                // EFUSE_SPICONFIG_RET_SPICLK(spiconfig);
498                // psram_io.flash_cs_io = EFUSE_SPICONFIG_RET_SPICS0(spiconfig);
499                // psram_io.psram_spiq_sd0_io =
500                // EFUSE_SPICONFIG_RET_SPIQ(spiconfig);
501                // psram_io.psram_spid_sd1_io =
502                // EFUSE_SPICONFIG_RET_SPID(spiconfig);
503                // psram_io.psram_spihd_sd2_io =
504                // EFUSE_SPICONFIG_RET_SPIHD(spiconfig);
505                // psram_io.psram_spiwp_sd3_io =
506                // esp_rom_efuse_get_flash_wp_gpio();
507            }
508            esp_rom_spiflash_select_qio_pins(psram_io.psram_spiwp_sd3_io, spiconfig);
509            // s_psram_cs_io = psram_io.psram_cs_io;
510        }
511    }
512
513    const PSRAM_IO_MATRIX_DUMMY_20M: u32 = 0;
514    const PSRAM_IO_MATRIX_DUMMY_40M: u32 = 0;
515    const PSRAM_IO_MATRIX_DUMMY_80M: u32 = 0;
516
517    /// Register initialization for sram cache params and r/w commands
518    fn psram_cache_init(psram_cache_mode: PsramCacheSpeed, _vaddrmode: PsramVaddrMode) {
519        let mut extra_dummy = 0;
520        match psram_cache_mode {
521            PsramCacheSpeed::PsramCacheS80m => {
522                psram_clock_set(1);
523                extra_dummy = PSRAM_IO_MATRIX_DUMMY_80M;
524            }
525            PsramCacheSpeed::PsramCacheS40m => {
526                psram_clock_set(2);
527                extra_dummy = PSRAM_IO_MATRIX_DUMMY_40M;
528            }
529            PsramCacheSpeed::PsramCacheS26m => {
530                psram_clock_set(3);
531                extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M;
532            }
533            PsramCacheSpeed::PsramCacheS20m => {
534                psram_clock_set(4);
535                extra_dummy = PSRAM_IO_MATRIX_DUMMY_20M;
536            }
537            _ => {
538                psram_clock_set(2);
539            }
540        }
541
542        const PSRAM_QUAD_WRITE: u32 = 0x38;
543        const PSRAM_FAST_READ_QUAD: u32 = 0xEB;
544        const PSRAM_FAST_READ_QUAD_DUMMY: u32 = 0x5;
545
546        unsafe {
547            let spi = SPI0::regs();
548
549            spi.cache_sctrl()
550                .modify(|_, w| w.usr_sram_dio().clear_bit()); // disable dio mode for cache command
551
552            spi.cache_sctrl().modify(|_, w| w.usr_sram_qio().set_bit()); // enable qio mode for cache command
553
554            spi.cache_sctrl()
555                .modify(|_, w| w.cache_sram_usr_rcmd().set_bit()); // enable cache read command
556
557            spi.cache_sctrl()
558                .modify(|_, w| w.cache_sram_usr_wcmd().set_bit()); // enable cache write command
559
560            // write address for cache command.
561            spi.cache_sctrl()
562                .modify(|_, w| w.sram_addr_bitlen().bits(23));
563
564            spi.cache_sctrl()
565                .modify(|_, w| w.usr_rd_sram_dummy().set_bit()); // enable cache read dummy
566
567            // config sram cache r/w command
568            spi.sram_dwr_cmd()
569                .modify(|_, w| w.cache_sram_usr_wr_cmd_bitlen().bits(7));
570
571            spi.sram_dwr_cmd().modify(|_, w| {
572                w.cache_sram_usr_wr_cmd_value()
573                    .bits(PSRAM_QUAD_WRITE as u16)
574            });
575
576            spi.sram_drd_cmd()
577                .modify(|_, w| w.cache_sram_usr_rd_cmd_bitlen().bits(7));
578
579            spi.sram_drd_cmd().modify(|_, w| {
580                w.cache_sram_usr_rd_cmd_value()
581                    .bits(PSRAM_FAST_READ_QUAD as u16)
582            });
583
584            // dummy, psram cache :  40m--+1dummy,80m--+2dummy
585            spi.cache_sctrl().modify(|_, w| {
586                w.sram_rdummy_cyclelen()
587                    .bits((PSRAM_FAST_READ_QUAD_DUMMY + extra_dummy) as u8)
588            });
589
590            // ESP-IDF has some code here to deal with `!CONFIG_FREERTOS_UNICORE` - not
591            // needed for ESP32-S2
592
593            // ENABLE SPI0 CS1 TO PSRAM(CS0--FLASH; CS1--SRAM)
594            spi.misc().modify(|_, w| w.cs1_dis().clear_bit());
595        }
596    }
597
598    fn psram_clock_set(freqdiv: i8) {
599        const SPI_MEM_SCLKCNT_N_S: u32 = 16;
600        const SPI_MEM_SCLKCNT_H_S: u32 = 8;
601        const SPI_MEM_SCLKCNT_L_S: u32 = 0;
602
603        if 1 >= freqdiv {
604            SPI0::regs()
605                .sram_clk()
606                .modify(|_, w| w.sclk_equ_sysclk().set_bit());
607        } else {
608            let freqbits: u32 = (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_N_S)
609                | (((freqdiv / 2 - 1) as u32) << SPI_MEM_SCLKCNT_H_S)
610                | (((freqdiv - 1) as u32) << SPI_MEM_SCLKCNT_L_S);
611            unsafe {
612                SPI0::regs().sram_clk().modify(|_, w| w.bits(freqbits));
613            }
614        }
615    }
616}