Commit 58e12726 authored by Henrik Brix Andersen's avatar Henrik Brix Andersen Committed by Christopher Friedt
Browse files

drivers: can: add driver for the Kvaser PCIcan CAN controller PCI card



Add driver for the Kvaser PCIcan CAN controller PCI card for use with QEMU.

Signed-off-by: default avatarHenrik Brix Andersen <henrik@brixandersen.dk>
parent 466db0ce
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ endif()

zephyr_library_sources_ifdef(CONFIG_CAN_SJA1000      can_sja1000.c)
zephyr_library_sources_ifdef(CONFIG_CAN_ESP32_TWAI   can_esp32_twai.c)
zephyr_library_sources_ifdef(CONFIG_CAN_KVASER_PCI   can_kvaser_pci.c)

zephyr_library_sources_ifdef(CONFIG_USERSPACE        can_handlers.c)
zephyr_library_sources_ifdef(CONFIG_CAN_SHELL        can_shell.c)
+1 −0
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ source "drivers/can/Kconfig.loopback"
source "drivers/can/Kconfig.native_posix_linux"
source "drivers/can/Kconfig.sja1000"
source "drivers/can/Kconfig.esp32"
source "drivers/can/Kconfig.kvaser"

source "drivers/can/transceiver/Kconfig"

+13 −0
Original line number Diff line number Diff line
# Kvaser PCIcan configuration options

# Copyright (c) 2022 Henrik Brix Andersen <henrik@brixandersen.dk>
# SPDX-License-Identifier: Apache-2.0

config CAN_KVASER_PCI
	bool "Kvaser PCIcan driver"
	default y
	depends on DT_HAS_KVASER_PCICAN_ENABLED
	select PCIE
	select CAN_SJA1000
	help
	  This enables support for the Kvaser PCIcan.
+191 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2022 Henrik Brix Andersen <henrik@brixandersen.dk>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT kvaser_pcican

#include "can_sja1000.h"

#include <zephyr/drivers/can.h>
#include <zephyr/drivers/pcie/pcie.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>

LOG_MODULE_REGISTER(can_kvaser_pci, CONFIG_CAN_LOG_LEVEL);

/* AMCC S5920 I/O BAR registers */
#define S5920_INTCSR_REG       0x38
#define S5920_INTCSR_ADDINT_EN BIT(13)
#define S5920_PTCR_REG	       0x60

/* Xilinx I/O BAR registers */
#define XLNX_VERINT_REG		0x07
#define XLNX_VERINT_VERSION_POS 4U

struct can_kvaser_pci_config {
	pcie_bdf_t pcie_bdf;
	pcie_id_t pcie_id;
	void (*irq_config_func)(const struct device *dev);
};

struct can_kvaser_pci_data {
	io_port_t sja1000_base;
};

static uint8_t can_kvaser_pci_read_reg(const struct device *dev, uint8_t reg)
{
	struct can_sja1000_data *sja1000_data = dev->data;
	struct can_kvaser_pci_data *kvaser_data = sja1000_data->custom;
	io_port_t addr = kvaser_data->sja1000_base + reg;

	return sys_in8(addr);
}

static void can_kvaser_pci_write_reg(const struct device *dev, uint8_t reg, uint8_t val)
{
	struct can_sja1000_data *sja1000_data = dev->data;
	struct can_kvaser_pci_data *kvaser_data = sja1000_data->custom;
	io_port_t addr = kvaser_data->sja1000_base + reg;

	sys_out8(val, addr);
}

static int can_kvaser_pci_get_core_clock(const struct device *dev, uint32_t *rate)
{
	ARG_UNUSED(dev);

	/* The internal clock operates at half of the oscillator frequency */
	*rate = MHZ(16) / 2;

	return 0;
}

static int can_kvaser_pci_init(const struct device *dev)
{
	const struct can_sja1000_config *sja1000_config = dev->config;
	const struct can_kvaser_pci_config *kvaser_config = sja1000_config->custom;
	struct can_sja1000_data *sja1000_data = dev->data;
	struct can_kvaser_pci_data *kvaser_data = sja1000_data->custom;
	struct pcie_bar iobar;
	static io_port_t amcc_base;
	static io_port_t xlnx_base;
	uint32_t intcsr;
	int err;

	if (!pcie_probe(kvaser_config->pcie_bdf, kvaser_config->pcie_id)) {
		LOG_ERR("failed to probe PCI(e)");
		return -ENODEV;
	}

	pcie_set_cmd(kvaser_config->pcie_bdf, PCIE_CONF_CMDSTAT_IO, true);

	/* AMCC S5920 registers */
	if (!pcie_probe_iobar(kvaser_config->pcie_bdf, 0, &iobar)) {
		LOG_ERR("failed to probe AMCC S5920 I/O BAR");
		return -ENODEV;
	}

	amcc_base = iobar.phys_addr;

	/* SJA1000 registers */
	if (!pcie_probe_iobar(kvaser_config->pcie_bdf, 1, &iobar)) {
		LOG_ERR("failed to probe SJA1000 I/O BAR");
		return -ENODEV;
	}

	kvaser_data->sja1000_base = iobar.phys_addr;

	/* Xilinx registers */
	if (!pcie_probe_iobar(kvaser_config->pcie_bdf, 2, &iobar)) {
		LOG_ERR("failed to probe Xilinx I/O BAR");
		return -ENODEV;
	}

	xlnx_base = iobar.phys_addr;
	LOG_DBG("Xilinx version: %d",
		sys_in8(xlnx_base + XLNX_VERINT_REG) >> XLNX_VERINT_VERSION_POS);

	/*
	 * Initialization sequence as per Kvaser PCIcan Hardware Reference Manual (UG 98048
	 * v3.0.0).
	 */

	/* AMCC S5920 PCI Pass-Thru Configuration Register (PTCR) */
	sys_out32(0x80808080UL, amcc_base + S5920_PTCR_REG);

	/* AMCC S5920 PCI Interrupt Control/Status Register (INTCSR) */
	intcsr = sys_in32(amcc_base + S5920_INTCSR_REG);
	intcsr |= S5920_INTCSR_ADDINT_EN;
	sys_out32(intcsr, amcc_base + S5920_INTCSR_REG);

	err = can_sja1000_init(dev);
	if (err != 0) {
		LOG_ERR("failed to initialize controller (err %d)", err);
		return err;
	}

	kvaser_config->irq_config_func(dev);

	return 0;
}

const struct can_driver_api can_kvaser_pci_driver_api = {
	.get_capabilities = can_sja1000_get_capabilities,
	.start = can_sja1000_start,
	.stop = can_sja1000_stop,
	.set_mode = can_sja1000_set_mode,
	.set_timing = can_sja1000_set_timing,
	.send = can_sja1000_send,
	.add_rx_filter = can_sja1000_add_rx_filter,
	.remove_rx_filter = can_sja1000_remove_rx_filter,
	.get_state = can_sja1000_get_state,
	.set_state_change_callback = can_sja1000_set_state_change_callback,
	.get_core_clock = can_kvaser_pci_get_core_clock,
	.get_max_filters = can_sja1000_get_max_filters,
	.get_max_bitrate = can_sja1000_get_max_bitrate,
#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY
	.recover = can_sja1000_recover,
#endif /* !CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */
	.timing_min = CAN_SJA1000_TIMING_MIN_INITIALIZER,
	.timing_max = CAN_SJA1000_TIMING_MAX_INITIALIZER,
};

#define CAN_KVASER_PCI_OCR                                                                         \
	(CAN_SJA1000_OCR_OCMODE_NORMAL | CAN_SJA1000_OCR_OCTN0 | CAN_SJA1000_OCR_OCTP0 |           \
	 CAN_SJA1000_OCR_OCTN1 | CAN_SJA1000_OCR_OCTP1)

#define CAN_KVASER_PCI_CDR (CAN_SJA1000_CDR_CD_DIV2 | CAN_SJA1000_CDR_CLOCK_OFF)

#define CAN_KVASER_PCI_INIT(inst)                                                                  \
	static void can_kvaser_pci_config_func_##inst(const struct device *dev);                   \
                                                                                                   \
	static const struct can_kvaser_pci_config can_kvaser_pci_config_##inst = {                 \
		.pcie_bdf = DT_INST_REG_ADDR(inst),                                                \
		.pcie_id = DT_INST_REG_SIZE(inst),                                                 \
		.irq_config_func = can_kvaser_pci_config_func_##inst                               \
	};                                                                                         \
                                                                                                   \
	static const struct can_sja1000_config can_sja1000_config_##inst =                         \
		CAN_SJA1000_DT_CONFIG_INST_GET(inst, &can_kvaser_pci_config_##inst,                \
					       can_kvaser_pci_read_reg, can_kvaser_pci_write_reg,  \
					       CAN_KVASER_PCI_OCR, CAN_KVASER_PCI_CDR);            \
                                                                                                   \
	static struct can_kvaser_pci_config can_kvaser_pci_data_##inst;                            \
                                                                                                   \
	static struct can_sja1000_data can_sja1000_data_##inst =                                   \
		CAN_SJA1000_DATA_INITIALIZER(&can_kvaser_pci_data_##inst);                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(inst, can_kvaser_pci_init, NULL, &can_sja1000_data_##inst,           \
			      &can_sja1000_config_##inst, POST_KERNEL, CONFIG_CAN_INIT_PRIORITY,   \
			      &can_kvaser_pci_driver_api);                                         \
                                                                                                   \
	static void can_kvaser_pci_config_func_##inst(const struct device *dev)                    \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), can_sja1000_isr,      \
			    DEVICE_DT_INST_GET(inst), DT_INST_IRQ(inst, sense));                   \
		irq_enable(DT_INST_IRQN(inst));                                                    \
	}

DT_INST_FOREACH_STATUS_OKAY(CAN_KVASER_PCI_INIT)