mirror of
https://github.com/tsukumijima/px4_drv.git
synced 2025-07-23 04:03:01 +02:00
646 lines
12 KiB
C
646 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* ITE IT930x bus driver (itedtv_bus.c)
|
|
*
|
|
* Copyright (c) 2018-2021 nns779
|
|
*/
|
|
|
|
#include "print_format.h"
|
|
#include "itedtv_bus.h"
|
|
|
|
#ifdef __linux__
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/slab.h>
|
|
#endif
|
|
|
|
#if defined(ITEDTV_BUS_USE_WORKQUEUE) && !defined(__linux__)
|
|
#undef ITEDTV_BUS_USE_WORKQUEUE
|
|
#endif
|
|
|
|
struct itedtv_usb_context;
|
|
|
|
struct itedtv_usb_work {
|
|
struct itedtv_usb_context *ctx;
|
|
struct urb *urb;
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
struct work_struct work;
|
|
#endif
|
|
};
|
|
|
|
struct itedtv_usb_context {
|
|
struct mutex lock;
|
|
struct itedtv_bus *bus;
|
|
itedtv_bus_stream_handler_t stream_handler;
|
|
void *ctx;
|
|
u32 num_urb;
|
|
bool no_dma;
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
struct workqueue_struct *wq;
|
|
#endif
|
|
u32 num_works;
|
|
struct itedtv_usb_work *works;
|
|
atomic_t streaming;
|
|
};
|
|
|
|
static int itedtv_usb_ctrl_tx(struct itedtv_bus *bus, void *buf, int len)
|
|
{
|
|
int ret = 0, rlen = 0;
|
|
struct usb_device *dev = bus->usb.dev;
|
|
|
|
if (unlikely(!buf || !len))
|
|
return -EINVAL;
|
|
|
|
/* Endpoint 0x02: Host->Device bulk endpoint for controlling the device */
|
|
ret = usb_bulk_msg(dev,
|
|
usb_sndbulkpipe(dev, 0x02),
|
|
buf, len,
|
|
&rlen, bus->usb.ctrl_timeout);
|
|
|
|
if (ret) {
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_ctrl_tx: usb_bulk_msg() failed. (ret: %d)\n",
|
|
ret);
|
|
}
|
|
|
|
mdelay(1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int itedtv_usb_ctrl_rx(struct itedtv_bus *bus, void *buf, int *len)
|
|
{
|
|
int ret = 0, rlen = 0;
|
|
struct usb_device *dev = bus->usb.dev;
|
|
|
|
if (unlikely(!buf || !len || !*len))
|
|
return -EINVAL;
|
|
|
|
/* Endpoint 0x81: Device->Host bulk endpoint for controlling the device */
|
|
ret = usb_bulk_msg(dev,
|
|
usb_rcvbulkpipe(dev, 0x81),
|
|
buf, *len,
|
|
&rlen, bus->usb.ctrl_timeout);
|
|
|
|
if (ret) {
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_ctrl_rx: usb_bulk_msg() failed. (ret: %d)\n",
|
|
ret);
|
|
}
|
|
|
|
*len = rlen;
|
|
|
|
mdelay(1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int itedtv_usb_stream_rx(struct itedtv_bus *bus,
|
|
void *buf, int *len,
|
|
int timeout)
|
|
{
|
|
int ret = 0, rlen = 0;
|
|
struct usb_device *dev = bus->usb.dev;
|
|
|
|
if (unlikely(!buf | !len || !*len))
|
|
return -EINVAL;
|
|
|
|
/* Endpoint 0x84: Device->Host bulk endpoint for receiving TS from the device */
|
|
ret = usb_bulk_msg(dev,
|
|
usb_rcvbulkpipe(dev, 0x84),
|
|
buf, *len,
|
|
&rlen, timeout);
|
|
|
|
*len = rlen;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
static void itedtv_usb_workqueue_handler(struct work_struct *work)
|
|
{
|
|
int ret = 0;
|
|
struct itedtv_usb_work *w = container_of(work,
|
|
struct itedtv_usb_work, work);
|
|
struct itedtv_usb_context *ctx = w->ctx;
|
|
struct urb *urb = w->urb;
|
|
|
|
if (likely(urb->actual_length))
|
|
ret = ctx->stream_handler(ctx->ctx,
|
|
urb->transfer_buffer,
|
|
urb->actual_length);
|
|
else
|
|
dev_dbg(ctx->bus->dev,
|
|
"itedtv_usb_workqueue_handler: !urb->actual_length\n");
|
|
|
|
if (unlikely(ret || (atomic_read_acquire(&ctx->streaming) < 1)))
|
|
return;
|
|
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
if (unlikely(ret))
|
|
dev_err(ctx->bus->dev,
|
|
"itedtv_usb_workqueue_handler: usb_submit_urb() failed. (ret: %d)\n",
|
|
ret);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
static void itedtv_usb_complete(struct urb *urb)
|
|
{
|
|
#ifndef ITEDTV_BUS_USE_WORKQUEUE
|
|
int ret = 0;
|
|
#endif
|
|
struct itedtv_usb_work *w = urb->context;
|
|
struct itedtv_usb_context *ctx = w->ctx;
|
|
|
|
if (unlikely(urb->status)) {
|
|
dev_dbg(ctx->bus->dev,
|
|
"itedtv_usb_complete: status: %d\n",
|
|
urb->status);
|
|
return;
|
|
}
|
|
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
if (unlikely(!queue_work(ctx->wq, &w->work)))
|
|
dev_err(ctx->bus->dev,
|
|
"itedtv_usb_complete: queue_work() failed.\n");
|
|
#else
|
|
if (likely(urb->actual_length))
|
|
ret = ctx->stream_handler(ctx->ctx,
|
|
urb->transfer_buffer,
|
|
urb->actual_length);
|
|
else
|
|
dev_dbg(ctx->bus->dev,
|
|
"itedtv_usb_complete: !urb->actual_length\n");
|
|
|
|
if (unlikely(ret || (atomic_read_acquire(&ctx->streaming) < 1)))
|
|
return;
|
|
|
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (unlikely(ret))
|
|
dev_err(ctx->bus->dev,
|
|
"itedtv_usb_complete: usb_submit_urb() failed. (ret: %d)\n",
|
|
ret);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static int itedtv_usb_alloc_urb_buffers(struct itedtv_usb_context *ctx,
|
|
u32 buf_size)
|
|
{
|
|
u32 i;
|
|
struct itedtv_bus *bus = ctx->bus;
|
|
struct usb_device *dev = bus->usb.dev;
|
|
u32 num = ctx->num_works;
|
|
bool no_dma = ctx->no_dma;
|
|
struct itedtv_usb_work *works = ctx->works;
|
|
|
|
if (!works)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
struct urb *urb;
|
|
void *p;
|
|
#ifdef __linux__
|
|
dma_addr_t dma;
|
|
#endif
|
|
|
|
if (works[i].urb) {
|
|
urb = works[i].urb;
|
|
|
|
if (urb->transfer_buffer) {
|
|
#ifdef __linux__
|
|
if ((urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) &&
|
|
(no_dma || urb->transfer_buffer_length != buf_size)) {
|
|
usb_free_coherent(dev,
|
|
urb->transfer_buffer_length,
|
|
urb->transfer_buffer,
|
|
urb->transfer_dma);
|
|
urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP;
|
|
urb->transfer_dma = 0;
|
|
|
|
urb->transfer_buffer = NULL;
|
|
urb->transfer_buffer_length = 0;
|
|
urb->actual_length = 0;
|
|
} else if (!(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) &&
|
|
(!no_dma || urb->transfer_buffer_length != buf_size)) {
|
|
kfree(urb->transfer_buffer);
|
|
|
|
urb->transfer_buffer = NULL;
|
|
urb->transfer_buffer_length = 0;
|
|
urb->actual_length = 0;
|
|
}
|
|
#else
|
|
kfree(urb->transfer_buffer);
|
|
|
|
urb->transfer_buffer = NULL;
|
|
urb->transfer_buffer_length = 0;
|
|
urb->actual_length = 0;
|
|
#endif
|
|
}
|
|
} else {
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!urb) {
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_alloc_urb_buffers: usb_alloc_urb() failed. (i: %u)\n",
|
|
i);
|
|
break;
|
|
}
|
|
|
|
works[i].urb = urb;
|
|
}
|
|
|
|
works[i].ctx = ctx;
|
|
|
|
if (!urb->transfer_buffer) {
|
|
#ifdef __linux__
|
|
#ifdef __GFP_RETRY_MAYFAIL
|
|
if (!no_dma)
|
|
p = usb_alloc_coherent(dev, buf_size,
|
|
GFP_KERNEL | __GFP_RETRY_MAYFAIL, &dma);
|
|
else
|
|
p = kmalloc(buf_size, GFP_KERNEL | __GFP_RETRY_MAYFAIL);
|
|
#else
|
|
if (!no_dma)
|
|
p = usb_alloc_coherent(dev, buf_size,
|
|
GFP_KERNEL | __GFP_REPEAT, &dma);
|
|
else
|
|
p = kmalloc(buf_size, GFP_KERNEL | __GFP_REPEAT);
|
|
#endif
|
|
#else
|
|
p = kmalloc(buf_size, GFP_KERNEL);
|
|
#endif
|
|
|
|
if (!p) {
|
|
#ifdef __linux__
|
|
if (!no_dma)
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_alloc_urb_buffers: usb_alloc_coherent() failed. (i: %u)\n",
|
|
i);
|
|
else
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_alloc_urb_buffers: kmalloc() failed. (i: %u)\n",
|
|
i);
|
|
#else
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_alloc_urb_buffers: kmalloc() failed. (i: %u)\n",
|
|
i);
|
|
#endif
|
|
|
|
usb_free_urb(urb);
|
|
works[i].urb = NULL;
|
|
|
|
break;
|
|
}
|
|
|
|
#ifdef __linux__
|
|
dev_dbg(bus->dev,
|
|
"itedtv_usb_alloc_urb_buffers: p: %p, buf_size: %u, dma: %pad\n",
|
|
p, buf_size, &dma);
|
|
#else
|
|
dev_dbg(bus->dev,
|
|
"itedtv_usb_alloc_urb_buffers: p: %p, buf_size: %u\n",
|
|
p, buf_size);
|
|
#endif
|
|
|
|
usb_fill_bulk_urb(urb, dev,
|
|
usb_rcvbulkpipe(dev, 0x84),
|
|
p, buf_size,
|
|
itedtv_usb_complete, &works[i]);
|
|
|
|
#ifdef __linux__
|
|
if (!no_dma) {
|
|
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
urb->transfer_dma = dma;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
INIT_WORK(&works[i].work, itedtv_usb_workqueue_handler);
|
|
#endif
|
|
}
|
|
|
|
ctx->num_urb = i;
|
|
|
|
if (!i)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void itedtv_usb_free_urb_buffers(struct itedtv_usb_context *ctx,
|
|
bool free_urb)
|
|
{
|
|
u32 i;
|
|
struct usb_device *dev = ctx->bus->usb.dev;
|
|
u32 num = ctx->num_works;
|
|
bool no_dma = ctx->no_dma;
|
|
struct itedtv_usb_work *works = ctx->works;
|
|
|
|
if (!works)
|
|
return;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
struct urb *urb = works[i].urb;
|
|
|
|
if (!urb)
|
|
continue;
|
|
|
|
if (urb->transfer_buffer) {
|
|
#ifdef __linux__
|
|
if (!no_dma) {
|
|
usb_free_coherent(dev,
|
|
urb->transfer_buffer_length,
|
|
urb->transfer_buffer,
|
|
urb->transfer_dma);
|
|
urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP;
|
|
urb->transfer_dma = 0;
|
|
} else {
|
|
kfree(urb->transfer_buffer);
|
|
}
|
|
#else
|
|
kfree(urb->transfer_buffer);
|
|
#endif
|
|
|
|
urb->transfer_buffer = NULL;
|
|
urb->transfer_buffer_length = 0;
|
|
urb->actual_length = 0;
|
|
}
|
|
|
|
if (free_urb) {
|
|
usb_free_urb(urb);
|
|
works[i].urb = NULL;
|
|
}
|
|
}
|
|
|
|
if (free_urb)
|
|
ctx->num_urb = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
static void itedtv_usb_clean_context(struct itedtv_usb_context *ctx, bool free_works)
|
|
{
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
if (ctx->wq)
|
|
destroy_workqueue(ctx->wq);
|
|
#endif
|
|
|
|
if (free_works && ctx->works) {
|
|
itedtv_usb_free_urb_buffers(ctx, true);
|
|
kfree(ctx->works);
|
|
ctx->num_urb = 0;
|
|
ctx->works = NULL;
|
|
ctx->num_works = 0;
|
|
}
|
|
|
|
ctx->stream_handler = NULL;
|
|
ctx->ctx = NULL;
|
|
ctx->no_dma = false;
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
ctx->wq = NULL;
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static int itedtv_usb_start_streaming(struct itedtv_bus *bus,
|
|
itedtv_bus_stream_handler_t stream_handler,
|
|
void *context)
|
|
{
|
|
int ret = 0;
|
|
u32 i, buf_size, num;
|
|
struct itedtv_usb_context *ctx = bus->usb.priv;
|
|
struct itedtv_usb_work *works;
|
|
|
|
if (!stream_handler)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(bus->dev, "itedtv_usb_start_streaming\n");
|
|
|
|
mutex_lock(&ctx->lock);
|
|
|
|
ctx->stream_handler = stream_handler;
|
|
ctx->ctx = context;
|
|
|
|
buf_size = bus->usb.streaming.urb_buffer_size;
|
|
num = bus->usb.streaming.urb_num;
|
|
ctx->no_dma = bus->usb.streaming.no_dma;
|
|
|
|
if (ctx->works && num != ctx->num_works) {
|
|
itedtv_usb_free_urb_buffers(ctx, true);
|
|
kfree(ctx->works);
|
|
ctx->works = NULL;
|
|
}
|
|
|
|
ctx->num_works = num;
|
|
|
|
if (!ctx->works) {
|
|
ctx->works = kcalloc(ctx->num_works,
|
|
sizeof(*works), GFP_KERNEL);
|
|
if (!ctx->works) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
ret = itedtv_usb_alloc_urb_buffers(ctx, buf_size);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
if (!ctx->wq) {
|
|
ctx->wq = create_singlethread_workqueue("itedtv_usb_workqueue");
|
|
if (!ctx->wq) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
usb_reset_endpoint(bus->usb.dev, 0x84);
|
|
atomic_xchg(&ctx->streaming, 1);
|
|
|
|
num = ctx->num_urb;
|
|
works = ctx->works;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
ret = usb_submit_urb(works[i].urb, GFP_KERNEL);
|
|
if (ret) {
|
|
u32 j;
|
|
|
|
dev_err(bus->dev,
|
|
"itedtv_usb_start_streaming: usb_submit_urb() failed. (i: %u, ret: %d)\n",
|
|
i, ret);
|
|
|
|
for (j = 0; j < i; j++)
|
|
usb_kill_urb(works[i].urb);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
goto fail;
|
|
|
|
dev_dbg(bus->dev, "itedtv_usb_start_streaming: num: %u\n", num);
|
|
|
|
mutex_unlock(&ctx->lock);
|
|
|
|
return ret;
|
|
|
|
fail:
|
|
atomic_xchg(&ctx->streaming, 0);
|
|
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
if (ctx->wq)
|
|
flush_workqueue(ctx->wq);
|
|
#endif
|
|
|
|
itedtv_usb_clean_context(ctx, true);
|
|
|
|
mutex_unlock(&ctx->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int itedtv_usb_stop_streaming(struct itedtv_bus *bus)
|
|
{
|
|
u32 i;
|
|
struct itedtv_usb_context *ctx = bus->usb.priv;
|
|
|
|
dev_dbg(bus->dev, "itedtv_usb_stop_streaming\n");
|
|
|
|
mutex_lock(&ctx->lock);
|
|
|
|
atomic_xchg(&ctx->streaming, 0);
|
|
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
if (ctx->wq)
|
|
flush_workqueue(ctx->wq);
|
|
#endif
|
|
|
|
if (ctx->works) {
|
|
u32 num = ctx->num_urb;
|
|
struct itedtv_usb_work *works = ctx->works;
|
|
|
|
for (i = 0; i < num; i++)
|
|
usb_kill_urb(works[i].urb);
|
|
}
|
|
|
|
itedtv_usb_clean_context(ctx, false);
|
|
|
|
mutex_unlock(&ctx->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int itedtv_bus_init(struct itedtv_bus *bus)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!bus)
|
|
return -EINVAL;
|
|
|
|
switch (bus->type) {
|
|
case ITEDTV_BUS_USB:
|
|
{
|
|
struct itedtv_usb_context *ctx;
|
|
|
|
if (!bus->usb.dev) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (bus->usb.dev->descriptor.bcdUSB < 0x0110) {
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
usb_get_dev(bus->usb.dev);
|
|
|
|
mutex_init(&ctx->lock);
|
|
ctx->bus = bus;
|
|
ctx->stream_handler = NULL;
|
|
ctx->ctx = NULL;
|
|
ctx->num_urb = 0;
|
|
ctx->no_dma = false;
|
|
#ifdef ITEDTV_BUS_USE_WORKQUEUE
|
|
ctx->wq = NULL;
|
|
#endif
|
|
ctx->num_works = 0;
|
|
ctx->works = NULL;
|
|
atomic_set(&ctx->streaming, 0);
|
|
|
|
bus->usb.priv = ctx;
|
|
|
|
if (!bus->usb.max_bulk_size)
|
|
bus->usb.max_bulk_size = (bus->usb.dev->descriptor.bcdUSB == 0x0110) ? 64
|
|
: 512;
|
|
|
|
bus->ops.ctrl_tx = itedtv_usb_ctrl_tx;
|
|
bus->ops.ctrl_rx = itedtv_usb_ctrl_rx;
|
|
bus->ops.stream_rx = itedtv_usb_stream_rx;
|
|
bus->ops.start_streaming = itedtv_usb_start_streaming;
|
|
bus->ops.stop_streaming = itedtv_usb_stop_streaming;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int itedtv_bus_term(struct itedtv_bus *bus)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!bus) {
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
switch (bus->type) {
|
|
case ITEDTV_BUS_USB:
|
|
{
|
|
struct itedtv_usb_context *ctx = bus->usb.priv;
|
|
|
|
if (ctx) {
|
|
if (atomic_read_acquire(&ctx->streaming))
|
|
itedtv_usb_stop_streaming(bus);
|
|
|
|
itedtv_usb_clean_context(ctx, true);
|
|
mutex_destroy(&ctx->lock);
|
|
kfree(ctx);
|
|
}
|
|
|
|
if (bus->usb.dev)
|
|
usb_put_dev(bus->usb.dev);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
memset(bus, 0, sizeof(*bus));
|
|
|
|
exit:
|
|
return ret;
|
|
}
|