blob: 554800a24f4006bb12c512203624df4953422d99 [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* File : amlogic-usb-cam.c
* Purpose : USB CAM driver
*
* Based on USB driver
*
*
*
*
*/
#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/acpi.h>
#include "usbci.h"
#include "usb_ci.h"
#define usbcam_dbg(fmt...) pr_info("usbcam: " fmt)
#define usbcam_err(fmt...) pr_err("usbcam: " fmt)
#define usbcam_crit(fmt...) pr_crit("usbcam: " fmt)
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_err("invalid parameter\n");
return -EINVAL;
}
ret = copy_to_user((unsigned int *)arg, &driver_version, sizeof(unsigned int));
if (ret) {
usbcam_err("copy data to user error\n");
return ret;
}
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_err("invalid parameter\n");
return -EINVAL;
}
ret = copy_to_user((struct aml_usbcam_info *)arg, &usbcam_dev->usbcam_module_info,
sizeof(struct aml_usbcam_info));
if (ret) {
usbcam_err("copy data to user error\n");
return ret;
}
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_err("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));
if (ret) {
usbcam_err("copy data to user error\n");
return ret;
}
return ret;
}
static unsigned long aml_cancel_transfer(struct file *filp, struct aml_usbcam *usbcam_dev)
{
if (!usbcam_dev) {
usbcam_err("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 int aml_intf_open(struct inode *node, struct file *filp)
{
struct aml_usbcam *usbcam_dev = NULL;
struct usb_interface *intf = NULL;
int minor;
int ret = 0;
usbcam_dbg("intf open\n");
if (!node || !filp) {
usbcam_err("invalid parameter\n");
return -EINVAL;
}
minor = iminor(node);
intf = usb_find_interface(&aml_usbcam_driver, minor);
if (!intf) {
usbcam_err("can not find device for minor %d\n", minor);
return -ENODEV;
}
usbcam_dev = usb_get_intfdata(intf);
if (!usbcam_dev) {
usbcam_err("can not get intfdata\n");
return -ENODEV;
}
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) {
usbcam_err("open failed: interface read fd is opened before\n");
mutex_unlock(&usbcam_dev->open_mux);
ret = -EPERM;
} else {
usbcam_dev->read_open_ref++;
usbcam_dbg(" interface read fd open successfully\n");
}
}
if (ret < 0)
return ret;
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) {
usbcam_err("open failed: interface write fd is opened before\n");
if ((filp->f_flags & 0x03) == O_RDWR)
usbcam_dev->read_open_ref--;
mutex_unlock(&usbcam_dev->open_mux);
ret = -EPERM;
} else {
usbcam_dev->write_open_ref++;
usbcam_dbg(" interface write fd open successfully\n");
}
}
if (ret < 0)
return ret;
mutex_unlock(&usbcam_dev->open_mux);
kref_get(&usbcam_dev->ref_count);
//save our object in the file's private structure
filp->private_data = usbcam_dev;
return 0;
}
static void aml_intf_read_complete(struct urb *urb)
{
int status;
struct aml_usbcam *usbcam_dev = NULL;
ulong flag;
if (!urb) {
usbcam_err("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_err("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,
usbcam_dev->read_buf, 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,
usbcam_dev->read_buf, usbcam_dev->buf_size,
aml_intf_read_complete, usbcam_dev);
}
ret = usb_submit_urb(usbcam_dev->read_urb, GFP_KERNEL);
if (ret) {
usbcam_err("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;
struct aml_usbcam *usbcam_dev = NULL;
size_t available, transfor, chunk;
ulong flag;
if (!filp || !buff || !ppos) {
usbcam_err("invalid parameter\n");
return -EINVAL;
}
usbcam_dev = filp->private_data;
if (!usbcam_dev) {
usbcam_err("private data is null");
return -ENODEV;
}
if (!usbcam_dev->intf) {
usbcam_err("read failed, interface is deregistered\n");
return -ENODEV;
}
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_err("submit %p failed, ret = %d", usbcam_dev->read_urb, ret);
return ret;
}
} else {
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
}
if (filp->f_flags & O_NONBLOCK) {
usbcam_dbg("return EAGAIN\n");
return -EAGAIN;
}
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) {
usbcam_err("wait_event_interruptible failed, ret = %d\n", ret);
return ret;
}
} else {
spin_unlock_irqrestore(&usbcam_dev->read_lock, flag);
}
if (usbcam_dev->read_open_status == READ_CLOSE) {
usbcam_err(" read_open_status %d\n", usbcam_dev->read_open_status);
return 0;
}
if (usbcam_dev->read_status == READ_STATUS_ERROR) {
usbcam_err(" read error, return = %d\n", usbcam_dev->read_err);
usbcam_dev->read_status = READ_STATUS_EMPTY;
return usbcam_dev->read_err;
}
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);
ret = copy_to_user(buff, usbcam_dev->read_buf + transfor, chunk);
if (ret) {
usbcam_err("data to user error\n");
return ret;
}
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;
}
static void aml_intf_write_empty_complete(struct urb *urb)
{
int status;
struct aml_usbcam *usbcam_dev = NULL;
ulong flag;
if (!urb) {
usbcam_err("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_err("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_err("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_err("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_err("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_err("urb already submitted,unable to repeat submit\n");
return -EAGAIN;
}
}
return ret;
}
static void aml_intf_write_complete(struct urb *urb)
{
int status;
struct aml_usbcam *usbcam_dev = NULL;
ulong flag;
if (!urb) {
usbcam_err("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_err("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, usbcam_dev->write_buf, 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, usbcam_dev->write_buf, size,
aml_intf_write_complete, usbcam_dev);
}
retval = usb_submit_urb(usbcam_dev->write_urb, GFP_KERNEL);
if (retval) {
usbcam_err("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;
struct aml_usbcam *usbcam_dev = NULL;
size_t writesize;
ulong flag;
if (!filp || !buff || !ppos) {
usbcam_err("invalid parameter\n");
return -EINVAL;
}
usbcam_dev = filp->private_data;
if (!usbcam_dev) {
usbcam_err("private data is null\n");
return -ENODEV;
}
if (!usbcam_dev->intf) {
usbcam_err("write failed, interface is deregistered\n");
return -ENODEV;
}
if (usbcam_dev->write_status != WRITE_STATUS_READY) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
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) {
usbcam_err("wait_event_interruptible failed, ret = %d\n", ret);
return ret;
}
}
if (usbcam_dev->write_open_status == WRITE_CLOSE) {
usbcam_err("write failed, interface is close\n");
return 0;
}
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);
return usbcam_dev->write_err;
}
spin_unlock_irqrestore(&usbcam_dev->write_lock, flag);
size = min(size, usbcam_dev->buf_size);
if (copy_from_user(usbcam_dev->write_buf, buff, size)) {
usbcam_err("copy_from_user error\n");
return -EFAULT;
}
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) {
usbcam_err("submit urb %p failed, return = %d", usbcam_dev->read_urb, ret);
return ret;
}
} else {
usbcam_err("urb already submitted, unable to repeat submit\n");
return -EAGAIN;
}
return writesize;
}
static void aml_usbcam_delete(struct kref *ref)
{
struct aml_usbcam *usbcam_dev = NULL;
if (!ref) {
usbcam_err("invalid parameter");
return;
}
usbcam_dev = container_of(ref, struct aml_usbcam, ref_count);
usb_put_dev(usbcam_dev->usbdev);
usb_kill_urb(usbcam_dev->read_urb);
usb_kill_urb(usbcam_dev->write_urb);
kfree(usbcam_dev->read_buf);
kfree(usbcam_dev->write_buf);
usb_free_urb(usbcam_dev->read_urb);
usb_free_urb(usbcam_dev->write_urb);
usbcam_dev->device_status = DEVICE_STATUS_DISCONNECT;
wake_up_interruptible(&usbcam_dev->write_wq);
wake_up_interruptible(&usbcam_dev->read_wq);
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;
if (!inode || !filp) {
usbcam_err("invalid parameter\n");
return -EINVAL;
}
usbcam_dev = filp->private_data;
if (!usbcam_dev) {
usbcam_err("private data is null\n");
return -ENODEV;
}
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_err("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_err("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;
}
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_err("no device connected\r\n");
ret = -ENODEV;
return ret;
}
switch (cmd) {
case AML_USBCAM_IOC_GET_DRIVER_VERSION:
usbcam_dbg("ioctl op: get usb cam driver version\n");
ret = aml_get_usbcam_version(usbcam_dev, arg);
break;
case AML_USBCAM_IOC_GET_INFO:
usbcam_dbg("ioctl op: get usb cam info\n");
ret = aml_get_usbcam_info(usbcam_dev, arg);
break;
case AML_USBCAM_IOC_RESET:
usbcam_dbg("ioctl op: reset usb cam\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:
usbcam_dbg("ioctl op: cancel transfer\n");
ret = aml_cancel_transfer(filp, usbcam_dev);
break;
case AML_USBCAM_IOC_MODULE_CAPABILITIES:
usbcam_dbg("ioctl op: get usbcam module capabilities\n");
ret = aml_get_usbcam_capabilities(usbcam_dev, arg);
break;
default:
usbcam_err("unknown op type %08x", cmd);
ret = -1;
break;
}
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;
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_err("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 struct usb_class_driver aml_usbcam_command_class = {
.name = "cimodule_command%d",
.fops = &aml_intf_fops,
.minor_base = USBCAM_COMMAND_MINOR_BASE,
};
static struct usb_class_driver aml_usbcam_media_class = {
.name = "cimodule_media%d",
.fops = &aml_intf_fops,
.minor_base = USBCAM_MEDIA_MINOR_BASE,
};
static ssize_t usb_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
int ret;
ret = sprintf(buf, "usb%d\n", 1);
return ret;
}
static CLASS_ATTR_RO(usb);
static struct attribute *aml_usbcam_attrs[] = {
&class_attr_usb.attr,
NULL
};
ATTRIBUTE_GROUPS(aml_usbcam);
struct class clp;
static int aml_usbcam_register_class(void)
{
int ret;
clp.name = kzalloc(CLASS_NAME_LEN, GFP_KERNEL);
if (!clp.name)
return -ENOMEM;
snprintf((char *)clp.name, CLASS_NAME_LEN, "amlusbcam-%d", 0);
clp.class_groups = aml_usbcam_groups;
ret = class_register(&clp);
if (ret)
kfree(clp.name);
return 0;
}
static int aml_usbcam_unregister_class(void)
{
class_unregister(&clp);
kfree(clp.name);
return 0;
}
static int aml_usbcam_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int ret = -ENOMEM;
struct usb_device *pdev = NULL;
struct usb_interface_assoc_descriptor *assoc_desc = NULL;
static struct aml_usbcam *usbcam_dev;
unsigned char intf_num;
unsigned char *extratemp = NULL;
int extrasize = 0;
unsigned char descriptorlen = 0;
unsigned char descriptortype = 0;
int len, temp;
if (!intf || !id) {
usbcam_err("invalid parameter");
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) {
usbcam_crit("interface association descriptor is null");
ret = -ENODEV;
goto error;
}
//usb association descriptor attribute determination
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)) {
usbcam_crit("mismatched function info in IAD");
ret = -ENODEV;
goto error;
}
if (intf_num < assoc_desc->bFirstInterface ||
intf_num > assoc_desc->bFirstInterface + assoc_desc->bInterfaceCount) {
usbcam_err("mismatched interface number in IAD");
ret = -ENODEV;
goto error;
}
usbcam_dev = kzalloc(sizeof(*usbcam_dev), GFP_KERNEL);
if (!usbcam_dev) {
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_dbg(" CI20_COMMAND_INTF !\n");
ret = usb_register_dev(intf, &aml_usbcam_command_class);
if (ret) {
usbcam_crit("get minor failed for command interface\n");
usb_set_intfdata(intf, NULL);
ret = -ENODEV;
goto error;
}
usbcam_dev->buf_size = USBCAM_CMD_BUFFER_SIZE;
usbcam_dev->read_buf = kmalloc(usbcam_dev->buf_size, GFP_KERNEL);
usbcam_dev->write_buf = kmalloc(usbcam_dev->buf_size, GFP_KERNEL);
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 = 1;
usbcam_dbg("usbcam_dev->usbcam_module_info.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_dbg(" CI20_MEDIA_INTF !\n");
ret = usb_register_dev(intf, &aml_usbcam_media_class);
if (ret) {
usbcam_crit("get minor failed for command interface\n");
usb_set_intfdata(intf, NULL);
ret = -ENODEV;
goto error;
}
usbcam_dev->buf_size = USBCAM_MEDIA_BUFFER_SIZE;
usbcam_dev->read_buf = kmalloc(usbcam_dev->buf_size, GFP_KERNEL);
usbcam_dev->write_buf = kmalloc(usbcam_dev->buf_size, GFP_KERNEL);
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 = 1;
usbcam_dbg("usbcam_dev->usbcam_module_info.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) {
usbcam_crit("parse the compatibility descriptor failed");
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) {
// 2:architecture version 2
usbcam_crit("this driver no support ARCH value:%d",
usbcam_dev->usbcam_module_info.ci_compatibility & 0x07);
goto error;
}
break;
}
}
usbcam_dev->usbdev = usb_get_dev(interface_to_usbdev(intf));
usbcam_dev->intf = intf;
if (usb_endpoint_dir_out(&intf->cur_altsetting->endpoint[0].desc)) {
usbcam_dbg(" out endpoint is 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(" out endpoint is endpoint[1]!\n");
usbcam_dev->out = &intf->cur_altsetting->endpoint[1].desc;
} else {
usbcam_err("out endpoint not found\n");
}
if (usb_endpoint_dir_in(&intf->cur_altsetting->endpoint[0].desc)) {
usbcam_dbg(" in endpoint is 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(" in endpoint is endpoint[1]!\n");
usbcam_dev->in = &intf->cur_altsetting->endpoint[1].desc;
} else {
usbcam_err("in endpoint not found\n");
}
//usbcam_dbg("endpoint_type(usbcam_dev->in) = %d\n",usb_endpoint_type(usbcam_dev->in));
//usbcam_dbg("endpoint_type(usbcam_dev->out) = %d\n",usb_endpoint_type(usbcam_dev->out));
//usbcam_dbg("bulk_out(usbcam_dev->out) = %d\n",usb_endpoint_is_bulk_out(usbcam_dev->out));
//usbcam_dbg("int_out(usbcam_dev->out) = %d\n",usb_endpoint_is_int_out(usbcam_dev->out));
//usbcam_dbg("is_bulk_in(usbcam_dev->in) = %d\n",usb_endpoint_is_bulk_in(usbcam_dev->in));
//usbcam_dbg("is_int_in(usbcam_dev->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_crit("mismatched endpoint type in interface\n");
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 < USBCAM_MAX_NAME_LEN ? temp : USBCAM_MAX_NAME_LEN;
strncpy(usbcam_dev->usbcam_module_capabilities.ci_manufacturer_name,
pdev->manufacturer, len);
if (len == USBCAM_MAX_NAME_LEN)
usbcam_dev->usbcam_module_capabilities.ci_manufacturer_name[len - 1] = '\0';
else
usbcam_dev->usbcam_module_capabilities.ci_manufacturer_name[len] = '\0';
} else {
usbcam_crit("pdev->manufacturer inexistence");
ret = -ENOMEM;
goto error;
}
if (pdev->product) {
usbcam_dbg("pdev->product = %s\n", pdev->product);
temp = strlen(pdev->product);
len = temp < USBCAM_MAX_NAME_LEN ? temp : USBCAM_MAX_NAME_LEN;
strncpy(usbcam_dev->usbcam_module_capabilities.ci_product_name,
pdev->product, len);
if (len == USBCAM_MAX_NAME_LEN)
usbcam_dev->usbcam_module_capabilities.ci_product_name[len - 1] = '\0';
else
usbcam_dev->usbcam_module_capabilities.ci_product_name[len] = '\0';
} else {
usbcam_crit("pdev->product inexistence");
ret = -ENOMEM;
goto error;
}
usbcam_dev->usbcam_module_capabilities.ci_plus_supported = 1;
usbcam_dev->usbcam_module_capabilities.op_profile_supported = 1;
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");
usbcam_dev->read_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!usbcam_dev->read_urb) {
usbcam_crit("read urb alloc failed\n");
ret = -ENOMEM;
goto error;
}
usbcam_dev->write_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!usbcam_dev->write_urb) {
usbcam_crit("write urb alloc failed\n");
ret = -ENOMEM;
goto error;
}
usbcam_dev->read_status = READ_STATUS_EMPTY;
usbcam_dev->device_status = DEVICE_STATUS_MATCH;
usb_set_intfdata(intf, usbcam_dev);
usbcam_dev->intf_reg = TRUE;
return 0;
error:
if (usbcam_dev && usbcam_dev->intf_reg) {
usb_set_intfdata(usbcam_dev->intf, NULL);
kthread_stop(usbcam_dev->thread);
usb_deregister_dev(usbcam_dev->intf, &aml_usbcam_command_class);
usb_deregister_dev(usbcam_dev->intf, &aml_usbcam_media_class);
usbcam_dev->intf = NULL;
}
if (usbcam_dev) {
kref_put(&usbcam_dev->ref_count, aml_usbcam_delete);
kfree(usbcam_dev->read_buf);
kfree(usbcam_dev->write_buf);
usbcam_dev->read_buf = NULL;
usbcam_dev->write_buf = NULL;
kfree(usbcam_dev);
usbcam_dev = NULL;
}
return ret;
}
static void aml_usbcam_disconnect(struct usb_interface *intf)
{
struct aml_usbcam *usbcam_dev = NULL;
usbcam_dev = usb_get_intfdata(intf);
usb_deregister_dev(usbcam_dev->intf, &aml_usbcam_command_class);
usb_deregister_dev(usbcam_dev->intf, &aml_usbcam_media_class);
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 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,
};
static int __init aml_usbcam_init(void)
{
usbcam_dbg("usbcam init!\n");
aml_usbcam_register_class();
usbcam_dbg("usbcam register class!\n");
return usb_register(&aml_usbcam_driver);
}
static void __exit aml_usbcam_exit(void)
{
usbcam_dbg("usbcam exit!\n");
aml_usbcam_unregister_class();
usb_deregister(&aml_usbcam_driver);
}
module_init(aml_usbcam_init);
module_exit(aml_usbcam_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("USB CAM MODULE DRIVER");