// SPDX-License-Identifier: GPL-2.0-only
/*
* CAN driver for PEAK System USB adapters
* Derived from the PCAN project file driver/src/pcan_usb_core.c
*
* Copyright (C) 2003-2010 PEAK System-Technik GmbH
* Copyright (C) 2010-2012 Stephane Grosjean <s.grosjean@peak-system.com>
*
* Many thanks to Klaus Hitschler <klaus.hitschler@gmx.de>
*/
#include <linux/device.h>
#include <linux/ethtool.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/signal.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/usb.h>
#include <linux/can.h>
#include <linux/can/dev.h>
#include <linux/can/error.h>
#include "pcan_usb_core.h"
MODULE_AUTHOR("Stephane Grosjean <s.grosjean@peak-system.com>");
MODULE_DESCRIPTION("CAN driver for PEAK-System USB adapters");
MODULE_LICENSE("GPL v2");
/* Table of devices that work with this driver */
static const struct usb_device_id peak_usb_table[] = {
{
USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USB_PRODUCT_ID),
.driver_info = (kernel_ulong_t)&pcan_usb,
}, {
USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBPRO_PRODUCT_ID),
.driver_info = (kernel_ulong_t)&pcan_usb_pro,
}, {
USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBFD_PRODUCT_ID),
.driver_info = (kernel_ulong_t)&pcan_usb_fd,
}, {
USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBPROFD_PRODUCT_ID),
.driver_info = (kernel_ulong_t)&pcan_usb_pro_fd,
}, {
USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBCHIP_PRODUCT_ID),
.driver_info = (kernel_ulong_t)&pcan_usb_chip,
}, {
USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBX6_PRODUCT_ID),
.driver_info = (kernel_ulong_t)&pcan_usb_x6,
}, {
/* Terminating entry */
}
};
MODULE_DEVICE_TABLE(usb, peak_usb_table);
static ssize_t can_channel_id_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct net_device *netdev = to_net_dev(dev);
struct peak_usb_device *peak_dev = netdev_priv(netdev);
return sysfs_emit(buf, "%08X\n", peak_dev->can_channel_id);
}
static DEVICE_ATTR_RO(can_channel_id);
/* mutable to avoid cast in attribute_group */
static struct attribute *peak_usb_sysfs_attrs[] = {
&dev_attr_can_channel_id.attr,
NULL,
};
static const struct attribute_group peak_usb_sysfs_group = {
.name = "peak_usb",
.attrs = peak_usb_sysfs_attrs,
};
/*
* dump memory
*/
#define DUMP_WIDTH 16
void pcan_dump_mem(const char *prompt, const void *p, int l)
{
pr_info("%s dumping %s (%d bytes):\n",
PCAN_USB_DRIVER_NAME, prompt ? prompt : "memory", l);
print_hex_dump(KERN_INFO, PCAN_USB_DRIVER_NAME " ", DUMP_PREFIX_NONE,
DUMP_WIDTH, 1, p, l, false);
}
/*
* initialize a time_ref object with usb adapter own settings
*/
void peak_usb_init_time_ref(struct peak_time_ref *time_ref,
const struct peak_usb_adapter *adapter)
{
if (time_ref) {
memset(time_ref, 0, sizeof(struct peak_time_ref));
time_ref->adapter = adapter;
}
}
/*
* sometimes, another now may be more recent than current one...
*/
void peak_usb_update_ts_now(struct peak_time_ref *time_ref, u32 ts_now)
{
time_ref->ts_dev_2 = ts_now;
/* should wait at least two passes before computing */
if (ktime_to_ns(time_ref->tv_host) > 0) {
u32 delta_ts = time_ref->ts_dev_2 - time_ref->ts_dev_1;
if (time_ref->ts_dev_2 < time_ref->ts_dev_1)
delta_ts &= (1 << time_ref->adapter->ts_used_bits) - 1;
time_ref->ts_total += delta_ts;
}
}
/*
* register device timestamp as now
*/
void peak_usb_set_ts_now(struct peak_time_ref *time_ref, u32 ts_now)
{
if (ktime_to_ns(time_ref->tv_host_0) == 0) {
/* use monotonic clock to correctly compute further deltas */
time_ref->tv_host_0 = ktime_get();
time_ref->tv_host = ktime_set(0, 0);
} else {
/*
* delta_us should not be >= 2^32 => delta should be < 4294s
* handle 32-bits wrapping here: if count of s. reaches 4200,
* reset counters and change time base
*/
if (ktime_to_ns(time_ref->tv_host)) {
ktime_t delta = ktime_sub(time_ref->tv_host,
time_ref->tv_host_0);
if (ktime_to_ns(delta) > (4200ull * NSEC_PER_SEC)) {
time_ref->tv_host_0 = time_ref->tv_host;
time_ref->ts_total = 0;
}
}
time_ref