Skip to content

drivers: can: Add Renesas R-Car RSCANFD driver#109782

Open
RICCIARDI-Adrien wants to merge 5 commits into
zephyrproject-rtos:mainfrom
RICCIARDI-Adrien:aricciardi/rcar_add_rscanfd_driver
Open

drivers: can: Add Renesas R-Car RSCANFD driver#109782
RICCIARDI-Adrien wants to merge 5 commits into
zephyrproject-rtos:mainfrom
RICCIARDI-Adrien:aricciardi/rcar_add_rscanfd_driver

Conversation

@RICCIARDI-Adrien

Copy link
Copy Markdown
Contributor

The RSCANFD CAN IP is found in Gen 4 and Gen 5 boards.
It features 2 controllers with 8 independant channels each.
All channels are classic CAN and CAN FD capable.

@henrikbrixandersen

Copy link
Copy Markdown
Member

Reassigned to self as CAN subsystem maintainer.

@RICCIARDI-Adrien RICCIARDI-Adrien force-pushed the aricciardi/rcar_add_rscanfd_driver branch 5 times, most recently from 7e289a3 to 5b2ca97 Compare June 1, 2026 11:18
@RICCIARDI-Adrien

Copy link
Copy Markdown
Contributor Author

The driver has been tested on the upcoming Renesas X5H board (the PRs adding this board support to Zephyr are still in review).

The CAN API tests are passing : west build -p -b rcar_ironhide_x5h/r8a78000/r52 -T drivers.can.api tests/drivers/can/api/

*** Booting Zephyr OS build v4.4.0-3467-g76345d72c192 ***
Running TESTSUITE can_classic
===================================================================
START - test_add_filter
 PASS - test_add_filter in 0.001 seconds
===================================================================
START - test_add_filter_without_callback
 PASS - test_add_filter_without_callback in 0.001 seconds
===================================================================
START - test_add_invalid_ext_filter
E: invalid filter with extended (29-bit) CAN ID 0x1fffffff, CAN ID mask 0x20000000
E: invalid filter with extended (29-bit) CAN ID 0x20000000, CAN ID mask 0x1fffffff
 PASS - test_add_invalid_ext_filter in 0.001 seconds
===================================================================
START - test_add_invalid_null_filter
 PASS - test_add_invalid_null_filter in 0.001 seconds
===================================================================
START - test_add_invalid_std_filter
E: invalid filter with standard (11-bit) CAN ID 0x7ff, CAN ID mask 0x800
E: invalid filter with standard (11-bit) CAN ID 0x800, CAN ID mask 0x7ff
 PASS - test_add_invalid_std_filter in 0.001 seconds
===================================================================
START - test_bitrate_limits
 PASS - test_bitrate_limits in 0.001 seconds
===================================================================
START - test_classic_get_capabilities
 PASS - test_classic_get_capabilities in 0.001 seconds
===================================================================
START - test_filters_added_while_stopped
 PASS - test_filters_added_while_stopped in 0.002 seconds
===================================================================
START - test_filters_preserved_through_bitrate_change
 PASS - test_filters_preserved_through_bitrate_change in 0.003 seconds
===================================================================
START - test_filters_preserved_through_mode_change
 PASS - test_filters_preserved_through_mode_change in 0.003 seconds
===================================================================
START - test_get_core_clock
 PASS - test_get_core_clock in 0.001 seconds
===================================================================
START - test_get_state
 PASS - test_get_state in 0.001 seconds
===================================================================
START - test_invalid_sample_point
 PASS - test_invalid_sample_point in 0.001 seconds
===================================================================
START - test_max_ext_filters
 PASS - test_max_ext_filters in 0.001 seconds
===================================================================
START - test_max_std_filters
 PASS - test_max_std_filters in 0.001 seconds
===================================================================
START - test_receive_timeout
 PASS - test_receive_timeout in 0.101 seconds
===================================================================
START - test_recover
 PASS - test_recover in 0.001 seconds
===================================================================
START - test_recover_while_stopped
 SKIP - test_recover_while_stopped in 0.001 seconds
===================================================================
START - test_reject_ext_id_rtr
 PASS - test_reject_ext_id_rtr in 0.101 seconds
===================================================================
START - test_reject_std_id_rtr
 PASS - test_reject_std_id_rtr in 0.101 seconds
===================================================================
START - test_send_and_forget
 PASS - test_send_and_forget in 0.002 seconds
===================================================================
START - test_send_callback
 PASS - test_send_callback in 0.002 seconds
===================================================================
START - test_send_ext_id_dlc_of_range
E: Classic CAN DLC of 9 exceeds maximum of 8 on can0.
 PASS - test_send_ext_id_dlc_of_range in 0.001 seconds
===================================================================
START - test_send_ext_id_out_of_range
E: invalid frame with extended (29-bit) CAN ID 0x20000000
 PASS - test_send_ext_id_out_of_range in 0.001 seconds
===================================================================
START - test_send_fd_format
 PASS - test_send_fd_format in 0.001 seconds
===================================================================
START - test_send_invalid_dlc
E: Classic CAN DLC of 9 exceeds maximum of 8 on can0.
 PASS - test_send_invalid_dlc in 0.001 seconds
===================================================================
START - test_send_null_frame
 PASS - test_send_null_frame in 0.001 seconds
===================================================================
START - test_send_receive_ext_id
 PASS - test_send_receive_ext_id in 0.004 seconds
===================================================================
START - test_send_receive_ext_id_masked
 PASS - test_send_receive_ext_id_masked in 0.004 seconds
===================================================================
START - test_send_receive_ext_id_rtr
 SKIP - test_send_receive_ext_id_rtr in 0.001 seconds
===================================================================
START - test_send_receive_msgq
 PASS - test_send_receive_msgq in 0.010 seconds
===================================================================
START - test_send_receive_std_id
 PASS - test_send_receive_std_id in 0.004 seconds
===================================================================
START - test_send_receive_std_id_masked
 PASS - test_send_receive_std_id_masked in 0.004 seconds
===================================================================
START - test_send_receive_std_id_no_data
 PASS - test_send_receive_std_id_no_data in 0.001 seconds
===================================================================
START - test_send_receive_std_id_rtr
 SKIP - test_send_receive_std_id_rtr in 0.001 seconds
===================================================================
START - test_send_receive_wrong_id
 PASS - test_send_receive_wrong_id in 0.102 seconds
===================================================================
START - test_send_std_id_dlc_of_range
E: Classic CAN DLC of 9 exceeds maximum of 8 on can0.
 PASS - test_send_std_id_dlc_of_range in 0.001 seconds
===================================================================
START - test_send_std_id_out_of_range
E: invalid frame with standard (11-bit) CAN ID 0x800
 PASS - test_send_std_id_out_of_range in 0.001 seconds
===================================================================
START - test_send_while_stopped
 PASS - test_send_while_stopped in 0.001 seconds
===================================================================
START - test_set_bitrate
 PASS - test_set_bitrate in 0.001 seconds
===================================================================
START - test_set_bitrate_too_high
 PASS - test_set_bitrate_too_high in 0.001 seconds
===================================================================
START - test_set_bitrate_too_low
 SKIP - test_set_bitrate_too_low in 0.001 seconds
===================================================================
START - test_set_bitrate_while_started
 PASS - test_set_bitrate_while_started in 0.001 seconds
===================================================================
START - test_set_mode_while_started
 PASS - test_set_mode_while_started in 0.001 seconds
===================================================================
START - test_set_state_change_callback
 PASS - test_set_state_change_callback in 0.001 seconds
===================================================================
START - test_set_timing_max
 PASS - test_set_timing_max in 0.001 seconds
===================================================================
START - test_set_timing_min
 PASS - test_set_timing_min in 0.001 seconds
===================================================================
START - test_set_timing_while_started
 PASS - test_set_timing_while_started in 0.001 seconds
===================================================================
START - test_start_while_started
 PASS - test_start_while_started in 0.001 seconds
===================================================================
START - test_stop_while_stopped
 PASS - test_stop_while_stopped in 0.001 seconds
===================================================================
TESTSUITE can_classic succeeded
CAN controller does not support device power managementRunning TESTSUITE can_stats
===================================================================
START - test_can_stats_accessors
 PASS - test_can_stats_accessors in 0.001 seconds
===================================================================
TESTSUITE can_stats succeeded
CAN transceiver device not readyRunning TESTSUITE can_utilities
===================================================================
START - test_can_bytes_to_dlc
 PASS - test_can_bytes_to_dlc in 0.001 seconds
===================================================================
START - test_can_dlc_to_bytes
 PASS - test_can_dlc_to_bytes in 0.001 seconds
===================================================================
START - test_can_frame_matches_filter
 PASS - test_can_frame_matches_filter in 0.001 seconds
===================================================================
TESTSUITE can_utilities succeeded
Running TESTSUITE canfd
===================================================================
START - test_canfd_get_capabilities
 PASS - test_canfd_get_capabilities in 0.001 seconds
===================================================================
START - test_filters_preserved_through_classic_to_fd_mode_change
 PASS - test_filters_preserved_through_classic_to_fd_mode_change in 0.005 seconds
===================================================================
START - test_filters_preserved_through_fd_to_classic_mode_change
 PASS - test_filters_preserved_through_fd_to_classic_mode_change in 0.005 seconds
===================================================================
START - test_invalid_sample_point
 PASS - test_invalid_sample_point in 0.001 seconds
===================================================================
START - test_send_fd_dlc_out_of_range
E: CAN FD DLC of 16 exceeds maximum of 15 on can0.
 PASS - test_send_fd_dlc_out_of_range in 0.001 seconds
===================================================================
START - test_send_fd_incorrect_esi
E: Unsupported CAN frame flags 0x14 for can0.
 PASS - test_send_fd_incorrect_esi in 0.001 seconds
===================================================================
START - test_send_receive_classic
 PASS - test_send_receive_classic in 0.004 seconds
===================================================================
START - test_send_receive_fd
 PASS - test_send_receive_fd in 0.003 seconds
===================================================================
START - test_send_receive_mixed
 PASS - test_send_receive_mixed in 0.003 seconds
===================================================================
START - test_set_bitrate_data_too_low
 SKIP - test_set_bitrate_data_too_low in 0.001 seconds
===================================================================
START - test_set_bitrate_data_while_started
 PASS - test_set_bitrate_data_while_started in 0.001 seconds
===================================================================
START - test_set_bitrate_too_high
 PASS - test_set_bitrate_too_high in 0.001 seconds
===================================================================
START - test_set_timing_data_max
 PASS - test_set_timing_data_max in 0.001 seconds
===================================================================
START - test_set_timing_data_min
 PASS - test_set_timing_data_min in 0.001 seconds
===================================================================
START - test_set_timing_data_while_started
 PASS - test_set_timing_data_while_started in 0.001 seconds
===================================================================
TESTSUITE canfd succeeded

------ TESTSUITE SUMMARY START ------

SUITE PASS - 100.00% [can_classic]: pass = 46, fail = 0, skip = 4, total = 50 duration = 0.479 seconds
 - PASS - [can_classic.test_add_filter] duration = 0.001 seconds
 - PASS - [can_classic.test_add_filter_without_callback] duration = 0.001 seconds
 - PASS - [can_classic.test_add_invalid_ext_filter] duration = 0.001 seconds
 - PASS - [can_classic.test_add_invalid_null_filter] duration = 0.001 seconds
 - PASS - [can_classic.test_add_invalid_std_filter] duration = 0.001 seconds
 - PASS - [can_classic.test_bitrate_limits] duration = 0.001 seconds
 - PASS - [can_classic.test_classic_get_capabilities] duration = 0.001 seconds
 - PASS - [can_classic.test_filters_added_while_stopped] duration = 0.002 seconds
 - PASS - [can_classic.test_filters_preserved_through_bitrate_change] duration = 0.003 seconds
 - PASS - [can_classic.test_filters_preserved_through_mode_change] duration = 0.003 seconds
 - PASS - [can_classic.test_get_core_clock] duration = 0.001 seconds
 - PASS - [can_classic.test_get_state] duration = 0.001 seconds
 - PASS - [can_classic.test_invalid_sample_point] duration = 0.001 seconds
 - PASS - [can_classic.test_max_ext_filters] duration = 0.001 seconds
 - PASS - [can_classic.test_max_std_filters] duration = 0.001 seconds
 - PASS - [can_classic.test_receive_timeout] duration = 0.101 seconds
 - PASS - [can_classic.test_recover] duration = 0.001 seconds
 - SKIP - [can_classic.test_recover_while_stopped] duration = 0.001 seconds
 - PASS - [can_classic.test_reject_ext_id_rtr] duration = 0.101 seconds
 - PASS - [can_classic.test_reject_std_id_rtr] duration = 0.101 seconds
 - PASS - [can_classic.test_send_and_forget] duration = 0.002 seconds
 - PASS - [can_classic.test_send_callback] duration = 0.002 seconds
 - PASS - [can_classic.test_send_ext_id_dlc_of_range] duration = 0.001 seconds
 - PASS - [can_classic.test_send_ext_id_out_of_range] duration = 0.001 seconds
 - PASS - [can_classic.test_send_fd_format] duration = 0.001 seconds
 - PASS - [can_classic.test_send_invalid_dlc] duration = 0.001 seconds
 - PASS - [can_classic.test_send_null_frame] duration = 0.001 seconds
 - PASS - [can_classic.test_send_receive_ext_id] duration = 0.004 seconds
 - PASS - [can_classic.test_send_receive_ext_id_masked] duration = 0.004 seconds
 - SKIP - [can_classic.test_send_receive_ext_id_rtr] duration = 0.001 seconds
 - PASS - [can_classic.test_send_receive_msgq] duration = 0.010 seconds
 - PASS - [can_classic.test_send_receive_std_id] duration = 0.004 seconds
 - PASS - [can_classic.test_send_receive_std_id_masked] duration = 0.004 seconds
 - PASS - [can_classic.test_send_receive_std_id_no_data] duration = 0.001 seconds
 - SKIP - [can_classic.test_send_receive_std_id_rtr] duration = 0.001 seconds
 - PASS - [can_classic.test_send_receive_wrong_id] duration = 0.102 seconds
 - PASS - [can_classic.test_send_std_id_dlc_of_range] duration = 0.001 seconds
 - PASS - [can_classic.test_send_std_id_out_of_range] duration = 0.001 seconds
 - PASS - [can_classic.test_send_while_stopped] duration = 0.001 seconds
 - PASS - [can_classic.test_set_bitrate] duration = 0.001 seconds
 - PASS - [can_classic.test_set_bitrate_too_high] duration = 0.001 seconds
 - SKIP - [can_classic.test_set_bitrate_too_low] duration = 0.001 seconds
 - PASS - [can_classic.test_set_bitrate_while_started] duration = 0.001 seconds
 - PASS - [can_classic.test_set_mode_while_started] duration = 0.001 seconds
 - PASS - [can_classic.test_set_state_change_callback] duration = 0.001 seconds
 - PASS - [can_classic.test_set_timing_max] duration = 0.001 seconds
 - PASS - [can_classic.test_set_timing_min] duration = 0.001 seconds
 - PASS - [can_classic.test_set_timing_while_started] duration = 0.001 seconds
 - PASS - [can_classic.test_start_while_started] duration = 0.001 seconds
 - PASS - [can_classic.test_stop_while_stopped] duration = 0.001 seconds

SUITE SKIP -   0.00% [can_powermgmt]: pass = 0, fail = 0, skip = 3, total = 3 duration = 0.000 seconds
 - SKIP - [can_powermgmt.test_suspend_resume] duration = 0.000 seconds
 - SKIP - [can_powermgmt.test_suspend_while_filters_added] duration = 0.000 seconds
 - SKIP - [can_powermgmt.test_suspend_while_started] duration = 0.000 seconds

SUITE PASS - 100.00% [can_stats]: pass = 1, fail = 0, skip = 0, total = 1 duration = 0.001 seconds
 - PASS - [can_stats.test_can_stats_accessors] duration = 0.001 seconds

SUITE SKIP -   0.00% [can_transceiver]: pass = 0, fail = 0, skip = 1, total = 1 duration = 0.000 seconds
 - SKIP - [can_transceiver.test_get_transceiver] duration = 0.000 seconds

SUITE PASS - 100.00% [can_utilities]: pass = 3, fail = 0, skip = 0, total = 3 duration = 0.003 seconds
 - PASS - [can_utilities.test_can_bytes_to_dlc] duration = 0.001 seconds
 - PASS - [can_utilities.test_can_dlc_to_bytes] duration = 0.001 seconds
 - PASS - [can_utilities.test_can_frame_matches_filter] duration = 0.001 seconds

SUITE PASS - 100.00% [canfd]: pass = 14, fail = 0, skip = 1, total = 15 duration = 0.030 seconds
 - PASS - [canfd.test_canfd_get_capabilities] duration = 0.001 seconds
 - PASS - [canfd.test_filters_preserved_through_classic_to_fd_mode_change] duration = 0.005 seconds
 - PASS - [canfd.test_filters_preserved_through_fd_to_classic_mode_change] duration = 0.005 seconds
 - PASS - [canfd.test_invalid_sample_point] duration = 0.001 seconds
 - PASS - [canfd.test_send_fd_dlc_out_of_range] duration = 0.001 seconds
 - PASS - [canfd.test_send_fd_incorrect_esi] duration = 0.001 seconds
 - PASS - [canfd.test_send_receive_classic] duration = 0.004 seconds
 - PASS - [canfd.test_send_receive_fd] duration = 0.003 seconds
 - PASS - [canfd.test_send_receive_mixed] duration = 0.003 seconds
 - SKIP - [canfd.test_set_bitrate_data_too_low] duration = 0.001 seconds
 - PASS - [canfd.test_set_bitrate_data_while_started] duration = 0.001 seconds
 - PASS - [canfd.test_set_bitrate_too_high] duration = 0.001 seconds
 - PASS - [canfd.test_set_timing_data_max] duration = 0.001 seconds
 - PASS - [canfd.test_set_timing_data_min] duration = 0.001 seconds
 - PASS - [canfd.test_set_timing_data_while_started] duration = 0.001 seconds

------ TESTSUITE SUMMARY END ------

===================================================================
PROJECT EXECUTION SUCCESSFUL

@RICCIARDI-Adrien RICCIARDI-Adrien marked this pull request as ready for review June 1, 2026 12:19
@RICCIARDI-Adrien RICCIARDI-Adrien changed the title [DRAFT] drivers: can: Add Renesas R-Car RSCANFD driver drivers: can: Add Renesas R-Car RSCANFD driver Jun 1, 2026
@RICCIARDI-Adrien RICCIARDI-Adrien force-pushed the aricciardi/rcar_add_rscanfd_driver branch from 5b2ca97 to de3695f Compare June 5, 2026 22:00
Comment thread dts/bindings/can/renesas,rcar-rscanfd.yaml
Comment thread drivers/can/can_rcar_rscanfd.c Outdated
Comment thread drivers/can/can_rcar_rscanfd.c
Comment thread drivers/can/can_rcar_rscanfd.c Outdated
can_rcar_rscanfd_configure_timing_data(dev, &timing);
#endif

config->configure_irq();

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.

Could the IRQ be connected/enabled only after the instance data has been fully
initialized and after stale TX/RX/error flags have been cleared?

Right now configure_irq() is called before the mutexes/semaphore are
initialized and before the channel is registered. Also, the TX path in the ISR
unconditionally calls data->tx_callback(). The CAN core guarantees a non-NULL
callback for a normal can_send() path, but a stale/pending TX interrupt before
the first send would still leave data->tx_callback == NULL.

At minimum I think the driver should initialize the software state first and
clear any pending hardware interrupt flags before enabling the IRQ.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a relevant remark. As I could manage to avoid any global controller interrupt, I updated the code to enable a channel interrupt only when the channel has been started, and to disable the channel interrupt when the channel is stopped. This way, a channel ignores interrupts while it is not started, this could save some cycles.

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.

Thanks, moving IRQ_CONNECT after the software state initialization addresses the
original initialization-order concern.

One remaining question: should the IRQ controller line really be used as the
runtime channel start/stop gate?

I would normally expect IRQ_CONNECT/irq_enable() to be part of the device
initialization path, after stale peripheral status has been cleared. Then
start()/stop() would control interrupt generation through the RSCANFD interrupt
enable bits in the device registers.

Using irq_enable()/irq_disable() in start()/stop() makes the IRQ controller line
part of the CAN channel runtime state. Is there a reason this cannot be handled
only through the controller's own interrupt mask/enable registers instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's correct, enabling or disabling the IRQ line (or even modifying the IP interrupt registers) should even not be needed, because the CAN IP is in halt mode when stopped. Thus, no interrupt will be serviced because nothing can be transmitted (there is also a software check in can_rcar_rscanfd_send()) or received. I reverted these changes back.

if (data->rx_callback[i] == NULL) {
data->rx_callback_user_data[i] = user_data;
data->filter[i] = *filter;
data->rx_callback[i] = callback;

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.

rx_mutex does not protect the RX filter table from the RX ISR. The ISR
traverses rx_callback[], rx_callback_user_data[] and filter[] without
taking that mutex, so a filter can be removed or reused while the ISR is in the
middle of matching it.

For example, remove_rx_filter() can clear rx_callback[i] after the ISR has
already observed it as non-NULL, and a later add_rx_filter() can reuse the same
slot before the ISR reads filter[i] / rx_callback_user_data[i] or invokes the
callback. This can result in callbacks after removal, or in a mixed old/new
filter entry being used.

Should filter table updates be protected with IRQ masking or an ISR-safe lock,
or should the driver document that callbacks may still run after removal and
that slots must not be immediately reused?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Another very good catch ! rx_mutex is a relic that should have been gone. I used the inst_mutex instead to protect the add_rx_filter() and remove_rx_filter() from concurrent accesses from different threads, and also masked the IRQs when each of these functions is accessing the variables shared with the IRQ handler.

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.

Thanks, this addresses part of the original issue, but I am still not sure this
is sufficient.

add_rx_filter() and remove_rx_filter() now unconditionally re-enable the channel
IRQ after updating the filter table. This can enable the IRQ while the channel is
stopped, which seems to break the new invariant that channel IRQs are enabled
only from start() and disabled from stop().

There is also a possible SMP aspect here. Nothing seems to restrict this driver
to a single-core Cortex-R configuration only; the same IP could be used from
application cores on an SMP system. In that case, disabling the IRQ line while
holding inst_mutex does not obviously serialize against an ISR that is already
running on another CPU, and the ISR does not take inst_mutex.

Would it be better to use an ISR-safe synchronization primitive for the filter
table, or at least preserve the previous IRQ enable state and only re-enable the
IRQ when the channel was already started?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Your concerns are legitimate. Let's indeed make the behavior more correct and robust ! I have updated the code.

Comment thread drivers/can/can_rcar_rscanfd.c Outdated
@RICCIARDI-Adrien RICCIARDI-Adrien force-pushed the aricciardi/rcar_add_rscanfd_driver branch from de3695f to e631339 Compare June 8, 2026 15:10
@RICCIARDI-Adrien

Copy link
Copy Markdown
Contributor Author

@xakep-amatop Thanks a lot for your thorough and very efficient reviews !

@RICCIARDI-Adrien RICCIARDI-Adrien force-pushed the aricciardi/rcar_add_rscanfd_driver branch from e631339 to a3afa03 Compare June 8, 2026 15:17
@RICCIARDI-Adrien RICCIARDI-Adrien force-pushed the aricciardi/rcar_add_rscanfd_driver branch from a3afa03 to 1ed1a26 Compare June 12, 2026 09:26
The RSCANFD is capable of CAN and CAN FD communications.
It features 8 channels per controller.

The controller itself is referred as "global", and there is a specific
binding for the channels.

Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
@RICCIARDI-Adrien

RICCIARDI-Adrien commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

This PR is fully complete now that the X5H board support has been merged. I could add all the required device tree nodes.

The samples/drivers/can/counter sample is a simple way to test CAN transmission and reception with upstream Zephyr tools.
You need to apply this little patch to circumvent some current SCP limitations :

diff --git a/samples/drivers/can/counter/prj.conf b/samples/drivers/can/counter/prj.conf
index 1b7858867b6..878df62c961 100644
--- a/samples/drivers/can/counter/prj.conf
+++ b/samples/drivers/can/counter/prj.conf
@@ -11,3 +11,8 @@ CONFIG_STATS=y
 CONFIG_STATS_NAMES=y
 CONFIG_STATS_SHELL=y
 CONFIG_CAN_STATS=y
+
+CONFIG_ARM_SCMI_POLLING_ONLY=y
+CONFIG_PINCTRL_RCAR_SCMI=n
+CONFIG_PM_DEVICE=y
+CONFIG_LOOPBACK_MODE=n

@henrikbrixandersen

Copy link
Copy Markdown
Member

The samples/drivers/can/counter sample is a simple way to test CAN transmission and reception with upstream Zephyr tools.

Samples are not tests. The correct way to verify this is to use the test suites under test/drivers/can/*.

@RICCIARDI-Adrien

Copy link
Copy Markdown
Contributor Author

Samples are not tests. The correct way to verify this is to use the test suites under test/drivers/can/*.

Sure, the test suites have also been run. The result of the API test is identical to the one in a previous comment of this PR : #109782 (comment).

@henrikbrixandersen henrikbrixandersen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please also add a build-only test to tests/drivers/build_all/can/.

Comment on lines +20 to +22
# CAN-related configurations
CONFIG_CAN=y
CONFIG_CAN_FD_MODE=y

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These should not be set in a board default configuration. Only options needed for booting the board with a console should be there (e.g. GPIO should not be there either, unless the board needs a given GPIO (hog) to be set for booting, same for I2C).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK, fixed for the CAN part. Other unwanted configuration options will require a new PR to be removed.

zephyr,sram = &sram0;
zephyr,console = &hscif1;
zephyr,shell-uart = &hscif1;
zephyr,canbus = &can0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You need to add can to boards/renesas/rcar_ironhide_x5h/rcar_ironhide_x5h_r8a78000_r52.yaml as well for this board to be picked up by twister for any CAN tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for spotting this, it's now fixed.

Comment thread drivers/can/Kconfig.rcar Outdated
config CAN_RCAR_MAX_FILTERS
int "Maximum number of concurrent active filters"
depends on CAN_RCAR
depends on CAN_RCAR || CAN_RCAR_RSCANFD

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would suggest introducing a CAN_RCAR_RSCANFD_MAX_FILTERS instead to keep the namespaces.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK, done.

Comment on lines +303 to +324
static inline int can_rcar_rscanfd_busy_wait(mem_addr_t reg, uint32_t bit_mask, bool expect_one)
{
const int MAX_RETRIES = 256; /* Don't wait for too long */
int i;
uint32_t val;

for (i = 0; i < MAX_RETRIES; i++) {
val = sys_read32(reg) & bit_mask;
if (!expect_one) {
val = !val;
}
if (val) {
return 0;
}

k_sleep(K_USEC(10));
}

LOG_DBG("Busy wait timed out. reg=0x%08lX, bit_mask=%08X, expect_one=%d",
reg, bit_mask, expect_one);
return -EAGAIN;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please investigate using the WAIT_FOR() macro from include/zephyr/sys/util.h here instead.

}
state_mask <<= RSCANFD_CFDCFCCN_CFE_SHIFT;

/* The first Common FIFO dedicated to the channel is used for transmission */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So this uses a FIFO for TX? This is usually not good, as CAN frames should not be transmitted in FIFO order, but rather in the priority order given by the CAN IDs of the frames. Please clarify if this FIFO implements priority ordering.

RX path should be a FIFO, if possible.

Comment on lines +977 to +979
if (frame->flags & CAN_FRAME_ESI) {
val |= RSCANFD_CFDCFFDCSTSBN_CFESI;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Software should not be able to set this bit on transmitted frames; that's up to the controller hardware.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The controller hardware automatically overwrites this bit if it is in the error passive state to tell that the node is in error passive state. However, when the node is not in error passive state, the user defined bit value is transmitted (a value of 0 means that the controller is in error active state).
I'm then forcing the bit to 0, so the controller takes indeed care of setting it when the node becomes error passive.

Comment on lines +1133 to +1141
/* Enable the transceiver delay compensation for high speeds */
bit_rate = (CAN_RCAR_RSCANFD_MODULE_CLOCK_RATE / timing->prescaler) /
(1 + timing->prop_seg + timing->phase_seg1 + timing->phase_seg2);
if (bit_rate >= 5000000) {
val = can_rcar_rscanfd_read(dev, base_offset + RSCANFD_CFDCNFDCFG);
/* Use the measured compensation + an offset of 0 */
val &= ~RSCANFD_CFDCNFDCFG_TDCOC;
val |= RSCANFD_CFDCNFDCFG_TDCE;
can_rcar_rscanfd_write(dev, base_offset + RSCANFD_CFDCNFDCFG, val);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please look into using CAN_CALC_TDCO().

Comment on lines +1185 to +1193
while (1) {
val = can_rcar_rscanfd_read(dev, base_offset);
if (val & bit_mask) {
val &= ~bit_mask;
can_rcar_rscanfd_write(dev, base_offset, val);
} else {
break;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

WAIT_FOR()?

return 0;
}

static DEVICE_API(can, can_rcar_rscanfd_driver_api) = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How about CAN bus recovery? How does this core handle that?

Comment on lines +1680 to +1683
BUILD_ASSERT(CONFIG_CLOCK_CONTROL_INIT_PRIORITY < CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
"The clocks must be initialized before the CAN controller.");
BUILD_ASSERT(CONFIG_KERNEL_INIT_PRIORITY_DEVICE < CONFIG_CAN_INIT_PRIORITY,
"The CAN controller must be initialized before its channels.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this automatically ensured based on devicetree ordinals?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is, I get the Device initialization priority validation failed, the sequence of initialization calls does not match the devicetree dependencies. message if I set the CONFIG_CAN_INIT_PRIORITY thread priority lower than the CONFIG_KERNEL_INIT_PRIORITY_DEVICE thread priority one.

The asserts just make the reason clearer, but I can remove the asserts if you prefer.

@RICCIARDI-Adrien

Copy link
Copy Markdown
Contributor Author

Please also add a build-only test to tests/drivers/build_all/can/.

Done !

The RSCANFD is capable of CAN and CAN FD communications.
It features 8 channels per controller.

Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
The SoC features two RSCANFD controllers with 8 channels each.

Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
Define all the supported CAN pins, but enable only the CAN controllers
that are available on the board.

Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
Add the RSCANFD driver.

Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
@RICCIARDI-Adrien RICCIARDI-Adrien force-pushed the aricciardi/rcar_add_rscanfd_driver branch from 69ac37d to d35e7d8 Compare June 23, 2026 12:32
@zephyrbot zephyrbot added the area: Tests Issues related to a particular existing or missing test label Jun 23, 2026
@zephyrbot zephyrbot requested a review from nashif June 23, 2026 12:34
@RICCIARDI-Adrien

Copy link
Copy Markdown
Contributor Author

I've pushed a first batch of changes to see how the CI will behave. I'm working on the remaining changes.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Boards/SoCs area: CAN area: Devicetree Binding PR modifies or adds a Device Tree binding area: Tests Issues related to a particular existing or missing test platform: Renesas R-Car Renesas R-Car

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants