drivers: can: stm32h7: fdcan: add support for domain clock and divider

Add support for specifying the domain/kernel clock along with a common
clock divider for the STM32H7 CAN controller driver via devicetree.

Previously, the driver only supported using the PLL1_Q clock for
domain/kernel clock, but now the driver defaults to the HSE clock, which is
the chip default. Update existing boards to continue to use the PLL1_Q
clock.

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
diff --git a/boards/arm/arduino_giga_r1/arduino_giga_r1_m7.dts b/boards/arm/arduino_giga_r1/arduino_giga_r1_m7.dts
index ef3544e..9cc042f 100644
--- a/boards/arm/arduino_giga_r1/arduino_giga_r1_m7.dts
+++ b/boards/arm/arduino_giga_r1/arduino_giga_r1_m7.dts
@@ -139,6 +139,8 @@
 	status = "okay";
 	pinctrl-0 = <&fdcan2_tx_pb13 &fdcan2_rx_pb5>;
 	pinctrl-names = "default";
+	clocks = <&rcc STM32_CLOCK_BUS_APB1_2 0x00000100>,
+		 <&rcc STM32_SRC_PLL1_Q FDCAN_SEL(1)>;
 	bus-speed = <125000>;
 	bus-speed-data = <1000000>;
 };
diff --git a/boards/arm/arduino_portenta_h7/arduino_portenta_h7-common.dtsi b/boards/arm/arduino_portenta_h7/arduino_portenta_h7-common.dtsi
index 87a22fa..87d85f2 100644
--- a/boards/arm/arduino_portenta_h7/arduino_portenta_h7-common.dtsi
+++ b/boards/arm/arduino_portenta_h7/arduino_portenta_h7-common.dtsi
@@ -105,6 +105,8 @@
 &fdcan1 {
 	pinctrl-0 = <&fdcan1_rx_pb8 &fdcan1_tx_ph13>;
 	pinctrl-names = "default";
+	clocks = <&rcc STM32_CLOCK_BUS_APB1_2 0x00000100>,
+		 <&rcc STM32_SRC_PLL1_Q FDCAN_SEL(1)>;
 };
 
 &rtc {
diff --git a/boards/arm/nucleo_h743zi/nucleo_h743zi.dts b/boards/arm/nucleo_h743zi/nucleo_h743zi.dts
index f939644..ad88d87 100644
--- a/boards/arm/nucleo_h743zi/nucleo_h743zi.dts
+++ b/boards/arm/nucleo_h743zi/nucleo_h743zi.dts
@@ -170,6 +170,8 @@
 &fdcan1 {
 	pinctrl-0 = <&fdcan1_rx_pd0 &fdcan1_tx_pd1>;
 	pinctrl-names = "default";
+	clocks = <&rcc STM32_CLOCK_BUS_APB1_2 0x00000100>,
+		 <&rcc STM32_SRC_PLL1_Q FDCAN_SEL(1)>;
 	bus-speed = <125000>;
 	bus-speed-data = <1000000>;
 	status = "okay";
diff --git a/boards/arm/stm32h7b3i_dk/stm32h7b3i_dk.dts b/boards/arm/stm32h7b3i_dk/stm32h7b3i_dk.dts
index 3547017..b8b781b 100644
--- a/boards/arm/stm32h7b3i_dk/stm32h7b3i_dk.dts
+++ b/boards/arm/stm32h7b3i_dk/stm32h7b3i_dk.dts
@@ -152,6 +152,8 @@
 &fdcan1 {
 	pinctrl-0 = <&fdcan1_rx_pa11 &fdcan1_tx_pa12>;
 	pinctrl-names = "default";
+	clocks = <&rcc STM32_CLOCK_BUS_APB1_2 0x00000100>,
+		 <&rcc STM32_SRC_PLL1_Q FDCAN_SEL(1)>;
 	phys = <&transceiver0>;
 	bus-speed = <125000>;
 	bus-speed-data = <1000000>;
diff --git a/drivers/can/can_stm32h7_fdcan.c b/drivers/can/can_stm32h7_fdcan.c
index 9b75932..977da15 100644
--- a/drivers/can/can_stm32h7_fdcan.c
+++ b/drivers/can/can_stm32h7_fdcan.c
@@ -14,18 +14,29 @@
 #include <stm32_ll_rcc.h>
 #include <zephyr/logging/log.h>
 #include <zephyr/irq.h>
+#include <zephyr/sys/util.h>
 
 LOG_MODULE_REGISTER(can_stm32h7, CONFIG_CAN_LOG_LEVEL);
 
 #define DT_DRV_COMPAT st_stm32h7_fdcan
 
+/* This symbol takes the value 1 if one of the device instances */
+/* is configured in dts with a domain clock */
+#if STM32_DT_INST_DEV_DOMAIN_CLOCK_SUPPORT
+#define STM32H7_FDCAN_DOMAIN_CLOCK_SUPPORT 1
+#else
+#define STM32H7_FDCAN_DOMAIN_CLOCK_SUPPORT 0
+#endif
+
 struct can_stm32h7_config {
 	mm_reg_t base;
 	mem_addr_t mrba;
 	mem_addr_t mram;
 	void (*config_irq)(void);
 	const struct pinctrl_dev_config *pcfg;
-	struct stm32_pclken pclken;
+	size_t pclk_len;
+	const struct stm32_pclken *pclken;
+	uint8_t clock_divider;
 };
 
 static int can_stm32h7_read_reg(const struct device *dev, uint16_t reg, uint32_t *val)
@@ -72,6 +83,7 @@
 static int can_stm32h7_get_core_clock(const struct device *dev, uint32_t *rate)
 {
 	const uint32_t rate_tmp = LL_RCC_GetFDCANClockFreq(LL_RCC_FDCAN_CLKSOURCE);
+	uint32_t cdiv;
 
 	ARG_UNUSED(dev);
 
@@ -80,9 +92,12 @@
 		return -EIO;
 	}
 
-	*rate = rate_tmp;
-
-	LOG_DBG("rate=%d", *rate);
+	cdiv = FIELD_GET(FDCANCCU_CCFG_CDIV, FDCAN_CCU->CCFG);
+	if (cdiv == 0U) {
+		*rate = rate_tmp;
+	} else {
+		*rate = rate_tmp / (cdiv << 1U);
+	}
 
 	return 0;
 }
@@ -94,22 +109,32 @@
 	const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
 	int ret;
 
-	LL_RCC_SetFDCANClockSource(LL_RCC_FDCAN_CLKSOURCE_PLL1Q);
-
 	if (!device_is_ready(clk)) {
 		LOG_ERR("clock control device not ready");
 		return -ENODEV;
 	}
 
-	ret = clock_control_on(clk, (clock_control_subsys_t)&stm32h7_cfg->pclken);
+	if (IS_ENABLED(STM32H7_FDCAN_DOMAIN_CLOCK_SUPPORT) && (stm32h7_cfg->pclk_len > 1)) {
+		ret = clock_control_configure(clk,
+				(clock_control_subsys_t)&stm32h7_cfg->pclken[1],
+				NULL);
+		if (ret < 0) {
+			LOG_ERR("Could not select can_stm32fd domain clock");
+			return ret;
+		}
+	}
+
+	ret = clock_control_on(clk, (clock_control_subsys_t)&stm32h7_cfg->pclken[0]);
 	if (ret != 0) {
 		LOG_ERR("failure enabling clock");
 		return ret;
 	}
 
-	if (!LL_RCC_PLL1Q_IsEnabled()) {
-		LOG_ERR("PLL1Q clock must be enabled!");
-		return -EIO;
+	if (stm32h7_cfg->clock_divider != 0U) {
+		can_mcan_enable_configuration_change(dev);
+
+		FDCAN_CCU->CCFG = FDCANCCU_CCFG_BCC |
+			FIELD_PREP(FDCANCCU_CCFG_CDIV, stm32h7_cfg->clock_divider >> 1U);
 	}
 
 	return 0;
@@ -204,16 +229,18 @@
 	PINCTRL_DT_INST_DEFINE(n);					    \
 	CAN_MCAN_DT_INST_CALLBACKS_DEFINE(n, can_stm32h7_cbs_##n);	    \
 									    \
+	static const struct stm32_pclken can_stm32h7_pclken_##n[] =	    \
+					STM32_DT_INST_CLOCKS(n);	    \
+									    \
 	static const struct can_stm32h7_config can_stm32h7_cfg_##n = {	    \
 		.base = CAN_MCAN_DT_INST_MCAN_ADDR(n),			    \
 		.mrba = CAN_MCAN_DT_INST_MRBA(n),			    \
 		.mram = CAN_MCAN_DT_INST_MRAM_ADDR(n),			    \
 		.config_irq = stm32h7_mcan_irq_config_##n,		    \
 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),		    \
-		.pclken = {						    \
-			.enr = DT_INST_CLOCKS_CELL(n, bits),		    \
-			.bus = DT_INST_CLOCKS_CELL(n, bus),		    \
-		},							    \
+		.pclken = can_stm32h7_pclken_##n,			    \
+		.pclk_len = DT_INST_NUM_CLOCKS(n),			    \
+		.clock_divider = DT_INST_PROP_OR(n, clk_divider, 0)	    \
 	};								    \
 									    \
 	static const struct can_mcan_config can_mcan_cfg_##n =		    \
diff --git a/dts/bindings/can/st,stm32h7-fdcan.yaml b/dts/bindings/can/st,stm32h7-fdcan.yaml
index 1d258ea..14f6fad 100644
--- a/dts/bindings/can/st,stm32h7-fdcan.yaml
+++ b/dts/bindings/can/st,stm32h7-fdcan.yaml
@@ -16,3 +16,29 @@
 
   interrupt-names:
     required: true
+
+  clk-divider:
+    type: int
+    enum:
+      - 1
+      - 2
+      - 4
+      - 6
+      - 8
+      - 10
+      - 12
+      - 14
+      - 16
+      - 18
+      - 20
+      - 22
+      - 24
+      - 26
+      - 28
+      - 30
+    description: |
+      Divides the kernel clock giving the time quanta clock that is fed to the FDCAN core
+      (FDCAN_CCU->CCFG CDIV register bits). Note that the divisor is common to all
+      'st,stm32h7-fdcan' instances.
+
+      Divide by 1 is the peripherals reset value and remains set unless this property is configured.