git subrepo clone https://github.com/libopencm3/libopencm3
subrepo: subdir: "libopencm3" merged: "f5813a54" upstream: origin: "https://github.com/libopencm3/libopencm3" branch: "master" commit: "f5813a54" git-subrepo: version: "0.4.3" origin: "???" commit: "???"
This commit is contained in:
56
libopencm3/lib/stm32/h7/Makefile
Normal file
56
libopencm3/lib/stm32/h7/Makefile
Normal file
@@ -0,0 +1,56 @@
|
||||
##
|
||||
## This file is part of the libopencm3 project.
|
||||
##
|
||||
## This library is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU Lesser General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This library is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU Lesser General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU Lesser General Public License
|
||||
## along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
LIBNAME = libopencm3_stm32h7
|
||||
SRCLIBDIR ?= ../..
|
||||
|
||||
CC = $(PREFIX)gcc
|
||||
AR = $(PREFIX)ar
|
||||
|
||||
# STM32H7 supports double precision FPU
|
||||
FP_FLAGS ?= -mfloat-abi=hard -mfpu=fpv5-d16
|
||||
|
||||
TGT_CFLAGS = -Os \
|
||||
-Wall -Wextra -Wimplicit-function-declaration \
|
||||
-Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes \
|
||||
-Wundef -Wshadow \
|
||||
-I../../../include -fno-common \
|
||||
-mcpu=cortex-m7 -mthumb $(FP_FLAGS) \
|
||||
-Wstrict-prototypes \
|
||||
-ffunction-sections -fdata-sections -MD -DSTM32H7
|
||||
TGT_CFLAGS += $(DEBUG_FLAGS)
|
||||
TGT_CFLAGS += $(STANDARD_FLAGS)
|
||||
|
||||
ARFLAGS = rcs
|
||||
|
||||
OBJS += dac_common_all.o dac_common_v2.o
|
||||
OBJS += exti_common_all.o
|
||||
OBJS += fdcan.o fdcan_common.o
|
||||
OBJS += flash_common_all.o flash_common_f.o flash_common_f24.o
|
||||
OBJS += fmc_common_f47.o
|
||||
OBJS += gpio_common_all.o gpio_common_f0234.o
|
||||
OBJS += pwr.o rcc.o
|
||||
OBJS += rcc_common_all.o
|
||||
OBJS += rng_common_v1.o
|
||||
OBJS += spi_common_all.o spi_common_v2.o
|
||||
OBJS += timer_common_all.o
|
||||
OBJS += usart_common_v2.o usart_common_fifos.o
|
||||
OBJS += quadspi_common_v1.o
|
||||
|
||||
VPATH += ../../usb:../:../../cm3:../common
|
||||
|
||||
include ../../Makefile.include
|
||||
471
libopencm3/lib/stm32/h7/fdcan.c
Normal file
471
libopencm3/lib/stm32/h7/fdcan.c
Normal file
@@ -0,0 +1,471 @@
|
||||
/** @addtogroup fdcan_file FDCAN peripheral API
|
||||
*
|
||||
* @ingroup peripheral_apis
|
||||
*
|
||||
* @brief <b>libopencm3 STM32 FDCAN</b>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*
|
||||
* @author @htmlonly © @endhtmlonly 2021 Eduard Drusa <ventyl86 at netkosice dot sk>
|
||||
*
|
||||
* Device is equipped with two FDCAN peripherals residing in one FDCAN block. The peripherals
|
||||
* support both CAN 2.0 A and B standard and Bosch FDCAN standard. FDCAN frame format and
|
||||
* bitrate switching is supported. The peripheral has several filters for incoming messages that
|
||||
* can be distributed between two FIFOs and transmit buffer all of configurable amount of
|
||||
* entries. For transmitted messages it is possible to opt for event notification once message
|
||||
* is transmitted.
|
||||
*
|
||||
* The FDCAN peripheral present in STM32 H7 is a superset of FDCAN peripheral found in other MCUs
|
||||
* such as STM32 G4. It allows more fine-grade control over buffer and filter allocation and
|
||||
* supports TTCAN on CAN1, etc. This driver provides source-level backwards compatible
|
||||
* implementation of driver, which allows build of unmodified software originally written for
|
||||
* STM32 G4 on STM32 H7.
|
||||
*
|
||||
* LGPL License Terms @ref lgpl_license
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of the libopencm3 project.
|
||||
*
|
||||
* Copyright (C) 2021 Eduard Drusa <ventyl86 at netkosice dot sk>
|
||||
*
|
||||
* This library is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libopencm3/stm32/fdcan.h>
|
||||
#include <libopencm3/stm32/rcc.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define FDCAN_LSS_COUNT(can_base) \
|
||||
((FDCAN_SIDFC(can_base) >> FDCAN_SIDFC_LSS_SHIFT) & FDCAN_SIDFC_LSS_MASK)
|
||||
|
||||
#define FDCAN_LSE_COUNT(can_base) \
|
||||
((FDCAN_XIDFC(can_base) >> FDCAN_XIDFC_LSE_SHIFT) & FDCAN_XIDFC_LSE_MASK)
|
||||
|
||||
/* --- FD-CAN functions ----------------------------------------------------- */
|
||||
|
||||
/** @ingroup fdcan_file */
|
||||
/**@{
|
||||
* */
|
||||
|
||||
/** Returns actual size of FIFO entry in FIFO for given CAN port and FIFO.
|
||||
*
|
||||
* Obtains value of FIFO entry length. This value covers both fixed-size frame
|
||||
* header block and payload buffer. User can configure payload buffer size individually
|
||||
* for each FIFO and designated RX buffer.
|
||||
*
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] fifo_id ID of FIFO whole length is queried.
|
||||
* @returns Length of FIFO entry length covering frame header and frame payload.
|
||||
*/
|
||||
unsigned fdcan_get_fifo_element_size(uint32_t canport, unsigned fifo_id)
|
||||
{
|
||||
unsigned element_size;
|
||||
if (fifo_id == 0) {
|
||||
element_size = FDCAN_RXESC(canport) >> FDCAN_RXESC_F0DS_SHIFT;
|
||||
} else {
|
||||
element_size = FDCAN_RXESC(canport) >> FDCAN_RXESC_F1DS_SHIFT;
|
||||
}
|
||||
|
||||
/* Mask is unshifted and at this point, element_size is unshifted too */
|
||||
return 8 + fdcan_dlc_to_length((element_size & FDCAN_RXESC_F0DS_MASK) | 0x8);
|
||||
}
|
||||
|
||||
/** Returns actual size of transmit entry in transmit queue/FIFO for given CAN port.
|
||||
*
|
||||
* Obtains value of entry length in transmit queue/FIFO. This value covers both
|
||||
* fixed-sized frame header block and payload buffer. User can configure payload buffer
|
||||
* size.
|
||||
*
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @returns Length of FIFO entry length covering frame header and frame payload.
|
||||
*/
|
||||
unsigned fdcan_get_txbuf_element_size(uint32_t canport)
|
||||
{
|
||||
unsigned element_size;
|
||||
element_size = (FDCAN_TXESC(canport) >> FDCAN_TXESC_TBDS_SHIFT) & FDCAN_TXESC_TBDS_MASK;
|
||||
return 8 + fdcan_dlc_to_length((element_size & FDCAN_TXESC_TBDS_MASK) | 0x8);
|
||||
}
|
||||
|
||||
/** Initialize allocation of standard filter block in CAN message RAM.
|
||||
*
|
||||
* Allows specifying size of standard filtering block (in term of available filtering
|
||||
* rules and filter base address within CAN message RAM. Note, that there are no limitations
|
||||
* nor checking on address provided. It is possible to share whole filtering block or
|
||||
* portion of it between multiple CAN interfaces.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] flssa standard filtering block start address offset in message RAM
|
||||
* @param [in] lss amount of standard filters
|
||||
*/
|
||||
void fdcan_init_std_filter_ram(uint32_t canport, uint32_t flssa, uint8_t lss)
|
||||
{
|
||||
FDCAN_SIDFC(canport) = flssa << FDCAN_SIDFC_FLSSA_SHIFT
|
||||
| lss << FDCAN_SIDFC_LSS_SHIFT;
|
||||
}
|
||||
|
||||
/** Initialize allocation of extended filter block in CAN message RAM.
|
||||
*
|
||||
* Allows specifying size of extended filtering block (in term of available filtering
|
||||
* rules and filter base address within CAN message RAM. Note, that there are no limitations
|
||||
* nor checking on address provided. It is possible to share whole filtering block or
|
||||
* portion of it between multiple CAN interfaces.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] flesa extended filtering block start address offset in message RAM
|
||||
* @param [in] lse amount of extended filters
|
||||
*/
|
||||
void fdcan_init_ext_filter_ram(uint32_t canport, uint32_t flesa, uint8_t lse)
|
||||
{
|
||||
FDCAN_XIDFC(canport) = flesa << FDCAN_XIDFC_FLESA_SHIFT
|
||||
| lse << FDCAN_XIDFC_LSE_SHIFT;
|
||||
}
|
||||
|
||||
/** Initialize allocation of FIFO block in CAN message RAM.
|
||||
*
|
||||
* Allows specifying size of FIFO block (in term of available messages in FIFO
|
||||
* and FIFO base address within CAN message RAM. Note, that there are no limitations
|
||||
* nor checking on address provided.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] fifo_id ID of fifo being configured
|
||||
* @param [in] fxsa FIFO block start address offset in message RAM
|
||||
* @param [in] fxs number of elements to assign
|
||||
*/
|
||||
void fdcan_init_fifo_ram(uint32_t canport, unsigned fifo_id, uint32_t fxsa, uint8_t fxs)
|
||||
{
|
||||
FDCAN_RXFIC(canport, fifo_id) = (FDCAN_RXFIC(canport, fifo_id)
|
||||
& ~(
|
||||
(FDCAN_RXFIC_FIS_MASK << FDCAN_RXFIC_FIS_SHIFT)
|
||||
| (FDCAN_RXFIC_FISA_MASK << FDCAN_RXFIC_FISA_SHIFT)
|
||||
))
|
||||
| (fxs << FDCAN_RXFIC_FIS_SHIFT)
|
||||
| (fxsa & (FDCAN_RXFIC_FISA_MASK << FDCAN_RXFIC_FISA_SHIFT));
|
||||
}
|
||||
|
||||
/** Initialize allocation of transmit event block in CAN message RAM.
|
||||
*
|
||||
* Allows specifying size of transmit event block (in term of allocated events and block
|
||||
* base address within CAN message RAM. Note, that there are no limitations
|
||||
* nor checking on address provided.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] tesa block start address offset in message RAM
|
||||
* @param [in] tes number of elements to assign
|
||||
*/
|
||||
void fdcan_init_tx_event_ram(uint32_t canport, uint32_t tesa, uint8_t tes)
|
||||
{
|
||||
FDCAN_TXEFC(canport) = (FDCAN_TXEFC(canport)
|
||||
& ~(
|
||||
(FDCAN_TXEFC_EFS_MASK << FDCAN_TXEFC_EFS_SHIFT)
|
||||
| (FDCAN_TXEFC_EFSA_MASK << FDCAN_TXEFC_EFSA_SHIFT)
|
||||
))
|
||||
| (tes << FDCAN_TXEFC_EFS_SHIFT)
|
||||
| (tesa & (FDCAN_TXEFC_EFSA_MASK << FDCAN_TXEFC_EFSA_SHIFT));
|
||||
}
|
||||
|
||||
/** Initialize allocation of transmit queue block in CAN message RAM.
|
||||
*
|
||||
* Allows specifying size of transmit queue block (in term of allocated buffers and block
|
||||
* base address within CAN message RAM. Note, that there are no limitations
|
||||
* nor checking on address provided.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] tbsa block start address offset in message RAM
|
||||
* @param [in] tbs number of elements to assign
|
||||
*/
|
||||
void fdcan_init_tx_buffer_ram(uint32_t canport, uint32_t tbsa, uint8_t tbs)
|
||||
{
|
||||
FDCAN_TXBC(canport) = (FDCAN_TXBC(canport)
|
||||
& ~(
|
||||
(FDCAN_TXBC_TFQS_MASK << FDCAN_TXBC_TFQS_SHIFT)
|
||||
| (FDCAN_TXBC_TBSA_MASK << FDCAN_TXBC_TBSA_SHIFT)
|
||||
))
|
||||
| (tbs << FDCAN_TXBC_TFQS_SHIFT)
|
||||
| (tbsa & (FDCAN_TXBC_TBSA_MASK << FDCAN_TXBC_TBSA_SHIFT));
|
||||
}
|
||||
|
||||
/** Initialize size of data fields in reception buffers.
|
||||
*
|
||||
* Configures maximum size of message payload, which can be stored in different
|
||||
* reception buffers. Each buffer can have maximum payload size configured independently.
|
||||
* Buffers can only be configured to sizes which are valid sizes of FDCAN payload. Sizes smaller
|
||||
* than 8 are automatically padded to 8.
|
||||
* @note If you change these values, then content of reception buffers is invalidated. You may
|
||||
* also want to recalculate base addresses of objects in message RAM as their size might have
|
||||
* changed.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] rxbuf maximum storable payload size of dedicated RX buffer
|
||||
* @param [in] rxfifo0 maximum storable payload size of RX FIFO 0
|
||||
* @param [in] rxfifo1 maximum storable payload size of RX FIFO 1
|
||||
* @returns operation return status. See @ref fdcan_error.
|
||||
*/
|
||||
int fdcan_set_rx_element_size(uint32_t canport, uint8_t rxbuf, uint8_t rxfifo0, uint8_t rxfifo1)
|
||||
{
|
||||
unsigned rxbufdlc = fdcan_length_to_dlc(rxbuf);
|
||||
unsigned rxfifo0dlc = fdcan_length_to_dlc(rxfifo0);
|
||||
unsigned rxfifo1dlc = fdcan_length_to_dlc(rxfifo1);
|
||||
|
||||
if (rxbufdlc == 0xFF || rxfifo0dlc == 0xFF || rxfifo1dlc == 0xFF) {
|
||||
return FDCAN_E_INVALID;
|
||||
}
|
||||
|
||||
/* RXESC fields use format of FDCAN DLC, albeit using only
|
||||
* three LSBs. DLC < 8 is always allocated as 8 bytes and is always
|
||||
* encoded as 0.
|
||||
*/
|
||||
if (rxbufdlc <= 8) {
|
||||
rxbufdlc = 0;
|
||||
} else {
|
||||
rxbufdlc &= ~(1 << 4);
|
||||
}
|
||||
|
||||
if (rxfifo0dlc <= 8) {
|
||||
rxfifo0dlc = 0;
|
||||
} else {
|
||||
rxfifo0dlc &= ~(1 << 4);
|
||||
}
|
||||
|
||||
if (rxfifo1dlc <= 8) {
|
||||
rxfifo1dlc = 0;
|
||||
} else {
|
||||
rxfifo1dlc &= ~(1 << 4);
|
||||
}
|
||||
|
||||
FDCAN_RXESC(canport) = rxbufdlc << FDCAN_RXESC_RBDS_SHIFT
|
||||
| rxfifo1dlc << FDCAN_RXESC_F1DS_SHIFT
|
||||
| rxfifo0dlc << FDCAN_RXESC_F0DS_SHIFT;
|
||||
|
||||
return FDCAN_E_OK;
|
||||
}
|
||||
|
||||
/** Initialize size of data fields in transmit buffers.
|
||||
*
|
||||
* Configures maximum size of message payload, which can be stored either in dedicated
|
||||
* transmit buffer or into transmit queue/FIFO. One size is applied both to transmit buffer
|
||||
* and transmit queue / FIFO. Buffers can only be configured to sizes which are valid sizes
|
||||
* of FDCAN payload. Sizes smaller than 8 are automatically padded to 8 bytes.
|
||||
* @note If you change these values, then content of transmission buffers is invalidated. You may
|
||||
* also want to recalculate base addresses of objects in message RAM as their size might have
|
||||
* changed.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] txbuf maximum storable payload size of TX buffer / FIFO / queue
|
||||
* @returns operation return status. See @ref fdcan_error.
|
||||
*/
|
||||
int fdcan_set_tx_element_size(uint32_t canport, uint8_t txbuf)
|
||||
{
|
||||
unsigned txbufdlc = fdcan_length_to_dlc(txbuf);
|
||||
|
||||
if (txbufdlc == 0xFF) {
|
||||
return FDCAN_E_INVALID;
|
||||
}
|
||||
|
||||
if (txbufdlc <= 8) {
|
||||
txbufdlc = 0;
|
||||
} else {
|
||||
txbufdlc &= ~(1 << 4);
|
||||
}
|
||||
|
||||
FDCAN_TXESC(canport) = txbufdlc << FDCAN_TXESC_TBDS_SHIFT;
|
||||
|
||||
return FDCAN_E_OK;
|
||||
}
|
||||
|
||||
/** Configure amount of filters and initialize filtering block.
|
||||
*
|
||||
* This function allows to configure global amount of filters present.
|
||||
* FDCAN block will only ever check as many filters as this function configures.
|
||||
* Function will also clear all filter blocks to zero values. This function
|
||||
* can be only called after @ref fdcan_init has already been called and
|
||||
* @ref fdcan_start has not been called yet as registers holding filter
|
||||
* count are write-protected unless FDCAN block is in INIT mode. It is possible
|
||||
* to reconfigure filters (@ref fdcan_set_std_filter and @ref fdcan_set_ext_filter)
|
||||
* after FDCAN block has already been started.
|
||||
*
|
||||
* This function is provided for source level compatibility with code written for
|
||||
* STM32G4. As an alternative, you can use @ref fdcan_init_std_filter_ram() and
|
||||
* @ref fdcan_init_ext_filter_ram() to have more control over filtering configuration.
|
||||
* Note that if you do so, your code won't be compatible with STM32G4 controllers.
|
||||
*
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] std_filt requested amount of standard ID filter rules (0-28)
|
||||
* @param [in] ext_filt requested amount of extended ID filter rules (0-8)
|
||||
*/
|
||||
void fdcan_init_filter(uint32_t canport, uint8_t std_filt, uint8_t ext_filt)
|
||||
{
|
||||
struct fdcan_standard_filter *lfssa;
|
||||
struct fdcan_extended_filter *lfesa;
|
||||
|
||||
int can_id = FDCAN_BLOCK_ID(canport);
|
||||
|
||||
int lfsofs = 0;
|
||||
int lfeofs = 0;
|
||||
int lfssize = std_filt * sizeof(struct fdcan_standard_filter);
|
||||
int lfesize = ext_filt * sizeof(struct fdcan_extended_filter);
|
||||
|
||||
if (can_id == 0) {
|
||||
lfsofs = 0;
|
||||
lfeofs = lfssize;
|
||||
} else {
|
||||
lfsofs = CAN_MSG_BASE + CAN_MSG_SIZE - lfssize;
|
||||
lfeofs = lfsofs - lfesize;
|
||||
}
|
||||
|
||||
fdcan_init_std_filter_ram(canport, lfsofs, 28);
|
||||
fdcan_init_ext_filter_ram(canport, lfeofs, 8);
|
||||
|
||||
lfssa = fdcan_get_flssa_addr(canport);
|
||||
lfesa = fdcan_get_flesa_addr(canport);
|
||||
|
||||
/* Only perform initialization of message RAM if there are
|
||||
* any filters required
|
||||
*/
|
||||
if (std_filt > 0) {
|
||||
for (int q = 0; q < 28; ++q) {
|
||||
lfssa[q].type_id1_conf_id2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext_filt > 0) {
|
||||
for (int q = 0; q < 8; ++q) {
|
||||
lfesa[q].conf_id1 = 0;
|
||||
lfesa[q].type_id2 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Enable FDCAN operation after FDCAN block has been set up.
|
||||
*
|
||||
* This function will disable FDCAN configuration effectively
|
||||
* allowing FDCAN to sync up with the bus. After calling this function
|
||||
* it is not possible to reconfigure amount of filter rules, yet
|
||||
* it is possible to configure rules themselves. FDCAN block operation
|
||||
* state can be checked using @ref fdcan_get_init_state.
|
||||
*
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] timeout Amount of empty busy loops, which routine should wait for FDCAN
|
||||
* confirming that it left INIT mode. If set to 0, function will return
|
||||
* immediately.
|
||||
* @returns Operation error status. See @ref fdcan_error.
|
||||
* @note If this function returns with timeout, it usually means that
|
||||
* FDCAN_clk is not set up properly.
|
||||
*/
|
||||
int fdcan_start(uint32_t canport, uint32_t timeout)
|
||||
{
|
||||
/* This block detects obviously invalid configuration of
|
||||
* RX FIFOs and TX buffers. Such situation may happen
|
||||
* if code originally targeted for STM32G4 is compiled for
|
||||
* STM32H7. This code is not aware of H7 abilities and
|
||||
* leaves this stuff unconfigured. We pick up here and
|
||||
* assume, that the code wants the FDCAN to behave as if
|
||||
* it was STM32G4. Therefore we will configure two three entry
|
||||
* long RX FIFOs, three entry long TX event buffer and three
|
||||
* entry long TX FIFO/queue here.
|
||||
*/
|
||||
if (FDCAN_RXFIFO_OFFSET(canport, 0) == 0
|
||||
&& FDCAN_RXFIFO_OFFSET(canport, 1) == 0
|
||||
&& FDCAN_TXBUF_OFFSET(canport) == 0
|
||||
&& FDCAN_TXEVT_OFFSET(canport) == 0
|
||||
&& FDCAN_RXESC(canport) == 0
|
||||
&& FDCAN_TXESC(canport) == 0
|
||||
) {
|
||||
/* These sizes are fixed based on what G4 contains. */
|
||||
int fifo0_size = 3 * sizeof(struct fdcan_rx_fifo_element);
|
||||
int fifo1_size = fifo0_size;
|
||||
int txevt_size = 3 * sizeof(struct fdcan_tx_event_element);
|
||||
int txbuf_size = 3 * sizeof(struct fdcan_tx_buffer_element);
|
||||
|
||||
fdcan_set_rx_element_size(canport, 0, 64, 64);
|
||||
fdcan_set_tx_element_size(canport, 64);
|
||||
|
||||
/* At this point we simply assume that FLSSA and FLESA were
|
||||
* set up by calling fdcan_init_filter(). It they weren't,
|
||||
* there just won't be allocated any space for them.
|
||||
* That's not a problem as after fdcan_start returns, it is
|
||||
* not possible to configure filter amount anymore.
|
||||
*
|
||||
* Default approach is to configure CAN1 to occupy bottom
|
||||
* of message RAM and CAN1 to occupy top of message RAM keeping
|
||||
* large unused space in between. This ensures that even if someone
|
||||
* starts playing with CAN1, "implicit" configuration of CAN2 done
|
||||
* here won't break things magically.
|
||||
*/
|
||||
if (FDCAN_BLOCK_ID(canport) == 0) {
|
||||
/* CAN1 will use bottom-up layout with
|
||||
* FLSSA < FLESA < F0SA < F1SA < TXESA < TXBSA
|
||||
* Rx buffer is not used.
|
||||
*/
|
||||
fdcan_init_fifo_ram(canport, 0,
|
||||
FDCAN_LFESA_OFFSET(canport)
|
||||
+ FDCAN_LSE_COUNT(canport) * sizeof(struct fdcan_extended_filter),
|
||||
3);
|
||||
fdcan_init_fifo_ram(canport, 1,
|
||||
FDCAN_RXFIFO_OFFSET(canport, 0) + fifo0_size, 3);
|
||||
fdcan_init_tx_event_ram(canport,
|
||||
FDCAN_RXFIFO_OFFSET(canport, 1) + fifo1_size, 3);
|
||||
fdcan_init_tx_buffer_ram(canport,
|
||||
FDCAN_TXEVT_OFFSET(canport) + txevt_size, 3);
|
||||
} else {
|
||||
/* LFESA might be uninitialized. In such case
|
||||
* we forge it's address at the end of MSG RAM
|
||||
*/
|
||||
int lfesa_offs = FDCAN_LFESA_OFFSET(canport);
|
||||
if (lfesa_offs == 0) {
|
||||
lfesa_offs = CAN_MSG_SIZE;
|
||||
}
|
||||
/* CAN2 will use top-down layout with
|
||||
* TXBSA < TXESA < F1SA < F0SA < FLESA < FLSSA
|
||||
* Rx buffer is not used.
|
||||
* This arrangement should ensure that even if
|
||||
* CAN1 was configured manually, CAN2 default
|
||||
* configuration should not break CAN1.
|
||||
*/
|
||||
fdcan_init_fifo_ram(canport, 0, lfesa_offs - fifo0_size, 3);
|
||||
fdcan_init_fifo_ram(canport, 1,
|
||||
FDCAN_RXFIFO_OFFSET(canport, 0) - fifo1_size, 3);
|
||||
fdcan_init_tx_event_ram(canport,
|
||||
FDCAN_RXFIFO_OFFSET(canport, 1) - txevt_size, 3);
|
||||
fdcan_init_tx_buffer_ram(canport,
|
||||
FDCAN_TXEVT_OFFSET(canport) - txbuf_size, 3);
|
||||
}
|
||||
|
||||
}
|
||||
/* Error here usually means, that FDCAN_clk is not set up
|
||||
* correctly, or at all. This usually can't be seen above
|
||||
* when INIT is set to 1, because default value for INIT is
|
||||
* 1 as long as one has FDCAN_pclk configured properly.
|
||||
**/
|
||||
if (fdcan_cccr_init_cfg(canport, false, timeout) != 0) {
|
||||
return FDCAN_E_TIMEOUT;
|
||||
}
|
||||
|
||||
return FDCAN_E_OK;
|
||||
}
|
||||
|
||||
/** Configure FDCAN FIFO lock mode
|
||||
*
|
||||
* This function allows to choose between locked and overewrite mode of FIFOs. In locked mode,
|
||||
* whenever FIFO is full and new frame arrives, which would normally been stored into given
|
||||
* FIFO, then frame is dropped. If overwrite mode is active, then most recent message in FIFO
|
||||
* is rewritten by frame just received.
|
||||
* @param [in] canport FDCAN block base address. See @ref fdcan_block.
|
||||
* @param [in] locked true activates locked mode, false activates overwrite mode
|
||||
*/
|
||||
void fdcan_set_fifo_locked_mode(uint32_t canport, bool locked)
|
||||
{
|
||||
if (locked) {
|
||||
FDCAN_RXF0C(canport) &= ~(FDCAN_RXF0C_F0OM);
|
||||
FDCAN_RXF1C(canport) &= ~(FDCAN_RXF1C_F1OM);
|
||||
} else {
|
||||
FDCAN_RXF0C(canport) |= FDCAN_RXF0C_F0OM;
|
||||
FDCAN_RXF1C(canport) |= FDCAN_RXF1C_F1OM;
|
||||
}
|
||||
}
|
||||
|
||||
/**@}*/
|
||||
163
libopencm3/lib/stm32/h7/pwr.c
Normal file
163
libopencm3/lib/stm32/h7/pwr.c
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* @brief <b>libopencm3 STM32H7xx Power Control</b>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*
|
||||
* @date 16 December, 2019
|
||||
*
|
||||
* This library supports the power control system for the
|
||||
* STM32H7 series of ARM Cortex Microcontrollers by ST Microelectronics.
|
||||
*
|
||||
* LGPL License Terms @ref lgpl_license
|
||||
*/
|
||||
/*
|
||||
* This file is part of the libopencm3 project.
|
||||
*
|
||||
* Copyright (C) 2011 Stephen Caudle <scaudle@doceme.com>
|
||||
* Copyright (C) 2017 Matthew Lai <m@matthewlai.ca>
|
||||
* Copyright (C) 2019 Brian Viele <vielster@allocor.tech>
|
||||
*
|
||||
* This library is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <libopencm3/cm3/assert.h>
|
||||
#include <libopencm3/stm32/dbgmcu.h>
|
||||
#include <libopencm3/stm32/pwr.h>
|
||||
#include <libopencm3/stm32/rcc.h>
|
||||
#include <libopencm3/stm32/syscfg.h>
|
||||
|
||||
/* DBGMCU_IDC DEV ID values needed to account for variations between part types. */
|
||||
#define DBGMCU_IDCODE_DEV_ID_STM32H74X_5X 0x450
|
||||
#define DBGMCU_IDCODE_DEV_ID_STM32H7A3_B3_B0 0x480
|
||||
|
||||
void pwr_set_mode_ldo(void) {
|
||||
/* Per table in manual for SMPS, mask and set SMPSEN=0 : LDOEN=1 : BYPASS=0. */
|
||||
const uint32_t cr3_mask = (PWR_CR3_SMPSEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
|
||||
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | (PWR_CR3_LDOEN);
|
||||
}
|
||||
|
||||
void pwr_set_mode_scu_ldo(void) {
|
||||
const uint32_t cr3_mask = (PWR_CR3_SCUEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
|
||||
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | (PWR_CR3_SCUEN | PWR_CR3_LDOEN);
|
||||
}
|
||||
|
||||
void pwr_set_mode_smps_ldo(bool supply_external, uint32_t smps_level, bool use_ldo) {
|
||||
uint32_t cr3_mask, cr3_set;
|
||||
cr3_mask = (PWR_CR3_SMPSEXTHP | PWR_CR3_SMPSEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
|
||||
cr3_mask |= PWR_CR3_SMPSLEVEL_MASK << PWR_CR3_SMPSLEVEL_SHIFT;
|
||||
|
||||
/* Default, take in unconditional settings, will OR in the rest. */
|
||||
cr3_set = PWR_CR3_SMPSEN | (smps_level << PWR_CR3_SMPSLEVEL_SHIFT);
|
||||
if (supply_external) {
|
||||
cm3_assert(smps_level != PWR_CR3_SMPSLEVEL_VOS); /* Unsupported setting! */
|
||||
cr3_set |= PWR_CR3_SMPSEXTHP;
|
||||
}
|
||||
|
||||
if (use_ldo) {
|
||||
cr3_set |= PWR_CR3_LDOEN;
|
||||
}
|
||||
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | cr3_set;
|
||||
}
|
||||
|
||||
void pwr_set_mode_bypass(void) {
|
||||
const uint32_t cr3_mask = (PWR_CR3_SMPSEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
|
||||
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | PWR_CR3_BYPASS;
|
||||
}
|
||||
|
||||
void pwr_set_mode_scu_bypass(void) {
|
||||
const uint32_t cr3_mask = (PWR_CR3_SCUEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
|
||||
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | (PWR_CR3_SCUEN | PWR_CR3_BYPASS);
|
||||
}
|
||||
|
||||
|
||||
void pwr_set_mode(enum pwr_sys_mode mode, uint8_t smps_level) {
|
||||
switch (mode) {
|
||||
case PWR_SYS_SCU_LDO:
|
||||
pwr_set_mode_scu_ldo();
|
||||
break;
|
||||
case PWR_SYS_SCU_BYPASS:
|
||||
pwr_set_mode_scu_bypass();
|
||||
break;
|
||||
case PWR_SYS_LDO:
|
||||
pwr_set_mode_ldo();
|
||||
break;
|
||||
case PWR_SYS_SMPS_DIRECT:
|
||||
case PWR_SYS_SMPS_LDO:
|
||||
pwr_set_mode_smps_ldo(false, PWR_CR3_SMPSLEVEL_VOS, mode == PWR_SYS_SMPS_LDO);
|
||||
break;
|
||||
case PWR_SYS_EXT_SMPS_LDO:
|
||||
case PWR_SYS_EXT_SMPS_LDO_BYP:
|
||||
pwr_set_mode_smps_ldo(false, smps_level, mode == PWR_SYS_EXT_SMPS_LDO);
|
||||
break;
|
||||
case PWR_SYS_BYPASS:
|
||||
pwr_set_mode_bypass();
|
||||
break;
|
||||
}
|
||||
/* Wait for power supply status to state ready. */
|
||||
while (!(PWR_CSR1 & PWR_CSR1_ACTVOSRDY));
|
||||
}
|
||||
|
||||
void pwr_set_svos_scale(enum pwr_svos_scale scale)
|
||||
{
|
||||
uint32_t pwr_cr1_reg = PWR_CR1;
|
||||
pwr_cr1_reg = (pwr_cr1_reg & ~(PWR_CR1_SVOS_MASK << PWR_CR1_SVOS_SHIFT));
|
||||
PWR_CR1 = pwr_cr1_reg | scale;
|
||||
}
|
||||
|
||||
void pwr_set_vos_scale(enum pwr_vos_scale scale) {
|
||||
static const uint8_t srdcr_vos_values[] = {
|
||||
PWR_SRDCR_VOS_SCALE_0,
|
||||
PWR_SRDCR_VOS_SCALE_1,
|
||||
PWR_SRDCR_VOS_SCALE_2,
|
||||
PWR_SRDCR_VOS_SCALE_3,
|
||||
};
|
||||
static const uint8_t d3cr_vos_values[] = {
|
||||
PWR_D3CR_VOS_SCALE_0,
|
||||
PWR_D3CR_VOS_SCALE_1,
|
||||
PWR_D3CR_VOS_SCALE_2,
|
||||
PWR_D3CR_VOS_SCALE_3,
|
||||
};
|
||||
cm3_assert(scale != PWR_VOS_SCALE_UNDEFINED); /* Make sure this has been set. */
|
||||
|
||||
/* "SmartRun Domain" devices (presently only know of A3/B3/B0) have different mapping.
|
||||
* Note: DBGMCU_IDCODE_DEV_ID_STM32H7A3 covers all three of these models.
|
||||
*/
|
||||
uint32_t devid = DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK;
|
||||
if (devid == DBGMCU_IDCODE_DEV_ID_STM32H7A3_B3_B0) {
|
||||
const uint32_t srdcr_vos_mask = (PWR_SRDCR_VOS_MASK << PWR_SRDCR_VOS_SHIFT);
|
||||
const uint32_t vos_value = srdcr_vos_values[scale - 1] << PWR_SRDCR_VOS_SHIFT;
|
||||
PWR_SRDCR = (PWR_SRDCR & ~srdcr_vos_mask) | vos_value;
|
||||
} else {
|
||||
/* Get the VOS value for the non-smart domain types. */
|
||||
uint32_t d3cr_vos = (uint32_t)d3cr_vos_values[scale - 1] << PWR_D3CR_VOS_SHIFT;
|
||||
uint32_t d3cr_masked = PWR_D3CR & ~(PWR_D3CR_VOS_MASK << PWR_D3CR_VOS_SHIFT);
|
||||
/* STM32H742/43/45/47/50/53/55/57 have special handling of VOS0, which is to set
|
||||
* VOS1, and also enable the ODEN in the SYSCFG_PWRCR.
|
||||
* Note: Conveniently, all devices with this setup share a devid, so pick one.
|
||||
*/
|
||||
if (devid == DBGMCU_IDCODE_DEV_ID_STM32H74X_5X) {
|
||||
rcc_periph_clock_enable(RCC_SYSCFG); /* Ensure we can access ODEN. */
|
||||
/* Per the manual, VOS0 is implemented as VOS1 + ODEN. Handle this case. */
|
||||
if (scale == PWR_VOS_SCALE_0) {
|
||||
PWR_D3CR = d3cr_masked | (PWR_D3CR_VOS_SCALE_1 << PWR_SRDCR_VOS_SHIFT);
|
||||
SYSCFG_PWRCR |= SYSCFG_PWRCR_ODEN;
|
||||
} else {
|
||||
SYSCFG_PWRCR &= ~SYSCFG_PWRCR_ODEN;
|
||||
PWR_D3CR = d3cr_masked | d3cr_vos;
|
||||
}
|
||||
} else {
|
||||
PWR_D3CR = d3cr_masked | d3cr_vos;
|
||||
}
|
||||
}
|
||||
while (!(PWR_D3CR & PWR_D3CR_VOSRDY)); /* VOSRDY bit is same between D3CR and SRDCR. */
|
||||
}
|
||||
441
libopencm3/lib/stm32/h7/rcc.c
Normal file
441
libopencm3/lib/stm32/h7/rcc.c
Normal file
@@ -0,0 +1,441 @@
|
||||
/** @defgroup rcc_file RCC peripheral API
|
||||
*
|
||||
* @ingroup peripheral_apis
|
||||
* This library supports the Reset and Clock Control System in the STM32 series
|
||||
* of ARM Cortex Microcontrollers by ST Microelectronics.
|
||||
*
|
||||
* LGPL License Terms @ref lgpl_license
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <libopencm3/cm3/assert.h>
|
||||
#include <libopencm3/stm32/rcc.h>
|
||||
#include <libopencm3/stm32/pwr.h>
|
||||
#include <libopencm3/stm32/flash.h>
|
||||
|
||||
#define HZ_PER_MHZ 1000000UL
|
||||
#define HZ_PER_KHZ 1000UL
|
||||
|
||||
/* Local private copy of the clock configuration for providing user with clock tree data. */
|
||||
static struct {
|
||||
uint16_t sysclk_mhz;
|
||||
uint16_t cpu_mhz;
|
||||
uint16_t hclk_mhz;
|
||||
struct {
|
||||
uint16_t pclk1_mhz; /* APB1 clock. */
|
||||
uint16_t pclk2_mhz; /* APB2 clock. */
|
||||
uint16_t pclk3_mhz; /* APB3 clock. */
|
||||
uint16_t pclk4_mhz; /* APB4 clock. */
|
||||
} per;
|
||||
struct pll_clocks { /* Each PLL output set of data. */
|
||||
uint16_t p_mhz;
|
||||
uint16_t q_mhz;
|
||||
uint16_t r_mhz;
|
||||
} pll1, pll2, pll3;
|
||||
uint16_t hse_khz; /* This can't exceed 50MHz */
|
||||
} rcc_clock_tree = {
|
||||
.sysclk_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
|
||||
.cpu_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
|
||||
.hclk_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
|
||||
.per.pclk1_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
|
||||
.per.pclk2_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
|
||||
.per.pclk3_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ,
|
||||
.per.pclk4_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ
|
||||
};
|
||||
|
||||
static void rcc_configure_pll(uint32_t clkin, const struct pll_config *config, int pll_num) {
|
||||
/* Only concern ourselves with the PLL if the input clock is enabled. */
|
||||
if (config->divm == 0 || pll_num < 1 || pll_num > 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct pll_clocks *pll_tree_ptr;
|
||||
if (pll_num == 1) {
|
||||
pll_tree_ptr = &rcc_clock_tree.pll1;
|
||||
} else if (pll_num == 2) {
|
||||
pll_tree_ptr = &rcc_clock_tree.pll2;
|
||||
} else {
|
||||
pll_tree_ptr = &rcc_clock_tree.pll3;
|
||||
}
|
||||
|
||||
/* Let's write all of the dividers as specified. */
|
||||
RCC_PLLDIVR(pll_num) = 0;
|
||||
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVN(config->divn);
|
||||
|
||||
/* Setup the PLL config values for this PLL. */
|
||||
uint8_t vco_addshift = 4 * (pll_num - 1); /* Values spaced by 4 for PLL 1/2/3 */
|
||||
|
||||
/* Set the PLL input frequency range. */
|
||||
uint32_t pll_clk_mhz = (clkin / config->divm) / HZ_PER_MHZ;
|
||||
if (pll_clk_mhz > 2 && pll_clk_mhz <= 4) {
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_PLLRGE_2_4MHZ << RCC_PLLCFGR_PLL1RGE_SHIFT) << vco_addshift;
|
||||
} else if (pll_clk_mhz > 4 && pll_clk_mhz <= 8) {
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_PLLRGE_4_8MHZ << RCC_PLLCFGR_PLL1RGE_SHIFT) << vco_addshift;
|
||||
} else if (pll_clk_mhz > 8) {
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_PLLRGE_8_16MHZ << RCC_PLLCFGR_PLL1RGE_SHIFT) << vco_addshift;
|
||||
}
|
||||
|
||||
/* Set the VCO output frequency range. */
|
||||
uint32_t pll_vco_clk_mhz = (pll_clk_mhz * config->divn);
|
||||
if (pll_vco_clk_mhz <= 420) {
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_PLL1VCO_MED << vco_addshift);
|
||||
}
|
||||
|
||||
/* Setup the enable bits for the PLL outputs. */
|
||||
uint8_t diven_addshift = 3 * (pll_num - 1); /* Values spaced by 3 for PLL1/2/3 */
|
||||
if (config->divp > 0) {
|
||||
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVP(config->divp);
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_DIVP1EN << diven_addshift);
|
||||
pll_tree_ptr->p_mhz = pll_vco_clk_mhz / config->divp;
|
||||
}
|
||||
if (config->divq > 0) {
|
||||
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVQ(config->divq);
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_DIVQ1EN << diven_addshift);
|
||||
pll_tree_ptr->q_mhz = pll_vco_clk_mhz / config->divq;
|
||||
}
|
||||
if (config->divr > 0) {
|
||||
RCC_PLLDIVR(pll_num) |= RCC_PLLNDIVR_DIVR(config->divr);
|
||||
RCC_PLLCFGR |= (RCC_PLLCFGR_DIVR1EN << diven_addshift);
|
||||
pll_tree_ptr->r_mhz = pll_vco_clk_mhz / config->divr;
|
||||
}
|
||||
|
||||
/* Attempt to enable and lock PLL. */
|
||||
uint8_t cr_addshift = 2 * (pll_num - 1);
|
||||
RCC_CR |= RCC_CR_PLL1ON << cr_addshift;
|
||||
while (!(RCC_CR & (RCC_CR_PLL1RDY << cr_addshift)));
|
||||
}
|
||||
|
||||
static void rcc_set_and_enable_plls(const struct rcc_pll_config *config) {
|
||||
/* It is assumed that this function is entered with PLLs disabled and not
|
||||
* running. Setup PLL1/2/3 with configurations specified in the config. */
|
||||
RCC_PLLCKSELR = RCC_PLLCKSELR_DIVM1(config->pll1.divm) |
|
||||
RCC_PLLCKSELR_DIVM2(config->pll2.divm) |
|
||||
RCC_PLLCKSELR_DIVM3(config->pll3.divm) |
|
||||
config->pll_source;
|
||||
|
||||
uint32_t clkin = (config->pll_source == RCC_PLLCKSELR_PLLSRC_HSI)
|
||||
? RCC_HSI_BASE_FREQUENCY : config->hse_frequency;
|
||||
|
||||
RCC_PLLCFGR = 0;
|
||||
rcc_configure_pll(clkin, &config->pll1, 1);
|
||||
rcc_configure_pll(clkin, &config->pll2, 2);
|
||||
rcc_configure_pll(clkin, &config->pll3, 3);
|
||||
}
|
||||
|
||||
/* This is a helper to calculate dividers that go 2/4/8/16/64/128/256/512.
|
||||
* These dividers also use the top bit as an "enable". */
|
||||
static uint16_t rcc_prediv_log_skip32_div(uint16_t clk_mhz, uint32_t div_val) {
|
||||
if (div_val < 0x8) {
|
||||
return clk_mhz;
|
||||
} else if (div_val <= RCC_D1CFGR_D1CPRE_DIV16) {
|
||||
return clk_mhz >> (div_val - 7);
|
||||
} else {
|
||||
return clk_mhz >> (div_val - 6);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a helper to help calculate simple 3-bit log dividers with top bit
|
||||
* used as enable bit. */
|
||||
static uint16_t rcc_prediv_3bit_log_div(uint16_t clk_mhz, uint32_t div_val) {
|
||||
if (div_val < 0x4) {
|
||||
return clk_mhz;
|
||||
} else {
|
||||
return clk_mhz >> (div_val - 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void rcc_clock_setup_domain1(const struct rcc_pll_config *config) {
|
||||
RCC_D1CFGR = 0;
|
||||
RCC_D1CFGR |= RCC_D1CFGR_D1CPRE(config->core_pre) |
|
||||
RCC_D1CFGR_D1HPRE(config->hpre) | RCC_D1CFGR_D1PPRE(config->ppre3);
|
||||
|
||||
/* Update our clock values in our tree based on the config values. */
|
||||
rcc_clock_tree.cpu_mhz =
|
||||
rcc_prediv_log_skip32_div(rcc_clock_tree.sysclk_mhz, config->core_pre);
|
||||
rcc_clock_tree.hclk_mhz =
|
||||
rcc_prediv_log_skip32_div(rcc_clock_tree.cpu_mhz, config->hpre);
|
||||
rcc_clock_tree.per.pclk3_mhz =
|
||||
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre3);
|
||||
}
|
||||
|
||||
static void rcc_clock_setup_domain2(const struct rcc_pll_config *config) {
|
||||
RCC_D2CFGR = 0;
|
||||
RCC_D2CFGR |= RCC_D2CFGR_D2PPRE1(config->ppre1) |
|
||||
RCC_D2CFGR_D2PPRE2(config->ppre2);
|
||||
|
||||
/* Update our clock values in our tree based on the config values. */
|
||||
rcc_clock_tree.per.pclk2_mhz =
|
||||
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre2);
|
||||
rcc_clock_tree.per.pclk1_mhz =
|
||||
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre1);
|
||||
}
|
||||
|
||||
static void rcc_clock_setup_domain3(const struct rcc_pll_config *config) {
|
||||
RCC_D3CFGR &= 0;
|
||||
RCC_D3CFGR |= RCC_D3CFGR_D3PPRE(config->ppre4);
|
||||
|
||||
/* Update our clock values in our tree based on the config values. */
|
||||
rcc_clock_tree.per.pclk4_mhz =
|
||||
rcc_prediv_3bit_log_div(rcc_clock_tree.hclk_mhz, config->ppre4);
|
||||
}
|
||||
|
||||
void rcc_clock_setup_pll(const struct rcc_pll_config *config) {
|
||||
/* First, set system clock to utilize HSI, then disable all but HSI. */
|
||||
RCC_CR |= RCC_CR_HSION;
|
||||
RCC_CFGR &= ~(RCC_CFGR_SW_MASK << RCC_CFGR_SW_SHIFT);
|
||||
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS_HSI);
|
||||
RCC_CR = RCC_CR_HSION;
|
||||
|
||||
/* Now that we're safely running on HSI, let's setup the power system for scaling. */
|
||||
pwr_set_mode(config->power_mode, config->smps_level);
|
||||
pwr_set_vos_scale(config->voltage_scale);
|
||||
|
||||
/* Set flash waitstates. Enable flash prefetch if we have at least 1WS */
|
||||
flash_set_ws(config->flash_waitstates);
|
||||
if (config->flash_waitstates > FLASH_ACR_LATENCY_0WS) {
|
||||
flash_prefetch_enable();
|
||||
} else {
|
||||
flash_prefetch_disable();
|
||||
}
|
||||
|
||||
/* User has specified an external oscillator, make sure we turn it on. */
|
||||
if (config->hse_frequency > 0) {
|
||||
RCC_CR |= RCC_CR_HSEON;
|
||||
while (!(RCC_CR & RCC_CR_HSERDY));
|
||||
rcc_clock_tree.hse_khz = config->hse_frequency / HZ_PER_KHZ;
|
||||
}
|
||||
|
||||
/* Set, enable and lock all of the pll from the config. */
|
||||
rcc_set_and_enable_plls(config);
|
||||
|
||||
/* Populate our base sysclk settings for use with domain clocks. */
|
||||
if (config->sysclock_source == RCC_PLL) {
|
||||
rcc_clock_tree.sysclk_mhz = rcc_clock_tree.pll1.p_mhz;
|
||||
} else if (config->sysclock_source == RCC_HSE) {
|
||||
rcc_clock_tree.sysclk_mhz = config->hse_frequency / HZ_PER_MHZ;
|
||||
} else {
|
||||
rcc_clock_tree.sysclk_mhz = RCC_HSI_BASE_FREQUENCY / HZ_PER_MHZ;
|
||||
}
|
||||
|
||||
/* PLL's are set, now we need to get everything switched over the correct domains. */
|
||||
rcc_clock_setup_domain1(config);
|
||||
rcc_clock_setup_domain2(config);
|
||||
rcc_clock_setup_domain3(config);
|
||||
|
||||
/* TODO: Configure custom kernel mappings. */
|
||||
|
||||
/* Domains dividers are all configured, now we can switchover to PLL. */
|
||||
RCC_CFGR |= RCC_CFGR_SW_PLL1;
|
||||
uint32_t cfgr_sws = ((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK);
|
||||
while(cfgr_sws != RCC_CFGR_SWS_PLL1) {
|
||||
cfgr_sws = ((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rcc_get_bus_clk_freq(enum rcc_clock_source source) {
|
||||
uint32_t clksel;
|
||||
switch (source) {
|
||||
case RCC_SYSCLK:
|
||||
return rcc_clock_tree.sysclk_mhz * HZ_PER_MHZ;
|
||||
case RCC_CPUCLK:
|
||||
case RCC_SYSTICKCLK:
|
||||
return rcc_clock_tree.cpu_mhz * HZ_PER_MHZ;
|
||||
case RCC_AHBCLK:
|
||||
case RCC_HCLK3:
|
||||
return rcc_clock_tree.hclk_mhz * HZ_PER_MHZ;
|
||||
case RCC_APB1CLK:
|
||||
return rcc_clock_tree.per.pclk1_mhz * HZ_PER_MHZ;
|
||||
case RCC_APB2CLK:
|
||||
return rcc_clock_tree.per.pclk2_mhz * HZ_PER_MHZ;
|
||||
case RCC_APB3CLK:
|
||||
return rcc_clock_tree.per.pclk3_mhz * HZ_PER_MHZ;
|
||||
case RCC_APB4CLK:
|
||||
return rcc_clock_tree.per.pclk4_mhz * HZ_PER_MHZ;
|
||||
case RCC_PERCLK:
|
||||
clksel = (RCC_D1CCIPR >> RCC_D1CCIPR_CKPERSEL_SHIFT) & RCC_D1CCIPR_CKPERSEL_MASK;
|
||||
if (clksel == RCC_D1CCIPR_CKPERSEL_HSI) {
|
||||
return RCC_HSI_BASE_FREQUENCY;
|
||||
} else if (clksel == RCC_D1CCIPR_CKPERSEL_HSE) {
|
||||
return rcc_clock_tree.hse_khz * HZ_PER_KHZ;
|
||||
} else {
|
||||
return 0U;
|
||||
}
|
||||
default:
|
||||
cm3_assert_not_reached();
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rcc_get_usart_clk_freq(uint32_t usart)
|
||||
{
|
||||
uint32_t clksel, pclk;
|
||||
if (usart == USART1_BASE || usart == USART6_BASE) {
|
||||
pclk = rcc_clock_tree.per.pclk2_mhz * HZ_PER_MHZ;;
|
||||
clksel = (RCC_D2CCIP2R >> RCC_D2CCIP2R_USART16SEL_SHIFT) & RCC_D2CCIP2R_USARTSEL_MASK;
|
||||
} else {
|
||||
pclk = rcc_clock_tree.per.pclk1_mhz * HZ_PER_MHZ;
|
||||
clksel = (RCC_D2CCIP2R >> RCC_D2CCIP2R_USART234578SEL_SHIFT) & RCC_D2CCIP2R_USARTSEL_MASK;
|
||||
}
|
||||
|
||||
/* Based on extracted clksel value, return the clock. */
|
||||
if (clksel == RCC_D2CCIP2R_USARTSEL_PCLK) {
|
||||
return pclk;
|
||||
} else if (clksel == RCC_D2CCIP2R_USARTSEL_PLL2Q) {
|
||||
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP2R_USARTSEL_PLL3Q) {
|
||||
return rcc_clock_tree.pll3.q_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP2R_USARTSEL_HSI) {
|
||||
return RCC_HSI_BASE_FREQUENCY;
|
||||
} else {
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rcc_get_timer_clk_freq(uint32_t timer __attribute__((unused)))
|
||||
{
|
||||
if (timer >= LPTIM2_BASE && timer <= LPTIM5_BASE) {
|
||||
/* TODO: Read LPTIMxSEL values from D3CCIPR to determine clock source. */
|
||||
return rcc_clock_tree.per.pclk4_mhz * HZ_PER_MHZ;
|
||||
} else if (timer >= TIM1_BASE && timer <= HRTIM_BASE) {
|
||||
return rcc_clock_tree.per.pclk2_mhz * HZ_PER_MHZ;
|
||||
} else {
|
||||
return rcc_clock_tree.per.pclk1_mhz * HZ_PER_MHZ;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rcc_get_i2c_clk_freq(uint32_t i2c)
|
||||
{
|
||||
if (i2c == I2C4_BASE) {
|
||||
/* TODO: Read I2C4SEL from D3CCIPR to determine clock source. */
|
||||
return rcc_clock_tree.per.pclk3_mhz * HZ_PER_MHZ;
|
||||
} else {
|
||||
/* TODO: Read I2C123SEL from D2CCIP2R to determine clock source. */
|
||||
return rcc_clock_tree.per.pclk1_mhz * HZ_PER_MHZ;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rcc_get_spi_clk_freq(uint32_t spi)
|
||||
{
|
||||
if (spi == SPI4_BASE || spi == SPI5_BASE) {
|
||||
uint32_t clksel =
|
||||
(RCC_D2CCIP1R >> RCC_D2CCIP1R_SPI45SEL_SHIFT) & RCC_D2CCIP1R_SPI45SEL_MASK;
|
||||
if (clksel == RCC_D2CCIP1R_SPI45SEL_APB4){
|
||||
return rcc_clock_tree.per.pclk2_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_PLL2Q){
|
||||
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_PLL3Q){
|
||||
return rcc_clock_tree.pll3.q_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_HSI){
|
||||
return RCC_HSI_BASE_FREQUENCY;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI45SEL_HSE) {
|
||||
return rcc_clock_tree.hse_khz * HZ_PER_KHZ;
|
||||
} else {
|
||||
return 0U;
|
||||
}
|
||||
} else {
|
||||
uint32_t clksel =
|
||||
(RCC_D2CCIP1R >> RCC_D2CCIP1R_SPI123SEL_SHIFT) & RCC_D2CCIP1R_SPI123SEL_MASK;
|
||||
if (clksel == RCC_D2CCIP1R_SPI123SEL_PLL1Q) {
|
||||
return rcc_clock_tree.pll1.q_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI123SEL_PLL2P) {
|
||||
return rcc_clock_tree.pll2.p_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI123SEL_PLL3P) {
|
||||
return rcc_clock_tree.pll3.p_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_SPI123SEL_PERCK) {
|
||||
return rcc_get_bus_clk_freq(RCC_PERCLK);
|
||||
} else {
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rcc_get_fdcan_clk_freq(uint32_t fdcan __attribute__((unused)))
|
||||
{
|
||||
uint32_t clksel =
|
||||
(RCC_D2CCIP1R >> RCC_D2CCIP1R_FDCANSEL_SHIFT) & RCC_D2CCIP1R_FDCANSEL_MASK;
|
||||
if (clksel == RCC_D2CCIP1R_FDCANSEL_HSE) {
|
||||
return rcc_clock_tree.hse_khz * HZ_PER_KHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_FDCANSEL_PLL1Q) {
|
||||
return rcc_clock_tree.pll1.q_mhz * HZ_PER_MHZ;
|
||||
} else if (clksel == RCC_D2CCIP1R_FDCANSEL_PLL2Q) {
|
||||
return rcc_clock_tree.pll2.q_mhz * HZ_PER_MHZ;
|
||||
} else {
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
void rcc_set_peripheral_clk_sel(uint32_t periph, uint32_t sel) {
|
||||
volatile uint32_t *reg;
|
||||
uint32_t mask;
|
||||
uint32_t val;
|
||||
|
||||
switch (periph) {
|
||||
case FDCAN1_BASE:
|
||||
case FDCAN2_BASE:
|
||||
reg = &RCC_D2CCIP1R;
|
||||
mask = RCC_D2CCIP1R_FDCANSEL_MASK << RCC_D2CCIP1R_FDCANSEL_SHIFT;
|
||||
val = sel << RCC_D2CCIP1R_FDCANSEL_SHIFT;
|
||||
break;
|
||||
case RNG_BASE:
|
||||
reg = &RCC_D2CCIP2R;
|
||||
mask = RCC_D2CCIP2R_RNGSEL_MASK << RCC_D2CCIP2R_RNGSEL_SHIFT;
|
||||
val = sel << RCC_D2CCIP2R_RNGSEL_SHIFT;
|
||||
break;
|
||||
case SPI1_BASE:
|
||||
case SPI2_BASE:
|
||||
case SPI3_BASE:
|
||||
reg = &RCC_D2CCIP2R;
|
||||
mask = RCC_D2CCIP1R_SPI123SEL_MASK << RCC_D2CCIP1R_SPI123SEL_SHIFT;
|
||||
val = sel << RCC_D2CCIP1R_SPI123SEL_SHIFT;
|
||||
break;
|
||||
case SPI4_BASE:
|
||||
case SPI5_BASE:
|
||||
reg = &RCC_D2CCIP1R;
|
||||
mask = RCC_D2CCIP1R_SPI45SEL_MASK << RCC_D2CCIP1R_SPI45SEL_SHIFT;
|
||||
val = sel << RCC_D2CCIP1R_SPI45SEL_SHIFT;
|
||||
break;
|
||||
case USART1_BASE:
|
||||
case USART6_BASE:
|
||||
reg = &RCC_D2CCIP2R;
|
||||
mask = RCC_D2CCIP2R_USARTSEL_MASK << RCC_D2CCIP2R_USART16SEL_SHIFT;
|
||||
val = sel << RCC_D2CCIP2R_USART16SEL_SHIFT;
|
||||
break;
|
||||
case USART2_BASE:
|
||||
case USART3_BASE:
|
||||
case UART4_BASE:
|
||||
case UART5_BASE:
|
||||
case UART7_BASE:
|
||||
case UART8_BASE:
|
||||
reg = &RCC_D2CCIP2R;
|
||||
mask = RCC_D2CCIP2R_USARTSEL_MASK << RCC_D2CCIP2R_USART234578SEL_SHIFT;
|
||||
val = sel << RCC_D2CCIP2R_USART234578SEL_SHIFT;
|
||||
break;
|
||||
|
||||
default:
|
||||
cm3_assert_not_reached();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the register value by masking and oring in new values.
|
||||
uint32_t regval = (*reg & mask) | val;
|
||||
*reg = regval;
|
||||
}
|
||||
|
||||
void rcc_set_fdcan_clksel(uint8_t clksel) {
|
||||
RCC_D2CCIP1R &= ~(RCC_D2CCIP1R_FDCANSEL_MASK << RCC_D2CCIP1R_FDCANSEL_SHIFT);
|
||||
RCC_D2CCIP1R |= clksel << RCC_D2CCIP1R_FDCANSEL_SHIFT;
|
||||
}
|
||||
|
||||
void rcc_set_rng_clksel(uint8_t clksel) {
|
||||
RCC_D2CCIP2R &= ~(RCC_D2CCIP2R_RNGSEL_MASK << RCC_D2CCIP2R_RNGSEL_SHIFT);
|
||||
RCC_D2CCIP2R |= clksel << RCC_D2CCIP2R_RNGSEL_SHIFT;
|
||||
}
|
||||
|
||||
void rcc_set_spi123_clksel(uint8_t clksel) {
|
||||
RCC_D2CCIP1R &= ~(RCC_D2CCIP1R_SPI123SEL_MASK << RCC_D2CCIP1R_SPI123SEL_SHIFT);
|
||||
RCC_D2CCIP1R |= clksel << RCC_D2CCIP1R_SPI123SEL_SHIFT;
|
||||
}
|
||||
|
||||
void rcc_set_spi45_clksel(uint8_t clksel) {
|
||||
RCC_D2CCIP1R &= ~(RCC_D2CCIP1R_SPI45SEL_MASK << RCC_D2CCIP1R_SPI45SEL_SHIFT);
|
||||
RCC_D2CCIP1R |= clksel << RCC_D2CCIP1R_SPI45SEL_SHIFT;
|
||||
}
|
||||
27
libopencm3/lib/stm32/h7/vector_chipset.c
Normal file
27
libopencm3/lib/stm32/h7/vector_chipset.c
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of the libopencm3 project.
|
||||
*
|
||||
* Copyright (C) 2010 Piotr Esden-Tempski <piotr@esden.net>
|
||||
* Copyright (C) 2011 Fergus Noble <fergusnoble@gmail.com>
|
||||
*
|
||||
* This library is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libopencm3/cm3/scb.h>
|
||||
|
||||
static void pre_main(void)
|
||||
{
|
||||
/* Enable access to Floating-Point coprocessor. */
|
||||
SCB_CPACR |= SCB_CPACR_FULL * (SCB_CPACR_CP10 | SCB_CPACR_CP11);
|
||||
}
|
||||
Reference in New Issue
Block a user