// SPDX-License-Identifier: GPL-2.0+
/*
* uvc_video.c -- USB Video Class Gadget driver
*
* Copyright (C) 2009-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/video.h>
#include <linux/unaligned.h>
#include <media/v4l2-dev.h>
#include "uvc.h"
#include "uvc_queue.h"
#include "uvc_video.h"
#include "uvc_trace.h"
/* --------------------------------------------------------------------------
* Video codecs
*/
static int
uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
u8 *data, int len)
{
struct uvc_device *uvc = container_of(video, struct uvc_device, video);
struct usb_composite_dev *cdev = uvc->func.config->cdev;
struct timespec64 ts = ns_to_timespec64(buf->buf.vb2_buf.timestamp);
int pos = 2;
data[1] = UVC_STREAM_EOH | video->fid;
if (video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE)
data[1] |= UVC_STREAM_ERR;
if (video->queue.buf_used == 0 && ts.tv_sec) {
/* dwClockFrequency is 48 MHz */
u32 pts = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48;
data[1] |= UVC_STREAM_PTS;
put_unaligned_le32(pts, &data[pos]);
pos += 4;
}
if (cdev->gadget->ops->get_frame) {
u32 sof, stc;
sof = usb_gadget_frame_number(cdev->gadget);
ktime_get_ts64(&ts);
stc = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48;
data[1] |= UVC_STREAM_SCR;
put_unaligned_le32(stc, &data[pos]);
put_unaligned_le16(sof, &data[pos+4]);
pos += 6;
}
data[0] = pos;
if (buf->bytesused - video->queue.buf_used <= len - pos)
data[1] |= UVC_STREAM_EOF;
return pos;
}
static int
uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
u8 *data, int len)
{
struct uvc_video_queue *queue = &video->queue;
unsigned int nbytes;
void *mem;
/* Copy video data to the USB buffer. */
mem = buf->mem + queue->buf_used;
nbytes = min_t(unsigned int, len, buf->bytesused - queue->buf_used);
memcpy(data, mem, nbytes);
queue->buf_used += nbytes;
return nbytes;
}
static void
uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf)
{
void *mem = req->buf;
struct uvc_request *ureq = req->context;
int len = video->req_size;
int ret;
/* Add a header at the beginning of the payload. */
if (video->payload_size == 0) {
ret = uvc_video_encode_header(video, buf, mem, len);
video->payload_size += ret;
mem += ret;
len -= ret;
}
/* Process video data. */
len = min_t(int, video->max_payload_size - video->payload_size, len);
ret = uvc_video_encode_data(video, buf, mem, len);
video->payload_size += ret;
len -= ret;
req->length = video->req_size - len;
req->zero = video->payload_size == video->max_payload_size;
if (buf->bytesused == video->queue.buf_used) {
video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE;
list_del(&buf->queue);
video->fid ^= UVC_STREAM_FID;
ureq->last_buf = buf;
video->payload_size = 0;
}
if (video->payload_size == video->max_payload_size ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE ||
buf->bytesused == video->queue.buf_used)
video->payload_size = 0;
}
static void
uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf)
{
unsigned int pending = buf->bytesused - video->queue.buf_used;
struct uvc_request *ureq = req->context;
struct scatterlist *sg, *iter;
unsigned int len = buf->req_payload_size;
unsigned int sg_left, part = 0;
unsigned int i;