1397 lines
36 KiB
C
1397 lines
36 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2025 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/mutex.h>
|
|
#include <asm/uaccess.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/compat.h>
|
|
#include <asm/current.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/cdev.h>
|
|
|
|
#include "usbci.h"
|
|
|
|
#define usbcam_dbg(fmt, args...) \
|
|
do {\
|
|
if (debug_usbci)\
|
|
pr_info("usbci: " fmt, ## args);\
|
|
} while (0)
|
|
|
|
static int debug_usbci;
|
|
|
|
MODULE_PARM_DESC(debug_usbci, "\n\t\t Enable usbci debug information");
|
|
module_param(debug_usbci, int, 0644);
|
|
|
|
#define MAX_CI_DEVICES 2
|
|
|
|
static dev_t aml_usbcam_devt;
|
|
static struct class *aml_usbcam_ci_class;
|
|
static struct aml_usbcam *command_devices[MAX_CI_DEVICES];
|
|
static struct aml_usbcam *media_devices[MAX_CI_DEVICES];
|
|
static DEFINE_MUTEX(ci_devices_mutex);
|
|
|
|
static unsigned long aml_get_usbcam_version(struct aml_usbcam *usbcam_dev, unsigned long arg)
|
|
{
|
|
int ret;
|
|
unsigned int driver_version = USBCAM_MODULE_DRIVER_VERSION;
|
|
|
|
usbcam_dbg("get usbcam version\n");
|
|
|
|
if (!usbcam_dev || !arg) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = copy_to_user((unsigned int *)arg, &driver_version, sizeof(unsigned int));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long aml_get_usbcam_info(struct aml_usbcam *usbcam_dev, unsigned long arg)
|
|
{
|
|
int ret;
|
|
|
|
usbcam_dbg("get usbcam info\n");
|
|
|
|
if (!usbcam_dev || !arg) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = copy_to_user((struct aml_usbcam_info *)arg, &usbcam_dev->usbcam_module_info,
|
|
sizeof(struct aml_usbcam_info));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long aml_get_usbcam_capabilities(struct aml_usbcam *usbcam_dev, unsigned long arg)
|
|
{
|
|
int ret;
|
|
|
|
usbcam_dbg("get usbcam capabilities\n");
|
|
|
|
if (!usbcam_dev || !arg) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = copy_to_user((struct aml_usbcam_module_capabilities *)arg,
|
|
&usbcam_dev->usbcam_module_capabilities,
|
|
sizeof(struct aml_usbcam_module_capabilities));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long aml_cancel_transfer(struct file *filp, struct aml_usbcam *usbcam_dev)
|
|
{
|
|
usbcam_dbg("cancel transfer\n");
|
|
|
|
if (!usbcam_dev) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
if ((filp->f_flags & 0x03) == O_RDONLY || (filp->f_flags & 0x03) == O_RDWR) {
|
|
if (atomic_read(&usbcam_dev->read_urb->use_count)) {
|
|
usbcam_dbg("unlink the read urb\n");
|
|
usb_unlink_urb(usbcam_dev->read_urb);
|
|
}
|
|
}
|
|
if ((filp->f_flags & 0x03) == O_RDWR || (filp->f_flags & 0x03) == O_WRONLY) {
|
|
if (atomic_read(&usbcam_dev->write_urb->use_count)) {
|
|
usbcam_dbg("unlink the write urb\n");
|
|
usb_unlink_urb(usbcam_dev->write_urb);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long aml_get_usbcam_state(struct aml_usbcam *usbcam_dev, unsigned long arg)
|
|
{
|
|
int ret;
|
|
unsigned int status;
|
|
|
|
usbcam_dbg("get usbcam state\n");
|
|
|
|
if (!usbcam_dev || !arg) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
status = usbcam_dev->device_state;
|
|
|
|
ret = copy_to_user((unsigned int *)arg, &status, sizeof(unsigned int));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long aml_set_usbcam_state(struct aml_usbcam *usbcam_dev, unsigned long arg)
|
|
{
|
|
int ret;
|
|
|
|
usbcam_dbg("set usbcam state\n");
|
|
|
|
if (!usbcam_dev || !arg) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = copy_from_user(&usbcam_dev->device_state, (unsigned int *)arg,
|
|
sizeof(unsigned int));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long aml_check_device_presence(struct aml_usbcam *usbcam_dev, unsigned long arg)
|
|
{
|
|
int ret;
|
|
int is_present = 0;
|
|
|
|
usbcam_dbg("check presence\n");
|
|
|
|
if (!arg) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (usbcam_dev && usbcam_dev->device_state != DEVICE_DISCONNECT)
|
|
is_present = 1;
|
|
|
|
ret = copy_to_user((int *)arg, &is_present, sizeof(int));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_intf_open(struct inode *node, struct file *filp)
|
|
{
|
|
int ret = 0;
|
|
int err_val = 0;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
usbcam_dbg("intf open\n");
|
|
|
|
if (!node || !filp) {
|
|
ret = -EINVAL;
|
|
err_val = -1;
|
|
goto error_handle;
|
|
}
|
|
|
|
usbcam_dev = container_of(node->i_cdev, struct aml_usbcam, cdev);
|
|
usbcam_dev->device_status = DEVICE_STATUS_RUNNING;
|
|
|
|
mutex_lock(&usbcam_dev->open_mux);
|
|
|
|
if ((filp->f_flags & 0x03) == O_RDONLY || (filp->f_flags & 0x03) == O_RDWR) {
|
|
usbcam_dev->read_open_status = READ_OPEN;
|
|
if (usbcam_dev->read_open_ref >= 1) {
|
|
mutex_unlock(&usbcam_dev->open_mux);
|
|
ret = -EPERM;
|
|
err_val = -3;
|
|
} else {
|
|
usbcam_dev->read_open_ref++;
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
goto error_handle;
|
|
|
|
if ((filp->f_flags & 0x03) == O_RDWR || (filp->f_flags & 0x03) == O_WRONLY) {
|
|
usbcam_dev->write_open_status = WRITE_OPEN;
|
|
if (usbcam_dev->write_open_ref >= 1) {
|
|
if ((filp->f_flags & 0x03) == O_RDWR)
|
|
usbcam_dev->read_open_ref--;
|
|
mutex_unlock(&usbcam_dev->open_mux);
|
|
ret = -EPERM;
|
|
err_val = -4;
|
|
} else {
|
|
usbcam_dev->write_open_ref++;
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
goto error_handle;
|
|
|
|
mutex_unlock(&usbcam_dev->open_mux);
|
|
|
|
kref_get(&usbcam_dev->ref_count);
|
|
|
|
filp->private_data = usbcam_dev;
|
|
usbcam_dbg("intf open success\n");
|
|
return 0;
|
|
|
|
error_handle:
|
|
usbcam_dbg("intf open failed! err_val:%d\n", err_val);
|
|
return ret;
|
|
}
|
|
|
|
static void aml_intf_read_complete(struct urb *urb)
|
|
{
|
|
int status;
|
|
ulong flag;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
if (!urb) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return;
|
|
}
|
|
|
|
status = urb->status;
|
|
usbcam_dev = urb->context;
|
|
|
|
spin_lock_irqsave(&usbcam_dev->read_lock, flag);
|
|
usbcam_dev->in_transfor = 0;
|
|
if (status) {
|
|
usbcam_dev->read_len = 0;
|
|
usbcam_dev->read_err = status;
|
|
usbcam_dev->read_status = READ_STATUS_ERROR;
|
|
usbcam_dbg("urb %p read urb error, status %d\n", urb, status);
|
|
} else {
|
|
usbcam_dev->read_len = urb->actual_length;
|
|
usbcam_dev->read_err = 0;
|
|
usbcam_dev->read_status = READ_STATUS_READY;
|
|
}
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
|
|
wake_up_interruptible(&usbcam_dev->read_wq);
|
|
}
|
|
|
|
static int aml_read_usb_submit(struct aml_usbcam *usbcam_dev)
|
|
{
|
|
int pipe;
|
|
int ret = 0;
|
|
|
|
if (usb_endpoint_is_int_in(usbcam_dev->in)) {
|
|
pipe = usb_rcvintpipe(usbcam_dev->usbdev, usbcam_dev->in->bEndpointAddress);
|
|
usb_fill_int_urb(usbcam_dev->read_urb, usbcam_dev->usbdev, pipe,
|
|
NULL, usbcam_dev->buf_size,
|
|
aml_intf_read_complete, usbcam_dev,
|
|
usbcam_dev->in->bInterval);
|
|
} else if (usb_endpoint_is_bulk_in(usbcam_dev->in)) {
|
|
pipe = usb_rcvbulkpipe(usbcam_dev->usbdev, usbcam_dev->in->bEndpointAddress);
|
|
usb_fill_bulk_urb(usbcam_dev->read_urb, usbcam_dev->usbdev, pipe,
|
|
NULL, usbcam_dev->buf_size,
|
|
aml_intf_read_complete, usbcam_dev);
|
|
}
|
|
|
|
usbcam_dev->read_urb->transfer_dma = usbcam_dev->read_dma;
|
|
usbcam_dev->read_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
|
|
ret = usb_submit_urb(usbcam_dev->read_urb, GFP_KERNEL);
|
|
if (ret) {
|
|
usbcam_dbg("submit urb %p failed, ret = %d", usbcam_dev->read_urb, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t aml_intf_read(struct file *filp, char *buff, size_t size, loff_t *ppos)
|
|
{
|
|
int ret = 0;
|
|
int err_val = 0;
|
|
ulong flag;
|
|
size_t available, transfor, chunk;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
if (!filp || !buff || !ppos) {
|
|
ret = -EINVAL;
|
|
err_val = -1;
|
|
goto error_handle;
|
|
}
|
|
|
|
usbcam_dev = filp->private_data;
|
|
if (!usbcam_dev) {
|
|
ret = -ENODEV;
|
|
err_val = -2;
|
|
goto error_handle;
|
|
}
|
|
|
|
if (!usbcam_dev->intf) {
|
|
ret = -ENODEV;
|
|
err_val = -3;
|
|
goto error_handle;
|
|
}
|
|
|
|
spin_lock_irqsave(&usbcam_dev->read_lock, flag);
|
|
if (usbcam_dev->read_status == READ_STATUS_READY) {
|
|
available = usbcam_dev->read_len - usbcam_dev->in_transfor;
|
|
if (!available)
|
|
usbcam_dev->read_status = READ_STATUS_EMPTY;
|
|
}
|
|
|
|
if (usbcam_dev->read_status == READ_STATUS_EMPTY ||
|
|
usbcam_dev->read_status == READ_STATUS_WAIT_SUBMIT) {
|
|
if (usbcam_dev->read_status == READ_STATUS_EMPTY) {
|
|
usbcam_dev->read_status = READ_STATUS_WAIT_SUBMIT;
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
ret = aml_read_usb_submit(usbcam_dev);
|
|
if (ret) {
|
|
usbcam_dbg("submit %p failed, ret = %d", usbcam_dev->read_urb, ret);
|
|
err_val = -4;
|
|
goto error_handle;
|
|
}
|
|
} else {
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
}
|
|
|
|
if (filp->f_flags & O_NONBLOCK) {
|
|
ret = -EAGAIN;
|
|
err_val = -5;
|
|
goto error_handle;
|
|
}
|
|
ret = wait_event_interruptible(usbcam_dev->read_wq,
|
|
(usbcam_dev->read_status == READ_STATUS_READY) ||
|
|
(usbcam_dev->read_status == READ_STATUS_ERROR) ||
|
|
(usbcam_dev->read_open_status == READ_CLOSE));
|
|
if (ret < 0) {
|
|
err_val = -6;
|
|
goto error_handle;
|
|
}
|
|
} else {
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
}
|
|
|
|
if (usbcam_dev->read_open_status == READ_CLOSE) {
|
|
usbcam_dbg(" read_open_status %d\n", usbcam_dev->read_open_status);
|
|
return 0;
|
|
}
|
|
|
|
if (usbcam_dev->read_status == READ_STATUS_ERROR) {
|
|
usbcam_dev->read_status = READ_STATUS_EMPTY;
|
|
ret = usbcam_dev->read_err;
|
|
err_val = -7;
|
|
goto error_handle;
|
|
}
|
|
|
|
spin_lock_irqsave(&usbcam_dev->read_lock, flag);
|
|
available = usbcam_dev->read_len - usbcam_dev->in_transfor;
|
|
transfor = usbcam_dev->in_transfor;
|
|
chunk = min(size, available);
|
|
usbcam_dev->in_transfor += chunk;
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
|
|
if (!usbcam_dev->read_buf) {
|
|
ret = -EFAULT;
|
|
err_val = -8;
|
|
goto error_handle;
|
|
}
|
|
|
|
ret = copy_to_user(buff, usbcam_dev->read_buf + transfor, chunk);
|
|
if (ret) {
|
|
err_val = -9;
|
|
goto error_handle;
|
|
}
|
|
|
|
spin_lock_irqsave(&usbcam_dev->read_lock, flag);
|
|
if (!(usbcam_dev->read_len - usbcam_dev->in_transfor))
|
|
usbcam_dev->read_status = READ_STATUS_EMPTY;
|
|
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
|
|
return chunk;
|
|
|
|
error_handle:
|
|
usbcam_dbg("intf read failed! err_val:%d ret:%d\n", err_val, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void aml_intf_write_empty_complete(struct urb *urb)
|
|
{
|
|
int status;
|
|
ulong flag;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
if (!urb) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return;
|
|
}
|
|
|
|
status = urb->status;
|
|
usbcam_dev = urb->context;
|
|
|
|
spin_lock_irqsave(&usbcam_dev->write_lock, flag);
|
|
if (status) {
|
|
usbcam_dev->write_len = 0;
|
|
usbcam_dev->write_err = status;
|
|
usbcam_dev->write_status = WRITE_STATUS_ERROR;
|
|
usbcam_dbg("urb %p write urb error, status %d\n", urb, status);
|
|
} else {
|
|
usbcam_dev->write_len = urb->actual_length;
|
|
usbcam_dev->write_err = 0;
|
|
usbcam_dev->write_status = WRITE_STATUS_READY;
|
|
}
|
|
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
|
|
wake_up_interruptible(&usbcam_dev->write_wq);
|
|
}
|
|
|
|
static int aml_write_usb_submit_empty(struct aml_usbcam *usbcam_dev, size_t size)
|
|
{
|
|
int pipe;
|
|
int retval = 0;
|
|
ulong flag;
|
|
|
|
if (!usbcam_dev->intf) {
|
|
usbcam_dbg("packet write failed,interface is deregistered\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
spin_lock_irqsave(&usbcam_dev->write_lock, flag);
|
|
usbcam_dev->write_status = WRITE_STATUS_WAIT_EMPTY_DONE;
|
|
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
|
|
|
|
if (usb_endpoint_is_int_out(usbcam_dev->out)) {
|
|
pipe = usb_sndintpipe(usbcam_dev->usbdev,
|
|
usbcam_dev->out->bEndpointAddress);
|
|
|
|
usb_fill_int_urb(usbcam_dev->write_urb, usbcam_dev->usbdev,
|
|
pipe, NULL, 0, aml_intf_write_empty_complete, usbcam_dev,
|
|
usbcam_dev->out->bInterval);
|
|
} else {
|
|
pipe = usb_sndbulkpipe(usbcam_dev->usbdev,
|
|
usbcam_dev->out->bEndpointAddress);
|
|
|
|
usb_fill_bulk_urb(usbcam_dev->write_urb, usbcam_dev->usbdev,
|
|
pipe, NULL, 0, aml_intf_write_empty_complete, usbcam_dev);
|
|
}
|
|
|
|
retval = usb_submit_urb(usbcam_dev->write_urb, GFP_KERNEL);
|
|
if (retval) {
|
|
usbcam_dbg("submit zero-length packet urb %p failed, return = %d\n",
|
|
usbcam_dev->write_urb, retval);
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_write_submit_empty_thread(void *data)
|
|
{
|
|
struct aml_usbcam *usbcam_dev = data;
|
|
size_t ret = 0;
|
|
|
|
while (1) {
|
|
ret = wait_event_interruptible(usbcam_dev->write_wq,
|
|
(usbcam_dev->device_status == DEVICE_STATUS_DISCONNECT) ||
|
|
(usbcam_dev->write_status == WRITE_STATUS_EMPTY));
|
|
if (usbcam_dev->device_status == DEVICE_STATUS_DISCONNECT) {
|
|
usbcam_dbg("device status is disconnect\n");
|
|
break;
|
|
}
|
|
if (usbcam_dev->write_status != WRITE_STATUS_WAIT_TS_DONE &&
|
|
usbcam_dev->write_status != WRITE_STATUS_WAIT_EMPTY_DONE) {
|
|
aml_write_usb_submit_empty(usbcam_dev, usbcam_dev->write_size);
|
|
} else {
|
|
usbcam_dbg("urb already submitted,unable to repeat submit\n");
|
|
return -EAGAIN;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void aml_intf_write_complete(struct urb *urb)
|
|
{
|
|
int status;
|
|
ulong flag;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
if (!urb) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return;
|
|
}
|
|
|
|
status = urb->status;
|
|
usbcam_dev = urb->context;
|
|
|
|
spin_lock_irqsave(&usbcam_dev->write_lock, flag);
|
|
if (status) {
|
|
usbcam_dev->write_len = 0;
|
|
usbcam_dev->write_err = status;
|
|
usbcam_dev->write_status = WRITE_STATUS_ERROR;
|
|
usbcam_dbg("urb %p write urb error, status %d\n", urb, status);
|
|
} else {
|
|
usbcam_dev->write_len = urb->actual_length;
|
|
usbcam_dev->write_err = 0;
|
|
|
|
if ((usbcam_dev->write_size % le16_to_cpu(usbcam_dev->out->wMaxPacketSize) == 0))
|
|
usbcam_dev->write_status = WRITE_STATUS_EMPTY;
|
|
else
|
|
usbcam_dev->write_status = WRITE_STATUS_READY;
|
|
}
|
|
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
|
|
wake_up_interruptible(&usbcam_dev->write_wq);
|
|
}
|
|
|
|
static int aml_write_usb_submit(struct aml_usbcam *usbcam_dev, size_t size)
|
|
{
|
|
int pipe;
|
|
int retval = 0;
|
|
ulong flag;
|
|
|
|
spin_lock_irqsave(&usbcam_dev->write_lock, flag);
|
|
usbcam_dev->write_status = WRITE_STATUS_WAIT_TS_DONE;
|
|
usbcam_dev->write_size = size;
|
|
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
|
|
if (usb_endpoint_is_int_out(usbcam_dev->out)) {
|
|
pipe = usb_sndintpipe(usbcam_dev->usbdev,
|
|
usbcam_dev->out->bEndpointAddress);
|
|
|
|
usb_fill_int_urb(usbcam_dev->write_urb, usbcam_dev->usbdev,
|
|
pipe, NULL, size,
|
|
aml_intf_write_complete, usbcam_dev,
|
|
usbcam_dev->out->bInterval);
|
|
} else {
|
|
pipe = usb_sndbulkpipe(usbcam_dev->usbdev,
|
|
usbcam_dev->out->bEndpointAddress);
|
|
|
|
usb_fill_bulk_urb(usbcam_dev->write_urb, usbcam_dev->usbdev,
|
|
pipe, NULL, size,
|
|
aml_intf_write_complete, usbcam_dev);
|
|
}
|
|
|
|
usbcam_dev->write_urb->transfer_dma = usbcam_dev->write_dma;
|
|
usbcam_dev->write_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
|
|
retval = usb_submit_urb(usbcam_dev->write_urb, GFP_KERNEL);
|
|
if (retval) {
|
|
usbcam_dbg("submit urb %p failed, return = %d\n",
|
|
usbcam_dev->write_urb, retval);
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t aml_intf_write(struct file *filp, const char *buff, size_t size, loff_t *ppos)
|
|
{
|
|
int ret = 0;
|
|
int err_val = 0;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
size_t writesize;
|
|
ulong flag;
|
|
|
|
if (!filp || !buff || !ppos) {
|
|
ret = -EINVAL;
|
|
err_val = -1;
|
|
goto error_handle;
|
|
}
|
|
|
|
usbcam_dev = filp->private_data;
|
|
if (!usbcam_dev) {
|
|
ret = -ENODEV;
|
|
err_val = -2;
|
|
goto error_handle;
|
|
}
|
|
|
|
if (!usbcam_dev->intf) {
|
|
ret = -ENODEV;
|
|
err_val = -3;
|
|
goto error_handle;
|
|
}
|
|
|
|
if (usbcam_dev->write_status != WRITE_STATUS_READY) {
|
|
if (filp->f_flags & O_NONBLOCK) {
|
|
ret = -EAGAIN;
|
|
err_val = -4;
|
|
goto error_handle;
|
|
}
|
|
|
|
ret = wait_event_interruptible(usbcam_dev->write_wq,
|
|
(usbcam_dev->write_status == WRITE_STATUS_READY) ||
|
|
(usbcam_dev->write_status == WRITE_STATUS_ERROR) ||
|
|
(usbcam_dev->write_open_status == WRITE_CLOSE));
|
|
if (ret < 0) {
|
|
err_val = -5;
|
|
goto error_handle;
|
|
}
|
|
}
|
|
|
|
if (usbcam_dev->write_open_status == WRITE_CLOSE) {
|
|
ret = 0;
|
|
err_val = -6;
|
|
goto error_handle;
|
|
}
|
|
|
|
spin_lock_irqsave(&usbcam_dev->write_lock, flag);
|
|
if (usbcam_dev->write_status == WRITE_STATUS_ERROR) {
|
|
usbcam_dev->write_status = WRITE_STATUS_READY;
|
|
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
|
|
ret = usbcam_dev->write_err;
|
|
err_val = -7;
|
|
goto error_handle;
|
|
}
|
|
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
|
|
|
|
if (!usbcam_dev->write_buf) {
|
|
ret = -EFAULT;
|
|
err_val = -8;
|
|
goto error_handle;
|
|
}
|
|
|
|
size = min(size, usbcam_dev->buf_size);
|
|
if (copy_from_user(usbcam_dev->write_buf, buff, size)) {
|
|
ret = -EFAULT;
|
|
err_val = -9;
|
|
goto error_handle;
|
|
}
|
|
|
|
writesize = size;
|
|
|
|
if (usbcam_dev->write_status != WRITE_STATUS_WAIT_TS_DONE &&
|
|
usbcam_dev->write_status != WRITE_STATUS_WAIT_EMPTY_DONE) {
|
|
ret = aml_write_usb_submit(usbcam_dev, size);//bulk transfer
|
|
if (ret) {
|
|
err_val = -10;
|
|
goto error_handle;
|
|
}
|
|
} else {
|
|
ret = -EAGAIN;
|
|
err_val = -11;
|
|
goto error_handle;
|
|
}
|
|
|
|
usbcam_dbg("intf write success\n");
|
|
return writesize;
|
|
|
|
error_handle:
|
|
usbcam_dbg("intf write failed! err_val:%d ret:%d\n", err_val, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void aml_usbcam_delete(struct kref *ref)
|
|
{
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
if (!ref) {
|
|
usbcam_dbg("invalid parameter");
|
|
return;
|
|
}
|
|
|
|
usbcam_dev = container_of(ref, struct aml_usbcam, ref_count);
|
|
usbcam_dev->device_status = DEVICE_STATUS_DISCONNECT;
|
|
|
|
usb_put_dev(usbcam_dev->usbdev);
|
|
|
|
usb_kill_urb(usbcam_dev->read_urb);
|
|
usb_kill_urb(usbcam_dev->write_urb);
|
|
|
|
if (usbcam_dev->read_buf)
|
|
usb_free_coherent(usbcam_dev->usbdev, usbcam_dev->buf_size,
|
|
usbcam_dev->read_buf, usbcam_dev->read_dma);
|
|
if (usbcam_dev->write_buf)
|
|
usb_free_coherent(usbcam_dev->usbdev, usbcam_dev->buf_size,
|
|
usbcam_dev->write_buf, usbcam_dev->write_dma);
|
|
|
|
usb_free_urb(usbcam_dev->read_urb);
|
|
usb_free_urb(usbcam_dev->write_urb);
|
|
|
|
wake_up_interruptible(&usbcam_dev->write_wq);
|
|
wake_up_interruptible(&usbcam_dev->read_wq);
|
|
|
|
if (!IS_ERR_OR_NULL(usbcam_dev->thread))
|
|
kthread_stop(usbcam_dev->thread);
|
|
kfree(usbcam_dev);
|
|
}
|
|
|
|
static int aml_intf_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
int ret;
|
|
|
|
if (!inode || !filp) {
|
|
ret = -EINVAL;
|
|
goto error_handle;
|
|
}
|
|
|
|
usbcam_dev = filp->private_data;
|
|
if (!usbcam_dev) {
|
|
ret = -ENODEV;
|
|
goto error_handle;
|
|
}
|
|
|
|
mutex_lock(&usbcam_dev->open_mux);
|
|
|
|
if ((filp->f_flags & 0x03) == O_RDONLY || (filp->f_flags & 0x03) == O_RDWR) {
|
|
usbcam_dev->read_open_status = READ_CLOSE;
|
|
if (atomic_read(&usbcam_dev->read_urb->use_count))
|
|
usb_unlink_urb(usbcam_dev->read_urb);
|
|
|
|
if (usbcam_dev->read_open_ref >= 1) {
|
|
usbcam_dev->read_open_ref--;
|
|
usbcam_dbg("interface read fd release successfully\n");
|
|
} else {
|
|
usbcam_dbg("interface read fd is released before\n");
|
|
}
|
|
}
|
|
|
|
if ((filp->f_flags & 0x03) == O_RDWR || (filp->f_flags & 0x03) == O_WRONLY) {
|
|
usbcam_dev->write_open_status = WRITE_CLOSE;
|
|
if (atomic_read(&usbcam_dev->write_urb->use_count))
|
|
usb_unlink_urb(usbcam_dev->write_urb);
|
|
|
|
if (usbcam_dev->write_open_ref >= 1) {
|
|
usbcam_dev->write_open_ref--;
|
|
usbcam_dbg("interface write fd release successfully\n");
|
|
} else {
|
|
usbcam_dbg("interface write fd is released before\n");
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&usbcam_dev->open_mux);
|
|
|
|
wake_up_interruptible(&usbcam_dev->read_wq);
|
|
wake_up_interruptible(&usbcam_dev->write_wq);
|
|
|
|
usbcam_dev->write_err = 0;
|
|
usbcam_dev->read_err = 0;
|
|
|
|
kref_put(&usbcam_dev->ref_count, aml_usbcam_delete);
|
|
|
|
return 0;
|
|
|
|
error_handle:
|
|
usbcam_dbg("intf release failed! ret:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static long aml_intf_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
usbcam_dev = filp->private_data;
|
|
|
|
if (!usbcam_dev) {
|
|
usbcam_dbg("no device connected\r\n");
|
|
ret = -ENODEV;
|
|
return ret;
|
|
}
|
|
|
|
usbcam_dbg("ioctl op:\n");
|
|
switch (cmd) {
|
|
case AML_USBCAM_IOC_GET_DRIVER_VERSION:
|
|
ret = aml_get_usbcam_version(usbcam_dev, arg);
|
|
break;
|
|
case AML_USBCAM_IOC_GET_INFO:
|
|
ret = aml_get_usbcam_info(usbcam_dev, arg);
|
|
break;
|
|
case AML_USBCAM_IOC_RESET:
|
|
usbcam_dbg("reset\n");
|
|
ret = usb_lock_device_for_reset(usbcam_dev->usbdev, NULL);
|
|
usb_reset_device(usbcam_dev->usbdev);
|
|
usb_unlock_device(usbcam_dev->usbdev);
|
|
break;
|
|
case AML_USBCAM_IOC_CANCEL_TRANSFER:
|
|
ret = aml_cancel_transfer(filp, usbcam_dev);
|
|
break;
|
|
case AML_USBCAM_IOC_MODULE_CAPABILITIES:
|
|
ret = aml_get_usbcam_capabilities(usbcam_dev, arg);
|
|
break;
|
|
case AML_USBCAM_IOC_GET_MODULE_STATE:
|
|
ret = aml_get_usbcam_state(usbcam_dev, arg);
|
|
break;
|
|
case AML_USBCAM_IOC_SET_MODULE_STATE:
|
|
ret = aml_set_usbcam_state(usbcam_dev, arg);
|
|
break;
|
|
case AML_USBCAM_IOC_CHECK_DEVICE_PRESENCE:
|
|
ret = aml_check_device_presence(usbcam_dev, arg);
|
|
break;
|
|
default:
|
|
usbcam_dbg("unknown op type %08x", cmd);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
usbcam_dbg("failed! ret: %d", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long aml_intf_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
unsigned long translated_arg;
|
|
|
|
translated_arg = (unsigned long)compat_ptr(arg);
|
|
ret = aml_intf_ioctl(filp, cmd, translated_arg);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static __poll_t aml_intf_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
struct aml_usbcam *usbcam_dev;
|
|
__poll_t mask = 0;
|
|
size_t ret = 0;
|
|
ulong flag;
|
|
|
|
usbcam_dev = file->private_data;
|
|
|
|
if (!usbcam_dev) {
|
|
usbcam_dbg("invalid parameter\n");
|
|
return -EPOLLERR;
|
|
}
|
|
|
|
spin_lock_irqsave(&usbcam_dev->read_lock, flag);
|
|
if (usbcam_dev->read_status == READ_STATUS_READY) {
|
|
if (!(usbcam_dev->read_len - usbcam_dev->in_transfor))
|
|
usbcam_dev->read_status = READ_STATUS_EMPTY;
|
|
}
|
|
|
|
if (usbcam_dev->read_status == READ_STATUS_EMPTY ||
|
|
usbcam_dev->read_status == READ_STATUS_WAIT_SUBMIT) {
|
|
if (usbcam_dev->read_status == READ_STATUS_EMPTY) {
|
|
usbcam_dev->read_status = READ_STATUS_WAIT_SUBMIT;
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
ret = aml_read_usb_submit(usbcam_dev);
|
|
if (ret)
|
|
usbcam_dbg("poll submit failed");
|
|
} else {
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
}
|
|
} else {
|
|
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
|
|
}
|
|
|
|
poll_wait(file, &usbcam_dev->read_wq, wait);
|
|
poll_wait(file, &usbcam_dev->write_wq, wait);
|
|
|
|
if (usbcam_dev->write_status == WRITE_STATUS_READY)
|
|
mask |= EPOLLOUT | EPOLLWRNORM;
|
|
|
|
if (usbcam_dev->read_status == READ_STATUS_READY)
|
|
mask |= EPOLLIN | EPOLLRDNORM;
|
|
|
|
if (usbcam_dev->write_status == WRITE_STATUS_ERROR ||
|
|
usbcam_dev->read_status == READ_STATUS_ERROR)
|
|
mask |= EPOLLERR;
|
|
|
|
return mask;
|
|
}
|
|
|
|
static const struct file_operations aml_intf_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = aml_intf_open,
|
|
.release = aml_intf_release,
|
|
.read = aml_intf_read,
|
|
.write = aml_intf_write,
|
|
.poll = aml_intf_poll,
|
|
//.mmap = aml_intf_mmap,
|
|
.unlocked_ioctl = aml_intf_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = aml_intf_compat_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static int aml_usbcam_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
|
{
|
|
int ret = -ENOMEM;
|
|
int extrasize = 0;
|
|
int len, temp;
|
|
int idx = -1;
|
|
int i;
|
|
int err_val = 0;
|
|
dev_t devno;
|
|
unsigned char intf_num;
|
|
unsigned char descriptorlen = 0;
|
|
unsigned char descriptortype = 0;
|
|
unsigned char *extratemp = NULL;
|
|
|
|
const char *dev_name_fmt;
|
|
struct usb_device *pdev = NULL;
|
|
struct usb_interface_assoc_descriptor *assoc_desc = NULL;
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
struct aml_usbcam **dev_array;
|
|
|
|
if (!intf || !id) {
|
|
err_val = -1;
|
|
ret = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
pdev = interface_to_usbdev(intf);
|
|
intf_num = intf->cur_altsetting->desc.bInterfaceNumber;
|
|
|
|
assoc_desc = pdev->actconfig->intf_assoc[0];
|
|
usbcam_dbg("assoc_desc = %p\n", assoc_desc);
|
|
if (!assoc_desc) {
|
|
err_val = -2;
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
if (!(assoc_desc->bFunctionClass == IAD_FUNCTION_CLASS &&
|
|
assoc_desc->bFunctionSubClass == IAD_FUNCTION_SUBCLASS &&
|
|
assoc_desc->bFunctionProtocol == IAD_FUNCTION_PROTOCOL) &&
|
|
!(assoc_desc->bFunctionClass == CI20_IAD_FUNCTION_CLASS &&
|
|
assoc_desc->bFunctionSubClass == CI20_IAD_FUNCTION_SUBCLASS &&
|
|
assoc_desc->bFunctionProtocol == CI20_IAD_FUNCTION_PROTOCOL)) {
|
|
err_val = -3;
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
if (intf_num < assoc_desc->bFirstInterface ||
|
|
intf_num > assoc_desc->bFirstInterface + assoc_desc->bInterfaceCount) {
|
|
err_val = -4;
|
|
ret = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
usbcam_dev = kzalloc(sizeof(*usbcam_dev), GFP_KERNEL);
|
|
if (!usbcam_dev) {
|
|
err_val = -5;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
if ((intf->cur_altsetting->desc.bInterfaceClass == COMMAND_INTF_CLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceSubClass == COMMAND_INTF_SUBCLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceProtocol == COMMAND_INTF_PROTOCOL) ||
|
|
(intf->cur_altsetting->desc.bInterfaceClass == CI20_COMMAND_INTF_CLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceSubClass == CI20_COMMAND_INTF_SUBCLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceProtocol == CI20_COMMAND_INTF_PROTOCOL)) {
|
|
usbcam_dev->device_type = DEVICE_COMMAND;
|
|
|
|
usbcam_dev->buf_size = USBCAM_CMD_BUFFER_SIZE;
|
|
usbcam_dev->read_buf = usb_alloc_coherent(pdev, usbcam_dev->buf_size,
|
|
GFP_KERNEL, &usbcam_dev->read_dma);
|
|
if (!usbcam_dev->read_buf) {
|
|
err_val = -6;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
usbcam_dev->write_buf = usb_alloc_coherent(pdev, usbcam_dev->buf_size,
|
|
GFP_KERNEL, &usbcam_dev->write_dma);
|
|
if (!usbcam_dev->write_buf) {
|
|
err_val = -7;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
if (intf->cur_altsetting->desc.bInterfaceClass == CI20_COMMAND_INTF_CLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceSubClass == CI20_COMMAND_INTF_SUBCLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceProtocol == CI20_COMMAND_INTF_PROTOCOL) {
|
|
usbcam_dev->usbcam_module_info.is_ci20_detected = TRUE;
|
|
usbcam_dbg("is_ci20_detected = %d\n",
|
|
usbcam_dev->usbcam_module_info.is_ci20_detected);
|
|
}
|
|
|
|
} else if ((intf->cur_altsetting->desc.bInterfaceClass == MEDIA_INTF_CLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceSubClass == MEDIA_INTF_SUBCLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceProtocol == MEDIA_INTF_PROTOCOL) ||
|
|
(intf->cur_altsetting->desc.bInterfaceClass == CI20_MEDIA_INTF_CLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceSubClass == CI20_MEDIA_INTF_SUBCLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceProtocol == CI20_MEDIA_INTF_PROTOCOL)) {
|
|
usbcam_dev->device_type = DEVICE_MEDIA;
|
|
|
|
usbcam_dev->buf_size = USBCAM_MEDIA_BUFFER_SIZE;
|
|
usbcam_dev->read_buf = usb_alloc_coherent(pdev, usbcam_dev->buf_size,
|
|
GFP_KERNEL, &usbcam_dev->read_dma);
|
|
if (!usbcam_dev->read_buf) {
|
|
err_val = -8;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
usbcam_dev->write_buf = usb_alloc_coherent(pdev, usbcam_dev->buf_size,
|
|
GFP_KERNEL, &usbcam_dev->write_dma);
|
|
if (!usbcam_dev->write_buf) {
|
|
err_val = -9;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
if (intf->cur_altsetting->desc.bInterfaceClass == CI20_MEDIA_INTF_CLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceSubClass == CI20_MEDIA_INTF_SUBCLASS &&
|
|
intf->cur_altsetting->desc.bInterfaceProtocol == CI20_MEDIA_INTF_PROTOCOL) {
|
|
usbcam_dev->usbcam_module_info.is_ci20_detected = TRUE;
|
|
usbcam_dbg("is_ci20_detected = %d\n",
|
|
usbcam_dev->usbcam_module_info.is_ci20_detected);
|
|
}
|
|
}
|
|
|
|
extratemp = pdev->actconfig->extra;
|
|
extrasize = pdev->actconfig->extralen;
|
|
while (extrasize > 0) {
|
|
descriptorlen = *extratemp;
|
|
descriptortype = *(extratemp + 1);
|
|
if (descriptorlen > extrasize || descriptorlen < 2) {
|
|
err_val = -10;
|
|
goto error;
|
|
}
|
|
if (descriptortype != 0x41 || descriptorlen != 6) {
|
|
usbcam_dbg("extra data contains an %s descriptor of type 0x%02X",
|
|
descriptortype == 0x0B ? "interface association" : "unexpected",
|
|
descriptortype);
|
|
extratemp += descriptorlen;
|
|
extrasize -= descriptorlen;
|
|
} else {
|
|
//0x41: compatibility descriptor type, 6:compatibility descriptor length
|
|
usbcam_dev->usbcam_module_info.ci_compatibility = *(extratemp + 2) << 24 |
|
|
*(extratemp + 3) << 16 | *(extratemp + 4) << 8 | *(extratemp + 5);
|
|
|
|
if ((usbcam_dev->usbcam_module_info.ci_compatibility & 0x07) != 2) {
|
|
usbcam_dbg("this driver no support ARCH value:%d",
|
|
usbcam_dev->usbcam_module_info.ci_compatibility & 0x07);
|
|
err_val = -11;
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
usbcam_dev->usbdev = usb_get_dev(interface_to_usbdev(intf));
|
|
usbcam_dev->intf = intf;
|
|
|
|
usbcam_dbg("out endpoint is\n");
|
|
if (usb_endpoint_dir_out(&intf->cur_altsetting->endpoint[0].desc)) {
|
|
usbcam_dbg(" endpoint[0]!\n");
|
|
usbcam_dev->out = &intf->cur_altsetting->endpoint[0].desc;
|
|
} else if (usb_endpoint_dir_out(&intf->cur_altsetting->endpoint[1].desc)) {
|
|
usbcam_dbg(" endpoint[1]!\n");
|
|
usbcam_dev->out = &intf->cur_altsetting->endpoint[1].desc;
|
|
} else {
|
|
usbcam_dbg(" not found\n");
|
|
}
|
|
|
|
usbcam_dbg("in endpoint is\n");
|
|
if (usb_endpoint_dir_in(&intf->cur_altsetting->endpoint[0].desc)) {
|
|
usbcam_dbg(" endpoint[0]!\n");
|
|
usbcam_dev->in = &intf->cur_altsetting->endpoint[0].desc;
|
|
} else if (usb_endpoint_dir_in(&intf->cur_altsetting->endpoint[1].desc)) {
|
|
usbcam_dbg(" endpoint[1]!\n");
|
|
usbcam_dev->in = &intf->cur_altsetting->endpoint[1].desc;
|
|
} else {
|
|
usbcam_dbg(" not found\n");
|
|
}
|
|
|
|
usbcam_dbg("endpoint_type(in) = %d\n", usb_endpoint_type(usbcam_dev->in));
|
|
usbcam_dbg("endpoint_type(out) = %d\n", usb_endpoint_type(usbcam_dev->out));
|
|
|
|
usbcam_dbg("bulk_out(out) = %d\n", usb_endpoint_is_bulk_out(usbcam_dev->out));
|
|
usbcam_dbg("int_out(out) = %d\n", usb_endpoint_is_int_out(usbcam_dev->out));
|
|
|
|
usbcam_dbg("is_bulk_in(in) = %d\n", usb_endpoint_is_bulk_in(usbcam_dev->in));
|
|
usbcam_dbg("is_int_in(in) = %d\n", usb_endpoint_is_int_in(usbcam_dev->in));
|
|
|
|
if ((!usb_endpoint_is_bulk_out(usbcam_dev->out) &&
|
|
!usb_endpoint_is_int_out(usbcam_dev->out)) ||
|
|
(!usb_endpoint_is_bulk_in(usbcam_dev->in) &&
|
|
!usb_endpoint_is_int_in(usbcam_dev->in))) {
|
|
usbcam_dbg("mismatched endpoint type in interface\n");
|
|
err_val = -12;
|
|
goto error;
|
|
}
|
|
|
|
usbcam_dev->read_open_ref = 0;
|
|
usbcam_dev->write_open_ref = 0;
|
|
mutex_init(&usbcam_dev->open_mux);
|
|
|
|
usbcam_dev->usbcam_module_info.vendor_id = le16_to_cpu(pdev->descriptor.idVendor);
|
|
usbcam_dev->usbcam_module_info.product_id = le16_to_cpu(pdev->descriptor.idProduct);
|
|
|
|
if (pdev->manufacturer) {
|
|
usbcam_dbg("pdev->manufacturer = %s\n", pdev->manufacturer);
|
|
temp = strlen(pdev->manufacturer);
|
|
len = temp < AML_USBCAM_MAX_NAME_LEN ? temp : (AML_USBCAM_MAX_NAME_LEN - 1);
|
|
strncpy(usbcam_dev->usbcam_module_capabilities.ci_manufacturer_name,
|
|
pdev->manufacturer, len);
|
|
usbcam_dev->usbcam_module_capabilities.ci_manufacturer_name[len] = '\0';
|
|
} else {
|
|
err_val = -13;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
if (pdev->product) {
|
|
usbcam_dbg("pdev->product = %s\n", pdev->product);
|
|
temp = strlen(pdev->product);
|
|
len = temp < AML_USBCAM_MAX_NAME_LEN ? temp : (AML_USBCAM_MAX_NAME_LEN - 1);
|
|
strncpy(usbcam_dev->usbcam_module_capabilities.ci_product_name,
|
|
pdev->product, len);
|
|
usbcam_dev->usbcam_module_capabilities.ci_product_name[len] = '\0';
|
|
} else {
|
|
err_val = -14;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
usbcam_dev->usbcam_module_capabilities.ci_plus_supported = TRUE;
|
|
usbcam_dev->usbcam_module_capabilities.op_profile_supported = TRUE;
|
|
|
|
kref_init(&usbcam_dev->ref_count);
|
|
mutex_init(&usbcam_dev->read_mux);
|
|
mutex_init(&usbcam_dev->write_mux);
|
|
|
|
init_waitqueue_head(&usbcam_dev->read_wq);
|
|
init_waitqueue_head(&usbcam_dev->write_wq);
|
|
|
|
spin_lock_init(&usbcam_dev->read_lock);
|
|
spin_lock_init(&usbcam_dev->write_lock);
|
|
|
|
usbcam_dev->thread = kthread_run(aml_write_submit_empty_thread, usbcam_dev, "%s",
|
|
"aml_write_submit_empty_thread");
|
|
if (IS_ERR(usbcam_dev->thread)) {
|
|
err_val = -15;
|
|
ret = PTR_ERR(usbcam_dev->thread);
|
|
goto error;
|
|
}
|
|
|
|
usbcam_dev->read_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!usbcam_dev->read_urb) {
|
|
err_val = -16;
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
usbcam_dev->write_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!usbcam_dev->write_urb) {
|
|
err_val = -17;
|
|
ret = -ENOMEM;
|
|
goto error_free_read_urb;
|
|
}
|
|
|
|
usbcam_dev->read_status = READ_STATUS_EMPTY;
|
|
usbcam_dev->device_status = DEVICE_STATUS_MATCH;
|
|
usbcam_dev->device_state = DEVICE_CONNECT;
|
|
|
|
usb_set_intfdata(intf, usbcam_dev);
|
|
|
|
if (usbcam_dev->device_type == DEVICE_COMMAND) {
|
|
dev_name_fmt = "cimodule_command%d";
|
|
dev_array = command_devices;
|
|
} else {
|
|
dev_name_fmt = "cimodule_media%d";
|
|
dev_array = media_devices;
|
|
}
|
|
|
|
mutex_lock(&ci_devices_mutex);
|
|
for (i = 0; i < MAX_CI_DEVICES; i++) {
|
|
if (!dev_array[i]) {
|
|
idx = i;
|
|
dev_array[i] = usbcam_dev;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&ci_devices_mutex);
|
|
|
|
if (idx < 0) {
|
|
err_val = -18;
|
|
ret = -ENOSPC;
|
|
goto error_clear_intfdata;
|
|
}
|
|
usbcam_dev->dev_idx = idx;
|
|
|
|
if (usbcam_dev->device_type == DEVICE_COMMAND)
|
|
devno = MKDEV(MAJOR(aml_usbcam_devt), 2 * idx);
|
|
else
|
|
devno = MKDEV(MAJOR(aml_usbcam_devt), 2 * idx + 1);
|
|
|
|
cdev_init(&usbcam_dev->cdev, &aml_intf_fops);
|
|
usbcam_dev->cdev.owner = THIS_MODULE;
|
|
|
|
ret = cdev_add(&usbcam_dev->cdev, devno, 1);
|
|
if (ret) {
|
|
err_val = -19;
|
|
goto error_clear_dev_array;
|
|
}
|
|
|
|
usbcam_dev->dev = device_create(aml_usbcam_ci_class, &intf->dev, devno,
|
|
NULL, dev_name_fmt, idx);
|
|
if (IS_ERR(usbcam_dev->dev)) {
|
|
err_val = -20;
|
|
ret = PTR_ERR(usbcam_dev->dev);
|
|
goto error_del_cdev;
|
|
}
|
|
|
|
usbcam_dbg("probe success\n");
|
|
return 0;
|
|
|
|
error_del_cdev:
|
|
cdev_del(&usbcam_dev->cdev);
|
|
error_clear_dev_array:
|
|
mutex_lock(&ci_devices_mutex);
|
|
if (usbcam_dev->device_type == DEVICE_COMMAND)
|
|
command_devices[usbcam_dev->dev_idx] = NULL;
|
|
else
|
|
media_devices[usbcam_dev->dev_idx] = NULL;
|
|
mutex_unlock(&ci_devices_mutex);
|
|
error_clear_intfdata:
|
|
usb_set_intfdata(intf, NULL);
|
|
error_free_read_urb:
|
|
usb_free_urb(usbcam_dev->read_urb);
|
|
usbcam_dev->read_urb = NULL;
|
|
error:
|
|
usbcam_dbg("probe failed! err_val:%d ret:%d\n", err_val, ret);
|
|
if (usbcam_dev) {
|
|
kref_put(&usbcam_dev->ref_count, aml_usbcam_delete);
|
|
if (usbcam_dev->read_buf)
|
|
usb_free_coherent(usbcam_dev->usbdev, usbcam_dev->buf_size,
|
|
usbcam_dev->read_buf, usbcam_dev->read_dma);
|
|
if (usbcam_dev->write_buf)
|
|
usb_free_coherent(usbcam_dev->usbdev, usbcam_dev->buf_size,
|
|
usbcam_dev->write_buf, usbcam_dev->write_dma);
|
|
kfree(usbcam_dev);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void aml_usbcam_disconnect(struct usb_interface *intf)
|
|
{
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
dev_t devno;
|
|
int idx;
|
|
|
|
usbcam_dev = usb_get_intfdata(intf);
|
|
if (!usbcam_dev)
|
|
return;
|
|
|
|
idx = usbcam_dev->dev_idx;
|
|
if (usbcam_dev->device_type == DEVICE_COMMAND)
|
|
devno = MKDEV(MAJOR(aml_usbcam_devt), 2 * idx);
|
|
else
|
|
devno = MKDEV(MAJOR(aml_usbcam_devt), 2 * idx + 1);
|
|
|
|
device_destroy(aml_usbcam_ci_class, devno);
|
|
cdev_del(&usbcam_dev->cdev);
|
|
|
|
mutex_lock(&ci_devices_mutex);
|
|
if (usbcam_dev->device_type == DEVICE_COMMAND)
|
|
command_devices[idx] = NULL;
|
|
else
|
|
media_devices[idx] = NULL;
|
|
mutex_unlock(&ci_devices_mutex);
|
|
|
|
|
|
usb_set_intfdata(usbcam_dev->intf, NULL);
|
|
usbcam_dev->intf = NULL;
|
|
|
|
kref_put(&usbcam_dev->ref_count, aml_usbcam_delete);
|
|
}
|
|
|
|
static int aml_usbcam_pre_reset(struct usb_interface *intf)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int aml_usbcam_post_reset(struct usb_interface *intf)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int aml_usbcam_suspend(struct usb_interface *intf, pm_message_t msg)
|
|
{
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
|
|
if (!intf)
|
|
return 0;
|
|
|
|
dev_set_uevent_suppress(&intf->dev, true);
|
|
|
|
usbcam_dev = usb_get_intfdata(intf);
|
|
usb_kill_urb(usbcam_dev->read_urb);
|
|
usb_kill_urb(usbcam_dev->write_urb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_usbcam_resume(struct usb_interface *intf)
|
|
{
|
|
struct aml_usbcam *usbcam_dev = NULL;
|
|
int ret;
|
|
int retval = 0;
|
|
|
|
if (!intf)
|
|
return 0;
|
|
dev_set_uevent_suppress(&intf->dev, false);
|
|
|
|
usbcam_dev = usb_get_intfdata(intf);
|
|
ret = usb_reset_device(usbcam_dev->usbdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (usbcam_dev->read_open_status == READ_OPEN) {
|
|
retval = usb_submit_urb(usbcam_dev->read_urb, GFP_KERNEL);
|
|
if (retval)
|
|
return retval;
|
|
}
|
|
if (usbcam_dev->write_open_status == WRITE_OPEN) {
|
|
retval = usb_submit_urb(usbcam_dev->write_urb, GFP_KERNEL);
|
|
if (retval)
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct usb_driver aml_usbcam_driver = {
|
|
.name = "aml_usbcam",
|
|
.id_table = aml_usbcam_table,
|
|
.probe = aml_usbcam_probe,
|
|
.disconnect = aml_usbcam_disconnect,
|
|
.pre_reset = aml_usbcam_pre_reset,
|
|
.post_reset = aml_usbcam_post_reset,
|
|
.suspend = aml_usbcam_suspend,
|
|
.resume = aml_usbcam_resume,
|
|
};
|
|
|
|
static int __init aml_usbcam_init(void)
|
|
{
|
|
int ret;
|
|
int err_val;
|
|
|
|
usbcam_dbg("usbcam init!\n");
|
|
|
|
ret = alloc_chrdev_region(&aml_usbcam_devt, 0, MAX_CI_DEVICES * 2, "aml_usbcam_ci");
|
|
if (ret < 0) {
|
|
err_val = -1;
|
|
goto error_handle;
|
|
}
|
|
|
|
aml_usbcam_ci_class = class_create("aml_usbcam_ci");
|
|
if (IS_ERR(aml_usbcam_ci_class)) {
|
|
unregister_chrdev_region(aml_usbcam_devt, MAX_CI_DEVICES * 2);
|
|
ret = PTR_ERR(aml_usbcam_ci_class);
|
|
err_val = -2;
|
|
goto error_handle;
|
|
}
|
|
|
|
return usb_register(&aml_usbcam_driver);
|
|
|
|
error_handle:
|
|
usbcam_dbg("init failed! err_val:%d ret:%d\n", err_val, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit aml_usbcam_exit(void)
|
|
{
|
|
usbcam_dbg("usbcam exit!\n");
|
|
|
|
class_destroy(aml_usbcam_ci_class);
|
|
unregister_chrdev_region(aml_usbcam_devt, MAX_CI_DEVICES * 2);
|
|
|
|
usb_deregister(&aml_usbcam_driver);
|
|
}
|
|
|
|
module_init(aml_usbcam_init);
|
|
module_exit(aml_usbcam_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("AMLOGIC USB CAM MODULE DRIVER");
|