/*
* 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>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/init.h>
#include <linux/signal.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/netdevice.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 struct usb_device_id peak_usb_table[] = {
{USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USB_PRODUCT_ID)},
{USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBPRO_PRODUCT_ID)},
{USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBFD_PRODUCT_ID)},
{USB_DEVICE(PCAN_USB_VENDOR_ID, PCAN_USBPROFD_PRODUCT_ID)},
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, peak_usb_table);
/* List of supported PCAN-USB adapters (NULL terminated list) */
static const struct peak_usb_adapter *const peak_usb_adapters_list[] = {
&pcan_usb,
&pcan_usb_pro,
&pcan_usb_fd,
&pcan_usb_pro_fd,
};
/*
* dump memory
*/
#define DUMP_WIDTH 16
void pcan_dump_mem(char *prompt, 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;
}
}
static void peak_usb_add_us(struct timeval *tv, u32 delta_us)
{
/* number of s. to add to final time */
u32 delta_s = delta_us / 1000000;
delta_us -= delta_s * 1000000;
tv->tv_usec += delta_us;
if (tv->tv_usec >= 1000000) {
tv->tv_usec -= 1000000;
delta_s++;
}
tv->tv_sec += delta_s;
}
/*
* 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 (time_ref->tv_host.tv_sec > 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 (time_ref->tv_host_0.tv_sec == 0) {
/* use monotonic clock to correctly compute further deltas */
time_ref->tv_host_0 = ktime_to_timeval(ktime_get());
time_ref->tv_host.tv_sec = 0;
} else {
/*
* delta_us should not be >= 2^32 => delta_s should be < 4294
* handle 32-bits wrapping here: if count of s. reaches 4200,
* reset counters and change time base
*/
if (time_ref->tv_host.tv_sec != 0) {
u32 delta_s = time_ref->tv_host.tv_sec
- time_ref->tv_host_0.tv_sec;
if (delta_s > 4200) {
time_ref->tv_host_0 = time_ref->tv_host;
time_ref->ts_total = 0;
}
}
time_ref->tv_host = ktime_to_timeval(ktime_get());
time_ref->tick_count++;
}
time_ref->ts_dev_1 = time_ref->ts_dev_2;
peak_usb_update_ts_now(time_ref, ts_now);
}
/*
* compute timeval according to current ts and time_ref data
*/
void peak_usb_get_ts_tv(struct peak_time_ref *time_ref, u32 ts,
struct timeval *tv)
{
/* protect from getting timeval before setting now */
if (time_ref->tv_host.tv_sec > 0) {
u64 delta_us;
delta_us = ts - time_ref->ts_dev_2;
if (ts < time_ref->ts_dev_2)
delta_us &= (1 << time_ref->adapter->ts_used_bits