/*
* Greybus "AP" USB driver for "ES2" controller chips
*
* Copyright 2014-2015 Google Inc.
* Copyright 2014-2015 Linaro Ltd.
*
* Released under the GPLv2 only.
*/
#include <linux/kthread.h>
#include <linux/sizes.h>
#include <linux/usb.h>
#include <linux/kfifo.h>
#include <linux/debugfs.h>
#include <asm/unaligned.h>
#include "greybus.h"
#include "kernel_ver.h"
#include "connection.h"
#include "greybus_trace.h"
/* Fixed CPort numbers */
#define ES2_CPORT_CDSI0 16
#define ES2_CPORT_CDSI1 17
/* Memory sizes for the buffers sent to/from the ES2 controller */
#define ES2_GBUF_MSG_SIZE_MAX 2048
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x18d1, 0x1eaf) },
{ },
};
MODULE_DEVICE_TABLE(usb, id_table);
#define APB1_LOG_SIZE SZ_16K
/* Number of bulk in and bulk out couple */
#define NUM_BULKS 7
/*
* Number of CPort IN urbs in flight at any point in time.
* Adjust if we are having stalls in the USB buffer due to not enough urbs in
* flight.
*/
#define NUM_CPORT_IN_URB 4
/* Number of CPort OUT urbs in flight at any point in time.
* Adjust if we get messages saying we are out of urbs in the system log.
*/
#define NUM_CPORT_OUT_URB (8 * NUM_BULKS)
/*
* @endpoint: bulk in endpoint for CPort data
* @urb: array of urbs for the CPort in messages
* @buffer: array of buffers for the @cport_in_urb urbs
*/
struct es2_cport_in {
__u8 endpoint;
struct urb *urb[NUM_CPORT_IN_URB];
u8 *buffer[NUM_CPORT_IN_URB];
};
/*
* @endpoint: bulk out endpoint for CPort data
*/
struct es2_cport_out {
__u8 endpoint;
};
/**
* es2_ap_dev - ES2 USB Bridge to AP structure
* @usb_dev: pointer to the USB device we are.
* @usb_intf: pointer to the USB interface we are bound to.
* @hd: pointer to our gb_host_device structure
* @cport_in: endpoint, urbs and buffer for cport in messages
* @cport_out: endpoint for for cport out messages
* @cport_out_urb: array of urbs for the CPort out messages
* @cport_out_urb_busy: array of flags to see if the @cport_out_urb is busy or
* not.
* @cport_out_urb_cancelled: array of flags indicating whether the
* corresponding @cport_out_urb is being cancelled
* @cport_out_urb_lock: locks the @cport_out_urb_busy "list"
*
* @apb_log_task: task pointer for logging thread
* @apb_log_dentry: file system entry for the log file interface
* @apb_log_enable_dentry: file system entry for enabling logging
* @apb_log_fifo: kernel FIFO to carry logged data
*/
struct es2_ap_dev {
struct usb_device *usb_dev;
struct usb_interface *usb_intf;
struct gb_host_device *hd;
struct es2_cport_in cport_in[NUM_BULKS];
struct es2_cport_out cport_out[NUM_BULKS];
struct urb *cport_out_urb[NUM_CPORT_OUT_URB];
bool cport_out_urb_busy[NUM_CPORT_OUT_URB];
bool cport_out_urb_cancelled[NUM_CPORT_OUT_URB];
spinlock_t cport_out_urb_lock;
int *cport_to_ep;
struct task_struct *apb_log_task;
struct dentry *apb_log_dentry;
struct dentry *apb_log_enable_dentry;
DECLARE_KFIFO(apb_log_fifo, char, APB1_LOG_SIZE);
};
/**
* cport_to_ep - information about cport to endpoints mapping
* @cport_id: the id of cport to map to endpoints
* @endpoint_in: the endpoint number to use for in transfer
* @endpoint_out: he endpoint number to use for out transfer
*/
struct cport_to_ep {
__le16 cport_id;
__u8 endpoint_in;
__u8 endpoint_out;
};
static inline struct es2_ap_dev *hd_to_es2(struct gb_host_device *hd)
{
return (struct es2_ap_dev *)&hd->hd_priv;
}
static void cport_out_callback(struct urb *urb);
static void usb_log_enable(struct es2_ap_dev *es2);
static void usb_log_disable(struct es2_ap_dev *es2);
/* Get the endpoints pair mapped to the cport */
static int cport_to_ep_pair(struct es2_ap_dev *es2, u16 cport_id)
{
if (cport_id >= es2->hd->num_cports)
return 0;
return es2->cport_to_ep[cport_id];
}
#define ES2_TIMEOUT 500 /* 500 ms for the SVC to do something */
/* Disable for now until we work all of this out to keep a warning-free build */
#if 0
/* Test if the endpoints pair is already mapped to a cport */
static int ep_pair_in_use(struct es2_ap_dev *es2, int ep_pair)
{
int i;
for (i = 0; i < es2->hd->num_cports; i++) {
if (es2->cport_to_ep[i] == ep_pair)
return 1;
}
return 0;
}
/* Configure the endpoint mapping and send the request to APBridge */
static int map_cport_to_ep(struct es2_ap_dev *es2,
u16 cport_id, int ep_pair)
{
int retval;
struct cport_to_ep *cport_to_ep;
if (ep_pair < 0 || ep_pair >= NUM_BULKS)
return -EINVAL;
if (cport_id >= es2->hd->num_cports)
return -EINVAL;
if (ep_pair && ep_pair_in_use(es2, ep_pair))
return -EINVAL;
cport_to_ep = kmalloc(sizeof(*cport_to_ep), GFP_KERNEL);
if (!cport_to_ep)
return -ENOMEM;
es2->cport_to_ep[cport_id] = ep_pair;
cport_to_ep->cport_id = cpu_to_le16(cport_id);
cport_to_ep->endpoint_in = es2->cport_in[ep_pair].endpoint;
cport_to_ep->endpoint_out = es2->cport_out[ep_pair].endpoint;
retval = usb_control_msg(es2->usb_dev,
usb_sndctrlpipe(es2->usb_dev, 0),
GB_APB_REQUEST_EP_MAPPING,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
0x00, 0x00,
(char *)cport_to_ep,
sizeof(*cport_to_ep),
ES2_TIMEOUT);
if (retval == sizeof(*cport_to_ep))
retval = 0;
kfree(cport_to_ep);
return retval;
}
/* Unmap a cport: use the muxed endpoints pair */
static int unmap_cport(struct es2_ap_dev *es2, u16 cport_id)
{
return map_cport_to_ep(es2, cport_id, 0);
}
#endif
static int output_sync(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
{
struct usb_device *udev = es2->usb_dev;
u8 *data;
int retval;
data = kmalloc(size, GFP_KERNEL);
if (!data)
return