Enabling CAN interface for beagleV-Fire

I am trying to use the CAN1 interface available on the SoC. I can see that the CAN1 TX, RX and EBL pins are exposed in the PF_SOC_MSS, which can be exposed to the GPIOs through the FPGA fabric. Therefore, I duplicated the Default Cape to route the CAN interface.

I added these TCL statements to ADD_CAPE.tcl of the new Cape to connect the pins to P_24,P_26 and P_25 to the TX, RX and EBL pins of CAN1. I tied the existing MMUART pins to unused/low.

sd_clear_pin_attributes -sd_name ${sd_name} -pin_names {BVF_RISCV_SUBSYSTEM:CAN_1_RXBUS}
sd_clear_pin_attributes -sd_name ${sd_name} -pin_names {BVF_RISCV_SUBSYSTEM:CAN_1_TXBUS}
sd_connect_pin_to_port -sd_name ${sd_name} -pin_name {BVF_RISCV_SUBSYSTEM:CAN_1_RXBUS} -port_name {}
sd_connect_pin_to_port -sd_name ${sd_name} -pin_name {BVF_RISCV_SUBSYSTEM:CAN_1_TXBUS} -port_name {}
sd_rename_port -sd_name ${sd_name} -current_port_name {CAN_1_RXBUS} -new_port_name {P9_24}
sd_rename_port -sd_name ${sd_name} -current_port_name {CAN_1_TXBUS} -new_port_name {P9_26}
sd_clear_pin_attributes -sd_name ${sd_name} -pin_names {BVF_RISCV_SUBSYSTEM:CAN_1_TX_EBL}
sd_connect_pin_to_port -sd_name ${sd_name} -pin_name {BVF_RISCV_SUBSYSTEM:CAN_1_TX_EBL} -port_name {}
sd_rename_port -sd_name ${sd_name} -current_port_name {CAN_1_TX_EBL} -new_port_name {P9_25}

To enable CAN1, I added this to the Cape’s DTSO file.

&can1 {
	status = "okay";
};

I can see in the boot messages that CAN1 is detected and registered, and the status is reflected in the device tree, but it is not listed in network interfaces when I use the command ip link show.

Here is the MSS configuration for CAN1:

image

What am I missing? Is the MSS Configuration wrong? Should I be loading a driver separately?

Have you tried something along these lines:
Getting Started with CAN Networking in Linux - open source for you (opensourceforu.com)

I don’t think the FPGA routing has any impact on the device availability in Linux…
( besides what you do in your device-tree, obviously )

Isn’t that tutorial for virtual-can? I tried loading a driver for CAN using modprobe, but I was not able to find where the driver is situated.

It was the first thing Google brought up.

You’re right though; vcan is for development only.

In any event, I plan to use CAN for a project myself, so I’m curious as to what you find out. :smiling_face:

Starting to think the kernel might be compiled w/o the modules we need…
( breaks out the kernel server )

Update:
The only interesting symbol I can find in .config is: CONFIG_UIO_MICROCHIP_CAN,
but I’m not sure exactly what it does.
I could be wrong but the device-tree suggests the device driver should bind to microchip,mpfs-can,
but that driver seem to not yet be written; that’s bad news…
Found it: linux/drivers/uio/uio-microchip-can.c

Found this; others have the same issue.

So, it looks like we can mmap() and then poke the CAN registers of the Polarfire directly.

Let’s see what @RobertCNelson has to say, once he wakes up. :wink:
( I somehow hope he proves me wrong )

Odd, so CAN should work… it is actually built in…

Symbol: UIO_MICROCHIP_CAN [=y]

UIO driver? that is so odd…

As long as you have: .compatible = "microchip,mpfs-can" in the device-tree…

// SPDX-License-Identifier: GPL-2.0
/*
 * This driver exports interrupts and CAN register space
 * to user space for applications interacting with PDMA
 *
 * Copyright (C) 2018 - 2021 Microchip Incorporated - http://www.microchip.com/
 *
 * Author: Daire McNamara <daire.mcnamara@microchip.com>
 */
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/genalloc.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>

#define DRV_NAME "can-uio"
#define DRV_VERSION "0.1"

#define CAN_INT_ENABLE (4)
#define CAN_INT_STATUS (0)

#define MAX_CAN_EVT	1

struct uio_can_dev {
	struct uio_info *uio_info;
	struct clk *clk;
	void __iomem *base;
	int irq;
};

static irqreturn_t can_handler(int irq, struct uio_info *uio_info)
{
	struct uio_can_dev *dev_info = uio_info->priv;
	void __iomem *base = dev_info->base;
	int val;

	val = ioread32(base + CAN_INT_STATUS);

	/* clear anything that was active */
	iowrite32(val, base + CAN_INT_STATUS);

	/* is interrupt enabled and active ? */
	if (!(val & 0xffff) && (ioread32(base + CAN_INT_ENABLE) & 0xffff))
		return IRQ_NONE;

	return IRQ_HANDLED;
}

static int can_probe(struct platform_device *pdev)
{
	struct uio_info *uio_info;
	struct uio_can_dev *dev_info;
	struct resource *res;
	struct device *dev = &pdev->dev;
	int ret = -ENODEV, cnt = 0, len;

	dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
	if (!dev_info)
		return -ENOMEM;

	dev_info->uio_info = kzalloc(sizeof(*dev_info->uio_info) * MAX_CAN_EVT, GFP_KERNEL);
	if (!dev_info->uio_info) {
		ret = -ENOMEM;
		goto out_free_dev;
	}

	/* power on PRU in case its not done as part of boot-loader */
	dev_info->clk = devm_clk_get(dev, NULL);
	if (!dev_info->clk || (IS_ERR(dev_info->clk))) {
		dev_err(dev, "failed to get clock\n");
		ret = PTR_ERR(dev_info->clk);
		goto out_free_uio;
	}

	ret = clk_prepare_enable(dev_info->clk);
	if (ret) {
		dev_err(dev, "failed to enable clock\n");
		goto out_free_uio;
	}

	ret = devm_add_action_or_reset(dev, (void (*) (void *))clk_disable_unprepare,
				       dev_info->clk);
	if (ret) {
		dev_err(dev, "failed to enable clock\n");
		goto out_free_uio;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "no CAN I/O resource specified\n");
		ret = -ENODEV;
		goto out_free_uio;
	}

	len = resource_size(res);

	dev_info->base = ioremap(res->start, len);
	if (!dev_info->base) {
		dev_err(dev, "failed to remap CAN I/O  address range\n");
		ret = -ENODEV;
		goto out_free_uio;
	}

	dev_info->irq = platform_get_irq(pdev, 0);
	if (dev_info->irq < 0) {
		dev_err(dev, "failed to get irq.\n");
		ret = -ENODEV;
		goto out_iounmap;
	}

	uio_info = dev_info->uio_info;

	uio_info->mem[0].addr = res->start;
	uio_info->mem[0].size = resource_size(res);
	uio_info->mem[0].memtype = UIO_MEM_PHYS;

	uio_info->mem[1].size = 0;

	uio_info->name = dev->of_node->full_name;
	uio_info->version = DRV_VERSION;

	/* register CAN IRQ lines */
	uio_info->irq = dev_info->irq;
	uio_info->irq_flags = IRQF_SHARED;
	uio_info->handler = can_handler;
	uio_info->priv = dev_info;

	ret = uio_register_device(dev, uio_info);
	if (ret < 0) {
		dev_err(dev, "failed to register CAN device\n");
		goto out_unregister;
	}

	platform_set_drvdata(pdev, dev_info);

	dev_info(dev, "registered device as %s\n", uio_info->name);

	return 0;

out_unregister:
	for (cnt = 0; cnt < MAX_CAN_EVT; cnt++, uio_info++) {
		uio_unregister_device(uio_info);
		kfree(uio_info->name);
	}
out_iounmap:
	iounmap(dev_info->base);
out_free_uio:
	kfree(dev_info->uio_info);
out_free_dev:
	kfree(dev_info);

	return ret;
}

static int can_remove(struct platform_device *dev)
{
	struct uio_can_dev *dev_info = platform_get_drvdata(dev);
	struct uio_info *uio_info = dev_info->uio_info;
	int cnt;

	for (cnt = 0; cnt < MAX_CAN_EVT; cnt++, uio_info++) {
		uio_unregister_device(uio_info);
		kfree(uio_info->name);
	}
	iounmap(dev_info->base);
	kfree(dev_info->uio_info);
	kfree(dev_info);

	return 0;
}

#define MICROCHIP_CAN_PM_OPS (NULL)

#if defined(CONFIG_OF)
static const struct of_device_id can_dt_ids[] = {
	{ .compatible = "microchip,mpfs-can" },
	{ /*sentinel */ }
};
#endif

static struct platform_driver can_driver = {
	.probe = can_probe,
	.remove = can_remove,
	.driver = {
		.name = DRV_NAME,
		.pm = MICROCHIP_CAN_PM_OPS,
		.of_match_table = of_match_ptr(can_dt_ids),
		.owner = THIS_MODULE,
		   },
};

module_platform_driver(can_driver);

MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>");
beagle@BeagleV:/proc/device-tree/soc/can@2010d000$ cat status; echo
okay
beagle@BeagleV:/proc/device-tree/soc/can@2010d000$ cat compatible ; echo
microchip,mpfs-can

I’m pretty sure it’s enabled. How can I try loading the driver you’ve posted above?

It already is @leoh. It’s what generates the message you see as the kernel boots:

[    3.172204] can-uio 2010c000.can: registered device as can@2010c000
[    3.179350] can-uio 2010d000.can: registered device as can@2010d000

You can also see evidence of it working if you do a tree /sys/class/uio.

1 Like

In that case, we should be able to follow the polarfire SoC example for CAN polarfire-soc-linux-examples/can at master · polarfire-soc/polarfire-soc-linux-examples · GitHub, but it does not work.

Can’t use SocketCAN. UIO is a very different beast.

UIO:
The Userspace I/O HOWTO — The Linux Kernel documentation

SocketCAN:

1 Like

Ah I see. I did not know that. I’ll read what you’ve given now. Out of curiosity, which resources could I follow to be more aware of devices and I/O in Linux?

Thats a very broad question, but for the BeagleV-fire,
I normally start at BeagleV-Fire / BeagleV-Fire-ubuntu · GitLab (beagleboard.org)
for all things Linux related.

You’d be amazed just how much can be gleaned from diving into the Linux source-tree.

01_git_sync.sh has all the sources used, so I follow the bread-crumbs from there.

Other than that “Google is your friend” never fails to hold true. :smiling_face:

1 Like

Wonders…

is it showing up under ifconfig?

sudo ifconfig -a

Regards,

What would be a good, useful, reasonably priced, device somebody could use to test a CAN interface?

Here’s one plug-able option for the Fire: beaglebone/Comms · master · BeagleBoard.org / Capes · GitLab

Expects: can on p9.24 and p9.26

As long as you have 2 of them and a 120 ohm resistor you can cross wire CANH/CANL…

With both devices (SocketCAN)…

sudo ip link set can0 type can bitrate 500000 ; sudo ifconfig can0 up

Enable Receiver:

candump can0

Transmit Test Data:

cansend can0 123#DEADBEEF

Result:

candump can0
  can0  123   [4]  DE AD BE EF

Regards,

1 Like