diff options
Diffstat (limited to 'drivers/staging')
69 files changed, 11520 insertions, 701 deletions
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index c394abffea86..e59a846bc909 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -42,4 +42,8 @@ source "drivers/staging/media/phy-rockchip-dphy-rx0/Kconfig" source "drivers/staging/media/rkisp1/Kconfig" +if MEDIA_ANALOG_TV_SUPPORT +source "drivers/staging/media/usbvision/Kconfig" +endif + endif diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index ea9fce8014bb..23c682461b62 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/ obj-$(CONFIG_SOC_CAMERA) += soc_camera/ obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0/ obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rkisp1/ +obj-$(CONFIG_VIDEO_USBVISION) += usbvision/ diff --git a/drivers/staging/media/allegro-dvt/Makefile b/drivers/staging/media/allegro-dvt/Makefile index 80817160815c..8e306dcdc55c 100644 --- a/drivers/staging/media/allegro-dvt/Makefile +++ b/drivers/staging/media/allegro-dvt/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -allegro-objs := allegro-core.o nal-h264.o +allegro-objs := allegro-core.o nal-h264.o allegro-mail.o obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro.o diff --git a/drivers/staging/media/allegro-dvt/allegro-core.c b/drivers/staging/media/allegro-dvt/allegro-core.c index 3be41698df4c..34c3e55be902 100644 --- a/drivers/staging/media/allegro-dvt/allegro-core.c +++ b/drivers/staging/media/allegro-dvt/allegro-core.c @@ -5,7 +5,9 @@ * Allegro DVT video encoder driver */ +#include <linux/bits.h> #include <linux/firmware.h> +#include <linux/gcd.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/kernel.h> @@ -26,6 +28,7 @@ #include <media/videobuf2-dma-contig.h> #include <media/videobuf2-v4l2.h> +#include "allegro-mail.h" #include "nal-h264.h" /* @@ -40,6 +43,8 @@ #define ALLEGRO_HEIGHT_DEFAULT 1080 #define ALLEGRO_HEIGHT_MAX 2160 +#define ALLEGRO_FRAMERATE_DEFAULT ((struct v4l2_fract) { 30, 1 }) + #define ALLEGRO_GOP_SIZE_DEFAULT 25 #define ALLEGRO_GOP_SIZE_MAX 1000 @@ -176,6 +181,7 @@ struct allegro_channel { unsigned int width; unsigned int height; unsigned int stride; + struct v4l2_fract framerate; enum v4l2_colorspace colorspace; enum v4l2_ycbcr_encoding ycbcr_enc; @@ -192,7 +198,7 @@ struct allegro_channel { unsigned int sizeimage_encoded; unsigned int csequence; - enum v4l2_mpeg_video_bitrate_mode bitrate_mode; + bool frame_rc_enable; unsigned int bitrate; unsigned int bitrate_peak; unsigned int cpb_size; @@ -200,9 +206,17 @@ struct allegro_channel { struct v4l2_ctrl *mpeg_video_h264_profile; struct v4l2_ctrl *mpeg_video_h264_level; - struct v4l2_ctrl *mpeg_video_bitrate_mode; - struct v4l2_ctrl *mpeg_video_bitrate; - struct v4l2_ctrl *mpeg_video_bitrate_peak; + struct v4l2_ctrl *mpeg_video_h264_i_frame_qp; + struct v4l2_ctrl *mpeg_video_h264_max_qp; + struct v4l2_ctrl *mpeg_video_h264_min_qp; + struct v4l2_ctrl *mpeg_video_h264_p_frame_qp; + struct v4l2_ctrl *mpeg_video_h264_b_frame_qp; + struct v4l2_ctrl *mpeg_video_frame_rc_enable; + struct { /* video bitrate mode control cluster */ + struct v4l2_ctrl *mpeg_video_bitrate_mode; + struct v4l2_ctrl *mpeg_video_bitrate; + struct v4l2_ctrl *mpeg_video_bitrate_peak; + }; struct v4l2_ctrl *mpeg_video_cpb_size; struct v4l2_ctrl *mpeg_video_gop_size; @@ -215,6 +229,11 @@ struct allegro_channel { struct list_head buffers_reference; struct list_head buffers_intermediate; + struct list_head source_shadow_list; + struct list_head stream_shadow_list; + /* protect shadow lists of buffers passed to firmware */ + struct mutex shadow_list_lock; + struct list_head list; struct completion completion; @@ -236,6 +255,14 @@ allegro_get_state(struct allegro_channel *channel) return channel->state; } +struct allegro_m2m_buffer { + struct v4l2_m2m_buffer buf; + struct list_head head; +}; + +#define to_allegro_m2m_buffer(__buf) \ + container_of(__buf, struct allegro_m2m_buffer, buf) + struct fw_info { unsigned int id; unsigned int id_codec; @@ -258,276 +285,33 @@ static const struct fw_info supported_firmware[] = { }, }; -enum mcu_msg_type { - MCU_MSG_TYPE_INIT = 0x0000, - MCU_MSG_TYPE_CREATE_CHANNEL = 0x0005, - MCU_MSG_TYPE_DESTROY_CHANNEL = 0x0006, - MCU_MSG_TYPE_ENCODE_FRAME = 0x0007, - MCU_MSG_TYPE_PUT_STREAM_BUFFER = 0x0012, - MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE = 0x000e, - MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE = 0x000f, -}; +static inline u32 to_mcu_addr(struct allegro_dev *dev, dma_addr_t phys) +{ + if (upper_32_bits(phys) || (lower_32_bits(phys) & MCU_CACHE_OFFSET)) + v4l2_warn(&dev->v4l2_dev, + "address %pad is outside mcu window\n", &phys); + + return lower_32_bits(phys) | MCU_CACHE_OFFSET; +} -static const char *msg_type_name(enum mcu_msg_type type) +static inline u32 to_mcu_size(struct allegro_dev *dev, size_t size) { - static char buf[9]; + return lower_32_bits(size); +} - switch (type) { - case MCU_MSG_TYPE_INIT: - return "INIT"; - case MCU_MSG_TYPE_CREATE_CHANNEL: - return "CREATE_CHANNEL"; - case MCU_MSG_TYPE_DESTROY_CHANNEL: - return "DESTROY_CHANNEL"; - case MCU_MSG_TYPE_ENCODE_FRAME: - return "ENCODE_FRAME"; - case MCU_MSG_TYPE_PUT_STREAM_BUFFER: - return "PUT_STREAM_BUFFER"; - case MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE: - return "PUSH_BUFFER_INTERMEDIATE"; - case MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE: - return "PUSH_BUFFER_REFERENCE"; - default: - snprintf(buf, sizeof(buf), "(0x%04x)", type); - return buf; - } -} - -struct mcu_msg_header { - u16 length; /* length of the body in bytes */ - u16 type; -} __attribute__ ((__packed__)); - -struct mcu_msg_init_request { - struct mcu_msg_header header; - u32 reserved0; /* maybe a unused channel id */ - u32 suballoc_dma; - u32 suballoc_size; - s32 l2_cache[3]; -} __attribute__ ((__packed__)); - -struct mcu_msg_init_response { - struct mcu_msg_header header; - u32 reserved0; -} __attribute__ ((__packed__)); - -struct mcu_msg_create_channel { - struct mcu_msg_header header; - u32 user_id; - u16 width; - u16 height; - u32 format; - u32 colorspace; - u32 src_mode; - u8 profile; - u16 constraint_set_flags; - s8 codec; - u16 level; - u16 tier; - u32 sps_param; - u32 pps_param; - - u32 enc_option; -#define AL_OPT_WPP BIT(0) -#define AL_OPT_TILE BIT(1) -#define AL_OPT_LF BIT(2) -#define AL_OPT_LF_X_SLICE BIT(3) -#define AL_OPT_LF_X_TILE BIT(4) -#define AL_OPT_SCL_LST BIT(5) -#define AL_OPT_CONST_INTRA_PRED BIT(6) -#define AL_OPT_QP_TAB_RELATIVE BIT(7) -#define AL_OPT_FIX_PREDICTOR BIT(8) -#define AL_OPT_CUSTOM_LDA BIT(9) -#define AL_OPT_ENABLE_AUTO_QP BIT(10) -#define AL_OPT_ADAPT_AUTO_QP BIT(11) -#define AL_OPT_TRANSFO_SKIP BIT(13) -#define AL_OPT_FORCE_REC BIT(15) -#define AL_OPT_FORCE_MV_OUT BIT(16) -#define AL_OPT_FORCE_MV_CLIP BIT(17) -#define AL_OPT_LOWLAT_SYNC BIT(18) -#define AL_OPT_LOWLAT_INT BIT(19) -#define AL_OPT_RDO_COST_MODE BIT(20) - - s8 beta_offset; - s8 tc_offset; - u16 reserved10; - u32 unknown11; - u32 unknown12; - u16 num_slices; - u16 prefetch_auto; - u32 prefetch_mem_offset; - u32 prefetch_mem_size; - u16 clip_hrz_range; - u16 clip_vrt_range; - u16 me_range[4]; - u8 max_cu_size; - u8 min_cu_size; - u8 max_tu_size; - u8 min_tu_size; - u8 max_transfo_depth_inter; - u8 max_transfo_depth_intra; - u16 reserved20; - u32 entropy_mode; - u32 wp_mode; - - /* rate control param */ - u32 rate_control_mode; - u32 initial_rem_delay; - u32 cpb_size; - u16 framerate; - u16 clk_ratio; - u32 target_bitrate; - u32 max_bitrate; - u16 initial_qp; - u16 min_qp; - u16 max_qp; - s16 ip_delta; - s16 pb_delta; - u16 golden_ref; - u16 golden_delta; - u16 golden_ref_frequency; - u32 rate_control_option; - - /* gop param */ - u32 gop_ctrl_mode; - u32 freq_ird; - u32 freq_lt; - u32 gdr_mode; - u32 gop_length; - u32 unknown39; - - u32 subframe_latency; - u32 lda_control_mode; -} __attribute__ ((__packed__)); - -struct mcu_msg_create_channel_response { - struct mcu_msg_header header; - u32 channel_id; - u32 user_id; - u32 options; - u32 num_core; - u32 pps_param; - u32 int_buffers_count; - u32 int_buffers_size; - u32 rec_buffers_count; - u32 rec_buffers_size; - u32 reserved; - u32 error_code; -} __attribute__ ((__packed__)); - -struct mcu_msg_destroy_channel { - struct mcu_msg_header header; - u32 channel_id; -} __attribute__ ((__packed__)); - -struct mcu_msg_destroy_channel_response { - struct mcu_msg_header header; - u32 channel_id; -} __attribute__ ((__packed__)); - -struct mcu_msg_push_buffers_internal_buffer { - u32 dma_addr; - u32 mcu_addr; - u32 size; -} __attribute__ ((__packed__)); - -struct mcu_msg_push_buffers_internal { - struct mcu_msg_header header; - u32 channel_id; - struct mcu_msg_push_buffers_internal_buffer buffer[0]; -} __attribute__ ((__packed__)); - -struct mcu_msg_put_stream_buffer { - struct mcu_msg_header header; - u32 channel_id; - u32 dma_addr; - u32 mcu_addr; - u32 size; - u32 offset; - u64 stream_id; -} __attribute__ ((__packed__)); - -struct mcu_msg_encode_frame { - struct mcu_msg_header header; - u32 channel_id; - u32 reserved; - - u32 encoding_options; -#define AL_OPT_USE_QP_TABLE BIT(0) -#define AL_OPT_FORCE_LOAD BIT(1) -#define AL_OPT_USE_L2 BIT(2) -#define AL_OPT_DISABLE_INTRA BIT(3) -#define AL_OPT_DEPENDENT_SLICES BIT(4) - - s16 pps_qp; - u16 padding; - u64 user_param; - u64 src_handle; +static inline u32 to_codec_addr(struct allegro_dev *dev, dma_addr_t phys) +{ + if (upper_32_bits(phys)) + v4l2_warn(&dev->v4l2_dev, + "address %pad cannot be used by codec\n", &phys); - u32 request_options; -#define AL_OPT_SCENE_CHANGE BIT(0) -#define AL_OPT_RESTART_GOP BIT(1) -#define AL_OPT_USE_LONG_TERM BIT(2) -#define AL_OPT_UPDATE_PARAMS BIT(3) - - /* u32 scene_change_delay (optional) */ - /* rate control param (optional) */ - /* gop param (optional) */ - u32 src_y; - u32 src_uv; - u32 stride; - u32 ep2; - u64 ep2_v; -} __attribute__ ((__packed__)); - -struct mcu_msg_encode_frame_response { - struct mcu_msg_header header; - u32 channel_id; - u64 stream_id; /* see mcu_msg_put_stream_buffer */ - u64 user_param; /* see mcu_msg_encode_frame */ - u64 src_handle; /* see mcu_msg_encode_frame */ - u16 skip; - u16 is_ref; - u32 initial_removal_delay; - u32 dpb_output_delay; - u32 size; - u32 frame_tag_size; - s32 stuffing; - s32 filler; - u16 num_column; - u16 num_row; - u16 qp; - u8 num_ref_idx_l0; - u8 num_ref_idx_l1; - u32 partition_table_offset; - s32 partition_table_size; - u32 sum_complex; - s32 tile_width[4]; - s32 tile_height[22]; - u32 error_code; - - u32 slice_type; -#define AL_ENC_SLICE_TYPE_B 0 -#define AL_ENC_SLICE_TYPE_P 1 -#define AL_ENC_SLICE_TYPE_I 2 - - u32 pic_struct; - u8 is_idr; - u8 is_first_slice; - u8 is_last_slice; - u8 reserved; - u16 pps_qp; - u16 reserved1; - u32 reserved2; -} __attribute__ ((__packed__)); - -union mcu_msg_response { - struct mcu_msg_header header; - struct mcu_msg_init_response init; - struct mcu_msg_create_channel_response create_channel; - struct mcu_msg_destroy_channel_response destroy_channel; - struct mcu_msg_encode_frame_response encode_frame; -}; + return lower_32_bits(phys); +} + +static inline u64 ptr_to_u64(const void *ptr) +{ + return (uintptr_t)ptr; +} /* Helper functions for channel and user operations */ @@ -572,6 +356,56 @@ static inline bool channel_exists(struct allegro_channel *channel) return channel->mcu_channel_id != -1; } +#define AL_ERROR 0x80 +#define AL_ERR_INIT_FAILED 0x81 +#define AL_ERR_NO_FRAME_DECODED 0x82 +#define AL_ERR_RESOLUTION_CHANGE 0x85 +#define AL_ERR_NO_MEMORY 0x87 +#define AL_ERR_STREAM_OVERFLOW 0x88 +#define AL_ERR_TOO_MANY_SLICES 0x89 +#define AL_ERR_BUF_NOT_READY 0x8c +#define AL_ERR_NO_CHANNEL_AVAILABLE 0x8d +#define AL_ERR_RESOURCE_UNAVAILABLE 0x8e +#define AL_ERR_NOT_ENOUGH_CORES 0x8f +#define AL_ERR_REQUEST_MALFORMED 0x90 +#define AL_ERR_CMD_NOT_ALLOWED 0x91 +#define AL_ERR_INVALID_CMD_VALUE 0x92 + +static inline const char *allegro_err_to_string(unsigned int err) +{ + switch (err) { + case AL_ERR_INIT_FAILED: + return "initialization failed"; + case AL_ERR_NO_FRAME_DECODED: + return "no frame decoded"; + case AL_ERR_RESOLUTION_CHANGE: + return "resolution change"; + case AL_ERR_NO_MEMORY: + return "out of memory"; + case AL_ERR_STREAM_OVERFLOW: + return "stream buffer overflow"; + case AL_ERR_TOO_MANY_SLICES: + return "too many slices"; + case AL_ERR_BUF_NOT_READY: + return "buffer not ready"; + case AL_ERR_NO_CHANNEL_AVAILABLE: + return "no channel available"; + case AL_ERR_RESOURCE_UNAVAILABLE: + return "resource unavailable"; + case AL_ERR_NOT_ENOUGH_CORES: + return "not enough cores"; + case AL_ERR_REQUEST_MALFORMED: + return "request malformed"; + case AL_ERR_CMD_NOT_ALLOWED: + return "command not allowed"; + case AL_ERR_INVALID_CMD_VALUE: + return "invalid command value"; + case AL_ERROR: + default: + return "unknown error"; + } +} + static unsigned int estimate_stream_size(unsigned int width, unsigned int height) { @@ -781,7 +615,7 @@ static int allegro_mbox_write(struct allegro_dev *dev, if (size > mbox->size) { v4l2_err(&dev->v4l2_dev, - "message (%zu bytes) to large for mailbox (%zu bytes)\n", + "message (%zu bytes) too large for mailbox (%zu bytes)\n", size, mbox->size); return -EINVAL; } @@ -894,8 +728,8 @@ static void allegro_mcu_send_init(struct allegro_dev *dev, msg.header.type = MCU_MSG_TYPE_INIT; msg.header.length = sizeof(msg) - sizeof(msg.header); - msg.suballoc_dma = lower_32_bits(suballoc_dma) | MCU_CACHE_OFFSET; - msg.suballoc_size = suballoc_size; + msg.suballoc_dma = to_mcu_addr(dev, suballoc_dma); + msg.suballoc_size = to_mcu_size(dev, suballoc_size); /* disable L2 cache */ msg.l2_cache[0] = -1; @@ -1001,6 +835,103 @@ v4l2_bitrate_mode_to_mcu_mode(enum v4l2_mpeg_video_bitrate_mode mode) } } +static u32 v4l2_cpb_size_to_mcu(unsigned int cpb_size, unsigned int bitrate) +{ + unsigned int cpb_size_kbit; + unsigned int bitrate_kbps; + + /* + * The mcu expects the CPB size in units of a 90 kHz clock, but the + * channel follows the V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE and stores + * the CPB size in kilobytes. + */ + cpb_size_kbit = cpb_size * BITS_PER_BYTE; + bitrate_kbps = bitrate / 1000; + + return (cpb_size_kbit * 90000) / bitrate_kbps; +} + +static s16 get_qp_delta(int minuend, int subtrahend) +{ + if (minuend == subtrahend) + return -1; + else + return minuend - subtrahend; +} + +static int fill_create_channel_param(struct allegro_channel *channel, + struct create_channel_param *param) +{ + int i_frame_qp = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_i_frame_qp); + int p_frame_qp = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_p_frame_qp); + int b_frame_qp = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_b_frame_qp); + int bitrate_mode = v4l2_ctrl_g_ctrl(channel->mpeg_video_bitrate_mode); + + param->width = channel->width; + param->height = channel->height; + param->format = v4l2_pixelformat_to_mcu_format(channel->pixelformat); + param->colorspace = + v4l2_colorspace_to_mcu_colorspace(channel->colorspace); + param->src_mode = 0x0; + param->profile = v4l2_profile_to_mcu_profile(channel->profile); + param->constraint_set_flags = BIT(1); + param->codec = v4l2_pixelformat_to_mcu_codec(channel->codec); + param->level = v4l2_level_to_mcu_level(channel->level); + param->tier = 0; + param->sps_param = BIT(20) | 0x4a; + param->pps_param = BIT(2); + param->enc_option = AL_OPT_RDO_COST_MODE | AL_OPT_LF_X_TILE | + AL_OPT_LF_X_SLICE | AL_OPT_LF; + param->beta_offset = -1; + param->tc_offset = -1; + param->num_slices = 1; + param->me_range[0] = 8; + param->me_range[1] = 8; + param->me_range[2] = 16; + param->me_range[3] = 16; + param->max_cu_size = ilog2(SIZE_MACROBLOCK); + param->min_cu_size = ilog2(8); + param->max_tu_size = 2; + param->min_tu_size = 2; + param->max_transfo_depth_intra = 1; + param->max_transfo_depth_inter = 1; + + param->prefetch_auto = 0; + param->prefetch_mem_offset = 0; + param->prefetch_mem_size = 0; + + param->rate_control_mode = channel->frame_rc_enable ? + v4l2_bitrate_mode_to_mcu_mode(bitrate_mode) : 0; + + param->cpb_size = v4l2_cpb_size_to_mcu(channel->cpb_size, + channel->bitrate_peak); + /* Shall be ]0;cpb_size in 90 kHz units]. Use maximum value. */ + param->initial_rem_delay = param->cpb_size; + param->framerate = DIV_ROUND_UP(channel->framerate.numerator, + channel->framerate.denominator); + param->clk_ratio = channel->framerate.denominator == 1001 ? 1001 : 1000; + param->target_bitrate = channel->bitrate; + param->max_bitrate = channel->bitrate_peak; + param->initial_qp = i_frame_qp; + param->min_qp = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_min_qp); + param->max_qp = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_max_qp); + param->ip_delta = get_qp_delta(i_frame_qp, p_frame_qp); + param->pb_delta = get_qp_delta(p_frame_qp, b_frame_qp); + param->golden_ref = 0; + param->golden_delta = 2; + param->golden_ref_frequency = 10; + param->rate_control_option = 0x00000000; + + param->gop_ctrl_mode = 0x00000000; + param->freq_idr = channel->gop_size; + param->freq_lt = 0; + param->gdr_mode = 0x00000000; + param->gop_length = channel->gop_size; + param->subframe_latency = 0x00000000; + + return 0; +} + static int allegro_mcu_send_create_channel(struct allegro_dev *dev, struct allegro_channel *channel) { @@ -1012,63 +943,8 @@ static int allegro_mcu_send_create_channel(struct allegro_dev *dev, msg.header.length = sizeof(msg) - sizeof(msg.header); msg.user_id = channel->user_id; - msg.width = channel->width; - msg.height = channel->height; - msg.format = v4l2_pixelformat_to_mcu_format(channel->pixelformat); - msg.colorspace = v4l2_colorspace_to_mcu_colorspace(channel->colorspace); - msg.src_mode = 0x0; - msg.profile = v4l2_profile_to_mcu_profile(channel->profile); - msg.constraint_set_flags = BIT(1); - msg.codec = v4l2_pixelformat_to_mcu_codec(channel->codec); - msg.level = v4l2_level_to_mcu_level(channel->level); - msg.tier = 0; - msg.sps_param = BIT(20) | 0x4a; - msg.pps_param = BIT(2); - msg.enc_option = AL_OPT_RDO_COST_MODE | AL_OPT_LF_X_TILE | - AL_OPT_LF_X_SLICE | AL_OPT_LF; - msg.beta_offset = -1; - msg.tc_offset = -1; - msg.num_slices = 1; - msg.me_range[0] = 8; - msg.me_range[1] = 8; - msg.me_range[2] = 16; - msg.me_range[3] = 16; - msg.max_cu_size = ilog2(SIZE_MACROBLOCK); - msg.min_cu_size = ilog2(8); - msg.max_tu_size = 2; - msg.min_tu_size = 2; - msg.max_transfo_depth_intra = 1; - msg.max_transfo_depth_inter = 1; - - msg.rate_control_mode = - v4l2_bitrate_mode_to_mcu_mode(channel->bitrate_mode); - /* Shall be ]0;cpb_size in 90 kHz units]. Use maximum value. */ - msg.initial_rem_delay = - ((channel->cpb_size * 1000) / channel->bitrate_peak) * 90000; - /* Encoder expects cpb_size in units of a 90 kHz clock. */ - msg.cpb_size = - ((channel->cpb_size * 1000) / channel->bitrate_peak) * 90000; - msg.framerate = 25; - msg.clk_ratio = 1000; - msg.target_bitrate = channel->bitrate; - msg.max_bitrate = channel->bitrate_peak; - msg.initial_qp = 25; - msg.min_qp = 10; - msg.max_qp = 51; - msg.ip_delta = -1; - msg.pb_delta = -1; - msg.golden_ref = 0; - msg.golden_delta = 2; - msg.golden_ref_frequency = 10; - msg.rate_control_option = 0x00000000; - - msg.gop_ctrl_mode = 0x00000000; - msg.freq_ird = 0x7fffffff; - msg.freq_lt = 0; - msg.gdr_mode = 0x00000000; - msg.gop_length = channel->gop_size; - msg.subframe_latency = 0x00000000; - msg.lda_control_mode = 0x700d0000; + + fill_create_channel_param(channel, &msg.param); allegro_mbox_write(dev, &dev->mbox_command, &msg, sizeof(msg)); allegro_mcu_interrupt(dev); @@ -1097,7 +973,8 @@ static int allegro_mcu_send_destroy_channel(struct allegro_dev *dev, static int allegro_mcu_send_put_stream_buffer(struct allegro_dev *dev, struct allegro_channel *channel, dma_addr_t paddr, - unsigned long size) + unsigned long size, + u64 stream_id) { struct mcu_msg_put_stream_buffer msg; @@ -1107,11 +984,12 @@ static int allegro_mcu_send_put_stream_buffer(struct allegro_dev *dev, msg.header.length = sizeof(msg) - sizeof(msg.header); msg.channel_id = channel->mcu_channel_id; - msg.dma_addr = paddr; - msg.mcu_addr = paddr | MCU_CACHE_OFFSET; + msg.dma_addr = to_codec_addr(dev, paddr); + msg.mcu_addr = to_mcu_addr(dev, paddr); msg.size = size; msg.offset = ENCODER_STREAM_OFFSET; - msg.stream_id = 0; /* copied to mcu_msg_encode_frame_response */ + /* copied to mcu_msg_encode_frame_response */ + msg.stream_id = stream_id; allegro_mbox_write(dev, &dev->mbox_command, &msg, sizeof(msg)); allegro_mcu_interrupt(dev); @@ -1121,7 +999,8 @@ static int allegro_mcu_send_put_stream_buffer(struct allegro_dev *dev, static int allegro_mcu_send_encode_frame(struct allegro_dev *dev, struct allegro_channel *channel, - dma_addr_t src_y, dma_addr_t src_uv) + dma_addr_t src_y, dma_addr_t src_uv, + u64 src_handle) { struct mcu_msg_encode_frame msg; @@ -1134,12 +1013,13 @@ static int allegro_mcu_send_encode_frame(struct allegro_dev *dev, msg.encoding_options = AL_OPT_FORCE_LOAD; msg.pps_qp = 26; /* qp are relative to 26 */ msg.user_param = 0; /* copied to mcu_msg_encode_frame_response */ - msg.src_handle = 0; /* copied to mcu_msg_encode_frame_response */ - msg.src_y = src_y; - msg.src_uv = src_uv; + /* src_handle is copied to mcu_msg_encode_frame_response */ + msg.src_handle = src_handle; + msg.src_y = to_codec_addr(dev, src_y); + msg.src_uv = to_codec_addr(dev, src_uv); msg.stride = channel->stride; msg.ep2 = 0x0; - msg.ep2_v = msg.ep2 | MCU_CACHE_OFFSET; + msg.ep2_v = to_mcu_addr(dev, msg.ep2); allegro_mbox_write(dev, &dev->mbox_command, &msg, sizeof(msg)); allegro_mcu_interrupt(dev); @@ -1198,10 +1078,9 @@ static int allegro_mcu_push_buffer_internal(struct allegro_channel *channel, buffer = msg->buffer; list_for_each_entry(al_buffer, list, head) { - buffer->dma_addr = lower_32_bits(al_buffer->paddr); - buffer->mcu_addr = - lower_32_bits(al_buffer->paddr) | MCU_CACHE_OFFSET; - buffer->size = al_buffer->size; + buffer->dma_addr = to_codec_addr(dev, al_buffer->paddr); + buffer->mcu_addr = to_mcu_addr(dev, al_buffer->paddr); + buffer->size = to_mcu_size(dev, al_buffer->size); buffer++; } @@ -1360,9 +1239,11 @@ static ssize_t allegro_h264_write_sps(struct allegro_channel *channel, sps->vui.chroma_loc_info_present_flag = 1; sps->vui.chroma_sample_loc_type_top_field = 0; sps->vui.chroma_sample_loc_type_bottom_field = 0; + sps->vui.timing_info_present_flag = 1; - sps->vui.num_units_in_tick = 1; - sps->vui.time_scale = 50; + sps->vui.num_units_in_tick = channel->framerate.denominator; + sps->vui.time_scale = 2 * channel->framerate.numerator; + sps->vui.fixed_frame_rate_flag = 1; sps->vui.nal_hrd_parameters_present_flag = 0; sps->vui.vcl_hrd_parameters_present_flag = 1; @@ -1375,7 +1256,8 @@ static ssize_t allegro_h264_write_sps(struct allegro_channel *channel, /* See Rec. ITU-T H.264 (04/2017) p. 410 E-54 */ sps->vui.vcl_hrd_parameters.cpb_size_value_minus1[0] = (channel->cpb_size * 1000) / (1 << (4 + sps->vui.vcl_hrd_parameters.cpb_size_scale)) - 1; - sps->vui.vcl_hrd_parameters.cbr_flag[0] = 1; + sps->vui.vcl_hrd_parameters.cbr_flag[0] = + !v4l2_ctrl_g_ctrl(channel->mpeg_video_frame_rc_enable); sps->vui.vcl_hrd_parameters.initial_cpb_removal_delay_length_minus1 = 31; sps->vui.vcl_hrd_parameters.cpb_removal_delay_length_minus1 = 31; sps->vui.vcl_hrd_parameters.dpb_output_delay_length_minus1 = 31; @@ -1438,8 +1320,11 @@ static bool allegro_channel_is_at_eos(struct allegro_channel *channel) break; case ALLEGRO_STATE_DRAIN: case ALLEGRO_STATE_WAIT_FOR_BUFFER: - if (v4l2_m2m_num_src_bufs_ready(channel->fh.m2m_ctx) == 0) + mutex_lock(&channel->shadow_list_lock); + if (v4l2_m2m_num_src_bufs_ready(channel->fh.m2m_ctx) == 0 && + list_empty(&channel->source_shadow_list)) is_at_eos = true; + mutex_unlock(&channel->shadow_list_lock); break; default: break; @@ -1466,6 +1351,41 @@ static void allegro_channel_buf_done(struct allegro_channel *channel, v4l2_m2m_buf_done(buf, state); } +static u64 allegro_put_buffer(struct allegro_channel *channel, + struct list_head *list, + struct vb2_v4l2_buffer *buffer) +{ + struct v4l2_m2m_buffer *b = container_of(buffer, + struct v4l2_m2m_buffer, vb); + struct allegro_m2m_buffer *shadow = to_allegro_m2m_buffer(b); + + mutex_lock(&channel->shadow_list_lock); + list_add_tail(&shadow->head, list); + mutex_unlock(&channel->shadow_list_lock); + + return ptr_to_u64(buffer); +} + +static struct vb2_v4l2_buffer * +allegro_get_buffer(struct allegro_channel *channel, + struct list_head *list, u64 handle) +{ + struct allegro_m2m_buffer *shadow, *tmp; + struct vb2_v4l2_buffer *buffer = NULL; + + mutex_lock(&channel->shadow_list_lock); + list_for_each_entry_safe(shadow, tmp, list, head) { + if (handle == ptr_to_u64(&shadow->buf.vb)) { + buffer = &shadow->buf.vb; + list_del_init(&shadow->head); + break; + } + } + mutex_unlock(&channel->shadow_list_lock); + + return buffer; +} + static void allegro_channel_finish_frame(struct allegro_channel *channel, struct mcu_msg_encode_frame_response *msg) { @@ -1481,15 +1401,31 @@ static void allegro_channel_finish_frame(struct allegro_channel *channel, ssize_t len; ssize_t free; - src_buf = v4l2_m2m_src_buf_remove(channel->fh.m2m_ctx); + src_buf = allegro_get_buffer(channel, &channel->source_shadow_list, + msg->src_handle); + if (!src_buf) + v4l2_warn(&dev->v4l2_dev, + "channel %d: invalid source buffer\n", + channel->mcu_channel_id); + + dst_buf = allegro_get_buffer(channel, &channel->stream_shadow_list, + msg->stream_id); + if (!dst_buf) + v4l2_warn(&dev->v4l2_dev, + "channel %d: invalid stream buffer\n", + channel->mcu_channel_id); + + if (!src_buf || !dst_buf) + goto err; - dst_buf = v4l2_m2m_dst_buf_remove(channel->fh.m2m_ctx); dst_buf->sequence = channel->csequence++; - if (msg->error_code) { + if (msg->error_code & AL_ERROR) { v4l2_err(&dev->v4l2_dev, - "channel %d: error while encoding frame: %x\n", - channel->mcu_channel_id, msg->error_code); + "channel %d: failed to encode frame: %s (%x)\n", + channel->mcu_channel_id, + allegro_err_to_string(msg->error_code), + msg->error_code); goto err; } @@ -1562,17 +1498,22 @@ static void allegro_channel_finish_frame(struct allegro_channel *channel, channel->mcu_channel_id, len); } - len = nal_h264_write_filler(&dev->plat_dev->dev, curr, free); - if (len < 0) { - v4l2_err(&dev->v4l2_dev, - "failed to write %zd filler data\n", free); - goto err; + if (msg->slice_type != AL_ENC_SLICE_TYPE_I && !msg->is_idr) { + dst_buf->vb2_buf.planes[0].data_offset = free; + free = 0; + } else { + len = nal_h264_write_filler(&dev->plat_dev->dev, curr, free); + if (len < 0) { + v4l2_err(&dev->v4l2_dev, + "failed to write %zd filler data\n", free); + goto err; + } + curr += len; + free -= len; + v4l2_dbg(2, debug, &dev->v4l2_dev, + "channel %d: wrote %zd bytes filler nal unit\n", + channel->mcu_channel_id, len); } - curr += len; - free -= len; - v4l2_dbg(2, debug, &dev->v4l2_dev, - "channel %d: wrote %zd bytes filler nal unit\n", - channel->mcu_channel_id, len); if (free != 0) { v4l2_err(&dev->v4l2_dev, @@ -1590,20 +1531,20 @@ static void allegro_channel_finish_frame(struct allegro_channel *channel, dst_buf->flags |= V4L2_BUF_FLAG_PFRAME; v4l2_dbg(1, debug, &dev->v4l2_dev, - "channel %d: encoded frame #%03d (%s%s, %d bytes)\n", + "channel %d: encoded frame #%03d (%s%s, QP %d, %d bytes)\n", channel->mcu_channel_id, dst_buf->sequence, msg->is_idr ? "IDR, " : "", msg->slice_type == AL_ENC_SLICE_TYPE_I ? "I slice" : msg->slice_type == AL_ENC_SLICE_TYPE_P ? "P slice" : "unknown", - partition->size); + msg->qp, partition->size); err: - v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); - - allegro_channel_buf_done(channel, dst_buf, state); + if (src_buf) + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); - v4l2_m2m_job_finish(dev->m2m_dev, channel->fh.m2m_ctx); + if (dst_buf) + allegro_channel_buf_done(channel, dst_buf, state); } static int allegro_handle_init(struct allegro_dev *dev, @@ -1621,6 +1562,12 @@ allegro_handle_create_channel(struct allegro_dev *dev, struct allegro_channel *channel; int err = 0; + if (msg->header.length != sizeof(*msg) - sizeof(msg->header)) + v4l2_warn(&dev->v4l2_dev, + "received message has %d bytes, but expected %zu\n", + msg->header.length, + sizeof(*msg) - sizeof(msg->header)); + channel = allegro_find_channel_by_user_id(dev, msg->user_id); if (IS_ERR(channel)) { v4l2_warn(&dev->v4l2_dev, @@ -1632,8 +1579,10 @@ allegro_handle_create_channel(struct allegro_dev *dev, if (msg->error_code) { v4l2_err(&dev->v4l2_dev, - "user %d: mcu failed to create channel: error %x\n", - channel->user_id, msg->error_code); + "user %d: mcu failed to create channel: %s (%x)\n", + channel->user_id, + allegro_err_to_string(msg->error_code), + msg->error_code); err = -EIO; goto out; } @@ -1712,6 +1661,12 @@ allegro_handle_encode_frame(struct allegro_dev *dev, { struct allegro_channel *channel; + if (msg->header.length != sizeof(*msg) - sizeof(msg->header)) + v4l2_warn(&dev->v4l2_dev, + "received message has %d bytes, but expected %zu\n", + msg->header.length, + sizeof(*msg) - sizeof(msg->header)); + channel = allegro_find_channel_by_channel_id(dev, msg->channel_id); if (IS_ERR(channel)) { v4l2_err(&dev->v4l2_dev, @@ -1920,6 +1875,14 @@ static int allegro_mcu_reset(struct allegro_dev *dev) { int err; + /* + * Ensure that the AL5_MCU_WAKEUP bit is set to 0 otherwise the mcu + * does not go to sleep after the reset. + */ + err = regmap_write(dev->regmap, AL5_MCU_WAKEUP, 0); + if (err) + return err; + err = regmap_write(dev->regmap, AL5_MCU_RESET_MODE, AL5_MCU_RESET_MODE_SLEEP); if (err < 0) @@ -1955,6 +1918,12 @@ static void allegro_destroy_channel(struct allegro_channel *channel) v4l2_ctrl_grab(channel->mpeg_video_h264_profile, false); v4l2_ctrl_grab(channel->mpeg_video_h264_level, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_i_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_max_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_min_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_p_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_b_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_frame_rc_enable, false); v4l2_ctrl_grab(channel->mpeg_video_bitrate_mode, false); v4l2_ctrl_grab(channel->mpeg_video_bitrate, false); v4l2_ctrl_grab(channel->mpeg_video_bitrate_peak, false); @@ -2000,7 +1969,9 @@ static int allegro_create_channel(struct allegro_channel *channel) v4l2_dbg(1, debug, &dev->v4l2_dev, "user %d: creating channel (%4.4s, %dx%d@%d)\n", channel->user_id, - (char *)&channel->codec, channel->width, channel->height, 25); + (char *)&channel->codec, channel->width, channel->height, + DIV_ROUND_UP(channel->framerate.numerator, + channel->framerate.denominator)); min_level = select_minimum_h264_level(channel->width, channel->height); if (channel->level < min_level) { @@ -2014,6 +1985,12 @@ static int allegro_create_channel(struct allegro_channel *channel) v4l2_ctrl_grab(channel->mpeg_video_h264_profile, true); v4l2_ctrl_grab(channel->mpeg_video_h264_level, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_i_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_max_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_min_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_p_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_b_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_frame_rc_enable, true); v4l2_ctrl_grab(channel->mpeg_video_bitrate_mode, true); v4l2_ctrl_grab(channel->mpeg_video_bitrate, true); v4l2_ctrl_grab(channel->mpeg_video_bitrate_peak, true); @@ -2046,6 +2023,7 @@ static void allegro_set_default_params(struct allegro_channel *channel) channel->width = ALLEGRO_WIDTH_DEFAULT; channel->height = ALLEGRO_HEIGHT_DEFAULT; channel->stride = round_up(channel->width, 32); + channel->framerate = ALLEGRO_FRAMERATE_DEFAULT; channel->colorspace = V4L2_COLORSPACE_REC709; channel->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; @@ -2062,7 +2040,6 @@ static void allegro_set_default_params(struct allegro_channel *channel) channel->sizeimage_encoded = estimate_stream_size(channel->width, channel->height); - channel->bitrate_mode = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR; channel->bitrate = maximum_bitrate(channel->level); channel->bitrate_peak = maximum_bitrate(channel->level); channel->cpb_size = maximum_cpb_size(channel->level); @@ -2163,16 +2140,33 @@ static void allegro_stop_streaming(struct vb2_queue *q) struct allegro_channel *channel = vb2_get_drv_priv(q); struct allegro_dev *dev = channel->dev; struct vb2_v4l2_buffer *buffer; + struct allegro_m2m_buffer *shadow, *tmp; v4l2_dbg(2, debug, &dev->v4l2_dev, "%s: stop streaming\n", V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture"); if (V4L2_TYPE_IS_OUTPUT(q->type)) { + mutex_lock(&channel->shadow_list_lock); + list_for_each_entry_safe(shadow, tmp, + &channel->source_shadow_list, head) { + list_del(&shadow->head); + v4l2_m2m_buf_done(&shadow->buf.vb, VB2_BUF_STATE_ERROR); + } + mutex_unlock(&channel->shadow_list_lock); + allegro_set_state(channel, ALLEGRO_STATE_STOPPED); while ((buffer = v4l2_m2m_src_buf_remove(channel->fh.m2m_ctx))) v4l2_m2m_buf_done(buffer, VB2_BUF_STATE_ERROR); } else if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_lock(&channel->shadow_list_lock); + list_for_each_entry_safe(shadow, tmp, + &channel->stream_shadow_list, head) { + list_del(&shadow->head); + v4l2_m2m_buf_done(&shadow->buf.vb, VB2_BUF_STATE_ERROR); + } + mutex_unlock(&channel->shadow_list_lock); + allegro_destroy_channel(channel); while ((buffer = v4l2_m2m_dst_buf_remove(channel->fh.m2m_ctx))) v4l2_m2m_buf_done(buffer, VB2_BUF_STATE_ERROR); @@ -2203,7 +2197,7 @@ static int allegro_queue_init(void *priv, src_vq->drv_priv = channel; src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; src_vq->ops = &allegro_queue_ops; - src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->buf_struct_size = sizeof(struct allegro_m2m_buffer); src_vq->lock = &channel->dev->lock; err = vb2_queue_init(src_vq); if (err) @@ -2216,7 +2210,7 @@ static int allegro_queue_init(void *priv, dst_vq->drv_priv = channel; dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; dst_vq->ops = &allegro_queue_ops; - dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->buf_struct_size = sizeof(struct allegro_m2m_buffer); dst_vq->lock = &channel->dev->lock; err = vb2_queue_init(dst_vq); if (err) @@ -2225,6 +2219,52 @@ static int allegro_queue_init(void *priv, return 0; } +static int allegro_clamp_qp(struct allegro_channel *channel, + struct v4l2_ctrl *ctrl) +{ + struct v4l2_ctrl *next_ctrl; + + if (ctrl->id == V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP) + next_ctrl = channel->mpeg_video_h264_p_frame_qp; + else if (ctrl->id == V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP) + next_ctrl = channel->mpeg_video_h264_b_frame_qp; + else + return 0; + + /* Modify range automatically updates the value */ + __v4l2_ctrl_modify_range(next_ctrl, ctrl->val, 51, 1, ctrl->val); + + return allegro_clamp_qp(channel, next_ctrl); +} + +static int allegro_clamp_bitrate(struct allegro_channel *channel, + struct v4l2_ctrl *ctrl) +{ + struct v4l2_ctrl *ctrl_bitrate = channel->mpeg_video_bitrate; + struct v4l2_ctrl *ctrl_bitrate_peak = channel->mpeg_video_bitrate_peak; + + if (ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR && + ctrl_bitrate_peak->val < ctrl_bitrate->val) + ctrl_bitrate_peak->val = ctrl_bitrate->val; + + return 0; +} + +static int allegro_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct allegro_channel *channel = container_of(ctrl->handler, + struct allegro_channel, + ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + allegro_clamp_bitrate(channel, ctrl); + break; + } + + return 0; +} + static int allegro_s_ctrl(struct v4l2_ctrl *ctrl) { struct allegro_channel *channel = container_of(ctrl->handler, @@ -2239,14 +2279,14 @@ static int allegro_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_MPEG_VIDEO_H264_LEVEL: channel->level = ctrl->val; break; - case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: - channel->bitrate_mode = ctrl->val; - break; - case V4L2_CID_MPEG_VIDEO_BITRATE: - channel->bitrate = ctrl->val; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + channel->frame_rc_enable = ctrl->val; break; - case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: - channel->bitrate_peak = ctrl->val; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + channel->bitrate = channel->mpeg_video_bitrate->val; + channel->bitrate_peak = channel->mpeg_video_bitrate_peak->val; + v4l2_ctrl_activate(channel->mpeg_video_bitrate_peak, + ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); break; case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE: channel->cpb_size = ctrl->val; @@ -2254,12 +2294,18 @@ static int allegro_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_MPEG_VIDEO_GOP_SIZE: channel->gop_size = ctrl->val; break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + allegro_clamp_qp(channel, ctrl); + break; } return 0; } static const struct v4l2_ctrl_ops allegro_ctrl_ops = { + .try_ctrl = allegro_try_ctrl, .s_ctrl = allegro_s_ctrl, }; @@ -2270,16 +2316,18 @@ static int allegro_open(struct file *file) struct allegro_channel *channel = NULL; struct v4l2_ctrl_handler *handler; u64 mask; + int ret; channel = kzalloc(sizeof(*channel), GFP_KERNEL); if (!channel) return -ENOMEM; v4l2_fh_init(&channel->fh, vdev); - file->private_data = &channel->fh; - v4l2_fh_add(&channel->fh); init_completion(&channel->completion); + INIT_LIST_HEAD(&channel->source_shadow_list); + INIT_LIST_HEAD(&channel->stream_shadow_list); + mutex_init(&channel->shadow_list_lock); channel->dev = dev; @@ -2298,11 +2346,42 @@ static int allegro_open(struct file *file) V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1, mask, V4L2_MPEG_VIDEO_H264_LEVEL_5_1); + channel->mpeg_video_h264_i_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_h264_max_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + 0, 51, 1, 51); + channel->mpeg_video_h264_min_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + 0, 51, 1, 0); + channel->mpeg_video_h264_p_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_h264_b_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_frame_rc_enable = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, + false, 0x1, + true, false); channel->mpeg_video_bitrate_mode = v4l2_ctrl_new_std_menu(handler, &allegro_ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE_MODE, V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, - channel->bitrate_mode); + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); channel->mpeg_video_bitrate = v4l2_ctrl_new_std(handler, &allegro_ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE, @@ -2328,8 +2407,15 @@ static int allegro_open(struct file *file) V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 32, 1, 1); + if (handler->error != 0) { + ret = handler->error; + goto error; + } + channel->fh.ctrl_handler = handler; + v4l2_ctrl_cluster(3, &channel->mpeg_video_bitrate_mode); + channel->mcu_channel_id = -1; channel->user_id = -1; @@ -2341,7 +2427,20 @@ static int allegro_open(struct file *file) channel->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, channel, allegro_queue_init); + if (IS_ERR(channel->fh.m2m_ctx)) { + ret = PTR_ERR(channel->fh.m2m_ctx); + goto error; + } + + file->private_data = &channel->fh; + v4l2_fh_add(&channel->fh); + return 0; + +error: + v4l2_ctrl_handler_free(handler); + kfree(channel); + return ret; } static int allegro_release(struct file *file) @@ -2636,6 +2735,46 @@ static int allegro_ioctl_streamon(struct file *file, void *priv, return v4l2_m2m_streamon(file, fh->m2m_ctx, type); } +static int allegro_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct allegro_channel *channel = fh_to_channel(fh); + struct v4l2_fract *timeperframe; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + timeperframe = &a->parm.output.timeperframe; + timeperframe->numerator = channel->framerate.denominator; + timeperframe->denominator = channel->framerate.numerator; + + return 0; +} + +static int allegro_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct allegro_channel *channel = fh_to_channel(fh); + struct v4l2_fract *timeperframe; + int div; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + timeperframe = &a->parm.output.timeperframe; + + if (timeperframe->numerator == 0 || timeperframe->denominator == 0) + return allegro_g_parm(file, fh, a); + + div = gcd(timeperframe->denominator, timeperframe->numerator); + channel->framerate.numerator = timeperframe->denominator / div; + channel->framerate.denominator = timeperframe->numerator / div; + + return 0; +} + static int allegro_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -2674,6 +2813,9 @@ static const struct v4l2_ioctl_ops allegro_ioctl_ops = { .vidioc_encoder_cmd = allegro_encoder_cmd, .vidioc_enum_framesizes = allegro_enum_framesizes, + .vidioc_g_parm = allegro_g_parm, + .vidioc_s_parm = allegro_s_parm, + .vidioc_subscribe_event = allegro_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; @@ -2701,7 +2843,7 @@ static int allegro_register_device(struct allegro_dev *dev) video_dev->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; video_set_drvdata(video_dev, dev); - return video_register_device(video_dev, VFL_TYPE_GRABBER, 0); + return video_register_device(video_dev, VFL_TYPE_VIDEO, 0); } static void allegro_device_run(void *priv) @@ -2714,18 +2856,26 @@ static void allegro_device_run(void *priv) dma_addr_t src_uv; dma_addr_t dst_addr; unsigned long dst_size; + u64 src_handle; + u64 dst_handle; - dst_buf = v4l2_m2m_next_dst_buf(channel->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(channel->fh.m2m_ctx); dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); dst_size = vb2_plane_size(&dst_buf->vb2_buf, 0); - allegro_mcu_send_put_stream_buffer(dev, channel, dst_addr, dst_size); + dst_handle = allegro_put_buffer(channel, &channel->stream_shadow_list, + dst_buf); + allegro_mcu_send_put_stream_buffer(dev, channel, dst_addr, dst_size, + dst_handle); - src_buf = v4l2_m2m_next_src_buf(channel->fh.m2m_ctx); + src_buf = v4l2_m2m_src_buf_remove(channel->fh.m2m_ctx); src_buf->sequence = channel->osequence++; - src_y = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); src_uv = src_y + (channel->stride * channel->height); - allegro_mcu_send_encode_frame(dev, channel, src_y, src_uv); + src_handle = allegro_put_buffer(channel, &channel->source_shadow_list, + src_buf); + allegro_mcu_send_encode_frame(dev, channel, src_y, src_uv, src_handle); + + v4l2_m2m_job_finish(dev->m2m_dev, channel->fh.m2m_ctx); } static const struct v4l2_m2m_ops allegro_m2m_ops = { diff --git a/drivers/staging/media/allegro-dvt/allegro-mail.c b/drivers/staging/media/allegro-dvt/allegro-mail.c new file mode 100644 index 000000000000..df0d8d26a6fb --- /dev/null +++ b/drivers/staging/media/allegro-dvt/allegro-mail.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter <kernel@pengutronix.de> + * + * Helper functions for handling messages that are send via mailbox to the + * Allegro VCU firmware. + */ + +#include <linux/export.h> + +#include "allegro-mail.h" + +const char *msg_type_name(enum mcu_msg_type type) +{ + static char buf[9]; + + switch (type) { + case MCU_MSG_TYPE_INIT: + return "INIT"; + case MCU_MSG_TYPE_CREATE_CHANNEL: + return "CREATE_CHANNEL"; + case MCU_MSG_TYPE_DESTROY_CHANNEL: + return "DESTROY_CHANNEL"; + case MCU_MSG_TYPE_ENCODE_FRAME: + return "ENCODE_FRAME"; + case MCU_MSG_TYPE_PUT_STREAM_BUFFER: + return "PUT_STREAM_BUFFER"; + case MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE: + return "PUSH_BUFFER_INTERMEDIATE"; + case MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE: + return "PUSH_BUFFER_REFERENCE"; + default: + snprintf(buf, sizeof(buf), "(0x%04x)", type); + return buf; + } +} +EXPORT_SYMBOL(msg_type_name); diff --git a/drivers/staging/media/allegro-dvt/allegro-mail.h b/drivers/staging/media/allegro-dvt/allegro-mail.h new file mode 100644 index 000000000000..1fd36f65be78 --- /dev/null +++ b/drivers/staging/media/allegro-dvt/allegro-mail.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter <kernel@pengutronix.de> + * + * Allegro VCU firmware mailbox mail definitions + */ + +#ifndef ALLEGRO_MAIL_H +#define ALLEGRO_MAIL_H + +#include <linux/kernel.h> + +enum mcu_msg_type { + MCU_MSG_TYPE_INIT = 0x0000, + MCU_MSG_TYPE_CREATE_CHANNEL = 0x0005, + MCU_MSG_TYPE_DESTROY_CHANNEL = 0x0006, + MCU_MSG_TYPE_ENCODE_FRAME = 0x0007, + MCU_MSG_TYPE_PUT_STREAM_BUFFER = 0x0012, + MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE = 0x000e, + MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE = 0x000f, +}; + +const char *msg_type_name(enum mcu_msg_type type); + +struct mcu_msg_header { + u16 length; /* length of the body in bytes */ + u16 type; +} __attribute__ ((__packed__)); + +struct mcu_msg_init_request { + struct mcu_msg_header header; + u32 reserved0; /* maybe a unused channel id */ + u32 suballoc_dma; + u32 suballoc_size; + s32 l2_cache[3]; +} __attribute__ ((__packed__)); + +struct mcu_msg_init_response { + struct mcu_msg_header header; + u32 reserved0; +} __attribute__ ((__packed__)); + +struct create_channel_param { + u16 width; + u16 height; + u32 format; + u32 colorspace; + u32 src_mode; + u8 profile; + u16 constraint_set_flags; + s8 codec; + u16 level; + u16 tier; + u32 sps_param; + u32 pps_param; + + u32 enc_option; +#define AL_OPT_WPP BIT(0) +#define AL_OPT_TILE BIT(1) +#define AL_OPT_LF BIT(2) +#define AL_OPT_LF_X_SLICE BIT(3) +#define AL_OPT_LF_X_TILE BIT(4) +#define AL_OPT_SCL_LST BIT(5) +#define AL_OPT_CONST_INTRA_PRED BIT(6) +#define AL_OPT_QP_TAB_RELATIVE BIT(7) +#define AL_OPT_FIX_PREDICTOR BIT(8) +#define AL_OPT_CUSTOM_LDA BIT(9) +#define AL_OPT_ENABLE_AUTO_QP BIT(10) +#define AL_OPT_ADAPT_AUTO_QP BIT(11) +#define AL_OPT_TRANSFO_SKIP BIT(13) +#define AL_OPT_FORCE_REC BIT(15) +#define AL_OPT_FORCE_MV_OUT BIT(16) +#define AL_OPT_FORCE_MV_CLIP BIT(17) +#define AL_OPT_LOWLAT_SYNC BIT(18) +#define AL_OPT_LOWLAT_INT BIT(19) +#define AL_OPT_RDO_COST_MODE BIT(20) + + s8 beta_offset; + s8 tc_offset; + u16 reserved10; + u32 unknown11; + u32 unknown12; + u16 num_slices; + u16 prefetch_auto; + u32 prefetch_mem_offset; + u32 prefetch_mem_size; + u16 clip_hrz_range; + u16 clip_vrt_range; + u16 me_range[4]; + u8 max_cu_size; + u8 min_cu_size; + u8 max_tu_size; + u8 min_tu_size; + u8 max_transfo_depth_inter; + u8 max_transfo_depth_intra; + u16 reserved20; + u32 entropy_mode; + u32 wp_mode; + + /* rate control param */ + u32 rate_control_mode; + u32 initial_rem_delay; + u32 cpb_size; + u16 framerate; + u16 clk_ratio; + u32 target_bitrate; + u32 max_bitrate; + u16 initial_qp; + u16 min_qp; + u16 max_qp; + s16 ip_delta; + s16 pb_delta; + u16 golden_ref; + u16 golden_delta; + u16 golden_ref_frequency; + u32 rate_control_option; + + /* gop param */ + u32 gop_ctrl_mode; + u32 freq_idr; + u32 freq_lt; + u32 gdr_mode; + u16 gop_length; + u8 num_b; + u8 freq_golden_ref; + + u32 subframe_latency; + u32 lda_control_mode; + u32 unknown41; +} __attribute__ ((__packed__)); + +struct mcu_msg_create_channel { + struct mcu_msg_header header; + u32 user_id; + struct create_channel_param param; +} __attribute__ ((__packed__)); + +struct mcu_msg_create_channel_response { + struct mcu_msg_header header; + u32 channel_id; + u32 user_id; + u32 options; + u32 num_core; + u32 pps_param; + u32 int_buffers_count; + u32 int_buffers_size; + u32 rec_buffers_count; + u32 rec_buffers_size; + u32 reserved; + u32 error_code; +} __attribute__ ((__packed__)); + +struct mcu_msg_destroy_channel { + struct mcu_msg_header header; + u32 channel_id; +} __attribute__ ((__packed__)); + +struct mcu_msg_destroy_channel_response { + struct mcu_msg_header header; + u32 channel_id; +} __attribute__ ((__packed__)); + +struct mcu_msg_push_buffers_internal_buffer { + u32 dma_addr; + u32 mcu_addr; + u32 size; +} __attribute__ ((__packed__)); + +struct mcu_msg_push_buffers_internal { + struct mcu_msg_header header; + u32 channel_id; + struct mcu_msg_push_buffers_internal_buffer buffer[0]; +} __attribute__ ((__packed__)); + +struct mcu_msg_put_stream_buffer { + struct mcu_msg_header header; + u32 channel_id; + u32 dma_addr; + u32 mcu_addr; + u32 size; + u32 offset; + u64 stream_id; +} __attribute__ ((__packed__)); + +struct mcu_msg_encode_frame { + struct mcu_msg_header header; + u32 channel_id; + u32 reserved; + + u32 encoding_options; +#define AL_OPT_USE_QP_TABLE BIT(0) +#define AL_OPT_FORCE_LOAD BIT(1) +#define AL_OPT_USE_L2 BIT(2) +#define AL_OPT_DISABLE_INTRA BIT(3) +#define AL_OPT_DEPENDENT_SLICES BIT(4) + + s16 pps_qp; + u16 padding; + u64 user_param; + u64 src_handle; + + u32 request_options; +#define AL_OPT_SCENE_CHANGE BIT(0) +#define AL_OPT_RESTART_GOP BIT(1) +#define AL_OPT_USE_LONG_TERM BIT(2) +#define AL_OPT_UPDATE_PARAMS BIT(3) + + /* u32 scene_change_delay (optional) */ + /* rate control param (optional) */ + /* gop param (optional) */ + u32 src_y; + u32 src_uv; + u32 stride; + u32 ep2; + u64 ep2_v; +} __attribute__ ((__packed__)); + +struct mcu_msg_encode_frame_response { + struct mcu_msg_header header; + u32 channel_id; + u64 stream_id; /* see mcu_msg_put_stream_buffer */ + u64 user_param; /* see mcu_msg_encode_frame */ + u64 src_handle; /* see mcu_msg_encode_frame */ + u16 skip; + u16 is_ref; + u32 initial_removal_delay; + u32 dpb_output_delay; + u32 size; + u32 frame_tag_size; + s32 stuffing; + s32 filler; + u16 num_column; + u16 num_row; + u16 qp; + u8 num_ref_idx_l0; + u8 num_ref_idx_l1; + u32 partition_table_offset; + s32 partition_table_size; + u32 sum_complex; + s32 tile_width[4]; + s32 tile_height[22]; + u32 error_code; + + u32 slice_type; +#define AL_ENC_SLICE_TYPE_B 0 +#define AL_ENC_SLICE_TYPE_P 1 +#define AL_ENC_SLICE_TYPE_I 2 + + u32 pic_struct; + u8 is_idr; + u8 is_first_slice; + u8 is_last_slice; + u8 reserved; + u16 pps_qp; + u16 reserved1; + u32 reserved2; +} __attribute__ ((__packed__)); + +union mcu_msg_response { + struct mcu_msg_header header; + struct mcu_msg_init_response init; + struct mcu_msg_create_channel_response create_channel; + struct mcu_msg_destroy_channel_response destroy_channel; + struct mcu_msg_encode_frame_response encode_frame; +}; + +#endif diff --git a/drivers/staging/media/hantro/Kconfig b/drivers/staging/media/hantro/Kconfig index de77fe6554e7..99aed9a5b0b9 100644 --- a/drivers/staging/media/hantro/Kconfig +++ b/drivers/staging/media/hantro/Kconfig @@ -1,19 +1,27 @@ # SPDX-License-Identifier: GPL-2.0 config VIDEO_HANTRO tristate "Hantro VPU driver" - depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on ARCH_MXC || ARCH_ROCKCHIP || COMPILE_TEST depends on VIDEO_DEV && VIDEO_V4L2 && MEDIA_CONTROLLER depends on MEDIA_CONTROLLER_REQUEST_API select VIDEOBUF2_DMA_CONTIG select VIDEOBUF2_VMALLOC select V4L2_MEM2MEM_DEV help - Support for the Hantro IP based Video Processing Unit present on - Rockchip SoC, which accelerates video and image encoding and - decoding. + Support for the Hantro IP based Video Processing Units present on + Rockchip and NXP i.MX8M SoCs, which accelerate video and image + encoding and decoding. To compile this driver as a module, choose M here: the module will be called hantro-vpu. +config VIDEO_HANTRO_IMX8M + bool "Hantro VPU i.MX8M support" + depends on VIDEO_HANTRO + depends on ARCH_MXC || COMPILE_TEST + default y + help + Enable support for i.MX8M SoCs. + config VIDEO_HANTRO_ROCKCHIP bool "Hantro VPU Rockchip support" depends on VIDEO_HANTRO diff --git a/drivers/staging/media/hantro/Makefile b/drivers/staging/media/hantro/Makefile index 496b30c3c396..68c29a9c4946 100644 --- a/drivers/staging/media/hantro/Makefile +++ b/drivers/staging/media/hantro/Makefile @@ -16,6 +16,9 @@ hantro-vpu-y += \ hantro_mpeg2.o \ hantro_vp8.o +hantro-vpu-$(CONFIG_VIDEO_HANTRO_IMX8M) += \ + imx8m_vpu_hw.o + hantro-vpu-$(CONFIG_VIDEO_HANTRO_ROCKCHIP) += \ rk3288_vpu_hw.o \ rk3399_vpu_hw.o diff --git a/drivers/staging/media/hantro/hantro.h b/drivers/staging/media/hantro/hantro.h index b0faa43b3f79..327ddef45345 100644 --- a/drivers/staging/media/hantro/hantro.h +++ b/drivers/staging/media/hantro/hantro.h @@ -423,7 +423,7 @@ hantro_get_dst_buf(struct hantro_ctx *ctx) static inline bool hantro_needs_postproc(struct hantro_ctx *ctx, const struct hantro_fmt *fmt) { - return fmt->fourcc != V4L2_PIX_FMT_NV12; + return !hantro_is_encoder_ctx(ctx) && fmt->fourcc != V4L2_PIX_FMT_NV12; } static inline dma_addr_t diff --git a/drivers/staging/media/hantro/hantro_drv.c b/drivers/staging/media/hantro/hantro_drv.c index c98835326135..ace13973e2d0 100644 --- a/drivers/staging/media/hantro/hantro_drv.c +++ b/drivers/staging/media/hantro/hantro_drv.c @@ -362,6 +362,16 @@ static const struct hantro_ctrl controls[] = { .max = V4L2_MPEG_VIDEO_H264_START_CODE_ANNEX_B, }, }, { + .codec = HANTRO_H264_DECODER, + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + .menu_skip_mask = + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED), + .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + } + }, { }, }; @@ -489,6 +499,9 @@ static const struct of_device_id of_hantro_match[] = { { .compatible = "rockchip,rk3328-vpu", .data = &rk3328_vpu_variant, }, { .compatible = "rockchip,rk3288-vpu", .data = &rk3288_vpu_variant, }, #endif +#ifdef CONFIG_VIDEO_HANTRO_IMX8M + { .compatible = "nxp,imx8mq-vpu", .data = &imx8mq_vpu_variant, }, +#endif { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, of_hantro_match); @@ -664,7 +677,7 @@ static int hantro_add_func(struct hantro_dev *vpu, unsigned int funcid) video_set_drvdata(vfd, vpu); - ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); if (ret) { v4l2_err(&vpu->v4l2_dev, "Failed to register video device\n"); return ret; diff --git a/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c b/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c index 0d8afc3e5d71..b22418436823 100644 --- a/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c +++ b/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c @@ -67,12 +67,23 @@ hantro_h1_jpeg_enc_set_qtable(struct hantro_dev *vpu, unsigned char *chroma_qtable) { u32 reg, i; + __be32 *luma_qtable_p; + __be32 *chroma_qtable_p; + luma_qtable_p = (__be32 *)luma_qtable; + chroma_qtable_p = (__be32 *)chroma_qtable; + + /* + * Quantization table registers must be written in contiguous blocks. + * DO NOT collapse the below two "for" loops into one. + */ for (i = 0; i < H1_JPEG_QUANT_TABLE_COUNT; i++) { - reg = get_unaligned_be32(&luma_qtable[i]); + reg = get_unaligned_be32(&luma_qtable_p[i]); vepu_write_relaxed(vpu, reg, H1_REG_JPEG_LUMA_QUAT(i)); + } - reg = get_unaligned_be32(&chroma_qtable[i]); + for (i = 0; i < H1_JPEG_QUANT_TABLE_COUNT; i++) { + reg = get_unaligned_be32(&chroma_qtable_p[i]); vepu_write_relaxed(vpu, reg, H1_REG_JPEG_CHROMA_QUAT(i)); } } @@ -103,8 +114,8 @@ void hantro_h1_jpeg_enc_run(struct hantro_ctx *ctx) hantro_h1_set_src_img_ctrl(vpu, ctx); hantro_h1_jpeg_enc_set_buffers(vpu, ctx, &src_buf->vb2_buf); hantro_h1_jpeg_enc_set_qtable(vpu, - hantro_jpeg_get_qtable(&jpeg_ctx, 0), - hantro_jpeg_get_qtable(&jpeg_ctx, 1)); + hantro_jpeg_get_qtable(0), + hantro_jpeg_get_qtable(1)); reg = H1_REG_AXI_CTRL_OUTPUT_SWAP16 | H1_REG_AXI_CTRL_INPUT_SWAP16 diff --git a/drivers/staging/media/hantro/hantro_hw.h b/drivers/staging/media/hantro/hantro_hw.h index 2398d4c1f207..7dfc9bad7297 100644 --- a/drivers/staging/media/hantro/hantro_hw.h +++ b/drivers/staging/media/hantro/hantro_hw.h @@ -151,6 +151,7 @@ enum hantro_enc_fmt { extern const struct hantro_variant rk3399_vpu_variant; extern const struct hantro_variant rk3328_vpu_variant; extern const struct hantro_variant rk3288_vpu_variant; +extern const struct hantro_variant imx8mq_vpu_variant; extern const struct hantro_postproc_regs hantro_g1_postproc_regs; diff --git a/drivers/staging/media/hantro/hantro_jpeg.c b/drivers/staging/media/hantro/hantro_jpeg.c index 125eb41f2ede..36c140fc6a36 100644 --- a/drivers/staging/media/hantro/hantro_jpeg.c +++ b/drivers/staging/media/hantro/hantro_jpeg.c @@ -23,19 +23,21 @@ #define HUFF_CHROMA_AC_OFF 409 /* Default tables from JPEG ITU-T.81 - * (ISO/IEC 10918-1) Annex K.3, I + * (ISO/IEC 10918-1) Annex K, tables K.1 and K.2 */ static const unsigned char luma_q_table[] = { - 0x10, 0x0b, 0x0a, 0x10, 0x7c, 0x8c, 0x97, 0xa1, - 0x0c, 0x0c, 0x0e, 0x13, 0x7e, 0x9e, 0xa0, 0x9b, - 0x0e, 0x0d, 0x10, 0x18, 0x8c, 0x9d, 0xa9, 0x9c, - 0x0e, 0x11, 0x16, 0x1d, 0x97, 0xbb, 0xb4, 0xa2, - 0x12, 0x16, 0x25, 0x38, 0xa8, 0x6d, 0x67, 0xb1, - 0x18, 0x23, 0x37, 0x40, 0xb5, 0x68, 0x71, 0xc0, + 0x10, 0x0b, 0x0a, 0x10, 0x18, 0x28, 0x33, 0x3d, + 0x0c, 0x0c, 0x0e, 0x13, 0x1a, 0x3a, 0x3c, 0x37, + 0x0e, 0x0d, 0x10, 0x18, 0x28, 0x39, 0x45, 0x38, + 0x0e, 0x11, 0x16, 0x1d, 0x33, 0x57, 0x50, 0x3e, + 0x12, 0x16, 0x25, 0x38, 0x44, 0x6d, 0x67, 0x4d, + 0x18, 0x23, 0x37, 0x40, 0x51, 0x68, 0x71, 0x5c, 0x31, 0x40, 0x4e, 0x57, 0x67, 0x79, 0x78, 0x65, - 0x48, 0x5c, 0x5f, 0x62, 0x70, 0x64, 0x67, 0xc7, + 0x48, 0x5c, 0x5f, 0x62, 0x70, 0x64, 0x67, 0x63 }; +static unsigned char luma_q_table_reordered[ARRAY_SIZE(luma_q_table)]; + static const unsigned char chroma_q_table[] = { 0x11, 0x12, 0x18, 0x2f, 0x63, 0x63, 0x63, 0x63, 0x12, 0x15, 0x1a, 0x42, 0x63, 0x63, 0x63, 0x63, @@ -47,6 +49,30 @@ static const unsigned char chroma_q_table[] = { 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 }; +static unsigned char chroma_q_table_reordered[ARRAY_SIZE(chroma_q_table)]; + +static const unsigned char zigzag[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static const u32 hw_reorder[64] = { + 0, 8, 16, 24, 1, 9, 17, 25, + 32, 40, 48, 56, 33, 41, 49, 57, + 2, 10, 18, 26, 3, 11, 19, 27, + 34, 42, 50, 58, 35, 43, 51, 59, + 4, 12, 20, 28, 5, 13, 21, 29, + 36, 44, 52, 60, 37, 45, 53, 61, + 6, 14, 22, 30, 7, 15, 23, 31, + 38, 46, 54, 62, 39, 47, 55, 63 +}; + /* Huffman tables are shared with CODA */ static const unsigned char luma_dc_table[] = { 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, @@ -225,20 +251,29 @@ static const unsigned char hantro_jpeg_header[JPEG_HEADER_SIZE] = { 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, }; +static unsigned char jpeg_scale_qp(const unsigned char qp, int scale) +{ + unsigned int temp; + + temp = DIV_ROUND_CLOSEST((unsigned int)qp * scale, 100); + if (temp <= 0) + temp = 1; + if (temp > 255) + temp = 255; + + return (unsigned char)temp; +} + static void -jpeg_scale_quant_table(unsigned char *q_tab, +jpeg_scale_quant_table(unsigned char *file_q_tab, + unsigned char *reordered_q_tab, const unsigned char *tab, int scale) { - unsigned int temp; int i; for (i = 0; i < 64; i++) { - temp = DIV_ROUND_CLOSEST((unsigned int)tab[i] * scale, 100); - if (temp <= 0) - temp = 1; - if (temp > 255) - temp = 255; - q_tab[i] = (unsigned char)temp; + file_q_tab[i] = jpeg_scale_qp(tab[zigzag[i]], scale); + reordered_q_tab[i] = jpeg_scale_qp(tab[hw_reorder[i]], scale); } } @@ -256,17 +291,18 @@ static void jpeg_set_quality(unsigned char *buffer, int quality) scale = 200 - 2 * quality; jpeg_scale_quant_table(buffer + LUMA_QUANT_OFF, + luma_q_table_reordered, luma_q_table, scale); jpeg_scale_quant_table(buffer + CHROMA_QUANT_OFF, + chroma_q_table_reordered, chroma_q_table, scale); } -unsigned char * -hantro_jpeg_get_qtable(struct hantro_jpeg_ctx *ctx, int index) +unsigned char *hantro_jpeg_get_qtable(int index) { if (index == 0) - return ctx->buffer + LUMA_QUANT_OFF; - return ctx->buffer + CHROMA_QUANT_OFF; + return luma_q_table_reordered; + return chroma_q_table_reordered; } void hantro_jpeg_header_assemble(struct hantro_jpeg_ctx *ctx) diff --git a/drivers/staging/media/hantro/hantro_jpeg.h b/drivers/staging/media/hantro/hantro_jpeg.h index 9e8397c71388..9474a00277f8 100644 --- a/drivers/staging/media/hantro/hantro_jpeg.h +++ b/drivers/staging/media/hantro/hantro_jpeg.h @@ -9,5 +9,5 @@ struct hantro_jpeg_ctx { unsigned char *buffer; }; -unsigned char *hantro_jpeg_get_qtable(struct hantro_jpeg_ctx *ctx, int index); +unsigned char *hantro_jpeg_get_qtable(int index); void hantro_jpeg_header_assemble(struct hantro_jpeg_ctx *ctx); diff --git a/drivers/staging/media/hantro/hantro_v4l2.c b/drivers/staging/media/hantro/hantro_v4l2.c index 0198bcda26b7..f4ae2cee0f18 100644 --- a/drivers/staging/media/hantro/hantro_v4l2.c +++ b/drivers/staging/media/hantro/hantro_v4l2.c @@ -295,7 +295,7 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f, * +---------------------------+ */ if (ctx->vpu_src_fmt->fourcc == V4L2_PIX_FMT_H264_SLICE && - !hantro_needs_postproc(ctx, ctx->vpu_dst_fmt)) + !hantro_needs_postproc(ctx, fmt)) pix_mp->plane_fmt[0].sizeimage += 64 * MB_WIDTH(pix_mp->width) * MB_WIDTH(pix_mp->height) + 32; diff --git a/drivers/staging/media/hantro/imx8m_vpu_hw.c b/drivers/staging/media/hantro/imx8m_vpu_hw.c new file mode 100644 index 000000000000..cb2420c5526e --- /dev/null +++ b/drivers/staging/media/hantro/imx8m_vpu_hw.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hantro VPU codec driver + * + * Copyright (C) 2019 Pengutronix, Philipp Zabel <kernel@pengutronix.de> + */ + +#include <linux/clk.h> +#include <linux/delay.h> + +#include "hantro.h" +#include "hantro_jpeg.h" +#include "hantro_g1_regs.h" + +#define CTRL_SOFT_RESET 0x00 +#define RESET_G1 BIT(1) +#define RESET_G2 BIT(0) + +#define CTRL_CLOCK_ENABLE 0x04 +#define CLOCK_G1 BIT(1) +#define CLOCK_G2 BIT(0) + +#define CTRL_G1_DEC_FUSE 0x08 +#define CTRL_G1_PP_FUSE 0x0c +#define CTRL_G2_DEC_FUSE 0x10 + +static void imx8m_soft_reset(struct hantro_dev *vpu, u32 reset_bits) +{ + u32 val; + + /* Assert */ + val = readl(vpu->ctrl_base + CTRL_SOFT_RESET); + val &= ~reset_bits; + writel(val, vpu->ctrl_base + CTRL_SOFT_RESET); + + udelay(2); + + /* Release */ + val = readl(vpu->ctrl_base + CTRL_SOFT_RESET); + val |= reset_bits; + writel(val, vpu->ctrl_base + CTRL_SOFT_RESET); +} + +static void imx8m_clk_enable(struct hantro_dev *vpu, u32 clock_bits) +{ + u32 val; + + val = readl(vpu->ctrl_base + CTRL_CLOCK_ENABLE); + val |= clock_bits; + writel(val, vpu->ctrl_base + CTRL_CLOCK_ENABLE); +} + +static int imx8mq_runtime_resume(struct hantro_dev *vpu) +{ + int ret; + + ret = clk_bulk_prepare_enable(vpu->variant->num_clocks, vpu->clocks); + if (ret) { + dev_err(vpu->dev, "Failed to enable clocks\n"); + return ret; + } + + imx8m_soft_reset(vpu, RESET_G1 | RESET_G2); + imx8m_clk_enable(vpu, CLOCK_G1 | CLOCK_G2); + + /* Set values of the fuse registers */ + writel(0xffffffff, vpu->ctrl_base + CTRL_G1_DEC_FUSE); + writel(0xffffffff, vpu->ctrl_base + CTRL_G1_PP_FUSE); + writel(0xffffffff, vpu->ctrl_base + CTRL_G2_DEC_FUSE); + + clk_bulk_disable_unprepare(vpu->variant->num_clocks, vpu->clocks); + + return 0; +} + +/* + * Supported formats. + */ + +static const struct hantro_fmt imx8m_vpu_postproc_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .codec_mode = HANTRO_MODE_NONE, + }, +}; + +static const struct hantro_fmt imx8m_vpu_dec_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_NV12, + .codec_mode = HANTRO_MODE_NONE, + }, + { + .fourcc = V4L2_PIX_FMT_MPEG2_SLICE, + .codec_mode = HANTRO_MODE_MPEG2_DEC, + .max_depth = 2, + .frmsize = { + .min_width = 48, + .max_width = 1920, + .step_width = MB_DIM, + .min_height = 48, + .max_height = 1088, + .step_height = MB_DIM, + }, + }, + { + .fourcc = V4L2_PIX_FMT_VP8_FRAME, + .codec_mode = HANTRO_MODE_VP8_DEC, + .max_depth = 2, + .frmsize = { + .min_width = 48, + .max_width = 3840, + .step_width = 16, + .min_height = 48, + .max_height = 2160, + .step_height = 16, + }, + }, + { + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .codec_mode = HANTRO_MODE_H264_DEC, + .max_depth = 2, + .frmsize = { + .min_width = 48, + .max_width = 3840, + .step_width = MB_DIM, + .min_height = 48, + .max_height = 2160, + .step_height = MB_DIM, + }, + }, +}; + +static irqreturn_t imx8m_vpu_g1_irq(int irq, void *dev_id) +{ + struct hantro_dev *vpu = dev_id; + enum vb2_buffer_state state; + u32 status; + + status = vdpu_read(vpu, G1_REG_INTERRUPT); + state = (status & G1_REG_INTERRUPT_DEC_RDY_INT) ? + VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; + + vdpu_write(vpu, 0, G1_REG_INTERRUPT); + vdpu_write(vpu, G1_REG_CONFIG_DEC_CLK_GATE_E, G1_REG_CONFIG); + + hantro_irq_done(vpu, 0, state); + + return IRQ_HANDLED; +} + +static int imx8mq_vpu_hw_init(struct hantro_dev *vpu) +{ + vpu->dec_base = vpu->reg_bases[0]; + vpu->ctrl_base = vpu->reg_bases[vpu->variant->num_regs - 1]; + + return 0; +} + +static void imx8m_vpu_g1_reset(struct hantro_ctx *ctx) +{ + struct hantro_dev *vpu = ctx->dev; + + imx8m_soft_reset(vpu, RESET_G1); +} + +/* + * Supported codec ops. + */ + +static const struct hantro_codec_ops imx8mq_vpu_codec_ops[] = { + [HANTRO_MODE_MPEG2_DEC] = { + .run = hantro_g1_mpeg2_dec_run, + .reset = imx8m_vpu_g1_reset, + .init = hantro_mpeg2_dec_init, + .exit = hantro_mpeg2_dec_exit, + }, + [HANTRO_MODE_VP8_DEC] = { + .run = hantro_g1_vp8_dec_run, + .reset = imx8m_vpu_g1_reset, + .init = hantro_vp8_dec_init, + .exit = hantro_vp8_dec_exit, + }, + [HANTRO_MODE_H264_DEC] = { + .run = hantro_g1_h264_dec_run, + .reset = imx8m_vpu_g1_reset, + .init = hantro_h264_dec_init, + .exit = hantro_h264_dec_exit, + }, +}; + +/* + * VPU variants. + */ + +static const struct hantro_irq imx8mq_irqs[] = { + { "g1", imx8m_vpu_g1_irq }, + { "g2", NULL /* TODO: imx8m_vpu_g2_irq */ }, +}; + +static const char * const imx8mq_clk_names[] = { "g1", "g2", "bus" }; +static const char * const imx8mq_reg_names[] = { "g1", "g2", "ctrl" }; + +const struct hantro_variant imx8mq_vpu_variant = { + .dec_fmts = imx8m_vpu_dec_fmts, + .num_dec_fmts = ARRAY_SIZE(imx8m_vpu_dec_fmts), + .postproc_fmts = imx8m_vpu_postproc_fmts, + .num_postproc_fmts = ARRAY_SIZE(imx8m_vpu_postproc_fmts), + .postproc_regs = &hantro_g1_postproc_regs, + .codec = HANTRO_MPEG2_DECODER | HANTRO_VP8_DECODER | + HANTRO_H264_DECODER, + .codec_ops = imx8mq_vpu_codec_ops, + .init = imx8mq_vpu_hw_init, + .runtime_resume = imx8mq_runtime_resume, + .irqs = imx8mq_irqs, + .num_irqs = ARRAY_SIZE(imx8mq_irqs), + .clk_names = imx8mq_clk_names, + .num_clocks = ARRAY_SIZE(imx8mq_clk_names), + .reg_names = imx8mq_reg_names, + .num_regs = ARRAY_SIZE(imx8mq_reg_names) +}; diff --git a/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c b/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c index 4c2d43fb6fd1..3498e6124acd 100644 --- a/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c +++ b/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c @@ -18,9 +18,8 @@ * * Quantization luma table values are written to registers * VEPU_swreg_0-VEPU_swreg_15, and chroma table values to - * VEPU_swreg_16-VEPU_swreg_31. - * - * JPEG zigzag order is expected on the quantization tables. + * VEPU_swreg_16-VEPU_swreg_31. A special order is needed, neither + * zigzag, nor linear. */ #include <asm/unaligned.h> @@ -98,12 +97,23 @@ rk3399_vpu_jpeg_enc_set_qtable(struct hantro_dev *vpu, unsigned char *chroma_qtable) { u32 reg, i; + __be32 *luma_qtable_p; + __be32 *chroma_qtable_p; + + luma_qtable_p = (__be32 *)luma_qtable; + chroma_qtable_p = (__be32 *)chroma_qtable; + /* + * Quantization table registers must be written in contiguous blocks. + * DO NOT collapse the below two "for" loops into one. + */ for (i = 0; i < VEPU_JPEG_QUANT_TABLE_COUNT; i++) { - reg = get_unaligned_be32(&luma_qtable[i]); + reg = get_unaligned_be32(&luma_qtable_p[i]); vepu_write_relaxed(vpu, reg, VEPU_REG_JPEG_LUMA_QUAT(i)); + } - reg = get_unaligned_be32(&chroma_qtable[i]); + for (i = 0; i < VEPU_JPEG_QUANT_TABLE_COUNT; i++) { + reg = get_unaligned_be32(&chroma_qtable_p[i]); vepu_write_relaxed(vpu, reg, VEPU_REG_JPEG_CHROMA_QUAT(i)); } } @@ -134,8 +144,8 @@ void rk3399_vpu_jpeg_enc_run(struct hantro_ctx *ctx) rk3399_vpu_set_src_img_ctrl(vpu, ctx); rk3399_vpu_jpeg_enc_set_buffers(vpu, ctx, &src_buf->vb2_buf); rk3399_vpu_jpeg_enc_set_qtable(vpu, - hantro_jpeg_get_qtable(&jpeg_ctx, 0), - hantro_jpeg_get_qtable(&jpeg_ctx, 1)); + hantro_jpeg_get_qtable(0), + hantro_jpeg_get_qtable(1)); reg = VEPU_REG_OUTPUT_SWAP32 | VEPU_REG_OUTPUT_SWAP16 diff --git a/drivers/staging/media/imx/imx-media-capture.c b/drivers/staging/media/imx/imx-media-capture.c index 7712e7be8625..d37b776ff86d 100644 --- a/drivers/staging/media/imx/imx-media-capture.c +++ b/drivers/staging/media/imx/imx-media-capture.c @@ -643,7 +643,7 @@ static int capture_open(struct file *file) if (ret) v4l2_err(priv->src_sd, "v4l2_fh_open failed\n"); - ret = v4l2_pipeline_pm_use(&vfd->entity, 1); + ret = v4l2_pipeline_pm_get(&vfd->entity); if (ret) v4l2_fh_release(file); @@ -664,7 +664,7 @@ static int capture_release(struct file *file) vq->owner = NULL; } - v4l2_pipeline_pm_use(&vfd->entity, 0); + v4l2_pipeline_pm_put(&vfd->entity); v4l2_fh_release(file); mutex_unlock(&priv->mutex); @@ -742,7 +742,7 @@ int imx_media_capture_device_register(struct imx_media_video_dev *vdev) vfd->v4l2_dev = v4l2_dev; - ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); if (ret) { v4l2_err(sd, "Failed to register video device\n"); return ret; @@ -778,7 +778,7 @@ int imx_media_capture_device_register(struct imx_media_video_dev *vdev) /* setup default format */ fmt_src.pad = priv->src_sd_pad; fmt_src.which = V4L2_SUBDEV_FORMAT_ACTIVE; - v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt_src); + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt_src); if (ret) { v4l2_err(sd, "failed to get src_sd format\n"); goto unreg; diff --git a/drivers/staging/media/imx/imx-media-csc-scaler.c b/drivers/staging/media/imx/imx-media-csc-scaler.c index 2b635ebf62d6..2cc77f6e84b6 100644 --- a/drivers/staging/media/imx/imx-media-csc-scaler.c +++ b/drivers/staging/media/imx/imx-media-csc-scaler.c @@ -849,7 +849,7 @@ int imx_media_csc_scaler_device_register(struct imx_media_video_dev *vdev) vfd->v4l2_dev = &priv->md->v4l2_dev; - ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); if (ret) { v4l2_err(vfd->v4l2_dev, "Failed to register video device\n"); return ret; diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c index b60ed4f22f6d..e76a6a85baa3 100644 --- a/drivers/staging/media/imx/imx-media-csi.c +++ b/drivers/staging/media/imx/imx-media-csi.c @@ -457,7 +457,8 @@ static int csi_idmac_setup_channel(struct csi_priv *priv) case V4L2_PIX_FMT_SGBRG16: case V4L2_PIX_FMT_SGRBG16: case V4L2_PIX_FMT_SRGGB16: - case V4L2_PIX_FMT_Y16: + case V4L2_PIX_FMT_Y10: + case V4L2_PIX_FMT_Y12: burst_size = 8; passthrough_bits = 16; break; @@ -1459,6 +1460,8 @@ static void csi_try_fmt(struct csi_priv *priv, /* propagate colorimetry from sink */ sdformat->format.colorspace = infmt->colorspace; sdformat->format.xfer_func = infmt->xfer_func; + sdformat->format.quantization = infmt->quantization; + sdformat->format.ycbcr_enc = infmt->ycbcr_enc; break; case CSI_SINK_PAD: diff --git a/drivers/staging/media/imx/imx-media-utils.c b/drivers/staging/media/imx/imx-media-utils.c index 0788a1874557..fae981698c49 100644 --- a/drivers/staging/media/imx/imx-media-utils.c +++ b/drivers/staging/media/imx/imx-media-utils.c @@ -161,17 +161,24 @@ static const struct imx_media_pixfmt rgb_formats[] = { .bayer = true, }, { .fourcc = V4L2_PIX_FMT_GREY, - .codes = {MEDIA_BUS_FMT_Y8_1X8}, - .cs = IPUV3_COLORSPACE_RGB, - .bpp = 8, - .bayer = true, - }, { - .fourcc = V4L2_PIX_FMT_Y16, .codes = { + MEDIA_BUS_FMT_Y8_1X8, MEDIA_BUS_FMT_Y10_1X10, MEDIA_BUS_FMT_Y12_1X12, }, .cs = IPUV3_COLORSPACE_RGB, + .bpp = 8, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + .codes = {MEDIA_BUS_FMT_Y10_1X10}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 16, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_Y12, + .codes = {MEDIA_BUS_FMT_Y12_1X12}, + .cs = IPUV3_COLORSPACE_RGB, .bpp = 16, .bayer = true, }, diff --git a/drivers/staging/media/imx/imx7-media-csi.c b/drivers/staging/media/imx/imx7-media-csi.c index db30e2c70f2f..acbdffb77668 100644 --- a/drivers/staging/media/imx/imx7-media-csi.c +++ b/drivers/staging/media/imx/imx7-media-csi.c @@ -292,7 +292,7 @@ static void imx7_csi_hw_disable(struct imx7_csi *csi) static void imx7_csi_dma_reflash(struct imx7_csi *csi) { - u32 cr3 = imx7_csi_reg_read(csi, CSI_CSICR18); + u32 cr3; cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); cr3 |= BIT_DMA_REFLASH_RFF; @@ -804,6 +804,22 @@ static int imx7_csi_configure(struct imx7_csi *csi) case V4L2_PIX_FMT_YUYV: cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B; break; + case V4L2_PIX_FMT_GREY: + if (in_code == MEDIA_BUS_FMT_Y8_1X8) + cr18 |= BIT_MIPI_DATA_FORMAT_RAW8; + else if (in_code == MEDIA_BUS_FMT_Y10_1X10) + cr18 |= BIT_MIPI_DATA_FORMAT_RAW10; + else + cr18 |= BIT_MIPI_DATA_FORMAT_RAW12; + break; + case V4L2_PIX_FMT_Y10: + cr18 |= BIT_MIPI_DATA_FORMAT_RAW10; + cr1 |= BIT_PIXEL_BIT; + break; + case V4L2_PIX_FMT_Y12: + cr18 |= BIT_MIPI_DATA_FORMAT_RAW12; + cr1 |= BIT_PIXEL_BIT; + break; case V4L2_PIX_FMT_SBGGR8: cr18 |= BIT_MIPI_DATA_FORMAT_RAW8; break; @@ -1009,10 +1025,13 @@ static int imx7_csi_try_fmt(struct imx7_csi *csi, sdformat->format.width = in_fmt->width; sdformat->format.height = in_fmt->height; sdformat->format.code = in_fmt->code; + sdformat->format.field = in_fmt->field; *cc = in_cc; sdformat->format.colorspace = in_fmt->colorspace; sdformat->format.xfer_func = in_fmt->xfer_func; + sdformat->format.quantization = in_fmt->quantization; + sdformat->format.ycbcr_enc = in_fmt->ycbcr_enc; break; case IMX7_CSI_PAD_SINK: *cc = imx_media_find_mbus_format(sdformat->format.code, @@ -1023,6 +1042,9 @@ static int imx7_csi_try_fmt(struct imx7_csi *csi, false); sdformat->format.code = (*cc)->codes[0]; } + + if (sdformat->format.field != V4L2_FIELD_INTERLACED) + sdformat->format.field = V4L2_FIELD_NONE; break; default: return -EINVAL; diff --git a/drivers/staging/media/imx/imx7-mipi-csis.c b/drivers/staging/media/imx/imx7-mipi-csis.c index 383abecb3bec..de17a1d22873 100644 --- a/drivers/staging/media/imx/imx7-mipi-csis.c +++ b/drivers/staging/media/imx/imx7-mipi-csis.c @@ -280,6 +280,18 @@ static const struct csis_pix_format mipi_csis_formats[] = { .code = MEDIA_BUS_FMT_YUYV8_2X8, .fmt_reg = MIPI_CSIS_ISPCFG_FMT_YCBCR422_8BIT, .data_alignment = 16, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW8, + .data_alignment = 8, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW10, + .data_alignment = 16, + }, { + .code = MEDIA_BUS_FMT_Y12_1X12, + .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW12, + .data_alignment = 16, } }; @@ -301,6 +313,7 @@ static int mipi_csis_dump_regs(struct csi_state *state) { 0x20, "DPHYSTS" }, { 0x10, "INTMSK" }, { 0x40, "CONFIG_CH0" }, + { 0x44, "RESOL_CH0" }, { 0xC0, "DBG_CONFIG" }, { 0x38, "DPHYSLAVE_L" }, { 0x3C, "DPHYSLAVE_H" }, @@ -417,6 +430,7 @@ static void mipi_csis_set_params(struct csi_state *state) val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL); val &= ~MIPI_CSIS_CMN_CTRL_LANE_NR_MASK; val |= (lanes - 1) << MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET; + val |= MIPI_CSIS_CMN_CTRL_INTER_MODE; mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val); __mipi_csis_set_format(state); @@ -577,7 +591,7 @@ static int mipi_csis_s_stream(struct v4l2_subdev *mipi_sd, int enable) state->flags |= ST_STREAMING; } else { v4l2_subdev_call(state->src_sd, video, s_stream, 0); - ret = v4l2_subdev_call(state->src_sd, core, s_power, 1); + ret = v4l2_subdev_call(state->src_sd, core, s_power, 0); mipi_csis_stop_stream(state); state->flags &= ~ST_STREAMING; if (state->debug) diff --git a/drivers/staging/media/ipu3/TODO b/drivers/staging/media/ipu3/TODO index dc356d6f03c4..52063b651810 100644 --- a/drivers/staging/media/ipu3/TODO +++ b/drivers/staging/media/ipu3/TODO @@ -13,8 +13,6 @@ staging directory. - Elaborate the functionality of different selection rectangles in driver documentation. This may require driver changes as well. -- More detailed documentation on calculating BDS, GCD etc. sizes needed. - - Document different operation modes, and which buffer queues are relevant in each mode. To process an image, which queues require a buffer an in which ones is it optional? diff --git a/drivers/staging/media/ipu3/ipu3-css.c b/drivers/staging/media/ipu3/ipu3-css.c index f36de501edc6..4f04fe838b0c 100644 --- a/drivers/staging/media/ipu3/ipu3-css.c +++ b/drivers/staging/media/ipu3/ipu3-css.c @@ -210,12 +210,12 @@ static int imgu_hw_wait(void __iomem *base, int reg, u32 mask, u32 cmp) /* Initialize the IPU3 CSS hardware and associated h/w blocks */ -int imgu_css_set_powerup(struct device *dev, void __iomem *base) +int imgu_css_set_powerup(struct device *dev, void __iomem *base, + unsigned int freq) { - static const unsigned int freq = 450; u32 pm_ctrl, state, val; - dev_dbg(dev, "%s\n", __func__); + dev_dbg(dev, "%s with freq %u\n", __func__, freq); /* Clear the CSS busy signal */ readl(base + IMGU_REG_GP_BUSY); writel(0, base + IMGU_REG_GP_BUSY); diff --git a/drivers/staging/media/ipu3/ipu3-css.h b/drivers/staging/media/ipu3/ipu3-css.h index 6b8bab27ab1f..6108a068b228 100644 --- a/drivers/staging/media/ipu3/ipu3-css.h +++ b/drivers/staging/media/ipu3/ipu3-css.h @@ -187,7 +187,8 @@ bool imgu_css_is_streaming(struct imgu_css *css); bool imgu_css_pipe_queue_empty(struct imgu_css *css, unsigned int pipe); /******************* css hw *******************/ -int imgu_css_set_powerup(struct device *dev, void __iomem *base); +int imgu_css_set_powerup(struct device *dev, void __iomem *base, + unsigned int freq); void imgu_css_set_powerdown(struct device *dev, void __iomem *base); int imgu_css_irq_ack(struct imgu_css *css); diff --git a/drivers/staging/media/ipu3/ipu3-mmu.c b/drivers/staging/media/ipu3/ipu3-mmu.c index 3d969b0522ab..5f3ff964f3e7 100644 --- a/drivers/staging/media/ipu3/ipu3-mmu.c +++ b/drivers/staging/media/ipu3/ipu3-mmu.c @@ -130,7 +130,7 @@ static u32 *imgu_mmu_alloc_page_table(u32 pteval) for (pte = 0; pte < IPU3_PT_PTES; pte++) pt[pte] = pteval; - set_memory_uc((unsigned long int)pt, IPU3_PT_ORDER); + set_memory_uc((unsigned long)pt, IPU3_PT_ORDER); return pt; } @@ -141,7 +141,7 @@ static u32 *imgu_mmu_alloc_page_table(u32 pteval) */ static void imgu_mmu_free_page_table(u32 *pt) { - set_memory_wb((unsigned long int)pt, IPU3_PT_ORDER); + set_memory_wb((unsigned long)pt, IPU3_PT_ORDER); free_page((unsigned long)pt); } diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index 569e27b824c8..09c8ede1457c 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -1245,7 +1245,7 @@ static int imgu_v4l2_node_setup(struct imgu_device *imgu, unsigned int pipe, vdev->queue = &node->vbq; vdev->vfl_dir = node->output ? VFL_DIR_TX : VFL_DIR_RX; video_set_drvdata(vdev, imgu); - r = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + r = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (r) { dev_err(dev, "failed to register video device (%d)", r); media_entity_cleanup(&vdev->entity); diff --git a/drivers/staging/media/ipu3/ipu3.c b/drivers/staging/media/ipu3/ipu3.c index 06a61f31ca50..4d53aad31483 100644 --- a/drivers/staging/media/ipu3/ipu3.c +++ b/drivers/staging/media/ipu3/ipu3.c @@ -345,8 +345,20 @@ failed: static int imgu_powerup(struct imgu_device *imgu) { int r; + unsigned int pipe; + unsigned int freq = 200; + struct v4l2_mbus_framefmt *fmt; + + /* input larger than 2048*1152, ask imgu to work on high freq */ + for_each_set_bit(pipe, imgu->css.enabled_pipes, IMGU_MAX_PIPE_NUM) { + fmt = &imgu->imgu_pipe[pipe].nodes[IMGU_NODE_IN].pad_fmt; + dev_dbg(&imgu->pci_dev->dev, "pipe %u input format = %ux%u", + pipe, fmt->width, fmt->height); + if ((fmt->width * fmt->height) >= (2048 * 1152)) + freq = 450; + } - r = imgu_css_set_powerup(&imgu->pci_dev->dev, imgu->base); + r = imgu_css_set_powerup(&imgu->pci_dev->dev, imgu->base, freq); if (r) return r; @@ -666,7 +678,7 @@ static int imgu_pci_probe(struct pci_dev *pci_dev, atomic_set(&imgu->qbuf_barrier, 0); init_waitqueue_head(&imgu->buf_drain_wq); - r = imgu_css_set_powerup(&pci_dev->dev, imgu->base); + r = imgu_css_set_powerup(&pci_dev->dev, imgu->base, 200); if (r) { dev_err(&pci_dev->dev, "failed to power up CSS (%d)\n", r); diff --git a/drivers/staging/media/meson/vdec/Makefile b/drivers/staging/media/meson/vdec/Makefile index 6bea129084b7..6e726af84ac9 100644 --- a/drivers/staging/media/meson/vdec/Makefile +++ b/drivers/staging/media/meson/vdec/Makefile @@ -2,7 +2,7 @@ # Makefile for Amlogic meson video decoder driver meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o -meson-vdec-objs += vdec_1.o -meson-vdec-objs += codec_mpeg12.o +meson-vdec-objs += vdec_1.o vdec_hevc.o +meson-vdec-objs += codec_mpeg12.o codec_h264.o codec_hevc_common.o codec_vp9.o obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o diff --git a/drivers/staging/media/meson/vdec/codec_h264.c b/drivers/staging/media/meson/vdec/codec_h264.c new file mode 100644 index 000000000000..c61128fc4bb9 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_h264.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "vdec_helpers.h" +#include "dos_regs.h" +#include "codec_h264.h" + +#define SIZE_EXT_FW (20 * SZ_1K) +#define SIZE_WORKSPACE 0x1ee000 +#define SIZE_SEI (8 * SZ_1K) + +/* + * Offset added by the firmware which must be substracted + * from the workspace phyaddr + */ +#define WORKSPACE_BUF_OFFSET 0x1000000 + +/* ISR status */ +#define CMD_MASK GENMASK(7, 0) +#define CMD_SRC_CHANGE 1 +#define CMD_FRAMES_READY 2 +#define CMD_FATAL_ERROR 6 +#define CMD_BAD_WIDTH 7 +#define CMD_BAD_HEIGHT 8 + +#define SEI_DATA_READY BIT(15) + +/* Picture type */ +#define PIC_TOP_BOT 5 +#define PIC_BOT_TOP 6 + +/* Size of Motion Vector per macroblock */ +#define MB_MV_SIZE 96 + +/* Frame status data */ +#define PIC_STRUCT_BIT 5 +#define PIC_STRUCT_MASK GENMASK(2, 0) +#define BUF_IDX_MASK GENMASK(4, 0) +#define ERROR_FLAG BIT(9) +#define OFFSET_BIT 16 +#define OFFSET_MASK GENMASK(15, 0) + +/* Bitstream parsed data */ +#define MB_TOTAL_BIT 8 +#define MB_TOTAL_MASK GENMASK(15, 0) +#define MB_WIDTH_MASK GENMASK(7, 0) +#define MAX_REF_BIT 24 +#define MAX_REF_MASK GENMASK(6, 0) +#define AR_IDC_BIT 16 +#define AR_IDC_MASK GENMASK(7, 0) +#define AR_PRESENT_FLAG BIT(0) +#define AR_EXTEND 0xff + +/* + * Buffer to send to the ESPARSER to signal End Of Stream for H.264. + * This is a 16x16 encoded picture that will trigger drain firmware-side. + * There is no known alternative. + */ +static const u8 eos_sequence[SZ_4K] = { + 0x00, 0x00, 0x00, 0x01, 0x06, 0x05, 0xff, 0xe4, 0xdc, 0x45, 0xe9, 0xbd, + 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, + 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20, + 0x36, 0x37, 0x20, 0x72, 0x31, 0x31, 0x33, 0x30, 0x20, 0x38, 0x34, 0x37, + 0x35, 0x39, 0x37, 0x37, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34, + 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, + 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, + 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30, + 0x30, 0x39, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, + 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e, + 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, + 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65, + 0x66, 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, + 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, + 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20, + 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, + 0x3d, 0x36, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, + 0x30, 0x3a, 0x30, 0x2e, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, + 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, + 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, + 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, + 0x73, 0x3d, 0x30, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30, + 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, + 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x63, 0x68, + 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, + 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x6d, 0x62, 0x61, 0x66, + 0x66, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, + 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, + 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, + 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, + 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x61, 0x74, + 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, + 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, + 0x6e, 0x3d, 0x31, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x35, + 0x31, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69, + 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, + 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0a, 0x9a, 0x74, 0xf4, 0x20, + 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x51, 0xe2, 0x44, 0xd4, + 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x32, 0xc8, 0x00, 0x00, 0x00, 0x01, + 0x65, 0x88, 0x80, 0x20, 0x00, 0x08, 0x7f, 0xea, 0x6a, 0xe2, 0x99, 0xb6, + 0x57, 0xae, 0x49, 0x30, 0xf5, 0xfe, 0x5e, 0x46, 0x0b, 0x72, 0x44, 0xc4, + 0xe1, 0xfc, 0x62, 0xda, 0xf1, 0xfb, 0xa2, 0xdb, 0xd6, 0xbe, 0x5c, 0xd7, + 0x24, 0xa3, 0xf5, 0xb9, 0x2f, 0x57, 0x16, 0x49, 0x75, 0x47, 0x77, 0x09, + 0x5c, 0xa1, 0xb4, 0xc3, 0x4f, 0x60, 0x2b, 0xb0, 0x0c, 0xc8, 0xd6, 0x66, + 0xba, 0x9b, 0x82, 0x29, 0x33, 0x92, 0x26, 0x99, 0x31, 0x1c, 0x7f, 0x9b, + 0x00, 0x00, 0x01, 0x0ff, +}; + +static const u8 *codec_h264_eos_sequence(u32 *len) +{ + *len = ARRAY_SIZE(eos_sequence); + return eos_sequence; +} + +struct codec_h264 { + /* H.264 decoder requires an extended firmware */ + void *ext_fw_vaddr; + dma_addr_t ext_fw_paddr; + + /* Buffer for the H.264 Workspace */ + void *workspace_vaddr; + dma_addr_t workspace_paddr; + + /* Buffer for the H.264 references MV */ + void *ref_vaddr; + dma_addr_t ref_paddr; + u32 ref_size; + + /* Buffer for parsed SEI data */ + void *sei_vaddr; + dma_addr_t sei_paddr; + + u32 mb_width; + u32 mb_height; + u32 max_refs; +}; + +static int codec_h264_can_recycle(struct amvdec_core *core) +{ + return !amvdec_read_dos(core, AV_SCRATCH_7) || + !amvdec_read_dos(core, AV_SCRATCH_8); +} + +static void codec_h264_recycle(struct amvdec_core *core, u32 buf_idx) +{ + /* + * Tell the firmware it can recycle this buffer. + * AV_SCRATCH_8 serves the same purpose. + */ + if (!amvdec_read_dos(core, AV_SCRATCH_7)) + amvdec_write_dos(core, AV_SCRATCH_7, buf_idx + 1); + else + amvdec_write_dos(core, AV_SCRATCH_8, buf_idx + 1); +} + +static int codec_h264_start(struct amvdec_session *sess) +{ + u32 workspace_offset; + struct amvdec_core *core = sess->core; + struct codec_h264 *h264 = sess->priv; + + /* Allocate some memory for the H.264 decoder's state */ + h264->workspace_vaddr = + dma_alloc_coherent(core->dev, SIZE_WORKSPACE, + &h264->workspace_paddr, GFP_KERNEL); + if (!h264->workspace_vaddr) + return -ENOMEM; + + /* Allocate some memory for the H.264 SEI dump */ + h264->sei_vaddr = dma_alloc_coherent(core->dev, SIZE_SEI, + &h264->sei_paddr, GFP_KERNEL); + if (!h264->sei_vaddr) + return -ENOMEM; + + amvdec_write_dos_bits(core, POWER_CTL_VLD, BIT(9) | BIT(6)); + + workspace_offset = h264->workspace_paddr - WORKSPACE_BUF_OFFSET; + amvdec_write_dos(core, AV_SCRATCH_1, workspace_offset); + amvdec_write_dos(core, AV_SCRATCH_G, h264->ext_fw_paddr); + amvdec_write_dos(core, AV_SCRATCH_I, h264->sei_paddr - + workspace_offset); + + /* Enable "error correction" */ + amvdec_write_dos(core, AV_SCRATCH_F, + (amvdec_read_dos(core, AV_SCRATCH_F) & 0xffffffc3) | + BIT(4) | BIT(7)); + + amvdec_write_dos(core, MDEC_PIC_DC_THRESH, 0x404038aa); + + return 0; +} + +static int codec_h264_stop(struct amvdec_session *sess) +{ + struct codec_h264 *h264 = sess->priv; + struct amvdec_core *core = sess->core; + + if (h264->ext_fw_vaddr) + dma_free_coherent(core->dev, SIZE_EXT_FW, + h264->ext_fw_vaddr, h264->ext_fw_paddr); + + if (h264->workspace_vaddr) + dma_free_coherent(core->dev, SIZE_WORKSPACE, + h264->workspace_vaddr, h264->workspace_paddr); + + if (h264->ref_vaddr) + dma_free_coherent(core->dev, h264->ref_size, + h264->ref_vaddr, h264->ref_paddr); + + if (h264->sei_vaddr) + dma_free_coherent(core->dev, SIZE_SEI, + h264->sei_vaddr, h264->sei_paddr); + + return 0; +} + +static int codec_h264_load_extended_firmware(struct amvdec_session *sess, + const u8 *data, u32 len) +{ + struct codec_h264 *h264; + struct amvdec_core *core = sess->core; + + if (len < SIZE_EXT_FW) + return -EINVAL; + + h264 = kzalloc(sizeof(*h264), GFP_KERNEL); + if (!h264) + return -ENOMEM; + + h264->ext_fw_vaddr = dma_alloc_coherent(core->dev, SIZE_EXT_FW, + &h264->ext_fw_paddr, + GFP_KERNEL); + if (!h264->ext_fw_vaddr) { + kfree(h264); + return -ENOMEM; + } + + memcpy(h264->ext_fw_vaddr, data, SIZE_EXT_FW); + sess->priv = h264; + + return 0; +} + +static const struct v4l2_fract par_table[] = { + { 1, 1 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, + { 16, 11 }, { 40, 33 }, { 24, 11 }, { 20, 11 }, + { 32, 11 }, { 80, 33 }, { 18, 11 }, { 15, 11 }, + { 64, 33 }, { 160, 99 }, { 4, 3 }, { 3, 2 }, + { 2, 1 } +}; + +static void codec_h264_set_par(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 seq_info = amvdec_read_dos(core, AV_SCRATCH_2); + u32 ar_idc = (seq_info >> AR_IDC_BIT) & AR_IDC_MASK; + + if (!(seq_info & AR_PRESENT_FLAG)) + return; + + if (ar_idc == AR_EXTEND) { + u32 ar_info = amvdec_read_dos(core, AV_SCRATCH_3); + + sess->pixelaspect.numerator = ar_info & 0xffff; + sess->pixelaspect.denominator = (ar_info >> 16) & 0xffff; + return; + } + + if (ar_idc >= ARRAY_SIZE(par_table)) + return; + + sess->pixelaspect = par_table[ar_idc]; +} + +static void codec_h264_resume(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_h264 *h264 = sess->priv; + u32 mb_width, mb_height, mb_total; + + amvdec_set_canvases(sess, + (u32[]){ ANC0_CANVAS_ADDR, 0 }, + (u32[]){ 24, 0 }); + + dev_dbg(core->dev, "max_refs = %u; actual_dpb_size = %u\n", + h264->max_refs, sess->num_dst_bufs); + + /* Align to a multiple of 4 macroblocks */ + mb_width = ALIGN(h264->mb_width, 4); + mb_height = ALIGN(h264->mb_height, 4); + mb_total = mb_width * mb_height; + + h264->ref_size = mb_total * MB_MV_SIZE * h264->max_refs; + h264->ref_vaddr = dma_alloc_coherent(core->dev, h264->ref_size, + &h264->ref_paddr, GFP_KERNEL); + if (!h264->ref_vaddr) { + amvdec_abort(sess); + return; + } + + /* Address to store the references' MVs */ + amvdec_write_dos(core, AV_SCRATCH_1, h264->ref_paddr); + /* End of ref MV */ + amvdec_write_dos(core, AV_SCRATCH_4, h264->ref_paddr + h264->ref_size); + + amvdec_write_dos(core, AV_SCRATCH_0, (h264->max_refs << 24) | + (sess->num_dst_bufs << 16) | + ((h264->max_refs - 1) << 8)); +} + +/* + * Configure the H.264 decoder when the parser detected a parameter set change + */ +static void codec_h264_src_change(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_h264 *h264 = sess->priv; + u32 parsed_info, mb_total; + u32 crop_infor, crop_bottom, crop_right; + u32 frame_width, frame_height; + + sess->keyframe_found = 1; + + parsed_info = amvdec_read_dos(core, AV_SCRATCH_1); + + /* Total number of 16x16 macroblocks */ + mb_total = (parsed_info >> MB_TOTAL_BIT) & MB_TOTAL_MASK; + /* Number of macroblocks per line */ + h264->mb_width = parsed_info & MB_WIDTH_MASK; + /* Number of macroblock lines */ + h264->mb_height = mb_total / h264->mb_width; + + h264->max_refs = ((parsed_info >> MAX_REF_BIT) & MAX_REF_MASK) + 1; + + crop_infor = amvdec_read_dos(core, AV_SCRATCH_6); + crop_bottom = (crop_infor & 0xff); + crop_right = (crop_infor >> 16) & 0xff; + + frame_width = h264->mb_width * 16 - crop_right; + frame_height = h264->mb_height * 16 - crop_bottom; + + dev_dbg(core->dev, "frame: %ux%u; crop: %u %u\n", + frame_width, frame_height, crop_right, crop_bottom); + + codec_h264_set_par(sess); + amvdec_src_change(sess, frame_width, frame_height, h264->max_refs + 5); +} + +/* + * The bitstream offset is split in half in 2 different registers. + * Fetch its MSB here, which location depends on the frame number. + */ +static u32 get_offset_msb(struct amvdec_core *core, int frame_num) +{ + int take_msb = frame_num % 2; + int reg_offset = (frame_num / 2) * 4; + u32 offset_msb = amvdec_read_dos(core, AV_SCRATCH_A + reg_offset); + + if (take_msb) + return offset_msb & 0xffff0000; + + return (offset_msb & 0x0000ffff) << 16; +} + +static void codec_h264_frames_ready(struct amvdec_session *sess, u32 status) +{ + struct amvdec_core *core = sess->core; + int error_count; + int num_frames; + int i; + + error_count = amvdec_read_dos(core, AV_SCRATCH_D); + num_frames = (status >> 8) & 0xff; + if (error_count) { + dev_warn(core->dev, + "decoder error(s) happened, count %d\n", error_count); + amvdec_write_dos(core, AV_SCRATCH_D, 0); + } + + for (i = 0; i < num_frames; i++) { + u32 frame_status = amvdec_read_dos(core, AV_SCRATCH_1 + i * 4); + u32 buffer_index = frame_status & BUF_IDX_MASK; + u32 pic_struct = (frame_status >> PIC_STRUCT_BIT) & + PIC_STRUCT_MASK; + u32 offset = (frame_status >> OFFSET_BIT) & OFFSET_MASK; + u32 field = V4L2_FIELD_NONE; + + /* + * A buffer decode error means it was decoded, + * but part of the picture will have artifacts. + * Typical reason is a temporarily corrupted bitstream + */ + if (frame_status & ERROR_FLAG) + dev_dbg(core->dev, "Buffer %d decode error\n", + buffer_index); + + if (pic_struct == PIC_TOP_BOT) + field = V4L2_FIELD_INTERLACED_TB; + else if (pic_struct == PIC_BOT_TOP) + field = V4L2_FIELD_INTERLACED_BT; + + offset |= get_offset_msb(core, i); + amvdec_dst_buf_done_idx(sess, buffer_index, offset, field); + } +} + +static irqreturn_t codec_h264_threaded_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 status; + u32 size; + u8 cmd; + + status = amvdec_read_dos(core, AV_SCRATCH_0); + cmd = status & CMD_MASK; + + switch (cmd) { + case CMD_SRC_CHANGE: + codec_h264_src_change(sess); + break; + case CMD_FRAMES_READY: + codec_h264_frames_ready(sess, status); + break; + case CMD_FATAL_ERROR: + dev_err(core->dev, "H.264 decoder fatal error\n"); + goto abort; + case CMD_BAD_WIDTH: + size = (amvdec_read_dos(core, AV_SCRATCH_1) + 1) * 16; + dev_err(core->dev, "Unsupported video width: %u\n", size); + goto abort; + case CMD_BAD_HEIGHT: + size = (amvdec_read_dos(core, AV_SCRATCH_1) + 1) * 16; + dev_err(core->dev, "Unsupported video height: %u\n", size); + goto abort; + case 0: /* Unused but not worth printing for */ + case 9: + break; + default: + dev_info(core->dev, "Unexpected H264 ISR: %08X\n", cmd); + break; + } + + if (cmd && cmd != CMD_SRC_CHANGE) + amvdec_write_dos(core, AV_SCRATCH_0, 0); + + /* Decoder has some SEI data for us ; ignore */ + if (amvdec_read_dos(core, AV_SCRATCH_J) & SEI_DATA_READY) + amvdec_write_dos(core, AV_SCRATCH_J, 0); + + return IRQ_HANDLED; +abort: + amvdec_abort(sess); + return IRQ_HANDLED; +} + +static irqreturn_t codec_h264_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); + + return IRQ_WAKE_THREAD; +} + +struct amvdec_codec_ops codec_h264_ops = { + .start = codec_h264_start, + .stop = codec_h264_stop, + .load_extended_firmware = codec_h264_load_extended_firmware, + .isr = codec_h264_isr, + .threaded_isr = codec_h264_threaded_isr, + .can_recycle = codec_h264_can_recycle, + .recycle = codec_h264_recycle, + .eos_sequence = codec_h264_eos_sequence, + .resume = codec_h264_resume, +}; diff --git a/drivers/staging/media/meson/vdec/codec_h264.h b/drivers/staging/media/meson/vdec/codec_h264.h new file mode 100644 index 000000000000..7cb4fb86ff36 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_h264.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_CODEC_H264_H_ +#define __MESON_VDEC_CODEC_H264_H_ + +#include "vdec.h" + +extern struct amvdec_codec_ops codec_h264_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/codec_hevc_common.c b/drivers/staging/media/meson/vdec/codec_hevc_common.c new file mode 100644 index 000000000000..0315cc0911cd --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_hevc_common.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "codec_hevc_common.h" +#include "vdec_helpers.h" +#include "hevc_regs.h" + +#define MMU_COMPRESS_HEADER_SIZE 0x48000 +#define MMU_MAP_SIZE 0x4800 + +const u16 vdec_hevc_parser_cmd[] = { + 0x0401, 0x8401, 0x0800, 0x0402, + 0x9002, 0x1423, 0x8CC3, 0x1423, + 0x8804, 0x9825, 0x0800, 0x04FE, + 0x8406, 0x8411, 0x1800, 0x8408, + 0x8409, 0x8C2A, 0x9C2B, 0x1C00, + 0x840F, 0x8407, 0x8000, 0x8408, + 0x2000, 0xA800, 0x8410, 0x04DE, + 0x840C, 0x840D, 0xAC00, 0xA000, + 0x08C0, 0x08E0, 0xA40E, 0xFC00, + 0x7C00 +}; + +/* Configure decode head read mode */ +void codec_hevc_setup_decode_head(struct amvdec_session *sess, int is_10bit) +{ + struct amvdec_core *core = sess->core; + u32 body_size = amvdec_am21c_body_size(sess->width, sess->height); + u32 head_size = amvdec_am21c_head_size(sess->width, sess->height); + + if (!codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) { + /* Enable 2-plane reference read mode */ + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, BIT(31)); + return; + } + + if (codec_hevc_use_mmu(core->platform->revision, + sess->pixfmt_cap, is_10bit)) + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, BIT(4)); + else + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, 0); + + if (core->platform->revision < VDEC_REVISION_SM1) + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL2, body_size / 32); + amvdec_write_dos(core, HEVC_CM_BODY_LENGTH, body_size); + amvdec_write_dos(core, HEVC_CM_HEADER_OFFSET, body_size); + amvdec_write_dos(core, HEVC_CM_HEADER_LENGTH, head_size); +} +EXPORT_SYMBOL_GPL(codec_hevc_setup_decode_head); + +static void codec_hevc_setup_buffers_gxbb(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + struct v4l2_m2m_buffer *buf; + u32 buf_num = v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); + dma_addr_t buf_y_paddr = 0; + dma_addr_t buf_uv_paddr = 0; + u32 idx = 0; + u32 val; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 0); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + struct vb2_buffer *vb = &buf->vb.vb2_buf; + + idx = vb->index; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, is_10bit)) + buf_y_paddr = comm->fbc_buffer_paddr[idx]; + else + buf_y_paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) { + val = buf_y_paddr | (idx << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + } else { + buf_uv_paddr = vb2_dma_contig_plane_dma_addr(vb, 1); + val = buf_y_paddr | ((idx * 2) << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + val = buf_uv_paddr | ((idx * 2 + 1) << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + } + } + + if (codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) + val = buf_y_paddr | (idx << 8) | 1; + else + val = buf_y_paddr | ((idx * 2) << 8) | 1; + + /* Fill the remaining unused slots with the last buffer's Y addr */ + for (i = buf_num; i < MAX_REF_PIC_NUM; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, val); + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + for (i = 0; i < 32; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, 0); +} + +static void codec_hevc_setup_buffers_gxl(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + struct v4l2_m2m_buffer *buf; + u32 revision = core->platform->revision; + u32 pixfmt_cap = sess->pixfmt_cap; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, + BIT(2) | BIT(1)); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + struct vb2_buffer *vb = &buf->vb.vb2_buf; + dma_addr_t buf_y_paddr = 0; + dma_addr_t buf_uv_paddr = 0; + u32 idx = vb->index; + + if (codec_hevc_use_mmu(revision, pixfmt_cap, is_10bit)) + buf_y_paddr = comm->mmu_header_paddr[idx]; + else if (codec_hevc_use_downsample(pixfmt_cap, is_10bit)) + buf_y_paddr = comm->fbc_buffer_paddr[idx]; + else + buf_y_paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_DATA, + buf_y_paddr >> 5); + + if (!codec_hevc_use_fbc(pixfmt_cap, is_10bit)) { + buf_uv_paddr = vb2_dma_contig_plane_dma_addr(vb, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_DATA, + buf_uv_paddr >> 5); + } + } + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + for (i = 0; i < 32; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, 0); +} + +void codec_hevc_free_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + u32 am21_size = amvdec_am21c_size(sess->width, sess->height); + int i; + + for (i = 0; i < MAX_REF_PIC_NUM; ++i) { + if (comm->fbc_buffer_vaddr[i]) { + dma_free_coherent(dev, am21_size, + comm->fbc_buffer_vaddr[i], + comm->fbc_buffer_paddr[i]); + comm->fbc_buffer_vaddr[i] = NULL; + } + } +} +EXPORT_SYMBOL_GPL(codec_hevc_free_fbc_buffers); + +static int codec_hevc_alloc_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + struct v4l2_m2m_buffer *buf; + u32 am21_size = amvdec_am21c_size(sess->width, sess->height); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + u32 idx = buf->vb.vb2_buf.index; + dma_addr_t paddr; + void *vaddr = dma_alloc_coherent(dev, am21_size, &paddr, + GFP_KERNEL); + if (!vaddr) { + codec_hevc_free_fbc_buffers(sess, comm); + return -ENOMEM; + } + + comm->fbc_buffer_vaddr[idx] = vaddr; + comm->fbc_buffer_paddr[idx] = paddr; + } + + return 0; +} + +void codec_hevc_free_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + int i; + + for (i = 0; i < MAX_REF_PIC_NUM; ++i) { + if (comm->mmu_header_vaddr[i]) { + dma_free_coherent(dev, MMU_COMPRESS_HEADER_SIZE, + comm->mmu_header_vaddr[i], + comm->mmu_header_paddr[i]); + comm->mmu_header_vaddr[i] = NULL; + } + } + + if (comm->mmu_map_vaddr) { + dma_free_coherent(dev, MMU_MAP_SIZE, + comm->mmu_map_vaddr, + comm->mmu_map_paddr); + comm->mmu_map_vaddr = NULL; + } +} +EXPORT_SYMBOL_GPL(codec_hevc_free_mmu_headers); + +static int codec_hevc_alloc_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + struct v4l2_m2m_buffer *buf; + + comm->mmu_map_vaddr = dma_alloc_coherent(dev, MMU_MAP_SIZE, + &comm->mmu_map_paddr, + GFP_KERNEL); + if (!comm->mmu_map_vaddr) + return -ENOMEM; + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + u32 idx = buf->vb.vb2_buf.index; + dma_addr_t paddr; + void *vaddr = dma_alloc_coherent(dev, MMU_COMPRESS_HEADER_SIZE, + &paddr, GFP_KERNEL); + if (!vaddr) { + codec_hevc_free_mmu_headers(sess, comm); + return -ENOMEM; + } + + comm->mmu_header_vaddr[idx] = vaddr; + comm->mmu_header_paddr[idx] = paddr; + } + + return 0; +} + +int codec_hevc_setup_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + int ret; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, is_10bit)) { + ret = codec_hevc_alloc_fbc_buffers(sess, comm); + if (ret) + return ret; + } + + if (codec_hevc_use_mmu(core->platform->revision, + sess->pixfmt_cap, is_10bit)) { + ret = codec_hevc_alloc_mmu_headers(sess, comm); + if (ret) { + codec_hevc_free_fbc_buffers(sess, comm); + return ret; + } + } + + if (core->platform->revision == VDEC_REVISION_GXBB) + codec_hevc_setup_buffers_gxbb(sess, comm, is_10bit); + else + codec_hevc_setup_buffers_gxl(sess, comm, is_10bit); + + return 0; +} +EXPORT_SYMBOL_GPL(codec_hevc_setup_buffers); + +void codec_hevc_fill_mmu_map(struct amvdec_session *sess, + struct codec_hevc_common *comm, + struct vb2_buffer *vb) +{ + u32 size = amvdec_am21c_size(sess->width, sess->height); + u32 nb_pages = size / PAGE_SIZE; + u32 *mmu_map = comm->mmu_map_vaddr; + u32 first_page; + u32 i; + + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) + first_page = comm->fbc_buffer_paddr[vb->index] >> PAGE_SHIFT; + else + first_page = vb2_dma_contig_plane_dma_addr(vb, 0) >> PAGE_SHIFT; + + for (i = 0; i < nb_pages; ++i) + mmu_map[i] = first_page + i; +} +EXPORT_SYMBOL_GPL(codec_hevc_fill_mmu_map); diff --git a/drivers/staging/media/meson/vdec/codec_hevc_common.h b/drivers/staging/media/meson/vdec/codec_hevc_common.h new file mode 100644 index 000000000000..88e4379ba1ee --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_hevc_common.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_HEVC_COMMON_H_ +#define __MESON_VDEC_HEVC_COMMON_H_ + +#include "vdec.h" + +#define PARSER_CMD_SKIP_CFG_0 0x0000090b +#define PARSER_CMD_SKIP_CFG_1 0x1b14140f +#define PARSER_CMD_SKIP_CFG_2 0x001b1910 + +#define VDEC_HEVC_PARSER_CMD_LEN 37 +extern const u16 vdec_hevc_parser_cmd[VDEC_HEVC_PARSER_CMD_LEN]; + +#define MAX_REF_PIC_NUM 24 + +struct codec_hevc_common { + void *fbc_buffer_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t fbc_buffer_paddr[MAX_REF_PIC_NUM]; + + void *mmu_header_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t mmu_header_paddr[MAX_REF_PIC_NUM]; + + void *mmu_map_vaddr; + dma_addr_t mmu_map_paddr; +}; + +/* Returns 1 if we must use framebuffer compression */ +static inline int codec_hevc_use_fbc(u32 pixfmt, int is_10bit) +{ + /* TOFIX: Handle Amlogic Compressed buffer for 8bit also */ + return is_10bit; +} + +/* Returns 1 if we are decoding 10-bit but outputting 8-bit NV12 */ +static inline int codec_hevc_use_downsample(u32 pixfmt, int is_10bit) +{ + return is_10bit; +} + +/* Returns 1 if we are decoding using the IOMMU */ +static inline int codec_hevc_use_mmu(u32 revision, u32 pixfmt, int is_10bit) +{ + return revision >= VDEC_REVISION_G12A && + codec_hevc_use_fbc(pixfmt, is_10bit); +} + +/** + * Configure decode head read mode + */ +void codec_hevc_setup_decode_head(struct amvdec_session *sess, int is_10bit); + +void codec_hevc_free_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm); + +void codec_hevc_free_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm); + +int codec_hevc_setup_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit); + +void codec_hevc_fill_mmu_map(struct amvdec_session *sess, + struct codec_hevc_common *comm, + struct vb2_buffer *vb); + +#endif diff --git a/drivers/staging/media/meson/vdec/codec_vp9.c b/drivers/staging/media/meson/vdec/codec_vp9.c new file mode 100644 index 000000000000..60e4fc0052b3 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_vp9.c @@ -0,0 +1,2141 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan <mjourdan@baylibre.com> + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "dos_regs.h" +#include "hevc_regs.h" +#include "codec_vp9.h" +#include "vdec_helpers.h" +#include "codec_hevc_common.h" + +/* HEVC reg mapping */ +#define VP9_DEC_STATUS_REG HEVC_ASSIST_SCRATCH_0 + #define VP9_10B_DECODE_SLICE 5 + #define VP9_HEAD_PARSER_DONE 0xf0 +#define VP9_RPM_BUFFER HEVC_ASSIST_SCRATCH_1 +#define VP9_SHORT_TERM_RPS HEVC_ASSIST_SCRATCH_2 +#define VP9_ADAPT_PROB_REG HEVC_ASSIST_SCRATCH_3 +#define VP9_MMU_MAP_BUFFER HEVC_ASSIST_SCRATCH_4 +#define VP9_PPS_BUFFER HEVC_ASSIST_SCRATCH_5 +#define VP9_SAO_UP HEVC_ASSIST_SCRATCH_6 +#define VP9_STREAM_SWAP_BUFFER HEVC_ASSIST_SCRATCH_7 +#define VP9_STREAM_SWAP_BUFFER2 HEVC_ASSIST_SCRATCH_8 +#define VP9_PROB_SWAP_BUFFER HEVC_ASSIST_SCRATCH_9 +#define VP9_COUNT_SWAP_BUFFER HEVC_ASSIST_SCRATCH_A +#define VP9_SEG_MAP_BUFFER HEVC_ASSIST_SCRATCH_B +#define VP9_SCALELUT HEVC_ASSIST_SCRATCH_D +#define VP9_WAIT_FLAG HEVC_ASSIST_SCRATCH_E +#define LMEM_DUMP_ADR HEVC_ASSIST_SCRATCH_F +#define NAL_SEARCH_CTL HEVC_ASSIST_SCRATCH_I +#define VP9_DECODE_MODE HEVC_ASSIST_SCRATCH_J + #define DECODE_MODE_SINGLE 0 +#define DECODE_STOP_POS HEVC_ASSIST_SCRATCH_K +#define HEVC_DECODE_COUNT HEVC_ASSIST_SCRATCH_M +#define HEVC_DECODE_SIZE HEVC_ASSIST_SCRATCH_N + +/* VP9 Constants */ +#define LCU_SIZE 64 +#define MAX_REF_PIC_NUM 24 +#define REFS_PER_FRAME 3 +#define REF_FRAMES 8 +#define MV_MEM_UNIT 0x240 +#define ADAPT_PROB_SIZE 0xf80 + +enum FRAME_TYPE { + KEY_FRAME = 0, + INTER_FRAME = 1, + FRAME_TYPES, +}; + +/* VP9 Workspace layout */ +#define MPRED_MV_BUF_SIZE 0x120000 + +#define IPP_SIZE 0x4000 +#define SAO_ABV_SIZE 0x30000 +#define SAO_VB_SIZE 0x30000 +#define SH_TM_RPS_SIZE 0x800 +#define VPS_SIZE 0x800 +#define SPS_SIZE 0x800 +#define PPS_SIZE 0x2000 +#define SAO_UP_SIZE 0x2800 +#define SWAP_BUF_SIZE 0x800 +#define SWAP_BUF2_SIZE 0x800 +#define SCALELUT_SIZE 0x8000 +#define DBLK_PARA_SIZE 0x80000 +#define DBLK_DATA_SIZE 0x80000 +#define SEG_MAP_SIZE 0xd800 +#define PROB_SIZE 0x5000 +#define COUNT_SIZE 0x3000 +#define MMU_VBH_SIZE 0x5000 +#define MPRED_ABV_SIZE 0x10000 +#define MPRED_MV_SIZE (MPRED_MV_BUF_SIZE * MAX_REF_PIC_NUM) +#define RPM_BUF_SIZE 0x100 +#define LMEM_SIZE 0x800 + +#define IPP_OFFSET 0x00 +#define SAO_ABV_OFFSET (IPP_OFFSET + IPP_SIZE) +#define SAO_VB_OFFSET (SAO_ABV_OFFSET + SAO_ABV_SIZE) +#define SH_TM_RPS_OFFSET (SAO_VB_OFFSET + SAO_VB_SIZE) +#define VPS_OFFSET (SH_TM_RPS_OFFSET + SH_TM_RPS_SIZE) +#define SPS_OFFSET (VPS_OFFSET + VPS_SIZE) +#define PPS_OFFSET (SPS_OFFSET + SPS_SIZE) +#define SAO_UP_OFFSET (PPS_OFFSET + PPS_SIZE) +#define SWAP_BUF_OFFSET (SAO_UP_OFFSET + SAO_UP_SIZE) +#define SWAP_BUF2_OFFSET (SWAP_BUF_OFFSET + SWAP_BUF_SIZE) +#define SCALELUT_OFFSET (SWAP_BUF2_OFFSET + SWAP_BUF2_SIZE) +#define DBLK_PARA_OFFSET (SCALELUT_OFFSET + SCALELUT_SIZE) +#define DBLK_DATA_OFFSET (DBLK_PARA_OFFSET + DBLK_PARA_SIZE) +#define SEG_MAP_OFFSET (DBLK_DATA_OFFSET + DBLK_DATA_SIZE) +#define PROB_OFFSET (SEG_MAP_OFFSET + SEG_MAP_SIZE) +#define COUNT_OFFSET (PROB_OFFSET + PROB_SIZE) +#define MMU_VBH_OFFSET (COUNT_OFFSET + COUNT_SIZE) +#define MPRED_ABV_OFFSET (MMU_VBH_OFFSET + MMU_VBH_SIZE) +#define MPRED_MV_OFFSET (MPRED_ABV_OFFSET + MPRED_ABV_SIZE) +#define RPM_OFFSET (MPRED_MV_OFFSET + MPRED_MV_SIZE) +#define LMEM_OFFSET (RPM_OFFSET + RPM_BUF_SIZE) + +#define SIZE_WORKSPACE ALIGN(LMEM_OFFSET + LMEM_SIZE, 64 * SZ_1K) + +#define NONE -1 +#define INTRA_FRAME 0 +#define LAST_FRAME 1 +#define GOLDEN_FRAME 2 +#define ALTREF_FRAME 3 +#define MAX_REF_FRAMES 4 + +/* + * Defines, declarations, sub-functions for vp9 de-block loop + filter Thr/Lvl table update + * - struct segmentation is for loop filter only (removed something) + * - function "vp9_loop_filter_init" and "vp9_loop_filter_frame_init" will + be instantiated in C_Entry + * - vp9_loop_filter_init run once before decoding start + * - vp9_loop_filter_frame_init run before every frame decoding start + * - set video format to VP9 is in vp9_loop_filter_init + */ +#define MAX_LOOP_FILTER 63 +#define MAX_REF_LF_DELTAS 4 +#define MAX_MODE_LF_DELTAS 2 +#define SEGMENT_DELTADATA 0 +#define SEGMENT_ABSDATA 1 +#define MAX_SEGMENTS 8 + +/* VP9 PROB processing defines */ +#define VP9_PARTITION_START 0 +#define VP9_PARTITION_SIZE_STEP (3 * 4) +#define VP9_PARTITION_ONE_SIZE (4 * VP9_PARTITION_SIZE_STEP) +#define VP9_PARTITION_KEY_START 0 +#define VP9_PARTITION_P_START VP9_PARTITION_ONE_SIZE +#define VP9_PARTITION_SIZE (2 * VP9_PARTITION_ONE_SIZE) +#define VP9_SKIP_START (VP9_PARTITION_START + VP9_PARTITION_SIZE) +#define VP9_SKIP_SIZE 4 /* only use 3*/ +#define VP9_TX_MODE_START (VP9_SKIP_START + VP9_SKIP_SIZE) +#define VP9_TX_MODE_8_0_OFFSET 0 +#define VP9_TX_MODE_8_1_OFFSET 1 +#define VP9_TX_MODE_16_0_OFFSET 2 +#define VP9_TX_MODE_16_1_OFFSET 4 +#define VP9_TX_MODE_32_0_OFFSET 6 +#define VP9_TX_MODE_32_1_OFFSET 9 +#define VP9_TX_MODE_SIZE 12 +#define VP9_COEF_START (VP9_TX_MODE_START + VP9_TX_MODE_SIZE) +#define VP9_COEF_BAND_0_OFFSET 0 +#define VP9_COEF_BAND_1_OFFSET (VP9_COEF_BAND_0_OFFSET + 3 * 3 + 1) +#define VP9_COEF_BAND_2_OFFSET (VP9_COEF_BAND_1_OFFSET + 6 * 3) +#define VP9_COEF_BAND_3_OFFSET (VP9_COEF_BAND_2_OFFSET + 6 * 3) +#define VP9_COEF_BAND_4_OFFSET (VP9_COEF_BAND_3_OFFSET + 6 * 3) +#define VP9_COEF_BAND_5_OFFSET (VP9_COEF_BAND_4_OFFSET + 6 * 3) +#define VP9_COEF_SIZE_ONE_SET 100 /* ((3 + 5 * 6) * 3 + 1 padding)*/ +#define VP9_COEF_4X4_START (VP9_COEF_START + 0 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_8X8_START (VP9_COEF_START + 4 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_16X16_START (VP9_COEF_START + 8 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_32X32_START (VP9_COEF_START + 12 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_SIZE_PLANE (2 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_SIZE (4 * 2 * 2 * VP9_COEF_SIZE_ONE_SET) +#define VP9_INTER_MODE_START (VP9_COEF_START + VP9_COEF_SIZE) +#define VP9_INTER_MODE_SIZE 24 /* only use 21 (# * 7)*/ +#define VP9_INTERP_START (VP9_INTER_MODE_START + VP9_INTER_MODE_SIZE) +#define VP9_INTERP_SIZE 8 +#define VP9_INTRA_INTER_START (VP9_INTERP_START + VP9_INTERP_SIZE) +#define VP9_INTRA_INTER_SIZE 4 +#define VP9_INTERP_INTRA_INTER_START VP9_INTERP_START +#define VP9_INTERP_INTRA_INTER_SIZE (VP9_INTERP_SIZE + VP9_INTRA_INTER_SIZE) +#define VP9_COMP_INTER_START \ + (VP9_INTERP_INTRA_INTER_START + VP9_INTERP_INTRA_INTER_SIZE) +#define VP9_COMP_INTER_SIZE 5 +#define VP9_COMP_REF_START (VP9_COMP_INTER_START + VP9_COMP_INTER_SIZE) +#define VP9_COMP_REF_SIZE 5 +#define VP9_SINGLE_REF_START (VP9_COMP_REF_START + VP9_COMP_REF_SIZE) +#define VP9_SINGLE_REF_SIZE 10 +#define VP9_REF_MODE_START VP9_COMP_INTER_START +#define VP9_REF_MODE_SIZE \ + (VP9_COMP_INTER_SIZE + VP9_COMP_REF_SIZE + VP9_SINGLE_REF_SIZE) +#define VP9_IF_Y_MODE_START (VP9_REF_MODE_START + VP9_REF_MODE_SIZE) +#define VP9_IF_Y_MODE_SIZE 36 +#define VP9_IF_UV_MODE_START (VP9_IF_Y_MODE_START + VP9_IF_Y_MODE_SIZE) +#define VP9_IF_UV_MODE_SIZE 92 /* only use 90*/ +#define VP9_MV_JOINTS_START (VP9_IF_UV_MODE_START + VP9_IF_UV_MODE_SIZE) +#define VP9_MV_JOINTS_SIZE 3 +#define VP9_MV_SIGN_0_START (VP9_MV_JOINTS_START + VP9_MV_JOINTS_SIZE) +#define VP9_MV_SIGN_0_SIZE 1 +#define VP9_MV_CLASSES_0_START (VP9_MV_SIGN_0_START + VP9_MV_SIGN_0_SIZE) +#define VP9_MV_CLASSES_0_SIZE 10 +#define VP9_MV_CLASS0_0_START \ + (VP9_MV_CLASSES_0_START + VP9_MV_CLASSES_0_SIZE) +#define VP9_MV_CLASS0_0_SIZE 1 +#define VP9_MV_BITS_0_START (VP9_MV_CLASS0_0_START + VP9_MV_CLASS0_0_SIZE) +#define VP9_MV_BITS_0_SIZE 10 +#define VP9_MV_SIGN_1_START (VP9_MV_BITS_0_START + VP9_MV_BITS_0_SIZE) +#define VP9_MV_SIGN_1_SIZE 1 +#define VP9_MV_CLASSES_1_START \ + (VP9_MV_SIGN_1_START + VP9_MV_SIGN_1_SIZE) +#define VP9_MV_CLASSES_1_SIZE 10 +#define VP9_MV_CLASS0_1_START \ + (VP9_MV_CLASSES_1_START + VP9_MV_CLASSES_1_SIZE) +#define VP9_MV_CLASS0_1_SIZE 1 +#define VP9_MV_BITS_1_START \ + (VP9_MV_CLASS0_1_START + VP9_MV_CLASS0_1_SIZE) +#define VP9_MV_BITS_1_SIZE 10 +#define VP9_MV_CLASS0_FP_0_START \ + (VP9_MV_BITS_1_START + VP9_MV_BITS_1_SIZE) +#define VP9_MV_CLASS0_FP_0_SIZE 9 +#define VP9_MV_CLASS0_FP_1_START \ + (VP9_MV_CLASS0_FP_0_START + VP9_MV_CLASS0_FP_0_SIZE) +#define VP9_MV_CLASS0_FP_1_SIZE 9 +#define VP9_MV_CLASS0_HP_0_START \ + (VP9_MV_CLASS0_FP_1_START + VP9_MV_CLASS0_FP_1_SIZE) +#define VP9_MV_CLASS0_HP_0_SIZE 2 +#define VP9_MV_CLASS0_HP_1_START \ + (VP9_MV_CLASS0_HP_0_START + VP9_MV_CLASS0_HP_0_SIZE) +#define VP9_MV_CLASS0_HP_1_SIZE 2 +#define VP9_MV_START VP9_MV_JOINTS_START +#define VP9_MV_SIZE 72 /*only use 69*/ + +#define VP9_TOTAL_SIZE (VP9_MV_START + VP9_MV_SIZE) + +/* VP9 COUNT mem processing defines */ +#define VP9_COEF_COUNT_START 0 +#define VP9_COEF_COUNT_BAND_0_OFFSET 0 +#define VP9_COEF_COUNT_BAND_1_OFFSET \ + (VP9_COEF_COUNT_BAND_0_OFFSET + 3 * 5) +#define VP9_COEF_COUNT_BAND_2_OFFSET \ + (VP9_COEF_COUNT_BAND_1_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_BAND_3_OFFSET \ + (VP9_COEF_COUNT_BAND_2_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_BAND_4_OFFSET \ + (VP9_COEF_COUNT_BAND_3_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_BAND_5_OFFSET \ + (VP9_COEF_COUNT_BAND_4_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_SIZE_ONE_SET 165 /* ((3 + 5 * 6) * 5 */ +#define VP9_COEF_COUNT_4X4_START \ + (VP9_COEF_COUNT_START + 0 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_8X8_START \ + (VP9_COEF_COUNT_START + 4 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_16X16_START \ + (VP9_COEF_COUNT_START + 8 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_32X32_START \ + (VP9_COEF_COUNT_START + 12 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_SIZE_PLANE (2 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_SIZE (4 * 2 * 2 * VP9_COEF_COUNT_SIZE_ONE_SET) + +#define VP9_INTRA_INTER_COUNT_START \ + (VP9_COEF_COUNT_START + VP9_COEF_COUNT_SIZE) +#define VP9_INTRA_INTER_COUNT_SIZE (4 * 2) +#define VP9_COMP_INTER_COUNT_START \ + (VP9_INTRA_INTER_COUNT_START + VP9_INTRA_INTER_COUNT_SIZE) +#define VP9_COMP_INTER_COUNT_SIZE (5 * 2) +#define VP9_COMP_REF_COUNT_START \ + (VP9_COMP_INTER_COUNT_START + VP9_COMP_INTER_COUNT_SIZE) +#define VP9_COMP_REF_COUNT_SIZE (5 * 2) +#define VP9_SINGLE_REF_COUNT_START \ + (VP9_COMP_REF_COUNT_START + VP9_COMP_REF_COUNT_SIZE) +#define VP9_SINGLE_REF_COUNT_SIZE (10 * 2) +#define VP9_TX_MODE_COUNT_START \ + (VP9_SINGLE_REF_COUNT_START + VP9_SINGLE_REF_COUNT_SIZE) +#define VP9_TX_MODE_COUNT_SIZE (12 * 2) +#define VP9_SKIP_COUNT_START \ + (VP9_TX_MODE_COUNT_START + VP9_TX_MODE_COUNT_SIZE) +#define VP9_SKIP_COUNT_SIZE (3 * 2) +#define VP9_MV_SIGN_0_COUNT_START \ + (VP9_SKIP_COUNT_START + VP9_SKIP_COUNT_SIZE) +#define VP9_MV_SIGN_0_COUNT_SIZE (1 * 2) +#define VP9_MV_SIGN_1_COUNT_START \ + (VP9_MV_SIGN_0_COUNT_START + VP9_MV_SIGN_0_COUNT_SIZE) +#define VP9_MV_SIGN_1_COUNT_SIZE (1 * 2) +#define VP9_MV_BITS_0_COUNT_START \ + (VP9_MV_SIGN_1_COUNT_START + VP9_MV_SIGN_1_COUNT_SIZE) +#define VP9_MV_BITS_0_COUNT_SIZE (10 * 2) +#define VP9_MV_BITS_1_COUNT_START \ + (VP9_MV_BITS_0_COUNT_START + VP9_MV_BITS_0_COUNT_SIZE) +#define VP9_MV_BITS_1_COUNT_SIZE (10 * 2) +#define VP9_MV_CLASS0_HP_0_COUNT_START \ + (VP9_MV_BITS_1_COUNT_START + VP9_MV_BITS_1_COUNT_SIZE) +#define VP9_MV_CLASS0_HP_0_COUNT_SIZE (2 * 2) +#define VP9_MV_CLASS0_HP_1_COUNT_START \ + (VP9_MV_CLASS0_HP_0_COUNT_START + VP9_MV_CLASS0_HP_0_COUNT_SIZE) +#define VP9_MV_CLASS0_HP_1_COUNT_SIZE (2 * 2) + +/* Start merge_tree */ +#define VP9_INTER_MODE_COUNT_START \ + (VP9_MV_CLASS0_HP_1_COUNT_START + VP9_MV_CLASS0_HP_1_COUNT_SIZE) +#define VP9_INTER_MODE_COUNT_SIZE (7 * 4) +#define VP9_IF_Y_MODE_COUNT_START \ + (VP9_INTER_MODE_COUNT_START + VP9_INTER_MODE_COUNT_SIZE) +#define VP9_IF_Y_MODE_COUNT_SIZE (10 * 4) +#define VP9_IF_UV_MODE_COUNT_START \ + (VP9_IF_Y_MODE_COUNT_START + VP9_IF_Y_MODE_COUNT_SIZE) +#define VP9_IF_UV_MODE_COUNT_SIZE (10 * 10) +#define VP9_PARTITION_P_COUNT_START \ + (VP9_IF_UV_MODE_COUNT_START + VP9_IF_UV_MODE_COUNT_SIZE) +#define VP9_PARTITION_P_COUNT_SIZE (4 * 4 * 4) +#define VP9_INTERP_COUNT_START \ + (VP9_PARTITION_P_COUNT_START + VP9_PARTITION_P_COUNT_SIZE) +#define VP9_INTERP_COUNT_SIZE (4 * 3) +#define VP9_MV_JOINTS_COUNT_START \ + (VP9_INTERP_COUNT_START + VP9_INTERP_COUNT_SIZE) +#define VP9_MV_JOINTS_COUNT_SIZE (1 * 4) +#define VP9_MV_CLASSES_0_COUNT_START \ + (VP9_MV_JOINTS_COUNT_START + VP9_MV_JOINTS_COUNT_SIZE) +#define VP9_MV_CLASSES_0_COUNT_SIZE (1 * 11) +#define VP9_MV_CLASS0_0_COUNT_START \ + (VP9_MV_CLASSES_0_COUNT_START + VP9_MV_CLASSES_0_COUNT_SIZE) +#define VP9_MV_CLASS0_0_COUNT_SIZE (1 * 2) +#define VP9_MV_CLASSES_1_COUNT_START \ + (VP9_MV_CLASS0_0_COUNT_START + VP9_MV_CLASS0_0_COUNT_SIZE) +#define VP9_MV_CLASSES_1_COUNT_SIZE (1 * 11) +#define VP9_MV_CLASS0_1_COUNT_START \ + (VP9_MV_CLASSES_1_COUNT_START + VP9_MV_CLASSES_1_COUNT_SIZE) +#define VP9_MV_CLASS0_1_COUNT_SIZE (1 * 2) +#define VP9_MV_CLASS0_FP_0_COUNT_START \ + (VP9_MV_CLASS0_1_COUNT_START + VP9_MV_CLASS0_1_COUNT_SIZE) +#define VP9_MV_CLASS0_FP_0_COUNT_SIZE (3 * 4) +#define VP9_MV_CLASS0_FP_1_COUNT_START \ + (VP9_MV_CLASS0_FP_0_COUNT_START + VP9_MV_CLASS0_FP_0_COUNT_SIZE) +#define VP9_MV_CLASS0_FP_1_COUNT_SIZE (3 * 4) + +#define DC_PRED 0 /* Average of above and left pixels */ +#define V_PRED 1 /* Vertical */ +#define H_PRED 2 /* Horizontal */ +#define D45_PRED 3 /* Directional 45 deg = round(arctan(1/1) * 180/pi) */ +#define D135_PRED 4 /* Directional 135 deg = 180 - 45 */ +#define D117_PRED 5 /* Directional 117 deg = 180 - 63 */ +#define D153_PRED 6 /* Directional 153 deg = 180 - 27 */ +#define D207_PRED 7 /* Directional 207 deg = 180 + 27 */ +#define D63_PRED 8 /* Directional 63 deg = round(arctan(2/1) * 180/pi) */ +#define TM_PRED 9 /* True-motion */ + +/* Use a static inline to avoid possible side effect from num being reused */ +static inline int round_power_of_two(int value, int num) +{ + return (value + (1 << (num - 1))) >> num; +} + +#define MODE_MV_COUNT_SAT 20 +static const int count_to_update_factor[MODE_MV_COUNT_SAT + 1] = { + 0, 6, 12, 19, 25, 32, 38, 44, 51, 57, 64, + 70, 76, 83, 89, 96, 102, 108, 115, 121, 128 +}; + +union rpm_param { + struct { + u16 data[RPM_BUF_SIZE]; + } l; + struct { + u16 profile; + u16 show_existing_frame; + u16 frame_to_show_idx; + u16 frame_type; /*1 bit*/ + u16 show_frame; /*1 bit*/ + u16 error_resilient_mode; /*1 bit*/ + u16 intra_only; /*1 bit*/ + u16 display_size_present; /*1 bit*/ + u16 reset_frame_context; + u16 refresh_frame_flags; + u16 width; + u16 height; + u16 display_width; + u16 display_height; + u16 ref_info; + u16 same_frame_size; + u16 mode_ref_delta_enabled; + u16 ref_deltas[4]; + u16 mode_deltas[2]; + u16 filter_level; + u16 sharpness_level; + u16 bit_depth; + u16 seg_quant_info[8]; + u16 seg_enabled; + u16 seg_abs_delta; + /* bit 15: feature enabled; bit 8, sign; bit[5:0], data */ + u16 seg_lf_info[8]; + } p; +}; + +enum SEG_LVL_FEATURES { + SEG_LVL_ALT_Q = 0, /* Use alternate Quantizer */ + SEG_LVL_ALT_LF = 1, /* Use alternate loop filter value */ + SEG_LVL_REF_FRAME = 2, /* Optional Segment reference frame */ + SEG_LVL_SKIP = 3, /* Optional Segment (0,0) + skip mode */ + SEG_LVL_MAX = 4 /* Number of features supported */ +}; + +struct segmentation { + u8 enabled; + u8 update_map; + u8 update_data; + u8 abs_delta; + u8 temporal_update; + s16 feature_data[MAX_SEGMENTS][SEG_LVL_MAX]; + unsigned int feature_mask[MAX_SEGMENTS]; +}; + +struct loop_filter_thresh { + u8 mblim; + u8 lim; + u8 hev_thr; +}; + +struct loop_filter_info_n { + struct loop_filter_thresh lfthr[MAX_LOOP_FILTER + 1]; + u8 lvl[MAX_SEGMENTS][MAX_REF_FRAMES][MAX_MODE_LF_DELTAS]; +}; + +struct loopfilter { + int filter_level; + + int sharpness_level; + int last_sharpness_level; + + u8 mode_ref_delta_enabled; + u8 mode_ref_delta_update; + + /*0 = Intra, Last, GF, ARF*/ + signed char ref_deltas[MAX_REF_LF_DELTAS]; + signed char last_ref_deltas[MAX_REF_LF_DELTAS]; + + /*0 = ZERO_MV, MV*/ + signed char mode_deltas[MAX_MODE_LF_DELTAS]; + signed char last_mode_deltas[MAX_MODE_LF_DELTAS]; +}; + +struct vp9_frame { + struct list_head list; + struct vb2_v4l2_buffer *vbuf; + int index; + int intra_only; + int show; + int type; + int done; + unsigned int width; + unsigned int height; +}; + +struct codec_vp9 { + /* VP9 context lock */ + struct mutex lock; + + /* Common part with the HEVC decoder */ + struct codec_hevc_common common; + + /* Buffer for the VP9 Workspace */ + void *workspace_vaddr; + dma_addr_t workspace_paddr; + + /* Contains many information parsed from the bitstream */ + union rpm_param rpm_param; + + /* Whether we detected the bitstream as 10-bit */ + int is_10bit; + + /* Coded resolution reported by the hardware */ + u32 width, height; + + /* All ref frames used by the HW at a given time */ + struct list_head ref_frames_list; + u32 frames_num; + + /* In case of downsampling (decoding with FBC but outputting in NV12M), + * we need to allocate additional buffers for FBC. + */ + void *fbc_buffer_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t fbc_buffer_paddr[MAX_REF_PIC_NUM]; + + int ref_frame_map[REF_FRAMES]; + int next_ref_frame_map[REF_FRAMES]; + struct vp9_frame *frame_refs[REFS_PER_FRAME]; + + u32 lcu_total; + + /* loop filter */ + int default_filt_lvl; + struct loop_filter_info_n lfi; + struct loopfilter lf; + struct segmentation seg_4lf; + + struct vp9_frame *cur_frame; + struct vp9_frame *prev_frame; +}; + +static int div_r32(s64 m, int n) +{ + s64 qu = div_s64(m, n); + + return (int)qu; +} + +static int clip_prob(int p) +{ + return clamp_val(p, 1, 255); +} + +static int segfeature_active(struct segmentation *seg, int segment_id, + enum SEG_LVL_FEATURES feature_id) +{ + return seg->enabled && + (seg->feature_mask[segment_id] & (1 << feature_id)); +} + +static int get_segdata(struct segmentation *seg, int segment_id, + enum SEG_LVL_FEATURES feature_id) +{ + return seg->feature_data[segment_id][feature_id]; +} + +static void vp9_update_sharpness(struct loop_filter_info_n *lfi, + int sharpness_lvl) +{ + int lvl; + + /* For each possible value for the loop filter fill out limits*/ + for (lvl = 0; lvl <= MAX_LOOP_FILTER; lvl++) { + /* Set loop filter parameters that control sharpness.*/ + int block_inside_limit = lvl >> ((sharpness_lvl > 0) + + (sharpness_lvl > 4)); + + if (sharpness_lvl > 0) { + if (block_inside_limit > (9 - sharpness_lvl)) + block_inside_limit = (9 - sharpness_lvl); + } + + if (block_inside_limit < 1) + block_inside_limit = 1; + + lfi->lfthr[lvl].lim = (u8)block_inside_limit; + lfi->lfthr[lvl].mblim = (u8)(2 * (lvl + 2) + + block_inside_limit); + } +} + +/* Instantiate this function once when decode is started */ +static void +vp9_loop_filter_init(struct amvdec_core *core, struct codec_vp9 *vp9) +{ + struct loop_filter_info_n *lfi = &vp9->lfi; + struct loopfilter *lf = &vp9->lf; + struct segmentation *seg_4lf = &vp9->seg_4lf; + int i; + + memset(lfi, 0, sizeof(struct loop_filter_info_n)); + memset(lf, 0, sizeof(struct loopfilter)); + memset(seg_4lf, 0, sizeof(struct segmentation)); + lf->sharpness_level = 0; + vp9_update_sharpness(lfi, lf->sharpness_level); + lf->last_sharpness_level = lf->sharpness_level; + + for (i = 0; i < 32; i++) { + unsigned int thr; + + thr = ((lfi->lfthr[i * 2 + 1].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2 + 1].mblim & 0xff); + thr = (thr << 16) | ((lfi->lfthr[i * 2].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2].mblim & 0xff); + + amvdec_write_dos(core, HEVC_DBLK_CFG9, thr); + } + + if (core->platform->revision >= VDEC_REVISION_SM1) + amvdec_write_dos(core, HEVC_DBLK_CFGB, + (0x3 << 14) | /* dw fifo thres r and b */ + (0x3 << 12) | /* dw fifo thres r or b */ + (0x3 << 10) | /* dw fifo thres not r/b */ + BIT(0)); /* VP9 video format */ + else if (core->platform->revision >= VDEC_REVISION_G12A) + /* VP9 video format */ + amvdec_write_dos(core, HEVC_DBLK_CFGB, (0x54 << 8) | BIT(0)); + else + amvdec_write_dos(core, HEVC_DBLK_CFGB, 0x40400001); +} + +static void +vp9_loop_filter_frame_init(struct amvdec_core *core, struct segmentation *seg, + struct loop_filter_info_n *lfi, + struct loopfilter *lf, int default_filt_lvl) +{ + int i; + int seg_id; + + /* + * n_shift is the multiplier for lf_deltas + * the multiplier is: + * - 1 for when filter_lvl is between 0 and 31 + * - 2 when filter_lvl is between 32 and 63 + */ + const int scale = 1 << (default_filt_lvl >> 5); + + /* update limits if sharpness has changed */ + if (lf->last_sharpness_level != lf->sharpness_level) { + vp9_update_sharpness(lfi, lf->sharpness_level); + lf->last_sharpness_level = lf->sharpness_level; + + /* Write to register */ + for (i = 0; i < 32; i++) { + unsigned int thr; + + thr = ((lfi->lfthr[i * 2 + 1].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2 + 1].mblim & 0xff); + thr = (thr << 16) | + ((lfi->lfthr[i * 2].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2].mblim & 0xff); + + amvdec_write_dos(core, HEVC_DBLK_CFG9, thr); + } + } + + for (seg_id = 0; seg_id < MAX_SEGMENTS; seg_id++) { + int lvl_seg = default_filt_lvl; + + if (segfeature_active(seg, seg_id, SEG_LVL_ALT_LF)) { + const int data = get_segdata(seg, seg_id, + SEG_LVL_ALT_LF); + lvl_seg = clamp_t(int, + seg->abs_delta == SEGMENT_ABSDATA ? + data : default_filt_lvl + data, + 0, MAX_LOOP_FILTER); + } + + if (!lf->mode_ref_delta_enabled) { + /* + * We could get rid of this if we assume that deltas + * are set to zero when not in use. + * encoder always uses deltas + */ + memset(lfi->lvl[seg_id], lvl_seg, + sizeof(lfi->lvl[seg_id])); + } else { + int ref, mode; + const int intra_lvl = + lvl_seg + lf->ref_deltas[INTRA_FRAME] * scale; + lfi->lvl[seg_id][INTRA_FRAME][0] = + clamp_val(intra_lvl, 0, MAX_LOOP_FILTER); + + for (ref = LAST_FRAME; ref < MAX_REF_FRAMES; ++ref) { + for (mode = 0; mode < MAX_MODE_LF_DELTAS; + ++mode) { + const int inter_lvl = + lvl_seg + + lf->ref_deltas[ref] * scale + + lf->mode_deltas[mode] * scale; + lfi->lvl[seg_id][ref][mode] = + clamp_val(inter_lvl, 0, + MAX_LOOP_FILTER); + } + } + } + } + + for (i = 0; i < 16; i++) { + unsigned int level; + + level = ((lfi->lvl[i >> 1][3][i & 1] & 0x3f) << 24) | + ((lfi->lvl[i >> 1][2][i & 1] & 0x3f) << 16) | + ((lfi->lvl[i >> 1][1][i & 1] & 0x3f) << 8) | + (lfi->lvl[i >> 1][0][i & 1] & 0x3f); + if (!default_filt_lvl) + level = 0; + + amvdec_write_dos(core, HEVC_DBLK_CFGA, level); + } +} + +static void codec_vp9_flush_output(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + struct vp9_frame *tmp, *n; + + mutex_lock(&vp9->lock); + list_for_each_entry_safe(tmp, n, &vp9->ref_frames_list, list) { + if (!tmp->done) { + if (tmp->show) + amvdec_dst_buf_done(sess, tmp->vbuf, + V4L2_FIELD_NONE); + else + v4l2_m2m_buf_queue(sess->m2m_ctx, tmp->vbuf); + + vp9->frames_num--; + } + + list_del(&tmp->list); + kfree(tmp); + } + mutex_unlock(&vp9->lock); +} + +static u32 codec_vp9_num_pending_bufs(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + + if (!vp9) + return 0; + + return vp9->frames_num; +} + +static int codec_vp9_alloc_workspace(struct amvdec_core *core, + struct codec_vp9 *vp9) +{ + /* Allocate some memory for the VP9 decoder's state */ + vp9->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE, + &vp9->workspace_paddr, + GFP_KERNEL); + if (!vp9->workspace_vaddr) { + dev_err(core->dev, "Failed to allocate VP9 Workspace\n"); + return -ENOMEM; + } + + return 0; +} + +static void codec_vp9_setup_workspace(struct amvdec_session *sess, + struct codec_vp9 *vp9) +{ + struct amvdec_core *core = sess->core; + u32 revision = core->platform->revision; + dma_addr_t wkaddr = vp9->workspace_paddr; + + amvdec_write_dos(core, HEVCD_IPP_LINEBUFF_BASE, wkaddr + IPP_OFFSET); + amvdec_write_dos(core, VP9_RPM_BUFFER, wkaddr + RPM_OFFSET); + amvdec_write_dos(core, VP9_SHORT_TERM_RPS, wkaddr + SH_TM_RPS_OFFSET); + amvdec_write_dos(core, VP9_PPS_BUFFER, wkaddr + PPS_OFFSET); + amvdec_write_dos(core, VP9_SAO_UP, wkaddr + SAO_UP_OFFSET); + + amvdec_write_dos(core, VP9_STREAM_SWAP_BUFFER, + wkaddr + SWAP_BUF_OFFSET); + amvdec_write_dos(core, VP9_STREAM_SWAP_BUFFER2, + wkaddr + SWAP_BUF2_OFFSET); + amvdec_write_dos(core, VP9_SCALELUT, wkaddr + SCALELUT_OFFSET); + + if (core->platform->revision >= VDEC_REVISION_G12A) + amvdec_write_dos(core, HEVC_DBLK_CFGE, + wkaddr + DBLK_PARA_OFFSET); + + amvdec_write_dos(core, HEVC_DBLK_CFG4, wkaddr + DBLK_PARA_OFFSET); + amvdec_write_dos(core, HEVC_DBLK_CFG5, wkaddr + DBLK_DATA_OFFSET); + amvdec_write_dos(core, VP9_SEG_MAP_BUFFER, wkaddr + SEG_MAP_OFFSET); + amvdec_write_dos(core, VP9_PROB_SWAP_BUFFER, wkaddr + PROB_OFFSET); + amvdec_write_dos(core, VP9_COUNT_SWAP_BUFFER, wkaddr + COUNT_OFFSET); + amvdec_write_dos(core, LMEM_DUMP_ADR, wkaddr + LMEM_OFFSET); + + if (codec_hevc_use_mmu(revision, sess->pixfmt_cap, vp9->is_10bit)) { + amvdec_write_dos(core, HEVC_SAO_MMU_VH0_ADDR, + wkaddr + MMU_VBH_OFFSET); + amvdec_write_dos(core, HEVC_SAO_MMU_VH1_ADDR, + wkaddr + MMU_VBH_OFFSET + (MMU_VBH_SIZE / 2)); + + if (revision >= VDEC_REVISION_G12A) + amvdec_write_dos(core, HEVC_ASSIST_MMU_MAP_ADDR, + vp9->common.mmu_map_paddr); + else + amvdec_write_dos(core, VP9_MMU_MAP_BUFFER, + vp9->common.mmu_map_paddr); + } +} + +static int codec_vp9_start(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9; + u32 val; + int i; + int ret; + + vp9 = kzalloc(sizeof(*vp9), GFP_KERNEL); + if (!vp9) + return -ENOMEM; + + ret = codec_vp9_alloc_workspace(core, vp9); + if (ret) + goto free_vp9; + + codec_vp9_setup_workspace(sess, vp9); + amvdec_write_dos_bits(core, HEVC_STREAM_CONTROL, BIT(0)); + /* stream_fifo_hole */ + if (core->platform->revision >= VDEC_REVISION_G12A) + amvdec_write_dos_bits(core, HEVC_STREAM_FIFO_CTL, BIT(29)); + + val = amvdec_read_dos(core, HEVC_PARSER_INT_CONTROL) & 0x7fffffff; + val |= (3 << 29) | BIT(24) | BIT(22) | BIT(7) | BIT(4) | BIT(0); + amvdec_write_dos(core, HEVC_PARSER_INT_CONTROL, val); + amvdec_write_dos_bits(core, HEVC_SHIFT_STATUS, BIT(0)); + amvdec_write_dos(core, HEVC_SHIFT_CONTROL, BIT(10) | BIT(9) | + (3 << 6) | BIT(5) | BIT(2) | BIT(1) | BIT(0)); + amvdec_write_dos(core, HEVC_CABAC_CONTROL, BIT(0)); + amvdec_write_dos(core, HEVC_PARSER_CORE_CONTROL, BIT(0)); + amvdec_write_dos(core, HEVC_SHIFT_STARTCODE, 0x00000001); + + amvdec_write_dos(core, VP9_DEC_STATUS_REG, 0); + + amvdec_write_dos(core, HEVC_PARSER_CMD_WRITE, BIT(16)); + for (i = 0; i < ARRAY_SIZE(vdec_hevc_parser_cmd); ++i) + amvdec_write_dos(core, HEVC_PARSER_CMD_WRITE, + vdec_hevc_parser_cmd[i]); + + amvdec_write_dos(core, HEVC_PARSER_CMD_SKIP_0, PARSER_CMD_SKIP_CFG_0); + amvdec_write_dos(core, HEVC_PARSER_CMD_SKIP_1, PARSER_CMD_SKIP_CFG_1); + amvdec_write_dos(core, HEVC_PARSER_CMD_SKIP_2, PARSER_CMD_SKIP_CFG_2); + amvdec_write_dos(core, HEVC_PARSER_IF_CONTROL, + BIT(5) | BIT(2) | BIT(0)); + + amvdec_write_dos(core, HEVCD_IPP_TOP_CNTL, BIT(0)); + amvdec_write_dos(core, HEVCD_IPP_TOP_CNTL, BIT(1)); + + amvdec_write_dos(core, VP9_WAIT_FLAG, 1); + + /* clear mailbox interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_CLR_REG, 1); + /* enable mailbox interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_MASK, 1); + /* disable PSCALE for hardware sharing */ + amvdec_write_dos(core, HEVC_PSCALE_CTRL, 0); + /* Let the uCode do all the parsing */ + amvdec_write_dos(core, NAL_SEARCH_CTL, 0x8); + + amvdec_write_dos(core, DECODE_STOP_POS, 0); + amvdec_write_dos(core, VP9_DECODE_MODE, DECODE_MODE_SINGLE); + + pr_debug("decode_count: %u; decode_size: %u\n", + amvdec_read_dos(core, HEVC_DECODE_COUNT), + amvdec_read_dos(core, HEVC_DECODE_SIZE)); + + vp9_loop_filter_init(core, vp9); + + INIT_LIST_HEAD(&vp9->ref_frames_list); + mutex_init(&vp9->lock); + memset(&vp9->ref_frame_map, -1, sizeof(vp9->ref_frame_map)); + memset(&vp9->next_ref_frame_map, -1, sizeof(vp9->next_ref_frame_map)); + for (i = 0; i < REFS_PER_FRAME; ++i) + vp9->frame_refs[i] = NULL; + sess->priv = vp9; + + return 0; + +free_vp9: + kfree(vp9); + return ret; +} + +static int codec_vp9_stop(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + + mutex_lock(&vp9->lock); + if (vp9->workspace_vaddr) + dma_free_coherent(core->dev, SIZE_WORKSPACE, + vp9->workspace_vaddr, + vp9->workspace_paddr); + + codec_hevc_free_fbc_buffers(sess, &vp9->common); + mutex_unlock(&vp9->lock); + + return 0; +} + +static void codec_vp9_set_sao(struct amvdec_session *sess, + struct vb2_buffer *vb) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + + dma_addr_t buf_y_paddr; + dma_addr_t buf_u_v_paddr; + u32 val; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, vp9->is_10bit)) + buf_y_paddr = + vp9->common.fbc_buffer_paddr[vb->index]; + else + buf_y_paddr = + vb2_dma_contig_plane_dma_addr(vb, 0); + + if (codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) { + val = amvdec_read_dos(core, HEVC_SAO_CTRL5) & ~0xff0200; + amvdec_write_dos(core, HEVC_SAO_CTRL5, val); + amvdec_write_dos(core, HEVC_CM_BODY_START_ADDR, buf_y_paddr); + } + + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) { + buf_y_paddr = + vb2_dma_contig_plane_dma_addr(vb, 0); + buf_u_v_paddr = + vb2_dma_contig_plane_dma_addr(vb, 1); + amvdec_write_dos(core, HEVC_SAO_Y_START_ADDR, buf_y_paddr); + amvdec_write_dos(core, HEVC_SAO_C_START_ADDR, buf_u_v_paddr); + amvdec_write_dos(core, HEVC_SAO_Y_WPTR, buf_y_paddr); + amvdec_write_dos(core, HEVC_SAO_C_WPTR, buf_u_v_paddr); + } + + if (codec_hevc_use_mmu(core->platform->revision, sess->pixfmt_cap, + vp9->is_10bit)) { + amvdec_write_dos(core, HEVC_CM_HEADER_START_ADDR, + vp9->common.mmu_header_paddr[vb->index]); + /* use HEVC_CM_HEADER_START_ADDR */ + amvdec_write_dos_bits(core, HEVC_SAO_CTRL5, BIT(10)); + } + + amvdec_write_dos(core, HEVC_SAO_Y_LENGTH, + amvdec_get_output_size(sess)); + amvdec_write_dos(core, HEVC_SAO_C_LENGTH, + (amvdec_get_output_size(sess) / 2)); + + if (core->platform->revision >= VDEC_REVISION_G12A) { + amvdec_clear_dos_bits(core, HEVC_DBLK_CFGB, + BIT(4) | BIT(5) | BIT(8) | BIT(9)); + /* enable first, compressed write */ + if (codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) + amvdec_write_dos_bits(core, HEVC_DBLK_CFGB, BIT(8)); + + /* enable second, uncompressed write */ + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) + amvdec_write_dos_bits(core, HEVC_DBLK_CFGB, BIT(9)); + + /* dblk pipeline mode=1 for performance */ + if (sess->width >= 1280) + amvdec_write_dos_bits(core, HEVC_DBLK_CFGB, BIT(4)); + + pr_debug("HEVC_DBLK_CFGB: %08X\n", + amvdec_read_dos(core, HEVC_DBLK_CFGB)); + } + + val = amvdec_read_dos(core, HEVC_SAO_CTRL1) & ~0x3ff0; + val |= 0xff0; /* Set endianness for 2-bytes swaps (nv12) */ + if (core->platform->revision < VDEC_REVISION_G12A) { + val &= ~0x3; + if (!codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) + val |= BIT(0); /* disable cm compression */ + /* TOFIX: Handle Amlogic Framebuffer compression */ + } + + amvdec_write_dos(core, HEVC_SAO_CTRL1, val); + pr_debug("HEVC_SAO_CTRL1: %08X\n", val); + + /* no downscale for NV12 */ + val = amvdec_read_dos(core, HEVC_SAO_CTRL5) & ~0xff0000; + amvdec_write_dos(core, HEVC_SAO_CTRL5, val); + + val = amvdec_read_dos(core, HEVCD_IPP_AXIIF_CONFIG) & ~0x30; + val |= 0xf; + val &= ~BIT(12); /* NV12 */ + amvdec_write_dos(core, HEVCD_IPP_AXIIF_CONFIG, val); +} + +static dma_addr_t codec_vp9_get_frame_mv_paddr(struct codec_vp9 *vp9, + struct vp9_frame *frame) +{ + return vp9->workspace_paddr + MPRED_MV_OFFSET + + (frame->index * MPRED_MV_BUF_SIZE); +} + +static void codec_vp9_set_mpred_mv(struct amvdec_core *core, + struct codec_vp9 *vp9) +{ + int mpred_mv_rd_end_addr; + int use_prev_frame_mvs = vp9->prev_frame->width == + vp9->cur_frame->width && + vp9->prev_frame->height == + vp9->cur_frame->height && + !vp9->prev_frame->intra_only && + vp9->prev_frame->show && + vp9->prev_frame->type != KEY_FRAME; + + amvdec_write_dos(core, HEVC_MPRED_CTRL3, 0x24122412); + amvdec_write_dos(core, HEVC_MPRED_ABV_START_ADDR, + vp9->workspace_paddr + MPRED_ABV_OFFSET); + + amvdec_clear_dos_bits(core, HEVC_MPRED_CTRL4, BIT(6)); + if (use_prev_frame_mvs) + amvdec_write_dos_bits(core, HEVC_MPRED_CTRL4, BIT(6)); + + amvdec_write_dos(core, HEVC_MPRED_MV_WR_START_ADDR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->cur_frame)); + amvdec_write_dos(core, HEVC_MPRED_MV_WPTR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->cur_frame)); + + amvdec_write_dos(core, HEVC_MPRED_MV_RD_START_ADDR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->prev_frame)); + amvdec_write_dos(core, HEVC_MPRED_MV_RPTR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->prev_frame)); + + mpred_mv_rd_end_addr = + codec_vp9_get_frame_mv_paddr(vp9, vp9->prev_frame) + + (vp9->lcu_total * MV_MEM_UNIT); + amvdec_write_dos(core, HEVC_MPRED_MV_RD_END_ADDR, mpred_mv_rd_end_addr); +} + +static void codec_vp9_update_next_ref(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + u32 buf_idx = vp9->cur_frame->index; + int ref_index = 0; + int refresh_frame_flags; + int mask; + + refresh_frame_flags = vp9->cur_frame->type == KEY_FRAME ? + 0xff : param->p.refresh_frame_flags; + + for (mask = refresh_frame_flags; mask; mask >>= 1) { + pr_debug("mask=%08X; ref_index=%d\n", mask, ref_index); + if (mask & 1) + vp9->next_ref_frame_map[ref_index] = buf_idx; + else + vp9->next_ref_frame_map[ref_index] = + vp9->ref_frame_map[ref_index]; + + ++ref_index; + } + + for (; ref_index < REF_FRAMES; ++ref_index) + vp9->next_ref_frame_map[ref_index] = + vp9->ref_frame_map[ref_index]; +} + +static void codec_vp9_save_refs(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int i; + + for (i = 0; i < REFS_PER_FRAME; ++i) { + const int ref = (param->p.ref_info >> + (((REFS_PER_FRAME - i - 1) * 4) + 1)) & 0x7; + + if (vp9->ref_frame_map[ref] < 0) + continue; + + pr_warn("%s: FIXME, would need to save ref %d\n", + __func__, vp9->ref_frame_map[ref]); + } +} + +static void codec_vp9_update_ref(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int ref_index = 0; + int mask; + int refresh_frame_flags; + + if (!vp9->cur_frame) + return; + + refresh_frame_flags = vp9->cur_frame->type == KEY_FRAME ? + 0xff : param->p.refresh_frame_flags; + + for (mask = refresh_frame_flags; mask; mask >>= 1) { + vp9->ref_frame_map[ref_index] = + vp9->next_ref_frame_map[ref_index]; + ++ref_index; + } + + if (param->p.show_existing_frame) + return; + + for (; ref_index < REF_FRAMES; ++ref_index) + vp9->ref_frame_map[ref_index] = + vp9->next_ref_frame_map[ref_index]; +} + +static struct vp9_frame *codec_vp9_get_frame_by_idx(struct codec_vp9 *vp9, + int idx) +{ + struct vp9_frame *frame; + + list_for_each_entry(frame, &vp9->ref_frames_list, list) { + if (frame->index == idx) + return frame; + } + + return NULL; +} + +static void codec_vp9_sync_ref(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int i; + + for (i = 0; i < REFS_PER_FRAME; ++i) { + const int ref = (param->p.ref_info >> + (((REFS_PER_FRAME - i - 1) * 4) + 1)) & 0x7; + const int idx = vp9->ref_frame_map[ref]; + + vp9->frame_refs[i] = codec_vp9_get_frame_by_idx(vp9, idx); + if (!vp9->frame_refs[i]) + pr_warn("%s: couldn't find VP9 ref %d\n", __func__, + idx); + } +} + +static void codec_vp9_set_refs(struct amvdec_session *sess, + struct codec_vp9 *vp9) +{ + struct amvdec_core *core = sess->core; + int i; + + for (i = 0; i < REFS_PER_FRAME; ++i) { + struct vp9_frame *frame = vp9->frame_refs[i]; + int id_y; + int id_u_v; + + if (!frame) + continue; + + if (codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) { + id_y = frame->index; + id_u_v = id_y; + } else { + id_y = frame->index * 2; + id_u_v = id_y + 1; + } + + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, + (id_u_v << 16) | (id_u_v << 8) | id_y); + } +} + +static void codec_vp9_set_mc(struct amvdec_session *sess, + struct codec_vp9 *vp9) +{ + struct amvdec_core *core = sess->core; + u32 scale = 0; + u32 sz; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + codec_vp9_set_refs(sess, vp9); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, + (16 << 8) | 1); + codec_vp9_set_refs(sess, vp9); + + amvdec_write_dos(core, VP9D_MPP_REFINFO_TBL_ACCCONFIG, BIT(2)); + for (i = 0; i < REFS_PER_FRAME; ++i) { + if (!vp9->frame_refs[i]) + continue; + + if (vp9->frame_refs[i]->width != vp9->width || + vp9->frame_refs[i]->height != vp9->height) + scale = 1; + + sz = amvdec_am21c_body_size(vp9->frame_refs[i]->width, + vp9->frame_refs[i]->height); + + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + vp9->frame_refs[i]->width); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + vp9->frame_refs[i]->height); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + (vp9->frame_refs[i]->width << 14) / + vp9->width); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + (vp9->frame_refs[i]->height << 14) / + vp9->height); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, sz >> 5); + } + + amvdec_write_dos(core, VP9D_MPP_REF_SCALE_ENBL, scale); +} + +static struct vp9_frame *codec_vp9_get_new_frame(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + union rpm_param *param = &vp9->rpm_param; + struct vb2_v4l2_buffer *vbuf; + struct vp9_frame *new_frame; + + new_frame = kzalloc(sizeof(*new_frame), GFP_KERNEL); + if (!new_frame) + return NULL; + + vbuf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx); + if (!vbuf) { + dev_err(sess->core->dev, "No dst buffer available\n"); + kfree(new_frame); + return NULL; + } + + while (codec_vp9_get_frame_by_idx(vp9, vbuf->vb2_buf.index)) { + struct vb2_v4l2_buffer *old_vbuf = vbuf; + + vbuf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx); + v4l2_m2m_buf_queue(sess->m2m_ctx, old_vbuf); + if (!vbuf) { + dev_err(sess->core->dev, "No dst buffer available\n"); + kfree(new_frame); + return NULL; + } + } + + new_frame->vbuf = vbuf; + new_frame->index = vbuf->vb2_buf.index; + new_frame->intra_only = param->p.intra_only; + new_frame->show = param->p.show_frame; + new_frame->type = param->p.frame_type; + new_frame->width = vp9->width; + new_frame->height = vp9->height; + list_add_tail(&new_frame->list, &vp9->ref_frames_list); + vp9->frames_num++; + + return new_frame; +} + +static void codec_vp9_show_existing_frame(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + + if (!param->p.show_existing_frame) + return; + + pr_debug("showing frame %u\n", param->p.frame_to_show_idx); +} + +static void codec_vp9_rm_noshow_frame(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + struct vp9_frame *tmp; + + list_for_each_entry(tmp, &vp9->ref_frames_list, list) { + if (tmp->show) + continue; + + pr_debug("rm noshow: %u\n", tmp->index); + v4l2_m2m_buf_queue(sess->m2m_ctx, tmp->vbuf); + list_del(&tmp->list); + kfree(tmp); + vp9->frames_num--; + return; + } +} + +static void codec_vp9_process_frame(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + union rpm_param *param = &vp9->rpm_param; + int intra_only; + + if (!param->p.show_frame) + codec_vp9_rm_noshow_frame(sess); + + vp9->cur_frame = codec_vp9_get_new_frame(sess); + if (!vp9->cur_frame) + return; + + pr_debug("frame %d: type: %08X; show_exist: %u; show: %u, intra_only: %u\n", + vp9->cur_frame->index, + param->p.frame_type, param->p.show_existing_frame, + param->p.show_frame, param->p.intra_only); + + if (param->p.frame_type != KEY_FRAME) + codec_vp9_sync_ref(vp9); + codec_vp9_update_next_ref(vp9); + codec_vp9_show_existing_frame(vp9); + + if (codec_hevc_use_mmu(core->platform->revision, sess->pixfmt_cap, + vp9->is_10bit)) + codec_hevc_fill_mmu_map(sess, &vp9->common, + &vp9->cur_frame->vbuf->vb2_buf); + + intra_only = param->p.show_frame ? 0 : param->p.intra_only; + + /* clear mpred (for keyframe only) */ + if (param->p.frame_type != KEY_FRAME && !intra_only) { + codec_vp9_set_mc(sess, vp9); + codec_vp9_set_mpred_mv(core, vp9); + } else { + amvdec_clear_dos_bits(core, HEVC_MPRED_CTRL4, BIT(6)); + } + + amvdec_write_dos(core, HEVC_PARSER_PICTURE_SIZE, + (vp9->height << 16) | vp9->width); + codec_vp9_set_sao(sess, &vp9->cur_frame->vbuf->vb2_buf); + + vp9_loop_filter_frame_init(core, &vp9->seg_4lf, + &vp9->lfi, &vp9->lf, + vp9->default_filt_lvl); + + /* ask uCode to start decoding */ + amvdec_write_dos(core, VP9_DEC_STATUS_REG, VP9_10B_DECODE_SLICE); +} + +static void codec_vp9_process_lf(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int i; + + vp9->lf.mode_ref_delta_enabled = param->p.mode_ref_delta_enabled; + vp9->lf.sharpness_level = param->p.sharpness_level; + vp9->default_filt_lvl = param->p.filter_level; + vp9->seg_4lf.enabled = param->p.seg_enabled; + vp9->seg_4lf.abs_delta = param->p.seg_abs_delta; + + for (i = 0; i < 4; i++) + vp9->lf.ref_deltas[i] = param->p.ref_deltas[i]; + + for (i = 0; i < 2; i++) + vp9->lf.mode_deltas[i] = param->p.mode_deltas[i]; + + for (i = 0; i < MAX_SEGMENTS; i++) + vp9->seg_4lf.feature_mask[i] = + (param->p.seg_lf_info[i] & 0x8000) ? + (1 << SEG_LVL_ALT_LF) : 0; + + for (i = 0; i < MAX_SEGMENTS; i++) + vp9->seg_4lf.feature_data[i][SEG_LVL_ALT_LF] = + (param->p.seg_lf_info[i] & 0x100) ? + -(param->p.seg_lf_info[i] & 0x3f) + : (param->p.seg_lf_info[i] & 0x3f); +} + +static void codec_vp9_resume(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + + mutex_lock(&vp9->lock); + if (codec_hevc_setup_buffers(sess, &vp9->common, vp9->is_10bit)) { + mutex_unlock(&vp9->lock); + amvdec_abort(sess); + return; + } + + codec_vp9_setup_workspace(sess, vp9); + codec_hevc_setup_decode_head(sess, vp9->is_10bit); + codec_vp9_process_lf(vp9); + codec_vp9_process_frame(sess); + + mutex_unlock(&vp9->lock); +} + +/* + * The RPM section within the workspace contains + * many information regarding the parsed bitstream + */ +static void codec_vp9_fetch_rpm(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + u16 *rpm_vaddr = vp9->workspace_vaddr + RPM_OFFSET; + int i, j; + + for (i = 0; i < RPM_BUF_SIZE; i += 4) + for (j = 0; j < 4; j++) + vp9->rpm_param.l.data[i + j] = rpm_vaddr[i + 3 - j]; +} + +static int codec_vp9_process_rpm(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int src_changed = 0; + int is_10bit = 0; + int pic_width_64 = ALIGN(param->p.width, 64); + int pic_height_32 = ALIGN(param->p.height, 32); + int pic_width_lcu = (pic_width_64 % LCU_SIZE) ? + pic_width_64 / LCU_SIZE + 1 + : pic_width_64 / LCU_SIZE; + int pic_height_lcu = (pic_height_32 % LCU_SIZE) ? + pic_height_32 / LCU_SIZE + 1 + : pic_height_32 / LCU_SIZE; + vp9->lcu_total = pic_width_lcu * pic_height_lcu; + + if (param->p.bit_depth == 10) + is_10bit = 1; + + if (vp9->width != param->p.width || vp9->height != param->p.height || + vp9->is_10bit != is_10bit) + src_changed = 1; + + vp9->width = param->p.width; + vp9->height = param->p.height; + vp9->is_10bit = is_10bit; + + pr_debug("width: %u; height: %u; is_10bit: %d; src_changed: %d\n", + vp9->width, vp9->height, is_10bit, src_changed); + + return src_changed; +} + +static bool codec_vp9_is_ref(struct codec_vp9 *vp9, struct vp9_frame *frame) +{ + int i; + + for (i = 0; i < REF_FRAMES; ++i) + if (vp9->ref_frame_map[i] == frame->index) + return true; + + return false; +} + +static void codec_vp9_show_frame(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + struct vp9_frame *tmp, *n; + + list_for_each_entry_safe(tmp, n, &vp9->ref_frames_list, list) { + if (!tmp->show || tmp == vp9->cur_frame) + continue; + + if (!tmp->done) { + pr_debug("Doning %u\n", tmp->index); + amvdec_dst_buf_done(sess, tmp->vbuf, V4L2_FIELD_NONE); + tmp->done = 1; + vp9->frames_num--; + } + + if (codec_vp9_is_ref(vp9, tmp) || tmp == vp9->prev_frame) + continue; + + pr_debug("deleting %d\n", tmp->index); + list_del(&tmp->list); + kfree(tmp); + } +} + +static void vp9_tree_merge_probs(unsigned int *prev_prob, + unsigned int *cur_prob, + int coef_node_start, int tree_left, + int tree_right, + int tree_i, int node) +{ + int prob_32, prob_res, prob_shift; + int pre_prob, new_prob; + int den, m_count, get_prob, factor; + + prob_32 = prev_prob[coef_node_start / 4 * 2]; + prob_res = coef_node_start & 3; + prob_shift = prob_res * 8; + pre_prob = (prob_32 >> prob_shift) & 0xff; + + den = tree_left + tree_right; + + if (den == 0) { + new_prob = pre_prob; + } else { + m_count = den < MODE_MV_COUNT_SAT ? den : MODE_MV_COUNT_SAT; + get_prob = + clip_prob(div_r32(((int64_t)tree_left * 256 + + (den >> 1)), + den)); + + /* weighted_prob */ + factor = count_to_update_factor[m_count]; + new_prob = round_power_of_two(pre_prob * (256 - factor) + + get_prob * factor, 8); + } + + cur_prob[coef_node_start / 4 * 2] = + (cur_prob[coef_node_start / 4 * 2] & (~(0xff << prob_shift))) | + (new_prob << prob_shift); +} + +static void adapt_coef_probs_cxt(unsigned int *prev_prob, + unsigned int *cur_prob, + unsigned int *count, + int update_factor, + int cxt_num, + int coef_cxt_start, + int coef_count_cxt_start) +{ + int prob_32, prob_res, prob_shift; + int pre_prob, new_prob; + int num, den, m_count, get_prob, factor; + int node, coef_node_start; + int count_sat = 24; + int cxt; + + for (cxt = 0; cxt < cxt_num; cxt++) { + const int n0 = count[coef_count_cxt_start]; + const int n1 = count[coef_count_cxt_start + 1]; + const int n2 = count[coef_count_cxt_start + 2]; + const int neob = count[coef_count_cxt_start + 3]; + const int nneob = count[coef_count_cxt_start + 4]; + const unsigned int branch_ct[3][2] = { + { neob, nneob }, + { n0, n1 + n2 }, + { n1, n2 } + }; + + coef_node_start = coef_cxt_start; + for (node = 0 ; node < 3 ; node++) { + prob_32 = prev_prob[coef_node_start / 4 * 2]; + prob_res = coef_node_start & 3; + prob_shift = prob_res * 8; + pre_prob = (prob_32 >> prob_shift) & 0xff; + + /* get binary prob */ + num = branch_ct[node][0]; + den = branch_ct[node][0] + branch_ct[node][1]; + m_count = den < count_sat ? den : count_sat; + + get_prob = (den == 0) ? + 128u : + clip_prob(div_r32(((int64_t)num * 256 + + (den >> 1)), den)); + + factor = update_factor * m_count / count_sat; + new_prob = + round_power_of_two(pre_prob * (256 - factor) + + get_prob * factor, 8); + + cur_prob[coef_node_start / 4 * 2] = + (cur_prob[coef_node_start / 4 * 2] & + (~(0xff << prob_shift))) | + (new_prob << prob_shift); + + coef_node_start += 1; + } + + coef_cxt_start = coef_cxt_start + 3; + coef_count_cxt_start = coef_count_cxt_start + 5; + } +} + +static void adapt_coef_probs(int prev_kf, int cur_kf, int pre_fc, + unsigned int *prev_prob, unsigned int *cur_prob, + unsigned int *count) +{ + int tx_size, coef_tx_size_start, coef_count_tx_size_start; + int plane, coef_plane_start, coef_count_plane_start; + int type, coef_type_start, coef_count_type_start; + int band, coef_band_start, coef_count_band_start; + int cxt_num; + int coef_cxt_start, coef_count_cxt_start; + int node, coef_node_start, coef_count_node_start; + + int tree_i, tree_left, tree_right; + int mvd_i; + + int update_factor = cur_kf ? 112 : (prev_kf ? 128 : 112); + + int prob_32; + int prob_res; + int prob_shift; + int pre_prob; + + int den; + int get_prob; + int m_count; + int factor; + + int new_prob; + + for (tx_size = 0 ; tx_size < 4 ; tx_size++) { + coef_tx_size_start = VP9_COEF_START + + tx_size * 4 * VP9_COEF_SIZE_ONE_SET; + coef_count_tx_size_start = VP9_COEF_COUNT_START + + tx_size * 4 * VP9_COEF_COUNT_SIZE_ONE_SET; + coef_plane_start = coef_tx_size_start; + coef_count_plane_start = coef_count_tx_size_start; + + for (plane = 0 ; plane < 2 ; plane++) { + coef_type_start = coef_plane_start; + coef_count_type_start = coef_count_plane_start; + + for (type = 0 ; type < 2 ; type++) { + coef_band_start = coef_type_start; + coef_count_band_start = coef_count_type_start; + + for (band = 0 ; band < 6 ; band++) { + if (band == 0) + cxt_num = 3; + else + cxt_num = 6; + coef_cxt_start = coef_band_start; + coef_count_cxt_start = + coef_count_band_start; + + adapt_coef_probs_cxt(prev_prob, + cur_prob, + count, + update_factor, + cxt_num, + coef_cxt_start, + coef_count_cxt_start); + + if (band == 0) { + coef_band_start += 10; + coef_count_band_start += 15; + } else { + coef_band_start += 18; + coef_count_band_start += 30; + } + } + coef_type_start += VP9_COEF_SIZE_ONE_SET; + coef_count_type_start += + VP9_COEF_COUNT_SIZE_ONE_SET; + } + + coef_plane_start += 2 * VP9_COEF_SIZE_ONE_SET; + coef_count_plane_start += + 2 * VP9_COEF_COUNT_SIZE_ONE_SET; + } + } + + if (cur_kf == 0) { + /* mode_mv_merge_probs - merge_intra_inter_prob */ + for (coef_count_node_start = VP9_INTRA_INTER_COUNT_START; + coef_count_node_start < (VP9_MV_CLASS0_HP_1_COUNT_START + + VP9_MV_CLASS0_HP_1_COUNT_SIZE); + coef_count_node_start += 2) { + if (coef_count_node_start == + VP9_INTRA_INTER_COUNT_START) + coef_node_start = VP9_INTRA_INTER_START; + else if (coef_count_node_start == + VP9_COMP_INTER_COUNT_START) + coef_node_start = VP9_COMP_INTER_START; + else if (coef_count_node_start == + VP9_TX_MODE_COUNT_START) + coef_node_start = VP9_TX_MODE_START; + else if (coef_count_node_start == + VP9_SKIP_COUNT_START) + coef_node_start = VP9_SKIP_START; + else if (coef_count_node_start == + VP9_MV_SIGN_0_COUNT_START) + coef_node_start = VP9_MV_SIGN_0_START; + else if (coef_count_node_start == + VP9_MV_SIGN_1_COUNT_START) + coef_node_start = VP9_MV_SIGN_1_START; + else if (coef_count_node_start == + VP9_MV_BITS_0_COUNT_START) + coef_node_start = VP9_MV_BITS_0_START; + else if (coef_count_node_start == + VP9_MV_BITS_1_COUNT_START) + coef_node_start = VP9_MV_BITS_1_START; + else if (coef_count_node_start == + VP9_MV_CLASS0_HP_0_COUNT_START) + coef_node_start = VP9_MV_CLASS0_HP_0_START; + + den = count[coef_count_node_start] + + count[coef_count_node_start + 1]; + + prob_32 = prev_prob[coef_node_start / 4 * 2]; + prob_res = coef_node_start & 3; + prob_shift = prob_res * 8; + pre_prob = (prob_32 >> prob_shift) & 0xff; + + if (den == 0) { + new_prob = pre_prob; + } else { + m_count = den < MODE_MV_COUNT_SAT ? + den : MODE_MV_COUNT_SAT; + get_prob = + clip_prob(div_r32(((int64_t) + count[coef_count_node_start] * 256 + + (den >> 1)), + den)); + + /* weighted prob */ + factor = count_to_update_factor[m_count]; + new_prob = + round_power_of_two(pre_prob * + (256 - factor) + + get_prob * factor, + 8); + } + + cur_prob[coef_node_start / 4 * 2] = + (cur_prob[coef_node_start / 4 * 2] & + (~(0xff << prob_shift))) | + (new_prob << prob_shift); + + coef_node_start = coef_node_start + 1; + } + + coef_node_start = VP9_INTER_MODE_START; + coef_count_node_start = VP9_INTER_MODE_COUNT_START; + for (tree_i = 0 ; tree_i < 7 ; tree_i++) { + for (node = 0 ; node < 3 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 2: + tree_left = count[start + 1]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 3]; + break; + default: + tree_left = count[start + 2]; + tree_right = count[start + 0] + + count[start + 1] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + + coef_count_node_start = coef_count_node_start + 4; + } + + coef_node_start = VP9_IF_Y_MODE_START; + coef_count_node_start = VP9_IF_Y_MODE_COUNT_START; + for (tree_i = 0 ; tree_i < 14 ; tree_i++) { + for (node = 0 ; node < 9 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 8: + tree_left = + count[start + D153_PRED]; + tree_right = + count[start + D207_PRED]; + break; + case 7: + tree_left = + count[start + D63_PRED]; + tree_right = + count[start + D207_PRED] + + count[start + D153_PRED]; + break; + case 6: + tree_left = + count[start + D45_PRED]; + tree_right = + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + case 5: + tree_left = + count[start + D135_PRED]; + tree_right = + count[start + D117_PRED]; + break; + case 4: + tree_left = + count[start + H_PRED]; + tree_right = + count[start + D117_PRED] + + count[start + D135_PRED]; + break; + case 3: + tree_left = + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED]; + tree_right = + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + case 2: + tree_left = + count[start + V_PRED]; + tree_right = + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED] + + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + case 1: + tree_left = + count[start + TM_PRED]; + tree_right = + count[start + V_PRED] + + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED] + + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + default: + tree_left = + count[start + DC_PRED]; + tree_right = + count[start + TM_PRED] + + count[start + V_PRED] + + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED] + + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = coef_count_node_start + 10; + } + + coef_node_start = VP9_PARTITION_P_START; + coef_count_node_start = VP9_PARTITION_P_COUNT_START; + for (tree_i = 0 ; tree_i < 16 ; tree_i++) { + for (node = 0 ; node < 3 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 2: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + + coef_count_node_start = coef_count_node_start + 4; + } + + coef_node_start = VP9_INTERP_START; + coef_count_node_start = VP9_INTERP_COUNT_START; + for (tree_i = 0 ; tree_i < 4 ; tree_i++) { + for (node = 0 ; node < 2 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = coef_count_node_start + 3; + } + + coef_node_start = VP9_MV_JOINTS_START; + coef_count_node_start = VP9_MV_JOINTS_COUNT_START; + for (tree_i = 0 ; tree_i < 1 ; tree_i++) { + for (node = 0 ; node < 3 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 2: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = coef_count_node_start + 4; + } + + for (mvd_i = 0 ; mvd_i < 2 ; mvd_i++) { + coef_node_start = mvd_i ? VP9_MV_CLASSES_1_START : + VP9_MV_CLASSES_0_START; + coef_count_node_start = mvd_i ? + VP9_MV_CLASSES_1_COUNT_START : + VP9_MV_CLASSES_0_COUNT_START; + tree_i = 0; + for (node = 0; node < 10; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 9: + tree_left = count[start + 9]; + tree_right = count[start + 10]; + break; + case 8: + tree_left = count[start + 7]; + tree_right = count[start + 8]; + break; + case 7: + tree_left = count[start + 7] + + count[start + 8]; + tree_right = count[start + 9] + + count[start + 10]; + break; + case 6: + tree_left = count[start + 6]; + tree_right = count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + case 5: + tree_left = count[start + 4]; + tree_right = count[start + 5]; + break; + case 4: + tree_left = count[start + 4] + + count[start + 5]; + tree_right = count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + case 3: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 2: + tree_left = count[start + 2] + + count[start + 3]; + tree_right = count[start + 4] + + count[start + 5] + + count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3] + + count[start + 4] + + count[start + 5] + + count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3] + + count[start + 4] + + count[start + 5] + + count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + + coef_node_start = mvd_i ? VP9_MV_CLASS0_1_START : + VP9_MV_CLASS0_0_START; + coef_count_node_start = mvd_i ? + VP9_MV_CLASS0_1_COUNT_START : + VP9_MV_CLASS0_0_COUNT_START; + tree_i = 0; + node = 0; + tree_left = count[coef_count_node_start + 0]; + tree_right = count[coef_count_node_start + 1]; + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + coef_node_start = mvd_i ? VP9_MV_CLASS0_FP_1_START : + VP9_MV_CLASS0_FP_0_START; + coef_count_node_start = mvd_i ? + VP9_MV_CLASS0_FP_1_COUNT_START : + VP9_MV_CLASS0_FP_0_COUNT_START; + + for (tree_i = 0; tree_i < 3; tree_i++) { + for (node = 0; node < 3; node++) { + unsigned int start = + coef_count_node_start; + switch (node) { + case 2: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, + cur_prob, + coef_node_start, + tree_left, + tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = + coef_count_node_start + 4; + } + } + } +} + +static irqreturn_t codec_vp9_threaded_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + u32 dec_status = amvdec_read_dos(core, VP9_DEC_STATUS_REG); + u32 prob_status = amvdec_read_dos(core, VP9_ADAPT_PROB_REG); + int i; + + if (!vp9) + return IRQ_HANDLED; + + mutex_lock(&vp9->lock); + if (dec_status != VP9_HEAD_PARSER_DONE) { + dev_err(core->dev_dec, "Unrecognized dec_status: %08X\n", + dec_status); + amvdec_abort(sess); + goto unlock; + } + + pr_debug("ISR: %08X;%08X\n", dec_status, prob_status); + sess->keyframe_found = 1; + + if ((prob_status & 0xff) == 0xfd && vp9->cur_frame) { + /* VP9_REQ_ADAPT_PROB */ + u8 *prev_prob_b = ((u8 *)vp9->workspace_vaddr + + PROB_OFFSET) + + ((prob_status >> 8) * 0x1000); + u8 *cur_prob_b = ((u8 *)vp9->workspace_vaddr + + PROB_OFFSET) + 0x4000; + u8 *count_b = (u8 *)vp9->workspace_vaddr + + COUNT_OFFSET; + int last_frame_type = vp9->prev_frame ? + vp9->prev_frame->type : + KEY_FRAME; + + adapt_coef_probs(last_frame_type == KEY_FRAME, + vp9->cur_frame->type == KEY_FRAME ? 1 : 0, + prob_status >> 8, + (unsigned int *)prev_prob_b, + (unsigned int *)cur_prob_b, + (unsigned int *)count_b); + + memcpy(prev_prob_b, cur_prob_b, ADAPT_PROB_SIZE); + amvdec_write_dos(core, VP9_ADAPT_PROB_REG, 0); + } + + /* Invalidate first 3 refs */ + for (i = 0; i < REFS_PER_FRAME ; ++i) + vp9->frame_refs[i] = NULL; + + vp9->prev_frame = vp9->cur_frame; + codec_vp9_update_ref(vp9); + + codec_vp9_fetch_rpm(sess); + if (codec_vp9_process_rpm(vp9)) { + amvdec_src_change(sess, vp9->width, vp9->height, 16); + + /* No frame is actually processed */ + vp9->cur_frame = NULL; + + /* Show the remaining frame */ + codec_vp9_show_frame(sess); + + /* FIXME: Save refs for resized frame */ + if (vp9->frames_num) + codec_vp9_save_refs(vp9); + + goto unlock; + } + + codec_vp9_process_lf(vp9); + codec_vp9_process_frame(sess); + codec_vp9_show_frame(sess); + +unlock: + mutex_unlock(&vp9->lock); + return IRQ_HANDLED; +} + +static irqreturn_t codec_vp9_isr(struct amvdec_session *sess) +{ + return IRQ_WAKE_THREAD; +} + +struct amvdec_codec_ops codec_vp9_ops = { + .start = codec_vp9_start, + .stop = codec_vp9_stop, + .isr = codec_vp9_isr, + .threaded_isr = codec_vp9_threaded_isr, + .num_pending_bufs = codec_vp9_num_pending_bufs, + .drain = codec_vp9_flush_output, + .resume = codec_vp9_resume, +}; diff --git a/drivers/staging/media/meson/vdec/codec_vp9.h b/drivers/staging/media/meson/vdec/codec_vp9.h new file mode 100644 index 000000000000..62db65a2b939 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_vp9.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Maxime Jourdan <maxi.jourdan@wanadoo.fr> + */ + +#ifndef __MESON_VDEC_CODEC_VP9_H_ +#define __MESON_VDEC_CODEC_VP9_H_ + +#include "vdec.h" + +extern struct amvdec_codec_ops codec_vp9_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/esparser.c b/drivers/staging/media/meson/vdec/esparser.c index 95102a4bdc62..db7022707ff8 100644 --- a/drivers/staging/media/meson/vdec/esparser.c +++ b/drivers/staging/media/meson/vdec/esparser.c @@ -52,6 +52,7 @@ #define PARSER_VIDEO_HOLE 0x90 #define SEARCH_PATTERN_LEN 512 +#define VP9_HEADER_SIZE 16 static DECLARE_WAIT_QUEUE_HEAD(wq); static int search_done; @@ -74,27 +75,140 @@ static irqreturn_t esparser_isr(int irq, void *dev) return IRQ_HANDLED; } +/* + * VP9 frame headers need to be appended by a 16-byte long + * Amlogic custom header + */ +static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) +{ + u8 *dp; + u8 marker; + int dsize; + int num_frames, cur_frame; + int cur_mag, mag, mag_ptr; + int frame_size[8], tot_frame_size[8]; + int total_datasize = 0; + int new_frame_size; + unsigned char *old_header = NULL; + + dp = (uint8_t *)vb2_plane_vaddr(buf, 0); + dsize = vb2_get_plane_payload(buf, 0); + + if (dsize == vb2_plane_size(buf, 0)) { + dev_warn(core->dev, "%s: unable to update header\n", __func__); + return 0; + } + + marker = dp[dsize - 1]; + if ((marker & 0xe0) == 0xc0) { + num_frames = (marker & 0x7) + 1; + mag = ((marker >> 3) & 0x3) + 1; + mag_ptr = dsize - mag * num_frames - 2; + if (dp[mag_ptr] != marker) + return 0; + + mag_ptr++; + for (cur_frame = 0; cur_frame < num_frames; cur_frame++) { + frame_size[cur_frame] = 0; + for (cur_mag = 0; cur_mag < mag; cur_mag++) { + frame_size[cur_frame] |= + (dp[mag_ptr] << (cur_mag * 8)); + mag_ptr++; + } + if (cur_frame == 0) + tot_frame_size[cur_frame] = + frame_size[cur_frame]; + else + tot_frame_size[cur_frame] = + tot_frame_size[cur_frame - 1] + + frame_size[cur_frame]; + total_datasize += frame_size[cur_frame]; + } + } else { + num_frames = 1; + frame_size[0] = dsize; + tot_frame_size[0] = dsize; + total_datasize = dsize; + } + + new_frame_size = total_datasize + num_frames * VP9_HEADER_SIZE; + + if (new_frame_size >= vb2_plane_size(buf, 0)) { + dev_warn(core->dev, "%s: unable to update header\n", __func__); + return 0; + } + + for (cur_frame = num_frames - 1; cur_frame >= 0; cur_frame--) { + int framesize = frame_size[cur_frame]; + int framesize_header = framesize + 4; + int oldframeoff = tot_frame_size[cur_frame] - framesize; + int outheaderoff = oldframeoff + cur_frame * VP9_HEADER_SIZE; + u8 *fdata = dp + outheaderoff; + u8 *old_framedata = dp + oldframeoff; + + memmove(fdata + VP9_HEADER_SIZE, old_framedata, framesize); + + fdata[0] = (framesize_header >> 24) & 0xff; + fdata[1] = (framesize_header >> 16) & 0xff; + fdata[2] = (framesize_header >> 8) & 0xff; + fdata[3] = (framesize_header >> 0) & 0xff; + fdata[4] = ((framesize_header >> 24) & 0xff) ^ 0xff; + fdata[5] = ((framesize_header >> 16) & 0xff) ^ 0xff; + fdata[6] = ((framesize_header >> 8) & 0xff) ^ 0xff; + fdata[7] = ((framesize_header >> 0) & 0xff) ^ 0xff; + fdata[8] = 0; + fdata[9] = 0; + fdata[10] = 0; + fdata[11] = 1; + fdata[12] = 'A'; + fdata[13] = 'M'; + fdata[14] = 'L'; + fdata[15] = 'V'; + + if (!old_header) { + /* nothing */ + } else if (old_header > fdata + 16 + framesize) { + dev_dbg(core->dev, "%s: data has gaps, setting to 0\n", + __func__); + memset(fdata + 16 + framesize, 0, + (old_header - fdata + 16 + framesize)); + } else if (old_header < fdata + 16 + framesize) { + dev_err(core->dev, "%s: data overwritten\n", __func__); + } + old_header = fdata; + } + + return new_frame_size; +} + /* Pad the packet to at least 4KiB bytes otherwise the VDEC unit won't trigger * ISRs. * Also append a start code 000001ff at the end to trigger * the ESPARSER interrupt. */ -static u32 esparser_pad_start_code(struct vb2_buffer *vb) +static u32 esparser_pad_start_code(struct amvdec_core *core, + struct vb2_buffer *vb, + u32 payload_size) { - u32 payload_size = vb2_get_plane_payload(vb, 0); u32 pad_size = 0; - u8 *vaddr = vb2_plane_vaddr(vb, 0) + payload_size; + u8 *vaddr = vb2_plane_vaddr(vb, 0); if (payload_size < ESPARSER_MIN_PACKET_SIZE) { pad_size = ESPARSER_MIN_PACKET_SIZE - payload_size; - memset(vaddr, 0, pad_size); + memset(vaddr + payload_size, 0, pad_size); + } + + if ((payload_size + pad_size + SEARCH_PATTERN_LEN) > + vb2_plane_size(vb, 0)) { + dev_warn(core->dev, "%s: unable to pad start code\n", __func__); + return pad_size; } - memset(vaddr + pad_size, 0, SEARCH_PATTERN_LEN); - vaddr[pad_size] = 0x00; - vaddr[pad_size + 1] = 0x00; - vaddr[pad_size + 2] = 0x01; - vaddr[pad_size + 3] = 0xff; + memset(vaddr + payload_size + pad_size, 0, SEARCH_PATTERN_LEN); + vaddr[payload_size + pad_size] = 0x00; + vaddr[payload_size + pad_size + 1] = 0x00; + vaddr[payload_size + pad_size + 2] = 0x01; + vaddr[payload_size + pad_size + 3] = 0xff; return pad_size; } @@ -181,30 +295,60 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) struct vb2_buffer *vb = &vbuf->vb2_buf; struct amvdec_core *core = sess->core; struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; - u32 num_dst_bufs = 0; u32 payload_size = vb2_get_plane_payload(vb, 0); dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0); + u32 num_dst_bufs = 0; u32 offset; u32 pad_size; - if (codec_ops->num_pending_bufs) - num_dst_bufs = codec_ops->num_pending_bufs(sess); - - num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); - - if (esparser_vififo_get_free_space(sess) < payload_size || - atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs) + /* + * When max ref frame is held by VP9, this should be -= 3 to prevent a + * shortage of CAPTURE buffers on the decoder side. + * For the future, a good enhancement of the way this is handled could + * be to notify new capture buffers to the decoding modules, so that + * they could pause when there is no capture buffer available and + * resume on this notification. + */ + if (sess->fmt_out->pixfmt == V4L2_PIX_FMT_VP9) { + if (codec_ops->num_pending_bufs) + num_dst_bufs = codec_ops->num_pending_bufs(sess); + + num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); + if (sess->fmt_out->pixfmt == V4L2_PIX_FMT_VP9) + num_dst_bufs -= 3; + + if (esparser_vififo_get_free_space(sess) < payload_size || + atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs) + return -EAGAIN; + } else if (esparser_vififo_get_free_space(sess) < payload_size) { return -EAGAIN; + } v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf); offset = esparser_get_offset(sess); - amvdec_add_ts_reorder(sess, vb->timestamp, offset); - dev_dbg(core->dev, "esparser: ts = %llu pld_size = %u offset = %08X\n", - vb->timestamp, payload_size, offset); + amvdec_add_ts(sess, vb->timestamp, vbuf->timecode, offset, vbuf->flags); + dev_dbg(core->dev, "esparser: ts = %llu pld_size = %u offset = %08X flags = %08X\n", + vb->timestamp, payload_size, offset, vbuf->flags); + + vbuf->flags = 0; + vbuf->field = V4L2_FIELD_NONE; + vbuf->sequence = sess->sequence_out++; + + if (sess->fmt_out->pixfmt == V4L2_PIX_FMT_VP9) { + payload_size = vp9_update_header(core, vb); - pad_size = esparser_pad_start_code(vb); + /* If unable to alter buffer to add headers */ + if (payload_size == 0) { + amvdec_remove_ts(sess, vb->timestamp); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + + return 0; + } + } + + pad_size = esparser_pad_start_code(core, vb, payload_size); ret = esparser_write_data(core, phy, payload_size + pad_size); if (ret <= 0) { @@ -216,19 +360,7 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) return 0; } - /* We need to wait until we parse the first keyframe. - * All buffers prior to the first keyframe must be dropped. - */ - if (!sess->keyframe_found) - usleep_range(1000, 2000); - - if (sess->keyframe_found) - atomic_inc(&sess->esparser_queued_bufs); - else - amvdec_remove_ts(sess, vb->timestamp); - - vbuf->flags = 0; - vbuf->field = V4L2_FIELD_NONE; + atomic_inc(&sess->esparser_queued_bufs); v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); return 0; diff --git a/drivers/staging/media/meson/vdec/hevc_regs.h b/drivers/staging/media/meson/vdec/hevc_regs.h new file mode 100644 index 000000000000..0392f41a1eed --- /dev/null +++ b/drivers/staging/media/meson/vdec/hevc_regs.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#ifndef __MESON_VDEC_HEVC_REGS_H_ +#define __MESON_VDEC_HEVC_REGS_H_ + +#define HEVC_ASSIST_MMU_MAP_ADDR 0xc024 + +#define HEVC_ASSIST_MBOX1_CLR_REG 0xc1d4 +#define HEVC_ASSIST_MBOX1_MASK 0xc1d8 + +#define HEVC_ASSIST_SCRATCH_0 0xc300 +#define HEVC_ASSIST_SCRATCH_1 0xc304 +#define HEVC_ASSIST_SCRATCH_2 0xc308 +#define HEVC_ASSIST_SCRATCH_3 0xc30c +#define HEVC_ASSIST_SCRATCH_4 0xc310 +#define HEVC_ASSIST_SCRATCH_5 0xc314 +#define HEVC_ASSIST_SCRATCH_6 0xc318 +#define HEVC_ASSIST_SCRATCH_7 0xc31c +#define HEVC_ASSIST_SCRATCH_8 0xc320 +#define HEVC_ASSIST_SCRATCH_9 0xc324 +#define HEVC_ASSIST_SCRATCH_A 0xc328 +#define HEVC_ASSIST_SCRATCH_B 0xc32c +#define HEVC_ASSIST_SCRATCH_C 0xc330 +#define HEVC_ASSIST_SCRATCH_D 0xc334 +#define HEVC_ASSIST_SCRATCH_E 0xc338 +#define HEVC_ASSIST_SCRATCH_F 0xc33c +#define HEVC_ASSIST_SCRATCH_G 0xc340 +#define HEVC_ASSIST_SCRATCH_H 0xc344 +#define HEVC_ASSIST_SCRATCH_I 0xc348 +#define HEVC_ASSIST_SCRATCH_J 0xc34c +#define HEVC_ASSIST_SCRATCH_K 0xc350 +#define HEVC_ASSIST_SCRATCH_L 0xc354 +#define HEVC_ASSIST_SCRATCH_M 0xc358 +#define HEVC_ASSIST_SCRATCH_N 0xc35c + +#define HEVC_PARSER_VERSION 0xc400 +#define HEVC_STREAM_CONTROL 0xc404 +#define HEVC_STREAM_START_ADDR 0xc408 +#define HEVC_STREAM_END_ADDR 0xc40c +#define HEVC_STREAM_WR_PTR 0xc410 +#define HEVC_STREAM_RD_PTR 0xc414 +#define HEVC_STREAM_LEVEL 0xc418 +#define HEVC_STREAM_FIFO_CTL 0xc41c +#define HEVC_SHIFT_CONTROL 0xc420 +#define HEVC_SHIFT_STARTCODE 0xc424 +#define HEVC_SHIFT_EMULATECODE 0xc428 +#define HEVC_SHIFT_STATUS 0xc42c +#define HEVC_SHIFTED_DATA 0xc430 +#define HEVC_SHIFT_BYTE_COUNT 0xc434 +#define HEVC_SHIFT_COMMAND 0xc438 +#define HEVC_ELEMENT_RESULT 0xc43c +#define HEVC_CABAC_CONTROL 0xc440 +#define HEVC_PARSER_SLICE_INFO 0xc444 +#define HEVC_PARSER_CMD_WRITE 0xc448 +#define HEVC_PARSER_CORE_CONTROL 0xc44c +#define HEVC_PARSER_CMD_FETCH 0xc450 +#define HEVC_PARSER_CMD_STATUS 0xc454 +#define HEVC_PARSER_LCU_INFO 0xc458 +#define HEVC_PARSER_HEADER_INFO 0xc45c +#define HEVC_PARSER_INT_CONTROL 0xc480 +#define HEVC_PARSER_INT_STATUS 0xc484 +#define HEVC_PARSER_IF_CONTROL 0xc488 +#define HEVC_PARSER_PICTURE_SIZE 0xc48c +#define HEVC_PARSER_LCU_START 0xc490 +#define HEVC_PARSER_HEADER_INFO2 0xc494 +#define HEVC_PARSER_QUANT_READ 0xc498 +#define HEVC_PARSER_RESERVED_27 0xc49c +#define HEVC_PARSER_CMD_SKIP_0 0xc4a0 +#define HEVC_PARSER_CMD_SKIP_1 0xc4a4 +#define HEVC_PARSER_CMD_SKIP_2 0xc4a8 +#define HEVC_SAO_IF_STATUS 0xc4c0 +#define HEVC_SAO_IF_DATA_Y 0xc4c4 +#define HEVC_SAO_IF_DATA_U 0xc4c8 +#define HEVC_SAO_IF_DATA_V 0xc4cc +#define HEVC_STREAM_SWAP_ADDR 0xc4d0 +#define HEVC_STREAM_SWAP_CTRL 0xc4d4 +#define HEVC_IQIT_IF_WAIT_CNT 0xc4d8 +#define HEVC_MPRED_IF_WAIT_CNT 0xc4dc +#define HEVC_SAO_IF_WAIT_CNT 0xc4e0 + +#define HEVC_MPRED_VERSION 0xc800 +#define HEVC_MPRED_CTRL0 0xc804 + #define MPRED_CTRL0_NEW_PIC BIT(2) + #define MPRED_CTRL0_NEW_TILE BIT(3) + #define MPRED_CTRL0_NEW_SLI_SEG BIT(4) + #define MPRED_CTRL0_TMVP BIT(5) + #define MPRED_CTRL0_LDC BIT(6) + #define MPRED_CTRL0_COL_FROM_L0 BIT(7) + #define MPRED_CTRL0_ABOVE_EN BIT(9) + #define MPRED_CTRL0_MV_WR_EN BIT(10) + #define MPRED_CTRL0_MV_RD_EN BIT(11) + #define MPRED_CTRL0_BUF_LINEAR BIT(13) +#define HEVC_MPRED_CTRL1 0xc808 +#define HEVC_MPRED_INT_EN 0xc80c +#define HEVC_MPRED_INT_STATUS 0xc810 +#define HEVC_MPRED_PIC_SIZE 0xc814 +#define HEVC_MPRED_PIC_SIZE_LCU 0xc818 +#define HEVC_MPRED_TILE_START 0xc81c +#define HEVC_MPRED_TILE_SIZE_LCU 0xc820 +#define HEVC_MPRED_REF_NUM 0xc824 +#define HEVC_MPRED_REF_EN_L0 0xc830 +#define HEVC_MPRED_REF_EN_L1 0xc834 +#define HEVC_MPRED_COLREF_EN_L0 0xc838 +#define HEVC_MPRED_COLREF_EN_L1 0xc83c +#define HEVC_MPRED_AXI_WCTRL 0xc840 +#define HEVC_MPRED_AXI_RCTRL 0xc844 +#define HEVC_MPRED_ABV_START_ADDR 0xc848 +#define HEVC_MPRED_MV_WR_START_ADDR 0xc84c +#define HEVC_MPRED_MV_RD_START_ADDR 0xc850 +#define HEVC_MPRED_MV_WPTR 0xc854 +#define HEVC_MPRED_MV_RPTR 0xc858 +#define HEVC_MPRED_MV_WR_ROW_JUMP 0xc85c +#define HEVC_MPRED_MV_RD_ROW_JUMP 0xc860 +#define HEVC_MPRED_CURR_LCU 0xc864 +#define HEVC_MPRED_ABV_WPTR 0xc868 +#define HEVC_MPRED_ABV_RPTR 0xc86c +#define HEVC_MPRED_CTRL2 0xc870 +#define HEVC_MPRED_CTRL3 0xc874 +#define HEVC_MPRED_L0_REF00_POC 0xc880 +#define HEVC_MPRED_L1_REF00_POC 0xc8c0 + +#define HEVC_MPRED_CTRL4 0xc930 + +#define HEVC_MPRED_CUR_POC 0xc980 +#define HEVC_MPRED_COL_POC 0xc984 +#define HEVC_MPRED_MV_RD_END_ADDR 0xc988 + +#define HEVC_MSP 0xcc00 +#define HEVC_MPSR 0xcc04 +#define HEVC_MCPU_INTR_MSK 0xcc10 +#define HEVC_MCPU_INTR_REQ 0xcc14 +#define HEVC_CPSR 0xcc84 + +#define HEVC_IMEM_DMA_CTRL 0xcd00 +#define HEVC_IMEM_DMA_ADR 0xcd04 +#define HEVC_IMEM_DMA_COUNT 0xcd08 + +#define HEVCD_IPP_TOP_CNTL 0xd000 +#define HEVCD_IPP_LINEBUFF_BASE 0xd024 +#define HEVCD_IPP_AXIIF_CONFIG 0xd02c + +#define VP9D_MPP_REF_SCALE_ENBL 0xd104 +#define VP9D_MPP_REFINFO_TBL_ACCCONFIG 0xd108 +#define VP9D_MPP_REFINFO_DATA 0xd10c + +#define HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR 0xd180 +#define HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR 0xd184 +#define HEVCD_MPP_ANC2AXI_TBL_DATA 0xd190 + +#define HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR 0xd300 +#define HEVCD_MPP_ANC_CANVAS_DATA_ADDR 0xd304 +#define HEVCD_MPP_DECOMP_CTL1 0xd308 +#define HEVCD_MPP_DECOMP_CTL2 0xd30c +#define HEVCD_MCRCC_CTL1 0xd3c0 +#define HEVCD_MCRCC_CTL2 0xd3c4 +#define HEVCD_MCRCC_CTL3 0xd3c8 + +#define HEVC_DBLK_CFG0 0xd400 +#define HEVC_DBLK_CFG1 0xd404 +#define HEVC_DBLK_CFG2 0xd408 +#define HEVC_DBLK_CFG3 0xd40c +#define HEVC_DBLK_CFG4 0xd410 +#define HEVC_DBLK_CFG5 0xd414 +#define HEVC_DBLK_CFG6 0xd418 +#define HEVC_DBLK_CFG7 0xd41c +#define HEVC_DBLK_CFG8 0xd420 +#define HEVC_DBLK_CFG9 0xd424 +#define HEVC_DBLK_CFGA 0xd428 +#define HEVC_DBLK_STS0 0xd42c +#define HEVC_DBLK_CFGB 0xd42c +#define HEVC_DBLK_STS1 0xd430 +#define HEVC_DBLK_CFGE 0xd438 + +#define HEVC_SAO_VERSION 0xd800 +#define HEVC_SAO_CTRL0 0xd804 +#define HEVC_SAO_CTRL1 0xd808 +#define HEVC_SAO_PIC_SIZE 0xd814 +#define HEVC_SAO_PIC_SIZE_LCU 0xd818 +#define HEVC_SAO_TILE_START 0xd81c +#define HEVC_SAO_TILE_SIZE_LCU 0xd820 +#define HEVC_SAO_Y_START_ADDR 0xd82c +#define HEVC_SAO_Y_LENGTH 0xd830 +#define HEVC_SAO_C_START_ADDR 0xd834 +#define HEVC_SAO_C_LENGTH 0xd838 +#define HEVC_SAO_Y_WPTR 0xd83c +#define HEVC_SAO_C_WPTR 0xd840 +#define HEVC_SAO_ABV_START_ADDR 0xd844 +#define HEVC_SAO_VB_WR_START_ADDR 0xd848 +#define HEVC_SAO_VB_RD_START_ADDR 0xd84c +#define HEVC_SAO_ABV_WPTR 0xd850 +#define HEVC_SAO_ABV_RPTR 0xd854 +#define HEVC_SAO_VB_WPTR 0xd858 +#define HEVC_SAO_VB_RPTR 0xd85c +#define HEVC_SAO_CTRL2 0xd880 +#define HEVC_SAO_CTRL3 0xd884 +#define HEVC_SAO_CTRL4 0xd888 +#define HEVC_SAO_CTRL5 0xd88c +#define HEVC_SAO_CTRL6 0xd890 +#define HEVC_SAO_CTRL7 0xd894 +#define HEVC_CM_BODY_START_ADDR 0xd898 +#define HEVC_CM_BODY_LENGTH 0xd89c +#define HEVC_CM_HEADER_START_ADDR 0xd8a0 +#define HEVC_CM_HEADER_LENGTH 0xd8a4 +#define HEVC_CM_HEADER_OFFSET 0xd8ac +#define HEVC_SAO_MMU_VH0_ADDR 0xd8e8 +#define HEVC_SAO_MMU_VH1_ADDR 0xd8ec + +#define HEVC_IQIT_CLK_RST_CTRL 0xdc00 +#define HEVC_IQIT_SCALELUT_WR_ADDR 0xdc08 +#define HEVC_IQIT_SCALELUT_RD_ADDR 0xdc0c +#define HEVC_IQIT_SCALELUT_DATA 0xdc10 + +#define HEVC_PSCALE_CTRL 0xe444 + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 5c5dabed2f09..3040136ceb77 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -168,7 +168,10 @@ static void process_num_buffers(struct vb2_queue *q, { const struct amvdec_format *fmt_out = sess->fmt_out; unsigned int buffers_total = q->num_buffers + *num_buffers; + u32 min_buf_capture = v4l2_ctrl_g_ctrl(sess->ctrl_min_buf_capture); + if (q->num_buffers + *num_buffers < min_buf_capture) + *num_buffers = min_buf_capture - q->num_buffers; if (is_reqbufs && buffers_total < fmt_out->min_buffers) *num_buffers = fmt_out->min_buffers - q->num_buffers; if (buffers_total > fmt_out->max_buffers) @@ -193,7 +196,8 @@ static int vdec_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, if (*num_planes) { switch (q->type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - if (*num_planes != 1 || sizes[0] < output_size) + if (*num_planes != 1 || + sizes[0] < sess->src_buffer_size) return -EINVAL; break; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: @@ -224,7 +228,7 @@ static int vdec_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, switch (q->type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - sizes[0] = amvdec_get_output_size(sess); + sizes[0] = sess->src_buffer_size; *num_planes = 1; break; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: @@ -250,6 +254,7 @@ static int vdec_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, return -EINVAL; } + sess->changed_format = 1; return 0; } @@ -261,10 +266,11 @@ static void vdec_vb2_buf_queue(struct vb2_buffer *vb) v4l2_m2m_buf_queue(m2m_ctx, vbuf); - if (!sess->streamon_out || !sess->streamon_cap) + if (!sess->streamon_out) return; - if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + if (sess->streamon_cap && + vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && vdec_codec_needs_recycle(sess)) vdec_queue_recycle(sess, vb); @@ -289,16 +295,22 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) else sess->streamon_cap = 1; - if (!sess->streamon_out || !sess->streamon_cap) + if (!sess->streamon_out) return 0; if (sess->status == STATUS_NEEDS_RESUME && - q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + sess->changed_format) { codec_ops->resume(sess); sess->status = STATUS_RUNNING; return 0; } + if (sess->status == STATUS_RUNNING || + sess->status == STATUS_NEEDS_RESUME || + sess->status == STATUS_INIT) + return 0; + sess->vififo_size = SIZE_VIFIFO; sess->vififo_vaddr = dma_alloc_coherent(sess->core->dev, sess->vififo_size, @@ -323,13 +335,14 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) goto vififo_free; sess->sequence_cap = 0; + sess->sequence_out = 0; if (vdec_codec_needs_recycle(sess)) sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, "vdec_recycle"); - sess->status = STATUS_RUNNING; + sess->status = STATUS_INIT; core->cur_sess = sess; - + schedule_work(&sess->esparser_queue_work); return 0; vififo_free: @@ -382,10 +395,12 @@ static void vdec_reset_bufs_recycle(struct amvdec_session *sess) static void vdec_stop_streaming(struct vb2_queue *q) { struct amvdec_session *sess = vb2_get_drv_priv(q); + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; struct amvdec_core *core = sess->core; struct vb2_v4l2_buffer *buf; if (sess->status == STATUS_RUNNING || + sess->status == STATUS_INIT || (sess->status == STATUS_NEEDS_RESUME && (!sess->streamon_out || !sess->streamon_cap))) { if (vdec_codec_needs_recycle(sess)) @@ -409,6 +424,10 @@ static void vdec_stop_streaming(struct vb2_queue *q) sess->streamon_out = 0; } else { + /* Drain remaining refs if was still running */ + if (sess->status >= STATUS_RUNNING && codec_ops->drain) + codec_ops->drain(sess); + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); @@ -476,20 +495,34 @@ vdec_try_fmt_common(struct amvdec_session *sess, u32 size, struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; const struct amvdec_format *fmts = sess->core->platform->formats; - const struct amvdec_format *fmt_out; + const struct amvdec_format *fmt_out = NULL; + u32 output_size = 0; memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); - if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: fmt_out = find_format(fmts, size, pixmp->pixelformat); if (!fmt_out) { pixmp->pixelformat = V4L2_PIX_FMT_MPEG2; fmt_out = find_format(fmts, size, pixmp->pixelformat); } + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + fmt_out = sess->fmt_out; + break; + default: + return NULL; + } + + pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width); + pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height); + output_size = get_output_size(pixmp->width, pixmp->height); - pfmt[0].sizeimage = - get_output_size(pixmp->width, pixmp->height); + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (!pfmt[0].sizeimage) + pfmt[0].sizeimage = sess->src_buffer_size; pfmt[0].bytesperline = 0; pixmp->num_planes = 1; } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { @@ -499,35 +532,25 @@ vdec_try_fmt_common(struct amvdec_session *sess, u32 size, memset(pfmt[1].reserved, 0, sizeof(pfmt[1].reserved)); if (pixmp->pixelformat == V4L2_PIX_FMT_NV12M) { - pfmt[0].sizeimage = - get_output_size(pixmp->width, pixmp->height); - pfmt[0].bytesperline = ALIGN(pixmp->width, 64); + pfmt[0].sizeimage = output_size; + pfmt[0].bytesperline = ALIGN(pixmp->width, 32); - pfmt[1].sizeimage = - get_output_size(pixmp->width, pixmp->height) / 2; - pfmt[1].bytesperline = ALIGN(pixmp->width, 64); + pfmt[1].sizeimage = output_size / 2; + pfmt[1].bytesperline = ALIGN(pixmp->width, 32); pixmp->num_planes = 2; } else if (pixmp->pixelformat == V4L2_PIX_FMT_YUV420M) { - pfmt[0].sizeimage = - get_output_size(pixmp->width, pixmp->height); - pfmt[0].bytesperline = ALIGN(pixmp->width, 64); + pfmt[0].sizeimage = output_size; + pfmt[0].bytesperline = ALIGN(pixmp->width, 32); - pfmt[1].sizeimage = - get_output_size(pixmp->width, pixmp->height) / 4; - pfmt[1].bytesperline = ALIGN(pixmp->width, 64) / 2; + pfmt[1].sizeimage = output_size / 4; + pfmt[1].bytesperline = ALIGN(pixmp->width, 32) / 2; - pfmt[2].sizeimage = - get_output_size(pixmp->width, pixmp->height) / 4; - pfmt[2].bytesperline = ALIGN(pixmp->width, 64) / 2; + pfmt[2].sizeimage = output_size / 2; + pfmt[2].bytesperline = ALIGN(pixmp->width, 32) / 2; pixmp->num_planes = 3; } - } else { - return NULL; } - pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width); - pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height); - if (pixmp->field == V4L2_FIELD_ANY) pixmp->field = V4L2_FIELD_NONE; @@ -586,6 +609,8 @@ static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) orig_pixmp = *pixmp; fmt_out = vdec_try_fmt_common(sess, num_formats, f); + if (!fmt_out) + return -EINVAL; if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { pixfmt_out = pixmp->pixelformat; @@ -610,6 +635,7 @@ static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) sess->ycbcr_enc = pixmp->ycbcr_enc; sess->quantization = pixmp->quantization; sess->xfer_func = pixmp->xfer_func; + sess->src_buffer_size = pixmp->plane_fmt[0].sizeimage; } memset(&format, 0, sizeof(format)); @@ -701,29 +727,31 @@ vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) if (!(sess->streamon_out & sess->streamon_cap)) return 0; - /* Currently not handled since we do not support dynamic resolution - * for MPEG2. We consider both queues streaming to mean that the - * decoding session is started - */ - if (cmd->cmd == V4L2_DEC_CMD_START) + if (cmd->cmd == V4L2_DEC_CMD_START) { + v4l2_m2m_clear_state(sess->m2m_ctx); + sess->should_stop = 0; return 0; + } /* Should not happen */ if (cmd->cmd != V4L2_DEC_CMD_STOP) return -EINVAL; dev_dbg(dev, "Received V4L2_DEC_CMD_STOP\n"); + sess->should_stop = 1; - vdec_wait_inactive(sess); + v4l2_m2m_mark_stopped(sess->m2m_ctx); if (codec_ops->drain) { + vdec_wait_inactive(sess); codec_ops->drain(sess); } else if (codec_ops->eos_sequence) { u32 len; const u8 *data = codec_ops->eos_sequence(&len); esparser_queue_eos(sess->core, data, len); + vdec_wait_inactive(sess); } return ret; @@ -883,6 +911,7 @@ static int vdec_open(struct file *file) sess->height = 720; sess->pixelaspect.numerator = 1; sess->pixelaspect.denominator = 1; + sess->src_buffer_size = SZ_1M; INIT_LIST_HEAD(&sess->timestamps); INIT_LIST_HEAD(&sess->bufs_recycle); @@ -1076,7 +1105,7 @@ static int vdec_probe(struct platform_device *pdev) video_set_drvdata(vdev, core); - ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) { dev_err(dev, "Failed registering video device\n"); goto err_vdev_release; diff --git a/drivers/staging/media/meson/vdec/vdec.h b/drivers/staging/media/meson/vdec/vdec.h index 0faa1ec4858e..f95445ac0658 100644 --- a/drivers/staging/media/meson/vdec/vdec.h +++ b/drivers/staging/media/meson/vdec/vdec.h @@ -29,13 +29,19 @@ struct amvdec_buffer { * struct amvdec_timestamp - stores a src timestamp along with a VIFIFO offset * * @list: used to make lists out of this struct - * @ts: timestamp + * @tc: timecode from the v4l2 buffer + * @ts: timestamp from the VB2 buffer * @offset: offset in the VIFIFO where the associated packet was written + * @flags: flags from the v4l2 buffer + * @used_count: times this timestamp was checked for a match with a dst buffer */ struct amvdec_timestamp { struct list_head list; + struct v4l2_timecode tc; u64 ts; u32 offset; + u32 flags; + u32 used_count; }; struct amvdec_session; @@ -165,6 +171,7 @@ struct amvdec_format { enum amvdec_status { STATUS_STOPPED, + STATUS_INIT, STATUS_RUNNING, STATUS_NEEDS_RESUME, }; @@ -180,6 +187,7 @@ enum amvdec_status { * @ctrl_min_buf_capture: V4L2 control V4L2_CID_MIN_BUFFERS_FOR_CAPTURE * @fmt_out: vdec pixel format for the OUTPUT queue * @pixfmt_cap: V4L2 pixel format for the CAPTURE queue + * @src_buffer_size: size in bytes of the OUTPUT buffers' only plane * @width: current picture width * @height: current picture height * @colorspace: current colorspace @@ -221,6 +229,7 @@ struct amvdec_session { const struct amvdec_format *fmt_out; u32 pixfmt_cap; + u32 src_buffer_size; u32 width; u32 height; @@ -235,10 +244,11 @@ struct amvdec_session { struct work_struct esparser_queue_work; unsigned int streamon_cap, streamon_out; - unsigned int sequence_cap; + unsigned int sequence_cap, sequence_out; unsigned int should_stop; unsigned int keyframe_found; unsigned int num_dst_bufs; + unsigned int changed_format; u8 canvas_alloc[MAX_CANVAS]; u32 canvas_num; diff --git a/drivers/staging/media/meson/vdec/vdec_helpers.c b/drivers/staging/media/meson/vdec/vdec_helpers.c index f16948bdbf2f..7f07a9175815 100644 --- a/drivers/staging/media/meson/vdec/vdec_helpers.c +++ b/drivers/staging/media/meson/vdec/vdec_helpers.c @@ -50,6 +50,33 @@ void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val) } EXPORT_SYMBOL_GPL(amvdec_write_parser); +/* 4 KiB per 64x32 block */ +u32 amvdec_am21c_body_size(u32 width, u32 height) +{ + u32 width_64 = ALIGN(width, 64) / 64; + u32 height_32 = ALIGN(height, 32) / 32; + + return SZ_4K * width_64 * height_32; +} +EXPORT_SYMBOL_GPL(amvdec_am21c_body_size); + +/* 32 bytes per 128x64 block */ +u32 amvdec_am21c_head_size(u32 width, u32 height) +{ + u32 width_128 = ALIGN(width, 128) / 128; + u32 height_64 = ALIGN(height, 64) / 64; + + return 32 * width_128 * height_64; +} +EXPORT_SYMBOL_GPL(amvdec_am21c_head_size); + +u32 amvdec_am21c_size(u32 width, u32 height) +{ + return ALIGN(amvdec_am21c_body_size(width, height) + + amvdec_am21c_head_size(width, height), SZ_64K); +} +EXPORT_SYMBOL_GPL(amvdec_am21c_size); + static int canvas_alloc(struct amvdec_session *sess, u8 *canvas_id) { int ret; @@ -154,8 +181,8 @@ int amvdec_set_canvases(struct amvdec_session *sess, { struct v4l2_m2m_buffer *buf; u32 pixfmt = sess->pixfmt_cap; - u32 width = ALIGN(sess->width, 64); - u32 height = ALIGN(sess->height, 64); + u32 width = ALIGN(sess->width, 32); + u32 height = ALIGN(sess->height, 32); u32 reg_cur = reg_base[0]; u32 reg_num_cur = 0; u32 reg_base_cur = 0; @@ -200,33 +227,23 @@ int amvdec_set_canvases(struct amvdec_session *sess, } EXPORT_SYMBOL_GPL(amvdec_set_canvases); -void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts, u32 offset) +void amvdec_add_ts(struct amvdec_session *sess, u64 ts, + struct v4l2_timecode tc, u32 offset, u32 vbuf_flags) { - struct amvdec_timestamp *new_ts, *tmp; + struct amvdec_timestamp *new_ts; unsigned long flags; - new_ts = kmalloc(sizeof(*new_ts), GFP_KERNEL); + new_ts = kzalloc(sizeof(*new_ts), GFP_KERNEL); new_ts->ts = ts; + new_ts->tc = tc; new_ts->offset = offset; + new_ts->flags = vbuf_flags; spin_lock_irqsave(&sess->ts_spinlock, flags); - - if (list_empty(&sess->timestamps)) - goto add_tail; - - list_for_each_entry(tmp, &sess->timestamps, list) { - if (ts <= tmp->ts) { - list_add_tail(&new_ts->list, &tmp->list); - goto unlock; - } - } - -add_tail: list_add_tail(&new_ts->list, &sess->timestamps); -unlock: spin_unlock_irqrestore(&sess->ts_spinlock, flags); } -EXPORT_SYMBOL_GPL(amvdec_add_ts_reorder); +EXPORT_SYMBOL_GPL(amvdec_add_ts); void amvdec_remove_ts(struct amvdec_session *sess, u64 ts) { @@ -251,8 +268,8 @@ EXPORT_SYMBOL_GPL(amvdec_remove_ts); static void dst_buf_done(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf, - u32 field, - u64 timestamp) + u32 field, u64 timestamp, + struct v4l2_timecode timecode, u32 flags) { struct device *dev = sess->core->dev_dec; u32 output_size = amvdec_get_output_size(sess); @@ -271,19 +288,27 @@ static void dst_buf_done(struct amvdec_session *sess, vbuf->vb2_buf.timestamp = timestamp; vbuf->sequence = sess->sequence_cap++; + vbuf->flags = flags; + vbuf->timecode = timecode; if (sess->should_stop && - atomic_read(&sess->esparser_queued_bufs) <= 2) { + atomic_read(&sess->esparser_queued_bufs) <= 1) { const struct v4l2_event ev = { .type = V4L2_EVENT_EOS }; - dev_dbg(dev, "Signaling EOS\n"); + dev_dbg(dev, "Signaling EOS, sequence_cap = %u\n", + sess->sequence_cap - 1); v4l2_event_queue_fh(&sess->fh, &ev); vbuf->flags |= V4L2_BUF_FLAG_LAST; + } else if (sess->status == STATUS_NEEDS_RESUME) { + /* Mark LAST for drained show frames during a source change */ + vbuf->flags |= V4L2_BUF_FLAG_LAST; + sess->sequence_cap = 0; } else if (sess->should_stop) dev_dbg(dev, "should_stop, %u bufs remain\n", atomic_read(&sess->esparser_queued_bufs)); - dev_dbg(dev, "Buffer %u done\n", vbuf->vb2_buf.index); + dev_dbg(dev, "Buffer %u done, ts = %llu, flags = %08X\n", + vbuf->vb2_buf.index, timestamp, flags); vbuf->field = field; v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); @@ -297,7 +322,9 @@ void amvdec_dst_buf_done(struct amvdec_session *sess, struct device *dev = sess->core->dev_dec; struct amvdec_timestamp *tmp; struct list_head *timestamps = &sess->timestamps; + struct v4l2_timecode timecode; u64 timestamp; + u32 vbuf_flags; unsigned long flags; spin_lock_irqsave(&sess->ts_spinlock, flags); @@ -312,11 +339,13 @@ void amvdec_dst_buf_done(struct amvdec_session *sess, tmp = list_first_entry(timestamps, struct amvdec_timestamp, list); timestamp = tmp->ts; + timecode = tmp->tc; + vbuf_flags = tmp->flags; list_del(&tmp->list); kfree(tmp); spin_unlock_irqrestore(&sess->ts_spinlock, flags); - dst_buf_done(sess, vbuf, field, timestamp); + dst_buf_done(sess, vbuf, field, timestamp, timecode, vbuf_flags); atomic_dec(&sess->esparser_queued_bufs); } EXPORT_SYMBOL_GPL(amvdec_dst_buf_done); @@ -328,48 +357,43 @@ void amvdec_dst_buf_done_offset(struct amvdec_session *sess, struct device *dev = sess->core->dev_dec; struct amvdec_timestamp *match = NULL; struct amvdec_timestamp *tmp, *n; + struct v4l2_timecode timecode = { 0 }; u64 timestamp = 0; + u32 vbuf_flags = 0; unsigned long flags; spin_lock_irqsave(&sess->ts_spinlock, flags); /* Look for our vififo offset to get the corresponding timestamp. */ list_for_each_entry_safe(tmp, n, &sess->timestamps, list) { - s64 delta = (s64)offset - tmp->offset; - - /* Offsets reported by codecs usually differ slightly, - * so we need some wiggle room. - * 4KiB being the minimum packet size, there is no risk here. - */ - if (delta > (-1 * (s32)SZ_4K) && delta < SZ_4K) { - match = tmp; + if (tmp->offset > offset) { + /* + * Delete any record that remained unused for 32 match + * checks + */ + if (tmp->used_count++ >= 32) { + list_del(&tmp->list); + kfree(tmp); + } break; } - if (!allow_drop) - continue; - - /* Delete any timestamp entry that appears before our target - * (not all src packets/timestamps lead to a frame) - */ - if (delta > 0 || delta < -1 * (s32)sess->vififo_size) { - atomic_dec(&sess->esparser_queued_bufs); - list_del(&tmp->list); - kfree(tmp); - } + match = tmp; } if (!match) { - dev_dbg(dev, "Buffer %u done but can't match offset (%08X)\n", + dev_err(dev, "Buffer %u done but can't match offset (%08X)\n", vbuf->vb2_buf.index, offset); } else { timestamp = match->ts; + timecode = match->tc; + vbuf_flags = match->flags; list_del(&match->list); kfree(match); } spin_unlock_irqrestore(&sess->ts_spinlock, flags); - dst_buf_done(sess, vbuf, field, timestamp); + dst_buf_done(sess, vbuf, field, timestamp, timecode, vbuf_flags); if (match) atomic_dec(&sess->esparser_queued_bufs); } @@ -420,16 +444,19 @@ void amvdec_src_change(struct amvdec_session *sess, u32 width, v4l2_ctrl_s_ctrl(sess->ctrl_min_buf_capture, dpb_size); - /* Check if the capture queue is already configured well for our + /* + * Check if the capture queue is already configured well for our * usecase. If so, keep decoding with it and do not send the event */ - if (sess->width == width && + if (sess->streamon_cap && + sess->width == width && sess->height == height && dpb_size <= sess->num_dst_bufs) { sess->fmt_out->codec_ops->resume(sess); return; } + sess->changed_format = 0; sess->width = width; sess->height = height; sess->status = STATUS_NEEDS_RESUME; diff --git a/drivers/staging/media/meson/vdec/vdec_helpers.h b/drivers/staging/media/meson/vdec/vdec_helpers.h index a455a9ee1cc2..cfaed52ab526 100644 --- a/drivers/staging/media/meson/vdec/vdec_helpers.h +++ b/drivers/staging/media/meson/vdec/vdec_helpers.h @@ -27,6 +27,10 @@ void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val); u32 amvdec_read_parser(struct amvdec_core *core, u32 reg); void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val); +u32 amvdec_am21c_body_size(u32 width, u32 height); +u32 amvdec_am21c_head_size(u32 width, u32 height); +u32 amvdec_am21c_size(u32 width, u32 height); + /** * amvdec_dst_buf_done_idx() - Signal that a buffer is done decoding * @@ -44,13 +48,15 @@ void amvdec_dst_buf_done_offset(struct amvdec_session *sess, u32 offset, u32 field, bool allow_drop); /** - * amvdec_add_ts_reorder() - Add a timestamp to the list in chronological order + * amvdec_add_ts() - Add a timestamp to the list * * @sess: current session * @ts: timestamp to add * @offset: offset in the VIFIFO where the associated packet was written + * @flags the vb2_v4l2_buffer flags */ -void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts, u32 offset); +void amvdec_add_ts(struct amvdec_session *sess, u64 ts, + struct v4l2_timecode tc, u32 offset, u32 flags); void amvdec_remove_ts(struct amvdec_session *sess, u64 ts); /** diff --git a/drivers/staging/media/meson/vdec/vdec_hevc.c b/drivers/staging/media/meson/vdec/vdec_hevc.c new file mode 100644 index 000000000000..9530e580e57a --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_hevc.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan <maxi.jourdan@wanadoo.fr> + * + * VDEC_HEVC is a video decoding block that allows decoding of + * HEVC, VP9 + */ + +#include <linux/firmware.h> +#include <linux/clk.h> + +#include "vdec_1.h" +#include "vdec_helpers.h" +#include "vdec_hevc.h" +#include "hevc_regs.h" +#include "dos_regs.h" + +/* AO Registers */ +#define AO_RTI_GEN_PWR_SLEEP0 0xe8 +#define AO_RTI_GEN_PWR_ISO0 0xec + #define GEN_PWR_VDEC_HEVC (BIT(7) | BIT(6)) + #define GEN_PWR_VDEC_HEVC_SM1 (BIT(2)) + +#define MC_SIZE (4096 * 4) + +static int vdec_hevc_load_firmware(struct amvdec_session *sess, + const char *fwname) +{ + struct amvdec_core *core = sess->core; + struct device *dev = core->dev_dec; + const struct firmware *fw; + static void *mc_addr; + static dma_addr_t mc_addr_map; + int ret; + u32 i = 100; + + ret = request_firmware(&fw, fwname, dev); + if (ret < 0) { + dev_err(dev, "Unable to request firmware %s\n", fwname); + return ret; + } + + if (fw->size < MC_SIZE) { + dev_err(dev, "Firmware size %zu is too small. Expected %u.\n", + fw->size, MC_SIZE); + ret = -EINVAL; + goto release_firmware; + } + + mc_addr = dma_alloc_coherent(core->dev, MC_SIZE, &mc_addr_map, + GFP_KERNEL); + if (!mc_addr) { + ret = -ENOMEM; + goto release_firmware; + } + + memcpy(mc_addr, fw->data, MC_SIZE); + + amvdec_write_dos(core, HEVC_MPSR, 0); + amvdec_write_dos(core, HEVC_CPSR, 0); + + amvdec_write_dos(core, HEVC_IMEM_DMA_ADR, mc_addr_map); + amvdec_write_dos(core, HEVC_IMEM_DMA_COUNT, MC_SIZE / 4); + amvdec_write_dos(core, HEVC_IMEM_DMA_CTRL, (0x8000 | (7 << 16))); + + while (i && (readl(core->dos_base + HEVC_IMEM_DMA_CTRL) & 0x8000)) + i--; + + if (i == 0) { + dev_err(dev, "Firmware load fail (DMA hang?)\n"); + ret = -ENODEV; + } + + dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map); +release_firmware: + release_firmware(fw); + return ret; +} + +static void vdec_hevc_stbuf_init(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) & ~1); + amvdec_write_dos(core, HEVC_STREAM_START_ADDR, sess->vififo_paddr); + amvdec_write_dos(core, HEVC_STREAM_END_ADDR, + sess->vififo_paddr + sess->vififo_size); + amvdec_write_dos(core, HEVC_STREAM_RD_PTR, sess->vififo_paddr); + amvdec_write_dos(core, HEVC_STREAM_WR_PTR, sess->vififo_paddr); +} + +/* VDEC_HEVC specific ESPARSER configuration */ +static void vdec_hevc_conf_esparser(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + /* set vififo_vbuf_rp_sel=>vdec_hevc */ + amvdec_write_dos(core, DOS_GEN_CTRL0, 3 << 1); + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) | BIT(3)); + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) | 1); + amvdec_write_dos(core, HEVC_STREAM_FIFO_CTL, + amvdec_read_dos(core, HEVC_STREAM_FIFO_CTL) | BIT(29)); +} + +static u32 vdec_hevc_vififo_level(struct amvdec_session *sess) +{ + return readl_relaxed(sess->core->dos_base + HEVC_STREAM_LEVEL); +} + +static int vdec_hevc_stop(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + /* Disable interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_MASK, 0); + /* Disable firmware processor */ + amvdec_write_dos(core, HEVC_MPSR, 0); + + if (sess->priv) + codec_ops->stop(sess); + + /* Enable VDEC_HEVC Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_HEVC_SM1, + GEN_PWR_VDEC_HEVC_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + 0xc00, 0xc00); + + /* VDEC_HEVC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_HEVC, 0xffffffffUL); + + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC_SM1, + GEN_PWR_VDEC_HEVC_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC, GEN_PWR_VDEC_HEVC); + + clk_disable_unprepare(core->vdec_hevc_clk); + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) + clk_disable_unprepare(core->vdec_hevcf_clk); + + return 0; +} + +static int vdec_hevc_start(struct amvdec_session *sess) +{ + int ret; + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) { + clk_set_rate(core->vdec_hevcf_clk, 666666666); + ret = clk_prepare_enable(core->vdec_hevcf_clk); + if (ret) + return ret; + } + + clk_set_rate(core->vdec_hevc_clk, 666666666); + ret = clk_prepare_enable(core->vdec_hevc_clk); + if (ret) + return ret; + + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC, 0); + usleep_range(10, 20); + + /* Reset VDEC_HEVC*/ + amvdec_write_dos(core, DOS_SW_RESET3, 0xffffffff); + amvdec_write_dos(core, DOS_SW_RESET3, 0x00000000); + + amvdec_write_dos(core, DOS_GCLK_EN3, 0xffffffff); + + /* VDEC_HEVC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_HEVC, 0x00000000); + + /* Remove VDEC_HEVC Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_HEVC_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + 0xc00, 0); + + amvdec_write_dos(core, DOS_SW_RESET3, 0xffffffff); + amvdec_write_dos(core, DOS_SW_RESET3, 0x00000000); + + vdec_hevc_stbuf_init(sess); + + ret = vdec_hevc_load_firmware(sess, sess->fmt_out->firmware_path); + if (ret) + goto stop; + + ret = codec_ops->start(sess); + if (ret) + goto stop; + + amvdec_write_dos(core, DOS_SW_RESET3, BIT(12) | BIT(11)); + amvdec_write_dos(core, DOS_SW_RESET3, 0); + amvdec_read_dos(core, DOS_SW_RESET3); + + amvdec_write_dos(core, HEVC_MPSR, 1); + /* Let the firmware settle */ + usleep_range(10, 20); + + return 0; + +stop: + vdec_hevc_stop(sess); + return ret; +} + +struct amvdec_ops vdec_hevc_ops = { + .start = vdec_hevc_start, + .stop = vdec_hevc_stop, + .conf_esparser = vdec_hevc_conf_esparser, + .vififo_level = vdec_hevc_vififo_level, +}; diff --git a/drivers/staging/media/meson/vdec/vdec_hevc.h b/drivers/staging/media/meson/vdec/vdec_hevc.h new file mode 100644 index 000000000000..cd576a73a966 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_hevc.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Maxime Jourdan <maxi.jourdan@wanadoo.fr> + */ + +#ifndef __MESON_VDEC_VDEC_HEVC_H_ +#define __MESON_VDEC_VDEC_HEVC_H_ + +#include "vdec.h" + +extern struct amvdec_ops vdec_hevc_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec_platform.c b/drivers/staging/media/meson/vdec/vdec_platform.c index ea39f8209ec7..eabbebab2da2 100644 --- a/drivers/staging/media/meson/vdec/vdec_platform.c +++ b/drivers/staging/media/meson/vdec/vdec_platform.c @@ -8,10 +8,25 @@ #include "vdec.h" #include "vdec_1.h" +#include "vdec_hevc.h" #include "codec_mpeg12.h" +#include "codec_h264.h" +#include "codec_vp9.h" static const struct amvdec_format vdec_formats_gxbb[] = { { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/gxbb_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { .pixfmt = V4L2_PIX_FMT_MPEG1, .min_buffers = 8, .max_buffers = 8, @@ -21,6 +36,7 @@ static const struct amvdec_format vdec_formats_gxbb[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, { .pixfmt = V4L2_PIX_FMT_MPEG2, .min_buffers = 8, @@ -31,11 +47,36 @@ static const struct amvdec_format vdec_formats_gxbb[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, }; static const struct amvdec_format vdec_formats_gxl[] = { { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/gxl_vp9.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/gxl_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { .pixfmt = V4L2_PIX_FMT_MPEG1, .min_buffers = 8, .max_buffers = 8, @@ -45,6 +86,7 @@ static const struct amvdec_format vdec_formats_gxl[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, { .pixfmt = V4L2_PIX_FMT_MPEG2, .min_buffers = 8, @@ -55,11 +97,24 @@ static const struct amvdec_format vdec_formats_gxl[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, }; static const struct amvdec_format vdec_formats_gxm[] = { { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/gxm_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { .pixfmt = V4L2_PIX_FMT_MPEG1, .min_buffers = 8, .max_buffers = 8, @@ -69,6 +124,7 @@ static const struct amvdec_format vdec_formats_gxm[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, { .pixfmt = V4L2_PIX_FMT_MPEG2, .min_buffers = 8, @@ -79,11 +135,36 @@ static const struct amvdec_format vdec_formats_gxm[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, }; static const struct amvdec_format vdec_formats_g12a[] = { { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/g12a_vp9.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/g12a_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { .pixfmt = V4L2_PIX_FMT_MPEG1, .min_buffers = 8, .max_buffers = 8, @@ -93,6 +174,7 @@ static const struct amvdec_format vdec_formats_g12a[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, { .pixfmt = V4L2_PIX_FMT_MPEG2, .min_buffers = 8, @@ -103,11 +185,36 @@ static const struct amvdec_format vdec_formats_g12a[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, }; static const struct amvdec_format vdec_formats_sm1[] = { { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/sm1_vp9_mmu.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/g12a_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { .pixfmt = V4L2_PIX_FMT_MPEG1, .min_buffers = 8, .max_buffers = 8, @@ -117,6 +224,7 @@ static const struct amvdec_format vdec_formats_sm1[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, { .pixfmt = V4L2_PIX_FMT_MPEG2, .min_buffers = 8, @@ -127,6 +235,7 @@ static const struct amvdec_format vdec_formats_sm1[] = { .codec_ops = &codec_mpeg12_ops, .firmware_path = "meson/vdec/gxl_mpeg12.bin", .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, }, }; diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c index 673aa3a5f2bd..66975a37dc85 100644 --- a/drivers/staging/media/omap4iss/iss_video.c +++ b/drivers/staging/media/omap4iss/iss_video.c @@ -1111,7 +1111,7 @@ static int iss_video_open(struct file *file) goto done; } - ret = v4l2_pipeline_pm_use(&video->video.entity, 1); + ret = v4l2_pipeline_pm_get(&video->video.entity); if (ret < 0) { omap4iss_put(video->iss); goto done; @@ -1160,7 +1160,7 @@ static int iss_video_release(struct file *file) /* Disable streaming and free the buffers queue resources. */ iss_video_streamoff(file, vfh, video->type); - v4l2_pipeline_pm_use(&video->video.entity, 0); + v4l2_pipeline_pm_put(&video->video.entity); /* Release the videobuf2 queue */ vb2_queue_release(&handle->queue); @@ -1242,7 +1242,7 @@ int omap4iss_video_init(struct iss_video *video, const char *name) video->video.fops = &iss_video_fops; snprintf(video->video.name, sizeof(video->video.name), "OMAP4 ISS %s %s", name, direction); - video->video.vfl_type = VFL_TYPE_GRABBER; + video->video.vfl_type = VFL_TYPE_VIDEO; video->video.release = video_device_release_empty; video->video.ioctl_ops = &iss_video_ioctl_ops; video->pipe.stream_state = ISS_PIPELINE_STREAM_STOPPED; @@ -1270,7 +1270,7 @@ int omap4iss_video_register(struct iss_video *video, struct v4l2_device *vdev) video->video.device_caps = V4L2_CAP_VIDEO_OUTPUT; video->video.device_caps |= V4L2_CAP_STREAMING; - ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); + ret = video_register_device(&video->video, VFL_TYPE_VIDEO, -1); if (ret < 0) dev_err(video->iss->dev, "could not register video device (%d)\n", ret); diff --git a/drivers/staging/media/rkisp1/TODO b/drivers/staging/media/rkisp1/TODO index 03cd9a4e70f7..0aa9877dd64a 100644 --- a/drivers/staging/media/rkisp1/TODO +++ b/drivers/staging/media/rkisp1/TODO @@ -1,4 +1,3 @@ -* Fix serialization on subdev ops. * Don't use v4l2_async_notifier_parse_fwnode_endpoints_by_port(). e.g. isp_parse_of_endpoints in drivers/media/platform/omap3isp/isp.c cio2_parse_firmware in drivers/media/pci/intel/ipu3/ipu3-cio2.c. diff --git a/drivers/staging/media/rkisp1/rkisp1-capture.c b/drivers/staging/media/rkisp1/rkisp1-capture.c index 524e0dd38c1b..24fe6a7888aa 100644 --- a/drivers/staging/media/rkisp1/rkisp1-capture.c +++ b/drivers/staging/media/rkisp1/rkisp1-capture.c @@ -937,10 +937,7 @@ static void rkisp1_vb2_stop_streaming(struct vb2_queue *queue) rkisp1_return_all_buffers(cap, VB2_BUF_STATE_ERROR); - ret = v4l2_pipeline_pm_use(&node->vdev.entity, 0); - if (ret) - dev_err(rkisp1->dev, "pipeline close failed error:%d\n", ret); - + v4l2_pipeline_pm_put(&node->vdev.entity); ret = pm_runtime_put(rkisp1->dev); if (ret) dev_err(rkisp1->dev, "power down failed error:%d\n", ret); @@ -999,7 +996,7 @@ rkisp1_vb2_start_streaming(struct vb2_queue *queue, unsigned int count) dev_err(cap->rkisp1->dev, "power up failed %d\n", ret); goto err_destroy_dummy; } - ret = v4l2_pipeline_pm_use(entity, 1); + ret = v4l2_pipeline_pm_get(entity); if (ret) { dev_err(cap->rkisp1->dev, "open cif pipeline failed %d\n", ret); goto err_pipe_pm_put; @@ -1025,7 +1022,7 @@ err_pipe_disable: rkisp1_pipeline_sink_walk(entity, NULL, rkisp1_pipeline_disable_cb); err_stop_stream: rkisp1_stream_stop(cap); - v4l2_pipeline_pm_use(entity, 0); + v4l2_pipeline_pm_put(entity); err_pipe_pm_put: pm_runtime_put(cap->rkisp1->dev); err_destroy_dummy: @@ -1344,7 +1341,7 @@ static int rkisp1_register_capture(struct rkisp1_capture *cap) q = &node->buf_queue; q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_USERPTR; + q->io_modes = VB2_MMAP | VB2_DMABUF; q->drv_priv = cap; q->ops = &rkisp1_vb2_ops; q->mem_ops = &vb2_dma_contig_memops; @@ -1362,7 +1359,7 @@ static int rkisp1_register_capture(struct rkisp1_capture *cap) vdev->queue = q; - ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) { dev_err(cap->rkisp1->dev, "failed to register %s, ret=%d\n", vdev->name, ret); diff --git a/drivers/staging/media/rkisp1/rkisp1-common.h b/drivers/staging/media/rkisp1/rkisp1-common.h index 369a401b098a..b291cc60de8e 100644 --- a/drivers/staging/media/rkisp1/rkisp1-common.h +++ b/drivers/staging/media/rkisp1/rkisp1-common.h @@ -96,6 +96,7 @@ struct rkisp1_sensor_async { * @sink_crop: crop for sink pad * @src_fmt: output format * @src_crop: output size + * @ops_lock: ops serialization * * @is_dphy_errctrl_disabled : if dphy errctrl is disabled (avoid endless interrupt) * @frame_sequence: used to synchronize frame_id between video devices. @@ -107,6 +108,7 @@ struct rkisp1_isp { struct v4l2_subdev_pad_config pad_cfg[RKISP1_ISP_PAD_MAX]; const struct rkisp1_isp_mbus_info *sink_fmt; const struct rkisp1_isp_mbus_info *src_fmt; + struct mutex ops_lock; bool is_dphy_errctrl_disabled; atomic_t frame_sequence; }; @@ -224,6 +226,7 @@ struct rkisp1_resizer { struct v4l2_subdev_pad_config pad_cfg[RKISP1_ISP_PAD_MAX]; const struct rkisp1_rsz_config *config; enum rkisp1_fmt_pix_type fmt_type; + struct mutex ops_lock; }; struct rkisp1_debug { diff --git a/drivers/staging/media/rkisp1/rkisp1-dev.c b/drivers/staging/media/rkisp1/rkisp1-dev.c index 558126e66465..b1b3c058e957 100644 --- a/drivers/staging/media/rkisp1/rkisp1-dev.c +++ b/drivers/staging/media/rkisp1/rkisp1-dev.c @@ -128,7 +128,7 @@ static int rkisp1_create_links(struct rkisp1_device *rkisp1) ret = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, MEDIA_PAD_FL_SOURCE); - if (ret) { + if (ret < 0) { dev_err(sd->dev, "failed to find src pad for %s\n", sd->name); return ret; @@ -145,14 +145,15 @@ static int rkisp1_create_links(struct rkisp1_device *rkisp1) flags = 0; } - flags = MEDIA_LNK_FL_ENABLED; + flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; /* create ISP->RSZ->CAP links */ for (i = 0; i < 2; i++) { source = &rkisp1->isp.sd.entity; sink = &rkisp1->resizer_devs[i].sd.entity; ret = media_create_pad_link(source, RKISP1_ISP_PAD_SOURCE_VIDEO, - sink, RKISP1_RSZ_PAD_SINK, flags); + sink, RKISP1_RSZ_PAD_SINK, + MEDIA_LNK_FL_ENABLED); if (ret) return ret; @@ -219,19 +220,17 @@ static int rkisp1_subdev_notifier_complete(struct v4l2_async_notifier *notifier) container_of(notifier, struct rkisp1_device, notifier); int ret; - mutex_lock(&rkisp1->media_dev.graph_mutex); ret = rkisp1_create_links(rkisp1); if (ret) - goto unlock; + return ret; + ret = v4l2_device_register_subdev_nodes(&rkisp1->v4l2_dev); if (ret) - goto unlock; + return ret; dev_dbg(rkisp1->dev, "Async subdev notifier completed\n"); -unlock: - mutex_unlock(&rkisp1->media_dev.graph_mutex); - return ret; + return 0; } static int rkisp1_fwnode_parse(struct device *dev, @@ -502,8 +501,7 @@ static int rkisp1_probe(struct platform_device *pdev) strscpy(rkisp1->media_dev.model, RKISP1_DRIVER_NAME, sizeof(rkisp1->media_dev.model)); rkisp1->media_dev.dev = &pdev->dev; - strscpy(rkisp1->media_dev.bus_info, - "platform: " RKISP1_DRIVER_NAME, + strscpy(rkisp1->media_dev.bus_info, RKISP1_BUS_INFO, sizeof(rkisp1->media_dev.bus_info)); media_device_init(&rkisp1->media_dev); diff --git a/drivers/staging/media/rkisp1/rkisp1-isp.c b/drivers/staging/media/rkisp1/rkisp1-isp.c index 328c7ea60971..fa53f05e37d8 100644 --- a/drivers/staging/media/rkisp1/rkisp1-isp.c +++ b/drivers/staging/media/rkisp1/rkisp1-isp.c @@ -28,9 +28,9 @@ #define RKISP1_DIR_SINK_SRC (RKISP1_DIR_SINK | RKISP1_DIR_SRC) /* - * NOTE: MIPI controller and input MUX are also configured in this file, - * because ISP Subdev is not only describe ISP submodule(input size,format, - * output size, format), but also a virtual route device. + * NOTE: MIPI controller and input MUX are also configured in this file. + * This is because ISP Subdev describes not only ISP submodule (input size, + * format, output size, format), but also a virtual route device. */ /* @@ -504,7 +504,7 @@ static int rkisp1_config_cif(struct rkisp1_device *rkisp1) return 0; } -static int rkisp1_isp_stop(struct rkisp1_device *rkisp1) +static void rkisp1_isp_stop(struct rkisp1_device *rkisp1) { u32 val; @@ -540,8 +540,6 @@ static int rkisp1_isp_stop(struct rkisp1_device *rkisp1) RKISP1_CIF_IRCL_MIPI_SW_RST | RKISP1_CIF_IRCL_ISP_SW_RST, RKISP1_CIF_IRCL); rkisp1_write(rkisp1, 0x0, RKISP1_CIF_IRCL); - - return 0; } static void rkisp1_config_clk(struct rkisp1_device *rkisp1) @@ -555,7 +553,7 @@ static void rkisp1_config_clk(struct rkisp1_device *rkisp1) rkisp1_write(rkisp1, val, RKISP1_CIF_ICCL); } -static int rkisp1_isp_start(struct rkisp1_device *rkisp1) +static void rkisp1_isp_start(struct rkisp1_device *rkisp1) { struct rkisp1_sensor_async *sensor = rkisp1->active_sensor; u32 val; @@ -580,8 +578,6 @@ static int rkisp1_isp_start(struct rkisp1_device *rkisp1) * the MIPI interface and before starting the sensor output. */ usleep_range(1000, 1200); - - return 0; } /* ---------------------------------------------------------------------------- @@ -683,7 +679,7 @@ static void rkisp1_isp_set_src_fmt(struct rkisp1_isp *isp, src_fmt->code = format->code; mbus_info = rkisp1_isp_mbus_info_get(src_fmt->code); - if (!mbus_info) { + if (!mbus_info || !(mbus_info->direction & RKISP1_DIR_SRC)) { src_fmt->code = RKISP1_DEF_SRC_PAD_FMT; mbus_info = rkisp1_isp_mbus_info_get(src_fmt->code); } @@ -767,7 +763,7 @@ static void rkisp1_isp_set_sink_fmt(struct rkisp1_isp *isp, which); sink_fmt->code = format->code; mbus_info = rkisp1_isp_mbus_info_get(sink_fmt->code); - if (!mbus_info) { + if (!mbus_info || !(mbus_info->direction & RKISP1_DIR_SINK)) { sink_fmt->code = RKISP1_DEF_SINK_PAD_FMT; mbus_info = rkisp1_isp_mbus_info_get(sink_fmt->code); } @@ -795,7 +791,9 @@ static int rkisp1_isp_get_fmt(struct v4l2_subdev *sd, { struct rkisp1_isp *isp = container_of(sd, struct rkisp1_isp, sd); + mutex_lock(&isp->ops_lock); fmt->format = *rkisp1_isp_get_pad_fmt(isp, cfg, fmt->pad, fmt->which); + mutex_unlock(&isp->ops_lock); return 0; } @@ -805,6 +803,7 @@ static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, { struct rkisp1_isp *isp = container_of(sd, struct rkisp1_isp, sd); + mutex_lock(&isp->ops_lock); if (fmt->pad == RKISP1_ISP_PAD_SINK_VIDEO) rkisp1_isp_set_sink_fmt(isp, cfg, &fmt->format, fmt->which); else if (fmt->pad == RKISP1_ISP_PAD_SOURCE_VIDEO) @@ -813,6 +812,7 @@ static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, fmt->format = *rkisp1_isp_get_pad_fmt(isp, cfg, fmt->pad, fmt->which); + mutex_unlock(&isp->ops_lock); return 0; } @@ -821,11 +821,13 @@ static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_selection *sel) { struct rkisp1_isp *isp = container_of(sd, struct rkisp1_isp, sd); + int ret = 0; if (sel->pad != RKISP1_ISP_PAD_SOURCE_VIDEO && sel->pad != RKISP1_ISP_PAD_SINK_VIDEO) return -EINVAL; + mutex_lock(&isp->ops_lock); switch (sel->target) { case V4L2_SEL_TGT_CROP_BOUNDS: if (sel->pad == RKISP1_ISP_PAD_SINK_VIDEO) { @@ -848,10 +850,10 @@ static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, sel->which); break; default: - return -EINVAL; + ret = -EINVAL; } - - return 0; + mutex_unlock(&isp->ops_lock); + return ret; } static int rkisp1_isp_set_selection(struct v4l2_subdev *sd, @@ -861,21 +863,23 @@ static int rkisp1_isp_set_selection(struct v4l2_subdev *sd, struct rkisp1_device *rkisp1 = container_of(sd->v4l2_dev, struct rkisp1_device, v4l2_dev); struct rkisp1_isp *isp = container_of(sd, struct rkisp1_isp, sd); + int ret = 0; if (sel->target != V4L2_SEL_TGT_CROP) return -EINVAL; dev_dbg(rkisp1->dev, "%s: pad: %d sel(%d,%d)/%dx%d\n", __func__, sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); - + mutex_lock(&isp->ops_lock); if (sel->pad == RKISP1_ISP_PAD_SINK_VIDEO) rkisp1_isp_set_sink_crop(isp, cfg, &sel->r, sel->which); else if (sel->pad == RKISP1_ISP_PAD_SOURCE_VIDEO) rkisp1_isp_set_src_crop(isp, cfg, &sel->r, sel->which); else - return -EINVAL; + ret = -EINVAL; - return 0; + mutex_unlock(&isp->ops_lock); + return ret; } static int rkisp1_subdev_link_validate(struct media_link *link) @@ -936,13 +940,12 @@ static int rkisp1_isp_s_stream(struct v4l2_subdev *sd, int enable) { struct rkisp1_device *rkisp1 = container_of(sd->v4l2_dev, struct rkisp1_device, v4l2_dev); + struct rkisp1_isp *isp = &rkisp1->isp; struct v4l2_subdev *sensor_sd; int ret = 0; if (!enable) { - ret = rkisp1_isp_stop(rkisp1); - if (ret) - return ret; + rkisp1_isp_stop(rkisp1); rkisp1_mipi_csi2_stop(rkisp1->active_sensor); return 0; } @@ -953,22 +956,23 @@ static int rkisp1_isp_s_stream(struct v4l2_subdev *sd, int enable) rkisp1->active_sensor = container_of(sensor_sd->asd, struct rkisp1_sensor_async, asd); + if (rkisp1->active_sensor->mbus.type != V4L2_MBUS_CSI2_DPHY) + return -EINVAL; + atomic_set(&rkisp1->isp.frame_sequence, -1); + mutex_lock(&isp->ops_lock); ret = rkisp1_config_cif(rkisp1); if (ret) - return ret; - - if (rkisp1->active_sensor->mbus.type != V4L2_MBUS_CSI2_DPHY) - return -EINVAL; + goto mutex_unlock; ret = rkisp1_mipi_csi2_start(&rkisp1->isp, rkisp1->active_sensor); if (ret) - return ret; + goto mutex_unlock; - ret = rkisp1_isp_start(rkisp1); - if (ret) - rkisp1_mipi_csi2_stop(rkisp1->active_sensor); + rkisp1_isp_start(rkisp1); +mutex_unlock: + mutex_unlock(&isp->ops_lock); return ret; } @@ -1028,6 +1032,7 @@ int rkisp1_isp_register(struct rkisp1_device *rkisp1, isp->sink_fmt = rkisp1_isp_mbus_info_get(RKISP1_DEF_SINK_PAD_FMT); isp->src_fmt = rkisp1_isp_mbus_info_get(RKISP1_DEF_SRC_PAD_FMT); + mutex_init(&isp->ops_lock); ret = media_entity_pads_init(&sd->entity, RKISP1_ISP_PAD_MAX, pads); if (ret) return ret; diff --git a/drivers/staging/media/rkisp1/rkisp1-params.c b/drivers/staging/media/rkisp1/rkisp1-params.c index 781f0ca85af1..44d542caf32b 100644 --- a/drivers/staging/media/rkisp1/rkisp1-params.c +++ b/drivers/staging/media/rkisp1/rkisp1-params.c @@ -1605,7 +1605,7 @@ int rkisp1_params_register(struct rkisp1_params *params, ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); if (ret) goto err_release_queue; - ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) { dev_err(&vdev->dev, "failed to register %s, ret=%d\n", vdev->name, ret); diff --git a/drivers/staging/media/rkisp1/rkisp1-resizer.c b/drivers/staging/media/rkisp1/rkisp1-resizer.c index 8cdc29c1a178..87799fbf0363 100644 --- a/drivers/staging/media/rkisp1/rkisp1-resizer.c +++ b/drivers/staging/media/rkisp1/rkisp1-resizer.c @@ -503,6 +503,8 @@ static void rkisp1_rsz_set_sink_crop(struct rkisp1_resizer *rsz, sink_crop->top = 0; sink_crop->width = sink_fmt->width; sink_crop->height = sink_fmt->height; + + *r = *sink_crop; return; } @@ -537,15 +539,6 @@ static void rkisp1_rsz_set_sink_fmt(struct rkisp1_resizer *rsz, if (which == V4L2_SUBDEV_FORMAT_ACTIVE) rsz->fmt_type = mbus_info->fmt_type; - if (rsz->id == RKISP1_MAINPATH && - mbus_info->fmt_type == RKISP1_FMT_BAYER) { - sink_crop->left = 0; - sink_crop->top = 0; - sink_crop->width = sink_fmt->width; - sink_crop->height = sink_fmt->height; - return; - } - /* Propagete to source pad */ src_fmt->code = sink_fmt->code; @@ -569,7 +562,9 @@ static int rkisp1_rsz_get_fmt(struct v4l2_subdev *sd, struct rkisp1_resizer *rsz = container_of(sd, struct rkisp1_resizer, sd); + mutex_lock(&rsz->ops_lock); fmt->format = *rkisp1_rsz_get_pad_fmt(rsz, cfg, fmt->pad, fmt->which); + mutex_unlock(&rsz->ops_lock); return 0; } @@ -580,11 +575,13 @@ static int rkisp1_rsz_set_fmt(struct v4l2_subdev *sd, struct rkisp1_resizer *rsz = container_of(sd, struct rkisp1_resizer, sd); + mutex_lock(&rsz->ops_lock); if (fmt->pad == RKISP1_RSZ_PAD_SINK) rkisp1_rsz_set_sink_fmt(rsz, cfg, &fmt->format, fmt->which); else rkisp1_rsz_set_src_fmt(rsz, cfg, &fmt->format, fmt->which); + mutex_unlock(&rsz->ops_lock); return 0; } @@ -595,10 +592,12 @@ static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, struct rkisp1_resizer *rsz = container_of(sd, struct rkisp1_resizer, sd); struct v4l2_mbus_framefmt *mf_sink; + int ret = 0; if (sel->pad == RKISP1_RSZ_PAD_SRC) return -EINVAL; + mutex_lock(&rsz->ops_lock); switch (sel->target) { case V4L2_SEL_TGT_CROP_BOUNDS: mf_sink = rkisp1_rsz_get_pad_fmt(rsz, cfg, RKISP1_RSZ_PAD_SINK, @@ -613,10 +612,11 @@ static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, sel->which); break; default: - return -EINVAL; + ret = -EINVAL; } - return 0; + mutex_unlock(&rsz->ops_lock); + return ret; } static int rkisp1_rsz_set_selection(struct v4l2_subdev *sd, @@ -632,7 +632,9 @@ static int rkisp1_rsz_set_selection(struct v4l2_subdev *sd, dev_dbg(sd->dev, "%s: pad: %d sel(%d,%d)/%dx%d\n", __func__, sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); + mutex_lock(&rsz->ops_lock); rkisp1_rsz_set_sink_crop(rsz, cfg, &sel->r, sel->which); + mutex_unlock(&rsz->ops_lock); return 0; } @@ -672,9 +674,11 @@ static int rkisp1_rsz_s_stream(struct v4l2_subdev *sd, int enable) if (other->is_streaming) when = RKISP1_SHADOW_REGS_ASYNC; + mutex_lock(&rsz->ops_lock); rkisp1_rsz_config(rsz, when); rkisp1_dcrop_config(rsz); + mutex_unlock(&rsz->ops_lock); return 0; } @@ -720,6 +724,7 @@ static int rkisp1_rsz_register(struct rkisp1_resizer *rsz) rsz->fmt_type = RKISP1_DEF_FMT_TYPE; + mutex_init(&rsz->ops_lock); ret = media_entity_pads_init(&sd->entity, 2, pads); if (ret) return ret; diff --git a/drivers/staging/media/rkisp1/rkisp1-stats.c b/drivers/staging/media/rkisp1/rkisp1-stats.c index d98ea15837de..6dfcbdc3deb8 100644 --- a/drivers/staging/media/rkisp1/rkisp1-stats.c +++ b/drivers/staging/media/rkisp1/rkisp1-stats.c @@ -70,8 +70,7 @@ static int rkisp1_stats_querycap(struct file *file, strscpy(cap->driver, RKISP1_DRIVER_NAME, sizeof(cap->driver)); strscpy(cap->card, vdev->name, sizeof(cap->card)); - strscpy(cap->bus_info, "platform: " RKISP1_DRIVER_NAME, - sizeof(cap->bus_info)); + strscpy(cap->bus_info, RKISP1_BUS_INFO, sizeof(cap->bus_info)); return 0; } @@ -487,7 +486,7 @@ int rkisp1_stats_register(struct rkisp1_stats *stats, if (ret) goto err_release_queue; - ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) { dev_err(&vdev->dev, "failed to register %s, ret=%d\n", vdev->name, ret); diff --git a/drivers/staging/media/soc_camera/soc_camera.c b/drivers/staging/media/soc_camera/soc_camera.c index 7b9448e3c9ba..39f513f69b89 100644 --- a/drivers/staging/media/soc_camera/soc_camera.c +++ b/drivers/staging/media/soc_camera/soc_camera.c @@ -2068,7 +2068,7 @@ static int soc_camera_video_start(struct soc_camera_device *icd) v4l2_disable_ioctl(icd->vdev, VIDIOC_S_STD); v4l2_disable_ioctl(icd->vdev, VIDIOC_ENUMSTD); } - ret = video_register_device(icd->vdev, VFL_TYPE_GRABBER, -1); + ret = video_register_device(icd->vdev, VFL_TYPE_VIDEO, -1); if (ret < 0) { dev_err(icd->pdev, "video_register_device failed: %d\n", ret); return ret; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c index c6ddd46eff82..05a85517ff60 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus.c @@ -414,7 +414,7 @@ static int cedrus_probe(struct platform_device *pdev) dev->mdev.ops = &cedrus_m2m_media_ops; dev->v4l2_dev.mdev = &dev->mdev; - ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); if (ret) { v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); goto err_m2m; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c index bfb4a4820a67..54ee2aa423e2 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c @@ -610,8 +610,12 @@ static int cedrus_h264_start(struct cedrus_ctx *ctx) goto err_mv_col_buf; } + /* + * NOTE: Multiplying by two deviates from CedarX logic, but it + * is for some unknown reason needed for H264 4K decoding on H6. + */ ctx->codec.h264.intra_pred_buf_size = - ALIGN(ctx->src_fmt.width, 64) * 5; + ALIGN(ctx->src_fmt.width, 64) * 5 * 2; ctx->codec.h264.intra_pred_buf = dma_alloc_coherent(dev->dev, ctx->codec.h264.intra_pred_buf_size, diff --git a/drivers/staging/media/tegra-vde/vde.c b/drivers/staging/media/tegra-vde/vde.c index e18fd48981da..d3e63512a765 100644 --- a/drivers/staging/media/tegra-vde/vde.c +++ b/drivers/staging/media/tegra-vde/vde.c @@ -949,7 +949,6 @@ static int tegra_vde_runtime_resume(struct device *dev) static int tegra_vde_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *regs; struct tegra_vde *vde; int irq, err; @@ -959,75 +958,39 @@ static int tegra_vde_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vde); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sxe"); - if (!regs) - return -ENODEV; - - vde->sxe = devm_ioremap_resource(dev, regs); + vde->sxe = devm_platform_ioremap_resource_byname(pdev, "sxe"); if (IS_ERR(vde->sxe)) return PTR_ERR(vde->sxe); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "bsev"); - if (!regs) - return -ENODEV; - - vde->bsev = devm_ioremap_resource(dev, regs); + vde->bsev = devm_platform_ioremap_resource_byname(pdev, "bsev"); if (IS_ERR(vde->bsev)) return PTR_ERR(vde->bsev); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mbe"); - if (!regs) - return -ENODEV; - - vde->mbe = devm_ioremap_resource(dev, regs); + vde->mbe = devm_platform_ioremap_resource_byname(pdev, "mbe"); if (IS_ERR(vde->mbe)) return PTR_ERR(vde->mbe); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ppe"); - if (!regs) - return -ENODEV; - - vde->ppe = devm_ioremap_resource(dev, regs); + vde->ppe = devm_platform_ioremap_resource_byname(pdev, "ppe"); if (IS_ERR(vde->ppe)) return PTR_ERR(vde->ppe); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mce"); - if (!regs) - return -ENODEV; - - vde->mce = devm_ioremap_resource(dev, regs); + vde->mce = devm_platform_ioremap_resource_byname(pdev, "mce"); if (IS_ERR(vde->mce)) return PTR_ERR(vde->mce); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tfe"); - if (!regs) - return -ENODEV; - - vde->tfe = devm_ioremap_resource(dev, regs); + vde->tfe = devm_platform_ioremap_resource_byname(pdev, "tfe"); if (IS_ERR(vde->tfe)) return PTR_ERR(vde->tfe); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ppb"); - if (!regs) - return -ENODEV; - - vde->ppb = devm_ioremap_resource(dev, regs); + vde->ppb = devm_platform_ioremap_resource_byname(pdev, "ppb"); if (IS_ERR(vde->ppb)) return PTR_ERR(vde->ppb); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vdma"); - if (!regs) - return -ENODEV; - - vde->vdma = devm_ioremap_resource(dev, regs); + vde->vdma = devm_platform_ioremap_resource_byname(pdev, "vdma"); if (IS_ERR(vde->vdma)) return PTR_ERR(vde->vdma); - regs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "frameid"); - if (!regs) - return -ENODEV; - - vde->frameid = devm_ioremap_resource(dev, regs); + vde->frameid = devm_platform_ioremap_resource_byname(pdev, "frameid"); if (IS_ERR(vde->frameid)) return PTR_ERR(vde->frameid); diff --git a/drivers/staging/media/usbvision/Kconfig b/drivers/staging/media/usbvision/Kconfig new file mode 100644 index 000000000000..c6e1afb5ac48 --- /dev/null +++ b/drivers/staging/media/usbvision/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_USBVISION + tristate "USB video devices based on Nogatech NT1003/1004/1005 (Deprecated)" + depends on MEDIA_USB_SUPPORT && I2C && VIDEO_V4L2 + select VIDEO_TUNER + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + help + There are more than 50 different USB video devices based on + NT1003/1004/1005 USB Bridges. This driver enables using those + devices. + + This driver is deprecated and scheduled for removal by the + end of 2020. See the TODO file in drivers/staging/media/usbvision + for a list of actions that have to be done in order to prevent + removal of this driver. + + To compile this driver as a module, choose M here: the + module will be called usbvision. diff --git a/drivers/staging/media/usbvision/Makefile b/drivers/staging/media/usbvision/Makefile new file mode 100644 index 000000000000..4d8541b9d4f8 --- /dev/null +++ b/drivers/staging/media/usbvision/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +usbvision-objs := usbvision-core.o usbvision-video.o usbvision-i2c.o usbvision-cards.o + +obj-$(CONFIG_VIDEO_USBVISION) += usbvision.o diff --git a/drivers/staging/media/usbvision/TODO b/drivers/staging/media/usbvision/TODO new file mode 100644 index 000000000000..e9fb4d125581 --- /dev/null +++ b/drivers/staging/media/usbvision/TODO @@ -0,0 +1,11 @@ +The driver is deprecated and scheduled for removal by the end +of 2020. + +In order to prevent removal the following actions would have to +be taken: + +- clean up the code +- convert to the vb2 framework +- fix the disconnect and free-on-last-user handling (i.e., add + a release callback for struct v4l2_device and rework the code + to use that correctly). diff --git a/drivers/staging/media/usbvision/usbvision-cards.c b/drivers/staging/media/usbvision/usbvision-cards.c new file mode 100644 index 000000000000..5e0cbbfe7c86 --- /dev/null +++ b/drivers/staging/media/usbvision/usbvision-cards.c @@ -0,0 +1,1120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbvision-cards.c + * usbvision cards definition file + * + * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de> + * + * This module is part of usbvision driver project. + * Updates to driver completed by Dwaine P. Garden + */ + + +#include <linux/list.h> +#include <linux/module.h> +#include <media/v4l2-dev.h> +#include <media/tuner.h> +#include "usbvision.h" +#include "usbvision-cards.h" + +/* Supported Devices: A table for usbvision.c*/ +struct usbvision_device_data_st usbvision_device_data[] = { + [XANBOO] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 4, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = -1, + .y_offset = -1, + .model_string = "Xanboo", + }, + [BELKIN_VIDEOBUS_II] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Belkin USB VideoBus II Adapter", + }, + [BELKIN_VIDEOBUS] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = -1, + .y_offset = -1, + .model_string = "Belkin Components USB VideoBus", + }, + [BELKIN_USB_VIDEOBUS_II] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Belkin USB VideoBus II", + }, + [ECHOFX_INTERVIEW_LITE] = { + .interface = 0, + .codec = CODEC_SAA7111, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = -1, + .y_offset = -1, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "echoFX InterView Lite", + }, + [USBGEAR_USBG_V1] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = -1, + .y_offset = -1, + .model_string = "USBGear USBG-V1 resp. HAMA USB", + }, + [D_LINK_V100] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 4, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "D-Link V100", + }, + [X10_USB_CAMERA] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = -1, + .y_offset = -1, + .model_string = "X10 USB Camera", + }, + [HPG_WINTV_LIVE_PAL_BG] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = -1, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Live (PAL B/G)", + }, + [HPG_WINTV_LIVE_PRO_NTSC_MN] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Live Pro (NTSC M/N)", + }, + [ZORAN_PMD_NOGATECH] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 2, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Zoran Co. PMD (Nogatech) AV-grabber Manhattan", + }, + [NOGATECH_USB_TV_NTSC_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = 20, + .model_string = "Nogatech USB-TV (NTSC) FM", + }, + [PNY_USB_TV_NTSC_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = 20, + .model_string = "PNY USB-TV (NTSC) FM", + }, + [PV_PLAYTV_USB_PRO_PAL_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "PixelView PlayTv-USB PRO (PAL) FM", + }, + [ZT_721] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "ZTV ZT-721 2.4GHz USB A/V Receiver", + }, + [HPG_WINTV_NTSC_MN] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = 20, + .model_string = "Hauppauge WinTV USB (NTSC M/N)", + }, + [HPG_WINTV_PAL_BG] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (PAL B/G)", + }, + [HPG_WINTV_PAL_I] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (PAL I)", + }, + [HPG_WINTV_PAL_SECAM_L] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_SECAM, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_SECAM, + .x_offset = 0x80, + .y_offset = 0x16, + .model_string = "Hauppauge WinTV USB (PAL/SECAM L)", + }, + [HPG_WINTV_PAL_D_K] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (PAL D/K)", + }, + [HPG_WINTV_NTSC_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (NTSC FM)", + }, + [HPG_WINTV_PAL_BG_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (PAL B/G FM)", + }, + [HPG_WINTV_PAL_I_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (PAL I FM)", + }, + [HPG_WINTV_PAL_D_K_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTV USB (PAL D/K FM)", + }, + [HPG_WINTV_PRO_NTSC_MN] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_MICROTUNE_4049FM5, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (NTSC M/N)", + }, + [HPG_WINTV_PRO_NTSC_MN_V2] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_MICROTUNE_4049FM5, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (NTSC M/N) V2", + }, + [HPG_WINTV_PRO_PAL] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL/SECAM B/G/I/D/K/L)", + }, + [HPG_WINTV_PRO_NTSC_MN_V3] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (NTSC M/N) V3", + }, + [HPG_WINTV_PRO_PAL_BG] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL B/G)", + }, + [HPG_WINTV_PRO_PAL_I] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL I)", + }, + [HPG_WINTV_PRO_PAL_SECAM_L] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_SECAM, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_SECAM, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL/SECAM L)", + }, + [HPG_WINTV_PRO_PAL_D_K] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL D/K)", + }, + [HPG_WINTV_PRO_PAL_SECAM] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_SECAM, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_SECAM, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL/SECAM BGDK/I/L)", + }, + [HPG_WINTV_PRO_PAL_SECAM_V2] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_SECAM, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_SECAM, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL/SECAM BGDK/I/L) V2", + }, + [HPG_WINTV_PRO_PAL_BG_V2] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_ALPS_TSBE1_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL B/G) V2", + }, + [HPG_WINTV_PRO_PAL_BG_D_K] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_ALPS_TSBE1_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL B/G,D/K)", + }, + [HPG_WINTV_PRO_PAL_I_D_K] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL I,D/K)", + }, + [HPG_WINTV_PRO_NTSC_MN_FM] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (NTSC M/N FM)", + }, + [HPG_WINTV_PRO_PAL_BG_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL B/G FM)", + }, + [HPG_WINTV_PRO_PAL_I_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL I FM)", + }, + [HPG_WINTV_PRO_PAL_D_K_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL D/K FM)", + }, + [HPG_WINTV_PRO_TEMIC_PAL_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_MICROTUNE_4049FM5, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (Temic PAL/SECAM B/G/I/D/K/L FM)", + }, + [HPG_WINTV_PRO_TEMIC_PAL_BG_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_MICROTUNE_4049FM5, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (Temic PAL B/G FM)", + }, + [HPG_WINTV_PRO_PAL_FM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (PAL/SECAM B/G/I/D/K/L FM)", + }, + [HPG_WINTV_PRO_NTSC_MN_FM_V2] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Hauppauge WinTV USB Pro (NTSC M/N FM) V2", + }, + [CAMTEL_TVB330] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = 5, + .y_offset = 5, + .model_string = "Camtel Technology USB TV Genie Pro FM Model TVB330", + }, + [DIGITAL_VIDEO_CREATOR_I] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Digital Video Creator I", + }, + [GLOBAL_VILLAGE_GV_007_NTSC] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 82, + .y_offset = 20, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Global Village GV-007 (NTSC)", + }, + [DAZZLE_DVC_50_REV_1_NTSC] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Dazzle Fusion Model DVC-50 Rev 1 (NTSC)", + }, + [DAZZLE_DVC_80_REV_1_PAL] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Dazzle Fusion Model DVC-80 Rev 1 (PAL)", + }, + [DAZZLE_DVC_90_REV_1_SECAM] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_SECAM, + .audio_channels = 0, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Dazzle Fusion Model DVC-90 Rev 1 (SECAM)", + }, + [ESKAPE_LABS_MYTV2GO] = { + .interface = 0, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Eskape Labs MyTV2Go", + }, + [PINNA_PCTV_USB_PAL] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 0, + .tuner = 1, + .tuner_type = TUNER_TEMIC_4066FY5_PAL_I, + .x_offset = -1, + .y_offset = -1, + .model_string = "Pinnacle Studio PCTV USB (PAL)", + }, + [PINNA_PCTV_USB_SECAM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_SECAM, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_SECAM, + .x_offset = -1, + .y_offset = -1, + .model_string = "Pinnacle Studio PCTV USB (SECAM)", + }, + [PINNA_PCTV_USB_PAL_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = 128, + .y_offset = 23, + .model_string = "Pinnacle Studio PCTV USB (PAL) FM", + }, + [MIRO_PCTV_USB] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .x_offset = -1, + .y_offset = -1, + .model_string = "Miro PCTV USB", + }, + [PINNA_PCTV_USB_NTSC_FM] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = -1, + .model_string = "Pinnacle Studio PCTV USB (NTSC) FM", + }, + [PINNA_PCTV_USB_NTSC_FM_V3] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = -1, + .model_string = "Pinnacle Studio PCTV USB (NTSC) FM V3", + }, + [PINNA_PCTV_USB_PAL_FM_V2] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_TEMIC_4009FR5_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Pinnacle Studio PCTV USB (PAL) FM V2", + }, + [PINNA_PCTV_USB_NTSC_FM_V2] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_TEMIC_4039FR5_NTSC, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Pinnacle Studio PCTV USB (NTSC) FM V2", + }, + [PINNA_PCTV_USB_PAL_FM_V3] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_TEMIC_4009FR5_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Pinnacle Studio PCTV USB (PAL) FM V3", + }, + [PINNA_LINX_VD_IN_CAB_NTSC] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Pinnacle Studio Linx Video input cable (NTSC)", + }, + [PINNA_LINX_VD_IN_CAB_PAL] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 2, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 0, + .tuner_type = 0, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Pinnacle Studio Linx Video input cable (PAL)", + }, + [PINNA_PCTV_BUNGEE_PAL_FM] = { + .interface = -1, + .codec = CODEC_SAA7113, + .video_channels = 3, + .video_norm = V4L2_STD_PAL, + .audio_channels = 1, + .radio = 1, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_TEMIC_4009FR5_PAL, + .x_offset = 0, + .y_offset = 3, + .dvi_yuv_override = 1, + .dvi_yuv = 7, + .model_string = "Pinnacle PCTV Bungee USB (PAL) FM", + }, + [HPG_WINTV] = { + .interface = -1, + .codec = CODEC_SAA7111, + .video_channels = 3, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 1, + .radio = 0, + .vbi = 1, + .tuner = 1, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .x_offset = -1, + .y_offset = -1, + .model_string = "Hauppauge WinTv-USB", + }, + [MICROCAM_NTSC] = { + .interface = -1, + .codec = CODEC_WEBCAM, + .video_channels = 1, + .video_norm = V4L2_STD_NTSC, + .audio_channels = 0, + .radio = 0, + .vbi = 0, + .tuner = 0, + .tuner_type = 0, + .x_offset = 71, + .y_offset = 15, + .model_string = "Nogatech USB MicroCam NTSC (NV3000N)", + }, + [MICROCAM_PAL] = { + .interface = -1, + .codec = CODEC_WEBCAM, + .video_channels = 1, + .video_norm = V4L2_STD_PAL, + .audio_channels = 0, + .radio = 0, + .vbi = 0, + .tuner = 0, + .tuner_type = 0, + .x_offset = 71, + .y_offset = 18, + .model_string = "Nogatech USB MicroCam PAL (NV3001P)", + }, +}; +const int usbvision_device_data_size = ARRAY_SIZE(usbvision_device_data); + +/* Supported Devices */ + +struct usb_device_id usbvision_table[] = { + { USB_DEVICE(0x0a6f, 0x0400), .driver_info = XANBOO }, + { USB_DEVICE(0x050d, 0x0106), .driver_info = BELKIN_VIDEOBUS_II }, + { USB_DEVICE(0x050d, 0x0207), .driver_info = BELKIN_VIDEOBUS }, + { USB_DEVICE(0x050d, 0x0208), .driver_info = BELKIN_USB_VIDEOBUS_II }, + { USB_DEVICE(0x0571, 0x0002), .driver_info = ECHOFX_INTERVIEW_LITE }, + { USB_DEVICE(0x0573, 0x0003), .driver_info = USBGEAR_USBG_V1 }, + { USB_DEVICE(0x0573, 0x0400), .driver_info = D_LINK_V100 }, + { USB_DEVICE(0x0573, 0x2000), .driver_info = X10_USB_CAMERA }, + { USB_DEVICE(0x0573, 0x2d00), .driver_info = HPG_WINTV_LIVE_PAL_BG }, + { USB_DEVICE(0x0573, 0x2d01), .driver_info = HPG_WINTV_LIVE_PRO_NTSC_MN }, + { USB_DEVICE(0x0573, 0x2101), .driver_info = ZORAN_PMD_NOGATECH }, + { USB_DEVICE(0x0573, 0x3000), .driver_info = MICROCAM_NTSC }, + { USB_DEVICE(0x0573, 0x3001), .driver_info = MICROCAM_PAL }, + { USB_DEVICE(0x0573, 0x4100), .driver_info = NOGATECH_USB_TV_NTSC_FM }, + { USB_DEVICE(0x0573, 0x4110), .driver_info = PNY_USB_TV_NTSC_FM }, + { USB_DEVICE(0x0573, 0x4450), .driver_info = PV_PLAYTV_USB_PRO_PAL_FM }, + { USB_DEVICE(0x0573, 0x4550), .driver_info = ZT_721 }, + { USB_DEVICE(0x0573, 0x4d00), .driver_info = HPG_WINTV_NTSC_MN }, + { USB_DEVICE(0x0573, 0x4d01), .driver_info = HPG_WINTV_PAL_BG }, + { USB_DEVICE(0x0573, 0x4d02), .driver_info = HPG_WINTV_PAL_I }, + { USB_DEVICE(0x0573, 0x4d03), .driver_info = HPG_WINTV_PAL_SECAM_L }, + { USB_DEVICE(0x0573, 0x4d04), .driver_info = HPG_WINTV_PAL_D_K }, + { USB_DEVICE(0x0573, 0x4d10), .driver_info = HPG_WINTV_NTSC_FM }, + { USB_DEVICE(0x0573, 0x4d11), .driver_info = HPG_WINTV_PAL_BG_FM }, + { USB_DEVICE(0x0573, 0x4d12), .driver_info = HPG_WINTV_PAL_I_FM }, + { USB_DEVICE(0x0573, 0x4d14), .driver_info = HPG_WINTV_PAL_D_K_FM }, + { USB_DEVICE(0x0573, 0x4d2a), .driver_info = HPG_WINTV_PRO_NTSC_MN }, + { USB_DEVICE(0x0573, 0x4d2b), .driver_info = HPG_WINTV_PRO_NTSC_MN_V2 }, + { USB_DEVICE(0x0573, 0x4d2c), .driver_info = HPG_WINTV_PRO_PAL }, + { USB_DEVICE(0x0573, 0x4d20), .driver_info = HPG_WINTV_PRO_NTSC_MN_V3 }, + { USB_DEVICE(0x0573, 0x4d21), .driver_info = HPG_WINTV_PRO_PAL_BG }, + { USB_DEVICE(0x0573, 0x4d22), .driver_info = HPG_WINTV_PRO_PAL_I }, + { USB_DEVICE(0x0573, 0x4d23), .driver_info = HPG_WINTV_PRO_PAL_SECAM_L }, + { USB_DEVICE(0x0573, 0x4d24), .driver_info = HPG_WINTV_PRO_PAL_D_K }, + { USB_DEVICE(0x0573, 0x4d25), .driver_info = HPG_WINTV_PRO_PAL_SECAM }, + { USB_DEVICE(0x0573, 0x4d26), .driver_info = HPG_WINTV_PRO_PAL_SECAM_V2 }, + { USB_DEVICE(0x0573, 0x4d27), .driver_info = HPG_WINTV_PRO_PAL_BG_V2 }, + { USB_DEVICE(0x0573, 0x4d28), .driver_info = HPG_WINTV_PRO_PAL_BG_D_K }, + { USB_DEVICE(0x0573, 0x4d29), .driver_info = HPG_WINTV_PRO_PAL_I_D_K }, + { USB_DEVICE(0x0573, 0x4d30), .driver_info = HPG_WINTV_PRO_NTSC_MN_FM }, + { USB_DEVICE(0x0573, 0x4d31), .driver_info = HPG_WINTV_PRO_PAL_BG_FM }, + { USB_DEVICE(0x0573, 0x4d32), .driver_info = HPG_WINTV_PRO_PAL_I_FM }, + { USB_DEVICE(0x0573, 0x4d34), .driver_info = HPG_WINTV_PRO_PAL_D_K_FM }, + { USB_DEVICE(0x0573, 0x4d35), .driver_info = HPG_WINTV_PRO_TEMIC_PAL_FM }, + { USB_DEVICE(0x0573, 0x4d36), .driver_info = HPG_WINTV_PRO_TEMIC_PAL_BG_FM }, + { USB_DEVICE(0x0573, 0x4d37), .driver_info = HPG_WINTV_PRO_PAL_FM }, + { USB_DEVICE(0x0573, 0x4d38), .driver_info = HPG_WINTV_PRO_NTSC_MN_FM_V2 }, + { USB_DEVICE(0x0768, 0x0006), .driver_info = CAMTEL_TVB330 }, + { USB_DEVICE(0x07d0, 0x0001), .driver_info = DIGITAL_VIDEO_CREATOR_I }, + { USB_DEVICE(0x07d0, 0x0002), .driver_info = GLOBAL_VILLAGE_GV_007_NTSC }, + { USB_DEVICE(0x07d0, 0x0003), .driver_info = DAZZLE_DVC_50_REV_1_NTSC }, + { USB_DEVICE(0x07d0, 0x0004), .driver_info = DAZZLE_DVC_80_REV_1_PAL }, + { USB_DEVICE(0x07d0, 0x0005), .driver_info = DAZZLE_DVC_90_REV_1_SECAM }, + { USB_DEVICE(0x07f8, 0x9104), .driver_info = ESKAPE_LABS_MYTV2GO }, + { USB_DEVICE(0x2304, 0x010d), .driver_info = PINNA_PCTV_USB_PAL }, + { USB_DEVICE(0x2304, 0x0109), .driver_info = PINNA_PCTV_USB_SECAM }, + { USB_DEVICE(0x2304, 0x0110), .driver_info = PINNA_PCTV_USB_PAL_FM }, + { USB_DEVICE(0x2304, 0x0111), .driver_info = MIRO_PCTV_USB }, + { USB_DEVICE(0x2304, 0x0112), .driver_info = PINNA_PCTV_USB_NTSC_FM }, + { USB_DEVICE(0x2304, 0x0113), .driver_info = PINNA_PCTV_USB_NTSC_FM_V3 }, + { USB_DEVICE(0x2304, 0x0210), .driver_info = PINNA_PCTV_USB_PAL_FM_V2 }, + { USB_DEVICE(0x2304, 0x0212), .driver_info = PINNA_PCTV_USB_NTSC_FM_V2 }, + { USB_DEVICE(0x2304, 0x0214), .driver_info = PINNA_PCTV_USB_PAL_FM_V3 }, + { USB_DEVICE(0x2304, 0x0300), .driver_info = PINNA_LINX_VD_IN_CAB_NTSC }, + { USB_DEVICE(0x2304, 0x0301), .driver_info = PINNA_LINX_VD_IN_CAB_PAL }, + { USB_DEVICE(0x2304, 0x0419), .driver_info = PINNA_PCTV_BUNGEE_PAL_FM }, + { USB_DEVICE(0x2400, 0x4200), .driver_info = HPG_WINTV }, + { }, /* terminate list */ +}; + +MODULE_DEVICE_TABLE(usb, usbvision_table); diff --git a/drivers/staging/media/usbvision/usbvision-cards.h b/drivers/staging/media/usbvision/usbvision-cards.h new file mode 100644 index 000000000000..07ec83512743 --- /dev/null +++ b/drivers/staging/media/usbvision/usbvision-cards.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define XANBOO 0 +#define BELKIN_VIDEOBUS_II 1 +#define BELKIN_VIDEOBUS 2 +#define BELKIN_USB_VIDEOBUS_II 3 +#define ECHOFX_INTERVIEW_LITE 4 +#define USBGEAR_USBG_V1 5 +#define D_LINK_V100 6 +#define X10_USB_CAMERA 7 +#define HPG_WINTV_LIVE_PAL_BG 8 +#define HPG_WINTV_LIVE_PRO_NTSC_MN 9 +#define ZORAN_PMD_NOGATECH 10 +#define NOGATECH_USB_TV_NTSC_FM 11 +#define PNY_USB_TV_NTSC_FM 12 +#define PV_PLAYTV_USB_PRO_PAL_FM 13 +#define ZT_721 14 +#define HPG_WINTV_NTSC_MN 15 +#define HPG_WINTV_PAL_BG 16 +#define HPG_WINTV_PAL_I 17 +#define HPG_WINTV_PAL_SECAM_L 18 +#define HPG_WINTV_PAL_D_K 19 +#define HPG_WINTV_NTSC_FM 20 +#define HPG_WINTV_PAL_BG_FM 21 +#define HPG_WINTV_PAL_I_FM 22 +#define HPG_WINTV_PAL_D_K_FM 23 +#define HPG_WINTV_PRO_NTSC_MN 24 +#define HPG_WINTV_PRO_NTSC_MN_V2 25 +#define HPG_WINTV_PRO_PAL 26 +#define HPG_WINTV_PRO_NTSC_MN_V3 27 +#define HPG_WINTV_PRO_PAL_BG 28 +#define HPG_WINTV_PRO_PAL_I 29 +#define HPG_WINTV_PRO_PAL_SECAM_L 30 +#define HPG_WINTV_PRO_PAL_D_K 31 +#define HPG_WINTV_PRO_PAL_SECAM 32 +#define HPG_WINTV_PRO_PAL_SECAM_V2 33 +#define HPG_WINTV_PRO_PAL_BG_V2 34 +#define HPG_WINTV_PRO_PAL_BG_D_K 35 +#define HPG_WINTV_PRO_PAL_I_D_K 36 +#define HPG_WINTV_PRO_NTSC_MN_FM 37 +#define HPG_WINTV_PRO_PAL_BG_FM 38 +#define HPG_WINTV_PRO_PAL_I_FM 39 +#define HPG_WINTV_PRO_PAL_D_K_FM 40 +#define HPG_WINTV_PRO_TEMIC_PAL_FM 41 +#define HPG_WINTV_PRO_TEMIC_PAL_BG_FM 42 +#define HPG_WINTV_PRO_PAL_FM 43 +#define HPG_WINTV_PRO_NTSC_MN_FM_V2 44 +#define CAMTEL_TVB330 45 +#define DIGITAL_VIDEO_CREATOR_I 46 +#define GLOBAL_VILLAGE_GV_007_NTSC 47 +#define DAZZLE_DVC_50_REV_1_NTSC 48 +#define DAZZLE_DVC_80_REV_1_PAL 49 +#define DAZZLE_DVC_90_REV_1_SECAM 50 +#define ESKAPE_LABS_MYTV2GO 51 +#define PINNA_PCTV_USB_PAL 52 +#define PINNA_PCTV_USB_SECAM 53 +#define PINNA_PCTV_USB_PAL_FM 54 +#define MIRO_PCTV_USB 55 +#define PINNA_PCTV_USB_NTSC_FM 56 +#define PINNA_PCTV_USB_PAL_FM_V2 57 +#define PINNA_PCTV_USB_NTSC_FM_V2 58 +#define PINNA_PCTV_USB_PAL_FM_V3 59 +#define PINNA_LINX_VD_IN_CAB_NTSC 60 +#define PINNA_LINX_VD_IN_CAB_PAL 61 +#define PINNA_PCTV_BUNGEE_PAL_FM 62 +#define HPG_WINTV 63 +#define PINNA_PCTV_USB_NTSC_FM_V3 64 +#define MICROCAM_NTSC 65 +#define MICROCAM_PAL 66 + +extern const int usbvision_device_data_size; diff --git a/drivers/staging/media/usbvision/usbvision-core.c b/drivers/staging/media/usbvision/usbvision-core.c new file mode 100644 index 000000000000..f05a5c84dc18 --- /dev/null +++ b/drivers/staging/media/usbvision/usbvision-core.c @@ -0,0 +1,2428 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbvision-core.c - driver for NT100x USB video capture devices + * + * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de> + * Dwaine Garden <dwainegarden@rogers.com> + * + * This module is part of usbvision driver project. + * Updates to driver completed by Dwaine P. Garden + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/timer.h> +#include <linux/gfp.h> +#include <linux/mm.h> +#include <linux/highmem.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> + +#include <media/i2c/saa7115.h> +#include <media/v4l2-common.h> +#include <media/tuner.h> + +#include <linux/workqueue.h> + +#include "usbvision.h" + +static unsigned int core_debug; +module_param(core_debug, int, 0644); +MODULE_PARM_DESC(core_debug, "enable debug messages [core]"); + +static int adjust_compression = 1; /* Set the compression to be adaptive */ +module_param(adjust_compression, int, 0444); +MODULE_PARM_DESC(adjust_compression, " Set the ADPCM compression for the device. Default: 1 (On)"); + +/* To help people with Black and White output with using s-video input. + * Some cables and input device are wired differently. */ +static int switch_svideo_input; +module_param(switch_svideo_input, int, 0444); +MODULE_PARM_DESC(switch_svideo_input, " Set the S-Video input. Some cables and input device are wired differently. Default: 0 (Off)"); + +static unsigned int adjust_x_offset = -1; +module_param(adjust_x_offset, int, 0644); +MODULE_PARM_DESC(adjust_x_offset, "adjust X offset display [core]"); + +static unsigned int adjust_y_offset = -1; +module_param(adjust_y_offset, int, 0644); +MODULE_PARM_DESC(adjust_y_offset, "adjust Y offset display [core]"); + + +#define ENABLE_HEXDUMP 0 /* Enable if you need it */ + + +#ifdef USBVISION_DEBUG + #define PDEBUG(level, fmt, args...) { \ + if (core_debug & (level)) \ + printk(KERN_INFO KBUILD_MODNAME ":[%s:%d] " fmt, \ + __func__, __LINE__ , ## args); \ + } +#else + #define PDEBUG(level, fmt, args...) do {} while (0) +#endif + +#define DBG_HEADER (1 << 0) +#define DBG_IRQ (1 << 1) +#define DBG_ISOC (1 << 2) +#define DBG_PARSE (1 << 3) +#define DBG_SCRATCH (1 << 4) +#define DBG_FUNC (1 << 5) + +/* The value of 'scratch_buf_size' affects quality of the picture + * in many ways. Shorter buffers may cause loss of data when client + * is too slow. Larger buffers are memory-consuming and take longer + * to work with. This setting can be adjusted, but the default value + * should be OK for most desktop users. + */ +#define DEFAULT_SCRATCH_BUF_SIZE (0x20000) /* 128kB memory scratch buffer */ +static const int scratch_buf_size = DEFAULT_SCRATCH_BUF_SIZE; + +/* Function prototypes */ +static int usbvision_request_intra(struct usb_usbvision *usbvision); +static int usbvision_unrequest_intra(struct usb_usbvision *usbvision); +static int usbvision_adjust_compression(struct usb_usbvision *usbvision); +static int usbvision_measure_bandwidth(struct usb_usbvision *usbvision); + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +/* + * Here we want the physical address of the memory. + * This is used when initializing the contents of the area. + */ + +static void *usbvision_rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void usbvision_rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + size = PAGE_ALIGN(size); + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + vfree(mem); +} + + +#if ENABLE_HEXDUMP +static void usbvision_hexdump(const unsigned char *data, int len) +{ + char tmp[80]; + int i, k; + + for (i = k = 0; len > 0; i++, len--) { + if (i > 0 && (i % 16 == 0)) { + printk("%s\n", tmp); + k = 0; + } + k += sprintf(&tmp[k], "%02x ", data[i]); + } + if (k > 0) + printk(KERN_CONT "%s\n", tmp); +} +#endif + +/******************************** + * scratch ring buffer handling + ********************************/ +static int scratch_len(struct usb_usbvision *usbvision) /* This returns the amount of data actually in the buffer */ +{ + int len = usbvision->scratch_write_ptr - usbvision->scratch_read_ptr; + + if (len < 0) + len += scratch_buf_size; + PDEBUG(DBG_SCRATCH, "scratch_len() = %d\n", len); + + return len; +} + + +/* This returns the free space left in the buffer */ +static int scratch_free(struct usb_usbvision *usbvision) +{ + int free = usbvision->scratch_read_ptr - usbvision->scratch_write_ptr; + if (free <= 0) + free += scratch_buf_size; + if (free) { + free -= 1; /* at least one byte in the buffer must */ + /* left blank, otherwise there is no chance to differ between full and empty */ + } + PDEBUG(DBG_SCRATCH, "return %d\n", free); + + return free; +} + + +/* This puts data into the buffer */ +static int scratch_put(struct usb_usbvision *usbvision, unsigned char *data, + int len) +{ + int len_part; + + if (usbvision->scratch_write_ptr + len < scratch_buf_size) { + memcpy(usbvision->scratch + usbvision->scratch_write_ptr, data, len); + usbvision->scratch_write_ptr += len; + } else { + len_part = scratch_buf_size - usbvision->scratch_write_ptr; + memcpy(usbvision->scratch + usbvision->scratch_write_ptr, data, len_part); + if (len == len_part) { + usbvision->scratch_write_ptr = 0; /* just set write_ptr to zero */ + } else { + memcpy(usbvision->scratch, data + len_part, len - len_part); + usbvision->scratch_write_ptr = len - len_part; + } + } + + PDEBUG(DBG_SCRATCH, "len=%d, new write_ptr=%d\n", len, usbvision->scratch_write_ptr); + + return len; +} + +/* This marks the write_ptr as position of new frame header */ +static void scratch_mark_header(struct usb_usbvision *usbvision) +{ + PDEBUG(DBG_SCRATCH, "header at write_ptr=%d\n", usbvision->scratch_headermarker_write_ptr); + + usbvision->scratch_headermarker[usbvision->scratch_headermarker_write_ptr] = + usbvision->scratch_write_ptr; + usbvision->scratch_headermarker_write_ptr += 1; + usbvision->scratch_headermarker_write_ptr %= USBVISION_NUM_HEADERMARKER; +} + +/* This gets data from the buffer at the given "ptr" position */ +static int scratch_get_extra(struct usb_usbvision *usbvision, + unsigned char *data, int *ptr, int len) +{ + int len_part; + + if (*ptr + len < scratch_buf_size) { + memcpy(data, usbvision->scratch + *ptr, len); + *ptr += len; + } else { + len_part = scratch_buf_size - *ptr; + memcpy(data, usbvision->scratch + *ptr, len_part); + if (len == len_part) { + *ptr = 0; /* just set the y_ptr to zero */ + } else { + memcpy(data + len_part, usbvision->scratch, len - len_part); + *ptr = len - len_part; + } + } + + PDEBUG(DBG_SCRATCH, "len=%d, new ptr=%d\n", len, *ptr); + + return len; +} + + +/* This sets the scratch extra read pointer */ +static void scratch_set_extra_ptr(struct usb_usbvision *usbvision, int *ptr, + int len) +{ + *ptr = (usbvision->scratch_read_ptr + len) % scratch_buf_size; + + PDEBUG(DBG_SCRATCH, "ptr=%d\n", *ptr); +} + + +/* This increments the scratch extra read pointer */ +static void scratch_inc_extra_ptr(int *ptr, int len) +{ + *ptr = (*ptr + len) % scratch_buf_size; + + PDEBUG(DBG_SCRATCH, "ptr=%d\n", *ptr); +} + + +/* This gets data from the buffer */ +static int scratch_get(struct usb_usbvision *usbvision, unsigned char *data, + int len) +{ + int len_part; + + if (usbvision->scratch_read_ptr + len < scratch_buf_size) { + memcpy(data, usbvision->scratch + usbvision->scratch_read_ptr, len); + usbvision->scratch_read_ptr += len; + } else { + len_part = scratch_buf_size - usbvision->scratch_read_ptr; + memcpy(data, usbvision->scratch + usbvision->scratch_read_ptr, len_part); + if (len == len_part) { + usbvision->scratch_read_ptr = 0; /* just set the read_ptr to zero */ + } else { + memcpy(data + len_part, usbvision->scratch, len - len_part); + usbvision->scratch_read_ptr = len - len_part; + } + } + + PDEBUG(DBG_SCRATCH, "len=%d, new read_ptr=%d\n", len, usbvision->scratch_read_ptr); + + return len; +} + + +/* This sets read pointer to next header and returns it */ +static int scratch_get_header(struct usb_usbvision *usbvision, + struct usbvision_frame_header *header) +{ + int err_code = 0; + + PDEBUG(DBG_SCRATCH, "from read_ptr=%d", usbvision->scratch_headermarker_read_ptr); + + while (usbvision->scratch_headermarker_write_ptr - + usbvision->scratch_headermarker_read_ptr != 0) { + usbvision->scratch_read_ptr = + usbvision->scratch_headermarker[usbvision->scratch_headermarker_read_ptr]; + usbvision->scratch_headermarker_read_ptr += 1; + usbvision->scratch_headermarker_read_ptr %= USBVISION_NUM_HEADERMARKER; + scratch_get(usbvision, (unsigned char *)header, USBVISION_HEADER_LENGTH); + if ((header->magic_1 == USBVISION_MAGIC_1) + && (header->magic_2 == USBVISION_MAGIC_2) + && (header->header_length == USBVISION_HEADER_LENGTH)) { + err_code = USBVISION_HEADER_LENGTH; + header->frame_width = header->frame_width_lo + (header->frame_width_hi << 8); + header->frame_height = header->frame_height_lo + (header->frame_height_hi << 8); + break; + } + } + + return err_code; +} + + +/* This removes len bytes of old data from the buffer */ +static void scratch_rm_old(struct usb_usbvision *usbvision, int len) +{ + usbvision->scratch_read_ptr += len; + usbvision->scratch_read_ptr %= scratch_buf_size; + PDEBUG(DBG_SCRATCH, "read_ptr is now %d\n", usbvision->scratch_read_ptr); +} + + +/* This resets the buffer - kills all data in it too */ +static void scratch_reset(struct usb_usbvision *usbvision) +{ + PDEBUG(DBG_SCRATCH, "\n"); + + usbvision->scratch_read_ptr = 0; + usbvision->scratch_write_ptr = 0; + usbvision->scratch_headermarker_read_ptr = 0; + usbvision->scratch_headermarker_write_ptr = 0; + usbvision->isocstate = isoc_state_no_frame; +} + +int usbvision_scratch_alloc(struct usb_usbvision *usbvision) +{ + usbvision->scratch = vmalloc_32(scratch_buf_size); + scratch_reset(usbvision); + if (usbvision->scratch == NULL) { + dev_err(&usbvision->dev->dev, + "%s: unable to allocate %d bytes for scratch\n", + __func__, scratch_buf_size); + return -ENOMEM; + } + return 0; +} + +void usbvision_scratch_free(struct usb_usbvision *usbvision) +{ + vfree(usbvision->scratch); + usbvision->scratch = NULL; +} + +/* + * usbvision_decompress_alloc() + * + * allocates intermediate buffer for decompression + */ +int usbvision_decompress_alloc(struct usb_usbvision *usbvision) +{ + int IFB_size = MAX_FRAME_WIDTH * MAX_FRAME_HEIGHT * 3 / 2; + + usbvision->intra_frame_buffer = vmalloc_32(IFB_size); + if (usbvision->intra_frame_buffer == NULL) { + dev_err(&usbvision->dev->dev, + "%s: unable to allocate %d for compr. frame buffer\n", + __func__, IFB_size); + return -ENOMEM; + } + return 0; +} + +/* + * usbvision_decompress_free() + * + * frees intermediate buffer for decompression + */ +void usbvision_decompress_free(struct usb_usbvision *usbvision) +{ + vfree(usbvision->intra_frame_buffer); + usbvision->intra_frame_buffer = NULL; + +} + +/************************************************************ + * Here comes the data parsing stuff that is run as interrupt + ************************************************************/ +/* + * usbvision_find_header() + * + * Locate one of supported header markers in the scratch buffer. + */ +static enum parse_state usbvision_find_header(struct usb_usbvision *usbvision) +{ + struct usbvision_frame *frame; + int found_header = 0; + + frame = usbvision->cur_frame; + + while (scratch_get_header(usbvision, &frame->isoc_header) == USBVISION_HEADER_LENGTH) { + /* found header in scratch */ + PDEBUG(DBG_HEADER, "found header: 0x%02x%02x %d %d %d %d %#x 0x%02x %u %u", + frame->isoc_header.magic_2, + frame->isoc_header.magic_1, + frame->isoc_header.header_length, + frame->isoc_header.frame_num, + frame->isoc_header.frame_phase, + frame->isoc_header.frame_latency, + frame->isoc_header.data_format, + frame->isoc_header.format_param, + frame->isoc_header.frame_width, + frame->isoc_header.frame_height); + + if (usbvision->request_intra) { + if (frame->isoc_header.format_param & 0x80) { + found_header = 1; + usbvision->last_isoc_frame_num = -1; /* do not check for lost frames this time */ + usbvision_unrequest_intra(usbvision); + break; + } + } else { + found_header = 1; + break; + } + } + + if (found_header) { + frame->frmwidth = frame->isoc_header.frame_width * usbvision->stretch_width; + frame->frmheight = frame->isoc_header.frame_height * usbvision->stretch_height; + frame->v4l2_linesize = (frame->frmwidth * frame->v4l2_format.depth) >> 3; + } else { /* no header found */ + PDEBUG(DBG_HEADER, "skipping scratch data, no header"); + scratch_reset(usbvision); + return parse_state_end_parse; + } + + /* found header */ + if (frame->isoc_header.data_format == ISOC_MODE_COMPRESS) { + /* check isoc_header.frame_num for lost frames */ + if (usbvision->last_isoc_frame_num >= 0) { + if (((usbvision->last_isoc_frame_num + 1) % 32) != frame->isoc_header.frame_num) { + /* unexpected frame drop: need to request new intra frame */ + PDEBUG(DBG_HEADER, "Lost frame before %d on USB", frame->isoc_header.frame_num); + usbvision_request_intra(usbvision); + return parse_state_next_frame; + } + } + usbvision->last_isoc_frame_num = frame->isoc_header.frame_num; + } + usbvision->header_count++; + frame->scanstate = scan_state_lines; + frame->curline = 0; + + return parse_state_continue; +} + +static enum parse_state usbvision_parse_lines_422(struct usb_usbvision *usbvision, + long *pcopylen) +{ + volatile struct usbvision_frame *frame; + unsigned char *f; + int len; + int i; + unsigned char yuyv[4] = { 180, 128, 10, 128 }; /* YUV components */ + unsigned char rv, gv, bv; /* RGB components */ + int clipmask_index, bytes_per_pixel; + int stretch_bytes, clipmask_add; + + frame = usbvision->cur_frame; + f = frame->data + (frame->v4l2_linesize * frame->curline); + + /* Make sure there's enough data for the entire line */ + len = (frame->isoc_header.frame_width * 2) + 5; + if (scratch_len(usbvision) < len) { + PDEBUG(DBG_PARSE, "out of data in line %d, need %u.\n", frame->curline, len); + return parse_state_out; + } + + if ((frame->curline + 1) >= frame->frmheight) + return parse_state_next_frame; + + bytes_per_pixel = frame->v4l2_format.bytes_per_pixel; + stretch_bytes = (usbvision->stretch_width - 1) * bytes_per_pixel; + clipmask_index = frame->curline * MAX_FRAME_WIDTH; + clipmask_add = usbvision->stretch_width; + + for (i = 0; i < frame->frmwidth; i += (2 * usbvision->stretch_width)) { + scratch_get(usbvision, &yuyv[0], 4); + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f++ = yuyv[0]; /* Y */ + *f++ = yuyv[3]; /* U */ + } else { + YUV_TO_RGB_BY_THE_BOOK(yuyv[0], yuyv[1], yuyv[3], rv, gv, bv); + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_RGB565: + *f++ = (0x1F & rv) | + (0xE0 & (gv << 5)); + *f++ = (0x07 & (gv >> 3)) | + (0xF8 & bv); + break; + case V4L2_PIX_FMT_RGB24: + *f++ = rv; + *f++ = gv; + *f++ = bv; + break; + case V4L2_PIX_FMT_RGB32: + *f++ = rv; + *f++ = gv; + *f++ = bv; + f++; + break; + case V4L2_PIX_FMT_RGB555: + *f++ = (0x1F & rv) | + (0xE0 & (gv << 5)); + *f++ = (0x03 & (gv >> 3)) | + (0x7C & (bv << 2)); + break; + } + } + clipmask_index += clipmask_add; + f += stretch_bytes; + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f++ = yuyv[2]; /* Y */ + *f++ = yuyv[1]; /* V */ + } else { + YUV_TO_RGB_BY_THE_BOOK(yuyv[2], yuyv[1], yuyv[3], rv, gv, bv); + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_RGB565: + *f++ = (0x1F & rv) | + (0xE0 & (gv << 5)); + *f++ = (0x07 & (gv >> 3)) | + (0xF8 & bv); + break; + case V4L2_PIX_FMT_RGB24: + *f++ = rv; + *f++ = gv; + *f++ = bv; + break; + case V4L2_PIX_FMT_RGB32: + *f++ = rv; + *f++ = gv; + *f++ = bv; + f++; + break; + case V4L2_PIX_FMT_RGB555: + *f++ = (0x1F & rv) | + (0xE0 & (gv << 5)); + *f++ = (0x03 & (gv >> 3)) | + (0x7C & (bv << 2)); + break; + } + } + clipmask_index += clipmask_add; + f += stretch_bytes; + } + + frame->curline += usbvision->stretch_height; + *pcopylen += frame->v4l2_linesize * usbvision->stretch_height; + + if (frame->curline >= frame->frmheight) + return parse_state_next_frame; + return parse_state_continue; +} + +/* The decompression routine */ +static int usbvision_decompress(struct usb_usbvision *usbvision, unsigned char *compressed, + unsigned char *decompressed, int *start_pos, + int *block_typestart_pos, int len) +{ + int rest_pixel, idx, pos, extra_pos, block_len, block_type_pos, block_type_len; + unsigned char block_byte, block_code, block_type, block_type_byte, integrator; + + integrator = 0; + pos = *start_pos; + block_type_pos = *block_typestart_pos; + extra_pos = pos; + block_len = 0; + block_byte = 0; + block_code = 0; + block_type = 0; + block_type_byte = 0; + block_type_len = 0; + rest_pixel = len; + + for (idx = 0; idx < len; idx++) { + if (block_len == 0) { + if (block_type_len == 0) { + block_type_byte = compressed[block_type_pos]; + block_type_pos++; + block_type_len = 4; + } + block_type = (block_type_byte & 0xC0) >> 6; + + /* statistic: */ + usbvision->compr_block_types[block_type]++; + + pos = extra_pos; + if (block_type == 0) { + if (rest_pixel >= 24) { + idx += 23; + rest_pixel -= 24; + integrator = decompressed[idx]; + } else { + idx += rest_pixel - 1; + rest_pixel = 0; + } + } else { + block_code = compressed[pos]; + pos++; + if (rest_pixel >= 24) + block_len = 24; + else + block_len = rest_pixel; + rest_pixel -= block_len; + extra_pos = pos + (block_len / 4); + } + block_type_byte <<= 2; + block_type_len -= 1; + } + if (block_len > 0) { + if ((block_len % 4) == 0) { + block_byte = compressed[pos]; + pos++; + } + if (block_type == 1) /* inter Block */ + integrator = decompressed[idx]; + switch (block_byte & 0xC0) { + case 0x03 << 6: + integrator += compressed[extra_pos]; + extra_pos++; + break; + case 0x02 << 6: + integrator += block_code; + break; + case 0x00: + integrator -= block_code; + break; + } + decompressed[idx] = integrator; + block_byte <<= 2; + block_len -= 1; + } + } + *start_pos = extra_pos; + *block_typestart_pos = block_type_pos; + return idx; +} + + +/* + * usbvision_parse_compress() + * + * Parse compressed frame from the scratch buffer, put + * decoded RGB value into the current frame buffer and add the written + * number of bytes (RGB) to the *pcopylen. + * + */ +static enum parse_state usbvision_parse_compress(struct usb_usbvision *usbvision, + long *pcopylen) +{ +#define USBVISION_STRIP_MAGIC 0x5A +#define USBVISION_STRIP_LEN_MAX 400 +#define USBVISION_STRIP_HEADER_LEN 3 + + struct usbvision_frame *frame; + unsigned char *f, *u = NULL, *v = NULL; + unsigned char strip_data[USBVISION_STRIP_LEN_MAX]; + unsigned char strip_header[USBVISION_STRIP_HEADER_LEN]; + int idx, idx_end, strip_len, strip_ptr, startblock_pos, block_pos, block_type_pos; + int clipmask_index; + int image_size; + unsigned char rv, gv, bv; + static unsigned char *Y, *U, *V; + + frame = usbvision->cur_frame; + image_size = frame->frmwidth * frame->frmheight; + if ((frame->v4l2_format.format == V4L2_PIX_FMT_YUV422P) || + (frame->v4l2_format.format == V4L2_PIX_FMT_YVU420)) { /* this is a planar format */ + /* ... v4l2_linesize not used here. */ + f = frame->data + (frame->width * frame->curline); + } else + f = frame->data + (frame->v4l2_linesize * frame->curline); + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { /* initialise u and v pointers */ + /* get base of u and b planes add halfoffset */ + u = frame->data + + image_size + + (frame->frmwidth >> 1) * frame->curline; + v = u + (image_size >> 1); + } else if (frame->v4l2_format.format == V4L2_PIX_FMT_YVU420) { + v = frame->data + image_size + ((frame->curline * (frame->width)) >> 2); + u = v + (image_size >> 2); + } + + if (frame->curline == 0) + usbvision_adjust_compression(usbvision); + + if (scratch_len(usbvision) < USBVISION_STRIP_HEADER_LEN) + return parse_state_out; + + /* get strip header without changing the scratch_read_ptr */ + scratch_set_extra_ptr(usbvision, &strip_ptr, 0); + scratch_get_extra(usbvision, &strip_header[0], &strip_ptr, + USBVISION_STRIP_HEADER_LEN); + + if (strip_header[0] != USBVISION_STRIP_MAGIC) { + /* wrong strip magic */ + usbvision->strip_magic_errors++; + return parse_state_next_frame; + } + + if (frame->curline != (int)strip_header[2]) { + /* line number mismatch error */ + usbvision->strip_line_number_errors++; + } + + strip_len = 2 * (unsigned int)strip_header[1]; + if (strip_len > USBVISION_STRIP_LEN_MAX) { + /* strip overrun */ + /* I think this never happens */ + usbvision_request_intra(usbvision); + } + + if (scratch_len(usbvision) < strip_len) { + /* there is not enough data for the strip */ + return parse_state_out; + } + + if (usbvision->intra_frame_buffer) { + Y = usbvision->intra_frame_buffer + frame->frmwidth * frame->curline; + U = usbvision->intra_frame_buffer + image_size + (frame->frmwidth / 2) * (frame->curline / 2); + V = usbvision->intra_frame_buffer + image_size / 4 * 5 + (frame->frmwidth / 2) * (frame->curline / 2); + } else { + return parse_state_next_frame; + } + + clipmask_index = frame->curline * MAX_FRAME_WIDTH; + + scratch_get(usbvision, strip_data, strip_len); + + idx_end = frame->frmwidth; + block_type_pos = USBVISION_STRIP_HEADER_LEN; + startblock_pos = block_type_pos + (idx_end - 1) / 96 + (idx_end / 2 - 1) / 96 + 2; + block_pos = startblock_pos; + + usbvision->block_pos = block_pos; + + usbvision_decompress(usbvision, strip_data, Y, &block_pos, &block_type_pos, idx_end); + if (strip_len > usbvision->max_strip_len) + usbvision->max_strip_len = strip_len; + + if (frame->curline % 2) + usbvision_decompress(usbvision, strip_data, V, &block_pos, &block_type_pos, idx_end / 2); + else + usbvision_decompress(usbvision, strip_data, U, &block_pos, &block_type_pos, idx_end / 2); + + if (block_pos > usbvision->comprblock_pos) + usbvision->comprblock_pos = block_pos; + if (block_pos > strip_len) + usbvision->strip_len_errors++; + + for (idx = 0; idx < idx_end; idx++) { + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f++ = Y[idx]; + *f++ = idx & 0x01 ? U[idx / 2] : V[idx / 2]; + } else if (frame->v4l2_format.format == V4L2_PIX_FMT_YUV422P) { + *f++ = Y[idx]; + if (idx & 0x01) + *u++ = U[idx >> 1]; + else + *v++ = V[idx >> 1]; + } else if (frame->v4l2_format.format == V4L2_PIX_FMT_YVU420) { + *f++ = Y[idx]; + if (!((idx & 0x01) | (frame->curline & 0x01))) { + /* only need do this for 1 in 4 pixels */ + /* intraframe buffer is YUV420 format */ + *u++ = U[idx >> 1]; + *v++ = V[idx >> 1]; + } + } else { + YUV_TO_RGB_BY_THE_BOOK(Y[idx], U[idx / 2], V[idx / 2], rv, gv, bv); + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_GREY: + *f++ = Y[idx]; + break; + case V4L2_PIX_FMT_RGB555: + *f++ = (0x1F & rv) | + (0xE0 & (gv << 5)); + *f++ = (0x03 & (gv >> 3)) | + (0x7C & (bv << 2)); + break; + case V4L2_PIX_FMT_RGB565: + *f++ = (0x1F & rv) | + (0xE0 & (gv << 5)); + *f++ = (0x07 & (gv >> 3)) | + (0xF8 & bv); + break; + case V4L2_PIX_FMT_RGB24: + *f++ = rv; + *f++ = gv; + *f++ = bv; + break; + case V4L2_PIX_FMT_RGB32: + *f++ = rv; + *f++ = gv; + *f++ = bv; + f++; + break; + } + } + clipmask_index++; + } + /* Deal with non-integer no. of bytes for YUV420P */ + if (frame->v4l2_format.format != V4L2_PIX_FMT_YVU420) + *pcopylen += frame->v4l2_linesize; + else + *pcopylen += frame->curline & 0x01 ? frame->v4l2_linesize : frame->v4l2_linesize << 1; + + frame->curline += 1; + + if (frame->curline >= frame->frmheight) + return parse_state_next_frame; + return parse_state_continue; + +} + + +/* + * usbvision_parse_lines_420() + * + * Parse two lines from the scratch buffer, put + * decoded RGB value into the current frame buffer and add the written + * number of bytes (RGB) to the *pcopylen. + * + */ +static enum parse_state usbvision_parse_lines_420(struct usb_usbvision *usbvision, + long *pcopylen) +{ + struct usbvision_frame *frame; + unsigned char *f_even = NULL, *f_odd = NULL; + unsigned int pixel_per_line, block; + int pixel, block_split; + int y_ptr, u_ptr, v_ptr, y_odd_offset; + const int y_block_size = 128; + const int uv_block_size = 64; + const int sub_block_size = 32; + const int y_step[] = { 0, 0, 0, 2 }, y_step_size = 4; + const int uv_step[] = { 0, 0, 0, 4 }, uv_step_size = 4; + unsigned char y[2], u, v; /* YUV components */ + int y_, u_, v_, vb, uvg, ur; + int r_, g_, b_; /* RGB components */ + unsigned char g; + int clipmask_even_index, clipmask_odd_index, bytes_per_pixel; + int clipmask_add, stretch_bytes; + + frame = usbvision->cur_frame; + f_even = frame->data + (frame->v4l2_linesize * frame->curline); + f_odd = f_even + frame->v4l2_linesize * usbvision->stretch_height; + + /* Make sure there's enough data for the entire line */ + /* In this mode usbvision transfer 3 bytes for every 2 pixels */ + /* I need two lines to decode the color */ + bytes_per_pixel = frame->v4l2_format.bytes_per_pixel; + stretch_bytes = (usbvision->stretch_width - 1) * bytes_per_pixel; + clipmask_even_index = frame->curline * MAX_FRAME_WIDTH; + clipmask_odd_index = clipmask_even_index + MAX_FRAME_WIDTH; + clipmask_add = usbvision->stretch_width; + pixel_per_line = frame->isoc_header.frame_width; + + if (scratch_len(usbvision) < (int)pixel_per_line * 3) { + /* printk(KERN_DEBUG "out of data, need %d\n", len); */ + return parse_state_out; + } + + if ((frame->curline + 1) >= frame->frmheight) + return parse_state_next_frame; + + block_split = (pixel_per_line%y_block_size) ? 1 : 0; /* are some blocks split into different lines? */ + + y_odd_offset = (pixel_per_line / y_block_size) * (y_block_size + uv_block_size) + + block_split * uv_block_size; + + scratch_set_extra_ptr(usbvision, &y_ptr, y_odd_offset); + scratch_set_extra_ptr(usbvision, &u_ptr, y_block_size); + scratch_set_extra_ptr(usbvision, &v_ptr, y_odd_offset + + (4 - block_split) * sub_block_size); + + for (block = 0; block < (pixel_per_line / sub_block_size); block++) { + for (pixel = 0; pixel < sub_block_size; pixel += 2) { + scratch_get(usbvision, &y[0], 2); + scratch_get_extra(usbvision, &u, &u_ptr, 1); + scratch_get_extra(usbvision, &v, &v_ptr, 1); + + /* I don't use the YUV_TO_RGB macro for better performance */ + v_ = v - 128; + u_ = u - 128; + vb = 132252 * v_; + uvg = -53281 * u_ - 25625 * v_; + ur = 104595 * u_; + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f_even++ = y[0]; + *f_even++ = v; + } else { + y_ = 76284 * (y[0] - 16); + + b_ = (y_ + vb) >> 16; + g_ = (y_ + uvg) >> 16; + r_ = (y_ + ur) >> 16; + + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_RGB565: + g = LIMIT_RGB(g_); + *f_even++ = + (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_even++ = + (0x07 & (g >> 3)) | + (0xF8 & LIMIT_RGB(b_)); + break; + case V4L2_PIX_FMT_RGB24: + *f_even++ = LIMIT_RGB(r_); + *f_even++ = LIMIT_RGB(g_); + *f_even++ = LIMIT_RGB(b_); + break; + case V4L2_PIX_FMT_RGB32: + *f_even++ = LIMIT_RGB(r_); + *f_even++ = LIMIT_RGB(g_); + *f_even++ = LIMIT_RGB(b_); + f_even++; + break; + case V4L2_PIX_FMT_RGB555: + g = LIMIT_RGB(g_); + *f_even++ = (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_even++ = (0x03 & (g >> 3)) | + (0x7C & (LIMIT_RGB(b_) << 2)); + break; + } + } + clipmask_even_index += clipmask_add; + f_even += stretch_bytes; + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f_even++ = y[1]; + *f_even++ = u; + } else { + y_ = 76284 * (y[1] - 16); + + b_ = (y_ + vb) >> 16; + g_ = (y_ + uvg) >> 16; + r_ = (y_ + ur) >> 16; + + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_RGB565: + g = LIMIT_RGB(g_); + *f_even++ = + (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_even++ = + (0x07 & (g >> 3)) | + (0xF8 & LIMIT_RGB(b_)); + break; + case V4L2_PIX_FMT_RGB24: + *f_even++ = LIMIT_RGB(r_); + *f_even++ = LIMIT_RGB(g_); + *f_even++ = LIMIT_RGB(b_); + break; + case V4L2_PIX_FMT_RGB32: + *f_even++ = LIMIT_RGB(r_); + *f_even++ = LIMIT_RGB(g_); + *f_even++ = LIMIT_RGB(b_); + f_even++; + break; + case V4L2_PIX_FMT_RGB555: + g = LIMIT_RGB(g_); + *f_even++ = (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_even++ = (0x03 & (g >> 3)) | + (0x7C & (LIMIT_RGB(b_) << 2)); + break; + } + } + clipmask_even_index += clipmask_add; + f_even += stretch_bytes; + + scratch_get_extra(usbvision, &y[0], &y_ptr, 2); + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f_odd++ = y[0]; + *f_odd++ = v; + } else { + y_ = 76284 * (y[0] - 16); + + b_ = (y_ + vb) >> 16; + g_ = (y_ + uvg) >> 16; + r_ = (y_ + ur) >> 16; + + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_RGB565: + g = LIMIT_RGB(g_); + *f_odd++ = + (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_odd++ = + (0x07 & (g >> 3)) | + (0xF8 & LIMIT_RGB(b_)); + break; + case V4L2_PIX_FMT_RGB24: + *f_odd++ = LIMIT_RGB(r_); + *f_odd++ = LIMIT_RGB(g_); + *f_odd++ = LIMIT_RGB(b_); + break; + case V4L2_PIX_FMT_RGB32: + *f_odd++ = LIMIT_RGB(r_); + *f_odd++ = LIMIT_RGB(g_); + *f_odd++ = LIMIT_RGB(b_); + f_odd++; + break; + case V4L2_PIX_FMT_RGB555: + g = LIMIT_RGB(g_); + *f_odd++ = (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_odd++ = (0x03 & (g >> 3)) | + (0x7C & (LIMIT_RGB(b_) << 2)); + break; + } + } + clipmask_odd_index += clipmask_add; + f_odd += stretch_bytes; + + if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { + *f_odd++ = y[1]; + *f_odd++ = u; + } else { + y_ = 76284 * (y[1] - 16); + + b_ = (y_ + vb) >> 16; + g_ = (y_ + uvg) >> 16; + r_ = (y_ + ur) >> 16; + + switch (frame->v4l2_format.format) { + case V4L2_PIX_FMT_RGB565: + g = LIMIT_RGB(g_); + *f_odd++ = + (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_odd++ = + (0x07 & (g >> 3)) | + (0xF8 & LIMIT_RGB(b_)); + break; + case V4L2_PIX_FMT_RGB24: + *f_odd++ = LIMIT_RGB(r_); + *f_odd++ = LIMIT_RGB(g_); + *f_odd++ = LIMIT_RGB(b_); + break; + case V4L2_PIX_FMT_RGB32: + *f_odd++ = LIMIT_RGB(r_); + *f_odd++ = LIMIT_RGB(g_); + *f_odd++ = LIMIT_RGB(b_); + f_odd++; + break; + case V4L2_PIX_FMT_RGB555: + g = LIMIT_RGB(g_); + *f_odd++ = (0x1F & LIMIT_RGB(r_)) | + (0xE0 & (g << 5)); + *f_odd++ = (0x03 & (g >> 3)) | + (0x7C & (LIMIT_RGB(b_) << 2)); + break; + } + } + clipmask_odd_index += clipmask_add; + f_odd += stretch_bytes; + } + + scratch_rm_old(usbvision, y_step[block % y_step_size] * sub_block_size); + scratch_inc_extra_ptr(&y_ptr, y_step[(block + 2 * block_split) % y_step_size] + * sub_block_size); + scratch_inc_extra_ptr(&u_ptr, uv_step[block % uv_step_size] + * sub_block_size); + scratch_inc_extra_ptr(&v_ptr, uv_step[(block + 2 * block_split) % uv_step_size] + * sub_block_size); + } + + scratch_rm_old(usbvision, pixel_per_line * 3 / 2 + + block_split * sub_block_size); + + frame->curline += 2 * usbvision->stretch_height; + *pcopylen += frame->v4l2_linesize * 2 * usbvision->stretch_height; + + if (frame->curline >= frame->frmheight) + return parse_state_next_frame; + return parse_state_continue; +} + +/* + * usbvision_parse_data() + * + * Generic routine to parse the scratch buffer. It employs either + * usbvision_find_header() or usbvision_parse_lines() to do most + * of work. + * + */ +static void usbvision_parse_data(struct usb_usbvision *usbvision) +{ + struct usbvision_frame *frame; + enum parse_state newstate; + long copylen = 0; + unsigned long lock_flags; + + frame = usbvision->cur_frame; + + PDEBUG(DBG_PARSE, "parsing len=%d\n", scratch_len(usbvision)); + + while (1) { + newstate = parse_state_out; + if (scratch_len(usbvision)) { + if (frame->scanstate == scan_state_scanning) { + newstate = usbvision_find_header(usbvision); + } else if (frame->scanstate == scan_state_lines) { + if (usbvision->isoc_mode == ISOC_MODE_YUV420) + newstate = usbvision_parse_lines_420(usbvision, ©len); + else if (usbvision->isoc_mode == ISOC_MODE_YUV422) + newstate = usbvision_parse_lines_422(usbvision, ©len); + else if (usbvision->isoc_mode == ISOC_MODE_COMPRESS) + newstate = usbvision_parse_compress(usbvision, ©len); + } + } + if (newstate == parse_state_continue) + continue; + if ((newstate == parse_state_next_frame) || (newstate == parse_state_out)) + break; + return; /* parse_state_end_parse */ + } + + if (newstate == parse_state_next_frame) { + frame->grabstate = frame_state_done; + frame->ts = ktime_get_ns(); + frame->sequence = usbvision->frame_num; + + spin_lock_irqsave(&usbvision->queue_lock, lock_flags); + list_move_tail(&(frame->frame), &usbvision->outqueue); + usbvision->cur_frame = NULL; + spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags); + + usbvision->frame_num++; + + /* This will cause the process to request another frame. */ + if (waitqueue_active(&usbvision->wait_frame)) { + PDEBUG(DBG_PARSE, "Wake up !"); + wake_up_interruptible(&usbvision->wait_frame); + } + } else { + frame->grabstate = frame_state_grabbing; + } + + /* Update the frame's uncompressed length. */ + frame->scanlength += copylen; +} + + +/* + * Make all of the blocks of data contiguous + */ +static int usbvision_compress_isochronous(struct usb_usbvision *usbvision, + struct urb *urb) +{ + unsigned char *packet_data; + int i, totlen = 0; + + for (i = 0; i < urb->number_of_packets; i++) { + int packet_len = urb->iso_frame_desc[i].actual_length; + int packet_stat = urb->iso_frame_desc[i].status; + + packet_data = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + /* Detect and ignore errored packets */ + if (packet_stat) { /* packet_stat != 0 ????????????? */ + PDEBUG(DBG_ISOC, "data error: [%d] len=%d, status=%X", i, packet_len, packet_stat); + usbvision->isoc_err_count++; + continue; + } + + /* Detect and ignore empty packets */ + if (packet_len < 0) { + PDEBUG(DBG_ISOC, "error packet [%d]", i); + usbvision->isoc_skip_count++; + continue; + } else if (packet_len == 0) { /* Frame end ????? */ + PDEBUG(DBG_ISOC, "null packet [%d]", i); + usbvision->isocstate = isoc_state_no_frame; + usbvision->isoc_skip_count++; + continue; + } else if (packet_len > usbvision->isoc_packet_size) { + PDEBUG(DBG_ISOC, "packet[%d] > isoc_packet_size", i); + usbvision->isoc_skip_count++; + continue; + } + + PDEBUG(DBG_ISOC, "packet ok [%d] len=%d", i, packet_len); + + if (usbvision->isocstate == isoc_state_no_frame) { /* new frame begins */ + usbvision->isocstate = isoc_state_in_frame; + scratch_mark_header(usbvision); + usbvision_measure_bandwidth(usbvision); + PDEBUG(DBG_ISOC, "packet with header"); + } + + /* + * If usbvision continues to feed us with data but there is no + * consumption (if, for example, V4L client fell asleep) we + * may overflow the buffer. We have to move old data over to + * free room for new data. This is bad for old data. If we + * just drop new data then it's bad for new data... choose + * your favorite evil here. + */ + if (scratch_free(usbvision) < packet_len) { + usbvision->scratch_ovf_count++; + PDEBUG(DBG_ISOC, "scratch buf overflow! scr_len: %d, n: %d", + scratch_len(usbvision), packet_len); + scratch_rm_old(usbvision, packet_len - scratch_free(usbvision)); + } + + /* Now we know that there is enough room in scratch buffer */ + scratch_put(usbvision, packet_data, packet_len); + totlen += packet_len; + usbvision->isoc_data_count += packet_len; + usbvision->isoc_packet_count++; + } +#if ENABLE_HEXDUMP + if (totlen > 0) { + static int foo; + + if (foo < 1) { + printk(KERN_DEBUG "+%d.\n", usbvision->scratchlen); + usbvision_hexdump(data0, (totlen > 64) ? 64 : totlen); + ++foo; + } + } +#endif + return totlen; +} + +static void usbvision_isoc_irq(struct urb *urb) +{ + int err_code = 0; + int len; + struct usb_usbvision *usbvision = urb->context; + int i; + struct usbvision_frame **f; + + /* We don't want to do anything if we are about to be removed! */ + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return; + + /* any urb with wrong status is ignored without acknowledgement */ + if (urb->status == -ENOENT) + return; + + f = &usbvision->cur_frame; + + /* Manage streaming interruption */ + if (usbvision->streaming == stream_interrupt) { + usbvision->streaming = stream_idle; + if ((*f)) { + (*f)->grabstate = frame_state_ready; + (*f)->scanstate = scan_state_scanning; + } + PDEBUG(DBG_IRQ, "stream interrupted"); + wake_up_interruptible(&usbvision->wait_stream); + } + + /* Copy the data received into our scratch buffer */ + len = usbvision_compress_isochronous(usbvision, urb); + + usbvision->isoc_urb_count++; + usbvision->urb_length = len; + + if (usbvision->streaming == stream_on) { + /* If we collected enough data let's parse! */ + if (scratch_len(usbvision) > USBVISION_HEADER_LENGTH && + !list_empty(&(usbvision->inqueue))) { + if (!(*f)) { + (*f) = list_entry(usbvision->inqueue.next, + struct usbvision_frame, + frame); + } + usbvision_parse_data(usbvision); + } else { + /* If we don't have a frame + we're current working on, complain */ + PDEBUG(DBG_IRQ, + "received data, but no one needs it"); + scratch_reset(usbvision); + } + } else { + PDEBUG(DBG_IRQ, "received data, but no one needs it"); + scratch_reset(usbvision); + } + + for (i = 0; i < USBVISION_URB_FRAMES; i++) { + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = 0; + } + + urb->status = 0; + urb->dev = usbvision->dev; + err_code = usb_submit_urb(urb, GFP_ATOMIC); + + if (err_code) { + dev_err(&usbvision->dev->dev, + "%s: usb_submit_urb failed: error %d\n", + __func__, err_code); + } + + return; +} + +/*************************************/ +/* Low level usbvision access functions */ +/*************************************/ + +/* + * usbvision_read_reg() + * + * return < 0 -> Error + * >= 0 -> Data + */ + +int usbvision_read_reg(struct usb_usbvision *usbvision, unsigned char reg) +{ + int err_code = 0; + unsigned char *buffer = usbvision->ctrl_urb_buffer; + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return -1; + + err_code = usb_control_msg(usbvision->dev, usb_rcvctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, + 0, (__u16) reg, buffer, 1, HZ); + + if (err_code < 0) { + dev_err(&usbvision->dev->dev, + "%s: failed: error %d\n", __func__, err_code); + return err_code; + } + return buffer[0]; +} + +/* + * usbvision_write_reg() + * + * return 1 -> Reg written + * 0 -> usbvision is not yet ready + * -1 -> Something went wrong + */ + +int usbvision_write_reg(struct usb_usbvision *usbvision, unsigned char reg, + unsigned char value) +{ + int err_code = 0; + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + usbvision->ctrl_urb_buffer[0] = value; + err_code = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, (__u16) reg, + usbvision->ctrl_urb_buffer, 1, HZ); + + if (err_code < 0) { + dev_err(&usbvision->dev->dev, + "%s: failed: error %d\n", __func__, err_code); + } + return err_code; +} + + +static void usbvision_ctrl_urb_complete(struct urb *urb) +{ + struct usb_usbvision *usbvision = (struct usb_usbvision *)urb->context; + + PDEBUG(DBG_IRQ, ""); + usbvision->ctrl_urb_busy = 0; +} + + +static int usbvision_write_reg_irq(struct usb_usbvision *usbvision, int address, + unsigned char *data, int len) +{ + int err_code = 0; + + PDEBUG(DBG_IRQ, ""); + if (len > 8) + return -EFAULT; + if (usbvision->ctrl_urb_busy) + return -EBUSY; + usbvision->ctrl_urb_busy = 1; + + usbvision->ctrl_urb_setup.bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT; + usbvision->ctrl_urb_setup.bRequest = USBVISION_OP_CODE; + usbvision->ctrl_urb_setup.wValue = 0; + usbvision->ctrl_urb_setup.wIndex = cpu_to_le16(address); + usbvision->ctrl_urb_setup.wLength = cpu_to_le16(len); + usb_fill_control_urb(usbvision->ctrl_urb, usbvision->dev, + usb_sndctrlpipe(usbvision->dev, 1), + (unsigned char *)&usbvision->ctrl_urb_setup, + (void *)usbvision->ctrl_urb_buffer, len, + usbvision_ctrl_urb_complete, + (void *)usbvision); + + memcpy(usbvision->ctrl_urb_buffer, data, len); + + err_code = usb_submit_urb(usbvision->ctrl_urb, GFP_ATOMIC); + if (err_code < 0) { + /* error in usb_submit_urb() */ + usbvision->ctrl_urb_busy = 0; + } + PDEBUG(DBG_IRQ, "submit %d byte: error %d", len, err_code); + return err_code; +} + + +static int usbvision_init_compression(struct usb_usbvision *usbvision) +{ + usbvision->last_isoc_frame_num = -1; + usbvision->isoc_data_count = 0; + usbvision->isoc_packet_count = 0; + usbvision->isoc_skip_count = 0; + usbvision->compr_level = 50; + usbvision->last_compr_level = -1; + usbvision->isoc_urb_count = 0; + usbvision->request_intra = 1; + usbvision->isoc_measure_bandwidth_count = 0; + + return 0; +} + +/* this function measures the used bandwidth since last call + * return: 0 : no error + * sets used_bandwidth to 1-100 : 1-100% of full bandwidth resp. to isoc_packet_size + */ +static int usbvision_measure_bandwidth(struct usb_usbvision *usbvision) +{ + if (usbvision->isoc_measure_bandwidth_count < 2) { /* this gives an average bandwidth of 3 frames */ + usbvision->isoc_measure_bandwidth_count++; + return 0; + } + if ((usbvision->isoc_packet_size > 0) && (usbvision->isoc_packet_count > 0)) { + usbvision->used_bandwidth = usbvision->isoc_data_count / + (usbvision->isoc_packet_count + usbvision->isoc_skip_count) * + 100 / usbvision->isoc_packet_size; + } + usbvision->isoc_measure_bandwidth_count = 0; + usbvision->isoc_data_count = 0; + usbvision->isoc_packet_count = 0; + usbvision->isoc_skip_count = 0; + return 0; +} + +static int usbvision_adjust_compression(struct usb_usbvision *usbvision) +{ + int err_code = 0; + unsigned char buffer[6]; + + PDEBUG(DBG_IRQ, ""); + if ((adjust_compression) && (usbvision->used_bandwidth > 0)) { + usbvision->compr_level += (usbvision->used_bandwidth - 90) / 2; + RESTRICT_TO_RANGE(usbvision->compr_level, 0, 100); + if (usbvision->compr_level != usbvision->last_compr_level) { + int distortion; + + if (usbvision->bridge_type == BRIDGE_NT1004 || usbvision->bridge_type == BRIDGE_NT1005) { + buffer[0] = (unsigned char)(4 + 16 * usbvision->compr_level / 100); /* PCM Threshold 1 */ + buffer[1] = (unsigned char)(4 + 8 * usbvision->compr_level / 100); /* PCM Threshold 2 */ + distortion = 7 + 248 * usbvision->compr_level / 100; + buffer[2] = (unsigned char)(distortion & 0xFF); /* Average distortion Threshold (inter) */ + buffer[3] = (unsigned char)(distortion & 0xFF); /* Average distortion Threshold (intra) */ + distortion = 1 + 42 * usbvision->compr_level / 100; + buffer[4] = (unsigned char)(distortion & 0xFF); /* Maximum distortion Threshold (inter) */ + buffer[5] = (unsigned char)(distortion & 0xFF); /* Maximum distortion Threshold (intra) */ + } else { /* BRIDGE_NT1003 */ + buffer[0] = (unsigned char)(4 + 16 * usbvision->compr_level / 100); /* PCM threshold 1 */ + buffer[1] = (unsigned char)(4 + 8 * usbvision->compr_level / 100); /* PCM threshold 2 */ + distortion = 2 + 253 * usbvision->compr_level / 100; + buffer[2] = (unsigned char)(distortion & 0xFF); /* distortion threshold bit0-7 */ + buffer[3] = 0; /* (unsigned char)((distortion >> 8) & 0x0F); distortion threshold bit 8-11 */ + distortion = 0 + 43 * usbvision->compr_level / 100; + buffer[4] = (unsigned char)(distortion & 0xFF); /* maximum distortion bit0-7 */ + buffer[5] = 0; /* (unsigned char)((distortion >> 8) & 0x01); maximum distortion bit 8 */ + } + err_code = usbvision_write_reg_irq(usbvision, USBVISION_PCM_THR1, buffer, 6); + if (err_code == 0) { + PDEBUG(DBG_IRQ, "new compr params %#02x %#02x %#02x %#02x %#02x %#02x", buffer[0], + buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]); + usbvision->last_compr_level = usbvision->compr_level; + } + } + } + return err_code; +} + +static int usbvision_request_intra(struct usb_usbvision *usbvision) +{ + unsigned char buffer[1]; + + PDEBUG(DBG_IRQ, ""); + usbvision->request_intra = 1; + buffer[0] = 1; + usbvision_write_reg_irq(usbvision, USBVISION_FORCE_INTRA, buffer, 1); + return 0; +} + +static int usbvision_unrequest_intra(struct usb_usbvision *usbvision) +{ + unsigned char buffer[1]; + + PDEBUG(DBG_IRQ, ""); + usbvision->request_intra = 0; + buffer[0] = 0; + usbvision_write_reg_irq(usbvision, USBVISION_FORCE_INTRA, buffer, 1); + return 0; +} + +/******************************* + * usbvision utility functions + *******************************/ + +int usbvision_power_off(struct usb_usbvision *usbvision) +{ + int err_code = 0; + + PDEBUG(DBG_FUNC, ""); + + err_code = usbvision_write_reg(usbvision, USBVISION_PWR_REG, USBVISION_SSPND_EN); + if (err_code == 1) + usbvision->power = 0; + PDEBUG(DBG_FUNC, "%s: err_code %d", (err_code != 1) ? "ERROR" : "power is off", err_code); + return err_code; +} + +/* configure webcam image sensor using the serial port */ +static int usbvision_init_webcam(struct usb_usbvision *usbvision) +{ + int rc; + int i; + static char init_values[38][3] = { + { 0x04, 0x12, 0x08 }, { 0x05, 0xff, 0xc8 }, { 0x06, 0x18, 0x07 }, { 0x07, 0x90, 0x00 }, + { 0x09, 0x00, 0x00 }, { 0x0a, 0x00, 0x00 }, { 0x0b, 0x08, 0x00 }, { 0x0d, 0xcc, 0xcc }, + { 0x0e, 0x13, 0x14 }, { 0x10, 0x9b, 0x83 }, { 0x11, 0x5a, 0x3f }, { 0x12, 0xe4, 0x73 }, + { 0x13, 0x88, 0x84 }, { 0x14, 0x89, 0x80 }, { 0x15, 0x00, 0x20 }, { 0x16, 0x00, 0x00 }, + { 0x17, 0xff, 0xa0 }, { 0x18, 0x6b, 0x20 }, { 0x19, 0x22, 0x40 }, { 0x1a, 0x10, 0x07 }, + { 0x1b, 0x00, 0x47 }, { 0x1c, 0x03, 0xe0 }, { 0x1d, 0x00, 0x00 }, { 0x1e, 0x00, 0x00 }, + { 0x1f, 0x00, 0x00 }, { 0x20, 0x00, 0x00 }, { 0x21, 0x00, 0x00 }, { 0x22, 0x00, 0x00 }, + { 0x23, 0x00, 0x00 }, { 0x24, 0x00, 0x00 }, { 0x25, 0x00, 0x00 }, { 0x26, 0x00, 0x00 }, + { 0x27, 0x00, 0x00 }, { 0x28, 0x00, 0x00 }, { 0x29, 0x00, 0x00 }, { 0x08, 0x80, 0x60 }, + { 0x0f, 0x2d, 0x24 }, { 0x0c, 0x80, 0x80 } + }; + unsigned char *value = usbvision->ctrl_urb_buffer; + + /* the only difference between PAL and NTSC init_values */ + if (usbvision_device_data[usbvision->dev_model].video_norm == V4L2_STD_NTSC) + init_values[4][1] = 0x34; + + for (i = 0; i < sizeof(init_values) / 3; i++) { + usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT); + memcpy(value, init_values[i], 3); + rc = usb_control_msg(usbvision->dev, + usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_SER_DAT1, value, + 3, HZ); + if (rc < 0) + return rc; + usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SIO); + /* write 3 bytes to the serial port using SIO mode */ + usbvision_write_reg(usbvision, USBVISION_SER_CONT, 3 | 0x10); + usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, 0); + usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT); + usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, USBVISION_IO_2); + usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT | USBVISION_CLK_OUT); + usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT | USBVISION_DAT_IO); + usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT | USBVISION_CLK_OUT | USBVISION_DAT_IO); + } + + return 0; +} + +/* + * usbvision_set_video_format() + * + */ +static int usbvision_set_video_format(struct usb_usbvision *usbvision, int format) +{ + static const char proc[] = "usbvision_set_video_format"; + unsigned char *value = usbvision->ctrl_urb_buffer; + int rc; + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + PDEBUG(DBG_FUNC, "isoc_mode %#02x", format); + + if ((format != ISOC_MODE_YUV422) + && (format != ISOC_MODE_YUV420) + && (format != ISOC_MODE_COMPRESS)) { + printk(KERN_ERR "usbvision: unknown video format %02x, using default YUV420", + format); + format = ISOC_MODE_YUV420; + } + value[0] = 0x0A; /* TODO: See the effect of the filter */ + value[1] = format; /* Sets the VO_MODE register which follows FILT_CONT */ + rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_FILT_CONT, value, 2, HZ); + + if (rc < 0) { + printk(KERN_ERR "%s: ERROR=%d. USBVISION stopped - reconnect or reload driver.\n", + proc, rc); + } + usbvision->isoc_mode = format; + return rc; +} + +/* + * usbvision_set_output() + * + */ + +int usbvision_set_output(struct usb_usbvision *usbvision, int width, + int height) +{ + int err_code = 0; + int usb_width, usb_height; + unsigned int frame_rate = 0, frame_drop = 0; + unsigned char *value = usbvision->ctrl_urb_buffer; + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + if (width > MAX_USB_WIDTH) { + usb_width = width / 2; + usbvision->stretch_width = 2; + } else { + usb_width = width; + usbvision->stretch_width = 1; + } + + if (height > MAX_USB_HEIGHT) { + usb_height = height / 2; + usbvision->stretch_height = 2; + } else { + usb_height = height; + usbvision->stretch_height = 1; + } + + RESTRICT_TO_RANGE(usb_width, MIN_FRAME_WIDTH, MAX_USB_WIDTH); + usb_width &= ~(MIN_FRAME_WIDTH-1); + RESTRICT_TO_RANGE(usb_height, MIN_FRAME_HEIGHT, MAX_USB_HEIGHT); + usb_height &= ~(1); + + PDEBUG(DBG_FUNC, "usb %dx%d; screen %dx%d; stretch %dx%d", + usb_width, usb_height, width, height, + usbvision->stretch_width, usbvision->stretch_height); + + /* I'll not rewrite the same values */ + if ((usb_width != usbvision->curwidth) || (usb_height != usbvision->curheight)) { + value[0] = usb_width & 0xff; /* LSB */ + value[1] = (usb_width >> 8) & 0x03; /* MSB */ + value[2] = usb_height & 0xff; /* LSB */ + value[3] = (usb_height >> 8) & 0x03; /* MSB */ + + err_code = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, + 0, (__u16) USBVISION_LXSIZE_O, value, 4, HZ); + + if (err_code < 0) { + dev_err(&usbvision->dev->dev, + "%s failed: error %d\n", __func__, err_code); + return err_code; + } + usbvision->curwidth = usbvision->stretch_width * usb_width; + usbvision->curheight = usbvision->stretch_height * usb_height; + } + + if (usbvision->isoc_mode == ISOC_MODE_YUV422) + frame_rate = (usbvision->isoc_packet_size * 1000) / (usb_width * usb_height * 2); + else if (usbvision->isoc_mode == ISOC_MODE_YUV420) + frame_rate = (usbvision->isoc_packet_size * 1000) / ((usb_width * usb_height * 12) / 8); + else + frame_rate = FRAMERATE_MAX; + + if (usbvision->tvnorm_id & V4L2_STD_625_50) + frame_drop = frame_rate * 32 / 25 - 1; + else if (usbvision->tvnorm_id & V4L2_STD_525_60) + frame_drop = frame_rate * 32 / 30 - 1; + + RESTRICT_TO_RANGE(frame_drop, FRAMERATE_MIN, FRAMERATE_MAX); + + PDEBUG(DBG_FUNC, "frame_rate %d fps, frame_drop %d", frame_rate, frame_drop); + + frame_drop = FRAMERATE_MAX; /* We can allow the maximum here, because dropping is controlled */ + + if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) { + if (usbvision_device_data[usbvision->dev_model].video_norm == V4L2_STD_PAL) + frame_drop = 25; + else + frame_drop = 30; + } + + /* frame_drop = 7; => frame_phase = 1, 5, 9, 13, 17, 21, 25, 0, 4, 8, ... + => frame_skip = 4; + => frame_rate = (7 + 1) * 25 / 32 = 200 / 32 = 6.25; + + frame_drop = 9; => frame_phase = 1, 5, 8, 11, 14, 17, 21, 24, 27, 1, 4, 8, ... + => frame_skip = 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, ... + => frame_rate = (9 + 1) * 25 / 32 = 250 / 32 = 7.8125; + */ + err_code = usbvision_write_reg(usbvision, USBVISION_FRM_RATE, frame_drop); + return err_code; +} + + +/* + * usbvision_frames_alloc + * allocate the required frames + */ +int usbvision_frames_alloc(struct usb_usbvision *usbvision, int number_of_frames) +{ + int i; + + /* needs to be page aligned cause the buffers can be mapped individually! */ + usbvision->max_frame_size = PAGE_ALIGN(usbvision->curwidth * + usbvision->curheight * + usbvision->palette.bytes_per_pixel); + + /* Try to do my best to allocate the frames the user want in the remaining memory */ + usbvision->num_frames = number_of_frames; + while (usbvision->num_frames > 0) { + usbvision->fbuf_size = usbvision->num_frames * usbvision->max_frame_size; + usbvision->fbuf = usbvision_rvmalloc(usbvision->fbuf_size); + if (usbvision->fbuf) + break; + usbvision->num_frames--; + } + + /* Allocate all buffers */ + for (i = 0; i < usbvision->num_frames; i++) { + usbvision->frame[i].index = i; + usbvision->frame[i].grabstate = frame_state_unused; + usbvision->frame[i].data = usbvision->fbuf + + i * usbvision->max_frame_size; + /* + * Set default sizes for read operation. + */ + usbvision->stretch_width = 1; + usbvision->stretch_height = 1; + usbvision->frame[i].width = usbvision->curwidth; + usbvision->frame[i].height = usbvision->curheight; + usbvision->frame[i].bytes_read = 0; + } + PDEBUG(DBG_FUNC, "allocated %d frames (%d bytes per frame)", + usbvision->num_frames, usbvision->max_frame_size); + return usbvision->num_frames; +} + +/* + * usbvision_frames_free + * frees memory allocated for the frames + */ +void usbvision_frames_free(struct usb_usbvision *usbvision) +{ + /* Have to free all that memory */ + PDEBUG(DBG_FUNC, "free %d frames", usbvision->num_frames); + + if (usbvision->fbuf != NULL) { + usbvision_rvfree(usbvision->fbuf, usbvision->fbuf_size); + usbvision->fbuf = NULL; + + usbvision->num_frames = 0; + } +} +/* + * usbvision_empty_framequeues() + * prepare queues for incoming and outgoing frames + */ +void usbvision_empty_framequeues(struct usb_usbvision *usbvision) +{ + u32 i; + + INIT_LIST_HEAD(&(usbvision->inqueue)); + INIT_LIST_HEAD(&(usbvision->outqueue)); + + for (i = 0; i < USBVISION_NUMFRAMES; i++) { + usbvision->frame[i].grabstate = frame_state_unused; + usbvision->frame[i].bytes_read = 0; + } +} + +/* + * usbvision_stream_interrupt() + * stops streaming + */ +int usbvision_stream_interrupt(struct usb_usbvision *usbvision) +{ + int ret = 0; + + /* stop reading from the device */ + + usbvision->streaming = stream_interrupt; + ret = wait_event_timeout(usbvision->wait_stream, + (usbvision->streaming == stream_idle), + msecs_to_jiffies(USBVISION_NUMSBUF*USBVISION_URB_FRAMES)); + return ret; +} + +/* + * usbvision_set_compress_params() + * + */ + +static int usbvision_set_compress_params(struct usb_usbvision *usbvision) +{ + static const char proc[] = "usbvision_set_compression_params: "; + int rc; + unsigned char *value = usbvision->ctrl_urb_buffer; + + value[0] = 0x0F; /* Intra-Compression cycle */ + value[1] = 0x01; /* Reg.45 one line per strip */ + value[2] = 0x00; /* Reg.46 Force intra mode on all new frames */ + value[3] = 0x00; /* Reg.47 FORCE_UP <- 0 normal operation (not force) */ + value[4] = 0xA2; /* Reg.48 BUF_THR I'm not sure if this does something in not compressed mode. */ + value[5] = 0x00; /* Reg.49 DVI_YUV This has nothing to do with compression */ + + /* caught values for NT1004 */ + /* value[0] = 0xFF; Never apply intra mode automatically */ + /* value[1] = 0xF1; Use full frame height for virtual strip width; One line per strip */ + /* value[2] = 0x01; Force intra mode on all new frames */ + /* value[3] = 0x00; Strip size 400 Bytes; do not force up */ + /* value[4] = 0xA2; */ + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_INTRA_CYC, value, 5, HZ); + + if (rc < 0) { + printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n", + proc, rc); + return rc; + } + + if (usbvision->bridge_type == BRIDGE_NT1004) { + value[0] = 20; /* PCM Threshold 1 */ + value[1] = 12; /* PCM Threshold 2 */ + value[2] = 255; /* Distortion Threshold inter */ + value[3] = 255; /* Distortion Threshold intra */ + value[4] = 43; /* Max Distortion inter */ + value[5] = 43; /* Max Distortion intra */ + } else { + value[0] = 20; /* PCM Threshold 1 */ + value[1] = 12; /* PCM Threshold 2 */ + value[2] = 255; /* Distortion Threshold d7-d0 */ + value[3] = 0; /* Distortion Threshold d11-d8 */ + value[4] = 43; /* Max Distortion d7-d0 */ + value[5] = 0; /* Max Distortion d8 */ + } + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_PCM_THR1, value, 6, HZ); + + if (rc < 0) { + printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n", + proc, rc); + } + return rc; +} + + +/* + * usbvision_set_input() + * + * Set the input (saa711x, ...) size x y and other misc input params + * I've no idea if this parameters are right + * + */ +int usbvision_set_input(struct usb_usbvision *usbvision) +{ + static const char proc[] = "usbvision_set_input: "; + int rc; + unsigned char *value = usbvision->ctrl_urb_buffer; + unsigned char dvi_yuv_value; + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + /* Set input format expected from decoder*/ + if (usbvision_device_data[usbvision->dev_model].vin_reg1_override) { + value[0] = usbvision_device_data[usbvision->dev_model].vin_reg1; + } else if (usbvision_device_data[usbvision->dev_model].codec == CODEC_SAA7113) { + /* SAA7113 uses 8 bit output */ + value[0] = USBVISION_8_422_SYNC; + } else { + /* I'm sure only about d2-d0 [010] 16 bit 4:2:2 using sync pulses + * as that is how saa7111 is configured */ + value[0] = USBVISION_16_422_SYNC; + /* | USBVISION_VSNC_POL | USBVISION_VCLK_POL);*/ + } + + rc = usbvision_write_reg(usbvision, USBVISION_VIN_REG1, value[0]); + if (rc < 0) { + printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n", + proc, rc); + return rc; + } + + + if (usbvision->tvnorm_id & V4L2_STD_PAL) { + value[0] = 0xC0; + value[1] = 0x02; /* 0x02C0 -> 704 Input video line length */ + value[2] = 0x20; + value[3] = 0x01; /* 0x0120 -> 288 Input video n. of lines */ + value[4] = 0x60; + value[5] = 0x00; /* 0x0060 -> 96 Input video h offset */ + value[6] = 0x16; + value[7] = 0x00; /* 0x0016 -> 22 Input video v offset */ + } else if (usbvision->tvnorm_id & V4L2_STD_SECAM) { + value[0] = 0xC0; + value[1] = 0x02; /* 0x02C0 -> 704 Input video line length */ + value[2] = 0x20; + value[3] = 0x01; /* 0x0120 -> 288 Input video n. of lines */ + value[4] = 0x01; + value[5] = 0x00; /* 0x0001 -> 01 Input video h offset */ + value[6] = 0x01; + value[7] = 0x00; /* 0x0001 -> 01 Input video v offset */ + } else { /* V4L2_STD_NTSC */ + value[0] = 0xD0; + value[1] = 0x02; /* 0x02D0 -> 720 Input video line length */ + value[2] = 0xF0; + value[3] = 0x00; /* 0x00F0 -> 240 Input video number of lines */ + value[4] = 0x50; + value[5] = 0x00; /* 0x0050 -> 80 Input video h offset */ + value[6] = 0x10; + value[7] = 0x00; /* 0x0010 -> 16 Input video v offset */ + } + + /* webcam is only 480 pixels wide, both PAL and NTSC version */ + if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) { + value[0] = 0xe0; + value[1] = 0x01; /* 0x01E0 -> 480 Input video line length */ + } + + if (usbvision_device_data[usbvision->dev_model].x_offset >= 0) { + value[4] = usbvision_device_data[usbvision->dev_model].x_offset & 0xff; + value[5] = (usbvision_device_data[usbvision->dev_model].x_offset & 0x0300) >> 8; + } + + if (adjust_x_offset != -1) { + value[4] = adjust_x_offset & 0xff; + value[5] = (adjust_x_offset & 0x0300) >> 8; + } + + if (usbvision_device_data[usbvision->dev_model].y_offset >= 0) { + value[6] = usbvision_device_data[usbvision->dev_model].y_offset & 0xff; + value[7] = (usbvision_device_data[usbvision->dev_model].y_offset & 0x0300) >> 8; + } + + if (adjust_y_offset != -1) { + value[6] = adjust_y_offset & 0xff; + value[7] = (adjust_y_offset & 0x0300) >> 8; + } + + rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, /* USBVISION specific code */ + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_LXSIZE_I, value, 8, HZ); + if (rc < 0) { + printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n", + proc, rc); + return rc; + } + + + dvi_yuv_value = 0x00; /* U comes after V, Ya comes after U/V, Yb comes after Yb */ + + if (usbvision_device_data[usbvision->dev_model].dvi_yuv_override) { + dvi_yuv_value = usbvision_device_data[usbvision->dev_model].dvi_yuv; + } else if (usbvision_device_data[usbvision->dev_model].codec == CODEC_SAA7113) { + /* This changes as the fine sync control changes. Further investigation necessary */ + dvi_yuv_value = 0x06; + } + + return usbvision_write_reg(usbvision, USBVISION_DVI_YUV, dvi_yuv_value); +} + + +/* + * usbvision_set_dram_settings() + * + * Set the buffer address needed by the usbvision dram to operate + * This values has been taken with usbsnoop. + * + */ + +static int usbvision_set_dram_settings(struct usb_usbvision *usbvision) +{ + unsigned char *value = usbvision->ctrl_urb_buffer; + int rc; + + if (usbvision->isoc_mode == ISOC_MODE_COMPRESS) { + value[0] = 0x42; + value[1] = 0x71; + value[2] = 0xff; + value[3] = 0x00; + value[4] = 0x98; + value[5] = 0xe0; + value[6] = 0x71; + value[7] = 0xff; + /* UR: 0x0E200-0x3FFFF = 204288 Words (1 Word = 2 Byte) */ + /* FDL: 0x00000-0x0E099 = 57498 Words */ + /* VDW: 0x0E3FF-0x3FFFF */ + } else { + value[0] = 0x42; + value[1] = 0x00; + value[2] = 0xff; + value[3] = 0x00; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + value[7] = 0xff; + } + /* These are the values of the address of the video buffer, + * they have to be loaded into the USBVISION_DRM_PRM1-8 + * + * Start address of video output buffer for read: drm_prm1-2 -> 0x00000 + * End address of video output buffer for read: drm_prm1-3 -> 0x1ffff + * Start address of video frame delay buffer: drm_prm1-4 -> 0x20000 + * Only used in compressed mode + * End address of video frame delay buffer: drm_prm1-5-6 -> 0x3ffff + * Only used in compressed mode + * Start address of video output buffer for write: drm_prm1-7 -> 0x00000 + * End address of video output buffer for write: drm_prm1-8 -> 0x1ffff + */ + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return 0; + + rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, /* USBVISION specific code */ + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_DRM_PRM1, value, 8, HZ); + + if (rc < 0) { + dev_err(&usbvision->dev->dev, "%s: ERROR=%d\n", __func__, rc); + return rc; + } + + /* Restart the video buffer logic */ + rc = usbvision_write_reg(usbvision, USBVISION_DRM_CONT, USBVISION_RES_UR | + USBVISION_RES_FDL | USBVISION_RES_VDW); + if (rc < 0) + return rc; + rc = usbvision_write_reg(usbvision, USBVISION_DRM_CONT, 0x00); + + return rc; +} + +/* + * () + * + * Power on the device, enables suspend-resume logic + * & reset the isoc End-Point + * + */ + +int usbvision_power_on(struct usb_usbvision *usbvision) +{ + int err_code = 0; + + PDEBUG(DBG_FUNC, ""); + + usbvision_write_reg(usbvision, USBVISION_PWR_REG, USBVISION_SSPND_EN); + usbvision_write_reg(usbvision, USBVISION_PWR_REG, + USBVISION_SSPND_EN | USBVISION_RES2); + + if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) { + usbvision_write_reg(usbvision, USBVISION_VIN_REG1, + USBVISION_16_422_SYNC | USBVISION_HVALID_PO); + usbvision_write_reg(usbvision, USBVISION_VIN_REG2, + USBVISION_NOHVALID | USBVISION_KEEP_BLANK); + } + usbvision_write_reg(usbvision, USBVISION_PWR_REG, + USBVISION_SSPND_EN | USBVISION_PWR_VID); + mdelay(10); + err_code = usbvision_write_reg(usbvision, USBVISION_PWR_REG, + USBVISION_SSPND_EN | USBVISION_PWR_VID | USBVISION_RES2); + if (err_code == 1) + usbvision->power = 1; + PDEBUG(DBG_FUNC, "%s: err_code %d", (err_code < 0) ? "ERROR" : "power is on", err_code); + return err_code; +} + + +/* + * usbvision_begin_streaming() + * Sure you have to put bit 7 to 0, if not incoming frames are dropped, but no + * idea about the rest + */ +int usbvision_begin_streaming(struct usb_usbvision *usbvision) +{ + if (usbvision->isoc_mode == ISOC_MODE_COMPRESS) + usbvision_init_compression(usbvision); + return usbvision_write_reg(usbvision, USBVISION_VIN_REG2, + USBVISION_NOHVALID | usbvision->vin_reg2_preset); +} + +/* + * usbvision_restart_isoc() + * Not sure yet if touching here PWR_REG make loose the config + */ + +int usbvision_restart_isoc(struct usb_usbvision *usbvision) +{ + int ret; + + ret = usbvision_write_reg(usbvision, USBVISION_PWR_REG, + USBVISION_SSPND_EN | USBVISION_PWR_VID); + if (ret < 0) + return ret; + ret = usbvision_write_reg(usbvision, USBVISION_PWR_REG, + USBVISION_SSPND_EN | USBVISION_PWR_VID | + USBVISION_RES2); + if (ret < 0) + return ret; + ret = usbvision_write_reg(usbvision, USBVISION_VIN_REG2, + USBVISION_KEEP_BLANK | USBVISION_NOHVALID | + usbvision->vin_reg2_preset); + if (ret < 0) + return ret; + + /* TODO: schedule timeout */ + while ((usbvision_read_reg(usbvision, USBVISION_STATUS_REG) & 0x01) != 1) + ; + + return 0; +} + +int usbvision_audio_off(struct usb_usbvision *usbvision) +{ + if (usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, USBVISION_AUDIO_MUTE) < 0) { + printk(KERN_ERR "usbvision_audio_off: can't write reg\n"); + return -1; + } + usbvision->audio_mute = 0; + usbvision->audio_channel = USBVISION_AUDIO_MUTE; + return 0; +} + +int usbvision_set_audio(struct usb_usbvision *usbvision, int audio_channel) +{ + if (!usbvision->audio_mute) { + if (usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, audio_channel) < 0) { + printk(KERN_ERR "usbvision_set_audio: can't write iopin register for audio switching\n"); + return -1; + } + } + usbvision->audio_channel = audio_channel; + return 0; +} + +int usbvision_setup(struct usb_usbvision *usbvision, int format) +{ + if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) + usbvision_init_webcam(usbvision); + usbvision_set_video_format(usbvision, format); + usbvision_set_dram_settings(usbvision); + usbvision_set_compress_params(usbvision); + usbvision_set_input(usbvision); + usbvision_set_output(usbvision, MAX_USB_WIDTH, MAX_USB_HEIGHT); + usbvision_restart_isoc(usbvision); + + /* cosas del PCM */ + return USBVISION_IS_OPERATIONAL(usbvision); +} + +int usbvision_set_alternate(struct usb_usbvision *dev) +{ + int err_code, prev_alt = dev->iface_alt; + int i; + + dev->iface_alt = 0; + for (i = 0; i < dev->num_alt; i++) + if (dev->alt_max_pkt_size[i] > dev->alt_max_pkt_size[dev->iface_alt]) + dev->iface_alt = i; + + if (dev->iface_alt != prev_alt) { + dev->isoc_packet_size = dev->alt_max_pkt_size[dev->iface_alt]; + PDEBUG(DBG_FUNC, "setting alternate %d with max_packet_size=%u", + dev->iface_alt, dev->isoc_packet_size); + err_code = usb_set_interface(dev->dev, dev->iface, dev->iface_alt); + if (err_code < 0) { + dev_err(&dev->dev->dev, + "cannot change alternate number to %d (error=%i)\n", + dev->iface_alt, err_code); + return err_code; + } + } + + PDEBUG(DBG_ISOC, "ISO Packet Length:%d", dev->isoc_packet_size); + + return 0; +} + +/* + * usbvision_init_isoc() + * + */ +int usbvision_init_isoc(struct usb_usbvision *usbvision) +{ + struct usb_device *dev = usbvision->dev; + int buf_idx, err_code, reg_value; + int sb_size; + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return -EFAULT; + + usbvision->cur_frame = NULL; + scratch_reset(usbvision); + + /* Alternate interface 1 is is the biggest frame size */ + err_code = usbvision_set_alternate(usbvision); + if (err_code < 0) { + usbvision->last_error = err_code; + return -EBUSY; + } + sb_size = USBVISION_URB_FRAMES * usbvision->isoc_packet_size; + + reg_value = (16 - usbvision_read_reg(usbvision, + USBVISION_ALTER_REG)) & 0x0F; + + usbvision->usb_bandwidth = reg_value >> 1; + PDEBUG(DBG_ISOC, "USB Bandwidth Usage: %dMbit/Sec", + usbvision->usb_bandwidth); + + + + /* We double buffer the Iso lists */ + + for (buf_idx = 0; buf_idx < USBVISION_NUMSBUF; buf_idx++) { + int j, k; + struct urb *urb; + + urb = usb_alloc_urb(USBVISION_URB_FRAMES, GFP_KERNEL); + if (urb == NULL) + return -ENOMEM; + usbvision->sbuf[buf_idx].urb = urb; + usbvision->sbuf[buf_idx].data = + usb_alloc_coherent(usbvision->dev, + sb_size, + GFP_KERNEL, + &urb->transfer_dma); + if (!usbvision->sbuf[buf_idx].data) + return -ENOMEM; + + urb->dev = dev; + urb->context = usbvision; + urb->pipe = usb_rcvisocpipe(dev, usbvision->video_endp); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval = 1; + urb->transfer_buffer = usbvision->sbuf[buf_idx].data; + urb->complete = usbvision_isoc_irq; + urb->number_of_packets = USBVISION_URB_FRAMES; + urb->transfer_buffer_length = + usbvision->isoc_packet_size * USBVISION_URB_FRAMES; + for (j = k = 0; j < USBVISION_URB_FRAMES; j++, + k += usbvision->isoc_packet_size) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = + usbvision->isoc_packet_size; + } + } + + /* Submit all URBs */ + for (buf_idx = 0; buf_idx < USBVISION_NUMSBUF; buf_idx++) { + err_code = usb_submit_urb(usbvision->sbuf[buf_idx].urb, + GFP_KERNEL); + if (err_code) { + dev_err(&usbvision->dev->dev, + "%s: usb_submit_urb(%d) failed: error %d\n", + __func__, buf_idx, err_code); + } + } + + usbvision->streaming = stream_idle; + PDEBUG(DBG_ISOC, "%s: streaming=1 usbvision->video_endp=$%02x", + __func__, + usbvision->video_endp); + return 0; +} + +/* + * usbvision_stop_isoc() + * + * This procedure stops streaming and deallocates URBs. Then it + * activates zero-bandwidth alt. setting of the video interface. + * + */ +void usbvision_stop_isoc(struct usb_usbvision *usbvision) +{ + int buf_idx, err_code, reg_value; + int sb_size = USBVISION_URB_FRAMES * usbvision->isoc_packet_size; + + if ((usbvision->streaming == stream_off) || (usbvision->dev == NULL)) + return; + + /* Unschedule all of the iso td's */ + for (buf_idx = 0; buf_idx < USBVISION_NUMSBUF; buf_idx++) { + usb_kill_urb(usbvision->sbuf[buf_idx].urb); + if (usbvision->sbuf[buf_idx].data) { + usb_free_coherent(usbvision->dev, + sb_size, + usbvision->sbuf[buf_idx].data, + usbvision->sbuf[buf_idx].urb->transfer_dma); + } + usb_free_urb(usbvision->sbuf[buf_idx].urb); + usbvision->sbuf[buf_idx].urb = NULL; + } + + PDEBUG(DBG_ISOC, "%s: streaming=stream_off\n", __func__); + usbvision->streaming = stream_off; + + if (!usbvision->remove_pending) { + /* Set packet size to 0 */ + usbvision->iface_alt = 0; + err_code = usb_set_interface(usbvision->dev, usbvision->iface, + usbvision->iface_alt); + if (err_code < 0) { + dev_err(&usbvision->dev->dev, + "%s: usb_set_interface() failed: error %d\n", + __func__, err_code); + usbvision->last_error = err_code; + } + reg_value = (16-usbvision_read_reg(usbvision, USBVISION_ALTER_REG)) & 0x0F; + usbvision->isoc_packet_size = + (reg_value == 0) ? 0 : (reg_value * 64) - 1; + PDEBUG(DBG_ISOC, "ISO Packet Length:%d", + usbvision->isoc_packet_size); + + usbvision->usb_bandwidth = reg_value >> 1; + PDEBUG(DBG_ISOC, "USB Bandwidth Usage: %dMbit/Sec", + usbvision->usb_bandwidth); + } +} + +int usbvision_muxsel(struct usb_usbvision *usbvision, int channel) +{ + /* inputs #0 and #3 are constant for every SAA711x. */ + /* inputs #1 and #2 are variable for SAA7111 and SAA7113 */ + int mode[4] = { SAA7115_COMPOSITE0, 0, 0, SAA7115_COMPOSITE3 }; + int audio[] = { 1, 0, 0, 0 }; + /* channel 0 is TV with audiochannel 1 (tuner mono) */ + /* channel 1 is Composite with audio channel 0 (line in) */ + /* channel 2 is S-Video with audio channel 0 (line in) */ + /* channel 3 is additional video inputs to the device with audio channel 0 (line in) */ + + RESTRICT_TO_RANGE(channel, 0, usbvision->video_inputs); + usbvision->ctl_input = channel; + + /* set the new channel */ + /* Regular USB TV Tuners -> channel: 0 = Television, 1 = Composite, 2 = S-Video */ + /* Four video input devices -> channel: 0 = Chan White, 1 = Chan Green, 2 = Chan Yellow, 3 = Chan Red */ + + switch (usbvision_device_data[usbvision->dev_model].codec) { + case CODEC_SAA7113: + mode[1] = SAA7115_COMPOSITE2; + if (switch_svideo_input) { + /* To handle problems with S-Video Input for + * some devices. Use switch_svideo_input + * parameter when loading the module.*/ + mode[2] = SAA7115_COMPOSITE1; + } else { + mode[2] = SAA7115_SVIDEO1; + } + break; + case CODEC_SAA7111: + default: + /* modes for saa7111 */ + mode[1] = SAA7115_COMPOSITE1; + mode[2] = SAA7115_SVIDEO1; + break; + } + call_all(usbvision, video, s_routing, mode[channel], 0, 0); + usbvision_set_audio(usbvision, audio[channel]); + return 0; +} diff --git a/drivers/staging/media/usbvision/usbvision-i2c.c b/drivers/staging/media/usbvision/usbvision-i2c.c new file mode 100644 index 000000000000..6e4df3335b1b --- /dev/null +++ b/drivers/staging/media/usbvision/usbvision-i2c.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbvision_i2c.c + * i2c algorithm for USB-I2C Bridges + * + * Copyright (c) 1999-2007 Joerg Heckenbach <joerg@heckenbach-aw.de> + * Dwaine Garden <dwainegarden@rogers.com> + * + * This module is part of usbvision driver project. + * Updates to driver completed by Dwaine P. Garden + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/uaccess.h> +#include <linux/ioport.h> +#include <linux/errno.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include "usbvision.h" + +#define DBG_I2C (1 << 0) + +static int i2c_debug; + +module_param(i2c_debug, int, 0644); /* debug_i2c_usb mode of the device driver */ +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#define PDEBUG(level, fmt, args...) { \ + if (i2c_debug & (level)) \ + printk(KERN_INFO KBUILD_MODNAME ":[%s:%d] " fmt, \ + __func__, __LINE__ , ## args); \ + } + +static int usbvision_i2c_write(struct usb_usbvision *usbvision, unsigned char addr, char *buf, + short len); +static int usbvision_i2c_read(struct usb_usbvision *usbvision, unsigned char addr, char *buf, + short len); + +static inline int try_write_address(struct i2c_adapter *i2c_adap, + unsigned char addr, int retries) +{ + struct usb_usbvision *usbvision; + int i, ret = -1; + char buf[4]; + + usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap); + buf[0] = 0x00; + for (i = 0; i <= retries; i++) { + ret = (usbvision_i2c_write(usbvision, addr, buf, 1)); + if (ret == 1) + break; /* success! */ + udelay(5); + if (i == retries) /* no success */ + break; + udelay(10); + } + if (i) { + PDEBUG(DBG_I2C, "Needed %d retries for address %#2x", i, addr); + PDEBUG(DBG_I2C, "Maybe there's no device at this address"); + } + return ret; +} + +static inline int try_read_address(struct i2c_adapter *i2c_adap, + unsigned char addr, int retries) +{ + struct usb_usbvision *usbvision; + int i, ret = -1; + char buf[4]; + + usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap); + for (i = 0; i <= retries; i++) { + ret = (usbvision_i2c_read(usbvision, addr, buf, 1)); + if (ret == 1) + break; /* success! */ + udelay(5); + if (i == retries) /* no success */ + break; + udelay(10); + } + if (i) { + PDEBUG(DBG_I2C, "Needed %d retries for address %#2x", i, addr); + PDEBUG(DBG_I2C, "Maybe there's no device at this address"); + } + return ret; +} + +static inline int usb_find_address(struct i2c_adapter *i2c_adap, + struct i2c_msg *msg, int retries, + unsigned char *add) +{ + unsigned short flags = msg->flags; + + unsigned char addr; + int ret; + + addr = (msg->addr << 1); + if (flags & I2C_M_RD) + addr |= 1; + + add[0] = addr; + if (flags & I2C_M_RD) + ret = try_read_address(i2c_adap, addr, retries); + else + ret = try_write_address(i2c_adap, addr, retries); + + if (ret != 1) + return -EREMOTEIO; + + return 0; +} + +static int +usbvision_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num) +{ + struct i2c_msg *pmsg; + struct usb_usbvision *usbvision; + int i, ret; + unsigned char addr = 0; + + usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap); + + for (i = 0; i < num; i++) { + pmsg = &msgs[i]; + ret = usb_find_address(i2c_adap, pmsg, i2c_adap->retries, &addr); + if (ret != 0) { + PDEBUG(DBG_I2C, "got NAK from device, message #%d", i); + return (ret < 0) ? ret : -EREMOTEIO; + } + + if (pmsg->flags & I2C_M_RD) { + /* read bytes into buffer */ + ret = (usbvision_i2c_read(usbvision, addr, pmsg->buf, pmsg->len)); + if (ret < pmsg->len) + return (ret < 0) ? ret : -EREMOTEIO; + } else { + /* write bytes from buffer */ + ret = (usbvision_i2c_write(usbvision, addr, pmsg->buf, pmsg->len)); + if (ret < pmsg->len) + return (ret < 0) ? ret : -EREMOTEIO; + } + } + return num; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +/* -----exported algorithm data: ------------------------------------- */ + +static const struct i2c_algorithm usbvision_algo = { + .master_xfer = usbvision_i2c_xfer, + .smbus_xfer = NULL, + .functionality = functionality, +}; + + +/* ----------------------------------------------------------------------- */ +/* usbvision specific I2C functions */ +/* ----------------------------------------------------------------------- */ +static const struct i2c_adapter i2c_adap_template; + +int usbvision_i2c_register(struct usb_usbvision *usbvision) +{ + static unsigned short saa711x_addrs[] = { + 0x4a >> 1, 0x48 >> 1, /* SAA7111, SAA7111A and SAA7113 */ + 0x42 >> 1, 0x40 >> 1, /* SAA7114, SAA7115 and SAA7118 */ + I2C_CLIENT_END }; + + if (usbvision->registered_i2c) + return 0; + + usbvision->i2c_adap = i2c_adap_template; + + snprintf(usbvision->i2c_adap.name, sizeof(usbvision->i2c_adap.name), + "usbvision-%d-%s", + usbvision->dev->bus->busnum, usbvision->dev->devpath); + PDEBUG(DBG_I2C, "Adaptername: %s", usbvision->i2c_adap.name); + usbvision->i2c_adap.dev.parent = &usbvision->dev->dev; + + i2c_set_adapdata(&usbvision->i2c_adap, &usbvision->v4l2_dev); + + if (usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_IIC_LRNACK) < 0) { + printk(KERN_ERR "usbvision_i2c_register: can't write reg\n"); + return -EBUSY; + } + + PDEBUG(DBG_I2C, "I2C debugging is enabled [i2c]"); + PDEBUG(DBG_I2C, "ALGO debugging is enabled [i2c]"); + + /* register new adapter to i2c module... */ + + usbvision->i2c_adap.algo = &usbvision_algo; + + usbvision->i2c_adap.timeout = 100; /* default values, should */ + usbvision->i2c_adap.retries = 3; /* be replaced by defines */ + + i2c_add_adapter(&usbvision->i2c_adap); + + PDEBUG(DBG_I2C, "i2c bus for %s registered", usbvision->i2c_adap.name); + + /* Request the load of the i2c modules we need */ + switch (usbvision_device_data[usbvision->dev_model].codec) { + case CODEC_SAA7113: + case CODEC_SAA7111: + /* Without this delay the detection of the saa711x is + hit-and-miss. */ + mdelay(10); + v4l2_i2c_new_subdev(&usbvision->v4l2_dev, + &usbvision->i2c_adap, + "saa7115_auto", 0, saa711x_addrs); + break; + } + if (usbvision_device_data[usbvision->dev_model].tuner == 1) { + struct v4l2_subdev *sd; + enum v4l2_i2c_tuner_type type; + struct tuner_setup tun_setup; + + sd = v4l2_i2c_new_subdev(&usbvision->v4l2_dev, + &usbvision->i2c_adap, + "tuner", 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD)); + /* depending on whether we found a demod or not, select + the tuner type. */ + type = sd ? ADDRS_TV_WITH_DEMOD : ADDRS_TV; + + sd = v4l2_i2c_new_subdev(&usbvision->v4l2_dev, + &usbvision->i2c_adap, + "tuner", 0, v4l2_i2c_tuner_addrs(type)); + + if (sd == NULL) + return -ENODEV; + if (usbvision->tuner_type != -1) { + tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; + tun_setup.type = usbvision->tuner_type; + tun_setup.addr = v4l2_i2c_subdev_addr(sd); + call_all(usbvision, tuner, s_type_addr, &tun_setup); + } + } + usbvision->registered_i2c = 1; + + return 0; +} + +int usbvision_i2c_unregister(struct usb_usbvision *usbvision) +{ + if (!usbvision->registered_i2c) + return 0; + + i2c_del_adapter(&(usbvision->i2c_adap)); + usbvision->registered_i2c = 0; + + PDEBUG(DBG_I2C, "i2c bus for %s unregistered", usbvision->i2c_adap.name); + + return 0; +} + +static int +usbvision_i2c_read_max4(struct usb_usbvision *usbvision, unsigned char addr, + char *buf, short len) +{ + int rc, retries; + + for (retries = 5;;) { + rc = usbvision_write_reg(usbvision, USBVISION_SER_ADRS, addr); + if (rc < 0) + return rc; + + /* Initiate byte read cycle */ + /* USBVISION_SER_CONT <- d0-d2 n. of bytes to r/w */ + /* d3 0=Wr 1=Rd */ + rc = usbvision_write_reg(usbvision, USBVISION_SER_CONT, + (len & 0x07) | 0x18); + if (rc < 0) + return rc; + + /* Test for Busy and ACK */ + do { + /* USBVISION_SER_CONT -> d4 == 0 busy */ + rc = usbvision_read_reg(usbvision, USBVISION_SER_CONT); + } while (rc > 0 && ((rc & 0x10) != 0)); /* Retry while busy */ + if (rc < 0) + return rc; + + /* USBVISION_SER_CONT -> d5 == 1 Not ack */ + if ((rc & 0x20) == 0) /* Ack? */ + break; + + /* I2C abort */ + rc = usbvision_write_reg(usbvision, USBVISION_SER_CONT, 0x00); + if (rc < 0) + return rc; + + if (--retries < 0) + return -1; + } + + switch (len) { + case 4: + buf[3] = usbvision_read_reg(usbvision, USBVISION_SER_DAT4); + /* fall through */ + case 3: + buf[2] = usbvision_read_reg(usbvision, USBVISION_SER_DAT3); + /* fall through */ + case 2: + buf[1] = usbvision_read_reg(usbvision, USBVISION_SER_DAT2); + /* fall through */ + case 1: + buf[0] = usbvision_read_reg(usbvision, USBVISION_SER_DAT1); + break; + default: + printk(KERN_ERR + "usbvision_i2c_read_max4: buffer length > 4\n"); + } + + if (i2c_debug & DBG_I2C) { + int idx; + + for (idx = 0; idx < len; idx++) + PDEBUG(DBG_I2C, "read %x from address %x", (unsigned char)buf[idx], addr); + } + return len; +} + + +static int usbvision_i2c_write_max4(struct usb_usbvision *usbvision, + unsigned char addr, const char *buf, + short len) +{ + int rc, retries; + int i; + unsigned char *value = usbvision->ctrl_urb_buffer; + unsigned char ser_cont; + + ser_cont = (len & 0x07) | 0x10; + + value[0] = addr; + value[1] = ser_cont; + for (i = 0; i < len; i++) + value[i + 2] = buf[i]; + + for (retries = 5;;) { + rc = usb_control_msg(usbvision->dev, + usb_sndctrlpipe(usbvision->dev, 1), + USBVISION_OP_CODE, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_ENDPOINT, 0, + (__u16) USBVISION_SER_ADRS, value, + len + 2, HZ); + + if (rc < 0) + return rc; + + rc = usbvision_write_reg(usbvision, USBVISION_SER_CONT, + (len & 0x07) | 0x10); + if (rc < 0) + return rc; + + /* Test for Busy and ACK */ + do { + rc = usbvision_read_reg(usbvision, USBVISION_SER_CONT); + } while (rc > 0 && ((rc & 0x10) != 0)); /* Retry while busy */ + if (rc < 0) + return rc; + + if ((rc & 0x20) == 0) /* Ack? */ + break; + + /* I2C abort */ + usbvision_write_reg(usbvision, USBVISION_SER_CONT, 0x00); + + if (--retries < 0) + return -1; + + } + + if (i2c_debug & DBG_I2C) { + int idx; + + for (idx = 0; idx < len; idx++) + PDEBUG(DBG_I2C, "wrote %x at address %x", (unsigned char)buf[idx], addr); + } + return len; +} + +static int usbvision_i2c_write(struct usb_usbvision *usbvision, unsigned char addr, char *buf, + short len) +{ + char *buf_ptr = buf; + int retval; + int wrcount = 0; + int count; + int max_len = 4; + + while (len > 0) { + count = (len > max_len) ? max_len : len; + retval = usbvision_i2c_write_max4(usbvision, addr, buf_ptr, count); + if (retval > 0) { + len -= count; + buf_ptr += count; + wrcount += count; + } else + return (retval < 0) ? retval : -EFAULT; + } + return wrcount; +} + +static int usbvision_i2c_read(struct usb_usbvision *usbvision, unsigned char addr, char *buf, + short len) +{ + char temp[4]; + int retval, i; + int rdcount = 0; + int count; + + while (len > 0) { + count = (len > 3) ? 4 : len; + retval = usbvision_i2c_read_max4(usbvision, addr, temp, count); + if (retval > 0) { + for (i = 0; i < len; i++) + buf[rdcount + i] = temp[i]; + len -= count; + rdcount += count; + } else + return (retval < 0) ? retval : -EFAULT; + } + return rdcount; +} + +static const struct i2c_adapter i2c_adap_template = { + .owner = THIS_MODULE, + .name = "usbvision", +}; diff --git a/drivers/staging/media/usbvision/usbvision-video.c b/drivers/staging/media/usbvision/usbvision-video.c new file mode 100644 index 000000000000..3ea25fdcf767 --- /dev/null +++ b/drivers/staging/media/usbvision/usbvision-video.c @@ -0,0 +1,1643 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * USB USBVISION Video device driver 0.9.10 + * + * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de> + * + * This module is part of usbvision driver project. + * + * Let's call the version 0.... until compression decoding is completely + * implemented. + * + * This driver is written by Jose Ignacio Gijon and Joerg Heckenbach. + * It was based on USB CPiA driver written by Peter Pregler, + * Scott J. Bertin and Johannes Erdfelt + * Ideas are taken from bttv driver by Ralph Metzler, Marcus Metzler & + * Gerd Knorr and zoran 36120/36125 driver by Pauline Middelink + * Updates to driver completed by Dwaine P. Garden + * + * TODO: + * - use submit_urb for all setup packets + * - Fix memory settings for nt1004. It is 4 times as big as the + * nt1003 memory. + * - Add audio on endpoint 3 for nt1004 chip. + * Seems impossible, needs a codec interface. Which one? + * - Clean up the driver. + * - optimization for performance. + * - Add Videotext capability (VBI). Working on it..... + * - Check audio for other devices + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/timer.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/highmem.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> + +#include <media/i2c/saa7115.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/tuner.h> + +#include <linux/workqueue.h> + +#include "usbvision.h" +#include "usbvision-cards.h" + +#define DRIVER_AUTHOR \ + "Joerg Heckenbach <joerg@heckenbach-aw.de>, " \ + "Dwaine Garden <DwaineGarden@rogers.com>" +#define DRIVER_NAME "usbvision" +#define DRIVER_ALIAS "USBVision" +#define DRIVER_DESC "USBVision USB Video Device Driver for Linux" +#define USBVISION_VERSION_STRING "0.9.11" + +#define ENABLE_HEXDUMP 0 /* Enable if you need it */ + + +#ifdef USBVISION_DEBUG + #define PDEBUG(level, fmt, args...) { \ + if (video_debug & (level)) \ + printk(KERN_INFO KBUILD_MODNAME ":[%s:%d] " fmt, \ + __func__, __LINE__ , ## args); \ + } +#else + #define PDEBUG(level, fmt, args...) do {} while (0) +#endif + +#define DBG_IO (1 << 1) +#define DBG_PROBE (1 << 2) +#define DBG_MMAP (1 << 3) + +/* String operations */ +#define rmspace(str) while (*str == ' ') str++; +#define goto2next(str) while (*str != ' ') str++; while (*str == ' ') str++; + + +/* sequential number of usbvision device */ +static int usbvision_nr; + +static struct usbvision_v4l2_format_st usbvision_v4l2_format[] = { + { 1, 1, 8, V4L2_PIX_FMT_GREY }, + { 1, 2, 16, V4L2_PIX_FMT_RGB565 }, + { 1, 3, 24, V4L2_PIX_FMT_RGB24 }, + { 1, 4, 32, V4L2_PIX_FMT_RGB32 }, + { 1, 2, 16, V4L2_PIX_FMT_RGB555 }, + { 1, 2, 16, V4L2_PIX_FMT_YUYV }, + { 1, 2, 12, V4L2_PIX_FMT_YVU420 }, /* 1.5 ! */ + { 1, 2, 16, V4L2_PIX_FMT_YUV422P } +}; + +/* Function prototypes */ +static void usbvision_release(struct usb_usbvision *usbvision); + +/* Default initialization of device driver parameters */ +/* Set the default format for ISOC endpoint */ +static int isoc_mode = ISOC_MODE_COMPRESS; +/* Set the default Debug Mode of the device driver */ +static int video_debug; +/* Sequential Number of Video Device */ +static int video_nr = -1; +/* Sequential Number of Radio Device */ +static int radio_nr = -1; + +/* Grab parameters for the device driver */ + +/* Showing parameters under SYSFS */ +module_param(isoc_mode, int, 0444); +module_param(video_debug, int, 0444); +module_param(video_nr, int, 0444); +module_param(radio_nr, int, 0444); + +MODULE_PARM_DESC(isoc_mode, " Set the default format for ISOC endpoint. Default: 0x60 (Compression On)"); +MODULE_PARM_DESC(video_debug, " Set the default Debug Mode of the device driver. Default: 0 (Off)"); +MODULE_PARM_DESC(video_nr, "Set video device number (/dev/videoX). Default: -1 (autodetect)"); +MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); + + +/* Misc stuff */ +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(USBVISION_VERSION_STRING); +MODULE_ALIAS(DRIVER_ALIAS); + + +/*****************************************************************************/ +/* SYSFS Code - Copied from the stv680.c usb module. */ +/* Device information is located at /sys/class/video4linux/video0 */ +/* Device parameters information is located at /sys/module/usbvision */ +/* Device USB Information is located at */ +/* /sys/bus/usb/drivers/USBVision Video Grabber */ +/*****************************************************************************/ + +#define YES_NO(x) ((x) ? "Yes" : "No") + +static inline struct usb_usbvision *cd_to_usbvision(struct device *cd) +{ + struct video_device *vdev = to_video_device(cd); + return video_get_drvdata(vdev); +} + +static ssize_t show_version(struct device *cd, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", USBVISION_VERSION_STRING); +} +static DEVICE_ATTR(version, S_IRUGO, show_version, NULL); + +static ssize_t show_model(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + return sprintf(buf, "%s\n", + usbvision_device_data[usbvision->dev_model].model_string); +} +static DEVICE_ATTR(model, S_IRUGO, show_model, NULL); + +static ssize_t show_hue(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl, + V4L2_CID_HUE)); + + return sprintf(buf, "%d\n", val); +} +static DEVICE_ATTR(hue, S_IRUGO, show_hue, NULL); + +static ssize_t show_contrast(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl, + V4L2_CID_CONTRAST)); + + return sprintf(buf, "%d\n", val); +} +static DEVICE_ATTR(contrast, S_IRUGO, show_contrast, NULL); + +static ssize_t show_brightness(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl, + V4L2_CID_BRIGHTNESS)); + + return sprintf(buf, "%d\n", val); +} +static DEVICE_ATTR(brightness, S_IRUGO, show_brightness, NULL); + +static ssize_t show_saturation(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl, + V4L2_CID_SATURATION)); + + return sprintf(buf, "%d\n", val); +} +static DEVICE_ATTR(saturation, S_IRUGO, show_saturation, NULL); + +static ssize_t show_streaming(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + return sprintf(buf, "%s\n", + YES_NO(usbvision->streaming == stream_on ? 1 : 0)); +} +static DEVICE_ATTR(streaming, S_IRUGO, show_streaming, NULL); + +static ssize_t show_compression(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + return sprintf(buf, "%s\n", + YES_NO(usbvision->isoc_mode == ISOC_MODE_COMPRESS)); +} +static DEVICE_ATTR(compression, S_IRUGO, show_compression, NULL); + +static ssize_t show_device_bridge(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct video_device *vdev = to_video_device(cd); + struct usb_usbvision *usbvision = video_get_drvdata(vdev); + return sprintf(buf, "%d\n", usbvision->bridge_type); +} +static DEVICE_ATTR(bridge, S_IRUGO, show_device_bridge, NULL); + +static void usbvision_create_sysfs(struct video_device *vdev) +{ + int res; + + if (!vdev) + return; + do { + res = device_create_file(&vdev->dev, &dev_attr_version); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_model); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_hue); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_contrast); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_brightness); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_saturation); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_streaming); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_compression); + if (res < 0) + break; + res = device_create_file(&vdev->dev, &dev_attr_bridge); + if (res >= 0) + return; + } while (0); + + dev_err(&vdev->dev, "%s error: %d\n", __func__, res); +} + +static void usbvision_remove_sysfs(struct video_device *vdev) +{ + if (vdev) { + device_remove_file(&vdev->dev, &dev_attr_version); + device_remove_file(&vdev->dev, &dev_attr_model); + device_remove_file(&vdev->dev, &dev_attr_hue); + device_remove_file(&vdev->dev, &dev_attr_contrast); + device_remove_file(&vdev->dev, &dev_attr_brightness); + device_remove_file(&vdev->dev, &dev_attr_saturation); + device_remove_file(&vdev->dev, &dev_attr_streaming); + device_remove_file(&vdev->dev, &dev_attr_compression); + device_remove_file(&vdev->dev, &dev_attr_bridge); + } +} + +/* + * usbvision_open() + * + * This is part of Video 4 Linux API. The driver can be opened by one + * client only (checks internal counter 'usbvision->user'). The procedure + * then allocates buffers needed for video processing. + * + */ +static int usbvision_v4l2_open(struct file *file) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int err_code = 0; + + PDEBUG(DBG_IO, "open"); + + if (mutex_lock_interruptible(&usbvision->v4l2_lock)) + return -ERESTARTSYS; + + if (usbvision->remove_pending) { + err_code = -ENODEV; + goto unlock; + } + if (usbvision->user) { + err_code = -EBUSY; + } else { + err_code = v4l2_fh_open(file); + if (err_code) + goto unlock; + + /* Allocate memory for the scratch ring buffer */ + err_code = usbvision_scratch_alloc(usbvision); + if (isoc_mode == ISOC_MODE_COMPRESS) { + /* Allocate intermediate decompression buffers + only if needed */ + err_code = usbvision_decompress_alloc(usbvision); + } + if (err_code) { + /* Deallocate all buffers if trouble */ + usbvision_scratch_free(usbvision); + usbvision_decompress_free(usbvision); + } + } + + /* If so far no errors then we shall start the camera */ + if (!err_code) { + /* Send init sequence only once, it's large! */ + if (!usbvision->initialized) { + int setup_ok = 0; + setup_ok = usbvision_setup(usbvision, isoc_mode); + if (setup_ok) + usbvision->initialized = 1; + else + err_code = -EBUSY; + } + + if (!err_code) { + usbvision_begin_streaming(usbvision); + err_code = usbvision_init_isoc(usbvision); + /* device must be initialized before isoc transfer */ + usbvision_muxsel(usbvision, 0); + + /* prepare queues */ + usbvision_empty_framequeues(usbvision); + usbvision->user++; + } + } + +unlock: + mutex_unlock(&usbvision->v4l2_lock); + + PDEBUG(DBG_IO, "success"); + return err_code; +} + +/* + * usbvision_v4l2_close() + * + * This is part of Video 4 Linux API. The procedure + * stops streaming and deallocates all buffers that were earlier + * allocated in usbvision_v4l2_open(). + * + */ +static int usbvision_v4l2_close(struct file *file) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int r; + + PDEBUG(DBG_IO, "close"); + + mutex_lock(&usbvision->v4l2_lock); + usbvision_audio_off(usbvision); + usbvision_restart_isoc(usbvision); + usbvision_stop_isoc(usbvision); + + usbvision_decompress_free(usbvision); + usbvision_frames_free(usbvision); + usbvision_empty_framequeues(usbvision); + usbvision_scratch_free(usbvision); + + usbvision->user--; + r = usbvision->remove_pending; + mutex_unlock(&usbvision->v4l2_lock); + + if (r) { + printk(KERN_INFO "%s: Final disconnect\n", __func__); + usbvision_release(usbvision); + return 0; + } + + PDEBUG(DBG_IO, "success"); + return v4l2_fh_release(file); +} + + +/* + * usbvision_ioctl() + * + * This is part of Video 4 Linux API. The procedure handles ioctl() calls. + * + */ +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int err_code; + + /* NT100x has a 8-bit register space */ + err_code = usbvision_read_reg(usbvision, reg->reg&0xff); + if (err_code < 0) { + dev_err(&usbvision->vdev.dev, + "%s: VIDIOC_DBG_G_REGISTER failed: error %d\n", + __func__, err_code); + return err_code; + } + reg->val = err_code; + reg->size = 1; + return 0; +} + +static int vidioc_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int err_code; + + /* NT100x has a 8-bit register space */ + err_code = usbvision_write_reg(usbvision, reg->reg & 0xff, reg->val); + if (err_code < 0) { + dev_err(&usbvision->vdev.dev, + "%s: VIDIOC_DBG_S_REGISTER failed: error %d\n", + __func__, err_code); + return err_code; + } + return 0; +} +#endif + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *vc) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + if (!usbvision->dev) + return -ENODEV; + + strscpy(vc->driver, "USBVision", sizeof(vc->driver)); + strscpy(vc->card, + usbvision_device_data[usbvision->dev_model].model_string, + sizeof(vc->card)); + usb_make_path(usbvision->dev, vc->bus_info, sizeof(vc->bus_info)); + vc->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS; + if (usbvision_device_data[usbvision->dev_model].radio) + vc->capabilities |= V4L2_CAP_RADIO; + if (usbvision->have_tuner) + vc->capabilities |= V4L2_CAP_TUNER; + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *vi) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int chan; + + if (vi->index >= usbvision->video_inputs) + return -EINVAL; + if (usbvision->have_tuner) + chan = vi->index; + else + chan = vi->index + 1; /* skip Television string*/ + + /* Determine the requested input characteristics + specific for each usbvision card model */ + switch (chan) { + case 0: + if (usbvision_device_data[usbvision->dev_model].video_channels == 4) { + strscpy(vi->name, "White Video Input", sizeof(vi->name)); + } else { + strscpy(vi->name, "Television", sizeof(vi->name)); + vi->type = V4L2_INPUT_TYPE_TUNER; + vi->tuner = chan; + vi->std = USBVISION_NORMS; + } + break; + case 1: + vi->type = V4L2_INPUT_TYPE_CAMERA; + if (usbvision_device_data[usbvision->dev_model].video_channels == 4) + strscpy(vi->name, "Green Video Input", sizeof(vi->name)); + else + strscpy(vi->name, "Composite Video Input", + sizeof(vi->name)); + vi->std = USBVISION_NORMS; + break; + case 2: + vi->type = V4L2_INPUT_TYPE_CAMERA; + if (usbvision_device_data[usbvision->dev_model].video_channels == 4) + strscpy(vi->name, "Yellow Video Input", sizeof(vi->name)); + else + strscpy(vi->name, "S-Video Input", sizeof(vi->name)); + vi->std = USBVISION_NORMS; + break; + case 3: + vi->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(vi->name, "Red Video Input", sizeof(vi->name)); + vi->std = USBVISION_NORMS; + break; + } + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *input) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + *input = usbvision->ctl_input; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int input) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + if (input >= usbvision->video_inputs) + return -EINVAL; + + usbvision_muxsel(usbvision, input); + usbvision_set_input(usbvision); + usbvision_set_output(usbvision, + usbvision->curwidth, + usbvision->curheight); + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + usbvision->tvnorm_id = id; + + call_all(usbvision, video, s_std, usbvision->tvnorm_id); + /* propagate the change to the decoder */ + usbvision_muxsel(usbvision, usbvision->ctl_input); + + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + *id = usbvision->tvnorm_id; + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *vt) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + if (vt->index) /* Only tuner 0 */ + return -EINVAL; + if (vt->type == V4L2_TUNER_RADIO) + strscpy(vt->name, "Radio", sizeof(vt->name)); + else + strscpy(vt->name, "Television", sizeof(vt->name)); + + /* Let clients fill in the remainder of this struct */ + call_all(usbvision, tuner, g_tuner, vt); + + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *vt) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + /* Only one tuner for now */ + if (vt->index) + return -EINVAL; + /* let clients handle this */ + call_all(usbvision, tuner, s_tuner, vt); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + /* Only one tuner */ + if (freq->tuner) + return -EINVAL; + if (freq->type == V4L2_TUNER_RADIO) + freq->frequency = usbvision->radio_freq; + else + freq->frequency = usbvision->tv_freq; + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *freq) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + struct v4l2_frequency new_freq = *freq; + + /* Only one tuner for now */ + if (freq->tuner) + return -EINVAL; + + call_all(usbvision, tuner, s_frequency, freq); + call_all(usbvision, tuner, g_frequency, &new_freq); + if (freq->type == V4L2_TUNER_RADIO) + usbvision->radio_freq = new_freq.frequency; + else + usbvision->tv_freq = new_freq.frequency; + + return 0; +} + +static int vidioc_reqbufs(struct file *file, + void *priv, struct v4l2_requestbuffers *vr) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int ret; + + RESTRICT_TO_RANGE(vr->count, 1, USBVISION_NUMFRAMES); + + /* Check input validity: + the user must do a VIDEO CAPTURE and MMAP method. */ + if (vr->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + if (usbvision->streaming == stream_on) { + ret = usbvision_stream_interrupt(usbvision); + if (ret) + return ret; + } + + usbvision_frames_free(usbvision); + usbvision_empty_framequeues(usbvision); + vr->count = usbvision_frames_alloc(usbvision, vr->count); + + usbvision->cur_frame = NULL; + + return 0; +} + +static int vidioc_querybuf(struct file *file, + void *priv, struct v4l2_buffer *vb) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + struct usbvision_frame *frame; + + /* FIXME : must control + that buffers are mapped (VIDIOC_REQBUFS has been called) */ + if (vb->index >= usbvision->num_frames) + return -EINVAL; + /* Updating the corresponding frame state */ + vb->flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + frame = &usbvision->frame[vb->index]; + if (frame->grabstate >= frame_state_ready) + vb->flags |= V4L2_BUF_FLAG_QUEUED; + if (frame->grabstate >= frame_state_done) + vb->flags |= V4L2_BUF_FLAG_DONE; + if (frame->grabstate == frame_state_unused) + vb->flags |= V4L2_BUF_FLAG_MAPPED; + vb->memory = V4L2_MEMORY_MMAP; + + vb->m.offset = vb->index * PAGE_ALIGN(usbvision->max_frame_size); + + vb->memory = V4L2_MEMORY_MMAP; + vb->field = V4L2_FIELD_NONE; + vb->length = usbvision->curwidth * + usbvision->curheight * + usbvision->palette.bytes_per_pixel; + v4l2_buffer_set_timestamp(vb, usbvision->frame[vb->index].ts); + vb->sequence = usbvision->frame[vb->index].sequence; + return 0; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *vb) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + struct usbvision_frame *frame; + unsigned long lock_flags; + + /* FIXME : works only on VIDEO_CAPTURE MODE, MMAP. */ + if (vb->index >= usbvision->num_frames) + return -EINVAL; + + frame = &usbvision->frame[vb->index]; + + if (frame->grabstate != frame_state_unused) + return -EAGAIN; + + /* Mark it as ready and enqueue frame */ + frame->grabstate = frame_state_ready; + frame->scanstate = scan_state_scanning; + frame->scanlength = 0; /* Accumulated in usbvision_parse_data() */ + + vb->flags &= ~V4L2_BUF_FLAG_DONE; + + /* set v4l2_format index */ + frame->v4l2_format = usbvision->palette; + + spin_lock_irqsave(&usbvision->queue_lock, lock_flags); + list_add_tail(&usbvision->frame[vb->index].frame, &usbvision->inqueue); + spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags); + + return 0; +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *vb) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int ret; + struct usbvision_frame *f; + unsigned long lock_flags; + + if (list_empty(&(usbvision->outqueue))) { + if (usbvision->streaming == stream_idle) + return -EINVAL; + ret = wait_event_interruptible + (usbvision->wait_frame, + !list_empty(&(usbvision->outqueue))); + if (ret) + return ret; + } + + spin_lock_irqsave(&usbvision->queue_lock, lock_flags); + f = list_entry(usbvision->outqueue.next, + struct usbvision_frame, frame); + list_del(usbvision->outqueue.next); + spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags); + + f->grabstate = frame_state_unused; + + vb->memory = V4L2_MEMORY_MMAP; + vb->flags = V4L2_BUF_FLAG_MAPPED | + V4L2_BUF_FLAG_QUEUED | + V4L2_BUF_FLAG_DONE | + V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vb->index = f->index; + vb->sequence = f->sequence; + v4l2_buffer_set_timestamp(vb, f->ts); + vb->field = V4L2_FIELD_NONE; + vb->bytesused = f->scanlength; + + return 0; +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + usbvision->streaming = stream_on; + call_all(usbvision, video, s_stream, 1); + + return 0; +} + +static int vidioc_streamoff(struct file *file, + void *priv, enum v4l2_buf_type type) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (usbvision->streaming == stream_on) { + usbvision_stream_interrupt(usbvision); + /* Stop all video streamings */ + call_all(usbvision, video, s_stream, 0); + } + usbvision_empty_framequeues(usbvision); + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *vfd) +{ + if (vfd->index >= USBVISION_SUPPORTED_PALETTES - 1) + return -EINVAL; + vfd->pixelformat = usbvision_v4l2_format[vfd->index].format; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *vf) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + vf->fmt.pix.width = usbvision->curwidth; + vf->fmt.pix.height = usbvision->curheight; + vf->fmt.pix.pixelformat = usbvision->palette.format; + vf->fmt.pix.bytesperline = + usbvision->curwidth * usbvision->palette.bytes_per_pixel; + vf->fmt.pix.sizeimage = vf->fmt.pix.bytesperline * usbvision->curheight; + vf->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + vf->fmt.pix.field = V4L2_FIELD_NONE; /* Always progressive image */ + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *vf) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int format_idx; + + /* Find requested format in available ones */ + for (format_idx = 0; format_idx < USBVISION_SUPPORTED_PALETTES; format_idx++) { + if (vf->fmt.pix.pixelformat == + usbvision_v4l2_format[format_idx].format) { + usbvision->palette = usbvision_v4l2_format[format_idx]; + break; + } + } + /* robustness */ + if (format_idx == USBVISION_SUPPORTED_PALETTES) + return -EINVAL; + RESTRICT_TO_RANGE(vf->fmt.pix.width, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH); + RESTRICT_TO_RANGE(vf->fmt.pix.height, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT); + + vf->fmt.pix.bytesperline = vf->fmt.pix.width* + usbvision->palette.bytes_per_pixel; + vf->fmt.pix.sizeimage = vf->fmt.pix.bytesperline*vf->fmt.pix.height; + vf->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + vf->fmt.pix.field = V4L2_FIELD_NONE; /* Always progressive image */ + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *vf) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, vf); + if (ret) + return ret; + + /* stop io in case it is already in progress */ + if (usbvision->streaming == stream_on) { + ret = usbvision_stream_interrupt(usbvision); + if (ret) + return ret; + } + usbvision_frames_free(usbvision); + usbvision_empty_framequeues(usbvision); + + usbvision->cur_frame = NULL; + + /* by now we are committed to the new data... */ + usbvision_set_output(usbvision, vf->fmt.pix.width, vf->fmt.pix.height); + + return 0; +} + +static ssize_t usbvision_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int noblock = file->f_flags & O_NONBLOCK; + unsigned long lock_flags; + int ret, i; + struct usbvision_frame *frame; + + PDEBUG(DBG_IO, "%s: %ld bytes, noblock=%d", __func__, + (unsigned long)count, noblock); + + if (!USBVISION_IS_OPERATIONAL(usbvision) || !buf) + return -EFAULT; + + /* This entry point is compatible with the mmap routines + so that a user can do either VIDIOC_QBUF/VIDIOC_DQBUF + to get frames or call read on the device. */ + if (!usbvision->num_frames) { + /* First, allocate some frames to work with + if this has not been done with VIDIOC_REQBUF */ + usbvision_frames_free(usbvision); + usbvision_empty_framequeues(usbvision); + usbvision_frames_alloc(usbvision, USBVISION_NUMFRAMES); + } + + if (usbvision->streaming != stream_on) { + /* no stream is running, make it running ! */ + usbvision->streaming = stream_on; + call_all(usbvision, video, s_stream, 1); + } + + /* Then, enqueue as many frames as possible + (like a user of VIDIOC_QBUF would do) */ + for (i = 0; i < usbvision->num_frames; i++) { + frame = &usbvision->frame[i]; + if (frame->grabstate == frame_state_unused) { + /* Mark it as ready and enqueue frame */ + frame->grabstate = frame_state_ready; + frame->scanstate = scan_state_scanning; + /* Accumulated in usbvision_parse_data() */ + frame->scanlength = 0; + + /* set v4l2_format index */ + frame->v4l2_format = usbvision->palette; + + spin_lock_irqsave(&usbvision->queue_lock, lock_flags); + list_add_tail(&frame->frame, &usbvision->inqueue); + spin_unlock_irqrestore(&usbvision->queue_lock, + lock_flags); + } + } + + /* Then try to steal a frame (like a VIDIOC_DQBUF would do) */ + if (list_empty(&(usbvision->outqueue))) { + if (noblock) + return -EAGAIN; + + ret = wait_event_interruptible + (usbvision->wait_frame, + !list_empty(&(usbvision->outqueue))); + if (ret) + return ret; + } + + spin_lock_irqsave(&usbvision->queue_lock, lock_flags); + frame = list_entry(usbvision->outqueue.next, + struct usbvision_frame, frame); + list_del(usbvision->outqueue.next); + spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags); + + /* An error returns an empty frame */ + if (frame->grabstate == frame_state_error) { + frame->bytes_read = 0; + return 0; + } + + PDEBUG(DBG_IO, "%s: frmx=%d, bytes_read=%ld, scanlength=%ld", + __func__, + frame->index, frame->bytes_read, frame->scanlength); + + /* copy bytes to user space; we allow for partials reads */ + if ((count + frame->bytes_read) > (unsigned long)frame->scanlength) + count = frame->scanlength - frame->bytes_read; + + if (copy_to_user(buf, frame->data + frame->bytes_read, count)) + return -EFAULT; + + frame->bytes_read += count; + PDEBUG(DBG_IO, "%s: {copy} count used=%ld, new bytes_read=%ld", + __func__, + (unsigned long)count, frame->bytes_read); + + /* + * FIXME: + * For now, forget the frame if it has not been read in one shot. + */ + frame->bytes_read = 0; + + /* Mark it as available to be used again. */ + frame->grabstate = frame_state_unused; + + return count; +} + +static ssize_t usbvision_v4l2_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int res; + + if (mutex_lock_interruptible(&usbvision->v4l2_lock)) + return -ERESTARTSYS; + res = usbvision_read(file, buf, count, ppos); + mutex_unlock(&usbvision->v4l2_lock); + return res; +} + +static int usbvision_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start, + start = vma->vm_start; + void *pos; + u32 i; + struct usb_usbvision *usbvision = video_drvdata(file); + + PDEBUG(DBG_MMAP, "mmap"); + + if (!USBVISION_IS_OPERATIONAL(usbvision)) + return -EFAULT; + + if (!(vma->vm_flags & VM_WRITE) || + size != PAGE_ALIGN(usbvision->max_frame_size)) { + return -EINVAL; + } + + for (i = 0; i < usbvision->num_frames; i++) { + if (((PAGE_ALIGN(usbvision->max_frame_size)*i) >> PAGE_SHIFT) == + vma->vm_pgoff) + break; + } + if (i == usbvision->num_frames) { + PDEBUG(DBG_MMAP, + "mmap: user supplied mapping address is out of range"); + return -EINVAL; + } + + /* VM_IO is eventually going to replace PageReserved altogether */ + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; + + pos = usbvision->frame[i].data; + while (size > 0) { + if (vm_insert_page(vma, start, vmalloc_to_page(pos))) { + PDEBUG(DBG_MMAP, "mmap: vm_insert_page failed"); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return 0; +} + +static int usbvision_v4l2_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int res; + + if (mutex_lock_interruptible(&usbvision->v4l2_lock)) + return -ERESTARTSYS; + res = usbvision_mmap(file, vma); + mutex_unlock(&usbvision->v4l2_lock); + return res; +} + +/* + * Here comes the stuff for radio on usbvision based devices + * + */ +static int usbvision_radio_open(struct file *file) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int err_code = 0; + + PDEBUG(DBG_IO, "%s:", __func__); + + if (mutex_lock_interruptible(&usbvision->v4l2_lock)) + return -ERESTARTSYS; + + if (usbvision->remove_pending) { + err_code = -ENODEV; + goto out; + } + err_code = v4l2_fh_open(file); + if (err_code) + goto out; + if (usbvision->user) { + dev_err(&usbvision->rdev.dev, + "%s: Someone tried to open an already opened USBVision Radio!\n", + __func__); + err_code = -EBUSY; + } else { + /* Alternate interface 1 is is the biggest frame size */ + err_code = usbvision_set_alternate(usbvision); + if (err_code < 0) { + usbvision->last_error = err_code; + err_code = -EBUSY; + goto out; + } + + /* If so far no errors then we shall start the radio */ + usbvision->radio = 1; + call_all(usbvision, tuner, s_radio); + usbvision_set_audio(usbvision, USBVISION_AUDIO_RADIO); + usbvision->user++; + } +out: + mutex_unlock(&usbvision->v4l2_lock); + return err_code; +} + + +static int usbvision_radio_close(struct file *file) +{ + struct usb_usbvision *usbvision = video_drvdata(file); + int r; + + PDEBUG(DBG_IO, ""); + + mutex_lock(&usbvision->v4l2_lock); + /* Set packet size to 0 */ + usbvision->iface_alt = 0; + if (usbvision->dev) + usb_set_interface(usbvision->dev, usbvision->iface, + usbvision->iface_alt); + + usbvision_audio_off(usbvision); + usbvision->radio = 0; + usbvision->user--; + r = usbvision->remove_pending; + mutex_unlock(&usbvision->v4l2_lock); + + if (r) { + printk(KERN_INFO "%s: Final disconnect\n", __func__); + v4l2_fh_release(file); + usbvision_release(usbvision); + return 0; + } + + PDEBUG(DBG_IO, "success"); + return v4l2_fh_release(file); +} + +/* Video registration stuff */ + +/* Video template */ +static const struct v4l2_file_operations usbvision_fops = { + .owner = THIS_MODULE, + .open = usbvision_v4l2_open, + .release = usbvision_v4l2_close, + .read = usbvision_v4l2_read, + .mmap = usbvision_v4l2_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops usbvision_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_s_std = vidioc_s_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +static struct video_device usbvision_video_template = { + .fops = &usbvision_fops, + .ioctl_ops = &usbvision_ioctl_ops, + .name = "usbvision-video", + .release = video_device_release_empty, + .tvnorms = USBVISION_NORMS, +}; + + +/* Radio template */ +static const struct v4l2_file_operations usbvision_radio_fops = { + .owner = THIS_MODULE, + .open = usbvision_radio_open, + .release = usbvision_radio_close, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops usbvision_radio_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device usbvision_radio_template = { + .fops = &usbvision_radio_fops, + .name = "usbvision-radio", + .release = video_device_release_empty, + .ioctl_ops = &usbvision_radio_ioctl_ops, +}; + + +static void usbvision_vdev_init(struct usb_usbvision *usbvision, + struct video_device *vdev, + const struct video_device *vdev_template, + const char *name) +{ + struct usb_device *usb_dev = usbvision->dev; + + if (!usb_dev) { + dev_err(&usbvision->dev->dev, + "%s: usbvision->dev is not set\n", __func__); + return; + } + + *vdev = *vdev_template; + vdev->lock = &usbvision->v4l2_lock; + vdev->v4l2_dev = &usbvision->v4l2_dev; + snprintf(vdev->name, sizeof(vdev->name), "%s", name); + video_set_drvdata(vdev, usbvision); +} + +/* unregister video4linux devices */ +static void usbvision_unregister_video(struct usb_usbvision *usbvision) +{ + /* Radio Device: */ + if (video_is_registered(&usbvision->rdev)) { + PDEBUG(DBG_PROBE, "unregister %s [v4l2]", + video_device_node_name(&usbvision->rdev)); + video_unregister_device(&usbvision->rdev); + } + + /* Video Device: */ + if (video_is_registered(&usbvision->vdev)) { + PDEBUG(DBG_PROBE, "unregister %s [v4l2]", + video_device_node_name(&usbvision->vdev)); + video_unregister_device(&usbvision->vdev); + } +} + +/* register video4linux devices */ +static int usbvision_register_video(struct usb_usbvision *usbvision) +{ + int res = -ENOMEM; + + /* Video Device: */ + usbvision_vdev_init(usbvision, &usbvision->vdev, + &usbvision_video_template, "USBVision Video"); + if (!usbvision->have_tuner) { + v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_G_FREQUENCY); + v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_S_TUNER); + v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_G_FREQUENCY); + v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_S_TUNER); + } + usbvision->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + if (usbvision->have_tuner) + usbvision->vdev.device_caps |= V4L2_CAP_TUNER; + + if (video_register_device(&usbvision->vdev, VFL_TYPE_VIDEO, video_nr) < 0) + goto err_exit; + printk(KERN_INFO "USBVision[%d]: registered USBVision Video device %s [v4l2]\n", + usbvision->nr, video_device_node_name(&usbvision->vdev)); + + /* Radio Device: */ + if (usbvision_device_data[usbvision->dev_model].radio) { + /* usbvision has radio */ + usbvision_vdev_init(usbvision, &usbvision->rdev, + &usbvision_radio_template, "USBVision Radio"); + usbvision->rdev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + if (video_register_device(&usbvision->rdev, VFL_TYPE_RADIO, radio_nr) < 0) + goto err_exit; + printk(KERN_INFO "USBVision[%d]: registered USBVision Radio device %s [v4l2]\n", + usbvision->nr, video_device_node_name(&usbvision->rdev)); + } + /* all done */ + return 0; + + err_exit: + dev_err(&usbvision->dev->dev, + "USBVision[%d]: video_register_device() failed\n", + usbvision->nr); + usbvision_unregister_video(usbvision); + return res; +} + +/* + * usbvision_alloc() + * + * This code allocates the struct usb_usbvision. + * It is filled with default values. + * + * Returns NULL on error, a pointer to usb_usbvision else. + * + */ +static struct usb_usbvision *usbvision_alloc(struct usb_device *dev, + struct usb_interface *intf) +{ + struct usb_usbvision *usbvision; + + usbvision = kzalloc(sizeof(*usbvision), GFP_KERNEL); + if (!usbvision) + return NULL; + + usbvision->dev = dev; + if (v4l2_device_register(&intf->dev, &usbvision->v4l2_dev)) + goto err_free; + + if (v4l2_ctrl_handler_init(&usbvision->hdl, 4)) + goto err_unreg; + usbvision->v4l2_dev.ctrl_handler = &usbvision->hdl; + mutex_init(&usbvision->v4l2_lock); + + /* prepare control urb for control messages during interrupts */ + usbvision->ctrl_urb = usb_alloc_urb(USBVISION_URB_FRAMES, GFP_KERNEL); + if (!usbvision->ctrl_urb) + goto err_unreg; + + return usbvision; + +err_unreg: + v4l2_ctrl_handler_free(&usbvision->hdl); + v4l2_device_unregister(&usbvision->v4l2_dev); +err_free: + kfree(usbvision); + return NULL; +} + +/* + * usbvision_release() + * + * This code does final release of struct usb_usbvision. This happens + * after the device is disconnected -and- all clients closed their files. + * + */ +static void usbvision_release(struct usb_usbvision *usbvision) +{ + PDEBUG(DBG_PROBE, ""); + + usbvision->initialized = 0; + + usbvision_remove_sysfs(&usbvision->vdev); + usbvision_unregister_video(usbvision); + kfree(usbvision->alt_max_pkt_size); + + usb_free_urb(usbvision->ctrl_urb); + + v4l2_ctrl_handler_free(&usbvision->hdl); + v4l2_device_unregister(&usbvision->v4l2_dev); + kfree(usbvision); + + PDEBUG(DBG_PROBE, "success"); +} + + +/*********************** usb interface **********************************/ + +static void usbvision_configure_video(struct usb_usbvision *usbvision) +{ + int model; + + if (!usbvision) + return; + + model = usbvision->dev_model; + usbvision->palette = usbvision_v4l2_format[2]; /* V4L2_PIX_FMT_RGB24; */ + + if (usbvision_device_data[usbvision->dev_model].vin_reg2_override) { + usbvision->vin_reg2_preset = + usbvision_device_data[usbvision->dev_model].vin_reg2; + } else { + usbvision->vin_reg2_preset = 0; + } + + usbvision->tvnorm_id = usbvision_device_data[model].video_norm; + usbvision->video_inputs = usbvision_device_data[model].video_channels; + usbvision->ctl_input = 0; + usbvision->radio_freq = 87.5 * 16000; + usbvision->tv_freq = 400 * 16; + + /* This should be here to make i2c clients to be able to register */ + /* first switch off audio */ + if (usbvision_device_data[model].audio_channels > 0) + usbvision_audio_off(usbvision); + /* and then power up the tuner */ + usbvision_power_on(usbvision); + usbvision_i2c_register(usbvision); +} + +/* + * usbvision_probe() + * + * This procedure queries device descriptor and accepts the interface + * if it looks like USBVISION video device + * + */ +static int usbvision_probe(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + struct usb_device *dev = usb_get_dev(interface_to_usbdev(intf)); + struct usb_interface *uif; + __u8 ifnum = intf->altsetting->desc.bInterfaceNumber; + const struct usb_host_interface *interface; + struct usb_usbvision *usbvision = NULL; + const struct usb_endpoint_descriptor *endpoint; + int model, i, ret; + + PDEBUG(DBG_PROBE, "VID=%#04x, PID=%#04x, ifnum=%u", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), ifnum); + + model = devid->driver_info; + if (model < 0 || model >= usbvision_device_data_size) { + PDEBUG(DBG_PROBE, "model out of bounds %d", model); + ret = -ENODEV; + goto err_usb; + } + printk(KERN_INFO "%s: %s found\n", __func__, + usbvision_device_data[model].model_string); + + if (usbvision_device_data[model].interface >= 0) + interface = &dev->actconfig->interface[usbvision_device_data[model].interface]->altsetting[0]; + else if (ifnum < dev->actconfig->desc.bNumInterfaces) + interface = &dev->actconfig->interface[ifnum]->altsetting[0]; + else { + dev_err(&intf->dev, "interface %d is invalid, max is %d\n", + ifnum, dev->actconfig->desc.bNumInterfaces - 1); + ret = -ENODEV; + goto err_usb; + } + + if (interface->desc.bNumEndpoints < 2) { + dev_err(&intf->dev, "interface %d has %d endpoints, but must have minimum 2\n", + ifnum, interface->desc.bNumEndpoints); + ret = -ENODEV; + goto err_usb; + } + endpoint = &interface->endpoint[1].desc; + + if (!usb_endpoint_xfer_isoc(endpoint)) { + dev_err(&intf->dev, "%s: interface %d. has non-ISO endpoint!\n", + __func__, ifnum); + dev_err(&intf->dev, "%s: Endpoint attributes %d", + __func__, endpoint->bmAttributes); + ret = -ENODEV; + goto err_usb; + } + if (usb_endpoint_dir_out(endpoint)) { + dev_err(&intf->dev, "%s: interface %d. has ISO OUT endpoint!\n", + __func__, ifnum); + ret = -ENODEV; + goto err_usb; + } + + usbvision = usbvision_alloc(dev, intf); + if (!usbvision) { + dev_err(&intf->dev, "%s: couldn't allocate USBVision struct\n", __func__); + ret = -ENOMEM; + goto err_usb; + } + + if (dev->descriptor.bNumConfigurations > 1) + usbvision->bridge_type = BRIDGE_NT1004; + else if (model == DAZZLE_DVC_90_REV_1_SECAM) + usbvision->bridge_type = BRIDGE_NT1005; + else + usbvision->bridge_type = BRIDGE_NT1003; + PDEBUG(DBG_PROBE, "bridge_type %d", usbvision->bridge_type); + + /* compute alternate max packet sizes */ + uif = dev->actconfig->interface[0]; + + usbvision->num_alt = uif->num_altsetting; + PDEBUG(DBG_PROBE, "Alternate settings: %i", usbvision->num_alt); + usbvision->alt_max_pkt_size = kmalloc_array(32, usbvision->num_alt, + GFP_KERNEL); + if (!usbvision->alt_max_pkt_size) { + ret = -ENOMEM; + goto err_pkt; + } + + for (i = 0; i < usbvision->num_alt; i++) { + u16 tmp; + + if (uif->altsetting[i].desc.bNumEndpoints < 2) { + ret = -ENODEV; + goto err_pkt; + } + + tmp = le16_to_cpu(uif->altsetting[i].endpoint[1].desc. + wMaxPacketSize); + usbvision->alt_max_pkt_size[i] = + (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1); + PDEBUG(DBG_PROBE, "Alternate setting %i, max size= %i", i, + usbvision->alt_max_pkt_size[i]); + } + + + usbvision->nr = usbvision_nr++; + + spin_lock_init(&usbvision->queue_lock); + init_waitqueue_head(&usbvision->wait_frame); + init_waitqueue_head(&usbvision->wait_stream); + + usbvision->have_tuner = usbvision_device_data[model].tuner; + if (usbvision->have_tuner) + usbvision->tuner_type = usbvision_device_data[model].tuner_type; + + usbvision->dev_model = model; + usbvision->remove_pending = 0; + usbvision->iface = ifnum; + usbvision->iface_alt = 0; + usbvision->video_endp = endpoint->bEndpointAddress; + usbvision->isoc_packet_size = 0; + usbvision->usb_bandwidth = 0; + usbvision->user = 0; + usbvision->streaming = stream_off; + usbvision_configure_video(usbvision); + usbvision_register_video(usbvision); + + usbvision_create_sysfs(&usbvision->vdev); + + PDEBUG(DBG_PROBE, "success"); + return 0; + +err_pkt: + usbvision_release(usbvision); +err_usb: + usb_put_dev(dev); + return ret; +} + + +/* + * usbvision_disconnect() + * + * This procedure stops all driver activity, deallocates interface-private + * structure (pointed by 'ptr') and after that driver should be removable + * with no ill consequences. + * + */ +static void usbvision_disconnect(struct usb_interface *intf) +{ + struct usb_usbvision *usbvision = to_usbvision(usb_get_intfdata(intf)); + int u; + + PDEBUG(DBG_PROBE, ""); + + if (!usbvision) { + pr_err("%s: usb_get_intfdata() failed\n", __func__); + return; + } + + mutex_lock(&usbvision->v4l2_lock); + + /* At this time we ask to cancel outstanding URBs */ + usbvision_stop_isoc(usbvision); + + v4l2_device_disconnect(&usbvision->v4l2_dev); + usbvision_i2c_unregister(usbvision); + usbvision->remove_pending = 1; /* Now all ISO data will be ignored */ + u = usbvision->user; + + usb_put_dev(usbvision->dev); + usbvision->dev = NULL; /* USB device is no more */ + + mutex_unlock(&usbvision->v4l2_lock); + + if (u) { + printk(KERN_INFO "%s: In use, disconnect pending\n", + __func__); + wake_up_interruptible(&usbvision->wait_frame); + wake_up_interruptible(&usbvision->wait_stream); + } else { + usbvision_release(usbvision); + } + + PDEBUG(DBG_PROBE, "success"); +} + +static struct usb_driver usbvision_driver = { + .name = "usbvision", + .id_table = usbvision_table, + .probe = usbvision_probe, + .disconnect = usbvision_disconnect, +}; + +/* + * usbvision_init() + * + * This code is run to initialize the driver. + * + */ +static int __init usbvision_init(void) +{ + int err_code; + + PDEBUG(DBG_PROBE, ""); + + PDEBUG(DBG_IO, "IO debugging is enabled [video]"); + PDEBUG(DBG_PROBE, "PROBE debugging is enabled [video]"); + PDEBUG(DBG_MMAP, "MMAP debugging is enabled [video]"); + + /* disable planar mode support unless compression enabled */ + if (isoc_mode != ISOC_MODE_COMPRESS) { + /* FIXME : not the right way to set supported flag */ + usbvision_v4l2_format[6].supported = 0; /* V4L2_PIX_FMT_YVU420 */ + usbvision_v4l2_format[7].supported = 0; /* V4L2_PIX_FMT_YUV422P */ + } + + err_code = usb_register(&usbvision_driver); + + if (err_code == 0) { + printk(KERN_INFO DRIVER_DESC " : " USBVISION_VERSION_STRING "\n"); + PDEBUG(DBG_PROBE, "success"); + } + return err_code; +} + +static void __exit usbvision_exit(void) +{ + PDEBUG(DBG_PROBE, ""); + + usb_deregister(&usbvision_driver); + PDEBUG(DBG_PROBE, "success"); +} + +module_init(usbvision_init); +module_exit(usbvision_exit); diff --git a/drivers/staging/media/usbvision/usbvision.h b/drivers/staging/media/usbvision/usbvision.h new file mode 100644 index 000000000000..11539578e8d2 --- /dev/null +++ b/drivers/staging/media/usbvision/usbvision.h @@ -0,0 +1,500 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * USBVISION.H + * usbvision header file + * + * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de> + * Dwaine Garden <dwainegarden@rogers.com> + * + * Report problems to v4l MailingList: linux-media@vger.kernel.org + * + * This module is part of usbvision driver project. + * Updates to driver completed by Dwaine P. Garden + * v4l2 conversion by Thierry Merle <thierry.merle@free.fr> + */ + + +#ifndef __LINUX_USBVISION_H +#define __LINUX_USBVISION_H + +#include <linux/list.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/tuner.h> +#include <linux/videodev2.h> + +#define USBVISION_DEBUG /* Turn on debug messages */ + +#define USBVISION_PWR_REG 0x00 + #define USBVISION_SSPND_EN (1 << 1) + #define USBVISION_RES2 (1 << 2) + #define USBVISION_PWR_VID (1 << 5) + #define USBVISION_E2_EN (1 << 7) +#define USBVISION_CONFIG_REG 0x01 +#define USBVISION_ADRS_REG 0x02 +#define USBVISION_ALTER_REG 0x03 +#define USBVISION_FORCE_ALTER_REG 0x04 +#define USBVISION_STATUS_REG 0x05 +#define USBVISION_IOPIN_REG 0x06 + #define USBVISION_IO_1 (1 << 0) + #define USBVISION_IO_2 (1 << 1) + #define USBVISION_AUDIO_IN 0 + #define USBVISION_AUDIO_TV 1 + #define USBVISION_AUDIO_RADIO 2 + #define USBVISION_AUDIO_MUTE 3 +#define USBVISION_SER_MODE 0x07 + #define USBVISION_CLK_OUT (1 << 0) + #define USBVISION_DAT_IO (1 << 1) + #define USBVISION_SENS_OUT (1 << 2) + #define USBVISION_SER_MODE_SOFT (0 << 4) + #define USBVISION_SER_MODE_SIO (1 << 4) +#define USBVISION_SER_ADRS 0x08 +#define USBVISION_SER_CONT 0x09 +#define USBVISION_SER_DAT1 0x0A +#define USBVISION_SER_DAT2 0x0B +#define USBVISION_SER_DAT3 0x0C +#define USBVISION_SER_DAT4 0x0D +#define USBVISION_EE_DATA 0x0E +#define USBVISION_EE_LSBAD 0x0F +#define USBVISION_EE_CONT 0x10 +#define USBVISION_DRM_CONT 0x12 + #define USBVISION_REF (1 << 0) + #define USBVISION_RES_UR (1 << 2) + #define USBVISION_RES_FDL (1 << 3) + #define USBVISION_RES_VDW (1 << 4) +#define USBVISION_DRM_PRM1 0x13 +#define USBVISION_DRM_PRM2 0x14 +#define USBVISION_DRM_PRM3 0x15 +#define USBVISION_DRM_PRM4 0x16 +#define USBVISION_DRM_PRM5 0x17 +#define USBVISION_DRM_PRM6 0x18 +#define USBVISION_DRM_PRM7 0x19 +#define USBVISION_DRM_PRM8 0x1A +#define USBVISION_VIN_REG1 0x1B + #define USBVISION_8_422_SYNC 0x01 + #define USBVISION_16_422_SYNC 0x02 + #define USBVISION_VSNC_POL (1 << 3) + #define USBVISION_HSNC_POL (1 << 4) + #define USBVISION_FID_POL (1 << 5) + #define USBVISION_HVALID_PO (1 << 6) + #define USBVISION_VCLK_POL (1 << 7) +#define USBVISION_VIN_REG2 0x1C + #define USBVISION_AUTO_FID (1 << 0) + #define USBVISION_NONE_INTER (1 << 1) + #define USBVISION_NOHVALID (1 << 2) + #define USBVISION_UV_ID (1 << 3) + #define USBVISION_FIX_2C (1 << 4) + #define USBVISION_SEND_FID (1 << 5) + #define USBVISION_KEEP_BLANK (1 << 7) +#define USBVISION_LXSIZE_I 0x1D +#define USBVISION_MXSIZE_I 0x1E +#define USBVISION_LYSIZE_I 0x1F +#define USBVISION_MYSIZE_I 0x20 +#define USBVISION_LX_OFFST 0x21 +#define USBVISION_MX_OFFST 0x22 +#define USBVISION_LY_OFFST 0x23 +#define USBVISION_MY_OFFST 0x24 +#define USBVISION_FRM_RATE 0x25 +#define USBVISION_LXSIZE_O 0x26 +#define USBVISION_MXSIZE_O 0x27 +#define USBVISION_LYSIZE_O 0x28 +#define USBVISION_MYSIZE_O 0x29 +#define USBVISION_FILT_CONT 0x2A +#define USBVISION_VO_MODE 0x2B +#define USBVISION_INTRA_CYC 0x2C +#define USBVISION_STRIP_SZ 0x2D +#define USBVISION_FORCE_INTRA 0x2E +#define USBVISION_FORCE_UP 0x2F +#define USBVISION_BUF_THR 0x30 +#define USBVISION_DVI_YUV 0x31 +#define USBVISION_AUDIO_CONT 0x32 +#define USBVISION_AUD_PK_LEN 0x33 +#define USBVISION_BLK_PK_LEN 0x34 +#define USBVISION_PCM_THR1 0x38 +#define USBVISION_PCM_THR2 0x39 +#define USBVISION_DIST_THR_L 0x3A +#define USBVISION_DIST_THR_H 0x3B +#define USBVISION_MAX_DIST_L 0x3C +#define USBVISION_MAX_DIST_H 0x3D +#define USBVISION_OP_CODE 0x33 + +#define MAX_BYTES_PER_PIXEL 4 + +#define MIN_FRAME_WIDTH 64 +#define MAX_USB_WIDTH 320 /* 384 */ +#define MAX_FRAME_WIDTH 320 /* 384 */ /* stretching sometimes causes crashes*/ + +#define MIN_FRAME_HEIGHT 48 +#define MAX_USB_HEIGHT 240 /* 288 */ +#define MAX_FRAME_HEIGHT 240 /* 288 */ /* Stretching sometimes causes crashes*/ + +#define MAX_FRAME_SIZE (MAX_FRAME_WIDTH * MAX_FRAME_HEIGHT * MAX_BYTES_PER_PIXEL) +#define USBVISION_CLIPMASK_SIZE (MAX_FRAME_WIDTH * MAX_FRAME_HEIGHT / 8) /* bytesize of clipmask */ + +#define USBVISION_URB_FRAMES 32 + +#define USBVISION_NUM_HEADERMARKER 20 +#define USBVISION_NUMFRAMES 3 /* Maximum number of frames an application can get */ +#define USBVISION_NUMSBUF 2 /* Dimensioning the USB S buffering */ + +#define USBVISION_POWEROFF_TIME (3 * HZ) /* 3 seconds */ + + +#define FRAMERATE_MIN 0 +#define FRAMERATE_MAX 31 + +enum { + ISOC_MODE_YUV422 = 0x03, + ISOC_MODE_YUV420 = 0x14, + ISOC_MODE_COMPRESS = 0x60, +}; + +/* This macro restricts an int variable to an inclusive range */ +#define RESTRICT_TO_RANGE(v, mi, ma) \ + { if (((int)v) < (mi)) (v) = (mi); else if ((v) > (ma)) (v) = (ma); } + +/* + * We use macros to do YUV -> RGB conversion because this is + * very important for speed and totally unimportant for size. + * + * YUV -> RGB Conversion + * --------------------- + * + * B = 1.164*(Y-16) + 2.018*(V-128) + * G = 1.164*(Y-16) - 0.813*(U-128) - 0.391*(V-128) + * R = 1.164*(Y-16) + 1.596*(U-128) + * + * If you fancy integer arithmetic (as you should), hear this: + * + * 65536*B = 76284*(Y-16) + 132252*(V-128) + * 65536*G = 76284*(Y-16) - 53281*(U-128) - 25625*(V-128) + * 65536*R = 76284*(Y-16) + 104595*(U-128) + * + * Make sure the output values are within [0..255] range. + */ +#define LIMIT_RGB(x) (((x) < 0) ? 0 : (((x) > 255) ? 255 : (x))) +#define YUV_TO_RGB_BY_THE_BOOK(my, mu, mv, mr, mg, mb) { \ + int mm_y, mm_yc, mm_u, mm_v, mm_r, mm_g, mm_b; \ + mm_y = (my) - 16; \ + mm_u = (mu) - 128; \ + mm_v = (mv) - 128; \ + mm_yc = mm_y * 76284; \ + mm_b = (mm_yc + 132252 * mm_v) >> 16; \ + mm_g = (mm_yc - 53281 * mm_u - 25625 * mm_v) >> 16; \ + mm_r = (mm_yc + 104595 * mm_u) >> 16; \ + mb = LIMIT_RGB(mm_b); \ + mg = LIMIT_RGB(mm_g); \ + mr = LIMIT_RGB(mm_r); \ +} + +/* + * This macro checks if usbvision is still operational. The 'usbvision' + * pointer must be valid, usbvision->dev must be valid, we are not + * removing the device and the device has not erred on us. + */ +#define USBVISION_IS_OPERATIONAL(udevice) (\ + (udevice != NULL) && \ + ((udevice)->dev != NULL) && \ + ((udevice)->last_error == 0) && \ + (!(udevice)->remove_pending)) + +#define I2C_USB_ADAP_MAX 16 + +#define USBVISION_NORMS (V4L2_STD_PAL | V4L2_STD_NTSC | V4L2_STD_SECAM | V4L2_STD_PAL_M) + +/* ----------------------------------------------------------------- */ +/* usbvision video structures */ +/* ----------------------------------------------------------------- */ +enum scan_state { + scan_state_scanning, /* Scanning for header */ + scan_state_lines /* Parsing lines */ +}; + +/* Completion states of the data parser */ +enum parse_state { + parse_state_continue, /* Just parse next item */ + parse_state_next_frame, /* Frame done, send it to V4L */ + parse_state_out, /* Not enough data for frame */ + parse_state_end_parse /* End parsing */ +}; + +enum frame_state { + frame_state_unused, /* Unused (no MCAPTURE) */ + frame_state_ready, /* Ready to start grabbing */ + frame_state_grabbing, /* In the process of being grabbed into */ + frame_state_done, /* Finished grabbing, but not been synced yet */ + frame_state_done_hold, /* Are syncing or reading */ + frame_state_error, /* Something bad happened while processing */ +}; + +/* stream states */ +enum stream_state { + stream_off, /* Driver streaming is completely OFF */ + stream_idle, /* Driver streaming is ready to be put ON by the application */ + stream_interrupt, /* Driver streaming must be interrupted */ + stream_on, /* Driver streaming is put ON by the application */ +}; + +enum isoc_state { + isoc_state_in_frame, /* Isoc packet is member of frame */ + isoc_state_no_frame, /* Isoc packet is not member of any frame */ +}; + +struct usb_device; + +struct usbvision_sbuf { + char *data; + struct urb *urb; +}; + +#define USBVISION_MAGIC_1 0x55 +#define USBVISION_MAGIC_2 0xAA +#define USBVISION_HEADER_LENGTH 0x0c +#define USBVISION_SAA7111_ADDR 0x48 +#define USBVISION_SAA7113_ADDR 0x4a +#define USBVISION_IIC_LRACK 0x20 +#define USBVISION_IIC_LRNACK 0x30 +#define USBVISION_FRAME_FORMAT_PARAM_INTRA (1<<7) + +struct usbvision_v4l2_format_st { + int supported; + int bytes_per_pixel; + int depth; + int format; +}; +#define USBVISION_SUPPORTED_PALETTES ARRAY_SIZE(usbvision_v4l2_format) + +struct usbvision_frame_header { + unsigned char magic_1; /* 0 magic */ + unsigned char magic_2; /* 1 magic */ + unsigned char header_length; /* 2 */ + unsigned char frame_num; /* 3 */ + unsigned char frame_phase; /* 4 */ + unsigned char frame_latency; /* 5 */ + unsigned char data_format; /* 6 */ + unsigned char format_param; /* 7 */ + unsigned char frame_width_lo; /* 8 */ + unsigned char frame_width_hi; /* 9 */ + unsigned char frame_height_lo; /* 10 */ + unsigned char frame_height_hi; /* 11 */ + __u16 frame_width; /* 8 - 9 after endian correction*/ + __u16 frame_height; /* 10 - 11 after endian correction*/ +}; + +struct usbvision_frame { + char *data; /* Frame buffer */ + struct usbvision_frame_header isoc_header; /* Header from stream */ + + int width; /* Width application is expecting */ + int height; /* Height */ + int index; /* Frame index */ + int frmwidth; /* Width the frame actually is */ + int frmheight; /* Height */ + + volatile int grabstate; /* State of grabbing */ + int scanstate; /* State of scanning */ + + struct list_head frame; + + int curline; /* Line of frame we're working on */ + + long scanlength; /* uncompressed, raw data length of frame */ + long bytes_read; /* amount of scanlength that has been read from data */ + struct usbvision_v4l2_format_st v4l2_format; /* format the user needs*/ + int v4l2_linesize; /* bytes for one videoline*/ + u64 ts; + int sequence; /* How many video frames we send to user */ +}; + +#define CODEC_SAA7113 7113 +#define CODEC_SAA7111 7111 +#define CODEC_WEBCAM 3000 +#define BRIDGE_NT1003 1003 +#define BRIDGE_NT1004 1004 +#define BRIDGE_NT1005 1005 + +struct usbvision_device_data_st { + __u64 video_norm; + const char *model_string; + int interface; /* to handle special interface number like BELKIN and Hauppauge WinTV-USB II */ + __u16 codec; + unsigned video_channels:3; + unsigned audio_channels:2; + unsigned radio:1; + unsigned vbi:1; + unsigned tuner:1; + unsigned vin_reg1_override:1; /* Override default value with */ + unsigned vin_reg2_override:1; /* vin_reg1, vin_reg2, etc. */ + unsigned dvi_yuv_override:1; + __u8 vin_reg1; + __u8 vin_reg2; + __u8 dvi_yuv; + __u8 tuner_type; + __s16 x_offset; + __s16 y_offset; +}; + +/* Declared on usbvision-cards.c */ +extern struct usbvision_device_data_st usbvision_device_data[]; +extern struct usb_device_id usbvision_table[]; + +struct usb_usbvision { + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct video_device vdev; /* Video Device */ + struct video_device rdev; /* Radio Device */ + + /* i2c Declaration Section*/ + struct i2c_adapter i2c_adap; + int registered_i2c; + + struct urb *ctrl_urb; + unsigned char ctrl_urb_buffer[8]; + int ctrl_urb_busy; + struct usb_ctrlrequest ctrl_urb_setup; + + /* configuration part */ + int have_tuner; + int tuner_type; + int bridge_type; /* NT1003, NT1004, NT1005 */ + int radio; + int video_inputs; /* # of inputs */ + unsigned long radio_freq; + unsigned long tv_freq; + int audio_mute; + int audio_channel; + int isoc_mode; /* format of video data for the usb isoc-transfer */ + unsigned int nr; /* Number of the device */ + + /* Device structure */ + struct usb_device *dev; + /* usb transfer */ + int num_alt; /* Number of alternative settings */ + unsigned int *alt_max_pkt_size; /* array of max_packet_size */ + unsigned char iface; /* Video interface number */ + unsigned char iface_alt; /* Alt settings */ + unsigned char vin_reg2_preset; + struct mutex v4l2_lock; + int power; /* is the device powered on? */ + int user; /* user count for exclusive use */ + int initialized; /* Had we already sent init sequence? */ + int dev_model; /* What type of USBVISION device we got? */ + enum stream_state streaming; /* Are we streaming Isochronous? */ + int last_error; /* What calamity struck us? */ + int curwidth; /* width of the frame the device is currently set to*/ + int curheight; /* height of the frame the device is currently set to*/ + int stretch_width; /* stretch-factor for frame width (from usb to screen)*/ + int stretch_height; /* stretch-factor for frame height (from usb to screen)*/ + char *fbuf; /* Videodev buffer area for mmap*/ + int max_frame_size; /* Bytes in one video frame */ + int fbuf_size; /* Videodev buffer size */ + spinlock_t queue_lock; /* spinlock for protecting mods on inqueue and outqueue */ + struct list_head inqueue, outqueue; /* queued frame list and ready to dequeue frame list */ + wait_queue_head_t wait_frame; /* Processes waiting */ + wait_queue_head_t wait_stream; /* Processes waiting */ + struct usbvision_frame *cur_frame; /* pointer to current frame, set by usbvision_find_header */ + struct usbvision_frame frame[USBVISION_NUMFRAMES]; /* frame buffer */ + int num_frames; /* number of frames allocated */ + struct usbvision_sbuf sbuf[USBVISION_NUMSBUF]; /* S buffering */ + volatile int remove_pending; /* If set then about to exit */ + + /* Scratch space from the Isochronous Pipe.*/ + unsigned char *scratch; + int scratch_read_ptr; + int scratch_write_ptr; + int scratch_headermarker[USBVISION_NUM_HEADERMARKER]; + int scratch_headermarker_read_ptr; + int scratch_headermarker_write_ptr; + enum isoc_state isocstate; + struct usbvision_v4l2_format_st palette; + + struct v4l2_capability vcap; /* Video capabilities */ + unsigned int ctl_input; /* selected input */ + v4l2_std_id tvnorm_id; /* selected tv norm */ + unsigned char video_endp; /* 0x82 for USBVISION devices based */ + + /* Decompression stuff: */ + unsigned char *intra_frame_buffer; /* Buffer for reference frame */ + int block_pos; /* for test only */ + int request_intra; /* 0 = normal; 1 = intra frame is requested; */ + int last_isoc_frame_num; /* check for lost isoc frames */ + int isoc_packet_size; /* need to calculate used_bandwidth */ + int used_bandwidth; /* used bandwidth 0-100%, need to set compr_level */ + int compr_level; /* How strong (100) or weak (0) is compression */ + int last_compr_level; /* How strong (100) or weak (0) was compression */ + int usb_bandwidth; /* Mbit/s */ + + /* Statistics that can be overlaid on the screen */ + unsigned long isoc_urb_count; /* How many URBs we received so far */ + unsigned long urb_length; /* Length of last URB */ + unsigned long isoc_data_count; /* How many bytes we received */ + unsigned long header_count; /* How many frame headers we found */ + unsigned long scratch_ovf_count; /* How many times we overflowed scratch */ + unsigned long isoc_skip_count; /* How many empty ISO packets received */ + unsigned long isoc_err_count; /* How many bad ISO packets received */ + unsigned long isoc_packet_count; /* How many packets we totally got */ + int isoc_measure_bandwidth_count; + int frame_num; /* How many video frames we send to user */ + int max_strip_len; /* How big is the biggest strip */ + int comprblock_pos; + int strip_len_errors; /* How many times was block_pos greater than strip_len */ + int strip_magic_errors; + int strip_line_number_errors; + int compr_block_types[4]; +}; + +static inline struct usb_usbvision *to_usbvision(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct usb_usbvision, v4l2_dev); +} + +#define call_all(usbvision, o, f, args...) \ + v4l2_device_call_all(&usbvision->v4l2_dev, 0, o, f, ##args) + +/* --------------------------------------------------------------- */ +/* defined in usbvision-i2c.c */ +/* i2c-algo-usb declaration */ +/* --------------------------------------------------------------- */ + +/* ----------------------------------------------------------------------- */ +/* usbvision specific I2C functions */ +/* ----------------------------------------------------------------------- */ +int usbvision_i2c_register(struct usb_usbvision *usbvision); +int usbvision_i2c_unregister(struct usb_usbvision *usbvision); + +/* defined in usbvision-core.c */ +int usbvision_read_reg(struct usb_usbvision *usbvision, unsigned char reg); +int usbvision_write_reg(struct usb_usbvision *usbvision, unsigned char reg, + unsigned char value); + +int usbvision_frames_alloc(struct usb_usbvision *usbvision, int number_of_frames); +void usbvision_frames_free(struct usb_usbvision *usbvision); +int usbvision_scratch_alloc(struct usb_usbvision *usbvision); +void usbvision_scratch_free(struct usb_usbvision *usbvision); +int usbvision_decompress_alloc(struct usb_usbvision *usbvision); +void usbvision_decompress_free(struct usb_usbvision *usbvision); + +int usbvision_setup(struct usb_usbvision *usbvision, int format); +int usbvision_init_isoc(struct usb_usbvision *usbvision); +int usbvision_restart_isoc(struct usb_usbvision *usbvision); +void usbvision_stop_isoc(struct usb_usbvision *usbvision); +int usbvision_set_alternate(struct usb_usbvision *dev); + +int usbvision_set_audio(struct usb_usbvision *usbvision, int audio_channel); +int usbvision_audio_off(struct usb_usbvision *usbvision); + +int usbvision_begin_streaming(struct usb_usbvision *usbvision); +void usbvision_empty_framequeues(struct usb_usbvision *dev); +int usbvision_stream_interrupt(struct usb_usbvision *dev); + +int usbvision_muxsel(struct usb_usbvision *usbvision, int channel); +int usbvision_set_input(struct usb_usbvision *usbvision); +int usbvision_set_output(struct usb_usbvision *usbvision, int width, int height); + +int usbvision_power_off(struct usb_usbvision *usbvision); +int usbvision_power_on(struct usb_usbvision *usbvision); + +#endif /* __LINUX_USBVISION_H */ diff --git a/drivers/staging/most/video/video.c b/drivers/staging/most/video/video.c index d32ae49d617b..4cffc8fd62bd 100644 --- a/drivers/staging/most/video/video.c +++ b/drivers/staging/most/video/video.c @@ -74,7 +74,7 @@ static int comp_vdev_open(struct file *filp) struct comp_fh *fh; switch (vdev->vfl_type) { - case VFL_TYPE_GRABBER: + case VFL_TYPE_VIDEO: break; default: return -EINVAL; @@ -424,7 +424,7 @@ static int comp_register_videodev(struct most_video_dev *mdev) /* Register the v4l2 device */ video_set_drvdata(mdev->vdev, mdev); - ret = video_register_device(mdev->vdev, VFL_TYPE_GRABBER, -1); + ret = video_register_device(mdev->vdev, VFL_TYPE_VIDEO, -1); if (ret) { v4l2_err(&mdev->v4l2_dev, "video_register_device failed (%d)\n", ret); diff --git a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c index 1ef31a984741..597acef35d0b 100644 --- a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c +++ b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c @@ -1776,7 +1776,7 @@ static int bm2835_mmal_init_device(struct bm2835_mmal_dev *dev, video_set_drvdata(vfd, dev); ret = video_register_device(vfd, - VFL_TYPE_GRABBER, + VFL_TYPE_VIDEO, video_nr[dev->camera_num]); if (ret < 0) return ret; |