/*
* CAN driver for PEAK System PCAN-USB Pro adapter
* Derived from the PCAN project file driver/src/pcan_usbpro.c
*
* Copyright (C) 2003-2011 PEAK System-Technik GmbH
* Copyright (C) 2011-2012 Stephane Grosjean <s.grosjean@peak-system.com>
*
* 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/netdevice.h>
#include <linux/usb.h>
#include <linux/module.h>
#include <linux/can.h>
#include <linux/can/dev.h>
#include <linux/can/error.h>
#include "pcan_usb_core.h"
#include "pcan_usb_pro.h"
MODULE_SUPPORTED_DEVICE("PEAK-System PCAN-USB Pro adapter");
/* PCAN-USB Pro Endpoints */
#define PCAN_USBPRO_EP_CMDOUT 1
#define PCAN_USBPRO_EP_CMDIN (PCAN_USBPRO_EP_CMDOUT | USB_DIR_IN)
#define PCAN_USBPRO_EP_MSGOUT_0 2
#define PCAN_USBPRO_EP_MSGIN (PCAN_USBPRO_EP_MSGOUT_0 | USB_DIR_IN)
#define PCAN_USBPRO_EP_MSGOUT_1 3
#define PCAN_USBPRO_EP_UNUSED (PCAN_USBPRO_EP_MSGOUT_1 | USB_DIR_IN)
#define PCAN_USBPRO_CHANNEL_COUNT 2
/* PCAN-USB Pro adapter internal clock (MHz) */
#define PCAN_USBPRO_CRYSTAL_HZ 56000000
/* PCAN-USB Pro command timeout (ms.) */
#define PCAN_USBPRO_COMMAND_TIMEOUT 1000
/* PCAN-USB Pro rx/tx buffers size */
#define PCAN_USBPRO_RX_BUFFER_SIZE 1024
#define PCAN_USBPRO_TX_BUFFER_SIZE 64
#define PCAN_USBPRO_MSG_HEADER_LEN 4
/* some commands responses need to be re-submitted */
#define PCAN_USBPRO_RSP_SUBMIT_MAX 2
#define PCAN_USBPRO_RTR 0x01
#define PCAN_USBPRO_EXT 0x02
#define PCAN_USBPRO_CMD_BUFFER_SIZE 512
/* handle device specific info used by the netdevices */
struct pcan_usb_pro_interface {
struct peak_usb_device *dev[PCAN_USBPRO_CHANNEL_COUNT];
struct peak_time_ref time_ref;
int cm_ignore_count;
int dev_opened_count;
};
/* device information */
struct pcan_usb_pro_device {
struct peak_usb_device dev;
struct pcan_usb_pro_interface *usb_if;
u32 cached_ccbt;
};
/* internal structure used to handle messages sent to bulk urb */
struct pcan_usb_pro_msg {
u8 *rec_ptr;
int rec_buffer_size;
int rec_buffer_len;
union {
u16 *rec_cnt_rd;
u32 *rec_cnt;
u8 *rec_buffer;
} u;
};
/* records sizes table indexed on message id. (8-bits value) */
static u16 pcan_usb_pro_sizeof_rec[256] = {
[PCAN_USBPRO_SETBTR] = sizeof(struct pcan_usb_pro_btr),
[PCAN_USBPRO_SETBUSACT] = sizeof(struct pcan_usb_pro_busact),
[PCAN_USBPRO_SETSILENT] = sizeof(struct pcan_usb_pro_silent),
[PCAN_USBPRO_SETFILTR] = sizeof(struct pcan_usb_pro_filter),
[PCAN_USBPRO_SETTS] = sizeof(struct pcan_usb_pro_setts),
[PCAN_USBPRO_GETDEVID] = sizeof(struct pcan_usb_pro_devid),
[PCAN_USBPRO_SETLED] = sizeof(struct pcan_usb_pro_setled),
[PCAN_USBPRO_RXMSG8] = sizeof(struct pcan_usb_pro_rxmsg),
[PCAN_USBPRO_RXMSG4] = sizeof(struct pcan_usb_pro_rxmsg) - 4,
[PCAN_USBPRO_RXMSG0] = sizeof(struct pcan_usb_pro_rxmsg) - 8,
[PCAN_USBPRO_RXRTR] = sizeof(struct pcan_usb_pro_rxmsg) - 8,
[PCAN_USBPRO_RXSTATUS] = sizeof(struct pcan_usb_pro_rxstatus),
[PCAN_USBPRO_RXTS] = sizeof(struct pcan_usb_pro_rxts),
[PCAN_USBPRO_TXMSG8] = sizeof(struct pcan_usb_pro_txmsg),
[PCAN_USBPRO_TXMSG4] = sizeof(struct pcan_usb_pro_txmsg) - 4,
[PCAN_USBPRO_TXMSG0] = sizeof(struct pcan_usb_pro_txmsg) - 8,
};
/*
* initialize PCAN-USB Pro message data structure
*/
static u8 *pcan_msg_init(struct pcan_usb_pro_msg *pm, void *buffer_addr,
int buffer_size)
{
if (buffer_size < PCAN_USBPRO_MSG_HEADER_LEN)
return NULL;
pm->u.rec_buffer = (u8 *)buffer_addr;
pm->rec_buffer_size = pm->rec_buffer_len = buffer_size;
pm->rec_ptr = pm->u.rec_buffer + PCAN_USBPRO_MSG_HEADER_LEN;
return pm->rec_ptr;
}
static u8 *pcan_msg_init_empty(struct pcan_usb_pro_msg *pm,
void *buffer_addr, int buffer_size)
{
u8 *pr = pcan_msg_init(pm, buffer_addr, buffer_size);
if (pr) {
pm->rec_buffer_len = PCAN_USBPRO_MSG_HEADER_LEN;
*pm->u.rec_cnt = 0;
}
return pr;
}
/*
* add one record to a message being built
*/
static int pcan_msg_add_rec(struct pcan_usb_pro_msg *pm, u8 id, ...)
{
int len, i;
u8 *pc;
va_list ap;
va_start(ap, id);
pc = pm->rec_ptr + 1;
i = 0;
switch (id) {
case PCAN_USBPRO_TXMSG8:
i +=